Files
wordpress-theme-aksrt/assets/js/front-submit-editor.js
T

924 lines
49 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Front-submit TinyMCE tools matching the reference article editor.
*
* @package AKSRT
*/
(function (window, document) {
'use strict';
var cfg = window.aksrtSubmitData || {};
var editorCfg = cfg.editor || {};
var i18n = cfg.i18n || {};
var activeModal = null;
function notify(message, type, duration, id) {
if (typeof window.notyf === 'function') {
window.notyf(
message,
type,
duration === undefined ? 5 : duration,
id || 'aksrt-front-submit-editor-media-toast'
);
}
}
function escapeAttr(value) {
return String(value)
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
function validHttpUrl(value) {
var raw = String(value).trim();
if (!/^https?:\/\//i.test(raw)) {
return '';
}
try {
var url = new URL(raw);
return url.protocol === 'http:' || url.protocol === 'https:' ? url.href : '';
} catch (error) {
return '';
}
}
function imageMarkup(url, attachmentId, alt, fullUrl) {
var safeUrl = validHttpUrl(url);
var safeFullUrl = validHttpUrl(fullUrl || url);
if (!safeUrl) {
return '';
}
var idAttr = attachmentId ? ' data-edit-file-id="' + Number(attachmentId) + '"' : '';
var altAttr = alt ? ' alt="' + escapeAttr(alt) + '"' : ' alt=""';
var fullAttr = safeFullUrl ? ' data-full-url="' + escapeAttr(safeFullUrl) + '"' : '';
return '<p><img src="' + escapeAttr(safeUrl) + '"' + idAttr + altAttr + fullAttr + '></p>';
}
function videoMarkup(url, attachmentId, name) {
var safeUrl = validHttpUrl(url);
if (!safeUrl) {
return '';
}
var idAttr = attachmentId ? ' data-edit-file-id="' + Number(attachmentId) + '"' : '';
return '<div contenteditable="false" data-video-url="' + escapeAttr(safeUrl) +
'" data-video-name="' + escapeAttr(name || safeUrl) + '"' + idAttr +
' class="new-dplayer post-dplayer dplayer"></div>';
}
function fileMarkup(url, attachmentId, name) {
var safeUrl = validHttpUrl(url);
if (!safeUrl) {
return '';
}
var filename = String(name || safeUrl.split('/').pop() || i18n.attachment || '附件');
var idAttr = attachmentId ? ' data-aksrt-file-id="' + Number(attachmentId) + '"' : '';
return '<div class="mb20 muted-box file-download-box" contenteditable="false">' +
'<div class="flex ac jsb"><div class="flex ac"><div class="mr10">' +
'<div class="file-download-icon"><i class="fa fas fa-file-alt" aria-hidden="true"></i></div></div>' +
'<div class="text-ellipsis-2 file-download-name" contenteditable="true">' + escapeAttr(filename) + '</div>' +
'</div><div class="flex0 ml20"><a href="' + escapeAttr(safeUrl) + '" data-mce-href="' +
escapeAttr(safeUrl) + '"' + idAttr + ' class="but c-blue file-download-btn">' +
'<i class="fa fas fa-download" aria-hidden="true"></i>' + escapeAttr(i18n.download || '下载') +
'</a></div></div></div>';
}
function embedVideoMarkup(value) {
var holder = document.createElement('div');
holder.innerHTML = String(value).trim();
var iframe = holder.querySelector('iframe');
var safeUrl = validHttpUrl(iframe ? iframe.getAttribute('src') : value);
if (!safeUrl) {
return '';
}
return '<div contenteditable="false" class="wp-block-embed is-type-video mb20">' +
'<div class="iframe-absbox" style="padding-bottom:56%;"><iframe src="' + escapeAttr(safeUrl) +
'" allowfullscreen="allowfullscreen" framespacing="0" border="0" width="100%" frameborder="no"></iframe>' +
'</div></div>';
}
function mediaUrl(type) {
var configured = cfg.media && cfg.media[type] && cfg.media[type].url;
if (configured) {
return String(configured).indexOf('{type}') !== -1
? String(configured).replace('{type}', encodeURIComponent(type))
: String(configured);
}
var base = cfg.mediaUrl || '';
if (String(base).indexOf('{type}') !== -1) return String(base).replace('{type}', encodeURIComponent(type));
if (base) return String(base).replace(/\/$/, '') + '/' + encodeURIComponent(type);
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) {
if (!file) return false;
if (type === 'image') return /^image\//.test(file.type || '');
if (type === 'video') return /^video\//.test(file.type || '');
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 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() {
// 0 = 不限制(沿用默认 100 的选取上限),1 = 不允许批量上传。
var configured = Number(cfg.imageUploadMultiple);
if (!isFinite(configured) || configured <= 0) {
return { multiple: true, maxItems: 100 };
}
if (configured === 1) {
return { multiple: false, maxItems: 1 };
}
return { multiple: true, maxItems: Math.min(configured, 100) };
}
function mediaDefinition(type) {
if (type === 'video') return {
title: i18n.insertVideo || '插入视频', mineTab: i18n.mineVideo || '我的视频', uploadTab: i18n.uploadVideo || '上传视频',
externalTab: i18n.externalVideo || '外链视频', embedTab: i18n.embedVideo || '嵌入视频', accept: 'video/*', multiple: false, maxItems: 1,
canUpload: !!editorCfg.canUploadVideo && !!((cfg.uploads || {}).video), canEmbed: !!editorCfg.canEmbedVideo,
select: i18n.selectVideo || '选择或拖入视频', hint: i18n.videoUploadHint || '上传完成后自动插入正文', externalHint: i18n.externalVideoHint || '请填写视频文件地址:',
};
if (type === 'file') return {
title: i18n.insertFile || '上传附件', mineTab: i18n.mineFile || '我的文件', uploadTab: i18n.uploadFile || '上传附件', externalTab: i18n.externalFile || '输入地址',
accept: '*/*', multiple: false, maxItems: 1, canUpload: !!editorCfg.canUploadFile && !!((cfg.uploads || {}).resource), canEmbed: false,
select: i18n.selectFile || '选择或拖入附件', hint: i18n.fileUploadHint || '上传完成后自动插入正文', externalHint: i18n.externalFileHint || '请填写附件地址:',
};
var batch = imageBatchLimit();
return {
title: i18n.insertImage || '插入图片', mineTab: i18n.mineImage || '我的图片', uploadTab: i18n.uploadImage || '上传图片', externalTab: i18n.externalImage || '外链图片',
accept: 'image/gif,image/jpeg,image/png,image/webp', multiple: batch.multiple, maxItems: batch.maxItems, canUpload: !!editorCfg.canUploadImage && !!((cfg.uploads || {}).image), canEmbed: false,
select: i18n.selectImages || '选择或拖入图片', hint: i18n.imageUploadHint || '可批量选择,上传完成后自动插入正文', externalHint: i18n.externalImageHint || '每行填写一个图片地址:',
};
}
function mediaMarkup(type, item) {
var id = item.attachment_id || item.id;
if (type === 'video') return videoMarkup(item.url, id, item.filename || item.name || '');
if (type === 'file') return fileMarkup(item.url, id, item.filename || item.name || '');
return imageMarkup(item.large_url || item.url, id, item.name || item.title || '', item.url);
}
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();
if (activeModal) return;
}
var type = ['image', 'video', 'file'].indexOf(options.type) === -1 ? 'image' : options.type;
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');
root.className = 'aksrt-editor-media';
root.setAttribute('data-editor-media-type', type);
root.setAttribute('role', 'dialog'); root.setAttribute('aria-modal', 'true'); root.setAttribute('aria-labelledby', 'aksrt-editor-media-title');
root.innerHTML =
'<button type="button" class="aksrt-editor-media__backdrop" data-editor-media-close tabindex="-1" aria-label="' + escapeAttr(i18n.close || '关闭') + '"></button>' +
'<div class="aksrt-editor-media__dialog" tabindex="-1"><button type="button" class="aksrt-editor-media__close" data-editor-media-close aria-label="' + escapeAttr(i18n.close || '关闭') + '">&times;</button>' +
'<h3 id="aksrt-editor-media-title" class="aksrt-editor-media__title">' + escapeAttr(options.title || definition.title) + '</h3>' +
'<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>' +
(canUpload ? '<button type="button" role="tab" aria-selected="false" data-editor-media-tab="upload">' + escapeAttr(definition.uploadTab) + '</button>' : '') +
(allowExternal ? '<button type="button" role="tab" aria-selected="false" data-editor-media-tab="external">' + escapeAttr(definition.externalTab) + '</button>' : '') +
(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__panels"><section class="aksrt-editor-media__panel is-active" data-editor-media-panel="mine">' +
'<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>' +
'<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>' +
(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>' : '') +
(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="&lt;iframe src=&quot;https://...&quot;&gt;&lt;/iframe&gt;" data-editor-media-embed></textarea><button type="button" class="but c-blue aksrt-editor-media__submit" data-editor-media-embed-submit>' + escapeAttr(i18n.confirmEmbed || '确认嵌入') + '</button></section>' : '') + '</div></div>';
document.body.appendChild(root); document.body.classList.add('aksrt-editor-media-open');
var state = { root: root, uploading: false, loading: false, requestId: 0, searchTimer: null, previousFocus: document.activeElement, page: 0, totalPages: 0, search: '', items: [], pinned: [], selected: [], exclude: [] };
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 search = root.querySelector('[data-editor-media-search]'), fileInput = root.querySelector('[data-editor-media-files]'), 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 = [];
function statusText(text, cls) { if (!status) return; status.className = 'aksrt-editor-media__status' + (cls ? ' is-' + cls : ''); status.textContent = text || ''; }
function itemId(item) { return Number(item.attachment_id || item.id || 0); }
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 renderItem(item) {
var id = itemId(item), index = selectedIndex(id), chosen = index !== -1, disabled = !chosen && state.selected.length >= maxItems;
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>';
}
function renderList() {
if (!list) return;
list.innerHTML = state.items.length ? state.items.map(renderItem).join('') : '';
if (more) more.hidden = state.loading || state.page >= state.totalPages || !state.items.length;
if (confirm) {
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) {
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) {
var valid = acceptsFile(file, type) && (!upload.maxSize || file.size <= upload.maxSize);
rejected = rejected || !valid;
return valid;
});
if (rejected) notify(i18n.invalidFile || i18n.fileTooLarge || '部分文件类型或大小不符合要求', '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');
}
renderSelection();
}
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) {
var close = event.target.closest('[data-editor-media-close]'); if (close) { event.preventDefault(); closeMediaModal(); return; }
var tab = event.target.closest('[data-editor-media-tab]'); if (tab) { var name = tab.getAttribute('data-editor-media-tab'); root.querySelectorAll('[data-editor-media-tab]').forEach(function (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; }
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; }
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;
}
request(1, true);
}, 600);
});
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(); }; state.onKeydown = onKeydown; activeModal = state; document.addEventListener('keydown', onKeydown);
var focusTarget = root.querySelector('.aksrt-editor-media__dialog'); if (focusTarget) focusTarget.focus({ preventScroll: true }); request(1, true);
}
window.AksrtSubmitMedia = { open: openMediaModal, close: function () { closeMediaModal(); }, upload: uploadMedia };
function selectedContent(editor) {
var content = String(editor.selection.getContent() || '').trim();
return content ? '<p>' + content + '</p>' : '<p>' + escapeAttr(i18n.hiddenPlaceholder || '隐藏内容') + '</p>';
}
function insertHiddenContent(editor, item) {
var node = editor.selection.getNode();
var content = selectedContent(editor);
var suffix = '<p></p>';
if (node && editor.dom.hasClass(node, 'tinymce-hide')) {
var editable = node.querySelector('[contenteditable="true"]');
content = editable ? editable.innerHTML : content;
editor.dom.remove(node);
suffix = '';
}
editor.insertContent(
'<div class="tinymce-hide" data-aksrt-hide-type="' + escapeAttr(item.type) + '" contenteditable="false">' +
'<p class="hide-before">' + escapeAttr(item.open) + '</p>' +
'<div contenteditable="true">' + content + '</div>' +
'<p class="hide-after">' + escapeAttr(item.close) + '</p>' +
'</div>' + suffix
);
editor.nodeChanged();
}
function insertQuote(editor, colorClass) {
var node = editor.selection.getNode();
var quote = editor.dom.getParent(node, '.quote_q');
if (quote) {
quote.className = 'quote_q quote-mce' + (colorClass ? ' ' + colorClass : '');
editor.nodeChanged();
return;
}
var content = String(editor.selection.getContent() || '').trim();
if (node && node.nodeName === 'P' && node.parentNode === editor.getBody()) {
content = node.outerHTML;
editor.dom.remove(node);
} else {
content = content ? '<p>' + content + '</p>' : '<p></p>';
}
editor.insertContent(
'<div class="quote_q quote-mce' + (colorClass ? ' ' + colorClass : '') +
'" contenteditable="false"><div contenteditable="true">' + content + '</div></div><p></p>'
);
editor.nodeChanged();
}
var codeLanguages = [
['yaml', 'yaml'],
['xml/html', 'xml'],
['visualbasic', 'visualbasic'],
['vhdl', 'vhdl'],
['typescript', 'typescript'],
['swift', 'swift'],
['sql', 'sql'],
['shell', 'shell'],
['scss/sass', 'scss'],
['rust', 'rust'],
['ruby', 'ruby'],
['python', 'python'],
['powershell', 'powershell'],
['php', 'php'],
['markdown', 'markdown'],
['lua', 'lua'],
['less', 'less'],
['kotlin', 'kotlin'],
['json', 'json'],
['javascript', 'javascript'],
['java', 'java'],
['ini/conf', 'ini'],
['go/golang', 'go'],
['docker', 'dockerfile'],
['diff', 'diff'],
['css', 'css'],
['csharp', 'csharp'],
['Cpp/C++/C', 'cpp'],
['assembly', 'assembly'],
[i18n.codeGeneric || '通用高亮', 'generic'],
];
var codeThemes = [
['enlighter', 'enlighter'],
['classic', 'classic'],
['beyond', 'beyond'],
['mowtwo', 'mowtwo'],
['eclipse', 'eclipse'],
['droide', 'droide'],
['minimal', 'minimal'],
['atomic', 'atomic'],
['dracula', 'dracula'],
['bootstrap4', 'bootstrap4'],
['rowhammer', 'rowhammer'],
['godzilla', 'godzilla'],
[i18n.codeFollowGlobal || '跟随全局设置', 'qj'],
];
function listboxValues(items) {
return items.map(function (item) {
return { text: item[0], value: item[1] };
});
}
function bindCharacterCount(editor) {
var counter = null;
function getCounter() {
var container = editor.getContainer();
var statusbar = container && container.querySelector('.mce-statusbar .mce-container-body');
if (!statusbar) {
return null;
}
counter = counter || statusbar.querySelector('[data-submit-word-count]');
if (!counter) {
counter = document.createElement('span');
counter.className = 'mce-wordcount aksrt-submit-word-count';
counter.setAttribute('data-submit-word-count', '');
counter.setAttribute('aria-live', 'polite');
statusbar.appendChild(counter);
}
return counter;
}
function updateCount() {
var count = String((editor.getBody() && editor.getBody().textContent) || '')
.replace(/\s+/g, '').length;
var element = getCounter();
if (element) {
element.textContent = '字数:' + count;
}
}
editor.on('init SetContent input keyup change Undo Redo', updateCount);
}
function openCodeModal(editor) {
var selected = editor.dom.getParent(editor.selection.getNode(), 'pre.enlighter-pre');
var code = selected ? selected.querySelector('code') : null;
var width = window.innerWidth > 800 ? Math.round(window.innerWidth * 0.5) :
(window.innerWidth > 640 ? Math.round(window.innerWidth * 0.7) : window.innerWidth - 20);
var height = Math.round(window.innerHeight * 0.6);
editor.windowManager.open({
title: (selected ? (i18n.edit || '编辑') : (i18n.insert || '插入')) +
(i18n.highlightCode || '高亮代码'),
width: width,
height: height,
body: [
{
type: 'listbox',
name: 'language',
label: i18n.selectLanguage || '选择语言',
value: code ? (code.getAttribute('data-enlighter-language') || 'generic') : 'generic',
values: listboxValues(codeLanguages),
},
{
type: 'listbox',
name: 'theme',
label: i18n.codeTheme || '主题',
value: code ? (code.getAttribute('data-enlighter-theme') || 'qj') : 'qj',
values: listboxValues(codeThemes),
},
{
type: 'textbox',
name: 'content',
label: i18n.codeLabel || '代码:',
value: code ? (code.textContent || '') : '',
multiline: true,
minHeight: Math.max(160, height - 115),
},
],
onsubmit: function (event) {
var content = String(event.data.content || '').replace(/\r\n/g, '\n');
if (!content) {
return;
}
var encoded = window.tinymce.html.Entities.encodeAllRaw(content);
var language = event.data.language === 'generic' ? '' :
' data-enlighter-language="' + escapeAttr(event.data.language) + '"';
var theme = event.data.theme === 'qj' ? '' :
' data-enlighter-theme="' + escapeAttr(event.data.theme) + '"';
var markup = '<pre contenteditable="false" class="enlighter-pre"><code class="gl"' +
language + theme + '>' + encoded + '</code></pre>';
if (selected) {
editor.dom.setOuterHTML(selected, markup);
} else {
editor.insertContent(markup + '<p></p>');
}
editor.nodeChanged();
editor.undoManager.add();
},
});
}
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;
}
window.aksrtSubmitEditorToolsRegistered = true;
window.tinymce.PluginManager.add('aksrt_editor_tools', function (editor) {
bindCharacterCount(editor);
if (editorCfg.canUploadImage) {
var imageBatch = imageBatchLimit();
editor.addButton('aksrt_image', {
text: '',
icon: 'image',
tooltip: i18n.insertImage || '插入图片',
onclick: function () {
window.AksrtSubmitMedia.open({
type: 'image', multiple: imageBatch.multiple, maxItems: imageBatch.maxItems,
onSelect: function (items) {
editor.insertContent(items.map(function (item) { return mediaMarkup('image', item); }).join('') + '<p></p>');
editor.nodeChanged();
},
});
},
});
}
editor.addButton('aksrt_video', {
text: '',
icon: 'media',
tooltip: i18n.insertVideo || '插入视频',
onclick: function () {
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();
},
});
},
});
if (editorCfg.canUploadFile) {
editor.addButton('aksrt_file', {
text: '',
icon: 'upload',
tooltip: i18n.insertFile || '上传附件',
onclick: function () {
window.AksrtSubmitMedia.open({
type: 'file', multiple: false,
onSelect: function (item) {
editor.insertContent(mediaMarkup('file', item) + '<p></p>');
editor.nodeChanged();
},
});
},
});
}
editor.addButton('aksrt_quote', {
text: '',
icon: 'blockquote',
tooltip: i18n.quote || '引言',
type: 'menubutton',
stateSelector: '.quote_q',
menu: [
{ text: i18n.quoteGray || '灰色', onclick: function () { insertQuote(editor, ''); } },
{ text: i18n.quoteRed || '红色', onclick: function () { insertQuote(editor, 'qe_wzk_c-red'); } },
{ text: i18n.quoteBlue || '蓝色', onclick: function () { insertQuote(editor, 'qe_wzk_lan'); } },
{ text: i18n.quoteGreen || '绿色', onclick: function () { insertQuote(editor, 'qe_wzk_lv'); } },
],
});
editor.addButton('aksrt_precode', {
text: '',
icon: 'code',
tooltip: i18n.highlightCode || '高亮代码',
stateSelector: 'pre.enlighter-pre',
onclick: function () {
openCodeModal(editor);
},
});
if (editorCfg.canHide) {
var menu = [
{
text: i18n.replyVisible || '回复可见',
type: 'reply',
open: '[reply]',
close: '[/reply]',
},
{
text: i18n.loginVisible || '登录可见',
type: 'login',
open: '[aksrt_hide mode="login"]',
close: '[/aksrt_hide]',
},
{
text: i18n.vipVisible || 'VIP 可见',
type: 'vip',
open: '[vip]',
close: '[/vip]',
},
];
if (editorCfg.canPay) {
menu.push({
text: i18n.payVisible || '付费可见',
type: 'payshow',
open: '[payshow]',
close: '[/payshow]',
});
}
menu = menu.map(function (item) {
return {
text: item.text,
onclick: function () {
insertHiddenContent(editor, item);
},
};
});
editor.addButton('aksrt_hide', {
text: '',
icon: 'preview',
tooltip: i18n.hiddenContent || '隐藏内容',
type: 'menubutton',
stateSelector: '.tinymce-hide',
menu: menu,
});
}
if (editorCfg.floatToolbar) {
registerFloatToolbar(editor);
}
});
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);