(() => { const endpoint = window.PROOFREAD_ENDPOINT; if (!endpoint) return; const csrfNameEl = document.getElementById('proofreadCsrfName'); const csrfHashEl = document.getElementById('proofreadCsrfHash'); const resize = (el) => { if (!el) return; el.style.height = 'auto'; el.style.height = `${el.scrollHeight}px`; }; const isSafeMatch = (m) => { const issueType = (m.rule?.issueType || '').toLowerCase(); const repl = m.replacements || []; if (repl.length !== 1) return false; const len = m.length ?? 0; if (len > 25) return false; if (issueType === 'misspelling') return true; const msg = (m.message || '').toLowerCase(); if (msg.includes('whitespace') || msg.includes('space') || msg.includes('punctuation')) return true; return false; }; const applyMatches = (text, matches) => { const sorted = [...matches].sort((a, b) => (b.offset || 0) - (a.offset || 0)); let out = text; for (const m of sorted) { const offset = m.offset || 0; const length = m.length || 0; const repl = m.replacements?.[0]?.value; if (typeof repl !== 'string') continue; out = out.slice(0, offset) + repl + out.slice(offset + length); } return out; }; const refreshCsrf = (hash) => { if (!hash) return; if (csrfHashEl) csrfHashEl.value = hash; const name = csrfNameEl ? csrfNameEl.value : null; if (!name) return; document.querySelectorAll(`input[name="${name}"]`).forEach((el) => { el.value = hash; }); }; const setStatus = (targetId, message, isError = false) => { const status = document.querySelector(`[data-status-for="${targetId}"]`); if (!status) return; status.textContent = message; status.classList.toggle('text-danger', !!isError); }; const clearList = (targetId) => { const list = document.querySelector(`[data-list-for="${targetId}"]`); if (list) list.innerHTML = ''; }; const renderRisky = (targetId, risky, textarea) => { const list = document.querySelector(`[data-list-for="${targetId}"]`); if (!list) return; list.innerHTML = ''; risky.forEach((match) => { const li = document.createElement('li'); li.className = 'mb-1'; const suggestion = match.replacements?.[0]?.value || '(no suggestion)'; const label = document.createElement('span'); label.textContent = `${match.message || 'Suggestion'} → ${suggestion}`; const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'btn btn-link btn-sm ps-0'; btn.textContent = 'Apply'; btn.addEventListener('click', () => { textarea.value = applyMatches(textarea.value, [match]); resize(textarea); textarea.dispatchEvent(new Event('input', { bubbles: true })); li.remove(); }); li.appendChild(label); li.appendChild(document.createTextNode(' ')); li.appendChild(btn); list.appendChild(li); }); }; const proofreadField = async (textarea) => { if (!textarea) return { ok: false }; const targetId = textarea.id || textarea.name || ''; const text = textarea.value || ''; if (!text.trim()) { setStatus(targetId, 'Enter text to proofread.', true); clearList(targetId); return { ok: false }; } setStatus(targetId, 'Checking...'); clearList(targetId); const body = new URLSearchParams(); body.append('text', text); const name = csrfNameEl ? csrfNameEl.value : null; const hash = csrfHashEl ? csrfHashEl.value : null; if (name && hash) body.append(name, hash); let data; try { const resp = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body, }); data = await resp.json(); } catch (err) { setStatus(targetId, 'Network error.', true); return { ok: false, error: 'Network error.' }; } refreshCsrf(data?.csrfHash); if (!data || !data.ok) { setStatus(targetId, data?.error || 'Proofread failed.', true); return { ok: false, error: data?.error || 'Proofread failed.' }; } const matches = data.result?.matches || []; if (!matches.length) { setStatus(targetId, 'No issues found.'); return { ok: true, safe: 0, risky: 0 }; } const safe = matches.filter(isSafeMatch); const risky = matches.filter((m) => !isSafeMatch(m)); const updated = applyMatches(text, safe); if (updated !== text) { textarea.value = updated; resize(textarea); textarea.dispatchEvent(new Event('input', { bubbles: true })); } renderRisky(targetId, risky, textarea); setStatus(targetId, `Auto-fixed: ${safe.length}. Needs review: ${risky.length}.`); return { ok: true, safe: safe.length, risky: risky.length }; }; const proofreadAll = async () => { const btn = document.getElementById('proofreadAllBtn'); const summary = document.getElementById('proofreadAllStatus'); const fields = Array.from(document.querySelectorAll('textarea.review-field')); if (!fields.length) return; if (btn) btn.disabled = true; if (summary) summary.textContent = 'Checking all reviews...'; let totalSafe = 0; let totalRisky = 0; let errors = 0; for (const field of fields) { const result = await proofreadField(field); if (result?.safe) totalSafe += result.safe; if (result?.risky) totalRisky += result.risky; if (!result?.ok) errors += 1; } if (summary) { summary.textContent = `Auto-fixed: ${totalSafe}. Needs review: ${totalRisky}. ${errors ? 'Errors on ' + errors + ' field(s).' : ''}`; } if (btn) btn.disabled = false; }; document.addEventListener('DOMContentLoaded', () => { const btn = document.getElementById('proofreadAllBtn'); if (btn) btn.addEventListener('click', proofreadAll); }); })();