修改功能
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
(function (root, factory) {
|
||||
if (typeof module === 'object' && module.exports) {
|
||||
module.exports = factory(root);
|
||||
return;
|
||||
}
|
||||
|
||||
root.MediaDisplay = factory(root);
|
||||
if (root.document) {
|
||||
root.document.addEventListener('DOMContentLoaded', function () {
|
||||
root.MediaDisplay.init(root.document);
|
||||
});
|
||||
}
|
||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||
'use strict';
|
||||
|
||||
function stringValue(value) {
|
||||
return value === undefined || value === null ? '' : String(value);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return stringValue(value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function normalizeId(value) {
|
||||
var text;
|
||||
|
||||
if (value === undefined || value === null || value === '') return '';
|
||||
if (typeof value === 'number' && !Number.isSafeInteger(value)) return '';
|
||||
text = String(value).trim();
|
||||
return /^[1-9][0-9]*$/.test(text) ? text : '';
|
||||
}
|
||||
|
||||
function normalizeAccessUrl(value) {
|
||||
var url;
|
||||
|
||||
try {
|
||||
url = new URL(stringValue(value).trim());
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : '';
|
||||
}
|
||||
|
||||
function normalizeFileAccess(value) {
|
||||
var source = value || {};
|
||||
var accessUrl = normalizeAccessUrl(source.accessUrl);
|
||||
var fileId = normalizeId(source.fileId);
|
||||
var fileSize;
|
||||
var normalized;
|
||||
|
||||
if (!accessUrl) return null;
|
||||
if (source.fileId !== undefined && source.fileId !== null && source.fileId !== '' && !fileId) return null;
|
||||
normalized = {
|
||||
fileId: fileId,
|
||||
fileName: stringValue(source.fileName).trim(),
|
||||
mediaType: stringValue(source.mediaType).trim().toLowerCase(),
|
||||
accessUrl: accessUrl,
|
||||
expiresAt: stringValue(source.expiresAt).trim()
|
||||
};
|
||||
fileSize = Number(source.fileSize);
|
||||
if (source.fileSize !== undefined && source.fileSize !== null && source.fileSize !== '' &&
|
||||
Number.isSafeInteger(fileSize) && fileSize >= 0) {
|
||||
normalized.fileSize = fileSize;
|
||||
}
|
||||
|
||||
if (!normalized.fileId) delete normalized.fileId;
|
||||
if (!normalized.fileName) delete normalized.fileName;
|
||||
if (!normalized.mediaType) delete normalized.mediaType;
|
||||
if (!normalized.expiresAt) delete normalized.expiresAt;
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function normalizeFileList(values) {
|
||||
if (!Array.isArray(values)) return [];
|
||||
return values.map(normalizeFileAccess).filter(Boolean);
|
||||
}
|
||||
|
||||
function classNames(base, value) {
|
||||
var additions = stringValue(value).split(/\s+/).filter(function (name) {
|
||||
return /^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name);
|
||||
});
|
||||
|
||||
return [base].concat(additions).join(' ');
|
||||
}
|
||||
|
||||
function renderImage(value, options) {
|
||||
var file = normalizeFileAccess(value);
|
||||
var settings = options || {};
|
||||
var alt;
|
||||
var loading;
|
||||
var caption;
|
||||
|
||||
if (!file) return '';
|
||||
alt = stringValue(settings.alt || file.fileName || '图片').trim();
|
||||
loading = settings.eager === true ? 'eager' : 'lazy';
|
||||
caption = stringValue(settings.caption).trim();
|
||||
return '<figure class="' + escapeHtml(classNames('media-frame', settings.className)) +
|
||||
'" data-media-kind="image"><button class="media-preview" type="button" data-media-preview-url="' +
|
||||
escapeHtml(file.accessUrl) + '" data-media-preview-alt="' + escapeHtml(alt) + '">' +
|
||||
'<img src="' + escapeHtml(file.accessUrl) + '" alt="' + escapeHtml(alt) +
|
||||
'" loading="' + loading + '" decoding="async" /></button>' +
|
||||
'<span class="media-fallback" hidden>图片暂时无法显示</span>' +
|
||||
(caption ? '<figcaption>' + escapeHtml(caption) + '</figcaption>' : '') + '</figure>';
|
||||
}
|
||||
|
||||
function renderAvatar(value, options) {
|
||||
var file = normalizeFileAccess(value);
|
||||
var settings = options || {};
|
||||
var name = stringValue(settings.name || '用户').trim() || '用户';
|
||||
var initial = Array.from(name)[0] || '家';
|
||||
var classes = classNames('media-avatar', settings.className);
|
||||
|
||||
if (!file) {
|
||||
return '<span class="' + escapeHtml(classes) + '" aria-label="' +
|
||||
escapeHtml(name + '的头像') + '">' + escapeHtml(initial) + '</span>';
|
||||
}
|
||||
return '<span class="' + escapeHtml(classes) + '"><img src="' + escapeHtml(file.accessUrl) +
|
||||
'" alt="' + escapeHtml(name + '的头像') + '" loading="lazy" decoding="async" />' +
|
||||
'<span class="media-avatar-fallback" aria-hidden="true">' + escapeHtml(initial) + '</span></span>';
|
||||
}
|
||||
|
||||
function renderVideo(value, options) {
|
||||
var file = normalizeFileAccess(value);
|
||||
var settings = options || {};
|
||||
var poster = normalizeFileAccess(settings.posterFile);
|
||||
var title;
|
||||
|
||||
if (!file) return '';
|
||||
title = stringValue(settings.title || file.fileName || '家族视频').trim();
|
||||
return '<figure class="' + escapeHtml(classNames('media-frame media-video', settings.className)) +
|
||||
'" data-media-kind="video"><video controls preload="metadata" src="' + escapeHtml(file.accessUrl) + '"' +
|
||||
(poster ? ' poster="' + escapeHtml(poster.accessUrl) + '"' : '') +
|
||||
' aria-label="' + escapeHtml(title) + '"></video>' +
|
||||
'<span class="media-fallback" hidden>视频暂时无法播放</span>' +
|
||||
(settings.caption ? '<figcaption>' + escapeHtml(settings.caption) + '</figcaption>' : '') + '</figure>';
|
||||
}
|
||||
|
||||
function mediaKind(file) {
|
||||
var type = stringValue(file && file.mediaType).toLowerCase();
|
||||
|
||||
if (type.indexOf('image/') === 0) return 'image';
|
||||
if (type.indexOf('video/') === 0) return 'video';
|
||||
return 'file';
|
||||
}
|
||||
|
||||
function renderDownload(file) {
|
||||
var label = file.fileName || '附件';
|
||||
|
||||
return '<a class="media-download" href="' + escapeHtml(file.accessUrl) +
|
||||
'" target="_blank" rel="noopener noreferrer" download>下载 ' + escapeHtml(label) + '</a>';
|
||||
}
|
||||
|
||||
function renderGallery(values, options) {
|
||||
var files = normalizeFileList(values);
|
||||
var settings = options || {};
|
||||
var label = stringValue(settings.label || '媒体附件').trim();
|
||||
var content;
|
||||
|
||||
if (!files.length) return '';
|
||||
content = files.map(function (file) {
|
||||
var kind = mediaKind(file);
|
||||
|
||||
if (kind === 'image') return renderImage(file, { alt: file.fileName || label });
|
||||
if (kind === 'video') return renderVideo(file, { title: file.fileName || label });
|
||||
return renderDownload(file);
|
||||
}).join('');
|
||||
return '<div class="' + escapeHtml(classNames('media-gallery', settings.className)) +
|
||||
'" role="group" aria-label="' + escapeHtml(label) + '">' + content + '</div>';
|
||||
}
|
||||
|
||||
function ensurePreviewDialog(documentRef) {
|
||||
var dialog = documentRef.querySelector('[data-media-preview-dialog]');
|
||||
var close;
|
||||
|
||||
if (dialog) return dialog;
|
||||
dialog = documentRef.createElement('dialog');
|
||||
dialog.className = 'media-dialog';
|
||||
dialog.setAttribute('data-media-preview-dialog', '');
|
||||
dialog.setAttribute('aria-label', '图片预览');
|
||||
dialog.innerHTML = '<button class="media-dialog-close" type="button" aria-label="关闭图片预览">关闭</button>' +
|
||||
'<img alt="" data-media-dialog-image />';
|
||||
documentRef.body.appendChild(dialog);
|
||||
close = dialog.querySelector('.media-dialog-close');
|
||||
close.addEventListener('click', function () {
|
||||
if (typeof dialog.close === 'function') dialog.close();
|
||||
else dialog.removeAttribute('open');
|
||||
});
|
||||
dialog.addEventListener('click', function (event) {
|
||||
if (event.target !== dialog) return;
|
||||
if (typeof dialog.close === 'function') dialog.close();
|
||||
else dialog.removeAttribute('open');
|
||||
});
|
||||
return dialog;
|
||||
}
|
||||
|
||||
function init(documentRef) {
|
||||
if (!documentRef || documentRef.documentElement.getAttribute('data-media-display-ready') === 'true') return;
|
||||
documentRef.documentElement.setAttribute('data-media-display-ready', 'true');
|
||||
documentRef.addEventListener('error', function (event) {
|
||||
var target = event.target;
|
||||
var frame;
|
||||
var fallback;
|
||||
|
||||
if (!target || !/^(IMG|VIDEO)$/.test(target.tagName)) return;
|
||||
frame = target.closest && target.closest('.media-frame, .media-avatar');
|
||||
if (!frame) return;
|
||||
frame.classList.add('is-media-error');
|
||||
target.hidden = true;
|
||||
fallback = frame.querySelector('.media-fallback, .media-avatar-fallback');
|
||||
if (fallback) fallback.hidden = false;
|
||||
}, true);
|
||||
documentRef.addEventListener('click', function (event) {
|
||||
var trigger = event.target.closest && event.target.closest('[data-media-preview-url]');
|
||||
var dialog;
|
||||
var image;
|
||||
var url;
|
||||
|
||||
if (!trigger) return;
|
||||
url = normalizeAccessUrl(trigger.getAttribute('data-media-preview-url'));
|
||||
if (!url) return;
|
||||
dialog = ensurePreviewDialog(documentRef);
|
||||
image = dialog.querySelector('[data-media-dialog-image]');
|
||||
image.src = url;
|
||||
image.alt = trigger.getAttribute('data-media-preview-alt') || '图片预览';
|
||||
if (typeof dialog.showModal === 'function') dialog.showModal();
|
||||
else dialog.setAttribute('open', '');
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
normalizeFileAccess: normalizeFileAccess,
|
||||
normalizeFileList: normalizeFileList,
|
||||
renderImage: renderImage,
|
||||
renderAvatar: renderAvatar,
|
||||
renderVideo: renderVideo,
|
||||
renderGallery: renderGallery,
|
||||
init: init
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user