- Display like count with thumbs-up emoji on post cards in index.html
- Add clickable like/unlike button with JS fetch on post_detail.html
- Add POST /web/posts/{slug}/like endpoint in web routes for cookie-auth users
- Guests redirected to /auth/dev-login on 401
- Use block extra_js (matching base template) for inline script
132 lines
6.1 KiB
HTML
132 lines
6.1 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ post.title }} - Blog{% endblock %}
|
|
{% block meta_description %}{{ post.content[:160] }}{% endblock %}
|
|
{% block meta_keywords %}{{ post.tags|join(', ') }}{% endblock %}
|
|
{% block meta_author %}{{ post.author_id }}{% endblock %}
|
|
|
|
{% block canonical_url %}{{ request.base_url }}web/posts/{{ post.slug }}{% endblock %}
|
|
|
|
{% block og_type %}article{% endblock %}
|
|
{% block og_url %}{{ request.base_url }}web/posts/{{ post.slug }}{% endblock %}
|
|
{% block og_title %}{{ post.title }}{% endblock %}
|
|
{% block og_description %}{{ post.content[:160] }}{% endblock %}
|
|
|
|
{% block twitter_title %}{{ post.title }}{% endblock %}
|
|
{% block twitter_description %}{{ post.content[:160] }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<article class="post-detail" data-testid="post-detail">
|
|
<header class="post-detail-header" data-testid="post-detail-header">
|
|
<h1 class="post-detail-title" data-testid="post-detail-title">{{ post.title }}</h1>
|
|
|
|
<div class="post-detail-meta" data-testid="post-detail-meta">
|
|
<span class="post-card-meta-item" data-testid="post-detail-author">
|
|
<span class="avatar avatar-sm" data-testid="post-detail-author-avatar">{{ post.author_id[0]|upper }}</span>
|
|
<span data-testid="post-detail-author-name">{{ post.author_id }}</span>
|
|
</span>
|
|
<span class="post-card-meta-item" data-testid="post-detail-date">
|
|
{{ post.created_at.strftime('%B %d, %Y') }}
|
|
</span>
|
|
{% if post.published %}
|
|
<span class="badge badge-success" data-testid="post-detail-status">{{ _('post.status_published', current_locale) }}</span>
|
|
{% else %}
|
|
<span class="badge" data-testid="post-detail-status">{{ _('post.status_draft', current_locale) }}</span>
|
|
{% endif %}
|
|
<span class="post-card-meta-item" data-testid="post-detail-like-count">
|
|
<button id="like-button" class="btn-like" data-testid="like-button"
|
|
data-post-slug="{{ post.slug }}"
|
|
data-liked="false">
|
|
👍 <span id="like-count">{{ post.like_count }}</span>
|
|
</button>
|
|
</span>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="post-detail-content markdown-body" data-testid="post-detail-content">
|
|
{{ post.content|markdown|safe }}
|
|
</div>
|
|
|
|
<footer class="post-detail-footer" data-testid="post-detail-footer">
|
|
<div class="post-detail-tags" data-testid="post-detail-tags">
|
|
{% for tag in post.tags %}
|
|
<span class="tag" data-testid="post-detail-tag-{{ loop.index }}">{{ tag }}</span>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<div class="divider" data-testid="post-detail-divider"></div>
|
|
|
|
<div class="flex justify-between items-center" data-testid="post-detail-actions">
|
|
<a href="/" class="btn" data-testid="btn-back-to-posts">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 0.5rem;">
|
|
<path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
{{ _('post.back_to_posts', current_locale) }}
|
|
</a>
|
|
|
|
{% if can_edit or can_delete %}
|
|
<div class="flex gap-2" data-testid="post-detail-edit-actions">
|
|
{% if can_edit %}
|
|
<a href="/web/posts/{{ post.slug }}/edit" class="btn" data-testid="btn-edit-post">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 0.5rem;">
|
|
<path d="M11 2L14 5M2 14L3 10L12 1L15 4L6 13L2 14Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
{{ _('post.edit', current_locale) }}
|
|
</a>
|
|
{% endif %}
|
|
{% if can_delete %}
|
|
<form action="/web/posts/{{ post.slug }}/delete" method="POST" style="display: inline;" data-testid="form-delete-post">
|
|
<button type="submit" class="btn btn-danger" data-testid="btn-delete-post" onclick="return confirm('{{ _('post.delete_confirm', current_locale) }}');">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 0.5rem;">
|
|
<path d="M2 4h12M6 4V2a2 2 0 012-2h0a2 2 0 012 2v2m3 0v10a2 2 0 01-2 2H5a2 2 0 01-2-2V4h9z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
{{ _('post.delete', current_locale) }}
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</footer>
|
|
</article>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script data-testid="like-script">
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
var likeButton = document.getElementById('like-button');
|
|
if (!likeButton) return;
|
|
|
|
likeButton.addEventListener('click', function() {
|
|
var slug = this.getAttribute('data-post-slug');
|
|
var countSpan = document.getElementById('like-count');
|
|
|
|
fetch('/web/posts/' + slug + '/like', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Accept': 'application/json'
|
|
}
|
|
})
|
|
.then(function(response) {
|
|
if (response.status === 401) {
|
|
window.location.href = '/auth/dev-login';
|
|
return;
|
|
}
|
|
if (!response.ok) {
|
|
throw new Error('Like request failed');
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(function(data) {
|
|
if (data && data.like_count !== undefined) {
|
|
countSpan.textContent = data.like_count;
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
console.error('Like error:', error);
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|