(function () { 'use strict'; const config = window.itstudioJoinOtp; if (!config) { return; } const OTP_COOLDOWN_SECONDS = 60; function getLanguage() { return document.documentElement.lang === 'en' ? 'en' : 'cn'; } function getMessage(payload, fallback) { const key = getLanguage() === 'en' ? 'message_en' : 'message_cn'; return payload && payload[key] ? payload[key] : fallback; } function findField(form, fieldId) { return form.querySelector('[name="item_meta[' + fieldId + ']"]'); } function findTargetForm() { const candidates = document.querySelectorAll('.join-form-content .frm_forms form'); for (const form of candidates) { const formIdInput = form.querySelector('input[name="form_id"]'); if (!formIdInput || Number(formIdInput.value) === Number(config.formId)) { if (findField(form, config.emailFieldId) && findField(form, config.codeFieldId)) { return form; } } } return null; } function setStatus(status, message, tone) { if (!message) { status.textContent = ''; status.className = 'itstudio-otp-status'; status.style.display = 'none'; return; } status.style.display = 'flex'; status.textContent = message; status.className = 'itstudio-otp-status ' + (tone === 'error' ? 'frm_error_style' : 'frm_message'); } function startCooldown(button, seconds) { let remaining = Math.max(1, Number(seconds) || OTP_COOLDOWN_SECONDS); if (button.itstudioOtpTimer) { window.clearInterval(button.itstudioOtpTimer); } button.disabled = true; button.dataset.otpLocked = '1'; button.setAttribute('aria-disabled', 'true'); button.classList.add('is-countdown'); const update = function () { button.textContent = String(config.labels.resend || '重新发送 (%s秒)').replace('%s', remaining); remaining -= 1; if (remaining < 0) { window.clearInterval(button.itstudioOtpTimer); button.itstudioOtpTimer = null; button.dataset.otpLocked = '0'; button.setAttribute('aria-disabled', 'false'); button.classList.remove('is-countdown'); button.textContent = config.labels.send || '发送验证码'; button.disabled = false; } }; update(); button.itstudioOtpTimer = window.setInterval(update, 1000); } async function readAjaxResult(response) { const raw = await response.text(); const normalized = raw.replace(/^\uFEFF/, '').trim(); if (!normalized) { throw new Error('Empty AJAX response'); } const result = JSON.parse(normalized); if (result && typeof result === 'object' && typeof result.success !== 'undefined') { return { success: result.success === true || result.success === 1 || result.success === '1', payload: result.data && typeof result.data === 'object' ? result.data : {} }; } if (result && typeof result === 'object' && (result.message_cn || result.message_en)) { return { success: true, payload: result }; } throw new Error('Unexpected AJAX response'); } async function sendCode(form, button, status) { if (button.dataset.otpLocked === '1' || button.disabled) { return; } const emailInput = findField(form, config.emailFieldId); const email = emailInput ? emailInput.value.trim() : ''; if (!email) { setStatus(status, config.labels.emailRequired || '请先填写邮箱地址。', 'error'); emailInput?.focus(); return; } if (emailInput && !emailInput.checkValidity()) { setStatus(status, config.labels.invalidEmail || '请输入有效的邮箱地址。', 'error'); emailInput.focus(); return; } // Block rapid duplicate clicks while the request is in flight. button.disabled = true; button.dataset.otpLocked = '1'; button.setAttribute('aria-disabled', 'true'); button.classList.add('is-loading'); button.textContent = config.labels.sending || '发送中…'; setStatus(status, config.labels.sending || '正在发送验证码…', 'info'); try { const response = await window.fetch(config.ajaxUrl, { method: 'POST', credentials: 'same-origin', headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}, body: new URLSearchParams({ action: 'itstudio_join_otp_send', email: email, nonce: config.nonce }).toString() }); const result = await readAjaxResult(response); const payload = result.payload || {}; button.classList.remove('is-loading'); if (!result.success) { setStatus(status, getMessage(payload, config.labels.networkError || '发送失败,请稍后重试。'), 'error'); button.dataset.otpLocked = '0'; button.setAttribute('aria-disabled', 'false'); button.textContent = config.labels.send || '发送验证码'; button.disabled = false; return; } setStatus(status, getMessage(payload, '验证码已发送,请检查邮箱及垃圾箱。'), 'success'); startCooldown(button, payload.cooldown || OTP_COOLDOWN_SECONDS); } catch (error) { button.classList.remove('is-loading'); setStatus(status, config.labels.networkError || '网络错误,请稍后重试。', 'error'); button.dataset.otpLocked = '0'; button.setAttribute('aria-disabled', 'false'); button.textContent = config.labels.send || '发送验证码'; button.disabled = false; } } function mount() { const form = findTargetForm(); if (!form || form.querySelector('.itstudio-otp-wrap')) { return; } const codeInput = findField(form, config.codeFieldId); if (!codeInput) { return; } codeInput.setAttribute('inputmode', 'numeric'); codeInput.setAttribute('pattern', '[0-9]{6}'); codeInput.setAttribute('maxlength', '6'); codeInput.setAttribute('autocomplete', 'one-time-code'); if (!codeInput.getAttribute('placeholder')) { codeInput.setAttribute('placeholder', '请输入 6 位验证码'); } codeInput.classList.add('itstudio-otp-input'); const wrapper = document.createElement('div'); wrapper.className = 'itstudio-otp-wrap'; const group = document.createElement('div'); group.className = 'itstudio-otp-group'; const button = document.createElement('button'); button.type = 'button'; button.className = 'itstudio-otp-send'; button.textContent = config.labels.send || '发送验证码'; button.dataset.otpLocked = '0'; button.setAttribute('aria-disabled', 'false'); const status = document.createElement('div'); status.className = 'itstudio-otp-status'; status.setAttribute('role', 'status'); status.setAttribute('aria-live', 'polite'); status.style.display = 'none'; if (codeInput.parentNode) { codeInput.parentNode.insertBefore(wrapper, codeInput); group.appendChild(codeInput); group.appendChild(button); wrapper.appendChild(group); wrapper.appendChild(status); } button.addEventListener('click', function (e) { e.preventDefault(); sendCode(form, button, status); }); } let mountQueued = false; const scheduleMount = function () { if (mountQueued) { return; } mountQueued = true; window.requestAnimationFrame(function () { mountQueued = false; mount(); }); }; mount(); const observerRoot = document.querySelector('.join-form-content') || document.body; const observer = new MutationObserver(function (mutations) { const formStructureChanged = mutations.some(function (mutation) { return Array.from(mutation.addedNodes).concat(Array.from(mutation.removedNodes)).some(function (node) { return node.nodeType === 1 && ( node.matches('.frm_forms, form') || Boolean(node.querySelector && node.querySelector('.frm_forms, form')) ); }); }); if (formStructureChanged) { scheduleMount(); } }); observer.observe(observerRoot, {childList: true, subtree: true}); })();