CommentThread
AppGeneric threaded comments with replies, like counts, delete-own actions, and a composer. Domain-agnostic — pass comments + handlers.
-
AB
-
Y
-
Y
-
MR
<%- include('modules/app/CommentThread', {
comments: comments,
currentUserId: 'me',
replyAction: '/comments/reply',
deleteAction: '/comments/delete'
}) %>
No comments yet. Be the first to comment.
<%- include('modules/app/CommentThread', {
comments: [],
currentUserId: 'me'
}) %>
<%
// Recursive partial. When called as a child (locals._isChild=true) it only
// renders the comment s — the wrapping +composer is skipped.
var _isChild = !!locals._isChild;
var _depth = (typeof locals._depth === 'number') ? locals._depth : 0;
var _comments = locals.comments || [];
var _currentUserId = locals.currentUserId || '';
var _maxDepth = (typeof locals.maxDepth === 'number') ? locals.maxDepth : 3;
var _replyAction = locals.replyAction || '#';
var _replyMethod = locals.replyMethod || 'post';
var _deleteAction = locals.deleteAction || '#';
var _emptyMessage = locals.emptyMessage || 'No comments yet. Be the first to comment.';
var _placeholder = locals.placeholder || 'Write a comment…';
var _showComposer = (locals.showComposer === undefined) ? true : !!locals.showComposer;
var _className = locals.className ? ' ' + locals.className : '';
function _initials(name) {
if (!name) return '?';
var parts = String(name).trim().split(/\s+/);
return (parts.slice(0,2).map(function(p){ return p[0]; }).join('') || '?').toUpperCase();
}
function _relative(value) {
var d = (value instanceof Date) ? value : new Date(value);
if (isNaN(d.getTime())) return '';
var diff = (Date.now() - d.getTime()) / 1000;
if (diff < 60) return Math.floor(diff) + 's';
if (diff < 3600) return Math.floor(diff / 60) + 'm';
if (diff < 86400) return Math.floor(diff / 3600) + 'h';
return d.toLocaleDateString();
}
function _iso(value) {
var d = (value instanceof Date) ? value : new Date(value);
return isNaN(d.getTime()) ? '' : d.toISOString();
}
%>
<% if (!_isChild) { %>
<% if (_showComposer) { %>
<% } %>
<% if (_comments.length === 0) { %>
<%= _emptyMessage %>
<% } else { %>
<% } %>
<% _comments.forEach(function(comment) {
var canReply = _depth < _maxDepth;
var isOwn = _currentUserId && comment.author && comment.author.id === _currentUserId;
%>
-
<% if (comment.author && comment.author.avatarUrl) { %>
<% } else { %>
<%= _initials(comment.author && comment.author.name) %>
<% } %>
<% if (canReply) { %>
<% } %>
<% if (isOwn) { %>
<% } %>
<% if (canReply) { %>
<% } %>
<% if (comment.replies && comment.replies.length > 0) { %>
<%- include('CommentThread', {
_isChild: true,
_depth: _depth + 1,
comments: comment.replies,
currentUserId: _currentUserId,
maxDepth: _maxDepth,
replyAction: _replyAction,
replyMethod: _replyMethod,
deleteAction: _deleteAction,
placeholder: _placeholder,
showComposer: false
}) %>
<% } %>
<% }); %>
<% if (!_isChild) { %>
<% } %>
<% } %>