diff --git a/assets/admin/src/pages/content/FloatingButtonsSettings.vue b/assets/admin/src/pages/content/FloatingButtonsSettings.vue index 09082ab9..87c22d7d 100644 --- a/assets/admin/src/pages/content/FloatingButtonsSettings.vue +++ b/assets/admin/src/pages/content/FloatingButtonsSettings.vue @@ -490,7 +490,7 @@ function applyAddPreset(key) { } const presetFactories = { - post: () => ({ label: '写文章', icon: 'fas fa-pen-nib', url: `${adminUrl || siteUrl}/post-new.php` }), + post: () => ({ label: '写文章', icon: 'fas fa-pen-nib', url: `${siteUrl}/submit/` }), submit: () => ({ label: '投稿页', icon: 'fas fa-paper-plane', url: `${siteUrl}/submit/` }), profile: () => ({ label: '创作中心', icon: 'fas fa-user-edit', url: `${siteUrl}/user/` }), } @@ -518,7 +518,7 @@ function isAddPresetExists(key) { function getAddPresetValue(key) { const presetFactories = { - post: () => ({ label: '写文章', icon: 'fas fa-pen-nib', url: `${adminUrl || siteUrl}/post-new.php` }), + post: () => ({ label: '写文章', icon: 'fas fa-pen-nib', url: `${siteUrl}/submit/` }), submit: () => ({ label: '投稿页', icon: 'fas fa-paper-plane', url: `${siteUrl}/submit/` }), profile: () => ({ label: '创作中心', icon: 'fas fa-user-edit', url: `${siteUrl}/user/` }), } diff --git a/assets/admin/src/pages/content/FooterTabbarSettings.vue b/assets/admin/src/pages/content/FooterTabbarSettings.vue index 041ca205..0595b09e 100644 --- a/assets/admin/src/pages/content/FooterTabbarSettings.vue +++ b/assets/admin/src/pages/content/FooterTabbarSettings.vue @@ -365,7 +365,7 @@ function getItemSummary(item) { } const addPresets = [ - { label: '写文章', icon: 'fas fa-pen-fancy', url: '/post-new' }, + { label: '写文章', icon: 'fas fa-pen-fancy', url: '/submit' }, { label: '投稿页', icon: 'fas fa-file-alt', url: '/submit' }, { label: '发布资源', icon: 'fas fa-cloud-upload-alt', url: '/resource-new' }, { label: '创作中心', icon: 'fas fa-palette', url: '/creator' }, diff --git a/assets/admin/src/schema/sections/post-list/submit.js b/assets/admin/src/schema/sections/post-list/submit.js index 940df576..a17d05c4 100644 --- a/assets/admin/src/schema/sections/post-list/submit.js +++ b/assets/admin/src/schema/sections/post-list/submit.js @@ -22,6 +22,8 @@ export const submitSchema = { front_submit_editor_mode: 'rich', front_submit_draft_save: true, front_submit_leave_warning: true, + front_submit_guest_enabled: false, + front_submit_captcha: false, post_article_btn_txte: '发布文章', post_article_title_strlen_limit_min: 5, post_article_title_strlen_limit_max: 30, @@ -59,7 +61,9 @@ export const submitSchema = { { key: 'front_submit_cover', label: '封面图', type: 'switch', default: true, dependsOn: { key: 'front_submit_enabled', value: true } }, { key: 'front_submit_category', label: '分类选择', type: 'switch', default: true, dependsOn: { key: 'front_submit_enabled', value: true } }, { key: 'post_article_cat', label: '允许投稿分类', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [], help: '为空则允许全部分类', dependsOn: { key: 'front_submit_enabled', value: true } }, - { key: 'post_article_user', label: '免登录归属用户', type: 'user_select', default: 0, help: '免登录投稿时文章归属的用户,搜索选择', dependsOn: { key: 'front_submit_enabled', value: true } }, + { key: 'front_submit_guest_enabled', label: '允许免登录投稿', type: 'switch', default: false, help: '开启后访客可投稿;文章归属到下方用户', dependsOn: { key: 'front_submit_enabled', value: true } }, + { key: 'post_article_user', label: '免登录归属用户', type: 'user_select', default: 0, help: '免登录投稿时文章归属的用户,搜索选择', dependsOn: { key: 'front_submit_guest_enabled', value: true } }, + { key: 'front_submit_captcha', label: '提交人机验证', type: 'switch', default: false, help: '提交正式投稿时校验人机验证(需在安全扩展启用 captcha)', dependsOn: { key: 'front_submit_enabled', value: true } }, { key: 'front_submit_tags', label: '标签输入', type: 'switch', default: true, dependsOn: { key: 'front_submit_enabled', value: true } }, { key: 'post_article_topics_s', label: '专题选择', type: 'switch', default: false, help: '投稿时可选择专题分类', dependsOn: { key: 'front_submit_enabled', value: true } }, { key: 'post_article_custom_filter_s', label: '高级筛选选择', type: 'switch', default: true, help: '投稿时可选择自定义筛选维度值', dependsOn: { key: 'front_submit_enabled', value: true } }, diff --git a/assets/css/frontend/pages.css b/assets/css/frontend/pages.css index c6342ebc..4c26339a 100644 --- a/assets/css/frontend/pages.css +++ b/assets/css/frontend/pages.css @@ -3,3 +3,4 @@ @import url("pages/topic-rows.css"); @import url("pages/sidebar.css"); @import url("pages/empty-state.css"); +@import url("pages/submit.css"); diff --git a/assets/css/frontend/pages/submit.css b/assets/css/frontend/pages/submit.css new file mode 100644 index 00000000..fd816f49 --- /dev/null +++ b/assets/css/frontend/pages/submit.css @@ -0,0 +1,487 @@ +/* Article compose / front submit page */ + +.aksrt-article-compose { + padding-top: 24px; + padding-bottom: 48px; +} + +.aksrt-article-compose__layout { + display: grid; + grid-template-columns: 1fr 320px; + gap: 20px; + align-items: start; +} + +.aksrt-submit-main { + min-width: 0; +} + +.aksrt-submit-sidebar { + display: flex; + flex-direction: column; + gap: 14px; +} + +.aksrt-submit-panel { + background: var(--surface-1, #fff); + border-radius: var(--main-radius, 8px); + box-shadow: var(--shadow-sm, 0 1px 4px rgba(0, 0, 0, 0.06)); +} + +.aksrt-submit-panel--editor { + padding: 16px 18px 14px; + min-height: 60vh; +} + +.aksrt-submit-panel__title { + margin: 0; + padding: 12px 16px; + font-size: 14px; + font-weight: 600; + border-bottom: 1px solid var(--color-line, #eceff4); +} + +.aksrt-submit-panel__body { + padding: 14px 16px; +} + +.aksrt-submit-title-wrap { + margin: 8px 0 12px; +} + +.aksrt-submit-title { + width: 100%; + border: 0; + border-bottom: 1px solid var(--color-line, #e5e7eb); + font-size: 22px; + font-weight: 600; + line-height: 1.4; + resize: none; + outline: none; + background: transparent; + color: var(--color-ink, #1f2329); + padding: 8px 0; +} + +.aksrt-submit-title:focus { + border-bottom-color: var(--color-primary, #3b82f6); +} + +.aksrt-submit-editor-wrap .wp-editor-container { + border: none; +} + +.aksrt-submit-editor-wrap .mce-toolbar-grp { + background: var(--surface-1, #fff); + border-bottom: 1px solid var(--color-line, #eceff4); +} + +.aksrt-submit-shortcode-tools { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 10px; +} + +.aksrt-submit-status-row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin-top: 12px; + font-size: 13px; +} + +.aksrt-submit-nocan { + min-height: 40vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: var(--color-ink-3, #8a909c); + text-align: center; +} + +.aksrt-submit-featured { + margin-bottom: 16px; + border: 1px dashed var(--color-line, #d9dee7); + border-radius: 8px; + overflow: hidden; + background: var(--surface-2, #f7f8fa); +} + +.aksrt-submit-featured__preview { + position: relative; + aspect-ratio: 2 / 1; + display: flex; + align-items: center; + justify-content: center; + background: var(--surface-2, #f3f4f6); +} + +.aksrt-submit-featured__preview img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.aksrt-submit-featured__empty { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + color: var(--color-ink-3, #8a909c); + font-size: 13px; +} + +.aksrt-submit-featured__badge { + position: absolute; + right: 10px; + bottom: 10px; + background: rgba(0, 0, 0, 0.55); + color: #fff; + border-radius: 999px; + padding: 4px 10px; + font-size: 12px; +} + +.aksrt-submit-featured__actions { + display: flex; + flex-wrap: wrap; + gap: 8px; + padding: 10px 12px; + border-top: 1px solid var(--color-line, #eceff4); + background: #fff; +} + +.aksrt-submit-drop { + border: 1px solid var(--color-line, #eceff4); + border-radius: 6px; + margin-bottom: 8px; + overflow: hidden; +} + +.aksrt-submit-drop > summary { + list-style: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 12px; + font-size: 13px; +} + +.aksrt-submit-drop > summary::-webkit-details-marker { + display: none; +} + +.aksrt-submit-drop[open] > summary { + border-bottom: 1px solid var(--color-line, #eceff4); + background: var(--surface-2, #fafbfc); +} + +.aksrt-submit-drop__body { + padding: 10px 12px; + max-height: 220px; + overflow: auto; +} + +.aksrt-submit-check { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 0; + font-size: 13px; + cursor: pointer; + font-weight: 400; +} + +.aksrt-submit-tags { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.aksrt-submit-tag { + display: inline-flex; + cursor: pointer; + font-weight: 400; +} + +.aksrt-submit-tag input { + display: none; +} + +.aksrt-submit-tag span { + display: inline-block; + padding: 4px 10px; + border-radius: 999px; + background: var(--surface-2, #f3f4f6); + font-size: 12px; +} + +.aksrt-submit-tag.is-active span, +.aksrt-submit-tag:has(input:checked) span { + background: #dbeafe; + color: #2563eb; +} + +.aksrt-submit-field { + display: block; + margin-top: 12px; +} + +.aksrt-submit-field__label { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 13px; + font-weight: 600; + margin-bottom: 6px; +} + +.aksrt-submit-hint { + margin: 0 0 8px; + font-size: 12px; + color: var(--color-ink-3, #8a909c); +} + +.aksrt-form-input { + width: 100%; + border: 1px solid var(--color-line, #e5e7eb); + border-radius: 6px; + padding: 8px 10px; + font-size: 13px; + background: #fff; + color: inherit; + box-sizing: border-box; +} + +.aksrt-form-input:focus { + outline: none; + border-color: var(--color-primary, #3b82f6); +} + +.aksrt-submit-pay__row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 8px 0; +} + +.aksrt-switch { + position: relative; + display: inline-flex; + cursor: pointer; +} + +.aksrt-switch input { + position: absolute; + opacity: 0; + width: 0; + height: 0; +} + +.aksrt-switch__ui { + width: 40px; + height: 22px; + border-radius: 999px; + background: #d1d5db; + position: relative; + transition: 0.2s; +} + +.aksrt-switch__ui::after { + content: ""; + position: absolute; + top: 2px; + left: 2px; + width: 18px; + height: 18px; + border-radius: 50%; + background: #fff; + transition: 0.2s; +} + +.aksrt-switch input:checked + .aksrt-switch__ui { + background: var(--color-primary, #3b82f6); +} + +.aksrt-switch input:checked + .aksrt-switch__ui::after { + transform: translateX(18px); +} + +.aksrt-submit-segmented { + display: inline-flex; + gap: 4px; +} + +.aksrt-submit-segmented .but.active { + background: var(--color-primary, #3b82f6); + color: #fff; + border-color: var(--color-primary, #3b82f6); +} + +.aksrt-submit-pay-downloads__item { + display: grid; + gap: 6px; + margin-bottom: 10px; + padding-bottom: 10px; + border-bottom: 1px dashed var(--color-line, #eceff4); +} + +.aksrt-submit-actions { + text-align: center; +} + +.aksrt-submit-ready { + color: var(--color-ink-3, #8a909c); + letter-spacing: 0.08em; + font-size: 12px; + margin: 0 0 12px; +} + +.aksrt-submit-actions__btns { + display: flex; + flex-wrap: wrap; + gap: 10px; + justify-content: center; +} + +.aksrt-submit-actions__btns .but { + flex: 1 1 120px; + min-height: 40px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + border-radius: 6px; + border: 0; + cursor: pointer; + font-size: 14px; +} + +.aksrt-submit-actions__btns .jb-green { + background: linear-gradient(135deg, #34d399, #10b981); + color: #fff; +} + +.aksrt-submit-actions__btns .jb-blue { + background: linear-gradient(135deg, #60a5fa, #3b82f6); + color: #fff; +} + +.aksrt-submit-actions__btns .but:disabled { + opacity: 0.65; + cursor: wait; +} + +.aksrt-submit-drafts__item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 8px 0; + border-bottom: 1px solid var(--color-line, #f0f1f3); +} + +.aksrt-submit-drafts__link { + flex: 1; + min-width: 0; + text-decoration: none; + color: inherit; + display: flex; + flex-direction: column; + gap: 2px; +} + +.aksrt-submit-drafts__name { + font-size: 13px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.aksrt-submit-drafts__link time { + font-size: 11px; + color: var(--color-ink-3, #8a909c); +} + +.aksrt-submit-drafts__delete { + border: 0; + background: transparent; + color: #ef4444; + cursor: pointer; + padding: 6px; +} + +.aksrt-article-compose .but { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 6px 12px; + border-radius: 6px; + border: 1px solid var(--color-line, #e5e7eb); + background: #fff; + color: var(--color-ink-2, #444); + font-size: 12px; + cursor: pointer; + text-decoration: none; +} + +.aksrt-article-compose .but.hollow { + background: transparent; +} + +.aksrt-article-compose .but.c-blue { + color: #2563eb; + border-color: #bfdbfe; +} + +.aksrt-article-compose .but.c-yellow { + color: #b45309; + border-color: #fde68a; +} + +.aksrt-article-compose .but.c-red { + color: #dc2626; + border-color: #fecaca; +} + +.aksrt-article-compose .but.cir { + width: 32px; + height: 32px; + border-radius: 50%; + justify-content: center; + padding: 0; +} + +.aksrt-article-compose .badg { + display: inline-block; + padding: 3px 8px; + border-radius: 4px; + background: var(--surface-2, #f3f4f6); + color: var(--color-ink-3, #8a909c); + font-size: 12px; +} + +body.aksrt-hide-float #float-right-btns, +body.aksrt-hide-tabbar #footer-tabbar, +body.aksrt-hide-tabbar .footer-tabbar-placeholder { + display: none !important; +} + +@media (max-width: 991px) { + .aksrt-article-compose__layout { + grid-template-columns: 1fr; + } + + .aksrt-submit-sidebar { + order: 2; + } + + .aksrt-submit-main { + order: 1; + } +} diff --git a/assets/js/front-submit.js b/assets/js/front-submit.js index c974ddf5..637bc9ef 100644 --- a/assets/js/front-submit.js +++ b/assets/js/front-submit.js @@ -1,471 +1,618 @@ /** - * AKSRT Front Submit - * - * Handles front-end post submission, draft save/load, - * editor toolbar, cover preview, and form protection. + * AKSRT front-end article submit (write/post page). + * Handles TinyMCE content, featured cover, pay box, draft/submit AJAX. * * @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 cfg = window.aksrtSubmitData || {}; + var i18n = cfg.i18n || {}; + var ajaxUrl = cfg.ajaxUrl || '/wp-admin/admin-ajax.php'; + var nonce = cfg.nonce || ''; - var FRONT_SUBMIT = { + 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.bindDirtyTracking(); - this.bindSubmit(); - this.bindDraftSave(); + if (!this.form) { + return; + } + this.bindDirty(); + this.bindAutoHeight(); + this.bindSubmitButtons(); this.bindDraftDelete(); - this.bindEditorToolbar(); - this.bindCoverPreview(); - this.bindWordCount(); + this.bindFeatured(); + this.bindPay(); + this.bindShortcodes(); this.bindLeaveWarning(); - this.bindAiAssist(); + this.bindAi(); + this.bindTagActive(); + this.loadFeaturedFromDom(); }, - /** - * Track form changes - */ - bindDirtyTracking: function () { + 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(); + } }, - /** - * Leave page warning - */ bindLeaveWarning: function () { var self = this; + if (cfg.leaveWarning === false) { + return; + } window.addEventListener('beforeunload', function (e) { - if (self.isDirty && !self.isSubmitting) { + if (self.isDirty && !self.isSubmitting && !window.post_is_save) { e.preventDefault(); e.returnValue = i18n.confirmLeave || ''; } }); }, - /** - * Form submission - */ - bindSubmit: function () { + 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(); - 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'); - }); + 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 form via AJAX - */ - submitForm: function (btn, status) { + submit: function (btn, action) { var self = this; - var formData = new FormData(this.form); - formData.append('action', 'aksrt_submit_post'); - formData.append('_nonce', nonce); - - var originalText = btn.innerHTML; + this.isSubmitting = true; + var original = btn.innerHTML; btn.disabled = true; - btn.innerHTML = ' ' + (status === 'draft' ? (i18n.saving || '保存中...') : (i18n.submitting || '提交中...')); - self.isSubmitting = true; + btn.innerHTML = + ' ' + + (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', - body: formData, credentials: 'same-origin', + headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, + body: toFormBody(payload), }) - .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; + .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') + + ''; + } + } + btn.innerHTML = + ' ' + + (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 () { - alert(i18n.error || '操作失败,请重试'); + .catch(function (err) { + alert(err.message || i18n.error || 'Error'); btn.disabled = false; - btn.innerHTML = originalText; - }) - .finally(function () { + btn.innerHTML = original; 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); - + 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', - body: formData, 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 (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 : '删除失败'); - } + .then(function (r) { + return r.json(); }) - .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```'); + .then(function (res) { + if (res && res.success) { + var item = btn.closest('.aksrt-submit-drafts__item'); + if (item) { + item.remove(); + } } else { - FRONT_SUBMIT.wrapSelection(ta, '`'); + alert((res && res.data && res.data.message) || i18n.error || 'Error'); } - 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(); - }); + }); + }, + + 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 = ''; + } else if (t === 'slide' && Array.isArray(d) && d[0] && d[0].url) { + html = + ''; + } else if (t === 'video') { + html = + (d.pic + ? '' + : '') + + ''; + } else { + html = ''; + } + preview.innerHTML = html; + var clearBtn = qs('[data-featured-action="close"]'); + if (clearBtn) { + clearBtn.hidden = !t || t === 'close'; } }, - /** - * 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(); + 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(); }, - /** - * AI 摘要 / 标签辅助(仅建议回填,不自动提交) - */ - bindAiAssist: function () { + bindFeatured: 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 || '请先填写正文内容'); + var box = qs('.featured-edit, .aksrt-submit-featured'); + if (!box) { 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 })); + box.addEventListener('click', function (e) { + var btn = e.target.closest('[data-featured-action]'); + if (!btn) { + return; } - }); - }, - - 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 })); + e.preventDefault(); + var act = btn.getAttribute('data-featured-action'); + if (act === 'close') { + self.setFeatured('close', {}); + return; } - }); - }, - - 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 }; + if (act === 'image') { + self.openMedia({ title: i18n.pickImage, multiple: false }, function (file) { + if (file) { + self.setFeatured('image', { url: file.url, id: file.id }); + } }); - }) - .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; + } + 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; } - onSuccess(res.json.data || {}); - }) - .catch(function () { - btn.removeAttribute('data-loading'); - btn.textContent = original; - alert(i18n.aiFailed || 'AI 生成失败'); + 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 () { FRONT_SUBMIT.init(); }); + document.addEventListener('DOMContentLoaded', function () { + App.init(); + }); } else { - FRONT_SUBMIT.init(); + App.init(); } })(); diff --git a/footer.php b/footer.php index 5343b7a5..c04f81fc 100644 --- a/footer.php +++ b/footer.php @@ -326,10 +326,11 @@ if ($_footer_conter === '') { - + @@ -462,7 +465,7 @@ if (!empty($aksrt_bg_styles)) {