react-mention-input 1.1.26 → 1.1.28

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.
@@ -32,6 +32,8 @@ interface MentionInputProps {
32
32
  }) => void;
33
33
  suggestionPosition?: 'top' | 'bottom' | 'left' | 'right';
34
34
  onImageUpload?: (file: File) => Promise<string>;
35
+ getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
36
+ isProtectedUrl?: boolean | ((url: string) => boolean);
35
37
  }
36
38
  declare const MentionInput: React.FC<MentionInputProps>;
37
39
  export default MentionInput;
@@ -46,9 +46,10 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
46
46
  import React, { useState, useRef, useEffect } from "react";
47
47
  import ReactDOM from "react-dom";
48
48
  import "./MentionInput.css";
49
+ import { useProtectedImage } from "./useProtectedImage";
49
50
  var MentionInput = function (_a) {
50
51
  var _b;
51
- var users = _a.users, _c = _a.placeholder, placeholder = _c === void 0 ? "Type a message... (or drag & drop an image)" : _c, containerClassName = _a.containerClassName, inputContainerClassName = _a.inputContainerClassName, inputClassName = _a.inputClassName, sendBtnClassName = _a.sendBtnClassName, suggestionListClassName = _a.suggestionListClassName, suggestionItemClassName = _a.suggestionItemClassName, imgClassName = _a.imgClassName, imgStyle = _a.imgStyle, attachedImageContainerClassName = _a.attachedImageContainerClassName, attachedImageContainerStyle = _a.attachedImageContainerStyle, sendButtonIcon = _a.sendButtonIcon, attachmentButtonIcon = _a.attachmentButtonIcon, onSendMessage = _a.onSendMessage, _d = _a.suggestionPosition, suggestionPosition = _d === void 0 ? 'top' : _d, onImageUpload = _a.onImageUpload;
52
+ var users = _a.users, _c = _a.placeholder, placeholder = _c === void 0 ? "Type a message... (or drag & drop an image)" : _c, containerClassName = _a.containerClassName, inputContainerClassName = _a.inputContainerClassName, inputClassName = _a.inputClassName, sendBtnClassName = _a.sendBtnClassName, suggestionListClassName = _a.suggestionListClassName, suggestionItemClassName = _a.suggestionItemClassName, imgClassName = _a.imgClassName, imgStyle = _a.imgStyle, attachedImageContainerClassName = _a.attachedImageContainerClassName, attachedImageContainerStyle = _a.attachedImageContainerStyle, sendButtonIcon = _a.sendButtonIcon, attachmentButtonIcon = _a.attachmentButtonIcon, onSendMessage = _a.onSendMessage, _d = _a.suggestionPosition, suggestionPosition = _d === void 0 ? 'top' : _d, onImageUpload = _a.onImageUpload, getAuthHeaders = _a.getAuthHeaders, isProtectedUrl = _a.isProtectedUrl;
52
53
  var _e = useState(""), inputValue = _e[0], setInputValue = _e[1]; // Plain text
53
54
  var _f = useState([]), suggestions = _f[0], setSuggestions = _f[1];
54
55
  var _g = useState(false), showSuggestions = _g[0], setShowSuggestions = _g[1];
@@ -56,6 +57,12 @@ var MentionInput = function (_a) {
56
57
  var _j = useState(null), imageUrl = _j[0], setImageUrl = _j[1];
57
58
  var _k = useState(false), isUploading = _k[0], setIsUploading = _k[1];
58
59
  var _l = useState(false), isDraggingOver = _l[0], setIsDraggingOver = _l[1];
60
+ // Use protected image hook to handle authenticated image URLs
61
+ var displayImageUrl = useProtectedImage({
62
+ url: imageUrl,
63
+ isProtected: isProtectedUrl,
64
+ getAuthHeaders: getAuthHeaders,
65
+ });
59
66
  var inputRef = useRef(null);
60
67
  var suggestionListRef = useRef(null);
61
68
  var caretOffsetRef = useRef(0);
@@ -319,7 +326,12 @@ var MentionInput = function (_a) {
319
326
  case 1:
320
327
  _a.sent();
321
328
  _a.label = 2;
322
- case 2: return [2 /*return*/];
329
+ case 2:
330
+ // Reset the input value to allow selecting the same file again
331
+ if (fileInputRef.current) {
332
+ fileInputRef.current.value = '';
333
+ }
334
+ return [2 /*return*/];
323
335
  }
324
336
  });
325
337
  }); };
@@ -400,6 +412,10 @@ var MentionInput = function (_a) {
400
412
  var removeImage = function () {
401
413
  setSelectedImage(null);
402
414
  setImageUrl(null);
415
+ // Reset the input value when image is removed
416
+ if (fileInputRef.current) {
417
+ fileInputRef.current.value = '';
418
+ }
403
419
  };
404
420
  var handleSendMessage = function () {
405
421
  if (inputRef.current) {
@@ -421,6 +437,10 @@ var MentionInput = function (_a) {
421
437
  setImageUrl(null);
422
438
  userSelectListRef.current = [];
423
439
  userSelectListWithIdsRef.current = [];
440
+ // Reset the file input value after sending
441
+ if (fileInputRef.current) {
442
+ fileInputRef.current.value = '';
443
+ }
424
444
  }
425
445
  }
426
446
  };
@@ -444,8 +464,8 @@ var MentionInput = function (_a) {
444
464
  }
445
465
  };
446
466
  return (React.createElement("div", { className: "mention-container ".concat(containerClassName || "") },
447
- imageUrl && selectedImage && (React.createElement("div", { className: "image-preview-card ".concat(attachedImageContainerClassName || ""), style: attachedImageContainerStyle },
448
- React.createElement("img", { src: imageUrl, alt: "Preview", className: imgClassName || "", style: imgStyle }),
467
+ displayImageUrl && selectedImage && (React.createElement("div", { className: "image-preview-card ".concat(attachedImageContainerClassName || ""), style: attachedImageContainerStyle },
468
+ React.createElement("img", { src: displayImageUrl, alt: "Preview", className: imgClassName || "", style: imgStyle }),
449
469
  React.createElement("button", { onClick: removeImage, className: "remove-image-btn", "aria-label": "Remove image" }, "\u00D7"))),
450
470
  React.createElement("div", { className: "mention-input-container ".concat(inputContainerClassName || "", " ").concat(isDraggingOver ? 'dragging-over' : ''), onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDragEnd: function () { return setIsDraggingOver(false); }, onDrop: handleDrop },
451
471
  isDraggingOver && (React.createElement("div", { className: "drag-overlay" },
@@ -46,6 +46,8 @@ interface ShowMessageCardProps {
46
46
  item: MessageCardProps;
47
47
  }) => ReactNode;
48
48
  renderItem?: (element: MessageCardProps) => ReactNode;
49
+ getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
50
+ isProtectedUrl?: boolean | ((url: string) => boolean);
49
51
  }
50
52
  export declare const ShowMessageCard: React.FC<ShowMessageCardProps>;
51
53
  export {};
@@ -20,6 +20,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
20
20
  };
21
21
  import React, { useState } from "react";
22
22
  import "./ShowMessageCard.css";
23
+ import { useProtectedImage } from "./useProtectedImage";
23
24
  export var ShowMessageCard = function (_a) {
24
25
  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, _f = _a.imageUrlKey, imageUrlKey = _f === void 0 ? "imageUrl" : _f, // Default key for attached image
25
26
  _g = _a.objectNameKey, // Default key for attached image
@@ -29,7 +30,8 @@ export var ShowMessageCard = function (_a) {
29
30
  _j = _a.objectTypeIconKey, // Default key for revision
30
31
  objectTypeIconKey = _j === void 0 ? "object_type_icon" : _j, // Default key for object type icon
31
32
  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, attachedImageClassName = _a.attachedImageClassName, attachedImageStyle = _a.attachedImageStyle, attachedImageContainerClassName = _a.attachedImageContainerClassName, attachedImageContainerStyle = _a.attachedImageContainerStyle, objectNameClassName = _a.objectNameClassName, objectNameStyle = _a.objectNameStyle, revisionClassName = _a.revisionClassName, revisionStyle = _a.revisionStyle, objectChipRender = _a.objectChipRender, // Custom render function for object chip
32
- renderItem = _a.renderItem;
33
+ renderItem = _a.renderItem, // Custom render function
34
+ getAuthHeaders = _a.getAuthHeaders, isProtectedUrl = _a.isProtectedUrl;
33
35
  // State to manage initials for images that fail to load
34
36
  var _k = useState({}), initialsState = _k[0], setInitialsState = _k[1];
35
37
  // Handle image load failure
@@ -48,6 +50,23 @@ export var ShowMessageCard = function (_a) {
48
50
  .join("");
49
51
  return initials;
50
52
  };
53
+ // Component to render protected images
54
+ var ProtectedImage = function (_a) {
55
+ var url = _a.url, alt = _a.alt, className = _a.className, style = _a.style, containerClassName = _a.containerClassName, containerStyle = _a.containerStyle, onError = _a.onError, _b = _a.renderInContainer, renderInContainer = _b === void 0 ? true : _b;
56
+ var displayUrl = useProtectedImage({
57
+ url: url,
58
+ isProtected: isProtectedUrl,
59
+ getAuthHeaders: getAuthHeaders,
60
+ });
61
+ if (!displayUrl) {
62
+ return null;
63
+ }
64
+ var imgElement = (React.createElement("img", { src: displayUrl, alt: alt, className: className, style: style, onError: onError }));
65
+ if (renderInContainer && containerClassName) {
66
+ return (React.createElement("div", { className: containerClassName, style: containerStyle }, imgElement));
67
+ }
68
+ return imgElement;
69
+ };
51
70
  // Helper function to extract hashtags and mentions from text
52
71
  var extractTagsAndMentions = function (text) {
53
72
  // First extract from HTML with spans
@@ -82,15 +101,14 @@ export var ShowMessageCard = function (_a) {
82
101
  return (React.createElement("div", { key: item.id || index, className: "message-card-wrapper" },
83
102
  React.createElement("div", { className: "message-card-header ".concat(headerClassName || ""), style: headerStyle },
84
103
  React.createElement("div", { className: "message-card-header-left" },
85
- 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); } })),
104
+ showInitials ? (React.createElement("div", { className: "message-card-initials ".concat(imgClassName || ""), style: imgStyle }, getInitials(item[nameKey]))) : (React.createElement(ProtectedImage, { url: item[imgSrcKey], alt: item[nameKey], className: "message-card-img ".concat(imgClassName || ""), style: imgStyle, onError: function () { return handleImageError(item.id || index); }, renderInContainer: false })),
86
105
  React.createElement("div", { className: "message-card-info ".concat(infoClassName || ""), style: infoStyle },
87
106
  React.createElement("h3", { className: "message-card-name ".concat(nameClassName || ""), style: nameStyle }, item[nameKey]),
88
107
  React.createElement("p", { className: "message-card-date ".concat(dateClassName || ""), style: dateStyle }, item[dateKey])))),
89
108
  React.createElement("div", { className: "message-card ".concat(cardClassName || ""), style: cardStyle },
90
109
  React.createElement("div", { className: "message-card-body ".concat(bodyClassName || ""), style: bodyStyle },
91
110
  React.createElement("p", { className: "message-card-comment ".concat(commentClassName || ""), style: commentStyle, dangerouslySetInnerHTML: { __html: item[commentKey] } }),
92
- (item === null || item === void 0 ? void 0 : item[imageUrlKey]) && (React.createElement("div", { className: "message-card-attached-image-container ".concat(attachedImageContainerClassName || ""), style: attachedImageContainerStyle },
93
- React.createElement("img", { src: item[imageUrlKey], alt: "Attached", className: "message-card-attached-image ".concat(attachedImageClassName || ""), style: attachedImageStyle }))),
111
+ (item === null || item === void 0 ? void 0 : item[imageUrlKey]) && (React.createElement(ProtectedImage, { url: item[imageUrlKey], alt: "Attached", className: "message-card-attached-image ".concat(attachedImageClassName || ""), style: attachedImageStyle, containerClassName: "message-card-attached-image-container ".concat(attachedImageContainerClassName || ""), containerStyle: attachedImageContainerStyle })),
94
112
  item[objectNameKey] && (React.createElement("div", { className: "message-card-related" },
95
113
  React.createElement("span", { className: "related-label" }, "Related: "),
96
114
  (item[objectNameKey] || item[revisionKey] || item[objectTypeIconKey]) && (React.createElement(React.Fragment, null, objectChipRender ? (objectChipRender({
@@ -0,0 +1,12 @@
1
+ interface UseProtectedImageOptions {
2
+ url: string | null;
3
+ isProtected?: boolean | ((url: string) => boolean);
4
+ getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
5
+ }
6
+ /**
7
+ * Custom hook to handle protected image URLs that require authentication tokens in headers.
8
+ * For protected URLs, it fetches the image with auth headers and converts it to a blob URL.
9
+ * For non-protected URLs, it returns the URL as-is.
10
+ */
11
+ export declare const useProtectedImage: ({ url, isProtected, getAuthHeaders, }: UseProtectedImageOptions) => string | null;
12
+ export {};
@@ -0,0 +1,129 @@
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
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
13
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
14
+ return new (P || (P = Promise))(function (resolve, reject) {
15
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
16
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
17
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
18
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
19
+ });
20
+ };
21
+ var __generator = (this && this.__generator) || function (thisArg, body) {
22
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
23
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
24
+ function verb(n) { return function (v) { return step([n, v]); }; }
25
+ function step(op) {
26
+ if (f) throw new TypeError("Generator is already executing.");
27
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
28
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
29
+ if (y = 0, t) op = [op[0] & 2, t.value];
30
+ switch (op[0]) {
31
+ case 0: case 1: t = op; break;
32
+ case 4: _.label++; return { value: op[1], done: false };
33
+ case 5: _.label++; y = op[1]; op = [0]; continue;
34
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
35
+ default:
36
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
37
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
38
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
39
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
40
+ if (t[2]) _.ops.pop();
41
+ _.trys.pop(); continue;
42
+ }
43
+ op = body.call(thisArg, _);
44
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
45
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
46
+ }
47
+ };
48
+ import { useState, useEffect, useRef } from 'react';
49
+ /**
50
+ * Custom hook to handle protected image URLs that require authentication tokens in headers.
51
+ * For protected URLs, it fetches the image with auth headers and converts it to a blob URL.
52
+ * For non-protected URLs, it returns the URL as-is.
53
+ */
54
+ export var useProtectedImage = function (_a) {
55
+ var url = _a.url, isProtected = _a.isProtected, getAuthHeaders = _a.getAuthHeaders;
56
+ var _b = useState(null), blobUrl = _b[0], setBlobUrl = _b[1];
57
+ var blobUrlRef = useRef(null);
58
+ useEffect(function () {
59
+ // Cleanup previous blob URL
60
+ if (blobUrlRef.current) {
61
+ URL.revokeObjectURL(blobUrlRef.current);
62
+ blobUrlRef.current = null;
63
+ }
64
+ setBlobUrl(null);
65
+ if (!url) {
66
+ return;
67
+ }
68
+ // Check if URL is protected
69
+ var shouldUseAuth = typeof isProtected === 'boolean'
70
+ ? isProtected
71
+ : isProtected ? isProtected(url) : false;
72
+ // If not protected, don't fetch - will use original URL
73
+ if (!shouldUseAuth || !getAuthHeaders) {
74
+ return;
75
+ }
76
+ // Fetch protected image with auth headers
77
+ var fetchProtectedImage = function () { return __awaiter(void 0, void 0, void 0, function () {
78
+ var headers, response, blob, newBlobUrl, error_1;
79
+ return __generator(this, function (_a) {
80
+ switch (_a.label) {
81
+ case 0:
82
+ _a.trys.push([0, 4, , 5]);
83
+ return [4 /*yield*/, Promise.resolve(getAuthHeaders())];
84
+ case 1:
85
+ headers = _a.sent();
86
+ return [4 /*yield*/, fetch(url, {
87
+ method: 'GET',
88
+ headers: __assign({}, headers),
89
+ })];
90
+ case 2:
91
+ response = _a.sent();
92
+ if (!response.ok) {
93
+ throw new Error("Failed to fetch protected image: ".concat(response.statusText));
94
+ }
95
+ return [4 /*yield*/, response.blob()];
96
+ case 3:
97
+ blob = _a.sent();
98
+ newBlobUrl = URL.createObjectURL(blob);
99
+ blobUrlRef.current = newBlobUrl;
100
+ setBlobUrl(newBlobUrl);
101
+ return [3 /*break*/, 5];
102
+ case 4:
103
+ error_1 = _a.sent();
104
+ console.error('Error fetching protected image:', error_1);
105
+ // On error, fallback to original URL (might show broken image, but won't crash)
106
+ setBlobUrl(null);
107
+ return [3 /*break*/, 5];
108
+ case 5: return [2 /*return*/];
109
+ }
110
+ });
111
+ }); };
112
+ fetchProtectedImage();
113
+ // Cleanup function
114
+ return function () {
115
+ if (blobUrlRef.current) {
116
+ URL.revokeObjectURL(blobUrlRef.current);
117
+ blobUrlRef.current = null;
118
+ }
119
+ };
120
+ }, [url, isProtected, getAuthHeaders]);
121
+ // Return blob URL if available, otherwise return original URL (or null)
122
+ if (!url) {
123
+ return null;
124
+ }
125
+ var isUrlProtected = typeof isProtected === 'boolean'
126
+ ? isProtected
127
+ : isProtected ? isProtected(url) : false;
128
+ return blobUrl || (!isUrlProtected ? url : null);
129
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-mention-input",
3
- "version": "1.1.26",
3
+ "version": "1.1.28",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {
@@ -1,6 +1,7 @@
1
1
  import React, { useState, useRef, ReactNode, useEffect } from "react";
2
2
  import ReactDOM from "react-dom";
3
3
  import "./MentionInput.css";
4
+ import { useProtectedImage } from "./useProtectedImage";
4
5
 
5
6
  interface User {
6
7
  id: number;
@@ -32,6 +33,8 @@ interface MentionInputProps {
32
33
  }) => void;
33
34
  suggestionPosition?: 'top' | 'bottom' | 'left' | 'right'; // New prop for tooltip position
34
35
  onImageUpload?: (file: File) => Promise<string>; // New prop for image upload
36
+ getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>; // Function to get auth headers for protected images
37
+ isProtectedUrl?: boolean | ((url: string) => boolean); // Boolean or function to determine if a URL is protected and requires auth
35
38
  }
36
39
 
37
40
  const MentionInput: React.FC<MentionInputProps> = ({
@@ -52,6 +55,8 @@ const MentionInput: React.FC<MentionInputProps> = ({
52
55
  onSendMessage,
53
56
  suggestionPosition = 'top',
54
57
  onImageUpload,
58
+ getAuthHeaders,
59
+ isProtectedUrl,
55
60
  }) => {
56
61
  const [inputValue, setInputValue] = useState<string>(""); // Plain text
57
62
  const [suggestions, setSuggestions] = useState<User[]>([]);
@@ -61,6 +66,13 @@ const MentionInput: React.FC<MentionInputProps> = ({
61
66
  const [isUploading, setIsUploading] = useState<boolean>(false);
62
67
  const [isDraggingOver, setIsDraggingOver] = useState<boolean>(false);
63
68
 
69
+ // Use protected image hook to handle authenticated image URLs
70
+ const displayImageUrl = useProtectedImage({
71
+ url: imageUrl,
72
+ isProtected: isProtectedUrl,
73
+ getAuthHeaders,
74
+ });
75
+
64
76
 
65
77
 
66
78
  const inputRef = useRef<HTMLDivElement>(null);
@@ -415,6 +427,10 @@ const MentionInput: React.FC<MentionInputProps> = ({
415
427
  await uploadImage(file);
416
428
  }
417
429
  }
430
+ // Reset the input value to allow selecting the same file again
431
+ if (fileInputRef.current) {
432
+ fileInputRef.current.value = '';
433
+ }
418
434
  };
419
435
 
420
436
  const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
@@ -481,6 +497,10 @@ const MentionInput: React.FC<MentionInputProps> = ({
481
497
  const removeImage = () => {
482
498
  setSelectedImage(null);
483
499
  setImageUrl(null);
500
+ // Reset the input value when image is removed
501
+ if (fileInputRef.current) {
502
+ fileInputRef.current.value = '';
503
+ }
484
504
  };
485
505
 
486
506
  const handleSendMessage = () => {
@@ -504,6 +524,10 @@ const MentionInput: React.FC<MentionInputProps> = ({
504
524
  setImageUrl(null);
505
525
  userSelectListRef.current = [];
506
526
  userSelectListWithIdsRef.current = [];
527
+ // Reset the file input value after sending
528
+ if (fileInputRef.current) {
529
+ fileInputRef.current.value = '';
530
+ }
507
531
  }
508
532
  }
509
533
  };
@@ -532,10 +556,10 @@ const MentionInput: React.FC<MentionInputProps> = ({
532
556
 
533
557
  return (
534
558
  <div className={`mention-container ${containerClassName || ""}`}>
535
- {imageUrl && selectedImage && (
559
+ {displayImageUrl && selectedImage && (
536
560
  <div className={`image-preview-card ${attachedImageContainerClassName || ""}`} style={attachedImageContainerStyle}>
537
561
  <img
538
- src={imageUrl}
562
+ src={displayImageUrl}
539
563
  alt="Preview"
540
564
  className={imgClassName || ""}
541
565
  style={imgStyle}
@@ -1,5 +1,6 @@
1
1
  import React, { CSSProperties, useState, ReactNode } from "react";
2
2
  import "./ShowMessageCard.css";
3
+ import { useProtectedImage } from "./useProtectedImage";
3
4
 
4
5
  interface MessageCardProps {
5
6
  [key: string]: any; // Support dynamic keys
@@ -43,6 +44,8 @@ interface ShowMessageCardProps {
43
44
  revisionStyle?: CSSProperties; // Style for the revision (top-right)
44
45
  objectChipRender?: (objectData: { objectName?: string; revision?: string; objectTypeIcon?: string; item: MessageCardProps }) => ReactNode; // Custom render function for object chip
45
46
  renderItem?: (element: MessageCardProps) => ReactNode; // Custom render function
47
+ getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>; // Function to get auth headers for protected images
48
+ isProtectedUrl?: boolean | ((url: string) => boolean); // Boolean or function to determine if a URL is protected and requires auth
46
49
  }
47
50
 
48
51
  export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
@@ -83,6 +86,8 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
83
86
  revisionStyle,
84
87
  objectChipRender, // Custom render function for object chip
85
88
  renderItem, // Custom render function
89
+ getAuthHeaders,
90
+ isProtectedUrl,
86
91
  }) => {
87
92
  // State to manage initials for images that fail to load
88
93
  const [initialsState, setInitialsState] = useState<{ [key: string]: boolean }>(
@@ -107,6 +112,48 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
107
112
  return initials;
108
113
  };
109
114
 
115
+ // Component to render protected images
116
+ const ProtectedImage: React.FC<{
117
+ url: string;
118
+ alt: string;
119
+ className?: string;
120
+ style?: CSSProperties;
121
+ containerClassName?: string;
122
+ containerStyle?: CSSProperties;
123
+ onError?: () => void;
124
+ renderInContainer?: boolean;
125
+ }> = ({ url, alt, className, style, containerClassName, containerStyle, onError, renderInContainer = true }) => {
126
+ const displayUrl = useProtectedImage({
127
+ url,
128
+ isProtected: isProtectedUrl,
129
+ getAuthHeaders,
130
+ });
131
+
132
+ if (!displayUrl) {
133
+ return null;
134
+ }
135
+
136
+ const imgElement = (
137
+ <img
138
+ src={displayUrl}
139
+ alt={alt}
140
+ className={className}
141
+ style={style}
142
+ onError={onError}
143
+ />
144
+ );
145
+
146
+ if (renderInContainer && containerClassName) {
147
+ return (
148
+ <div className={containerClassName} style={containerStyle}>
149
+ {imgElement}
150
+ </div>
151
+ );
152
+ }
153
+
154
+ return imgElement;
155
+ };
156
+
110
157
  // Helper function to extract hashtags and mentions from text
111
158
  const extractTagsAndMentions = (text: string) => {
112
159
  // First extract from HTML with spans
@@ -171,12 +218,13 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
171
218
  {getInitials(item[nameKey])}
172
219
  </div>
173
220
  ) : (
174
- <img
175
- src={item[imgSrcKey]}
221
+ <ProtectedImage
222
+ url={item[imgSrcKey]}
176
223
  alt={item[nameKey]}
177
224
  className={`message-card-img ${imgClassName || ""}`}
178
225
  style={imgStyle}
179
226
  onError={() => handleImageError(item.id || index)}
227
+ renderInContainer={false}
180
228
  />
181
229
  )}
182
230
  <div
@@ -216,17 +264,14 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
216
264
 
217
265
  {/* Display attached image if available */}
218
266
  {item?.[imageUrlKey] && (
219
- <div
220
- className={`message-card-attached-image-container ${attachedImageContainerClassName || ""}`}
221
- style={attachedImageContainerStyle}
222
- >
223
- <img
224
- src={item[imageUrlKey]}
225
- alt="Attached"
226
- className={`message-card-attached-image ${attachedImageClassName || ""}`}
227
- style={attachedImageStyle}
228
- />
229
- </div>
267
+ <ProtectedImage
268
+ url={item[imageUrlKey]}
269
+ alt="Attached"
270
+ className={`message-card-attached-image ${attachedImageClassName || ""}`}
271
+ style={attachedImageStyle}
272
+ containerClassName={`message-card-attached-image-container ${attachedImageContainerClassName || ""}`}
273
+ containerStyle={attachedImageContainerStyle}
274
+ />
230
275
  )}
231
276
 
232
277
  {/* Display related object at bottom-left */}
@@ -0,0 +1,91 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+
3
+ interface UseProtectedImageOptions {
4
+ url: string | null;
5
+ isProtected?: boolean | ((url: string) => boolean);
6
+ getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
7
+ }
8
+
9
+ /**
10
+ * Custom hook to handle protected image URLs that require authentication tokens in headers.
11
+ * For protected URLs, it fetches the image with auth headers and converts it to a blob URL.
12
+ * For non-protected URLs, it returns the URL as-is.
13
+ */
14
+ export const useProtectedImage = ({
15
+ url,
16
+ isProtected,
17
+ getAuthHeaders,
18
+ }: UseProtectedImageOptions): string | null => {
19
+ const [blobUrl, setBlobUrl] = useState<string | null>(null);
20
+ const blobUrlRef = useRef<string | null>(null);
21
+
22
+ useEffect(() => {
23
+ // Cleanup previous blob URL
24
+ if (blobUrlRef.current) {
25
+ URL.revokeObjectURL(blobUrlRef.current);
26
+ blobUrlRef.current = null;
27
+ }
28
+ setBlobUrl(null);
29
+
30
+ if (!url) {
31
+ return;
32
+ }
33
+
34
+ // Check if URL is protected
35
+ const shouldUseAuth = typeof isProtected === 'boolean'
36
+ ? isProtected
37
+ : isProtected ? isProtected(url) : false;
38
+
39
+ // If not protected, don't fetch - will use original URL
40
+ if (!shouldUseAuth || !getAuthHeaders) {
41
+ return;
42
+ }
43
+
44
+ // Fetch protected image with auth headers
45
+ const fetchProtectedImage = async () => {
46
+ try {
47
+ const headers = await Promise.resolve(getAuthHeaders());
48
+ const response = await fetch(url, {
49
+ method: 'GET',
50
+ headers: {
51
+ ...headers,
52
+ },
53
+ });
54
+
55
+ if (!response.ok) {
56
+ throw new Error(`Failed to fetch protected image: ${response.statusText}`);
57
+ }
58
+
59
+ const blob = await response.blob();
60
+ const newBlobUrl = URL.createObjectURL(blob);
61
+ blobUrlRef.current = newBlobUrl;
62
+ setBlobUrl(newBlobUrl);
63
+ } catch (error) {
64
+ console.error('Error fetching protected image:', error);
65
+ // On error, fallback to original URL (might show broken image, but won't crash)
66
+ setBlobUrl(null);
67
+ }
68
+ };
69
+
70
+ fetchProtectedImage();
71
+
72
+ // Cleanup function
73
+ return () => {
74
+ if (blobUrlRef.current) {
75
+ URL.revokeObjectURL(blobUrlRef.current);
76
+ blobUrlRef.current = null;
77
+ }
78
+ };
79
+ }, [url, isProtected, getAuthHeaders]);
80
+
81
+ // Return blob URL if available, otherwise return original URL (or null)
82
+ if (!url) {
83
+ return null;
84
+ }
85
+
86
+ const isUrlProtected = typeof isProtected === 'boolean'
87
+ ? isProtected
88
+ : isProtected ? isProtected(url) : false;
89
+ return blobUrl || (!isUrlProtected ? url : null);
90
+ };
91
+