/**
* 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 = '
';
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);
})();