react-mention-input 1.1.29 → 1.1.30
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/ShowMessageCard.js
CHANGED
|
@@ -18,7 +18,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
|
18
18
|
}
|
|
19
19
|
return to.concat(ar || Array.prototype.slice.call(from));
|
|
20
20
|
};
|
|
21
|
-
import React, { useState } from "react";
|
|
21
|
+
import React, { useState, memo } from "react";
|
|
22
22
|
import "./ShowMessageCard.css";
|
|
23
23
|
import { useProtectedImage } from "./useProtectedImage";
|
|
24
24
|
export var ShowMessageCard = function (_a) {
|
|
@@ -50,8 +50,8 @@ export var ShowMessageCard = function (_a) {
|
|
|
50
50
|
.join("");
|
|
51
51
|
return initials;
|
|
52
52
|
};
|
|
53
|
-
// Component to render protected images
|
|
54
|
-
var ProtectedImage = function (_a) {
|
|
53
|
+
// Component to render protected images - memoized to prevent recreation on every render
|
|
54
|
+
var ProtectedImage = memo(function (_a) {
|
|
55
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
56
|
var displayUrl = useProtectedImage({
|
|
57
57
|
url: url,
|
|
@@ -66,7 +66,7 @@ export var ShowMessageCard = function (_a) {
|
|
|
66
66
|
return (React.createElement("div", { className: containerClassName, style: containerStyle }, imgElement));
|
|
67
67
|
}
|
|
68
68
|
return imgElement;
|
|
69
|
-
};
|
|
69
|
+
});
|
|
70
70
|
// Helper function to extract hashtags and mentions from text
|
|
71
71
|
var extractTagsAndMentions = function (text) {
|
|
72
72
|
// First extract from HTML with spans
|
|
@@ -7,6 +7,7 @@ interface UseProtectedImageOptions {
|
|
|
7
7
|
* Custom hook to handle protected image URLs that require authentication tokens in headers.
|
|
8
8
|
* For protected URLs, it fetches the image with auth headers and converts it to a blob URL.
|
|
9
9
|
* For non-protected URLs, it returns the URL as-is.
|
|
10
|
+
* Uses a module-level cache to prevent blinking on re-renders.
|
|
10
11
|
*/
|
|
11
12
|
export declare const useProtectedImage: ({ url, isProtected, getAuthHeaders, }: UseProtectedImageOptions) => string | null;
|
|
12
13
|
export {};
|
|
@@ -46,40 +46,49 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
48
|
import { useState, useEffect, useRef } from 'react';
|
|
49
|
+
// Module-level cache to persist blob URLs across component re-renders
|
|
50
|
+
var blobUrlCache = new Map();
|
|
51
|
+
var fetchingUrls = new Set();
|
|
49
52
|
/**
|
|
50
53
|
* Custom hook to handle protected image URLs that require authentication tokens in headers.
|
|
51
54
|
* For protected URLs, it fetches the image with auth headers and converts it to a blob URL.
|
|
52
55
|
* For non-protected URLs, it returns the URL as-is.
|
|
56
|
+
* Uses a module-level cache to prevent blinking on re-renders.
|
|
53
57
|
*/
|
|
54
58
|
export var useProtectedImage = function (_a) {
|
|
55
59
|
var url = _a.url, isProtected = _a.isProtected, getAuthHeaders = _a.getAuthHeaders;
|
|
56
|
-
var _b = useState(
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
var _b = useState(function () {
|
|
61
|
+
// Initialize from cache if available
|
|
62
|
+
return url ? blobUrlCache.get(url) || null : null;
|
|
63
|
+
}), blobUrl = _b[0], setBlobUrl = _b[1];
|
|
59
64
|
var previousUrlRef = useRef(null);
|
|
60
65
|
var isProtectedRef = useRef(isProtected);
|
|
61
66
|
var getAuthHeadersRef = useRef(getAuthHeaders);
|
|
67
|
+
var mountedRef = useRef(true);
|
|
62
68
|
// Update refs when props change (but don't trigger re-fetch)
|
|
63
69
|
useEffect(function () {
|
|
64
70
|
isProtectedRef.current = isProtected;
|
|
65
71
|
getAuthHeadersRef.current = getAuthHeaders;
|
|
66
72
|
}, [isProtected, getAuthHeaders]);
|
|
67
73
|
useEffect(function () {
|
|
68
|
-
|
|
74
|
+
mountedRef.current = true;
|
|
75
|
+
return function () {
|
|
76
|
+
mountedRef.current = false;
|
|
77
|
+
};
|
|
78
|
+
}, []);
|
|
79
|
+
useEffect(function () {
|
|
80
|
+
// Always check cache first and restore if needed (synchronous)
|
|
81
|
+
if (url && blobUrlCache.has(url) && !blobUrl) {
|
|
82
|
+
var cachedBlobUrl = blobUrlCache.get(url);
|
|
83
|
+
setBlobUrl(cachedBlobUrl);
|
|
84
|
+
}
|
|
85
|
+
// If URL hasn't changed, keep using cached blob URL
|
|
69
86
|
if (url === previousUrlRef.current) {
|
|
70
87
|
return;
|
|
71
88
|
}
|
|
72
|
-
// Store previous URL and blob URL for later cleanup
|
|
73
89
|
var oldUrl = previousUrlRef.current;
|
|
74
|
-
var oldBlobUrl = blobUrlRef.current;
|
|
75
90
|
previousUrlRef.current = url;
|
|
76
91
|
if (!url) {
|
|
77
|
-
// If URL is null, clean up old blob URL and clear state
|
|
78
|
-
if (oldBlobUrl) {
|
|
79
|
-
URL.revokeObjectURL(oldBlobUrl);
|
|
80
|
-
blobUrlRef.current = null;
|
|
81
|
-
blobUrlForUrlRef.current = null;
|
|
82
|
-
}
|
|
83
92
|
setBlobUrl(null);
|
|
84
93
|
return;
|
|
85
94
|
}
|
|
@@ -87,23 +96,35 @@ export var useProtectedImage = function (_a) {
|
|
|
87
96
|
var shouldUseAuth = typeof isProtectedRef.current === 'boolean'
|
|
88
97
|
? isProtectedRef.current
|
|
89
98
|
: isProtectedRef.current ? isProtectedRef.current(url) : false;
|
|
90
|
-
// If not protected,
|
|
99
|
+
// If not protected, use original URL
|
|
91
100
|
if (!shouldUseAuth || !getAuthHeadersRef.current) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
101
|
+
// Clear any cached blob URL for this URL
|
|
102
|
+
if (blobUrlCache.has(url)) {
|
|
103
|
+
var cachedBlobUrl = blobUrlCache.get(url);
|
|
104
|
+
URL.revokeObjectURL(cachedBlobUrl);
|
|
105
|
+
blobUrlCache.delete(url);
|
|
96
106
|
}
|
|
97
107
|
setBlobUrl(null);
|
|
98
108
|
return;
|
|
99
109
|
}
|
|
110
|
+
// Check cache first - if we have a cached blob URL, use it immediately
|
|
111
|
+
if (blobUrlCache.has(url)) {
|
|
112
|
+
var cachedBlobUrl = blobUrlCache.get(url);
|
|
113
|
+
setBlobUrl(cachedBlobUrl);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// If already fetching this URL, don't fetch again
|
|
117
|
+
if (fetchingUrls.has(url)) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
100
120
|
// Fetch protected image with auth headers
|
|
121
|
+
fetchingUrls.add(url);
|
|
101
122
|
var fetchProtectedImage = function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
102
123
|
var headers, response, blob, newBlobUrl, error_1;
|
|
103
124
|
return __generator(this, function (_a) {
|
|
104
125
|
switch (_a.label) {
|
|
105
126
|
case 0:
|
|
106
|
-
_a.trys.push([0, 4, ,
|
|
127
|
+
_a.trys.push([0, 4, 5, 6]);
|
|
107
128
|
return [4 /*yield*/, Promise.resolve(getAuthHeadersRef.current())];
|
|
108
129
|
case 1:
|
|
109
130
|
headers = _a.sent();
|
|
@@ -120,44 +141,36 @@ export var useProtectedImage = function (_a) {
|
|
|
120
141
|
case 3:
|
|
121
142
|
blob = _a.sent();
|
|
122
143
|
newBlobUrl = URL.createObjectURL(blob);
|
|
123
|
-
// Only update if URL hasn't changed during fetch
|
|
124
|
-
if (previousUrlRef.current === url) {
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
URL.revokeObjectURL(oldBlobUrl);
|
|
128
|
-
}
|
|
129
|
-
blobUrlRef.current = newBlobUrl;
|
|
130
|
-
blobUrlForUrlRef.current = url;
|
|
144
|
+
// Only update if URL hasn't changed during fetch and component is still mounted
|
|
145
|
+
if (previousUrlRef.current === url && mountedRef.current) {
|
|
146
|
+
// Cache the blob URL
|
|
147
|
+
blobUrlCache.set(url, newBlobUrl);
|
|
131
148
|
setBlobUrl(newBlobUrl);
|
|
132
149
|
}
|
|
133
150
|
else {
|
|
134
|
-
// URL changed during fetch, revoke this blob URL
|
|
151
|
+
// URL changed during fetch or component unmounted, revoke this blob URL
|
|
135
152
|
URL.revokeObjectURL(newBlobUrl);
|
|
136
153
|
}
|
|
137
|
-
return [3 /*break*/,
|
|
154
|
+
return [3 /*break*/, 6];
|
|
138
155
|
case 4:
|
|
139
156
|
error_1 = _a.sent();
|
|
140
157
|
console.error('Error fetching protected image:', error_1);
|
|
141
|
-
// On error, only clear if URL hasn't changed
|
|
142
|
-
if (previousUrlRef.current === url) {
|
|
158
|
+
// On error, only clear if URL hasn't changed and component is mounted
|
|
159
|
+
if (previousUrlRef.current === url && mountedRef.current) {
|
|
143
160
|
setBlobUrl(null);
|
|
144
161
|
}
|
|
145
|
-
return [3 /*break*/,
|
|
146
|
-
case 5:
|
|
162
|
+
return [3 /*break*/, 6];
|
|
163
|
+
case 5:
|
|
164
|
+
fetchingUrls.delete(url);
|
|
165
|
+
return [7 /*endfinally*/];
|
|
166
|
+
case 6: return [2 /*return*/];
|
|
147
167
|
}
|
|
148
168
|
});
|
|
149
169
|
}); };
|
|
150
170
|
fetchProtectedImage();
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (blobUrlRef.current) {
|
|
155
|
-
URL.revokeObjectURL(blobUrlRef.current);
|
|
156
|
-
blobUrlRef.current = null;
|
|
157
|
-
blobUrlForUrlRef.current = null;
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
}, [url]); // Only depend on url, not isProtected or getAuthHeaders
|
|
171
|
+
}, [url]); // Only depend on url
|
|
172
|
+
// Cleanup: Don't revoke blob URLs on unmount - keep them in cache for reuse
|
|
173
|
+
// They will be cleaned up when the URL changes or when the cache is cleared
|
|
161
174
|
// Return blob URL if available, otherwise return original URL (or null)
|
|
162
175
|
if (!url) {
|
|
163
176
|
return null;
|
|
@@ -165,5 +178,10 @@ export var useProtectedImage = function (_a) {
|
|
|
165
178
|
var isUrlProtected = typeof isProtectedRef.current === 'boolean'
|
|
166
179
|
? isProtectedRef.current
|
|
167
180
|
: isProtectedRef.current ? isProtectedRef.current(url) : false;
|
|
168
|
-
return
|
|
181
|
+
// For protected URLs, return cached blob URL or current blobUrl state
|
|
182
|
+
// For non-protected URLs, return the original URL
|
|
183
|
+
if (isUrlProtected) {
|
|
184
|
+
return blobUrl || blobUrlCache.get(url) || null;
|
|
185
|
+
}
|
|
186
|
+
return url;
|
|
169
187
|
};
|
package/package.json
CHANGED
package/src/ShowMessageCard.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { CSSProperties, useState, ReactNode } from "react";
|
|
1
|
+
import React, { CSSProperties, useState, ReactNode, useMemo, memo } from "react";
|
|
2
2
|
import "./ShowMessageCard.css";
|
|
3
3
|
import { useProtectedImage } from "./useProtectedImage";
|
|
4
4
|
|
|
@@ -112,8 +112,8 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
|
|
|
112
112
|
return initials;
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
-
// Component to render protected images
|
|
116
|
-
const ProtectedImage
|
|
115
|
+
// Component to render protected images - memoized to prevent recreation on every render
|
|
116
|
+
const ProtectedImage = memo<{
|
|
117
117
|
url: string;
|
|
118
118
|
alt: string;
|
|
119
119
|
className?: string;
|
|
@@ -122,7 +122,7 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
|
|
|
122
122
|
containerStyle?: CSSProperties;
|
|
123
123
|
onError?: () => void;
|
|
124
124
|
renderInContainer?: boolean;
|
|
125
|
-
}>
|
|
125
|
+
}>(({ url, alt, className, style, containerClassName, containerStyle, onError, renderInContainer = true }) => {
|
|
126
126
|
const displayUrl = useProtectedImage({
|
|
127
127
|
url,
|
|
128
128
|
isProtected: isProtectedUrl,
|
|
@@ -152,7 +152,7 @@ export const ShowMessageCard: React.FC<ShowMessageCardProps> = ({
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
return imgElement;
|
|
155
|
-
};
|
|
155
|
+
});
|
|
156
156
|
|
|
157
157
|
// Helper function to extract hashtags and mentions from text
|
|
158
158
|
const extractTagsAndMentions = (text: string) => {
|
package/src/useProtectedImage.ts
CHANGED
|
@@ -6,22 +6,29 @@ interface UseProtectedImageOptions {
|
|
|
6
6
|
getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
// Module-level cache to persist blob URLs across component re-renders
|
|
10
|
+
const blobUrlCache = new Map<string, string>();
|
|
11
|
+
const fetchingUrls = new Set<string>();
|
|
12
|
+
|
|
9
13
|
/**
|
|
10
14
|
* Custom hook to handle protected image URLs that require authentication tokens in headers.
|
|
11
15
|
* For protected URLs, it fetches the image with auth headers and converts it to a blob URL.
|
|
12
16
|
* For non-protected URLs, it returns the URL as-is.
|
|
17
|
+
* Uses a module-level cache to prevent blinking on re-renders.
|
|
13
18
|
*/
|
|
14
19
|
export const useProtectedImage = ({
|
|
15
20
|
url,
|
|
16
21
|
isProtected,
|
|
17
22
|
getAuthHeaders,
|
|
18
23
|
}: UseProtectedImageOptions): string | null => {
|
|
19
|
-
const [blobUrl, setBlobUrl] = useState<string | null>(
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
const [blobUrl, setBlobUrl] = useState<string | null>(() => {
|
|
25
|
+
// Initialize from cache if available
|
|
26
|
+
return url ? blobUrlCache.get(url) || null : null;
|
|
27
|
+
});
|
|
22
28
|
const previousUrlRef = useRef<string | null>(null);
|
|
23
29
|
const isProtectedRef = useRef(isProtected);
|
|
24
30
|
const getAuthHeadersRef = useRef(getAuthHeaders);
|
|
31
|
+
const mountedRef = useRef(true);
|
|
25
32
|
|
|
26
33
|
// Update refs when props change (but don't trigger re-fetch)
|
|
27
34
|
useEffect(() => {
|
|
@@ -30,23 +37,28 @@ export const useProtectedImage = ({
|
|
|
30
37
|
}, [isProtected, getAuthHeaders]);
|
|
31
38
|
|
|
32
39
|
useEffect(() => {
|
|
33
|
-
|
|
40
|
+
mountedRef.current = true;
|
|
41
|
+
return () => {
|
|
42
|
+
mountedRef.current = false;
|
|
43
|
+
};
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
// Always check cache first and restore if needed (synchronous)
|
|
48
|
+
if (url && blobUrlCache.has(url) && !blobUrl) {
|
|
49
|
+
const cachedBlobUrl = blobUrlCache.get(url)!;
|
|
50
|
+
setBlobUrl(cachedBlobUrl);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// If URL hasn't changed, keep using cached blob URL
|
|
34
54
|
if (url === previousUrlRef.current) {
|
|
35
55
|
return;
|
|
36
56
|
}
|
|
37
57
|
|
|
38
|
-
// Store previous URL and blob URL for later cleanup
|
|
39
58
|
const oldUrl = previousUrlRef.current;
|
|
40
|
-
const oldBlobUrl = blobUrlRef.current;
|
|
41
59
|
previousUrlRef.current = url;
|
|
42
60
|
|
|
43
61
|
if (!url) {
|
|
44
|
-
// If URL is null, clean up old blob URL and clear state
|
|
45
|
-
if (oldBlobUrl) {
|
|
46
|
-
URL.revokeObjectURL(oldBlobUrl);
|
|
47
|
-
blobUrlRef.current = null;
|
|
48
|
-
blobUrlForUrlRef.current = null;
|
|
49
|
-
}
|
|
50
62
|
setBlobUrl(null);
|
|
51
63
|
return;
|
|
52
64
|
}
|
|
@@ -56,18 +68,32 @@ export const useProtectedImage = ({
|
|
|
56
68
|
? isProtectedRef.current
|
|
57
69
|
: isProtectedRef.current ? isProtectedRef.current(url) : false;
|
|
58
70
|
|
|
59
|
-
// If not protected,
|
|
71
|
+
// If not protected, use original URL
|
|
60
72
|
if (!shouldUseAuth || !getAuthHeadersRef.current) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
// Clear any cached blob URL for this URL
|
|
74
|
+
if (blobUrlCache.has(url)) {
|
|
75
|
+
const cachedBlobUrl = blobUrlCache.get(url)!;
|
|
76
|
+
URL.revokeObjectURL(cachedBlobUrl);
|
|
77
|
+
blobUrlCache.delete(url);
|
|
65
78
|
}
|
|
66
79
|
setBlobUrl(null);
|
|
67
80
|
return;
|
|
68
81
|
}
|
|
69
82
|
|
|
83
|
+
// Check cache first - if we have a cached blob URL, use it immediately
|
|
84
|
+
if (blobUrlCache.has(url)) {
|
|
85
|
+
const cachedBlobUrl = blobUrlCache.get(url)!;
|
|
86
|
+
setBlobUrl(cachedBlobUrl);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// If already fetching this URL, don't fetch again
|
|
91
|
+
if (fetchingUrls.has(url)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
70
95
|
// Fetch protected image with auth headers
|
|
96
|
+
fetchingUrls.add(url);
|
|
71
97
|
const fetchProtectedImage = async () => {
|
|
72
98
|
try {
|
|
73
99
|
const headers = await Promise.resolve(getAuthHeadersRef.current!());
|
|
@@ -85,40 +111,31 @@ export const useProtectedImage = ({
|
|
|
85
111
|
const blob = await response.blob();
|
|
86
112
|
const newBlobUrl = URL.createObjectURL(blob);
|
|
87
113
|
|
|
88
|
-
// Only update if URL hasn't changed during fetch
|
|
89
|
-
if (previousUrlRef.current === url) {
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
URL.revokeObjectURL(oldBlobUrl);
|
|
93
|
-
}
|
|
94
|
-
blobUrlRef.current = newBlobUrl;
|
|
95
|
-
blobUrlForUrlRef.current = url;
|
|
114
|
+
// Only update if URL hasn't changed during fetch and component is still mounted
|
|
115
|
+
if (previousUrlRef.current === url && mountedRef.current) {
|
|
116
|
+
// Cache the blob URL
|
|
117
|
+
blobUrlCache.set(url, newBlobUrl);
|
|
96
118
|
setBlobUrl(newBlobUrl);
|
|
97
119
|
} else {
|
|
98
|
-
// URL changed during fetch, revoke this blob URL
|
|
120
|
+
// URL changed during fetch or component unmounted, revoke this blob URL
|
|
99
121
|
URL.revokeObjectURL(newBlobUrl);
|
|
100
122
|
}
|
|
101
123
|
} catch (error) {
|
|
102
124
|
console.error('Error fetching protected image:', error);
|
|
103
|
-
// On error, only clear if URL hasn't changed
|
|
104
|
-
if (previousUrlRef.current === url) {
|
|
125
|
+
// On error, only clear if URL hasn't changed and component is mounted
|
|
126
|
+
if (previousUrlRef.current === url && mountedRef.current) {
|
|
105
127
|
setBlobUrl(null);
|
|
106
128
|
}
|
|
129
|
+
} finally {
|
|
130
|
+
fetchingUrls.delete(url);
|
|
107
131
|
}
|
|
108
132
|
};
|
|
109
133
|
|
|
110
134
|
fetchProtectedImage();
|
|
135
|
+
}, [url]); // Only depend on url
|
|
111
136
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// On unmount, clean up the current blob URL
|
|
115
|
-
if (blobUrlRef.current) {
|
|
116
|
-
URL.revokeObjectURL(blobUrlRef.current);
|
|
117
|
-
blobUrlRef.current = null;
|
|
118
|
-
blobUrlForUrlRef.current = null;
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
}, [url]); // Only depend on url, not isProtected or getAuthHeaders
|
|
137
|
+
// Cleanup: Don't revoke blob URLs on unmount - keep them in cache for reuse
|
|
138
|
+
// They will be cleaned up when the URL changes or when the cache is cleared
|
|
122
139
|
|
|
123
140
|
// Return blob URL if available, otherwise return original URL (or null)
|
|
124
141
|
if (!url) {
|
|
@@ -128,6 +145,13 @@ export const useProtectedImage = ({
|
|
|
128
145
|
const isUrlProtected = typeof isProtectedRef.current === 'boolean'
|
|
129
146
|
? isProtectedRef.current
|
|
130
147
|
: isProtectedRef.current ? isProtectedRef.current(url) : false;
|
|
131
|
-
|
|
148
|
+
|
|
149
|
+
// For protected URLs, return cached blob URL or current blobUrl state
|
|
150
|
+
// For non-protected URLs, return the original URL
|
|
151
|
+
if (isUrlProtected) {
|
|
152
|
+
return blobUrl || blobUrlCache.get(url) || null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return url;
|
|
132
156
|
};
|
|
133
157
|
|