feat(extensions): 新增系统工具页面与前台投稿分片上传
This commit is contained in:
@@ -121,27 +121,100 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
var SPLIT_CHUNK_BYTES = 2 * 1048576;
|
||||
|
||||
function splitUploadConfig() {
|
||||
var split = cfg.splitUpload || {};
|
||||
var minSize = parseInt(split.minSize, 10);
|
||||
if (!isFinite(minSize) || minSize < 4) minSize = 4;
|
||||
return { enabled: !!split.enabled, minSize: minSize };
|
||||
}
|
||||
|
||||
function requestJson(url, options) {
|
||||
var init = options || {};
|
||||
init.credentials = 'same-origin';
|
||||
init.headers = { 'X-WP-Nonce': cfg.restNonce || '' };
|
||||
return fetch(url, init).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;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 分片上传:状态(断点续传)→ 逐片上传 → 合并走标准落库管线,响应与整传一致。
|
||||
function uploadInChunks(file, type) {
|
||||
var uploadKey = type === 'file' ? 'resource' : type;
|
||||
var base = String(cfg.restUrl || '').replace(/\/$/, '');
|
||||
if (!base) return Promise.reject(new Error(i18n.uploadUnavailable || '当前账号不能上传此类文件'));
|
||||
var postId = currentPostId();
|
||||
|
||||
function chunkUrl(action) {
|
||||
var target = base + '/front-submit/' + action + '/' + uploadKey;
|
||||
if (postId) target += (target.indexOf('?') === -1 ? '?' : '&') + 'post_id=' + encodeURIComponent(String(postId));
|
||||
return target;
|
||||
}
|
||||
|
||||
var total = Math.ceil(file.size / SPLIT_CHUNK_BYTES);
|
||||
return requestJson(chunkUrl('upload-status')).catch(function () { return { uploaded: [] }; }).then(function (status) {
|
||||
var uploaded = {};
|
||||
(Array.isArray(status.uploaded) ? status.uploaded : []).forEach(function (index) { uploaded[index] = true; });
|
||||
var chain = Promise.resolve();
|
||||
for (var index = 0; index < total; index++) {
|
||||
(function (i) {
|
||||
chain = chain.then(function () {
|
||||
if (uploaded[i]) return;
|
||||
var body = new FormData();
|
||||
body.append('file', file.slice(i * SPLIT_CHUNK_BYTES, Math.min(file.size, (i + 1) * SPLIT_CHUNK_BYTES)));
|
||||
body.append('chunk', String(i));
|
||||
body.append('chunks', String(total));
|
||||
body.append('name', file.name);
|
||||
body.append('size', String(file.size));
|
||||
return requestJson(chunkUrl('upload-chunk'), { method: 'POST', body: body });
|
||||
});
|
||||
})(index);
|
||||
}
|
||||
return chain;
|
||||
}).then(function () {
|
||||
var body = new FormData();
|
||||
body.append('chunks', String(total));
|
||||
body.append('name', file.name);
|
||||
body.append('size', String(file.size));
|
||||
return requestJson(chunkUrl('upload-merge'), { method: 'POST', body: body });
|
||||
}).then(function (payload) {
|
||||
if (!payload || !payload.success || !payload.url) {
|
||||
throw new Error((payload && (payload.message || (payload.data && payload.data.message))) || i18n.error || '文件上传失败');
|
||||
}
|
||||
return payload;
|
||||
});
|
||||
}
|
||||
|
||||
function uploadMedia(file, type) {
|
||||
var uploadKey = type === 'file' ? 'resource' : type;
|
||||
var upload = (cfg.uploads || {})[uploadKey] || null;
|
||||
if (!upload || !upload.url) return Promise.reject(new Error(i18n.uploadUnavailable || '当前账号不能上传此类文件'));
|
||||
if (!acceptsFile(file, type)) return Promise.reject(new Error(i18n.invalidFile || '请选择有效文件'));
|
||||
if (upload.maxSize && file.size > upload.maxSize) return Promise.reject(new Error(i18n.fileTooLarge || '文件大小超过限制'));
|
||||
var split = splitUploadConfig();
|
||||
if (split.enabled && typeof file.slice === 'function' && file.size > split.minSize * 1048576) {
|
||||
if (Math.ceil(file.size / SPLIT_CHUNK_BYTES) > 1) {
|
||||
return uploadInChunks(file, type);
|
||||
}
|
||||
}
|
||||
var body = new FormData();
|
||||
body.append('file', file);
|
||||
var uploadUrl = String(upload.url);
|
||||
if (currentPostId()) {
|
||||
uploadUrl += (uploadUrl.indexOf('?') === -1 ? '?' : '&') + 'post_id=' + encodeURIComponent(String(currentPostId()));
|
||||
}
|
||||
return fetch(uploadUrl, { method: 'POST', credentials: 'same-origin', headers: { 'X-WP-Nonce': cfg.restNonce || '' }, body: body })
|
||||
.then(function (response) {
|
||||
return response.json().catch(function () { return {}; }).then(function (payload) {
|
||||
if (!response.ok || !payload || !payload.success || !payload.url) {
|
||||
throw new Error((payload && (payload.message || (payload.data && payload.data.message))) || i18n.error || '文件上传失败');
|
||||
}
|
||||
return payload;
|
||||
});
|
||||
});
|
||||
return requestJson(uploadUrl, { method: 'POST', body: body }).then(function (payload) {
|
||||
if (!payload || !payload.success || !payload.url) {
|
||||
throw new Error((payload && (payload.message || (payload.data && payload.data.message))) || i18n.error || '文件上传失败');
|
||||
}
|
||||
return payload;
|
||||
});
|
||||
}
|
||||
|
||||
function imageBatchLimit() {
|
||||
@@ -578,6 +651,120 @@
|
||||
});
|
||||
}
|
||||
|
||||
function toggleBlockFormat(editor, format) {
|
||||
editor.formatter.toggle(format);
|
||||
editor.nodeChanged();
|
||||
}
|
||||
|
||||
function removeEditorBlock(editor, node) {
|
||||
if (!node || node === editor.getBody()) return;
|
||||
editor.dom.remove(node);
|
||||
editor.nodeChanged();
|
||||
}
|
||||
|
||||
function replaceButtonIconWithSvg(button, pathData) {
|
||||
var node = button && button.$el && button.$el[0];
|
||||
var icon = node ? node.querySelector('.mce-i-c-svg') : null;
|
||||
if (!icon) return;
|
||||
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 1024 1024');
|
||||
svg.setAttribute('style', 'width:21px;height:21px;fill:currentColor;');
|
||||
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('d', pathData);
|
||||
svg.appendChild(path);
|
||||
icon.parentNode.replaceChild(svg, icon);
|
||||
}
|
||||
|
||||
// 经典编辑器增强浮动工具栏(后台「扩展&增强 → 系统工具」mce_float_toolbar):
|
||||
// 复刻参考主题 editextend.js 的 wptoolbar 上下文工具条。
|
||||
function registerFloatToolbar(editor) {
|
||||
editor.addButton('aksrt_h2', {
|
||||
title: 'Heading 2',
|
||||
icon: 'c-svg heading2',
|
||||
stateSelector: 'h2',
|
||||
onclick: function () { toggleBlockFormat(editor, 'h2'); },
|
||||
onPostRender: function () {
|
||||
replaceButtonIconWithSvg(this, 'M143.616 219.648v228.864h278.016V219.648h89.856V768H421.632v-242.688H143.616V768H53.76V219.648h89.856z m660.48-10.752c52.992 0 96.768 15.36 131.328 46.08 33.792 30.72 50.688 69.888 50.688 119.04 0 47.616-18.432 90.624-53.76 129.792-16.554667 17.706667-43.093333 39.082667-78.933333 64.426667l-22.613334 15.701333-12.117333 8.192c-52.309333 34.389333-85.248 64.810667-99.413333 91.178667l-2.730667 5.589333h270.336V768h-382.464c0-56.064 17.664-104.448 54.528-145.92 8.746667-10.069333 21.76-22.186667 38.912-36.352l15.786667-12.586667c5.589333-4.352 11.52-8.874667 17.834666-13.568l19.84-14.506666 21.888-15.488 11.690667-8.106667c35.328-24.576 59.904-45.312 75.264-61.44 23.808-26.88 36.096-56.064 36.096-86.784 0-29.952-8.448-52.224-23.808-66.816-16.128-14.592-39.936-21.504-71.424-21.504-33.792 0-59.136 11.52-76.032 34.56-15.36 19.541333-24.362667 48.64-27.050667 86.058667l-0.597333 11.477333h-89.856c0.768-61.44 18.432-110.592 53.76-148.224 36.096-39.936 83.712-59.904 142.848-59.904z');
|
||||
},
|
||||
});
|
||||
|
||||
editor.addButton('aksrt_h3', {
|
||||
title: 'Heading 3',
|
||||
icon: 'c-svg heading3',
|
||||
stateSelector: 'h3',
|
||||
onclick: function () { toggleBlockFormat(editor, 'h3'); },
|
||||
onPostRender: function () {
|
||||
replaceButtonIconWithSvg(this, 'M801.024 208.896c55.296 0 100.608 13.056 134.4 39.936 33.024 26.88 49.92 63.744 49.92 111.36 0 59.904-30.72 99.84-91.392 119.808 32.256 9.984 57.6 24.576 74.496 44.544 18.432 20.736 27.648 47.616 27.648 79.872 0 50.688-17.664 92.16-52.992 124.416-36.864 33.024-85.248 49.92-145.152 49.92-56.832 0-102.912-14.592-137.472-43.776-38.4-32.256-59.904-79.872-64.512-141.312h91.392c1.536 35.328 12.288 62.976 33.792 82.176 19.2 17.664 44.544 26.88 76.032 26.88 34.56 0 62.208-9.984 82.176-29.184 17.664-17.664 26.88-39.168 26.88-65.28 0-31.488-9.984-54.528-28.416-69.12-18.432-15.36-45.312-22.272-80.64-22.272h-38.4V449.28h38.4c32.256 0 56.832-6.912 73.728-20.736 16.128-13.824 24.576-34.56 24.576-61.44 0-26.88-7.68-46.848-22.272-60.672-16.128-13.824-39.936-20.736-71.424-20.736-32.256 0-56.832 7.68-74.496 23.808-18.432 16.128-29.184 40.704-32.256 73.728h-88.32c4.608-55.296 24.576-98.304 61.44-129.024 34.56-30.72 79.104-45.312 132.864-45.312z m-657.408 10.752v228.864h278.016V219.648h89.856V768H421.632v-242.688H143.616V768H53.76V219.648h89.856z');
|
||||
},
|
||||
});
|
||||
|
||||
editor.addButton('aksrt_remove', {
|
||||
tooltip: i18n.removeBlock || '删除',
|
||||
icon: 'dashicon dashicons-no-alt',
|
||||
onclick: function () {
|
||||
removeEditorBlock(editor, editor.selection.getNode());
|
||||
},
|
||||
});
|
||||
|
||||
editor.on('wptoolbar', function (event) {
|
||||
if (event.toolbar) return;
|
||||
var probe = document.createElement('div');
|
||||
probe.innerHTML = editor.getContent();
|
||||
if (probe.querySelectorAll('p,h1,h2,h3,h4,h5,h6,ul,ol,div,blockquote,pre,img,iframe,table,hr').length <= 4) return;
|
||||
|
||||
var bars = floatToolbarButtons(editor, event);
|
||||
if (bars.length && editor.wp && typeof editor.wp._createToolbar === 'function') {
|
||||
event.toolbar = editor.wp._createToolbar(bars);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 复刻参考主题 get_all_toolbar:按光标所在块与是否选中文字给出上下文按钮。
|
||||
function floatToolbarButtons(editor, event) {
|
||||
var element = event.element;
|
||||
if (!element) return [];
|
||||
var nodeName = element.nodeName;
|
||||
var innerText = element.innerText ? String(element.innerText).trim() : '';
|
||||
var hasSelection = !!editor.selection.getContent();
|
||||
var parent = element.parentElement;
|
||||
var inRoot = !!(parent && parent.id === 'tinymce');
|
||||
var bars = [];
|
||||
|
||||
if (['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'UL', 'LI', 'STRONG', 'SPAN', 'CODE'].indexOf(nodeName) !== -1) {
|
||||
if (inRoot || (nodeName === 'SPAN' && parent && parent.parentElement && parent.parentElement.id === 'tinymce')) {
|
||||
bars.push('aksrt_h2', 'aksrt_h3');
|
||||
}
|
||||
|
||||
if (hasSelection) {
|
||||
bars = ['H1', 'H2', 'H3', 'H4', 'H5'].indexOf(nodeName) === -1 ? ['bold', 'link', 'wp_code'] : [];
|
||||
bars.push('forecolor');
|
||||
if (nodeName === 'P' && editorCfg.canHide) bars.push('aksrt_hide');
|
||||
bars.push('aksrt_remove');
|
||||
return bars;
|
||||
}
|
||||
|
||||
if (innerText) {
|
||||
bars.push('bullist', 'numlist', 'aligncenter');
|
||||
if (nodeName === 'P' && inRoot) {
|
||||
bars.push('aksrt_quote');
|
||||
if (editorCfg.canHide) bars.push('aksrt_hide');
|
||||
}
|
||||
bars.push('aksrt_remove');
|
||||
} else {
|
||||
bars.push('bullist', 'aksrt_quote');
|
||||
if (editorCfg.canHide) bars.push('aksrt_hide');
|
||||
if (editorCfg.canUploadImage) bars.push('aksrt_image');
|
||||
bars.push('aksrt_video', 'aksrt_precode');
|
||||
}
|
||||
}
|
||||
|
||||
if (!bars.length && (nodeName === 'DIV' || nodeName === 'PRE') && !(element.classList && element.classList.contains('file-download-name'))) {
|
||||
bars.push('aksrt_remove');
|
||||
}
|
||||
|
||||
return bars;
|
||||
}
|
||||
|
||||
function registerEditorTools() {
|
||||
if (!window.tinymce || !window.tinymce.PluginManager || window.aksrtSubmitEditorToolsRegistered) {
|
||||
return false;
|
||||
@@ -711,6 +898,10 @@
|
||||
menu: menu,
|
||||
});
|
||||
}
|
||||
|
||||
if (editorCfg.floatToolbar) {
|
||||
registerFloatToolbar(editor);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user