From a9b3b28d001941921aa70d37fdc87c758a163a44 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Fri, 05 Jun 2026 16:47:03 +0000
Subject: [PATCH] =Some hefty changes to FeedBlock. Transitioning to loading first page in php to save on extra requests. Got a bit to do yet, but I have to work on Northeh for a bit here.
---
assets/js/Gallery.js | 352 +++++++++++++++++++++++++---------------------------------
1 files changed, 154 insertions(+), 198 deletions(-)
diff --git a/assets/js/Gallery.js b/assets/js/Gallery.js
index e952b26..9c596c4 100644
--- a/assets/js/Gallery.js
+++ b/assets/js/Gallery.js
@@ -1,272 +1,228 @@
class Gallery {
- constructor(modal, config) {
- this.imageWrapper = config.imageWrapper??null;
- this.container = config.container ? document.querySelector(config.container) : document.querySelector('main');
- this.modal = new window.jvbModal(
- modal,
- {
- onOpen: false,
- onSave: false,
- onClose: () => this.closeGallery(false)
- }
- );
- this.items = this.getGalleryItems() || [];
- this.loadMore = config.loadMore??false;
+ constructor(modal, config = {}) {
+ this.container = config.container
+ ? document.querySelector(config.container)
+ : document.querySelector('main');
- this.modalElement = (typeof modal === 'string') ? document.querySelector(modal)??false : modal;
- if (!this.modal) {
- return;
- }
+ this.gallerySelector = config.gallerySelector || 'img[data-small]';
+
+ this.modal = new window.jvbModal(modal, {
+ onOpen: false,
+ onSave: false,
+ onClose: () => this.closeGallery(false)
+ });
+
+ this.modalElement = (typeof modal === 'string')
+ ? document.querySelector(modal)
+ : modal;
+
+ if (!this.modalElement) return;
this.a11y = window.jvbA11y;
- this.openWhenReady = false;
-
-
- this.initElements();
- this.imageWrapper = (typeof this.imageWrapper === 'string') ? this.imageWrapper : '.'+this.imageWrapper.classList.join('.');
-
this.index = 0;
+ this.items = [];
+
this.swipe = {
touchStart: null,
touchEnd: null,
minSwipe: 50,
};
- this.isLoading = false;
+ this.initElements();
+ this.initListeners();
}
initElements() {
this.prevBtn = this.modalElement.querySelector('.prev');
this.nextBtn = this.modalElement.querySelector('.next');
- this.favourite = this.modalElement.querySelector('.favourite');
this.image = this.modalElement.querySelector('.image');
this.counter = this.modalElement.querySelector('.counter');
- this.extra = this.modalElement.querySelector('.item-info');
-
- //If we don't have the wrapper set up, we can remove these elements
- if (!this.imageWrapper) {
- this.favourite.remove();
- this.extra.remove();
- }
- }
-
- getGalleryItems() {
- let search = (this.imageWrapper) ? this.imageWrapper : 'img';
-
- return Array.from(this.container.querySelectorAll(search))
- .map(item => {
- const img = (this.imageWrapper) ? item.querySelector('img') : item;
- if (!img) return null;
-
- return {
- id: (this.imageWrapper) ? item.querySelector('button.favourite').dataset.id : '',
- small: img.dataset.small || img.src,
- large: img.dataset.large || img.src,
- full: img.dataset.full || img.src,
- alt: img.alt || '',
- fav: (this.imageWrapper) ? item.querySelector('button.favourite')?.cloneNode(true) : '',
- info: (this.imageWrapper) ? item.querySelector('.item-info')?.cloneNode(true) : ''
- };
- }).filter(Boolean);
}
initListeners() {
+ // Delegate click handling to container
this.container.addEventListener('click', this.handleClick.bind(this));
- document.addEventListener('keydown', this.handleKeys.bind(this));
- document.addEventListener('touchstart', this.handleTouchStart.bind(this));
- document.addEventListener('touchend', this.handleTouchEnd.bind(this));
- document.addEventListener('touchmove', this.handleTouchMove.bind(this));
+
+ // Modal-specific listeners added only when open
+ this.boundKeyHandler = this.handleKeys.bind(this);
+ this.boundTouchStart = this.handleTouchStart.bind(this);
+ this.boundTouchMove = this.handleTouchMove.bind(this);
+ this.boundTouchEnd = this.handleTouchEnd.bind(this);
}
- destroyListeners() {
- this.container.removeEventListener('click', this.handleClick.bind(this));
- document.removeEventListener('keydown', this.handleKeys.bind(this));
- document.removeEventListener('touchstart', this.handleTouchStart.bind(this));
- document.removeEventListener('touchend', this.handleTouchEnd.bind(this));
- document.removeEventListener('touchmove', this.handleTouchMove.bind(this));
+
+ handleClick(e) {
+ // Open gallery when clicking images
+ const img = e.target.closest(this.gallerySelector);
+ if (img && !this.modal.isOpen) {
+ e.preventDefault();
+ this.openGallery(img);
+ return;
+ }
+
+ // Navigation within gallery
+ if (this.modal.isOpen) {
+ if (e.target.closest('.next')) {
+ this.navigate(1);
+ } else if (e.target.closest('.prev')) {
+ this.navigate(-1);
+ }
+ }
}
handleKeys(e) {
+ if (!this.modal.isOpen) return;
+
switch (e.key) {
case 'ArrowLeft':
+ e.preventDefault();
this.navigate(-1);
break;
case 'ArrowRight':
+ e.preventDefault();
this.navigate(1);
break;
}
}
handleTouchStart(e) {
+ if (!this.modal.isOpen) return;
this.swipe.touchStart = e.touches[0].clientX;
}
handleTouchMove(e) {
+ if (!this.modal.isOpen) return;
this.swipe.touchEnd = e.touches[0].clientX;
}
handleTouchEnd(e) {
- if (!this.swipe.touchStart || !this.swipe.touchEnd) return;
- const distance = this.swipe.touchStart - this.swipe.touchEnd;
- const isLeftSwipe = distance > this.swipe.minSwipe;
- const isRightSwipe = distance < -this.swipe.minSwipe;
+ if (!this.modal.isOpen || !this.swipe.touchStart || !this.swipe.touchEnd) return;
- if (isLeftSwipe) {
- this.navigate(1);
- } else if (isRightSwipe) {
- this.navigate(-1);
+ const distance = this.swipe.touchStart - this.swipe.touchEnd;
+
+ if (Math.abs(distance) > this.swipe.minSwipe) {
+ this.navigate(distance > 0 ? 1 : -1);
}
+
this.swipe.touchStart = null;
this.swipe.touchEnd = null;
}
- handleClick(e) {
- //test if it's a link click, and the click has an image
- if(window.targetCheck(e, '.feed-images')){
- this.handleGalleryOpen(e);
- }
+ buildGalleryItems() {
+ return Array.from(this.container.querySelectorAll(this.gallerySelector))
+ .map((img, index) => ({
+ id: img.dataset.id || index,
+ small: img.dataset.small || img.src,
+ medium: img.dataset.medium || img.src,
+ full: img.dataset.full || img.src,
+ alt: img.alt || '',
+ element: img
+ }));
+ }
- if(window.targetCheck(e, '.nav')) {
- if (window.targetCheck(e, '.next')) {
- this.navigate(1);
- } else {
- this.navigate(-1);
- }
- }
- if (window.targetCheck(e, 'button.cancel')) {
- this.closeGallery();
+ openGallery(clickedImg) {
+ // Build fresh gallery items
+ this.items = this.buildGalleryItems();
+
+ // Find clicked image index
+ this.index = this.items.findIndex(item =>
+ item.element === clickedImg
+ );
+
+ if (this.index === -1) this.index = 0;
+
+ // Attach modal-specific listeners
+ document.addEventListener('keydown', this.boundKeyHandler);
+ document.addEventListener('touchstart', this.boundTouchStart, { passive: true });
+ document.addEventListener('touchmove', this.boundTouchMove, { passive: true });
+ document.addEventListener('touchend', this.boundTouchEnd, { passive: true });
+
+ this.modal.handleOpen();
+ this.updateDisplay();
+ this.preloadAdjacent();
+
+ this.a11y.announce(
+ `Gallery opened. Image ${this.index + 1} of ${this.items.length}. Use arrow keys to navigate.`
+ );
+ }
+
+ closeGallery(useModal = true) {
+ // Remove modal-specific listeners
+ document.removeEventListener('keydown', this.boundKeyHandler);
+ document.removeEventListener('touchstart', this.boundTouchStart);
+ document.removeEventListener('touchmove', this.boundTouchMove);
+ document.removeEventListener('touchend', this.boundTouchEnd);
+
+ if (useModal) {
+ this.modal.handleClose();
}
}
- async navigate(direction) {
+ navigate(direction) {
let newIndex = this.index + direction;
- //Check if out of bounds
- if (newIndex <0 || newIndex >= this.items.length) {
- this.a11y.announceNavigation(newIndex, this.items.length, direction < 0, direction > 0);
- if (newIndex <0) {
- newIndex = this.items.length - 1;
- } else {
- newIndex = 0;
- }
+ // Wrap around
+ if (newIndex < 0) {
+ newIndex = this.items.length - 1;
+ } else if (newIndex >= this.items.length) {
+ newIndex = 0;
+ } else if (this.items.length - newIndex === 3) {
+ this.notify('load-more');
}
- //update index
this.index = newIndex;
-
this.updateDisplay();
- this.preloadImages();
+ this.preloadAdjacent();
- this.a11y.announceNavigation(this.index, this.items.length);
-
- if (typeof this.loadMore === 'function' &&
- direction > 0 &&
- newIndex >= (this.items.length - 3)) {
- if (window.feedBlock.hasMore) {
- await this.loadMore();
- this.updateGalleryItems(this.getGalleryItems());
- }
- }
- }
-
- updateGalleryItems(newItems) {
- const currentItem = this.items[this.index];
-
- this.items = newItems;
- if (currentItem) {
- const newIndex = this.items.findIndex(item =>
- item.full === currentItem.full ||
- item.large === currentItem.large
- );
-
- if (newIndex !== -1) {
- this.index = newIndex;
- }
- }
-
- this.updateNavigationButtons();
- if(this.items.length > 0 && this.openWhenReady) {
- let index = this.findIndex('id', this.openWhenReady);
- this.openGallery(index);
- }
- }
- preloadImages() {
- [-1,0,1].forEach(offset => {
- const index = this.index + offset;
- if (index >=0 && index < this.items.length) {
- const img = new Image();
- const item = this.items[index];
- if (window.innerWidth < 1000) {
- img.src = item.large || item.src;
- } else {
- img.src = item.full || item.src;
- }
- }
- });
+ this.a11y.announce(`Image ${this.index + 1} of ${this.items.length}`);
}
updateDisplay() {
const item = this.items[this.index];
if (!item) return;
- [
- this.image.src,
- this.image.alt,
- this.counter.textContent
- ] = [
- (window.innerWidth < 1000) ?
- (item.large || item.src) :
- (item.full || item.src),
- item.alt || '',
- `${this.index + 1} / ${this.items.length}`
- ];
- if (this.imageWrapper) {
- if (item.fav) {
- window.removeChildren(this.favourite);
- this.favourite.appendChild(item.fav.cloneNode(true));
+ // Use medium/full based on viewport
+ const src = window.innerWidth < 1000
+ ? (item.medium || item.src)
+ : (item.full || item.src);
+
+ this.image.src = src;
+ this.image.alt = item.alt;
+ this.counter.textContent = `${this.index + 1} / ${this.items.length}`;
+
+ // Update button states
+ this.prevBtn.classList.toggle('disabled', this.items.length <= 1);
+ this.nextBtn.classList.toggle('disabled', this.items.length <= 1);
+ }
+
+ preloadAdjacent() {
+ [-1, 1].forEach(offset => {
+ const index = this.index + offset;
+ if (index >= 0 && index < this.items.length) {
+ const item = this.items[index];
+ const img = new Image();
+ img.src = window.innerWidth < 1000
+ ? (item.medium || item.src)
+ : (item.full || item.src);
}
- if (item.info) {
- window.removeChildren(this.extra);
- this.extra.appendChild(item.info.cloneNode(true));
- }
- }
-
- this.updateNavigationButtons();
+ });
}
- updateNavigationButtons() {
- this.prevBtn.classList.toggle('end', this.index > 0 ? '' : 'none');
- this.nextBtn.classList.toggle('end', this.index < this.items.length -1 ? '' : 'none');
- }
-
- handleGalleryOpen(e) {
- let item = (this.imageWrapper) ? e.target.closest(this.imageWrapper) : e.target.closest('img');
- let key = (this.imageWrapper) ? 'id' : 'small';
- let value = item.dataset[key];
-
- let index = this.findIndex(key, value);
- this.openGallery(index);
- }
-
- findIndex(property, value) {
- return this.items.findIndex(obj => obj[property] === value);
- }
-
- openGallery(index) {
- this.initListeners();
- this.index = index;
- this.modal.handleOpen();
- this.updateDisplay();
- this.preloadImages();
-
- this.a11y.announce(`Image ${this.index + 1} of ${this.items.length}. Use arrow keys to navigate.`);
- }
-
- closeGallery(useModal = true) {
- this.destroyListeners();
- if (useModal) {
- this.modal.handleClose();
- }
+ cleanup() {
+ this.container.removeEventListener('click', this.handleClick);
+ this.closeGallery(false);
}
}
-window.jvbGallery = Gallery;
+
+document.addEventListener('DOMContentLoaded', function() {
+ let galleries = document.querySelectorAll('dialog.gallery');
+ if (galleries.length > 0) {
+ window.galleries = new Map();
+
+ galleries.forEach(gallery => {
+ let id = gallery.id;
+ window.galleries.set(id, new Gallery(gallery));
+ });
+ }
+});
+
--
Gitblit v1.10.0