jekyll-image-links 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9d85919e65b8d1b2cf32c8d1ffccc9b149f8d84e7950020f5d291e0262dee60
4
- data.tar.gz: f933adffe5a1970b78669ae40edd0f5595342c01e43e7f1f53c50072a1f5236b
3
+ metadata.gz: 1d2fc33af0a1cda25f029a7a72d68e32eefa8f1fa412fdd4f69281fdb8676a29
4
+ data.tar.gz: 174005c6a08c2f617d953ebe2d874f324c3bfd0107436e93ff23f490bc623606
5
5
  SHA512:
6
- metadata.gz: 4454cc915358b308486232099c78a6db594d6c86318e4608cf490ba9a2b6de954a76f276c0e2b91a61127115535ca1eb550ab901a2218cd42be86acd5b7ac83d
7
- data.tar.gz: 59a5bbac2e96705896e0ddbcedefe36b215f3fc6fbe541c562b4e3a3b91f8f604df03fd0570daf86bcc0898059715eae29a22fb95c7c489457c27a603ff6e76f
6
+ metadata.gz: 6cab285f59d4c32b302686e4995113c2b8ceaba8bbeaf29d9ec2a329ffd2ded5b8de487f6b26f62049c25c9fd155a7d64665d5cd5f807966d6aefae15618670f
7
+ data.tar.gz: 3be18ab75e132c7c7ad4b054879fac3ebb27d72efaedc7b34be916765b049b4c27cc8c45648f3c819ec8bd55fa1df6fe06895adfb380cec51087c14fd74ca526
@@ -3,6 +3,12 @@
3
3
 
4
4
  const ROOT_SELECTOR = '[data-jil-root="true"]';
5
5
  const config = window.__JIL_CONFIG__ || {};
6
+ // Hosts that already have listeners on this element instance.
7
+ // data-jil-ready is not enough: a hover-popup preview clones the live map,
8
+ // so the attribute is copied, but click listeners are not. A WeakSet tracks
9
+ // the actual element. Clones are absent from it and get set up again, and
10
+ // entries disappear when the preview window is discarded.
11
+ const readyHosts = new WeakSet();
6
12
 
7
13
  function getHoverPopup() {
8
14
  const api = window.JekyllHoverPopup;
@@ -83,7 +89,23 @@
83
89
  );
84
90
  });
85
91
 
86
- document.querySelectorAll("[data-jil-map]").forEach((host) => {
92
+ // Maps already in the page. Maps that appear later, such as inside a
93
+ // preview window, are handled by watchInsertedMaps.
94
+ initWithin(document);
95
+ }
96
+
97
+ // Set up every map host inside root. root may be the document (first load)
98
+ // or a node just inserted into a preview window.
99
+ function initWithin(root) {
100
+ if (!root || !root.querySelectorAll) return;
101
+
102
+ const hosts = [];
103
+ // The inserted node can be the host itself. querySelectorAll only looks
104
+ // at descendants, so include root when it is a map host.
105
+ if (root.nodeType === 1 && root.matches("[data-jil-map]")) hosts.push(root);
106
+ root.querySelectorAll("[data-jil-map]").forEach((host) => hosts.push(host));
107
+
108
+ hosts.forEach((host) => {
87
109
  try {
88
110
  initMapHost(host);
89
111
  } catch (error) {
@@ -92,9 +114,45 @@
92
114
  });
93
115
  }
94
116
 
117
+ // Page load only sees maps in the original document. Preview windows fetch
118
+ // or clone section HTML later, so watch for those nodes and set them up then.
119
+ function watchInsertedMaps() {
120
+ if (!document.body || typeof MutationObserver === "undefined") return;
121
+
122
+ const observer = new MutationObserver((mutations) => {
123
+ mutations.forEach((mutation) => {
124
+ mutation.addedNodes.forEach((node) => {
125
+ // Text nodes cannot contain a map host.
126
+ if (node.nodeType !== 1) return;
127
+ initWithin(node);
128
+ });
129
+ });
130
+ });
131
+
132
+ // subtree covers maps nested inside the preview's section wrapper,
133
+ // not only nodes appended directly to body.
134
+ observer.observe(document.body, { childList: true, subtree: true });
135
+ }
136
+
137
+ // Drop UI that was copied from an already-initialized map. Labels and the
138
+ // viewer button are created in JS, so a clone includes the elements but not
139
+ // their listeners. Removing them lets init add a fresh, working copy.
140
+ // Server-rendered markup has neither, so this is a no-op on first setup.
141
+ function stripGeneratedUi(host) {
142
+ host.querySelectorAll(".jil-label-layer").forEach((node) => node.remove());
143
+ const figure = host.closest(".jil-figure");
144
+ if (!figure) return;
145
+ // The toolbar is a sibling of the host, inserted after the caption.
146
+ figure.querySelectorAll(":scope > .jil-toolbar").forEach((node) => node.remove());
147
+ }
148
+
95
149
  function initMapHost(host) {
96
- if (host.dataset.jilReady === "true") return;
150
+ // Skip hosts already bound in this document. Clones are different elements
151
+ // and fall through even when they carry data-jil-ready from the original.
152
+ if (readyHosts.has(host)) return;
153
+ readyHosts.add(host);
97
154
  host.dataset.jilReady = "true";
155
+ stripGeneratedUi(host);
98
156
 
99
157
  let mapData;
100
158
  try {
@@ -120,24 +178,57 @@
120
178
  if (viewer) addViewerButton(host, mapData);
121
179
  }
122
180
 
181
+ // Keep overlay geometry in sync with the image, including inside a preview
182
+ // that is opened, resized, and then closed.
183
+ function bindLiveUpdates(host, img, update) {
184
+ const onLoad = () => update();
185
+ img.addEventListener("load", onLoad);
186
+
187
+ // Window listeners and a ResizeObserver would keep a closed preview's
188
+ // nodes alive. Drop them once the host has left the document.
189
+ const detach = () => {
190
+ window.removeEventListener("resize", onResize);
191
+ if (observer) observer.disconnect();
192
+ };
193
+
194
+ const onResize = () => {
195
+ if (!host.isConnected) {
196
+ detach();
197
+ return;
198
+ }
199
+ update();
200
+ };
201
+
202
+ let observer = null;
203
+ if (typeof ResizeObserver !== "undefined") {
204
+ observer = new ResizeObserver(() => {
205
+ // Closing a preview disconnects the host and fires this observer.
206
+ if (!host.isConnected) {
207
+ detach();
208
+ return;
209
+ }
210
+ update();
211
+ });
212
+ // Watch both: the host size follows the preview window, and the image
213
+ // size follows its own load and CSS.
214
+ observer.observe(host);
215
+ observer.observe(img);
216
+ }
217
+
218
+ window.addEventListener("resize", onResize);
219
+ // A cached image may already be complete, so the load event never fires.
220
+ if (img.complete) update();
221
+ }
222
+
123
223
  function bindOverlayGeometry(overlay, host, mapData, img) {
124
- const update = () => {
224
+ bindLiveUpdates(host, img, () => {
125
225
  const geom = getMapGeometry(img, mapData);
126
226
  if (!geom || !geom.width || !geom.height) return;
127
227
  overlay.style.left = `${geom.offsetX}px`;
128
228
  overlay.style.top = `${geom.offsetY}px`;
129
229
  overlay.style.width = `${geom.width}px`;
130
230
  overlay.style.height = `${geom.height}px`;
131
- };
132
-
133
- if (img.complete) update();
134
- img.addEventListener("load", update);
135
- window.addEventListener("resize", update);
136
- if (typeof ResizeObserver !== "undefined") {
137
- const observer = new ResizeObserver(update);
138
- observer.observe(host);
139
- observer.observe(img);
140
- }
231
+ });
141
232
  }
142
233
 
143
234
  function renderLabels(host, mapData, img) {
@@ -146,7 +237,7 @@
146
237
  host.appendChild(overlay);
147
238
  bindOverlayGeometry(overlay, host, mapData, img);
148
239
 
149
- const update = () => {
240
+ bindLiveUpdates(host, img, () => {
150
241
  overlay.innerHTML = "";
151
242
  const geom = getMapGeometry(img, mapData);
152
243
  if (!geom || !geom.width || !geom.height) return;
@@ -169,16 +260,7 @@
169
260
  link.title = region.title || label;
170
261
  overlay.appendChild(link);
171
262
  });
172
- };
173
-
174
- if (img.complete) update();
175
- img.addEventListener("load", update);
176
- window.addEventListener("resize", update);
177
- if (typeof ResizeObserver !== "undefined") {
178
- const observer = new ResizeObserver(update);
179
- observer.observe(host);
180
- observer.observe(img);
181
- }
263
+ });
182
264
  }
183
265
 
184
266
  function bindInlineClicks(host, mapData, img) {
@@ -593,7 +675,13 @@
593
675
  return;
594
676
  }
595
677
 
596
- const run = () => init();
678
+ const run = () => {
679
+ // Observe before the first pass so a preview inserted during init is
680
+ // not missed. Init itself does not add map hosts, so this does not
681
+ // set the same map up twice.
682
+ watchInsertedMaps();
683
+ init();
684
+ };
597
685
 
598
686
  if (document.querySelector('[data-hover-popup-root="true"]') && !getHoverPopup()) {
599
687
  window.setTimeout(run, 0);
@@ -1,5 +1,5 @@
1
1
  module Jekyll
2
2
  module ImageLinks
3
- VERSION = "0.3.0"
3
+ VERSION = "0.3.1"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-image-links
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - directsun