PageSourceSearch

https://primo-color.jp/wp-content/themes/SumahoDeHoken/js/bookmark.js?ver=1.0.8

js primo-color.jp collected 2026-10-04 03:33:41 UTC 13,252 bytes, 472 lines download raw bytes

1document.addEventListener('DOMContentLoaded', () => {
2  const COOKIE_NAME = 'bookmarked_products';
3  const COOKIE_DAYS = 7;
4
5  let popupElement = null;
6  let popupHideTimer = null;
7  let popupRemoveTimer = null;
8
9  //デバイスを取得
10  function setShareDeviceClass() {
11    const ua = navigator.userAgent || '';
12    const platform = navigator.platform || '';
13
14    const isAndroid = /Android/i.test(ua);
15
16    const isIOS =
17      /iPhone|iPad|iPod/i.test(ua) ||
18      (
19        platform === 'MacIntel' &&
20        navigator.maxTouchPoints > 1
21      );
22
23    document.body.classList.remove(
24      'is-ios',
25      'is-android',
26      'is-other-device'
27    );
28
29    if (isIOS) {
30      document.body.classList.add('is-ios');
31    } else if (isAndroid) {
32      document.body.classList.add('is-android');
33    } else {
34      document.body.classList.add('is-other-device');
35    }
36  }
37
38  setShareDeviceClass();
39
40  function getCookie(name) {
41    const cookies = document.cookie ? document.cookie.split('; ') : [];
42    for (const cookie of cookies) {
43      const [key, ...rest] = cookie.split('=');
44      if (key === name) {
45        return decodeURIComponent(rest.join('='));
46      }
47    }
48    return null;
49  }
50
51  function setCookie(name, value, days) {
52    const expires = new Date();
53    expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000);
54    document.cookie =
55      `${name}=${encodeURIComponent(value)}; expires=${expires.toUTCString()}; path=/; SameSite=Lax`;
56  }
57
58  function getBookmarks() {
59    const value = getCookie(COOKIE_NAME);
60    if (!value) return [];
61
62    try {
63      const parsed = JSON.parse(value);
64      if (!Array.isArray(parsed)) return [];
65      return parsed.map(String);
66    } catch (e) {
67      return [];
68    }
69  }
70
71  function saveBookmarks(bookmarks) {
72    const unique = [...new Set(bookmarks.map(String))];
73    setCookie(COOKIE_NAME, JSON.stringify(unique), COOKIE_DAYS);
74  }
75
76  function isBookmarked(productId) {
77    return getBookmarks().includes(String(productId));
78  }
79
80  function addBookmark(productId) {
81    const bookmarks = getBookmarks();
82    const id = String(productId);
83
84    if (!bookmarks.includes(id)) {
85      bookmarks.push(id);
86      saveBookmarks(bookmarks);
87    }
88  }
89
90  function removeBookmark(productId) {
91    const id = String(productId);
92    const bookmarks = getBookmarks().filter(bookmarkId => bookmarkId !== id);
93    saveBookmarks(bookmarks);
94  }
95
96  function updateBookmarkClass(productLoop) {
97    if (!productLoop) return;
98
99    const productId = productLoop.dataset.productId;
100    if (!productId) return;
101
102    if (isBookmarked(productId)) {
103      productLoop.classList.remove('not-bookmarked');
104      productLoop.classList.add('bookmarked');
105    } else {
106      productLoop.classList.remove('bookmarked');
107      productLoop.classList.add('not-bookmarked');
108    }
109  }
110
111  function updateAllBookmarkClasses() {
112    document.querySelectorAll('.product-loop-full').forEach(updateBookmarkClass);
113  }
114
115  function setInitialSaveOrder() {
116    const productItems = document.querySelectorAll('.product-list .product-loop-full');
117    productItems.forEach((item, index) => {
118      item.dataset.saveOrder = String(index);
119    });
120  }
121
122  function sortProductList(sortType = 'save') {
123    const productList = document.querySelector('.product-list');
124    if (!productList) return;
125
126    const items = Array.from(productList.querySelectorAll('.product-loop-full'));
127    if (!items.length) return;
128
129    items.sort((a, b) => {
130      if (sortType === 'popularity') {
131        const rankA = parseInt(a.dataset.productRank || '999999', 10);
132        const rankB = parseInt(b.dataset.productRank || '999999', 10);
133        return rankA - rankB;
134      }
135
136      const saveOrderA = parseInt(a.dataset.saveOrder || '999999', 10);
137      const saveOrderB = parseInt(b.dataset.saveOrder || '999999', 10);
138      return saveOrderA - saveOrderB;
139    });
140
141    items.forEach(item => {
142      productList.appendChild(item);
143    });
144  }
145
146  function initBookmarkSort() {
147    const orderSelect = document.querySelector('#order');
148    if (!orderSelect) return;
149
150    setInitialSaveOrder();
151    sortProductList(orderSelect.value);
152
153    orderSelect.addEventListener('change', () => {
154      sortProductList(orderSelect.value);
155    });
156  }
157
158  function clearBookmarkPopup() {
159    if (popupHideTimer) {
160      clearTimeout(popupHideTimer);
161      popupHideTimer = null;
162    }
163
164    if (popupRemoveTimer) {
165      clearTimeout(popupRemoveTimer);
166      popupRemoveTimer = null;
167    }
168
169    if (popupElement) {
170      popupElement.remove();
171      popupElement = null;
172    }
173  }
174
175  function showBookmarkPopup(message) {
176    clearBookmarkPopup();
177
178    const popup = document.createElement('div');
179    popup.className = 'bookmark-popup';
180    popup.innerHTML = `<div class="bookmark-popup__inner">${message}</div>`;
181
182    document.body.appendChild(popup);
183    popupElement = popup;
184
185    requestAnimationFrame(() => {
186      if (popupElement) {
187        popupElement.classList.add('is-show');
188      }
189    });
190
191    popupHideTimer = setTimeout(() => {
192      if (!popupElement) return;
193      popupElement.classList.remove('is-show');
194      popupElement.classList.add('is-hide');
195    }, 2000);
196
197    popupRemoveTimer = setTimeout(() => {
198      clearBookmarkPopup();
199    }, 2600);
200  }
201
202  // 初期表示時にCookieの内容を反映
203  updateAllBookmarkClasses();
204
205  // 並び順初期化
206  initBookmarkSort();
207
208  // クリックでブックマーク切り替え
209  document.addEventListener('click', (event) => {
210    const button = event.target.closest('.bookmark-button');
211    if (!button) return;
212
213    const productLoop = button.closest('.product-loop-full');
214    if (!productLoop) return;
215
216    const productId = productLoop.dataset.productId;
217    if (!productId) return;
218
219    if (productLoop.classList.contains('bookmarked')) {
220      removeBookmark(productId);
221      updateBookmarkClass(productLoop);
222      showBookmarkPopup('商品を保存リストから削除しました');
223    } else {
224      addBookmark(productId);
225      updateBookmarkClass(productLoop);
226      showBookmarkPopup('商品を保存リストに追加しました');
227    }
228  });
229
230  (() => {
231    const sheet = document.getElementById('product-share-sheet');
232    const toast = document.getElementById('share-toast');
233
234    if (!sheet) return;
235
236    const previewImage = document.getElementById('share-preview-image');
237    const previewCompany = document.getElementById('share-preview-company');
238    const previewTitle = document.getElementById('share-preview-title');
239    const previewSummary = document.getElementById('share-preview-summary');
240
241    let activeTrigger = null;
242    let toastTimer = null;
243
244    function getShareData() {
245      return {
246        url: sheet.dataset.shareUrl || window.location.href,
247        title: sheet.dataset.shareTitle || document.title,
248        text: sheet.dataset.shareText || document.title,
249        image: sheet.dataset.shareImage || '',
250        company: sheet.dataset.shareCompany || '',
251        summary: sheet.dataset.shareSummary || ''
252      };
253    }
254
255    function buildShareBody(data) {
256      return [data.text, data.url].filter(Boolean).join('\n');
257    }
258
259    function showToast(message) {
260      if (!toast) return;
261
262      clearTimeout(toastTimer);
263      toast.textContent = message;
264      toast.hidden = false;
265
266      requestAnimationFrame(() => {
267        toast.classList.add('is-show');
268      });
269
270      toastTimer = window.setTimeout(() => {
271        toast.classList.remove('is-show');
272        window.setTimeout(() => {
273          toast.hidden = true;
274        }, 200);
275      }, 2000);
276    }
277
278    async function copyText(text, message = 'リンクをコピーしました') {
279      try {
280        await navigator.clipboard.writeText(text);
281        showToast(message);
282        return true;
283      } catch (error) {
284        const textarea = document.createElement('textarea');
285        textarea.value = text;
286        textarea.setAttribute('readonly', '');
287        textarea.style.position = 'absolute';
288        textarea.style.left = '-9999px';
289        document.body.appendChild(textarea);
290        textarea.select();
291
292        try {
293          document.execCommand('copy');
294          showToast(message);
295          document.body.removeChild(textarea);
296          return true;
297        } catch (e) {
298          document.body.removeChild(textarea);
299          showToast('コピーできませんでした');
300          return false;
301        }
302      }
303    }
304
305    function openSheet(button) {
306      activeTrigger = button;
307
308      sheet.dataset.shareUrl = button.dataset.shareUrl || window.location.href;
309      sheet.dataset.shareTitle = button.dataset.shareTitle || document.title;
310      sheet.dataset.shareText = button.dataset.shareText || document.title;
311      sheet.dataset.shareImage = button.dataset.shareImage || '';
312      sheet.dataset.shareCompany = button.dataset.shareCompany || '';
313      sheet.dataset.shareSummary = button.dataset.shareSummary || '';
314
315      const data = getShareData();
316
317      if (previewImage) {
318        previewImage.src = data.image || '';
319        previewImage.alt = data.title || '';
320        previewImage.style.display = data.image ? 'block' : 'none';
321      }
322
323      if (previewCompany) previewCompany.textContent = data.company;
324      if (previewTitle) previewTitle.textContent = data.title;
325      if (previewSummary) previewSummary.textContent = data.summary;
326
327      sheet.hidden = false;
328      document.body.classList.add('is-share-open');
329
330      requestAnimationFrame(() => {
331        sheet.classList.add('is-open');
332      });
333    }
334
335    function closeSheet() {
336      sheet.classList.remove('is-open');
337      document.body.classList.remove('is-share-open');
338
339      window.setTimeout(() => {
340        sheet.hidden = true;
341        if (activeTrigger) {
342          activeTrigger.focus();
343        }
344      }, 280);
345    }
346
347    async function runShareAction(action) {
348      const data = getShareData();
349      const body = buildShareBody(data);
350
351      switch (action) {
352        case 'messages': {
353          const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
354
355          if (isMobile) {
356            window.location.href = `sms:?&body=${encodeURIComponent(body)}`;
357          } else {
358            await copyText(body, 'SMS共有用の本文をコピーしました');
359          }
360          break;
361        }
362
363        case 'line': {
364          window.open(
365            `https://social-plugins.line.me/lineit/share?url=${encodeURIComponent(data.url)}`,
366            '_blank',
367            'noopener'
368          );
369          break;
370        }
371
372        case 'x': {
373          window.open(
374            `https://twitter.com/intent/tweet?text=${encodeURIComponent(data.text)}&url=${encodeURIComponent(data.url)}`,
375            '_blank',
376            'noopener'
377          );
378          break;
379        }
380
381        case 'instagram': {
382          if (navigator.share) {
383            try {
384              await navigator.share({
385                title: data.title,
386                text: data.text,
387                url: data.url
388              });
389            } catch (error) {
390              // キャンセル時は何もしない
391            }
392          } else {
393            await copyText(data.url, 'Instagram共有用にリンクをコピーしました');
394
395            window.open(
396              'https://www.instagram.com/',
397              '_blank',
398              'noopener'
399            );
400          }
401          break;
402        }
403
404        case 'whatsapp': {
405          window.open(
406            `https://wa.me/?text=${encodeURIComponent(body)}`,
407            '_blank',
408            'noopener'
409          );
410          break;
411        }
412
413        case 'email': {
414          const subject = encodeURIComponent(data.title);
415          const mailBody = encodeURIComponent(body);
416          window.location.href = `mailto:?subject=${subject}&body=${mailBody}`;
417          break;
418        }
419
420        case 'copy': {
421          await copyText(data.url);
422          break;
423        }
424
425        case 'more': {
426          if (navigator.share) {
427            try {
428              await navigator.share({
429                title: data.title,
430                text: data.text,
431                url: data.url
432              });
433            } catch (error) {
434              // キャンセル時は何もしない
435            }
436          } else {
437            await copyText(data.url, '共有機能が使えないため、リンクをコピーしました');
438          }
439          break;
440        }
441      }
442    }
443
444    document.addEventListener('click', async (event) => {
445      const openButton = event.target.closest('.js-share-open');
446      if (openButton) {
447        event.preventDefault();
448        openSheet(openButton);
449        return;
450      }
451
452      const closeButton = event.target.closest('.js-share-close');
453      if (closeButton) {
454        event.preventDefault();
455        closeSheet();
456        return;
457      }
458
459      const actionButton = event.target.closest('.js-share-action');
460      if (actionButton) {
461        event.preventDefault();
462        await runShareAction(actionButton.dataset.shareAction || '');
463      }
464    });
465
466    document.addEventListener('keydown', (event) => {
467      if (event.key === 'Escape' && sheet.classList.contains('is-open')) {
468        closeSheet();
469      }
470    });
471  })();
472});

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.