part2
This commit is contained in:
parent
f93c384c2c
commit
98880c0fde
67
template/edit_group_post.html
Normal file
67
template/edit_group_post.html
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Edit Post in {{ group.name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="mb-4">Edit Post in {{ group.name }}</h1>
|
||||||
|
<form method="POST" id="editGroupPostForm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="content" class="form-label">Content</label>
|
||||||
|
<textarea class="form-control" id="content" name="content" rows="10">{{ post.original_content }}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<button type="submit" class="btn btn-primary">Update Post</button>
|
||||||
|
<a href="{{ url_for('group_detail', vanity_url=group.vanity_url) }}" class="btn btn-secondary">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="https://unpkg.com/easymde/dist/easymde.min.js"></script>
|
||||||
|
<script>
|
||||||
|
var easyMDE = new EasyMDE({
|
||||||
|
element: document.getElementById('content'),
|
||||||
|
spellChecker: false,
|
||||||
|
autofocus: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
toolbar: [
|
||||||
|
"bold", "italic", "heading", "quote", "unordered-list", "ordered-list",
|
||||||
|
"link", "code", "table", "undo", "redo"
|
||||||
|
],
|
||||||
|
status: false
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('editGroupPostForm').addEventListener('submit', function(e) {
|
||||||
|
document.getElementById('content').value = easyMDE.value();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
{{ super() }}
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/easymde/dist/easymde.min.css">
|
||||||
|
<style>
|
||||||
|
.EasyMDEContainer {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
.EasyMDEContainer .CodeMirror {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
.editor-toolbar {
|
||||||
|
background-color: #444;
|
||||||
|
border-color: #555;
|
||||||
|
}
|
||||||
|
.editor-toolbar button {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.editor-toolbar button:hover,
|
||||||
|
.editor-toolbar button.active {
|
||||||
|
background-color: #555;
|
||||||
|
}
|
||||||
|
.CodeMirror-cursor {
|
||||||
|
border-left: 1px solid #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
123
template/edit_post.html
Normal file
123
template/edit_post.html
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Edit Post{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="mb-4">Edit Post</h1>
|
||||||
|
<form method="POST" id="editPostForm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="content" class="form-label">Content</label>
|
||||||
|
<textarea class="form-control" id="content" name="content" rows="10"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<button type="submit" class="btn btn-primary">Update Post</button>
|
||||||
|
<button type="submit" name="delete" value="1" class="btn btn-danger" onclick="return confirm('Are you sure you want to delete this post?');">Delete Post</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
{{ super() }}
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/easymde/dist/easymde.min.css">
|
||||||
|
<style>
|
||||||
|
.EasyMDEContainer {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
.EasyMDEContainer .CodeMirror {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
.editor-toolbar {
|
||||||
|
background-color: #444;
|
||||||
|
border-color: #555;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.editor-toolbar button {
|
||||||
|
color: #fff !important;
|
||||||
|
margin: 0 1px;
|
||||||
|
}
|
||||||
|
.editor-toolbar button:hover,
|
||||||
|
.editor-toolbar button.active {
|
||||||
|
background-color: #555;
|
||||||
|
}
|
||||||
|
.CodeMirror-cursor {
|
||||||
|
border-left: 1px solid #fff;
|
||||||
|
}
|
||||||
|
.separator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="https://unpkg.com/easymde/dist/easymde.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
var easyMDE = new EasyMDE({
|
||||||
|
element: document.getElementById('content'),
|
||||||
|
spellChecker: false,
|
||||||
|
autofocus: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
toolbar: [
|
||||||
|
"bold", "italic", "heading", "quote", "unordered-list", "ordered-list",
|
||||||
|
"link", "code", "table", "undo", "redo",
|
||||||
|
{
|
||||||
|
name: "custom-h1",
|
||||||
|
action: function(editor){
|
||||||
|
var cm = editor.codemirror;
|
||||||
|
var output = '';
|
||||||
|
var selectedText = cm.getSelection();
|
||||||
|
output = '# ' + selectedText;
|
||||||
|
cm.replaceSelection(output);
|
||||||
|
},
|
||||||
|
className: "fa fa-header",
|
||||||
|
title: "Custom H1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom-h2",
|
||||||
|
action: function(editor){
|
||||||
|
var cm = editor.codemirror;
|
||||||
|
var output = '';
|
||||||
|
var selectedText = cm.getSelection();
|
||||||
|
output = '## ' + selectedText;
|
||||||
|
cm.replaceSelection(output);
|
||||||
|
},
|
||||||
|
className: "fa fa-header",
|
||||||
|
title: "Custom H2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom-p",
|
||||||
|
action: function(editor){
|
||||||
|
var cm = editor.codemirror;
|
||||||
|
var output = '';
|
||||||
|
var selectedText = cm.getSelection();
|
||||||
|
var color = prompt("Enter color (e.g., red, #ff0000):");
|
||||||
|
if (color) {
|
||||||
|
output = '<p style="color: ' + color + ';">' + selectedText + '</p>';
|
||||||
|
cm.replaceSelection(output);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
className: "fa fa-paragraph",
|
||||||
|
title: "Custom Paragraph",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
status: false,
|
||||||
|
initialValue: {{ post.original_content|tojson|safe }},
|
||||||
|
inputStyle: 'textarea'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Force mobile keyboard to show up
|
||||||
|
easyMDE.codemirror.getInputField().addEventListener('touchstart', function(e) {
|
||||||
|
e.target.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('editPostForm').addEventListener('submit', function(e) {
|
||||||
|
document.getElementById('content').value = easyMDE.value();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
511
template/group_detail.html
Normal file
511
template/group_detail.html
Normal file
@ -0,0 +1,511 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ group.name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<!-- Left sidebar -->
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
{% if group.image_url %}
|
||||||
|
<img src="{{ url_for('static', filename='uploads/' + group.image_url) }}" class="rounded-circle img-fluid mb-3" style="width: 150px; height: 150px; object-fit: cover;" alt="{{ group.name }}">
|
||||||
|
{% else %}
|
||||||
|
<div class="rounded-circle bg-primary d-flex align-items-center justify-content-center mb-3 mx-auto" style="width: 150px; height: 150px;">
|
||||||
|
<span class="text-white" style="font-size: 4rem;">{{ group.name[0].upper() }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<h3>{{ group.name }}</h3>
|
||||||
|
<p>{{ group.description }}</p>
|
||||||
|
{% if current_user.is_authenticated and current_user not in group.members %}
|
||||||
|
{% if group.visibility == 'public' or group.visibility == 'registered_only' %}
|
||||||
|
<form action="{{ url_for('join_group', group_id=group.id) }}" method="POST">
|
||||||
|
<button type="submit" class="btn btn-primary">Join Group</button>
|
||||||
|
</form>
|
||||||
|
{% elif group.visibility == 'invite_only' and group.allow_requests %}
|
||||||
|
<form action="{{ url_for('join_group', group_id=group.id) }}" method="POST">
|
||||||
|
<button type="submit" class="btn btn-primary">Request to Join</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="nav nav-pills flex-column mb-3">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" data-bs-toggle="pill" href="#posts">Posts</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" data-bs-toggle="pill" href="#users">Users</a>
|
||||||
|
</li>
|
||||||
|
{% if current_user.is_authenticated and current_user in group.members %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" data-bs-toggle="pill" href="#settings">Settings</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_user == group.creator %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" data-bs-toggle="pill" href="#group-settings">Group Settings</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main content area -->
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane fade show active" id="posts">
|
||||||
|
{% if current_user.is_authenticated and current_user in group.members %}
|
||||||
|
<button class="btn btn-primary mb-3" data-bs-toggle="modal" data-bs-target="#newPostModal">New Post</button>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Add sorting options -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="sort-select" class="form-label">Sort by:</label>
|
||||||
|
<select id="sort-select" class="form-select">
|
||||||
|
<option value="newest">Newest</option>
|
||||||
|
<option value="oldest">Oldest</option>
|
||||||
|
<option value="most_liked">Most Liked</option>
|
||||||
|
<option value="most_commented">Most Commented</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Wrap posts in a container for easy updating -->
|
||||||
|
<div id="posts-container">
|
||||||
|
{% for post in posts %}
|
||||||
|
<div class="card mb-3" id="post-{{ post.id }}">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center mb-2">
|
||||||
|
{% if post.author %}
|
||||||
|
{% if post.author.profile_picture %}
|
||||||
|
<img src="{{ url_for('static', filename='uploads/' + post.author.profile_picture) }}" class="rounded-circle me-2" alt="Profile Picture" style="width: 30px; height: 30px; object-fit: cover;">
|
||||||
|
{% else %}
|
||||||
|
<div class="rounded-circle me-2 d-flex justify-content-center align-items-center bg-primary" style="width: 30px; height: 30px;">
|
||||||
|
<span class="text-white" style="font-size: 0.8rem;">{{ post.author.username[0].upper() }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<small class="text-muted">
|
||||||
|
<a href="{{ url_for('profile', username=post.author.username) }}" class="text-decoration-none">{{ post.author.username }}</a>
|
||||||
|
</small>
|
||||||
|
{% else %}
|
||||||
|
<small class="text-muted">{{ post.anonymous_username }}</small>
|
||||||
|
{% endif %}
|
||||||
|
<span class="ms-auto text-muted">{{ post.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="card-text">{{ post.content|safe }}</p>
|
||||||
|
{% if post.image_url %}
|
||||||
|
<img src="{{ url_for('static', filename='uploads/' + post.image_url) }}" class="img-fluid mb-2 post-image" alt="Post image" data-bs-toggle="modal" data-bs-target="#imageModal{{ post.id }}">
|
||||||
|
{% endif %}
|
||||||
|
<div class="d-flex flex-wrap justify-content-between align-items-center">
|
||||||
|
<div class="btn-group mb-2">
|
||||||
|
<button class="btn btn-outline-primary btn-sm like-btn" data-post-id="{{ post.id }}">
|
||||||
|
<i class="bi bi-heart-fill"></i> Like (<span class="like-count">{{ post.likes|length }}</span>)
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-secondary btn-sm comment-btn" data-post-id="{{ post.id }}">
|
||||||
|
<i class="bi bi-chat-fill"></i> Comment ({{ post.comments|length }})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group mb-2">
|
||||||
|
<a href="{{ url_for('post_detail', post_id=post.id) }}" class="btn btn-outline-info btn-sm" target="_blank">
|
||||||
|
<i class="bi bi-box-arrow-up-right"></i> Open in new tab
|
||||||
|
</a>
|
||||||
|
<button class="btn btn-outline-info btn-sm copy-link-btn" data-post-url="{{ url_for('post_detail', post_id=post.id, _external=True, _scheme='https') }}">
|
||||||
|
<i class="bi bi-link-45deg"></i> Copy Link
|
||||||
|
</button>
|
||||||
|
{% if current_user.is_authenticated and current_user.id == post.author.id %}
|
||||||
|
<a href="{{ url_for('edit_post', post_id=post.id) }}" class="btn btn-outline-warning btn-sm">
|
||||||
|
<i class="bi bi-pencil-fill"></i> Edit
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="comment-section" id="comment-section-{{ post.id }}" style="display: none;">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="mb-3">Comments:</h6>
|
||||||
|
<ul class="list-unstyled mb-3">
|
||||||
|
{% for comment in post.comments %}
|
||||||
|
{% include 'comment.html' %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<form class="comment-form" data-post-id="{{ post.id }}">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" placeholder="Add a comment" name="content" required>
|
||||||
|
<button class="btn btn-outline-secondary" type="submit">Send</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade" id="users">
|
||||||
|
<h3>Group Members</h3>
|
||||||
|
<ul class="list-group">
|
||||||
|
{% for member in group.members %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<a href="{{ url_for('profile', username=member.username) }}">{{ member.username }}</a>
|
||||||
|
{% if member == group.creator %}
|
||||||
|
<span class="badge bg-primary">Creator</span>
|
||||||
|
{% elif current_user == group.creator %}
|
||||||
|
<form action="{{ url_for('remove_group_member', group_id=group.id, user_id=member.id) }}" method="POST" class="d-inline">
|
||||||
|
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure you want to remove this member?');">Remove</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if current_user.is_authenticated and current_user in group.members %}
|
||||||
|
<div class="tab-pane fade" id="settings">
|
||||||
|
<h3>User Settings</h3>
|
||||||
|
<form action="{{ url_for('update_user_group_settings', group_id=group.id) }}" method="POST">
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" id="showInHomepage" name="show_in_homepage" {% if current_user.show_group_posts_in_homepage(group) %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="showInHomepage">
|
||||||
|
Show this group's posts in homepage
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Save Settings</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% if current_user != group.creator %}
|
||||||
|
<hr>
|
||||||
|
<h4>Leave Group</h4>
|
||||||
|
<form action="{{ url_for('leave_group', group_id=group.id) }}" method="POST">
|
||||||
|
<button type="submit" class="btn btn-danger" onclick="return confirm('Are you sure you want to leave this group?');">Leave Group</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if current_user == group.creator %}
|
||||||
|
<div class="tab-pane fade" id="group-settings">
|
||||||
|
<h3>Group Settings</h3>
|
||||||
|
<form action="{{ url_for('update_group_settings', group_id=group.id) }}" method="POST" enctype="multipart/form-data">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label">Group Name</label>
|
||||||
|
<input type="text" class="form-control" id="name" name="name" value="{{ group.name }}" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="description" class="form-label">Description</label>
|
||||||
|
<textarea class="form-control" id="description" name="description" rows="3">{{ group.description }}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="vanity_url" class="form-label">Vanity URL</label>
|
||||||
|
<input type="text" class="form-control" id="vanity_url" name="vanity_url" value="{{ group.vanity_url }}" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="visibility" class="form-label">Visibility</label>
|
||||||
|
<select class="form-select" id="visibility" name="visibility" required>
|
||||||
|
<option value="public" {% if group.visibility == 'public' %}selected{% endif %}>Public</option>
|
||||||
|
<option value="registered_only" {% if group.visibility == 'registered_only' %}selected{% endif %}>Registered Only</option>
|
||||||
|
<option value="invite_only" {% if group.visibility == 'invite_only' %}selected{% endif %}>Invite Only</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 form-check" id="allow_requests_div" {% if group.visibility != 'invite_only' %}style="display: none;"{% endif %}>
|
||||||
|
<input type="checkbox" class="form-check-input" id="allow_requests" name="allow_requests" {% if group.allow_requests %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="allow_requests">Allow Join Requests</label>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="image" class="form-label">Group Image</label>
|
||||||
|
<input type="file" class="form-control" id="image" name="image" accept="image/*">
|
||||||
|
</div>
|
||||||
|
<div id="cropperContainer" style="display: none;">
|
||||||
|
<img id="cropperImage" src="" alt="Group Image">
|
||||||
|
</div>
|
||||||
|
<input type="hidden" id="croppedData" name="cropped_data">
|
||||||
|
<button type="submit" class="btn btn-primary">Update Group Settings</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- New Post Modal -->
|
||||||
|
<div class="modal fade" id="newPostModal" tabindex="-1" aria-labelledby="newPostModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="newPostModalLabel">Create New Post</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form action="{{ url_for('group_post', vanity_url=group.vanity_url) }}" method="POST" id="newPostForm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<textarea class="form-control" id="newPostContent" name="content" rows="5"></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Post</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
{{ super() }}
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/easymde/dist/easymde.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.12/cropper.min.css">
|
||||||
|
<style>
|
||||||
|
.EasyMDEContainer {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
.EasyMDEContainer .CodeMirror {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
.editor-toolbar {
|
||||||
|
background-color: #444;
|
||||||
|
border-color: #555;
|
||||||
|
}
|
||||||
|
.editor-toolbar button {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.editor-toolbar button:hover,
|
||||||
|
.editor-toolbar button.active {
|
||||||
|
background-color: #555;
|
||||||
|
}
|
||||||
|
.CodeMirror-cursor {
|
||||||
|
border-left: 1px solid #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.btn-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.btn-group .btn {
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
border-radius: 0.25rem !important;
|
||||||
|
}
|
||||||
|
.comment-section {
|
||||||
|
background-color: rgba(0, 0, 0, 0.9);
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.comment-section {
|
||||||
|
border-top: 1px solid rgba(0,0,0,.125);
|
||||||
|
}
|
||||||
|
.comment-section .card-body {
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="https://unpkg.com/easymde/dist/easymde.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.12/cropper.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
var easyMDE;
|
||||||
|
var newPostModal = document.getElementById('newPostModal');
|
||||||
|
|
||||||
|
newPostModal.addEventListener('shown.bs.modal', function () {
|
||||||
|
if (!easyMDE) {
|
||||||
|
easyMDE = new EasyMDE({
|
||||||
|
element: document.getElementById('newPostContent'),
|
||||||
|
spellChecker: false,
|
||||||
|
autofocus: false,
|
||||||
|
toolbar: [
|
||||||
|
"bold", "italic", "heading", "quote", "unordered-list", "ordered-list",
|
||||||
|
"link", "image", "code", "table", "undo", "redo"
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('newPostForm').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var content = easyMDE.value();
|
||||||
|
if (content.trim() === '') {
|
||||||
|
alert('Please enter some content for your post.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the hidden textarea with the EasyMDE content
|
||||||
|
document.getElementById('newPostContent').value = content;
|
||||||
|
|
||||||
|
// Submit the form
|
||||||
|
this.submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize all modals
|
||||||
|
var modals = document.querySelectorAll('.modal');
|
||||||
|
modals.forEach(function(modal) {
|
||||||
|
new bootstrap.Modal(modal);
|
||||||
|
});
|
||||||
|
|
||||||
|
function initializePostInteractions() {
|
||||||
|
// Re-initialize like buttons
|
||||||
|
document.querySelectorAll('.like-btn').forEach(button => {
|
||||||
|
button.addEventListener('click', handleLike);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-initialize comment buttons
|
||||||
|
document.querySelectorAll('.comment-btn').forEach(button => {
|
||||||
|
button.addEventListener('click', handleComment);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-initialize copy link buttons
|
||||||
|
document.querySelectorAll('.copy-link-btn').forEach(button => {
|
||||||
|
button.addEventListener('click', handleCopyLink);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-initialize comment forms
|
||||||
|
document.querySelectorAll('.comment-form').forEach(form => {
|
||||||
|
form.addEventListener('submit', handleCommentSubmit);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize comment like buttons
|
||||||
|
document.querySelectorAll('.like-comment-btn').forEach(button => {
|
||||||
|
button.addEventListener('click', handleCommentLike);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLike(event) {
|
||||||
|
var postId = this.dataset.postId;
|
||||||
|
fetch('/like/' + postId, { method: 'POST' })
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
this.querySelector('.like-count').textContent = data.likes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleComment(event) {
|
||||||
|
var postId = this.dataset.postId;
|
||||||
|
var commentSection = document.getElementById('comment-section-' + postId);
|
||||||
|
if (commentSection.style.display === 'none' || commentSection.style.display === '') {
|
||||||
|
commentSection.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
commentSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCommentSubmit(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
var postId = this.dataset.postId;
|
||||||
|
var content = this.querySelector('input[name="content"]').value;
|
||||||
|
fetch('/comment/' + postId, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: 'content=' + encodeURIComponent(content)
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
var commentList = document.querySelector('#comment-section-' + postId + ' ul');
|
||||||
|
var newComment = document.createElement('li');
|
||||||
|
newComment.innerHTML = '<strong>' + (data.username !== 'Anonymous' ? '<a href="/profile/' + data.username + '">' + data.username + '</a>' : '<span class="text-white">Anonymous</span>') + ':</strong> ' + data.content +
|
||||||
|
'<button class="btn btn-sm btn-outline-primary like-comment-btn" data-comment-id="' + data.comment_id + '">' +
|
||||||
|
'<i class="bi bi-heart-fill"></i> (<span class="comment-like-count">0</span>)</button>';
|
||||||
|
commentList.appendChild(newComment);
|
||||||
|
this.querySelector('input[name="content"]').value = '';
|
||||||
|
newComment.querySelector('.like-comment-btn').addEventListener('click', handleCommentLike);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCopyLink(event) {
|
||||||
|
var postUrl = this.dataset.postUrl;
|
||||||
|
navigator.clipboard.writeText(postUrl).then(function() {
|
||||||
|
alert('Link copied to clipboard!');
|
||||||
|
}, function(err) {
|
||||||
|
console.error('Could not copy text: ', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCommentLike(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
var commentId = this.dataset.commentId;
|
||||||
|
fetch('/like_comment/' + commentId, { method: 'POST' })
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
this.querySelector('.comment-like-count').textContent = data.likes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize interactions for the initial page load
|
||||||
|
initializePostInteractions();
|
||||||
|
|
||||||
|
const visibilitySelect = document.getElementById('visibility');
|
||||||
|
const allowRequestsDiv = document.getElementById('allow_requests_div');
|
||||||
|
|
||||||
|
visibilitySelect.addEventListener('change', function() {
|
||||||
|
if (this.value === 'invite_only') {
|
||||||
|
allowRequestsDiv.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
allowRequestsDiv.style.display = 'none';
|
||||||
|
document.getElementById('allow_requests').checked = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sorting functionality
|
||||||
|
const sortSelect = document.getElementById('sort-select');
|
||||||
|
const postsContainer = document.getElementById('posts-container');
|
||||||
|
|
||||||
|
sortSelect.addEventListener('change', function() {
|
||||||
|
const sortOption = this.value;
|
||||||
|
const groupId = '{{ group.id }}'; // Make sure to pass the group ID from the server
|
||||||
|
|
||||||
|
fetch(`/sort_group_posts/${groupId}?sort=${sortOption}`)
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(html => {
|
||||||
|
postsContainer.innerHTML = html;
|
||||||
|
// Reinitialize any event listeners for the new content
|
||||||
|
initializePostInteractions();
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error:', error));
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageInput = document.getElementById('image');
|
||||||
|
const cropperContainer = document.getElementById('cropperContainer');
|
||||||
|
const cropperImage = document.getElementById('cropperImage');
|
||||||
|
const croppedDataInput = document.getElementById('croppedData');
|
||||||
|
let cropper;
|
||||||
|
|
||||||
|
imageInput.addEventListener('change', function(e) {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = function(event) {
|
||||||
|
cropperImage.src = event.target.result;
|
||||||
|
cropperContainer.style.display = 'block';
|
||||||
|
|
||||||
|
if (cropper) {
|
||||||
|
cropper.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
cropper = new Cropper(cropperImage, {
|
||||||
|
aspectRatio: 1,
|
||||||
|
viewMode: 1,
|
||||||
|
minCropBoxWidth: 200,
|
||||||
|
minCropBoxHeight: 200,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('form').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (cropper) {
|
||||||
|
croppedDataInput.value = cropper.getCroppedCanvas().toDataURL();
|
||||||
|
}
|
||||||
|
this.submit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
44
template/group_post_detail.html
Normal file
44
template/group_post_detail.html
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ group.name }} - Post by {{ post.author.username }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<h1>{{ group.name }}</h1>
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">{{ post.author.username }}</h5>
|
||||||
|
<p class="card-text">{{ post.content|safe }}</p>
|
||||||
|
<p class="card-text"><small class="text-muted">{{ post.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</small></p>
|
||||||
|
<button class="btn btn-primary btn-sm like-btn" data-post-id="{{ post.id }}">
|
||||||
|
<i class="bi bi-heart-fill"></i> Like (<span class="like-count">{{ post.likes|length }}</span>)
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary btn-sm comment-btn" data-post-id="{{ post.id }}">
|
||||||
|
<i class="bi bi-chat-fill"></i> Comment ({{ post.comments|length }})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer comment-section" id="comment-section-{{ post.id }}">
|
||||||
|
<h6>Comments:</h6>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
{% for comment in post.comments %}
|
||||||
|
{% include 'comment.html' %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<form class="comment-form" data-post-id="{{ post.id }}">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="text" class="form-control" placeholder="Add a comment" name="content" required>
|
||||||
|
<button class="btn btn-outline-secondary" type="submit">Send</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a href="{{ url_for('group_detail', vanity_url=group.vanity_url) }}" class="btn btn-secondary">Back to Group</a>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script>
|
||||||
|
// Add your JavaScript for likes and comments here, similar to the post_detail.html template
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
31
template/groups.html
Normal file
31
template/groups.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Groups{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<h1>Groups</h1>
|
||||||
|
<a href="{{ url_for('create_group') }}" class="btn btn-primary mb-3">Create New Group</a>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
{% for group in groups %}
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
{% if group.image_url %}
|
||||||
|
<img src="{{ url_for('static', filename='uploads/' + group.image_url) }}" class="rounded-circle img-fluid mb-3" style="width: 100px; height: 100px; object-fit: cover;" alt="{{ group.name }}">
|
||||||
|
{% else %}
|
||||||
|
<div class="rounded-circle bg-primary d-flex align-items-center justify-content-center mb-3 mx-auto" style="width: 100px; height: 100px;">
|
||||||
|
<span class="text-white" style="font-size: 2rem;">{{ group.name[0].upper() }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<h5 class="card-title">{{ group.name }}</h5>
|
||||||
|
<p class="card-text">{{ group.description }}</p>
|
||||||
|
<a href="{{ url_for('group_detail', vanity_url=group.vanity_url) }}" class="btn btn-primary">View Group</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue
Block a user