@alexsab-ru/scripts 0.0.4 → 0.3.1
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 +74 -24
- package/index.js +2 -0
- package/lib/analytics.js +6 -6
- package/lib/cookie.js +52 -0
- package/lib/form.js +218 -0
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,36 +1,86 @@
|
|
|
1
|
-
# scripts
|
|
2
|
-
common libs for our websites
|
|
1
|
+
# @alexsab-ru/scripts
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
common libs for websites
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
5
6
|
```bash
|
|
6
|
-
pnpm i
|
|
7
|
+
pnpm i @alexsab-ru/scripts
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Analytics module
|
|
11
|
+
|
|
12
|
+
`reachGoal` and `pageView` functions push to `dataLayer` some data with goal name
|
|
13
|
+
```js
|
|
14
|
+
reachGoal("goalName");
|
|
15
|
+
pageView(goalName);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
In GTM you can use them for send goals to your analytic system
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
{
|
|
22
|
+
event: "reachGoal-goalName",
|
|
23
|
+
eventAction: "goalName"
|
|
24
|
+
}
|
|
7
25
|
```
|
|
8
26
|
|
|
9
|
-
|
|
27
|
+
Use example:
|
|
10
28
|
|
|
11
29
|
```js
|
|
12
|
-
import '/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
window.WebsiteAnalytics.dataLayer("form-open");
|
|
22
|
-
window.WebsiteAnalytics.dataLayer("form-submit");
|
|
23
|
-
window.WebsiteAnalytics.dataLayer("form-required");
|
|
24
|
-
window.WebsiteAnalytics.dataLayer("form-submit");
|
|
25
|
-
window.WebsiteAnalytics.dataLayer("form-error");
|
|
30
|
+
import { reachGoal } from '@alexsab-ru/scripts';
|
|
31
|
+
|
|
32
|
+
// automatic assign from module
|
|
33
|
+
reachGoal("phone_click");
|
|
34
|
+
reachGoal("phone_copy");
|
|
35
|
+
reachGoal("phone_contextmenu");
|
|
36
|
+
reachGoal("email_click");
|
|
37
|
+
reachGoal("email_copy");
|
|
38
|
+
reachGoal("email_contextmenu");
|
|
26
39
|
```
|
|
27
40
|
|
|
28
|
-
|
|
41
|
+
For form's analytics you may use these goals
|
|
42
|
+
|
|
29
43
|
```js
|
|
30
|
-
|
|
44
|
+
reachGoal("form_open");
|
|
45
|
+
reachGoal("form_click"); // automatic assign from module
|
|
46
|
+
reachGoal("form_change"); // automatic assign from module
|
|
47
|
+
reachGoal("form_submit");
|
|
48
|
+
reachGoal("form_required");
|
|
49
|
+
reachGoal("form_error");
|
|
50
|
+
reachGoal("form_success");
|
|
31
51
|
```
|
|
32
52
|
|
|
33
|
-
|
|
53
|
+
`getFormDataObject` is needed for Calltouch request tag.
|
|
54
|
+
|
|
34
55
|
```js
|
|
35
|
-
|
|
36
|
-
|
|
56
|
+
import { getFormDataObject } from '@alexsab-ru/scripts';
|
|
57
|
+
|
|
58
|
+
document.querySelectorAll("form").forEach((form) => {
|
|
59
|
+
form.onsubmit = async (event) => {
|
|
60
|
+
|
|
61
|
+
var formData = new FormData(form);
|
|
62
|
+
// ...
|
|
63
|
+
var formDataObj = getFormDataObject(formData, form.id);
|
|
64
|
+
|
|
65
|
+
await fetch("https://example.com/api/lead/", {
|
|
66
|
+
// ...
|
|
67
|
+
})
|
|
68
|
+
.then((res) => res.json())
|
|
69
|
+
.then((data) => {
|
|
70
|
+
if (data.answer == "required") {
|
|
71
|
+
reachGoal("form_required");
|
|
72
|
+
return;
|
|
73
|
+
} else if (data.answer == "error") {
|
|
74
|
+
reachGoal("form_error");
|
|
75
|
+
return;
|
|
76
|
+
} else {
|
|
77
|
+
reachGoal("form_success", formDataObj);
|
|
78
|
+
}
|
|
79
|
+
form.reset();
|
|
80
|
+
})
|
|
81
|
+
.catch((error) => {
|
|
82
|
+
reachGoal("form_error");
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
```
|
package/index.js
CHANGED
package/lib/analytics.js
CHANGED
|
@@ -15,7 +15,7 @@ export function pageView(eventAction, t = {}) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export function dl(event, t = {}) {
|
|
18
|
-
console.log(event, t);
|
|
18
|
+
// console.log(event, t);
|
|
19
19
|
void 0 !== window.dataLayer && window.dataLayer.push({
|
|
20
20
|
event: event,
|
|
21
21
|
...t
|
|
@@ -82,17 +82,17 @@ export function addEmailGoals(item) {
|
|
|
82
82
|
addEmailGoals(tel);
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
let goals = [
|
|
86
86
|
{
|
|
87
87
|
selector: 'form input',
|
|
88
88
|
action: 'click',
|
|
89
|
-
goal: '
|
|
89
|
+
goal: 'form_click',
|
|
90
90
|
title: 'Клик в поле любой формы',
|
|
91
91
|
},
|
|
92
92
|
{
|
|
93
93
|
selector: 'form input',
|
|
94
94
|
action: 'change',
|
|
95
|
-
goal: '
|
|
95
|
+
goal: 'form_change',
|
|
96
96
|
title: 'Изменения полей любой формы',
|
|
97
97
|
},
|
|
98
98
|
|
|
@@ -101,7 +101,7 @@ export function addEmailGoals(item) {
|
|
|
101
101
|
goals.forEach(function(value, index, array){
|
|
102
102
|
if(value.goal != null) {
|
|
103
103
|
document.querySelectorAll(value.selector).forEach(function(element) {
|
|
104
|
-
console.log("Set \"" + value.goal + "\" goal", element);
|
|
104
|
+
// console.log("Set \"" + value.goal + "\" goal", element);
|
|
105
105
|
element.addEventListener(value.action, function(){
|
|
106
106
|
reachGoal(value.goal, {
|
|
107
107
|
title: value.title,
|
|
@@ -110,7 +110,7 @@ export function addEmailGoals(item) {
|
|
|
110
110
|
});
|
|
111
111
|
} else if(value.hit != null) {
|
|
112
112
|
document.querySelectorAll(value.selector).forEach(function(element) {
|
|
113
|
-
console.log("Set \"" + value.goal + "\" hit", element);
|
|
113
|
+
// console.log("Set \"" + value.goal + "\" hit", element);
|
|
114
114
|
element.addEventListener(value.action, function(){
|
|
115
115
|
pageView(value.hit, {
|
|
116
116
|
title: value.title,
|
package/lib/cookie.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export function getCookie(name)
|
|
2
|
+
{
|
|
3
|
+
let matches = document.cookie.match(new RegExp(
|
|
4
|
+
"(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
|
|
5
|
+
));
|
|
6
|
+
return matches ? decodeURIComponent(matches[1]) : undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function setCookie(name, value, props)
|
|
10
|
+
{
|
|
11
|
+
props = props || {}
|
|
12
|
+
var exp = props.expires
|
|
13
|
+
if (typeof exp == "number" && exp) {
|
|
14
|
+
var d = new Date()
|
|
15
|
+
d.setTime(d.getTime() + exp*1000)
|
|
16
|
+
exp = props.expires = d
|
|
17
|
+
}
|
|
18
|
+
if(exp && exp.toUTCString) { props.expires = exp.toUTCString() }
|
|
19
|
+
value = encodeURIComponent(value)
|
|
20
|
+
var updatedCookie = name + "=" + value
|
|
21
|
+
for(var propName in props){
|
|
22
|
+
updatedCookie += "; " + propName
|
|
23
|
+
var propValue = props[propName]
|
|
24
|
+
if(propValue !== true){ updatedCookie += "=" + propValue }
|
|
25
|
+
}
|
|
26
|
+
document.cookie = updatedCookie
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function deleteCookie(name)
|
|
30
|
+
{
|
|
31
|
+
setCookie(name, null, { 'domain':settings.domain,'path':'/','expires': -1 })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function cookiecook(days = 90)
|
|
35
|
+
{
|
|
36
|
+
let cookiecook = getCookie("cookiecook"),
|
|
37
|
+
cookiewin = document.querySelector('.cookie_notice');
|
|
38
|
+
|
|
39
|
+
if (cookiecook != "no") {
|
|
40
|
+
|
|
41
|
+
cookiewin.classList.remove("hidden");
|
|
42
|
+
|
|
43
|
+
document.getElementById("cookie_close").addEventListener("click", function(e){
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
cookiewin.classList.add("hidden");
|
|
46
|
+
|
|
47
|
+
let date = new Date;
|
|
48
|
+
date.setDate(date.getDate() + days);
|
|
49
|
+
document.cookie = "cookiecook=no; path=/; expires=" + date.toUTCString();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
package/lib/form.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { getCookie } from './cookie';
|
|
2
|
+
import { reachGoal, getFormDataObject } from './analytics';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export const noValidPhone = (phoneValue) => {
|
|
6
|
+
return ([...new Set(phoneValue.replace(/^(\+7)/g, "").replace(/\D/g, ""))].length === 1);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function maskphone(e) {
|
|
10
|
+
let num = this.value.replace(/^(\+7|8|7)/g, "").replace(/\D/g, "").split(/(?=.)/),
|
|
11
|
+
i = num.length;
|
|
12
|
+
|
|
13
|
+
if(this.value != "" && this.value != "+") {
|
|
14
|
+
if (0 <= i) num.unshift("+7");
|
|
15
|
+
if (1 <= i) num.splice(1, 0, " ");
|
|
16
|
+
if (4 <= i) num.splice(5, 0, " ");
|
|
17
|
+
if (7 <= i) num.splice(9, 0, "-");
|
|
18
|
+
if (9 <= i) num.splice(12, 0, "-");
|
|
19
|
+
this.value = num.join("");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
export const phoneChecker = (phone) => {
|
|
25
|
+
let form = phone.closest("form"),
|
|
26
|
+
btn = form.querySelector('input[type="submit"]');
|
|
27
|
+
if (!phone.value.length) {
|
|
28
|
+
showErrorMes(form, ".phone", "Телефон является обязательным полем");
|
|
29
|
+
stateBtn(btn, "Отправить");
|
|
30
|
+
return false;
|
|
31
|
+
} else {
|
|
32
|
+
const phoneRe = new RegExp(/^\+7 [0-9]{3} [0-9]{3}-[0-9]{2}-[0-9]{2}$/);
|
|
33
|
+
if (!phoneRe.test(phone.value) || noValidPhone(phone.value)) {
|
|
34
|
+
showErrorMes(form, ".phone", "Введен некорректный номер телефона");
|
|
35
|
+
stateBtn(btn, "Отправить");
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
showErrorMes(form, ".phone", "");
|
|
40
|
+
return true;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
document.querySelectorAll("input[name=phone]").forEach(function (element) {
|
|
44
|
+
// element.addEventListener("focus", maskphone);
|
|
45
|
+
element.addEventListener("input", maskphone);
|
|
46
|
+
element.addEventListener("change", () => phoneChecker(element));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// AGREE CHECKBOX
|
|
50
|
+
// Проверка на состояние чекбокса, показ/скрытие ошибки
|
|
51
|
+
document.querySelectorAll("input[name=agree]").forEach(function (element) {
|
|
52
|
+
let errorMes = element.parentElement.querySelector(".agree");
|
|
53
|
+
element.addEventListener("change", (e) => {
|
|
54
|
+
if (!e.target.checked) {
|
|
55
|
+
errorMes.classList.remove("hidden");
|
|
56
|
+
} else {
|
|
57
|
+
errorMes.classList.add("hidden");
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// TEXTAREA
|
|
63
|
+
const minLengthTextareaField = 10; // минимальное кол-во символов
|
|
64
|
+
// проверка на минимальное кол-во символов и скрытие ошибки
|
|
65
|
+
const checkTextareaLength = (textarea, minLength) => {
|
|
66
|
+
if (textarea.value.length >= minLength) {
|
|
67
|
+
textarea.nextSibling.nextElementSibling.classList.add("hidden");
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// CHANGE textarea для всез браузеров
|
|
72
|
+
document.querySelectorAll("textarea").forEach(function (textarea) {
|
|
73
|
+
if (textarea.addEventListener) {
|
|
74
|
+
textarea.addEventListener(
|
|
75
|
+
"input",
|
|
76
|
+
function () {
|
|
77
|
+
// event handling code for sane browsers
|
|
78
|
+
checkTextareaLength(textarea, minLengthTextareaField);
|
|
79
|
+
},
|
|
80
|
+
false
|
|
81
|
+
);
|
|
82
|
+
} else if (textarea.attachEvent) {
|
|
83
|
+
textarea.attachEvent("onpropertychange", function () {
|
|
84
|
+
// IE-specific event handling code
|
|
85
|
+
checkTextareaLength(textarea, minLengthTextareaField);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// BUTTON
|
|
91
|
+
// Состояние кнопки
|
|
92
|
+
const stateBtn = (btn, value, disable = false) => {
|
|
93
|
+
btn.value = value;
|
|
94
|
+
btn.disabled = disable;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const showErrorMes = (form, el, text) => {
|
|
98
|
+
let field = form.querySelector(el);
|
|
99
|
+
field.innerText = text;
|
|
100
|
+
field.classList.remove("hidden");
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const showMessageModal = (messageModal, icon, message) => {
|
|
104
|
+
document.querySelectorAll(".modal-overlay").forEach((el) => {
|
|
105
|
+
el.classList.add("hidden");
|
|
106
|
+
});
|
|
107
|
+
messageModal.querySelector("#icon").innerHTML = icon;
|
|
108
|
+
messageModal.querySelector("p").innerHTML = message;
|
|
109
|
+
messageModal.classList.remove("hidden");
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
export const connectForms = (url, callback) => {
|
|
114
|
+
|
|
115
|
+
// Отправка всех форм
|
|
116
|
+
document.querySelectorAll("form").forEach((form) => {
|
|
117
|
+
const btn = form.querySelector('input[type="submit"]');
|
|
118
|
+
form.onsubmit = async (event) => {
|
|
119
|
+
event.preventDefault();
|
|
120
|
+
stateBtn(btn, "Отправляем...", true);
|
|
121
|
+
|
|
122
|
+
const agree = form.querySelector('[name="agree"]');
|
|
123
|
+
const phone = form.querySelector('[name="phone"]');
|
|
124
|
+
const errorIcon =
|
|
125
|
+
'<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52"><path fill="#ed1c24" d="M26,0A26,26,0,1,0,52,26,26,26,0,0,0,26,0Zm9.6,17.5a1.94,1.94,0,0,1,2,2,2,2,0,1,1-2-2Zm-19.2,0a1.94,1.94,0,0,1,2,2,2,2,0,1,1-2-2ZM39.65,40.69a.93.93,0,0,1-.45.11,1,1,0,0,1-.89-.55,13.81,13.81,0,0,0-24.62,0,1,1,0,1,1-1.78-.9,15.8,15.8,0,0,1,28.18,0A1,1,0,0,1,39.65,40.69Z"></path></svg>';
|
|
126
|
+
const successIcon =
|
|
127
|
+
'<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52"><path fill="#279548" d="M26,0A26,26,0,1,0,52,26,26,26,0,0,0,26,0Zm9.6,17.5a1.94,1.94,0,0,1,2,2,2,2,0,1,1-2-2Zm-19.2,0a2,2,0,1,1-2,2A2,2,0,0,1,16.4,17.5ZM40.09,32.15a15.8,15.8,0,0,1-28.18,0,1,1,0,0,1,1.78-.9,13.81,13.81,0,0,0,24.62,0,1,1,0,1,1,1.78.9Z"></path></svg>';
|
|
128
|
+
const errorText =
|
|
129
|
+
'<b class="text-bold block text-2xl mb-4">Упс!</b> Что-то пошло не так. Перезагрузите страницу и попробуйте снова. ';
|
|
130
|
+
let successText = '<b class="text-bold block text-2xl mb-4">Спасибо!</b> В скором времени мы свяжемся с Вами!';
|
|
131
|
+
const messageModal = document.getElementById("message-modal");
|
|
132
|
+
|
|
133
|
+
if (!phoneChecker(phone)) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// если флажок не установлен - фронт
|
|
138
|
+
if (!agree.checked) {
|
|
139
|
+
showErrorMes(form, ".agree", "Чтобы продолжить, установите флажок");
|
|
140
|
+
stateBtn(btn, "Отправить");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
reachGoal("form_submit");
|
|
144
|
+
let formData = new FormData(form);
|
|
145
|
+
if(getCookie('fta')) {
|
|
146
|
+
formData.append("fta", true);
|
|
147
|
+
}
|
|
148
|
+
if(getCookie('__gtm_campaign_url')) {
|
|
149
|
+
var source = new URL(getCookie('__gtm_campaign_url'));
|
|
150
|
+
source.search.slice(1).split("&").forEach(function(pair) {
|
|
151
|
+
var param = pair.split("=");
|
|
152
|
+
formData.append(param[0], param[1]);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
formData.append(
|
|
156
|
+
"page_url",
|
|
157
|
+
window.location.origin + window.location.pathname
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
window.location.search
|
|
161
|
+
.slice(1)
|
|
162
|
+
.split("&")
|
|
163
|
+
.forEach(function (pair) {
|
|
164
|
+
var param = pair.split("=");
|
|
165
|
+
if(formData.get(param[0])){
|
|
166
|
+
formData.set(param[0], decodeURIComponent(param[1]));
|
|
167
|
+
} else {
|
|
168
|
+
formData.append(param[0], decodeURIComponent(param[1]));
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const params = new URLSearchParams([...formData]);
|
|
173
|
+
var formDataObj = getFormDataObject(formData, form.id);
|
|
174
|
+
|
|
175
|
+
await fetch(url, {
|
|
176
|
+
method: "POST",
|
|
177
|
+
mode: "cors",
|
|
178
|
+
cache: "no-cache",
|
|
179
|
+
credentials: "same-origin",
|
|
180
|
+
headers: {
|
|
181
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
182
|
+
},
|
|
183
|
+
body: params,
|
|
184
|
+
})
|
|
185
|
+
.then((res) => res.json())
|
|
186
|
+
.then((data) => {
|
|
187
|
+
console.log(data);
|
|
188
|
+
stateBtn(btn, "Отправить");
|
|
189
|
+
if (data.answer == "required") {
|
|
190
|
+
reachGoal("form_required");
|
|
191
|
+
showErrorMes(form, data.field, data.message);
|
|
192
|
+
return;
|
|
193
|
+
} else if (data.answer == "error") {
|
|
194
|
+
reachGoal("form_error");
|
|
195
|
+
showMessageModal(messageModal, errorIcon, errorText + "<br>" + data.error);
|
|
196
|
+
return;
|
|
197
|
+
} else {
|
|
198
|
+
reachGoal("form_success", formDataObj);
|
|
199
|
+
showMessageModal(messageModal, successIcon, successText);
|
|
200
|
+
|
|
201
|
+
// Вызов callback в конце функции
|
|
202
|
+
if (callback && typeof callback === 'function') {
|
|
203
|
+
callback();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
form.reset();
|
|
207
|
+
})
|
|
208
|
+
.catch((error) => {
|
|
209
|
+
reachGoal("form_error");
|
|
210
|
+
console.error("Ошибка отправки данных формы: " + error);
|
|
211
|
+
showMessageModal(messageModal, errorIcon, errorText + "<br>" + error);
|
|
212
|
+
stateBtn(btn, "Отправить");
|
|
213
|
+
});
|
|
214
|
+
return false;
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexsab-ru/scripts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "common libs for websites",
|
|
5
5
|
"main": "index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
-
},
|
|
9
6
|
"repository": {
|
|
10
7
|
"type": "git",
|
|
11
8
|
"url": "git+https://github.com/alexsab-ru/scripts.git"
|
|
@@ -25,5 +22,8 @@
|
|
|
25
22
|
"publishConfig": {
|
|
26
23
|
"access": "public"
|
|
27
24
|
},
|
|
28
|
-
"homepage": "https://github.com/alexsab-ru/scripts#readme"
|
|
29
|
-
|
|
25
|
+
"homepage": "https://github.com/alexsab-ru/scripts#readme",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
28
|
+
}
|
|
29
|
+
}
|