Files
wordpress-theme-aksrt/assets/js/front-submit.js
T
2026-08-25 19:42:16 +08:00

807 lines
27 KiB
JavaScript

/**
* 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 insertEditorContent(html) {
if (window.tinymce) {
var ed = window.tinymce.get('post_content');
if (ed) {
ed.focus();
ed.insertContent(html);
return;
}
}
var ta = qs('#post_content') || qs('[name="post_content"]');
if (ta) {
ta.value += html;
}
}
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.hydrateEditor();
this.bindDirty();
this.bindAutoHeight();
this.bindSubmitButtons();
this.bindDraftDelete();
this.bindFeatured();
this.bindPay();
this.bindShortcodes();
this.bindLeaveWarning();
this.bindAi();
this.bindTagActive();
this.loadFeaturedFromDom();
},
hydrateEditor: function () {
var snapshot = document.getElementById('aksrt-initial-post-content');
var textarea = qs('#post_content') || qs('[name="post_content"]');
var initialContent = snapshot ? snapshot.value : textarea ? textarea.value : '';
if (!initialContent) {
return;
}
var fallbackToTextEditor = function () {
if (window.switchEditors && typeof window.switchEditors.go === 'function') {
window.switchEditors.go('post_content', 'html');
}
if (textarea) {
textarea.value = initialContent;
textarea.style.opacity = '1';
textarea.style.display = 'block';
}
};
var attempts = 0;
var restore = function () {
var editor = window.tinymce && window.tinymce.get('post_content');
if (!editor) {
attempts += 1;
if (attempts < 20) {
setTimeout(restore, 250);
} else {
fallbackToTextEditor();
}
return;
}
var hydrate = function () {
var current = editor.getContent({ format: 'raw' });
if (!current || !current.trim()) {
editor.setContent(initialContent);
editor.save();
}
if (!editor.getContent({ format: 'raw' }).trim()) {
fallbackToTextEditor();
}
};
if (editor.initialized) {
hydrate();
} else {
editor.on('init', hydrate);
}
};
restore();
},
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').forEach(function (input) {
var sync = function () {
var tag = input.closest('.aksrt-submit-tag');
if (tag) {
tag.classList.toggle('is-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 =
'<i class="fa fas fa-spinner fa-spin"></i> ' +
(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 =
'<span class="badg">' + (i18n.lastSaved || 'Last saved: ') + d.time + '</span>';
}
}
if (d.url) {
var view = qs('.aksrt-submit-view-btn');
if (view) {
view.innerHTML =
'<a class="but c-blue" target="_blank" href="' +
d.url +
'"><i class="fa fas fa-file-alt"></i> ' +
(i18n.preview || 'Preview') +
'</a>';
}
}
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 typeInput = qs('[data-featured-type]');
var jsonInput = qs('[data-featured-json]');
if (typeInput) {
typeInput.value = type || '';
}
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 html = '';
if (t === 'image' && d.url) {
html = '<img src="' + d.url + '" alt="">';
} else if (t === 'slide' && Array.isArray(d) && d[0] && d[0].url) {
html =
'<img src="' +
d[0].url +
'" alt=""><span class="aksrt-submit-featured__badge"><i class="fa fas fa-image"></i></span>';
} else if (t === 'video') {
html =
(d.pic
? '<img src="' + d.pic + '" alt="">'
: '<div class="aksrt-submit-featured__video-placeholder"><i class="fa fas fa-play"></i></div>') +
'<span class="aksrt-submit-featured__badge"><i class="fa fas fa-video"></i></span>';
} else {
html = '<div class="aksrt-submit-featured__empty"><i class="fa fas fa-image"></i><span>Cover</span></div>';
}
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';
}
});
});
var list = qs('[data-pay-download-list]', box);
var tpl = qs('[data-pay-download-tpl]', box);
var addBtn = qs('[data-pay-download-add]', box);
if (addBtn && list && tpl) {
addBtn.addEventListener('click', function () {
var node = tpl.content.cloneNode(true);
var idx = list.querySelectorAll('[data-pay-download-item]').length;
qsa('[data-name]', node).forEach(function (input) {
input.name = 'aksrt_pay[downloads][' + idx + '][' + input.getAttribute('data-name') + ']';
});
list.appendChild(node);
});
}
var uploadBtn = qs('[data-pay-download-upload]', box);
if (uploadBtn && list && tpl) {
uploadBtn.addEventListener('click', function () {
var toastId = 'aksrt-front-submit-resource-upload-toast';
App.pickUpload('resource', '*/*').then(function (file) {
if (!file) return null;
uploadBtn.disabled = true;
notyf(i18n.uploading || '正在上传附件…', 'load', '', toastId);
return App.uploadFile('resource', file);
}).then(function (file) {
if (!file) return;
var node = tpl.content.cloneNode(true);
var idx = list.querySelectorAll('[data-pay-download-item]').length;
qsa('[data-name]', node).forEach(function (input) {
input.name = 'aksrt_pay[downloads][' + idx + '][' + input.getAttribute('data-name') + ']';
});
var url = qs('[data-pay-download-url]', node);
var label = qs('[data-pay-download-label]', node);
if (url) url.value = file.url || '';
if (label) label.value = file.name || '';
list.appendChild(node);
notyf(i18n.uploaded || '附件上传成功', 'success', 5, toastId);
}).catch(function (error) {
notyf(error.message || i18n.error || '附件上传失败', 'danger', 5, toastId);
}).finally(function () {
uploadBtn.disabled = false;
});
});
}
if (list) {
list.addEventListener('click', function (e) {
var rm = e.target.closest('[data-pay-download-remove]');
if (!rm) {
return;
}
var item = rm.closest('[data-pay-download-item]');
if (item) {
item.remove();
}
});
}
},
bindShortcodes: function () {
document.addEventListener('click', function (e) {
var btn = e.target.closest('[data-insert-shortcode]');
if (!btn) {
return;
}
e.preventDefault();
if (btn.hasAttribute('data-insert-upload-image')) {
var toastId = 'aksrt-front-submit-editor-image-toast';
App.pickUpload('image', 'image/*').then(function (file) {
if (!file) return null;
notyf(i18n.uploading || '正在上传正文图片…', 'load', '', toastId);
return App.uploadFile('image', file);
}).then(function (file) {
if (!file || !file.url) return;
insertEditorContent('<img src="' + file.url.replace(/"/g, '&quot;') + '" alt="">');
notyf(i18n.uploaded || '正文图片上传成功', 'success', 5, toastId);
}).catch(function (error) {
notyf(error.message || i18n.error || '正文图片上传失败', 'danger', 5, toastId);
});
return;
}
var type = btn.getAttribute('data-insert-shortcode');
if (type === 'hide') {
insertEditorContent('[hidecontent type="login"]' + (i18n.hidePlaceholder || '隐藏内容') + '[/hidecontent]');
} else if (type === 'payshow') {
insertEditorContent('[hidecontent type="payshow"]' + (i18n.payPlaceholder || '付费可见内容') + '[/hidecontent]');
} else if (type === 'reply') {
insertEditorContent('[hidecontent type="reply"]' + (i18n.replyPlaceholder || '回复后可见') + '[/hidecontent]');
} else if (type === 'vip') {
insertEditorContent('[vip]' + (i18n.vipPlaceholder || 'VIP 可见内容') + '[/vip]');
}
});
},
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();
}
})();