进行了一些小修复
This commit is contained in:
@@ -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%;
|
||||
}
|
||||
|
||||
@@ -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 || '关闭') + '">×</button>' +
|
||||
escapeAttr(i18n.close || '关闭') + '">×</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="<iframe src="https://..."></iframe>" 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 = [
|
||||
{
|
||||
|
||||
@@ -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' => [
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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*\(/);
|
||||
|
||||
Reference in New Issue
Block a user