/**
* 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, '');
}
});
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();
}
})();