183 lines
7.3 KiB
JavaScript
183 lines
7.3 KiB
JavaScript
(function (root, factory) {
|
|
if (typeof module === 'object' && module.exports) {
|
|
module.exports = factory(root);
|
|
return;
|
|
}
|
|
root.AttachmentEditor = factory(root);
|
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
|
'use strict';
|
|
|
|
var editors = new Map();
|
|
|
|
function normalizeFiles(files) {
|
|
var seen = new Set();
|
|
return (files || []).map(function (file) {
|
|
var ossId = file && file.ossId;
|
|
if ((typeof ossId === 'number' && !Number.isSafeInteger(ossId)) || !/^[1-9][0-9]*$/.test(String(ossId))) {
|
|
throw new Error('原附件缺少有效的上传标识,暂不能保存,请刷新后重试');
|
|
}
|
|
return {
|
|
ossId: String(ossId),
|
|
fileName: String(file.fileName || '附件'),
|
|
mediaType: String(file.mediaType || '').toLowerCase(),
|
|
accessUrl: root.MediaDisplay ? (root.MediaDisplay.normalizeFileAccess(file) || {}).accessUrl || '' : ''
|
|
};
|
|
}).filter(function (file) {
|
|
if (seen.has(file.ossId)) return false;
|
|
seen.add(file.ossId);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
function release(files) {
|
|
files.forEach(function (file) {
|
|
if (file.objectUrl) root.URL.revokeObjectURL(file.objectUrl);
|
|
});
|
|
}
|
|
|
|
function getEditor(target) {
|
|
if (typeof target === 'string') target = root.document.querySelector(target);
|
|
return editors.get(target);
|
|
}
|
|
|
|
function sync(editor) {
|
|
editor.target.value = editor.files.map(function (file) { return file.ossId; }).join(',');
|
|
editor.preview.replaceChildren();
|
|
editor.files.forEach(function (file) {
|
|
var card = root.document.createElement('div');
|
|
var url = file.objectUrl || file.accessUrl;
|
|
var name = root.document.createElement('span');
|
|
var remove = root.document.createElement('button');
|
|
var media;
|
|
card.className = 'attachment-edit-item';
|
|
if (url && /^(image|video)(\/|$)/.test(file.mediaType)) {
|
|
media = root.document.createElement(/^image/.test(file.mediaType) ? 'img' : 'video');
|
|
media.src = url;
|
|
if (media.tagName === 'IMG') media.alt = file.fileName;
|
|
else { media.controls = true; media.preload = 'metadata'; }
|
|
media.addEventListener('error', function () {
|
|
media.hidden = true;
|
|
name.textContent = file.fileName + '(预览不可用,附件仍保留)';
|
|
});
|
|
card.appendChild(media);
|
|
}
|
|
name.className = 'attachment-edit-name';
|
|
name.textContent = file.fileName;
|
|
card.appendChild(name);
|
|
remove.type = 'button';
|
|
remove.className = 'pill attachment-edit-remove';
|
|
remove.textContent = '移除';
|
|
remove.setAttribute('aria-label', '移除 ' + file.fileName);
|
|
remove.disabled = editor.busy || editor.input.disabled;
|
|
// 相册接口尚不支持可靠清空封面,仅允许替换。
|
|
if (editor.input.hasAttribute('data-attachment-replace-only')) {
|
|
remove.hidden = true;
|
|
}
|
|
remove.addEventListener('click', function () {
|
|
if (editor.busy || editor.input.disabled) return;
|
|
editor.files = editor.files.filter(function (existing) { return existing !== file; });
|
|
release([file]);
|
|
editor.cleared = true;
|
|
sync(editor);
|
|
});
|
|
card.appendChild(remove);
|
|
editor.preview.appendChild(card);
|
|
});
|
|
if (editor.status && !editor.busy) {
|
|
editor.status.textContent = editor.files.length
|
|
? '已选择 ' + editor.files.length + ' 个文件;移除或替换将在保存后生效'
|
|
: (editor.cleared ? '附件已移除,保存后生效' : '未选择文件');
|
|
}
|
|
}
|
|
|
|
function init() {
|
|
root.document.querySelectorAll('[data-attachment-editor]').forEach(function (input) {
|
|
var target = root.document.querySelector(input.getAttribute('data-upload-target'));
|
|
var preview;
|
|
var editor;
|
|
if (!target || editors.has(target)) return;
|
|
preview = root.document.createElement('div');
|
|
preview.className = 'attachment-edit-list';
|
|
preview.setAttribute('aria-label', '已选择的附件');
|
|
input.insertAdjacentElement('afterend', preview);
|
|
editor = {
|
|
target: target, input: input, preview: preview,
|
|
status: root.document.querySelector(input.getAttribute('data-upload-status')),
|
|
multiple: input.getAttribute('data-upload-multiple') === 'true',
|
|
files: [], cleared: false, busy: false
|
|
};
|
|
editors.set(target, editor);
|
|
new root.MutationObserver(function () {
|
|
preview.querySelectorAll('button').forEach(function (button) { button.disabled = input.disabled || editor.busy; });
|
|
}).observe(input, { attributes: true, attributeFilter: ['disabled'] });
|
|
});
|
|
}
|
|
|
|
function setFiles(target, files) {
|
|
init();
|
|
var editor = getEditor(target);
|
|
if (!editor) throw new Error('附件编辑控件未初始化');
|
|
editor.invalid = true;
|
|
var normalized = normalizeFiles(files);
|
|
release(editor.files);
|
|
editor.files = normalized;
|
|
editor.cleared = false;
|
|
editor.invalid = false;
|
|
sync(editor);
|
|
}
|
|
|
|
function addUploaded(target, upload, file) {
|
|
var editor = getEditor(target);
|
|
if (!editor) return false;
|
|
var attachment = normalizeFiles([{ ossId: upload.ossId, fileName: file.name, mediaType: file.type }])[0];
|
|
if (editor.files.some(function (existing) { return existing.ossId === attachment.ossId; })) return true;
|
|
attachment.objectUrl = root.URL.createObjectURL(file);
|
|
if (!editor.multiple) { release(editor.files); editor.files = []; }
|
|
editor.files.push(attachment);
|
|
editor.cleared = false;
|
|
sync(editor);
|
|
return true;
|
|
}
|
|
|
|
function clear(target) {
|
|
var editor = getEditor(target);
|
|
if (!editor || editor.busy || editor.input.disabled) return;
|
|
release(editor.files);
|
|
editor.files = [];
|
|
editor.cleared = true;
|
|
sync(editor);
|
|
}
|
|
|
|
function readField(field) {
|
|
var editor = getEditor(field);
|
|
// nullable 单封面 PATCH:空值仅在用户主动移除时提交 null。
|
|
if (editor && !editor.multiple && editor.cleared && !editor.files.length) return null;
|
|
return field.value;
|
|
}
|
|
|
|
function setBusy(target, busy) {
|
|
var editor = getEditor(target);
|
|
if (!editor) return;
|
|
editor.busy = busy;
|
|
editor.preview.querySelectorAll('button').forEach(function (button) { button.disabled = busy || editor.input.disabled; });
|
|
}
|
|
|
|
if (root.document) {
|
|
root.document.addEventListener('DOMContentLoaded', init);
|
|
root.document.addEventListener('submit', function (event) {
|
|
var pending = Array.from(editors.values()).some(function (editor) { return editor.input.form === event.target && editor.busy; });
|
|
var invalid = Array.from(editors.values()).some(function (editor) { return editor.input.form === event.target && editor.invalid; });
|
|
if (!pending && !invalid) return;
|
|
event.preventDefault();
|
|
event.stopImmediatePropagation();
|
|
var status = event.target.querySelector('.upload-status');
|
|
if (status) status.textContent = invalid ? '原附件读取不完整,请刷新后重试,暂不能保存' : '文件正在上传,请等待上传完成后再保存';
|
|
}, true);
|
|
root.addEventListener('pagehide', function (event) {
|
|
if (!event.persisted) editors.forEach(function (editor) { release(editor.files); });
|
|
});
|
|
}
|
|
|
|
return { init: init, normalizeFiles: normalizeFiles, setFiles: setFiles, addUploaded: addUploaded, clear: clear, readField: readField, setBusy: setBusy };
|
|
});
|