PageSourceSearch

https://river-travel.ru/wp-content/themes/river/source/js/modules/feedback-submit.js?ver=1.0.30

js river-travel.ru collected 2026-10-04 11:50:53 UTC 6,588 bytes, 216 lines download raw bytes

1import { serverReq, block, unblock, cleanFields, openModalSuccess, displayErrors, cleanErrors } from "./helpers.js";
2import { formValidate } from '../popup.js';
3
4const antispamFieldNames = ['rt_hp_bot', 'website', 'company', 'url'];
5const MIN_SUBMIT_DELAY_SEC = 2;
6
7function clearFormMessage(form) {
8	const box = form.querySelector('[data-form-status]');
9	if (!box) {
10		return;
11	}
12	box.textContent = '';
13	box.hidden = true;
14	box.style.display = 'none';
15}
16
17function showFormMessage(form, message) {
18	if (!message) {
19		clearFormMessage(form);
20		return;
21	}
22
23	let box = form.querySelector('[data-form-status]');
24	if (!box) {
25		box = document.createElement('div');
26		box.dataset.formStatus = '1';
27		box.className = 'popup__form-alert';
28		box.setAttribute('role', 'alert');
29		box.style.cssText = 'margin:12px 0 0;padding:10px 12px;font-size:14px;line-height:1.4;color:#c62828;background:#ffebee;border-radius:8px;';
30		const submitBtn = form.querySelector('button[type="submit"]');
31		if (submitBtn) {
32			submitBtn.parentNode.insertBefore(box, submitBtn);
33		} else {
34			form.appendChild(box);
35		}
36	}
37
38	box.textContent = message;
39	box.hidden = false;
40	box.style.display = 'block';
41	box.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
42}
43
44function getResponseErrorMessage(res) {
45	if (!res || typeof res !== 'object') {
46		return 'Не удалось отправить форму. Попробуйте ещё раз.';
47	}
48	if (res.status) {
49		return String(res.status);
50	}
51	if (res.message) {
52		return String(res.message);
53	}
54	if (res.result === 'error') {
55		return 'Не удалось отправить форму. Попробуйте ещё раз.';
56	}
57	return '';
58}
59
60function setHiddenField(form, name, value) {
61	let input = form.querySelector(`input[name="${name}"]`);
62	if (!input) {
63		input = document.createElement('input');
64		input.type = 'hidden';
65		input.name = name;
66		form.appendChild(input);
67	}
68	input.value = value;
69}
70
71function ensureHoneypotFields(form) {
72	if (!form.querySelector('input[name="rt_hp_bot"]')) {
73		const wrap = document.createElement('div');
74		wrap.className = 'hidden';
75		wrap.setAttribute('aria-hidden', 'true');
76		wrap.style.cssText = 'position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden;opacity:0;pointer-events:none;';
77		wrap.innerHTML = antispamFieldNames.map((name) => (
78			`<input type="text" name="${name}" value="" tabindex="-1" autocomplete="off">`
79		)).join('');
80		form.prepend(wrap);
81	}
82}
83
84function markFormReady(form) {
85	ensureHoneypotFields(form);
86	if (!form.querySelector('input[name="rt_form_ts"]')?.value) {
87		setHiddenField(form, 'rt_form_ts', String(Math.floor(Date.now() / 1000)));
88	}
89}
90
91function ensureFormTiming(form) {
92	if (form.dataset.rtTimingReady === '1') {
93		return;
94	}
95	form.dataset.rtTimingReady = '1';
96	const markTs = () => markFormReady(form);
97	form.addEventListener('focusin', markTs, { once: true });
98	form.addEventListener('input', markTs, { once: true });
99}
100
101function initPopupFormReady() {
102	document.addEventListener('click', (e) => {
103		const opener = e.target.closest('[data-fls-popup-open], [data-fls-popup-link]');
104		if (!opener) {
105			return;
106		}
107		const popupId = opener.getAttribute('data-fls-popup-open') || opener.getAttribute('data-fls-popup-link');
108		if (!popupId) {
109			return;
110		}
111		setTimeout(() => {
112			const popup = document.querySelector(`[data-fls-popup="${popupId}"]`);
113			if (!popup) {
114				return;
115			}
116			popup.querySelectorAll('form[data-submit="feedback"], form[data-submit="feedback_calc"]').forEach((form) => {
117				ensureFormTiming(form);
118				markFormReady(form);
119			});
120		}, 100);
121	});
122}
123
124async function waitForSubmitDelay(form) {
125	const ts = parseInt(form.querySelector('input[name="rt_form_ts"]')?.value || '0', 10);
126	if (!ts) {
127		return false;
128	}
129	const elapsed = Math.floor(Date.now() / 1000) - ts;
130	const need = MIN_SUBMIT_DELAY_SEC - elapsed;
131	if (need > 0) {
132		await new Promise((resolve) => setTimeout(resolve, need * 1000));
133	}
134	return true;
135}
136
137function honeypotTriggered(formData) {
138	return !!(formData.get('rt_hp_bot') || formData.get('website') || formData.get('company') || formData.get('url'));
139}
140
141initPopupFormReady();
142
143export const feedback = async (form) => {
144	if (form.dataset.rtSubmitting === '1') {
145		return;
146	}
147	form.dataset.rtSubmitting = '1';
148
149	try {
150		ensureFormTiming(form);
151		markFormReady(form);
152		clearFormMessage(form);
153		cleanErrors(form);
154
155		if (formValidate.getErrors(form) > 0) {
156			showFormMessage(form, 'Проверьте обязательные поля формы.');
157			return;
158		}
159
160		const body = new FormData(form);
161		if (honeypotTriggered(body)) {
162			showFormMessage(form, 'Не удалось отправить форму. Обновите страницу и попробуйте снова.');
163			return;
164		}
165
166		if (!form.querySelector('input[name="rt_form_ts"]')?.value) {
167			showFormMessage(form, 'Заполните поля формы и попробуйте снова.');
168			return;
169		}
170
171		await waitForSubmitDelay(form);
172
173		setHiddenField(form, 'rt_js_ok', '1');
174		body.set('rt_js_ok', '1');
175		body.set('rt_form_ts', form.querySelector('input[name="rt_form_ts"]').value);
176
177		const targetYandex = body.get('ya_target');
178		const blockContainer = form.closest('._to_block');
179		if (blockContainer) {
180			block(blockContainer);
181		}
182
183		const res = await serverReq(body, form.getAttribute('action'));
184
185		if (res.errors && Object.keys(res.errors).length > 0) {
186			displayErrors(form, res.errors);
187			const firstError = Object.values(res.errors).find(Boolean);
188			showFormMessage(form, firstError || 'Проверьте поля формы.');
189			return;
190		}
191
192		if (res.result === 'success' && res.popup_title) {
193			clearFormMessage(form);
194			openModalSuccess('popup-success', res.popup_title, res.popup_message);
195			cleanFields(form, ['inp-policy', 'form_type', 'url_refer', 'ya_target']);
196			if (targetYandex && typeof ym === 'function') {
197				ym(54157144, 'reachGoal', targetYandex);
198			}
199			return;
200		}
201
202		const errorMessage = getResponseErrorMessage(res);
203		if (errorMessage) {
204			showFormMessage(form, errorMessage);
205		}
206	} catch (e) {
207		showFormMessage(form, 'Ошибка отправки. Попробуйте ещё раз.');
208		console.error(e);
209	} finally {
210		form.dataset.rtSubmitting = '0';
211		const blockContainer = form.closest('._to_block');
212		if (blockContainer) {
213			unblock(blockContainer);
214		}
215	}
216};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.