feat(submit): add user media library
This commit is contained in:
@@ -44,6 +44,7 @@
|
|||||||
!/tests/widget-system/financial-flow-contract.test.cjs
|
!/tests/widget-system/financial-flow-contract.test.cjs
|
||||||
!/tests/widget-system/pay-orders-filter-contract.test.cjs
|
!/tests/widget-system/pay-orders-filter-contract.test.cjs
|
||||||
!/tests/widget-system/front-submit-editor-contract.test.cjs
|
!/tests/widget-system/front-submit-editor-contract.test.cjs
|
||||||
|
!/tests/widget-system/front-submit-attachment-contract.test.cjs
|
||||||
!/tests/widget-system/comment-smilies-removal-contract.test.cjs
|
!/tests/widget-system/comment-smilies-removal-contract.test.cjs
|
||||||
!/tests/widget-system/comment-children-list-reset-contract.test.cjs
|
!/tests/widget-system/comment-children-list-reset-contract.test.cjs
|
||||||
!/tests/widget-system/comment-image-upload-contract.test.cjs
|
!/tests/widget-system/comment-image-upload-contract.test.cjs
|
||||||
|
|||||||
@@ -289,6 +289,200 @@ body.aksrt-editor-media-open {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__search {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__search .form-control {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__search .but {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__status {
|
||||||
|
min-height: 20px;
|
||||||
|
margin: 4px 0 10px;
|
||||||
|
color: var(--muted-color, #777);
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__status.is-loading {
|
||||||
|
color: var(--muted-color, #777);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__status.is-error {
|
||||||
|
color: var(--danger-color, #e05252);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__status.is-empty {
|
||||||
|
padding: 28px 10px;
|
||||||
|
color: var(--muted-2-color, #999);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__list {
|
||||||
|
display: grid;
|
||||||
|
max-height: min(390px, 48vh);
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__item {
|
||||||
|
position: relative;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--main-border-color, rgba(50, 50, 50, 0.1));
|
||||||
|
border-radius: var(--main-radius, 8px);
|
||||||
|
background: var(--muted-bg-color, rgba(50, 50, 50, 0.03));
|
||||||
|
color: var(--main-color, #333);
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s, opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__item:hover,
|
||||||
|
.aksrt-editor-media__item:focus-visible {
|
||||||
|
border-color: var(--focus-color, #f04494);
|
||||||
|
outline: 0;
|
||||||
|
box-shadow: 0 0 0 2px var(--focus-shadow-color, rgba(240, 68, 148, 0.15));
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__item.is-selected {
|
||||||
|
border-color: var(--focus-color, #f04494);
|
||||||
|
box-shadow: inset 0 0 0 2px var(--focus-color, #f04494);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__item:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__check {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: 6px;
|
||||||
|
left: 6px;
|
||||||
|
display: flex;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(0, 0, 0, 0.58);
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__item.is-selected .aksrt-editor-media__check {
|
||||||
|
background: var(--focus-color, #f04494);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__thumb {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 104px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--muted-border-color, rgba(50, 50, 50, 0.06));
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__thumb img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__file-icon {
|
||||||
|
color: var(--focus-color, #f04494);
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__meta {
|
||||||
|
display: block;
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 7px 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__mine-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__mine-actions .but {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__load-more {
|
||||||
|
display: block;
|
||||||
|
min-width: 110px;
|
||||||
|
margin: 12px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__load-more[hidden],
|
||||||
|
.aksrt-editor-media__confirm[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__confirm {
|
||||||
|
margin: 12px 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__selection {
|
||||||
|
min-height: 20px;
|
||||||
|
margin-top: 10px;
|
||||||
|
color: var(--muted-color, #777);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__progress {
|
||||||
|
position: relative;
|
||||||
|
min-height: 34px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 9px 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--muted-border-color, rgba(50, 50, 50, 0.08));
|
||||||
|
color: var(--muted-color, #777);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__progress::before {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 3px;
|
||||||
|
width: var(--aksrt-upload-progress, 0%);
|
||||||
|
background: var(--focus-color, #f04494);
|
||||||
|
content: '';
|
||||||
|
transition: width 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__submit:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
.featured-edit {
|
.featured-edit {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -548,6 +742,38 @@ body.aksrt-editor-media-open {
|
|||||||
border-radius: 12px 12px 0 0;
|
border-radius: 12px 12px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__list {
|
||||||
|
max-height: 42vh;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__thumb {
|
||||||
|
height: 92px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__tabs {
|
||||||
|
gap: 14px;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__panel {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__mine-actions {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__mine-actions .aksrt-editor-media__load-more {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aksrt-editor-media__search .but {
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
.aksrt-editor-media__drop {
|
.aksrt-editor-media__drop {
|
||||||
min-height: 150px;
|
min-height: 150px;
|
||||||
}
|
}
|
||||||
|
|||||||
+273
-286
@@ -6,10 +6,6 @@
|
|||||||
(function (window, document) {
|
(function (window, document) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
if (!window.tinymce || !window.tinymce.PluginManager) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cfg = window.aksrtSubmitData || {};
|
var cfg = window.aksrtSubmitData || {};
|
||||||
var editorCfg = cfg.editor || {};
|
var editorCfg = cfg.editor || {};
|
||||||
var i18n = cfg.i18n || {};
|
var i18n = cfg.i18n || {};
|
||||||
@@ -47,14 +43,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function imageMarkup(url, attachmentId, alt) {
|
function imageMarkup(url, attachmentId, alt, fullUrl) {
|
||||||
var safeUrl = validHttpUrl(url);
|
var safeUrl = validHttpUrl(url);
|
||||||
|
var safeFullUrl = validHttpUrl(fullUrl || url);
|
||||||
if (!safeUrl) {
|
if (!safeUrl) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
var idAttr = attachmentId ? ' data-edit-file-id="' + Number(attachmentId) + '"' : '';
|
var idAttr = attachmentId ? ' data-edit-file-id="' + Number(attachmentId) + '"' : '';
|
||||||
var altAttr = alt ? ' alt="' + escapeAttr(alt) + '"' : ' alt=""';
|
var altAttr = alt ? ' alt="' + escapeAttr(alt) + '"' : ' alt=""';
|
||||||
return '<p><img src="' + escapeAttr(safeUrl) + '"' + idAttr + altAttr + '></p>';
|
var fullAttr = safeFullUrl ? ' data-full-url="' + escapeAttr(safeFullUrl) + '"' : '';
|
||||||
|
return '<p><img src="' + escapeAttr(safeUrl) + '"' + idAttr + altAttr + fullAttr + '></p>';
|
||||||
}
|
}
|
||||||
|
|
||||||
function videoMarkup(url, attachmentId, name) {
|
function videoMarkup(url, attachmentId, name) {
|
||||||
@@ -74,7 +72,7 @@
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
var filename = String(name || safeUrl.split('/').pop() || i18n.attachment || '附件');
|
var filename = String(name || safeUrl.split('/').pop() || i18n.attachment || '附件');
|
||||||
var idAttr = attachmentId ? ' data-download-file="' + Number(attachmentId) + '"' : '';
|
var idAttr = attachmentId ? ' data-aksrt-file-id="' + Number(attachmentId) + '"' : '';
|
||||||
return '<div class="mb20 muted-box file-download-box" contenteditable="false">' +
|
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="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="file-download-icon"><i class="fa fas fa-file-alt" aria-hidden="true"></i></div></div>' +
|
||||||
@@ -99,55 +97,47 @@
|
|||||||
'</div></div>';
|
'</div></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeMediaModal() {
|
function mediaUrl(type) {
|
||||||
if (!activeModal) {
|
var configured = cfg.media && cfg.media[type] && cfg.media[type].url;
|
||||||
return;
|
if (configured) {
|
||||||
|
return String(configured).indexOf('{type}') !== -1
|
||||||
|
? String(configured).replace('{type}', encodeURIComponent(type))
|
||||||
|
: String(configured);
|
||||||
}
|
}
|
||||||
document.removeEventListener('keydown', activeModal.onKeydown);
|
var base = cfg.mediaUrl || '';
|
||||||
activeModal.root.remove();
|
if (String(base).indexOf('{type}') !== -1) return String(base).replace('{type}', encodeURIComponent(type));
|
||||||
activeModal = null;
|
if (base) return String(base).replace(/\/$/, '') + '/' + encodeURIComponent(type);
|
||||||
document.body.classList.remove('aksrt-editor-media-open');
|
return cfg.restUrl ? cfg.restUrl.replace(/\/$/, '') + '/front-submit/media/' + encodeURIComponent(type) : ''; }
|
||||||
|
|
||||||
|
function currentPostId() {
|
||||||
|
var field = document.querySelector('#aksrt-submit-form input[name="posts_id"]');
|
||||||
|
return Number((field && field.value) || cfg.postId || 0) || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function acceptsFile(file, type) {
|
function acceptsFile(file, type) {
|
||||||
if (!file) {
|
if (!file) return false;
|
||||||
return false;
|
if (type === 'image') return /^image\//.test(file.type || '');
|
||||||
}
|
if (type === 'video') return /^video\//.test(file.type || '');
|
||||||
if (type === 'image') {
|
|
||||||
return /^image\//.test(file.type || '');
|
|
||||||
}
|
|
||||||
if (type === 'video') {
|
|
||||||
return /^video\//.test(file.type || '');
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadMedia(file, type) {
|
function uploadMedia(file, type) {
|
||||||
var uploadKey = type === 'file' ? 'resource' : type;
|
var uploadKey = type === 'file' ? 'resource' : type;
|
||||||
var upload = (cfg.uploads || {})[uploadKey] || null;
|
var upload = (cfg.uploads || {})[uploadKey] || null;
|
||||||
if (!upload || !upload.url) {
|
if (!upload || !upload.url) return Promise.reject(new Error(i18n.uploadUnavailable || '当前账号不能上传此类文件'));
|
||||||
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 || '文件大小超过限制'));
|
||||||
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 || '文件大小超过限制'));
|
|
||||||
}
|
|
||||||
|
|
||||||
var body = new FormData();
|
var body = new FormData();
|
||||||
body.append('file', file);
|
body.append('file', file);
|
||||||
return fetch(upload.url, {
|
var uploadUrl = String(upload.url);
|
||||||
method: 'POST',
|
if (currentPostId()) {
|
||||||
credentials: 'same-origin',
|
uploadUrl += (uploadUrl.indexOf('?') === -1 ? '?' : '&') + 'post_id=' + encodeURIComponent(String(currentPostId()));
|
||||||
headers: { 'X-WP-Nonce': cfg.restNonce || '' },
|
}
|
||||||
body: body,
|
return fetch(uploadUrl, { method: 'POST', credentials: 'same-origin', headers: { 'X-WP-Nonce': cfg.restNonce || '' }, body: body })
|
||||||
}).then(function (response) {
|
.then(function (response) {
|
||||||
return response.json().catch(function () {
|
return response.json().catch(function () { return {}; }).then(function (payload) {
|
||||||
return {};
|
|
||||||
}).then(function (payload) {
|
|
||||||
if (!response.ok || !payload || !payload.success || !payload.url) {
|
if (!response.ok || !payload || !payload.success || !payload.url) {
|
||||||
throw new Error((payload && payload.message) || i18n.error || '文件上传失败');
|
throw new Error((payload && (payload.message || (payload.data && payload.data.message))) || i18n.error || '文件上传失败');
|
||||||
}
|
}
|
||||||
return payload;
|
return payload;
|
||||||
});
|
});
|
||||||
@@ -155,277 +145,229 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mediaDefinition(type) {
|
function mediaDefinition(type) {
|
||||||
if (type === 'video') {
|
if (type === 'video') return {
|
||||||
return {
|
title: i18n.insertVideo || '插入视频', mineTab: i18n.mineVideo || '我的视频', uploadTab: i18n.uploadVideo || '上传视频',
|
||||||
title: i18n.insertVideo || '插入视频',
|
externalTab: i18n.externalVideo || '外链视频', embedTab: i18n.embedVideo || '嵌入视频', accept: 'video/*', multiple: false, maxItems: 1,
|
||||||
uploadTab: i18n.uploadVideo || '上传视频',
|
canUpload: !!editorCfg.canUploadVideo && !!((cfg.uploads || {}).video), canEmbed: !!editorCfg.canEmbedVideo,
|
||||||
externalTab: i18n.externalVideo || '外链视频',
|
select: i18n.selectVideo || '选择或拖入视频', hint: i18n.videoUploadHint || '上传完成后自动插入正文', externalHint: i18n.externalVideoHint || '请填写视频文件地址:',
|
||||||
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 {
|
||||||
if (type === 'file') {
|
title: i18n.insertFile || '上传附件', mineTab: i18n.mineFile || '我的文件', uploadTab: i18n.uploadFile || '上传附件', externalTab: i18n.externalFile || '输入地址',
|
||||||
return {
|
accept: '*/*', multiple: false, maxItems: 1, canUpload: !!editorCfg.canUploadFile && !!((cfg.uploads || {}).resource), canEmbed: false,
|
||||||
title: i18n.insertFile || '上传附件',
|
select: i18n.selectFile || '选择或拖入附件', hint: i18n.fileUploadHint || '上传完成后自动插入正文', externalHint: i18n.externalFileHint || '请填写附件地址:',
|
||||||
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 {
|
return {
|
||||||
title: i18n.insertImage || '插入图片',
|
title: i18n.insertImage || '插入图片', mineTab: i18n.mineImage || '我的图片', uploadTab: i18n.uploadImage || '上传图片', externalTab: i18n.externalImage || '外链图片',
|
||||||
uploadTab: i18n.uploadImage || '上传图片',
|
accept: 'image/gif,image/jpeg,image/png,image/webp', multiple: true, maxItems: 100, canUpload: !!editorCfg.canUploadImage && !!((cfg.uploads || {}).image), canEmbed: false,
|
||||||
externalTab: i18n.externalImage || '外链图片',
|
select: i18n.selectImages || '选择或拖入图片', hint: i18n.imageUploadHint || '可批量选择,上传完成后自动插入正文', externalHint: i18n.externalImageHint || '每行填写一个图片地址:',
|
||||||
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 || '每行填写一个图片地址:',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mediaMarkup(type, item) {
|
function mediaMarkup(type, item) {
|
||||||
if (type === 'video') {
|
var id = item.attachment_id || item.id;
|
||||||
return videoMarkup(item.url, item.attachment_id, item.filename || item.name || '');
|
if (type === 'video') return videoMarkup(item.url, id, item.filename || item.name || '');
|
||||||
}
|
if (type === 'file') return fileMarkup(item.url, id, item.filename || item.name || '');
|
||||||
if (type === 'file') {
|
return imageMarkup(item.large_url || item.url, id, item.name || item.title || '', item.url);
|
||||||
return fileMarkup(item.url, item.attachment_id, item.filename || item.name || '');
|
|
||||||
}
|
|
||||||
return imageMarkup(item.url, item.attachment_id, item.name || '');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMediaModal(editor, type) {
|
function formatBytes(value) {
|
||||||
|
var size = Number(value) || 0, units = ['B', 'KB', 'MB', 'GB'], index = 0;
|
||||||
|
if (!size) return '';
|
||||||
|
while (size >= 1024 && index < units.length - 1) { size /= 1024; index += 1; }
|
||||||
|
return (index ? size.toFixed(size >= 10 ? 0 : 1) : String(Math.round(size))) + ' ' + units[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
function extension(item) {
|
||||||
|
var name = String(item.filename || item.name || item.url || ''), match = name.match(/\.([a-z0-9]{1,8})(?:[?#].*)?$/i);
|
||||||
|
return match ? match[1].toUpperCase() : String(item.subtype || item.type || '').toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMediaModal(force) {
|
||||||
|
if (!activeModal) return;
|
||||||
|
if (activeModal.uploading && !force) { notify(i18n.uploadingClose || '上传中,请稍候再关闭', 'warning'); return; }
|
||||||
|
document.removeEventListener('keydown', activeModal.onKeydown);
|
||||||
|
if (activeModal.searchTimer) window.clearTimeout(activeModal.searchTimer);
|
||||||
|
activeModal.root.remove();
|
||||||
|
if (activeModal.previousFocus && typeof activeModal.previousFocus.focus === 'function') activeModal.previousFocus.focus();
|
||||||
|
activeModal = null;
|
||||||
|
document.body.classList.remove('aksrt-editor-media-open');
|
||||||
|
}
|
||||||
|
|
||||||
|
function openMediaModal(options) {
|
||||||
|
options = options || {};
|
||||||
|
if (activeModal) {
|
||||||
closeMediaModal();
|
closeMediaModal();
|
||||||
|
if (activeModal) return;
|
||||||
var definition = mediaDefinition(type);
|
}
|
||||||
var canUpload = definition.canUpload;
|
var type = ['image', 'video', 'file'].indexOf(options.type) === -1 ? 'image' : options.type;
|
||||||
var firstTab = canUpload ? 'upload' : 'external';
|
var definition = mediaDefinition(type), multiple = options.multiple === undefined ? definition.multiple : !!options.multiple;
|
||||||
|
var maxItems = Math.max(1, Number(options.maxItems || (multiple ? definition.maxItems : 1)) || 1);
|
||||||
|
if (!multiple) maxItems = 1;
|
||||||
|
var allowExternal = options.allowExternal !== false;
|
||||||
|
var allowEmbed = definition.canEmbed && options.allowEmbed !== false;
|
||||||
|
var canUpload = definition.canUpload && options.canUpload !== false;
|
||||||
var root = document.createElement('div');
|
var root = document.createElement('div');
|
||||||
root.className = 'aksrt-editor-media';
|
root.className = 'aksrt-editor-media';
|
||||||
root.setAttribute('data-editor-media-type', type);
|
root.setAttribute('data-editor-media-type', type);
|
||||||
root.setAttribute('role', 'dialog');
|
root.setAttribute('role', 'dialog'); root.setAttribute('aria-modal', 'true'); root.setAttribute('aria-labelledby', 'aksrt-editor-media-title');
|
||||||
root.setAttribute('aria-modal', 'true');
|
|
||||||
root.setAttribute('aria-labelledby', 'aksrt-editor-media-title');
|
|
||||||
root.innerHTML =
|
root.innerHTML =
|
||||||
'<button type="button" class="aksrt-editor-media__backdrop" data-editor-media-close tabindex="-1" aria-label="' +
|
'<button type="button" class="aksrt-editor-media__backdrop" data-editor-media-close tabindex="-1" aria-label="' + escapeAttr(i18n.close || '关闭') + '"></button>' +
|
||||||
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>' +
|
||||||
'<div class="aksrt-editor-media__dialog" tabindex="-1">' +
|
'<h3 id="aksrt-editor-media-title" class="aksrt-editor-media__title">' + escapeAttr(options.title || definition.title) + '</h3>' +
|
||||||
'<button type="button" class="aksrt-editor-media__close" data-editor-media-close aria-label="' +
|
'<div class="aksrt-editor-media__tabs" role="tablist"><button type="button" class="is-active" role="tab" aria-selected="true" data-editor-media-tab="mine">' + escapeAttr(definition.mineTab) + '</button>' +
|
||||||
escapeAttr(i18n.close || '关闭') + '">×</button>' +
|
(canUpload ? '<button type="button" role="tab" aria-selected="false" data-editor-media-tab="upload">' + escapeAttr(definition.uploadTab) + '</button>' : '') +
|
||||||
'<h3 id="aksrt-editor-media-title" class="aksrt-editor-media__title">' +
|
(allowExternal ? '<button type="button" role="tab" aria-selected="false" data-editor-media-tab="external">' + escapeAttr(definition.externalTab) + '</button>' : '') +
|
||||||
escapeAttr(definition.title) + '</h3>' +
|
(allowEmbed && type === 'video' ? '<button type="button" role="tab" aria-selected="false" data-editor-media-tab="embed">' + escapeAttr(definition.embedTab) + '</button>' : '') + '</div>' +
|
||||||
'<div class="aksrt-editor-media__tabs" role="tablist">' +
|
'<div class="aksrt-editor-media__panels"><section class="aksrt-editor-media__panel is-active" data-editor-media-panel="mine">' +
|
||||||
(canUpload
|
'<div class="aksrt-editor-media__search"><label class="screen-reader-text" for="aksrt-editor-media-search">' + escapeAttr(i18n.search || '搜索') + '</label><input id="aksrt-editor-media-search" type="search" class="form-control" placeholder="' + escapeAttr(i18n.searchMedia || '搜索媒体(至少 2 个字符)') + '" data-editor-media-search><button type="button" class="but hollow" data-editor-media-clear>' + escapeAttr(i18n.clear || '清除') + '</button></div>' +
|
||||||
? '<button type="button" class="is-active" role="tab" aria-selected="true" data-editor-media-tab="upload">' +
|
'<div class="aksrt-editor-media__status" data-editor-media-status aria-live="polite"></div><div class="aksrt-editor-media__list" data-editor-media-list></div><div class="aksrt-editor-media__mine-actions"><button type="button" class="but hollow aksrt-editor-media__load-more" data-editor-media-more hidden>' + escapeAttr(i18n.loadMore || '加载更多') + '</button><button type="button" class="but c-blue aksrt-editor-media__confirm" data-editor-media-confirm hidden>' + escapeAttr(i18n.confirmInsert || '确认插入') + '</button></div></section>' +
|
||||||
escapeAttr(definition.uploadTab) + '</button>'
|
(canUpload ? '<section class="aksrt-editor-media__panel" data-editor-media-panel="upload"><label class="aksrt-editor-media__drop"><input type="file" accept="' + escapeAttr(definition.accept) + '"' + (multiple ? ' multiple' : '') + ' data-editor-media-files><i class="fa fas fa-cloud-upload-alt" aria-hidden="true"></i><strong>' + escapeAttr(definition.select) + '</strong><span>' + escapeAttr(definition.hint) + '</span></label><div class="aksrt-editor-media__selection" data-editor-media-selection></div><div class="aksrt-editor-media__progress" data-editor-media-progress hidden></div><button type="button" class="but c-blue aksrt-editor-media__submit" data-editor-media-upload disabled>' + escapeAttr(i18n.confirmUpload || '确认上传') + '</button></section>' : '') +
|
||||||
: '') +
|
(allowExternal ? '<section class="aksrt-editor-media__panel" 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>' : '') +
|
||||||
'<button type="button" class="' + (firstTab === 'external' ? 'is-active' : '') + '" role="tab" aria-selected="' +
|
(allowEmbed && type === 'video' ? '<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>';
|
||||||
(canUpload ? 'false' : 'true') + '" data-editor-media-tab="external">' +
|
document.body.appendChild(root); document.body.classList.add('aksrt-editor-media-open');
|
||||||
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="' + escapeAttr(definition.accept) + '"' +
|
|
||||||
(definition.multiple ? ' multiple' : '') + ' data-editor-media-files>' +
|
|
||||||
'<i class="fa fas fa-cloud-upload-alt" aria-hidden="true"></i>' +
|
|
||||||
'<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 ' + (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>';
|
|
||||||
|
|
||||||
document.body.appendChild(root);
|
var state = { root: root, uploading: false, loading: false, requestId: 0, searchTimer: null, previousFocus: document.activeElement, page: 0, totalPages: 0, search: '', items: [], pinned: [], selected: [], exclude: [] };
|
||||||
document.body.classList.add('aksrt-editor-media-open');
|
var list = root.querySelector('[data-editor-media-list]'), status = root.querySelector('[data-editor-media-status]'), more = root.querySelector('[data-editor-media-more]');
|
||||||
|
var confirm = root.querySelector('[data-editor-media-confirm]');
|
||||||
var fileInput = root.querySelector('[data-editor-media-files]');
|
var search = root.querySelector('[data-editor-media-search]'), fileInput = root.querySelector('[data-editor-media-files]'), uploadButton = root.querySelector('[data-editor-media-upload]');
|
||||||
var uploadButton = root.querySelector('[data-editor-media-upload]');
|
var selection = root.querySelector('[data-editor-media-selection]'), progress = root.querySelector('[data-editor-media-progress]'), drop = root.querySelector('.aksrt-editor-media__drop'), files = [];
|
||||||
var selection = root.querySelector('[data-editor-media-selection]');
|
function statusText(text, cls) { if (!status) return; status.className = 'aksrt-editor-media__status' + (cls ? ' is-' + cls : ''); status.textContent = text || ''; }
|
||||||
var drop = root.querySelector('.aksrt-editor-media__drop');
|
function itemId(item) { return Number(item.attachment_id || item.id || 0); }
|
||||||
var files = [];
|
function selectedIndex(id) { return state.selected.findIndex(function (item) { return itemId(item) === id; }); }
|
||||||
|
function renderSelection() { if (selection) selection.textContent = files.length ? (multiple ? (i18n.selectedFiles || '已选择 %d 个文件').replace('%d', String(files.length)) : files[0].name) : ''; if (uploadButton) uploadButton.disabled = !files.length || state.uploading; }
|
||||||
function showSelection() {
|
function renderItem(item) {
|
||||||
if (!selection || !uploadButton) {
|
var id = itemId(item), index = selectedIndex(id), chosen = index !== -1, disabled = !chosen && state.selected.length >= maxItems;
|
||||||
return;
|
var label = escapeAttr(item.filename || item.name || item.title || item.url || '');
|
||||||
|
var itemType = String(item.type || type);
|
||||||
|
var hasImagePreview = itemType === 'image' && (item.thumbnail_url || item.medium_url || item.url);
|
||||||
|
var thumb = hasImagePreview
|
||||||
|
? '<img src="' + escapeAttr(item.thumbnail_url || item.medium_url || item.url || '') + '" alt="' + label + '">'
|
||||||
|
: '<span class="aksrt-editor-media__file-icon"><i class="fa fas fa-' + (itemType === 'video' ? 'video' : 'file-alt') + '" aria-hidden="true"></i></span>';
|
||||||
|
var meta = type === 'image' ? label : label + (extension(item) ? ' · ' + escapeAttr(extension(item)) : '') + (formatBytes(item.filesizeInBytes) ? ' · ' + escapeAttr(formatBytes(item.filesizeInBytes)) : '');
|
||||||
|
return '<button type="button" class="aksrt-editor-media__item' + (chosen ? ' is-selected' : '') + '" data-editor-media-item="' + id + '"' + (disabled ? ' disabled' : '') + ' aria-pressed="' + (chosen ? 'true' : 'false') + '"><span class="aksrt-editor-media__check">' + (chosen ? String(index + 1) : '<i class="fa fas fa-check" aria-hidden="true"></i>') + '</span><span class="aksrt-editor-media__thumb">' + thumb + '</span><span class="aksrt-editor-media__meta">' + meta + '</span></button>';
|
||||||
}
|
}
|
||||||
selection.textContent = files.length
|
function renderList() {
|
||||||
? (definition.multiple
|
if (!list) return;
|
||||||
? (i18n.selectedFiles || '已选择 %d 个文件').replace('%d', String(files.length))
|
list.innerHTML = state.items.length ? state.items.map(renderItem).join('') : '';
|
||||||
: files[0].name)
|
if (more) more.hidden = state.loading || state.page >= state.totalPages || !state.items.length;
|
||||||
: '';
|
if (confirm) {
|
||||||
uploadButton.disabled = files.length === 0;
|
confirm.hidden = !multiple || !state.selected.length;
|
||||||
|
confirm.disabled = state.uploading;
|
||||||
|
confirm.textContent = (i18n.confirmSelected || '确认选择(%d)').replace('%d', String(state.selected.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function finish(value) { if (state.uploading) return; if (typeof options.onSelect === 'function') options.onSelect(multiple ? value.slice() : value[0]); closeMediaModal(true); }
|
||||||
|
function selectItem(item) { var id = itemId(item), index = selectedIndex(id); if (index !== -1) state.selected.splice(index, 1); else { if (!multiple) state.selected = []; if (state.selected.length >= maxItems) return; state.selected.push(item); } renderList(); if (!multiple && state.selected.length) finish(state.selected); }
|
||||||
|
function addItems(items) { (items || []).forEach(function (item) { var id = itemId(item); if (!id) return; if (state.exclude.indexOf(id) === -1) state.exclude.push(id); if (!state.items.some(function (old) { return itemId(old) === id; })) state.items.unshift(item); if (!state.pinned.some(function (old) { return itemId(old) === id; })) state.pinned.unshift(item); }); renderList(); }
|
||||||
|
function activateTab(name) { var tab = root.querySelector('[data-editor-media-tab="' + name + '"]'); if (tab) tab.click(); }
|
||||||
|
function request(page, replace) {
|
||||||
|
var url = mediaUrl(type); if (!url) { statusText(i18n.mediaUnavailable || '媒体库暂不可用', 'error'); return; }
|
||||||
|
if (replace) { state.items = state.search.length >= 2 ? [] : state.pinned.slice(); state.page = 0; }
|
||||||
|
state.loading = true; renderList(); statusText(i18n.loading || '加载中…', 'loading');
|
||||||
|
var requestId = ++state.requestId;
|
||||||
|
var params = new URLSearchParams(); params.set('paged', String(page)); params.set('per_page', '48'); if (currentPostId()) params.set('post_id', String(currentPostId())); if (state.search.length >= 2) params.set('search', state.search); if (state.exclude.length) params.set('exclude', state.exclude.join(','));
|
||||||
|
fetch(url + (url.indexOf('?') === -1 ? '?' : '&') + params.toString(), { credentials: 'same-origin', headers: { 'X-WP-Nonce': cfg.restNonce || '' } }).then(function (response) { return response.json().catch(function () { return {}; }).then(function (payload) { if (!response.ok) throw new Error((payload && (payload.message || (payload.data && payload.data.message))) || i18n.error || '加载失败'); return payload || {}; }); }).then(function (payload) {
|
||||||
|
if (requestId !== state.requestId) return;
|
||||||
|
var incoming = Array.isArray(payload.items) ? payload.items : []; if (replace) state.items = state.search.length >= 2 ? [] : state.pinned.slice(); incoming.forEach(function (item) { if (state.exclude.indexOf(itemId(item)) === -1) state.items.push(item); }); state.page = Number(payload.page || page); state.totalPages = Number(payload.total_pages || 0); state.loading = false; renderList(); statusText(state.items.length ? '' : (i18n.emptyMedia || '暂无媒体文件'), state.items.length ? '' : 'empty');
|
||||||
|
}).catch(function (error) { if (requestId !== state.requestId) return; state.loading = false; renderList(); statusText(error.message || i18n.mediaError || '媒体加载失败', 'error'); });
|
||||||
}
|
}
|
||||||
|
|
||||||
function setFiles(nextFiles) {
|
function setFiles(nextFiles) {
|
||||||
var candidates = Array.prototype.slice.call(nextFiles || []);
|
var candidates = Array.prototype.slice.call(nextFiles || []);
|
||||||
|
var uploadKey = type === 'file' ? 'resource' : type;
|
||||||
|
var upload = (cfg.uploads || {})[uploadKey] || {};
|
||||||
|
var rejected = false;
|
||||||
files = candidates.filter(function (file) {
|
files = candidates.filter(function (file) {
|
||||||
return acceptsFile(file, type);
|
var valid = acceptsFile(file, type) && (!upload.maxSize || file.size <= upload.maxSize);
|
||||||
|
rejected = rejected || !valid;
|
||||||
|
return valid;
|
||||||
});
|
});
|
||||||
if (candidates.length && !files.length) {
|
if (rejected) notify(i18n.invalidFile || i18n.fileTooLarge || '部分文件类型或大小不符合要求', 'warning');
|
||||||
notify(i18n.invalidFile || '请选择有效文件', 'warning');
|
if (!multiple) files = files.slice(0, 1);
|
||||||
|
if (files.length > maxItems) {
|
||||||
|
files = files.slice(0, maxItems);
|
||||||
|
notify((i18n.maxMediaItems || '最多选择 %d 个文件').replace('%d', String(maxItems)), 'warning');
|
||||||
}
|
}
|
||||||
if (!definition.multiple && files.length > 1) {
|
renderSelection();
|
||||||
files = files.slice(0, 1);
|
|
||||||
}
|
}
|
||||||
showSelection();
|
function uploadSelected() {
|
||||||
|
if (!files.length || state.uploading) return;
|
||||||
|
state.uploading = true; renderSelection(); renderList();
|
||||||
|
if (uploadButton) uploadButton.textContent = i18n.uploading || '正在上传…';
|
||||||
|
if (progress) { progress.hidden = false; progress.textContent = i18n.uploading || '正在上传…'; progress.style.setProperty('--aksrt-upload-progress', '0%'); }
|
||||||
|
var complete = 0, total = files.length, nextIndex = 0, results = [];
|
||||||
|
var toastId = 'aksrt-front-submit-editor-media-toast';
|
||||||
|
notify(i18n.uploading || '正在上传…', 'load', '', toastId);
|
||||||
|
function worker() {
|
||||||
|
var index = nextIndex++;
|
||||||
|
if (index >= total) return Promise.resolve();
|
||||||
|
return uploadMedia(files[index], type).then(function (item) {
|
||||||
|
results[index] = { item: item };
|
||||||
|
}).catch(function (error) {
|
||||||
|
results[index] = { error: error };
|
||||||
|
}).finally(function () {
|
||||||
|
complete += 1;
|
||||||
|
if (progress) progress.style.setProperty('--aksrt-upload-progress', Math.round((complete / total) * 100) + '%');
|
||||||
|
}).then(worker);
|
||||||
}
|
}
|
||||||
|
var workers = Math.min(4, total);
|
||||||
|
Promise.all(Array.apply(null, Array(workers)).map(function () { return worker(); })).then(function () {
|
||||||
|
var items = results.map(function (result) { return result && result.item; }).filter(Boolean);
|
||||||
|
var failure = results.find(function (result) { return result && result.error; });
|
||||||
|
addItems(items);
|
||||||
|
state.selected = items.slice(0, maxItems);
|
||||||
|
state.uploading = false;
|
||||||
|
renderList();
|
||||||
|
files = [];
|
||||||
|
renderSelection();
|
||||||
|
if (items.length) notify(i18n.uploaded || '上传成功', 'success', 5, toastId);
|
||||||
|
if (failure) notify(failure.error.message || i18n.error || '部分文件上传失败', 'danger', 5, toastId);
|
||||||
|
if (!items.length) return;
|
||||||
|
if (!multiple) finish(state.selected);
|
||||||
|
else activateTab('mine');
|
||||||
|
}).finally(function () {
|
||||||
|
state.uploading = false;
|
||||||
|
renderList();
|
||||||
|
if (uploadButton) { uploadButton.disabled = !files.length; uploadButton.textContent = i18n.confirmUpload || '确认上传'; }
|
||||||
|
if (progress) progress.hidden = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function externalSelect() { var textarea = root.querySelector('[data-editor-media-urls]'), urls = String(textarea ? textarea.value : '').split(/\r?\n/).map(function (url) { var valid = validHttpUrl(url); return valid ? { url: valid } : null; }).filter(Boolean).slice(0, maxItems); if (!urls.length) { notify(i18n.invalidMediaUrl || '请填写有效地址', 'warning'); return; } finish(urls); }
|
||||||
|
function embedSelect() { var input = root.querySelector('[data-editor-media-embed]'), html = embedVideoMarkup(input ? input.value : ''); if (!html) { notify(i18n.invalidVideoUrl || '请填写有效的视频嵌入地址', 'warning'); return; } if (typeof options.onEmbed === 'function') options.onEmbed(html); else if (typeof options.onSelect === 'function') options.onSelect({ embed: html }); closeMediaModal(true); }
|
||||||
|
|
||||||
root.addEventListener('click', function (event) {
|
root.addEventListener('click', function (event) {
|
||||||
var close = event.target.closest('[data-editor-media-close]');
|
var close = event.target.closest('[data-editor-media-close]'); if (close) { event.preventDefault(); closeMediaModal(); return; }
|
||||||
if (close) {
|
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 (button) { var active = button === tab; button.classList.toggle('is-active', active); button.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); }); if (name === 'mine' && state.page === 0) request(1, true); return; }
|
||||||
event.preventDefault();
|
var itemButton = event.target.closest('[data-editor-media-item]'); if (itemButton && !itemButton.disabled) { var item = state.items.find(function (entry) { return itemId(entry) === Number(itemButton.getAttribute('data-editor-media-item')); }); if (item) selectItem(item); return; }
|
||||||
closeMediaModal();
|
if (event.target.closest('[data-editor-media-clear]')) { if (search) search.value = ''; window.clearTimeout(state.searchTimer); state.search = ''; request(1, true); return; }
|
||||||
|
if (event.target.closest('[data-editor-media-confirm]')) { if (state.selected.length) finish(state.selected); return; }
|
||||||
|
if (event.target.closest('[data-editor-media-more]')) { if (!state.loading) request(state.page + 1, false); return; }
|
||||||
|
if (event.target.closest('[data-editor-media-external]')) { externalSelect(); return; }
|
||||||
|
if (event.target.closest('[data-editor-media-embed-submit]')) { embedSelect(); return; }
|
||||||
|
if (event.target.closest('[data-editor-media-upload]')) uploadSelected();
|
||||||
|
});
|
||||||
|
if (search) search.addEventListener('input', function () {
|
||||||
|
state.search = String(search.value || '').trim();
|
||||||
|
window.clearTimeout(state.searchTimer);
|
||||||
|
state.searchTimer = window.setTimeout(function () {
|
||||||
|
if (state.search.length === 1) {
|
||||||
|
state.items = [];
|
||||||
|
state.page = 0;
|
||||||
|
state.totalPages = 0;
|
||||||
|
state.loading = false;
|
||||||
|
renderList();
|
||||||
|
statusText(i18n.searchMin || '请输入至少 2 个字符进行搜索', 'empty');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
request(1, true);
|
||||||
var tab = event.target.closest('[data-editor-media-tab]');
|
}, 600);
|
||||||
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) {
|
if (fileInput) fileInput.addEventListener('change', function () { setFiles(fileInput.files); });
|
||||||
panel.classList.toggle('is-active', panel.getAttribute('data-editor-media-panel') === name);
|
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') closeMediaModal(); }; state.onKeydown = onKeydown; activeModal = state; document.addEventListener('keydown', onKeydown);
|
||||||
return;
|
var focusTarget = root.querySelector('.aksrt-editor-media__dialog'); if (focusTarget) focusTarget.focus({ preventScroll: true }); request(1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.target.closest('[data-editor-media-external]')) {
|
window.AksrtSubmitMedia = { open: openMediaModal, close: function () { closeMediaModal(); }, upload: uploadMedia };
|
||||||
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 mediaMarkup(type, { url: url });
|
|
||||||
}).join('');
|
|
||||||
if (!html) {
|
|
||||||
notify(i18n.invalidMediaUrl || '请填写有效地址', 'warning');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
editor.insertContent(html + '<p></p>');
|
|
||||||
editor.nodeChanged();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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(function (file) {
|
|
||||||
return uploadMedia(file, type);
|
|
||||||
})).then(function (items) {
|
|
||||||
var html = items.map(function (item) {
|
|
||||||
return mediaMarkup(type, item);
|
|
||||||
}).join('');
|
|
||||||
if (!html) {
|
|
||||||
throw new Error(i18n.error || '文件上传失败');
|
|
||||||
}
|
|
||||||
editor.insertContent(html + '<p></p>');
|
|
||||||
editor.nodeChanged();
|
|
||||||
notify(i18n.uploaded || '上传成功', 'success');
|
|
||||||
closeMediaModal();
|
|
||||||
}).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') {
|
|
||||||
closeMediaModal();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
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) {
|
function selectedContent(editor) {
|
||||||
var content = String(editor.selection.getContent() || '').trim();
|
var content = String(editor.selection.getContent() || '').trim();
|
||||||
@@ -623,6 +565,12 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function registerEditorTools() {
|
||||||
|
if (!window.tinymce || !window.tinymce.PluginManager || window.aksrtSubmitEditorToolsRegistered) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
window.aksrtSubmitEditorToolsRegistered = true;
|
||||||
|
|
||||||
window.tinymce.PluginManager.add('aksrt_editor_tools', function (editor) {
|
window.tinymce.PluginManager.add('aksrt_editor_tools', function (editor) {
|
||||||
bindCharacterCount(editor);
|
bindCharacterCount(editor);
|
||||||
|
|
||||||
@@ -632,7 +580,13 @@
|
|||||||
icon: 'image',
|
icon: 'image',
|
||||||
tooltip: i18n.insertImage || '插入图片',
|
tooltip: i18n.insertImage || '插入图片',
|
||||||
onclick: function () {
|
onclick: function () {
|
||||||
createMediaModal(editor, 'image');
|
window.AksrtSubmitMedia.open({
|
||||||
|
type: 'image', multiple: true, maxItems: 100,
|
||||||
|
onSelect: function (items) {
|
||||||
|
editor.insertContent(items.map(function (item) { return mediaMarkup('image', item); }).join('') + '<p></p>');
|
||||||
|
editor.nodeChanged();
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -642,7 +596,17 @@
|
|||||||
icon: 'media',
|
icon: 'media',
|
||||||
tooltip: i18n.insertVideo || '插入视频',
|
tooltip: i18n.insertVideo || '插入视频',
|
||||||
onclick: function () {
|
onclick: function () {
|
||||||
createMediaModal(editor, 'video');
|
window.AksrtSubmitMedia.open({
|
||||||
|
type: 'video', multiple: false,
|
||||||
|
onSelect: function (item) {
|
||||||
|
editor.insertContent(mediaMarkup('video', item) + '<p></p>');
|
||||||
|
editor.nodeChanged();
|
||||||
|
},
|
||||||
|
onEmbed: function (html) {
|
||||||
|
editor.insertContent(html + '<p></p>');
|
||||||
|
editor.nodeChanged();
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -652,7 +616,13 @@
|
|||||||
icon: 'upload',
|
icon: 'upload',
|
||||||
tooltip: i18n.insertFile || '上传附件',
|
tooltip: i18n.insertFile || '上传附件',
|
||||||
onclick: function () {
|
onclick: function () {
|
||||||
createMediaModal(editor, 'file');
|
window.AksrtSubmitMedia.open({
|
||||||
|
type: 'file', multiple: false,
|
||||||
|
onSelect: function (item) {
|
||||||
|
editor.insertContent(mediaMarkup('file', item) + '<p></p>');
|
||||||
|
editor.nodeChanged();
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -728,4 +698,21 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerEditorTools();
|
||||||
|
if (!window.aksrtSubmitEditorToolsRegistered) {
|
||||||
|
var retryCount = 0;
|
||||||
|
var retryRegistration = function () {
|
||||||
|
if (registerEditorTools()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
retryCount += 1;
|
||||||
|
if (retryCount < 100) {
|
||||||
|
window.setTimeout(retryRegistration, 50);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
retryRegistration();
|
||||||
|
}
|
||||||
})(window, document);
|
})(window, document);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var fields = ['url', 'extract_code', 'copy_key', 'copy_val', 'label', 'class'];
|
var fields = ['url', 'attachment_id', 'extract_code', 'copy_key', 'copy_val', 'label', 'class'];
|
||||||
var buttonClasses = [
|
var buttonClasses = [
|
||||||
'',
|
'',
|
||||||
'c-red', 'c-yellow', 'c-cyan', 'c-blue', 'c-green', 'c-purple',
|
'c-red', 'c-yellow', 'c-cyan', 'c-blue', 'c-green', 'c-purple',
|
||||||
@@ -96,6 +96,7 @@
|
|||||||
'<div class="mini-scrollbar scroll-y max-vh7 pay-dawnload-edit-fields">' +
|
'<div class="mini-scrollbar scroll-y max-vh7 pay-dawnload-edit-fields">' +
|
||||||
'<div class="mb20"><div class="muted-color mb6">' + escapeHtml(i18n.downloadUrl || '下载地址') + '</div>' +
|
'<div class="mb20"><div class="muted-color mb6">' + escapeHtml(i18n.downloadUrl || '下载地址') + '</div>' +
|
||||||
'<div class="flex ac pay-dawnload-url-row"><input type="url" class="form-control" data-pay-resource-field="url" placeholder="' + escapeHtml(i18n.enterDownloadUrl || '请输入资源下载地址') + '">' +
|
'<div class="flex ac pay-dawnload-url-row"><input type="url" class="form-control" data-pay-resource-field="url" placeholder="' + escapeHtml(i18n.enterDownloadUrl || '请输入资源下载地址') + '">' +
|
||||||
|
'<input type="hidden" data-pay-resource-field="attachment_id">' +
|
||||||
'<button type="button" class="flex0 ml6 pay-upload-btn but hollow c-blue" data-pay-resource-upload><i class="fa fas fa-upload" aria-hidden="true"></i>' + escapeHtml(i18n.uploadFile || '上传附件') + '</button></div>' +
|
'<button type="button" class="flex0 ml6 pay-upload-btn but hollow c-blue" data-pay-resource-upload><i class="fa fas fa-upload" aria-hidden="true"></i>' + escapeHtml(i18n.uploadFile || '上传附件') + '</button></div>' +
|
||||||
'<div class="px12 muted-3-color mt6">' + escapeHtml(i18n.pasteLinkHint || '支持直接粘贴网盘分享链接,并自动识别提取码') + '</div></div>' +
|
'<div class="px12 muted-3-color mt6">' + escapeHtml(i18n.pasteLinkHint || '支持直接粘贴网盘分享链接,并自动识别提取码') + '</div></div>' +
|
||||||
'<div class="mb20"><div class="muted-color mb6">' + escapeHtml(i18n.resourceNote || '资源备注') + '</div>' +
|
'<div class="mb20"><div class="muted-color mb6">' + escapeHtml(i18n.resourceNote || '资源备注') + '</div>' +
|
||||||
@@ -243,7 +244,10 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
modal.querySelector('[data-pay-download-submit]').addEventListener('click', saveResource);
|
modal.querySelector('[data-pay-download-submit]').addEventListener('click', saveResource);
|
||||||
inputMap.url.addEventListener('input', syncActions);
|
inputMap.url.addEventListener('input', function () {
|
||||||
|
inputMap.attachment_id.value = '';
|
||||||
|
syncActions();
|
||||||
|
});
|
||||||
inputMap.url.addEventListener('paste', function (event) {
|
inputMap.url.addEventListener('paste', function (event) {
|
||||||
var text = event.clipboardData ? event.clipboardData.getData('text') : '';
|
var text = event.clipboardData ? event.clipboardData.getData('text') : '';
|
||||||
if (!text) {
|
if (!text) {
|
||||||
@@ -260,35 +264,30 @@
|
|||||||
inputMap.copy_key.value = i18n.extractCode || '提取码';
|
inputMap.copy_key.value = i18n.extractCode || '提取码';
|
||||||
inputMap.copy_val.value = codeMatch[1];
|
inputMap.copy_val.value = codeMatch[1];
|
||||||
}
|
}
|
||||||
|
inputMap.attachment_id.value = '';
|
||||||
syncActions();
|
syncActions();
|
||||||
});
|
});
|
||||||
|
|
||||||
var canUpload = !!((cfg.uploads || {}).resource && typeof options.pickUpload === 'function' && typeof options.uploadFile === 'function');
|
var canUseMediaPicker = !!(window.AksrtSubmitMedia && typeof window.AksrtSubmitMedia.open === 'function');
|
||||||
uploadButton.hidden = !canUpload;
|
uploadButton.hidden = false;
|
||||||
if (canUpload) {
|
|
||||||
uploadButton.addEventListener('click', function () {
|
uploadButton.addEventListener('click', function () {
|
||||||
var toastId = 'aksrt-front-submit-resource-upload-toast';
|
if (!canUseMediaPicker) {
|
||||||
options.pickUpload('resource', '*/*').then(function (file) {
|
notify(i18n.mediaPickerUnavailable || '媒体选择器加载失败,请刷新页面后重试', 'danger');
|
||||||
if (!file) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
uploadButton.disabled = true;
|
|
||||||
notify(i18n.uploading || '正在上传附件…', 'load', '', toastId);
|
|
||||||
return options.uploadFile('resource', file);
|
|
||||||
}).then(function (file) {
|
|
||||||
if (!file) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
inputMap.url.value = file.url || '';
|
window.AksrtSubmitMedia.open({
|
||||||
|
type: 'file',
|
||||||
|
multiple: false,
|
||||||
|
allowExternal: false,
|
||||||
|
onSelect: function (item) {
|
||||||
|
if (!item) return;
|
||||||
|
inputMap.url.value = item.url || '';
|
||||||
|
inputMap.attachment_id.value = String(item.attachment_id || item.id || 0);
|
||||||
syncActions();
|
syncActions();
|
||||||
notify(i18n.uploaded || '附件上传成功', 'success', 5, toastId);
|
notify(i18n.uploaded || '附件上传成功', 'success', 5, 'aksrt-front-submit-resource-upload-toast');
|
||||||
}).catch(function (error) {
|
},
|
||||||
notify(error.message || i18n.error || '附件上传失败', 'danger', 5, toastId);
|
|
||||||
}).finally(function () {
|
|
||||||
uploadButton.disabled = false;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('keydown', function (event) {
|
document.addEventListener('keydown', function (event) {
|
||||||
if (!modal.hidden && event.key === 'Escape') {
|
if (!modal.hidden && event.key === 'Escape') {
|
||||||
|
|||||||
+86
-88
@@ -21,6 +21,15 @@
|
|||||||
return Array.prototype.slice.call((root || document).querySelectorAll(sel));
|
return Array.prototype.slice.call((root || document).querySelectorAll(sel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeHtml(value) {
|
||||||
|
return String(value || '')
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
function getEditorContent() {
|
function getEditorContent() {
|
||||||
if (window.tinymce) {
|
if (window.tinymce) {
|
||||||
var ed = window.tinymce.get('post_content');
|
var ed = window.tinymce.get('post_content');
|
||||||
@@ -82,9 +91,10 @@
|
|||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
this.form = document.getElementById('aksrt-submit-form');
|
this.form = document.getElementById('aksrt-submit-form');
|
||||||
if (!this.form) {
|
if (!this.form || this.form.getAttribute('data-aksrt-submit-ready') === '1') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.form.setAttribute('data-aksrt-submit-ready', '1');
|
||||||
this.bindDirty();
|
this.bindDirty();
|
||||||
this.bindAutoHeight();
|
this.bindAutoHeight();
|
||||||
this.bindSubmitButtons();
|
this.bindSubmitButtons();
|
||||||
@@ -258,19 +268,27 @@
|
|||||||
if (d.time) {
|
if (d.time) {
|
||||||
var timeEl = qs('.aksrt-submit-modified-time');
|
var timeEl = qs('.aksrt-submit-modified-time');
|
||||||
if (timeEl) {
|
if (timeEl) {
|
||||||
timeEl.innerHTML =
|
timeEl.textContent = '';
|
||||||
'<span class="badg">' + (i18n.lastSaved || 'Last saved: ') + d.time + '</span>';
|
var timeBadge = document.createElement('span');
|
||||||
|
timeBadge.className = 'badg';
|
||||||
|
timeBadge.textContent = (i18n.lastSaved || '最后保存:') + d.time;
|
||||||
|
timeEl.appendChild(timeBadge);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (d.url) {
|
if (d.url) {
|
||||||
var view = qs('.aksrt-submit-view-btn');
|
var view = qs('.aksrt-submit-view-btn');
|
||||||
if (view) {
|
if (view) {
|
||||||
view.innerHTML =
|
view.textContent = '';
|
||||||
'<a class="but c-blue" target="_blank" href="' +
|
var previewLink = document.createElement('a');
|
||||||
d.url +
|
previewLink.className = 'but c-blue';
|
||||||
'"><i class="fa fas fa-file-alt"></i> ' +
|
previewLink.target = '_blank';
|
||||||
(i18n.preview || 'Preview') +
|
previewLink.rel = 'noopener';
|
||||||
'</a>';
|
previewLink.href = String(d.url);
|
||||||
|
var previewIcon = document.createElement('i');
|
||||||
|
previewIcon.className = 'fa fas fa-file-alt';
|
||||||
|
previewLink.appendChild(previewIcon);
|
||||||
|
previewLink.appendChild(document.createTextNode(' ' + (i18n.preview || 'Preview')));
|
||||||
|
view.appendChild(previewLink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
notyf(
|
notyf(
|
||||||
@@ -398,16 +416,16 @@
|
|||||||
}
|
}
|
||||||
var html = '';
|
var html = '';
|
||||||
if (t === 'image' && d.url) {
|
if (t === 'image' && d.url) {
|
||||||
html = '<img src="' + d.url + '" alt="">';
|
html = '<img src="' + escapeHtml(d.url) + '" alt="">';
|
||||||
} else if (t === 'slide' && Array.isArray(d) && d[0] && d[0].url) {
|
} else if (t === 'slide' && Array.isArray(d) && d[0] && d[0].url) {
|
||||||
html =
|
html =
|
||||||
'<img src="' +
|
'<img src="' +
|
||||||
d[0].url +
|
escapeHtml(d[0].url) +
|
||||||
'" alt=""><span class="featured-type-badge"><i class="fa fas fa-image"></i></span>';
|
'" alt=""><span class="featured-type-badge"><i class="fa fas fa-image"></i></span>';
|
||||||
} else if (t === 'video') {
|
} else if (t === 'video') {
|
||||||
html =
|
html =
|
||||||
(d.pic
|
(d.pic
|
||||||
? '<img src="' + d.pic + '" alt="">'
|
? '<img src="' + escapeHtml(d.pic) + '" alt="">'
|
||||||
: '<div class="featured-video-placeholder"><i class="fa fas fa-play"></i></div>') +
|
: '<div class="featured-video-placeholder"><i class="fa fas fa-play"></i></div>') +
|
||||||
'<span class="featured-type-badge"><i class="fa fas fa-video"></i></span>';
|
'<span class="featured-type-badge"><i class="fa fas fa-video"></i></span>';
|
||||||
}
|
}
|
||||||
@@ -418,41 +436,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadFile: function (kind, file) {
|
openMediaPicker: function (options) {
|
||||||
var upload = (cfg.uploads || {})[kind] || null;
|
if (!window.AksrtSubmitMedia || typeof window.AksrtSubmitMedia.open !== 'function') {
|
||||||
if (!upload || !upload.url || !file) {
|
notyf(i18n.mediaPickerUnavailable || '媒体选择器加载失败,请刷新页面后重试', 'danger');
|
||||||
return Promise.reject(new Error(i18n.error || 'Upload unavailable'));
|
return false;
|
||||||
}
|
}
|
||||||
if (upload.maxSize && file.size > upload.maxSize) {
|
window.AksrtSubmitMedia.open(options);
|
||||||
return Promise.reject(new Error(i18n.fileTooLarge || '文件大小超过限制'));
|
return true;
|
||||||
}
|
|
||||||
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) {
|
|
||||||
throw new Error((payload && payload.message) || i18n.error || 'Upload failed');
|
|
||||||
}
|
|
||||||
return payload;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
pickUpload: function (kind, accept) {
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
var input = document.createElement('input');
|
|
||||||
input.type = 'file';
|
|
||||||
input.accept = accept;
|
|
||||||
input.addEventListener('change', function () {
|
|
||||||
resolve(input.files && input.files[0] ? input.files[0] : null);
|
|
||||||
}, { once: true });
|
|
||||||
input.click();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
bindFeatured: function () {
|
bindFeatured: function () {
|
||||||
@@ -473,54 +463,64 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (act === 'image') {
|
if (act === 'image') {
|
||||||
var imageToastId = 'aksrt-front-submit-featured-upload-toast';
|
self.openMediaPicker({
|
||||||
self.pickUpload('image', 'image/*').then(function (file) {
|
type: 'image',
|
||||||
if (!file) return null;
|
multiple: false,
|
||||||
notyf(i18n.uploading || '正在上传封面…', 'load', '', imageToastId);
|
allowExternal: false,
|
||||||
return self.uploadFile('image', file);
|
onSelect: function (item) {
|
||||||
}).then(function (file) {
|
if (!item) return;
|
||||||
if (!file) return;
|
self.setFeatured('image', { url: item.url || '', id: item.attachment_id || item.id || 0 });
|
||||||
self.setFeatured('image', { url: file.url, id: file.attachment_id });
|
},
|
||||||
notyf(i18n.uploaded || '封面上传成功', 'success', 5, imageToastId);
|
|
||||||
}).catch(function (error) {
|
|
||||||
notyf(error.message || i18n.error || '封面上传失败', 'danger', 5, imageToastId);
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (act === 'slide') {
|
if (act === 'slide') {
|
||||||
var slideToastId = 'aksrt-front-submit-slide-upload-toast';
|
self.openMediaPicker({
|
||||||
self.pickUpload('image', 'image/*').then(function (file) {
|
type: 'image',
|
||||||
if (!file) return null;
|
multiple: true,
|
||||||
notyf(i18n.uploading || '正在上传幻灯片封面…', 'load', '', slideToastId);
|
maxItems: 30,
|
||||||
return self.uploadFile('image', file);
|
allowExternal: false,
|
||||||
}).then(function (file) {
|
onSelect: function (items) {
|
||||||
if (!file) return;
|
var slides = (Array.isArray(items) ? items : []).map(function (item) {
|
||||||
self.setFeatured('slide', [{ url: file.url, id: file.attachment_id }]);
|
return { url: item.url || '', id: item.attachment_id || item.id || 0 };
|
||||||
notyf(i18n.uploaded || '幻灯片封面上传成功', 'success', 5, slideToastId);
|
}).filter(function (item) { return item.url || item.id; });
|
||||||
}).catch(function (error) {
|
if (slides.length) self.setFeatured('slide', slides);
|
||||||
notyf(error.message || i18n.error || '幻灯片封面上传失败', 'danger', 5, slideToastId);
|
},
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (act === 'video') {
|
if (act === 'video') {
|
||||||
var url = window.prompt(i18n.videoUrl || 'Video URL');
|
self.openMediaPicker({
|
||||||
if (!url) {
|
type: 'video',
|
||||||
return;
|
multiple: false,
|
||||||
}
|
allowExternal: true,
|
||||||
var videoToastId = 'aksrt-front-submit-video-cover-toast';
|
allowEmbed: false,
|
||||||
self.pickUpload('image', 'image/*').then(function (file) {
|
onSelect: function (item) {
|
||||||
if (!file) {
|
if (!item || !item.url) return;
|
||||||
self.setFeatured('video', { url: url, pic: '' });
|
var video = {
|
||||||
return null;
|
url: item.url,
|
||||||
}
|
pic: '',
|
||||||
notyf(i18n.uploading || '正在上传视频封面…', 'load', '', videoToastId);
|
pic_id: 0,
|
||||||
return self.uploadFile('image', file);
|
id: item.attachment_id || item.id || 0,
|
||||||
}).then(function (file) {
|
};
|
||||||
if (!file) return;
|
self.setFeatured('video', video);
|
||||||
self.setFeatured('video', { url: url, pic: file.url });
|
window.setTimeout(function () {
|
||||||
notyf(i18n.uploaded || '视频封面上传成功', 'success', 5, videoToastId);
|
self.openMediaPicker({
|
||||||
}).catch(function (error) {
|
type: 'image',
|
||||||
notyf(error.message || i18n.error || '视频封面上传失败', 'danger', 5, videoToastId);
|
multiple: false,
|
||||||
|
allowExternal: false,
|
||||||
|
title: i18n.pickVideoCover || '选择视频封面(可选)',
|
||||||
|
onSelect: function (cover) {
|
||||||
|
self.setFeatured('video', {
|
||||||
|
url: video.url,
|
||||||
|
pic: cover && cover.url ? cover.url : '',
|
||||||
|
pic_id: cover ? (cover.attachment_id || cover.id || 0) : 0,
|
||||||
|
id: video.id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -572,8 +572,6 @@
|
|||||||
|
|
||||||
window.aksrtSubmitPayDownloads.init({
|
window.aksrtSubmitPayDownloads.init({
|
||||||
root: box,
|
root: box,
|
||||||
pickUpload: this.pickUpload.bind(this),
|
|
||||||
uploadFile: this.uploadFile.bind(this),
|
|
||||||
notify: notyf,
|
notify: notyf,
|
||||||
markDirty: function () {
|
markDirty: function () {
|
||||||
App.isDirty = true;
|
App.isDirty = true;
|
||||||
|
|||||||
@@ -1,328 +0,0 @@
|
|||||||
/**
|
|
||||||
* AKSRT Mini Upload — 小型文件上传器
|
|
||||||
*
|
|
||||||
* 提供拖拽上传、进度显示、预览功能。
|
|
||||||
* 用于前台投稿、商品编辑等需要文件上传的页面。
|
|
||||||
*
|
|
||||||
* @package AKSRT
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化小型上传器
|
|
||||||
*
|
|
||||||
* @param {HTMLElement} container 上传器容器
|
|
||||||
* @param {Object} options 配置项
|
|
||||||
*/
|
|
||||||
function MiniUpload(container, options) {
|
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
this.options = Object.assign({
|
|
||||||
maxFileSize: 5 * 1024 * 1024, // 5MB
|
|
||||||
allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
|
|
||||||
multiple: false,
|
|
||||||
uploadUrl: '',
|
|
||||||
fieldName: 'file',
|
|
||||||
nonce: '',
|
|
||||||
onSelect: null,
|
|
||||||
onSuccess: null,
|
|
||||||
onError: null,
|
|
||||||
}, options || {});
|
|
||||||
|
|
||||||
this.container = container;
|
|
||||||
this.files = [];
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
MiniUpload.prototype.init = function () {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// 创建文件输入
|
|
||||||
this.input = document.createElement('input');
|
|
||||||
this.input.type = 'file';
|
|
||||||
this.input.accept = this.options.allowedTypes.join(',');
|
|
||||||
this.input.multiple = this.options.multiple;
|
|
||||||
this.input.style.display = 'none';
|
|
||||||
this.container.appendChild(this.input);
|
|
||||||
|
|
||||||
// 创建拖拽区域
|
|
||||||
this.dropzone = document.createElement('div');
|
|
||||||
this.dropzone.className = 'aksrt-mini-upload__dropzone';
|
|
||||||
this.dropzone.innerHTML =
|
|
||||||
'<div class="aksrt-mini-upload__icon"><i class="fa fas fa-cloud-upload-alt"></i></div>' +
|
|
||||||
'<div class="aksrt-mini-upload__text">点击或拖拽文件到此处上传</div>' +
|
|
||||||
'<div class="aksrt-mini-upload__hint">支持 ' + this.options.allowedTypes.map(function (t) {
|
|
||||||
return t.split('/')[1].toUpperCase();
|
|
||||||
}).join(', ') + ',最大 ' + Math.round(this.options.maxFileSize / 1024 / 1024) + 'MB</div>';
|
|
||||||
this.container.appendChild(this.dropzone);
|
|
||||||
|
|
||||||
// 创建预览区域
|
|
||||||
this.preview = document.createElement('div');
|
|
||||||
this.preview.className = 'aksrt-mini-upload__preview';
|
|
||||||
this.container.appendChild(this.preview);
|
|
||||||
|
|
||||||
// 事件绑定
|
|
||||||
this.dropzone.addEventListener('click', function () {
|
|
||||||
self.input.click();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.dropzone.addEventListener('dragover', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
self.dropzone.classList.add('aksrt-mini-upload__dropzone--dragover');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.dropzone.addEventListener('dragleave', function () {
|
|
||||||
self.dropzone.classList.remove('aksrt-mini-upload__dropzone--dragover');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.dropzone.addEventListener('drop', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
self.dropzone.classList.remove('aksrt-mini-upload__dropzone--dragover');
|
|
||||||
if (e.dataTransfer.files.length > 0) {
|
|
||||||
self.handleFiles(e.dataTransfer.files);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.input.addEventListener('change', function () {
|
|
||||||
if (self.input.files.length > 0) {
|
|
||||||
self.handleFiles(self.input.files);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
MiniUpload.prototype.handleFiles = function (fileList) {
|
|
||||||
var self = this;
|
|
||||||
var files = Array.prototype.slice.call(fileList);
|
|
||||||
|
|
||||||
files.forEach(function (file) {
|
|
||||||
// 验证文件类型
|
|
||||||
if (self.options.allowedTypes.length > 0 && self.options.allowedTypes.indexOf(file.type) === -1) {
|
|
||||||
self.showError('不支持的文件类型: ' + file.type);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证文件大小
|
|
||||||
if (file.size > self.options.maxFileSize) {
|
|
||||||
self.showError('文件大小超过限制: ' + Math.round(file.size / 1024 / 1024) + 'MB');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.files.push(file);
|
|
||||||
self.addPreview(file);
|
|
||||||
|
|
||||||
if (typeof self.options.onSelect === 'function') {
|
|
||||||
self.options.onSelect(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果有上传 URL,自动上传
|
|
||||||
if (self.options.uploadUrl) {
|
|
||||||
self.uploadFile(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
MiniUpload.prototype.addPreview = function (file) {
|
|
||||||
var self = this;
|
|
||||||
var item = document.createElement('div');
|
|
||||||
item.className = 'aksrt-mini-upload__item';
|
|
||||||
|
|
||||||
if (file.type.startsWith('image/')) {
|
|
||||||
var reader = new FileReader();
|
|
||||||
reader.onload = function (e) {
|
|
||||||
item.innerHTML =
|
|
||||||
'<img src="' + e.target.result + '" class="aksrt-mini-upload__thumb">' +
|
|
||||||
'<div class="aksrt-mini-upload__info">' +
|
|
||||||
'<span class="aksrt-mini-upload__name">' + file.name + '</span>' +
|
|
||||||
'<span class="aksrt-mini-upload__size">' + self.formatSize(file.size) + '</span>' +
|
|
||||||
'</div>' +
|
|
||||||
'<button type="button" class="aksrt-mini-upload__remove"><i class="fa fas fa-times"></i></button>' +
|
|
||||||
'<div class="aksrt-mini-upload__progress"><div class="aksrt-mini-upload__progress-bar"></div></div>';
|
|
||||||
self.bindRemove(item, file);
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
} else {
|
|
||||||
item.innerHTML =
|
|
||||||
'<div class="aksrt-mini-upload__file-icon"><i class="fa fas fa-file"></i></div>' +
|
|
||||||
'<div class="aksrt-mini-upload__info">' +
|
|
||||||
'<span class="aksrt-mini-upload__name">' + file.name + '</span>' +
|
|
||||||
'<span class="aksrt-mini-upload__size">' + self.formatSize(file.size) + '</span>' +
|
|
||||||
'</div>' +
|
|
||||||
'<button type="button" class="aksrt-mini-upload__remove"><i class="fa fas fa-times"></i></button>' +
|
|
||||||
'<div class="aksrt-mini-upload__progress"><div class="aksrt-mini-upload__progress-bar"></div></div>';
|
|
||||||
this.bindRemove(item, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.preview.appendChild(item);
|
|
||||||
};
|
|
||||||
|
|
||||||
MiniUpload.prototype.bindRemove = function (item, file) {
|
|
||||||
var self = this;
|
|
||||||
var removeBtn = item.querySelector('.aksrt-mini-upload__remove');
|
|
||||||
if (removeBtn) {
|
|
||||||
removeBtn.addEventListener('click', function () {
|
|
||||||
var index = self.files.indexOf(file);
|
|
||||||
if (index > -1) self.files.splice(index, 1);
|
|
||||||
item.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MiniUpload.prototype.uploadFile = function (file) {
|
|
||||||
var self = this;
|
|
||||||
var formData = new FormData();
|
|
||||||
formData.append(this.options.fieldName, file);
|
|
||||||
|
|
||||||
if (this.options.nonce) {
|
|
||||||
formData.append('_wpnonce', this.options.nonce);
|
|
||||||
}
|
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('POST', this.options.uploadUrl, true);
|
|
||||||
|
|
||||||
// 进度更新
|
|
||||||
var item = this.preview.lastElementChild;
|
|
||||||
var progressBar = item ? item.querySelector('.aksrt-mini-upload__progress-bar') : null;
|
|
||||||
|
|
||||||
xhr.upload.onprogress = function (e) {
|
|
||||||
if (e.lengthComputable && progressBar) {
|
|
||||||
var percent = Math.round((e.loaded / e.total) * 100);
|
|
||||||
progressBar.style.width = percent + '%';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.onload = function () {
|
|
||||||
if (xhr.status >= 200 && xhr.status < 300) {
|
|
||||||
try {
|
|
||||||
var response = JSON.parse(xhr.responseText);
|
|
||||||
if (typeof self.options.onSuccess === 'function') {
|
|
||||||
self.options.onSuccess(response, file);
|
|
||||||
}
|
|
||||||
if (item) item.classList.add('aksrt-mini-upload__item--success');
|
|
||||||
} catch (e) {
|
|
||||||
self.showError('上传响应解析失败');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.showError('上传失败: HTTP ' + xhr.status);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.onerror = function () {
|
|
||||||
self.showError('网络错误,上传失败');
|
|
||||||
if (item) item.classList.add('aksrt-mini-upload__item--error');
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.send(formData);
|
|
||||||
};
|
|
||||||
|
|
||||||
MiniUpload.prototype.showError = function (msg) {
|
|
||||||
if (typeof this.options.onError === 'function') {
|
|
||||||
this.options.onError(msg);
|
|
||||||
}
|
|
||||||
console.error('[AKSRT MiniUpload]', msg);
|
|
||||||
};
|
|
||||||
|
|
||||||
MiniUpload.prototype.formatSize = function (bytes) {
|
|
||||||
if (bytes < 1024) return bytes + ' B';
|
|
||||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
||||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
||||||
};
|
|
||||||
|
|
||||||
MiniUpload.prototype.getFiles = function () {
|
|
||||||
return this.files;
|
|
||||||
};
|
|
||||||
|
|
||||||
MiniUpload.prototype.clear = function () {
|
|
||||||
this.files = [];
|
|
||||||
this.preview.innerHTML = '';
|
|
||||||
this.input.value = '';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 分片上传大文件
|
|
||||||
* @param {File} file - 要上传的文件
|
|
||||||
* @param {Object} opts - { chunkSize, uploadUrl, fieldName, nonce, onProgress, onComplete, onError }
|
|
||||||
*/
|
|
||||||
MiniUpload.prototype.uploadChunked = function (file, opts) {
|
|
||||||
var chunkSize = (opts.chunkSize || 2) * 1024 * 1024; // default 2MB
|
|
||||||
var totalChunks = Math.ceil(file.size / chunkSize);
|
|
||||||
var currentChunk = 0;
|
|
||||||
var fileId = file.name + '-' + file.size + '-' + file.lastModified;
|
|
||||||
|
|
||||||
function uploadNext() {
|
|
||||||
if (currentChunk >= totalChunks) {
|
|
||||||
if (opts.onComplete) opts.onComplete({ fileId: fileId, totalChunks: totalChunks });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var start = currentChunk * chunkSize;
|
|
||||||
var end = Math.min(start + chunkSize, file.size);
|
|
||||||
var blob = file.slice(start, end);
|
|
||||||
|
|
||||||
var formData = new FormData();
|
|
||||||
formData.append(opts.fieldName || 'file', blob, file.name);
|
|
||||||
formData.append('chunk', currentChunk);
|
|
||||||
formData.append('total_chunks', totalChunks);
|
|
||||||
formData.append('file_id', fileId);
|
|
||||||
formData.append('file_name', file.name);
|
|
||||||
formData.append('file_size', file.size);
|
|
||||||
if (opts.nonce) formData.append('_ajax_nonce', opts.nonce);
|
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('POST', opts.uploadUrl, true);
|
|
||||||
|
|
||||||
xhr.upload.onprogress = function (e) {
|
|
||||||
if (e.lengthComputable && opts.onProgress) {
|
|
||||||
var chunkProgress = e.loaded / e.total;
|
|
||||||
var totalProgress = (currentChunk + chunkProgress) / totalChunks;
|
|
||||||
opts.onProgress(totalProgress, currentChunk, totalChunks);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.onload = function () {
|
|
||||||
if (xhr.status >= 200 && xhr.status < 300) {
|
|
||||||
currentChunk++;
|
|
||||||
uploadNext();
|
|
||||||
} else {
|
|
||||||
if (opts.onError) opts.onError('上传失败: HTTP ' + xhr.status);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.onerror = function () {
|
|
||||||
if (opts.onError) opts.onError('网络错误');
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.send(formData);
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadNext();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 快速文件标识(替代 spark-md5,用文件名+大小+最后修改时间生成简易 hash)
|
|
||||||
*/
|
|
||||||
MiniUpload.prototype.getFileId = function (file) {
|
|
||||||
return file.name + '-' + file.size + '-' + file.lastModified;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 自动初始化
|
|
||||||
function autoInit() {
|
|
||||||
document.querySelectorAll('[data-aksrt-mini-upload]').forEach(function (el) {
|
|
||||||
if (el.dataset.miniUploadInit) return;
|
|
||||||
el.dataset.miniUploadInit = '1';
|
|
||||||
|
|
||||||
var options = {};
|
|
||||||
try {
|
|
||||||
options = JSON.parse(el.dataset.aksrtMiniUpload || '{}');
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
new MiniUpload(el, options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
jQuery(document).off('auto_fun.aksrt-mini-upload').on('auto_fun.aksrt-mini-upload', autoInit);
|
|
||||||
|
|
||||||
// 暴露到全局
|
|
||||||
window.AksrtMiniUpload = MiniUpload;
|
|
||||||
})();
|
|
||||||
@@ -446,7 +446,7 @@
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
source: 'attribute',
|
source: 'attribute',
|
||||||
selector: '.file-download-btn',
|
selector: '.file-download-btn',
|
||||||
attribute: 'data-download-file',
|
attribute: 'data-aksrt-file-id',
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
url: {
|
url: {
|
||||||
@@ -921,7 +921,7 @@
|
|||||||
href: url,
|
href: url,
|
||||||
};
|
};
|
||||||
if (file_id) {
|
if (file_id) {
|
||||||
a_attr['data-download-file'] = file_id;
|
a_attr['data-aksrt-file-id'] = file_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
var desc_lists = desc.map(function (item) {
|
var desc_lists = desc.map(function (item) {
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -1364,17 +1364,6 @@ _win.bd.on('click', '.switch-iframe', function () {
|
|||||||
_this.addClass('active').siblings().removeClass('active');
|
_this.addClass('active').siblings().removeClass('active');
|
||||||
});
|
});
|
||||||
|
|
||||||
//下载文件
|
|
||||||
_win.bd.on('click', '.file-download-btn', function () {
|
|
||||||
var _this = jQuery(this);
|
|
||||||
var id = _this.attr('data-download-file');
|
|
||||||
var nonce = _this.attr('download-nonce');
|
|
||||||
_this.attr('target', '_blank');
|
|
||||||
if (!id) return;
|
|
||||||
var href = _win.ajax_url + '?action=download_file&file=' + id + '&nonce=' + nonce;
|
|
||||||
_this.attr('target', '_blank').removeAttr('data-download-file').attr('href', href);
|
|
||||||
});
|
|
||||||
|
|
||||||
//文章古腾堡TAB
|
//文章古腾堡TAB
|
||||||
_win.bd.on('click', '.post-tab-toggle', function () {
|
_win.bd.on('click', '.post-tab-toggle', function () {
|
||||||
var _this = jQuery(this);
|
var _this = jQuery(this);
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -821,59 +821,6 @@ if (!function_exists('aksrt_can_access_content')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!function_exists('aksrt_can_download')) {
|
|
||||||
/**
|
|
||||||
* 下载授权门禁 —— 单一合同。
|
|
||||||
*
|
|
||||||
* 实际判定交给 \AKSRT\Content\Visibility::can_download_resource()。
|
|
||||||
* 不再使用 download_credit 做按次扣积分门禁。
|
|
||||||
*
|
|
||||||
* @param int|WP_Post|null $post
|
|
||||||
* @param int $user_id 0 表示当前用户
|
|
||||||
* @return true|WP_Error
|
|
||||||
*/
|
|
||||||
function aksrt_can_download($post = null, int $user_id = 0) {
|
|
||||||
if (class_exists('\AKSRT\Content\Visibility')) {
|
|
||||||
return \AKSRT\Content\Visibility::can_download_resource($post, $user_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
$p = get_post($post);
|
|
||||||
if (!($p instanceof WP_Post)) {
|
|
||||||
return new WP_Error('aksrt_invalid', __('无效内容。', 'aksrt'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$meta = aksrt_get_pay_meta($p);
|
|
||||||
if (($meta['pay_type'] ?? '') !== AKSRT_PAY_TYPE_DOWNLOAD) {
|
|
||||||
return new WP_Error('aksrt_not_downloadable', __('该内容不是可下载资源。', 'aksrt'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$uid = $user_id > 0 ? $user_id : get_current_user_id();
|
|
||||||
if (!function_exists('aksrt_user_can') || !aksrt_user_can('resource_download', $uid, (int) $p->ID)) {
|
|
||||||
$login_url = wp_login_url(get_permalink($p));
|
|
||||||
return new WP_Error('aksrt_need_login', __('请先登录。', 'aksrt'), ['login_url' => $login_url]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($uid > 0 && function_exists('aksrt_has_paid_access') && aksrt_has_paid_access($p, $uid)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if ($uid > 0 && function_exists('aksrt_is_vip_exempt') && aksrt_is_vip_exempt($uid)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback without Content_Visibility: require purchase when priced.
|
|
||||||
$cost = function_exists('aksrt_get_download_cost') ? aksrt_get_download_cost($p) : 0;
|
|
||||||
if ($cost > 0) {
|
|
||||||
return new WP_Error('aksrt_need_purchase', __('请先购买后再下载。', 'aksrt'), [
|
|
||||||
'status' => 402,
|
|
||||||
'cost' => $cost,
|
|
||||||
'post_id'=> (int) $p->ID,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!function_exists('aksrt_get_download_cost')) {
|
if (!function_exists('aksrt_get_download_cost')) {
|
||||||
function aksrt_get_download_cost($post = null): int {
|
function aksrt_get_download_cost($post = null): int {
|
||||||
if (function_exists('aksrt_resource_download_cost')) {
|
if (function_exists('aksrt_resource_download_cost')) {
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ class DomainModel {
|
|||||||
'cover_image' => '',
|
'cover_image' => '',
|
||||||
'featured_slide' => '',
|
'featured_slide' => '',
|
||||||
'featured_video' => '',
|
'featured_video' => '',
|
||||||
|
'featured_video_attachment_id' => 0,
|
||||||
'featured_video_title' => '',
|
'featured_video_title' => '',
|
||||||
'featured_video_episode' => [],
|
'featured_video_episode' => [],
|
||||||
'subtitle' => '',
|
'subtitle' => '',
|
||||||
@@ -173,7 +174,7 @@ class DomainModel {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
* @return array<int, array<string, string>>
|
* @return array<int, array{label:string,url:string,type:string,extract_code:string,copy_key:string,copy_val:string,class:string,attachment_id:int}>
|
||||||
*/
|
*/
|
||||||
public static function sanitize_downloads($value): array {
|
public static function sanitize_downloads($value): array {
|
||||||
if (!is_array($value)) {
|
if (!is_array($value)) {
|
||||||
@@ -241,6 +242,7 @@ class DomainModel {
|
|||||||
'class' => isset($row['class']) && in_array($row['class'], $allowed_classes, true)
|
'class' => isset($row['class']) && in_array($row['class'], $allowed_classes, true)
|
||||||
? (string) $row['class']
|
? (string) $row['class']
|
||||||
: '',
|
: '',
|
||||||
|
'attachment_id' => is_scalar($row['attachment_id'] ?? null) ? max(0, (int) $row['attachment_id']) : 0,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+45
-11
@@ -16,6 +16,7 @@ class FrontSubmit {
|
|||||||
use FrontSubmitSave;
|
use FrontSubmitSave;
|
||||||
use FrontSubmitPay;
|
use FrontSubmitPay;
|
||||||
use FrontSubmitFeatured;
|
use FrontSubmitFeatured;
|
||||||
|
use FrontSubmitMedia;
|
||||||
|
|
||||||
private static $instance = null;
|
private static $instance = null;
|
||||||
|
|
||||||
@@ -37,6 +38,8 @@ class FrontSubmit {
|
|||||||
add_action('wp_ajax_nopriv_aksrt_submit_post', [$this, 'ajax_submit_post']);
|
add_action('wp_ajax_nopriv_aksrt_submit_post', [$this, 'ajax_submit_post']);
|
||||||
|
|
||||||
add_action('rest_api_init', [$this, 'register_rest_routes']);
|
add_action('rest_api_init', [$this, 'register_rest_routes']);
|
||||||
|
add_action('aksrt_new_add_posts', [$this, 'sync_post_attachments']);
|
||||||
|
add_action('aksrt_new_edit_posts', [$this, 'sync_post_attachments']);
|
||||||
|
|
||||||
add_filter('body_class', [$this, 'body_class']);
|
add_filter('body_class', [$this, 'body_class']);
|
||||||
add_filter('wp_robots', [$this, 'robots_noindex']);
|
add_filter('wp_robots', [$this, 'robots_noindex']);
|
||||||
@@ -45,6 +48,38 @@ class FrontSubmit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function register_rest_routes(): void {
|
public function register_rest_routes(): void {
|
||||||
|
register_rest_route('aksrt/v1', '/front-submit/media/(?P<type>image|video|file)', [
|
||||||
|
'methods' => \WP_REST_Server::READABLE,
|
||||||
|
'callback' => [$this, 'rest_list_attachments'],
|
||||||
|
'permission_callback' => [$this, 'can_list_attachments'],
|
||||||
|
'args' => [
|
||||||
|
'type' => [
|
||||||
|
'validate_callback' => static function ($value): bool {
|
||||||
|
return in_array((string) $value, ['image', 'video', 'file'], true);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'post_id' => [
|
||||||
|
'default' => 0,
|
||||||
|
'sanitize_callback' => 'absint',
|
||||||
|
],
|
||||||
|
'paged' => [
|
||||||
|
'default' => 1,
|
||||||
|
'sanitize_callback' => 'absint',
|
||||||
|
'validate_callback' => static function ($value): bool {
|
||||||
|
return absint($value) > 0;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'search' => [
|
||||||
|
'default' => '',
|
||||||
|
'sanitize_callback' => 'sanitize_text_field',
|
||||||
|
],
|
||||||
|
'exclude' => [
|
||||||
|
'default' => [],
|
||||||
|
'sanitize_callback' => [$this, 'sanitize_attachment_ids'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
register_rest_route('aksrt/v1', '/front-submit/upload/(?P<kind>image|video|resource)', [
|
register_rest_route('aksrt/v1', '/front-submit/upload/(?P<kind>image|video|resource)', [
|
||||||
'methods' => 'POST',
|
'methods' => 'POST',
|
||||||
'callback' => [$this, 'rest_upload_attachment'],
|
'callback' => [$this, 'rest_upload_attachment'],
|
||||||
@@ -55,6 +90,10 @@ class FrontSubmit {
|
|||||||
return in_array((string) $value, ['image', 'video', 'resource'], true);
|
return in_array((string) $value, ['image', 'video', 'resource'], true);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
'post_id' => [
|
||||||
|
'default' => 0,
|
||||||
|
'sanitize_callback' => 'absint',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -64,7 +103,7 @@ class FrontSubmit {
|
|||||||
if ($user_id <= 0) {
|
if ($user_id <= 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!$this->can_submit($user_id)) {
|
if (!$this->can_use_media($request, $user_id)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,21 +214,16 @@ class FrontSubmit {
|
|||||||
|
|
||||||
$attachment_id = (int) $attachment_id;
|
$attachment_id = (int) $attachment_id;
|
||||||
wp_update_post(['ID' => $attachment_id, 'post_author' => $user_id]);
|
wp_update_post(['ID' => $attachment_id, 'post_author' => $user_id]);
|
||||||
|
update_post_meta($attachment_id, '_aksrt_front_submit_media', $kind);
|
||||||
|
|
||||||
$url = wp_get_attachment_url($attachment_id);
|
$attachment = $this->prepare_attachment_response($attachment_id);
|
||||||
if (!$url) {
|
if ($attachment === null) {
|
||||||
wp_delete_attachment($attachment_id, true);
|
wp_delete_attachment($attachment_id, true);
|
||||||
return new \WP_Error('aksrt_upload_failed', __('上传后无法读取文件。', 'aksrt'), ['status' => 500]);
|
return new \WP_Error('aksrt_upload_failed', __('上传后无法读取文件。', 'aksrt'), ['status' => 500]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new \WP_REST_Response([
|
$attachment['success'] = true;
|
||||||
'success' => true,
|
return new \WP_REST_Response($attachment, 201);
|
||||||
'attachment_id' => $attachment_id,
|
|
||||||
'url' => $url,
|
|
||||||
'name' => get_the_title($attachment_id),
|
|
||||||
'filename' => wp_basename((string) get_attached_file($attachment_id)),
|
|
||||||
'mime' => $mime,
|
|
||||||
], 201);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -44,12 +44,20 @@ trait FrontSubmitFeatured {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$video = esc_url_raw((string) ($payload['url'] ?? ''));
|
$video = esc_url_raw((string) ($payload['url'] ?? ''));
|
||||||
|
$video_id = absint($payload['id'] ?? 0);
|
||||||
|
if ($video_id > 0 && !$this->can_use_front_submit_video($video_id, $user_id)) {
|
||||||
|
$video_id = 0;
|
||||||
|
}
|
||||||
$pic = esc_url_raw((string) ($payload['pic'] ?? ''));
|
$pic = esc_url_raw((string) ($payload['pic'] ?? ''));
|
||||||
if ($pic !== '' && !$this->user_can_cap('upload_submit_image', $user_id)) {
|
if ($pic !== '' && !$this->user_can_cap('upload_submit_image', $user_id)) {
|
||||||
$pic = '';
|
$pic = '';
|
||||||
}
|
}
|
||||||
$post_meta['featured_video'] = $video;
|
$post_meta['featured_video'] = $video;
|
||||||
|
$post_meta['featured_video_attachment_id'] = $video_id;
|
||||||
$post_meta['featured_slide'] = '';
|
$post_meta['featured_slide'] = '';
|
||||||
|
$post_meta['thumbnail_url'] = '';
|
||||||
|
$post_meta['cover_image'] = '';
|
||||||
|
delete_post_thumbnail($post_id);
|
||||||
if ($pic !== '' && $this->can_use_front_submit_image_url($pic, $user_id)) {
|
if ($pic !== '' && $this->can_use_front_submit_image_url($pic, $user_id)) {
|
||||||
$post_meta['thumbnail_url'] = $pic;
|
$post_meta['thumbnail_url'] = $pic;
|
||||||
$post_meta['cover_image'] = $pic;
|
$post_meta['cover_image'] = $pic;
|
||||||
@@ -100,6 +108,10 @@ trait FrontSubmitFeatured {
|
|||||||
}
|
}
|
||||||
$post_meta['featured_slide'] = implode(',', array_values(array_unique($attachment_ids)));
|
$post_meta['featured_slide'] = implode(',', array_values(array_unique($attachment_ids)));
|
||||||
$post_meta['featured_video'] = '';
|
$post_meta['featured_video'] = '';
|
||||||
|
$post_meta['featured_video_attachment_id'] = 0;
|
||||||
|
$post_meta['thumbnail_url'] = '';
|
||||||
|
$post_meta['cover_image'] = '';
|
||||||
|
delete_post_thumbnail($post_id);
|
||||||
if (!empty($urls[0]) && $this->can_use_front_submit_image_url((string) $urls[0], $user_id)) {
|
if (!empty($urls[0]) && $this->can_use_front_submit_image_url((string) $urls[0], $user_id)) {
|
||||||
$post_meta['thumbnail_url'] = $urls[0];
|
$post_meta['thumbnail_url'] = $urls[0];
|
||||||
$post_meta['cover_image'] = $urls[0];
|
$post_meta['cover_image'] = $urls[0];
|
||||||
@@ -123,6 +135,7 @@ trait FrontSubmitFeatured {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$post_meta['featured_video'] = '';
|
$post_meta['featured_video'] = '';
|
||||||
|
$post_meta['featured_video_attachment_id'] = 0;
|
||||||
$post_meta['featured_slide'] = '';
|
$post_meta['featured_slide'] = '';
|
||||||
if ($url !== '' && $this->can_use_front_submit_image_url($url, $user_id)) {
|
if ($url !== '' && $this->can_use_front_submit_image_url($url, $user_id)) {
|
||||||
$post_meta['thumbnail_url'] = $url;
|
$post_meta['thumbnail_url'] = $url;
|
||||||
@@ -139,6 +152,7 @@ trait FrontSubmitFeatured {
|
|||||||
case 'none':
|
case 'none':
|
||||||
case 'clear':
|
case 'clear':
|
||||||
$post_meta['featured_video'] = '';
|
$post_meta['featured_video'] = '';
|
||||||
|
$post_meta['featured_video_attachment_id'] = 0;
|
||||||
$post_meta['featured_slide'] = '';
|
$post_meta['featured_slide'] = '';
|
||||||
$post_meta['thumbnail_url'] = '';
|
$post_meta['thumbnail_url'] = '';
|
||||||
$post_meta['cover_image'] = '';
|
$post_meta['cover_image'] = '';
|
||||||
@@ -181,4 +195,16 @@ trait FrontSubmitFeatured {
|
|||||||
$attachment_id = attachment_url_to_postid($url);
|
$attachment_id = attachment_url_to_postid($url);
|
||||||
return $attachment_id > 0 && $this->can_use_front_submit_image($attachment_id, $user_id);
|
return $attachment_id > 0 && $this->can_use_front_submit_image($attachment_id, $user_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function can_use_front_submit_video(int $attachment_id, int $user_id): bool {
|
||||||
|
if ($attachment_id <= 0 || !$this->user_can_cap('new_post_video_cover', $user_id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$attachment = get_post($attachment_id);
|
||||||
|
return $attachment instanceof \WP_Post
|
||||||
|
&& $attachment->post_type === 'attachment'
|
||||||
|
&& (int) $attachment->post_author === $user_id
|
||||||
|
&& in_array($attachment->post_status, ['inherit', 'private'], true)
|
||||||
|
&& strpos((string) $attachment->post_mime_type, 'video/') === 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,294 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package AKSRT
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace AKSRT\Core;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Front-submit media library, attachment normalization, and post relations.
|
||||||
|
*/
|
||||||
|
trait FrontSubmitMedia {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the current user may browse their front-submit media.
|
||||||
|
*/
|
||||||
|
public function can_list_attachments(\WP_REST_Request $request): bool {
|
||||||
|
$user_id = get_current_user_id();
|
||||||
|
return $user_id > 0 && $this->can_use_media($request, $user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether a user may use the submit media routes for this post.
|
||||||
|
*/
|
||||||
|
private function can_use_media(\WP_REST_Request $request, int $user_id): bool {
|
||||||
|
$post_id = absint($request->get_param('post_id'));
|
||||||
|
if ($post_id > 0) {
|
||||||
|
return $this->can_edit_post($post_id, $user_id);
|
||||||
|
}
|
||||||
|
return $this->can_submit($user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize a REST attachment exclusion list.
|
||||||
|
*
|
||||||
|
* @param mixed $value Comma-separated or array attachment IDs.
|
||||||
|
* @return int[]
|
||||||
|
*/
|
||||||
|
public function sanitize_attachment_ids($value): array {
|
||||||
|
if (!is_array($value)) {
|
||||||
|
$value = preg_split('/[,\s]+/', (string) $value) ?: [];
|
||||||
|
}
|
||||||
|
return array_values(array_unique(array_filter(array_map(static function ($id): int {
|
||||||
|
return is_scalar($id) ? absint($id) : 0;
|
||||||
|
}, $value))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current user's front-submit attachments.
|
||||||
|
*/
|
||||||
|
public function rest_list_attachments(\WP_REST_Request $request): \WP_REST_Response {
|
||||||
|
$user_id = get_current_user_id();
|
||||||
|
$type = sanitize_key((string) $request->get_param('type'));
|
||||||
|
$page = max(1, absint($request->get_param('paged')));
|
||||||
|
$search = sanitize_text_field((string) $request->get_param('search'));
|
||||||
|
$exclude = $this->sanitize_attachment_ids($request->get_param('exclude'));
|
||||||
|
|
||||||
|
if ($user_id <= 0 || !in_array($type, ['image', 'video', 'file'], true)) {
|
||||||
|
return new \WP_REST_Response([
|
||||||
|
'items' => [],
|
||||||
|
'total' => 0,
|
||||||
|
'total_pages' => 0,
|
||||||
|
'page' => $page,
|
||||||
|
'per_page' => 48,
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
$args = [
|
||||||
|
'post_type' => 'attachment',
|
||||||
|
'post_status' => ['inherit', 'private'],
|
||||||
|
'author' => $user_id,
|
||||||
|
'posts_per_page' => 48,
|
||||||
|
'paged' => $page,
|
||||||
|
'orderby' => 'ID',
|
||||||
|
'order' => 'DESC',
|
||||||
|
'no_found_rows' => false,
|
||||||
|
];
|
||||||
|
if ($type === 'image') {
|
||||||
|
$args['post_mime_type'] = 'image';
|
||||||
|
} elseif ($type === 'video') {
|
||||||
|
$args['post_mime_type'] = 'video';
|
||||||
|
}
|
||||||
|
if ($exclude !== []) {
|
||||||
|
$args['post__not_in'] = $exclude;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($search !== '') {
|
||||||
|
$args['s'] = $search;
|
||||||
|
add_filter('wp_allow_query_attachment_by_filename', '__return_true');
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = new \WP_Query($args);
|
||||||
|
if ($search !== '') {
|
||||||
|
remove_filter('wp_allow_query_attachment_by_filename', '__return_true');
|
||||||
|
}
|
||||||
|
$items = [];
|
||||||
|
foreach ($query->posts as $attachment) {
|
||||||
|
$prepared = $this->prepare_attachment_response($attachment);
|
||||||
|
if ($prepared !== null) {
|
||||||
|
$items[] = $prepared;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = (int) $query->found_posts;
|
||||||
|
return new \WP_REST_Response([
|
||||||
|
'items' => $items,
|
||||||
|
'total' => $total,
|
||||||
|
'total_pages' => $total > 0 ? (int) ceil($total / 48) : 0,
|
||||||
|
'page' => $page,
|
||||||
|
'per_page' => 48,
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize an attachment with WordPress's media response model.
|
||||||
|
*
|
||||||
|
* Only fields needed by the front-submit picker are returned. In
|
||||||
|
* particular, admin links, icons, nonces, and author data are omitted.
|
||||||
|
*
|
||||||
|
* @param int|\WP_Post $attachment Attachment object or ID.
|
||||||
|
* @return array<string, mixed>|null
|
||||||
|
*/
|
||||||
|
private function prepare_attachment_response($attachment): ?array {
|
||||||
|
$attachment = $attachment instanceof \WP_Post ? $attachment : get_post(absint($attachment));
|
||||||
|
if (!$attachment || $attachment->post_type !== 'attachment') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!function_exists('wp_prepare_attachment_for_js')) {
|
||||||
|
require_once ABSPATH . 'wp-admin/includes/media.php';
|
||||||
|
}
|
||||||
|
$data = wp_prepare_attachment_for_js($attachment->ID);
|
||||||
|
if (!is_array($data)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$sizes = is_array($data['sizes'] ?? null) ? $data['sizes'] : [];
|
||||||
|
$size_url = static function (array $sizes, string $size, string $fallback): string {
|
||||||
|
return !empty($sizes[$size]['url']) ? (string) $sizes[$size]['url'] : $fallback;
|
||||||
|
};
|
||||||
|
$url = (string) ($data['url'] ?? wp_get_attachment_url($attachment->ID) ?? '');
|
||||||
|
$filename = (string) ($data['filename'] ?? wp_basename((string) get_attached_file($attachment->ID)));
|
||||||
|
$mime = (string) ($data['mime'] ?? get_post_mime_type($attachment->ID));
|
||||||
|
return [
|
||||||
|
'attachment_id' => (int) $attachment->ID,
|
||||||
|
'id' => (int) $attachment->ID,
|
||||||
|
'title' => (string) ($data['title'] ?? get_the_title($attachment->ID)),
|
||||||
|
'name' => (string) ($data['name'] ?? $filename),
|
||||||
|
'filename' => $filename,
|
||||||
|
'type' => (string) ($data['type'] ?? (strpos($mime, '/') !== false ? strtok($mime, '/') : '')),
|
||||||
|
'subtype' => (string) ($data['subtype'] ?? (strpos($mime, '/') !== false ? substr($mime, strpos($mime, '/') + 1) : '')),
|
||||||
|
'url' => $url,
|
||||||
|
'large_url' => $size_url($sizes, 'large', $url),
|
||||||
|
'medium_url' => $size_url($sizes, 'medium', $url),
|
||||||
|
'thumbnail_url' => $size_url($sizes, 'thumbnail', $url),
|
||||||
|
'filesizeInBytes' => (int) ($data['filesizeInBytes'] ?? (@filesize((string) get_attached_file($attachment->ID)) ?: 0)),
|
||||||
|
'mime' => $mime,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach IDs referenced by a saved submission to that post.
|
||||||
|
*
|
||||||
|
* @param mixed $post Saved post passed by the existing submission hooks.
|
||||||
|
*/
|
||||||
|
public function sync_post_attachments($post): void {
|
||||||
|
if (!$post instanceof \WP_Post || $post->post_type !== 'post') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$user_id = get_current_user_id();
|
||||||
|
$post_id = (int) $post->ID;
|
||||||
|
if ($user_id <= 0 || (int) $post->post_author !== $user_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$image_ids = $this->extract_attachment_ids((string) $post->post_content, 'image');
|
||||||
|
$video_ids = $this->extract_attachment_ids((string) $post->post_content, 'video');
|
||||||
|
$file_ids = $this->extract_attachment_ids((string) $post->post_content, 'file');
|
||||||
|
$desired = [];
|
||||||
|
foreach ($image_ids as $id) {
|
||||||
|
if ($this->attachment_can_attach($id, $user_id, $post_id, 'image')) {
|
||||||
|
$desired[$id] = 'image';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($video_ids as $id) {
|
||||||
|
if ($this->attachment_can_attach($id, $user_id, $post_id, 'video')) {
|
||||||
|
$desired[$id] = 'video';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($file_ids as $id) {
|
||||||
|
if ($this->attachment_can_attach($id, $user_id, $post_id, 'file')) {
|
||||||
|
$desired[$id] = 'file';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pay_meta = function_exists('aksrt_get_pay_meta')
|
||||||
|
? aksrt_get_pay_meta($post_id)
|
||||||
|
: get_post_meta($post_id, DomainModel::PAY_META_KEY, true);
|
||||||
|
$pay_meta = is_array($pay_meta) ? $pay_meta : [];
|
||||||
|
foreach (is_array($pay_meta['downloads'] ?? null) ? $pay_meta['downloads'] : [] as $download) {
|
||||||
|
$id = absint($download['attachment_id'] ?? 0);
|
||||||
|
if ($id > 0 && $this->attachment_can_attach($id, $user_id, $post_id, 'file')) {
|
||||||
|
$desired[$id] = 'file';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$post_meta = get_post_meta($post_id, DomainModel::POST_META_KEY, true);
|
||||||
|
$post_meta = is_array($post_meta) ? $post_meta : [];
|
||||||
|
$featured_ids = [];
|
||||||
|
$video_id = absint($post_meta['featured_video_attachment_id'] ?? 0);
|
||||||
|
if ($video_id > 0 && $this->attachment_can_attach($video_id, $user_id, $post_id, 'video')) {
|
||||||
|
$desired[$video_id] = 'video';
|
||||||
|
}
|
||||||
|
$thumb_id = get_post_thumbnail_id($post_id);
|
||||||
|
if ($thumb_id > 0) {
|
||||||
|
$featured_ids[] = $thumb_id;
|
||||||
|
}
|
||||||
|
foreach (preg_split('/[\\s,]+/', (string) ($post_meta['featured_slide'] ?? '')) ?: [] as $token) {
|
||||||
|
$id = absint($token);
|
||||||
|
if ($id > 0) $featured_ids[] = $id;
|
||||||
|
}
|
||||||
|
foreach ($featured_ids as $id) {
|
||||||
|
if ($this->attachment_can_attach($id, $user_id, $post_id, 'image')) {
|
||||||
|
$desired[$id] = 'image';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global $wpdb;
|
||||||
|
$existing = get_posts([
|
||||||
|
'post_type' => 'attachment',
|
||||||
|
'post_status' => ['inherit', 'private'],
|
||||||
|
'post_parent' => $post_id,
|
||||||
|
'author' => $user_id,
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
'fields' => 'ids',
|
||||||
|
'no_found_rows' => true,
|
||||||
|
'meta_key' => '_aksrt_front_submit_media',
|
||||||
|
]);
|
||||||
|
foreach (array_map('absint', $existing) as $attachment_id) {
|
||||||
|
if (!isset($desired[$attachment_id])) {
|
||||||
|
$wpdb->update($wpdb->posts, ['post_parent' => 0], ['ID' => $attachment_id], ['%d'], ['%d']);
|
||||||
|
clean_post_cache($attachment_id);
|
||||||
|
delete_post_meta($attachment_id, '_aksrt_front_submit_media');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($desired as $attachment_id => $kind) {
|
||||||
|
$attachment_id = (int) $attachment_id;
|
||||||
|
update_post_meta($attachment_id, '_aksrt_front_submit_media', $kind);
|
||||||
|
$wpdb->update($wpdb->posts, ['post_parent' => $post_id], ['ID' => $attachment_id], ['%d'], ['%d']);
|
||||||
|
clean_post_cache($attachment_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract IDs from image/video edit markers or attachment relation markers.
|
||||||
|
*
|
||||||
|
* @return int[]
|
||||||
|
*/
|
||||||
|
private function extract_attachment_ids(string $content, string $type): array {
|
||||||
|
$ids = [];
|
||||||
|
if ($type === 'file') {
|
||||||
|
preg_match_all('/data-aksrt-file-id\s*=\s*["\']([^"\']+)["\']/i', $content, $matches);
|
||||||
|
} elseif ($type === 'image') {
|
||||||
|
preg_match_all('/<img\b[^>]*data-edit-file-id\s*=\s*["\']([^"\']+)["\'][^>]*>/i', $content, $matches);
|
||||||
|
} else {
|
||||||
|
preg_match_all('/<(?:div|video)\b[^>]*data-edit-file-id\s*=\s*["\']([^"\']+)["\'][^>]*data-video-url[^>]*>/i', $content, $matches);
|
||||||
|
if (empty($matches[1])) {
|
||||||
|
preg_match_all('/<(?:div|video)\b[^>]*data-video-url[^>]*data-edit-file-id\s*=\s*["\']([^"\']+)["\'][^>]*>/i', $content, $matches);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array_values(array_unique(array_filter(array_map('absint', $matches[1] ?? []))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function attachment_can_attach(int $attachment_id, int $user_id, int $post_id, string $type): bool {
|
||||||
|
$attachment = get_post($attachment_id);
|
||||||
|
if (!$attachment || $attachment->post_type !== 'attachment' || (int) $attachment->post_author !== $user_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!in_array($attachment->post_status, ['inherit', 'private'], true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((int) $attachment->post_parent !== 0 && (int) $attachment->post_parent !== $post_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$mime = (string) $attachment->post_mime_type;
|
||||||
|
if ($type === 'image') {
|
||||||
|
return wp_attachment_is_image($attachment_id);
|
||||||
|
}
|
||||||
|
if ($type === 'video') {
|
||||||
|
return strpos($mime, 'video/') === 0;
|
||||||
|
}
|
||||||
|
return $mime !== '';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,10 +49,14 @@ trait FrontSubmitPay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$downloads = DomainModel::sanitize_downloads($raw['downloads'] ?? []);
|
$downloads = DomainModel::sanitize_downloads($raw['downloads'] ?? []);
|
||||||
if (!empty($downloads) && $this->user_can_cap('new_post_pay_download', $user_id)) {
|
if ($this->user_can_cap('new_post_pay_download', $user_id)) {
|
||||||
$pay_type = DomainModel::PAY_TYPE_DOWNLOAD;
|
|
||||||
$meta['downloads'] = $downloads;
|
$meta['downloads'] = $downloads;
|
||||||
} elseif ($pay_type === DomainModel::PAY_TYPE_DOWNLOAD && !$this->user_can_cap('new_post_pay_download', $user_id)) {
|
if ($downloads !== []) {
|
||||||
|
$pay_type = DomainModel::PAY_TYPE_DOWNLOAD;
|
||||||
|
} elseif ($pay_type === DomainModel::PAY_TYPE_DOWNLOAD) {
|
||||||
|
$pay_type = DomainModel::PAY_TYPE_READING;
|
||||||
|
}
|
||||||
|
} elseif ($pay_type === DomainModel::PAY_TYPE_DOWNLOAD) {
|
||||||
$pay_type = DomainModel::PAY_TYPE_READING;
|
$pay_type = DomainModel::PAY_TYPE_READING;
|
||||||
$meta['downloads'] = [];
|
$meta['downloads'] = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,41 +53,53 @@ if (file_exists($css)) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
$js = get_template_directory() . '/assets/js/front-submit.js';
|
$js = get_template_directory() . '/assets/js/front-submit.js';
|
||||||
if (file_exists($js)) {
|
$editor_js = get_template_directory() . '/assets/js/front-submit-editor.js';
|
||||||
$pay_download_js = get_template_directory() . '/assets/js/front-submit-pay-download.js';
|
if (file_exists($editor_js)) {
|
||||||
|
wp_enqueue_script(
|
||||||
|
'aksrt-front-submit-editor',
|
||||||
|
get_template_directory_uri() . '/assets/js/front-submit-editor.js',
|
||||||
|
['editor'],
|
||||||
|
(string) filemtime($editor_js),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$pay_download_js = get_template_directory() . '/assets/js/front-submit-pay-download.js';
|
||||||
|
if (file_exists($pay_download_js)) {
|
||||||
wp_enqueue_script(
|
wp_enqueue_script(
|
||||||
'aksrt-front-submit-pay-download',
|
'aksrt-front-submit-pay-download',
|
||||||
get_template_directory_uri() . '/assets/js/front-submit-pay-download.js',
|
get_template_directory_uri() . '/assets/js/front-submit-pay-download.js',
|
||||||
[],
|
['aksrt-front-submit-editor'],
|
||||||
(string) filemtime($pay_download_js),
|
(string) filemtime($pay_download_js),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
if (file_exists($js)) {
|
||||||
wp_enqueue_script(
|
wp_enqueue_script(
|
||||||
'aksrt-front-submit',
|
'aksrt-front-submit',
|
||||||
get_template_directory_uri() . '/assets/js/front-submit.js',
|
get_template_directory_uri() . '/assets/js/front-submit.js',
|
||||||
['loader_js', 'aksrt-front-submit-pay-download'],
|
['loader_js', 'aksrt-front-submit-editor', 'aksrt-front-submit-pay-download'],
|
||||||
(string) filemtime($js),
|
(string) filemtime($js),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
$mini = get_template_directory() . '/assets/js/mini-upload.js';
|
|
||||||
if (file_exists($mini)) {
|
|
||||||
wp_enqueue_script(
|
|
||||||
'aksrt-mini-upload',
|
|
||||||
get_template_directory_uri() . '/assets/js/mini-upload.js',
|
|
||||||
[],
|
|
||||||
(string) filemtime($mini),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
$submit_image_max_size = \AKSRT\Core\FrontSubmit::upload_limit_bytes('image');
|
$submit_image_max_size = \AKSRT\Core\FrontSubmit::upload_limit_bytes('image');
|
||||||
$submit_video_max_size = \AKSRT\Core\FrontSubmit::upload_limit_bytes('video');
|
$submit_video_max_size = \AKSRT\Core\FrontSubmit::upload_limit_bytes('video');
|
||||||
$resource_max_size = \AKSRT\Core\FrontSubmit::upload_limit_bytes('resource');
|
$resource_max_size = \AKSRT\Core\FrontSubmit::upload_limit_bytes('resource');
|
||||||
|
|
||||||
wp_localize_script('aksrt-front-submit', 'aksrtSubmitData', [
|
wp_localize_script('aksrt-front-submit-editor', 'aksrtSubmitData', [
|
||||||
'ajaxUrl' => admin_url('admin-ajax.php'),
|
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||||
'nonce' => wp_create_nonce('aksrt_submit'),
|
'nonce' => wp_create_nonce('aksrt_submit'),
|
||||||
'restUrl' => esc_url_raw(rest_url('aksrt/v1')),
|
'restUrl' => esc_url_raw(rest_url('aksrt/v1')),
|
||||||
'restNonce' => wp_create_nonce('wp_rest'),
|
'restNonce' => wp_create_nonce('wp_rest'),
|
||||||
|
'postId' => (int) ($ctx['edit_id'] ?? 0),
|
||||||
|
|
||||||
|
'mediaUrl' => esc_url_raw(rest_url('aksrt/v1/front-submit/media')),
|
||||||
|
'media' => [
|
||||||
|
'image' => ['url' => esc_url_raw(rest_url('aksrt/v1/front-submit/media/image'))],
|
||||||
|
'video' => ['url' => esc_url_raw(rest_url('aksrt/v1/front-submit/media/video'))],
|
||||||
|
'file' => ['url' => esc_url_raw(rest_url('aksrt/v1/front-submit/media/file'))],
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
'leaveWarning' => !empty($ctx['settings']['leave_warning']),
|
'leaveWarning' => !empty($ctx['settings']['leave_warning']),
|
||||||
'ai' => [
|
'ai' => [
|
||||||
'available' => !empty($ctx['ai_available']),
|
'available' => !empty($ctx['ai_available']),
|
||||||
@@ -167,7 +179,29 @@ if (file_exists($js)) {
|
|||||||
'invalidVideoUrl' => __('请填写有效的视频嵌入地址', 'aksrt'),
|
'invalidVideoUrl' => __('请填写有效的视频嵌入地址', 'aksrt'),
|
||||||
'invalidFile' => __('请选择有效文件', 'aksrt'),
|
'invalidFile' => __('请选择有效文件', 'aksrt'),
|
||||||
'uploadUnavailable' => __('当前账号不能上传此类文件', 'aksrt'),
|
'uploadUnavailable' => __('当前账号不能上传此类文件', 'aksrt'),
|
||||||
|
'mine' => __('我的媒体', 'aksrt'),
|
||||||
|
'mineImage' => __('我的图片', 'aksrt'),
|
||||||
|
'mineVideo' => __('我的视频', 'aksrt'),
|
||||||
|
'mineFile' => __('我的文件', 'aksrt'),
|
||||||
|
'search' => __('搜索', 'aksrt'),
|
||||||
|
'searchMedia' => __('搜索媒体(至少 2 个字符)', 'aksrt'),
|
||||||
|
'searchMin' => __('请输入至少 2 个字符进行搜索', 'aksrt'),
|
||||||
|
'clear' => __('清除', 'aksrt'),
|
||||||
|
'loadMore' => __('加载更多', 'aksrt'),
|
||||||
|
'loading' => __('加载中…', 'aksrt'),
|
||||||
|
'empty' => __('暂无媒体文件', 'aksrt'),
|
||||||
|
'emptyMedia' => __('暂无媒体文件', 'aksrt'),
|
||||||
|
'mediaError' => __('媒体加载失败', 'aksrt'),
|
||||||
|
'mediaUnavailable' => __('媒体库暂不可用', 'aksrt'),
|
||||||
|
'confirmSelected' => __('确认选择(%d)', 'aksrt'),
|
||||||
|
'maxItems' => __('最多选择 %d 个文件', 'aksrt'),
|
||||||
|
'maxMediaItems' => __('最多选择 %d 个文件', 'aksrt'),
|
||||||
|
'uploadingClose' => __('上传中,请稍候再关闭', 'aksrt'),
|
||||||
|
'mediaPickerUnavailable' => __('媒体选择器加载失败,请刷新页面后重试', 'aksrt'),
|
||||||
|
'pickVideoResource' => __('选择视频资源', 'aksrt'),
|
||||||
|
'pickVideoCover' => __('选择视频封面(可选)', 'aksrt'),
|
||||||
'download' => __('下载', 'aksrt'),
|
'download' => __('下载', 'aksrt'),
|
||||||
|
|
||||||
'quote' => __('引言', 'aksrt'),
|
'quote' => __('引言', 'aksrt'),
|
||||||
'quoteGray' => __('灰色引言', 'aksrt'),
|
'quoteGray' => __('灰色引言', 'aksrt'),
|
||||||
'quoteRed' => __('红色引言', 'aksrt'),
|
'quoteRed' => __('红色引言', 'aksrt'),
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ if ($edit_post) {
|
|||||||
$featured_args['data'] = [
|
$featured_args['data'] = [
|
||||||
'url' => (string) $post_meta['featured_video'],
|
'url' => (string) $post_meta['featured_video'],
|
||||||
'pic' => (string) ($post_meta['thumbnail_url'] ?? ''),
|
'pic' => (string) ($post_meta['thumbnail_url'] ?? ''),
|
||||||
|
'id' => absint($post_meta['featured_video_attachment_id'] ?? 0),
|
||||||
];
|
];
|
||||||
|
|
||||||
} elseif (!empty($post_meta['featured_slide'])) {
|
} elseif (!empty($post_meta['featured_slide'])) {
|
||||||
$slides = array_filter(array_map('trim', explode(',', (string) $post_meta['featured_slide'])));
|
$slides = array_filter(array_map('trim', explode(',', (string) $post_meta['featured_slide'])));
|
||||||
$slide_data = [];
|
$slide_data = [];
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ if (!function_exists('aksrt_submit_pay_line')) {
|
|||||||
<button type="button" class="flex1 name-link pay-dawnload-add-edit" data-pay-download-edit><?php echo esc_html((string) ($row['url'] ?? '')); ?></button>
|
<button type="button" class="flex1 name-link pay-dawnload-add-edit" data-pay-download-edit><?php echo esc_html((string) ($row['url'] ?? '')); ?></button>
|
||||||
<button type="button" class="pay-dawnload-add-delete em12 ml10 hollow but cir c-red" data-pay-download-remove aria-label="<?php esc_attr_e('删除资源', 'aksrt'); ?>"><i class="fa far fa-trash-alt" aria-hidden="true"></i></button>
|
<button type="button" class="pay-dawnload-add-delete em12 ml10 hollow but cir c-red" data-pay-download-remove aria-label="<?php esc_attr_e('删除资源', 'aksrt'); ?>"><i class="fa far fa-trash-alt" aria-hidden="true"></i></button>
|
||||||
<div class="hidden-input">
|
<div class="hidden-input">
|
||||||
<?php foreach (['url', 'extract_code', 'copy_key', 'copy_val', 'label', 'class'] as $field) : ?>
|
<?php foreach (['url', 'attachment_id', 'extract_code', 'copy_key', 'copy_val', 'label', 'class'] as $field) : ?>
|
||||||
|
|
||||||
<input type="hidden" name="aksrt_pay[downloads][<?php echo (int) $index; ?>][<?php echo esc_attr($field); ?>]" data-pay-download-field="<?php echo esc_attr($field); ?>" value="<?php echo esc_attr((string) ($row[$field] ?? '')); ?>">
|
<input type="hidden" name="aksrt_pay[downloads][<?php echo (int) $index; ?>][<?php echo esc_attr($field); ?>]" data-pay-download-field="<?php echo esc_attr($field); ?>" value="<?php echo esc_attr((string) ($row[$field] ?? '')); ?>">
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
const test = require('node:test');
|
||||||
|
|
||||||
|
const root = path.resolve(__dirname, '..', '..');
|
||||||
|
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||||
|
|
||||||
|
const frontSubmit = read('src/Core/FrontSubmit.php');
|
||||||
|
const mediaTrait = read('src/Core/FrontSubmitMedia.php');
|
||||||
|
const page = read('templates/pages/page-submit.php');
|
||||||
|
const editor = read('assets/js/front-submit-editor.js');
|
||||||
|
const submit = read('assets/js/front-submit.js');
|
||||||
|
const payDownload = read('assets/js/front-submit-pay-download.js');
|
||||||
|
const domainModel = read('src/Core/DomainModel.php');
|
||||||
|
const featured = read('src/Core/FrontSubmitFeatured.php');
|
||||||
|
const frontSubmitPay = read('src/Core/FrontSubmitPay.php');
|
||||||
|
|
||||||
|
const runtimeFiles = [
|
||||||
|
'assets/js/runtime/main.js',
|
||||||
|
'assets/js/runtime/main.min.js',
|
||||||
|
];
|
||||||
|
|
||||||
|
test('front submit owns the typed media REST routes and media trait wiring', () => {
|
||||||
|
assert.match(frontSubmit, /use FrontSubmitMedia\s*;/);
|
||||||
|
assert.match(frontSubmit, /add_action\('rest_api_init', \[\$this, 'register_rest_routes'\]\)/);
|
||||||
|
assert.match(frontSubmit, /add_action\('aksrt_new_add_posts', \[\$this, 'sync_post_attachments'\]\)/);
|
||||||
|
assert.match(frontSubmit, /add_action\('aksrt_new_edit_posts', \[\$this, 'sync_post_attachments'\]\)/);
|
||||||
|
|
||||||
|
assert.match(frontSubmit, /register_rest_route\('aksrt\/v1', '\/front-submit\/media\/\(\?P<type>image\|video\|file\)'/);
|
||||||
|
assert.match(frontSubmit, /'methods'\s*=>\s*\\WP_REST_Server::READABLE/);
|
||||||
|
assert.match(frontSubmit, /'callback'\s*=>\s*\[\$this, 'rest_list_attachments'\]/);
|
||||||
|
assert.match(frontSubmit, /'post_id'\s*=>\s*\[/);
|
||||||
|
assert.match(frontSubmit, /'permission_callback'\s*=>\s*\[\$this, 'can_list_attachments'\]/);
|
||||||
|
|
||||||
|
assert.match(frontSubmit, /register_rest_route\('aksrt\/v1', '\/front-submit\/upload\/\(\?P<kind>image\|video\|resource\)'/);
|
||||||
|
assert.match(frontSubmit, /'methods'\s*=>\s*'POST'/);
|
||||||
|
assert.match(frontSubmit, /'callback'\s*=>\s*\[\$this, 'rest_upload_attachment'\]/);
|
||||||
|
assert.match(frontSubmit, /'permission_callback'\s*=>\s*\[\$this, 'can_upload_attachment'\]/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('front submit media listing freezes ownership, visibility, ordering, paging, search, and MIME semantics', () => {
|
||||||
|
assert.match(mediaTrait, /\$user_id\s*=\s*get_current_user_id\(\)/);
|
||||||
|
assert.match(mediaTrait, /'post_type'\s*=>\s*'attachment'/);
|
||||||
|
assert.match(mediaTrait, /'post_status'\s*=>\s*\['inherit', 'private'\]/);
|
||||||
|
assert.match(mediaTrait, /'author'\s*=>\s*\$user_id/);
|
||||||
|
assert.match(mediaTrait, /'posts_per_page'\s*=>\s*48/);
|
||||||
|
assert.match(mediaTrait, /'paged'\s*=>\s*\$page/);
|
||||||
|
assert.match(mediaTrait, /'orderby'\s*=>\s*'ID'/);
|
||||||
|
assert.match(mediaTrait, /'order'\s*=>\s*'DESC'/);
|
||||||
|
assert.match(mediaTrait, /\$request->get_param\('search'\)/);
|
||||||
|
assert.match(mediaTrait, /\$request->get_param\('exclude'\)/);
|
||||||
|
assert.match(mediaTrait, /\$args\['post__not_in'\]\s*=\s*\$exclude/);
|
||||||
|
assert.match(mediaTrait, /\$args\['s'\]\s*=\s*\$search/);
|
||||||
|
assert.match(mediaTrait, /add_filter\('wp_allow_query_attachment_by_filename', '__return_true'\)/);
|
||||||
|
assert.match(mediaTrait, /remove_filter\('wp_allow_query_attachment_by_filename', '__return_true'\)/);
|
||||||
|
assert.match(mediaTrait, /\$args\['post_mime_type'\]\s*=\s*'image'/);
|
||||||
|
assert.match(mediaTrait, /\$args\['post_mime_type'\]\s*=\s*'video'/);
|
||||||
|
assert.doesNotMatch(mediaTrait, /\$args\['post_mime_type'\]\s*=\s*'file'/);
|
||||||
|
assert.match(mediaTrait, /return \$mime !== '';/);
|
||||||
|
assert.match(mediaTrait, /!in_array\(\$type, \['image', 'video', 'file'\], true\)/);
|
||||||
|
assert.match(frontSubmit, /\['image', 'video', 'file'\]/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('front submit media responses and uploads use the attachment contract', () => {
|
||||||
|
for (const field of ['attachment_id', 'id', 'url', 'filename', 'type', 'subtype', 'thumbnail_url', 'filesizeInBytes']) {
|
||||||
|
assert.match(mediaTrait, new RegExp(`['"]${field}['"]`), `missing normalized field ${field}`);
|
||||||
|
}
|
||||||
|
assert.match(mediaTrait, /wp_prepare_attachment_for_js/);
|
||||||
|
assert.match(mediaTrait, /'attachment_id'\s*=>\s*\(int\) \$attachment->ID/);
|
||||||
|
assert.match(mediaTrait, /'id'\s*=>\s*\(int\) \$attachment->ID/);
|
||||||
|
assert.match(mediaTrait, /'thumbnail_url'\s*=>/);
|
||||||
|
assert.match(mediaTrait, /'filesizeInBytes'\s*=>/);
|
||||||
|
|
||||||
|
assert.match(frontSubmit, /\$files\['file'\]/);
|
||||||
|
assert.match(frontSubmit, /media_handle_upload\('aksrt_front_submit_file'/);
|
||||||
|
assert.match(frontSubmit, /update_post_meta\(\$attachment_id, '_aksrt_front_submit_media', \$kind\)/);
|
||||||
|
assert.match(frontSubmit, /\$attachment\['success'\]\s*=\s*true/);
|
||||||
|
assert.match(editor, /body\.append\('file', file\)/);
|
||||||
|
assert.match(editor, /method:\s*'POST'/);
|
||||||
|
assert.match(editor, /payload\.success/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('saved submission media sync enforces ownership and cleans stale parent/meta relations', () => {
|
||||||
|
assert.match(mediaTrait, /if \(\$user_id <= 0 \|\| \(int\) \$post->post_author !== \$user_id\)/);
|
||||||
|
assert.match(mediaTrait, /'post_parent'\s*=>\s*0/);
|
||||||
|
assert.match(mediaTrait, /\(int\) \$attachment->post_author !== \$user_id/);
|
||||||
|
assert.match(mediaTrait, /\['inherit', 'private'\]/);
|
||||||
|
assert.match(mediaTrait, /\(int\) \$attachment->post_parent !== 0/);
|
||||||
|
assert.match(mediaTrait, /'meta_key'\s*=>\s*'_aksrt_front_submit_media'/);
|
||||||
|
assert.match(mediaTrait, /delete_post_meta\(\$attachment_id, '_aksrt_front_submit_media'\)/);
|
||||||
|
assert.match(mediaTrait, /update_post_meta\(\$attachment_id, '_aksrt_front_submit_media', \$kind\)/);
|
||||||
|
assert.match(mediaTrait, /featured_video_attachment_id/);
|
||||||
|
assert.match(mediaTrait, /data-aksrt-file-id/);
|
||||||
|
assert.match(mediaTrait, /\['post_parent'\s*=>\s*\$post_id\]/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('submit page configures media URLs, REST nonce, and the three media copy groups without mini-upload', () => {
|
||||||
|
assert.match(page, /'restNonce'\s*=>\s*wp_create_nonce\('wp_rest'\)/);
|
||||||
|
assert.match(page, /'mediaUrl'\s*=>\s*esc_url_raw\(rest_url\('aksrt\/v1\/front-submit\/media'\)\)/);
|
||||||
|
assert.match(page, /'image'\s*=>\s*\['url'\s*=>\s*esc_url_raw\(rest_url\('aksrt\/v1\/front-submit\/media\/image'\)\)\]/);
|
||||||
|
assert.match(page, /'video'\s*=>\s*\['url'\s*=>\s*esc_url_raw\(rest_url\('aksrt\/v1\/front-submit\/media\/video'\)\)\]/);
|
||||||
|
assert.match(page, /'file'\s*=>\s*\['url'\s*=>\s*esc_url_raw\(rest_url\('aksrt\/v1\/front-submit\/media\/file'\)\)\]/);
|
||||||
|
for (const copy of ['mineImage', 'mineVideo', 'mineFile', 'uploadImage', 'uploadVideo', 'uploadFile']) {
|
||||||
|
assert.match(page, new RegExp(`['"]${copy}['"]`), `missing copy ${copy}`);
|
||||||
|
}
|
||||||
|
assert.doesNotMatch(page, /aksrt-mini-upload/);
|
||||||
|
assert.equal(fs.existsSync(path.join(root, 'assets/js/mini-upload.js')), false, 'mini-upload.js must stay removed');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shared submit media picker exposes mine search paging selection and nonce-backed upload', () => {
|
||||||
|
assert.match(editor, /window\.AksrtSubmitMedia\s*=\s*\{\s*open:/);
|
||||||
|
assert.match(editor, /data-editor-media-tab="mine"/);
|
||||||
|
assert.match(editor, /data-editor-media-search/);
|
||||||
|
assert.match(editor, /state\.search\.length >= 2/);
|
||||||
|
assert.match(editor, /state\.search\.length === 1/);
|
||||||
|
assert.match(editor, /state\.searchTimer = window\.setTimeout/);
|
||||||
|
assert.match(editor, /state\.searchTimer[\s\S]*?600\)/);
|
||||||
|
assert.match(editor, /params\.set\('paged', String\(page\)\)/);
|
||||||
|
assert.match(editor, /data-editor-media-more/);
|
||||||
|
assert.match(editor, /params\.set\('exclude', state\.exclude\.join\(','\)\)/);
|
||||||
|
assert.match(editor, /multiple\s*=\s*options\.multiple/);
|
||||||
|
assert.match(editor, /if \(!multiple\) maxItems = 1/);
|
||||||
|
assert.match(editor, /state\.selected\.length >= maxItems/);
|
||||||
|
assert.match(editor, /headers:\s*\{\s*'X-WP-Nonce': cfg\.restNonce \|\| ''\s*\}/);
|
||||||
|
assert.match(editor, /data-aksrt-file-id/);
|
||||||
|
assert.doesNotMatch(editor, /data-download-file/);
|
||||||
|
assert.match(submit, /data-aksrt-submit-ready/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('paid downloads and featured covers use the shared picker and attachment IDs', () => {
|
||||||
|
assert.match(payDownload, /window\.AksrtSubmitMedia\.open\(/);
|
||||||
|
assert.match(payDownload, /type:\s*'file'/);
|
||||||
|
assert.match(payDownload, /item\.attachment_id \|\| item\.id/);
|
||||||
|
assert.match(payDownload, /data-pay-resource-field="attachment_id"/);
|
||||||
|
assert.match(domainModel, /'attachment_id'\s*=>\s*is_scalar\(\$row\['attachment_id'\]/);
|
||||||
|
assert.match(domainModel, /max\(0, \(int\) \$row\['attachment_id'\]\)/);
|
||||||
|
|
||||||
|
assert.match(submit, /openMediaPicker:\s*function \(options\)/);
|
||||||
|
assert.match(submit, /window\.AksrtSubmitMedia\.open\(options\)/);
|
||||||
|
assert.match(submit, /type:\s*'image'/);
|
||||||
|
assert.match(submit, /type:\s*'video'/);
|
||||||
|
assert.match(submit, /item\.attachment_id \|\| item\.id/);
|
||||||
|
assert.match(domainModel, /'featured_video_attachment_id'\s*=>\s*0/);
|
||||||
|
assert.match(payDownload, /attachment_id/);
|
||||||
|
assert.match(frontSubmitPay, /\$meta\['downloads'\]\s*=\s*\$downloads/);
|
||||||
|
assert.match(featured, /featured_video_attachment_id/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runtime bundles contain no legacy download-file action handler', () => {
|
||||||
|
for (const file of runtimeFiles) {
|
||||||
|
const source = read(file);
|
||||||
|
assert.doesNotMatch(source, /action=download_file/);
|
||||||
|
assert.doesNotMatch(source, /download-nonce/);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -47,7 +47,7 @@ test('front submit uses the registered local jQuery runtime', () => {
|
|||||||
assert.doesNotMatch(source, /jquery-migrate/);
|
assert.doesNotMatch(source, /jquery-migrate/);
|
||||||
|
|
||||||
const page = read('templates/pages/page-submit.php');
|
const page = read('templates/pages/page-submit.php');
|
||||||
assert.match(page, /\['loader_js', 'aksrt-front-submit-pay-download'\]/);
|
assert.match(page, /\['loader_js', 'aksrt-front-submit-editor', 'aksrt-front-submit-pay-download'\]/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('legacy runtime modules use jQuery explicitly without a dollar alias', () => {
|
test('legacy runtime modules use jQuery explicitly without a dollar alias', () => {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ test('front submit paid resources use the reference modal editor and compact lis
|
|||||||
assert.match(pay, /class="hidden-input"/);
|
assert.match(pay, /class="hidden-input"/);
|
||||||
assert.doesNotMatch(pay, /data-pay-download-upload|data-pay-download-tpl/);
|
assert.doesNotMatch(pay, /data-pay-download-upload|data-pay-download-tpl/);
|
||||||
|
|
||||||
['url', 'extract_code', 'copy_key', 'copy_val', 'label', 'class'].forEach((field) => {
|
['url', 'attachment_id', 'extract_code', 'copy_key', 'copy_val', 'label', 'class'].forEach((field) => {
|
||||||
assert.match(downloads, new RegExp(`['"]${field}['"]`));
|
assert.match(downloads, new RegExp(`['"]${field}['"]`));
|
||||||
assert.match(model, new RegExp(`['"]${field}['"]`));
|
assert.match(model, new RegExp(`['"]${field}['"]`));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ test('legacy theme action endpoint stays removed from runtime bundles', () => {
|
|||||||
assert.doesNotMatch(source, /_win\.bd\.on\(['"]click['"],\s*['"]\[data-action\]['"]/, file)
|
assert.doesNotMatch(source, /_win\.bd\.on\(['"]click['"],\s*['"]\[data-action\]['"]/, file)
|
||||||
assert.doesNotMatch(source, /\baction_ajax\s*\(/, file)
|
assert.doesNotMatch(source, /\baction_ajax\s*\(/, file)
|
||||||
assert.doesNotMatch(source, /\/action\/action\.php/, file)
|
assert.doesNotMatch(source, /\/action\/action\.php/, file)
|
||||||
|
assert.doesNotMatch(source, /action=download_file/, `${file} must not restore the legacy download-file handler`)
|
||||||
|
assert.doesNotMatch(source, /download-nonce/, `${file} must not restore the legacy download nonce attribute`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user