Forum / templates /forum /topic_view.html
kuro223's picture
o9
91073d4
{% extends "layout.html" %}
{% block title %}{{ topic.title }} - Community Forum{% endblock %}
{% block breadcrumb %}
<a href="{{ url_for('forum.index') }}" class="hover:text-blue-600">Home</a>
<span class="mx-2">/</span>
<a href="{{ url_for('forum.category_list') }}" class="hover:text-blue-600">Categories</a>
<span class="mx-2">/</span>
<a href="{{ url_for('forum.category_view', id=topic.category_id) }}" class="hover:text-blue-600">{{ topic.category.name }}</a>
<span class="mx-2">/</span>
<span>{{ topic.title }}</span>
{% endblock %}
{% block content %}
<div class="bg-white rounded-lg shadow overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200 flex flex-wrap justify-between items-center gap-2">
<div>
<h1 class="text-2xl font-bold text-gray-800">{{ topic.title }}</h1>
<div class="mt-1 flex flex-wrap items-center gap-2">
{% if topic.tags %}
{% for tag in topic.tags %}
<a href="{{ url_for('forum.tag_view', tag_name=tag.name) }}" class="tag">
{{ tag.name }}
</a>
{% endfor %}
{% endif %}
{% if topic.is_pinned %}
<span class="flex items-center text-xs text-yellow-600 bg-yellow-100 px-2 py-1 rounded-full">
<i data-feather="star" class="w-3 h-3 mr-1"></i> Pinned
</span>
{% endif %}
{% if topic.is_locked %}
<span class="flex items-center text-xs text-red-600 bg-red-100 px-2 py-1 rounded-full">
<i data-feather="lock" class="w-3 h-3 mr-1"></i> Locked
</span>
{% endif %}
</div>
</div>
<div class="flex flex-wrap gap-2">
{% if current_user.is_authenticated and current_user.is_moderator() %}
<form method="POST" action="{{ url_for('forum.pin_topic', id=topic.id) }}" class="inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" id="pin-topic-btn" data-is-pinned="{{ 'true' if topic.is_pinned else 'false' }}" class="px-3 py-1 bg-yellow-100 text-yellow-700 rounded hover:bg-yellow-200 focus:outline-none focus:ring">
{% if topic.is_pinned %}
<i data-feather="star-off" class="w-4 h-4 inline-block mr-1"></i> Unpin
{% else %}
<i data-feather="star" class="w-4 h-4 inline-block mr-1"></i> Pin
{% endif %}
</button>
</form>
<form method="POST" action="{{ url_for('forum.lock_topic', id=topic.id) }}" class="inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" id="lock-topic-btn" data-is-locked="{{ 'true' if topic.is_locked else 'false' }}" class="px-3 py-1 bg-gray-100 text-gray-700 rounded hover:bg-gray-200 focus:outline-none focus:ring">
{% if topic.is_locked %}
<i data-feather="unlock" class="w-4 h-4 inline-block mr-1"></i> Unlock
{% else %}
<i data-feather="lock" class="w-4 h-4 inline-block mr-1"></i> Lock
{% endif %}
</button>
</form>
{% endif %}
{% if current_user.is_authenticated %}
<button class="report-button px-3 py-1 bg-red-100 text-red-700 rounded hover:bg-red-200 focus:outline-none focus:ring" data-topic-id="{{ topic.id }}">
<i data-feather="flag" class="w-4 h-4 inline-block mr-1"></i> Report
</button>
{% endif %}
</div>
</div>
<!-- Posts -->
<div class="divide-y divide-gray-200">
{% for post in posts.items %}
<div id="post-{{ post.id }}" class="post flex flex-col md:flex-row p-0 md:divide-x divide-gray-200">
<!-- Author Info -->
<div class="w-full md:w-56 lg:w-64 p-4 md:p-6 bg-gray-50">
<div class="flex flex-row md:flex-col items-center md:items-start">
<div class="flex-shrink-0 mr-4 md:mr-0 md:mb-3">
<img
src="{{ url_for('static', filename='uploads/avatars/' + post.author.avatar) if post.author.avatar else url_for('static', filename='uploads/avatars/default.png') }}"
alt="{{ post.author.username }}"
class="w-12 h-12 md:w-16 md:h-16 rounded-full object-cover"
>
</div>
<div>
<div class="post-author">
<a href="{{ url_for('user.profile', username=post.author.username) }}" class="text-blue-600 font-medium hover:underline">
{{ post.author.username }}
</a>
</div>
<div class="text-gray-500 text-sm">
{% if post.author.role == 'admin' %}
<span class="inline-block bg-red-100 text-red-800 text-xs px-2 py-1 rounded-full">Admin</span>
{% elif post.author.role == 'moderator' %}
<span class="inline-block bg-green-100 text-green-800 text-xs px-2 py-1 rounded-full">Moderator</span>
{% else %}
<span class="inline-block bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full">Member</span>
{% endif %}
</div>
<div class="text-gray-500 text-xs mt-2 hidden md:block">
Joined: {{ post.author.created_at.strftime('%b %Y') }}
</div>
</div>
</div>
{% if post.author.signature and loop.index > 1 %}
<div class="mt-4 pt-4 border-t border-gray-200 text-xs text-gray-500 hidden md:block">
{{ post.author.signature }}
</div>
{% endif %}
</div>
<!-- Post Content -->
<div class="flex-1 p-4 md:p-6">
<div class="flex justify-between items-start mb-4">
<div class="text-sm text-gray-500">
<a href="#post-{{ post.id }}" class="hover:text-blue-600">
{{ post.created_at.strftime('%b %d, %Y %H:%M') }}
</a>
{% if post.updated_at and post.updated_at != post.created_at %}
<span class="text-xs ml-2">
(Edited {% if post.edited_by %}by {{ post.edited_by.username }}{% endif %}
on {{ post.updated_at.strftime('%b %d, %Y %H:%M') }})
</span>
{% endif %}
</div>
<div class="flex space-x-1">
<a href="{{ url_for('forum.quote_post', id=post.id) }}" class="quote-button p-1 text-gray-500 hover:text-blue-600 hover:bg-blue-50 rounded">
<i data-feather="message-square" class="w-4 h-4"></i>
<span class="sr-only">Quote</span>
</a>
{% if current_user.is_authenticated and (current_user.id == post.author_id or current_user.is_moderator()) %}
<a href="{{ url_for('forum.edit_post', id=post.id) }}" class="p-1 text-gray-500 hover:text-blue-600 hover:bg-blue-50 rounded">
<i data-feather="edit" class="w-4 h-4"></i>
<span class="sr-only">Edit</span>
</a>
<form method="POST" action="{{ url_for('forum.delete_post', id=post.id) }}" class="inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="delete-button p-1 text-gray-500 hover:text-red-600 hover:bg-red-50 rounded">
<i data-feather="trash-2" class="w-4 h-4"></i>
<span class="sr-only">Delete</span>
</button>
</form>
{% endif %}
{% if current_user.is_authenticated and current_user.id != post.author_id %}
<button class="report-button p-1 text-gray-500 hover:text-red-600 hover:bg-red-50 rounded" data-post-id="{{ post.id }}">
<i data-feather="flag" class="w-4 h-4"></i>
<span class="sr-only">Report</span>
</button>
{% endif %}
</div>
</div>
<div class="post-content prose max-w-none">
{{ post.content|safe }}
</div>
<!-- Reactions -->
{% if current_user.is_authenticated %}
<div class="mt-6 flex space-x-2">
<button class="reaction-btn {% if current_user.is_authenticated and post.reactions.filter_by(user_id=current_user.id, reaction_type='like').first() %}active{% endif %}" data-post-id="{{ post.id }}" data-reaction-type="like">
<i data-feather="thumbs-up" class="w-4 h-4"></i>
<span class="reaction-count {% if post.get_reaction_count('like') == 0 %}hidden{% endif %}">{{ post.get_reaction_count('like') }}</span>
</button>
<button class="reaction-btn {% if current_user.is_authenticated and post.reactions.filter_by(user_id=current_user.id, reaction_type='heart').first() %}active{% endif %}" data-post-id="{{ post.id }}" data-reaction-type="heart">
<i data-feather="heart" class="w-4 h-4"></i>
<span class="reaction-count {% if post.get_reaction_count('heart') == 0 %}hidden{% endif %}">{{ post.get_reaction_count('heart') }}</span>
</button>
<button class="reaction-btn {% if current_user.is_authenticated and post.reactions.filter_by(user_id=current_user.id, reaction_type='smile').first() %}active{% endif %}" data-post-id="{{ post.id }}" data-reaction-type="smile">
<i data-feather="smile" class="w-4 h-4"></i>
<span class="reaction-count {% if post.get_reaction_count('smile') == 0 %}hidden{% endif %}">{{ post.get_reaction_count('smile') }}</span>
</button>
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
<!-- Pagination -->
{% if posts.pages > 1 %}
<div class="px-6 py-4 bg-gray-50 border-t border-gray-200">
<div class="flex justify-center">
<nav class="inline-flex rounded-md shadow">
{% if posts.has_prev %}
<a href="{{ url_for('forum.topic_view', id=topic.id, page=posts.prev_num) }}" class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-l-md hover:bg-gray-50">
Previous
</a>
{% else %}
<span class="px-4 py-2 text-sm font-medium text-gray-400 bg-gray-100 border border-gray-300 rounded-l-md cursor-not-allowed">
Previous
</span>
{% endif %}
{% for page_num in posts.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %}
{% if page_num %}
{% if page_num == posts.page %}
<span class="px-4 py-2 text-sm font-medium text-blue-600 bg-blue-50 border border-gray-300">
{{ page_num }}
</span>
{% else %}
<a href="{{ url_for('forum.topic_view', id=topic.id, page=page_num) }}" class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 hover:bg-gray-50">
{{ page_num }}
</a>
{% endif %}
{% else %}
<span class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300">
</span>
{% endif %}
{% endfor %}
{% if posts.has_next %}
<a href="{{ url_for('forum.topic_view', id=topic.id, page=posts.next_num) }}" class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-r-md hover:bg-gray-50">
Next
</a>
{% else %}
<span class="px-4 py-2 text-sm font-medium text-gray-400 bg-gray-100 border border-gray-300 rounded-r-md cursor-not-allowed">
Next
</span>
{% endif %}
</nav>
</div>
</div>
{% endif %}
<!-- Reply Form -->
{% if current_user.is_authenticated and not topic.is_locked or (current_user.is_authenticated and current_user.is_moderator()) %}
<div id="reply-form" class="p-6 bg-gray-50 border-t border-gray-200">
<h3 class="text-lg font-medium text-gray-800 mb-4">Post a Reply</h3>
<form method="POST" action="{{ url_for('forum.topic_view', id=topic.id) }}">
{{ post_form.hidden_tag() }}
{{ post_form.topic_id(value=topic.id) }}
<div class="editor-container mb-4">
<div class="editor-toolbar bg-white border border-gray-300">
<!-- Buttons will be added by the JavaScript -->
</div>
{{ post_form.content(class="w-full px-4 py-2 border border-gray-300 editor-textarea focus:outline-none focus:border-blue-500 focus:ring focus:ring-blue-200", id="content", rows="6") }}
{% if post_form.content.errors %}
<div class="text-red-600 text-sm mt-1">
{% for error in post_form.content.errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
</div>
<div class="mt-4">
<button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors">
Post Reply
</button>
</div>
</form>
</div>
{% elif current_user.is_authenticated and topic.is_locked %}
<div class="p-6 bg-gray-50 border-t border-gray-200">
<div class="flex items-center justify-center p-4 bg-red-50 text-red-700 rounded-md">
<i data-feather="lock" class="w-5 h-5 mr-2"></i>
<span>This topic is locked. New replies are not allowed.</span>
</div>
</div>
{% elif not current_user.is_authenticated %}
<div class="p-6 bg-gray-50 border-t border-gray-200">
<div class="flex items-center justify-center p-4 bg-blue-50 text-blue-700 rounded-md">
<i data-feather="info" class="w-5 h-5 mr-2"></i>
<span>Please <a href="{{ url_for('auth.login') }}" class="underline font-medium">log in</a> to post a reply.</span>
</div>
</div>
{% endif %}
</div>
<!-- Report Modal Dialog -->
{% if current_user.is_authenticated %}
<div id="report-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md">
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="text-lg font-medium text-gray-800">Report Content</h3>
<button type="button" class="close-modal text-gray-400 hover:text-gray-500">
<i data-feather="x" class="w-5 h-5"></i>
<span class="sr-only">Close</span>
</button>
</div>
<form id="report-form" method="POST" action="{{ url_for('forum.create_report') }}">
{{ report_form.hidden_tag() }}
{{ report_form.post_id(id="post_id") }}
{{ report_form.topic_id(id="topic_id") }}
<div class="p-6">
<div class="mb-4">
<label for="reason" class="block text-gray-700 font-medium mb-2">Reason for Report</label>
{{ report_form.reason(class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:border-blue-500 focus:ring focus:ring-blue-200", id="reason", rows="4", placeholder="Please provide a detailed explanation of why you're reporting this content...") }}
{% if report_form.reason.errors %}
<div class="text-red-600 text-sm mt-1">
{% for error in report_form.reason.errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
</div>
<div class="mt-4 flex justify-end">
<button type="button" class="close-modal px-4 py-2 bg-gray-200 text-gray-800 rounded-md mr-2 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition-colors">
Cancel
</button>
<button type="submit" class="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition-colors">
Submit Report
</button>
</div>
</div>
</form>
</div>
</div>
{% endif %}
{% endblock %}
{% block extra_js %}
<script src="{{ url_for('static', filename='js/editor.js') }}"></script>
{% endblock %}