/** * AKSRT Front Submit * * Handles front-end post submission, draft save/load, * editor toolbar, cover preview, and form protection. * * @package AKSRT */ (function () { 'use strict'; var i18n = (window.aksrtSubmitData && window.aksrtSubmitData.i18n) || {}; var ajaxUrl = (window.aksrtSubmitData && window.aksrtSubmitData.ajaxUrl) || '/wp-admin/admin-ajax.php'; var nonce = (window.aksrtSubmitData && window.aksrtSubmitData.nonce) || ''; var restUrl = ((window.aksrtSubmitData && window.aksrtSubmitData.restUrl) || '/wp-json/aksrt/v1').replace(/\/$/, ''); var restNonce = (window.aksrtSubmitData && window.aksrtSubmitData.restNonce) || ''; var aiCfg = (window.aksrtSubmitData && window.aksrtSubmitData.ai) || {}; var FRONT_SUBMIT = { form: null, isDirty: false, isSubmitting: false, init: function () { this.form = document.getElementById('aksrt-submit-form'); if (!this.form) return; this.bindDirtyTracking(); this.bindSubmit(); this.bindDraftSave(); this.bindDraftDelete(); this.bindEditorToolbar(); this.bindCoverPreview(); this.bindWordCount(); this.bindLeaveWarning(); this.bindAiAssist(); }, /** * Track form changes */ bindDirtyTracking: function () { var self = this; this.form.addEventListener('input', function () { self.isDirty = true; self.form.setAttribute('data-dirty', 'true'); }); }, /** * Leave page warning */ bindLeaveWarning: function () { var self = this; window.addEventListener('beforeunload', function (e) { if (self.isDirty && !self.isSubmitting) { e.preventDefault(); e.returnValue = i18n.confirmLeave || ''; } }); }, /** * Form submission */ bindSubmit: function () { var self = this; this.form.addEventListener('submit', function (e) { e.preventDefault(); if (self.isSubmitting) return; var submitBtn = document.getElementById('aksrt-submit-btn'); var statusInput = document.getElementById('submit-status'); statusInput.value = 'pending'; self.submitForm(submitBtn, 'pending'); }); }, /** * Draft save */ bindDraftSave: function () { var self = this; var draftBtn = document.getElementById('aksrt-save-draft'); if (!draftBtn) return; draftBtn.addEventListener('click', function () { if (self.isSubmitting) return; var statusInput = document.getElementById('submit-status'); statusInput.value = 'draft'; self.submitForm(draftBtn, 'draft'); }); }, /** * Submit form via AJAX */ submitForm: function (btn, status) { var self = this; var formData = new FormData(this.form); formData.append('action', 'aksrt_submit_post'); formData.append('_nonce', nonce); var originalText = btn.innerHTML; btn.disabled = true; btn.innerHTML = ' ' + (status === 'draft' ? (i18n.saving || '保存中...') : (i18n.submitting || '提交中...')); self.isSubmitting = true; fetch(ajaxUrl, { method: 'POST', body: formData, credentials: 'same-origin', }) .then(function (res) { return res.json(); }) .then(function (data) { if (data.success) { self.isDirty = false; self.form.setAttribute('data-dirty', 'false'); btn.innerHTML = ' ' + (data.data && data.data.message ? data.data.message : (status === 'draft' ? (i18n.saved || '草稿已保存') : (i18n.submitted || '投稿已提交'))); btn.classList.add('is-success'); if (status === 'pending' && data.data && data.data.redirect) { setTimeout(function () { window.location.href = data.data.redirect; }, 1500); } else if (status === 'draft') { // Reload to update draft list setTimeout(function () { window.location.reload(); }, 1000); } } else { alert(data.data && data.data.message ? data.data.message : (i18n.error || '操作失败')); btn.disabled = false; btn.innerHTML = originalText; } }) .catch(function () { alert(i18n.error || '操作失败,请重试'); btn.disabled = false; btn.innerHTML = originalText; }) .finally(function () { self.isSubmitting = false; }); }, /** * Draft delete */ bindDraftDelete: function () { document.addEventListener('click', function (e) { var btn = e.target.closest('.aksrt-submit-drafts__delete'); if (!btn) return; var draftId = btn.getAttribute('data-draft-id'); if (!draftId) return; if (!confirm(i18n.confirmDelete || '确定要删除这个草稿吗?')) return; var formData = new FormData(); formData.append('action', 'aksrt_delete_draft'); formData.append('_nonce', nonce); formData.append('draft_id', draftId); fetch(ajaxUrl, { method: 'POST', body: formData, credentials: 'same-origin', }) .then(function (res) { return res.json(); }) .then(function (data) { if (data.success) { var item = btn.closest('.aksrt-submit-drafts__item'); if (item) item.remove(); } else { alert(data.data && data.data.message ? data.data.message : '删除失败'); } }) .catch(function () { alert('删除失败'); }); }); }, /** * Editor toolbar actions */ bindEditorToolbar: function () { var textarea = document.getElementById('submit-content'); if (!textarea) return; document.addEventListener('click', function (e) { var btn = e.target.closest('[data-action]'); if (!btn) return; var action = btn.getAttribute('data-action'); var form = btn.closest('.aksrt-submit-form'); if (!form) return; var ta = form.querySelector('textarea'); if (!ta) return; switch (action) { case 'bold': FRONT_SUBMIT.wrapSelection(ta, '**'); break; case 'italic': FRONT_SUBMIT.wrapSelection(ta, '*'); break; case 'code': var sel = ta.value.substring(ta.selectionStart, ta.selectionEnd); if (sel.indexOf('\n') >= 0) { FRONT_SUBMIT.insertAtCursor(ta, '```\n', '\n```'); } else { FRONT_SUBMIT.wrapSelection(ta, '`'); } break; case 'link': FRONT_SUBMIT.askUrl(btn, '链接地址', function (url) { if (url) { var linkText = ta.value.substring(ta.selectionStart, ta.selectionEnd) || '链接文字'; FRONT_SUBMIT.insertAtCursor(ta, '[' + linkText + '](' + url + ')'); } }); break; case 'image': FRONT_SUBMIT.askUrl(btn, '图片地址', function (url) { if (url) { FRONT_SUBMIT.insertAtCursor(ta, '![图片](' + url + ')'); } }); break; case 'quote': FRONT_SUBMIT.insertAtCursor(ta, '> ', '\n'); break; case 'heading': FRONT_SUBMIT.insertAtCursor(ta, '## ', '\n'); break; case 'list': FRONT_SUBMIT.insertAtCursor(ta, '- ', '\n'); break; } }); }, askUrl: function (anchor, placeholder, callback) { var existing = document.querySelector('.aksrt-inline-url-input'); if (existing) existing.remove(); var input = document.createElement('input'); input.type = 'url'; input.className = 'aksrt-inline-url-input'; input.placeholder = placeholder || 'https://'; input.value = 'https://'; input.style.cssText = 'display:block;width:100%;max-width:320px;margin:4px 0;padding:6px 10px;border:1px solid #dce3ec;border-radius:8px;font-size:13px;'; var parent = anchor.parentElement; parent.insertBefore(input, anchor.nextSibling); input.focus(); input.select(); function done() { var val = input.value.trim(); input.remove(); callback(val); } input.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); done(); } if (e.key === 'Escape') { input.remove(); callback(''); } }); input.addEventListener('blur', function () { done(); }); }, insertAtCursor: function (textarea, before, after) { var start = textarea.selectionStart; var end = textarea.selectionEnd; var text = textarea.value; var selected = text.substring(start, end); var replacement = before + (selected || '') + (after || ''); textarea.value = text.substring(0, start) + replacement + text.substring(end); textarea.focus(); var newPos = start + before.length + selected.length; textarea.setSelectionRange(newPos, newPos); textarea.dispatchEvent(new Event('input', { bubbles: true })); }, wrapSelection: function (textarea, marker) { this.insertAtCursor(textarea, marker, marker); }, /** * Cover image preview */ bindCoverPreview: function () { var thumbInput = document.getElementById('submit-thumbnail'); var previewWrap = document.getElementById('cover-preview'); var previewImg = document.getElementById('cover-preview-img'); var removeBtn = document.getElementById('cover-remove'); if (!thumbInput || !previewWrap || !previewImg) return; thumbInput.addEventListener('change', function () { var url = thumbInput.value.trim(); if (url && /^https?:\/\//.test(url)) { previewImg.src = url; previewWrap.style.display = 'flex'; previewImg.onerror = function () { previewWrap.style.display = 'none'; }; } else { previewWrap.style.display = 'none'; } }); if (removeBtn) { removeBtn.addEventListener('click', function () { thumbInput.value = ''; previewWrap.style.display = 'none'; thumbInput.dispatchEvent(new Event('input', { bubbles: true })); }); } // Media library picker var pickBtn = document.getElementById('aksrt-pick-cover'); if (pickBtn && window.wp && window.wp.media) { pickBtn.addEventListener('click', function (e) { e.preventDefault(); var mediaFrame = wp.media({ title: '选择封面图', button: { text: '使用此图片' }, multiple: false, library: { type: 'image' }, }); mediaFrame.on('select', function () { var attachment = mediaFrame.state().get('selection').first().toJSON(); thumbInput.value = attachment.url; previewImg.src = attachment.url; previewWrap.style.display = 'flex'; thumbInput.dispatchEvent(new Event('input', { bubbles: true })); }); mediaFrame.open(); }); } }, /** * Word count */ bindWordCount: function () { var contentEl = document.getElementById('submit-content'); var countEl = document.getElementById('submit-word-count'); if (!contentEl || !countEl) return; var updateCount = function () { var text = contentEl.value.replace(/<[^>]*>/g, '').trim(); var chars = text.length; var words = text ? text.split(/\s+/).length : 0; countEl.textContent = chars + ' 字 · ~' + words + ' 词'; }; contentEl.addEventListener('input', updateCount); updateCount(); }, /** * AI 摘要 / 标签辅助(仅建议回填,不自动提交) */ bindAiAssist: function () { var self = this; if (!aiCfg.available) return; var buttons = this.form.querySelectorAll('.aksrt-ai-assist-btn'); if (!buttons.length) return; buttons.forEach(function (btn) { btn.hidden = false; btn.addEventListener('click', function (e) { e.preventDefault(); var action = btn.getAttribute('data-ai-action'); if (action === 'summary') self.runAiSummary(btn); if (action === 'tags') self.runAiTags(btn); }); }); }, getSubmitContent: function () { var titleEl = document.getElementById('submit-title'); var contentEl = document.getElementById('submit-content'); return { title: titleEl ? titleEl.value.trim() : '', content: contentEl ? contentEl.value.trim() : '', }; }, runAiSummary: function (btn) { var data = this.getSubmitContent(); if (!data.content) { alert(i18n.needContent || '请先填写正文内容'); return; } this.aiRequest(btn, '/ai/summary-suggest', { title: data.title, content: data.content, max_length: 200, }, function (payload) { var summary = payload && payload.summary ? String(payload.summary) : ''; var el = document.getElementById('submit-excerpt'); if (el && summary) { el.value = summary; el.dispatchEvent(new Event('input', { bubbles: true })); } }); }, runAiTags: function (btn) { var data = this.getSubmitContent(); if (!data.content && !data.title) { alert(i18n.needContent || '请先填写正文内容'); return; } this.aiRequest(btn, '/ai/tags-suggest', { title: data.title, content: data.content, }, function (payload) { var tags = payload && Array.isArray(payload.tags) ? payload.tags : []; var el = document.getElementById('submit-tags'); if (el && tags.length) { el.value = tags.join(', '); el.dispatchEvent(new Event('input', { bubbles: true })); } }); }, aiRequest: function (btn, path, body, onSuccess) { if (btn.getAttribute('data-loading') === '1') return; var original = btn.textContent; btn.setAttribute('data-loading', '1'); btn.textContent = i18n.aiGenerating || '生成中…'; fetch(restUrl + path, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': restNonce, }, body: JSON.stringify(body || {}), }) .then(function (r) { return r.json().then(function (json) { return { ok: r.ok, json: json }; }); }) .then(function (res) { btn.removeAttribute('data-loading'); btn.textContent = original; if (!res.ok || !res.json || res.json.success === false) { var msg = (res.json && res.json.message) || i18n.aiFailed || 'AI 生成失败'; alert(msg); return; } onSuccess(res.json.data || {}); }) .catch(function () { btn.removeAttribute('data-loading'); btn.textContent = original; alert(i18n.aiFailed || 'AI 生成失败'); }); }, }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function () { FRONT_SUBMIT.init(); }); } else { FRONT_SUBMIT.init(); } })();