Fix comment tree display (Issue #10)
- Add build_comment_tree() to organize comments hierarchically - Create recursive Jinja macro to render nested comments - Add visual styling with left border and indentation - Comments now display as threaded tree structure Fixes #10
This commit is contained in:
51
app.py
51
app.py
@@ -575,35 +575,62 @@ def api_content_timestamp():
|
|||||||
return jsonify({'error': 'Failed to get content timestamp'}), 500
|
return jsonify({'error': 'Failed to get content timestamp'}), 500
|
||||||
|
|
||||||
|
|
||||||
|
def build_comment_tree(comments):
|
||||||
|
"""Build a hierarchical comment tree from flat comment list"""
|
||||||
|
# Create lookup dict by UUID
|
||||||
|
comment_dict = {c['uuid']: {**c, 'replies': []} for c in comments}
|
||||||
|
|
||||||
|
# Build tree structure
|
||||||
|
root_comments = []
|
||||||
|
for comment in comments:
|
||||||
|
parent_uuid = comment.get('parent_comment_uuid')
|
||||||
|
if parent_uuid and parent_uuid in comment_dict:
|
||||||
|
# Add as reply to parent
|
||||||
|
comment_dict[parent_uuid]['replies'].append(comment_dict[comment['uuid']])
|
||||||
|
else:
|
||||||
|
# Top-level comment
|
||||||
|
root_comments.append(comment_dict[comment['uuid']])
|
||||||
|
|
||||||
|
# Sort at each level by timestamp
|
||||||
|
def sort_tree(comments_list):
|
||||||
|
comments_list.sort(key=lambda x: x.get('timestamp', 0))
|
||||||
|
for comment in comments_list:
|
||||||
|
if comment.get('replies'):
|
||||||
|
sort_tree(comment['replies'])
|
||||||
|
|
||||||
|
sort_tree(root_comments)
|
||||||
|
return root_comments
|
||||||
|
|
||||||
|
|
||||||
@app.route('/post/<post_id>')
|
@app.route('/post/<post_id>')
|
||||||
def post_detail(post_id):
|
def post_detail(post_id):
|
||||||
"""Serve individual post detail page with modern theme"""
|
"""Serve individual post detail page with modern theme"""
|
||||||
try:
|
try:
|
||||||
# Load platform configuration
|
# Load platform configuration
|
||||||
platform_config = load_platform_config()
|
platform_config = load_platform_config()
|
||||||
|
|
||||||
# Use cached data for better performance
|
# Use cached data for better performance
|
||||||
cached_posts, cached_comments = _load_posts_cache()
|
cached_posts, cached_comments = _load_posts_cache()
|
||||||
|
|
||||||
# Get post data from cache
|
# Get post data from cache
|
||||||
post_data = cached_posts.get(post_id)
|
post_data = cached_posts.get(post_id)
|
||||||
if not post_data:
|
if not post_data:
|
||||||
return render_template('404.html'), 404
|
return render_template('404.html'), 404
|
||||||
|
|
||||||
# Add source display name
|
# Add source display name
|
||||||
post_data['source_display'] = get_display_name_for_source(
|
post_data['source_display'] = get_display_name_for_source(
|
||||||
post_data.get('platform', ''),
|
post_data.get('platform', ''),
|
||||||
post_data.get('source', ''),
|
post_data.get('source', ''),
|
||||||
platform_config
|
platform_config
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get comments from cache
|
|
||||||
comments = cached_comments.get(post_id, [])
|
|
||||||
logger.info(f"Loading post {post_id}: found {len(comments)} comments")
|
|
||||||
|
|
||||||
# Sort comments by timestamp
|
# Get comments from cache
|
||||||
comments.sort(key=lambda x: x.get('timestamp', 0))
|
comments_flat = cached_comments.get(post_id, [])
|
||||||
|
logger.info(f"Loading post {post_id}: found {len(comments_flat)} comments")
|
||||||
|
|
||||||
|
# Build comment tree
|
||||||
|
comments = build_comment_tree(comments_flat)
|
||||||
|
|
||||||
# Load user settings if authenticated
|
# Load user settings if authenticated
|
||||||
user_settings = {}
|
user_settings = {}
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
@@ -611,9 +638,9 @@ def post_detail(post_id):
|
|||||||
user_settings = json.loads(current_user.settings) if current_user.settings else {}
|
user_settings = json.loads(current_user.settings) if current_user.settings else {}
|
||||||
except:
|
except:
|
||||||
user_settings = {}
|
user_settings = {}
|
||||||
|
|
||||||
return render_template('post_detail.html', post=post_data, comments=comments, user_settings=user_settings)
|
return render_template('post_detail.html', post=post_data, comments=comments, user_settings=user_settings)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading post {post_id}: {e}")
|
print(f"Error loading post {post_id}: {e}")
|
||||||
return render_template('404.html'), 404
|
return render_template('404.html'), 404
|
||||||
|
|||||||
@@ -135,25 +135,37 @@
|
|||||||
<!-- Comments Section -->
|
<!-- Comments Section -->
|
||||||
<section class="comments-section">
|
<section class="comments-section">
|
||||||
<h2>Comments ({{ comments|length }})</h2>
|
<h2>Comments ({{ comments|length }})</h2>
|
||||||
|
|
||||||
|
{% macro render_comment(comment, depth=0) %}
|
||||||
|
<div class="comment" style="margin-left: {{ depth * 24 }}px;">
|
||||||
|
<div class="comment-header">
|
||||||
|
<span class="comment-author">{{ comment.author }}</span>
|
||||||
|
<span class="comment-separator">•</span>
|
||||||
|
<span class="comment-time">{{ moment(comment.timestamp).fromNow() if moment else 'Recently' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="comment-content">
|
||||||
|
{{ comment.content | safe | nl2br }}
|
||||||
|
</div>
|
||||||
|
<div class="comment-footer">
|
||||||
|
<div class="comment-score">
|
||||||
|
<span>▲ {{ comment.score or 0 }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if comment.replies %}
|
||||||
|
<div class="comment-replies">
|
||||||
|
{% for reply in comment.replies %}
|
||||||
|
{{ render_comment(reply, depth + 1) }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% if comments %}
|
{% if comments %}
|
||||||
<div class="comments-list">
|
<div class="comments-list">
|
||||||
{% for comment in comments %}
|
{% for comment in comments %}
|
||||||
<div class="comment">
|
{{ render_comment(comment) }}
|
||||||
<div class="comment-header">
|
|
||||||
<span class="comment-author">{{ comment.author }}</span>
|
|
||||||
<span class="comment-separator">•</span>
|
|
||||||
<span class="comment-time">{{ moment(comment.timestamp).fromNow() if moment else 'Recently' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="comment-content">
|
|
||||||
{{ comment.content | safe | nl2br }}
|
|
||||||
</div>
|
|
||||||
<div class="comment-footer">
|
|
||||||
<div class="comment-score">
|
|
||||||
<span>▲ {{ comment.score or 0 }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -554,12 +566,24 @@
|
|||||||
.comment {
|
.comment {
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
border-bottom: 1px solid #f1f5f9;
|
border-bottom: 1px solid #f1f5f9;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment:last-child {
|
.comment:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Threaded comment styling */
|
||||||
|
.comment[style*="margin-left"] {
|
||||||
|
padding-left: 16px;
|
||||||
|
border-left: 2px solid #e2e8f0;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-replies {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.comment-header {
|
.comment-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user