Add themes, static assets, and logo
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
127
themes/vanilla-js/renderer.js
Normal file
127
themes/vanilla-js/renderer.js
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 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, '</p><p>')
|
||||
.replace(/\n/g, '<br>')
|
||||
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
||||
.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use
|
||||
export default VanillaRenderer;
|
||||
Reference in New Issue
Block a user