Register AKSRT 写文章/投稿 page template, wire header publish entry to front submit URL, rewrite page-submit as dual-column TinyMCE compose with featured cover, multi-category/topics/filters, pay box, drafts, and capability-gated FrontSubmit AJAX (pay/featured traits).
619 lines
18 KiB
JavaScript
619 lines
18 KiB
JavaScript
/**
|
|
* AKSRT front-end article submit (write/post page).
|
|
* Handles TinyMCE content, featured cover, pay box, draft/submit AJAX.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
(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.bindDirty();
|
|
this.bindAutoHeight();
|
|
this.bindSubmitButtons();
|
|
this.bindDraftDelete();
|
|
this.bindFeatured();
|
|
this.bindPay();
|
|
this.bindShortcodes();
|
|
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').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;
|
|
this.isSubmitting = true;
|
|
var original = btn.innerHTML;
|
|
btn.disabled = true;
|
|
btn.innerHTML =
|
|
'<i class="fas fa-spinner fa-spin"></i> ' +
|
|
(action === 'posts_draft' ? i18n.saving || 'Saving...' : i18n.submitting || 'Submitting...');
|
|
|
|
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 fa-file-text-o"></i> ' +
|
|
(i18n.preview || 'Preview') +
|
|
'</a>';
|
|
}
|
|
}
|
|
btn.innerHTML =
|
|
'<i class="fas fa-check"></i> ' +
|
|
(d.message || (action === 'posts_draft' ? i18n.saved : i18n.submitted));
|
|
if (d.goto) {
|
|
setTimeout(function () {
|
|
window.location.href = d.goto;
|
|
}, 600);
|
|
return;
|
|
}
|
|
if (d.reload) {
|
|
setTimeout(function () {
|
|
window.location.reload();
|
|
}, 600);
|
|
return;
|
|
}
|
|
setTimeout(function () {
|
|
btn.disabled = false;
|
|
btn.innerHTML = original;
|
|
self.isSubmitting = false;
|
|
}, 1200);
|
|
})
|
|
.catch(function (err) {
|
|
alert(err.message || i18n.error || 'Error');
|
|
btn.disabled = false;
|
|
btn.innerHTML = original;
|
|
self.isSubmitting = 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;
|
|
}
|
|
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();
|
|
}
|
|
} else {
|
|
alert((res && res.data && res.data.message) || i18n.error || 'Error');
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
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="fas fa-images"></i></span>';
|
|
} else if (t === 'video') {
|
|
html =
|
|
(d.pic
|
|
? '<img src="' + d.pic + '" alt="">'
|
|
: '<div class="aksrt-submit-featured__video-placeholder"><i class="fas fa-play"></i></div>') +
|
|
'<span class="aksrt-submit-featured__badge"><i class="fas fa-video"></i></span>';
|
|
} else {
|
|
html = '<div class="aksrt-submit-featured__empty"><i class="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';
|
|
}
|
|
},
|
|
|
|
openMedia: function (opts, cb) {
|
|
opts = opts || {};
|
|
if (!window.wp || !wp.media) {
|
|
alert('Media library unavailable');
|
|
return;
|
|
}
|
|
var frame = wp.media({
|
|
title: opts.title || i18n.pickImage || 'Select',
|
|
button: { text: opts.button || 'OK' },
|
|
multiple: !!opts.multiple,
|
|
library: opts.library || { type: 'image' },
|
|
});
|
|
frame.on('select', function () {
|
|
var selection = frame.state().get('selection');
|
|
var files = selection.map(function (att) {
|
|
var a = att.toJSON();
|
|
return { id: a.id, url: a.url, title: a.title || '' };
|
|
});
|
|
cb(opts.multiple ? files : files[0]);
|
|
});
|
|
frame.open();
|
|
},
|
|
|
|
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') {
|
|
self.openMedia({ title: i18n.pickImage, multiple: false }, function (file) {
|
|
if (file) {
|
|
self.setFeatured('image', { url: file.url, id: file.id });
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
if (act === 'slide') {
|
|
self.openMedia({ title: i18n.pickImage, multiple: true }, function (files) {
|
|
if (files && files.length) {
|
|
self.setFeatured(
|
|
'slide',
|
|
files.map(function (f) {
|
|
return { url: f.url, id: f.id };
|
|
})
|
|
);
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
if (act === 'video') {
|
|
var url = window.prompt(i18n.videoUrl || 'Video URL');
|
|
if (!url) {
|
|
return;
|
|
}
|
|
self.openMedia({ title: i18n.pickVideo || 'Poster', multiple: false }, function (file) {
|
|
self.setFeatured('video', { url: url, pic: file ? file.url : '' });
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
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);
|
|
});
|
|
}
|
|
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();
|
|
var type = btn.getAttribute('data-insert-shortcode');
|
|
if (type === 'hide') {
|
|
insertEditorContent('[hidecontent type="logged"]hidden[/hidecontent]');
|
|
} else if (type === 'payshow') {
|
|
insertEditorContent('[hidecontent type="payshow"]paid content[/hidecontent]');
|
|
}
|
|
});
|
|
},
|
|
|
|
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) {
|
|
alert(i18n.needContent || 'Need content');
|
|
return;
|
|
}
|
|
var old = btn.textContent;
|
|
btn.disabled = true;
|
|
btn.textContent = i18n.aiGenerating || '...';
|
|
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;
|
|
}
|
|
}
|
|
})
|
|
.catch(function (err) {
|
|
alert(err.message || i18n.aiFailed || 'AI failed');
|
|
})
|
|
.finally(function () {
|
|
btn.disabled = false;
|
|
btn.textContent = old;
|
|
});
|
|
});
|
|
});
|
|
},
|
|
};
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
App.init();
|
|
});
|
|
} else {
|
|
App.init();
|
|
}
|
|
})();
|