mirror of
https://github.com/Xevion/runnerspace.git
synced 2025-12-11 04:08:27 -06:00
Use jQuery to allow users to like/unlike posts with dynamic updates
- Only the pages that need jQuery and the likes.js script will load it
This commit is contained in:
25
routes.py
25
routes.py
@@ -1,7 +1,7 @@
|
|||||||
from flask import Blueprint, redirect, render_template, url_for, request
|
from flask import Blueprint, redirect, render_template, url_for, request, jsonify
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
|
|
||||||
from models import User, Post, Comment
|
from models import User, Post, Comment, PostLike, CommentLike
|
||||||
from forms import NewPostForm, NewCommentForm, EditProfileForm
|
from forms import NewPostForm, NewCommentForm, EditProfileForm
|
||||||
from database import db
|
from database import db
|
||||||
|
|
||||||
@@ -46,6 +46,27 @@ def view_post(post_id: int):
|
|||||||
return render_template('pages/post.html', form=NewCommentForm(), post=post)
|
return render_template('pages/post.html', form=NewCommentForm(), post=post)
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/post/<post_id>/like', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def like_post(post_id: int):
|
||||||
|
# Check that the relevant post exists
|
||||||
|
post = db.session.query(Post).get_or_404(post_id)
|
||||||
|
|
||||||
|
# Acquire the relevant PostLike in question
|
||||||
|
post_like = db.session.query(PostLike).filter_by(post=post, user=current_user).first()
|
||||||
|
if post_like is None:
|
||||||
|
post_like = PostLike(post=post, user=current_user)
|
||||||
|
db.session.add(post_like)
|
||||||
|
else:
|
||||||
|
db.session.delete(post_like)
|
||||||
|
post_like = None
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# post_like is only NOT None if the user had not liked it before, but has liked it now after db.commit().
|
||||||
|
return jsonify({'liked': post_like is not None, 'status_text': post.get_like_text()})
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/search')
|
@blueprint.route('/search')
|
||||||
def search():
|
def search():
|
||||||
return render_template('pages/search.html')
|
return render_template('pages/search.html')
|
||||||
|
|||||||
19
static/likes.js
Normal file
19
static/likes.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
function like(id) {
|
||||||
|
$.ajax({url: `/post/${id}/like`, method: "POST", dataType: "json"})
|
||||||
|
.done(function (data) {
|
||||||
|
let post_parent = $(`#post-${id}`)
|
||||||
|
let heart_icon = post_parent.find('.fa-heart')
|
||||||
|
let pre_liked = heart_icon.hasClass('liked')
|
||||||
|
|
||||||
|
// Toggle if the current state no longer matches the database state.
|
||||||
|
if (pre_liked !== data.liked) {
|
||||||
|
if (pre_liked)
|
||||||
|
heart_icon.removeClass('liked')
|
||||||
|
else
|
||||||
|
heart_icon.addClass('liked')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set new state of the like status text
|
||||||
|
post_parent.find('.post-like-status').html(data.status_text)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -257,6 +257,7 @@ nav .links li:not(:last-child)::after, footer .links li:not(:last-child)::after
|
|||||||
top: 1em;
|
top: 1em;
|
||||||
right: 1em;
|
right: 1em;
|
||||||
color: #b0c9f3;
|
color: #b0c9f3;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.post-box .fa-heart.liked {
|
.post-box .fa-heart.liked {
|
||||||
color: #1b53a8;
|
color: #1b53a8;
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EAOE;;AALA;EAEE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;;AAMR;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAMF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AACA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;;AAIJ;EAEE;EACA;EACA;;;AAMR;EACE;EACA;;AACA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAME;EACA;EACA;EAEA;EACA;EACA;;AAXA;EACE;;;AAcN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;;;AAGF;EACE","file":"styles.css"}
|
{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EAOE;;AALA;EAEE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;;AAMR;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAMF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AACA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;;AAIJ;EAEE;EACA;EACA;;;AAMR;EACE;EACA;;AACA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAME;EACA;EACA;EAEA;EACA;EACA;;AAXA;EACE;;;AAcN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;;;AAGF;EACE","file":"styles.css"}
|
||||||
@@ -303,6 +303,7 @@ nav, footer {
|
|||||||
top: 1em;
|
top: 1em;
|
||||||
right: 1em;
|
right: 1em;
|
||||||
color: #b0c9f3;
|
color: #b0c9f3;
|
||||||
|
cursor: pointer;
|
||||||
&.liked {
|
&.liked {
|
||||||
color: #1b53a8;
|
color: #1b53a8;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,25 @@
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.5.0/js/all.min.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.5.0/js/all.min.js"
|
||||||
integrity="sha512-YUwFoN1yaVzHxZ1cLsNYJzVt1opqtVLKgBQ+wDj+JyfvOkH66ck1fleCm8eyJG9O1HpKIf86HrgTXkWDyHy9HA=="
|
integrity="sha512-YUwFoN1yaVzHxZ1cLsNYJzVt1opqtVLKgBQ+wDj+JyfvOkH66ck1fleCm8eyJG9O1HpKIf86HrgTXkWDyHy9HA=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
{% if use_jquery %}
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"
|
||||||
|
integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ=="
|
||||||
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
<script>
|
||||||
|
let csrf_token = "{{ csrf_token() }}";
|
||||||
|
|
||||||
|
$.ajaxSetup({
|
||||||
|
beforeSend: function (xhr, settings) {
|
||||||
|
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
|
||||||
|
xhr.setRequestHeader("X-CSRFToken", csrf_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% if use_likes %}
|
||||||
|
<script src="{{ url_for('static', filename='likes.js') }}"></script>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
<link rel="preload" href="{{ url_for('static', filename='roadrunner_header.png') }}" as="image">
|
<link rel="preload" href="{{ url_for('static', filename='roadrunner_header.png') }}" as="image">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{% extends 'layouts/index.html' %}
|
{% extends 'layouts/index.html' %}
|
||||||
{% from 'macros.html' import render_field %}
|
{% from 'macros.html' import render_field %}
|
||||||
|
{% set use_jquery = true %}
|
||||||
|
{% set use_likes = true %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
@@ -13,15 +15,15 @@
|
|||||||
<hr style="margin: 1.5em 0">
|
<hr style="margin: 1.5em 0">
|
||||||
|
|
||||||
{% for post in posts %}
|
{% for post in posts %}
|
||||||
<div class="post-box">
|
<div id="post-{{ post.id }}" class="post-box">
|
||||||
<i class="fas fa-heart {% if current_user.has_liked_post(post.id) %}liked{% endif %}"></i>
|
<i class="fas fa-heart {% if current_user.has_liked_post(post.id) %}liked{% endif %}" onclick="like({{ post.id }})"></i>
|
||||||
{{ post.text }}
|
{{ post.text }}
|
||||||
<div class="post-author no-border">
|
<div class="post-author no-border">
|
||||||
{% with comment_count = post.comments|length %}
|
{% with comment_count = post.comments|length %}
|
||||||
<em>Posted by <a href="{{ url_for('main.view_user', username=post.author.username) }}">{{ post.author.name }}</a></em>
|
<em>Posted by <a href="{{ url_for('main.view_user', username=post.author.username) }}">{{ post.author.name }}</a></em>
|
||||||
<span title="{{ post.date_posted }}">{{ post.get_time_ago() }} ago</span>. |
|
<span title="{{ post.date_posted }}">{{ post.get_time_ago() }} ago</span>. |
|
||||||
<a href="{{ url_for('main.view_post', post_id=post.id) }}"><span> {{ comment_count }} comment{{ comment_count|pluralize }}</span></a>
|
<a href="{{ url_for('main.view_post', post_id=post.id) }}"><span> {{ comment_count }} comment{{ comment_count|pluralize }}</span></a>
|
||||||
| {{ post.get_like_text()|safe }}
|
| <span class="post-like-status">{{ post.get_like_text()|safe }}</span>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user