/** * @fileoverview Rant Feed Component for Rantii * @author retoor * @description Infinite scrolling list of rants * @keywords feed, list, rants, infinite, scroll */ import { BaseComponent } from './base-component.js'; class RantFeed extends BaseComponent { static get observedAttributes() { return ['sort', 'feed-type']; } init() { this.rants = []; this.skip = 0; this.limit = 20; this.isLoading = false; this.hasMore = true; this.sort = this.getAttr('sort') || 'recent'; this.feedType = this.getAttr('feed-type') || 'rants'; this.render(); this.bindEvents(); } async load(reset = false) { if (this.isLoading) return; if (!reset && !this.hasMore) return; if (reset) { this.rants = []; this.skip = 0; this.hasMore = true; } this.isLoading = true; this.updateLoadingState(); try { let result; const api = this.getApi(); switch (this.feedType) { case 'weekly': result = await api?.getWeeklyRants(this.sort, this.limit, this.skip); break; case 'collabs': result = await api?.getCollabs(this.sort, this.limit, this.skip); break; case 'stories': result = await api?.getStories(this.sort, this.limit, this.skip); break; case 'search': break; default: result = await api?.getRants(this.sort, this.limit, this.skip); } if (result?.success) { const newRants = result.rants || []; this.rants = [...this.rants, ...newRants]; this.skip += newRants.length; this.hasMore = newRants.length >= this.limit; } else { this.hasMore = false; } } catch (error) { this.hasMore = false; } finally { this.isLoading = false; this.render(); } } async search(term) { if (this.isLoading) return; this.isLoading = true; this.rants = []; this.updateLoadingState(); try { const result = await this.getApi()?.search(term); if (result?.success) { this.rants = result.rants || []; } this.hasMore = false; } catch (error) { this.rants = []; } finally { this.isLoading = false; this.render(); } } render() { this.addClass('rant-feed'); if (this.rants.length === 0 && !this.isLoading) { this.setHtml(`

No rants found

`); return; } this.setHtml(`
${this.rants.map(rant => ` `).join('')}
${this.isLoading ? `
` : ''} ${this.hasMore && !this.isLoading ? `
` : ''} `); this.initRantCards(); } initRantCards() { const cards = this.$$('rant-card'); cards.forEach((card, index) => { if (this.rants[index]) { card.setRant(this.rants[index]); } }); } updateLoadingState() { const loadingEl = this.$('.feed-loading'); if (this.isLoading && !loadingEl && this.rants.length > 0) { const loadMore = this.$('.feed-loadmore'); if (loadMore) { loadMore.innerHTML = ''; } } } bindEvents() { this.on(this, 'click', this.handleClick); this.setupInfiniteScroll(); } handleClick(e) { const sortTab = e.target.closest('.sort-tab'); const loadMoreBtn = e.target.closest('.load-more-btn'); if (sortTab) { const newSort = sortTab.dataset.sort; if (newSort !== this.sort) { this.sort = newSort; this.setAttr('sort', newSort); this.load(true); } return; } if (loadMoreBtn) { this.load(); } } setupInfiniteScroll() { const observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (entry.isIntersecting && !this.isLoading && this.hasMore) { this.load(); } }); }, { rootMargin: '200px' } ); this.intersectionObserver = observer; } onConnected() { this.load(true); } onDisconnected() { if (this.intersectionObserver) { this.intersectionObserver.disconnect(); } this.isLoading = false; this.hasMore = true; } onAttributeChanged(name, oldValue, newValue) { if (name === 'sort' && oldValue !== newValue) { this.sort = newValue; this.load(true); } if (name === 'feed-type' && oldValue !== newValue) { this.feedType = newValue; this.load(true); } } setSort(sort) { this.sort = sort; this.setAttr('sort', sort); this.load(true); } setFeedType(type) { this.feedType = type; this.setAttr('feed-type', type); this.load(true); } refresh() { this.load(true); } getRants() { return this.rants; } } customElements.define('rant-feed', RantFeed); export { RantFeed };