/** * Frontend reporting for articles, comments and users. * @package AKSRT */ (function () { 'use strict'; var config = window.aksrtData || {}; var API_ROOT = String(config.apiUrl || '/index.php?rest_route=/aksrt/v1').replace(/\/$/, ''); var NONCE = config.restNonce || ''; var MAX_IMAGES = 3; var sequence = 0; var TARGETS = { user: { title: '举报用户', label: '用户' }, post: { title: '举报文章', label: '文章' }, comment: { title: '举报评论', label: '评论' }, }; function escapeHtml(value) { return String(value == null ? '' : value) .replace(/&/g, '&').replace(//g, '>') .replace(/"/g, '"').replace(/'/g, '''); } function getReportCategories() { return (Array.isArray(config.reportCategories) ? config.reportCategories : []) .filter(function (cat) { return cat && cat.name && cat.name !== '其他原因'; }); } function buildReportModalHtml(data, categories) { var target = TARGETS[data.target_type]; var report = config.report || {}; var id = 'aksrt-report-' + (++sequence); var name = data.target_name || target.label + ' #' + data.target_id; var html = '
'; html += '
'; html += '
关联对象:'; html += '' + escapeHtml(name) + '
'; html += '
'; html += '举报原因必选一项'; html += '
'; categories.forEach(function (cat, index) { html += ''; }); html += ''; html += '
'; html += '
'; html += ''; html += '
'; html += '
'; if (report.imgEnabled) { html += '
'; html += '
图片举证选填'; html += '0 / ' + MAX_IMAGES + '
'; html += '
'; html += '
'; html += '

支持 JPG、PNG、GIF,最多 ' + MAX_IMAGES + ' 张

'; html += '
'; } // The settings reader sanitizes this administrator-authored notice with wp_kses_post. if (report.desc) html += '
' + report.desc + '
'; html += '
'; return html; } function showError(element, message) { element.textContent = message; element.hidden = !message; } function bindReportModal(shell, data, categories) { var form = shell.querySelector('.aksrt-report-form'); var submit = form.querySelector('[data-aksrt-report-submit]'); var reasonError = form.querySelector('[data-aksrt-report-reason-error]'); var error = form.querySelector('[data-aksrt-report-error]'); var status = form.querySelector('[data-aksrt-report-status]'); var otherBox = form.querySelector('.aksrt-report-other'); var otherInput = form.querySelector('[name="reason_other"]'); var descInput = form.querySelector('[name="report_desc"]'); var fileInput = form.querySelector('[data-aksrt-report-file]'); var images = []; var submitAttempts = 0; var submitting = false; var closed = false; function isActive() { return !closed && window.AksrtModalRuntime.getActive() === shell; } function syncState() { var uploading = images.some(function (image) { return image.status === 'uploading'; }); submit.disabled = submitting || uploading; submit.textContent = uploading ? '图片上传中…' : '提交举报'; form.setAttribute('aria-busy', String(submitting || uploading)); status.textContent = uploading ? '图片正在上传,完成后即可提交' : ''; var add = form.querySelector('[data-aksrt-report-add]'); if (add) { add.disabled = submitting || images.length >= MAX_IMAGES; form.querySelector('[data-aksrt-report-image-count]').textContent = images.length + ' / ' + MAX_IMAGES; } } function deletePending(image) { if (!image.attachmentId) return; window.fetch(API_ROOT + '/upload/pending/' + encodeURIComponent(image.attachmentId), { method: 'DELETE', headers: { 'X-WP-Nonce': NONCE }, }).catch(function () {}); } function renderImage(image) { var html = image.url ? '举证图片' : ''; html += '
'; if (image.status === 'uploading') html += '上传中…'; if (image.status === 'error') { html += '上传失败'; } html += '
'; image.node.innerHTML = html; image.node.setAttribute('data-upload-state', image.status); } function uploadImage(image) { image.status = 'uploading'; renderImage(image); syncState(); var body = new FormData(); body.append('image', image.file); window.fetch(API_ROOT + '/report/upload-image', { method: 'POST', headers: { 'X-WP-Nonce': NONCE }, body: body, }).then(function (response) { return response.json(); }).then(function (result) { if (!result || !result.success || !result.data || !result.data.url) { throw new Error((result && result.message) || '图片上传失败,请重试'); } image.attachmentId = result.data.id; if (closed || images.indexOf(image) === -1) { deletePending(image); return; } image.url = result.data.url; image.status = 'ready'; renderImage(image); }).catch(function (failure) { if (closed || images.indexOf(image) === -1) return; image.status = 'error'; renderImage(image); showError(error, failure.message || '图片上传失败,请重试'); }).then(function () { if (!closed) syncState(); }); } form.addEventListener('change', function (event) { if (event.target.name === 'report_reason') { var other = event.target.value === 'other'; otherBox.hidden = !other; otherInput.disabled = !other; otherInput.required = other; otherInput.removeAttribute('aria-invalid'); showError(reasonError, ''); if (other) otherInput.focus(); } if (event.target !== fileInput || submitting) return; var files = Array.prototype.slice.call(fileInput.files || []); showError(error, ''); files.forEach(function (file) { if (images.length >= MAX_IMAGES) { showError(error, '最多上传 ' + MAX_IMAGES + ' 张图片,请先删除不需要的图片'); return; } if (!/^image\/(jpeg|png|gif)$/.test(file.type)) { showError(error, '请选择 JPG、PNG 或 GIF 图片'); return; } var image = { file: file, status: 'uploading', url: '', attachmentId: 0, node: document.createElement('div') }; image.node.className = 'aksrt-report-image'; images.push(image); form.querySelector('[data-aksrt-report-images]').appendChild(image.node); uploadImage(image); }); fileInput.value = ''; }); form.addEventListener('input', function (event) { if (event.target === descInput) form.querySelector('[data-aksrt-report-count]').textContent = descInput.value.length + ' / 2000'; if (event.target === otherInput) { otherInput.removeAttribute('aria-invalid'); showError(reasonError, ''); } }); form.addEventListener('click', function (event) { var add = event.target.closest('[data-aksrt-report-add]'); if (add && !add.disabled && !submitting) fileInput.click(); var imageNode = event.target.closest('.aksrt-report-image'); if (!imageNode || submitting) return; var image = images.find(function (entry) { return entry.node === imageNode; }); if (!image) return; if (event.target.closest('[data-aksrt-report-remove]')) { images.splice(images.indexOf(image), 1); image.node.remove(); deletePending(image); showError(error, ''); syncState(); } else if (event.target.closest('[data-aksrt-report-retry]') && image.status === 'error') { showError(error, ''); uploadImage(image); } }); form.addEventListener('submit', function (event) { event.preventDefault(); if (submitting || closed) return; showError(error, ''); var selected = form.querySelector('[name="report_reason"]:checked'); if (!selected) { showError(reasonError, '请选择一项举报原因'); form.querySelector('[name="report_reason"]').focus(); return; } var other = selected.value === 'other'; var category = other ? '其他原因' : String(categories[Number(selected.value)].name); var explanation = other ? otherInput.value.trim() : ''; if (other && !explanation) { showError(reasonError, '请填写举报原因'); otherInput.setAttribute('aria-invalid', 'true'); otherInput.focus(); return; } if (images.some(function (image) { return image.status !== 'ready'; })) { showError(error, '请等待图片上传完成,或重试、删除上传失败的图片'); return; } var content = []; if (explanation) content.push(explanation); var description = descInput.value.trim(); if (description) content.push(description); if (images.length) content.push('图片举证:\n' + images.map(function (image) { return image.url; }).join('\n')); var reason = content.join('\n\n'); submitting = true; syncState(); var toastId = form.id + '-submit-' + (++submitAttempts); notyf('正在提交举报…', 'load', '', toastId); form.querySelectorAll('input, textarea, [data-aksrt-report-remove], [data-aksrt-report-retry]').forEach(function (element) { element.disabled = true; }); window.fetch(API_ROOT + '/report/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': NONCE }, body: JSON.stringify({ target_type: data.target_type, target_id: data.target_id, category: category, reason: reason }), }).then(function (response) { return response.json(); }).then(function (result) { if (!result || !result.success) throw new Error((result && result.message) || '举报提交失败,请重试'); // Submitted evidence belongs to the report even if its dialog has already closed. images = []; form.setAttribute('aria-busy', 'false'); submit.textContent = '举报已提交'; status.textContent = result.message || '举报已提交,我们会尽快处理'; form.querySelector('[data-aksrt-modal-close]').textContent = '关闭'; if (isActive()) window.AksrtModalRuntime.close('submit'); notyf(result.message || '举报已提交,我们会尽快处理', 'success', 5, toastId); }).catch(function (failure) { notyf(failure.message || '网络错误,请重试', 'danger', 5, toastId); submitting = false; if (closed) { images.forEach(deletePending); return; } form.querySelectorAll('input, textarea, [data-aksrt-report-remove], [data-aksrt-report-retry]').forEach(function (element) { element.disabled = false; }); otherInput.disabled = otherBox.hidden; syncState(); }); }); function onClose(event) { if (!event.detail || event.detail.shell !== shell) return; closed = true; document.removeEventListener('aksrt:modal:close', onClose); // While submitting, the response decides whether pending evidence is still needed. if (!submitting) images.forEach(deletePending); } document.addEventListener('aksrt:modal:close', onClose); otherInput.disabled = true; syncState(); } function openReportModal(data, opener) { var runtime = window.AksrtModalRuntime; if (!runtime || typeof runtime.open !== 'function') { notyf('举报组件加载失败,请刷新页面后重试', 'danger'); return; } var categories = getReportCategories(); var shell = runtime.open({ html: buildReportModalHtml(data, categories), title: TARGETS[data.target_type].title, className: 'aksrt-report-modal', kind: 'report', mobileBottom: true, focusReturn: opener, }); bindReportModal(shell, data, categories); } function initReportButtons() { if (document.documentElement.dataset.aksrtReportInitialized === '1') return; document.documentElement.dataset.aksrtReportInitialized = '1'; document.addEventListener('click', function (event) { var button = event.target.closest('[data-aksrt-report]'); if (!button) return; event.preventDefault(); var data; try { data = JSON.parse(button.getAttribute('data-aksrt-report')); } catch (error) { return; } if (data && TARGETS[data.target_type] && Number(data.target_id) > 0) openReportModal(data, button); }); } jQuery(document).off('auto_fun.aksrt-report').on('auto_fun.aksrt-report', initReportButtons); })();