/**
* @fileoverview Rant Feed Component for Rantii
* @author retoor <retoor@molodetz.nl>
* @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(`
<div class="feed-empty">
<p>No rants found</p>
</div>
`);
return;
}
this.setHtml(`
<div class="feed-controls">
<div class="sort-tabs">
<button class="sort-tab ${this.sort === 'recent' ? 'active' : ''}" data-sort="recent">Recent</button>
<button class="sort-tab ${this.sort === 'top' ? 'active' : ''}" data-sort="top">Top</button>
<button class="sort-tab ${this.sort === 'algo' ? 'active' : ''}" data-sort="algo">Algo</button>
</div>
</div>
<div class="feed-list">
${this.rants.map(rant => `
<rant-card rant-id="${rant.id}"></rant-card>
`).join('')}
</div>
${this.isLoading ? `
<div class="feed-loading">
<loading-spinner></loading-spinner>
</div>
` : ''}
${this.hasMore && !this.isLoading ? `
<div class="feed-loadmore">
<button class="btn btn-secondary load-more-btn">Load More</button>
</div>
` : ''}
`);
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 = '<loading-spinner></loading-spinner>';
}
}
}
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 };