PageSourceSearch

https://buzzen.com/assets/js/auth.js?v=572

js buzzen.com collected 2026-09-26 00:31:37 UTC 12,407 bytes, 262 lines download raw bytes

1(() => {
2    'use strict';
3
4    const qs = (selector, root = document) => root.querySelector(selector);
5    const qsa = (selector, root = document) => Array.from(root.querySelectorAll(selector));
6
7    const codePointLength = (value) => Array.from(value || '').length;
8
9    const normalizeEmailField = (field) => {
10        if (!(field instanceof HTMLInputElement)) return;
11        const normalized = field.value.toLowerCase();
12        if (field.value !== normalized) field.value = normalized;
13    };
14
15    qsa('input[type="email"][name="email"]').forEach((field) => {
16        if (!(field instanceof HTMLInputElement)) return;
17        ['input', 'change', 'blur'].forEach((eventName) => {
18            field.addEventListener(eventName, () => normalizeEmailField(field));
19        });
20        normalizeEmailField(field);
21    });
22
23    qsa('[data-auth-password-toggle]').forEach((button) => {
24        const selector = button.getAttribute('data-auth-password-toggle');
25        const field = selector ? qs(selector) : null;
26        if (!(field instanceof HTMLInputElement)) return;
27
28        button.addEventListener('click', () => {
29            const showing = field.type === 'text';
30            field.type = showing ? 'password' : 'text';
31            button.setAttribute('aria-pressed', showing ? 'false' : 'true');
32            button.setAttribute('aria-label', showing ? 'Show password' : 'Hide password');
33            const icon = qs('i', button);
34            if (icon) {
35                icon.classList.toggle('fa-eye', showing);
36                icon.classList.toggle('fa-eye-slash', !showing);
37            }
38            field.focus({ preventScroll: true });
39        });
40    });
41
42    qsa('[data-auth-counter]').forEach((field) => {
43        if (!(field instanceof HTMLInputElement)) return;
44        const output = qs(field.getAttribute('data-auth-counter') || '');
45        if (!output) return;
46        const max = Number(field.getAttribute('data-auth-codepoint-max') || field.getAttribute('maxlength') || 0);
47        const update = () => {
48            output.textContent = `${codePointLength(field.value)}${max > 0 ? ` / ${max}` : ''}`;
49        };
50        field.addEventListener('input', update);
51        update();
52    });
53
54    const registrationForm = qs('form[data-auth-availability-url]');
55    const availabilityUrl = registrationForm instanceof HTMLFormElement
56        ? registrationForm.getAttribute('data-auth-availability-url') || ''
57        : '';
58
59    if (registrationForm instanceof HTMLFormElement) {
60        const interactionField = qs('[data-auth-registration-interaction-field]', registrationForm);
61        const interactionToken = registrationForm.getAttribute('data-auth-registration-interaction') || '';
62        if (interactionField instanceof HTMLInputElement && interactionToken !== '') {
63            const interactionEvents = ['pointerdown', 'keydown', 'input', 'change', 'click', 'touchstart'];
64            const markInteraction = (event) => {
65                if (event && event.isTrusted === false) return;
66                interactionField.value = interactionToken;
67                interactionEvents.forEach((type) => registrationForm.removeEventListener(type, markInteraction, true));
68            };
69            interactionEvents.forEach((type) => registrationForm.addEventListener(type, markInteraction, true));
70        }
71    }
72
73    const nicknameValidationMessage = (value) => {
74        if (value === '') return '';
75        const length = codePointLength(value);
76        const bytes = typeof TextEncoder !== 'undefined' ? new TextEncoder().encode(value).length : value.length;
77        if (length < 3 || length > 30) return 'Nickname must be between 3 and 30 characters.';
78        if (!/^[\p{L}\p{N}_-]+$/u.test(value)) return 'Use letters, numbers, underscores, or hyphens only.';
79        if (/^(?:guide|sysop|admin|dt|staff)_/iu.test(value)) return 'That nickname starts with a reserved staff prefix.';
80        if (bytes > 50) return 'Use fewer decorative characters in this nickname.';
81        return '';
82    };
83
84    const profileValidationMessage = (value) => {
85        if (value === '') return '';
86        if (value.length < 3 || value.length > 30 || !/^[A-Za-z0-9_-]+$/.test(value)) {
87            return 'Profile name must be 3-30 characters and use letters, numbers, underscores, or hyphens only.';
88        }
89        return '';
90    };
91
92    const setAvailabilityStatus = (status, state, message) => {
93        if (!status) return;
94        status.textContent = message;
95        status.className = 'buzzen-auth-inline-status' + (state ? ` is-${state}` : '');
96    };
97
98    const wireAvailabilityCheck = (field, type, status, validate) => {
99        if (!(field instanceof HTMLInputElement) || !availabilityUrl) return;
100
101        let timer = 0;
102        let requestId = 0;
103        let controller = null;
104
105        const run = () => {
106            window.clearTimeout(timer);
107            if (controller) controller.abort();
108
109            const value = field.value.trim();
110            const localMessage = validate(value);
111            field.setCustomValidity(localMessage);
112
113            if (value === '') {
114                setAvailabilityStatus(status, '', '');
115                return;
116            }
117            if (localMessage !== '') {
118                setAvailabilityStatus(status, 'invalid', localMessage);
119                return;
120            }
121
122            const currentRequest = ++requestId;
123            setAvailabilityStatus(status, 'checking', `Checking ${type === 'nickname' ? 'nickname' : 'profile name'} availability...`);
124
125            timer = window.setTimeout(async () => {
126                controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
127                const query = new URLSearchParams({ type, value });
128                try {
129                    const response = await fetch(`${availabilityUrl}?${query.toString()}`, {
130                        method: 'GET',
131                        headers: { 'Accept': 'application/json' },
132                        credentials: 'same-origin',
133                        cache: 'no-store',
134                        signal: controller ? controller.signal : undefined,
135                    });
136                    const data = await response.json();
137                    if (currentRequest !== requestId || field.value.trim() !== value) return;
138
139                    if (response.ok && data && data.success === true) {
140                        const serverMessage = typeof data.message === 'string' ? data.message : '';
141                        if (data.valid === true && data.available === true) {
142                            field.setCustomValidity('');
143                            setAvailabilityStatus(status, 'valid', serverMessage || 'Available.');
144                        } else {
145                            field.setCustomValidity(serverMessage || 'That value is not available.');
146                            setAvailabilityStatus(status, 'invalid', serverMessage || 'That value is not available.');
147                        }
148                        return;
149                    }
150
151                    field.setCustomValidity('');
152                    setAvailabilityStatus(status, 'neutral', 'Availability check is temporarily unavailable. We will verify it when you register.');
153                } catch (error) {
154                    if (error && error.name === 'AbortError') return;
155                    if (currentRequest !== requestId) return;
156                    field.setCustomValidity('');
157                    setAvailabilityStatus(status, 'neutral', 'Availability check is temporarily unavailable. We will verify it when you register.');
158                }
159            }, 450);
160        };
161
162        field.addEventListener('input', run);
163        field.addEventListener('blur', () => {
164            if (field.value.trim() !== '' && field.validationMessage === '') run();
165        });
166        run();
167    };
168
169    const nicknameField = qs('[data-auth-nickname]');
170    const nicknameStatus = qs('[data-auth-nickname-status]');
171    wireAvailabilityCheck(nicknameField, 'nickname', nicknameStatus, nicknameValidationMessage);
172
173    const profileField = qs('[data-auth-profile-name]');
174    const profilePreview = qs('[data-auth-profile-preview]');
175    const profileStatus = qs('[data-auth-profile-status]');
176    if (profileField instanceof HTMLInputElement && profilePreview) {
177        const base = profilePreview.getAttribute('data-profile-base') || '/user/';
178        const suffix = profilePreview.getAttribute('data-profile-suffix') || '/profile';
179        const updatePreview = () => {
180            const value = profileField.value.trim() || 'your-name';
181            profilePreview.textContent = `${base}${encodeURIComponent(value)}${suffix}`;
182        };
183        profileField.addEventListener('input', updatePreview);
184        updatePreview();
185    }
186    wireAvailabilityCheck(profileField, 'profile', profileStatus, profileValidationMessage);
187
188    const updatePasswordRules = (password, confirmation, root) => {
189        const value = password.value;
190        const minimum = Number(password.getAttribute('data-auth-password-min') || password.getAttribute('minlength') || 15);
191        const maximum = Number(password.getAttribute('data-auth-password-max') || password.getAttribute('maxlength') || 128);
192        const tests = {
193            length: value.length >= minimum && value.length <= maximum,
194            edges: value.length > 0 && !value.startsWith(' ') && !value.endsWith(' '),
195        };
196
197        Object.entries(tests).forEach(([rule, valid]) => {
198            const item = qs(`[data-auth-rule="${rule}"]`, root);
199            if (!item) return;
200            item.classList.toggle('is-valid', valid);
201            item.classList.toggle('is-invalid', value.length > 0 && !valid);
202        });
203
204        const match = qs('[data-auth-password-match]', root);
205        if (confirmation instanceof HTMLInputElement && match) {
206            if (confirmation.value === '') {
207                match.textContent = '';
208                match.className = 'buzzen-auth-inline-status';
209            } else if (confirmation.value === value) {
210                match.textContent = 'Passwords match.';
211                match.className = 'buzzen-auth-inline-status is-valid';
212            } else {
213                match.textContent = 'Passwords do not match yet.';
214                match.className = 'buzzen-auth-inline-status is-invalid';
215            }
216        }
217    };
218
219    qsa('[data-auth-password-rules]').forEach((root) => {
220        const password = qs('[data-auth-password-primary]', root);
221        const confirmation = qs('[data-auth-password-confirm]', root);
222        if (!(password instanceof HTMLInputElement)) return;
223        const update = () => updatePasswordRules(password, confirmation, root);
224        password.addEventListener('input', update);
225        if (confirmation instanceof HTMLInputElement) confirmation.addEventListener('input', update);
226        update();
227    });
228
229    qsa('form[data-auth-submit]').forEach((form) => {
230        form.addEventListener('submit', () => {
231            qsa('input[type="email"][name="email"]', form).forEach(normalizeEmailField);
232            if (!form.checkValidity()) return;
233            const button = qs('button[type="submit"]', form);
234            if (!(button instanceof HTMLButtonElement)) return;
235            button.dataset.authOriginalHtml = button.innerHTML;
236            button.disabled = true;
237            button.classList.add('buzzen-auth-submit');
238            button.setAttribute('aria-busy', 'true');
239            const label = button.getAttribute('data-submitting-label');
240            if (label) {
241                const icon = qs('i', button);
242                button.textContent = '';
243                if (icon) button.append(icon);
244                button.append(document.createTextNode(` ${label}`));
245            }
246        });
247    });
248
249    window.addEventListener('pageshow', () => {
250        qsa('form[data-auth-submit] button[type="submit"]').forEach((button) => {
251            if (!(button instanceof HTMLButtonElement)) return;
252            button.disabled = false;
253            button.removeAttribute('aria-busy');
254            button.classList.remove('buzzen-auth-submit');
255            if (button.dataset.authOriginalHtml) {
256                button.innerHTML = button.dataset.authOriginalHtml;
257                delete button.dataset.authOriginalHtml;
258            }
259        });
260    });
261
262})();

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.