PageSourceSearch

https://dealsite.co.kr/js/pages/category/category.js

js dealsite.co.kr collected 2026-09-24 21:20:20 UTC 15,250 bytes, 406 lines download raw bytes

1import { initCategoryArticleFaceCrop } from "/js/common/face_crop.js";
2// /js/pages/category/category.js
3document.addEventListener("DOMContentLoaded", () => {
4    // =========================
5    // 0) breadcrumb (hidden input -> nav-thispage)
6    // =========================
7    const d1Name = document.querySelector("#__paramDepth1Name")?.value || "";
8    const d2Name = document.querySelector("#__paramDepth2Name")?.value || "";
9    const d1 = document.querySelector("#__paramDepth1")?.value || "";
10    const d2 = document.querySelector("#__paramDepth2")?.value || "";
11    const cc = document.querySelector("#__categoryCode")?.value || "000000";
12
13    const nav = document.querySelector(".nav-thispage");
14    if (nav) {
15        // HOME만 남기고 나머지 제거(중복 방지)
16        nav.querySelectorAll("a.nav-depth:not(:first-child)").forEach((el) => el.remove());
17
18        if (d1Name && d1) {
19            const a1 = document.createElement("a");
20            a1.className = "nav-depth";
21            a1.tabIndex = 0;
22            a1.href = `/categories/${d1}000`;
23            a1.textContent = d1Name;
24            nav.appendChild(a1);
25        }
26
27        if (d2Name && d1 && d2 && d2 !== "000") {
28            const a2 = document.createElement("a");
29            a2.className = "nav-depth";
30            a2.tabIndex = 0;
31            a2.href = `/categories/${d1}${d2}`;
32            a2.textContent = d2Name;
33            nav.appendChild(a2);
34        }
35    }
36
37    // =========================
38    // 1) state + URL helpers
39    // =========================
40    const $list = document.querySelector(".mnm-news-list");
41    const $alignBtns = document.querySelectorAll(".align-list-btns .align-list");
42
43    function getCategoryCodeFromPath() {
44        const m = window.location.pathname.match(/^\/categories\/(\d{6})$/);
45        return m ? m[1] : "000000";
46    }
47
48    function getQueryParam(name) {
49        return new URLSearchParams(window.location.search).get(name);
50    }
51
52    // URL 쿼리 -> state 초기화
53    const initialSort = getQueryParam("sort") || "";
54    const initialStartDt = getQueryParam("startDt");
55    const initialEndDt = getQueryParam("endDt");
56    const hasDateSearch = Boolean(
57        (initialStartDt && String(initialStartDt).trim()) ||
58        (initialEndDt && String(initialEndDt).trim())
59    );
60    const initialPage1Base = parseInt(getQueryParam("page") || "1", 10);
61    const initialPage0Base = Number.isFinite(initialPage1Base) && initialPage1Base > 0 ? initialPage1Base - 1 : 0;
62
63    const state = {
64        categoryCode: getCategoryCodeFromPath(),
65        page: initialPage0Base,
66        size: 20,
67        sort: initialSort,
68        startDt: initialStartDt,
69        endDt: initialEndDt,
70    };
71
72    const $oldestSortBtn = document.querySelector('.align-list-btns .align-list[data-sort="reversed"]');
73    if ($oldestSortBtn && !hasDateSearch) {
74        $oldestSortBtn.remove();
75    }
76
77    if (!hasDateSearch && state.sort === "reversed") {
78        state.sort = "";
79    }
80
81    // 정렬 버튼 active 상태 URL과 동기화
82    $alignBtns.forEach((btn) => {
83        const s = btn.dataset.sort || "";
84        btn.classList.toggle("active", s === state.sort);
85    });
86
87    const DATE_FMT = "YYYY-MM-DD";
88
89    function toYmdOrNull(v) {
90        if (!v) return null;
91        if (!window.moment) return String(v);
92        const m = window.moment(v, [DATE_FMT, window.moment.ISO_8601], true);
93        return m.isValid() ? m.format(DATE_FMT) : null;
94    }
95
96    function setOrDeleteParam(params, key, value) {
97        if (value === null || value === undefined || value === "") params.delete(key);
98        else params.set(key, String(value));
99    }
100
101    function applyFiltersAndReload({ sort, startDt, endDt, page } = {}) {
102        const url = new URL(window.location.href);
103
104        const sStart = toYmdOrNull(startDt !== undefined ? startDt : state.startDt);
105        const sEnd   = toYmdOrNull(endDt   !== undefined ? endDt   : state.endDt);
106        const s      = sort     !== undefined ? sort     : (state.sort ?? "");
107        const page0  = typeof page === "number" ? page : state.page;
108        const page1  = page0 + 1;
109
110        // ✅ 기존 쿼리스트링 전부 초기화 후 원하는 순서로 재설정
111        url.search = "";
112
113        if (sStart)      url.searchParams.set("startDt", sStart);
114        if (sEnd)        url.searchParams.set("endDt",   sEnd);
115        if (s)           url.searchParams.set("sort",    s);
116        if (page1 > 1)   url.searchParams.set("page",    String(page1));
117
118        window.location.href = url.toString();
119    }
120
121    // =========================
122    // 2) API fetch - HTML 렌더링 버전
123    // =========================
124    async function fetchCategoryNews() {
125        const params = new URLSearchParams();
126        const isMobileDevice = window.isMobile === true || window.isMobile === "true";
127        params.set("categoryCode", state.categoryCode);
128        params.set("page", String(state.page));
129        params.set("size", String(state.size));
130        params.set("pageBlockSize", isMobileDevice ? "5" : "10");
131        if (state.sort) params.set("sort", state.sort);
132
133        const start = toYmdOrNull(state.startDt);
134        const end = toYmdOrNull(state.endDt);
135        if (start) params.set("startDt", start);
136        if (end) params.set("endDt", end);
137
138        // ✅ HTML 렌더링 API 호출
139        const res = await fetch(`/api/articles/categoryNews?${params.toString()}`, {
140            method: "GET",
141            headers: { Accept: "application/json" },
142        });
143
144        if (!res.ok) throw new Error(`API error: ${res.status}`);
145        const json = await res.json();
146        return {
147            articlesHtml: json.articlesHtml,
148            paginationHtml: json.paginationHtml
149        };
150    }
151
152    // =========================
153    // 3) render helpers
154    // =========================
155    function initMemberSwiper() {
156        if (!document.querySelector(".mbs-swiper")) return;
157        if (window.__mbsSwiperInited) return;
158
159        window.__mbsSwiperInited = true;
160
161        if (typeof Swiper !== "function") return;
162
163        new Swiper(".mbs-swiper", {
164            slidesPerView: 1.5,
165            spaceBetween: 12,
166            loop: false,
167            on: {
168                reachEnd: function () {
169                    const lastIndex = this.slides.length - 1;
170                    this.slideTo(lastIndex, 500, false);
171                },
172            },
173            touchRatio: 1,
174        });
175    }
176
177    function isMobileCategoryView() {
178        return window.matchMedia("(max-width: 640px)").matches
179            || window.isMobile === true
180            || window.isMobile === "true";
181    }
182
183    function placeMemberNewsMoAfterFirstArticle() {
184        if (!isMobileCategoryView()) return;
185
186        const listRoot = document.querySelector(".mnm-news-list");
187        const firstItem = listRoot?.querySelector(".mnm-news");
188        if (!listRoot || !firstItem) return;
189
190        const existsInList = Array.from(listRoot.children).some((child) =>
191            child.classList?.contains("membership-cont-mo")
192            && child.classList?.contains("mbs-news-cont-mo")
193        );
194        if (existsInList) return;
195
196        const widgetContainer = document.querySelector('#side-root [data-widget="MEMBER_NEWS_MO"]');
197        const memberBlock = widgetContainer?.querySelector(".membership-cont-mo.mbs-news-cont-mo");
198        if (!memberBlock) return;
199
200        firstItem.insertAdjacentElement("afterend", memberBlock);
201
202        const swiperEl = memberBlock.querySelector(".mbs-swiper");
203        if (swiperEl?.dataset.swiperInited !== "1") {
204            initMemberSwiper?.();
205        }
206    }
207
208    // =========================
209    // 4) 날짜 네비게이션 업데이트
210    // =========================
211    function updateDateNavigation() {
212        const $prevBtn = document.querySelector("#prev-date");
213        const $nextBtn = document.querySelector("#next-date");
214        const $selectedDate = document.querySelector("#selected-date");
215
216        if (!$prevBtn || !$nextBtn || !$selectedDate) return;
217
218        if (state.startDt && state.endDt) {
219            $selectedDate.textContent = `${state.startDt} ~ ${state.endDt}`;
220        } else if (state.endDt) {
221            $selectedDate.textContent = `~ ${state.endDt}`;
222        } else {
223            $selectedDate.textContent = `~ ${window.moment().format(DATE_FMT)}`;
224        }
225
226        if (window.moment) {
227            const endMoment = state.endDt ? window.moment(state.endDt, DATE_FMT) : window.moment();
228            const today = window.moment();
229
230            const prevDate = endMoment.clone().subtract(1, 'days');
231            $prevBtn.className = "prev-date active";
232            $prevBtn.onclick = (e) => {
233                e.preventDefault();
234                applyFiltersAndReload({
235                    startDt: state.startDt ? window.moment(state.startDt, DATE_FMT).subtract(1, 'days').format(DATE_FMT) : null,
236                    endDt: prevDate.format(DATE_FMT),
237                    page: 0
238                });
239            };
240
241            if (endMoment.isBefore(today, 'day')) {
242                const nextDate = endMoment.clone().add(1, 'days');
243                $nextBtn.className = "next-date active";
244                $nextBtn.onclick = (e) => {
245                    e.preventDefault();
246                    applyFiltersAndReload({
247                        startDt: state.startDt ? window.moment(state.startDt, DATE_FMT).add(1, 'days').format(DATE_FMT) : null,
248                        endDt: nextDate.format(DATE_FMT),
249                        page: 0
250                    });
251                };
252            } else {
253                $nextBtn.className = "next-date";
254                $nextBtn.onclick = null;
255                $nextBtn.style.cursor = "default";
256            }
257        }
258    }
259
260    // ✅ 페이지네이션 이벤트 바인딩
261    function bindPaginationEvents() {
262        document.querySelectorAll("#pagination .paging-btns a[data-page]").forEach(btn => {
263            btn.addEventListener("click", (e) => {
264                e.preventDefault();
265                const page = parseInt(btn.dataset.page) - 1; // 0-base로 변환
266                applyFiltersAndReload({ page });
267            });
268        });
269    }
270
271    // =========================
272    // 5) ⭐ load - 카테고리 먼저 로드
273    // =========================
274    async function load() {
275        try {
276            // ✅ 1. 카테고리 기사 HTML 받아오기
277            const { articlesHtml, paginationHtml } = await fetchCategoryNews();
278
279            // ✅ 2. HTML 삽입
280            if ($list) {
281                $list.innerHTML = articlesHtml;
282                initCategoryArticleFaceCrop($list);
283                window.packSize?.apply?.();
284            }
285
286            const $paginationRoot = document.querySelector("#pagination .paging-btns");
287            if ($paginationRoot) {
288                $paginationRoot.innerHTML = paginationHtml;
289
290                // ✅ 3. 페이지네이션 이벤트 바인딩
291                bindPaginationEvents();
292            }
293
294            updateDateNavigation();
295
296        } catch (err) {
297            console.error("로드 중 에러:", err);
298            if ($list) $list.innerHTML = `<div class="mnm-news-blank">오류가 발생했습니다.</div>`;
299        }
300
301        try {
302            // 카테고리 본문 로드 이후 사이드 위젯을 교체합니다.
303            // 본문 API가 실패해도 사이드 스켈레톤이 계속 남지 않도록 별도 try로 분리합니다.
304            const { initSideItems } = await import("/js/pages/side-item/side_item.js");
305            await initSideItems();
306
307            // 모바일 회원 기사 위젯은 실제 기사 목록이 생긴 뒤 첫 기사 아래로 이동합니다.
308            placeMemberNewsMoAfterFirstArticle();
309        } catch (sideErr) {
310            console.error("사이드 로드 중 에러:", sideErr);
311        }
312    }
313
314    // =========================
315    // 6) 정렬 클릭
316    // =========================
317    $alignBtns.forEach((btn) => {
318        btn.addEventListener("click", (e) => {
319            e.preventDefault();
320            e.stopPropagation();
321
322            const newSort = btn.dataset.sort || "";
323            applyFiltersAndReload({ sort: newSort, page: 0 });
324        });
325    });
326
327    // =========================
328    // 7) 날짜/기간 버튼
329    // =========================
330    const $selectDateBtn = $('.select-date-btn');
331    const $selectPeriodBtn = $('.select-period-btn');
332
333    if (window.moment && window.$ && typeof $.fn.daterangepicker === "function") {
334
335        if ($selectDateBtn.data('daterangepicker')) {
336            $selectDateBtn.data('daterangepicker').remove();
337        }
338        if ($selectPeriodBtn.data('daterangepicker')) {
339            $selectPeriodBtn.data('daterangepicker').remove();
340        }
341
342        function safeMoment(val, fallback) {
343            if (!val) return fallback || window.moment();
344            const m = window.moment(val, [DATE_FMT, window.moment.ISO_8601], true);
345            return m.isValid() ? m : (fallback || window.moment());
346        }
347
348        $selectDateBtn.daterangepicker({
349            singleDatePicker: true,
350            autoApply: true,
351            startDate: safeMoment(state.endDt, window.moment()),
352            endDate: safeMoment(state.endDt, window.moment()),
353            maxDate: window.moment(),
354            showDropdowns: true,
355            showWeekNumbers: false,
356            locale: {
357                format: "YYYY-MM-DD",
358                applyLabel: "확인",
359                cancelLabel: "취소",
360                daysOfWeek: ['일','월','화','수','목','금','토'],
361                monthNames: ['01','02','03','04','05','06','07','08','09','10','11','12'],
362                firstDay: 0
363            }
364        }, function(start) {
365            applyFiltersAndReload({
366                startDt: null,
367                endDt: start.format('YYYY-MM-DD'),
368                page: 0
369            });
370        });
371
372        // ✅ 수정: endDate 먼저 계산하고, startDate는 endDate 기준 -7일로 설정
373        const endDate   = safeMoment(state.endDt, window.moment());
374        const startDate = safeMoment(state.startDt, endDate.clone().subtract(7, 'days'));
375
376        $selectPeriodBtn.daterangepicker({
377            showDropdowns: true,
378            showWeekNumbers: false,
379            startDate: startDate,
380            endDate: endDate,
381            maxDate: window.moment(),
382            locale: {
383                format: "YYYY-MM-DD",
384                separator: " ~ ",
385                applyLabel: "확인",
386                cancelLabel: "취소",
387                fromLabel: "From",
388                toLabel: "To",
389                daysOfWeek: ['일','월','화','수','목','금','토'],
390                monthNames: ['01','02','03','04','05','06','07','08','09','10','11','12'],
391                firstDay: 0
392            }
393        }, function(start, end) {
394            applyFiltersAndReload({
395                startDt: start.format('YYYY-MM-DD'),
396                endDt: end.format('YYYY-MM-DD'),
397                page: 0
398            });
399        });
400    }
401
402    // =========================
403    // 8) 최초 로딩
404    // =========================
405    load();
406});

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.