1(function () { 2 const config = window.bsPartnerContact || {}; 3 const forms = document.querySelectorAll('[data-partner-contact-form]'); 4 5 if (!forms.length || !config.endpoint || !config.nonce) { 6 return; 7 } 8 9 // Wires one form instance up independently (its own challenge state, 10 // status element, submit handler) â there can be more than one on a 11 // page at once, e.g. the site-wide "Contact Us" modal (in every page's 12 // header.php) plus an inline copy of the same form embedded directly 13 // on a page like About. Returns ensureChallenge so a caller elsewhere 14 // (the modal-open trigger below) can pre-fetch for a *specific* 15 // instance rather than guessing which form on the page it means. 16 function setupForm(form) { 17 const status = form.querySelector('[data-partner-contact-status]'); 18 const submitButton = form.querySelector('button[type="submit"]'); 19 const startedAtField = form.querySelector('input[name="started_at"]'); 20 const challengeIdField = form.querySelector('input[name="challenge_id"]'); 21 const challengeTokenField = form.querySelector('input[name="challenge_token"]'); 22 let challengePromise = null; 23 24 function setStatus(message, type) { 25 if (!status) { 26 return; 27 } 28 29 status.textContent = message || ''; 30 status.classList.remove('is-error', 'is-success'); 31 32 if (type) { 33 status.classList.add(`is-${type}`); 34 } 35 } 36 37 function setBusy(isBusy) { 38 if (!submitButton) { 39 return; 40 } 41 42 submitButton.disabled = isBusy; 43 submitButton.setAttribute('aria-busy', isBusy ? 'true' : 'false'); 44 } 45 46 function clearChallenge() { 47 challengePromise = null; 48 49 if (challengeIdField) { 50 challengeIdField.value = ''; 51 } 52 53 if (challengeTokenField) { 54 challengeTokenField.value = ''; 55 } 56 } 57 58 async function requestChallenge() { 59 if (!config.challengeEndpoint || !challengeIdField || !challengeTokenField) { 60 throw new Error(config.errorMessage); 61 } 62 63 const response = await fetch(config.challengeEndpoint, { 64 method: 'POST', 65 headers: { 66 'Content-Type': 'application/json', 67 'X-WP-Nonce': config.nonce, 68 }, 69 credentials: 'same-origin', 70 body: JSON.stringify({}), 71 }); 72 73 const data = await response.json().catch(() => ({})); 74 if (!response.ok || !data.challengeId || !data.challengeToken) { 75 throw new Error(data.message || config.errorMessage); 76 } 77 78 challengeIdField.value = data.challengeId; 79 challengeTokenField.value = data.challengeToken; 80 81 if (startedAtField) { 82 startedAtField.value = String(Math.floor(Date.now() / 1000)); 83 } 84 85 return data; 86 } 87 88 function ensureChallenge() { 89 if (challengeIdField?.value && challengeTokenField?.value) { 90 return Promise.resolve(); 91 } 92 93 if (!challengePromise) { 94 challengePromise = requestChallenge().catch((error) => { 95 clearChallenge(); 96 throw error; 97 }); 98 } 99 100 return challengePromise; 101 } 102 103 form.addEventListener('focusin', () => { 104 ensureChallenge().catch(() => {}); 105 }); 106 107 form.addEventListener('submit', async (event) => { 108 event.preventDefault(); 109 110 if (!form.checkValidity()) { 111 form.reportValidity(); 112 return; 113 } 114 115 setBusy(true); 116 setStatus('', ''); 117 118 try { 119 await ensureChallenge(); 120 121 const payload = Object.fromEntries(new FormData(form).entries()); 122 payload.page_url = window.location.href; 123 124 const response = await fetch(config.endpoint, { 125 method: 'POST', 126 headers: { 127 'Content-Type': 'application/json', 128 'X-WP-Nonce': config.nonce, 129 }, 130 credentials: 'same-origin', 131 body: JSON.stringify(payload), 132 }); 133 134 const data = await response.json().catch(() => ({})); 135 136 if (!response.ok) { 137 throw new Error(data.message || config.errorMessage); 138 } 139 140 form.reset();
141 clearChallenge(); 142 setStatus(data.message || config.successMessage, 'success'); 143 } catch (error) { 144 clearChallenge(); 145 setStatus(error instanceof Error ? error.message : config.errorMessage, 'error'); 146 } finally { 147 setBusy(false); 148 } 149 }); 150 151 return { ensureChallenge }; 152 } 153 154 let modalFormEnsureChallenge = null; 155 156 forms.forEach((form) => { 157 const instance = setupForm(form); 158 159 if (form.closest(`#${config.modalId}`)) { 160 modalFormEnsureChallenge = instance.ensureChallenge; 161 } 162 }); 163 164 // Pre-fetches a challenge as soon as the modal is opened, specifically 165 // for the modal's own form instance â an inline form elsewhere on the 166 // page (e.g. About) pre-fetches for itself independently, via its own 167 // focusin listener set up above. 168 document.addEventListener('click', (event) => { 169 const trigger = event.target.closest(`[data-modal-open="${config.modalId}"]`); 170 if (!trigger || !modalFormEnsureChallenge) { 171 return; 172 } 173 174 modalFormEnsureChallenge().catch(() => {}); 175 }); 176})();
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.