mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 19:36:56 -08:00
refactoring and bug fixes
This commit is contained in:
parent
26074bd85e
commit
4a6492a15c
13 changed files with 374 additions and 251 deletions
|
@ -13,7 +13,8 @@ from app.models import User, Community, CommunityJoinRequest, CommunityMember, C
|
|||
PostReply, Instance, PostVote, PostReplyVote, File, AllowedInstances, BannedInstances, utcnow
|
||||
from app.activitypub.util import public_key, users_total, active_half_year, active_month, local_posts, local_comments, \
|
||||
post_to_activity, find_actor_or_create, default_context, instance_blocked, find_reply_parent, find_liked_object, \
|
||||
lemmy_site_data, instance_weight, is_activitypub_request
|
||||
lemmy_site_data, instance_weight, is_activitypub_request, downvote_post_reply, downvote_post, upvote_post_reply, \
|
||||
upvote_post
|
||||
from app.utils import gibberish, get_setting, is_image_url, allowlist_html, html_to_markdown, render_template, \
|
||||
domain_from_url, markdown_to_html, community_membership, ap_datetime
|
||||
import werkzeug.exceptions
|
||||
|
@ -254,7 +255,7 @@ def community_profile(actor):
|
|||
@bp.route('/inbox', methods=['GET', 'POST'])
|
||||
def shared_inbox():
|
||||
if request.method == 'POST':
|
||||
# save all incoming data to aid in debugging and development
|
||||
# save all incoming data to aid in debugging and development. Set result to 'success' if things go well
|
||||
activity_log = ActivityPubLog(direction='in', activity_json=request.data, result='failure')
|
||||
|
||||
try:
|
||||
|
@ -456,78 +457,86 @@ def shared_inbox():
|
|||
db.session.commit()
|
||||
else:
|
||||
post_id, parent_comment_id, root_id = find_reply_parent(in_reply_to)
|
||||
post_reply = PostReply(user_id=user.id, community_id=community.id,
|
||||
post_id=post_id, parent_id=parent_comment_id,
|
||||
root_id=root_id,
|
||||
nsfw=community.nsfw,
|
||||
nsfl=community.nsfl,
|
||||
ap_id=request_json['object']['object']['id'],
|
||||
ap_create_id=request_json['object']['id'],
|
||||
ap_announce_id=request_json['id'])
|
||||
if 'source' in request_json['object']['object'] and \
|
||||
request_json['object']['object']['source'][
|
||||
'mediaType'] == 'text/markdown':
|
||||
post_reply.body = request_json['object']['object']['source']['content']
|
||||
post_reply.body_html = markdown_to_html(post_reply.body)
|
||||
elif 'content' in request_json['object']['object']:
|
||||
post_reply.body_html = allowlist_html(
|
||||
request_json['object']['object']['content'])
|
||||
post_reply.body = html_to_markdown(post_reply.body_html)
|
||||
if post_id or parent_comment_id or root_id:
|
||||
post_reply = PostReply(user_id=user.id, community_id=community.id,
|
||||
post_id=post_id, parent_id=parent_comment_id,
|
||||
root_id=root_id,
|
||||
nsfw=community.nsfw,
|
||||
nsfl=community.nsfl,
|
||||
ap_id=request_json['object']['object']['id'],
|
||||
ap_create_id=request_json['object']['id'],
|
||||
ap_announce_id=request_json['id'])
|
||||
if 'source' in request_json['object']['object'] and \
|
||||
request_json['object']['object']['source'][
|
||||
'mediaType'] == 'text/markdown':
|
||||
post_reply.body = request_json['object']['object']['source']['content']
|
||||
post_reply.body_html = markdown_to_html(post_reply.body)
|
||||
elif 'content' in request_json['object']['object']:
|
||||
post_reply.body_html = allowlist_html(
|
||||
request_json['object']['object']['content'])
|
||||
post_reply.body = html_to_markdown(post_reply.body_html)
|
||||
|
||||
if post_reply is not None:
|
||||
post = Post.query.get(post_id)
|
||||
if post.comments_enabled:
|
||||
db.session.add(post_reply)
|
||||
community.post_reply_count += 1
|
||||
community.last_active = utcnow()
|
||||
post.last_active = utcnow()
|
||||
activity_log.result = 'success'
|
||||
db.session.commit()
|
||||
else:
|
||||
activity_log.exception_message = 'Comments disabled'
|
||||
if post_reply is not None:
|
||||
post = Post.query.get(post_id)
|
||||
if post.comments_enabled:
|
||||
db.session.add(post_reply)
|
||||
community.post_reply_count += 1
|
||||
community.last_active = utcnow()
|
||||
post.last_active = utcnow()
|
||||
activity_log.result = 'success'
|
||||
db.session.commit()
|
||||
else:
|
||||
activity_log.exception_message = 'Comments disabled'
|
||||
else:
|
||||
activity_log.exception_message = 'Parent not found'
|
||||
else:
|
||||
activity_log.exception_message = 'Unacceptable type: ' + object_type
|
||||
|
||||
elif request_json['object']['type'] == 'Like' or request_json['object']['type'] == 'Dislike':
|
||||
elif request_json['object']['type'] == 'Like':
|
||||
activity_log.activity_type = request_json['object']['type']
|
||||
vote_effect = 1.0 if request_json['object']['type'] == 'Like' else -1.0
|
||||
if vote_effect < 0 and get_setting('allow_dislike', True) is False:
|
||||
user_ap_id = request_json['object']['actor']
|
||||
liked_ap_id = request_json['object']['object']
|
||||
user = find_actor_or_create(user_ap_id)
|
||||
if user:
|
||||
liked = find_liked_object(liked_ap_id)
|
||||
# insert into voted table
|
||||
if liked is None:
|
||||
activity_log.exception_message = 'Liked object not found'
|
||||
elif liked is not None and isinstance(liked, Post):
|
||||
upvote_post(liked, user)
|
||||
activity_log.result = 'success'
|
||||
elif liked is not None and isinstance(liked, PostReply):
|
||||
upvote_post_reply(liked, user)
|
||||
activity_log.result = 'success'
|
||||
else:
|
||||
activity_log.exception_message = 'Could not detect type of like'
|
||||
if activity_log.result == 'success':
|
||||
... # todo: recalculate 'hotness' of liked post/reply
|
||||
# todo: if vote was on content in local community, federate the vote out to followers
|
||||
elif request_json['object']['type'] == 'Dislike':
|
||||
activity_log.activity_type = request_json['object']['type']
|
||||
if g.site.enable_downvotes is False:
|
||||
activity_log.exception_message = 'Dislike ignored because of allow_dislike setting'
|
||||
else:
|
||||
user_ap_id = request_json['object']['actor']
|
||||
liked_ap_id = request_json['object']['object']
|
||||
user = find_actor_or_create(user_ap_id)
|
||||
if user:
|
||||
vote_weight = instance_weight(user.ap_domain)
|
||||
liked = find_liked_object(liked_ap_id)
|
||||
disliked = find_liked_object(liked_ap_id)
|
||||
# insert into voted table
|
||||
if liked is None:
|
||||
if disliked is None:
|
||||
activity_log.exception_message = 'Liked object not found'
|
||||
elif liked is not None and isinstance(liked, Post):
|
||||
existing_vote = PostVote.query.filter_by(user_id=user.id, post_id=liked.id).first()
|
||||
if existing_vote:
|
||||
existing_vote.effect = vote_effect * vote_weight
|
||||
else:
|
||||
vote = PostVote(user_id=user.id, author_id=liked.user_id, post_id=liked.id,
|
||||
effect=vote_effect * vote_weight)
|
||||
db.session.add(vote)
|
||||
db.session.commit()
|
||||
elif disliked is not None and isinstance(disliked, Post):
|
||||
downvote_post(disliked, user)
|
||||
activity_log.result = 'success'
|
||||
elif liked is not None and isinstance(liked, PostReply):
|
||||
existing_vote = PostVote.query.filter_by(user_id=user.id, post_id=liked.id).first()
|
||||
if existing_vote:
|
||||
existing_vote.effect = vote_effect * vote_weight
|
||||
else:
|
||||
vote = PostReplyVote(user_id=user.id, author_id=liked.user_id, post_reply_id=liked.id,
|
||||
effect=vote_effect * vote_weight)
|
||||
db.session.add(vote)
|
||||
db.session.commit()
|
||||
elif disliked is not None and isinstance(disliked, PostReply):
|
||||
downvote_post_reply(disliked, user)
|
||||
activity_log.result = 'success'
|
||||
else:
|
||||
activity_log.exception_message = 'Could not detect type of like'
|
||||
if activity_log.result == 'success':
|
||||
... # todo: recalculate 'hotness' of liked post/reply
|
||||
# todo: if vote was on content in local community, federate the vote out to followers
|
||||
... # todo: recalculate 'hotness' of liked post/reply
|
||||
# todo: if vote was on content in local community, federate the vote out to followers
|
||||
|
||||
# Follow: remote user wants to join/follow one of our communities
|
||||
elif request_json['type'] == 'Follow': # Follow is when someone wants to join a community
|
||||
|
@ -726,65 +735,15 @@ def shared_inbox():
|
|||
target_ap_id = request_json['object']
|
||||
post = None
|
||||
comment = None
|
||||
effect = instance_weight(user.ap_domain)
|
||||
if '/comment/' in target_ap_id:
|
||||
comment = PostReply.query.filter_by(ap_id=target_ap_id).first()
|
||||
if '/post/' in target_ap_id:
|
||||
post = Post.query.filter_by(ap_id=target_ap_id).first()
|
||||
if user and post:
|
||||
user.last_seen = utcnow()
|
||||
existing_vote = PostVote.query.filter_by(user_id=user.id, post_id=post.id).first()
|
||||
if not existing_vote:
|
||||
post.up_votes += 1
|
||||
post.score += effect
|
||||
vote = PostVote(user_id=user.id, post_id=post.id, author_id=post.author.id,
|
||||
effect=effect)
|
||||
post.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
else:
|
||||
# remove previous cast downvote
|
||||
if existing_vote.effect < 0:
|
||||
post.author.reputation -= existing_vote.effect
|
||||
post.down_votes -= 1
|
||||
post.score -= existing_vote.effect
|
||||
db.session.delete(existing_vote)
|
||||
|
||||
# apply up vote
|
||||
post.up_votes += 1
|
||||
post.score += effect
|
||||
vote = PostVote(user_id=user.id, post_id=post.id, author_id=post.author.id,
|
||||
effect=effect)
|
||||
post.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
upvote_post(post, user)
|
||||
activity_log.result = 'success'
|
||||
elif user and comment:
|
||||
user.last_seen = utcnow()
|
||||
existing_vote = PostReplyVote.query.filter_by(user_id=user.id,
|
||||
post_reply_id=comment.id).first()
|
||||
if not existing_vote:
|
||||
comment.up_votes += 1
|
||||
comment.score += effect
|
||||
vote = PostReplyVote(user_id=user.id, post_reply_id=comment.id,
|
||||
author_id=comment.author.id, effect=effect)
|
||||
comment.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
else:
|
||||
# remove previously cast downvote
|
||||
if existing_vote.effect < 0:
|
||||
comment.author.reputation -= existing_vote.effect
|
||||
comment.down_votes -= 1
|
||||
comment.score -= existing_vote.effect
|
||||
db.session.delete(existing_vote)
|
||||
|
||||
# apply up vote
|
||||
comment.up_votes += 1
|
||||
comment.score += effect
|
||||
vote = PostReplyVote(user_id=user.id, post_reply_id=comment.id,
|
||||
author_id=comment.author.id, effect=effect)
|
||||
comment.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
else:
|
||||
pass # they have already upvoted this reply
|
||||
upvote_post_reply(comment, user)
|
||||
activity_log.result = 'success'
|
||||
|
||||
elif request_json['type'] == 'Dislike': # Downvote
|
||||
|
@ -802,65 +761,10 @@ def shared_inbox():
|
|||
if '/post/' in target_ap_id:
|
||||
post = Post.query.filter_by(ap_id=target_ap_id).first()
|
||||
if user and comment:
|
||||
user.last_seen = utcnow()
|
||||
existing_vote = PostReplyVote.query.filter_by(user_id=user.id,
|
||||
post_reply_id=comment.id).first()
|
||||
if not existing_vote:
|
||||
effect = -1.0
|
||||
comment.down_votes += 1
|
||||
comment.score -= 1.0
|
||||
vote = PostReplyVote(user_id=user.id, post_reply_id=comment.id,
|
||||
author_id=comment.author.id, effect=effect)
|
||||
comment.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
else:
|
||||
# remove previously cast upvote
|
||||
if existing_vote.effect > 0:
|
||||
comment.author.reputation -= existing_vote.effect
|
||||
comment.up_votes -= 1
|
||||
comment.score -= existing_vote.effect
|
||||
db.session.delete(existing_vote)
|
||||
|
||||
# apply down vote
|
||||
effect = -1.0
|
||||
comment.down_votes += 1
|
||||
comment.score -= 1.0
|
||||
vote = PostReplyVote(user_id=user.id, post_reply_id=comment.id,
|
||||
author_id=comment.author.id, effect=effect)
|
||||
comment.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
else:
|
||||
pass # they have already downvoted this reply
|
||||
downvote_post_reply(comment, user)
|
||||
activity_log.result = 'success'
|
||||
elif user and post:
|
||||
user.last_seen = utcnow()
|
||||
existing_vote = PostVote.query.filter_by(user_id=user.id, post_id=post.id).first()
|
||||
if not existing_vote:
|
||||
effect = -1.0
|
||||
post.down_votes += 1
|
||||
post.score -= 1.0
|
||||
vote = PostVote(user_id=user.id, post_id=post.id, author_id=post.author.id,
|
||||
effect=effect)
|
||||
post.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
else:
|
||||
# remove previously cast upvote
|
||||
if existing_vote.effect > 0:
|
||||
post.author.reputation -= existing_vote.effect
|
||||
post.up_votes -= 1
|
||||
post.score -= existing_vote.effect
|
||||
db.session.delete(existing_vote)
|
||||
|
||||
# apply down vote
|
||||
effect = -1.0
|
||||
post.down_votes += 1
|
||||
post.score -= 1.0
|
||||
vote = PostVote(user_id=user.id, post_id=post.id, author_id=post.author.id,
|
||||
effect=effect)
|
||||
post.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
else:
|
||||
pass # they have already downvoted this post
|
||||
downvote_post(post, user)
|
||||
activity_log.result = 'success'
|
||||
else:
|
||||
activity_log.exception_message = 'Could not find user or content for vote'
|
||||
|
@ -885,6 +789,8 @@ def shared_inbox():
|
|||
return ''
|
||||
|
||||
|
||||
|
||||
|
||||
@bp.route('/c/<actor>/outbox', methods=['GET'])
|
||||
def community_outbox(actor):
|
||||
actor = actor.strip()
|
||||
|
@ -1003,3 +909,12 @@ def post_ap(post_id):
|
|||
return resp
|
||||
else:
|
||||
return show_post(post_id)
|
||||
|
||||
|
||||
@bp.route('/activities/<type>/<id>')
|
||||
def activities_json(type, id):
|
||||
activity = ActivityPubLog.query.filter_by(activity_id=f"https://{current_app.config['SERVER_NAME']}/activities/{type}/{id}").first()
|
||||
if activity:
|
||||
...
|
||||
else:
|
||||
abort(404)
|
||||
|
|
|
@ -5,7 +5,8 @@ from typing import Union, Tuple
|
|||
from flask import current_app, request
|
||||
from sqlalchemy import text
|
||||
from app import db, cache, constants
|
||||
from app.models import User, Post, Community, BannedInstances, File, PostReply, AllowedInstances, Instance, utcnow, Site
|
||||
from app.models import User, Post, Community, BannedInstances, File, PostReply, AllowedInstances, Instance, utcnow, \
|
||||
Site, PostVote, PostReplyVote
|
||||
import time
|
||||
import base64
|
||||
import requests
|
||||
|
@ -439,12 +440,16 @@ def default_context():
|
|||
def find_reply_parent(in_reply_to: str) -> Tuple[int, int, int]:
|
||||
if 'comment' in in_reply_to:
|
||||
parent_comment = PostReply.get_by_ap_id(in_reply_to)
|
||||
if not parent_comment:
|
||||
return (None, None, None)
|
||||
parent_comment_id = parent_comment.id
|
||||
post_id = parent_comment.post_id
|
||||
root_id = parent_comment.root_id
|
||||
elif 'post' in in_reply_to:
|
||||
parent_comment_id = None
|
||||
post = Post.get_by_ap_id(in_reply_to)
|
||||
if not post:
|
||||
return (None, None, None)
|
||||
post_id = post.id
|
||||
root_id = None
|
||||
else:
|
||||
|
@ -460,6 +465,8 @@ def find_reply_parent(in_reply_to: str) -> Tuple[int, int, int]:
|
|||
parent_comment_id = parent_comment.id
|
||||
post_id = parent_comment.post_id
|
||||
root_id = parent_comment.root_id
|
||||
else:
|
||||
return (None, None, None)
|
||||
|
||||
return post_id, parent_comment_id, root_id
|
||||
|
||||
|
@ -523,6 +530,128 @@ def is_activitypub_request():
|
|||
return 'application/ld+json' in request.headers.get('Accept', '') or 'application/activity+json' in request.headers.get('Accept', '')
|
||||
|
||||
|
||||
def downvote_post(post, user):
|
||||
user.last_seen = utcnow()
|
||||
existing_vote = PostVote.query.filter_by(user_id=user.id, post_id=post.id).first()
|
||||
if not existing_vote:
|
||||
effect = -1.0
|
||||
post.down_votes += 1
|
||||
post.score -= 1.0
|
||||
vote = PostVote(user_id=user.id, post_id=post.id, author_id=post.author.id,
|
||||
effect=effect)
|
||||
post.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
else:
|
||||
# remove previously cast upvote
|
||||
if existing_vote.effect > 0:
|
||||
post.author.reputation -= existing_vote.effect
|
||||
post.up_votes -= 1
|
||||
post.score -= existing_vote.effect
|
||||
db.session.delete(existing_vote)
|
||||
|
||||
# apply down vote
|
||||
effect = -1.0
|
||||
post.down_votes += 1
|
||||
post.score -= 1.0
|
||||
vote = PostVote(user_id=user.id, post_id=post.id, author_id=post.author.id,
|
||||
effect=effect)
|
||||
post.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
else:
|
||||
pass # they have already downvoted this post
|
||||
|
||||
|
||||
def downvote_post_reply(comment, user):
|
||||
user.last_seen = utcnow()
|
||||
existing_vote = PostReplyVote.query.filter_by(user_id=user.id,
|
||||
post_reply_id=comment.id).first()
|
||||
if not existing_vote:
|
||||
effect = -1.0
|
||||
comment.down_votes += 1
|
||||
comment.score -= 1.0
|
||||
vote = PostReplyVote(user_id=user.id, post_reply_id=comment.id,
|
||||
author_id=comment.author.id, effect=effect)
|
||||
comment.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
else:
|
||||
# remove previously cast upvote
|
||||
if existing_vote.effect > 0:
|
||||
comment.author.reputation -= existing_vote.effect
|
||||
comment.up_votes -= 1
|
||||
comment.score -= existing_vote.effect
|
||||
db.session.delete(existing_vote)
|
||||
|
||||
# apply down vote
|
||||
effect = -1.0
|
||||
comment.down_votes += 1
|
||||
comment.score -= 1.0
|
||||
vote = PostReplyVote(user_id=user.id, post_reply_id=comment.id,
|
||||
author_id=comment.author.id, effect=effect)
|
||||
comment.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
else:
|
||||
pass # they have already downvoted this reply
|
||||
|
||||
|
||||
def upvote_post_reply(comment, user):
|
||||
user.last_seen = utcnow()
|
||||
effect = instance_weight(user.ap_domain)
|
||||
existing_vote = PostReplyVote.query.filter_by(user_id=user.id,
|
||||
post_reply_id=comment.id).first()
|
||||
if not existing_vote:
|
||||
comment.up_votes += 1
|
||||
comment.score += effect
|
||||
vote = PostReplyVote(user_id=user.id, post_reply_id=comment.id,
|
||||
author_id=comment.author.id, effect=effect)
|
||||
comment.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
else:
|
||||
# remove previously cast downvote
|
||||
if existing_vote.effect < 0:
|
||||
comment.author.reputation -= existing_vote.effect
|
||||
comment.down_votes -= 1
|
||||
comment.score -= existing_vote.effect
|
||||
db.session.delete(existing_vote)
|
||||
|
||||
# apply up vote
|
||||
comment.up_votes += 1
|
||||
comment.score += effect
|
||||
vote = PostReplyVote(user_id=user.id, post_reply_id=comment.id,
|
||||
author_id=comment.author.id, effect=effect)
|
||||
comment.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
else:
|
||||
pass # they have already upvoted this reply
|
||||
|
||||
|
||||
def upvote_post(post, user):
|
||||
user.last_seen = utcnow()
|
||||
effect = instance_weight(user.ap_domain)
|
||||
existing_vote = PostVote.query.filter_by(user_id=user.id, post_id=post.id).first()
|
||||
if not existing_vote:
|
||||
post.up_votes += 1
|
||||
post.score += effect
|
||||
vote = PostVote(user_id=user.id, post_id=post.id, author_id=post.author.id,
|
||||
effect=effect)
|
||||
post.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
else:
|
||||
# remove previous cast downvote
|
||||
if existing_vote.effect < 0:
|
||||
post.author.reputation -= existing_vote.effect
|
||||
post.down_votes -= 1
|
||||
post.score -= existing_vote.effect
|
||||
db.session.delete(existing_vote)
|
||||
|
||||
# apply up vote
|
||||
post.up_votes += 1
|
||||
post.score += effect
|
||||
vote = PostVote(user_id=user.id, post_id=post.id, author_id=post.author.id,
|
||||
effect=effect)
|
||||
post.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
|
||||
|
||||
def lemmy_site_data():
|
||||
site = Site.query.get(1)
|
||||
data = {
|
||||
|
|
|
@ -100,6 +100,7 @@ def show_community(community: Community):
|
|||
is_moderator = current_user.is_authenticated and any(mod.user_id == current_user.id for mod in mods)
|
||||
is_owner = current_user.is_authenticated and any(
|
||||
mod.user_id == current_user.id and mod.is_owner == True for mod in mods)
|
||||
is_admin = current_user.is_authenticated and current_user.is_admin()
|
||||
|
||||
if community.private_mods:
|
||||
mod_list = []
|
||||
|
@ -121,7 +122,7 @@ def show_community(community: Community):
|
|||
page=posts.prev_num) if posts.has_prev and page != 1 else None
|
||||
|
||||
return render_template('community/community.html', community=community, title=community.title,
|
||||
is_moderator=is_moderator, is_owner=is_owner, mods=mod_list, posts=posts, description=description,
|
||||
is_moderator=is_moderator, is_owner=is_owner, is_admin=is_admin, mods=mod_list, posts=posts, description=description,
|
||||
og_image=og_image, POST_TYPE_IMAGE=POST_TYPE_IMAGE, POST_TYPE_LINK=POST_TYPE_LINK, SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING,
|
||||
SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER, etag=f"{community.id}_{hash(community.last_active)}",
|
||||
next_url=next_url, prev_url=prev_url,
|
||||
|
|
|
@ -101,7 +101,9 @@ def retrieve_mods_and_backfill_thread(community: Community, app):
|
|||
activities_processed += 1
|
||||
if activities_processed >= 50:
|
||||
break
|
||||
community.post_count = activities_processed # todo: figure out why this value is not being saved
|
||||
c = Community.query.get(community.id)
|
||||
c.post_count = activities_processed
|
||||
c.last_active = utcnow()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
|
|
|
@ -388,27 +388,36 @@ fieldset legend {
|
|||
background-position: center center;
|
||||
background-size: cover;
|
||||
border-radius: 5px;
|
||||
height: 176px;
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.community_header {
|
||||
height: 240px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.community_header #breadcrumb_nav {
|
||||
padding-left: 20px;
|
||||
padding-top: 13px;
|
||||
}
|
||||
.community_header #breadcrumb_nav .breadcrumb {
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
background-color: inherit;
|
||||
}
|
||||
.community_header #breadcrumb_nav .breadcrumb .breadcrumb-item {
|
||||
color: white;
|
||||
}
|
||||
.community_header #breadcrumb_nav .breadcrumb .breadcrumb-item a {
|
||||
color: white;
|
||||
}
|
||||
.community_header #breadcrumb_nav .breadcrumb .breadcrumb-item + .breadcrumb-item::before {
|
||||
content: ">";
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
.community_header #breadcrumb_nav .breadcrumb {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.community_header #breadcrumb_nav .breadcrumb .breadcrumb-item {
|
||||
color: white;
|
||||
display: inline-block;
|
||||
}
|
||||
.community_header #breadcrumb_nav .breadcrumb .breadcrumb-item a {
|
||||
color: white;
|
||||
}
|
||||
.community_header #breadcrumb_nav .breadcrumb .breadcrumb-item + .breadcrumb-item::before {
|
||||
content: ">";
|
||||
color: white;
|
||||
}
|
||||
|
||||
.community_header_no_background .community_icon, .community_header .community_icon {
|
||||
|
@ -451,14 +460,22 @@ fieldset legend {
|
|||
text-decoration: none;
|
||||
}
|
||||
.post_list .post_teaser .thumbnail {
|
||||
float: right;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
.post_list .post_teaser .thumbnail img {
|
||||
position: absolute;
|
||||
right: 70px;
|
||||
height: 70px;
|
||||
margin-top: -47px;
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
border-radius: 5px;
|
||||
object-fit: cover;
|
||||
margin-left: 5px;
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.post_list .post_teaser .thumbnail img {
|
||||
height: 70px;
|
||||
width: 133px;
|
||||
}
|
||||
}
|
||||
|
||||
.url_thumbnail {
|
||||
|
@ -498,8 +515,9 @@ fieldset legend {
|
|||
float: right;
|
||||
display: block;
|
||||
width: 55px;
|
||||
padding: 5px;
|
||||
padding-right: 0;
|
||||
padding: 0 0 5px 5px;
|
||||
line-height: 22px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.voting_buttons div {
|
||||
border: solid 1px #0071CE;
|
||||
|
|
|
@ -77,28 +77,35 @@ nav, etc which are used site-wide */
|
|||
background-position: center center;
|
||||
background-size: cover;
|
||||
border-radius: 5px;
|
||||
height: 176px;
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
#breadcrumb_nav {
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
#breadcrumb_nav {
|
||||
@include breakpoint(tablet) {
|
||||
padding-left: 20px;
|
||||
padding-top: 13px;
|
||||
}
|
||||
.breadcrumb {
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 0;
|
||||
|
||||
.breadcrumb {
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
background-color: inherit;
|
||||
|
||||
.breadcrumb-item {
|
||||
.breadcrumb-item {
|
||||
color: white;
|
||||
display: inline-block;
|
||||
a {
|
||||
color: white;
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb-item + .breadcrumb-item::before {
|
||||
content: ">";
|
||||
color: white;
|
||||
}
|
||||
.breadcrumb-item + .breadcrumb-item::before {
|
||||
content: ">";
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -155,13 +162,21 @@ nav, etc which are used site-wide */
|
|||
}
|
||||
|
||||
.thumbnail {
|
||||
float: right;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
right: 70px;
|
||||
height: 70px;
|
||||
margin-top: -47px;
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
border-radius: 5px;
|
||||
object-fit: cover;
|
||||
margin-left: 5px;
|
||||
@include breakpoint(tablet) {
|
||||
height: 70px;
|
||||
width: 133px;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,8 +230,9 @@ nav, etc which are used site-wide */
|
|||
float: right;
|
||||
display: block;
|
||||
width: 55px;
|
||||
padding: 5px;
|
||||
padding-right: 0;
|
||||
padding: 0 0 5px 5px;
|
||||
line-height: 22px;
|
||||
font-size: 14px;
|
||||
|
||||
div {
|
||||
border: solid 1px $primary-colour;
|
||||
|
|
|
@ -355,6 +355,16 @@ nav.navbar {
|
|||
background-color: #0071CE;
|
||||
}
|
||||
|
||||
#outer_container {
|
||||
margin-top: -4px;
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
#outer_container {
|
||||
margin-top: 1rem;
|
||||
padding-top: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 140%;
|
||||
}
|
||||
|
@ -387,20 +397,36 @@ nav.navbar {
|
|||
padding-top: 8px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
.main_pane .url_thumbnail {
|
||||
width: 120px;
|
||||
height: auto;
|
||||
}
|
||||
.main_pane .url_thumbnail img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.community_icon {
|
||||
width: 30px;
|
||||
height: auto;
|
||||
width: 20vw;
|
||||
height: 20vw;
|
||||
max-width: 30px;
|
||||
max-height: 30px;
|
||||
min-width: 20px;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.community_icon_big {
|
||||
width: 120px;
|
||||
height: auto;
|
||||
width: 20vw;
|
||||
height: 20vw;
|
||||
max-width: 120px;
|
||||
max-height: 120px;
|
||||
min-width: 80px;
|
||||
min-height: 80px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.bump_up {
|
||||
position: absolute;
|
||||
top: 104px;
|
||||
top: 115px;
|
||||
left: 26px;
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,14 @@ nav.navbar {
|
|||
}
|
||||
}
|
||||
|
||||
#outer_container {
|
||||
margin-top: -4px;
|
||||
@include breakpoint(tablet) {
|
||||
margin-top: 1rem;
|
||||
padding-top: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 140%;
|
||||
}
|
||||
|
@ -85,21 +93,39 @@ nav.navbar {
|
|||
background-color: white;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 12px;
|
||||
|
||||
.url_thumbnail {
|
||||
width: 120px;
|
||||
height: auto;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.community_icon {
|
||||
width: 30px;
|
||||
height: auto;
|
||||
width: 20vw;
|
||||
height: 20vw;
|
||||
max-width: 30px;
|
||||
max-height: 30px;
|
||||
min-width: 20px;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.community_icon_big {
|
||||
width: 120px;
|
||||
height: auto;
|
||||
width: 20vw;
|
||||
height: 20vw;
|
||||
max-width: 120px;
|
||||
max-height: 120px;
|
||||
min-width: 80px;
|
||||
min-height: 80px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.bump_up {
|
||||
position: absolute;
|
||||
top: 104px;
|
||||
top: 115px;
|
||||
left: 26px;
|
||||
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="outer_container" class="container-lg flex-shrink-0 mt-3 pt-1">
|
||||
<div id="outer_container" class="container-lg flex-shrink-0">
|
||||
{% with messages = get_flashed_messages(with_categories=True) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<div class="row">
|
||||
<div class="col-12 col-md-8 position-relative main_pane">
|
||||
{% if community.header_image() != '' %}
|
||||
<div class="community_header" style="height: 240px; background-image: url({{ community.header_image() }});">
|
||||
<div class="community_header" style="background-image: url({{ community.header_image() }});">
|
||||
<nav aria-label="breadcrumb" id="breadcrumb_nav" title="Navigation">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">{{ _('Home') }}</a></li>
|
||||
|
@ -88,18 +88,18 @@
|
|||
<p>{{ community.rules|safe }}</p>
|
||||
{% if len(mods) > 0 and not community.private_mods %}
|
||||
<h3>Moderators</h3>
|
||||
<ol>
|
||||
<ul>
|
||||
{% for mod in mods %}
|
||||
<li>{{ render_username(mod) }}</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</ul>
|
||||
{% endif %}
|
||||
<p class="mt-4">
|
||||
<a class="no-underline" href="{{ rss_feed }}" rel="nofollow"><span class="fe fe-rss"></span> RSS feed</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% if is_moderator or current_user.is_admin() %}
|
||||
{% if is_moderator or is_admin %}
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h2>{{ _('Community Settings') }}</h2>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{% from 'bootstrap5/form.html' import render_form %}
|
||||
|
||||
{% block app_content %}
|
||||
<div class="row g-2 justify-content-between">
|
||||
<div class="row g-2 justify-content-between mt-2">
|
||||
<div class="col-auto">
|
||||
<div class="btn-group">
|
||||
<a href="/communities" class="btn {{ 'btn-primary' if request.path == '/communities' else 'btn-outline-secondary' }}">
|
||||
|
|
|
@ -39,13 +39,13 @@
|
|||
<div class="voting_buttons">
|
||||
{% include "post/_post_voting_buttons.html" %}
|
||||
</div>
|
||||
<h1 class="mt-2">{{ post.title }}</h1>
|
||||
{% if post.type == POST_TYPE_LINK and post.image_id and not (post.url and 'youtube.com' in post.url) %}
|
||||
<div class="url_thumbnail">
|
||||
<img src="{{ post.image.thumbnail_url() }}" alt="{{ post.image.alt_text }}"
|
||||
width="{{ post.image.thumbnail_width }}" height="{{ post.image.thumbnail_height }}" />
|
||||
<a href="{{ post.url }}"><img src="{{ post.image.thumbnail_url() }}" alt="{{ post.image.alt_text }}"
|
||||
width="{{ post.image.thumbnail_width }}" height="{{ post.image.thumbnail_height }}" /></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<h1 class="mt-2">{{ post.title }}</h1>
|
||||
<p class="small">submitted {{ moment(post.posted_at).fromNow() }} by
|
||||
{{ render_username(post.author) }}
|
||||
{% if post.edited_at %} edited {{ moment(post.edited_at).fromNow() }}{% endif %}
|
||||
|
@ -68,17 +68,8 @@
|
|||
width="{{ post.image.thumbnail_width }}" height="{{ post.image.thumbnail_height }}" /></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
{{ post.body_html|safe if post.body_html else '' }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('post.post_options', post_id=post.id) }}" class="post_options_link" rel="nofollow"><span class="fe fe-options" title="Options"> </span></a>
|
||||
</div>
|
||||
|
||||
{% if post.body_html %}
|
||||
<div class="row post_full">
|
||||
<div class="col">
|
||||
{{ post.body_html|safe }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
|
@ -1,9 +1,19 @@
|
|||
<div class="post_teaser">
|
||||
<div class="row">
|
||||
<div class="col col-md-10">
|
||||
<div class="col-12">
|
||||
<div class="row main_row">
|
||||
<div class="col">
|
||||
<h3>
|
||||
<div class="voting_buttons">
|
||||
{% include "post/_post_voting_buttons.html" %}
|
||||
</div>
|
||||
{% if post.image_id %}
|
||||
<div class="thumbnail">
|
||||
<a href="{{ url_for('activitypub.post_ap', post_id=post.id) }}"><img src="{{ post.image.thumbnail_url() }}"
|
||||
alt="{{ post.image.alt_text if post.image.alt_text else '' }}"
|
||||
height="50" /></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('activitypub.post_ap', post_id=post.id) }}">{{ post.title }}</a>
|
||||
{% if post.type == POST_TYPE_IMAGE %}<span class="fe fe-image"> </span>{% endif %}
|
||||
{% if post.type == POST_TYPE_LINK and post.domain_id %}
|
||||
|
@ -17,13 +27,7 @@
|
|||
{% endif %}
|
||||
</h3>
|
||||
<span class="small">{{ render_username(post.author) }} · {{ moment(post.posted_at).fromNow() }}</span>
|
||||
{% if post.image_id %}
|
||||
<div class="thumbnail">
|
||||
<a href="{{ url_for('activitypub.post_ap', post_id=post.id) }}"><img src="{{ post.image.thumbnail_url() }}"
|
||||
alt="{{ post.image.alt_text if post.image.alt_text else '' }}"
|
||||
height="50" /></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -35,11 +39,6 @@
|
|||
<div class="col-2"><a href="{{ url_for('post.post_options', post_id=post.id) }}" rel="nofollow"><span class="fe fe-options" title="Options"> </span></a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-md-2">
|
||||
<div class="voting_buttons pt-2">
|
||||
{% include "post/_post_voting_buttons.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue