vgapp 1.2.0 → 1.2.2
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/app/modules/vgfiles/js/vgfiles.js +63 -29
- package/app/modules/vgformsender/js/hideshowpass.js +16 -11
- package/app/modules/vgformsender/js/vgformsender.js +146 -84
- package/app/modules/vgformsender/scss/vgformsender.scss +34 -10
- package/app/modules/vgtoast/js/vgtoast.js +8 -7
- package/build/vgapp.css +1 -1
- package/build/vgapp.css.map +1 -1
- package/package.json +1 -1
|
@@ -51,11 +51,12 @@ class VGFiles extends VGFilesBase {
|
|
|
51
51
|
maxConcurrent: 1,
|
|
52
52
|
retryAttempts: 1,
|
|
53
53
|
retryDelay: 1000,
|
|
54
|
-
},
|
|
55
|
-
removes: {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
},
|
|
55
|
+
removes: {
|
|
56
|
+
buttons: null,
|
|
57
|
+
all: { route: '', alert: true, toast: true, confirm: null, buttons: null },
|
|
58
|
+
single: { route: '', alert: true, toast: true, confirm: null, buttons: null }
|
|
59
|
+
},
|
|
59
60
|
sortable: {
|
|
60
61
|
enabled: false,
|
|
61
62
|
route: '',
|
|
@@ -585,11 +586,11 @@ class VGFiles extends VGFilesBase {
|
|
|
585
586
|
return { accepted: Boolean(result), data: null };
|
|
586
587
|
}
|
|
587
588
|
|
|
588
|
-
_runDefaultRemoveConfirm(trigger, params) {
|
|
589
|
-
return new Promise((resolve) => {
|
|
590
|
-
VGAlert.confirm(trigger, {
|
|
591
|
-
lang: params.lang,
|
|
592
|
-
ajax: params.ajax,
|
|
589
|
+
_runDefaultRemoveConfirm(trigger, params) {
|
|
590
|
+
return new Promise((resolve) => {
|
|
591
|
+
VGAlert.confirm(trigger, {
|
|
592
|
+
lang: params.lang,
|
|
593
|
+
ajax: params.ajax,
|
|
593
594
|
buttons: params.buttons,
|
|
594
595
|
message: params.message
|
|
595
596
|
});
|
|
@@ -600,25 +601,58 @@ class VGFiles extends VGFilesBase {
|
|
|
600
601
|
|
|
601
602
|
EventHandler.one(trigger, 'vg.alert.reject', () => {
|
|
602
603
|
resolve({ accepted: false, data: null });
|
|
603
|
-
});
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
_isPlainObject(value) {
|
|
609
|
+
return value && typeof value === 'object' && !Array.isArray(value);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
_mergeButtonConfigs(...configs) {
|
|
613
|
+
return configs.reduce((acc, config) => {
|
|
614
|
+
if (!this._isPlainObject(config)) return acc;
|
|
615
|
+
|
|
616
|
+
Object.entries(config).forEach(([key, value]) => {
|
|
617
|
+
if (this._isPlainObject(acc[key]) && this._isPlainObject(value)) {
|
|
618
|
+
acc[key] = this._mergeButtonConfigs(acc[key], value);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
acc[key] = Array.isArray(value) ? value.slice() : value;
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
return acc;
|
|
626
|
+
}, {});
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
_getDefaultRemoveConfirmButtons() {
|
|
630
|
+
return {
|
|
631
|
+
agree: {
|
|
632
|
+
text: lang_buttons(this._params.lang, NAME)['agree'],
|
|
633
|
+
class: ["btn-danger"],
|
|
634
|
+
},
|
|
635
|
+
cancel: {
|
|
636
|
+
text: lang_buttons(this._params.lang, NAME)['cancel'],
|
|
637
|
+
class: ["btn-outline-danger"],
|
|
638
|
+
},
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
_getRemoveConfirmButtons(type) {
|
|
643
|
+
return this._mergeButtonConfigs(
|
|
644
|
+
this._getDefaultRemoveConfirmButtons(),
|
|
645
|
+
this._params?.removes?.buttons,
|
|
646
|
+
this._params?.removes?.[type]?.buttons
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
_confirmRemove(type, trigger, ajax, message) {
|
|
651
|
+
const buttons = this._getRemoveConfirmButtons(type);
|
|
652
|
+
|
|
653
|
+
const confirmParams = {
|
|
654
|
+
type,
|
|
655
|
+
trigger,
|
|
622
656
|
lang: this._params.lang,
|
|
623
657
|
ajax,
|
|
624
658
|
buttons,
|
|
@@ -17,10 +17,12 @@ const CLASS_NAME_SHOW = 'show';
|
|
|
17
17
|
const EVENT_KEY_CLICK_DATA_API = `click.${NAME_KEY}.data.api`;
|
|
18
18
|
|
|
19
19
|
class VGHideShowPass extends BaseModule{
|
|
20
|
-
constructor(el, params = {}) {
|
|
21
|
-
super(el, params);
|
|
22
|
-
|
|
23
|
-
this._params = this._getParams(el, mergeDeepObject({
|
|
20
|
+
constructor(el, params = {}) {
|
|
21
|
+
super(el, params);
|
|
22
|
+
|
|
23
|
+
this._params = this._getParams(el, mergeDeepObject({
|
|
24
|
+
parentClass: 'vg-form-sender--content-pass'
|
|
25
|
+
}, params));
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
static get NAME() {
|
|
@@ -60,12 +62,15 @@ class VGHideShowPass extends BaseModule{
|
|
|
60
62
|
instance.build(false);
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
build(isShow = false) {
|
|
64
|
-
let classes = this._params.classes.join(' '), elm = '';
|
|
65
|
-
const HTML = Html('string');
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
build(isShow = false) {
|
|
66
|
+
let classes = this._params.classes.join(' '), elm = '';
|
|
67
|
+
const HTML = Html('string');
|
|
68
|
+
if (this._element.parentElement && this._params.parentClass) {
|
|
69
|
+
this._element.parentElement.classList.add(this._params.parentClass);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!isShow) {
|
|
73
|
+
elm = HTML.component('eye', {class: classes});
|
|
69
74
|
} else {
|
|
70
75
|
elm = HTML.component('eye', {class: classes, type: 'hide'});
|
|
71
76
|
}
|
|
@@ -95,4 +100,4 @@ EventHandler.on(document, EVENT_KEY_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, functi
|
|
|
95
100
|
instance.toggle(this);
|
|
96
101
|
});
|
|
97
102
|
|
|
98
|
-
export default VGHideShowPass
|
|
103
|
+
export default VGHideShowPass
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
2
|
* VGFormSender Module
|
|
3
3
|
*
|
|
4
4
|
* Этот модуль отвечает за отправку форм с поддержкой AJAX, валидации, отображения уведомлений
|
|
@@ -66,7 +66,8 @@ const NAME_KEY = 'vg.fs';
|
|
|
66
66
|
* CSS-класс для алертов
|
|
67
67
|
* @type {string}
|
|
68
68
|
*/
|
|
69
|
-
const CLASS_NAME_ALERT = 'vg-form-sender-alert';
|
|
69
|
+
const CLASS_NAME_ALERT = 'vg-form-sender-alert';
|
|
70
|
+
const CLASS_NAME_MODAL_STACKED = 'vg-modal-stacked';
|
|
70
71
|
|
|
71
72
|
/**
|
|
72
73
|
* Событие: успешная отправка формы
|
|
@@ -130,13 +131,19 @@ class VGFormSender extends BaseModule {
|
|
|
130
131
|
classes: ['vg-form-sender--hide-show-pass'],
|
|
131
132
|
insert: 'afterend'
|
|
132
133
|
},
|
|
133
|
-
alert: {
|
|
134
|
-
enabled: true,
|
|
135
|
-
type: 'modal',
|
|
136
|
-
errors: true,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
alert: {
|
|
135
|
+
enabled: true,
|
|
136
|
+
type: 'modal',
|
|
137
|
+
errors: true,
|
|
138
|
+
title: true,
|
|
139
|
+
delay: 0,
|
|
140
|
+
modal: {
|
|
141
|
+
closeModalsBeforeModal: true
|
|
142
|
+
},
|
|
143
|
+
toast: {
|
|
144
|
+
closeModalsBeforeToast: false
|
|
145
|
+
}
|
|
146
|
+
},
|
|
140
147
|
ajax: {
|
|
141
148
|
route: '',
|
|
142
149
|
target: '',
|
|
@@ -524,59 +531,107 @@ class VGFormSender extends BaseModule {
|
|
|
524
531
|
* @param {string} status - Статус (success/error)
|
|
525
532
|
* @private
|
|
526
533
|
*/
|
|
527
|
-
_alertModal(data, status) {
|
|
528
|
-
const _this = this;
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
534
|
+
_alertModal(data, status) {
|
|
535
|
+
const _this = this;
|
|
536
|
+
const closeModalsBeforeModal = _this._params.alert.modal?.closeModalsBeforeModal;
|
|
537
|
+
const isStackedModal = !closeModalsBeforeModal && _this._hasOpenedModals();
|
|
538
|
+
|
|
539
|
+
if (closeModalsBeforeModal) {
|
|
540
|
+
_this._closeOpenedModals();
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
let id = _this._params.classes.general + '-' + makeRandomString(),
|
|
544
|
+
$modal = Selectors.find('.' + _this._params.classes.alertModal);
|
|
545
|
+
|
|
546
|
+
if ($modal) $modal.remove();
|
|
547
|
+
|
|
548
|
+
setTimeout(() => {
|
|
549
|
+
VGModal.init(id, {
|
|
550
|
+
dismiss: true,
|
|
551
|
+
classes: {
|
|
552
|
+
alert: _this._params.classes.alertModal
|
|
553
|
+
}
|
|
554
|
+
}, function (self) {
|
|
555
|
+
let element = self._element;
|
|
556
|
+
element.classList.add(_this._params.classes.alertModal);
|
|
557
|
+
if (isStackedModal) {
|
|
558
|
+
_this._setStackedModalState(element);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
let $content = Selectors.find('.vg-modal-content', element);
|
|
562
|
+
if ($content) $content.classList.add(CLASS_NAME_ALERT, CLASS_NAME_ALERT + '-' + status);
|
|
563
|
+
|
|
564
|
+
let $body = Selectors.find('.vg-modal-body', element);
|
|
565
|
+
if ($body) $body.append(_this.setDataRelationStatus(element, status, data, 'modal'));
|
|
566
|
+
|
|
567
|
+
self.toggle();
|
|
568
|
+
|
|
569
|
+
if (_this._params.alert.delay > 0) {
|
|
570
|
+
setTimeout(() => {
|
|
571
|
+
self.hide();
|
|
572
|
+
}, _this._params.alert.delay)
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
}, _this._params.timeout);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Проверка наличия открытых модальных окон
|
|
580
|
+
* @returns {boolean}
|
|
581
|
+
* @private
|
|
582
|
+
*/
|
|
583
|
+
_hasOpenedModals() {
|
|
584
|
+
return Boolean(Selectors.find('.modal.show, .vg-modal.show'));
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Помечает alert-modal как вторую модалку и поднимает ее над уже открытыми
|
|
589
|
+
* @param {HTMLElement} element
|
|
590
|
+
* @private
|
|
591
|
+
*/
|
|
592
|
+
_setStackedModalState(element) {
|
|
593
|
+
if (!element) return;
|
|
594
|
+
|
|
595
|
+
element.classList.add(CLASS_NAME_MODAL_STACKED);
|
|
596
|
+
element.style.zIndex = this._getNextModalZIndex();
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Вычисление z-index выше всех открытых модалок
|
|
601
|
+
* @returns {string}
|
|
602
|
+
* @private
|
|
603
|
+
*/
|
|
604
|
+
_getNextModalZIndex() {
|
|
605
|
+
const indexes = Selectors.findAll('.modal.show, .vg-modal.show')
|
|
606
|
+
.map((element) => Number.parseInt(window.getComputedStyle(element).zIndex, 10))
|
|
607
|
+
.filter((index) => Number.isFinite(index));
|
|
608
|
+
|
|
609
|
+
return String((indexes.length ? Math.max(...indexes) : 1040) + 10);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Закрытие всех открытых модальных окон
|
|
614
|
+
* @private
|
|
615
|
+
*/
|
|
616
|
+
_closeOpenedModals() {
|
|
617
|
+
[...document.getElementsByClassName('modal')].forEach((element) => {
|
|
618
|
+
if (element && element.classList.contains('show')) {
|
|
619
|
+
if (typeof bootstrap !== 'undefined' && bootstrap.Modal?.getOrCreateInstance) {
|
|
620
|
+
const mBS = bootstrap.Modal.getOrCreateInstance(element);
|
|
621
|
+
mBS.hide();
|
|
622
|
+
} else {
|
|
623
|
+
console.warn(lang_messages(this._params.lang, NAME).bootstrap_not_found)
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
[...document.getElementsByClassName('vg-modal')].forEach((element) => {
|
|
629
|
+
if (element && element.classList.contains('show')) {
|
|
630
|
+
const mVG = VGModal.getOrCreateInstance(element);
|
|
631
|
+
mVG.hide([mVG]);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
580
635
|
|
|
581
636
|
/**
|
|
582
637
|
* Показ алерта в виде collapse
|
|
@@ -614,13 +669,17 @@ class VGFormSender extends BaseModule {
|
|
|
614
669
|
* @param {string} status - Статус (success/error)
|
|
615
670
|
* @private
|
|
616
671
|
*/
|
|
617
|
-
_alertToast(data, status) {
|
|
618
|
-
const response = this._prepareAlertResponse(status, data);
|
|
619
|
-
const toastParams = this._getToastParams(status);
|
|
620
|
-
|
|
621
|
-
if (
|
|
622
|
-
|
|
623
|
-
|
|
672
|
+
_alertToast(data, status) {
|
|
673
|
+
const response = this._prepareAlertResponse(status, data);
|
|
674
|
+
const toastParams = this._getToastParams(status);
|
|
675
|
+
|
|
676
|
+
if (this._params.alert.toast?.closeModalsBeforeToast) {
|
|
677
|
+
this._closeOpenedModals();
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (response.title) {
|
|
681
|
+
VGToast.run([response.title, response.message], toastParams);
|
|
682
|
+
return;
|
|
624
683
|
}
|
|
625
684
|
|
|
626
685
|
VGToast.run(response.message, toastParams);
|
|
@@ -634,9 +693,11 @@ class VGFormSender extends BaseModule {
|
|
|
634
693
|
*/
|
|
635
694
|
_getToastParams(status) {
|
|
636
695
|
const theme = status === 'error' ? 'danger' : status;
|
|
637
|
-
const
|
|
638
|
-
const
|
|
639
|
-
const
|
|
696
|
+
const toastType = status === 'danger' ? 'error' : status;
|
|
697
|
+
const delay = this._params.alert.delay > 0 ? this._params.alert.delay : 3000;
|
|
698
|
+
const flatToastParams = {};
|
|
699
|
+
const {closeModalsBeforeToast, ...toastParams} = this._params.alert.toast || {};
|
|
700
|
+
const allowedKeys = [
|
|
640
701
|
'static',
|
|
641
702
|
'placement',
|
|
642
703
|
'autohide',
|
|
@@ -645,7 +706,6 @@ class VGFormSender extends BaseModule {
|
|
|
645
706
|
'enableButtonClose',
|
|
646
707
|
'keyboard',
|
|
647
708
|
'theme',
|
|
648
|
-
'type',
|
|
649
709
|
'drag',
|
|
650
710
|
'resize',
|
|
651
711
|
'stack',
|
|
@@ -659,13 +719,14 @@ class VGFormSender extends BaseModule {
|
|
|
659
719
|
}
|
|
660
720
|
});
|
|
661
721
|
|
|
662
|
-
return mergeDeepObject({
|
|
663
|
-
theme: theme || 'dark',
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
722
|
+
return mergeDeepObject({
|
|
723
|
+
theme: theme || 'dark',
|
|
724
|
+
type: toastType || null,
|
|
725
|
+
enableButtonClose: true,
|
|
726
|
+
autohide: this._params.alert.delay > 0,
|
|
727
|
+
delay: delay
|
|
728
|
+
}, flatToastParams, toastParams);
|
|
729
|
+
}
|
|
669
730
|
|
|
670
731
|
/**
|
|
671
732
|
* Формирование содержимого алерта (заголовок, текст, иконка)
|
|
@@ -771,10 +832,10 @@ class VGFormSender extends BaseModule {
|
|
|
771
832
|
}, response.message);
|
|
772
833
|
}
|
|
773
834
|
|
|
774
|
-
return {
|
|
775
|
-
title: response.title || '',
|
|
776
|
-
message: message
|
|
777
|
-
};
|
|
835
|
+
return {
|
|
836
|
+
title: this._params.alert.title ? response.title || '' : '',
|
|
837
|
+
message: message
|
|
838
|
+
};
|
|
778
839
|
}
|
|
779
840
|
}
|
|
780
841
|
|
|
@@ -873,3 +934,4 @@ EventHandler.on(document, EVENT_SUBMIT_DATA_API, function (event) {
|
|
|
873
934
|
})
|
|
874
935
|
|
|
875
936
|
export default VGFormSender;
|
|
937
|
+
|
|
@@ -20,9 +20,21 @@
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
&--content {
|
|
24
|
-
position: relative;
|
|
25
|
-
|
|
23
|
+
&--content {
|
|
24
|
+
position: relative;
|
|
25
|
+
|
|
26
|
+
&-pass {
|
|
27
|
+
&:focus-within {
|
|
28
|
+
input {
|
|
29
|
+
padding-right: 5rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.vg-form-sender--hide-show-pass {
|
|
33
|
+
right: 44px;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
26
38
|
|
|
27
39
|
&--hide-show-pass {
|
|
28
40
|
position: absolute;
|
|
@@ -40,12 +52,24 @@
|
|
|
40
52
|
}
|
|
41
53
|
}
|
|
42
54
|
|
|
43
|
-
&.was-validated {
|
|
44
|
-
.vg-form-sender--hide-show-pass {
|
|
45
|
-
right: 34px;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
&.was-validated {
|
|
56
|
+
.vg-form-sender--hide-show-pass {
|
|
57
|
+
right: 34px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.vg-form-sender--content-pass {
|
|
61
|
+
&:focus-within {
|
|
62
|
+
input {
|
|
63
|
+
padding-right: 6.25rem;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.vg-form-sender--hide-show-pass {
|
|
67
|
+
right: 64px;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
49
73
|
|
|
50
74
|
.vg-form-sender-alert {
|
|
51
75
|
@include mix-alert-color-mode($class: vg-form-sender-alert);
|
|
@@ -102,4 +126,4 @@
|
|
|
102
126
|
.vg-form-sender-alert-content--text {
|
|
103
127
|
font-size: 1.25rem;
|
|
104
128
|
color: var(--vg-form-sender-alert-color);
|
|
105
|
-
}
|
|
129
|
+
}
|
|
@@ -289,13 +289,14 @@ class VGToast extends BaseModule {
|
|
|
289
289
|
|
|
290
290
|
wrapper.append(content);
|
|
291
291
|
|
|
292
|
-
// Кнопка закрытия
|
|
293
|
-
if (params.enableButtonClose) {
|
|
294
|
-
|
|
295
|
-
button.
|
|
296
|
-
button.
|
|
297
|
-
|
|
298
|
-
|
|
292
|
+
// Кнопка закрытия
|
|
293
|
+
if (params.enableButtonClose) {
|
|
294
|
+
target.classList.add('vg-toast-has-button');
|
|
295
|
+
const button = document.createElement('div');
|
|
296
|
+
button.classList.add('vg-toast-button');
|
|
297
|
+
button.innerHTML = '<button class="vg-btn-close" data-vg-dismiss="toast"></button>';
|
|
298
|
+
wrapper.append(button);
|
|
299
|
+
}
|
|
299
300
|
|
|
300
301
|
target.append(wrapper);
|
|
301
302
|
document.body.append(target);
|