@subiz/widget-v4 4.1.215 → 4.1.217
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/api.md +9 -5
- package/assets/img/bubble_default.svg +1 -0
- package/assets/less/message.less +83 -0
- package/assets/less/widget.less +12 -0
- package/compiled_src/chatbox/AudioAttachments.js +5 -0
- package/compiled_src/chatbox/ChatButton.js +19 -17
- package/compiled_src/chatbox/Event.js +9 -2
- package/compiled_src/chatbox/FileAttachments.js +9 -3
- package/compiled_src/chatbox/ImageAttachments.js +16 -6
- package/compiled_src/chatbox/UploadProgress.js +44 -0
- package/compiled_src/chatbox/VideoAttachments.js +5 -0
- package/compiled_src/store/api.js +77 -78
- package/compiled_src/store/api_fake.js +1 -1
- package/compiled_src/store/attachments.js +5 -1
- package/compiled_src/store/store.js +25 -4
- package/dist/{1a12a5ed6a9.278.js → 1a12ed33347.278.js} +1 -1
- package/dist/1a12ed33347.app.js +1 -0
- package/dist/{1a12a5ed6a9.chat.html → 1a12ed33347.chat.html} +1 -1
- package/dist/1a12ed33347.chat.js +1 -0
- package/dist/{1a12a5ed6a9.form.html → 1a12ed33347.form.html} +1 -1
- package/dist/{1a12a5ed6a9.form.js → 1a12ed33347.form.js} +1 -1
- package/dist/{1a12a5ed6a9.index.html → 1a12ed33347.index.html} +1 -1
- package/dist/{1a12a5ed6a9.library.html → 1a12ed33347.library.html} +1 -1
- package/dist/1a12ed33347.library.js +1 -0
- package/dist/{1a12a5ed6a9.preview.html → 1a12ed33347.preview.html} +1 -1
- package/dist/1a12ed33347.preview.js +1 -0
- package/dist/1a12ed33347.webview.js +1 -0
- package/dist/HASH +1 -0
- package/dist/assets/img/{bubble_default.7d5e4ab.svg → bubble_default.6f6eec8.svg} +1 -0
- package/dist/screenshoot.js +25 -20
- package/package.json +11 -10
- package/src/chatbox/AudioAttachments.js +2 -0
- package/src/chatbox/ChatButton.js +22 -20
- package/src/chatbox/Event.js +8 -4
- package/src/chatbox/FileAttachments.js +5 -2
- package/src/chatbox/ImageAttachments.js +6 -2
- package/src/chatbox/UploadProgress.js +21 -0
- package/src/chatbox/VideoAttachments.js +2 -0
- package/src/store/api.js +66 -81
- package/src/store/api_fake.js +1 -1
- package/src/store/attachments.js +5 -1
- package/src/store/store.js +13 -5
- package/up +18 -0
- package/dist/.HASH +0 -1
- package/dist/1a12a5ed6a9.app.js +0 -1
- package/dist/1a12a5ed6a9.chat.js +0 -1
- package/dist/1a12a5ed6a9.library.js +0 -1
- package/dist/1a12a5ed6a9.preview.js +0 -1
- package/dist/1a12a5ed6a9.webview.js +0 -1
- /package/dist/{1a12a5ed6a9.127.js → 1a12ed33347.127.js} +0 -0
- /package/dist/{1a12a5ed6a9.132.js → 1a12ed33347.132.js} +0 -0
- /package/dist/{1a12a5ed6a9.185.js → 1a12ed33347.185.js} +0 -0
- /package/dist/{1a12a5ed6a9.27.js → 1a12ed33347.27.js} +0 -0
- /package/dist/{1a12a5ed6a9.339.js → 1a12ed33347.339.js} +0 -0
- /package/dist/{1a12a5ed6a9.459.js → 1a12ed33347.459.js} +0 -0
- /package/dist/{1a12a5ed6a9.490.js → 1a12ed33347.490.js} +0 -0
package/README.md
CHANGED
|
@@ -6,6 +6,12 @@ $ npm run dev
|
|
|
6
6
|
|
|
7
7
|
visit: http://localhost:8082/?account_id=acqcsmrppbftadjzxnvo
|
|
8
8
|
|
|
9
|
+
The fallback button keeps the configured width and height and has an opaque
|
|
10
|
+
white backing. Each dimension defaults to 70px only when it is not configured.
|
|
11
|
+
|
|
12
|
+
Masks use data URLs rather than blob URLs. If data URLs are blocked, the widget
|
|
13
|
+
tries the original image URL with CORS before using the fallback button.
|
|
14
|
+
|
|
9
15
|
### To test widget in injection mode
|
|
10
16
|
|
|
11
17
|
``` sh
|
package/api.md
CHANGED
|
@@ -1264,6 +1264,7 @@ nghiệp vụ của form do website thực hiện.
|
|
|
1264
1264
|
subiz('waitReady', async function () {
|
|
1265
1265
|
try {
|
|
1266
1266
|
const result = await subiz('getFormStatus', 'website_contact')
|
|
1267
|
+
// result.values: [{key: 'fullname', label: 'Họ tên', value: 'Nguyễn Văn An'}, ...]
|
|
1267
1268
|
if (result.submitted) document.getElementById('contact-form').hidden = true
|
|
1268
1269
|
} catch (error) {
|
|
1269
1270
|
// Không ẩn form khi kiểm tra thất bại.
|
|
@@ -1272,11 +1273,13 @@ subiz('waitReady', async function () {
|
|
|
1272
1273
|
```
|
|
1273
1274
|
|
|
1274
1275
|
`getFormStatus(formId)` đọc qua store KV/LSDB của widget, không gọi API trạng thái
|
|
1275
|
-
hay lấy hội thoại. Sau khi submit thành công, SDK lưu dấu đã gửi
|
|
1276
|
-
tách theo account, ID khách và ID form.
|
|
1277
|
-
|
|
1276
|
+
hay lấy hội thoại. Sau khi submit thành công, SDK lưu dấu đã gửi và nội dung form
|
|
1277
|
+
trong 7 ngày, tách theo account, ID khách và ID form. Bản ghi gồm mã hội thoại,
|
|
1278
|
+
thời điểm hết hạn và `values` của lần gửi thành công gần nhất, theo dạng
|
|
1279
|
+
`[{key, label, value}]` với giá trị đã chuẩn hóa thành chuỗi như khi gửi.
|
|
1280
|
+
Đọc trạng thái không gia hạn thời gian lưu. Lần gửi thất bại không ghi đè dữ liệu cũ.
|
|
1278
1281
|
|
|
1279
|
-
Kết quả: `{submitted: true, status: 'found', conversation_id}` nếu còn hạn;
|
|
1282
|
+
Kết quả: `{submitted: true, status: 'found', conversation_id, values}` nếu còn hạn;
|
|
1280
1283
|
`{submitted: false, status: 'not_found'}` nếu chưa lưu; `status: 'expired'` khi
|
|
1281
1284
|
hết hạn (đồng thời xóa bản ghi); `status: 'storage_unavailable'` khi trình duyệt
|
|
1282
1285
|
không cho truy cập localStorage. Lỗi lưu localStorage không biến một submit đã
|
|
@@ -1286,7 +1289,8 @@ SDK bỏ qua sự kiện realtime của hội thoại kênh form trước khi l
|
|
|
1286
1289
|
thoại; không mở hội thoại form trong chat widget. Luồng chat thông thường giữ
|
|
1287
1290
|
nguyên. Trạng thái chỉ có trên cùng website/trình duyệt và cùng ID khách, không
|
|
1288
1291
|
đồng bộ thiết bị khác, mất khi xóa localStorage. Các lần gửi trước khi bổ sung
|
|
1289
|
-
cơ chế lưu này không được tự khôi phục từ server.
|
|
1292
|
+
cơ chế lưu này không được tự khôi phục từ server. Bản ghi cũ chỉ lưu dấu đã gửi
|
|
1293
|
+
vẫn trả `submitted: true` nếu còn hạn, nhưng `values: []` vì chưa lưu nội dung.
|
|
1290
1294
|
|
|
1291
1295
|
SDK không truy cập localStorage trực tiếp: store dùng `kv.put/match/del` và
|
|
1292
1296
|
LSDB hiện có. Khi widget dùng chế độ in-memory hoặc trình duyệt chặn lưu trữ,
|
package/assets/less/message.less
CHANGED
|
@@ -303,6 +303,89 @@ a.message_error {
|
|
|
303
303
|
border: 1px solid @gray-200;
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
+
.image-attachment__preview {
|
|
307
|
+
position: relative;
|
|
308
|
+
display: inline-block;
|
|
309
|
+
vertical-align: top;
|
|
310
|
+
margin-top: 5px;
|
|
311
|
+
border-radius: 7px;
|
|
312
|
+
overflow: hidden;
|
|
313
|
+
max-width: 100%;
|
|
314
|
+
|
|
315
|
+
.image-attachment__img {
|
|
316
|
+
display: block;
|
|
317
|
+
margin: 0;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.image-attachment__sending {
|
|
322
|
+
position: absolute;
|
|
323
|
+
inset: 0;
|
|
324
|
+
display: flex;
|
|
325
|
+
align-items: center;
|
|
326
|
+
justify-content: center;
|
|
327
|
+
cursor: wait;
|
|
328
|
+
background: rgba(0, 0, 0, 0.28);
|
|
329
|
+
|
|
330
|
+
&::before {
|
|
331
|
+
content: '';
|
|
332
|
+
position: absolute;
|
|
333
|
+
inset: 0;
|
|
334
|
+
background: @primary-color;
|
|
335
|
+
opacity: 0.3;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.image-attachment__progress {
|
|
340
|
+
position: relative;
|
|
341
|
+
width: 96px;
|
|
342
|
+
max-width: 80%;
|
|
343
|
+
color: #fff;
|
|
344
|
+
text-align: center;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.image-attachment__progress-label {
|
|
348
|
+
display: block;
|
|
349
|
+
font-size: 13px;
|
|
350
|
+
font-weight: 600;
|
|
351
|
+
margin-bottom: 6px;
|
|
352
|
+
font-variant-numeric: tabular-nums;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.image-attachment__progress-track {
|
|
356
|
+
display: block;
|
|
357
|
+
height: 4px;
|
|
358
|
+
border-radius: 4px;
|
|
359
|
+
overflow: hidden;
|
|
360
|
+
background: rgba(255, 255, 255, 0.35);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.image-attachment__progress-fill {
|
|
364
|
+
display: block;
|
|
365
|
+
height: 100%;
|
|
366
|
+
border-radius: inherit;
|
|
367
|
+
background: #fff;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.file-attachment--sending {
|
|
371
|
+
cursor: wait;
|
|
372
|
+
|
|
373
|
+
.file-attachment--filename { text-decoration: none; }
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.file-attachment__progress {
|
|
377
|
+
display: block;
|
|
378
|
+
width: 100%;
|
|
379
|
+
min-width: 120px;
|
|
380
|
+
max-width: 100%;
|
|
381
|
+
margin-top: 8px;
|
|
382
|
+
color: @primary-color;
|
|
383
|
+
text-align: right;
|
|
384
|
+
|
|
385
|
+
.image-attachment__progress-track { background: rgba(0, 0, 0, 0.1); }
|
|
386
|
+
.image-attachment__progress-fill { background: @primary-color; }
|
|
387
|
+
}
|
|
388
|
+
|
|
306
389
|
.audio-attachment {
|
|
307
390
|
width: 100%;
|
|
308
391
|
margin: 5px 0 0 0;
|
package/assets/less/widget.less
CHANGED
|
@@ -438,6 +438,18 @@
|
|
|
438
438
|
// min-height: @desktop-chat-button-height;
|
|
439
439
|
}
|
|
440
440
|
|
|
441
|
+
.chat-button--fallback {
|
|
442
|
+
.chat-button--img-container {
|
|
443
|
+
background-size: 100% 100%;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.chat-button--img {
|
|
447
|
+
width: 100%;
|
|
448
|
+
min-width: 0;
|
|
449
|
+
height: 100%;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
441
453
|
.chat-button.chat-button--left {
|
|
442
454
|
float: left;
|
|
443
455
|
}
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = AudioAttachments;
|
|
7
|
+
var _UploadProgress = require("./UploadProgress.js");
|
|
7
8
|
var _index = _interopRequireDefault(require("../util/index.js"));
|
|
8
9
|
var _preact = require("preact");
|
|
9
10
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -17,6 +18,10 @@ function AudioAttachments(props) {
|
|
|
17
18
|
});
|
|
18
19
|
if (audios.length === 0) return null;
|
|
19
20
|
var $audios = _index.default.map(audios, (audio, index) => {
|
|
21
|
+
if (props.sending) return (0, _preact.h)(_UploadProgress.UploadingFile, {
|
|
22
|
+
key: index,
|
|
23
|
+
attachment: audio
|
|
24
|
+
});
|
|
20
25
|
return (0, _preact.h)("div", {
|
|
21
26
|
key: index,
|
|
22
27
|
class: `audio-attachment audio-attachment--${byType}`
|
|
@@ -400,18 +400,10 @@ class ChatButton extends _common.Com {
|
|
|
400
400
|
this.readyGate.close();
|
|
401
401
|
const [loaded, mask] = await Promise.all([Promise.all(appearance.urls.filter(url => url !== appearance.maskAsset).map(url => preloadButtonImage(url))), appearance.maskAsset ? prepareButtonMask(appearance.maskAsset) : Promise.resolve(null)]);
|
|
402
402
|
const stale = () => !this._isMounted || this._buttonImageVersion !== version;
|
|
403
|
-
if (stale())
|
|
404
|
-
if (mask) URL.revokeObjectURL(mask);
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
403
|
+
if (stale()) return;
|
|
407
404
|
const fallback = loaded.some(success => !success) || !!appearance.maskAsset && !mask;
|
|
408
405
|
const ready = !fallback || (await preloadButtonImage(defaultBubble));
|
|
409
|
-
if (stale())
|
|
410
|
-
if (mask) URL.revokeObjectURL(mask);
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
if (this._buttonMaskUrl) URL.revokeObjectURL(this._buttonMaskUrl);
|
|
414
|
-
this._buttonMaskUrl = mask;
|
|
406
|
+
if (stale()) return;
|
|
415
407
|
if (this._buttonElement) window.getComputedStyle(this._buttonElement).transform;
|
|
416
408
|
this.setState({
|
|
417
409
|
ready,
|
|
@@ -422,7 +414,6 @@ class ChatButton extends _common.Com {
|
|
|
422
414
|
this.readyGate.open();
|
|
423
415
|
}
|
|
424
416
|
componentWillUnmount() {
|
|
425
|
-
if (this._buttonMaskUrl) URL.revokeObjectURL(this._buttonMaskUrl);
|
|
426
417
|
if (this._animateTimeout) clearTimeout(this._animateTimeout);
|
|
427
418
|
}
|
|
428
419
|
renderButton() {
|
|
@@ -437,11 +428,12 @@ class ChatButton extends _common.Com {
|
|
|
437
428
|
imgwidth,
|
|
438
429
|
imgheight
|
|
439
430
|
} = appearance;
|
|
431
|
+
const fallback = this.state.readyImageKey === appearance.key && this.state.fallbackButtonImage;
|
|
440
432
|
if (appearance.maskAsset) {
|
|
441
433
|
maskurl = this.state.readyImageKey === appearance.key ? this.state.buttonMaskUrl : '';
|
|
442
434
|
if (imgsrc === appearance.maskAsset) imgsrc = maskurl || '';
|
|
443
435
|
}
|
|
444
|
-
if (
|
|
436
|
+
if (fallback) {
|
|
445
437
|
imgsrc = imgurl = defaultBubble;
|
|
446
438
|
maskurl = '';
|
|
447
439
|
}
|
|
@@ -501,7 +493,11 @@ class ChatButton extends _common.Com {
|
|
|
501
493
|
mask-size: cover !important;
|
|
502
494
|
`;
|
|
503
495
|
}
|
|
496
|
+
if (fallback) {
|
|
497
|
+
imgcontainerstyle += `width: ${imgwidth}px !important; height: ${imgheight}px !important;`;
|
|
498
|
+
}
|
|
504
499
|
let cls = 'chat-button chat-button--' + position;
|
|
500
|
+
if (fallback) cls += ' chat-button--fallback';
|
|
505
501
|
let whiteBgStyle = `
|
|
506
502
|
width: ${imgwidth - 4}px !important;
|
|
507
503
|
height: ${imgheight - 4}px !important;
|
|
@@ -624,13 +620,19 @@ function preloadButtonImage(src, {
|
|
|
624
620
|
});
|
|
625
621
|
}
|
|
626
622
|
async function prepareButtonMask(src) {
|
|
627
|
-
let url;
|
|
628
623
|
try {
|
|
629
624
|
const response = await fetch(src);
|
|
630
625
|
if (!response.ok) return null;
|
|
631
|
-
|
|
632
|
-
|
|
626
|
+
const blob = await response.blob();
|
|
627
|
+
const url = await new Promise(resolve => {
|
|
628
|
+
const reader = new FileReader();
|
|
629
|
+
reader.onload = () => resolve(reader.result);
|
|
630
|
+
reader.onerror = reader.onabort = () => resolve('');
|
|
631
|
+
reader.readAsDataURL(blob);
|
|
632
|
+
});
|
|
633
|
+
if (url && (await preloadButtonImage(url))) return url;
|
|
633
634
|
} catch (_) {}
|
|
634
|
-
|
|
635
|
-
|
|
635
|
+
return (await preloadButtonImage(src, {
|
|
636
|
+
crossOrigin: true
|
|
637
|
+
})) ? src : null;
|
|
636
638
|
}
|
|
@@ -379,6 +379,9 @@ class Event extends _common.Com {
|
|
|
379
379
|
class: "sending_dot sending_dot__hide"
|
|
380
380
|
});
|
|
381
381
|
}
|
|
382
|
+
const hasFile = _index.default.find(attachments, att => att.type === 'file' || att._file);
|
|
383
|
+
const sendingAttachment = !ev._error && ev.source_event_id === ev.id;
|
|
384
|
+
if (hasFile) $dot = null;
|
|
382
385
|
let $sender = null;
|
|
383
386
|
if (this.props.event.mode === 'lead' && _index.default.get(ev, 'by.type') !== 'user') $sender = (0, _preact.h)("div", {
|
|
384
387
|
class: "pt-3"
|
|
@@ -413,16 +416,20 @@ class Event extends _common.Com {
|
|
|
413
416
|
}), (0, _preact.h)(_ImageAttachments.default, {
|
|
414
417
|
store: this.props.store,
|
|
415
418
|
ev: ev,
|
|
419
|
+
sending: sendingAttachment,
|
|
416
420
|
ref: this.imageAttachmentRef
|
|
417
421
|
}), (0, _preact.h)(_AudioAttachments.default, {
|
|
418
422
|
store: this.props.store,
|
|
419
|
-
ev: ev
|
|
423
|
+
ev: ev,
|
|
424
|
+
sending: sendingAttachment
|
|
420
425
|
}), (0, _preact.h)(_VideoAttachments.default, {
|
|
421
426
|
store: this.props.store,
|
|
422
|
-
ev: ev
|
|
427
|
+
ev: ev,
|
|
428
|
+
sending: sendingAttachment
|
|
423
429
|
}), (0, _preact.h)(_FileAttachments.default, {
|
|
424
430
|
store: this.props.store,
|
|
425
431
|
ev: ev,
|
|
432
|
+
sending: sendingAttachment,
|
|
426
433
|
ref: this.fileAttachmentRef
|
|
427
434
|
}), (0, _preact.h)(_ButtonAttachments.default, {
|
|
428
435
|
store: this.props.store,
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
|
+
var _UploadProgress = _interopRequireDefault(require("./UploadProgress.js"));
|
|
7
8
|
var _index = _interopRequireDefault(require("../util/index.js"));
|
|
8
9
|
var _common = require("../common.js");
|
|
9
10
|
var _preact = require("preact");
|
|
@@ -33,19 +34,24 @@ class FileAttachments extends _common.Com {
|
|
|
33
34
|
if (files.length === 0) return null;
|
|
34
35
|
let $files = _index.default.map(files, (file, index) => {
|
|
35
36
|
let cls = `file-attachment file-attachment--${byType}`;
|
|
37
|
+
if (this.props.sending) cls += ' file-attachment--sending';
|
|
36
38
|
if (file._true_idx === this.state.focusedIdx) cls += ` just-focused`;
|
|
37
39
|
return (0, _preact.h)("div", {
|
|
38
40
|
key: index,
|
|
39
|
-
class: cls
|
|
41
|
+
class: cls,
|
|
42
|
+
"aria-busy": !!this.props.sending
|
|
40
43
|
}, (0, _preact.h)("a", {
|
|
41
44
|
class: "file-attachment--link",
|
|
42
|
-
href: this.props.store.replaceFileUrl(file.url),
|
|
45
|
+
href: this.props.sending ? undefined : this.props.store.replaceFileUrl(file.url),
|
|
43
46
|
target: "_blank"
|
|
44
47
|
}, (0, _preact.h)("span", {
|
|
45
48
|
class: "file-attachment--download mr-2"
|
|
46
49
|
}), (0, _preact.h)("span", {
|
|
47
50
|
class: "file-attachment--filename"
|
|
48
|
-
}, file.name)))
|
|
51
|
+
}, file.name)), this.props.sending && (0, _preact.h)(_UploadProgress.default, {
|
|
52
|
+
attachment: file,
|
|
53
|
+
compact: true
|
|
54
|
+
}));
|
|
49
55
|
});
|
|
50
56
|
return (0, _preact.h)("div", {
|
|
51
57
|
class: `message-${byType}`
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
|
+
var _UploadProgress = _interopRequireDefault(require("./UploadProgress.js"));
|
|
7
8
|
var _index = _interopRequireDefault(require("../util/index.js"));
|
|
8
9
|
var _common = require("../common.js");
|
|
9
10
|
var _preact = require("preact");
|
|
@@ -95,18 +96,25 @@ class ImageAttachment extends _common.Com {
|
|
|
95
96
|
if (byType !== 'user') byType = 'agent';
|
|
96
97
|
let cls = `image-attachment image-attachment--${byType}`;
|
|
97
98
|
if (image._true_idx === this.state.focusedIdx) cls += ' just-focused';
|
|
99
|
+
const $sending = this.props.sending ? (0, _preact.h)("span", {
|
|
100
|
+
class: "image-attachment__sending"
|
|
101
|
+
}, (0, _preact.h)(_UploadProgress.default, {
|
|
102
|
+
attachment: image
|
|
103
|
+
})) : null;
|
|
98
104
|
if (!image.width || !image.height) {
|
|
99
105
|
return (0, _preact.h)("div", {
|
|
100
106
|
class: cls
|
|
101
107
|
}, (0, _preact.h)("a", {
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
class: "image-attachment__preview",
|
|
109
|
+
href: this.props.sending ? undefined : this.props.store.replaceFileUrl(image.url),
|
|
110
|
+
target: "_blank",
|
|
111
|
+
"aria-busy": !!this.props.sending
|
|
104
112
|
}, (0, _preact.h)("img", {
|
|
105
113
|
alt: true,
|
|
106
114
|
class: "image-attachment__img",
|
|
107
115
|
src: this.props.store.replaceFileUrl(image.thumbnail_url || image.url),
|
|
108
116
|
onload: e => this.props.onLoad && this.props.onLoad()
|
|
109
|
-
})));
|
|
117
|
+
}), $sending));
|
|
110
118
|
}
|
|
111
119
|
let {
|
|
112
120
|
width,
|
|
@@ -121,8 +129,10 @@ class ImageAttachment extends _common.Com {
|
|
|
121
129
|
return (0, _preact.h)("div", {
|
|
122
130
|
class: cls
|
|
123
131
|
}, (0, _preact.h)("a", {
|
|
124
|
-
|
|
125
|
-
|
|
132
|
+
class: "image-attachment__preview",
|
|
133
|
+
href: this.props.sending ? undefined : trueUrl,
|
|
134
|
+
target: "_blank",
|
|
135
|
+
"aria-busy": !!this.props.sending
|
|
126
136
|
}, (0, _preact.h)("img", {
|
|
127
137
|
alt: true,
|
|
128
138
|
ref: el => {
|
|
@@ -135,7 +145,7 @@ class ImageAttachment extends _common.Com {
|
|
|
135
145
|
src: url,
|
|
136
146
|
onload: e => this.props.onLoad && this.props.onLoad(),
|
|
137
147
|
onerror: e => this.onErrorImage(index)
|
|
138
|
-
})));
|
|
148
|
+
}), $sending));
|
|
139
149
|
}
|
|
140
150
|
render() {
|
|
141
151
|
var attachments = _index.default.get(this.props.ev, 'data.message.attachments', []);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.UploadingFile = UploadingFile;
|
|
7
|
+
exports.default = UploadProgress;
|
|
8
|
+
var _preact = require("preact");
|
|
9
|
+
function UploadProgress({
|
|
10
|
+
attachment,
|
|
11
|
+
compact = false
|
|
12
|
+
}) {
|
|
13
|
+
const progress = attachment._upload?.putDone || attachment._upload?.file ? 100 : attachment._upload?.progress ?? 0;
|
|
14
|
+
return (0, _preact.h)("span", {
|
|
15
|
+
class: 'image-attachment__progress' + (compact ? ' file-attachment__progress' : '')
|
|
16
|
+
}, (0, _preact.h)("span", {
|
|
17
|
+
class: "image-attachment__progress-label"
|
|
18
|
+
}, progress, "%"), (0, _preact.h)("span", {
|
|
19
|
+
class: "image-attachment__progress-track",
|
|
20
|
+
role: "progressbar",
|
|
21
|
+
"aria-label": attachment.name || 'Uploading file',
|
|
22
|
+
"aria-valuemin": 0,
|
|
23
|
+
"aria-valuemax": 100,
|
|
24
|
+
"aria-valuenow": progress
|
|
25
|
+
}, (0, _preact.h)("span", {
|
|
26
|
+
class: "image-attachment__progress-fill",
|
|
27
|
+
ref: el => {
|
|
28
|
+
if (el) el.style.setProperty('width', `${progress}%`, 'important');
|
|
29
|
+
}
|
|
30
|
+
})));
|
|
31
|
+
}
|
|
32
|
+
function UploadingFile({
|
|
33
|
+
attachment
|
|
34
|
+
}) {
|
|
35
|
+
return (0, _preact.h)("div", {
|
|
36
|
+
class: "file-attachment file-attachment--user file-attachment--sending",
|
|
37
|
+
"aria-busy": "true"
|
|
38
|
+
}, (0, _preact.h)("span", {
|
|
39
|
+
class: "file-attachment--filename"
|
|
40
|
+
}, attachment.name), (0, _preact.h)(UploadProgress, {
|
|
41
|
+
attachment: attachment,
|
|
42
|
+
compact: true
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
|
+
var _UploadProgress = require("./UploadProgress.js");
|
|
7
8
|
var _index = _interopRequireDefault(require("../util/index.js"));
|
|
8
9
|
var _common = require("../common.js");
|
|
9
10
|
var _preact = require("preact");
|
|
@@ -22,6 +23,10 @@ class VideoAttachments extends _common.Com {
|
|
|
22
23
|
});
|
|
23
24
|
if (videos.length === 0) return null;
|
|
24
25
|
var $videos = _index.default.map(videos, (video, index) => {
|
|
26
|
+
if (this.props.sending) return (0, _preact.h)(_UploadProgress.UploadingFile, {
|
|
27
|
+
key: index,
|
|
28
|
+
attachment: video
|
|
29
|
+
});
|
|
25
30
|
return (0, _preact.h)("div", {
|
|
26
31
|
key: index,
|
|
27
32
|
class: `video-attachment video-attachment--${byType}`
|
|
@@ -84,89 +84,88 @@ class _default {
|
|
|
84
84
|
}));
|
|
85
85
|
_defineProperty(this, "sendTyping", cid => this.apireq.post(`/accounts/${this.accid}/conversations/${cid}/typing`));
|
|
86
86
|
_defineProperty(this, "sendLostFocus", () => this.apireq.post(`/accounts/${this.accid}/focuses/${this.uid}/delete`));
|
|
87
|
-
_defineProperty(this, "
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
body
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
let signedurl = body.signed_url;
|
|
105
|
-
let fileid = body.id;
|
|
106
|
-
let err = await new Promise(resolve => {
|
|
107
|
-
const xhr = new _index.default.XMLHttpRequest();
|
|
108
|
-
xhr.open('PUT', signedurl, true);
|
|
109
|
-
xhr.setRequestHeader('Content-Type', file.type || '');
|
|
110
|
-
xhr.onload = function () {
|
|
111
|
-
if (xhr.status === 200) {
|
|
112
|
-
resolve();
|
|
113
|
-
} else {
|
|
114
|
-
resolve(new Error(`Upload failed: ${xhr.statusText}`));
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
xhr.onerror = function () {
|
|
118
|
-
resolve(new Error('Network error during upload.'));
|
|
119
|
-
};
|
|
120
|
-
xhr.send(file);
|
|
121
|
-
});
|
|
122
|
-
if (err) return {
|
|
123
|
-
error: err
|
|
124
|
-
};
|
|
125
|
-
var {
|
|
126
|
-
body,
|
|
127
|
-
error
|
|
128
|
-
} = await this.apireq.setBaseUrl('https://api5.subiz.com.vn/4.1').setQuery({
|
|
129
|
-
'account-id': this.accid
|
|
130
|
-
}).post(`/files/${fileid}/uploaded`);
|
|
131
|
-
if (error) return {
|
|
132
|
-
error
|
|
87
|
+
_defineProperty(this, "attachmentRequest", () => this.apireq.setBaseUrl(new URL('/4.1', this.ApiURL).href).addQuery('x-account-id', this.accid));
|
|
88
|
+
_defineProperty(this, "attachmentJSON", async (path, body, method = 'post') => {
|
|
89
|
+
const result = await this.attachmentRequest()[method](path, body);
|
|
90
|
+
if (result.error || result.body?.error || result.code < 200 || result.code >= 300) {
|
|
91
|
+
throw Object.assign(new Error('Attachment request failed'), {
|
|
92
|
+
code: result.body?.error?.code,
|
|
93
|
+
response: result
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return result.body;
|
|
97
|
+
});
|
|
98
|
+
_defineProperty(this, "uploadAttachment", async (file, state = {}, onProgress = () => {}) => {
|
|
99
|
+
if (file.size > 25000000) return {
|
|
100
|
+
error: 'invalid_field_size',
|
|
101
|
+
body: {
|
|
102
|
+
code: 'invalid_field_size'
|
|
103
|
+
}
|
|
133
104
|
};
|
|
134
|
-
|
|
135
|
-
|
|
105
|
+
const identity = JSON.stringify([this.accid, this.uid, this.ref, this.mask, this.profile_id, this.tokensession]);
|
|
106
|
+
if (state.identity && state.identity !== identity) throw new Error('Upload identity changed');
|
|
107
|
+
state.identity = identity;
|
|
108
|
+
const checkIdentity = () => {
|
|
109
|
+
if (identity !== JSON.stringify([this.accid, this.uid, this.ref, this.mask, this.profile_id, this.tokensession])) throw new Error('Upload identity changed');
|
|
136
110
|
};
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
name: file.name
|
|
111
|
+
if (state.file) return state.file;
|
|
112
|
+
if (state.upload && !state.putDone && state.upload.upload_expires_at <= _index.default.Date.now()) {
|
|
113
|
+
delete state.upload;
|
|
114
|
+
delete state.key;
|
|
115
|
+
}
|
|
116
|
+
if (!state.key) state.key = _index.default.randomString(36);
|
|
117
|
+
if (!state.metadata) state.metadata = {
|
|
118
|
+
category: 'attachment',
|
|
119
|
+
name: file.name,
|
|
120
|
+
content_type: file.type || 'application/octet-stream',
|
|
146
121
|
size: file.size,
|
|
147
|
-
|
|
148
|
-
}).setParser('json').beforeHook((param, done) => {
|
|
149
|
-
if (this.token_generator) {
|
|
150
|
-
let user_id = this.profile_id;
|
|
151
|
-
this.token_generator().then(token => {
|
|
152
|
-
if (user_id != this.profile_id) return done(true);
|
|
153
|
-
if (!token) return done(false);
|
|
154
|
-
if (token == 'error') return done(false);
|
|
155
|
-
param.request = param.request.addQuery('x-access-token', token);
|
|
156
|
-
return done(true);
|
|
157
|
-
});
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
if (this.ref) param.request = param.request.addQuery('x-user-ref', this.ref);else if (this.mask) param.request = param.request.addQuery('x-user-mask', this.mask);
|
|
161
|
-
return done(true);
|
|
162
|
-
}).post('/files', file);
|
|
163
|
-
if (error) return {
|
|
164
|
-
body,
|
|
165
|
-
error
|
|
122
|
+
ttl_seconds: 0
|
|
166
123
|
};
|
|
167
|
-
|
|
168
|
-
|
|
124
|
+
if (!state.upload) {
|
|
125
|
+
const created = await this.attachmentJSON('/uploads', {
|
|
126
|
+
...state.metadata,
|
|
127
|
+
idempotency_key: state.key
|
|
128
|
+
});
|
|
129
|
+
checkIdentity();
|
|
130
|
+
if (!created.upload?.upload_id || !created.upload.upload_url) throw new Error('Missing upload session');
|
|
131
|
+
state.upload = created.upload;
|
|
132
|
+
}
|
|
133
|
+
if (!state.putDone) {
|
|
134
|
+
checkIdentity();
|
|
135
|
+
const report = percent => {
|
|
136
|
+
if (state.progress === percent) return;
|
|
137
|
+
state.progress = percent;
|
|
138
|
+
onProgress(percent);
|
|
139
|
+
};
|
|
140
|
+
report(0);
|
|
141
|
+
await new Promise((resolve, reject) => {
|
|
142
|
+
const xhr = new _index.default.XMLHttpRequest();
|
|
143
|
+
xhr.upload.onprogress = event => {
|
|
144
|
+
if (event.lengthComputable) report(Math.min(100, Math.floor(event.loaded / event.total * 100)));
|
|
145
|
+
};
|
|
146
|
+
xhr.open('PUT', state.upload.upload_url, true);
|
|
147
|
+
xhr.withCredentials = false;
|
|
148
|
+
Object.entries(state.upload.required_headers || {}).forEach(([key, value]) => xhr.setRequestHeader(key, value));
|
|
149
|
+
xhr.onload = () => xhr.status >= 200 && xhr.status < 300 ? resolve() : reject(new Error(`Upload failed: HTTP ${xhr.status}`));
|
|
150
|
+
xhr.onerror = () => reject(new Error('Network error during upload'));
|
|
151
|
+
xhr.onabort = () => reject(new Error('Upload aborted'));
|
|
152
|
+
xhr.ontimeout = () => reject(new Error('Upload timed out'));
|
|
153
|
+
xhr.send(file);
|
|
154
|
+
});
|
|
155
|
+
report(100);
|
|
156
|
+
state.putDone = true;
|
|
157
|
+
}
|
|
158
|
+
checkIdentity();
|
|
159
|
+
const finished = await this.attachmentJSON(`/uploads/${encodeURIComponent(state.upload.upload_id)}/finish`);
|
|
160
|
+
checkIdentity();
|
|
161
|
+
const f = finished.file;
|
|
162
|
+
if (!f?.id || !f.url || f.error_code) throw new Error('File is not available');
|
|
163
|
+
state.file = {
|
|
164
|
+
url: f.url,
|
|
165
|
+
v: 'cdn',
|
|
166
|
+
file_id: f.id
|
|
169
167
|
};
|
|
168
|
+
return state.file;
|
|
170
169
|
});
|
|
171
170
|
_defineProperty(this, "time", _ => ajax.setBaseUrl(this.ApiURL).withCredentials(true).setParser('json').get('/time'));
|
|
172
171
|
_defineProperty(this, "updateUser", data => this.apireq.post(`/accounts/${this.accid}/users/${this.uid}`, data));
|
|
@@ -55,7 +55,7 @@ class _default {
|
|
|
55
55
|
_defineProperty(this, "sendContent", data => OK200);
|
|
56
56
|
_defineProperty(this, "sendLostFocus", data => OK200);
|
|
57
57
|
_defineProperty(this, "sendContentView", data => OK200);
|
|
58
|
-
_defineProperty(this, "
|
|
58
|
+
_defineProperty(this, "uploadAttachment", async file => OK200);
|
|
59
59
|
_defineProperty(this, "time", _ => Promise.resolve({
|
|
60
60
|
code: 200,
|
|
61
61
|
body: new _index.default.Date().toISOString()
|
|
@@ -8,7 +8,7 @@ async function uploadAttachments(attachments, upload) {
|
|
|
8
8
|
let error, errorMessage;
|
|
9
9
|
for (const attachment of attachments) {
|
|
10
10
|
try {
|
|
11
|
-
const result = await upload(attachment._file);
|
|
11
|
+
const result = await upload(attachment._file, attachment._upload || (attachment._upload = {}));
|
|
12
12
|
if (result.body && result.body.code === 'invalid_field_size') {
|
|
13
13
|
errorMessage = 'cannot_send_too_big_file';
|
|
14
14
|
}
|
|
@@ -17,6 +17,10 @@ async function uploadAttachments(attachments, upload) {
|
|
|
17
17
|
continue;
|
|
18
18
|
}
|
|
19
19
|
attachment.url = result.url;
|
|
20
|
+
if (result.file_id) {
|
|
21
|
+
attachment.v = 'cdn';
|
|
22
|
+
attachment.file_id = result.file_id;
|
|
23
|
+
}
|
|
20
24
|
} catch (err) {
|
|
21
25
|
error = error || err;
|
|
22
26
|
}
|