import type { App, Directive, DirectiveBinding, Plugin } from 'vue' import type { LightboxItem } from './lightbox.types' import { IMAGE_FORMATS_REGEXP, pathKeyToURL, VIDEO_FORMATS_REGEXP } from '@bagelink/vue' import { createApp } from 'vue' import Lightbox from './Lightbox.vue' type OpenFunction = (item: LightboxItem, groupItems?: LightboxItem[]) => void const groups: { [key: string]: LightboxItem[] } = {} let clickHandler: ((e: MouseEvent) => any) = (_e: MouseEvent) => void 0 const lightboxDirective: Directive = { mounted(el: HTMLElement, binding) { const item = bindingToItem(binding, el) groupHandler(item) clickHandler = (e: MouseEvent) => { openClickHandler(e, el, binding) } el.addEventListener('click', clickHandler) }, updated(el: HTMLElement, binding) { const item = bindingToItem(binding, el) groupHandler(item) el.removeEventListener('click', clickHandler) clickHandler = (e: MouseEvent) => { openClickHandler(e, el, binding) } el.addEventListener('click', clickHandler) }, unmounted(el: HTMLElement) { el.removeEventListener('click', clickHandler) }, getSSRProps() { return {} }, } function groupHandler(item: LightboxItem) { if (item.group) { if (!groups[item.group]) { groups[item.group] = [] } const currentIndex = groups[item.group].findIndex(i => i.src === item.src) if (currentIndex === -1) { groups[item.group].push(item) } } } function openClickHandler(e: MouseEvent, el: HTMLElement, binding: DirectiveBinding) { e.stopPropagation() const item = bindingToItem(binding, el) const lightboxInstance = getLightboxInstance() if (!lightboxInstance || !lightboxInstance.open) { return } const open = lightboxInstance.open as OpenFunction const group = item && item.group ? groups[item.group] : undefined open(item, group) } function bindingToItem(binding: DirectiveBinding, el: HTMLElement): LightboxItem { let { group, src, type, name, thumbnail, enableZoom, openFile, download, pathKey } = binding.value || {} src = pathKeyToURL(src || pathKey) || binding.value || el.getAttribute('src') || '' type = type || determineFileType(src) name = name || urlToName(src) || el.getAttribute('alt') || '' return { src, type, name, thumbnail, group, enableZoom, openFile, download } } const youtubeRegex = /youtube\.com|youtu\.be/ const vimeoRegex = /vimeo\.com/ function urlToName(url: string): string { if (!url || typeof url !== 'string') { return '' } const name = url.split('/').pop() || '' return name.replace(/\.[^/.]+$/, '') } function determineFileType(url: any): string { if (typeof url !== 'string' || !url) { return 'unknown' } // Extensionless sources (object URLs, data URLs, signed/CDN URLs) can't be sniffed // by extension — fall back to MIME hints so they still preview as images/video. if (url.startsWith('blob:') || /^data:image\//i.test(url)) { return 'image' } if (/^data:video\//i.test(url)) { return 'video' } const extension = (url.split('.').pop() || '').toLowerCase() const altExtension = url.split('?')[0].split('.').pop()?.toLowerCase() || '' if (IMAGE_FORMATS_REGEXP.test(extension) || IMAGE_FORMATS_REGEXP.test(altExtension)) { return 'image' } if (VIDEO_FORMATS_REGEXP.test(extension) || youtubeRegex.test(url) || vimeoRegex.test(url)) { return 'video' } if (['pdf'].includes(extension)) { return 'pdf' } return extension ?? 'unknown' } let lightboxInstance: InstanceType | undefined function getLightboxInstance(): InstanceType | undefined { if (typeof document === 'undefined') { return undefined } if (!lightboxInstance) { const lightboxEl = document.createElement('div') document.body.prepend(lightboxEl) const app = createApp(Lightbox) lightboxInstance = app.mount(lightboxEl) as InstanceType } return lightboxInstance } const lightboxPlugin: Plugin = { install(app: App) { app.directive('lightbox', lightboxDirective) }, } export default lightboxPlugin