/** * Vanilla JS Template Renderer * Renders posts using HTML template literals */ class VanillaRenderer { constructor() { this.templates = new Map(); this.formatters = { formatTime: this.formatTime.bind(this), formatTimeAgo: this.formatTimeAgo.bind(this), formatDateTime: this.formatDateTime.bind(this), truncate: this.truncate.bind(this), renderMarkdown: this.renderMarkdown.bind(this) }; } /** * Load a template from HTML file */ async loadTemplate(templateId, templatePath) { const response = await fetch(templatePath); const html = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const template = doc.querySelector('template'); if (template) { this.templates.set(templateId, template.innerHTML); } } /** * Render a post using a template */ render(templateId, postData) { const template = this.templates.get(templateId); if (!template) { throw new Error(`Template ${templateId} not loaded`); } // Create context with data and helper functions const context = { ...postData, ...this.formatters }; // Use Function constructor to evaluate template literal const rendered = new Function(...Object.keys(context), `return \`${template}\`;`)(...Object.values(context)); return rendered; } /** * Render multiple posts */ renderBatch(templateId, posts, container) { const fragment = document.createDocumentFragment(); posts.forEach(post => { const html = this.render(templateId, post); const temp = document.createElement('div'); temp.innerHTML = html; fragment.appendChild(temp.firstElementChild); }); container.appendChild(fragment); } // Helper functions available in templates formatTime(timestamp) { const date = new Date(timestamp * 1000); return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); } formatTimeAgo(timestamp) { const seconds = Math.floor(Date.now() / 1000 - timestamp); const intervals = { year: 31536000, month: 2592000, week: 604800, day: 86400, hour: 3600, minute: 60 }; for (const [unit, secondsInUnit] of Object.entries(intervals)) { const interval = Math.floor(seconds / secondsInUnit); if (interval >= 1) { return `${interval} ${unit}${interval !== 1 ? 's' : ''} ago`; } } return 'just now'; } formatDateTime(timestamp) { const date = new Date(timestamp * 1000); return date.toLocaleString('en-US', { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } truncate(text, maxLength) { if (text.length <= maxLength) return text; return text.substring(0, maxLength).trim() + '...'; } renderMarkdown(text) { // Basic markdown rendering (expand as needed) return text .replace(/\n\n/g, '
')
.replace(/\n/g, '
')
.replace(/\*\*(.*?)\*\*/g, '$1')
.replace(/\*(.*?)\*/g, '$1')
.replace(/\[(.*?)\]\((.*?)\)/g, '$1');
}
}
// Export for use
export default VanillaRenderer;