tangled
alpha
login
or
join now
dunkirk.sh
/
zera
5
fork
atom
the home site for me: also iteration 3 or 4 of my site
5
fork
atom
overview
issues
pulls
pipelines
feat: move lightbox over to web components
dunkirk.sh
1 month ago
8cd4e7f4
481559f0
verified
This commit was signed with the committer's
known signature
.
dunkirk.sh
SSH Key Fingerprint:
SHA256:DqcG0RXYExE26KiWo3VxJnsxswN1QNfTBvB+bdSpk80=
+132
-72
3 changed files
expand all
collapse all
unified
split
static
js
lightbox.js
templates
shortcodes
img.html
imgs.html
+130
-70
static/js/lightbox.js
···
1
1
-
let currentLightboxImages = [];
2
2
-
let currentLightboxIndex = 0;
1
1
+
class ImageLightbox extends HTMLElement {
2
2
+
constructor() {
3
3
+
super();
4
4
+
this.currentImages = [];
5
5
+
this.currentIndex = 0;
6
6
+
this.handleImageClick = this.handleImageClick.bind(this);
7
7
+
this.handleKeyPress = this.handleKeyPress.bind(this);
8
8
+
}
3
9
4
4
-
function openLightbox(src) {
5
5
-
currentLightboxImages = [src];
6
6
-
currentLightboxIndex = 0;
7
7
-
showLightbox();
8
8
-
}
10
10
+
connectedCallback() {
11
11
+
this.render();
12
12
+
this.setupEventListeners();
13
13
+
}
9
14
10
10
-
function openLightboxGroup(element) {
11
11
-
const group = element.closest('.img-group');
12
12
-
const images = Array.from(group.querySelectorAll('img')).map(img => img.src);
13
13
-
const clickedImg = element.querySelector('img');
14
14
-
15
15
-
currentLightboxImages = images;
16
16
-
currentLightboxIndex = images.indexOf(clickedImg.src);
17
17
-
showLightbox();
18
18
-
}
15
15
+
disconnectedCallback() {
16
16
+
this.cleanup();
17
17
+
}
19
18
20
20
-
function showLightbox() {
21
21
-
let lightbox = document.getElementById('lightbox');
22
22
-
23
23
-
if (!lightbox) {
24
24
-
lightbox = document.createElement('div');
25
25
-
lightbox.id = 'lightbox';
26
26
-
lightbox.innerHTML = `
19
19
+
render() {
20
20
+
this.id = 'lightbox';
21
21
+
this.style.display = 'none';
22
22
+
this.innerHTML = `
27
23
<div class="lightbox-content">
28
28
-
<button class="lightbox-close" onclick="closeLightbox()">×</button>
29
29
-
<img id="lightbox-img" src="" alt="">
24
24
+
<button class="lightbox-close" aria-label="Close lightbox">×</button>
25
25
+
<img id="lightbox-img" src="" alt="" />
30
26
<div class="lightbox-controls">
31
31
-
<button class="lightbox-prev" onclick="prevImage()">←</button>
32
32
-
<button class="lightbox-next" onclick="nextImage()">→</button>
27
27
+
<button class="lightbox-prev" aria-label="Previous image">←</button>
28
28
+
<button class="lightbox-next" aria-label="Next image">→</button>
33
29
</div>
34
30
</div>
35
31
`;
36
36
-
document.body.appendChild(lightbox);
37
37
-
38
38
-
lightbox.addEventListener('click', (e) => {
39
39
-
if (e.target === lightbox) closeLightbox();
32
32
+
}
33
33
+
34
34
+
setupEventListeners() {
35
35
+
// Delegate clicks on images with data-lightbox attribute
36
36
+
document.addEventListener('click', this.handleImageClick);
37
37
+
38
38
+
// Lightbox controls
39
39
+
this.querySelector('.lightbox-close').addEventListener('click', () => this.close());
40
40
+
this.querySelector('.lightbox-prev').addEventListener('click', () => this.prev());
41
41
+
this.querySelector('.lightbox-next').addEventListener('click', () => this.next());
42
42
+
43
43
+
// Click backdrop to close
44
44
+
this.addEventListener('click', (e) => {
45
45
+
if (e.target === this) this.close();
40
46
});
41
41
-
42
42
-
document.addEventListener('keydown', handleKeyPress);
47
47
+
48
48
+
// Keyboard navigation
49
49
+
document.addEventListener('keydown', this.handleKeyPress);
43
50
}
44
44
-
45
45
-
updateLightboxImage();
46
46
-
lightbox.style.display = 'flex';
47
47
-
document.body.style.overflow = 'hidden';
48
48
-
}
51
51
+
52
52
+
handleImageClick(e) {
53
53
+
// Check if clicked element or its parent has data-lightbox
54
54
+
const target = e.target.closest('[data-lightbox]');
55
55
+
if (!target) return;
56
56
+
57
57
+
e.preventDefault();
58
58
+
59
59
+
const group = target.dataset.lightboxGroup;
60
60
+
61
61
+
if (group) {
62
62
+
// Find all images in the same group
63
63
+
const groupElements = Array.from(
64
64
+
document.querySelectorAll(`[data-lightbox-group="${group}"]`)
65
65
+
);
49
66
50
50
-
function closeLightbox() {
51
51
-
const lightbox = document.getElementById('lightbox');
52
52
-
if (lightbox) {
53
53
-
lightbox.style.display = 'none';
67
67
+
// Extract image sources
68
68
+
this.currentImages = groupElements.map(el => {
69
69
+
const img = el.tagName === 'IMG' ? el : el.querySelector('img');
70
70
+
return img ? img.src : null;
71
71
+
}).filter(Boolean);
72
72
+
73
73
+
// Find the index of the clicked image
74
74
+
this.currentIndex = groupElements.indexOf(target);
75
75
+
} else {
76
76
+
// Single image
77
77
+
const img = target.tagName === 'IMG' ? target : target.querySelector('img');
78
78
+
this.currentImages = img ? [img.src] : [];
79
79
+
this.currentIndex = 0;
80
80
+
}
81
81
+
82
82
+
if (this.currentImages.length > 0) {
83
83
+
this.open();
84
84
+
}
85
85
+
}
86
86
+
87
87
+
handleKeyPress(e) {
88
88
+
if (this.style.display !== 'flex') return;
89
89
+
90
90
+
if (e.key === 'Escape') {
91
91
+
this.close();
92
92
+
} else if (e.key === 'ArrowLeft') {
93
93
+
this.prev();
94
94
+
} else if (e.key === 'ArrowRight') {
95
95
+
this.next();
96
96
+
}
97
97
+
}
98
98
+
99
99
+
open() {
100
100
+
this.style.display = 'flex';
101
101
+
const controls = this.querySelector('.lightbox-controls');
102
102
+
if (this.currentImages.length > 1) {
103
103
+
controls.style.display = 'flex';
104
104
+
} else {
105
105
+
controls.style.display = 'none';
106
106
+
}
107
107
+
document.body.style.overflow = 'hidden';
108
108
+
this.updateImage();
109
109
+
}
110
110
+
111
111
+
close() {
112
112
+
this.style.display = 'none';
54
113
document.body.style.overflow = '';
55
114
}
56
56
-
}
57
115
58
58
-
function updateLightboxImage() {
59
59
-
const img = document.getElementById('lightbox-img');
60
60
-
const controls = document.querySelector('.lightbox-controls');
61
61
-
62
62
-
img.src = currentLightboxImages[currentLightboxIndex];
63
63
-
64
64
-
if (currentLightboxImages.length === 1) {
65
65
-
controls.style.display = 'none';
66
66
-
} else {
67
67
-
controls.style.display = 'flex';
116
116
+
updateImage() {
117
117
+
const img = this.querySelector('#lightbox-img');
118
118
+
img.src = this.currentImages[this.currentIndex];
119
119
+
}
120
120
+
121
121
+
prev() {
122
122
+
this.currentIndex = (this.currentIndex - 1 + this.currentImages.length) % this.currentImages.length;
123
123
+
this.updateImage();
124
124
+
}
125
125
+
126
126
+
next() {
127
127
+
this.currentIndex = (this.currentIndex + 1) % this.currentImages.length;
128
128
+
this.updateImage();
68
129
}
69
69
-
}
70
130
71
71
-
function prevImage() {
72
72
-
currentLightboxIndex = (currentLightboxIndex - 1 + currentLightboxImages.length) % currentLightboxImages.length;
73
73
-
updateLightboxImage();
131
131
+
cleanup() {
132
132
+
document.removeEventListener('click', this.handleImageClick);
133
133
+
document.removeEventListener('keydown', this.handleKeyPress);
134
134
+
document.body.style.overflow = '';
135
135
+
}
74
136
}
75
137
76
76
-
function nextImage() {
77
77
-
currentLightboxIndex = (currentLightboxIndex + 1) % currentLightboxImages.length;
78
78
-
updateLightboxImage();
138
138
+
customElements.define('image-lightbox', ImageLightbox);
139
139
+
140
140
+
// Auto-initialize: add lightbox to page if not already present
141
141
+
if (document.readyState === 'loading') {
142
142
+
document.addEventListener('DOMContentLoaded', initLightbox);
143
143
+
} else {
144
144
+
initLightbox();
79
145
}
80
146
81
81
-
function handleKeyPress(e) {
82
82
-
const lightbox = document.getElementById('lightbox');
83
83
-
if (!lightbox || lightbox.style.display !== 'flex') return;
84
84
-
85
85
-
if (e.key === 'Escape') {
86
86
-
closeLightbox();
87
87
-
} else if (e.key === 'ArrowLeft') {
88
88
-
prevImage();
89
89
-
} else if (e.key === 'ArrowRight') {
90
90
-
nextImage();
147
147
+
function initLightbox() {
148
148
+
if (!document.querySelector('image-lightbox')) {
149
149
+
const lightbox = document.createElement('image-lightbox');
150
150
+
document.body.appendChild(lightbox);
91
151
}
92
152
}
+1
-1
templates/shortcodes/img.html
···
1
1
<figure {% if class %}class="{{class}}" {% else %}class="center" {% endif %}>
2
2
-
<div class="img-container" onclick="openLightbox('{{id}}')">
2
2
+
<div class="img-container" data-lightbox>
3
3
<img src="{{id}}" {% if alt %}alt="{{alt}}" {% endif %} />
4
4
</div>
5
5
{% if caption %}
+1
-1
templates/shortcodes/imgs.html
···
3
3
{% set images = id | split(pat=",") %}
4
4
{% set alts = alt | default(value="") | split(pat=",") %}
5
5
{% for image in images %}
6
6
-
<div class="img-container" onclick="openLightboxGroup(this)">
6
6
+
<div class="img-container" data-lightbox data-lightbox-group="group-{{ id | slugify }}">
7
7
<img src="{{image | trim}}" {% if alts[loop.index0] %}alt="{{alts[loop.index0] | trim}}" {% endif %} />
8
8
</div>
9
9
{% endfor %}