Files
wordpress-theme-aksrt/assets/js/front-submit-editor.js
T
2026-08-26 17:32:26 +08:00

363 lines
13 KiB
JavaScript

/**
* Front-submit TinyMCE tools matching the reference article editor.
*
* @package AKSRT
*/
(function (window, document) {
'use strict';
if (!window.tinymce || !window.tinymce.PluginManager) {
return;
}
var cfg = window.aksrtSubmitData || {};
var editorCfg = cfg.editor || {};
var i18n = cfg.i18n || {};
var activeModal = null;
function notify(message, type, duration, id) {
if (typeof window.notyf === 'function') {
window.notyf(
message,
type,
duration === undefined ? 5 : duration,
id || 'aksrt-front-submit-editor-image-toast'
);
}
}
function escapeAttr(value) {
return String(value)
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
function validImageUrl(value) {
var raw = String(value).trim();
if (!/^https?:\/\//i.test(raw)) {
return '';
}
try {
var url = new URL(raw);
return url.protocol === 'http:' || url.protocol === 'https:' ? url.href : '';
} catch (error) {
return '';
}
}
function imageMarkup(url, attachmentId, alt) {
var safeUrl = validImageUrl(url);
if (!safeUrl) {
return '';
}
var idAttr = attachmentId ? ' data-edit-file-id="' + Number(attachmentId) + '"' : '';
var altAttr = alt ? ' alt="' + escapeAttr(alt) + '"' : ' alt=""';
return '<p><img src="' + escapeAttr(safeUrl) + '"' + idAttr + altAttr + '></p>';
}
function closeImageModal() {
if (!activeModal) {
return;
}
document.removeEventListener('keydown', activeModal.onKeydown);
activeModal.root.remove();
activeModal = null;
document.body.classList.remove('aksrt-editor-media-open');
}
function uploadImage(file) {
var upload = (cfg.uploads || {}).image || null;
if (!upload || !upload.url) {
return Promise.reject(new Error(i18n.uploadUnavailable || '当前账号不能上传图片'));
}
if (!file || !/^image\//.test(file.type || '')) {
return Promise.reject(new Error(i18n.invalidImage || '请选择有效的图片文件'));
}
if (upload.maxSize && file.size > upload.maxSize) {
return Promise.reject(new Error(i18n.fileTooLarge || '文件大小超过限制'));
}
var body = new FormData();
body.append('file', file);
return fetch(upload.url, {
method: 'POST',
credentials: 'same-origin',
headers: { 'X-WP-Nonce': cfg.restNonce || '' },
body: body,
}).then(function (response) {
return response.json().catch(function () {
return {};
}).then(function (payload) {
if (!response.ok || !payload || !payload.success || !payload.url) {
throw new Error((payload && payload.message) || i18n.error || '图片上传失败');
}
return payload;
});
});
}
function createImageModal(editor) {
closeImageModal();
var canUpload = !!editorCfg.canUploadImage && !!((cfg.uploads || {}).image);
var root = document.createElement('div');
root.className = 'aksrt-editor-media';
root.setAttribute('role', 'dialog');
root.setAttribute('aria-modal', 'true');
root.setAttribute('aria-labelledby', 'aksrt-editor-media-title');
root.innerHTML =
'<button type="button" class="aksrt-editor-media__backdrop" data-editor-media-close tabindex="-1" aria-label="' +
escapeAttr(i18n.close || '关闭') + '"></button>' +
'<div class="aksrt-editor-media__dialog" tabindex="-1">' +
'<button type="button" class="aksrt-editor-media__close" data-editor-media-close aria-label="' +
escapeAttr(i18n.close || '关闭') + '">&times;</button>' +
'<h3 id="aksrt-editor-media-title" class="aksrt-editor-media__title">' +
escapeAttr(i18n.insertImage || '插入图片') + '</h3>' +
'<div class="aksrt-editor-media__tabs" role="tablist">' +
(canUpload
? '<button type="button" class="is-active" role="tab" aria-selected="true" data-editor-media-tab="upload">' +
escapeAttr(i18n.uploadImage || '上传图片') + '</button>'
: '') +
'<button type="button" class="' + (canUpload ? '' : 'is-active') + '" role="tab" aria-selected="' +
(canUpload ? 'false' : 'true') + '" data-editor-media-tab="external">' +
escapeAttr(i18n.externalImage || '外链图片') + '</button>' +
'</div>' +
'<div class="aksrt-editor-media__panels">' +
(canUpload
? '<section class="aksrt-editor-media__panel is-active" data-editor-media-panel="upload">' +
'<label class="aksrt-editor-media__drop">' +
'<input type="file" accept="image/gif,image/jpeg,image/png,image/webp" multiple data-editor-media-files>' +
'<i class="fa fas fa-cloud-upload-alt" aria-hidden="true"></i>' +
'<strong>' + escapeAttr(i18n.selectImages || '选择或拖入图片') + '</strong>' +
'<span>' + escapeAttr(i18n.imageUploadHint || '可批量选择,上传完成后自动插入正文') + '</span>' +
'</label>' +
'<div class="aksrt-editor-media__selection" data-editor-media-selection></div>' +
'<button type="button" class="but c-blue aksrt-editor-media__submit" data-editor-media-upload disabled>' +
escapeAttr(i18n.confirmUpload || '确认上传') + '</button>' +
'</section>'
: '') +
'<section class="aksrt-editor-media__panel ' + (canUpload ? '' : 'is-active') + '" data-editor-media-panel="external">' +
'<p>' + escapeAttr(i18n.externalImageHint || '每行填写一个图片地址:') + '</p>' +
'<textarea class="form-control" rows="5" placeholder="https://..." data-editor-media-urls></textarea>' +
'<button type="button" class="but c-blue aksrt-editor-media__submit" data-editor-media-external>' +
escapeAttr(i18n.confirmInsert || '确认插入') + '</button>' +
'</section>' +
'</div>' +
'</div>';
document.body.appendChild(root);
document.body.classList.add('aksrt-editor-media-open');
var fileInput = root.querySelector('[data-editor-media-files]');
var uploadButton = root.querySelector('[data-editor-media-upload]');
var selection = root.querySelector('[data-editor-media-selection]');
var drop = root.querySelector('.aksrt-editor-media__drop');
var files = [];
function showSelection() {
if (!selection || !uploadButton) {
return;
}
selection.textContent = files.length
? (i18n.selectedImages || '已选择 %d 张图片').replace('%d', String(files.length))
: '';
uploadButton.disabled = files.length === 0;
}
function setFiles(nextFiles) {
files = Array.prototype.filter.call(nextFiles || [], function (file) {
return /^image\//.test(file.type || '');
});
showSelection();
}
root.addEventListener('click', function (event) {
var close = event.target.closest('[data-editor-media-close]');
if (close) {
event.preventDefault();
closeImageModal();
return;
}
var tab = event.target.closest('[data-editor-media-tab]');
if (tab) {
var name = tab.getAttribute('data-editor-media-tab');
root.querySelectorAll('[data-editor-media-tab]').forEach(function (item) {
var active = item === tab;
item.classList.toggle('is-active', active);
item.setAttribute('aria-selected', active ? 'true' : 'false');
});
root.querySelectorAll('[data-editor-media-panel]').forEach(function (panel) {
panel.classList.toggle('is-active', panel.getAttribute('data-editor-media-panel') === name);
});
return;
}
if (event.target.closest('[data-editor-media-external]')) {
var textarea = root.querySelector('[data-editor-media-urls]');
var urls = String(textarea ? textarea.value : '').split(/\r?\n/);
var html = urls.map(function (url) {
return imageMarkup(url, 0, '');
}).join('');
if (!html) {
notify(i18n.invalidImageUrl || '请填写有效的图片地址', 'warning');
return;
}
editor.insertContent(html + '<p></p>');
editor.nodeChanged();
closeImageModal();
return;
}
if (event.target.closest('[data-editor-media-upload]')) {
if (!files.length || !uploadButton) {
return;
}
uploadButton.disabled = true;
uploadButton.textContent = i18n.uploading || '正在上传…';
notify(i18n.uploading || '正在上传正文图片…', 'load', '');
Promise.all(files.map(uploadImage)).then(function (items) {
var html = items.map(function (item) {
return imageMarkup(item.url, item.attachment_id, item.name || '');
}).join('');
if (!html) {
throw new Error(i18n.error || '图片上传失败');
}
editor.insertContent(html + '<p></p>');
editor.nodeChanged();
notify(i18n.uploaded || '正文图片上传成功', 'success');
closeImageModal();
}).catch(function (error) {
notify(error.message || i18n.error || '正文图片上传失败', 'danger');
uploadButton.disabled = false;
uploadButton.textContent = i18n.confirmUpload || '确认上传';
});
}
});
if (fileInput) {
fileInput.addEventListener('change', function () {
setFiles(fileInput.files);
});
}
if (drop) {
['dragenter', 'dragover'].forEach(function (name) {
drop.addEventListener(name, function (event) {
event.preventDefault();
drop.classList.add('is-dragging');
});
});
['dragleave', 'drop'].forEach(function (name) {
drop.addEventListener(name, function (event) {
event.preventDefault();
drop.classList.remove('is-dragging');
});
});
drop.addEventListener('drop', function (event) {
setFiles(event.dataTransfer && event.dataTransfer.files);
});
}
var onKeydown = function (event) {
if (event.key === 'Escape') {
closeImageModal();
}
};
activeModal = { root: root, onKeydown: onKeydown };
document.addEventListener('keydown', onKeydown);
var focusTarget = root.querySelector('.aksrt-editor-media__dialog');
if (focusTarget) {
focusTarget.focus({ preventScroll: true });
}
}
function selectedContent(editor) {
var content = String(editor.selection.getContent() || '').trim();
return content ? '<p>' + content + '</p>' : '<p>' + escapeAttr(i18n.hiddenPlaceholder || '隐藏内容') + '</p>';
}
function insertHiddenContent(editor, item) {
var node = editor.selection.getNode();
var content = selectedContent(editor);
var suffix = '<p></p>';
if (node && editor.dom.hasClass(node, 'tinymce-hide')) {
var editable = node.querySelector('[contenteditable="true"]');
content = editable ? editable.innerHTML : content;
editor.dom.remove(node);
suffix = '';
}
editor.insertContent(
'<div class="tinymce-hide" data-aksrt-hide-type="' + escapeAttr(item.type) + '" contenteditable="false">' +
'<p class="hide-before">' + escapeAttr(item.open) + '</p>' +
'<div contenteditable="true">' + content + '</div>' +
'<p class="hide-after">' + escapeAttr(item.close) + '</p>' +
'</div>' + suffix
);
editor.nodeChanged();
}
window.tinymce.PluginManager.add('aksrt_editor_tools', function (editor) {
if (editorCfg.canUploadImage) {
editor.addButton('aksrt_image', {
text: '',
icon: 'image',
tooltip: i18n.insertImage || '插入图片',
onclick: function () {
createImageModal(editor);
},
});
}
if (editorCfg.canHide) {
var menu = [
{
text: i18n.replyVisible || '回复可见',
type: 'reply',
open: '[reply]',
close: '[/reply]',
},
{
text: i18n.loginVisible || '登录可见',
type: 'login',
open: '[aksrt_hide mode="login"]',
close: '[/aksrt_hide]',
},
{
text: i18n.vipVisible || 'VIP 可见',
type: 'vip',
open: '[vip]',
close: '[/vip]',
},
];
if (editorCfg.canPay) {
menu.push({
text: i18n.payVisible || '付费可见',
type: 'payshow',
open: '[payshow]',
close: '[/payshow]',
});
}
menu = menu.map(function (item) {
return {
text: item.text,
onclick: function () {
insertHiddenContent(editor, item);
},
};
});
editor.addButton('aksrt_hide', {
text: '',
icon: 'preview',
tooltip: i18n.hiddenContent || '隐藏内容',
type: 'menubutton',
stateSelector: '.tinymce-hide',
menu: menu,
});
}
});
})(window, document);