/** * @fileoverview Rant Card Component for Rantii * @author retoor * @description Compact rant display for feed listings * @keywords rant, card, feed, listing, preview */ import { BaseComponent } from './base-component.js'; import { formatRelativeTime } from '../utils/date.js'; import { buildDevrantImageUrl } from '../utils/url.js'; class RantCard extends BaseComponent { static get observedAttributes() { return ['rant-id']; } init() { this.rantData = null; this.render(); this.bindEvents(); } setRant(rant) { this.rantData = rant; this.setAttr('rant-id', rant.id); this.render(); } render() { if (!this.rantData) { this.setHtml('
'); return; } const rant = this.rantData; const hasImage = rant.attached_image && typeof rant.attached_image === 'object'; const imageUrl = hasImage ? buildDevrantImageUrl(rant.attached_image.url) : null; this.addClass('rant-card'); this.setHtml(`
${rant.user_username} +${rant.user_score} ${formatRelativeTime(rant.created_time)}
${imageUrl ? `
` : ''}
`); } escapeAttr(str) { if (!str) return ''; return str .replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, ''') .replace(//g, '>'); } bindEvents() { this.on(this, 'click', this.handleClick); this.on(this, 'vote', this.handleVote); } handleClick(e) { const username = e.target.closest('.card-username'); const avatar = e.target.closest('user-avatar'); const commentsBtn = e.target.closest('.card-comments'); const voteBtn = e.target.closest('.vote-btn'); const tag = e.target.closest('.tag'); const imagePreview = e.target.closest('image-preview'); const youtubeEmbed = e.target.closest('youtube-embed'); const linkPreview = e.target.closest('link-preview'); if (voteBtn || imagePreview || youtubeEmbed || linkPreview) { return; } if (username || avatar) { e.stopPropagation(); this.getRouter()?.goToUser(this.rantData.user_username); return; } if (tag) { e.stopPropagation(); this.getRouter()?.goToSearch(tag.textContent); return; } this.getRouter()?.goToRant(this.rantData.id); } async handleVote(e) { e.stopPropagation(); const { vote, itemId } = e.detail; const voteButtons = this.$('vote-buttons'); if (voteButtons) { voteButtons.disable(); } try { const result = await this.getApi()?.voteRant(itemId, vote); if (result?.success && result.rant) { this.rantData.score = result.rant.score; this.rantData.vote_state = result.rant.vote_state; if (voteButtons) { voteButtons.updateVote(result.rant.score, result.rant.vote_state); voteButtons.enable(); } } } catch (error) { if (voteButtons) { voteButtons.enable(); } } } getRantId() { return this.rantData?.id || this.getAttr('rant-id'); } } customElements.define('rant-card', RantCard); export { RantCard };