(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, '''); } 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 '
' + '' + (caption ? '
' + escapeHtml(caption) + '
' : '') + '
'; } 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 '' + escapeHtml(initial) + ''; } return '' + escapeHtml(name + '的头像') + '' + ''; } 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 '
' + '' + (settings.caption ? '
' + escapeHtml(settings.caption) + '
' : '') + '
'; } 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 '下载 ' + escapeHtml(label) + ''; } 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 '
' + content + '
'; } 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 = '' + ''; 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 }; });