diff --git a/app/community/forms.py b/app/community/forms.py index 0b2d3450..230c9564 100644 --- a/app/community/forms.py +++ b/app/community/forms.py @@ -53,7 +53,7 @@ class CreatePost(FlaskForm): self.link_url.errors.append(_('URL is required.')) return False domain = domain_from_url(self.link_url.data) - if domain.banned: + if domain and domain.banned: self.link_url.errors.append(_(f"Links to %s are not allowed.".format(domain.name))) return False elif self.type.data == 'image': diff --git a/app/community/routes.py b/app/community/routes.py index f6f2e52c..e4c8799b 100644 --- a/app/community/routes.py +++ b/app/community/routes.py @@ -9,10 +9,10 @@ from app import db, constants from app.activitypub.signature import RsaKeys, HttpSignature from app.community.forms import SearchRemoteCommunity, AddLocalCommunity, CreatePost, NewReplyForm from app.community.util import search_for_community, community_url_exists, actor_to_community, post_replies, \ - get_comment_branch + get_comment_branch, post_reply_count from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, POST_TYPE_LINK, POST_TYPE_ARTICLE, POST_TYPE_IMAGE from app.models import User, Community, CommunityMember, CommunityJoinRequest, CommunityBan, Post, PostReply, \ - PostReplyVote + PostReplyVote, PostVote from app.community import bp from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required @@ -252,6 +252,58 @@ def show_post(post_id: int): canonical=post.ap_id, form=form, replies=replies, THREAD_CUTOFF_DEPTH=constants.THREAD_CUTOFF_DEPTH) +@bp.route('/post//', methods=['GET', 'POST']) +@login_required +@validation_required +def post_vote(post_id: int, vote_direction): + upvoted_class = downvoted_class = '' + post = Post.query.get_or_404(post_id) + existing_vote = PostVote.query.filter_by(user_id=current_user.id, post_id=post.id).first() + if existing_vote: + post.author.reputation -= existing_vote.effect + if existing_vote.effect > 0: # previous vote was up + if vote_direction == 'upvote': # new vote is also up, so remove it + db.session.delete(existing_vote) + post.up_votes -= 1 + post.score -= 1 + else: # new vote is down while previous vote was up, so reverse their previous vote + existing_vote.effect = -1 + post.up_votes -= 1 + post.down_votes += 1 + post.score -= 2 + downvoted_class = 'voted_down' + else: # previous vote was down + if vote_direction == 'upvote': # new vote is upvote + existing_vote.effect = 1 + post.up_votes += 1 + post.down_votes -= 1 + post.score += 1 + upvoted_class = 'voted_up' + else: # reverse a previous downvote + db.session.delete(existing_vote) + post.down_votes -= 1 + post.score += 2 + else: + if vote_direction == 'upvote': + effect = 1 + post.up_votes += 1 + post.score += 1 + upvoted_class = 'voted_up' + else: + effect = -1 + post.down_votes += 1 + post.score -= 1 + downvoted_class = 'voted_down' + vote = PostVote(user_id=current_user.id, post_id=post.id, author_id=post.author.id, + effect=effect) + post.author.reputation += effect + db.session.add(vote) + db.session.commit() + return render_template('community/_post_voting_buttons.html', post=post, + upvoted_class=upvoted_class, + downvoted_class=downvoted_class) + + @bp.route('/comment//', methods=['POST']) @login_required @validation_required @@ -293,7 +345,8 @@ def comment_vote(comment_id, vote_direction): comment.down_votes += 1 comment.score -= 1 downvoted_class = 'voted_down' - vote = PostReplyVote(user_id=current_user.id, post_reply_id=comment_id, author_id=comment.user_id, effect=effect) + vote = PostReplyVote(user_id=current_user.id, post_reply_id=comment_id, author_id=comment.author.id, effect=effect) + comment.author.reputation += effect db.session.add(vote) db.session.commit() return render_template('community/_voting_buttons.html', comment=comment, @@ -331,6 +384,7 @@ def add_reply(post_id: int, comment_id: int): reply_vote = PostReplyVote(user_id=current_user.id, author_id=current_user.id, post_reply_id=reply.id, effect=1.0) db.session.add(reply_vote) + post.reply_count = post_reply_count(post.id) db.session.commit() form.body.data = '' flash('Your comment has been added.') diff --git a/app/community/util.py b/app/community/util.py index b733bdd1..3462bd28 100644 --- a/app/community/util.py +++ b/app/community/util.py @@ -126,3 +126,9 @@ def get_comment_branch(post_id: int, comment_id: int, sort_by: str) -> List[Post parent_comment['replies'].append(comments_dict[comment.id]) return [comment for comment in comments_dict.values() if comment['comment'].id == comment_id] + + +# The number of replies a post has +def post_reply_count(post_id) -> int: + return db.session.execute('SELECT COUNT(id) as c FROM "post_reply" WHERE post_id = :post_id', + {'post_id': post_id}).scalar() diff --git a/app/static/structure.css b/app/static/structure.css index c439e327..265d3233 100644 --- a/app/static/structure.css +++ b/app/static/structure.css @@ -378,6 +378,44 @@ fieldset legend { padding-right: 5px; } +.voting_buttons { + float: right; + display: block; + width: 60px; + padding: 5px; +} +.voting_buttons div { + border: solid 1px #0071CE; +} +.voting_buttons .upvote_button, .voting_buttons .downvote_button { + padding-left: 3px; + border-radius: 3px; + cursor: pointer; +} +.voting_buttons .upvote_button.digits_4, .voting_buttons .downvote_button.digits_4 { + width: 68px; +} +.voting_buttons .upvote_button.digits_5, .voting_buttons .downvote_button.digits_5 { + width: 76px; +} +.voting_buttons .upvote_button.digits_6, .voting_buttons .downvote_button.digits_6 { + width: 84px; +} +.voting_buttons .upvote_button.voted_up, .voting_buttons .downvote_button.voted_up { + color: green; + font-weight: bold; +} +.voting_buttons .upvote_button.voted_down, .voting_buttons .downvote_button.voted_down { + color: darkred; + font-weight: bold; +} +.voting_buttons .downvote_button { + margin-top: 5px; +} +.voting_buttons a { + text-decoration: none; +} + .comment { clear: both; margin-bottom: 20px; @@ -413,43 +451,6 @@ fieldset legend { .comment .hide_button a { text-decoration: none; } -.comment .voting_buttons { - float: right; - display: block; - width: 60px; - padding: 5px; -} -.comment .voting_buttons div { - border: solid 1px #0071CE; -} -.comment .voting_buttons .upvote_button, .comment .voting_buttons .downvote_button { - padding-left: 3px; - border-radius: 3px; - cursor: pointer; -} -.comment .voting_buttons .upvote_button.digits_4, .comment .voting_buttons .downvote_button.digits_4 { - width: 68px; -} -.comment .voting_buttons .upvote_button.digits_5, .comment .voting_buttons .downvote_button.digits_5 { - width: 76px; -} -.comment .voting_buttons .upvote_button.digits_6, .comment .voting_buttons .downvote_button.digits_6 { - width: 84px; -} -.comment .voting_buttons .upvote_button.voted_up, .comment .voting_buttons .downvote_button.voted_up { - color: green; - font-weight: bold; -} -.comment .voting_buttons .upvote_button.voted_down, .comment .voting_buttons .downvote_button.voted_down { - color: darkred; - font-weight: bold; -} -.comment .voting_buttons .downvote_button { - margin-top: 5px; -} -.comment .voting_buttons a { - text-decoration: none; -} .comment .comment_actions { margin-top: -10px; } diff --git a/app/static/structure.scss b/app/static/structure.scss index 5b8d5a96..9ed20e0f 100644 --- a/app/static/structure.scss +++ b/app/static/structure.scss @@ -148,6 +148,52 @@ nav, etc which are used site-wide */ padding-right: 5px; } +.voting_buttons { + float: right; + display: block; + width: 60px; + padding: 5px; + + div { + border: solid 1px $primary-colour; + } + + .upvote_button, .downvote_button { + padding-left: 3px; + border-radius: 3px; + cursor: pointer; + + &.digits_4 { + width: 68px; + } + + &.digits_5 { + width: 76px; + } + + &.digits_6 { + width: 84px; + } + + &.voted_up { + color: green; + font-weight: bold; + } + &.voted_down { + color: darkred; + font-weight: bold; + } + } + + .downvote_button { + margin-top: 5px; + } + + a { + text-decoration: none; + } +} + .comment { clear: both; margin-bottom: 20px; @@ -192,52 +238,6 @@ nav, etc which are used site-wide */ } } - .voting_buttons { - float: right; - display: block; - width: 60px; - padding: 5px; - - div { - border: solid 1px $primary-colour; - } - - .upvote_button, .downvote_button { - padding-left: 3px; - border-radius: 3px; - cursor: pointer; - - &.digits_4 { - width: 68px; - } - - &.digits_5 { - width: 76px; - } - - &.digits_6 { - width: 84px; - } - - &.voted_up { - color: green; - font-weight: bold; - } - &.voted_down { - color: darkred; - font-weight: bold; - } - } - - .downvote_button { - margin-top: 5px; - } - - a { - text-decoration: none; - } - } - .comment_actions { margin-top: -10px; a { diff --git a/app/templates/community/_post_full.html b/app/templates/community/_post_full.html index 117cfedc..5615d996 100644 --- a/app/templates/community/_post_full.html +++ b/app/templates/community/_post_full.html @@ -9,6 +9,9 @@ +
+ {% include "community/_post_voting_buttons.html" %} +

{{ post.title }}

{% if post.url %}

{{ post.url|shorten_url }} @@ -29,26 +32,31 @@ {% endif %} {% else %} -

-

{{ post.title }}

- {% if post.url %} -

{{ post.url|shorten_url }} - External link - {% if post.type == post_type_link %} - (domain) +

+ +
+ {% include "community/_post_voting_buttons.html" %} +
+

{{ post.title }}

+ {% if post.url %} +

{{ post.url|shorten_url }} + External link + {% if post.type == post_type_link %} + (domain) + {% endif %} +

{% endif %} -

- {% endif %} -

submitted {{ moment(post.posted_at).fromNow() }} by - {{ render_username(post.author) }} -

+

submitted {{ moment(post.posted_at).fromNow() }} by + {{ render_username(post.author) }} +

+
{% endif %} diff --git a/app/templates/community/_post_teaser.html b/app/templates/community/_post_teaser.html index 02b471b1..8cafb17b 100644 --- a/app/templates/community/_post_teaser.html +++ b/app/templates/community/_post_teaser.html @@ -1,31 +1,38 @@