User comments
All checks were successful
Rust / Test (push) Successful in 6m17s
Rust / Rustfmt (push) Successful in 21s
Rust / Clippy (push) Successful in 1m37s
Rust / Code coverage (push) Successful in 5m5s

This commit is contained in:
Alphonse Paix
2025-10-08 14:23:43 +02:00
parent 8a5605812c
commit ef9f860da2
21 changed files with 316 additions and 2961 deletions

View File

@@ -3,13 +3,20 @@
<div class="flex items-start justify-between">
<div class="flex-1 min-w-0">
<div class="mb-1">
<span class="text-sm font-medium text-gray-900">
{% if let Some(name) = comment.author %}
{{ name }}
{% else %}
Anonymous
{% endif %}
</span>
{% if let Some(user_id) = comment.user_id %}
<a href="/users/{{ comment.username.as_ref().unwrap() }}"
class="font-semibold text-blue-600 hover:text-blue-800 hover:underline">
{{ comment.username.as_ref().unwrap() }}
</a>
{% else %}
<span class="font-medium text-gray-600">
{% if let Some(name) = comment.author %}
{{ name }}
{% else %}
Anonymous
{% endif %}
</span>
{% endif %}
</div>
<p class="text-sm text-gray-700 mb-2 line-clamp-2">{{ comment.content }}</p>
<div class="flex items-center text-xs text-gray-500 mb-1">
@@ -17,8 +24,7 @@
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<time datetime="{{ comment.published_at }}">
{{ comment.formatted_date() }}
@@ -26,9 +32,8 @@
</div>
<div class="flex items-center text-xs text-gray-500">
<span class="mr-1">on</span>
<a href="/posts/{{ comment.post_id }}" class="text-blue-600 hover:underline truncate">
#{{ comment.post_id }}
</a>
<a href="/posts/{{ comment.post_id }}"
class="text-blue-600 hover:underline truncate">#{{ comment.post_id }}</a>
</div>
</div>
<button hx-delete="/admin/comments/{{ comment.comment_id }}"
@@ -40,9 +45,8 @@
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
</div>
</div>

View File

@@ -1,5 +1,5 @@
<div class="bg-white rounded-lg p-4 border border-gray-200">
<div class="flex items-start space-x-3">
<div class="flex items-center space-x-3">
<div class="flex-shrink-0">
<div class="w-10 h-10 bg-gray-200 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-gray-500"
@@ -11,20 +11,29 @@
</div>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center space-x-2 mb-1">
<span class="font-medium text-gray-900">
{% if let Some(user_id) = comment.user_id %}
<a href="/users/{{ comment.username.as_ref().unwrap() }}"
class="font-semibold text-blue-600 hover:text-blue-800 hover:underline">
{% if let Some(name) = comment.author %}
{{ name }}
{% else %}
{{ comment.username.as_ref().unwrap() }}
{% endif %}
</a>
{% else %}
<span class="font-medium text-gray-600">
{% if let Some(name) = comment.author %}
{{ name }}
{% else %}
Anonymous
{% endif %}
</span>
<span class="text-gray-400"></span>
<time class="text-sm text-gray-500" datetime="{{ comment.published_at }}">
{{ comment.formatted_date() }}
</time>
</div>
<p class="text-gray-700 whitespace-pre-line">{{ comment.content }}</p>
{% endif %}
<time class="block text-sm text-gray-500 mt-0.5"
datetime="{{ comment.published_at }}">
{{ comment.formatted_date() }}
</time>
</div>
</div>
<p class="text-gray-700 whitespace-pre-line mt-2">{{ comment.content }}</p>
</div>

View File

@@ -7,12 +7,27 @@
hx-target="#form-messages"
hx-swap="innerHTML"
class="space-y-4">
<input type="hidden" name="idempotency_key" value="{{ idempotency_key }}"/>
<input type="hidden" name="idempotency_key" value="{{ idempotency_key }}" />
{% if session_user_id.is_some() %}
<input type="hidden"
name="user_id"
value="{{ session_user_id.as_ref().unwrap() }}" />
{% endif %}
<div>
<input type="text"
name="author"
placeholder="Your name (optional)"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
{% if session_username.is_none() %}
<input type="text"
name="author"
placeholder="Your name (optional)"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
{% else %}
<input type="text"
name="author"
value="{{ session_username.as_ref().unwrap() }}"
readonly
disabled
class="w-full px-4 py-2 border border-gray-200 rounded-md bg-gray-50 text-gray-600 cursor-not-allowed">
<p class="text-sm text-gray-500 mt-1">You are authenticated.</p>
{% endif %}
</div>
<div>
<textarea name="content"
@@ -29,42 +44,41 @@
</form>
</div>
{% block comments %}
{% if comments.is_empty() %}
<div id="comments-list" class="text-center py-8 text-gray-500">
<svg class="w-12 h-12 mx-auto mb-3 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
</svg>
<p>No comments yet. Be the first to comment!</p>
</div>
{% else %}
{% let post_id = comments[0].post_id %}
<div id="comments-list" class="space-y-4">
{% for comment in comments %}
{% include "posts/comments/card.html" %}
{% endfor %}
<div id="load-more-comments" class="text-center mt-6">
{% if current_page < max_page %}
<div class="flex flex-col items-center space-y-6">
<button hx-get="/posts/{{ post_id }}/comments?page={{ current_page + 1 }}"
hx-target="#load-more-comments"
hx-swap="outerHTML"
hx-indicator="#comment-indicator"
class="text-center bg-gray-200 text-gray-700 hover:bg-gray-300 font-medium py-2 px-6 rounded-md transition-colors">
Load more comments
</button>
<span id="comment-indicator" class="htmx-indicator">
{% if comments.is_empty() %}
<div id="comments-list" class="text-center py-8 text-gray-500">
<svg class="w-12 h-12 mx-auto mb-3 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
<p>No comments yet. Be the first to comment!</p>
</div>
{% else %}
{% let post_id = comments[0].post_id %}
<div id="comments-list" class="space-y-4">
{% for comment in comments %}
{% include "posts/comments/card.html" %}
{% endfor %}
<div id="load-more-comments" class="text-center mt-6">
{% if current_page < max_page %}
<div class="flex flex-col items-center space-y-6">
<button hx-get="/posts/{{ post_id }}/comments?page={{ current_page + 1 }}"
hx-target="#load-more-comments"
hx-swap="outerHTML"
hx-indicator="#comment-indicator"
class="text-center bg-gray-200 text-gray-700 hover:bg-gray-300 font-medium py-2 px-6 rounded-md transition-colors">
Load more comments
</button>
<span id="comment-indicator" class="htmx-indicator">
{% call macros::spinner(class="text-gray-300 w-6 h-6", highlight_class="text-gray-700", size=24) %}
</span>
</div>
{% else %}
<p class="text-gray-600">No more comments. Check back later for more!</p>
</div>
{% else %}
<p class="text-gray-600">No more comments. Check back later for more!</p>
{% endif %}
</div>
</div>
{% endif %}
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% endblock %}

View File

@@ -31,7 +31,7 @@
</time>
</div>
</div>
{% if session_username.as_deref() == Some(post.author) %}
{% if session_user_id.as_ref() == Some(post.author_id) %}
<div class="mt-4 sm:mt-0">
<button onclick="document.getElementById('edit-form').classList.toggle('hidden')"
class="inline-flex items-center px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-md transition-colors">
@@ -47,7 +47,7 @@
{% endif %}
</div>
</header>
{% if session_username.as_deref() == Some(post.author) %}
{% if session_user_id.as_ref() == Some(post.author_id) %}
<div id="edit-form"
class="hidden bg-gray-50 border border-gray-200 rounded-lg p-6">
<h2 class="text-xl font-bold text-gray-900 mb-4">Edit post</h2>

View File

@@ -1,47 +1,47 @@
<div class="bg-white rounded-lg shadow-md border border-gray-200">
<h2 class="text-xl font-semibold text-gray-900 px-8 py-6 border-b border-gray-200">Activity</h2>
{% if posts.is_empty() %}
<div class="text-center text-gray-500 p-8">
<svg class="w-12 h-12 mx-auto mb-3 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
<p>No posts yet</p>
</div>
<div class="text-center text-gray-500 p-8">
<svg class="w-12 h-12 mx-auto mb-3 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<p>No posts yet</p>
</div>
{% else %}
<div class="divide-y divide-gray-200">
{% for post in posts %}
<a href="/posts/{{ post.post_id }}"
class="block py-4 hover:bg-gray-50 px-8 transition-colors group">
<div class="flex items-start justify-between">
<div class="flex-1 min-w-0">
<h3 class="text-base font-medium text-gray-900 group-hover:text-blue-600 transition-colors mb-1">{{
post.title }}</h3>
<div class="flex items-center text-sm text-gray-500">
<svg class="w-4 h-4 mr-1.5"
<div class="divide-y divide-gray-200">
{% for post in posts %}
<a href="/posts/{{ post.post_id }}"
class="block hover:bg-gray-50 px-8 py-5 transition-colors group">
<div class="flex items-start justify-between">
<div class="flex-1 min-w-0">
<h3 class="text-base font-medium text-gray-900 group-hover:text-blue-600 transition-colors mb-1">
{{
post.title }}
</h3>
<div class="flex items-center text-sm text-gray-500">
<svg class="w-4 h-4 mr-1.5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<time datetime="{{ post.published_at }}">
{{ post.formatted_date() }}
</time>
</div>
</div>
<svg class="w-5 h-5 text-gray-400 group-hover:text-blue-600 transition-colors flex-shrink-0 ml-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
<time datetime="{{ post.published_at }}">
{{ post.formatted_date() }}
</time>
</div>
</div>
<svg class="w-5 h-5 text-gray-400 group-hover:text-blue-600 transition-colors flex-shrink-0 ml-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>
</div>
</a>
{% endfor %}
</div>
</a>
{% endfor %}
</div>
{% endif %}
</div>