@gudhub/ssg-web-components-library 1.0.24 → 1.0.26
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/package.json +1 -1
- package/src/components/blog/article/article-component.js +6 -2
- package/src/components/blog/author-page/author-page.html +1 -1
- package/src/components/blog/authors-list/authors-list.html +1 -1
- package/src/components/blog/blog-banner/blog-banner.js +9 -1
- package/src/components/blog/category-banner/category-banner.js +13 -1
- package/src/components/breadcrumbs/breadcrumbs-component.js +14 -6
- package/src/components/breadcrumbs/breadcrumbs.html +1 -1
- package/src/components/get-in-touch-form/get-in-touch-form.html +12 -8
- package/src/components/get-in-touch-form/get-in-touch-form.js +82 -70
- package/src/components/get-in-touch-form/get-in-touch-form.readme.md +58 -3
- package/src/components/get-in-touch-form/validationCallbacks.js +36 -0
- package/src/components/image-component/image-component.js +41 -20
- package/src/components/masonry-gallery/config.js +5 -0
- package/src/components/masonry-gallery/masonry-gallery.html +29 -0
- package/src/components/masonry-gallery/masonry-gallery.js +196 -0
- package/src/components/masonry-gallery/masonry-gallery.md +40 -0
- package/src/components/masonry-gallery/masonry-gallery.scss +205 -0
- package/src/components/masonry-gallery/masonry.js +1242 -0
- package/src/config.js +2 -1
- package/src/components/get-in-touch-form/sendEmail.js +0 -152
package/package.json
CHANGED
|
@@ -86,13 +86,17 @@ class ArticleComponent extends GHComponent {
|
|
|
86
86
|
delete this.article.category;
|
|
87
87
|
|
|
88
88
|
this.breadcrumbs = JSON.stringify([
|
|
89
|
+
{
|
|
90
|
+
"title": "Головна",
|
|
91
|
+
"link": "/blog/"
|
|
92
|
+
},
|
|
89
93
|
{
|
|
90
94
|
"title": "Блог",
|
|
91
|
-
"
|
|
95
|
+
"link": "/blog/"
|
|
92
96
|
},
|
|
93
97
|
{
|
|
94
98
|
"title": this.article.categories[0].name,
|
|
95
|
-
"
|
|
99
|
+
"link": this.article.categories[0].slug
|
|
96
100
|
},
|
|
97
101
|
{
|
|
98
102
|
"title": this.article.h1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<section class="author-page">
|
|
2
2
|
<div class="container">
|
|
3
|
-
<breadcrumbs-component data-items='[{"title": "Блог", "
|
|
3
|
+
<breadcrumbs-component data-items='[{"title": "Головна", "link": "/"},{"title": "Блог", "link": "/blog/"}, {"title": "Автори", "link": "/blog/authors/"}, {"title": "${author.h1}"}]'></breadcrumbs-component>
|
|
4
4
|
<div class="author">
|
|
5
5
|
<div class="author_top">
|
|
6
6
|
<div class="image">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<section class="authors-list-section">
|
|
2
2
|
<div class="container">
|
|
3
|
-
<breadcrumbs-component data-items='[{"title": "Блог", "
|
|
3
|
+
<breadcrumbs-component data-items='[{"title": "Головна", "link": "/"},{"title": "Блог", "link": "/blog/"}, {"title": "Автори"}]'></breadcrumbs-component>
|
|
4
4
|
<h1 gh-id="${ghId}">Authors</h1>
|
|
5
5
|
<div class="authors-list">
|
|
6
6
|
${
|
|
@@ -39,7 +39,15 @@ class BlogBanner extends GHComponent {
|
|
|
39
39
|
let breadcrumbsTitle = document.createElement('div')
|
|
40
40
|
breadcrumbsTitle.innerHTML = this.json.title;
|
|
41
41
|
|
|
42
|
-
this.breadcrumbs = JSON.stringify([
|
|
42
|
+
this.breadcrumbs = JSON.stringify([
|
|
43
|
+
{
|
|
44
|
+
title: 'Головна',
|
|
45
|
+
link: '/'
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
title: breadcrumbsTitle.innerText
|
|
49
|
+
}
|
|
50
|
+
]);
|
|
43
51
|
|
|
44
52
|
this.image = this.json.image || false;
|
|
45
53
|
}
|
|
@@ -40,7 +40,19 @@ class CategoryBanner extends GHComponent {
|
|
|
40
40
|
this.description = div.querySelector('div').innerText;
|
|
41
41
|
this.title = item.fields.find(field => field.field_id == window.getConfig().chapters.blog.heading_field_id).field_value;
|
|
42
42
|
|
|
43
|
-
this.breadcrumbs = JSON.stringify([
|
|
43
|
+
this.breadcrumbs = JSON.stringify([
|
|
44
|
+
{
|
|
45
|
+
"title": "Головна",
|
|
46
|
+
"link": "/"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"title": this.config.breadcrumbs.blog,
|
|
50
|
+
"link": "/blog/"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"title": this.title
|
|
54
|
+
}
|
|
55
|
+
]);
|
|
44
56
|
|
|
45
57
|
super.render(html);
|
|
46
58
|
}
|
|
@@ -7,13 +7,21 @@ class BreadcrumbsComponent extends GHComponent {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
async onServerRender() {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
if (this.hasAttribute('data-items')) {
|
|
11
|
+
//manual mode when you passing items in JSON format through attribute "data-items"
|
|
12
|
+
try {
|
|
13
|
+
this.items = JSON.parse(this.getAttribute('data-items'));
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error(error);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
this.breadcrumbsConfig = window.getConfig().componentsConfigs.breadcrumbsConfig;
|
|
20
|
+
this.initialRoute = this.breadcrumbsConfig[0].routesTree;
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
let currentUrl = new URL(window.location.href);
|
|
23
|
+
currentUrl = currentUrl.searchParams.get('path');
|
|
24
|
+
}
|
|
17
25
|
|
|
18
26
|
this.items === null ? console.error(`Didn't find current route in config, current URL: ${currentUrl}`) : null;
|
|
19
27
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<li>
|
|
6
6
|
${item.image
|
|
7
7
|
? ` <a href="${item.link}" aria-label="${item.title}">
|
|
8
|
-
<
|
|
8
|
+
<img src="${item.image.src}" alt="${item.image.alt}" title="${item.image.title}"></img>
|
|
9
9
|
</a>
|
|
10
10
|
` : `
|
|
11
11
|
<a href="${item.link}">${item.title}</a>
|
|
@@ -21,14 +21,18 @@
|
|
|
21
21
|
<div class="bold">${config.titleSuccess ? config.titleSuccess : 'Successfull'}</div>
|
|
22
22
|
<div>${config.subtitleSuccess ? config.subtitleSuccess : 'Your form has been succesfully submitted! Please, check if info you provided is correct:'}</div>
|
|
23
23
|
<div class="check">
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
${config.inputs.find(input => input.name === "email") ? `
|
|
25
|
+
<div class="check_entity">
|
|
26
|
+
<span>Email:</span>
|
|
27
|
+
<span class="email"></span>
|
|
28
|
+
</div>
|
|
29
|
+
` : ''}
|
|
30
|
+
${config.inputs.find(input => input.name === "phone") ? `
|
|
31
|
+
<div class="check_entity phone_entity">
|
|
32
|
+
<span>Phone:</span>
|
|
33
|
+
<span class="phone"></span>
|
|
34
|
+
</div>
|
|
35
|
+
` : ''}
|
|
32
36
|
</div>
|
|
33
37
|
</div>
|
|
34
38
|
</div>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import html from './get-in-touch-form.html';
|
|
2
2
|
import './get-in-touch-form.scss';
|
|
3
|
-
import { sendEmail } from './sendEmail.js';
|
|
4
3
|
import defaultConfigs from './get-in-touch-form-data.json';
|
|
4
|
+
import { validationCallbacks } from './validationCallbacks.js';
|
|
5
5
|
|
|
6
6
|
class GetInTouchForm extends GHComponent {
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
constructor() {
|
|
9
9
|
super();
|
|
10
10
|
this.formId = this.getAttribute("data-form-id");
|
|
@@ -21,7 +21,7 @@ class GetInTouchForm extends GHComponent {
|
|
|
21
21
|
if (this.hasAttribute('data-in-popup')) {
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
this.initConfig(this.config);
|
|
26
26
|
super.render(html);
|
|
27
27
|
}
|
|
@@ -40,7 +40,7 @@ class GetInTouchForm extends GHComponent {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
attachEventListeners() {
|
|
43
|
-
this.getElementsByTagName('form')[0].addEventListener('submit', (e) => this.handleSubmit(e
|
|
43
|
+
this.getElementsByTagName('form')[0].addEventListener('submit', (e) => this.handleSubmit(e));
|
|
44
44
|
this.getElementsByClassName('restart_button')[0].addEventListener('click', (e) => this.hideFail());
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -55,98 +55,106 @@ class GetInTouchForm extends GHComponent {
|
|
|
55
55
|
|
|
56
56
|
initConfig(formConfigs) {
|
|
57
57
|
try {
|
|
58
|
-
this.config = formConfigs.find(({id}) => id === this.formId);
|
|
58
|
+
this.config = formConfigs.find(({ id }) => id === this.formId);
|
|
59
59
|
if (!this.config) {
|
|
60
|
-
throw
|
|
60
|
+
throw new Error("Config not found");
|
|
61
61
|
}
|
|
62
62
|
} catch (error) {
|
|
63
63
|
const defaultId = this.isInPopup ? 'default popup' : 'default';
|
|
64
|
-
this.config = defaultConfigs.find(({id}) => id === defaultId);
|
|
64
|
+
this.config = defaultConfigs.find(({ id }) => id === defaultId);
|
|
65
65
|
}
|
|
66
|
+
|
|
66
67
|
this.titleName = this.hasAttribute('data-form-title') ? this.getAttribute('data-form-title') : this.config.title;
|
|
67
68
|
this.subtitleName = this.hasAttribute('data-form-subtitle') ? this.getAttribute('data-form-subtitle') : this.config.subtitle;
|
|
68
69
|
this.placement = this.hasAttribute('data-form-placement') ? this.getAttribute('data-form-placement') : "main";
|
|
69
70
|
this.buttonText = this.hasAttribute('data-form-button-text') ? this.getAttribute('data-form-button-text') : this.config.button_text;
|
|
70
|
-
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
checkInputsValidations = (inputs) => {
|
|
74
|
+
const result = inputs.map((input) => {
|
|
75
|
+
const validationCallback = validationCallbacks[input.name];
|
|
76
|
+
if (!validationCallback) return {
|
|
77
|
+
input,
|
|
78
|
+
isValid: true
|
|
79
|
+
};
|
|
71
80
|
|
|
72
|
-
|
|
73
|
-
|
|
81
|
+
return {
|
|
82
|
+
input,
|
|
83
|
+
isValid: validationCallback(input)
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return result;
|
|
88
|
+
};
|
|
74
89
|
|
|
90
|
+
async handleSubmit(event) {
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
const form = event.target;
|
|
93
|
+
|
|
94
|
+
const inputs = Array.from(form.querySelectorAll('input'));
|
|
75
95
|
const emailInput = this.querySelector('[name="email"]');
|
|
76
|
-
const email = emailInput ? emailInput.value : '';
|
|
77
|
-
|
|
78
96
|
const phoneInput = this.querySelector('[name="phone"]');
|
|
79
|
-
const phone = phoneInput ? phoneInput.value || '' : '';
|
|
80
|
-
|
|
81
|
-
const isValidFields = this.validation(email, phone);
|
|
82
|
-
|
|
83
|
-
if (isValidFields.phoneValid && isValidFields.emailValid) {
|
|
84
|
-
this.addLoader();
|
|
85
97
|
|
|
86
|
-
|
|
98
|
+
const validationResults = this.checkInputsValidations(inputs);
|
|
87
99
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
if (validationResults.every((res) => res.isValid)) {
|
|
101
|
+
this.addLoader();
|
|
102
|
+
try {
|
|
103
|
+
const res = await new Promise((resolve, reject) => {
|
|
104
|
+
const TIMEOUT_DURATION = 2000;
|
|
105
|
+
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
const isSuccess = true;
|
|
108
|
+
if (isSuccess) {
|
|
109
|
+
resolve(true);
|
|
110
|
+
} else {
|
|
111
|
+
reject(new Error('Failed to send email'));
|
|
112
|
+
}
|
|
92
113
|
}, TIMEOUT_DURATION);
|
|
93
|
-
|
|
94
|
-
sendEmail(element, config, placement)
|
|
95
|
-
.then((res) => {
|
|
96
|
-
clearTimeout(timer);
|
|
97
|
-
resolve(res);
|
|
98
|
-
})
|
|
99
|
-
.catch((error) => {
|
|
100
|
-
clearTimeout(timer);
|
|
101
|
-
reject(error);
|
|
102
|
-
});
|
|
103
114
|
});
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const res = await sendEmailWithTimeout(element, this.config, this.placement);
|
|
108
|
-
this.removeLoader(element);
|
|
115
|
+
|
|
116
|
+
this.removeLoader(form);
|
|
117
|
+
|
|
109
118
|
if (res) {
|
|
110
|
-
this.showSuccess({ email, phone });
|
|
119
|
+
this.showSuccess({ email: emailInput ? emailInput.value : '', phone: phoneInput ? phoneInput.value : '' });
|
|
111
120
|
} else {
|
|
112
121
|
this.showFail();
|
|
113
122
|
}
|
|
114
123
|
} catch (error) {
|
|
115
|
-
this.removeLoader(
|
|
124
|
+
this.removeLoader(form);
|
|
116
125
|
this.showFail();
|
|
117
126
|
}
|
|
118
127
|
this.isFormSubmitted = true;
|
|
119
|
-
|
|
120
128
|
} else {
|
|
121
|
-
|
|
129
|
+
const validationResultsForErrors = validationResults.filter(item => typeof item === 'object')
|
|
130
|
+
validationResultsForErrors.forEach(({ input, isValid }) => this.toggleError(input, isValid));
|
|
122
131
|
}
|
|
123
|
-
}
|
|
124
132
|
|
|
125
|
-
|
|
126
|
-
isValidFields.emailValid ? emailInput.classList.remove('error') : emailInput.classList.add('error');
|
|
127
|
-
|
|
128
|
-
isValidFields.phoneValid ? phoneInput.classList.remove('error') : phoneInput.classList.add('error');
|
|
133
|
+
this.createDataObject(form, this.config, this.placement)
|
|
129
134
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
let emailValid;
|
|
136
|
-
if (email.length === 0 && isEmailRequired == 'false') {
|
|
137
|
-
emailValid = true;
|
|
138
|
-
} else {
|
|
139
|
-
emailValid = /\S+@\S+\.\S+/.test(email);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
let phoneValid;
|
|
143
|
-
if (phone.length === 0 && isPhoneRequired == 'false') {
|
|
144
|
-
phoneValid = true;
|
|
145
|
-
} else {
|
|
146
|
-
phoneValid = /^\+?[\d()-\s]+$/.test(phone);
|
|
135
|
+
|
|
136
|
+
async createDataObject(form, formId, placement) {
|
|
137
|
+
const formData = {};
|
|
138
|
+
for (const [name, value] of (new FormData(form)).entries()) {
|
|
139
|
+
formData[name] = value;
|
|
147
140
|
}
|
|
148
141
|
|
|
149
|
-
|
|
142
|
+
const formDataObj = {
|
|
143
|
+
Website: window.location.hostname,
|
|
144
|
+
Url: window.location.pathname,
|
|
145
|
+
FormId: formId,
|
|
146
|
+
FormPlacement: placement,
|
|
147
|
+
FormData: {
|
|
148
|
+
...formData
|
|
149
|
+
},
|
|
150
|
+
Referrer: localStorage.getItem('referrer'),
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
window.dispatchEvent(new CustomEvent('submitForm', { detail: { formDataObj } }));
|
|
154
|
+
}
|
|
155
|
+
toggleError(input, isValid) {
|
|
156
|
+
input.classList[isValid ? 'remove' : 'add']('error');
|
|
157
|
+
input.parentElement.classList[isValid ? 'remove' : 'add']('error-input');
|
|
150
158
|
}
|
|
151
159
|
async addLoader() {
|
|
152
160
|
this.classList.add('loading');
|
|
@@ -160,7 +168,11 @@ class GetInTouchForm extends GHComponent {
|
|
|
160
168
|
}, 500);
|
|
161
169
|
}
|
|
162
170
|
async showSuccess({email, phone}) {
|
|
163
|
-
|
|
171
|
+
if (email) {
|
|
172
|
+
this.querySelector('.check_entity').classList.add('provided');
|
|
173
|
+
this.getElementsByClassName('email')[0].innerText = email;
|
|
174
|
+
}
|
|
175
|
+
this.classList.add('success');
|
|
164
176
|
if (phone) {
|
|
165
177
|
this.querySelector('.check_entity.phone_entity').classList.add('provided');
|
|
166
178
|
this.getElementsByClassName('phone')[0].innerText = phone;
|
|
@@ -184,7 +196,6 @@ class GetInTouchForm extends GHComponent {
|
|
|
184
196
|
overflowFail.style.opacity = '';
|
|
185
197
|
}, 500);
|
|
186
198
|
}
|
|
187
|
-
|
|
188
199
|
generateInput(config) {
|
|
189
200
|
return config.inputs.reduce((acc, input) => {
|
|
190
201
|
const maxSymbols = {
|
|
@@ -198,13 +209,14 @@ class GetInTouchForm extends GHComponent {
|
|
|
198
209
|
const maxLength = tag === 'textarea' ? maxSymbols.long : maxSymbols[input.type];
|
|
199
210
|
return acc + `
|
|
200
211
|
<div class="input-wrap col-${input.width}">
|
|
201
|
-
<${tag} type="text" name=${input.name} placeholder="${input.placeholder}" ${JSON.parse(input.required) ? 'required' : ''} ${
|
|
212
|
+
<${tag} type="text" name=${input.name} placeholder="${input.placeholder}" ${JSON.parse(input.required) ? 'required' : ''} ${maxLength ? `maxlength=${maxLength}` : ''}></${tag}>
|
|
202
213
|
${input.type === 'email' || input.type === 'phone' ? `<span class="${input.type}-error">${input.errorText}</span>` : ''}
|
|
203
214
|
</div>
|
|
204
|
-
|
|
215
|
+
`;
|
|
216
|
+
}, '');
|
|
205
217
|
}
|
|
206
218
|
}
|
|
207
219
|
|
|
208
|
-
if(!customElements.get('get-in-touch-form')) {
|
|
220
|
+
if (!customElements.get('get-in-touch-form')) {
|
|
209
221
|
customElements.define('get-in-touch-form', GetInTouchForm);
|
|
210
222
|
}
|
|
@@ -1,18 +1,24 @@
|
|
|
1
|
-
# Config:
|
|
1
|
+
# Config:
|
|
2
|
+
|
|
2
3
|
The form field settings are defined in the site's config.mjs, when rendered on the server it is stored in "window.getConfig().componentsConfigs.form_config", and this config is also defined in the client's "window.getConfig().componentsConfigs.formConfig" and is used when rendering the form on the client. If the config in "window" is not available, then the config is taken from "get-in-touch-form-data.json". There can be several settings for forms, the form selects it by id (it is defined in the "data-form-id" attribute of the form component), this allows you to customize the fields of several forms in different ways (for example, on the page the form has all the fields, and in the popup the form has other types of fields)
|
|
3
4
|
|
|
4
5
|
# Form in popup:
|
|
6
|
+
|
|
5
7
|
if form will be rendered in popup we need to define attribute "data-in-popup", that will change some styles of form to fit the popup styles.
|
|
6
8
|
|
|
7
9
|
# Placement:
|
|
10
|
+
|
|
8
11
|
The "placement" variable is needed to track conversions. If the form is in a popup, then "placement" determined by the button that opened the popup with the form. If the form is already on the page, this value is defined in the form constructor (currently "this.placement = 'main' ").
|
|
9
12
|
|
|
10
13
|
# Data-attributes:
|
|
14
|
+
|
|
11
15
|
data-in-popup: if form is on popup
|
|
12
16
|
data-form-id="form-id": determine id of config that will be applyed to form
|
|
13
17
|
|
|
14
|
-
# Config object:
|
|
18
|
+
# Config object:
|
|
19
|
+
|
|
15
20
|
("?" means "unnecessary")
|
|
21
|
+
|
|
16
22
|
```json
|
|
17
23
|
{
|
|
18
24
|
"id": "string",
|
|
@@ -26,6 +32,7 @@ data-form-id="form-id": determine id of config that will be applyed to form
|
|
|
26
32
|
"from": "string",
|
|
27
33
|
"subject": "string"
|
|
28
34
|
},
|
|
35
|
+
"endpointForEmails": "some fancy url for gudhub API",
|
|
29
36
|
"inputs": [
|
|
30
37
|
{
|
|
31
38
|
"name": "string",
|
|
@@ -37,7 +44,9 @@ data-form-id="form-id": determine id of config that will be applyed to form
|
|
|
37
44
|
]
|
|
38
45
|
},
|
|
39
46
|
```
|
|
47
|
+
|
|
40
48
|
## Types description:
|
|
49
|
+
|
|
41
50
|
email: will be checked by email rules;
|
|
42
51
|
phone: will be checked by phone number rules;
|
|
43
52
|
short: max length 64 symbols;
|
|
@@ -45,4 +54,50 @@ long: max length 128 symbols;
|
|
|
45
54
|
textarea: tag "textarea";
|
|
46
55
|
|
|
47
56
|
## Width:
|
|
48
|
-
|
|
57
|
+
|
|
58
|
+
defines the width of the field in the row. The total width of the row is 12. That is, if 2 fields have a width of 6, then they will take up half of the line each
|
|
59
|
+
|
|
60
|
+
## Correct way to handle submit in your project
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
// by default this event handled in your main.js
|
|
64
|
+
window.addEventListener("submitForm", async (event) => {
|
|
65
|
+
const { formDataObj } = event.detail;
|
|
66
|
+
|
|
67
|
+
// add other code here
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### What data contains in formDataObj
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
{
|
|
75
|
+
FormData :
|
|
76
|
+
// all data from your form inputs on website
|
|
77
|
+
name : "name"
|
|
78
|
+
phone: "phhone"
|
|
79
|
+
email: 'email'
|
|
80
|
+
FormId:
|
|
81
|
+
// data from your form-config
|
|
82
|
+
button_text:"text"
|
|
83
|
+
defaultLang: "true or false"
|
|
84
|
+
id: "text"
|
|
85
|
+
inputs: Array()
|
|
86
|
+
0: {name: 'name', type: 'type', required: 'required', placeholder: "placeholder", width: 12}
|
|
87
|
+
1: {name: 'name', type: 'type', required: 'required', placeholder: 'placeholder *', errorText: 'errorText', …}
|
|
88
|
+
langCode: "text"
|
|
89
|
+
mailConfig:
|
|
90
|
+
from: "from"
|
|
91
|
+
subject: "subject"
|
|
92
|
+
to: "to"
|
|
93
|
+
subtitle: "subtitle"
|
|
94
|
+
subtitleSuccess: "subtitleSuccess"
|
|
95
|
+
title: "title"
|
|
96
|
+
titleFail:"titleFail"
|
|
97
|
+
titleSuccess: "titleSuccess"
|
|
98
|
+
FormPlacement: "FormPlacement"
|
|
99
|
+
Referrer: "Referrer"
|
|
100
|
+
Url: "/"
|
|
101
|
+
Website: "Website"
|
|
102
|
+
}
|
|
103
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const emailValidation = (input) => {
|
|
2
|
+
const regex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
|
|
3
|
+
|
|
4
|
+
let isValid = regex.test(input.value);
|
|
5
|
+
|
|
6
|
+
if (!isValid) {
|
|
7
|
+
input.classList.add('error');
|
|
8
|
+
input.parentElement.classList.add('error-input');
|
|
9
|
+
} else {
|
|
10
|
+
input.classList.remove('error');
|
|
11
|
+
input.parentElement.classList.remove('error-input');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return isValid;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const phoneValidation = (input) => {
|
|
18
|
+
const regex = /^\d{10}$/;
|
|
19
|
+
|
|
20
|
+
let isValid = regex.test(input.value);
|
|
21
|
+
|
|
22
|
+
if (!isValid) {
|
|
23
|
+
input.classList.add('error');
|
|
24
|
+
input.parentElement.classList.add('error-input');
|
|
25
|
+
} else {
|
|
26
|
+
input.classList.remove('error');
|
|
27
|
+
input.parentElement.classList.remove('error-input');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return isValid;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const validationCallbacks = {
|
|
34
|
+
email: emailValidation,
|
|
35
|
+
phone: phoneValidation
|
|
36
|
+
};
|
|
@@ -53,26 +53,47 @@ class ImageComponent extends GHComponent {
|
|
|
53
53
|
}
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
56
|
+
let attempts = 0;
|
|
57
|
+
let imageLoaded = false;
|
|
58
|
+
|
|
59
|
+
while (!imageLoaded && attempts < 5) {
|
|
60
|
+
try {
|
|
61
|
+
await new Promise(async (resolve, reject) => {
|
|
62
|
+
// Use new Image to have ability get params like from real image, for example naturalWidth
|
|
63
|
+
this.image = new Image();
|
|
64
|
+
|
|
65
|
+
this.image.addEventListener('load', () => {
|
|
66
|
+
const srcHasParams = this.image.getAttribute('src').indexOf('?') !== -1;
|
|
67
|
+
let src = srcHasParams ? this.image.getAttribute('src').substring(0, this.image.getAttribute('src').indexOf('?')) : this.image.getAttribute('src');
|
|
68
|
+
if (src.indexOf('&') !== -1) {
|
|
69
|
+
src = src.substring(0, src.indexOf('&'))
|
|
70
|
+
}
|
|
71
|
+
this.extension = src.substring(src.lastIndexOf('.'), src.length);
|
|
72
|
+
this.path = src.substring(0, src.length - this.extension.length);
|
|
73
|
+
|
|
74
|
+
this.imageWidth = this.image.naturalWidth;
|
|
75
|
+
|
|
76
|
+
imageLoaded = true;
|
|
77
|
+
|
|
78
|
+
resolve();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
this.image.onerror = () => {
|
|
82
|
+
reject();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
this.image.setAttribute('src', this.src);
|
|
86
|
+
});
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(`11111111111111111 Image load failed "${this.src}". Attempt "${attempts + 1}"`);
|
|
89
|
+
attempts++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (imageLoaded) {
|
|
93
|
+
super.render(html);
|
|
94
|
+
} else {
|
|
95
|
+
console.error(`11111111111111111 Image load failed "${this.src}".`);
|
|
96
|
+
}
|
|
76
97
|
// caller == 'client' ? this.clientRender() : super.render(html);
|
|
77
98
|
}
|
|
78
99
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<section>
|
|
2
|
+
<div class="container">
|
|
3
|
+
${json.title ? `
|
|
4
|
+
<h2 class="h2" gh-id="${ghId}.title"></h2>
|
|
5
|
+
` : ''}
|
|
6
|
+
${json.subtitle ? `
|
|
7
|
+
<p class="subtitle" gh-id="${ghId}.subtitle"></p>
|
|
8
|
+
` : ''}
|
|
9
|
+
|
|
10
|
+
<div class="masonry-grid"></div>
|
|
11
|
+
|
|
12
|
+
<div class="button-wrapper">
|
|
13
|
+
${json.button ? `
|
|
14
|
+
<button id="grid-add-items" class="btn" gh-id="${ghId}.button"></button>
|
|
15
|
+
` : ''}
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div id="modal">
|
|
19
|
+
<div class="modal-loader">
|
|
20
|
+
<svg viewBox="25 25 50 50">
|
|
21
|
+
<circle r="20" cy="50" cx="50"></circle>
|
|
22
|
+
</svg>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="close-modal">
|
|
25
|
+
<span>×</span>
|
|
26
|
+
</div>
|
|
27
|
+
<img class="modal-img" src="">
|
|
28
|
+
</div>
|
|
29
|
+
</section>
|