PageSourceSearch

https://tradezing.com/wp-content/themes/cwicly/ai-newsletter-feed/assets/js/newsletter-feed.js?ver=1.0.5

js tradezing.com collected 2026-09-24 13:31:36 UTC 14,814 bytes, 396 lines download raw bytes

1(function($) {
2    'use strict';
3
4    // --- State ---
5    var state = {
6        daily_tag: '',
7        email_source: [],
8        newsletter_category: '',
9        search: '',
10        page: 1
11    };
12
13    var $wrapper, $feedList, $skeletonTpl, $overlay, searchTimer;
14    var isLoading = false;
15
16    // --- Init ---
17    $(document).ready(function() {
18        $wrapper = $('.tznl-wrapper');
19        if (!$wrapper.length) return;
20
21        $feedList = $wrapper.find('.tznl-feed-list');
22        $skeletonTpl = $wrapper.find('.tznl-skeleton-template');
23        $overlay = $wrapper.find('.tznl-modal-overlay');
24
25        bindEvents();
26        initInfiniteScroll();
27    });
28
29    function bindEvents() {
30        // --- Dropdown toggles ---
31        $wrapper.on('click', '.tznl-filter-icon-btn', function(e) {
32            e.stopPropagation();
33            var type = $(this).data('dropdown');
34            var $dropdown = $wrapper.find('.tznl-dropdown-' + type);
35            var isVisible = $dropdown.is(':visible');
36
37            closeDropdowns();
38
39            if (!isVisible) {
40                // On mobile, add backdrop
41                if (window.innerWidth <= 768) {
42                    $('<div class="tznl-dropdown-backdrop"></div>').insertBefore($dropdown);
43                }
44                $dropdown.show();
45                $(this).addClass('active');
46            }
47        });
48
49        $wrapper.on('click', '.tznl-dropdown-close', function() {
50            closeDropdowns();
51        });
52
53        $(document).on('click', '.tznl-dropdown-backdrop', function() {
54            closeDropdowns();
55        });
56
57        $(document).on('click', function(e) {
58            if (!$(e.target).closest('.tznl-filter-toggles').length && !$(e.target).closest('.tznl-dropdown').length) {
59                closeDropdowns();
60            }
61        });
62
63        // --- Date filter ---
64        $wrapper.on('click', '.tznl-date-btn', function() {
65            var slug = $(this).data('slug');
66            var wasActive = $(this).hasClass('active');
67
68            $wrapper.find('.tznl-date-btn').removeClass('active');
69
70            if (wasActive) {
71                state.daily_tag = '';
72            } else {
73                $(this).addClass('active');
74                state.daily_tag = slug;
75            }
76            resetAndLoad();
77        });
78
79        // --- Source filter: row click (multi-select toggle) ---
80        $wrapper.on('click', '.tznl-source-item', function(e) {
81            if ($(e.target).closest('.tznl-source-only-btn').length) return; // let Only handle itself
82            var slug = $(this).find('.tznl-source-only-btn').data('slug');
83            var idx = state.email_source.indexOf(slug);
84
85            if (idx > -1) {
86                state.email_source.splice(idx, 1);
87                $(this).removeClass('active');
88            } else {
89                state.email_source.push(slug);
90                $(this).addClass('active');
91            }
92            resetAndLoad();
93        });
94
95        // --- Source filter: Only button (single-select) ---
96        $wrapper.on('click', '.tznl-source-only-btn', function(e) {
97            e.stopPropagation();
98            var slug = $(this).data('slug');
99            var isOnlyActive = state.email_source.length === 1 && state.email_source[0] === slug;
100
101            $wrapper.find('.tznl-source-item').removeClass('active');
102            state.email_source = [];
103
104            if (!isOnlyActive) {
105                state.email_source = [slug];
106                $(this).closest('.tznl-source-item').addClass('active');
107            }
108            resetAndLoad();
109        });
110
111        // --- Category tabs ---
112        $wrapper.on('click', '.tznl-cat-tab', function() {
113            $wrapper.find('.tznl-cat-tab').removeClass('tznl-cat-active');
114            $(this).addClass('tznl-cat-active');
115            state.newsletter_category = $(this).data('slug');
116            resetAndLoad();
117        });
118
119        // --- Search (debounced) ---
120        $wrapper.on('input', '.tznl-search-input', function() {
121            var val = $(this).val();
122            clearTimeout(searchTimer);
123            searchTimer = setTimeout(function() {
124                state.search = val;
125                resetAndLoad();
126            }, 400);
127        });
128
129        // Infinite scroll: observer is initialized via initInfiniteScroll().
130        // No click handler needed — the load-more button has been replaced
131        // by a viewport-triggered sentinel.
132
133        // --- Read all / Show less toggle ---
134        $wrapper.on('click', '.tznl-read-toggle', function(e) {
135            e.preventDefault();
136            var $container = $(this).closest('.tznl-excerpt-container');
137            $container.find('.tznl-excerpt-short, .tznl-excerpt-full').toggle();
138        });
139
140        // --- Source Newsletter modal ---
141        $wrapper.on('click', '.tznl-source-btn', function() {
142            var postId = $(this).data('post-id');
143            openModal(postId);
144        });
145
146        // --- Close modal ---
147        $overlay.on('click', '.tznl-modal-close', function() {
148            closeModal();
149        });
150        $overlay.on('click', function(e) {
151            if ($(e.target).is('.tznl-modal-overlay')) {
152                closeModal();
153            }
154        });
155        $(document).on('keydown', function(e) {
156            if (e.key === 'Escape' && $overlay.is(':visible')) {
157                closeModal();
158            }
159        });
160
161        // --- Clear filter tags ---
162        $wrapper.on('click', '.tznl-clear-filter', function() {
163            var type = $(this).data('type');
164            if (type === 'daily_tag') {
165                state.daily_tag = '';
166                $wrapper.find('.tznl-date-btn').removeClass('active');
167            } else if (type === 'email_source') {
168                var slug = $(this).data('slug');
169                if (slug) {
170                    var idx = state.email_source.indexOf(slug);
171                    if (idx > -1) state.email_source.splice(idx, 1);
172                    $wrapper.find('.tznl-source-only-btn[data-slug="' + slug + '"]').closest('.tznl-source-item').removeClass('active');
173                } else {
174                    state.email_source = [];
175                    $wrapper.find('.tznl-source-item').removeClass('active');
176                }
177            }
178            resetAndLoad();
179        });
180    }
181
182    // --- Close all dropdowns and remove backdrop ---
183    function closeDropdowns() {
184        $wrapper.find('.tznl-dropdown').hide();
185        $wrapper.find('.tznl-filter-icon-btn').removeClass('active');
186        $('.tznl-dropdown-backdrop').remove();
187    }
188
189    // --- Infinite Scroll ---
190    // IntersectionObserver watches a sentinel element at the end of the
191    // feed. When it enters the viewport we fetch the next page. The
192    // sentinel is re-rendered by each AJAX response so we re-observe
193    // after every append; `infiniteDone` short-circuits once the server
194    // reports no more pages.
195    var io = null;
196    var infiniteDone = false;
197
198    function initInfiniteScroll() {
199        if (typeof IntersectionObserver === 'undefined') return;
200        io = new IntersectionObserver(function(entries) {
201            entries.forEach(function(entry) {
202                if (!entry.isIntersecting) return;
203                if (isLoading || infiniteDone) return;
204                var next = parseInt($(entry.target).data('next-page'), 10);
205                if (!next) return;
206                // Reveal the "Loading more newsletters..." indicator while
207                // the fetch is in flight.
208                $(entry.target).addClass('is-loading');
209                state.page = next;
210                loadMore();
211            });
212        }, { rootMargin: '400px 0px' });
213        observeSentinel();
214    }
215
216    function observeSentinel() {
217        if (!io) return;
218        var sentinel = $wrapper.find('.tznl-infinite-sentinel')[0];
219        if (sentinel) io.observe(sentinel);
220    }
221
222    function sentinelMarkup(nextPage) {
223        return '<div class="tznl-infinite-sentinel" data-next-page="' + nextPage + '" aria-hidden="true">'
224             +   '<div class="tznl-infinite-loader"><span class="tznl-btn-spinner"></span> Loading more newsletters...</div>'
225             + '</div>';
226    }
227
228    // --- Reset filters and load fresh (replaces content) ---
229    function resetAndLoad() {
230        state.page = 1;
231        infiniteDone = false;
232        showSkeleton();
233        updateActiveFilters();
234        doAjaxLoad(false);
235    }
236
237    // --- Load More (appends content) ---
238    function loadMore() {
239        isLoading = true;
240        doAjaxLoad(true);
241    }
242
243    // --- Core AJAX loader ---
244    function doAjaxLoad(isAppend) {
245        $.ajax({
246            url: tznl_ajax.ajax_url,
247            type: 'POST',
248            data: {
249                action: 'tznl_load_posts',
250                nonce: tznl_ajax.nonce,
251                daily_tag: state.daily_tag,
252                email_source: state.email_source.join(','),
253                newsletter_category: state.newsletter_category,
254                search: state.search,
255                page: state.page,
256                posts_per_page: $wrapper.data('posts-per-page') || 15,
257                is_load_more: isAppend ? '1' : '0'
258            },
259            success: function(res) {
260                isLoading = false;
261
262                if (!res.success) {
263                    if (!isAppend) {
264                        $feedList.html('<div class="tznl-no-results"><p>No newsletters found.</p></div>');
265                    }
266                    $wrapper.find('.tznl-infinite-sentinel').remove();
267                    infiniteDone = true;
268                    return;
269                }
270
271                var d = res.data;
272
273                if (isAppend) {
274                    // Append: merge date groups intelligently
275                    var $newContent = $('<div>').html(d.html);
276                    var $lastGroup = $feedList.find('.tznl-date-group:last');
277                    var lastHeading = $lastGroup.find('.tznl-date-heading').text();
278
279                    $newContent.find('.tznl-date-group').each(function() {
280                        var $group = $(this);
281                        var heading = $group.find('.tznl-date-heading').text();
282
283                        if (heading === lastHeading && $lastGroup.length) {
284                            // Same date group — append cards without duplicate heading
285                            $group.find('.tznl-card').each(function() {
286                                $lastGroup.append($(this));
287                            });
288                        } else {
289                            // New date group — append entire group
290                            $feedList.append($group);
291                            $lastGroup = $group;
292                            lastHeading = heading;
293                        }
294                    });
295                } else {
296                    // Replace
297                    $feedList.html(d.html);
298                }
299
300                // Swap the sentinel for infinite scroll
301                $wrapper.find('.tznl-infinite-sentinel').remove();
302                if (d.has_more) {
303                    infiniteDone = false;
304                    $feedList.after(sentinelMarkup(d.next_page));
305                    observeSentinel();
306                } else {
307                    infiniteDone = true;
308                }
309            },
310            error: function() {
311                isLoading = false;
312                if (!isAppend) {
313                    $feedList.html('<div class="tznl-no-results"><p>Failed to load. Please try again.</p></div>');
314                }
315                $wrapper.find('.tznl-infinite-sentinel').remove();
316                infiniteDone = true;
317            }
318        });
319    }
320
321    // --- Skeleton ---
322    function showSkeleton() {
323        $feedList.html($skeletonTpl.html());
324        // A new filter/search starts over — drop any stale sentinel; the
325        // response will re-render a fresh one if there are more pages.
326        $wrapper.find('.tznl-infinite-sentinel').remove();
327        infiniteDone = false;
328    }
329
330    // --- Active Filter Tags ---
331    function updateActiveFilters() {
332        $wrapper.find('.tznl-active-filters').remove();
333
334        var tags = [];
335        if (state.daily_tag) {
336            var label = $wrapper.find('.tznl-date-btn[data-slug="' + state.daily_tag + '"]').text() || state.daily_tag;
337            tags.push('<span class="tznl-active-filter-tag">' + label + ' <button type="button" class="tznl-clear-filter" data-type="daily_tag">&times;</button></span>');
338        }
339        if (state.email_source.length) {
340            state.email_source.forEach(function(slug) {
341                tags.push('<span class="tznl-active-filter-tag">' + slug + ' <button type="button" class="tznl-clear-filter" data-type="email_source" data-slug="' + slug + '">&times;</button></span>');
342            });
343        }
344
345        if (tags.length) {
346            $feedList.before('<div class="tznl-active-filters">' + tags.join('') + '</div>');
347        }
348    }
349
350    // --- Modal ---
351    function openModal(postId) {
352        $overlay.find('.tznl-modal-header').hide();
353        $overlay.find('.tznl-modal-body').html('<div class="tznl-modal-loading"><div class="tznl-modal-spinner"></div></div>');
354        $overlay.css('display', 'flex');
355
356        $overlay[0].offsetHeight;
357        $overlay.addClass('tznl-modal-visible');
358        document.body.style.overflow = 'hidden';
359
360        $.ajax({
361            url: tznl_ajax.ajax_url,
362            type: 'POST',
363            data: {
364                action: 'tznl_load_modal',
365                nonce: tznl_ajax.nonce,
366                post_id: postId
367            },
368            success: function(res) {
369                if (res.success) {
370                    var d = res.data;
371                    $overlay.find('.tznl-modal-logo').attr('src', d.source_logo);
372                    $overlay.find('.tznl-modal-source-name').text(d.source_name);
373                    $overlay.find('.tznl-modal-title').text(d.title);
374                    $overlay.find('.tznl-modal-date').text(d.date + '  |  ' + d.time);
375                    $overlay.find('.tznl-modal-header').show();
376                    $overlay.find('.tznl-modal-body').html(d.content);
377                } else {
378                    $overlay.find('.tznl-modal-body').html('<p>Failed to load content.</p>');
379                }
380            },
381            error: function() {
382                $overlay.find('.tznl-modal-body').html('<p>Network error. Please try again.</p>');
383            }
384        });
385    }
386
387    function closeModal() {
388        $overlay.removeClass('tznl-modal-visible');
389        document.body.style.overflow = '';
390        setTimeout(function() {
391            $overlay.hide();
392            $overlay.find('.tznl-modal-body').html('');
393        }, 250);
394    }
395
396})(jQuery);

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.