PageSourceSearch

https://samuraiswordtokyo.com/wp-content/mu-plugins/sst-qo-lightbox-gallery/gallery.js?ver=0.1.0

js samuraiswordtokyo.com collected 2026-10-06 04:17:39 UTC 6,752 bytes, 144 lines download raw bytes

1/* SST QO Lightbox Gallery 0.1.0
2 * Quick Order の PSA10 / back order 一覧で画像をタップして出る拡大表示 (#sst-qo-lightbox) は
3 * 1 枚専用なので、商品にギャラリー画像 (裏面など) があるときだけ前後ボタンを足す。
4 * 開閉そのものは quick-order.js の担当のまま。ここは「開いた後の 2 枚目以降」だけを受け持つ。
5 */
6(function () {
7  'use strict';
8
9  var lightbox = document.getElementById('sst-qo-lightbox');
10  var view = lightbox && lightbox.querySelector('img');
11  if (!lightbox || !view || !window.fetch) return;
12
13  var ROW_IMG = '.sst-qo__group:is([data-qo-subgroup="psa10"], [data-qo-subgroup="psabo"]) tbody tr a.sst-qo__prod img';
14  var cache = Object.create(null); // slug -> Promise<string[]> (2 枚目以降の URL)
15  var state = null;                // { urls: string[], index: number }
16  var seq = 0;
17  var ui = null;
18
19  function chevron(d) {
20    return '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="' + d + '" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>';
21  }
22
23  function ensureUi() {
24    if (ui) return ui;
25    var prev = document.createElement('button');
26    prev.type = 'button';
27    prev.className = 'sst-qo-lbg__nav sst-qo-lbg__nav--prev';
28    prev.setAttribute('aria-label', 'Previous image');
29    prev.innerHTML = chevron('M15 5l-7 7 7 7');
30    var next = document.createElement('button');
31    next.type = 'button';
32    next.className = 'sst-qo-lbg__nav sst-qo-lbg__nav--next';
33    next.setAttribute('aria-label', 'Next image');
34    next.innerHTML = chevron('M9 5l7 7-7 7');
35    var count = document.createElement('div');
36    count.className = 'sst-qo-lbg__count';
37    count.setAttribute('aria-live', 'polite');
38    prev.hidden = next.hidden = count.hidden = true;
39    // 拡大表示は「どこを押しても閉じる」作りなので、ボタンの click はそこへ届かせない。
40    prev.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); go(-1); });
41    next.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); go(1); });
42    lightbox.appendChild(prev);
43    lightbox.appendChild(next);
44    lightbox.appendChild(count);
45    ui = { prev: prev, next: next, count: count };
46    return ui;
47  }
48
49  function hideUi() {
50    state = null;
51    if (!ui) return;
52    ui.prev.hidden = ui.next.hidden = ui.count.hidden = true;
53  }
54
55  function render() {
56    if (!state) return;
57    var u = ensureUi();
58    u.prev.hidden = u.next.hidden = u.count.hidden = false;
59    u.count.textContent = (state.index + 1) + ' / ' + state.urls.length;
60    if (view.getAttribute('src') !== state.urls[state.index]) view.src = state.urls[state.index];
61    var ahead = state.urls[(state.index + 1) % state.urls.length];
62    if (ahead) { var pre = new Image(); pre.src = ahead; }
63  }
64
65  function go(step) {
66    if (!state) return;
67    var n = state.urls.length;
68    state.index = (state.index + step + n) % n;
69    render();
70  }
71
72  // 拡大用に大きすぎない候補を選ぶ (原寸 1800px は約 1MB。表示は画面高さまで)。
73  function pickUrl(item) {
74    var best = '';
75    var bestW = Infinity;
76    String(item.srcset || '').split(',').forEach(function (part) {
77      var m = part.trim().match(/^(\S+)\s+(\d+)w$/);
78      if (!m) return;
79      var w = parseInt(m[2], 10);
80      if (w >= 1100 && w < bestW) { best = m[1]; bestW = w; }
81    });
82    return best || item.full || item.src || '';
83  }
84
85  function loadExtra(slug) {
86    if (cache[slug]) return cache[slug];
87    // 商品詳細モーダル (fetchDescriptionDetails) と同じ URL にして、ブラウザ / CDN のキャッシュを共有する。
88    var endpoint = new URL('/wp-json/wp/v2/product', window.location.origin);
89    endpoint.searchParams.set('slug', slug);
90    endpoint.searchParams.set('_fields', 'id,content,excerpt,featured_media,sst_media');
91    cache[slug] = fetch(endpoint.toString(), { credentials: 'same-origin', headers: { Accept: 'application/json' } })
92      .then(function (res) { return res.ok ? res.json() : []; })
93      .then(function (list) {
94        var media = (list && list[0] && Array.isArray(list[0].sst_media)) ? list[0].sst_media : [];
95        return media.filter(function (item) { return item && item.type === 'image'; }
95).slice(1).map(pickUrl).filter(Boolean);
96      })
97      .catch(function () { delete cache[slug]; return []; });
98    return cache[slug];
99  }
100
101  // quick-order.js と同じ document 捕獲段。あちらの stopPropagation は同じノードの
102  // リスナーには効かないので、登録順に関係なくここにも届く。
103  document.addEventListener('click', function (e) {
104    var img = e.target && e.target.closest ? e.target.closest(ROW_IMG) : null;
105    if (!img) return;
106    var root = img.closest('.sst-qo');
107    if (root && root.classList.contains('sst-qo--list')) return;
108    var tr = img.closest('tr');
109    var slug = tr && tr.dataset ? (tr.dataset.handle || '') : '';
110    var first = img.dataset.cardZoom || (tr && tr.dataset.img) || img.currentSrc || img.src || '';
111    var token = ++seq;
112    hideUi();
113    // スニダン正規画像 (uploads/sst-snkr/) の商品はギャラリーを持たない運用なので問い合わせない。
114    if (!slug || !first || first.indexOf('/sst-snkr/') !== -1) return;
115    loadExtra(slug).then(function (extra) {
116      if (token !== seq || !extra.length || lightbox.getAttribute('data-open') !== '1') return;
117      state = { urls: [first].concat(extra), index: 0 };
118      render();
119    });
120  }, true);
121
122  document.addEventListener('keydown', function (e) {
123    if (!state || lightbox.getAttribute('data-open') !== '1') return;
124    if (e.key === 'ArrowLeft') { e.preventDefault(); go(-1); }
125    else if (e.key === 'ArrowRight') { e.preventDefault(); go(1); }
126  });
127
128  var touch = null;
129  lightbox.addEventListener('touchstart', function (e) {
130    touch = (state && e.touches.length === 1) ? { x: e.touches[0].clientX, y: e.touches[0].clientY } : null;
131  }, { passive: true });
132  lightbox.addEventListener('touchend', function (e) {
133    if (!touch || !state || !e.changedTouches.length) { touch = null; return; }
134    var dx = e.changedTouches[0].clientX - touch.x;
135    var dy = e.changedTouches[0].clientY - touch.y;
136    touch = null;
137    if (Math.abs(dx) > 48 && Math.abs(dx) > Math.abs(dy) * 1.5) go(dx < 0 ? 1 : -1);
138  }, { passive: true });
139
140  // 閉じたら必ず片付ける (閉じ方は quick-order.js 側に複数ある)。
141  new MutationObserver(function () {
142    if (lightbox.getAttribute('data-open') !== '1') { seq++; hideUi(); }
143  }).observe(lightbox, { attributes: true, attributeFilter: ['data-open'] });
144})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.