jekyll-image-links 0.3.0 → 0.4.0

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: bdf6ba05c3bcb44228b8870062fbb9f63d2109ddb90f77d619be852393d68498
4
+ data.tar.gz: 00dbc1cad4345d24f0524cb94b824e7fbc49175d97332e9271ff765dabb67d25
5
5
  SHA512:
6
- metadata.gz: 4454cc915358b308486232099c78a6db594d6c86318e4608cf490ba9a2b6de954a76f276c0e2b91a61127115535ca1eb550ab901a2218cd42be86acd5b7ac83d
7
- data.tar.gz: 59a5bbac2e96705896e0ddbcedefe36b215f3fc6fbe541c562b4e3a3b91f8f604df03fd0570daf86bcc0898059715eae29a22fb95c7c489457c27a603ff6e76f
6
+ metadata.gz: 6a968dfeb35aeac174223c9f1403ccce199be670a940bc00da656e864b5b7ff09de4f7b25f92f7ca2677094afea3b05a8218308d428f33c5a87169f45d90b710
7
+ data.tar.gz: cabf1ad7e11d7f455d86f6b82f152451c0e122f3416fe24d66b89e66ca118136e93b06dcc1a08c1f5c573a81f6bb21315d4b73b65840bcc0c60737b99a83d559
data/README.md CHANGED
@@ -14,7 +14,7 @@ Inspired by the Dynamic Map Viewer in [5etools](https://github.com/5etools/5etoo
14
14
  - Optional region labels overlaid on the image
15
15
  - Integrates with [jekyll-hover-popup](https://github.com/sunflowermans/hover-popup) when both plugins are enabled:
16
16
  - Map viewer opens inside a hover-popup window
17
- - Region clicks open pinned hover-popup windows
17
+ - Region clicks open pinned hover-popup windows, unless the map sets `preview="false"`
18
18
 
19
19
  https://github.com/user-attachments/assets/9b2f123b-56a4-43f4-984f-2dbfa7b844f9
20
20
 
@@ -46,10 +46,11 @@ image_links:
46
46
  viewer_by_default: true
47
47
  inline_by_default: true
48
48
  labels_by_default: false
49
+ preview_by_default: true
49
50
  use_hover_popup: auto # auto | true | false
50
51
  ```
51
52
 
52
- When `jekyll-hover-popup` is also installed, image map region clicks open section previews in hover-popup windows instead of navigating the page. Set `use_hover_popup: false` to disable that integration.
53
+ When `jekyll-hover-popup` is also installed, image map region clicks open section previews in hover-popup windows instead of navigating the page. Set `use_hover_popup: false` to disable that integration for the whole site. Set `preview_by_default: false`, or `preview="false"` on a single map, to keep the integration and still navigate the current page when a region is clicked.
53
54
 
54
55
  ## Usage
55
56
 
@@ -113,6 +114,7 @@ When `width` and `height` are in the YAML file, you can omit them from the tag a
113
114
  | `viewer="false"` | Disable the Dynamic Map Viewer button |
114
115
  | `inline="false"` | Disable direct click regions on the inline image |
115
116
  | `labels="true"` | Show region labels on the inline image |
117
+ | `preview="false"` | Navigate the current page on region click instead of opening a preview window |
116
118
 
117
119
  ### Manual HTML (portable markup)
118
120
 
@@ -135,6 +137,7 @@ If the plugin is disabled or not installed, the image still renders normally and
135
137
  data-jil-viewer="true"
136
138
  data-jil-inline="true"
137
139
  data-jil-labels="true"
140
+ data-jil-preview="true"
138
141
  />
139
142
  {:/nomarkdown}
140
143
  ```
@@ -160,6 +163,7 @@ Qualified images are detected by `class="jil-map-image"` together with region da
160
163
  | `data-jil-viewer` | Enable/disable the Dynamic Map Viewer button |
161
164
  | `data-jil-inline` | Enable/disable direct click regions on the inline image |
162
165
  | `data-jil-labels` | Show region labels on the inline image |
166
+ | `data-jil-preview` | Open a preview window on region click, or navigate the current page |
163
167
 
164
168
  ## Coordinate system
165
169
 
@@ -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 {
@@ -107,6 +165,8 @@
107
165
  const inline = host.dataset.jilInline !== "false";
108
166
  const viewer = host.dataset.jilViewer !== "false";
109
167
  const labels = host.dataset.jilLabels === "true";
168
+ // Absent means preview, so maps rendered before this flag still open one.
169
+ const preview = host.dataset.jilPreview !== "false";
110
170
  const img = host.querySelector(".jil-map-image");
111
171
 
112
172
  if (!img) {
@@ -116,28 +176,61 @@
116
176
 
117
177
  reportMapProblems(img, mapData);
118
178
  if (labels) renderLabels(host, mapData, img);
119
- if (inline) bindInlineClicks(host, mapData, img);
120
- if (viewer) addViewerButton(host, mapData);
179
+ if (inline) bindInlineClicks(host, mapData, img, preview);
180
+ if (viewer) addViewerButton(host, mapData, preview);
181
+ }
182
+
183
+ // Keep overlay geometry in sync with the image, including inside a preview
184
+ // that is opened, resized, and then closed.
185
+ function bindLiveUpdates(host, img, update) {
186
+ const onLoad = () => update();
187
+ img.addEventListener("load", onLoad);
188
+
189
+ // Window listeners and a ResizeObserver would keep a closed preview's
190
+ // nodes alive. Drop them once the host has left the document.
191
+ const detach = () => {
192
+ window.removeEventListener("resize", onResize);
193
+ if (observer) observer.disconnect();
194
+ };
195
+
196
+ const onResize = () => {
197
+ if (!host.isConnected) {
198
+ detach();
199
+ return;
200
+ }
201
+ update();
202
+ };
203
+
204
+ let observer = null;
205
+ if (typeof ResizeObserver !== "undefined") {
206
+ observer = new ResizeObserver(() => {
207
+ // Closing a preview disconnects the host and fires this observer.
208
+ if (!host.isConnected) {
209
+ detach();
210
+ return;
211
+ }
212
+ update();
213
+ });
214
+ // Watch both: the host size follows the preview window, and the image
215
+ // size follows its own load and CSS.
216
+ observer.observe(host);
217
+ observer.observe(img);
218
+ }
219
+
220
+ window.addEventListener("resize", onResize);
221
+ // A cached image may already be complete, so the load event never fires.
222
+ if (img.complete) update();
121
223
  }
122
224
 
123
225
  function bindOverlayGeometry(overlay, host, mapData, img) {
124
- const update = () => {
226
+ bindLiveUpdates(host, img, () => {
125
227
  const geom = getMapGeometry(img, mapData);
126
228
  if (!geom || !geom.width || !geom.height) return;
127
229
  overlay.style.left = `${geom.offsetX}px`;
128
230
  overlay.style.top = `${geom.offsetY}px`;
129
231
  overlay.style.width = `${geom.width}px`;
130
232
  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
- }
233
+ });
141
234
  }
142
235
 
143
236
  function renderLabels(host, mapData, img) {
@@ -146,7 +239,7 @@
146
239
  host.appendChild(overlay);
147
240
  bindOverlayGeometry(overlay, host, mapData, img);
148
241
 
149
- const update = () => {
242
+ bindLiveUpdates(host, img, () => {
150
243
  overlay.innerHTML = "";
151
244
  const geom = getMapGeometry(img, mapData);
152
245
  if (!geom || !geom.width || !geom.height) return;
@@ -169,19 +262,10 @@
169
262
  link.title = region.title || label;
170
263
  overlay.appendChild(link);
171
264
  });
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
- }
265
+ });
182
266
  }
183
267
 
184
- function bindInlineClicks(host, mapData, img) {
268
+ function bindInlineClicks(host, mapData, img, preview) {
185
269
  host.classList.add("jil-inline");
186
270
  img.addEventListener("click", (event) => {
187
271
  const point = imagePointFromEvent(img, mapData, event);
@@ -189,7 +273,7 @@
189
273
  if (!region) return;
190
274
 
191
275
  event.preventDefault();
192
- openRegion(region, event);
276
+ openRegion(region, event, preview);
193
277
  });
194
278
 
195
279
  img.addEventListener("mousemove", (event) => {
@@ -199,7 +283,7 @@
199
283
  });
200
284
  }
201
285
 
202
- function addViewerButton(host, mapData) {
286
+ function addViewerButton(host, mapData, preview) {
203
287
  const figure = host.closest(".jil-figure");
204
288
  if (!figure) return;
205
289
 
@@ -211,7 +295,7 @@
211
295
  button.type = "button";
212
296
  button.className = "jil-viewer-button btn btn-outline";
213
297
  button.textContent = mapData.title ? `Open ${mapData.title}` : "Open map viewer";
214
- button.addEventListener("click", (event) => openViewer(mapData, event));
298
+ button.addEventListener("click", (event) => openViewer(mapData, event, preview));
215
299
  toolbar.appendChild(button);
216
300
 
217
301
  if (caption) {
@@ -221,8 +305,8 @@
221
305
  }
222
306
  }
223
307
 
224
- function openViewer(mapData, event) {
225
- const panel = buildViewerPanel(mapData);
308
+ function openViewer(mapData, event, preview) {
309
+ const panel = buildViewerPanel(mapData, preview);
226
310
  const jhp = useHoverPopup() ? getHoverPopup() : null;
227
311
 
228
312
  if (jhp) {
@@ -254,7 +338,7 @@
254
338
  });
255
339
  }
256
340
 
257
- function buildViewerPanel(mapData) {
341
+ function buildViewerPanel(mapData, preview) {
258
342
  const root = document.createElement("div");
259
343
  root.className = "jil-viewer-panel jil-viewer-in-popup";
260
344
 
@@ -365,7 +449,7 @@
365
449
  const region = firstIntersectedRegion(mapData.regions, point);
366
450
  if (!region) return;
367
451
  event.preventDefault();
368
- openRegion(region, event);
452
+ openRegion(region, event, preview);
369
453
  });
370
454
 
371
455
  canvas.addEventListener("mousemove", (event) => {
@@ -446,13 +530,13 @@
446
530
  return button;
447
531
  }
448
532
 
449
- async function openRegion(region, event) {
533
+ async function openRegion(region, event, preview) {
450
534
  if (event.shiftKey) {
451
535
  window.open(region.href, "_blank", "noopener,noreferrer");
452
536
  return;
453
537
  }
454
538
 
455
- const jhp = useHoverPopup() ? getHoverPopup() : null;
539
+ const jhp = preview !== false && useHoverPopup() ? getHoverPopup() : null;
456
540
  if (jhp) {
457
541
  event.preventDefault();
458
542
  try {
@@ -593,7 +677,13 @@
593
677
  return;
594
678
  }
595
679
 
596
- const run = () => init();
680
+ const run = () => {
681
+ // Observe before the first pass so a preview inserted during init is
682
+ // not missed. Init itself does not add map hosts, so this does not
683
+ // set the same map up twice.
684
+ watchInsertedMaps();
685
+ init();
686
+ };
597
687
 
598
688
  if (document.querySelector('[data-hover-popup-root="true"]') && !getHoverPopup()) {
599
689
  window.setTimeout(run, 0);
@@ -45,6 +45,7 @@ module Jekyll
45
45
  viewer = attrs["viewer"]
46
46
  inline = attrs["inline"]
47
47
  labels = attrs["labels"]
48
+ preview = attrs["preview"]
48
49
 
49
50
  resolved_src = resolve_url(src, site: site, page: page, liquid_context: liquid_context)
50
51
  map_source = load_map_source(
@@ -59,6 +60,7 @@ module Jekyll
59
60
  viewer_attr = %( data-jil-viewer="#{viewer}") unless viewer.nil? || viewer.empty?
60
61
  inline_attr = %( data-jil-inline="#{inline}") unless inline.nil? || inline.empty?
61
62
  labels_attr = %( data-jil-labels="#{labels}") unless labels.nil? || labels.empty?
63
+ preview_attr = %( data-jil-preview="#{preview}") unless preview.nil? || preview.empty?
62
64
  title_attr = title ? %( data-jil-title="#{escape_attr(title)}") : ""
63
65
 
64
66
  regions_script = ""
@@ -78,12 +80,12 @@ module Jekyll
78
80
  <img
79
81
  class="jil-map-image"
80
82
  #{img_markup}#{style_attr}
81
- loading="lazy"#{title_attr}#{regions_attr}#{viewer_attr}#{inline_attr}#{labels_attr}
83
+ loading="lazy"#{title_attr}#{regions_attr}#{viewer_attr}#{inline_attr}#{labels_attr}#{preview_attr}
82
84
  />#{regions_script}
83
85
  HTML
84
86
  end
85
87
 
86
- def render_interactive(map_data, viewer:, inline:, labels:, alt:, display_style: nil)
88
+ def render_interactive(map_data, viewer:, inline:, labels:, preview:, alt:, display_style: nil)
87
89
  map_json = JSON.generate(map_data)
88
90
  id = "jil-map-#{Digest::MD5.hexdigest(map_json)[0, 8]}"
89
91
  title = map_data["title"]
@@ -99,7 +101,7 @@ module Jekyll
99
101
  host_style_attr = display_style.to_s.strip.empty? ? "" : %( style="#{escape_attr(display_style)}")
100
102
 
101
103
  <<~HTML.strip
102
- <div class="jil-figure" data-jil-image-map="true"><div class="#{host_class}" id="#{id}" data-jil-map="#{escape_attr(map_json)}" data-jil-viewer="#{viewer}" data-jil-inline="#{inline}" data-jil-labels="#{labels}"#{host_style_attr.empty? ? "" : " #{host_style_attr}"}><img class="jil-map-image" #{img_attrs} loading="lazy" /></div>#{caption_html}</div>
104
+ <div class="jil-figure" data-jil-image-map="true"><div class="#{host_class}" id="#{id}" data-jil-map="#{escape_attr(map_json)}" data-jil-viewer="#{viewer}" data-jil-inline="#{inline}" data-jil-labels="#{labels}" data-jil-preview="#{preview}"#{host_style_attr.empty? ? "" : " #{host_style_attr}"}><img class="jil-map-image" #{img_attrs} loading="lazy" /></div>#{caption_html}</div>
103
105
  HTML
104
106
  end
105
107
 
@@ -125,6 +127,7 @@ module Jekyll
125
127
  "viewer" => container_attrs["data-jil-viewer"] || img_attrs["data-jil-viewer"],
126
128
  "inline" => container_attrs["data-jil-inline"] || img_attrs["data-jil-inline"],
127
129
  "labels" => container_attrs["data-jil-labels"] || img_attrs["data-jil-labels"],
130
+ "preview" => container_attrs["data-jil-preview"] || img_attrs["data-jil-preview"],
128
131
  }
129
132
 
130
133
  merged["title"] ||= img_attrs["data-jil-title"] || merged["alt"]
@@ -148,6 +151,7 @@ module Jekyll
148
151
  viewer = parse_bool(attrs["viewer"], default: cfg.fetch("viewer_by_default", true))
149
152
  inline = parse_bool(attrs["inline"], default: cfg.fetch("inline_by_default", true))
150
153
  labels = parse_bool(attrs["labels"], default: cfg.fetch("labels_by_default", false))
154
+ preview = parse_bool(attrs["preview"], default: cfg.fetch("preview_by_default", true))
151
155
 
152
156
  map_source = load_map_source(
153
157
  site: site,
@@ -178,6 +182,7 @@ module Jekyll
178
182
  viewer: viewer,
179
183
  inline: inline,
180
184
  labels: labels,
185
+ preview: preview,
181
186
  alt: alt,
182
187
  display_style: display_style
183
188
  )
@@ -1,5 +1,5 @@
1
1
  module Jekyll
2
2
  module ImageLinks
3
- VERSION = "0.3.0"
3
+ VERSION = "0.4.0"
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.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - directsun