进行了一些小修复

This commit is contained in:
2026-08-26 17:57:29 +08:00
parent 1f84e6aa18
commit fb2bbb64c5
8 changed files with 630 additions and 54 deletions
+146
View File
@@ -42,3 +42,149 @@
.tinymce-hide > [contenteditable="true"] > :last-child {
margin-bottom: 0;
}
.quote_q {
position: relative;
margin: 10px 0 20px;
padding: 1.2em;
border-radius: 6px;
color: var(--quote-color);
--quote-color: #555;
}
.quote_q::before {
position: absolute;
inset: 0;
border-radius: 6px;
background: var(--quote-color);
content: '';
opacity: 0.04;
}
.quote_q.quote-mce::after {
position: absolute;
top: 20px;
left: 20px;
color: var(--quote-color);
content: '\201c';
font-size: 24px;
line-height: 1;
opacity: 0.6;
}
.quote_q > div {
position: relative;
min-height: 24px;
padding-left: 2em;
opacity: 0.7;
}
.quote_q > div > :first-child {
margin-top: 0;
}
.quote_q > div > :last-child {
margin-bottom: 0;
}
.quote_q [contenteditable="true"] {
outline: 0;
}
.qe_wzk_c-red {
--quote-color: #dc3f3f;
}
.qe_wzk_lan {
--quote-color: #0092ff;
}
.qe_wzk_lv {
--quote-color: #23790e;
}
.enlighter-pre {
position: relative;
max-height: 300px;
overflow: auto;
padding: 32px 12px 12px;
border-radius: 6px;
background: #f2f4f5;
}
.enlighter-pre code.gl {
display: block;
border: 0;
background: transparent;
color: inherit;
font-family: Consolas, Monaco, monospace;
line-height: 1.45;
white-space: break-spaces;
}
.enlighter-pre code.gl::before {
position: absolute;
top: 8px;
right: 8px;
padding: 3px 8px;
border-radius: 4px;
background: linear-gradient(135deg, #3dcabd 10%, #254bdd 100%);
color: #fff;
content: '高亮代码';
font-size: 12px;
}
.file-download-box {
margin-bottom: 20px;
padding: 15px;
border: 1px dashed #bbb;
border-radius: 6px;
background: #f5f5f5;
}
.file-download-box .file-download-icon {
font-size: 24px;
}
.file-download-box .file-download-name {
min-width: 80px;
outline: 0;
}
.new-dplayer.post-dplayer {
position: relative;
min-height: 220px;
margin-bottom: 20px;
border-radius: 6px;
background: #222;
}
.new-dplayer.post-dplayer::before {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
content: '视频';
opacity: 0.65;
}
.wp-block-embed.is-type-video {
margin-bottom: 20px;
}
.iframe-absbox {
position: relative;
height: 0;
overflow: hidden;
border-radius: 6px;
background: #222;
}
.iframe-absbox iframe {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
+374 -40
View File
@@ -21,7 +21,7 @@
message,
type,
duration === undefined ? 5 : duration,
id || 'aksrt-front-submit-editor-image-toast'
id || 'aksrt-front-submit-editor-media-toast'
);
}
}
@@ -34,7 +34,7 @@
.replace(/>/g, '>');
}
function validImageUrl(value) {
function validHttpUrl(value) {
var raw = String(value).trim();
if (!/^https?:\/\//i.test(raw)) {
return '';
@@ -48,7 +48,7 @@
}
function imageMarkup(url, attachmentId, alt) {
var safeUrl = validImageUrl(url);
var safeUrl = validHttpUrl(url);
if (!safeUrl) {
return '';
}
@@ -57,7 +57,49 @@
return '<p><img src="' + escapeAttr(safeUrl) + '"' + idAttr + altAttr + '></p>';
}
function closeImageModal() {
function videoMarkup(url, attachmentId, name) {
var safeUrl = validHttpUrl(url);
if (!safeUrl) {
return '';
}
var idAttr = attachmentId ? ' data-edit-file-id="' + Number(attachmentId) + '"' : '';
return '<div contenteditable="false" data-video-url="' + escapeAttr(safeUrl) +
'" data-video-name="' + escapeAttr(name || safeUrl) + '"' + idAttr +
' class="new-dplayer post-dplayer dplayer"></div>';
}
function fileMarkup(url, attachmentId, name) {
var safeUrl = validHttpUrl(url);
if (!safeUrl) {
return '';
}
var filename = String(name || safeUrl.split('/').pop() || i18n.attachment || '附件');
var idAttr = attachmentId ? ' data-download-file="' + Number(attachmentId) + '"' : '';
return '<div class="mb20 muted-box file-download-box" contenteditable="false">' +
'<div class="flex ac jsb"><div class="flex ac"><div class="mr10">' +
'<div class="file-download-icon"><i class="fa fas fa-file-alt" aria-hidden="true"></i></div></div>' +
'<div class="text-ellipsis-2 file-download-name" contenteditable="true">' + escapeAttr(filename) + '</div>' +
'</div><div class="flex0 ml20"><a href="' + escapeAttr(safeUrl) + '" data-mce-href="' +
escapeAttr(safeUrl) + '"' + idAttr + ' class="but c-blue file-download-btn">' +
'<i class="fa fas fa-download" aria-hidden="true"></i>' + escapeAttr(i18n.download || '下载') +
'</a></div></div></div>';
}
function embedVideoMarkup(value) {
var holder = document.createElement('div');
holder.innerHTML = String(value).trim();
var iframe = holder.querySelector('iframe');
var safeUrl = validHttpUrl(iframe ? iframe.getAttribute('src') : value);
if (!safeUrl) {
return '';
}
return '<div contenteditable="false" class="wp-block-embed is-type-video mb20">' +
'<div class="iframe-absbox" style="padding-bottom:56%;"><iframe src="' + escapeAttr(safeUrl) +
'" allowfullscreen="allowfullscreen" framespacing="0" border="0" width="100%" frameborder="no"></iframe>' +
'</div></div>';
}
function closeMediaModal() {
if (!activeModal) {
return;
}
@@ -67,13 +109,27 @@
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 || '当前账号不能上传图片'));
function acceptsFile(file, type) {
if (!file) {
return false;
}
if (!file || !/^image\//.test(file.type || '')) {
return Promise.reject(new Error(i18n.invalidImage || '请选择有效的图片文件'));
if (type === 'image') {
return /^image\//.test(file.type || '');
}
if (type === 'video') {
return /^video\//.test(file.type || '');
}
return true;
}
function uploadMedia(file, type) {
var uploadKey = type === 'file' ? 'resource' : type;
var upload = (cfg.uploads || {})[uploadKey] || null;
if (!upload || !upload.url) {
return Promise.reject(new Error(i18n.uploadUnavailable || '当前账号不能上传此类文件'));
}
if (!acceptsFile(file, type)) {
return Promise.reject(new Error(i18n.invalidFile || '请选择有效文件'));
}
if (upload.maxSize && file.size > upload.maxSize) {
return Promise.reject(new Error(i18n.fileTooLarge || '文件大小超过限制'));
@@ -91,19 +147,76 @@
return {};
}).then(function (payload) {
if (!response.ok || !payload || !payload.success || !payload.url) {
throw new Error((payload && payload.message) || i18n.error || '图片上传失败');
throw new Error((payload && payload.message) || i18n.error || '文件上传失败');
}
return payload;
});
});
}
function createImageModal(editor) {
closeImageModal();
function mediaDefinition(type) {
if (type === 'video') {
return {
title: i18n.insertVideo || '插入视频',
uploadTab: i18n.uploadVideo || '上传视频',
externalTab: i18n.externalVideo || '外链视频',
embedTab: i18n.embedVideo || '嵌入视频',
accept: 'video/*',
multiple: false,
canUpload: !!editorCfg.canUploadVideo && !!((cfg.uploads || {}).video),
canEmbed: !!editorCfg.canEmbedVideo,
select: i18n.selectVideo || '选择或拖入视频',
hint: i18n.videoUploadHint || '上传完成后自动插入正文',
externalHint: i18n.externalVideoHint || '请填写视频文件地址:',
};
}
if (type === 'file') {
return {
title: i18n.insertFile || '上传附件',
uploadTab: i18n.uploadFile || '上传附件',
externalTab: i18n.externalFile || '输入地址',
accept: '*/*',
multiple: false,
canUpload: !!editorCfg.canUploadFile && !!((cfg.uploads || {}).resource),
canEmbed: false,
select: i18n.selectFile || '选择或拖入附件',
hint: i18n.fileUploadHint || '上传完成后自动插入正文',
externalHint: i18n.externalFileHint || '请填写附件地址:',
};
}
return {
title: i18n.insertImage || '插入图片',
uploadTab: i18n.uploadImage || '上传图片',
externalTab: i18n.externalImage || '外链图片',
accept: 'image/gif,image/jpeg,image/png,image/webp',
multiple: true,
canUpload: !!editorCfg.canUploadImage && !!((cfg.uploads || {}).image),
canEmbed: false,
select: i18n.selectImages || '选择或拖入图片',
hint: i18n.imageUploadHint || '可批量选择,上传完成后自动插入正文',
externalHint: i18n.externalImageHint || '每行填写一个图片地址:',
};
}
var canUpload = !!editorCfg.canUploadImage && !!((cfg.uploads || {}).image);
function mediaMarkup(type, item) {
if (type === 'video') {
return videoMarkup(item.url, item.attachment_id, item.filename || item.name || '');
}
if (type === 'file') {
return fileMarkup(item.url, item.attachment_id, item.filename || item.name || '');
}
return imageMarkup(item.url, item.attachment_id, item.name || '');
}
function createMediaModal(editor, type) {
closeMediaModal();
var definition = mediaDefinition(type);
var canUpload = definition.canUpload;
var firstTab = canUpload ? 'upload' : 'external';
var root = document.createElement('div');
root.className = 'aksrt-editor-media';
root.setAttribute('data-editor-media-type', type);
root.setAttribute('role', 'dialog');
root.setAttribute('aria-modal', 'true');
root.setAttribute('aria-labelledby', 'aksrt-editor-media-title');
@@ -112,38 +225,51 @@
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>' +
escapeAttr(i18n.close || '关闭') + '">&times;</button>' +
'<h3 id="aksrt-editor-media-title" class="aksrt-editor-media__title">' +
escapeAttr(i18n.insertImage || '插入图片') + '</h3>' +
escapeAttr(definition.title) + '</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>'
escapeAttr(definition.uploadTab) + '</button>'
: '') +
'<button type="button" class="' + (canUpload ? '' : 'is-active') + '" role="tab" aria-selected="' +
'<button type="button" class="' + (firstTab === 'external' ? 'is-active' : '') + '" role="tab" aria-selected="' +
(canUpload ? 'false' : 'true') + '" data-editor-media-tab="external">' +
escapeAttr(i18n.externalImage || '外链图片') + '</button>' +
escapeAttr(definition.externalTab) + '</button>' +
(definition.canEmbed
? '<button type="button" role="tab" aria-selected="false" data-editor-media-tab="embed">' +
escapeAttr(definition.embedTab) + '</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>' +
'<input type="file" accept="' + escapeAttr(definition.accept) + '"' +
(definition.multiple ? ' 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>' +
'<strong>' + escapeAttr(definition.select) + '</strong>' +
'<span>' + escapeAttr(definition.hint) + '</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>' +
'<section class="aksrt-editor-media__panel ' + (firstTab === 'external' ? 'is-active' : '') + '" data-editor-media-panel="external">' +
'<p>' + escapeAttr(definition.externalHint) + '</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>' +
(definition.canEmbed
? '<section class="aksrt-editor-media__panel" data-editor-media-panel="embed">' +
'<p>' + escapeAttr(i18n.embedVideoHint || '请输入嵌入地址或者直接粘贴 iframe 嵌入代码:') + '</p>' +
'<textarea class="form-control" rows="5" placeholder="&lt;iframe src=&quot;https://...&quot;&gt;&lt;/iframe&gt;" data-editor-media-embed></textarea>' +
'<button type="button" class="but c-blue aksrt-editor-media__submit" data-editor-media-embed-submit>' +
escapeAttr(i18n.confirmEmbed || '确认嵌入') + '</button>' +
'</section>'
: '') +
'</div>' +
'</div>';
@@ -161,15 +287,24 @@
return;
}
selection.textContent = files.length
? (i18n.selectedImages || '已选择 %d 张图片').replace('%d', String(files.length))
? (definition.multiple
? (i18n.selectedFiles || '已选择 %d 个文件').replace('%d', String(files.length))
: files[0].name)
: '';
uploadButton.disabled = files.length === 0;
}
function setFiles(nextFiles) {
files = Array.prototype.filter.call(nextFiles || [], function (file) {
return /^image\//.test(file.type || '');
var candidates = Array.prototype.slice.call(nextFiles || []);
files = candidates.filter(function (file) {
return acceptsFile(file, type);
});
if (candidates.length && !files.length) {
notify(i18n.invalidFile || '请选择有效文件', 'warning');
}
if (!definition.multiple && files.length > 1) {
files = files.slice(0, 1);
}
showSelection();
}
@@ -177,7 +312,7 @@
var close = event.target.closest('[data-editor-media-close]');
if (close) {
event.preventDefault();
closeImageModal();
closeMediaModal();
return;
}
@@ -198,16 +333,32 @@
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/);
if (!definition.multiple) {
urls = urls.slice(0, 1);
}
var html = urls.map(function (url) {
return imageMarkup(url, 0, '');
return mediaMarkup(type, { url: url });
}).join('');
if (!html) {
notify(i18n.invalidImageUrl || '请填写有效的图片地址', 'warning');
notify(i18n.invalidMediaUrl || '请填写有效地址', 'warning');
return;
}
editor.insertContent(html + '<p></p>');
editor.nodeChanged();
closeImageModal();
closeMediaModal();
return;
}
if (event.target.closest('[data-editor-media-embed-submit]')) {
var embedInput = root.querySelector('[data-editor-media-embed]');
var embedHtml = embedVideoMarkup(embedInput ? embedInput.value : '');
if (!embedHtml) {
notify(i18n.invalidVideoUrl || '请填写有效的视频嵌入地址', 'warning');
return;
}
editor.insertContent(embedHtml + '<p></p>');
editor.nodeChanged();
closeMediaModal();
return;
}
@@ -217,20 +368,22 @@
}
uploadButton.disabled = true;
uploadButton.textContent = i18n.uploading || '正在上传…';
notify(i18n.uploading || '正在上传正文图片…', 'load', '');
Promise.all(files.map(uploadImage)).then(function (items) {
notify(i18n.uploading || '正在上传…', 'load', '');
Promise.all(files.map(function (file) {
return uploadMedia(file, type);
})).then(function (items) {
var html = items.map(function (item) {
return imageMarkup(item.url, item.attachment_id, item.name || '');
return mediaMarkup(type, item);
}).join('');
if (!html) {
throw new Error(i18n.error || '图片上传失败');
throw new Error(i18n.error || '文件上传失败');
}
editor.insertContent(html + '<p></p>');
editor.nodeChanged();
notify(i18n.uploaded || '正文图片上传成功', 'success');
closeImageModal();
notify(i18n.uploaded || '上传成功', 'success');
closeMediaModal();
}).catch(function (error) {
notify(error.message || i18n.error || '正文图片上传失败', 'danger');
notify(error.message || i18n.error || '文件上传失败', 'danger');
uploadButton.disabled = false;
uploadButton.textContent = i18n.confirmUpload || '确认上传';
});
@@ -263,7 +416,7 @@
var onKeydown = function (event) {
if (event.key === 'Escape') {
closeImageModal();
closeMediaModal();
}
};
activeModal = { root: root, onKeydown: onKeydown };
@@ -300,6 +453,143 @@
editor.nodeChanged();
}
function insertQuote(editor, colorClass) {
var node = editor.selection.getNode();
var quote = editor.dom.getParent(node, '.quote_q');
if (quote) {
quote.className = 'quote_q quote-mce' + (colorClass ? ' ' + colorClass : '');
editor.nodeChanged();
return;
}
var content = String(editor.selection.getContent() || '').trim();
if (node && node.nodeName === 'P' && node.parentNode === editor.getBody()) {
content = node.outerHTML;
editor.dom.remove(node);
} else {
content = content ? '<p>' + content + '</p>' : '<p></p>';
}
editor.insertContent(
'<div class="quote_q quote-mce' + (colorClass ? ' ' + colorClass : '') +
'" contenteditable="false"><div contenteditable="true">' + content + '</div></div><p></p>'
);
editor.nodeChanged();
}
var codeLanguages = [
['yaml', 'yaml'],
['xml/html', 'xml'],
['visualbasic', 'visualbasic'],
['vhdl', 'vhdl'],
['typescript', 'typescript'],
['swift', 'swift'],
['sql', 'sql'],
['shell', 'shell'],
['scss/sass', 'scss'],
['rust', 'rust'],
['ruby', 'ruby'],
['python', 'python'],
['powershell', 'powershell'],
['php', 'php'],
['markdown', 'markdown'],
['lua', 'lua'],
['less', 'less'],
['kotlin', 'kotlin'],
['json', 'json'],
['javascript', 'javascript'],
['java', 'java'],
['ini/conf', 'ini'],
['go/golang', 'go'],
['docker', 'dockerfile'],
['diff', 'diff'],
['css', 'css'],
['csharp', 'csharp'],
['Cpp/C++/C', 'cpp'],
['assembly', 'assembly'],
[i18n.codeGeneric || '通用高亮', 'generic'],
];
var codeThemes = [
['enlighter', 'enlighter'],
['classic', 'classic'],
['beyond', 'beyond'],
['mowtwo', 'mowtwo'],
['eclipse', 'eclipse'],
['droide', 'droide'],
['minimal', 'minimal'],
['atomic', 'atomic'],
['dracula', 'dracula'],
['bootstrap4', 'bootstrap4'],
['rowhammer', 'rowhammer'],
['godzilla', 'godzilla'],
[i18n.codeFollowGlobal || '跟随全局设置', 'qj'],
];
function listboxValues(items) {
return items.map(function (item) {
return { text: item[0], value: item[1] };
});
}
function openCodeModal(editor) {
var selected = editor.dom.getParent(editor.selection.getNode(), 'pre.enlighter-pre');
var code = selected ? selected.querySelector('code') : null;
var width = window.innerWidth > 800 ? Math.round(window.innerWidth * 0.5) :
(window.innerWidth > 640 ? Math.round(window.innerWidth * 0.7) : window.innerWidth - 20);
var height = Math.round(window.innerHeight * 0.6);
editor.windowManager.open({
title: (selected ? (i18n.edit || '编辑') : (i18n.insert || '插入')) +
(i18n.highlightCode || '高亮代码'),
width: width,
height: height,
body: [
{
type: 'listbox',
name: 'language',
label: i18n.selectLanguage || '选择语言',
value: code ? (code.getAttribute('data-enlighter-language') || 'generic') : 'generic',
values: listboxValues(codeLanguages),
},
{
type: 'listbox',
name: 'theme',
label: i18n.codeTheme || '主题',
value: code ? (code.getAttribute('data-enlighter-theme') || 'qj') : 'qj',
values: listboxValues(codeThemes),
},
{
type: 'textbox',
name: 'content',
label: i18n.codeLabel || '代码:',
value: code ? (code.textContent || '') : '',
multiline: true,
minHeight: Math.max(160, height - 115),
},
],
onsubmit: function (event) {
var content = String(event.data.content || '').replace(/\r\n/g, '\n');
if (!content) {
return;
}
var encoded = window.tinymce.html.Entities.encodeAllRaw(content);
var language = event.data.language === 'generic' ? '' :
' data-enlighter-language="' + escapeAttr(event.data.language) + '"';
var theme = event.data.theme === 'qj' ? '' :
' data-enlighter-theme="' + escapeAttr(event.data.theme) + '"';
var markup = '<pre contenteditable="false" class="enlighter-pre"><code class="gl"' +
language + theme + '>' + encoded + '</code></pre>';
if (selected) {
editor.dom.setOuterHTML(selected, markup);
} else {
editor.insertContent(markup + '<p></p>');
}
editor.nodeChanged();
editor.undoManager.add();
},
});
}
window.tinymce.PluginManager.add('aksrt_editor_tools', function (editor) {
if (editorCfg.canUploadImage) {
editor.addButton('aksrt_image', {
@@ -307,11 +597,55 @@
icon: 'image',
tooltip: i18n.insertImage || '插入图片',
onclick: function () {
createImageModal(editor);
createMediaModal(editor, 'image');
},
});
}
editor.addButton('aksrt_video', {
text: '',
icon: 'media',
tooltip: i18n.insertVideo || '插入视频',
onclick: function () {
createMediaModal(editor, 'video');
},
});
if (editorCfg.canUploadFile) {
editor.addButton('aksrt_file', {
text: '',
icon: 'upload',
tooltip: i18n.insertFile || '上传附件',
onclick: function () {
createMediaModal(editor, 'file');
},
});
}
editor.addButton('aksrt_quote', {
text: '',
icon: 'blockquote',
tooltip: i18n.quote || '引言',
type: 'menubutton',
stateSelector: '.quote_q',
menu: [
{ text: i18n.quoteGray || '灰色', onclick: function () { insertQuote(editor, ''); } },
{ text: i18n.quoteRed || '红色', onclick: function () { insertQuote(editor, 'qe_wzk_c-red'); } },
{ text: i18n.quoteBlue || '蓝色', onclick: function () { insertQuote(editor, 'qe_wzk_lan'); } },
{ text: i18n.quoteGreen || '绿色', onclick: function () { insertQuote(editor, 'qe_wzk_lv'); } },
],
});
editor.addButton('aksrt_precode', {
text: '',
icon: 'code',
tooltip: i18n.highlightCode || '高亮代码',
stateSelector: 'pre.enlighter-pre',
onclick: function () {
openCodeModal(editor);
},
});
if (editorCfg.canHide) {
var menu = [
{
+1
View File
@@ -65,6 +65,7 @@ class SchemaPermissions {
]],
['key' => 'content', 'title' => '内容上传限制', 'fields' => [
['key' => 'submit_image_max_size', 'label' => '投稿图片大小上限 (MB)', 'type' => 'number', 'default' => 5, 'min' => 1, 'max' => 20],
['key' => 'submit_video_max_size', 'label' => '投稿视频大小上限 (MB)', 'type' => 'number', 'default' => 30, 'min' => 1, 'max' => 200],
['key' => 'comment_image_max_size', 'label' => '评论图片大小上限 (MB)', 'type' => 'number', 'default' => 2, 'min' => 1, 'max' => 10],
]],
['key' => 'resource', 'title' => '资源附件限制', 'fields' => [
+19 -6
View File
@@ -45,14 +45,14 @@ class FrontSubmit {
}
public function register_rest_routes(): void {
register_rest_route('aksrt/v1', '/front-submit/upload/(?P<kind>image|resource)', [
register_rest_route('aksrt/v1', '/front-submit/upload/(?P<kind>image|video|resource)', [
'methods' => 'POST',
'callback' => [$this, 'rest_upload_attachment'],
'permission_callback' => [$this, 'can_upload_attachment'],
'args' => [
'kind' => [
'validate_callback' => static function ($value): bool {
return in_array((string) $value, ['image', 'resource'], true);
return in_array((string) $value, ['image', 'video', 'resource'], true);
},
],
],
@@ -69,7 +69,15 @@ class FrontSubmit {
}
$kind = (string) $request->get_param('kind');
$cap = $kind === 'resource' ? 'upload_resource_attachment' : 'upload_submit_image';
$caps = [
'image' => 'upload_submit_image',
'video' => 'new_post_upload_video',
'resource' => 'upload_resource_attachment',
];
$cap = $caps[$kind] ?? '';
if ($cap === '') {
return false;
}
return $this->user_can_cap($cap, $user_id);
}
@@ -84,9 +92,11 @@ class FrontSubmit {
}
$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
$max_mb = $kind === 'resource'
? max(1, min(200, (int) ($settings['resource_max_size'] ?? 50)))
: max(1, min(20, (int) ($settings['submit_image_max_size'] ?? 5)));
$max_mb = match ($kind) {
'resource' => max(1, min(200, (int) ($settings['resource_max_size'] ?? 50))),
'video' => max(1, min(200, (int) ($settings['submit_video_max_size'] ?? 30))),
default => max(1, min(20, (int) ($settings['submit_image_max_size'] ?? 5))),
};
$max_size = $max_mb * MB_IN_BYTES;
$size = (int) ($file['size'] ?? 0);
@@ -119,6 +129,8 @@ class FrontSubmit {
return new \WP_Error('aksrt_upload_image_rejected', __('图片未通过内容审核。', 'aksrt'), ['status' => 422]);
}
}
} elseif ($kind === 'video' && strpos($mime, 'video/') !== 0) {
return new \WP_Error('aksrt_upload_invalid_video', __('请上传有效的视频文件。', 'aksrt'), ['status' => 400]);
}
if (!function_exists('media_handle_upload')) {
@@ -150,6 +162,7 @@ class FrontSubmit {
'attachment_id' => $attachment_id,
'url' => $url,
'name' => get_the_title($attachment_id),
'filename' => wp_basename((string) get_attached_file($attachment_id)),
'mime' => $mime,
], 201);
}
+42 -4
View File
@@ -73,6 +73,7 @@ if (file_exists($js)) {
}
$attachment_settings = class_exists('\\AKSRT\\Settings\\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
$submit_image_max_size = max(1, min(20, (int) ($attachment_settings['submit_image_max_size'] ?? 5))) * MB_IN_BYTES;
$submit_video_max_size = max(1, min(200, (int) ($attachment_settings['submit_video_max_size'] ?? 30))) * MB_IN_BYTES;
$resource_max_size = max(1, min(200, (int) ($attachment_settings['resource_max_size'] ?? 50))) * MB_IN_BYTES;
wp_localize_script('aksrt-front-submit', 'aksrtSubmitData', [
@@ -88,6 +89,9 @@ if (file_exists($js)) {
],
'editor' => [
'canUploadImage' => !empty($ctx['can_upload_img']),
'canUploadVideo' => !empty($ctx['can_upload_video']),
'canEmbedVideo' => !empty($ctx['can_iframe']),
'canUploadFile' => !empty($ctx['can_upload_file']),
'canHide' => !empty($ctx['can_hide']),
'canPay' => !empty($ctx['can_pay']),
],
@@ -96,6 +100,10 @@ if (file_exists($js)) {
'url' => esc_url_raw(rest_url('aksrt/v1/front-submit/upload/image')),
'maxSize' => $submit_image_max_size,
] : null,
'video' => !empty($ctx['can_upload_video']) ? [
'url' => esc_url_raw(rest_url('aksrt/v1/front-submit/upload/video')),
'maxSize' => $submit_video_max_size,
] : null,
'resource' => !empty($ctx['can_upload_file']) ? [
'url' => esc_url_raw(rest_url('aksrt/v1/front-submit/upload/resource')),
'maxSize' => $resource_max_size,
@@ -127,15 +135,45 @@ if (file_exists($js)) {
'insertImage' => __('插入图片', 'aksrt'),
'uploadImage' => __('上传图片', 'aksrt'),
'externalImage' => __('外链图片', 'aksrt'),
'insertVideo' => __('插入视频', 'aksrt'),
'uploadVideo' => __('上传视频', 'aksrt'),
'externalVideo' => __('外链视频', 'aksrt'),
'embedVideo' => __('嵌入视频', 'aksrt'),
'selectVideo' => __('选择或拖入视频', 'aksrt'),
'videoUploadHint' => __('上传完成后自动插入正文', 'aksrt'),
'externalVideoHint' => __('请填写视频文件地址:', 'aksrt'),
'embedVideoHint' => __('请输入嵌入地址或者直接粘贴 iframe 嵌入代码:', 'aksrt'),
'confirmEmbed' => __('确认嵌入', 'aksrt'),
'insertFile' => __('上传附件', 'aksrt'),
'uploadFile' => __('上传附件', 'aksrt'),
'externalFile' => __('输入地址', 'aksrt'),
'selectFile' => __('选择或拖入附件', 'aksrt'),
'fileUploadHint' => __('上传完成后自动插入正文', 'aksrt'),
'externalFileHint' => __('请填写附件地址:', 'aksrt'),
'selectImages' => __('选择或拖入图片', 'aksrt'),
'imageUploadHint' => __('可批量选择,上传完成后自动插入正文', 'aksrt'),
'selectedImages' => __('已选择 %d 张图片', 'aksrt'),
'selectedFiles' => __('已选择 %d 个文件', 'aksrt'),
'confirmUpload' => __('确认上传', 'aksrt'),
'externalImageHint' => __('每行填写一个图片地址:', 'aksrt'),
'confirmInsert' => __('确认插入', 'aksrt'),
'invalidImage' => __('请选择有效的图片文件', 'aksrt'),
'invalidImageUrl' => __('请填写有效的图片地址', 'aksrt'),
'uploadUnavailable' => __('当前账号不能上传图片', 'aksrt'),
'invalidMediaUrl' => __('请填写有效地址', 'aksrt'),
'invalidVideoUrl' => __('请填写有效的视频嵌入地址', 'aksrt'),
'invalidFile' => __('请选择有效文件', 'aksrt'),
'uploadUnavailable' => __('当前账号不能上传此类文件', 'aksrt'),
'download' => __('下载', 'aksrt'),
'quote' => __('引言', 'aksrt'),
'quoteGray' => __('灰色引言', 'aksrt'),
'quoteRed' => __('红色引言', 'aksrt'),
'quoteBlue' => __('蓝色引言', 'aksrt'),
'quoteGreen' => __('绿色引言', 'aksrt'),
'edit' => __('编辑', 'aksrt'),
'insert' => __('插入', 'aksrt'),
'highlightCode' => __('高亮代码', 'aksrt'),
'selectLanguage' => __('选择语言', 'aksrt'),
'codeGeneric' => __('通用高亮', 'aksrt'),
'codeTheme' => __('主题', 'aksrt'),
'codeFollowGlobal' => __('跟随全局设置', 'aksrt'),
'codeLabel' => __('代码:', 'aksrt'),
'hiddenContent' => __('隐藏内容', 'aksrt'),
'hiddenPlaceholder' => __('隐藏内容', 'aksrt'),
'replyVisible' => __('回复可见', 'aksrt'),
+6 -2
View File
@@ -47,11 +47,15 @@ $is_can_new = !empty($ctx['is_can_new']);
if (!empty($ctx['can_upload_img'])) {
$toolbar1[] = 'aksrt_image';
}
$toolbar1[] = 'aksrt_video';
if (!empty($ctx['can_upload_file'])) {
$toolbar1[] = 'aksrt_file';
}
if (!empty($ctx['can_hide'])) {
$toolbar1[] = 'aksrt_hide';
}
$toolbar1 = implode(',', array_merge($toolbar1, ['blockquote', 'code', 'fullscreen']));
$plugins = 'paste,link,lists,fullscreen,wordpress,wplink,charmap';
$toolbar1 = implode(',', array_merge($toolbar1, ['aksrt_quote', 'aksrt_precode', 'fullscreen']));
$plugins = 'paste,link,lists,fullscreen,wordpress,wplink,charmap,colorpicker,textcolor';
$editor_plugin_path = get_template_directory() . '/assets/js/front-submit-editor.js';
$editor_plugin_url = get_template_directory_uri() . '/assets/js/front-submit-editor.js';
if (file_exists($editor_plugin_path)) {
@@ -75,6 +75,46 @@ test('front submit image and visibility actions live in the TinyMCE toolbar', ()
assert.doesNotMatch(editor, /hidecontent|shop|forum|bbs_/i);
});
test('front submit exposes the complete reference media and formatting toolset', () => {
const main = read('templates/parts/submit/editor-main.php');
const page = read('templates/pages/page-submit.php');
const editor = read('assets/js/front-submit-editor.js');
const editorCss = read('assets/css/frontend/pages/submit-editor.css');
const submit = read('src/Core/FrontSubmit.php');
['aksrt_video', 'aksrt_file', 'aksrt_quote', 'aksrt_precode'].forEach((button) => {
assert.match(main, new RegExp(`['"]${button}['"]`));
assert.match(editor, new RegExp(`addButton\\(['"]${button}['"]`));
});
assert.doesNotMatch(main, /array_merge\(\$toolbar1, \['blockquote'/);
assert.match(main, /toolbar2[\s\S]*forecolor/);
assert.match(main, /colorpicker,textcolor/);
assert.match(page, /'canUploadVideo'\s*=>\s*!empty\(\$ctx\['can_upload_video'\]\)/);
assert.match(page, /'canEmbedVideo'\s*=>\s*!empty\(\$ctx\['can_iframe'\]\)/);
assert.match(page, /'canUploadFile'\s*=>\s*!empty\(\$ctx\['can_upload_file'\]\)/);
assert.match(page, /'video'\s*=>\s*!empty\(\$ctx\['can_upload_video'\]\)/);
assert.match(submit, /\(\?P<kind>image\|video\|resource\)/);
assert.match(submit, /new_post_upload_video/);
assert.match(editor, /data-editor-media-tab="upload"/);
assert.match(editor, /data-editor-media-tab="external"/);
assert.match(editor, /data-editor-media-tab="embed"/);
assert.match(editor, /new-dplayer post-dplayer dplayer/);
assert.match(editor, /file-download-box/);
assert.match(editor, /wp-block-embed is-type-video/);
assert.match(editor, /data-enlighter-language/);
assert.match(editor, /data-enlighter-theme/);
assert.match(editor, /enlighter-pre/);
assert.match(editor, /qe_wzk_c-red/);
assert.match(editor, /qe_wzk_lan/);
assert.match(editor, /qe_wzk_lv/);
assert.match(editorCss, /\.quote_q/);
assert.match(editorCss, /\.file-download-box/);
assert.match(editorCss, /\.enlighter-pre/);
assert.doesNotMatch(editor, /shop|forum|bbs_/i);
});
test('article paid-content metabox follows the reference field set without shop enhancements', () => {
const meta = read('src/Core/ResourceMetaBox.php');
const fields = [
+2 -2
View File
@@ -381,7 +381,7 @@ test('front submit actions use direct fixed-id notyf flows', () => {
'aksrt-front-submit-slide-upload-toast',
'aksrt-front-submit-video-cover-toast',
'aksrt-front-submit-resource-upload-toast',
'aksrt-front-submit-editor-image-toast',
'aksrt-front-submit-editor-media-toast',
'aksrt-front-submit-ai-tags-toast',
'aksrt-front-submit-ai-summary-toast',
].forEach((id) => assert.match(source, new RegExp("['\"]" + id + "['\"]")));
@@ -400,7 +400,7 @@ test('front submit actions use direct fixed-id notyf flows', () => {
});
assert.match(submit, /aksrt-front-submit-resource-upload-toast[\s\S]*?'load'[\s\S]*?'success'[\s\S]*?'danger'/);
assert.match(editor, /aksrt-front-submit-editor-image-toast[\s\S]*?'load'[\s\S]*?'success'[\s\S]*?'danger'/);
assert.match(editor, /aksrt-front-submit-editor-media-toast[\s\S]*?'load'[\s\S]*?'success'[\s\S]*?'danger'/);
assert.match(submit, /aksrt-front-submit-ai-tags-toast[\s\S]*?aksrt-front-submit-ai-summary-toast[\s\S]*?'load'[\s\S]*?'success'[\s\S]*?'danger'/);
assert.match(submit, /needContent[^;]+?'warning'/);
assert.doesNotMatch(submit, /\b(?:window\.)?alert\s*\(/);