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:
+39
-128
@@ -1,67 +1,22 @@
|
|||||||
declare global {
|
const wrap = (figures: HTMLElement[]) => {
|
||||||
interface Window {
|
const galleryContainer = document.createElement('div');
|
||||||
PhotoSwipe: any;
|
galleryContainer.className = 'gallery';
|
||||||
PhotoSwipeUI_Default: any
|
|
||||||
|
const parentNode = figures[0].parentNode!,
|
||||||
|
first = figures[0];
|
||||||
|
|
||||||
|
parentNode.insertBefore(galleryContainer, first)
|
||||||
|
|
||||||
|
for (const figure of figures) {
|
||||||
|
galleryContainer.appendChild(figure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PhotoSwipeItem {
|
export default (container: HTMLElement) => {
|
||||||
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
|
/// 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
|
/// 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>)
|
/// and it lead to a invalid HTML construction (<a><figure><img></figure></a>)
|
||||||
|
const images = container.querySelectorAll('img.gallery-image') as NodeListOf<HTMLImageElement>;
|
||||||
const images = container.querySelectorAll('img.gallery-image');
|
|
||||||
for (const img of Array.from(images)) {
|
for (const img of Array.from(images)) {
|
||||||
/// Images are wrapped with figure tag if the paragraph has only images without texts
|
/// Images are wrapped with figure tag if the paragraph has only images without texts
|
||||||
/// This is done to allow inline images within paragraphs
|
/// This is done to allow inline images within paragraphs
|
||||||
@@ -78,44 +33,49 @@ class StackGallery {
|
|||||||
let isNewLineImage = paragraph.classList.contains('no-text');
|
let isNewLineImage = paragraph.classList.contains('no-text');
|
||||||
if (!isNewLineImage) continue;
|
if (!isNewLineImage) continue;
|
||||||
|
|
||||||
const hasLink = img.parentElement.tagName == 'A';
|
const hasLink = img.parentElement!.tagName == 'A';
|
||||||
|
|
||||||
let el: HTMLElement = img;
|
let el: HTMLElement = img;
|
||||||
/// Wrap image with figure tag, with flex-grow and flex-basis values extracted from img's data attributes
|
/// Wrap image with figure tag, with flex-grow and flex-basis values extracted from img's data attributes
|
||||||
const figure = document.createElement('figure');
|
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-grow', img.getAttribute('data-flex-grow') || '1');
|
||||||
figure.style.setProperty('flex-basis', img.getAttribute('data-flex-basis') || '0');
|
figure.style.setProperty('flex-basis', img.getAttribute('data-flex-basis') || '0');
|
||||||
|
|
||||||
if (hasLink) {
|
if (hasLink) {
|
||||||
/// Wrap <a> if it exists
|
/// Wrap <a> if it exists
|
||||||
el = img.parentElement;
|
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;
|
||||||
}
|
}
|
||||||
el.parentElement.insertBefore(figure, el);
|
|
||||||
|
el.parentElement!.insertBefore(figure, el);
|
||||||
figure.appendChild(el);
|
figure.appendChild(el);
|
||||||
|
|
||||||
/// Add figcaption if it exists
|
/// Add figcaption if it exists
|
||||||
if (img.hasAttribute('alt')) {
|
if (img.hasAttribute('alt')) {
|
||||||
const figcaption = document.createElement('figcaption');
|
const figcaption = document.createElement('figcaption');
|
||||||
figcaption.innerText = img.getAttribute('alt');
|
figcaption.innerText = img.getAttribute('alt')!;
|
||||||
figure.appendChild(figcaption);
|
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');
|
const figuresEl = container.querySelectorAll('figure.gallery-image') as NodeListOf<HTMLElement>;
|
||||||
|
|
||||||
let currentGallery = [];
|
let currentGallery: HTMLElement[] = [];
|
||||||
|
|
||||||
for (const figure of figuresEl) {
|
for (const figure of Array.from(figuresEl)) {
|
||||||
if (!currentGallery.length) {
|
if (!currentGallery.length) {
|
||||||
/// First iteration
|
/// First iteration
|
||||||
currentGallery = [figure];
|
currentGallery = [figure];
|
||||||
@@ -126,61 +86,12 @@ class StackGallery {
|
|||||||
}
|
}
|
||||||
else if (currentGallery.length) {
|
else if (currentGallery.length) {
|
||||||
/// End gallery
|
/// End gallery
|
||||||
StackGallery.wrap(currentGallery);
|
wrap(currentGallery);
|
||||||
currentGallery = [figure];
|
currentGallery = [figure];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentGallery.length > 0) {
|
if (currentGallery.length > 0) {
|
||||||
StackGallery.wrap(currentGallery);
|
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;
|
|
||||||
+7
-9
@@ -5,13 +5,12 @@
|
|||||||
* @website: https://jimmycai.com
|
* @website: https://jimmycai.com
|
||||||
* @link: https://github.com/CaiJimmy/hugo-theme-stack
|
* @link: https://github.com/CaiJimmy/hugo-theme-stack
|
||||||
*/
|
*/
|
||||||
import StackGallery from "ts/gallery";
|
import { getColor } from './color';
|
||||||
import { getColor } from 'ts/color';
|
import menu from './menu';
|
||||||
import menu from 'ts/menu';
|
import createElement from './createElement';
|
||||||
import createElement from 'ts/createElement';
|
import StackColorScheme from './colorScheme';
|
||||||
import StackColorScheme from 'ts/colorScheme';
|
import { setupScrollspy } from './scrollspy';
|
||||||
import { setupScrollspy } from 'ts/scrollspy';
|
import { setupSmoothAnchors } from './smoothAnchors';
|
||||||
import { setupSmoothAnchors } from "ts/smoothAnchors";
|
|
||||||
|
|
||||||
let Stack = {
|
let Stack = {
|
||||||
init: () => {
|
init: () => {
|
||||||
@@ -22,7 +21,6 @@ let Stack = {
|
|||||||
|
|
||||||
const articleContent = document.querySelector('.article-content') as HTMLElement;
|
const articleContent = document.querySelector('.article-content') as HTMLElement;
|
||||||
if (articleContent) {
|
if (articleContent) {
|
||||||
new StackGallery(articleContent);
|
|
||||||
setupSmoothAnchors();
|
setupSmoothAnchors();
|
||||||
setupScrollspy();
|
setupScrollspy();
|
||||||
}
|
}
|
||||||
@@ -91,7 +89,7 @@ let Stack = {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
new StackColorScheme(document.getElementById('dark-mode-toggle'));
|
new StackColorScheme(document.getElementById('dark-mode-toggle')!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
Vibrant = [
|
||||||
|
{ src = "https://cdn.jsdelivr.net/npm/node-vibrant@3.1.6/dist/vibrant.min.js", integrity = "sha256-awcR2jno4kI5X0zL8ex0vi2z+KMkF24hUW8WePSA9HM=", type = "script" },
|
||||||
|
]
|
||||||
|
|
||||||
|
KaTeX = [
|
||||||
|
{ src = "https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css", integrity = "sha384-n8MVd4RsNIU0tAv4ct0nTaAbDJwPJzDEaqSD1odI+WdtXRGWt2kTvGFasHpSy3SV", type = "style" },
|
||||||
|
{ src = "https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js", integrity = "sha384-XjKyOOlGwcjNTAIQHIpgOno0Hl1YQqzUOEleOLALmuqehneUG+vnGctmUb0ZY0l8", type = "script", defer = true },
|
||||||
|
{ src = "https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js", integrity = "sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05", type = "script", defer = true },
|
||||||
|
]
|
||||||
|
|
||||||
|
Cactus = [
|
||||||
|
{ src = "https://latest.cactus.chat/cactus.js", type = "script" },
|
||||||
|
{ src = "https://latest.cactus.chat/style.css", type = "style" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[PhotoSwipe]
|
||||||
|
Style = "https://cdn.jsdelivr.net/npm/photoswipe@5.4.4/dist/photoswipe.css"
|
||||||
|
Core = "https://cdn.jsdelivr.net/npm/photoswipe@5.4.4/dist/photoswipe.esm.min.js"
|
||||||
|
Lightbox = "https://cdn.jsdelivr.net/npm/photoswipe@5.4.4/dist/photoswipe-lightbox.esm.min.js"
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
Vibrant:
|
|
||||||
- src: https://cdn.jsdelivr.net/npm/node-vibrant@3.1.6/dist/vibrant.min.js
|
|
||||||
integrity: sha256-awcR2jno4kI5X0zL8ex0vi2z+KMkF24hUW8WePSA9HM=
|
|
||||||
type: script
|
|
||||||
|
|
||||||
PhotoSwipe:
|
|
||||||
- src: https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js
|
|
||||||
integrity: sha256-ePwmChbbvXbsO02lbM3HoHbSHTHFAeChekF1xKJdleo=
|
|
||||||
type: script
|
|
||||||
defer: true
|
|
||||||
|
|
||||||
- src: https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js
|
|
||||||
integrity: sha256-UKkzOn/w1mBxRmLLGrSeyB4e1xbrp4xylgAWb3M42pU=
|
|
||||||
type: script
|
|
||||||
defer: true
|
|
||||||
|
|
||||||
- src: https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css
|
|
||||||
type: style
|
|
||||||
|
|
||||||
- src: https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css
|
|
||||||
type: style
|
|
||||||
|
|
||||||
KaTeX:
|
|
||||||
- src: https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css
|
|
||||||
integrity: sha384-n8MVd4RsNIU0tAv4ct0nTaAbDJwPJzDEaqSD1odI+WdtXRGWt2kTvGFasHpSy3SV
|
|
||||||
type: style
|
|
||||||
|
|
||||||
- src: https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js
|
|
||||||
integrity: sha384-XjKyOOlGwcjNTAIQHIpgOno0Hl1YQqzUOEleOLALmuqehneUG+vnGctmUb0ZY0l8
|
|
||||||
type: script
|
|
||||||
defer: true
|
|
||||||
|
|
||||||
- src: https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js
|
|
||||||
integrity: sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05
|
|
||||||
type: script
|
|
||||||
defer: true
|
|
||||||
|
|
||||||
Cactus:
|
|
||||||
- src: https://latest.cactus.chat/cactus.js
|
|
||||||
integrity:
|
|
||||||
type: script
|
|
||||||
- src: https://latest.cactus.chat/style.css
|
|
||||||
integrity:
|
|
||||||
type: style
|
|
||||||
@@ -1,68 +1,32 @@
|
|||||||
<!-- Root element of PhotoSwipe. Must have class pswp. -->
|
{{- $opts := dict "minify" hugo.IsProduction "format" "esm" -}}
|
||||||
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
|
{{- $galleryScript := resources.Get "ts/gallery.ts" | js.Build $opts -}}
|
||||||
|
|
||||||
<!-- Background of PhotoSwipe.
|
{{ $style := .Site.Data.external.PhotoSwipe.Style }}
|
||||||
It's a separate element as animating opacity is faster than rgba(). -->
|
{{ $core := .Site.Data.external.PhotoSwipe.Core }}
|
||||||
<div class="pswp__bg"></div>
|
{{ $lightbox := .Site.Data.external.PhotoSwipe.Lightbox }}
|
||||||
|
|
||||||
<!-- Slides wrapper with overflow:hidden. -->
|
<script type="module">
|
||||||
<div class="pswp__scroll-wrap">
|
import gallery from '{{ $galleryScript.RelPermalink }}';
|
||||||
|
|
||||||
<!-- Container that holds slides.
|
const articleContent = document.querySelector('.article-content');
|
||||||
PhotoSwipe keeps only 3 of them in the DOM to save memory.
|
const shouldLoad = articleContent && (articleContent.querySelectorAll('figure').length > 0 || articleContent.querySelectorAll('img.gallery-image').length > 0);
|
||||||
Don't modify these 3 pswp__item elements, data is added later on. -->
|
|
||||||
<div class="pswp__container">
|
|
||||||
<div class="pswp__item"></div>
|
|
||||||
<div class="pswp__item"></div>
|
|
||||||
<div class="pswp__item"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
|
if (shouldLoad) {
|
||||||
<div class="pswp__ui pswp__ui--hidden">
|
gallery(articleContent);
|
||||||
|
|
||||||
<div class="pswp__top-bar">
|
const PhotoSwipeLightbox = (await import("{{ $lightbox | safeJS }}")).default;
|
||||||
|
const styleHref = "{{ $style | safeJS }}";
|
||||||
|
|
||||||
<!-- Controls are self-explanatory. Order can be changed. -->
|
const styleTag = document.createElement('link');
|
||||||
|
styleTag.rel = 'stylesheet';
|
||||||
|
styleTag.href = styleHref;
|
||||||
|
document.head.appendChild(styleTag);
|
||||||
|
|
||||||
<div class="pswp__counter"></div>
|
const lightbox = new PhotoSwipeLightbox({
|
||||||
|
gallerySelector: '.article-content',
|
||||||
<button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
|
childSelector: 'figure a.image-link',
|
||||||
|
pswpModule: () => import("{{ $core | safeJS }}")
|
||||||
<button class="pswp__button pswp__button--share" title="Share"></button>
|
});
|
||||||
|
lightbox.init();
|
||||||
<button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
|
}
|
||||||
|
</script>
|
||||||
<button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>
|
|
||||||
|
|
||||||
<!-- Preloader demo https://codepen.io/dimsemenov/pen/yyBWoR -->
|
|
||||||
<!-- element will get class pswp__preloader--active when preloader is running -->
|
|
||||||
<div class="pswp__preloader">
|
|
||||||
<div class="pswp__preloader__icn">
|
|
||||||
<div class="pswp__preloader__cut">
|
|
||||||
<div class="pswp__preloader__donut"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
|
|
||||||
<div class="pswp__share-tooltip"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="pswp__caption">
|
|
||||||
<div class="pswp__caption__center"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{- partial "helper/external" (dict "Context" . "Namespace" "PhotoSwipe") -}}
|
|
||||||
Reference in New Issue
Block a user