327 lines
16 KiB
HTML
327 lines
16 KiB
HTML
{% extends "base.html" %}
|
|
{% block content %}
|
|
<div class="container py-4">
|
|
<div class="row justify-content-center">
|
|
<div class="col-md-8">
|
|
<!-- 文章內容卡片 -->
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<!-- 作者資訊 -->
|
|
<div class="d-flex align-items-center mb-4">
|
|
{% if post.author.avatar_path %}
|
|
<img src="{{ url_for('static', filename=post.author.avatar_path) }}"
|
|
class="rounded-circle me-3"
|
|
style="width: 50px; height: 50px; object-fit: cover;">
|
|
{% else %}
|
|
<div class="avatar-circle bg-primary text-white d-flex align-items-center justify-content-center me-3"
|
|
style="width: 50px; height: 50px; border-radius: 50%; font-size: 1.5rem;">
|
|
{{ post.author.username[0].upper() }}
|
|
</div>
|
|
{% endif %}
|
|
<div>
|
|
<h6 class="mb-0">{{ post.author.username }}</h6>
|
|
<small class="text-muted">
|
|
發布於 {{ post.created_at.strftime('%Y-%m-%d %H:%M') }}
|
|
{% if post.updated_at != post.created_at %}
|
|
• 更新於 {{ post.updated_at.strftime('%Y-%m-%d %H:%M') }}
|
|
{% endif %}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 文章標題和內容 -->
|
|
<h2 class="card-title">{{ post.title }}</h2>
|
|
<div class="card-text mb-3" style="white-space: pre-wrap;">
|
|
<p>{{ post.content | safe }}</p>
|
|
</div>
|
|
|
|
<!-- 按讚按鈕 -->
|
|
<div class="mb-4">
|
|
{% if current_user.is_authenticated %}
|
|
<button class="btn {% if post.is_liked_by(current_user) %}btn-primary{% else %}btn-outline-primary{% endif %} like-btn"
|
|
data-post-id="{{ post.id }}">
|
|
<i class="bi bi-heart-fill"></i>
|
|
<span class="like-count">{{ post.like_count }}</span>
|
|
</button>
|
|
{% else %}
|
|
<button class="btn btn-outline-primary" disabled>
|
|
<i class="bi bi-heart-fill"></i>
|
|
<span class="like-count">{{ post.like_count }}</span>
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- 文章操作按鈕 -->
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<a href="{{ url_for('post.index') }}" class="btn btn-outline-secondary">
|
|
返回列表
|
|
</a>
|
|
{% if current_user == post.author %}
|
|
<div class="btn-group">
|
|
<a href="{{ url_for('post.edit', id=post.id) }}" class="btn btn-outline-primary">
|
|
編輯
|
|
</a>
|
|
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal"
|
|
data-bs-target="#deleteModal">
|
|
刪除
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 留言區塊 -->
|
|
<div class="card shadow-sm mt-4">
|
|
<div class="card-header bg-white">
|
|
<h5 class="card-title mb-0">
|
|
留言區
|
|
<span class="badge bg-secondary">{{ post.comments_count }}</span>
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- 留言表單 -->
|
|
{% if current_user.is_authenticated %}
|
|
<form action="{{ url_for('post.create_comment', post_id=post.id) }}" method="post">
|
|
<div class="mb-3">
|
|
<textarea name="content" class="form-control" rows="3"
|
|
placeholder="寫下你的留言..." required></textarea>
|
|
</div>
|
|
<div class="text-end">
|
|
<button type="submit" class="btn btn-primary">發布留言</button>
|
|
</div>
|
|
</form>
|
|
{% else %}
|
|
<div class="alert alert-info">
|
|
請<a href="{{ url_for('auth.login') }}" class="alert-link">登入</a>後參與留言
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- 留言列表 -->
|
|
<div class="comments-list mt-4">
|
|
{% for comment in post.comments %}
|
|
{% if not comment.parent_id %}
|
|
<div class="comment-item mb-4">
|
|
<div class="d-flex">
|
|
{% if comment.author.avatar_path %}
|
|
<img src="{{ url_for('static', filename=comment.author.avatar_path) }}"
|
|
class="rounded-circle me-2"
|
|
style="width: 40px; height: 40px; object-fit: cover;">
|
|
{% else %}
|
|
<div class="avatar-circle bg-primary text-white d-flex align-items-center justify-content-center me-2"
|
|
style="width: 40px; height: 40px; border-radius: 50%; font-size: 1.2rem;">
|
|
{{ comment.author.username[0].upper() }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="flex-grow-1">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="mb-0">{{ comment.author.username }}</h6>
|
|
<small class="text-muted">
|
|
{{ comment.created_at.strftime('%Y-%m-%d %H:%M') }}
|
|
</small>
|
|
</div>
|
|
{% if current_user == comment.author %}
|
|
<button class="btn btn-sm btn-outline-danger delete-comment"
|
|
data-comment-id="{{ comment.id }}">
|
|
刪除
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
<div class="mt-2">
|
|
{{ comment.content|nl2br }}
|
|
</div>
|
|
|
|
<!-- 回覆按鈕 -->
|
|
{% if current_user.is_authenticated %}
|
|
<button class="btn btn-sm btn-link reply-btn"
|
|
data-comment-id="{{ comment.id }}">
|
|
回覆
|
|
</button>
|
|
{% endif %}
|
|
|
|
<!-- 回覆表單(預設隱藏) -->
|
|
<div class="reply-form mt-2" id="reply-form-{{ comment.id }}"
|
|
style="display: none;">
|
|
<form action="{{ url_for('post.create_comment', post_id=post.id) }}"
|
|
method="post">
|
|
<input type="hidden" name="parent_id" value="{{ comment.id }}">
|
|
<div class="mb-2">
|
|
<textarea name="content" class="form-control form-control-sm"
|
|
rows="2" placeholder="寫下你的回覆..."
|
|
required></textarea>
|
|
</div>
|
|
<div class="text-end">
|
|
<button type="button" class="btn btn-sm btn-link cancel-reply">取消
|
|
</button>
|
|
<button type="submit" class="btn btn-sm btn-primary">回覆</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- 顯示回覆 -->
|
|
{% if comment.replies.count() > 0 %}
|
|
<div class="replies ms-4 mt-3">
|
|
{% for reply in comment.replies %}
|
|
<div class="reply-item mb-3">
|
|
<div class="d-flex">
|
|
{% if reply.author.avatar_path %}
|
|
<img src="{{ url_for('static', filename=reply.author.avatar_path) }}"
|
|
class="rounded-circle me-2"
|
|
style="width: 32px; height: 32px; object-fit: cover;">
|
|
{% else %}
|
|
<div class="avatar-circle bg-primary text-white d-flex align-items-center justify-content-center me-2"
|
|
style="width: 32px; height: 32px; border-radius: 50%; font-size: 1rem;">
|
|
{{ reply.author.username[0].upper() }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="flex-grow-1">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="mb-0">{{ reply.author.username }}</h6>
|
|
<small class="text-muted">
|
|
{{ reply.created_at.strftime('%Y-%m-%d %H:%M') }}
|
|
</small>
|
|
</div>
|
|
{% if current_user == reply.author %}
|
|
<button class="btn btn-sm btn-outline-danger delete-comment"
|
|
data-comment-id="{{ reply.id }}">
|
|
刪除
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
<div class="mt-2">
|
|
{{ reply.content|nl2br }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% else %}
|
|
<div class="text-center text-muted py-4">
|
|
暫無留言
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 刪除文章確認對話框 -->
|
|
<div class="modal fade" id="deleteModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">確認刪除</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
確定要刪除這篇文章嗎?此操作無法復原。
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
|
<form action="{{ url_for('post.delete', id=post.id) }}" method="post" class="d-inline">
|
|
<button type="submit" class="btn btn-danger">確定刪除</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
// 按讚功能
|
|
document.querySelectorAll('.like-btn').forEach(button => {
|
|
button.addEventListener('click', async function () {
|
|
const postId = this.dataset.postId;
|
|
try {
|
|
const response = await fetch(`/posts/${postId}/like`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
});
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
// 更新按鈕狀態
|
|
this.classList.toggle('btn-primary', data.liked);
|
|
this.classList.toggle('btn-outline-primary', !data.liked);
|
|
|
|
// 更新按讚數
|
|
this.querySelector('.like-count').textContent = data.count;
|
|
} else {
|
|
alert(data.message || '操作失敗');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
alert('操作失敗');
|
|
}
|
|
});
|
|
});
|
|
|
|
// 刪除留言功能
|
|
document.querySelectorAll('.delete-comment').forEach(button => {
|
|
button.addEventListener('click', async function () {
|
|
if (!confirm('確定要刪除這條留言嗎?')) return;
|
|
|
|
const commentId = this.dataset.commentId;
|
|
try {
|
|
const response = await fetch(`/posts/comments/${commentId}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
});
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
this.closest('.comment-item, .reply-item').remove();
|
|
} else {
|
|
alert(data.message || '刪除失敗');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
alert('操作失敗');
|
|
}
|
|
});
|
|
});
|
|
|
|
// 回覆功能
|
|
document.querySelectorAll('.reply-btn').forEach(button => {
|
|
button.addEventListener('click', function () {
|
|
const commentId = this.dataset.commentId;
|
|
const replyForm = document.getElementById(`reply-form-${commentId}`);
|
|
|
|
// 隱藏其他所有回覆表單
|
|
document.querySelectorAll('.reply-form').forEach(form => {
|
|
if (form !== replyForm) {
|
|
form.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
// 切換當前回覆表單的顯示狀態
|
|
replyForm.style.display = replyForm.style.display === 'none' ? 'block' : 'none';
|
|
});
|
|
});
|
|
|
|
// 取消回覆
|
|
document.querySelectorAll('.cancel-reply').forEach(button => {
|
|
button.addEventListener('click', function () {
|
|
this.closest('.reply-form').style.display = 'none';
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|