PageSourceSearch

https://www.thejakartapost.com/skin/js/tjp-general.js

js thejakartapost.com collected 2026-09-24 08:25:50 UTC 31,883 bytes, 894 lines download raw bytes

1const cdbId = $('meta[name="cdb-id"]').attr('content');
2const path = $('meta[name="path"]').attr('content');
3const isLogin = $('meta[name="is-login"]').attr('content');
4const isSubscriber = $('meta[name="is-subscriber"]').attr('content');
5const isPartnerIP = $('meta[name="is-partner-ip"]').attr('content');
6const csrfToken = $('meta[name="csrf-token"]').attr('content');
7
8// Function to generate a unique random ID
9const generateRandomId = () => {
10    // Using Math.random() and Date.now() to create a unique ID
11    return 'tjp-' + Math.random().toString(36).substr(2, 16) + '-' + Date.now();
12};
13
14// Function to retrieve or generate a unique ID from localStorage
15const getRandomId = () => {
16    // Check if unique_id exists in localStorage
17    let unique_id = localStorage.getItem('unique_id');
18    
19    // If unique_id does not exist, generate one and store it
20    if (!unique_id) {
21        unique_id = generateRandomId();
22        localStorage.setItem('unique_id', unique_id);
23    }
24    
25    // Return the unique ID
26    return unique_id;
27};
28
29// JS FOR PARTNER IP
30$(document).ready(function() {
31    // Get the unique ID from localStorage or generate it if not found
32    let unique_id = getRandomId();
33    let path = window.location.pathname + window.location.search;
34
35    function fetchData() {
36        $.ajax({
37            url: `${baseURL}/partner/analytic/store`, 
38            data: {
39                _token: csrfToken,
40                unique_id: unique_id,
41                path: path
42            },
43            method: 'POST',
44            success: function(data) {
45                console.log(data); // Process the data received
46            },
47            error: function(error) {
48                console.error('Error fetching data:', error);
49            }
50        });
51    }
52
53    // Function to check if the page is visible
54    function isPageVisible() {
55        return document.visibilityState === 'visible';
56    }
57
58    // Check if 'is-partner-ip' value is '1' and if the page is visible
59    if (isPartnerIP == '1') {
60        // Call fetchData when the page first loads
61        fetchData();
62
63        // Run fetchData every 10 seconds (10,000 ms) if the page is visible
64        setInterval(function() {
65            if (isPageVisible()) {
66                fetchData();
67            }
68        }, 10000);
69    }
70});
71// END JS FOR PARTNER IP
72
73const isValidEmail = function(email) {
74    // Add your email validation logic here
75    // For simplicity, assuming a basic check for a valid email format
76    const emailRegex = /\S+@\S+\.\S+/;
77    return emailRegex.test(email);
78}
79
80const isEmptyValue = function(value) {
81    return (
82        value === null || // check for null
83        value === undefined || // check for undefined
84        value === '' || // check for empty string
85        (Array.isArray(value) && value.length === 0) || // check for empty array
86        (typeof value === 'object' && Object.keys(value).length === 0) // check for empty object
87    );
88}
89
90const isMobile = function() {
91    // Get the user agent string from the browser
92    var userAgent = navigator.userAgent.toLowerCase();
93
94    // List of keywords indicating mobile devices
95    var mobileKeywords = ['android', 'iphone', 'ipad', 'windows phone', 'blackberry', 'iemobile', 'opera mini', 'mobile', 'tablet'];
96
97    // Variable to store whether the user is on a mobile device or not
98    var isMobile = false;
99
100    // Check if the user agent contains any of the mobile keywords
101    for (var i = 0; i < mobileKeywords.length; i++) {
102        if (userAgent.indexOf(mobileKeywords[i]) !== -1) {
103            isMobile = true;
104            break;
105        }
106    }
107
108    return isMobile;
109}
110
111const toIDR = function(value) {
112    return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
113}
114
115const displayErrorMessage = function(element, className, message) {
116    element.addClass(className);
117    element.html(message);
118}
119
120const displayToast = function(type, message) {
121    // Define an object mapping types to their corresponding icons
122    const icons = {
123        error: `<i class="fa-solid fa-exclamation"></i>`,
124        success: `<i class="fa-solid fa-check"></i>`,
125        info: `<i class="fa-solid fa-info"></i>`,
126        warning: `<i class="fa-solid fa-exclamation"></i>` // Assuming the warning icon is the same as error, adjust if needed
127    };
128
129    // Use the type to determine the icon, defaulting to an empty string if the type is unknown
130    const iconHTML = icons[type] || '';
131
132    // Apply the type-specific classes and content
133    $(".tjp-toast").addClass(`active`);
134    $(".tjp-toast").addClass(`tjp-toast--${type}`);
135    $(".tjp-toast__content-icon").html(iconHTML);
136    $(".tjp-toast__content-info__message").text(message);
137    
138
139    setTimeout(() => {
140        $(".tjp-toast").removeClass(`active`);
141    }, 5000);
142}
143
144const moveTabs = function(elementID) {
145    $(".tjp-tabs__nav-item").removeClass("active");
146    $(`.tjp-tabs__nav-item#${elementID}`).addClass("active");
147
148    $(".tjp-tabs__content-item").removeClass("active");
149    $(`.tjp-tabs__content-item#${elementID}`).addClass("active");
150}
151
152const moveStep = function(elementID) {
153    $('.tjp-step').each(function(index, element) {
154        var $content = $(this).find(".tjp-step__content");
155        (index === stepIndex) ? $content.slideDown() : $content.slideUp();
156    });
157}
158
159const resetTextSize = function() {
160    if ($(window).width() > 768) {
161        $(".tjp-sticky-popup__content-list-btn.text").removeClass("active");
162    } else {
163        $(".tjp-text-size-list__item").removeClass("active");
164        bottomPopupTextSizeWrapper.removeClass("active");
165
166        setTimeout(function() {
167            bottomPopupTextSize.removeClass("active");
168        }, 500); // Delay matches the transition duration
169    }
170    $(".tjp-single__content").removeClass("tjp-single__content--small");
171    $(".tjp-single__content").removeClass("tjp-single__content--medium");
172    $(".tjp-single__content").removeClass("tjp-single__content--large");
173}
174
175const setTextSize = function(size) {
176    localStorage.setItem("size", size);
177}
178
179const getTextSize = function() {
180    resetTextSize();
181
182    var size = localStorage.getItem("size");
183    if (size) {
184        $(".tjp-single__content").addClass(`tjp-single__content--${size}`);
185        if ($(window).width() > 768) {
186            $(`.tjp-sticky-popup__content-list-btn.text-${size}`).addClass("active");
187        } else {
188            $(`.tjp-text-size-list__item.text-${size}`).addClass("active");
189        }
190    } else {
191        $(".tjp-single__content").addClass(`tjp-single__content--small`);
192        if ($(window).width() > 768) {
193            $(`.tjp-sticky-popup__content-list-btn.text-small`).addClass("active");
194        } else {
195            $(`.tjp-text-size-list__item.text-small`).addClass("active");
196            bottomPopupTextSizeWrapper.removeClass("active");
197
198            setTimeout(function() {
199                bottomPopupTextSize.removeClass("active");
200            }, 500); // Delay matches the transition duration
201        }
202    }
203}
204
205const changeTextSize = function(size) {
206    setTextSize(size);
207    getTextSize();
208}
209
210const copyLink = (element) => {
211    var url      = window.location.href;
212    // Copy the text inside the text field
213    navigator.clipboard.writeText(url);
214    
215    if ($(window).width() > 768) {
216        stickyPopupLinkNotes.html("Link Copied");
217        setTimeout(function() {
218            stickyPopupLinkNotes.html("");
219        }, 1500);
220    } else {
221        $(element).find(".tjp-social-media-list__item-text").html(`<span style="color: green;"><i class="fa-solid fa-circle-check"></i> Link Copied</span>`);
222
223        setTimeout(function() {
224            $(element).find(".tjp-social-media-list__item-text").html(`<span>Copy Link</span>`)
225        }, 1500);
226    }
227}
228
229const btnPopupShare = $(".tjp-sticky-popup--share .tjp-sticky-popup__action");
230const btnPopupTextSize = $(".tjp-sticky-popup--text-size .tjp-sticky-popup__action");
231const btnPopupGift = $(".tjp-sticky-popup--gift .tjp-sticky-popup__action");
232
233const stickyPopupShare = $(".tjp-sticky-popup--share");
234const stickyPopupTextSize = $(".tjp-sticky-popup--text-size");
235const stickyPopupGift = $(".tjp-sticky-popup--gift");
236
237const stickyPopupActionShare = $(".tjp-sticky-popup__content-action-btn");
238const stickyPopupLinkBtn = $(".tjp-sticky-popup__content-link button");
239const stickyPopupLinkNotes = $(".tjp-sticky-popup__content-notes");
240
241const bottomPopupShare = $(".tjp-bottom-popup--share");
242const bottomPopupShareClose = $(".tjp-bottom-popup--share .tjp-bottom-popup__header-action-close");
243const bottomPopupShareWrapper = $(".tjp-bottom-popup--share .tjp-bottom-popup__wrapper");
244const bottomPopupTextSize = $(".tjp-bottom-popup--text-size");
245const bottomPopupTextSizeClose = $(".tjp-bottom-popup--text-size .tjp-bottom-popup__header-action-close");
246const bottomPopupTextSizeWrapper = $(".tjp-bottom-popup--text-size .tjp-bottom-popup__wrapper");
247const bottomPopupGift = $(".tjp-bottom-popup--gift");
248const bottomPopupGiftClose = $(".tjp-bottom-popup--gift .tjp-bottom-popup__header-action-close");
249const bottomPopupGiftWrapper = $(".tjp-bottom-popup--gift .tjp-bottom-popup__wrapper");
250
251const bottomPopupActionShare = $(".tjp-bottom-popup__footer-action-share");
252const bottomPopupAuthLogin = $(".tjp-sticky-popup__content-action-login");
253const bottomPopupAuthSubscribe = $(".tjp-sticky-popup__content-action-subscribe");
254
255btnPopupShare.click(function() {
256    if ($(window).width() > 768) {
257        stickyPopupShare.toggleClass("active");
258        stickyPopupGift.removeClass("active");            
259        stickyPopupTextSize.removeClass("active");
260    } else {
261        bottomPopupShare.addClass("active");
262        bottomPopupShareWrapper.addClass("active");
263    }
264});
265
266btnPopupTextSize.click(function() {
267    if ($(window).width() > 768) {
268        stickyPopupTextSize.toggleClass("active");
269        stickyPopupShare.removeClass("active");
270        stickyPopupGift.removeClass("active");          
271    } else {
272        bottomPopupTextSize.addClass("active");
273        bottomPopupTextSizeWrapper.addClass("active");
274    }
275})
276
277btnPopupGift.click(function() {
278    if ($(window).width() > 768) {
279        $(this).parent().toggleClass("active");
280        stickyPopupShare.removeClass("active")
281        stickyPopupTextSize.removeClass("active");       
282    } else {
283        bottomPopupGift.addClass("active");
284        bottomPopupGiftWrapper.addClass("active");
285    }
286})
287
288bottomPopupShare.click(function(event) {
289    // Check if the click was on the background (not on the wrapper)
290    if (!$(event.target).closest(".tjp-bottom-popup__wrapper").length) {
291        bottomPopupShareWrapper.removeClass("active");
292
293        setTimeout(function() {
294            bottomPopupShare.removeClass("active");
295        }, 500); // Delay matches the transition duration
296    } else if ($(event.target).closest(".tjp-social-media-list__item").length) {
297        return;
298    }
299});
300
301bottomPopupShareClose.click(function() {
302    bottomPopupShareWrapper.removeClass("active");
303    setTimeout(function() {
304        bottomPopupShare.removeClass("active");
305    }, 500); // Delay matches the transition duration
306})
307
308bottomPopupTextSize.click(function(event) {
309    // Check if the click was on the background (not on the wrapper)
310    if (!$(event.target).closest(".tjp-bottom-popup__wrapper").length) {
311        bottomPopupTextSizeWrapper.removeClass("active");
312
313        setTimeout(function() {
314            bottomPopupTextSize.removeClass("active");
315        }, 500); // Delay matches the transition duration
316    }
317});
318
319bottomPopupTextSizeClose.click(function() {
320    bottomPopupTextSizeWrapper.removeClass("active");
321
322    setTimeout(function() {
323        bottomPopupTextSize.removeClass("active");
324    }, 500); // Delay matches the transition duration
325})
326
327bottomPopupGift.click(function(event) {
328    // Check if the click was on the background (not on the wrapper)
329    if (!$(event.target).closest(".tjp-bottom-popup__wrapper").length) {
330        bottomPopupGiftWrapper.removeClass("active");
331
332        setTimeout(function() {
333            bottomPopupGift.removeClass("active");
334        }, 500); // Delay matches the transition duration
335    }
336});
337
338bottomPopupGiftClose.click(function() {
339    bottomPopupGiftWrapper.removeClass("active");
340
341    setTimeout(function() {
342        bottomPopupGift.removeClass("active");
343    }, 500); // Delay matches the transition duration
344})
345
346bottomPopupActionShare.click(function() {
347    pushIntoGTMEvents('gift_article_btn_share_click');
348    bottomPopupShareWrapper.removeClass("active");
349
350    setTimeout(function() {
351        bottomPopupShare.removeClass("active");
352        bottomPopupGiftWrapper.addClass("active");
353        bottomPopupGift.addClass("active");
354    }, 500); // Delay matches the transition duration
355})
356
357$(document).click(function(event) {
358    // Check if the clicked element is not inside the shares or text-size containers
359    if (!stickyPopupShare.is(event.target) && stickyPopupShare.has(event.target).length === 0 &&
360        !stickyPopupTextSize.is(event.target) && stickyPopupTextSize.has(event.target).length === 0 &&
361        !stickyPopupGift.is(event.target) && stickyPopupGift.has(event.target).length === 0) {
362      // Clicked outside of both elements
363      stickyPopupShare.removeClass("active");
364      stickyPopupTextSize.removeClass("active");
365      stickyPopupGift.removeClass("active");
366    }
367});
368
369// TJP ANALYTICS TRIGGERS
370const shareIntoSocmed = (platform, data) => {
371    switch (platform) {
372        case 'WhatsApp':
373            window.open(`https://web.whatsapp.com/send?text=${data.gift_title} ${data.gift_path}`, '_blank');
374            break;
375        case 'Facebook':
376            window.open(`https://www.facebook.com/sharer/sharer.php?u=${data.gift_path}`, '_blank');
377            break;
378        case 'Twitter':
379            somedShare('https://twitter.com/share?text='+data.gift_title+'&url='+data.gift_path, 'tw');
380            break;
381        case 'LinkedIn':
382            somedShare('http://www.linkedin.com/shareArticle?mini=true&summary='+data.gift_summary+'&title='+data.gift_title+'&url='+data.gift_path, 'pn');
383            break;
384        case 'Telegram':
385            window.open(`https://telegram.me/share/url?url=${data.gift_path}&text=${data.gift_title}`);
386            break;
387        case 'Email':
388            window.open(`mailto:?subject=${data.gift_title}&body=Check out this article:%0A%0A${data.gift_path}%0A%0A${data.gift_title}`);
389            break;
390        case 'CopyLink':
391            var url      = data.gift_path;
392            // Copy the text inside the text field
393            navigator.clipboard.writeText(url);
394            if ($(window).width() > 768) {
395                stickyPopupLinkNotes.html("Link Copied");
396                setTimeout(function() {
397                    stickyPopupLinkNotes.html("");
398                }, 1500);
399            } else {
400                const element = ".tjp-bottom-popup--gift .tjp-social-media-list__item:nth-child(1)";
401                $(element).find(".tjp-social-media-list__item-text").html(`<span style="color: green;"><i class="fa-solid fa-circle-check"></i> Link Copied</span>`);
402
403                setTimeout(function() {
404                    $(element).find(".tjp-social-media-list__item-text").html(`<span>Copy Link</span>`)
405                }, 1500);
406            }
407            break;
408        default:
409            break;
410    }
411}
412
413$('.tjp-social-media-list__item').click(function(event) {
414    event.stopPropagation(); // Prevent event from bubbling up
415});
416
417$(".tjp-paywall-gifted-article .action__btn").click(function() {
418    const source = $(this).data("source");
419    if (source == "gift_article") {
420        pushIntoGTMEvents('gift_article_btn_subscribe_click');
421        window.location.href = `${baseURL}/subscribe`;   
422    } else {
423        pushIntoGTMEvents('ppa_btn_get_unlimited_click');
424        window.location.href = `${baseURL}/subscribe`;   
425    }
426});
427
428bottomPopupAuthLogin.click(function() {
429    pushIntoGTMEvents('gift_article_btn_authlogin_click');
430    window.location.href = `${baseURL}/user/account/login`;
431})
432
433bottomPopupAuthSubscribe.click(function() {
434    pushIntoGTMEvents('gift_article_btn_authsubscribe_click');
435    window.location.href = `${baseURL}/subscribe`;
436})
437
438btnPopupGift.click(function() {
439    const path = $(this).data("path");
440    sendGiftDetailIntoGTMEvent('gift_article_btn_share_click', path);
441})
442
443stickyPopupActionShare.click(function() {
444    pushIntoGTMEvents('gift_article_btn_share_click');
445    btnPopupGift.click();
446});
447
448// Track untuk tombol Subscribe (buat semua visitor dan user)
449$(document).on('click', '.tjp-btn-subscribe--new', function () {
450    pushIntoGTMEvents('btn_subscribe_new_click');
451    window.location.href = `${baseURL}/subscribe`;
452});
453
454// Track untuk tombol Renew (khusus subscriber, dengan userDetails)
455$(document).on('click', '.tjp-btn-subscribe--renew', function () {
456    pushIntoGTMEvents('btn_subscribe_renew_click');
457    window.location.href = `${baseURL}/subscribe`;
458});
459
460const giftArticle = (platform, path = '') => {
461    let event = 'gift_article_btn_platform_click';
462    let pathAPI = window.location.pathname.substring(1);
463    if (path != '') {
464        pathAPI = path;
465    }
466    $.ajax({   
467        type: "POST",
468        url: `${baseURL}/api/gift-article/create`, 
469        data: {
470            path: pathAPI
471        },
472        success: function(response) {
473            const data = response.data;
474
475            $(".tjp-sticky-popup__content-head-number .current").html(data.gift_remaining);
476            $(".tjp-sticky-popup__content-head-number .limit").html(data.gift_maximum);
477
478            shareIntoSocmed(platform, data);
479            sendShareDetailsIntoGTMEvent(event, platform, pathAPI);
480
481            // if (data.gift_remaining == 0) {
482            //     $(".tjp-sticky-popup--gift .tjp-sticky-popup__content-list").remove();
483            //     $(".tjp-sticky-popup--gift .tjp-sticky-popup__content-link").remove();
484            //     $(".tjp-sticky-popup--gift .tjp-sticky-popup__content-text").html("You've Shared All Your Articles This Month. Come back next month for more articles to share with everyone.");
485            // }
486
487        },
488        error: function(){
489            console.log('No data available'); // Optional: log for debugging
490        }
491    });
492}
493
494// BOTTOM POPUP OPEN APP
495$(document).ready(function () {
496    const VISIT_COUNT_KEY = 'visitCount';
497    const LAST_VISITED_PATH_KEY = 'lastVisitedPath';
498    const POPUP_DISMISSED_KEY = 'popupDismissedTime';
499    const ONE_MINUTE_MS = 24 * 60 * 60 * 1000;
500
501    const currentPath = window.location.pathname;
502
503    // Get current visit count and last visited path from localStorage
504    let visitCount = parseInt(localStorage.getItem(VISIT_COUNT_KEY)) || 0;
505    const lastVisitedPath = localStorage.getItem(LAST_VISITED_PATH_KEY);
506
507    // Increment visit count only if the current path is different from the last visited path
508    if (currentPath !== lastVisitedPath) {
509        visitCount++;
510        localStorage.setItem(VISIT_COUNT_KEY, visitCount);
511        localStorage.setItem(LAST_VISITED_PATH_KEY, currentPath); // Update the last visited path
512    }
513
514    // Check if popup should be shown
515    const popupDismissedTime = localStorage.getItem(POPUP_DISMISSED_KEY);
516    const isDismissedRecently = popupDismissedTime && (Date.now() - popupDismissedTime < ONE_MINUTE_MS);
517
518    if (visitCount >= 3 && !isDismissedRecently) {
519        sendOpenAppDetailIntoGTMEvent('open_app_popup_show', currentPath.substring(1));
520        $('.tjp-bottom-popup--open-app').addClass('active');
521        $('.tjp-bottom-popup--open-app .tjp-bottom-popup__wrapper').addClass('active');
522    }
523
524    // Handle Open App button click
525    $('.tjp-cta-btn--open-open-app').on('click', function () {
526        pushIntoGTMEvents('open_app_btn_open_click');
527
528        const ANDROID_APP_URL = "https://play.google.com/store/apps/details?id=com.thejakartapost.ndroid";
529        const IOS_APP_URL = "https://apps.apple.com/id/app/the-jakarta-post/id1321863437";
530
531        // Detect the device and set the appropriate URL
532        const userAgent = navigator.userAgent || navigator.vendor || window.opera;
533        let url;
534
535        if (/android/i.test(userAgent)) {
536            url = ANDROID_APP_URL; // Android URL
537        } else if (/iPhone|iPad|iPod/i.test(userAgent)) {
538            url = IOS_APP_URL; // iOS URL
539        } else {
540            url = "https://www.thejakartapost.com/apps/download"; // Default fallback URL
541        }
542        window.location.href = url;
543    });
544
545    // Handle popup close button click
546    $('.tjp-cta-btn--close-open-app').on('click', function () {
547        $('.tjp-bottom-popup--open-app').removeClass('active');
548        $('.tjp-bottom-popup--open-app .tjp-bottom-popup__wrapper').removeClass('active');
549        pushIntoGTMEvents('open_app_btn_close_click');
550        // Store the dismissal timestamp in localStorage
551        localStorage.setItem(POPUP_DISMISSED_KEY, Date.now().toString());
552    });
553
554    $(".tjp-preferred-source-btn").click(function() {
555        pushIntoGTMEvents('set_as_preferred_source_click');
556    });
557});
558
559$(".tjp-account-action").click(function() {
560
561    if ($(".channelHeader").hasClass("fix-head")) {
562        $(".channelHeader .tjp-account-action").toggleClass("active");
563        $(".channelHeader .tjp-account-nav").slideToggle("slow");
564    } else {
565        $(".tjp-account-action").toggleClass("active");
566        $(".tjp-account-nav").slideToggle("slow");
567    }
568});
569
570function getSearchInput() {
571    return $(window).width() < 1024
572        ? $(".tjp-Head .tjp-search-form__input input")
573        : $(".channelHeader .tjp-search-form__input input");
574}
575
576function getSearchPopularSection() {
577    return $(window).width() < 1024
578        ? $(".tjp-Head .tjp-search-result--popular")
579        : $(".channelHeader .tjp-search-result--popular");
580}
581
582function getSearchTopSection() {
583    return $(window).width() < 1024
584        ? $(".tjp-Head .tjp-search-result--top")
585        : $(".channelHeader .tjp-search-result--top");
586}
587
588function getSearchResultList() {
589    return $(window).width() < 1024
590        ? $(".tjp-Head .tjp-search-result--top .tjp-search-result__list")
591        : $(".channelHeader .tjp-search-result--top .tjp-search-result__list");
592}
593
594function toggleSearchBox() {
595    const $searchBox = $(window).width() < 1024
596        ? $(".tjp-Head .tjp-search")
597        : $(".channelHeader .tjp-search");
598
599    $searchBox.toggleClass("active");
600}
601
602function clearSearch() {
603    const $searchBox = $(window).width() < 1024
604        ? $(".tjp-Head .tjp-search")
605        : $(".channelHeader .tjp-search");
606
607    const $input = getSearchInput();
608    const $resultList = getSearchResultList();
609    const $sectionTop = getSearchTopSection();
610    const $sectionPopular = getSearchPopularSection();
611
612    $searchBox.removeClass("active");
613    $input.val("");
614    $resultList.empty();
615    $sectionTop.hide();
616    $sectionPopular.show(); // tampilkan popular kembali
617}
618
619function submitSearch() {
620    const query = getSearchInput().val().trim();
621
622    if (!query) return;
623
624    pushIntoGTMEvents('btn_search_submit_click', { query });
625
626    const encodedQuery = encodeURIComponent(query);
627    const url = `${baseURL}
627/search?q=${encodedQuery}#gsc.tab=0&gsc.q=${encodedQuery}&gsc.page=1`;
628
629    // Uncomment untuk redirect:
630    window.location.href = url;
631}
632
633function renderSearchResults(data) {
634    const $sectionTop = getSearchTopSection();
635    const $sectionPopular = getSearchPopularSection();
636    const $resultList = getSearchResultList();
637    $resultList.empty();
638    $sectionPopular.hide();
639    $sectionTop.show();
640
641    if (data.length > 0) {
642        data.forEach(item => {
643            $resultList.append(`
644                <div class="tjp-search-result__list-item">
645                    <a href="${baseURL}${item.path}">${item.title}</a>
646                </div>
647            `);
648        });
649    } else {
650        $resultList.append(`
651            <div class="tjp-search-result__list-item">
652                <em>No results found. Please check your search term and try again</em>
653            </div>
654        `);
655    }
656}
657
658function handleSearchInput(query) {
659    const $resultList = getSearchResultList();
660    const $sectionTop = getSearchTopSection();
661    const $sectionPopular = getSearchPopularSection();
662
663    if (!query) {
664        console.log(query);
665        $sectionPopular.show();
666        $sectionTop.hide();
667        $resultList.empty();
668        return;
669    }
670
671    $.ajax({
672        url: `${baseURL}/api/search/top-result`,
673        method: "GET",
674        data: { query },
675        success: (response) => renderSearchResults(response.data),
676        error: (xhr) => {
677            console.error("Search error:", xhr.responseText);
678            $resultList.empty().append(`
679                <div class="tjp-search-result__list-item">
680                    <em>Error loading results</em>
681                </div>
682            `);
683        }
684    });
685}
686
687// === Event Bindings ===
688$(".btn-search").on("click", toggleSearchBox);
689
690$(".tjp-search-btn--close").on("click", clearSearch);
691
692$(".tjp-search-form__input input").on("keypress", function (e) {
693    if (e.key === "Enter") {
694        const $submitBtn = $(window).width() < 1024
695            ? $(".tjp-Head .tjp-search-btn--submit")
696            : $(".channelHeader .tjp-search-btn--submit");
697
698        $submitBtn.click();
699    }
700});
701
702$(".tjp-search-btn--submit").on("click", submitSearch);
703$(".tjp-search-result__action-link").on("click", function (e) {
704    e.preventDefault(); // Hindari reload default
705    submitSearch();     // Panggil fungsi submit pencarian
706});
707
708
709let debounceTimeout;
710$(".tjp-search-form__input input").on("input", function () {
711    const query = $(this).val().trim();
712
713    clearTimeout(debounceTimeout);
714    debounceTimeout = setTimeout(() => handleSearchInput(query), 300);
715});
716
717$(document).on("click", function (e) {
718    const $searchBox = $(window).width() < 1024
719        ? $(".tjp-Head .tjp-search")
720        : $(".channelHeader .tjp-search");
721
722    const $btnSearch = $(".btn-search");
723
724    // Kalau klik di luar search box dan bukan tombol .btn-search
725    if (
726        $searchBox.hasClass("active") &&
727        !$searchBox.is(e.target) && $searchBox.has(e.target).length === 0 &&
728        !$btnSearch.is(e.target) && $btnSearch.has(e.target).length === 0
729    ) {
730        clearSearch();
731    }
732});
733
734$(function () {
735    const $subheaders = $('.jpx-subheader');
736    if (!$subheaders.length) return;
737
738    function initSubheader($subheader) {
739        const $scrollContainer = $subheader.find('.jpx-subheader__scroll');
740        const $wrapper = $subheader.find('.jpx-subheader__scroll-wrapper');
741        const $prevBtn = $subheader.find('.jpx-subheader__nav--prev');
742        const $nextBtn = $subheader.find('.jpx-subheader__nav--next');
743        const scrollAmount = 150;
744
745        if (!$scrollContainer.length) return;
746
747        function updateArrowsAndGradient() {
748            const scrollLeft = $scrollContainer.scrollLeft();
749            const scrollWidth = $scrollContainer[0].scrollWidth;
750            const clientWidth = $scrollContainer[0].clientWidth;
751            const maxScroll = scrollWidth - clientWidth;
752            const atStart = scrollLeft <= 1;
753            const atEnd = scrollLeft >= maxScroll - 1;
754
755            if ($prevBtn.length) $prevBtn.toggleClass('is-hidden', atStart);
756            if ($nextBtn.length) $nextBtn.toggleClass('is-hidden', atEnd);
757
758            if ($wrapper.length) {
759                $wrapper.removeClass('is-start is-end is-middle');
760                if (atStart) $wrapper.addClass('is-start');
761                else if (atEnd) $wrapper.addClass('is-end');
762                else $wrapper.addClass('is-middle');
763            }
764        }
765
766        function scrollToActiveItem() {
767            const $activeItem = $scrollContainer.find('.jpx-subheader__item.active');
768            if (!$activeItem.length) {
769                updateArrowsAndGradient();
770                return;
771            }
772
773            const containerWidth = $scrollContainer[0].clientWidth;
774            const itemEl = $activeItem[0];
775
776            // hitung offsetLeft relatif ke scroll container langsung,
777            // bukan ke offsetParent, biar akurat walau ada nested wrapper
778            const itemLeft = itemEl.offsetLeft - $scrollContainer[0].offsetLeft;
779            const itemWidth = itemEl.offsetWidth;
780            const targetScrollLeft = itemLeft - (containerWidth / 2) + (itemWidth / 2);
781
782            $scrollContainer.scrollLeft(Math.max(0, targetScrollLeft));
783            updateArrowsAndGradient();
784        }
785
786        if ($prevBtn.length) {
787            $prevBtn.on('click', function () {
788                $scrollContainer.stop().animate({ scrollLeft: '-=' + scrollAmount }, 300);
789            });
790        }
791        if ($nextBtn.length) {
792            $nextBtn.on('click', function () {
793                $scrollContainer.stop().animate({ scrollLeft: '+=' + scrollAmount }, 300);
794            });
795        }
796
797        $scrollContainer.on('scroll', updateArrowsAndGradient);
798        $(window).on('resize', updateArrowsAndGradient);
799
800        // pakai requestAnimationFrame 2x supaya browser benar2 selesai
801        // layout & paint dulu sebelum kita baca offsetLeft/clientWidth,
802        // dan pakai setTimeout kecil untuk 'menang' dari scroll restoration browser
803        requestAnimationFrame(() => {
804            requestAnimationFrame(() => {
805                scrollToActiveItem();
806                // extra safety: beberapa mobile browser reset scroll
807                // beberapa saat setelah load, jadi kita paksa lagi
808                setTimeout(scrollToActiveItem, 150);
809            });
810        });
811    }
812
813    // guard: kalau document sudah 'complete' (load event sudah lewat),
814    // langsung jalankan; kalau belum, baru nunggu event 'load'
815    if (document.readyState === 'complete') {
816        $subheaders.each(function () {
817            initSubheader($(this));
818        });
819    } else {
820        $(window).on('load', function () {
821            $subheaders.each(function () {
822                initSubheader($(this));
823            });
824        });
825    }
826});
827
828$(function () {
829    // buat toast container sekali aja, reuse terus
830    let $toast = $('.jpx-toast');
831    if (!$toast.length) {
832        $toast = $('<div class="jpx-toast"><i class="fa-solid fa-check"></i><span>Link copied!</span></div>');
833        $('body').append($toast);
834    }
835
836    let toastTimeout;
837
838    function showToast(message) {
839        clearTimeout(toastTimeout);
840        $toast.find('span').text(message);
841        $toast.addClass('is-visible');
842
843        toastTimeout = setTimeout(function () {
844            $toast.removeClass('is-visible');
845        }, 1800);
846    }
847
848    $(document).on('click', '.jpx-copy-link', function (e) {
849        e.preventDefault();
850
851        const $btn = $(this);
852        const url = $btn.data('url');
853
854        if (!url) return;
855
856        function onCopySuccess() {
857            const $icon = $btn.find('i');
858            const originalClass = $icon.attr('class');
859
860            $icon.attr('class', 'fa-solid fa-check');
861            $btn.addClass('is-copied');
862
863            setTimeout(function () {
864                $icon.attr('class', originalClass);
865                $btn.removeClass('is-copied');
866            }, 1500);
867
868            showToast('Link copied!');
869        }
870
871        if (navigator.clipboard && navigator.clipboard.writeText) {
872            navigator.clipboard.writeText(url).then(onCopySuccess).catch(function () {
873                fallbackCopy(url, onCopySuccess);
874            });
875        } else {
876            fallbackCopy(url, onCopySuccess);
877        }
878    });
879
880    function fallbackCopy(text, callback) {
881        const $temp = $('<textarea>');
882        $('body').append($temp);
883        $temp.val(text).select();
884
885        try {
886            document.execCommand('copy');
887            callback();
888        } catch (err) {
889            console.error('Copy failed:', err);
890        }
891
892        $temp.remove();
893    }
894});

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.