245 lines
11 KiB
HTML
245 lines
11 KiB
HTML
|
{% extends "base.html" %}
|
||
|
|
||
|
{% block title %}{{ user.username }}'s Profile{% endblock %}
|
||
|
|
||
|
{% block content %}
|
||
|
<div class="container mt-4">
|
||
|
<div class="row">
|
||
|
<div class="col-md-4">
|
||
|
<div class="card">
|
||
|
<div class="card-body">
|
||
|
<h1 class="card-title">{{ user.username }}'s Profile</h1>
|
||
|
{% if user.profile_picture %}
|
||
|
<img src="{{ url_for('static', filename='uploads/' + user.profile_picture) }}" class="img-fluid rounded-circle mb-3" alt="Profile Picture" style="background-color: transparent;">
|
||
|
{% else %}
|
||
|
<div class="profile-initial rounded-circle mb-3 d-flex justify-content-center align-items-center bg-primary" style="width: 100px; height: 100px;">
|
||
|
<span class="text-white" style="font-size: 3rem;">{{ user.username[0].upper() }}</span>
|
||
|
</div>
|
||
|
{% endif %}
|
||
|
<p class="card-text"><strong>Bio:</strong></p>
|
||
|
<div class="card-text bio-text">{{ user.rendered_bio|safe or "No bio available." }}</div>
|
||
|
<h5 class="card-title">Stats</h5>
|
||
|
<p class="card-text">Posts: {{ post_count }}</p>
|
||
|
<p class="card-text">Total Likes: {{ like_count }}</p>
|
||
|
<p class="card-text">Total Comments: {{ comment_count }}</p>
|
||
|
{% if current_user.is_authenticated and current_user.id == user.id %}
|
||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#editProfileModal">Edit Profile</button>
|
||
|
{% endif %}
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-md-8">
|
||
|
<h2>Posts</h2>
|
||
|
{% for post in posts %}
|
||
|
<div class="card mb-3">
|
||
|
<div class="card-body">
|
||
|
<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" alt="Post image">
|
||
|
{% endif %}
|
||
|
<div class="d-flex align-items-center mt-2">
|
||
|
{% 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>
|
||
|
</div>
|
||
|
<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>
|
||
|
{% 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-warning btn-sm">
|
||
|
<i class="bi bi-pencil-fill"></i> Edit
|
||
|
</a>
|
||
|
{% endif %}
|
||
|
</div>
|
||
|
<div class="card-footer comment-section" id="comment-section-{{ post.id }}" style="display: none;">
|
||
|
<h6>Comments:</h6>
|
||
|
<ul class="list-unstyled">
|
||
|
{% for comment in post.comments if not comment.parent %}
|
||
|
{% with depth=1 %}
|
||
|
{% include 'comment.html' %}
|
||
|
{% endwith %}
|
||
|
{% 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>
|
||
|
{% endfor %}
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<!-- Edit Profile Modal -->
|
||
|
{% if current_user.is_authenticated and current_user.id == user.id %}
|
||
|
<div class="modal fade" id="editProfileModal" tabindex="-1" aria-labelledby="editProfileModalLabel" aria-hidden="true">
|
||
|
<div class="modal-dialog">
|
||
|
<div class="modal-content">
|
||
|
<div class="modal-header">
|
||
|
<h5 class="modal-title" id="editProfileModalLabel">Edit Profile</h5>
|
||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
|
</div>
|
||
|
<div class="modal-body">
|
||
|
<form method="POST" enctype="multipart/form-data" id="profileForm">
|
||
|
<div class="mb-3">
|
||
|
<label for="bio" class="form-label">Bio</label>
|
||
|
<textarea class="form-control" id="bio" name="bio" rows="5">{{ user.bio }}</textarea>
|
||
|
<small class="form-text text-muted">Markdown and HTML color styling are supported.</small>
|
||
|
</div>
|
||
|
<div class="mb-3">
|
||
|
<label for="profile_picture" class="form-label">Profile Picture</label>
|
||
|
<input type="file" class="form-control" id="profile_picture" name="profile_picture" accept="image/*">
|
||
|
</div>
|
||
|
<div id="cropperContainer" style="display: none;">
|
||
|
<img id="cropperImage" src="" alt="Image to crop" style="max-width: 100%;">
|
||
|
</div>
|
||
|
{% if user.profile_picture %}
|
||
|
<div class="mb-3 form-check">
|
||
|
<input type="checkbox" class="form-check-input" id="remove_picture" name="remove_picture">
|
||
|
<label class="form-check-label" for="remove_picture">Remove current profile picture</label>
|
||
|
</div>
|
||
|
{% endif %}
|
||
|
<input type="hidden" name="cropped_data" id="croppedData">
|
||
|
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||
|
</form>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
{% endif %}
|
||
|
{% endblock %}
|
||
|
|
||
|
{% block scripts %}
|
||
|
{{ super() }}
|
||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.12/cropper.min.js"></script>
|
||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.12/cropper.min.css">
|
||
|
<script src="https://unpkg.com/easymde/dist/easymde.min.js"></script>
|
||
|
<script>
|
||
|
$(document).ready(function() {
|
||
|
let cropper;
|
||
|
|
||
|
$('#profile_picture').change(function(e) {
|
||
|
const file = e.target.files[0];
|
||
|
if (file) {
|
||
|
const reader = new FileReader();
|
||
|
reader.onload = function(event) {
|
||
|
$('#cropperImage').attr('src', event.target.result);
|
||
|
$('#cropperContainer').show();
|
||
|
if (cropper) {
|
||
|
cropper.destroy();
|
||
|
}
|
||
|
cropper = new Cropper($('#cropperImage')[0], {
|
||
|
aspectRatio: 1,
|
||
|
viewMode: 1,
|
||
|
minCropBoxWidth: 200,
|
||
|
minCropBoxHeight: 200,
|
||
|
});
|
||
|
}
|
||
|
reader.readAsDataURL(file);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
$('#profileForm').submit(function(e) {
|
||
|
e.preventDefault();
|
||
|
if (cropper) {
|
||
|
const croppedCanvas = cropper.getCroppedCanvas({
|
||
|
width: 200,
|
||
|
height: 200
|
||
|
});
|
||
|
$('#croppedData').val(croppedCanvas.toDataURL());
|
||
|
}
|
||
|
this.submit();
|
||
|
});
|
||
|
|
||
|
var easyMDE = new EasyMDE({
|
||
|
element: document.getElementById('bio'),
|
||
|
spellChecker: false,
|
||
|
autofocus: true,
|
||
|
lineWrapping: true,
|
||
|
toolbar: [
|
||
|
"bold", "italic", "heading", "quote", "unordered-list", "ordered-list",
|
||
|
"link", "code", "table", "undo", "redo",
|
||
|
{
|
||
|
name: "custom-color",
|
||
|
action: function(editor){
|
||
|
var cm = editor.codemirror;
|
||
|
var output = '';
|
||
|
var selectedText = cm.getSelection();
|
||
|
var color = prompt("Enter color (e.g., red, #ff0000, rgb(255,0,0)):");
|
||
|
if (color) {
|
||
|
output = '<span style="color: ' + color + ';">' + selectedText + '</span>';
|
||
|
cm.replaceSelection(output);
|
||
|
}
|
||
|
},
|
||
|
className: "fa fa-paint-brush",
|
||
|
title: "Custom Color",
|
||
|
}
|
||
|
],
|
||
|
status: false,
|
||
|
theme: "dark"
|
||
|
});
|
||
|
|
||
|
// Adjust line height in the editor
|
||
|
easyMDE.codemirror.getWrapperElement().style.lineHeight = "1.3";
|
||
|
});
|
||
|
</script>
|
||
|
{% endblock %}
|
||
|
|
||
|
{% block styles %}
|
||
|
{{ super() }}
|
||
|
<link rel="stylesheet" href="https://unpkg.com/easymde/dist/easymde.min.css">
|
||
|
<style>
|
||
|
.bio-text {
|
||
|
white-space: pre-wrap;
|
||
|
word-wrap: break-word;
|
||
|
max-height: 200px;
|
||
|
overflow-y: auto;
|
||
|
line-height: 1.3; /* Reduce line spacing */
|
||
|
}
|
||
|
.bio-text a {
|
||
|
color: #007bff;
|
||
|
text-decoration: underline;
|
||
|
}
|
||
|
.bio-text a:hover {
|
||
|
color: #0056b3;
|
||
|
}
|
||
|
.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;
|
||
|
}
|
||
|
.CodeMirror-line {
|
||
|
padding: 0 !important; /* Remove extra padding */
|
||
|
}
|
||
|
</style>
|
||
|
{% endblock %}
|