/** * AKSRT front-end article submit (write/post page). * Handles TinyMCE content, featured cover, pay box, draft/submit AJAX. * * @package AKSRT */ /* global notyf */ (function () { 'use strict'; var cfg = window.aksrtSubmitData || {}; var i18n = cfg.i18n || {}; var ajaxUrl = cfg.ajaxUrl || '/wp-admin/admin-ajax.php'; var nonce = cfg.nonce || ''; function qs(sel, root) { return (root || document).querySelector(sel); } function qsa(sel, root) { return Array.prototype.slice.call((root || document).querySelectorAll(sel)); } function getEditorContent() { if (window.tinymce) { var ed = window.tinymce.get('post_content'); if (ed) { return ed.getContent(); } } var ta = qs('#post_content') || qs('[name="post_content"]'); return ta ? ta.value : ''; } function serializeForm(form) { var data = {}; var fd = new FormData(form); fd.forEach(function (value, key) { if (Object.prototype.hasOwnProperty.call(data, key)) { if (!Array.isArray(data[key])) { data[key] = [data[key]]; } data[key].push(value); } else { data[key] = value; } }); return data; } function toFormBody(obj) { var params = new URLSearchParams(); function append(key, val) { if (val === undefined || val === null) { return; } if (Array.isArray(val)) { val.forEach(function (v) { append(key, v); }); return; } if (typeof val === 'object') { Object.keys(val).forEach(function (k) { append(key + '[' + k + ']', val[k]); }); return; } params.append(key, String(val)); } Object.keys(obj).forEach(function (k) { append(k, obj[k]); }); return params; } var App = { form: null, isDirty: false, isSubmitting: false, featured: { type: '', data: {} }, init: function () { this.form = document.getElementById('aksrt-submit-form'); if (!this.form) { return; } this.bindDirty(); this.bindAutoHeight(); this.bindSubmitButtons(); this.bindDraftDelete(); this.bindFeatured(); this.bindPay(); this.bindLeaveWarning(); this.bindAi(); this.bindTagActive(); this.loadFeaturedFromDom(); }, bindDirty: function () { var self = this; this.form.addEventListener('input', function () { self.isDirty = true; self.form.setAttribute('data-dirty', 'true'); }); if (window.tinymce) { var tryBind = function () { var ed = window.tinymce.get('post_content'); if (ed) { ed.on('change keyup', function () { self.isDirty = true; }); } else { setTimeout(tryBind, 400); } }; tryBind(); } }, bindLeaveWarning: function () { var self = this; if (cfg.leaveWarning === false) { return; } window.addEventListener('beforeunload', function (e) { if (self.isDirty && !self.isSubmitting && !window.post_is_save) { e.preventDefault(); e.returnValue = i18n.confirmLeave || ''; } }); }, bindAutoHeight: function () { qsa('[data-auto-height]').forEach(function (el) { var max = parseInt(el.getAttribute('data-max-height') || '78', 10); var resize = function () { el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, max) + 'px'; }; el.addEventListener('input', resize); resize(); }); }, bindTagActive: function () { qsa('.aksrt-submit-tag input, .newpost-filter-tags input').forEach(function (input) { var sync = function () { var tag = input.closest('.aksrt-submit-tag, .tag-list'); if (tag) { tag.classList.toggle('is-active', input.checked); tag.classList.toggle('active', input.checked); } }; input.addEventListener('change', sync); sync(); }); }, bindSubmitButtons: function () { var self = this; qsa('.aksrt-submit-btn', this.form).forEach(function (btn) { btn.addEventListener('click', function (e) { e.preventDefault(); if (self.isSubmitting) { return; } self.submit(btn, btn.getAttribute('data-action') || 'posts_save'); }); }); this.form.addEventListener('submit', function (e) { e.preventDefault(); }); }, collectPayload: function (action) { var data = serializeForm(this.form); data.action = action === 'posts_draft' ? 'aksrt_save_draft' : 'aksrt_submit_post'; data._nonce = nonce || data._nonce || ''; data.post_content = getEditorContent(); data.post_title = data.post_title || ((qs('#submit-title') && qs('#submit-title').value) || ''); data.status = action === 'posts_draft' ? 'draft' : 'pending'; data.submit_action = action; var featuredJson = qs('[data-featured-json]', this.form); if (featuredJson && featuredJson.value) { try { data.featured_data = JSON.parse(featuredJson.value); } catch (err) { data.featured_data = this.featured; } } else if (this.featured && this.featured.type) { data.featured_data = this.featured; } return data; }, submit: function (btn, action) { var self = this; if (action !== 'posts_draft' && !this.requestCaptcha(function () { self.submit(btn, action); })) { return; } this.isSubmitting = true; var toastId = action === 'posts_draft' ? 'aksrt-front-submit-draft-toast' : 'aksrt-front-submit-publish-toast'; var original = btn.innerHTML; btn.disabled = true; btn.innerHTML = ' ' + (action === 'posts_draft' ? i18n.saving || 'Saving...' : i18n.submitting || 'Submitting...'); notyf( action === 'posts_draft' ? i18n.saving || '正在保存草稿…' : i18n.submitting || '正在提交文章…', 'load', '', toastId ); var payload = this.collectPayload(action); if (payload.featured_data && typeof payload.featured_data === 'object') { payload.featured_data = JSON.stringify(payload.featured_data); } fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, body: toFormBody(payload), }) .then(function (r) { return r.json(); }) .then(function (res) { var ok = !!(res && res.success); var d = (res && (res.data || res)) || {}; if (!ok) { throw new Error((d && d.message) || i18n.error || 'Error'); } self.isDirty = false; window.post_is_save = true; if (d.posts_id || d.post_id) { var hid = self.form.querySelector('input[name="posts_id"]'); if (hid) { hid.value = String(d.posts_id || d.post_id); } } if (d.time) { var timeEl = qs('.aksrt-submit-modified-time'); if (timeEl) { timeEl.innerHTML = '' + (i18n.lastSaved || 'Last saved: ') + d.time + ''; } } if (d.url) { var view = qs('.aksrt-submit-view-btn'); if (view) { view.innerHTML = ' ' + (i18n.preview || 'Preview') + ''; } } notyf( d.message || (action === 'posts_draft' ? i18n.saved || '草稿已保存' : i18n.submitted || '投稿已提交'), 'success', 5, toastId ); if (d.goto) { setTimeout(function () { window.location.href = d.goto; }, 1000); return; } if (d.reload) { setTimeout(function () { window.location.reload(); }, 1000); return; } setTimeout(function () { btn.disabled = false; btn.innerHTML = original; self.isSubmitting = false; }, 300); }) .catch(function (err) { notyf(err.message || i18n.error || '操作失败,请重试', 'danger', 5, toastId); btn.disabled = false; btn.innerHTML = original; self.isSubmitting = false; }); }, requestCaptcha: function (onVerified) { var verified = this.form.querySelector('input[name="captcha_verified"]'); if (verified && verified.value === '1') { return true; } var captcha = window.AKSRTCaptchaState && window.AKSRTCaptchaState.scenes && window.AKSRTCaptchaState.scenes.front_submit; if (!captcha || typeof captcha.verify !== 'function') { return true; } captcha.verify(function (passed) { if (passed) { onVerified(); } }); return false; }, bindDraftDelete: function () { document.addEventListener('click', function (e) { var btn = e.target.closest('.aksrt-submit-drafts__delete'); if (!btn) { return; } e.preventDefault(); var id = btn.getAttribute('data-draft-id'); if (!id) { return; } if (!window.confirm(i18n.confirmDelete || 'Delete?')) { return; } var toastId = 'aksrt-front-submit-delete-draft-toast'; btn.disabled = true; notyf(i18n.deleting || '正在删除草稿…', 'load', '', toastId); fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, body: toFormBody({ action: 'aksrt_delete_draft', _nonce: nonce, draft_id: id }), }) .then(function (r) { return r.json(); }) .then(function (res) { if (res && res.success) { var item = btn.closest('.aksrt-submit-drafts__item'); if (item) { item.remove(); } notyf((res.data && res.data.message) || i18n.deleted || '草稿已删除', 'success', 5, toastId); } else { btn.disabled = false; notyf((res && res.data && res.data.message) || i18n.error || '删除失败', 'danger', 5, toastId); } }) .catch(function () { btn.disabled = false; notyf(i18n.error || '网络错误,请重试', 'danger', 5, toastId); }); }); }, loadFeaturedFromDom: function () { var box = qs('.featured-edit, .aksrt-submit-featured'); if (!box) { return; } var raw = box.getAttribute('data-featured-args'); if (!raw) { return; } try { var args = JSON.parse(raw); if (args && args.type) { this.featured = { type: args.type, data: args.data || {} }; } } catch (e) {} }, setFeatured: function (type, data) { this.featured = { type: type || '', data: data || {} }; var jsonInput = qs('[data-featured-json]'); if (jsonInput) { jsonInput.value = JSON.stringify(this.featured); } this.renderFeaturedPreview(); this.isDirty = true; }, renderFeaturedPreview: function () { var preview = qs('[data-featured-preview]'); if (!preview) { return; } var t = this.featured.type; var d = this.featured.data || {}; var box = qs('.featured-edit'); if (box) { box.classList.remove('featured-loaded', 'image', 'slide', 'video'); if (t && t !== 'close') { box.classList.add('featured-loaded', t); } } var html = ''; if (t === 'image' && d.url) { html = ''; } else if (t === 'slide' && Array.isArray(d) && d[0] && d[0].url) { html = ''; } else if (t === 'video') { html = (d.pic ? '' : '') + ''; } preview.innerHTML = html; var clearBtn = qs('[data-featured-action="close"]'); if (clearBtn) { clearBtn.hidden = !t || t === 'close'; } }, uploadFile: function (kind, file) { var upload = (cfg.uploads || {})[kind] || null; if (!upload || !upload.url || !file) { return Promise.reject(new Error(i18n.error || 'Upload unavailable')); } if (upload.maxSize && file.size > upload.maxSize) { return Promise.reject(new Error(i18n.fileTooLarge || '文件大小超过限制')); } var body = new FormData(); body.append('file', file); return fetch(upload.url, { method: 'POST', credentials: 'same-origin', headers: { 'X-WP-Nonce': cfg.restNonce || '' }, body: body, }).then(function (response) { return response.json().catch(function () { return {}; }).then(function (payload) { if (!response.ok || !payload || !payload.success) { throw new Error((payload && payload.message) || i18n.error || 'Upload failed'); } return payload; }); }); }, pickUpload: function (kind, accept) { return new Promise(function (resolve) { var input = document.createElement('input'); input.type = 'file'; input.accept = accept; input.addEventListener('change', function () { resolve(input.files && input.files[0] ? input.files[0] : null); }, { once: true }); input.click(); }); }, bindFeatured: function () { var self = this; var box = qs('.featured-edit, .aksrt-submit-featured'); if (!box) { return; } box.addEventListener('click', function (e) { var btn = e.target.closest('[data-featured-action]'); if (!btn) { return; } e.preventDefault(); var act = btn.getAttribute('data-featured-action'); if (act === 'close') { self.setFeatured('close', {}); return; } if (act === 'image') { var imageToastId = 'aksrt-front-submit-featured-upload-toast'; self.pickUpload('image', 'image/*').then(function (file) { if (!file) return null; notyf(i18n.uploading || '正在上传封面…', 'load', '', imageToastId); return self.uploadFile('image', file); }).then(function (file) { if (!file) return; self.setFeatured('image', { url: file.url, id: file.attachment_id }); notyf(i18n.uploaded || '封面上传成功', 'success', 5, imageToastId); }).catch(function (error) { notyf(error.message || i18n.error || '封面上传失败', 'danger', 5, imageToastId); }); return; } if (act === 'slide') { var slideToastId = 'aksrt-front-submit-slide-upload-toast'; self.pickUpload('image', 'image/*').then(function (file) { if (!file) return null; notyf(i18n.uploading || '正在上传幻灯片封面…', 'load', '', slideToastId); return self.uploadFile('image', file); }).then(function (file) { if (!file) return; self.setFeatured('slide', [{ url: file.url, id: file.attachment_id }]); notyf(i18n.uploaded || '幻灯片封面上传成功', 'success', 5, slideToastId); }).catch(function (error) { notyf(error.message || i18n.error || '幻灯片封面上传失败', 'danger', 5, slideToastId); }); return; } if (act === 'video') { var url = window.prompt(i18n.videoUrl || 'Video URL'); if (!url) { return; } var videoToastId = 'aksrt-front-submit-video-cover-toast'; self.pickUpload('image', 'image/*').then(function (file) { if (!file) { self.setFeatured('video', { url: url, pic: '' }); return null; } notyf(i18n.uploading || '正在上传视频封面…', 'load', '', videoToastId); return self.uploadFile('image', file); }).then(function (file) { if (!file) return; self.setFeatured('video', { url: url, pic: file.url }); notyf(i18n.uploaded || '视频封面上传成功', 'success', 5, videoToastId); }).catch(function (error) { notyf(error.message || i18n.error || '视频封面上传失败', 'danger', 5, videoToastId); }); } }); }, bindPay: function () { var box = qs('[data-pay-box]'); if (!box) { return; } var toggle = qs('[data-pay-toggle]', box); var fields = qs('[data-pay-fields]', box); var offHint = qs('[data-pay-off-hint]', box); var modoInput = qs('[data-pay-modo-input]', box); var cash = qs('[data-pay-cash]', box); var points = qs('[data-pay-points]', box); function syncToggle() { var on = toggle && toggle.checked; if (fields) { fields.hidden = !on; } if (offHint) { offHint.hidden = !!on; } } if (toggle) { toggle.addEventListener('change', syncToggle); } syncToggle(); qsa('[data-pay-modo]', box).forEach(function (btn) { btn.addEventListener('click', function () { var v = btn.getAttribute('data-pay-modo') || '0'; if (modoInput) { modoInput.value = v; } qsa('[data-pay-modo]', box).forEach(function (b) { b.classList.toggle('active', b === btn); }); if (cash) { cash.hidden = v === 'points'; } if (points) { points.hidden = v !== 'points'; } }); }); window.aksrtSubmitPayDownloads.init({ root: box, pickUpload: this.pickUpload.bind(this), uploadFile: this.uploadFile.bind(this), notify: notyf, markDirty: function () { App.isDirty = true; App.form.setAttribute('data-dirty', 'true'); }, }); }, bindAi: function () { var ai = cfg.ai || {}; if (!ai.available) { return; } qsa('.aksrt-ai-assist-btn').forEach(function (btn) { btn.hidden = false; btn.addEventListener('click', function () { var action = btn.getAttribute('data-ai-action'); var content = getEditorContent(); if (!content || content.replace(/<[^>]+>/g, '').trim().length < 10) { notyf(i18n.needContent || '请先填写正文内容', 'warning'); return; } var toastId = action === 'tags' ? 'aksrt-front-submit-ai-tags-toast' : 'aksrt-front-submit-ai-summary-toast'; var old = btn.textContent; btn.disabled = true; btn.textContent = i18n.aiGenerating || '...'; notyf(i18n.aiGenerating || '正在生成…', 'load', '', toastId); fetch((cfg.restUrl || '').replace(/\/$/, '') + '/ai/' + action, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': cfg.restNonce || '', }, body: JSON.stringify({ content: content }), }) .then(function (r) { return r.json(); }) .then(function (res) { var text = (res && (res.data || res).text) || (res && res.result) || ''; if (!text) { throw new Error(i18n.aiFailed || 'AI failed'); } if (action === 'summary') { var ex = qs('[name="excerpt"]'); if (ex) { ex.value = text; } } else if (action === 'tags') { var tags = qs('[name="tags"]'); if (tags) { tags.value = text; } } notyf(i18n.aiFilled || '已填入,可继续编辑', 'success', 5, toastId); }) .catch(function (err) { notyf(err.message || i18n.aiFailed || 'AI 生成失败', 'danger', 5, toastId); }) .finally(function () { btn.disabled = false; btn.textContent = old; }); }); }); }, }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function () { App.init(); }); } else { App.init(); } })();