PageSourceSearch

https://m.thankqcamping.com/inc/pop_noti/pop_noti.js?version=20261001-1

js thankqcamping.com collected 2026-10-01 15:27:28 UTC 21,062 bytes, 745 lines download raw bytes

1document.addEventListener("DOMContentLoaded", function () {
2  const popup = document.getElementById("noticePopup");
3  const track = document.getElementById("noticeTrack");
4
5  if (!popup || !track) return;
6
7  const popupSheet = popup.querySelector(".popup-sheet");
8  const dotsContainer = document.getElementById("slideDots");
9  const currentSlideElement = document.getElementById("currentSlide");
10  const totalSlideElement = document.getElementById("totalSlide");
11  const detailButton = document.getElementById("detailButton");
12  const closeButton = document.getElementById("closePopupButton");
13  const hideDayButton = document.getElementById("hideDayButton");
14
15  let slides = Array.from(track.querySelectorAll(".notice-slide"));
16  const initialSlideSet = new Set(slides);
17  const initialTrackChildren = new Set(Array.from(track.children));
18
19  let dots = [];
20  let activeIndex = -1;
21  let scrollTimer = null;
22  let viewTimer = null;
23  let initializeTimer = null;
24  let popupInitialized = false;
25
26  let isDragging = false;
27  let isHorizontalDrag = false;
28  let dragStartX = 0;
29  let dragStartY = 0;
30  let dragCurrentX = 0;
31  let dragStartScrollLeft = 0;
32  let dragStartIndex = 0;
33  let ignoreNextClick = false;
34
35  const DRAG_THRESHOLD = 40;
36  const ONE_DAY = 24 * 60 * 60 * 1000;
37  const ADROP_NOTICE_ID = "adrop_main_popup";
38  const ADROP_UNIT_ID = "01J8M2R6NS7D248TH24MFGTMEN";
39
40  function getStorageValue(storage, key) {
41    try {
42      return storage.getItem(key);
43    } catch (error) {
44      return null;
45    }
46  }
47
48  function setStorageValue(storage, key, value) {
49    try {
50      storage.setItem(key, value);
51      return true;
52    } catch (error) {
53      return false;
54    }
55  }
56
57  function removeStorageValue(storage, key) {
58    try {
59      storage.removeItem(key);
60    } catch (error) {
61      // Storage 사용이 불가능한 환경에서는 무시합니다.
62    }
63  }
64
65  function refreshSlides() {
66    slides = Array.from(track.querySelectorAll(".notice-slide"));
67  }
68
69  function isAdropSlide(slide) {
70    if (!slide) return false;
71
72    const popGb = String(slide.dataset.popGb || "").trim().toLowerCase();
73    const noticeId = String(slide.dataset.noticeId || "").trim().toLowerCase();
74
75    return (
76      slide.classList.contains("notice-slide--adrop") ||
77      popGb === "adrop" ||
78      noticeId.startsWith("adrop_")
79    );
80  }
81
82  function isImageDetailAction(action) {
83    return (
84      action === "bn_link" ||
85      action === "bn_pop" ||
86      action === "bn_outopen"
87    );
88  }
89
90  function getNoticeHideKey(slide) {
91    if (!slide) return "";
92
93    const noticeId = String(slide.dataset.noticeId || "").trim();
94    const version = String(slide.dataset.version || "1").trim();
95
96    if (!noticeId) return "";
97
98    return "notice-hidden:" + noticeId + ":" + version;
99  }
100
101  function hideNoticeForOneDay(slide) {
102    const storageKey = getNoticeHideKey(slide);
103    if (!storageKey) return false;
104
105    return setStorageValue(
106      localStorage,
107      storageKey,
108      String(Date.now() + ONE_DAY)
109    );
110  }
111
112  function removeHiddenNotices() {
113    const now = Date.now();
114
115    slides.forEach(function (slide) {
116      const storageKey = getNoticeHideKey(slide);
117      if (!storageKey) return;
118
119      const hiddenUntil = Number(
120        getStorageValue(localStorage, storageKey) || 0
121      );
122
123      if (hiddenUntil > now) {
124        slide.remove();
125        return;
126      }
127
128      if (hiddenUntil > 0) {
129        removeStorageValue(localStorage, storageKey);
130      }
131    });
132
133    refreshSlides();
134  }
135
136  function renderDots() {
137    const showNavigation = slides.length > 1;
138    const slideCountElement = currentSlideElement
139      ? currentSlideElement.closest(".slide-count")
140      : null;
141
142    if (slideCountElement) {
143      slideCountElement.hidden = !showNavigation;
144    }
145
146    if (totalSlideElement) {
147      totalSlideElement.textContent = String(slides.length);
148    }
149
150    if (!dotsContainer) {
151      dots = [];
152      return;
153    }
154
155    dotsContainer.hidden = !showNavigation;
156    dotsContainer.innerHTML = "";
157
158    if (!showNavigation) {
159      dots = [];
160      return;
161    }
162
163    slides.forEach(function (_, index) {
164      const dot = document.createElement("button");
165      dot.type = "button";
166      dot.className = "slide-dot";
167      dot.setAttribute("aria-label", String(index + 1) + "번째 공지 보기");
168
169      dot.addEventListener("click", function () {
170        moveToSlide(index, true);
171      });
172
173      dotsContainer.appendChild(dot);
174    });
175
176    dots = Array.from(dotsContainer.children);
177  }
178
179  function getNearestSlideIndex() {
180    if (slides.length === 0) return 0;
181
182    const trackRect = track.getBoundingClientRect();
183    const trackCenter = trackRect.left + trackRect.width / 2;
184    let nearestIndex = 0;
185    let nearestDistance = Infinity;
186
187    slides.forEach(function (slide, index) {
188      const slideRect = slide.getBoundingClientRect();
189      const slideCenter = slideRect.left + slideRect.width / 2;
190      const distance = Math.abs(trackCenter - slideCenter);
191
192      if (distance < nearestDistance) {
193        nearestDistance = distance;
194        nearestIndex = index;
195      }
196    });
197
198    return nearestIndex;
199  }
200
201  function moveToSlide(index, useAnimation) {
202    if (index < 0 || index >= slides.length) return;
203
204    track.scrollTo({
205      left: index * track.clientWidth,
206      behavior: useAnimation ? "smooth" : "auto"
207    });
208
209    changeActiveSlide(index);
210  }
211
212  function updateDetailButton(slide) {
213    const action = String(slide ? slide.dataset.detailAction || "" : "").trim();
214    const seq = String(slide ? slide.dataset.detailSeq || "" : "").trim();
215    const showDetailButton =
216      !isAdropSlide(slide) &&
217      action === "notice_view" &&
218      seq !== "";
219    const useImageLink =
220      !isAdropSlide(slide) &&
221      isImageDetailAction(action) &&
222      seq !== "";
223    const actionArea = detailButton
224      ? detailButton.closest(".popup-action")
225      : null;
226
227    if (detailButton) {
228      detailButton.disabled = !showDetailButton;
229      detailButton.hidden = !showDetailButton;
230    }
231
232    if (slide) {
233      const noticeBody = slide.querySelector(".notice-body");
234      slide.classList.toggle("notice-slide--layer-link", useImageLink);
235
236      if (noticeBody && useImageLink) {
237        noticeBody.setAttribute("role", "link");
238        noticeBody.setAttribute("tabindex", "0");
239        noticeBody.setAttribute("aria-label", "배너 상세보기");
240        noticeBody.dataset.popupLayerLink = "true";
241      } else if (noticeBody && noticeBody.dataset.popupLayerLink === "true") {
242        noticeBody.removeAttribute("role");
243        noticeBody.removeAttribute("tabindex");
244        noticeBody.removeAttribute("aria-label");
245        delete noticeBody.dataset.popupLayerLink;
246      }
247    }
248
249    if (actionArea) {
250      actionArea.classList.toggle("is-hidden", !showDetailButton);
251    }
252  }
253
254  function updatePopupLayout(slide) {
255    if (!popupSheet || !slide) return;
256
257    const adrop = isAdropSlide(slide);
258    popupSheet.classList.toggle("is-adrop-active", adrop);
259
260    // 일반 공지와 Adrop 모두 동일한 팝업 가로폭을 사용합니다.
261    popupSheet.style.width = "";
262    popupSheet.style.maxWidth = "";
263  }
264
265  function changeActiveSlide(index) {
266    if (index < 0 || index >= slides.length) return;
267    if (activeIndex === index) return;
268
269    activeIndex = index;
270
271    slides.forEach(function (slide, slideIndex) {
272      slide.setAttribute(
273        "aria-hidden",
274        slideIndex === index ? "false" : "true"
275      );
276    });
277
278    dots.forEach(function (dot, dotIndex) {
279      const active = dotIndex === index;
280      dot.classList.toggle("active", active);
281
282      if (active) {
283        dot.setAttribute("aria-current", "true");
284      } else {
285        dot.removeAttribute("aria-current");
286      }
287    });
288
289    if (currentSlideElement) {
290      currentSlideElement.textContent = String(index + 1);
291    }
292
293    const currentSlide = slides[index];
294    updatePopupLayout(currentSlide);
295    updateDetailButton(currentSlide);
296    scheduleViewCount(currentSlide);
297  }
298
299  function scheduleViewCount(slide) {
300    window.clearTimeout(viewTimer);
301
302    if (!slide || isAdropSlide(slide)) return;
303
304    viewTimer = window.setTimeout(function () {
305      increaseViewCount(slide);
306    }, 500);
307  }
308
309  async function increaseViewCount(slide) {
310    if (!slide) return;
311
312    const noticeId = String(slide.dataset.noticeId || "").trim();
313    const version = String(slide.dataset.version || "1").trim();
314    const seq = String(slide.dataset.detailSeq || "").trim();
315    const popGb = String(slide.dataset.popGb || "").trim();
316
317    if (!noticeId) return;
318
319    const storageKey = "notice-view:" + noticeId + ":" + version;
320    if (getStorageValue(sessionStorage, storageKey)) return;
321
322    setStorageValue(sessionStorage, storageKey, "1");
323
324    try {
325      const response = await fetch("/inc/axPopBnLog.hbb", {
326        method: "POST",
327        credentials: "same-origin",
328        keepalive: true,
329        headers: {
330          "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
331        },
332        body: new URLSearchParams({
333          noticeId: noticeId,
334          seq: seq,
335          popGb: popGb
336        }).toString()
337      });
338
339      if (!response.ok) {
340        throw new Error("조회수 저장 실패: " + response.status);
341      }
342    } catch (error) {
343      removeStorageValue(sessionStorage, storageKey);
344      console.error(error);
345    }
346  }
347
348  track.addEventListener("scroll", function () {
349    if (isDragging || slides.length === 0) return;
350
351    window.clearTimeout(scrollTimer);
352    scrollTimer = window.setTimeout(function () {
353      if (track.clientWidth <= 0) return;
354
355      const index = Math.round(track.scrollLeft / track.clientWidth);
356      const safeIndex = Math.max(0, Math.min(index, slides.length - 1));
357      changeActiveSlide(safeIndex);
358    }, 150);
359  }, { passive: true });
360
361  track.addEventListener("pointerdown", function (event) {
362    if (event.pointerType === "mouse" && event.button !== 0) return;
363    if (slides.length === 0) return;
364
365    isDragging = true;
366    isHorizontalDrag = false;
367    ignoreNextClick = false;
368    dragStartX = event.clientX;
369    dragStartY = event.clientY;
370    dragCurrentX = event.clientX;
371    dragStartScrollLeft = track.scrollLeft;
372    dragStartIndex = activeIndex >= 0 ? activeIndex : getNearestSlideIndex();
373
374  });
375
376  track.addEventListener("pointermove", function (event) {
377    if (!isDragging) return;
378
379    const moveX = event.clientX - dragStartX;
380    const moveY = event.clientY - dragStartY;
381    dragCurrentX = event.clientX;
382
383    if (!isHorizontalDrag) {
384      if (Math.abs(moveX) < 6) return;
385      if (Math.abs(moveY) > Math.abs(moveX)) return;
386
387      isHorizontalDrag = true;
388      track.classList.add("is-dragging");
389
390      // 단순 클릭은 배너 내부 링크가 그대로 처리하도록 두고,
391      // 실제 가로 스와이프가 시작된 경우에만 포인터를 캡처합니다.
392      try {
393        track.setPointerCapture(event.pointerId);
394      } catch (error) {
395        // 일부 브라우저에서는 pointer capture를 지원하지 않을 수 있습니다.
396      }
397    }
398
399    track.scrollLeft = dragStartScrollLeft - moveX;
400    event.preventDefault();
401  });
402
403  function finishDrag(event) {
404    if (!isDragging) return;
405
406    const moveX = dragCurrentX - dragStartX;
407    isDragging = false;
408    track.classList.remove("is-dragging");
409
410    try {
411      if (
412        track.hasPointerCapture &&
413        track.hasPointerCapture(event.pointerId)
414      ) {
415        track.releasePointerCapture(event.pointerId);
416      }
417    } catch (error) {
418      // 일부 브라우저 예외를 무시합니다.
419    }
420
421    if (!isHorizontalDrag) {
422      isHorizontalDrag = false;
423      return;
424    }
425
426    isHorizontalDrag = false;
427    ignoreNextClick = Math.abs(moveX) > 5;
428
429    let targetIndex = dragStartIndex;
430    if (Math.abs(moveX) >= DRAG_THRESHOLD) {
431      targetIndex = moveX < 0 ? dragStartIndex + 1 : dragStartIndex - 1;
432    }
433
434    targetIndex = Math.max(0, Math.min(targetIndex, slides.length - 1));
435    moveToSlide(targetIndex, true);
436  }
437
438  track.addEventListener("pointerup", finishDrag);
439  track.addEventListener("pointercancel", finishDrag);
440
441  track.addEventListener("click", function (event) {
442    if (!ignoreNextClick) return;
443
444    event.preventDefault();
445    event.stopPropagation();
446    ignoreNextClick = false;
447  }, true);
448
449  track.addEventListener("dragstart", function (event) {
450    event.preventDefault();
451  });
452
453  function executeDetailAction(slide) {
454    if (!slide) return;
455
456    const action = String(slide.dataset.detailAction || "").trim();
457    const seq = String(slide.dataset.detailSeq || "").trim();
458    const popGb = String(slide.dataset.popGb || "").trim().toLowerCase();
459
460    if (!action) return;
461
462    if (!/^\d+$/.test(seq)) {
463      console.error("잘못된 상세보기 번호입니다.", seq);
464      return;
465    }
466
467    const encodedSeq = encodeURIComponent(seq);
468
469    if (popGb === "brd" && action === "notice_view") {
470      closePopup();
471      window.location.href = "/cs/notice_view.hbb?brd_id=" + encodedSeq;
472      return;
473    }
474
475    if (popGb === "brd" && action === "bn_link") {
476      if (typeof window.actBnLink !== "function") {
477        console.error("actBnLink 함수가 없습니다.");
478        return;
479      }
480
481      closePopup();
482      window.actBnLink(seq);
483      return;
484    }
485
486    if (popGb === "banner" && action === "bn_link") {
487      closePopup();
488      window.location.href = "/inc/bn_link.hbb?seq=" + encodedSeq;
489      return;
490    }
491
492    if (popGb === "banner" && action === "bn_pop") {
493      if (typeof window.goUrl !== "function") {
494        console.error("goUrl 함수가 없습니다.");
495        return;
496      }
497
498      closePopup();
499      window.goUrl("/inc/bn_link.hbb?seq=" + encodedSeq, "Y", "");
500      return;
501    }
502
503    if (popGb === "banner" && action === "bn_outopen") {
504      const externalUrl = "/inc/bn_link.hbb?seq=" + encodedSeq;
505      closePopup();
506
507      if (typeof window.outOpen === "function") {
508        window.outOpen(externalUrl);
509      } else {
510        window.open(externalUrl, "_blank", "noopener,noreferrer");
511      }
512      return;
513    }
514
515    console.warn("등록되지 않은 상세보기 조합:", {
516      popGb: popGb,
517      action: action,
518      seq: seq
519    });
520  }
521
522  function executeLayerDetailAction(event) {
523    const noticeBody = event.target.closest(
524      ".notice-slide--layer-link .notice-body"
525    );
526    if (!noticeBody || !track.contains(noticeBody)) return;
527
528    const slide = noticeBody.closest(".notice-slide");
529    if (!slide || isAdropSlide(slide)) return;
530
531    event.preventDefault();
532    executeDetailAction(slide);
533  }
534
535  track.addEventListener("click", executeLayerDetailAction);
536
537  track.addEventListener("keydown", function (event) {
538    if (event.key !== "Enter" && event.key !== " ") return;
539
540    const noticeBody = event.target.closest(
541      ".notice-slide--layer-link .notice-body"
542    );
543    if (!noticeBody || !track.contains(noticeBody)) return;
544
545    event.preventDefault();
546    executeDetailAction(noticeBody.closest(".notice-slide"));
547  });
548
549  if (detailButton) {
550    detailButton.addEventListener("click", function (event) {
551      event.preventDefault();
552
553      if (
554        detailButton.disabled ||
555        detailButton.hidden ||
556        activeIndex < 0 ||
557        activeIndex >= slides.length
558      ) {
559        return;
560      }
561
562      executeDetailAction(slides[activeIndex]);
563    });
564  }
565
566  function hideActiveNoticeForOneDay() {
567    if (activeIndex < 0 || activeIndex >= slides.length) return;
568
569    window.clearTimeout(viewTimer);
570    window.clearTimeout(scrollTimer);
571
572    const currentSlide = slides[activeIndex];
573    if (!hideNoticeForOneDay(currentSlide)) {
574      console.error("하루 숨김 정보를 저장하지 못했습니다.");
575      return;
576    }
577
578    const removedIndex = activeIndex;
579    currentSlide.remove();
580    refreshSlides();
581
582    if (slides.length === 0) {
583      closePopup();
584      return;
585    }
586
587    renderDots();
588
589    const nextIndex = Math.min(removedIndex, slides.length - 1);
590    activeIndex = -1;
591    track.scrollLeft = 0;
592
593    window.requestAnimationFrame(function () {
594      moveToSlide(nextIndex, false);
595    });
596  }
597
598  function setPopupVisible(visible) {
599    popup.hidden = !visible;
600    popup.setAttribute("aria-hidden", visible ? "false" : "true");
601    document.body.classList.toggle("popup-open", visible);
602  }
603
604  function openPopup() {
605    refreshSlides();
606
607    if (slides.length === 0) {
608      setPopupVisible(false);
609      return;
610    }
611
612    activeIndex = -1;
613    setPopupVisible(true);
614
615    window.requestAnimationFrame(function () {
616      track.scrollLeft = 0;
617      changeActiveSlide(0);
618    });
619  }
620
621  function closePopup() {
622    window.clearTimeout(scrollTimer);
623    window.clearTimeout(viewTimer);
624    isDragging = false;
625    isHorizontalDrag = false;
626    track.classList.remove("is-dragging");
627    setPopupVisible(false);
628  }
629
630  if (closeButton) {
631    closeButton.addEventListener("click", closePopup);
632  }
633
634  if (hideDayButton) {
635    hideDayButton.addEventListener("click", hideActiveNoticeForOneDay);
636  }
637
638  popup.addEventListener("click", function (event) {
639    if (event.target === popup) closePopup();
640  });
641
642  document.addEventListener("keydown", function (event) {
643    if (event.key === "Escape" && !popup.hidden) closePopup();
644  });
645
646  window.addEventListener("resize", function () {
647    if (popup.hidden || activeIndex < 0 || activeIndex >= slides.length) return;
648
649    window.clearTimeout(scrollTimer);
650    scrollTimer = window.setTimeout(function () {
651      updatePopupLayout(slides[activeIndex]);
652      moveToSlide(activeIndex, false);
653    }, 100);
654  });
655
656  function prepareAdropSlides() {
657    const currentSlides = Array.from(track.querySelectorAll(".notice-slide"));
658    let adropSlides = currentSlides.filter(function (slide) {
659      const popGb = String(slide.dataset.popGb || "").trim().toLowerCase();
660      return popGb === "adrop" || !initialSlideSet.has(slide);
661    });
662
663    // SDK가 article.notice-slide 없이 일반 요소만 추가한 경우 하나의 슬라이드로 감쌉니다.
664    if (adropSlides.length === 0) {
665      const newChildren = Array.from(track.children).filter(function (child) {
666        return !initialTrackChildren.has(child);
667      });
668
669      if (newChildren.length > 0) {
670        const article = document.createElement("article");
671        const body = document.createElement("div");
672        article.className = "notice-slide notice-slide--adrop";
673        body.className = "notice-body";
674
675        track.insertBefore(article, newChildren[0]);
676        article.appendChild(body);
677        newChildren.forEach(function (child) {
678          body.appendChild(child);
679        });
680
681        adropSlides = [article];
682      }
683    }
684
685    adropSlides.forEach(function (slide, index) {
686      slide.classList.add("notice-slide--adrop");
687      slide.dataset.noticeId = index === 0
688        ? ADROP_NOTICE_ID
689        : ADROP_NOTICE_ID + "_" + String(index + 1);
690      slide.dataset.version = "1";
691      slide.dataset.popGb = "adrop";
692      slide.dataset.detailAction = "";
693      slide.dataset.detailSeq = "";
694      slide.setAttribute("aria-hidden", "true");
695      track.prepend(slide);
696    });
697  }
698
699  function initializeNoticePopup() {
700    if (popupInitialized) return;
701    popupInitialized = true;
702    window.clearTimeout(initializeTimer);
703    setPopupVisible(false);
704
705    refreshSlides();
706    prepareAdropSlides();
707    refreshSlides();
708    removeHiddenNotices();
709    refreshSlides();
710
711    if (slides.length === 0) {
712      setPopupVisible(false);
713      return;
714    }
715
716    renderDots();
717    openPopup();
718  }
719
720  function finishAdropInitialization() {
721    initializeNoticePopup();
722  }
723
724  // SDK가 콜백을 호출하지 않는 경우에도 일반 공지는 표시합니다.
725  initializeTimer = window.setTimeout(finishAdropInitialization, 4000);
726
727  if (
728    window.jQuery &&
729    window.jQuery.adropAd &&
730    typeof window.jQuery.adropAd.getAdropBanner === "function"
731  ) {
732    const adropSessionId =
733      typeof sessionId !== "undefined" ? sessionId : "";
734
735    window.jQuery.adropAd.getAdropBanner(
736      ADROP_UNIT_ID,
737      "noticeTrack",
738      adropSessionId,
739      finishAdropInitialization,
740      finishAdropInitialization
741    );
742  } else {
743    finishAdropInitialization();
744  }
745});

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.