/** * email-comment.js — 客户端邮件评论加载器 * Fetch /comments/{post-id}.json,用 comment-renderer 的 HTML 结构渲染 */ class EmailCommentLoader { constructor() { const isEn = document.documentElement.lang === 'en'; this.labels = { title: isEn ? 'Email Comments' : '邮件评论', intro_prefix: isEn ? 'Join the discussion via email: ' : '通过邮件参与讨论:', send_aria: isEn ? 'Send a comment via email' : '通过邮件发送评论', empty: isEn ? 'No email comments yet.' : '还没有邮件评论。', reply: isEn ? 'Reply' : '回复', reply_aria: isEn ? "Reply to {name}'s comment" : '回复 {name} 的评论', expand: isEn ? 'Show more' : '展开', hint: isEn ? 'Send an email to the address above to comment. Your name will be shown publicly, but your email address will not.' : '发送邮件到上方地址即可评论。你的名字会公开显示,但邮箱地址不会。', loading: isEn ? 'Loading comments...' : '加载评论中…', error: isEn ? 'Failed to load comments' : '评论加载失败' }; this.FOLD_THRESHOLD = 300; this.init(); } init() { const containers = document.querySelectorAll('.email-comment-section[data-post-id]'); if (containers.length === 0) return; containers.forEach(container => this.loadComments(container)); } async loadComments(container) { const postId = container.getAttribute('data-post-id'); const blogDomain = container.getAttribute('data-blog-domain'); if (!postId) return; try { const response = await fetch(`/comments/${postId}.json`); if (!response.ok) { if (response.status === 404) { // 没有评论数据,渲染空状态 this.renderEmpty(container, postId, blogDomain); return; } throw new Error(`HTTP ${response.status}`); } const data = await response.json(); this.renderSection(container, data, postId, blogDomain); } catch (err) { console.error('[email-comment] Failed to load:', err); this.renderError(container, postId, blogDomain); } } renderSection(container, data, postId, blogDomain) { const emailAddr = `post-${postId}@${blogDomain}`; const tree = data.tree || []; const count = data.count || 0; let html = `
${this.labels.intro_prefix}${this.esc(emailAddr)}
`; if (tree.length === 0) { html += `${this.labels.empty}
`; } else { html += this.renderList(tree, 0, postId, blogDomain); } html += `${this.labels.hint}
`; container.innerHTML = html; container.classList.remove('email-comment-loading'); // 绑定折叠按钮事件 this.bindExpandButtons(container); } renderList(nodes, depth, postId, blogDomain) { if (!nodes || nodes.length === 0) return ''; let html = `${this.labels.intro_prefix}${this.esc(emailAddr)}
${this.labels.empty}
${this.labels.hint}
`; container.classList.remove('email-comment-loading'); } renderError(container, postId, blogDomain) { const emailAddr = `post-${postId}@${blogDomain}`; container.innerHTML = `${this.labels.intro_prefix}${this.esc(emailAddr)}
${this.labels.error}
${this.labels.hint}
`; container.classList.remove('email-comment-loading'); } bindExpandButtons(container) { container.querySelectorAll('.email-comment-expand').forEach(btn => { btn.addEventListener('click', () => { const content = btn.closest('.email-comment-content'); if (content) { content.classList.toggle('email-comment-folded'); content.classList.toggle('email-comment-expanded'); btn.style.display = 'none'; } }); }); } formatDate(dateStr) { try { return new Date(dateStr).toLocaleDateString('zh-CN'); } catch { return dateStr || ''; } } simpleHash(messageId) { if (!messageId) return '0'; let hash = 0; const str = messageId.replace(/[<>]/g, ''); for (let i = 0; i < str.length; i++) { hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0; } return Math.abs(hash).toString(36); } esc(str) { if (!str) return ''; return str .replace(/&/g, '&') .replace(/"/g, '"') .replace(//g, '>'); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => new EmailCommentLoader()); } else { new EmailCommentLoader(); }