PageSourceSearch

https://www.secrets-center.ru/wa-apps/content/themes/common/js/email-domain-notice.js?v=1790583463

js secrets-center.ru collected 2026-09-28 08:18:24 UTC 9,975 bytes, 234 lines download raw bytes

1/**
2 * Плашка-предупреждение о зарубежных почтовых сервисах под полем email (задача #10501).
3 *
4 * Ничего не блокирует: не трогает submit, не ставит is-invalid, не участвует в
5 * валидации из common/js/forms.js. Только показывает и прячет подсказку.
6 *
7 * Дополнять нужно ТОЛЬКО два списка ниже — FOREIGN_EMAIL_DOMAINS (точное совпадение)
8 * и FOREIGN_EMAIL_DOMAIN_FAMILIES (сервис вместе со всеми региональными зонами).
9 * Логику при этом править не надо.
10 *
11 * Работает по чёрному списку, а не по «всё, что не .ru», чтобы не ругаться на
12 * корпоративные домены клиентов. Обработчики делегированы на document, поэтому
13 * поля, которые появятся в вёрстке позже (попапы, новые формы), подхватываются сами.
14 */
15(function () {
16    'use strict';
17
18    // Точное совпадение домена после @ (регистр не важен).
19    var FOREIGN_EMAIL_DOMAINS = [
20        'gmail.com', 'googlemail.com',
21        'outlook.com', 'hotmail.com', 'live.com', 'msn.com',
22        'yahoo.com', 'ymail.com', 'rocketmail.com',
23        'aol.com',
24        'icloud.com', 'me.com', 'mac.com',
25        'proton.me', 'protonmail.com', 'protonmail.ch', 'pm.me',
26        'gmx.com', 'gmx.net', 'gmx.de',
27        'zoho.com', 'zohomail.com',
28        'tutanota.com', 'tutanota.de', 'tuta.com', 'tuta.io',
29        'fastmail.com', 'fastmail.fm',
30        'mail.com'
31    ];
32
33    // Сервисы, у которых много региональных зон: yahoo.co.uk, yahoo.fr, hotmail.de,
34    // outlook.com.br и т.п. Совпадением считается «<имя>.<зона>», где зона — это
35    // 2-4 буквы плюс необязательный второй уровень (co.uk, com.br). Корпоративный
36    // домен вида live.company.ru под это не попадает.
37    var FOREIGN_EMAIL_DOMAIN_FAMILIES = [
38        'yahoo', 'hotmail', 'outlook', 'live', 'msn',
39        'gmx', 'aol', 'googlemail', 'icloud', 'proton', 'protonmail',
40        'tutanota', 'fastmail', 'zoho'
41    ];
42
43    var NOTICE_TEXT = 'Письма на Gmail, Outlook, Yahoo и другие зарубежные сервисы часто ' +
44        'приходят с задержкой или попадают в спам. Если есть возможность — укажите почту ' +
45        'на Яндексе или Mail.ru, так вы точно получите доступы и чек. Отправить заявку ' +
46        'с текущим адресом тоже можно.';
47
48    var NOTICE_CLASS = 'foreign-email-notice';
49    var TYPING_DELAY_MS = 500;
50
51    if (window.foreignEmailNoticeInitialized) {
52        return;
53    }
54    window.foreignEmailNoticeInitialized = true;
55
56    var exactDomains = {};
57    for (var i = 0; i < FOREIGN_EMAIL_DOMAINS.length; i++) {
58        exactDomains[FOREIGN_EMAIL_DOMAINS[i].toLowerCase()] = true;
59    }
60
61    var familyPattern = new RegExp(
62        '^(?:' + FOREIGN_EMAIL_DOMAIN_FAMILIES.join('|') + ')\\.[a-z]{2,4}(?:\\.[a-z]{2,3})?$'
63    );
64
65    var noticeSeq = 0;
66
67    function injectStyles() {
68        var style = document.createElement('style');
69        style.setAttribute('data-source', 'email-domain-notice');
70        style.appendChild(document.createTextNode(
71            '.' + NOTICE_CLASS + '{' +
72                'display:flex;align-items:flex-start;box-sizing:border-box;' +
73                'width:100%;max-width:100%;margin:6px 0 0;padding:8px 10px;' +
74                'border:1px solid #ffe08a;border-radius:6px;background:#fff8e1;' +
75                'color:#5c4400;font-size:13px;line-height:1.35;text-align:left;' +
76                'font-weight:400;white-space:normal;' +
77            '}' +
78            '.' + NOTICE_CLASS + '__icon{flex:0 0 auto;margin-right:8px;font-weight:700;}' +
79            '.' + NOTICE_CLASS + '__text{flex:1 1 auto;min-width:0;word-wrap:break-word;overflow-wrap:break-word;}' +
80            '@media (max-width:576px){.' + NOTICE_CLASS + '{font-size:12px;padding:7px 9px;}}'
81        ));
82        (document.head || document.documentElement).appendChild(style);
83    }
84
85    function isEmailField(el) {
86        if (!el || el.tagName !== 'INPUT') {
87            return false;
88        }
89        var type = (el.getAttribute('type') || 'text').toLowerCase();
90        if (type === 'email') {
91            return true;
92        }
93        if (type !== 'text') {
94            return false;
95        }
96        if ((' ' + (el.className || '') + ' ').indexOf(' form-email ') !== -1) {
97            return true;
98        }
99        var hint = ((el.getAttribute('name') || '') + ' ' + (el.id || '')).toLowerCase();
100        return hint.indexOf('email') !== -1 || hint.indexOf('e-mail') !== -1;
101    }
102
103    function domainOf(value) {
104        var raw = String(value == null ? '' : value).trim().toLowerCase();
105        var at = raw.lastIndexOf('@');
106        if (at === -1) {
107            return '';
108        }
109        var domain = raw.slice(at + 1).replace(/[.\s]+$/, '');
110        return /^[a-z0-9-]+(?:\.[a-z0-9-]+)+$/.test(domain) ? domain : '';
111    }
112
113    function isForeignDomain(domain) {
114        if (!domain) {
115            return false;
116        }
117        return exactDomains[domain] === true || familyPattern.test(domain);
118    }
119
120    function buildNotice() {
121        var box = document.createElement('div');
122        box.className = NOTICE_CLASS;
123        box.setAttribute('role', 'status');
124        box.id = NOTICE_CLASS + '-' + (++noticeSeq);
125
126        var icon = document.createElement('span');
127        icon.className = NOTICE_CLASS + '__icon';
128        icon.setAttribute('aria-hidden', 'true');
129        icon.appendChild(document.createTextNode('!'));
130
131        var text = document.createElement('span');
132        text.className = NOTICE_CLASS + '__text';
133        text.appendChild(document.createTextNode(NOTICE_TEXT));
134
135        box.appendChild(icon);
136        box.appendChild(text);
137        return box;
138    }
139
140    // Поле может стоять в узкой bootstrap-колонке (телефон | e-mail в одном .row,
141    // см. online2/parts/index/form.html). В колонке шириной 170px плашка вытягивается
142    // на 280px в высоту и распирает форму, поэтому в таком случае ставим её под весь
143    // ряд — она всё равно оказывается прямо под полем, но на всю ширину формы.
144    // Ряд с кнопкой отправки не трогаем, чтобы плашка не уехала под кнопку.
145    function placeAfterRow(input) {
146        var node = input.parentNode;
147        while (node && node.nodeType === 1 && node !== document.body) {
148            if (node.className && /(^|\s)col(-[a-z0-9]+)*(\s|$)/.test(node.className)) {
149                var row = node.parentNode;
150                if (row && row.className && /(^|\s)row(\s|$)/.test(row.className) &&
151                    row.parentNode && !row.querySelector('button, input[type="submit"]')) {
152                    return row;
153                }
154                return null;
155            }
156            node = node.parentNode;
157        }
158        return null;
159    }
160
161    function showNotice(input) {
162        var existing = input.foreignEmailNotice;
163        if (existing && existing.parentNode) {
164            return;
165        }
166        if (!input.parentNode) {
167            return;
168        }
169        var notice = buildNotice();
170        var row = placeAfterRow(input);
171        if (row) {
172            row.parentNode.insertBefore(notice, row.nextSibling);
173        } else {
174            input.parentNode.insertBefore(notice, input.nextSibling);
175        }
176        input.foreignEmailNotice = notice;
177        if (!input.getAttribute('aria-describedby')) {
178            input.setAttribute('aria-describedby', notice.id);
179        }
180    }
181
182    function hideNotice(input) {
183        var notice = input.foreignEmailNotice;
184        if (notice && notice.parentNode) {
185            notice.parentNode.removeChild(notice);
186        }
187        input.foreignEmailNotice = null;
188        if (input.getAttribute('aria-describedby') &&
189            input.getAttribute('aria-describedby').indexOf(NOTICE_CLASS + '-') === 0) {
190            input.removeAttribute('aria-describedby');
191        }
192    }
193
194    function update(input) {
195        if (isForeignDomain(domainOf(input.value))) {
196            showNotice(input);
197        } else {
198            hideNotice(input);
199        }
200    }
201
202    function updateDelayed(input) {
203        if (input.foreignEmailNoticeTimer) {
204            clearTimeout(input.foreignEmailNoticeTimer);
205        }
206        input.foreignEmailNoticeTimer = setTimeout(function () {
207            input.foreignEmailNoticeTimer = null;
208            update(input);
209        }, TYPING_DELAY_MS);
210    }
211
212    function onImmediate(e) {
213        if (isEmailField(e.target)) {
214            if (e.target.foreignEmailNoticeTimer) {
215                clearTimeout(e.target.foreignEmailNoticeTimer);
216                e.target.foreignEmailNoticeTimer = null;
217            }
218            update(e.target);
219        }
220    }
221
222    function onTyping(e) {
223        if (isEmailField(e.target)) {
224            updateDelayed(e.target);
225        }
226    }
227
228    injectStyles();
229
230    // Capture-фаза: чужие обработчики форм иногда гасят всплытие.
231    document.addEventListener('focusout', onImmediate, true);
232    document.addEventListener('change', onImmediate, true);
233    document.addEventListener('input', onTyping, true);
234})();

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.