one-design-next 0.0.40 → 0.0.42
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/_genui-types.d.ts +8 -0
- package/dist/attachments/index.js +75 -68
- package/dist/attachments/style/index.css +21 -36
- package/dist/composer/index.js +2 -9
- package/dist/composer/style/index.css +9 -37
- package/dist/image/index.d.ts +17 -2
- package/dist/image/index.js +34 -3
- package/dist/image/style/index.css +24 -0
- package/dist/message-image/image-viewer.d.ts +24 -0
- package/dist/message-image/image-viewer.js +119 -0
- package/dist/message-image/index.d.ts +24 -4
- package/dist/message-image/index.js +90 -5
- package/dist/message-image/style/index.css +98 -0
- package/dist/user-bubble/index.d.ts +6 -7
- package/dist/user-bubble/index.js +7 -4
- package/package.json +1 -1
package/dist/_genui-types.d.ts
CHANGED
|
@@ -35,6 +35,14 @@ export interface Attachment {
|
|
|
35
35
|
status?: 'uploading' | 'error' | 'expired' | 'done';
|
|
36
36
|
/** status='error' 时展示的失败原因文案。 */
|
|
37
37
|
errorMessage?: string;
|
|
38
|
+
/**
|
|
39
|
+
* 图片自然像素宽 / 高(image/* 附件可选携带)。
|
|
40
|
+
* 主要用途:图片过期后 url 已失效、无法再测量,用存下来的原始尺寸
|
|
41
|
+
* 让「已过期」占位复刻原图 footprint(单图场景),消息流不因过期而跳版。
|
|
42
|
+
* 拿不到时占位退化成 88×88(同多图)。
|
|
43
|
+
*/
|
|
44
|
+
width?: number;
|
|
45
|
+
height?: number;
|
|
38
46
|
/**
|
|
39
47
|
* 特殊变体:
|
|
40
48
|
* - 'report':AI 生成报告的大卡片(含创建时间 + 右侧预览缩略图)。
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
2
2
|
import HoverFill from "../hover-fill";
|
|
3
3
|
import Icon from "../icon";
|
|
4
|
+
import Image from "../image";
|
|
5
|
+
import Tooltip from "../tooltip";
|
|
4
6
|
import "./style";
|
|
5
7
|
var categoryMap = {
|
|
6
|
-
|
|
7
|
-
icon: '
|
|
8
|
+
sheet: {
|
|
9
|
+
icon: 'table',
|
|
8
10
|
color: 'var(--odn-color-green-6)',
|
|
9
11
|
bg: 'var(--odn-color-green-2)'
|
|
10
12
|
},
|
|
13
|
+
slides: {
|
|
14
|
+
icon: 'presentation',
|
|
15
|
+
color: 'var(--odn-color-orange-6)',
|
|
16
|
+
bg: 'var(--odn-color-orange-2)'
|
|
17
|
+
},
|
|
11
18
|
code: {
|
|
12
19
|
icon: 'file-code',
|
|
13
20
|
color: 'var(--odn-color-blue-8)',
|
|
@@ -40,17 +47,17 @@ var defaultMeta = {
|
|
|
40
47
|
bg: 'var(--odn-color-solid-black-3)'
|
|
41
48
|
};
|
|
42
49
|
var mimeCategory = {
|
|
43
|
-
'application/pdf': '
|
|
50
|
+
'application/pdf': 'text',
|
|
44
51
|
'application/msword': 'doc',
|
|
45
52
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'doc',
|
|
46
|
-
'application/vnd.ms-excel': '
|
|
47
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '
|
|
48
|
-
'application/vnd.ms-powerpoint': '
|
|
49
|
-
'application/vnd.openxmlformats-officedocument.presentationml.presentation': '
|
|
53
|
+
'application/vnd.ms-excel': 'sheet',
|
|
54
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'sheet',
|
|
55
|
+
'application/vnd.ms-powerpoint': 'slides',
|
|
56
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'slides',
|
|
50
57
|
'application/json': 'data',
|
|
51
58
|
'application/xml': 'data',
|
|
52
59
|
'text/xml': 'data',
|
|
53
|
-
'text/csv': '
|
|
60
|
+
'text/csv': 'sheet',
|
|
54
61
|
'text/html': 'code',
|
|
55
62
|
'text/css': 'code',
|
|
56
63
|
'text/javascript': 'code',
|
|
@@ -71,7 +78,6 @@ var mimeCategory = {
|
|
|
71
78
|
function getFileMeta(mime) {
|
|
72
79
|
if (mimeCategory[mime]) return categoryMap[mimeCategory[mime]];
|
|
73
80
|
var prefix = mime.split('/')[0];
|
|
74
|
-
if (prefix === 'image') return categoryMap.image;
|
|
75
81
|
if (prefix === 'text') return categoryMap.text;
|
|
76
82
|
return defaultMeta;
|
|
77
83
|
}
|
|
@@ -123,18 +129,24 @@ function formatSize(bytes) {
|
|
|
123
129
|
if (bytes < 1024 * 1024) return "".concat((bytes / 1024).toFixed(1), " KB");
|
|
124
130
|
return "".concat((bytes / (1024 * 1024)).toFixed(1), " MB");
|
|
125
131
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
132
|
+
|
|
133
|
+
/** image/* 附件缩略:统一走 Image leaf,不再内联裸 `<img>`。 */
|
|
134
|
+
function AttachmentImage(_ref) {
|
|
135
|
+
var attachment = _ref.attachment;
|
|
136
|
+
if (!attachment.url) return null;
|
|
137
|
+
return /*#__PURE__*/React.createElement(Image, {
|
|
138
|
+
url: attachment.url,
|
|
139
|
+
alt: attachment.name
|
|
140
|
+
});
|
|
129
141
|
}
|
|
130
|
-
export function Attachments(
|
|
131
|
-
var attachments =
|
|
132
|
-
onRemove =
|
|
133
|
-
onFileClick =
|
|
134
|
-
onRetry =
|
|
135
|
-
onDownload =
|
|
136
|
-
|
|
137
|
-
compact =
|
|
142
|
+
export function Attachments(_ref2) {
|
|
143
|
+
var attachments = _ref2.attachments,
|
|
144
|
+
onRemove = _ref2.onRemove,
|
|
145
|
+
onFileClick = _ref2.onFileClick,
|
|
146
|
+
onRetry = _ref2.onRetry,
|
|
147
|
+
onDownload = _ref2.onDownload,
|
|
148
|
+
_ref2$compact = _ref2.compact,
|
|
149
|
+
compact = _ref2$compact === void 0 ? false : _ref2$compact;
|
|
138
150
|
if (attachments.length === 0) return null;
|
|
139
151
|
|
|
140
152
|
/**
|
|
@@ -153,8 +165,7 @@ export function Attachments(_ref) {
|
|
|
153
165
|
var isUploading = status === 'uploading';
|
|
154
166
|
var isError = status === 'error';
|
|
155
167
|
var isExpired = status === 'expired';
|
|
156
|
-
var clickable = !!onFileClick && !isUploading && !isExpired;
|
|
157
|
-
var retryable = !!onRetry && isError;
|
|
168
|
+
var clickable = !!onFileClick && !isUploading && !isExpired && !isError;
|
|
158
169
|
var thumbCardInner = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
|
|
159
170
|
"data-odn-attachments-thumb-card-thumb": true,
|
|
160
171
|
style: isImage && !isUploading && !isError ? undefined : isUploading || isError ? undefined : {
|
|
@@ -173,9 +184,8 @@ export function Attachments(_ref) {
|
|
|
173
184
|
style: {
|
|
174
185
|
color: 'var(--odn-color-error)'
|
|
175
186
|
}
|
|
176
|
-
}) : isImage ? /*#__PURE__*/React.createElement(
|
|
177
|
-
|
|
178
|
-
alt: ""
|
|
187
|
+
}) : isImage ? /*#__PURE__*/React.createElement(AttachmentImage, {
|
|
188
|
+
attachment: a
|
|
179
189
|
}) : /*#__PURE__*/React.createElement(Icon, {
|
|
180
190
|
name: meta.icon,
|
|
181
191
|
size: 16,
|
|
@@ -188,8 +198,8 @@ export function Attachments(_ref) {
|
|
|
188
198
|
"data-odn-attachments-thumb-card-name": true
|
|
189
199
|
}, a.name), /*#__PURE__*/React.createElement("div", {
|
|
190
200
|
"data-odn-attachments-thumb-card-type": true
|
|
191
|
-
},
|
|
192
|
-
|
|
201
|
+
}, isExpired ? '已过期' : formatType(a.type, a.name))));
|
|
202
|
+
var cardEl = /*#__PURE__*/React.createElement("div", {
|
|
193
203
|
key: i,
|
|
194
204
|
"data-odn-attachments-item": true,
|
|
195
205
|
"data-odn-attachments-state": status,
|
|
@@ -202,17 +212,10 @@ export function Attachments(_ref) {
|
|
|
202
212
|
"data-odn-attachments-thumb-card": true,
|
|
203
213
|
"data-odn-attachments-thumb-card-image": isImage || undefined,
|
|
204
214
|
"data-odn-attachments-clickable": ""
|
|
205
|
-
}, thumbCardInner) : retryable ? /*#__PURE__*/React.createElement("button", {
|
|
206
|
-
type: "button",
|
|
207
|
-
onClick: function onClick() {
|
|
208
|
-
return onRetry(i);
|
|
209
|
-
},
|
|
210
|
-
"data-odn-attachments-thumb-card": true,
|
|
211
|
-
"data-odn-attachments-thumb-card-image": isImage || undefined,
|
|
212
|
-
"data-odn-attachments-retryable": ""
|
|
213
215
|
}, thumbCardInner) : /*#__PURE__*/React.createElement("div", {
|
|
214
216
|
"data-odn-attachments-thumb-card": true,
|
|
215
|
-
"data-odn-attachments-thumb-card-image": isImage || undefined
|
|
217
|
+
"data-odn-attachments-thumb-card-image": isImage || undefined,
|
|
218
|
+
"data-odn-attachments-error": isError || undefined
|
|
216
219
|
}, thumbCardInner), onRemove && /*#__PURE__*/React.createElement("button", {
|
|
217
220
|
onClick: function onClick() {
|
|
218
221
|
return onRemove(i);
|
|
@@ -222,10 +225,18 @@ export function Attachments(_ref) {
|
|
|
222
225
|
name: "x",
|
|
223
226
|
size: 10
|
|
224
227
|
})));
|
|
228
|
+
if (isError) {
|
|
229
|
+
return /*#__PURE__*/React.createElement(Tooltip, {
|
|
230
|
+
key: i,
|
|
231
|
+
placement: "top",
|
|
232
|
+
popup: a.errorMessage || '上传失败'
|
|
233
|
+
}, cardEl);
|
|
234
|
+
}
|
|
235
|
+
return cardEl;
|
|
225
236
|
}
|
|
226
237
|
|
|
227
238
|
/**
|
|
228
|
-
* image-thumb
|
|
239
|
+
* image-thumb 渲染:纯图缩略方块(48×48 cover,与 file-card 等高)。
|
|
229
240
|
*
|
|
230
241
|
* 与文件信息卡(data-odn-attachments-card)严格同源——共享
|
|
231
242
|
* data-odn-attachments-state 三态机、remove 角标。composer 待发送区把 image/*
|
|
@@ -238,10 +249,9 @@ export function Attachments(_ref) {
|
|
|
238
249
|
var isError = status === 'error';
|
|
239
250
|
var isExpired = status === 'expired';
|
|
240
251
|
var clickable = !!onFileClick && !isUploading && !isError && !isExpired;
|
|
241
|
-
var thumb = /*#__PURE__*/React.createElement(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
});
|
|
252
|
+
var thumb = a.url ? /*#__PURE__*/React.createElement(AttachmentImage, {
|
|
253
|
+
attachment: a
|
|
254
|
+
}) : null;
|
|
245
255
|
return /*#__PURE__*/React.createElement("div", {
|
|
246
256
|
key: i,
|
|
247
257
|
"data-odn-attachments-item": true,
|
|
@@ -262,25 +272,21 @@ export function Attachments(_ref) {
|
|
|
262
272
|
name: "loading",
|
|
263
273
|
size: 16,
|
|
264
274
|
spin: true
|
|
265
|
-
})), isError &&
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}, /*#__PURE__*/React.createElement(
|
|
274
|
-
name: "alert-triangle",
|
|
275
|
-
size: 16
|
|
276
|
-
}), /*#__PURE__*/React.createElement("span", null, "\u91CD\u8BD5")) : /*#__PURE__*/React.createElement("div", {
|
|
275
|
+
})), isError &&
|
|
276
|
+
/*#__PURE__*/
|
|
277
|
+
// 图片缩略方块太小塞不下错误文案:错误态只留居中的告警图标,
|
|
278
|
+
// 具体原因(errorMessage,来自业务层回写)走 hover 向上的 Tooltip。
|
|
279
|
+
// 图片上传不支持重试——overlay 不承载点击。
|
|
280
|
+
React.createElement(Tooltip, {
|
|
281
|
+
placement: "top",
|
|
282
|
+
popup: a.errorMessage || '上传失败'
|
|
283
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
277
284
|
"data-odn-attachments-image-overlay": true,
|
|
278
|
-
"data-odn-attachments-image-overlay-error": ""
|
|
279
|
-
title: a.errorMessage || '上传失败'
|
|
285
|
+
"data-odn-attachments-image-overlay-error": ""
|
|
280
286
|
}, /*#__PURE__*/React.createElement(Icon, {
|
|
281
287
|
name: "alert-triangle",
|
|
282
288
|
size: 16
|
|
283
|
-
})
|
|
289
|
+
}))), onRemove && /*#__PURE__*/React.createElement("button", {
|
|
284
290
|
onClick: function onClick() {
|
|
285
291
|
return onRemove(i);
|
|
286
292
|
},
|
|
@@ -423,8 +429,7 @@ export function Attachments(_ref) {
|
|
|
423
429
|
var isUploading = status === 'uploading';
|
|
424
430
|
var isError = status === 'error';
|
|
425
431
|
var isExpired = status === 'expired';
|
|
426
|
-
var clickable = !!onFileClick && !isUploading && !isExpired;
|
|
427
|
-
var retryable = !!onRetry && isError;
|
|
432
|
+
var clickable = !!onFileClick && !isUploading && !isExpired && !isError;
|
|
428
433
|
var fileCardInner = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
|
|
429
434
|
"data-odn-attachments-file-icon": true,
|
|
430
435
|
style: isUploading || isError ? undefined : {
|
|
@@ -455,8 +460,8 @@ export function Attachments(_ref) {
|
|
|
455
460
|
"data-odn-attachments-name": true
|
|
456
461
|
}, a.name), /*#__PURE__*/React.createElement("div", {
|
|
457
462
|
"data-odn-attachments-meta": true
|
|
458
|
-
},
|
|
459
|
-
|
|
463
|
+
}, isExpired ? '已过期' : "".concat(formatType(a.type, a.name)).concat(a.size ? " \xB7 ".concat(formatSize(a.size)) : ''))));
|
|
464
|
+
var cardEl = /*#__PURE__*/React.createElement("div", _extends({
|
|
460
465
|
key: i,
|
|
461
466
|
"data-odn-attachments-item": true,
|
|
462
467
|
"data-odn-attachments-state": status
|
|
@@ -469,15 +474,9 @@ export function Attachments(_ref) {
|
|
|
469
474
|
},
|
|
470
475
|
"data-odn-attachments-card": true,
|
|
471
476
|
"data-odn-attachments-clickable": ""
|
|
472
|
-
}, fileCardInner) : retryable ? /*#__PURE__*/React.createElement("button", {
|
|
473
|
-
type: "button",
|
|
474
|
-
onClick: function onClick() {
|
|
475
|
-
return onRetry(i);
|
|
476
|
-
},
|
|
477
|
-
"data-odn-attachments-card": true,
|
|
478
|
-
"data-odn-attachments-retryable": ""
|
|
479
477
|
}, fileCardInner) : /*#__PURE__*/React.createElement("div", {
|
|
480
|
-
"data-odn-attachments-card": true
|
|
478
|
+
"data-odn-attachments-card": true,
|
|
479
|
+
"data-odn-attachments-error": isError || undefined
|
|
481
480
|
}, fileCardInner), onRemove && /*#__PURE__*/React.createElement("button", {
|
|
482
481
|
onClick: function onClick() {
|
|
483
482
|
return onRemove(i);
|
|
@@ -487,6 +486,14 @@ export function Attachments(_ref) {
|
|
|
487
486
|
name: "x",
|
|
488
487
|
size: 10
|
|
489
488
|
})));
|
|
489
|
+
if (isError) {
|
|
490
|
+
return /*#__PURE__*/React.createElement(Tooltip, {
|
|
491
|
+
key: i,
|
|
492
|
+
placement: "top",
|
|
493
|
+
popup: a.errorMessage || '上传失败'
|
|
494
|
+
}, cardEl);
|
|
495
|
+
}
|
|
496
|
+
return cardEl;
|
|
490
497
|
}));
|
|
491
498
|
}
|
|
492
499
|
Attachments.displayName = 'Attachments';
|
|
@@ -83,15 +83,8 @@
|
|
|
83
83
|
[data-odn-attachments-card][data-odn-attachments-clickable]:hover {
|
|
84
84
|
border-color: color-mix(in srgb, var(--odn-color-primary) 50%, transparent);
|
|
85
85
|
}
|
|
86
|
-
[data-odn-attachments-card][data-odn-attachments-
|
|
87
|
-
|
|
88
|
-
border: 1px solid color-mix(in srgb, var(--odn-color-error) 35%, var(--odn-color-black-6));
|
|
89
|
-
font: inherit;
|
|
90
|
-
text-align: left;
|
|
91
|
-
appearance: none;
|
|
92
|
-
}
|
|
93
|
-
[data-odn-attachments-card][data-odn-attachments-retryable]:hover {
|
|
94
|
-
border-color: color-mix(in srgb, var(--odn-color-error) 55%, transparent);
|
|
86
|
+
[data-odn-attachments-card][data-odn-attachments-error] {
|
|
87
|
+
border-color: color-mix(in srgb, var(--odn-color-error) 35%, var(--odn-color-black-6));
|
|
95
88
|
}
|
|
96
89
|
|
|
97
90
|
[data-odn-attachments-file-icon] {
|
|
@@ -133,10 +126,6 @@
|
|
|
133
126
|
color: var(--odn-color-black-8);
|
|
134
127
|
}
|
|
135
128
|
|
|
136
|
-
[data-odn-attachments-item][data-odn-attachments-state=error] [data-odn-attachments-meta] {
|
|
137
|
-
color: var(--odn-color-error);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
129
|
[data-odn-attachments-item][data-odn-attachments-state=expired] {
|
|
141
130
|
opacity: 0.55;
|
|
142
131
|
}
|
|
@@ -190,7 +179,7 @@
|
|
|
190
179
|
grid-template-columns: auto 1fr;
|
|
191
180
|
gap: 8px;
|
|
192
181
|
padding: 6px 10px 6px 6px;
|
|
193
|
-
min-height:
|
|
182
|
+
min-height: 48px;
|
|
194
183
|
border-radius: 6px;
|
|
195
184
|
border: 1px solid var(--odn-color-black-6);
|
|
196
185
|
background: var(--odn-color-solid-black-1);
|
|
@@ -207,12 +196,8 @@
|
|
|
207
196
|
[data-odn-attachments-thumb-card][data-odn-attachments-clickable]:hover {
|
|
208
197
|
border-color: color-mix(in srgb, var(--odn-color-primary) 50%, transparent);
|
|
209
198
|
}
|
|
210
|
-
[data-odn-attachments-thumb-card][data-odn-attachments-
|
|
211
|
-
|
|
212
|
-
border: 1px solid color-mix(in srgb, var(--odn-color-error) 35%, var(--odn-color-black-6));
|
|
213
|
-
}
|
|
214
|
-
[data-odn-attachments-thumb-card][data-odn-attachments-retryable]:hover {
|
|
215
|
-
border-color: color-mix(in srgb, var(--odn-color-error) 55%, transparent);
|
|
199
|
+
[data-odn-attachments-thumb-card][data-odn-attachments-error] {
|
|
200
|
+
border-color: color-mix(in srgb, var(--odn-color-error) 35%, var(--odn-color-black-6));
|
|
216
201
|
}
|
|
217
202
|
|
|
218
203
|
/* 图片保持原先「无边框」视觉,仅与文件卡片对齐高度。 */
|
|
@@ -220,7 +205,7 @@
|
|
|
220
205
|
border: none;
|
|
221
206
|
background: transparent;
|
|
222
207
|
}
|
|
223
|
-
[data-odn-attachments-thumb-card][data-odn-attachments-thumb-card-image][data-odn-attachments-clickable]:hover
|
|
208
|
+
[data-odn-attachments-thumb-card][data-odn-attachments-thumb-card-image][data-odn-attachments-clickable]:hover {
|
|
224
209
|
border: none;
|
|
225
210
|
}
|
|
226
211
|
|
|
@@ -234,6 +219,11 @@
|
|
|
234
219
|
border-radius: 3px;
|
|
235
220
|
overflow: hidden;
|
|
236
221
|
}
|
|
222
|
+
[data-odn-attachments-thumb-card-thumb] [data-odn-image] {
|
|
223
|
+
display: block;
|
|
224
|
+
width: 100%;
|
|
225
|
+
height: 100%;
|
|
226
|
+
}
|
|
237
227
|
[data-odn-attachments-thumb-card-thumb] img {
|
|
238
228
|
width: 100%;
|
|
239
229
|
height: 100%;
|
|
@@ -275,12 +265,12 @@
|
|
|
275
265
|
[data-odn-attachments-card]:not([data-odn-attachments-image-card]),
|
|
276
266
|
[data-odn-attachments-thumb-card] {
|
|
277
267
|
width: 192px;
|
|
278
|
-
min-height:
|
|
268
|
+
min-height: 48px;
|
|
279
269
|
}
|
|
280
270
|
|
|
281
271
|
/* ==========================================================================
|
|
282
|
-
* image-thumb
|
|
283
|
-
*
|
|
272
|
+
* image-thumb 变体(纯图缩略方块)
|
|
273
|
+
* 48×48 cover(与 file-card 等高),承载 image/* + url。缩略走 Image leaf(不再裸 `<img>`)。
|
|
284
274
|
* 同源:共享 data-odn-attachments-state 三态机、remove 角标、fresh-done。
|
|
285
275
|
* composer 待发送区即以此变体让"图片大预览 + 文件信息卡"并列同行、等高同源。
|
|
286
276
|
* ========================================================================== */
|
|
@@ -290,8 +280,8 @@
|
|
|
290
280
|
|
|
291
281
|
[data-odn-attachments-image-thumb] {
|
|
292
282
|
position: relative;
|
|
293
|
-
width:
|
|
294
|
-
height:
|
|
283
|
+
width: 48px;
|
|
284
|
+
height: 48px;
|
|
295
285
|
border-radius: 6px;
|
|
296
286
|
overflow: hidden;
|
|
297
287
|
padding: 0;
|
|
@@ -299,6 +289,11 @@
|
|
|
299
289
|
background: var(--odn-color-black-2);
|
|
300
290
|
box-sizing: border-box;
|
|
301
291
|
}
|
|
292
|
+
[data-odn-attachments-image-thumb] [data-odn-image] {
|
|
293
|
+
display: block;
|
|
294
|
+
width: 100%;
|
|
295
|
+
height: 100%;
|
|
296
|
+
}
|
|
302
297
|
[data-odn-attachments-image-thumb] img {
|
|
303
298
|
width: 100%;
|
|
304
299
|
height: 100%;
|
|
@@ -342,20 +337,10 @@
|
|
|
342
337
|
}
|
|
343
338
|
[data-odn-attachments-image-overlay][data-odn-attachments-image-overlay-error] {
|
|
344
339
|
background: var(--odn-color-solid-black-1);
|
|
345
|
-
flex-direction: column;
|
|
346
340
|
border: 1px solid color-mix(in srgb, var(--odn-color-error) 35%, var(--odn-color-black-6));
|
|
347
341
|
color: var(--odn-color-error);
|
|
348
|
-
}
|
|
349
|
-
[data-odn-attachments-image-overlay][data-odn-attachments-image-overlay-error]:is(button) {
|
|
350
|
-
cursor: pointer;
|
|
351
|
-
appearance: none;
|
|
352
|
-
font: inherit;
|
|
353
|
-
font-size: 10px;
|
|
354
342
|
pointer-events: auto;
|
|
355
343
|
}
|
|
356
|
-
[data-odn-attachments-image-overlay][data-odn-attachments-image-overlay-error]:is(button):hover {
|
|
357
|
-
border-color: color-mix(in srgb, var(--odn-color-error) 55%, transparent);
|
|
358
|
-
}
|
|
359
344
|
|
|
360
345
|
/* ==========================================================================
|
|
361
346
|
* Report 变体(AI 生成报告入口卡片)
|
package/dist/composer/index.js
CHANGED
|
@@ -490,7 +490,6 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
490
490
|
var composerBoxFileDragAttrs = fileDragOver ? {
|
|
491
491
|
'data-odn-composer-file-drag': ''
|
|
492
492
|
} : {};
|
|
493
|
-
var showFileDragOverlay = fileDragOver && !disabled && !attachmentsDisabled;
|
|
494
493
|
var handleComposerDragEnter = function handleComposerDragEnter(e) {
|
|
495
494
|
e.preventDefault();
|
|
496
495
|
if (disabled || attachmentsDisabled) return;
|
|
@@ -889,7 +888,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
889
888
|
ref: editorRef,
|
|
890
889
|
autoSize: {
|
|
891
890
|
minRows: 1,
|
|
892
|
-
maxRows:
|
|
891
|
+
maxRows: 10
|
|
893
892
|
},
|
|
894
893
|
placeholder: placeholder,
|
|
895
894
|
value: text,
|
|
@@ -997,13 +996,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
997
996
|
"data-odn-composer-lite-tools": true
|
|
998
997
|
}, toolNodes), isLite && /*#__PURE__*/React.createElement("div", {
|
|
999
998
|
"data-odn-composer-lite-send": true
|
|
1000
|
-
}, sendOrStopButton),
|
|
1001
|
-
"data-odn-composer-file-drag-overlay": true,
|
|
1002
|
-
"aria-hidden": true
|
|
1003
|
-
}, /*#__PURE__*/React.createElement("span", null, /*#__PURE__*/React.createElement(Icon, {
|
|
1004
|
-
name: "paperclip",
|
|
1005
|
-
size: 14
|
|
1006
|
-
}), "\u62D6\u653E\u6587\u4EF6\u5230\u6B64\u5904\u4E0A\u4F20"))), hint && /*#__PURE__*/React.createElement("p", {
|
|
999
|
+
}, sendOrStopButton)), hint && /*#__PURE__*/React.createElement("p", {
|
|
1007
1000
|
"data-odn-composer-hint": true
|
|
1008
1001
|
}, submitType === 'enter' ? 'Enter 发送 · Shift+Enter 换行 · 可拖入或粘贴文件' : 'Shift+Enter 发送 · Enter 换行 · 可拖入或粘贴文件')));
|
|
1009
1002
|
});
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
border: 1px solid var(--odn-color-black-6);
|
|
27
27
|
background: var(--odn-color-bg-elevated);
|
|
28
28
|
transition: border-color 0.15s;
|
|
29
|
+
max-height: 50vh;
|
|
30
|
+
min-height: 120px;
|
|
29
31
|
}
|
|
30
32
|
[data-odn-composer-box]:focus-within {
|
|
31
33
|
border-color: color-mix(in srgb, var(--odn-color-brand-6) 50%, transparent);
|
|
@@ -35,42 +37,6 @@
|
|
|
35
37
|
background: color-mix(in srgb, var(--odn-color-brand-6) 6%, var(--odn-color-bg-elevated));
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
[data-odn-composer-file-drag-overlay] {
|
|
39
|
-
position: absolute;
|
|
40
|
-
inset: 0;
|
|
41
|
-
border-radius: inherit;
|
|
42
|
-
background: color-mix(in srgb, var(--odn-color-brand-6) 8%, transparent);
|
|
43
|
-
backdrop-filter: blur(4px);
|
|
44
|
-
-webkit-backdrop-filter: blur(4px);
|
|
45
|
-
display: flex;
|
|
46
|
-
align-items: center;
|
|
47
|
-
justify-content: center;
|
|
48
|
-
pointer-events: none;
|
|
49
|
-
z-index: 4;
|
|
50
|
-
}
|
|
51
|
-
[data-odn-composer-file-drag-overlay] span {
|
|
52
|
-
display: inline-flex;
|
|
53
|
-
align-items: center;
|
|
54
|
-
gap: 6px;
|
|
55
|
-
color: var(--odn-color-brand-6);
|
|
56
|
-
font-size: 14px;
|
|
57
|
-
line-height: 24px;
|
|
58
|
-
font-weight: 600;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/* 拖放蒙层出现时,冻结内部交互样式事件;保留 box 自身 drop 能力。 */
|
|
62
|
-
[data-odn-composer-box][data-odn-composer-file-drag] [data-odn-composer-editor],
|
|
63
|
-
[data-odn-composer-box][data-odn-composer-file-drag] [data-odn-composer-toolbar],
|
|
64
|
-
[data-odn-composer-box][data-odn-composer-file-drag] [data-odn-composer-lite-tools],
|
|
65
|
-
[data-odn-composer-box][data-odn-composer-file-drag] [data-odn-composer-lite-send],
|
|
66
|
-
[data-odn-composer-box][data-odn-composer-file-drag] [data-odn-composer-attachments] {
|
|
67
|
-
pointer-events: none !important;
|
|
68
|
-
}
|
|
69
|
-
[data-odn-composer-box][data-odn-composer-file-drag] [data-odn-composer-editor] {
|
|
70
|
-
caret-color: transparent;
|
|
71
|
-
user-select: none;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
40
|
/* ------------------------------------------------------------------
|
|
75
41
|
* lite variant:
|
|
76
42
|
* tools 和 send 按钮 absolute 在 box 的左下/右下;
|
|
@@ -211,6 +177,12 @@
|
|
|
211
177
|
* ScrollArea root 的 height transition 按"目标态"分别定义在 lite 单行 /
|
|
212
178
|
* lite 升格 selector 上,做出「先上再左 / 先右再下」的对称两段动效。
|
|
213
179
|
* ----------------------------------------------------------------- */
|
|
180
|
+
[data-odn-composer] .odn-composer-editor-scroll {
|
|
181
|
+
flex: 1 1 auto;
|
|
182
|
+
min-height: 0;
|
|
183
|
+
overflow: hidden;
|
|
184
|
+
}
|
|
185
|
+
|
|
214
186
|
[data-odn-composer] [data-odn-composer-editor] {
|
|
215
187
|
position: relative;
|
|
216
188
|
width: 100%;
|
|
@@ -471,7 +443,7 @@ p[data-odn-composer-hint] {
|
|
|
471
443
|
* 三态机 / remove 角标 / fresh-done 全部下沉到 Attachments 组件。
|
|
472
444
|
* ----------------------------------------------------------------- */
|
|
473
445
|
[data-odn-composer-attachments] {
|
|
474
|
-
--odn-composer-attachment-h:
|
|
446
|
+
--odn-composer-attachment-h: 48px;
|
|
475
447
|
display: flex;
|
|
476
448
|
flex-wrap: wrap;
|
|
477
449
|
align-items: flex-start;
|
package/dist/image/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface ImageProps {
|
|
|
8
8
|
alt?: string;
|
|
9
9
|
/**
|
|
10
10
|
* 整图点击回调。传此 prop 即把图片包一层 button,业务方按需对接
|
|
11
|
-
* `PreviewPanel` 等 lightbox
|
|
11
|
+
* `PreviewPanel` 等 lightbox 组件。失败态(unavailable / 加载出错)下自动屏蔽。
|
|
12
12
|
*/
|
|
13
13
|
onPreview?: () => void;
|
|
14
14
|
/**
|
|
@@ -16,6 +16,20 @@ export interface ImageProps {
|
|
|
16
16
|
* trash icon,用于 Composer 待发送态等需要删除的场景。
|
|
17
17
|
*/
|
|
18
18
|
onRemove?: () => void;
|
|
19
|
+
/**
|
|
20
|
+
* 主动标记图片不可用(如上层已知 url 失效)。为 true 时**不加载 url**、
|
|
21
|
+
* 直接渲染 `fallback`,并屏蔽 onPreview。
|
|
22
|
+
*
|
|
23
|
+
* 这是渲染层的中性概念——leaf 不认识「过期 / 已删除」等业务原因,只知道
|
|
24
|
+
* 「这张图画不出来」。业务原因与文案由上层(如 `MessageImage`)翻译后
|
|
25
|
+
* 通过 `unavailable` + `fallback` 传入。
|
|
26
|
+
*/
|
|
27
|
+
unavailable?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* 画不出图时(`unavailable` 或 `<img>` 运行时 onError)渲染的兜底内容。
|
|
30
|
+
* 不传则用内置中性占位(image-off 图标)。
|
|
31
|
+
*/
|
|
32
|
+
fallback?: React.ReactNode;
|
|
19
33
|
/** 透传到容器(非 img 本体)的 className,用于上层套尺寸约束。 */
|
|
20
34
|
className?: string;
|
|
21
35
|
/** 透传到容器(非 img 本体)的 style,用于上层套尺寸约束。 */
|
|
@@ -32,12 +46,13 @@ export interface ImageProps {
|
|
|
32
46
|
* 不职责:
|
|
33
47
|
* - 不带尺寸 opinion(max-width / max-height / object-fit 全由上层套)
|
|
34
48
|
* - 不带消息态 / 输入态等业务语义
|
|
49
|
+
* - 不认识「过期 / 已删除」等业务原因,只认「画得出 / 画不出」(unavailable / fallback)
|
|
35
50
|
*
|
|
36
51
|
* 业务态包装在更上层的组件里实现:
|
|
37
52
|
* - 消息态:`<MessageImage>` 套一层 max 280×360 contain(单图)或 88×88 cover(多图)容器
|
|
38
53
|
* - Composer 待发送态:composer 内部直接套一层 72×72 cover 容器
|
|
39
54
|
*/
|
|
40
|
-
export declare function Image({ url, alt, onPreview, onRemove, className, style }: ImageProps): import("react").JSX.Element;
|
|
55
|
+
export declare function Image({ url, alt, onPreview, onRemove, unavailable, fallback, className, style, }: ImageProps): import("react").JSX.Element;
|
|
41
56
|
export declare namespace Image {
|
|
42
57
|
var displayName: string;
|
|
43
58
|
}
|
package/dist/image/index.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
|
2
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
3
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
4
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
5
|
+
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
6
|
+
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
7
|
+
import { useState } from 'react';
|
|
1
8
|
import Icon from "../icon";
|
|
2
9
|
import "./style";
|
|
3
10
|
/**
|
|
@@ -11,6 +18,7 @@ import "./style";
|
|
|
11
18
|
* 不职责:
|
|
12
19
|
* - 不带尺寸 opinion(max-width / max-height / object-fit 全由上层套)
|
|
13
20
|
* - 不带消息态 / 输入态等业务语义
|
|
21
|
+
* - 不认识「过期 / 已删除」等业务原因,只认「画得出 / 画不出」(unavailable / fallback)
|
|
14
22
|
*
|
|
15
23
|
* 业务态包装在更上层的组件里实现:
|
|
16
24
|
* - 消息态:`<MessageImage>` 套一层 max 280×360 contain(单图)或 88×88 cover(多图)容器
|
|
@@ -21,18 +29,41 @@ export function Image(_ref) {
|
|
|
21
29
|
alt = _ref.alt,
|
|
22
30
|
onPreview = _ref.onPreview,
|
|
23
31
|
onRemove = _ref.onRemove,
|
|
32
|
+
unavailable = _ref.unavailable,
|
|
33
|
+
fallback = _ref.fallback,
|
|
24
34
|
className = _ref.className,
|
|
25
35
|
style = _ref.style;
|
|
26
|
-
|
|
36
|
+
// 运行时加载失败:url 存活性不由上层保证(签名过期 / 404 / 断网都可能),
|
|
37
|
+
// onError 兜住让「画不出」自动降级,不出碎图。
|
|
38
|
+
var _useState = useState(false),
|
|
39
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
40
|
+
loadError = _useState2[0],
|
|
41
|
+
setLoadError = _useState2[1];
|
|
42
|
+
var showFallback = !!unavailable || loadError;
|
|
43
|
+
|
|
44
|
+
// 默认占位只有图标、无文案——leaf 不知道「为什么画不出」,任何原因文字
|
|
45
|
+
// 都是业务语义,由上层通过 fallback 传入(如 MessageImage 传「已过期」)。
|
|
46
|
+
var fallbackEl = /*#__PURE__*/React.createElement("div", {
|
|
47
|
+
"data-odn-image-fallback": true
|
|
48
|
+
}, fallback !== null && fallback !== void 0 ? fallback : /*#__PURE__*/React.createElement(Icon, {
|
|
49
|
+
name: "image-off",
|
|
50
|
+
size: 20
|
|
51
|
+
}));
|
|
52
|
+
var imgEl = showFallback ? fallbackEl : /*#__PURE__*/React.createElement("img", {
|
|
27
53
|
src: url,
|
|
28
54
|
alt: alt !== null && alt !== void 0 ? alt : '',
|
|
29
|
-
loading: "lazy"
|
|
55
|
+
loading: "lazy",
|
|
56
|
+
onError: function onError() {
|
|
57
|
+
return setLoadError(true);
|
|
58
|
+
}
|
|
30
59
|
});
|
|
60
|
+
var canPreview = !!onPreview && !showFallback;
|
|
31
61
|
return /*#__PURE__*/React.createElement("div", {
|
|
32
62
|
"data-odn-image": true,
|
|
63
|
+
"data-odn-image-unavailable": showFallback || undefined,
|
|
33
64
|
className: className,
|
|
34
65
|
style: style
|
|
35
|
-
},
|
|
66
|
+
}, canPreview ? /*#__PURE__*/React.createElement("button", {
|
|
36
67
|
type: "button",
|
|
37
68
|
onClick: onPreview,
|
|
38
69
|
"data-odn-image-trigger": true,
|
|
@@ -7,6 +7,30 @@
|
|
|
7
7
|
display: block;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/* 不可用态(unavailable 或运行时加载失败):渲染中性占位。
|
|
11
|
+
* 弱化感由占位块自身的纯色灰底 + 纯色图标承担,不用容器 opacity——
|
|
12
|
+
* 整体降透明会让图标 / 文字变成半透明合成,视觉上「层叠」发虚。 */
|
|
13
|
+
/* 占位块:和 <img> 一样填满上层套的尺寸盒(width/height 100%),leaf 不带
|
|
14
|
+
* 尺寸 opinion;上层没套尺寸时退化成按内容(图标 / 文字)撑开——不硬编码 min。
|
|
15
|
+
* 纯色 solid-black:底 solid-black-2、图标 / 文字 solid-black-8,
|
|
16
|
+
* 全程不透明,避免 alpha token 叠底产生的透视感。 */
|
|
17
|
+
[data-odn-image-fallback] {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
gap: 8px;
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: 100%;
|
|
25
|
+
padding: 8px;
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
background: var(--odn-color-solid-black-2);
|
|
28
|
+
color: var(--odn-color-solid-black-8);
|
|
29
|
+
font-size: 12px;
|
|
30
|
+
line-height: 1.25;
|
|
31
|
+
text-align: center;
|
|
32
|
+
}
|
|
33
|
+
|
|
10
34
|
/** 整图作为 onPreview 触发区时的 button 包装;样式上完全融入容器(无边框、无 padding)。 */
|
|
11
35
|
[data-odn-image-trigger] {
|
|
12
36
|
display: block;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
/**
|
|
3
|
+
* 大图查看器的最小数据形态。刻意不复用 `MessageImageItem`——查看器只认
|
|
4
|
+
* url / alt,与「消息态」无关,保持解耦(MessageImageItem 结构上可直接传入)。
|
|
5
|
+
*/
|
|
6
|
+
export interface ImageViewerItem {
|
|
7
|
+
url: string;
|
|
8
|
+
alt?: string;
|
|
9
|
+
}
|
|
10
|
+
interface ImageViewerProps {
|
|
11
|
+
images: ImageViewerItem[];
|
|
12
|
+
initialIndex: number;
|
|
13
|
+
onClose: () => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* MessageImage 的**私有内置**大图查看器——不写进 `components/index.ts`,
|
|
17
|
+
* 外部 import 不到。多个图片组件(未来含 Attachments)可共用这一份实现。
|
|
18
|
+
*
|
|
19
|
+
* 交互:ESC / 点遮罩 关闭;←/→ 切换;点图本身不关。
|
|
20
|
+
* 用 portal 挂到 body,规避祖先 `transform` / `overflow` 对 `position: fixed`
|
|
21
|
+
* 的截断(聊天流容器常带这些)。mounted guard 兜 SSR。
|
|
22
|
+
*/
|
|
23
|
+
export declare function ImageViewer({ images, initialIndex, onClose }: ImageViewerProps): import("react").ReactPortal | null;
|
|
24
|
+
export default ImageViewer;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
|
4
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
5
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
6
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
7
|
+
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
8
|
+
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
9
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
10
|
+
import { createPortal } from 'react-dom';
|
|
11
|
+
import Icon from "../icon";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 大图查看器的最小数据形态。刻意不复用 `MessageImageItem`——查看器只认
|
|
15
|
+
* url / alt,与「消息态」无关,保持解耦(MessageImageItem 结构上可直接传入)。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* MessageImage 的**私有内置**大图查看器——不写进 `components/index.ts`,
|
|
20
|
+
* 外部 import 不到。多个图片组件(未来含 Attachments)可共用这一份实现。
|
|
21
|
+
*
|
|
22
|
+
* 交互:ESC / 点遮罩 关闭;←/→ 切换;点图本身不关。
|
|
23
|
+
* 用 portal 挂到 body,规避祖先 `transform` / `overflow` 对 `position: fixed`
|
|
24
|
+
* 的截断(聊天流容器常带这些)。mounted guard 兜 SSR。
|
|
25
|
+
*/
|
|
26
|
+
export function ImageViewer(_ref) {
|
|
27
|
+
var _it$alt;
|
|
28
|
+
var images = _ref.images,
|
|
29
|
+
initialIndex = _ref.initialIndex,
|
|
30
|
+
onClose = _ref.onClose;
|
|
31
|
+
var _useState = useState(initialIndex),
|
|
32
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
33
|
+
index = _useState2[0],
|
|
34
|
+
setIndex = _useState2[1];
|
|
35
|
+
var _useState3 = useState(false),
|
|
36
|
+
_useState4 = _slicedToArray(_useState3, 2),
|
|
37
|
+
mounted = _useState4[0],
|
|
38
|
+
setMounted = _useState4[1];
|
|
39
|
+
var total = images.length;
|
|
40
|
+
var prev = useCallback(function () {
|
|
41
|
+
return setIndex(function (i) {
|
|
42
|
+
return (i - 1 + total) % total;
|
|
43
|
+
});
|
|
44
|
+
}, [total]);
|
|
45
|
+
var next = useCallback(function () {
|
|
46
|
+
return setIndex(function (i) {
|
|
47
|
+
return (i + 1) % total;
|
|
48
|
+
});
|
|
49
|
+
}, [total]);
|
|
50
|
+
useEffect(function () {
|
|
51
|
+
return setMounted(true);
|
|
52
|
+
}, []);
|
|
53
|
+
useEffect(function () {
|
|
54
|
+
var onKey = function onKey(e) {
|
|
55
|
+
if (e.key === 'Escape') onClose();
|
|
56
|
+
if (e.key === 'ArrowLeft') prev();
|
|
57
|
+
if (e.key === 'ArrowRight') next();
|
|
58
|
+
};
|
|
59
|
+
document.addEventListener('keydown', onKey);
|
|
60
|
+
return function () {
|
|
61
|
+
return document.removeEventListener('keydown', onKey);
|
|
62
|
+
};
|
|
63
|
+
}, [onClose, prev, next]);
|
|
64
|
+
var it = images[index];
|
|
65
|
+
if (!mounted || !it) return null;
|
|
66
|
+
var overlay = /*#__PURE__*/React.createElement("div", {
|
|
67
|
+
"data-odn-image-viewer": true,
|
|
68
|
+
role: "dialog",
|
|
69
|
+
"aria-modal": "true",
|
|
70
|
+
onClick: onClose
|
|
71
|
+
}, /*#__PURE__*/React.createElement("img", {
|
|
72
|
+
src: it.url,
|
|
73
|
+
alt: (_it$alt = it.alt) !== null && _it$alt !== void 0 ? _it$alt : '',
|
|
74
|
+
onClick: function onClick(e) {
|
|
75
|
+
return e.stopPropagation();
|
|
76
|
+
},
|
|
77
|
+
"data-odn-image-viewer-img": true
|
|
78
|
+
}), /*#__PURE__*/React.createElement("button", {
|
|
79
|
+
type: "button",
|
|
80
|
+
onClick: function onClick(e) {
|
|
81
|
+
e.stopPropagation();
|
|
82
|
+
onClose();
|
|
83
|
+
},
|
|
84
|
+
"data-odn-image-viewer-btn": "close",
|
|
85
|
+
"aria-label": "\u5173\u95ED\uFF08ESC\uFF09",
|
|
86
|
+
title: "\u5173\u95ED\uFF08ESC\uFF09"
|
|
87
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
88
|
+
name: "x",
|
|
89
|
+
size: 20
|
|
90
|
+
})), total > 1 && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
|
|
91
|
+
type: "button",
|
|
92
|
+
onClick: function onClick(e) {
|
|
93
|
+
e.stopPropagation();
|
|
94
|
+
prev();
|
|
95
|
+
},
|
|
96
|
+
"data-odn-image-viewer-btn": "prev",
|
|
97
|
+
"aria-label": "\u4E0A\u4E00\u5F20\uFF08\u2190\uFF09",
|
|
98
|
+
title: "\u4E0A\u4E00\u5F20\uFF08\u2190\uFF09"
|
|
99
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
100
|
+
name: "chevron-left",
|
|
101
|
+
size: 24
|
|
102
|
+
})), /*#__PURE__*/React.createElement("button", {
|
|
103
|
+
type: "button",
|
|
104
|
+
onClick: function onClick(e) {
|
|
105
|
+
e.stopPropagation();
|
|
106
|
+
next();
|
|
107
|
+
},
|
|
108
|
+
"data-odn-image-viewer-btn": "next",
|
|
109
|
+
"aria-label": "\u4E0B\u4E00\u5F20\uFF08\u2192\uFF09",
|
|
110
|
+
title: "\u4E0B\u4E00\u5F20\uFF08\u2192\uFF09"
|
|
111
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
112
|
+
name: "chevron-right",
|
|
113
|
+
size: 24
|
|
114
|
+
})), /*#__PURE__*/React.createElement("div", {
|
|
115
|
+
"data-odn-image-viewer-counter": true
|
|
116
|
+
}, index + 1, " / ", total)));
|
|
117
|
+
return /*#__PURE__*/createPortal(overlay, document.body);
|
|
118
|
+
}
|
|
119
|
+
export default ImageViewer;
|
|
@@ -8,15 +8,35 @@ import './style';
|
|
|
8
8
|
export interface MessageImageItem {
|
|
9
9
|
url: string;
|
|
10
10
|
alt?: string;
|
|
11
|
+
/**
|
|
12
|
+
* 图片已过期(消息流只读态)——历史消息里的图 url 随时间失效。
|
|
13
|
+
* 这是「消息态」的业务语义,由 MessageImage 翻译成 `Image` leaf 的
|
|
14
|
+
* `unavailable` + 「已过期」占位(leaf 本身不认识「过期」这个词)。
|
|
15
|
+
*/
|
|
16
|
+
expired?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* 图片自然像素宽 / 高(可选)。仅用于单图过期时让占位复刻原图尺寸;
|
|
19
|
+
* 拿不到则占位退化成 88×88。
|
|
20
|
+
*/
|
|
21
|
+
width?: number;
|
|
22
|
+
height?: number;
|
|
11
23
|
}
|
|
12
24
|
export interface MessageImageProps {
|
|
13
25
|
/** 待渲染的图片列表。 */
|
|
14
26
|
images: MessageImageItem[];
|
|
15
27
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
28
|
+
* 是否可点开看大图(**内置**查看器,opt-out)。默认 `true`——「点开看大图」是
|
|
29
|
+
* 消息图片的标配语义。纯展示 / 由业务自己接预览的场景传 `false` 关掉。
|
|
30
|
+
*
|
|
31
|
+
* 过期 / 不可用的图始终不可点开,与此开关无关(`Image` leaf 自动屏蔽)。
|
|
32
|
+
*/
|
|
33
|
+
preview?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* 图片点击**通知**(埋点 / 统计用),传入被点图片 index。这**不是**「自定义
|
|
36
|
+
* 查看器」的挂载点——大图预览由内置查看器(`preview`)负责,此回调只是知会
|
|
37
|
+
* 上层「第 i 张被点了」,不阻止内置行为。过期图不触发。
|
|
18
38
|
*/
|
|
19
|
-
|
|
39
|
+
onImageClick?: (index: number) => void;
|
|
20
40
|
/**
|
|
21
41
|
* 删除按钮点击回调,传入图片 index。本期 MessageImage 主要承载消息态展示,
|
|
22
42
|
* 该回调主要为未来扩展(如撤回未发送消息中的图)保留。
|
|
@@ -35,7 +55,7 @@ export interface MessageImageProps {
|
|
|
35
55
|
* Composer 待发送态等其他场景**不应消费此组件**——那是输入态语义,
|
|
36
56
|
* 应直接用 `Image` leaf + 自己的尺寸 wrap。
|
|
37
57
|
*/
|
|
38
|
-
export declare function MessageImage({ images,
|
|
58
|
+
export declare function MessageImage({ images, preview, onImageClick, onRemove, }: MessageImageProps): import("react").JSX.Element | null;
|
|
39
59
|
export declare namespace MessageImage {
|
|
40
60
|
var displayName: string;
|
|
41
61
|
}
|
|
@@ -1,6 +1,55 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
|
4
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
5
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
6
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
7
|
+
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
8
|
+
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
9
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
1
10
|
import Image from "../image";
|
|
11
|
+
import Icon from "../icon";
|
|
12
|
+
import ImageViewer from "./image-viewer";
|
|
2
13
|
import "./style";
|
|
3
14
|
|
|
15
|
+
/**
|
|
16
|
+
* 单图过期占位的尺寸边界,镜像 style/index.scss 的尺寸 token:
|
|
17
|
+
* - SINGLE_MAX_* 对齐 --odn-message-image-max-width / -max-height
|
|
18
|
+
* - THUMB 对齐 --odn-message-image-thumb-size(多图缩略边长)
|
|
19
|
+
* 放在 wrapper(MessageImage)而非 leaf——尺寸始终归 wrapper。
|
|
20
|
+
*/
|
|
21
|
+
var SINGLE_MAX_W = 280;
|
|
22
|
+
var SINGLE_MAX_H = 360;
|
|
23
|
+
var THUMB = 88;
|
|
24
|
+
|
|
25
|
+
/** 过期图片占位:文案由 MessageImage(消息态)传入 leaf——leaf 不认识「过期」。 */
|
|
26
|
+
function ExpiredFallback() {
|
|
27
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Icon, {
|
|
28
|
+
name: "image-off",
|
|
29
|
+
size: 20
|
|
30
|
+
}), /*#__PURE__*/React.createElement("span", null, "\u56FE\u7247\u5DF2\u8FC7\u671F"));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 单图过期时占位盒的尺寸:
|
|
35
|
+
* - 有原图尺寸 → 按 contain 缩放到 max 280×360 内,复刻原图 footprint,不跳版
|
|
36
|
+
* - 没有原图尺寸 → 退化成 88×88(同多图缩略)
|
|
37
|
+
* 多图不走这里——多图恒为 88×88,由 scss 的 multi 选择器兜住。
|
|
38
|
+
*/
|
|
39
|
+
function expiredSingleSize(item) {
|
|
40
|
+
if (item.width && item.height) {
|
|
41
|
+
var scale = Math.min(SINGLE_MAX_W / item.width, SINGLE_MAX_H / item.height, 1);
|
|
42
|
+
return {
|
|
43
|
+
width: Math.round(item.width * scale),
|
|
44
|
+
height: Math.round(item.height * scale)
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
width: THUMB,
|
|
49
|
+
height: THUMB
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
4
53
|
/**
|
|
5
54
|
* MessageImage 渲染的图片项。
|
|
6
55
|
* 与 `Attachment` 不同:图片消息的「主体」就是图本身,不需要 name / type / size 等
|
|
@@ -21,25 +70,61 @@ import "./style";
|
|
|
21
70
|
*/
|
|
22
71
|
export function MessageImage(_ref) {
|
|
23
72
|
var images = _ref.images,
|
|
24
|
-
|
|
73
|
+
_ref$preview = _ref.preview,
|
|
74
|
+
preview = _ref$preview === void 0 ? true : _ref$preview,
|
|
75
|
+
onImageClick = _ref.onImageClick,
|
|
25
76
|
onRemove = _ref.onRemove;
|
|
77
|
+
var _useState = useState(null),
|
|
78
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
79
|
+
viewerIndex = _useState2[0],
|
|
80
|
+
setViewerIndex = _useState2[1];
|
|
81
|
+
|
|
82
|
+
// 查看器只收「画得出」的图——过期 / 不可用的排除,避免上下张切到碎图。
|
|
83
|
+
var viewable = useMemo(function () {
|
|
84
|
+
return images.filter(function (img) {
|
|
85
|
+
return !img.expired;
|
|
86
|
+
});
|
|
87
|
+
}, [images]);
|
|
88
|
+
|
|
89
|
+
// 点第 i 张(原始 index):先知会上层,再按需打开内置查看器。
|
|
90
|
+
var handleClick = useCallback(function (i) {
|
|
91
|
+
onImageClick === null || onImageClick === void 0 || onImageClick(i);
|
|
92
|
+
if (!preview) return;
|
|
93
|
+
var pos = viewable.indexOf(images[i]);
|
|
94
|
+
if (pos >= 0) setViewerIndex(pos);
|
|
95
|
+
}, [onImageClick, preview, images, viewable]);
|
|
26
96
|
if (images.length === 0) return null;
|
|
27
97
|
var isMulti = images.length > 1;
|
|
28
|
-
|
|
98
|
+
// 只要「可预览」或「要通知」,就把图包成可点区(过期图由 leaf 内部再屏蔽)。
|
|
99
|
+
var clickable = preview || !!onImageClick;
|
|
100
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
|
|
29
101
|
"data-odn-message-image": true,
|
|
30
102
|
"data-odn-message-image-multi": isMulti || undefined
|
|
31
103
|
}, images.map(function (img, i) {
|
|
32
104
|
return /*#__PURE__*/React.createElement(Image, {
|
|
33
105
|
key: i,
|
|
34
106
|
url: img.url,
|
|
35
|
-
alt: img.alt
|
|
36
|
-
|
|
37
|
-
|
|
107
|
+
alt: img.alt
|
|
108
|
+
// 「过期」是消息态业务语义,翻译成 leaf 的渲染态:不加载失效 url + 已过期占位。
|
|
109
|
+
,
|
|
110
|
+
unavailable: img.expired || undefined,
|
|
111
|
+
fallback: img.expired ? /*#__PURE__*/React.createElement(ExpiredFallback, null) : undefined
|
|
112
|
+
// 单图过期时用原图尺寸复刻 footprint;多图恒 88×88 由 scss 兜。
|
|
113
|
+
,
|
|
114
|
+
style: !isMulti && img.expired ? expiredSingleSize(img) : undefined,
|
|
115
|
+
onPreview: clickable ? function () {
|
|
116
|
+
return handleClick(i);
|
|
38
117
|
} : undefined,
|
|
39
118
|
onRemove: onRemove ? function () {
|
|
40
119
|
return onRemove(i);
|
|
41
120
|
} : undefined
|
|
42
121
|
});
|
|
122
|
+
})), viewerIndex !== null && viewable.length > 0 && /*#__PURE__*/React.createElement(ImageViewer, {
|
|
123
|
+
images: viewable,
|
|
124
|
+
initialIndex: viewerIndex,
|
|
125
|
+
onClose: function onClose() {
|
|
126
|
+
return setViewerIndex(null);
|
|
127
|
+
}
|
|
43
128
|
}));
|
|
44
129
|
}
|
|
45
130
|
MessageImage.displayName = 'MessageImage';
|
|
@@ -57,4 +57,102 @@ html {
|
|
|
57
57
|
height: 100%;
|
|
58
58
|
max-height: none;
|
|
59
59
|
object-fit: cover;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* 内置大图查看器(私有 image-viewer.tsx 渲染,portal 到 body):
|
|
63
|
+
* ESC / 点遮罩 关闭;←/→ 切换;点图不关。样式从 message-image-verify 的
|
|
64
|
+
* SimpleLightbox 收编而来——不再散落在 demo 里各写一份。 */
|
|
65
|
+
[data-odn-image-viewer] {
|
|
66
|
+
position: fixed;
|
|
67
|
+
inset: 0;
|
|
68
|
+
z-index: 9999;
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
justify-content: center;
|
|
72
|
+
padding: 64px;
|
|
73
|
+
background: rgba(0, 0, 0, 0.78);
|
|
74
|
+
cursor: zoom-out;
|
|
75
|
+
animation: odn-image-viewer-fade 0.15s ease-out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@keyframes odn-image-viewer-fade {
|
|
79
|
+
from {
|
|
80
|
+
opacity: 0;
|
|
81
|
+
}
|
|
82
|
+
to {
|
|
83
|
+
opacity: 1;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
[data-odn-image-viewer-img] {
|
|
87
|
+
max-width: 90vw;
|
|
88
|
+
max-height: 90vh;
|
|
89
|
+
object-fit: contain;
|
|
90
|
+
border-radius: 8px;
|
|
91
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
|
92
|
+
cursor: default;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
[data-odn-image-viewer-btn] {
|
|
96
|
+
position: absolute;
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
justify-content: center;
|
|
100
|
+
border: none;
|
|
101
|
+
border-radius: 50%;
|
|
102
|
+
background: rgba(255, 255, 255, 0.12);
|
|
103
|
+
color: rgba(255, 255, 255, 0.95);
|
|
104
|
+
cursor: pointer;
|
|
105
|
+
backdrop-filter: blur(8px);
|
|
106
|
+
transition: background 0.15s, transform 0.1s;
|
|
107
|
+
}
|
|
108
|
+
[data-odn-image-viewer-btn]:hover {
|
|
109
|
+
background: rgba(255, 255, 255, 0.22);
|
|
110
|
+
}
|
|
111
|
+
[data-odn-image-viewer-btn]:active {
|
|
112
|
+
transform: scale(0.94);
|
|
113
|
+
}
|
|
114
|
+
[data-odn-image-viewer-btn]:focus-visible {
|
|
115
|
+
outline: 2px solid color-mix(in srgb, var(--odn-color-primary) 60%, transparent);
|
|
116
|
+
outline-offset: 2px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
[data-odn-image-viewer-btn=close] {
|
|
120
|
+
top: 20px;
|
|
121
|
+
right: 24px;
|
|
122
|
+
width: 40px;
|
|
123
|
+
height: 40px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
[data-odn-image-viewer-btn=prev],
|
|
127
|
+
[data-odn-image-viewer-btn=next] {
|
|
128
|
+
top: 50%;
|
|
129
|
+
width: 48px;
|
|
130
|
+
height: 48px;
|
|
131
|
+
transform: translateY(-50%);
|
|
132
|
+
}
|
|
133
|
+
[data-odn-image-viewer-btn=prev]:active,
|
|
134
|
+
[data-odn-image-viewer-btn=next]:active {
|
|
135
|
+
transform: translateY(-50%) scale(0.94);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
[data-odn-image-viewer-btn=prev] {
|
|
139
|
+
left: 24px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
[data-odn-image-viewer-btn=next] {
|
|
143
|
+
right: 24px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
[data-odn-image-viewer-counter] {
|
|
147
|
+
position: absolute;
|
|
148
|
+
bottom: 24px;
|
|
149
|
+
left: 50%;
|
|
150
|
+
padding: 6px 14px;
|
|
151
|
+
border-radius: 999px;
|
|
152
|
+
background: rgba(0, 0, 0, 0.4);
|
|
153
|
+
color: rgba(255, 255, 255, 0.85);
|
|
154
|
+
font-size: 13px;
|
|
155
|
+
font-variant-numeric: tabular-nums;
|
|
156
|
+
backdrop-filter: blur(8px);
|
|
157
|
+
transform: translateX(-50%);
|
|
60
158
|
}
|
|
@@ -6,16 +6,15 @@ export interface UserBubbleProps {
|
|
|
6
6
|
message: ChatMessage;
|
|
7
7
|
onFileClick?: (attachment: Attachment) => void;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* 图片点击**通知**(埋点 / 统计用)。大图预览由 `MessageImage` 内置查看器
|
|
10
|
+
* 负责——用户点图默认即可看大图,**业务方无需对接任何 lightbox**。此回调只
|
|
11
|
+
* 知会「第 i 张被点了」,不影响内置预览;过期图不触发。
|
|
10
12
|
* @param index 被点击的图片在「当前消息内已分流的图片子集」中的位置(0-based)
|
|
11
|
-
* @param images 当前消息内所有 image attachments(已转成 `MessageImageItem`
|
|
12
|
-
* 的形态,包含 `url` 与 `alt`),方便直接 push 到 lightbox
|
|
13
|
-
* 实现上下张切换
|
|
14
|
-
* 不传 → 图片不可点击预览。
|
|
13
|
+
* @param images 当前消息内所有 image attachments(已转成 `MessageImageItem` 形态)
|
|
15
14
|
*/
|
|
16
|
-
|
|
15
|
+
onImageClick?: (index: number, images: MessageImageItem[]) => void;
|
|
17
16
|
}
|
|
18
|
-
export declare function UserBubble({ message, onFileClick,
|
|
17
|
+
export declare function UserBubble({ message, onFileClick, onImageClick }: UserBubbleProps): import("react").JSX.Element;
|
|
19
18
|
export declare namespace UserBubble {
|
|
20
19
|
var displayName: string;
|
|
21
20
|
}
|
|
@@ -6,7 +6,7 @@ export function UserBubble(_ref) {
|
|
|
6
6
|
var _message$content, _message$attachments;
|
|
7
7
|
var message = _ref.message,
|
|
8
8
|
onFileClick = _ref.onFileClick,
|
|
9
|
-
|
|
9
|
+
onImageClick = _ref.onImageClick;
|
|
10
10
|
var segments = message.segments;
|
|
11
11
|
var hasText = segments !== null && segments !== void 0 && segments.length ? segmentsHaveVisibleContent(segments) : !!((_message$content = message.content) !== null && _message$content !== void 0 && _message$content.trim());
|
|
12
12
|
var textBody = segments !== null && segments !== void 0 && segments.length ? renderSegmentsContent(segments) : message.content;
|
|
@@ -20,7 +20,10 @@ export function UserBubble(_ref) {
|
|
|
20
20
|
}).map(function (a) {
|
|
21
21
|
return {
|
|
22
22
|
url: a.url,
|
|
23
|
-
alt: a.name
|
|
23
|
+
alt: a.name,
|
|
24
|
+
expired: a.status === 'expired',
|
|
25
|
+
width: a.width,
|
|
26
|
+
height: a.height
|
|
24
27
|
};
|
|
25
28
|
});
|
|
26
29
|
var files = all.filter(function (a) {
|
|
@@ -34,8 +37,8 @@ export function UserBubble(_ref) {
|
|
|
34
37
|
"data-odn-user-bubble-images": true
|
|
35
38
|
}, /*#__PURE__*/React.createElement(MessageImage, {
|
|
36
39
|
images: images,
|
|
37
|
-
|
|
38
|
-
return
|
|
40
|
+
onImageClick: onImageClick ? function (i) {
|
|
41
|
+
return onImageClick(i, images);
|
|
39
42
|
} : undefined
|
|
40
43
|
})), hasText && textBody != null && /*#__PURE__*/React.createElement("div", {
|
|
41
44
|
"data-odn-user-bubble-text": true
|