aboutsummaryrefslogblamecommitdiff
path: root/themes/hugo-theme-stack/assets/ts/gallery.ts
blob: 9840f1ec16171c242adf092fbf13fd516cff62eb (plain) (tree)

























































































































































































                                                                                                                            
declare global {
    interface Window {
        PhotoSwipe: any;
        PhotoSwipeUI_Default: any
    }
}

interface PhotoSwipeItem {
    w: number;
    h: number;
    src: string;
    msrc: string;
    title?: string;
    el: HTMLElement;
}

class StackGallery {
    private galleryUID: number;
    private items: PhotoSwipeItem[] = [];

    constructor(container: HTMLElement, galleryUID = 1) {
        if (window.PhotoSwipe == undefined || window.PhotoSwipeUI_Default == undefined) {
            console.error("PhotoSwipe lib not loaded.");
            return;
        }

        this.galleryUID = galleryUID;

        StackGallery.createGallery(container);
        this.loadItems(container);
        this.bindClick();
    }

    private loadItems(container: HTMLElement) {
        this.items = [];

        const figures = container.querySelectorAll('figure.gallery-image');

        for (const el of figures) {
            const figcaption = el.querySelector('figcaption'),
                img = el.querySelector('img');

            let aux: PhotoSwipeItem = {
                w: parseInt(img.getAttribute('width')),
                h: parseInt(img.getAttribute('height')),
                src: img.src,
                msrc: img.getAttribute('data-thumb') || img.src,
                el: el
            }

            if (figcaption) {
                aux.title = figcaption.innerHTML;
            }

            this.items.push(aux);
        }
    }

    public static createGallery(container: HTMLElement) {
        /// The process of wrapping image with figure tag is done using JavaScript instead of only Hugo markdown render hook
        /// because it can not detect whether image is being wrapped by a link or not
        /// and it lead to a invalid HTML construction (<a><figure><img></figure></a>)

        const images = container.querySelectorAll('img.gallery-image');
        for (const img of Array.from(images)) {
            /// Images are wrapped with figure tag if the paragraph has only images without texts
            /// This is done to allow inline images within paragraphs
            const paragraph = img.closest('p');

            if (!paragraph || !container.contains(paragraph)) continue;

            if (paragraph.textContent.trim() == '') {
                /// Once we insert figcaption, this check no longer works
                /// So we add a class to paragraph to mark it
                paragraph.classList.add('no-text');
            }

            let isNewLineImage = paragraph.classList.contains('no-text');
            if (!isNewLineImage) continue;

            const hasLink = img.parentElement.tagName == 'A';

            let el: HTMLElement = img;
            /// Wrap image with figure tag, with flex-grow and flex-basis values extracted from img's data attributes
            const figure = document.createElement('figure');
            figure.style.setProperty('flex-grow', img.getAttribute('data-flex-grow') || '1');
            figure.style.setProperty('flex-basis', img.getAttribute('data-flex-basis') || '0');
            if (hasLink) {
                /// Wrap <a> if it exists
                el = img.parentElement;
            }
            el.parentElement.insertBefore(figure, el);
            figure.appendChild(el);

            /// Add figcaption if it exists
            if (img.hasAttribute('alt')) {
                const figcaption = document.createElement('figcaption');
                figcaption.innerText = img.getAttribute('alt');
                figure.appendChild(figcaption);
            }

            /// Wrap img tag with <a> tag if image was not wrapped by <a> tag
            if (!hasLink) {
                figure.className = 'gallery-image';

                const a = document.createElement('a');
                a.href = img.src;
                a.setAttribute('target', '_blank');
                img.parentNode.insertBefore(a, img);
                a.appendChild(img);
            }
        }

        const figuresEl = container.querySelectorAll('figure.gallery-image');

        let currentGallery = [];

        for (const figure of figuresEl) {
            if (!currentGallery.length) {
                /// First iteration
                currentGallery = [figure];
            }
            else if (figure.previousElementSibling === currentGallery[currentGallery.length - 1]) {
                /// Adjacent figures
                currentGallery.push(figure);
            }
            else if (currentGallery.length) {
                /// End gallery
                StackGallery.wrap(currentGallery);
                currentGallery = [figure];
            }
        }

        if (currentGallery.length > 0) {
            StackGallery.wrap(currentGallery);
        }
    }

    /**
     * Wrap adjacent figure tags with div.gallery
     * @param figures 
     */
    public static wrap(figures: HTMLElement[]) {
        const galleryContainer = document.createElement('div');
        galleryContainer.className = 'gallery';

        const parentNode = figures[0].parentNode,
            first = figures[0];

        parentNode.insertBefore(galleryContainer, first)

        for (const figure of figures) {
            galleryContainer.appendChild(figure);
        }
    }

    public open(index: number) {
        const pswp = document.querySelector('.pswp') as HTMLDivElement;
        const ps = new window.PhotoSwipe(pswp, window.PhotoSwipeUI_Default, this.items, {
            index: index,
            galleryUID: this.galleryUID,
            getThumbBoundsFn: (index) => {
                const thumbnail = this.items[index].el.getElementsByTagName('img')[0],
                    pageYScroll = window.pageYOffset || document.documentElement.scrollTop,
                    rect = thumbnail.getBoundingClientRect();

                return { x: rect.left, y: rect.top + pageYScroll, w: rect.width };
            }
        });

        ps.init();
    }

    private bindClick() {
        for (const [index, item] of this.items.entries()) {
            const a = item.el.querySelector('a');

            a.addEventListener('click', (e) => {
                e.preventDefault();
                this.open(index);
            })
        }
    }
}

export default StackGallery;