feat: upgrade to PhotoSwipe v5 (#1233)

* refactor: migrate external resource definitions from YAML to TOML format

* feat: upgrade to PhotoSwipe v5
This commit is contained in:
Jimmy
2026-01-25 23:13:34 +01:00
committed by GitHub
parent 9d9ecc4d00
commit b1ddad1d22
5 changed files with 126 additions and 278 deletions
+74 -163
View File
@@ -1,186 +1,97 @@
declare global {
interface Window {
PhotoSwipe: any;
PhotoSwipeUI_Default: any
const 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);
}
}
interface PhotoSwipeItem {
w: number;
h: number;
src: string;
msrc: string;
title?: string;
el: HTMLElement;
}
export default (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') as NodeListOf<HTMLImageElement>;
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');
class StackGallery {
private galleryUID: number;
private items: PhotoSwipeItem[] = [];
if (!paragraph || !container.contains(paragraph)) continue;
constructor(container: HTMLElement, galleryUID = 1) {
if (window.PhotoSwipe == undefined || window.PhotoSwipeUI_Default == undefined) {
console.error("PhotoSwipe lib not loaded.");
return;
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');
}
this.galleryUID = galleryUID;
let isNewLineImage = paragraph.classList.contains('no-text');
if (!isNewLineImage) continue;
StackGallery.createGallery(container);
this.loadItems(container);
this.bindClick();
}
const hasLink = img.parentElement!.tagName == 'A';
private loadItems(container: HTMLElement) {
this.items = [];
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.classList.add('gallery-image');
figure.style.setProperty('flex-grow', img.getAttribute('data-flex-grow') || '1');
figure.style.setProperty('flex-basis', img.getAttribute('data-flex-basis') || '0');
const figures = container.querySelectorAll('figure.gallery-image');
if (hasLink) {
/// Wrap <a> if it exists
el = img.parentElement!;
el.classList.add('image-link');
el.setAttribute('data-pswp-width', img.getAttribute('width')!);
el.setAttribute('data-pswp-height', img.getAttribute('height')!);
} else {
const a = document.createElement('a');
a.href = img.src;
a.setAttribute('class', 'image-link');
a.setAttribute('target', '_blank');
a.setAttribute('data-pswp-width', img.getAttribute('width')!);
a.setAttribute('data-pswp-height', img.getAttribute('height')!);
img.parentNode!.insertBefore(a, img);
a.appendChild(img);
el = a;
}
for (const el of figures) {
const figcaption = el.querySelector('figcaption'),
img = el.querySelector('img');
el.parentElement!.insertBefore(figure, el);
figure.appendChild(el);
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);
/// Add figcaption if it exists
if (img.hasAttribute('alt')) {
const figcaption = document.createElement('figcaption');
figcaption.innerText = img.getAttribute('alt')!;
figure.appendChild(figcaption);
}
}
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 figuresEl = container.querySelectorAll('figure.gallery-image') as NodeListOf<HTMLElement>;
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');
let currentGallery: HTMLElement[] = [];
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);
}
for (const figure of Array.from(figuresEl)) {
if (!currentGallery.length) {
/// First iteration
currentGallery = [figure];
}
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];
}
else if (figure.previousElementSibling === currentGallery[currentGallery.length - 1]) {
/// Adjacent figures
currentGallery.push(figure);
}
if (currentGallery.length > 0) {
StackGallery.wrap(currentGallery);
else if (currentGallery.length) {
/// End gallery
wrap(currentGallery);
currentGallery = [figure];
}
}
/**
* 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);
}
if (currentGallery.length > 0) {
wrap(currentGallery);
}
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;
};