gallery.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. declare global {
  2. interface Window {
  3. PhotoSwipe: any;
  4. PhotoSwipeUI_Default: any
  5. }
  6. }
  7. interface PhotoSwipeItem {
  8. w: number;
  9. h: number;
  10. src: string;
  11. msrc: string;
  12. title?: string;
  13. el: HTMLElement;
  14. }
  15. class StackGallery {
  16. private galleryUID: number;
  17. private items: PhotoSwipeItem[] = [];
  18. constructor(container: HTMLElement, galleryUID = 1) {
  19. if (window.PhotoSwipe == undefined || window.PhotoSwipeUI_Default == undefined) {
  20. console.error("PhotoSwipe lib not loaded.");
  21. return;
  22. }
  23. this.galleryUID = galleryUID;
  24. StackGallery.createGallery(container);
  25. this.loadItems(container);
  26. this.bindClick();
  27. }
  28. private loadItems(container: HTMLElement) {
  29. this.items = [];
  30. const figures = container.querySelectorAll('figure.gallery-image');
  31. for (const el of figures) {
  32. const figcaption = el.querySelector('figcaption'),
  33. img = el.querySelector('img');
  34. let aux: PhotoSwipeItem = {
  35. w: parseInt(img.getAttribute('width')),
  36. h: parseInt(img.getAttribute('height')),
  37. src: img.src,
  38. msrc: img.getAttribute('data-thumb') || img.src,
  39. el: el
  40. }
  41. if (figcaption) {
  42. aux.title = figcaption.innerHTML;
  43. }
  44. this.items.push(aux);
  45. }
  46. }
  47. public static createGallery(container: HTMLElement) {
  48. /// The process of wrapping image with figure tag is done using JavaScript instead of only Hugo markdown render hook
  49. /// because it can not detect whether image is being wrapped by a link or not
  50. /// and it lead to a invalid HTML construction (<a><figure><img></figure></a>)
  51. const images = container.querySelectorAll('img.gallery-image');
  52. for (const img of Array.from(images)) {
  53. /// Images are wrapped with figure tag if the paragraph has only images without texts
  54. /// This is done to allow inline images within paragraphs
  55. const paragraph = img.closest('p');
  56. if (!paragraph || !container.contains(paragraph)) continue;
  57. if (paragraph.textContent.trim() == '') {
  58. /// Once we insert figcaption, this check no longer works
  59. /// So we add a class to paragraph to mark it
  60. paragraph.classList.add('no-text');
  61. }
  62. let isNewLineImage = paragraph.classList.contains('no-text');
  63. if (!isNewLineImage) continue;
  64. const hasLink = img.parentElement.tagName == 'A';
  65. let el: HTMLElement = img;
  66. /// Wrap image with figure tag, with flex-grow and flex-basis values extracted from img's data attributes
  67. const figure = document.createElement('figure');
  68. figure.style.setProperty('flex-grow', img.getAttribute('data-flex-grow') || '1');
  69. figure.style.setProperty('flex-basis', img.getAttribute('data-flex-basis') || '0');
  70. if (hasLink) {
  71. /// Wrap <a> if it exists
  72. el = img.parentElement;
  73. }
  74. el.parentElement.insertBefore(figure, el);
  75. figure.appendChild(el);
  76. /// Add figcaption if it exists
  77. if (img.hasAttribute('alt')) {
  78. const figcaption = document.createElement('figcaption');
  79. figcaption.innerText = img.getAttribute('alt');
  80. figure.appendChild(figcaption);
  81. }
  82. /// Wrap img tag with <a> tag if image was not wrapped by <a> tag
  83. if (!hasLink) {
  84. figure.className = 'gallery-image';
  85. const a = document.createElement('a');
  86. a.href = img.src;
  87. a.setAttribute('target', '_blank');
  88. img.parentNode.insertBefore(a, img);
  89. a.appendChild(img);
  90. }
  91. }
  92. const figuresEl = container.querySelectorAll('figure.gallery-image');
  93. let currentGallery = [];
  94. for (const figure of figuresEl) {
  95. if (!currentGallery.length) {
  96. /// First iteration
  97. currentGallery = [figure];
  98. }
  99. else if (figure.previousElementSibling === currentGallery[currentGallery.length - 1]) {
  100. /// Adjacent figures
  101. currentGallery.push(figure);
  102. }
  103. else if (currentGallery.length) {
  104. /// End gallery
  105. StackGallery.wrap(currentGallery);
  106. currentGallery = [figure];
  107. }
  108. }
  109. if (currentGallery.length > 0) {
  110. StackGallery.wrap(currentGallery);
  111. }
  112. }
  113. /**
  114. * Wrap adjacent figure tags with div.gallery
  115. * @param figures
  116. */
  117. public static wrap(figures: HTMLElement[]) {
  118. const galleryContainer = document.createElement('div');
  119. galleryContainer.className = 'gallery';
  120. const parentNode = figures[0].parentNode,
  121. first = figures[0];
  122. parentNode.insertBefore(galleryContainer, first)
  123. for (const figure of figures) {
  124. galleryContainer.appendChild(figure);
  125. }
  126. }
  127. public open(index: number) {
  128. const pswp = document.querySelector('.pswp') as HTMLDivElement;
  129. const ps = new window.PhotoSwipe(pswp, window.PhotoSwipeUI_Default, this.items, {
  130. index: index,
  131. galleryUID: this.galleryUID,
  132. getThumbBoundsFn: (index) => {
  133. const thumbnail = this.items[index].el.getElementsByTagName('img')[0],
  134. pageYScroll = window.pageYOffset || document.documentElement.scrollTop,
  135. rect = thumbnail.getBoundingClientRect();
  136. return { x: rect.left, y: rect.top + pageYScroll, w: rect.width };
  137. }
  138. });
  139. ps.init();
  140. }
  141. private bindClick() {
  142. for (const [index, item] of this.items.entries()) {
  143. const a = item.el.querySelector('a');
  144. a.addEventListener('click', (e) => {
  145. e.preventDefault();
  146. this.open(index);
  147. })
  148. }
  149. }
  150. }
  151. export default StackGallery;