feat(forum): add complete forum workflows
This commit is contained in:
1 parent
a254cbd3e9
commit
ee65fc7fd2
39 files changed
+3425
-4
No files matched your search
@@ -0,0 +1,27 @@
|
||||
import { renderFaIcon, schemaPage } from './helpers'
|
||||
|
||||
const pages = [
|
||||
['general', '全局设置'],
|
||||
['home', '首页与列表'],
|
||||
['publishing', '发布与审核'],
|
||||
['access', '权限与版主'],
|
||||
['replies', '回复与互动'],
|
||||
['payment', '付费与积分'],
|
||||
['urls', '链接设置'],
|
||||
]
|
||||
|
||||
export const forumSection = {
|
||||
key: 'forum',
|
||||
label: '社区论坛',
|
||||
icon: renderFaIcon('fa fas fa-comments'),
|
||||
route: '/forum',
|
||||
children: pages.map(([key, label]) => schemaPage({
|
||||
key: `forum-${key}`,
|
||||
name: `Forum_${key}`,
|
||||
label,
|
||||
title: label,
|
||||
route: `/forum/${key}`,
|
||||
canonicalDomain: 'forum',
|
||||
section: key,
|
||||
})),
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { extensionsSection } from './extensions'
|
||||
import { themeInfoSection } from './theme-info'
|
||||
import { docsSection } from './docs'
|
||||
import { toolsSection } from './tools'
|
||||
import { forumSection } from './forum'
|
||||
|
||||
const SECTION_DEFAULT_CHILDREN = {
|
||||
global: 'global-common',
|
||||
@@ -18,6 +19,7 @@ const SECTION_DEFAULT_CHILDREN = {
|
||||
payment: 'payment-knowledge',
|
||||
extensions: 'extensions-system',
|
||||
tools: 'tools-import-export',
|
||||
forum: 'forum-general',
|
||||
'theme-info': 'theme-info-dashboard',
|
||||
docs: 'docs-dashboard',
|
||||
}
|
||||
@@ -43,6 +45,7 @@ export const ADMIN_SECTIONS = [
|
||||
attachSectionDefaultRoute(permissionsSection),
|
||||
attachSectionDefaultRoute(usersSection),
|
||||
attachSectionDefaultRoute(paymentSection),
|
||||
attachSectionDefaultRoute(forumSection),
|
||||
attachSectionDefaultRoute(extensionsSection),
|
||||
attachSectionDefaultRoute(toolsSection),
|
||||
attachSectionDefaultRoute(themeInfoSection),
|
||||
|
||||
@@ -170,7 +170,7 @@ function resolveSectionFromPath(path) {
|
||||
|
||||
/** Load schema from the Vue-side registry */
|
||||
const pageMeta = computed(() => {
|
||||
const schema = (isEmailPage.value || resolvedDomain.value === 'page_display')
|
||||
const schema = (isEmailPage.value || ['page_display', 'forum'].includes(resolvedDomain.value))
|
||||
? serverSchema.value
|
||||
: getSchema(resolvedDomain.value, resolvedSection.value)
|
||||
if (schema && schema.groups && schema.groups.length > 0) {
|
||||
@@ -192,7 +192,7 @@ const pageMeta = computed(() => {
|
||||
|
||||
/** Default values from schema */
|
||||
const defaults = computed(() => {
|
||||
const schema = (isEmailPage.value || resolvedDomain.value === 'page_display')
|
||||
const schema = (isEmailPage.value || ['page_display', 'forum'].includes(resolvedDomain.value))
|
||||
? serverSchema.value
|
||||
: getSchema(resolvedDomain.value, resolvedSection.value)
|
||||
if (!schema) return {}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
#forum{--forum-ink:var(--key-color,#24282c);--forum-muted:var(--muted-color,#737b82);--forum-line:var(--muted-4-color,#e6e9eb);--forum-accent:var(--focus-color,#2869d1);color:var(--forum-ink)}
|
||||
#forum .jio-forum__wrap{width:min(1200px,calc(100% - 32px));margin:22px auto 70px}
|
||||
#forum .jio-forum__breadcrumbs{display:flex;gap:9px;align-items:center;color:var(--forum-muted);font-size:13px;margin-bottom:18px}
|
||||
#forum .jio-forum__breadcrumbs a{color:inherit}
|
||||
#forum .jio-forum__layout{display:grid;grid-template-columns:minmax(0,1fr) 286px;gap:24px;align-items:start}
|
||||
#forum .jio-forum__main{min-width:0}
|
||||
#forum .jio-forum__hero,#forum .jio-forum__page-head{padding:20px 0;border-bottom:1px solid var(--forum-line)}
|
||||
#forum h1{font-size:27px;line-height:1.25;margin:0 0 10px}
|
||||
#forum .jio-forum__hero p,#forum .jio-forum__page-head p{margin:0;color:var(--forum-muted)}
|
||||
#forum .jio-forum__tabs{display:flex;gap:26px;overflow:auto;white-space:nowrap;border-bottom:1px solid var(--forum-line);margin-bottom:16px}
|
||||
#forum .jio-forum__tabs a{padding:14px 0;color:var(--forum-muted);border-bottom:2px solid transparent}
|
||||
#forum .jio-forum__tabs .is-active{color:var(--forum-accent);border-color:var(--forum-accent);font-weight:600}
|
||||
#forum .jio-forum__chips{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}
|
||||
#forum .jio-forum__chips a,#forum .jio-forum__badge,#forum .jio-forum__badges a,#forum .jio-forum__badges span{padding:4px 8px;background:#f1f5f7;color:#48717c;border-radius:4px;font-size:12px}
|
||||
#forum .jio-forum__post{display:flex;gap:13px;padding:20px 2px;border-bottom:1px solid var(--forum-line)}
|
||||
#forum .jio-forum__avatar{flex:0 0 44px}#forum .jio-forum__avatar img{width:44px;height:44px;border-radius:50%}
|
||||
#forum .jio-forum__post-body{min-width:0;flex:1}#forum .jio-forum__post-top{display:flex;gap:12px;color:var(--forum-muted);font-size:12px}
|
||||
#forum .jio-forum__post h2{font-size:17px;line-height:1.45;margin:7px 0}#forum .jio-forum__post h2 a{color:var(--forum-ink)}
|
||||
#forum .jio-forum__post p{color:var(--forum-muted);font-size:14px;line-height:1.6;margin:0 0 10px}
|
||||
#forum .jio-forum__post-meta{display:flex;align-items:center;gap:10px;color:var(--forum-muted);font-size:12px;flex-wrap:wrap}
|
||||
#forum .jio-forum__post-meta a{color:var(--forum-accent)}
|
||||
#forum .jio-forum__aside{border-left:1px solid var(--forum-line);padding-left:20px}
|
||||
#forum .jio-forum__new,#forum .jio-forum__primary{display:inline-flex;align-items:center;justify-content:center;background:var(--forum-accent);color:#fff;border:0;border-radius:4px;min-height:40px;padding:8px 16px;cursor:pointer}
|
||||
#forum .jio-forum__new{width:100%}#forum .jio-forum__aside-section{padding:22px 0;border-bottom:1px solid var(--forum-line)}
|
||||
#forum .jio-forum__aside-section h2{font-size:16px;margin:0 0 12px}#forum .jio-forum__aside-section a{display:block;padding:6px 0;color:var(--forum-ink)}
|
||||
#forum .jio-forum__plates{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}
|
||||
#forum .jio-forum__plate{display:flex;gap:13px;padding:14px;border:1px solid var(--forum-line);border-radius:6px}
|
||||
#forum .jio-forum__plate-cover{width:80px;height:80px;flex:none;background:#edf2f5;border-radius:5px;overflow:hidden}
|
||||
#forum .jio-forum__plate-cover img{width:100%;height:100%;object-fit:cover}#forum .jio-forum__plate h2{font-size:16px;margin:3px 0 8px}
|
||||
#forum .jio-forum__plate p{font-size:13px;color:var(--forum-muted);margin:0}
|
||||
#forum .jio-forum__single{padding:20px 0}#forum .jio-forum__single-meta{display:flex;align-items:center;gap:10px;color:var(--forum-muted);font-size:13px}
|
||||
#forum .jio-forum__single-meta img{border-radius:50%}#forum .jio-forum__single-head h1{margin:18px 0}
|
||||
#forum .jio-forum__badges,#forum .jio-forum__actions{display:flex;gap:8px;flex-wrap:wrap}
|
||||
#forum .jio-forum__single-content{font-size:15px;line-height:1.8;padding:22px 0;overflow-wrap:anywhere}
|
||||
#forum .jio-forum__actions{border-top:1px solid var(--forum-line);padding:16px 0}
|
||||
#forum .jio-forum__action{border:1px solid var(--forum-line);border-radius:4px;background:transparent;color:var(--forum-muted);padding:7px 12px;cursor:pointer}
|
||||
#forum .jio-forum__action:hover{color:var(--forum-accent);border-color:var(--forum-accent)}
|
||||
#forum .jio-forum__form{display:grid;gap:15px;margin:20px 0}#forum .jio-forum__form label{display:grid;gap:6px;font-size:14px;font-weight:600}
|
||||
#forum .jio-forum__form input,#forum .jio-forum__form select,#forum .jio-forum__form textarea{width:100%;border:1px solid #d6dce1;border-radius:4px;padding:10px 12px;background:var(--body-bg,#fff);color:inherit;font:inherit}
|
||||
#forum .jio-forum__form-extra[hidden]{display:none}#forum .jio-forum__message{color:#b64343;min-height:20px}
|
||||
#forum .jio-forum__vote{border-top:1px solid var(--forum-line);padding:20px 0}#forum .jio-forum__vote h2{font-size:17px}
|
||||
#forum .jio-forum__vote label{display:flex;gap:12px;padding:12px;border:1px solid var(--forum-line);margin-bottom:8px;border-radius:4px}
|
||||
#forum .jio-forum__vote-count{margin-left:auto}#forum .jio-forum__locked{padding:30px;text-align:center;background:#f7f9fa}
|
||||
#forum .jio-forum__pagination ul{list-style:none;display:flex;gap:7px;padding:0;margin:24px 0}
|
||||
#forum .jio-forum__pagination a,#forum .jio-forum__pagination span{display:block;padding:6px 10px;border:1px solid var(--forum-line);border-radius:4px}
|
||||
#forum .jio-forum__empty{padding:36px;text-align:center;color:var(--forum-muted)}
|
||||
@media(max-width:800px){#forum .jio-forum__layout{grid-template-columns:1fr}#forum .jio-forum__aside{border-left:0;padding-left:0}#forum .jio-forum__plates{grid-template-columns:1fr}}
|
||||
@media(max-width:480px){#forum .jio-forum__wrap{width:calc(100% - 24px)}#forum h1{font-size:23px}#forum .jio-forum__post{padding:16px 0}#forum .jio-forum__tabs{gap:18px}}
|
||||
#forum .jio-forum__create-plate{width:100%;margin-top:10px}
|
||||
#forum .jio-forum__section-title{font-size:18px;margin:28px 0 0}
|
||||
#forum .jio-forum__reply{display:flex;gap:12px;padding:17px 0;border-bottom:1px solid var(--forum-line)}
|
||||
#forum .jio-forum__reply-avatar img{border-radius:50%}
|
||||
#forum .jio-forum__reply-main{flex:1;min-width:0}
|
||||
#forum .jio-forum__reply-head{display:flex;gap:12px;align-items:center;font-size:13px}
|
||||
#forum .jio-forum__reply-head time{color:var(--forum-muted)}#forum .jio-forum__reply-head strong{color:#147e70}
|
||||
#forum .jio-forum__reply-text{padding:10px 0;line-height:1.7;overflow-wrap:anywhere}
|
||||
#forum .jio-forum__reply-actions button{border:0;background:none;color:var(--forum-accent);padding:4px 10px 4px 0;cursor:pointer}
|
||||
#forum .jio-forum__reply-form{display:grid;gap:12px;margin:24px 0}
|
||||
#forum .jio-forum__reply-form h3{font-size:17px;margin:0}
|
||||
#forum .jio-forum__reply-form textarea{width:100%;border:1px solid var(--forum-line);padding:12px;border-radius:4px;background:var(--body-bg,#fff);color:inherit}
|
||||
#forum .jio-forum__reply-form button{justify-self:start}
|
||||
#forum .jio-forum__widget-link{display:flex;justify-content:space-between;gap:10px;padding:7px 0;color:var(--forum-ink)}
|
||||
#forum .jio-forum__widget-link span{color:var(--forum-muted)}
|
||||
.jio-forum__dialog{width:min(440px,calc(100% - 28px));max-height:90vh;border:1px solid #dce2e5;border-radius:6px;padding:22px;background:var(--body-bg,#fff);color:inherit;box-shadow:0 20px 60px #0003}
|
||||
.jio-forum__dialog::backdrop{background:#10182088}
|
||||
.jio-forum__dialog h2{font-size:20px;margin:0 0 18px;overflow-wrap:anywhere}
|
||||
.jio-forum__dialog form,.jio-forum__dialog-fields{display:grid;gap:14px}
|
||||
.jio-forum__dialog label{display:grid;gap:6px;font-size:14px}
|
||||
.jio-forum__dialog input,.jio-forum__dialog select,.jio-forum__dialog textarea{width:100%;min-height:39px;border:1px solid #cfd7dc;padding:9px;border-radius:4px;background:var(--body-bg,#fff);color:inherit}
|
||||
.jio-forum__dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:10px}
|
||||
.jio-forum__dialog-actions button{border:1px solid #d6dce0;border-radius:4px;padding:8px 14px;cursor:pointer}
|
||||
.jio-forum__qr{width:180px;height:180px;margin:auto}
|
||||
.jio-forum__user-panel h2{font-size:18px;margin:0 0 14px}
|
||||
.jio-forum__user-row{display:flex;justify-content:space-between;gap:12px;padding:12px 0;border-bottom:1px solid #e6e9eb}
|
||||
#forum .jio-forum__applications{margin:18px 0;padding:14px 0;border-top:1px solid var(--forum-line)}
|
||||
#forum .jio-forum__applications h2{font-size:16px;margin:0 0 8px}
|
||||
#forum .jio-forum__application{display:flex;align-items:center;gap:10px;padding:6px 0}
|
||||
#forum .jio-forum__application button{border:1px solid var(--forum-line);border-radius:4px;background:transparent;padding:4px 9px;cursor:pointer}
|
||||
#forum .jio-forum__reply-parent{color:var(--forum-muted);font-size:12px;margin:8px 0 0}
|
||||
#forum .jio-forum__hot-reply{margin:0 0 10px 58px;padding:9px 12px;background:#f5f8f9;color:var(--forum-muted);font-size:13px;border-radius:4px}
|
||||
#forum .jio-forum__pending{padding:16px 0;border-bottom:1px solid var(--forum-line)}
|
||||
#forum .jio-forum__pending h2{font-size:16px;margin:0 0 10px}
|
||||
#forum .jio-forum__pending>div{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:6px 0}
|
||||
#forum .jio-forum__pending button{border:1px solid var(--forum-line);background:transparent;border-radius:4px;padding:5px 9px;cursor:pointer}
|
||||
#forum .jio-forum__editor-tools{display:flex;gap:8px;flex-wrap:wrap}
|
||||
#forum .wp-editor-wrap{border:1px solid var(--forum-line)}
|
||||
body.dark-theme #forum .jio-forum__chips a,body.dark-theme #forum .jio-forum__badge,body.dark-theme #forum .jio-forum__badges a,body.dark-theme #forum .jio-forum__badges span{background:var(--muted-bg-2-color);color:var(--forum-accent)}
|
||||
body.dark-theme #forum .jio-forum__plate-cover{background:var(--muted-bg-2-color)}
|
||||
body.dark-theme #forum .jio-forum__locked,body.dark-theme #forum .jio-forum__hot-reply{background:var(--muted-bg-2-color)}
|
||||
body.dark-theme #forum .jio-forum__form input,body.dark-theme #forum .jio-forum__form select,body.dark-theme #forum .jio-forum__form textarea,body.dark-theme #forum .jio-forum__reply-form textarea{background:var(--main-bg-color);border-color:var(--forum-line);color:var(--forum-ink)}
|
||||
body.dark-theme .jio-forum__dialog{background:var(--main-bg-color);color:var(--key-color);border-color:var(--muted-4-color)}
|
||||
body.dark-theme .jio-forum__dialog input,body.dark-theme .jio-forum__dialog select,body.dark-theme .jio-forum__dialog textarea{background:var(--main-bg-color);color:var(--key-color);border-color:var(--muted-4-color)}
|
||||
body.dark-theme #forum .jio-forum__action{color:var(--forum-muted)}
|
||||
#forum .jio-forum__term-management{margin:16px 0;padding:16px 0;border-bottom:1px solid var(--forum-line)}
|
||||
#forum .jio-forum__term-management h2{font-size:16px}
|
||||
#forum .jio-forum__term-management form{display:flex;gap:8px;align-items:end;flex-wrap:wrap}
|
||||
#forum .jio-forum__term-management label{display:grid;gap:5px;font-size:13px}
|
||||
#forum .jio-forum__term-management input,#forum .jio-forum__term-management select{min-height:34px;padding:5px 8px;border:1px solid var(--forum-line);border-radius:4px;background:var(--main-bg-color);color:var(--forum-ink)}
|
||||
#forum .jio-forum__term-row{display:flex;gap:8px;align-items:center;padding:5px 0}
|
||||
#forum .jio-forum__term-row button{border:1px solid var(--forum-line);border-radius:4px;background:transparent;padding:4px 7px;cursor:pointer}
|
||||
@@ -0,0 +1,266 @@
|
||||
(() => {
|
||||
const config = window.jioForum || {}
|
||||
const root = document.getElementById('forum')
|
||||
if (!root) return
|
||||
|
||||
const send = async (path, method = 'GET', data = {}, idempotencyKey = '') => {
|
||||
const headers = { 'Content-Type': 'application/json', 'X-WP-Nonce': config.nonce }
|
||||
if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey
|
||||
const response = await fetch(config.api + path, {
|
||||
method,
|
||||
credentials: 'same-origin',
|
||||
headers,
|
||||
body: method === 'GET' ? undefined : JSON.stringify(data),
|
||||
})
|
||||
const payload = await response.json()
|
||||
if (!response.ok || payload.success === false) throw new Error(payload.message || '操作失败')
|
||||
return payload
|
||||
}
|
||||
|
||||
const message = (element, value) => {
|
||||
const target = element.querySelector('.jio-forum__message') || element.closest('.jio-forum__single, .jio-forum__replies, .jio-forum__page-head')?.querySelector('.jio-forum__message')
|
||||
if (target) target.textContent = value
|
||||
else if (value) window.alert(value)
|
||||
}
|
||||
|
||||
const modal = (title, fields, submitText = '确认') => new Promise((resolve) => {
|
||||
const dialog = document.createElement('dialog')
|
||||
dialog.className = 'jio-forum__dialog'
|
||||
const form = document.createElement('form')
|
||||
form.innerHTML = '<h2></h2><div class="jio-forum__dialog-fields"></div><div class="jio-forum__dialog-actions"><button type="button" data-cancel>取消</button><button type="submit" class="jio-forum__primary"></button></div>'
|
||||
form.querySelector('h2').textContent = title
|
||||
form.querySelector('[type="submit"]').textContent = submitText
|
||||
const body = form.querySelector('.jio-forum__dialog-fields')
|
||||
fields.forEach(({ name, label, type = 'text', options = [], value = '' }) => {
|
||||
const wrapper = document.createElement('label')
|
||||
wrapper.textContent = label
|
||||
const control = type === 'select' ? document.createElement('select') : document.createElement(type === 'textarea' ? 'textarea' : 'input')
|
||||
control.name = name
|
||||
control.required = true
|
||||
if (type === 'select') options.forEach(({ value: optionValue, label: optionLabel }) => {
|
||||
const option = document.createElement('option')
|
||||
option.value = optionValue
|
||||
option.textContent = optionLabel
|
||||
control.append(option)
|
||||
})
|
||||
else if (type === 'text') control.type = 'text'
|
||||
control.value = value
|
||||
wrapper.append(control)
|
||||
body.append(wrapper)
|
||||
})
|
||||
dialog.append(form)
|
||||
root.append(dialog)
|
||||
const close = (value) => { dialog.close(); dialog.remove(); resolve(value) }
|
||||
form.querySelector('[data-cancel]').addEventListener('click', () => close(null))
|
||||
dialog.addEventListener('cancel', (event) => { event.preventDefault(); close(null) })
|
||||
form.addEventListener('submit', (event) => { event.preventDefault(); close(Object.fromEntries(new FormData(form))) })
|
||||
dialog.showModal()
|
||||
})
|
||||
|
||||
const checkout = async (kind, id) => {
|
||||
const preview = (await send(`${kind}s/${id}/checkout`)).data
|
||||
const methods = (preview.methods || []).map((value) => ({ value, label: {
|
||||
points: '积分', balance: '站内余额', alipay: '支付宝', wechatpay: '微信支付', paypal: 'PayPal', stripe: '银行卡',
|
||||
}[value] || value }))
|
||||
const data = await modal(`解锁 ${preview.title} · ${preview.price}${preview.mode === 'points' ? '积分' : '元'}`, [
|
||||
{ name: 'payment_method', label: '支付方式', type: 'select', options: methods },
|
||||
], '确认支付')
|
||||
if (!data) return
|
||||
const key = crypto.randomUUID()
|
||||
const result = (await send(`${kind}s/${id}/checkout`, 'POST', data, key)).data
|
||||
if (result.mode === 'redirect' && result.payment_url) window.location.href = result.payment_url
|
||||
else if (result.mode === 'form' && result.form_html) {
|
||||
const wrapper = document.createElement('div')
|
||||
wrapper.innerHTML = result.form_html
|
||||
document.body.append(wrapper)
|
||||
wrapper.querySelector('form')?.submit()
|
||||
} else if (result.mode === 'qrcode' && result.code_url && window.QRCode) {
|
||||
const dialog = document.createElement('dialog')
|
||||
dialog.className = 'jio-forum__dialog'
|
||||
dialog.innerHTML = '<h2>扫码支付</h2><div class="jio-forum__qr"></div><p>支付完成后刷新页面</p><button type="button">完成</button>'
|
||||
root.append(dialog)
|
||||
new window.QRCode(dialog.querySelector('.jio-forum__qr'), { text: result.code_url, width: 180, height: 180 })
|
||||
dialog.querySelector('button').addEventListener('click', () => window.location.reload())
|
||||
dialog.showModal()
|
||||
} else if (result.mode) window.location.href = config.orders
|
||||
else window.location.reload()
|
||||
}
|
||||
|
||||
const typeSelect = root.querySelector('select[name="type"]')
|
||||
const syncType = () => {
|
||||
const vote = root.querySelector('[data-forum-type="vote"]')
|
||||
if (vote) vote.hidden = typeSelect?.value !== 'vote'
|
||||
}
|
||||
typeSelect?.addEventListener('change', syncType)
|
||||
syncType()
|
||||
|
||||
const insertMedia = (kind, url, name) => {
|
||||
const element = document.createElement(kind === 'image' ? 'img' : kind === 'video' ? 'video' : 'a')
|
||||
if (kind === 'resource') { element.href = url; element.textContent = name || '下载附件' }
|
||||
else element.src = url
|
||||
if (kind === 'video') element.controls = true
|
||||
if (kind === 'image') element.alt = name || ''
|
||||
const html = element.outerHTML
|
||||
const editor = window.tinymce?.get('jio_forum_content')
|
||||
if (editor && !editor.isHidden()) editor.insertContent(html)
|
||||
else {
|
||||
const textarea = root.querySelector('textarea[name="content"]')
|
||||
if (textarea) textarea.setRangeText(html, textarea.selectionStart, textarea.selectionEnd, 'end')
|
||||
}
|
||||
}
|
||||
|
||||
root.addEventListener('change', async (event) => {
|
||||
const input = event.target.closest('[data-forum-file]')
|
||||
if (!input?.files?.[0]) return
|
||||
const kind = input.dataset.kind
|
||||
const data = new FormData()
|
||||
data.append('file', input.files[0])
|
||||
try {
|
||||
const response = await fetch(config.api + `upload/${kind}`, { method: 'POST', credentials: 'same-origin', headers: { 'X-WP-Nonce': config.nonce }, body: data })
|
||||
const payload = await response.json()
|
||||
if (!response.ok || !payload.success) throw new Error(payload.message || '上传失败')
|
||||
insertMedia(kind, payload.data.url, payload.data.name)
|
||||
} catch (error) {
|
||||
message(input.closest('.jio-forum__form') || root, error.message)
|
||||
} finally {
|
||||
input.value = ''
|
||||
}
|
||||
})
|
||||
|
||||
root.addEventListener('submit', async (event) => {
|
||||
const form = event.target.closest('[data-forum-form]')
|
||||
if (!form) return
|
||||
event.preventDefault()
|
||||
const button = form.querySelector('[type="submit"]')
|
||||
const kind = form.dataset.forumForm
|
||||
const id = Number(form.dataset.id || 0)
|
||||
if (kind === 'post') window.tinymce?.get('jio_forum_content')?.save()
|
||||
const body = Object.fromEntries(new FormData(form).entries())
|
||||
if (kind === 'vote' && form.querySelector('input[name="option[]"]')) {
|
||||
body.options = new FormData(form).getAll('option[]')
|
||||
}
|
||||
button.disabled = true
|
||||
try {
|
||||
const result = kind === 'vote' ? await send(`posts/${id}/vote`, 'POST', body)
|
||||
: kind === 'plate' ? await send(id ? `plates/${id}` : 'plates', id ? 'PATCH' : 'POST', body)
|
||||
: kind === 'term' ? await send('terms', 'POST', body)
|
||||
: kind === 'moderator' ? await send(`plates/${id}/moderators`, 'POST', { ...body, active: true })
|
||||
: kind === 'reply' ? await send(`posts/${id}/replies`, 'POST', body)
|
||||
: await send(id ? `posts/${id}` : 'posts', id ? 'PATCH' : 'POST', body)
|
||||
if (result.data?.url) window.location.href = result.data.url
|
||||
else if (kind === 'moderator') window.location.reload()
|
||||
else if (kind === 'term') window.location.reload()
|
||||
else if (kind === 'vote') window.location.reload()
|
||||
else if (kind === 'reply' && result.data?.status === 'publish') window.location.reload()
|
||||
else message(form, result.message || '已保存')
|
||||
} catch (error) {
|
||||
message(form, error.message)
|
||||
} finally {
|
||||
button.disabled = false
|
||||
}
|
||||
})
|
||||
|
||||
root.addEventListener('click', async (event) => {
|
||||
const upload = event.target.closest('[data-forum-upload]')
|
||||
if (upload) {
|
||||
const input = root.querySelector('[data-forum-file]')
|
||||
if (input) {
|
||||
input.dataset.kind = upload.dataset.forumUpload
|
||||
input.accept = { image: 'image/*', video: 'video/*', resource: '' }[input.dataset.kind] || ''
|
||||
input.click()
|
||||
}
|
||||
return
|
||||
}
|
||||
const open = event.target.closest('[data-forum-open]')
|
||||
if (open) { root.querySelector(`[data-forum-dialog="${open.dataset.forumOpen}"]`)?.showModal(); return }
|
||||
const close = event.target.closest('[data-forum-close]')
|
||||
if (close) { close.closest('dialog')?.close(); return }
|
||||
const reply = event.target.closest('[data-forum-reply-to]')
|
||||
if (reply) {
|
||||
const form = root.querySelector('[data-forum-form="reply"]')
|
||||
if (form) { form.querySelector('[name="parent_id"]').value = reply.dataset.forumReplyTo; form.scrollIntoView({ behavior: 'smooth' }); form.querySelector('textarea')?.focus() }
|
||||
return
|
||||
}
|
||||
const button = event.target.closest('[data-forum-action]')
|
||||
if (!button) return
|
||||
const action = button.dataset.forumAction
|
||||
const id = Number(button.dataset.id || 0)
|
||||
if (action === 'delete' && !window.confirm('确定删除此内容?')) return
|
||||
button.disabled = true
|
||||
try {
|
||||
let result
|
||||
if (action === 'unlock') { await checkout('post', id); return }
|
||||
if (action === 'plate-unlock') { await checkout('plate', id); return }
|
||||
if (action === 'plate-audit') {
|
||||
result = await send(`plates/${id}/audit`, 'POST', { status: 'publish' })
|
||||
button.closest('.jio-forum__pending > div')?.remove()
|
||||
} else if (action === 'plate-delete') {
|
||||
const data = await modal('删除版块', [
|
||||
{ name: 'posts_action', label: '关联帖子', type: 'select', options: [{ value: 'trash', label: '一并移入回收站' }, { value: 'move', label: '移动到其他版块' }] },
|
||||
{ name: 'target_plate_id', label: '目标版块', type: 'select', options: [{ value: '0', label: '无需选择' }, ...(config.plates || []).filter((plate) => plate.id !== id).map((plate) => ({ value: String(plate.id), label: plate.title }))] },
|
||||
], '确认删除')
|
||||
if (!data) return
|
||||
result = await send(`plates/${id}/delete`, 'POST', data)
|
||||
button.dataset.forumAction = 'plate-restore'
|
||||
button.textContent = '撤销删除'
|
||||
} else if (action === 'plate-restore') {
|
||||
result = await send(`plates/${id}/restore`, 'POST')
|
||||
button.dataset.forumAction = 'plate-delete'
|
||||
button.textContent = '删除版块'
|
||||
} else if (action === 'reply-like' || action === 'reply-hot') {
|
||||
result = await send(`replies/${id}/${action === 'reply-like' ? 'like' : 'hot'}`, 'POST')
|
||||
} else if (action === 'reply-delete') {
|
||||
if (!window.confirm('确定删除此回复?')) return
|
||||
result = await send(`replies/${id}/delete`, 'POST')
|
||||
button.closest('.jio-forum__reply')?.remove()
|
||||
} else if (action === 'term-edit') {
|
||||
const name = button.closest('.jio-forum__term-row')?.querySelector('span')?.textContent || ''
|
||||
const data = await modal('编辑分类', [{ name: 'name', label: '名称', value: name }], '保存')
|
||||
if (!data) return
|
||||
result = await send(`terms/${id}`, 'PATCH', data)
|
||||
button.closest('.jio-forum__term-row').querySelector('span').textContent = data.name
|
||||
} else if (action === 'term-delete') {
|
||||
if (!window.confirm('确定删除这个分类?')) return
|
||||
result = await send(`terms/${id}`, 'DELETE')
|
||||
button.closest('.jio-forum__term-row')?.remove()
|
||||
} else if (action === 'follow') {
|
||||
try { result = await send(`plates/${id}/follow`, 'POST') }
|
||||
catch (error) { if (error.message.includes('付费')) { await checkout('plate', id); return } throw error }
|
||||
} else if (action === 'adopt') result = await send(`posts/${id}/adopt`, 'POST', { comment_id: Number(button.dataset.commentId) })
|
||||
else if (action === 'report') {
|
||||
const data = await modal('举报帖子', [{ name: 'category', label: '类别', type: 'select', options: [{ value: '违规内容', label: '违规内容' }, { value: '侵权', label: '侵权' }, { value: '其他', label: '其他' }] }, { name: 'reason', label: '原因', type: 'textarea' }], '提交举报')
|
||||
if (!data) return
|
||||
result = await send(`posts/${id}/report`, 'POST', data)
|
||||
} else if (action === 'apply') {
|
||||
const data = await modal('申请版主', [{ name: 'reason', label: '申请理由', type: 'textarea' }], '提交申请')
|
||||
if (!data) return
|
||||
result = await send(`plates/${id}/apply`, 'POST', data)
|
||||
} else if (action === 'move') {
|
||||
const data = await modal('移动帖子', [{ name: 'plate_id', label: '目标版块', type: 'select', options: (config.plates || []).map((plate) => ({ value: String(plate.id), label: plate.title })) }], '确认移动')
|
||||
if (!data) return
|
||||
result = await send(`posts/${id}/move`, 'POST', data)
|
||||
} else if (action === 'application') {
|
||||
result = await send(`plates/${id}/applications/${Number(button.dataset.userId)}`, 'POST', { status: button.dataset.status })
|
||||
button.closest('.jio-forum__application')?.remove()
|
||||
} else if (action === 'moderator-remove') {
|
||||
result = await send(`plates/${id}/moderators`, 'POST', { user_id: Number(button.dataset.userId), active: false })
|
||||
button.closest('.jio-forum__application')?.remove()
|
||||
} else if (action === 'comments') {
|
||||
const status = button.textContent.includes('关闭') ? 'closed' : 'open'
|
||||
result = await send(`posts/${id}/comments`, 'POST', { status })
|
||||
button.textContent = status === 'closed' ? '开放回复' : '关闭回复'
|
||||
} else result = await send(`posts/${id}/${action}`, 'POST', action === 'score' ? { direction: button.dataset.direction || 'up' } : {})
|
||||
if (action === 'delete') {
|
||||
button.dataset.forumAction = 'restore'
|
||||
button.textContent = '撤销删除'
|
||||
} else if (action === 'adopt' || action === 'audit') window.location.reload()
|
||||
else button.textContent = result.data?.label || result.message || button.textContent
|
||||
message(button, result.message || '')
|
||||
} catch (error) {
|
||||
message(button, error.message)
|
||||
if (error.message.includes('登录') && config.login) window.location.href = config.login
|
||||
} finally {
|
||||
button.disabled = false
|
||||
}
|
||||
})
|
||||
})()
|
||||
Reference in new issue
Block a user