/**
* @fileoverview Rant Content Component for Rantii
* @author retoor <retoor@molodetz.nl>
* @description Renders rant text with markdown, images, and media
* @keywords rant, content, markdown, media, render
*/
import { BaseComponent } from './base-component.js';
import { categorizeLinks, processTextWithLinks, extractImageUrls, extractYoutubeUrls, extractNonMediaUrls, sanitizeUrl } from '../utils/url.js';
class RantContent extends BaseComponent {
static get observedAttributes() {
return ['text', 'links'];
}
init() {
this.linksData = null;
this.render();
}
setLinks(links) {
this.linksData = links;
this.render();
}
render() {
const text = this.getAttr('text') || '';
const linksAttr = this.getAttr('links');
const links = this.linksData || (linksAttr ? JSON.parse(linksAttr) : null);
this.addClass('rant-content');
let images = [];
let youtubeLinks = [];
let otherLinks = [];
let renderedText = '';
if (links && links.length > 0) {
const categorized = categorizeLinks(links);
images = categorized.images.map(l => l.url);
youtubeLinks = categorized.youtube.map(l => l.url);
otherLinks = categorized.other;
renderedText = processTextWithLinks(text, links);
renderedText = renderedText.replace(/\n/g, '<br>');
} else {
images = extractImageUrls(text);
youtubeLinks = extractYoutubeUrls(text);
const extractedOther = extractNonMediaUrls(text);
otherLinks = extractedOther.map(url => ({ url, title: url }));
renderedText = this.escapeHtml(text).replace(/\n/g, '<br>');
}
let html = `<div class="content-text">${renderedText}</div>`;
if (images.length > 0) {
html += `
<div class="content-images">
${images.map(url => {
const safeUrl = sanitizeUrl(url);
return safeUrl ? `<image-preview src="${safeUrl}"></image-preview>` : '';
}).join('')}
</div>
`;
}
if (youtubeLinks.length > 0) {
html += `
<div class="content-videos">
${youtubeLinks.map(url => {
const safeUrl = sanitizeUrl(url);
return safeUrl ? `<youtube-embed url="${safeUrl}"></youtube-embed>` : '';
}).join('')}
</div>
`;
}
if (otherLinks.length > 0) {
html += `
<div class="content-links">
${otherLinks.slice(0, 3).map(link => {
const url = typeof link === 'string' ? link : link.url;
const title = typeof link === 'string' ? link : (link.title || link.url);
const safeUrl = sanitizeUrl(url);
return safeUrl ? `<link-preview url="${safeUrl}" title="${this.escapeAttr(title)}"></link-preview>` : '';
}).join('')}
</div>
`;
}
this.setHtml(html);
}
escapeHtml(str) {
if (!str) return '';
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
escapeAttr(str) {
if (!str) return '';
return str
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
onAttributeChanged(name, oldValue, newValue) {
this.render();
}
setText(text, links = null) {
this.linksData = links;
this.setAttr('text', text);
}
}
customElements.define('rant-content', RantContent);
export { RantContent };