PageSourceSearch

https://cdn.talentrecap.com/wp-content/themes/talentrecap/includes/ajax-load-more.js?ver=1.0.0

js talentrecap.com collected 2026-10-02 04:01:40 UTC 13,368 bytes, 492 lines download raw bytes

1(function ($) {
2  'use strict';
3
4  class AjaxLoadMore {
5    constructor() {
6      this.container = $('.ajax-load-more-container');
7      this.postsContainer = $('.ajax-load-more-posts');
8      this.loadBtn = $('.ajax-load-more-btn');
9      this.loading = $('.ajax-load-more-loading');
10      this.noMore = $('.ajax-load-more-no-more');
11
12      const urlPage = this.getPageFromURL();
13      this.targetPage = urlPage;
14      this.currentPage = 1;
15      this.maxPages = parseInt(this.container.data('max-pages')) || 1;
16      this.postsPerPage = parseInt(this.container.data('posts-per-page')) || 10;
17      this.autoLoadOnce = false;
18      this.scrollThreshold = 0;
19      // this.autoLoadOnce = ajax_load_more_params.auto_load_once;
20      // this.scrollThreshold = ajax_load_more_params.scroll_threshold || 200;
21      this.autoScrollEnabled = this.container.data('auto-scroll') !== false;
22
23      this.isLoading = false;
24      this.autoLoaded = false;
25      this.scrollTimeout = null;
26      this.urlUpdateTimeout = null;
27
28      this.init();
29    }
30
31    init() {
32      if (this.container.length === 0) return;
33
34      this.bindEvents();
35      this.handleInitialLoad();
36      this.restoreScrollPosition();
37
38      if (this.currentPage < this.maxPages) {
39        this.loadBtn.show();
40      }
41    }
42
43    bindEvents() {
44      this.loadBtn.on('click', (e) => {
45        e.preventDefault();
46        this.loadMorePosts();
47      });
48
49      $(window).on(
50        'scroll.ajaxLoadMore',
51        this.throttle(() => {
52          this.handleScroll();
53        }, 250)
54      );
55
56      $(window).on('popstate', (e) => {
57        if (e.originalEvent.state && e.originalEvent.state.page) {
58          this.handlePopState(e.originalEvent.state);
59        }
60      });
61    }
62
63    handleInitialLoad() {
64      const urlPage = this.getPageFromURL();
65
66      this.currentPage = urlPage;
67      this.updateURL(urlPage, false);
68
69      this.updateUI();
70    }
71
72    async loadPreviousPages(targetPage) {
73      for (let page = 2; page <= targetPage; page++) {
74        while (this.isLoading) {
75          await new Promise((resolve) => setTimeout(resolve, 50));
76        }
77
78        await this.loadPage(page, true);
79
80        if (page < targetPage) {
81          await new Promise((resolve) => setTimeout(resolve, 100));
82        }
83      }
84
85      this.currentPage = targetPage;
86      this.updateURL(targetPage, false);
87      this.updateUI();
88    }
89
90    handleScroll() {
91      if (
92        this.isLoading ||
93        this.currentPage >= this.maxPages ||
94        !this.autoScrollEnabled
95      )
96        return;
97
98      const loadMoreBtn = this.loadBtn.length
99        ? this.loadBtn
100        : this.container.find('.ajax-load-more-controls');
101
102      if (loadMoreBtn.length === 0) return;
103
104      const buttonTop = loadMoreBtn.offset().top;
105      const scrollTop = $(window).scrollTop();
106      const windowHeight = $(window).height();
107      const viewportBottom = scrollTop + windowHeight;
108
109      const distanceToButton = buttonTop - viewportBottom;
110
111      const effectiveThreshold =
112        this.scrollThreshold + (this.isLoading ? 500 : 0);
113      const shouldTriggerLoad =
114        distanceToButton <= effectiveThreshold && distanceToButton > -100;
115
116      if (this.autoLoadOnce && !this.autoLoaded && shouldTriggerLoad) {
117        this.autoLoaded = true;
118        this.loadMorePosts();
119        return;
120      }
121
122      if (!this.autoLoadOnce && shouldTriggerLoad) {
123        this.loadMorePosts();
124      }
125
126      this.updateURLBasedOnScroll();
127    }
128
129    updateURLBasedOnScroll() {
130      return;
131      /*
132      if (this.urlUpdateTimeout) {
133        clearTimeout(this.urlUpdateTimeout);
134      }
135
136      this.urlUpdateTimeout = setTimeout(() => {
137        const scrollTop = $(window).scrollTop();
138        const items = this.postsContainer.find('.ajax-load-more-item');
139        let currentVisiblePage = 1;
140
141        items.each((index, item) => {
142          const itemTop = $(item).offset().top;
143          if (scrollTop >= itemTop - 100) {
144            currentVisiblePage = Math.ceil((index + 1) / this.postsPerPage);
145          }
146        });
147
148        if (currentVisiblePage !== this.getPageFromURL()) {
149          this.updateURL(currentVisiblePage, false);
150          this.storeScrollPosition();
151        }
152      }, 500);
153      */
154    }
155
156    async loadMorePosts() {
157      if (this.isLoading || this.currentPage >= this.maxPages) return;
158
159      const nextPage = this.currentPage + 1;
160
161      if (nextPage > this.maxPages) {
162        this.updateUI();
163        return;
164      }
165
166      await this.loadPage(nextPage);
167    }
168
169    async loadPage(page, silent = false) {
170      if (this.isLoading) {
171        return;
172      }
173
174      this.isLoading = true;
175
176      if (!silent) {
177        this.showLoading();
178      }
179
180      // Check if this is the home page with special layout
181      const isHome = $('body').hasClass('home');
182
183      const data = {
184        action: isHome ? 'load_more_home_posts' : 'load_more_posts',
185        nonce: ajax_load_more_params.nonce,
186        page: page,
187        posts_per_page: this.postsPerPage,
188        post_type: this.container.data('post-type') || 'post',
189        category: this.container.data('category') || '',
190        tag: this.container.data('tag') || '',
191        meta_key: this.container.data('meta-key') || '',
192        meta_value: this.container.data('meta-value') || '',
193        hide_category: this.container.attr('data-hide-category') || '0',
194      };
195
196      const containerData = this.container.data();
197      Object.keys(containerData).forEach((key) => {
198        if (key.startsWith('query')) {
199          const cleanKey = key.replace('query', '').toLowerCase();
200          data[cleanKey] = containerData[key];
201        }
202      });
203
204      try {
205        const response = await $.ajax({
206          url: ajax_load_more_params.ajax_url,
207          type: 'POST',
208          data: data,
209        });
210
211        if (response.success) {
212          if (
213            !response.data.content ||
214            response.data.content.trim().length === 0
215          ) {
216            this.currentPage = this.maxPages;
217            this.updateUI();
218            this.isLoading = false;
219            this.hideLoading();
220            return;
221          }
222
223          const newContent = $(response.data.content);
224          this.postsContainer.append(newContent);
225
226          this.currentPage = page;
227
228          if (
229            response.data.max_pages &&
230            response.data.max_pages !== this.maxPages
231          ) {
232            this.maxPages = response.data.max_pages;
233            this.container.attr('data-max-pages', this.maxPages);
234          }
235
236          if (!silent) {
237            this.updateURL(page, true);
238            this.storeScrollPosition();
239          }
240
241          const customEvent = new CustomEvent('ajax_load_more:loaded', {
242            detail: {
243              newContent: newContent.toArray(),
244              newContentEl: newContent,
245              page: page,
246            },
247          });
248          document.dispatchEvent(customEvent);
249
250          $(document).trigger('ajax_load_more:loaded', [newContent, page]);
251
252          this.updateUI();
253
254          setTimeout(() => {
255            this.isLoading = false;
256            this.hideLoading();
257          }, 500);
258        } else {
259          if (this.currentPage >= this.maxPages) {
260            this.updateUI();
261          } else {
262            this.showNoMore();
263          }
264        }
265
266        setTimeout(() => {
267          this.isLoading = false;
268          this.hideLoading();
269        }, 500);
270      } catch (error) {
271        if (error.responseText) {
272          console.error('📄 Server response:', error.responseText);
273        }
274
275        if (this.currentPage >= this.maxPages) {
276          this.updateUI();
277        } else {
278          this.showNoMore();
279        }
280      }
281
282      setTimeout(() => {
283        this.isLoading = false;
284        this.hideLoading();
285      }, 500);
286    }
287
288    updateUI() {
289      if (this.currentPage >= this.maxPages) {
290        this.loadBtn.hide();
291        this.showNoMore();
292
293        $(window).off('scroll.ajaxLoadMore');
294        return;
295      }
296
297      if (this.autoLoadOnce && this.autoLoaded) {
298        this.loadBtn.show();
299      } else if (!this.autoLoadOnce) {
300        this.loadBtn.show();
301      }
302    }
303
304    showLoading() {
305      this.loadBtn.hide();
306      this.loading.show();
307    }
308
309    hideLoading() {
310      this.loading.hide();
311    }
312
313    showNoMore() {
314      this.loadBtn.hide();
315      this.noMore.show();
316    }
317
318    updateURL(page, pushState = true) {
319      const url = this.buildURL(page);
320      const state = {
321        page: page,
322        scroll_pos: $(window).scrollTop(),
323      };
324
325      if (pushState) {
326        history.pushState(state, '', url);
327      } else {
328        history.replaceState(state, '', url);
329      }
330    }
331
332    buildURL(page) {
333      const searchParams = new URLSearchParams(window.location.search);
334
335      // For search pages, use ?s=term&paged=N format
336      if (searchParams.has('s')) {
337        if (page > 1) {
338          searchParams.set('paged', page);
339        } else {
340          searchParams.delete('paged');
341        }
342        return `${window.location.pathname}?${searchParams.toString()}`;
343      }
344
345      // For other pages, use /page/N/ format
346      let baseUrl = window.location.pathname.replace(/\/page\/\d+\/?$/, '');
347      if (baseUrl.endsWith('/')) {
348        baseUrl = baseUrl.slice(0, -1);
349      }
350      return page > 1 ? `${baseUrl}/page/${page}/` : baseUrl;
351    }
352
353    getPageFromURL() {
354      // Check for ?paged=N (search pages)
355      const searchParams = new URLSearchParams(window.location.search);
356      if (searchParams.has('paged')) {
357        return parseInt(searchParams.get('paged')) || 1;
358      }
359
360      // Check for /page/N/ (other pages)
361      const match = window.location.pathname.match(/\/page\/(\d+)\/?$/);
362      return match ? parseInt(match[1]) : 1;
363    }
364
365    storeScrollPosition() {
366      sessionStorage.setItem(
367        'ajax_load_more_scroll_pos',
368        $(window).scrollTop()
369      );
370      sessionStorage.setItem('ajax_load_more_page', this.currentPage);
371    }
372
373    waitForContentAndRestoreScroll(targetScrollPos, targetPage) {
374      const expectedItems = targetPage * this.postsPerPage;
375      let attempts = 0;
376      const maxAttempts = 20;
377
378      const checkAndRestore = () => {
379        const currentItems = this.postsContainer.find(
380          '.ajax-load-more-item'
381        ).length;
382        attempts++;
383
384        if (currentItems >= expectedItems || attempts >= maxAttempts) {
385          setTimeout(() => {
386            if (targetScrollPos) {
387              $(window).scrollTop(targetScrollPos);
388            } else {
389              const items = this.postsContainer.find('.ajax-load-more-item');
390              const startIndex = (targetPage - 1) * this.postsPerPage;
391
392              if (items.length > startIndex) {
393                const targetItem = items.eq(startIndex);
394                if (targetItem.length) {
395                  $(window).scrollTop(targetItem.offset().top - 100);
396                }
397              }
398            }
399          }, 100);
400        } else {
401          setTimeout(checkAndRestore, 200);
402        }
403      };
404
405      checkAndRestore();
406    }
407
408    restoreScrollPosition() {
409      const urlPage = this.getPageFromURL();
410
411      if (urlPage > 1) {
412        setTimeout(() => {
413          const items = this.postsContainer.find('.ajax-load-more-item');
414          const targetItemIndex = (urlPage - 1) * this.postsPerPage;
415
416          if (items.length > targetItemIndex) {
417            const targetItem = items.eq(targetItemIndex);
418            if (targetItem.length) {
419              $('html, body').animate(
420                {
421                  scrollTop: targetItem.offset().top - 100,
422                },
423                500
424              );
425            }
426          }
427        }, 500);
428
429        return;
430      }
431
432      const scrollPos = sessionStorage.getItem('ajax_load_more_scroll_pos');
433      const storedPage = sessionStorage.getItem('ajax_load_more_page');
434
435      if (
436        scrollPos &&
437        storedPage &&
438        parseInt(storedPage) === this.currentPage
439      ) {
440        setTimeout(() => {
441          $(window).scrollTop(parseInt(scrollPos));
442        }, 100);
443      }
444    }
445
446    handlePopState(state) {
447      const targetPage = this.getPageFromURL();
448      const targetScrollPos = state ? state.scroll_pos : null;
449
450      if (targetPage < this.currentPage) {
451        window.location.reload();
452      } else if (targetPage > this.currentPage) {
453        this.loadToPage(targetPage).then(() => {
454          this.waitForContentAndRestoreScroll(targetScrollPos, targetPage);
455        });
456      } else if (targetScrollPos) {
457        setTimeout(() => {
458          $(window).scrollTop(targetScrollPos);
459        }, 100);
460      }
461    }
462
463    async loadToPage(targetPage) {
464      while (
465        this.currentPage < targetPage &&
466        this.currentPage < this.maxPages
467      ) {
468        const nextPage = this.currentPage + 1;
469        await this.loadPage(nextPage, true);
470      }
471
472      this.updateUI();
473    }
474
475    throttle(func, limit) {
476      let inThrottle;
477      return function () {
478        const args = arguments;
479        const context = this;
480        if (!inThrottle) {
481          func.apply(context, args);
482          inThrottle = true;
483          setTimeout(() => (inThrottle = false), limit);
484        }
485      };
486    }
487  }
488
489  $(document).ready(function () {
490    new AjaxLoadMore();
491  });
492})(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.