From 136cb64a691d37bdf2337d9d85eb38ff3f54b0c8 Mon Sep 17 00:00:00 2001 From: freamon Date: Tue, 19 Mar 2024 07:34:19 +0000 Subject: [PATCH 1/3] Checked featured url for sticky posts #16 --- app/activitypub/util.py | 1 + app/community/util.py | 12 +++++++ app/models.py | 1 + .../versions/12d60b9d5417_ap_featured_url.py | 32 +++++++++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 migrations/versions/12d60b9d5417_ap_featured_url.py diff --git a/app/activitypub/util.py b/app/activitypub/util.py index 63c419e3..a094b19f 100644 --- a/app/activitypub/util.py +++ b/app/activitypub/util.py @@ -562,6 +562,7 @@ def actor_json_to_model(activity_json, address, server): ap_followers_url=activity_json['followers'], ap_inbox_url=activity_json['endpoints']['sharedInbox'], ap_outbox_url=activity_json['outbox'], + ap_featured_url=activity_json['featured'], ap_moderators_url=mods_url, ap_fetched_at=utcnow(), ap_domain=server, diff --git a/app/community/util.py b/app/community/util.py index b07e2c16..f96e915d 100644 --- a/app/community/util.py +++ b/app/community/util.py @@ -121,6 +121,18 @@ def retrieve_mods_and_backfill(community_id: int): if c.post_count > 0: c.last_active = Post.query.filter(Post.community_id == community_id).order_by(desc(Post.posted_at)).first().posted_at db.session.commit() + if community.ap_featured_url: + featured_request = get_request(community.ap_featured_url, headers={'Accept': 'application/activityjson'}) + if featured_request.status_code == 200: + featured_data = featured_request.json() + featured_request.close() + if featured_data['type'] == 'OrderedCollection' and 'orderedItems' in featured_data: + for item in featured_data['orderedItems']: + featured_id = item['id'] + p = Post.query.filter(Post.ap_id == featured_id).first() + if p: + p.sticky = True + db.session.commit() def community_url_exists(url) -> bool: diff --git a/app/models.py b/app/models.py index 10f792b2..3a82c5b9 100644 --- a/app/models.py +++ b/app/models.py @@ -266,6 +266,7 @@ class Community(db.Model): ap_deleted_at = db.Column(db.DateTime) ap_inbox_url = db.Column(db.String(255)) ap_outbox_url = db.Column(db.String(255)) + ap_featured_url = db.Column(db.String(255)) ap_moderators_url = db.Column(db.String(255)) ap_domain = db.Column(db.String(255)) diff --git a/migrations/versions/12d60b9d5417_ap_featured_url.py b/migrations/versions/12d60b9d5417_ap_featured_url.py new file mode 100644 index 00000000..9311d82e --- /dev/null +++ b/migrations/versions/12d60b9d5417_ap_featured_url.py @@ -0,0 +1,32 @@ +"""ap_featured_url + +Revision ID: 12d60b9d5417 +Revises: 81175e11c083 +Create Date: 2024-03-19 07:05:04.588051 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '12d60b9d5417' +down_revision = '81175e11c083' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('community', schema=None) as batch_op: + batch_op.add_column(sa.Column('ap_featured_url', sa.String(length=255), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('community', schema=None) as batch_op: + batch_op.drop_column('ap_featured_url') + + # ### end Alembic commands ### From e49cf220e9dddd7b030602a3bae0dfd066270f10 Mon Sep 17 00:00:00 2001 From: freamon Date: Tue, 19 Mar 2024 15:38:35 +0000 Subject: [PATCH 2/3] Add/Remove activity for featured posts --- app/activitypub/routes.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/activitypub/routes.py b/app/activitypub/routes.py index 9df2ef01..1084de75 100644 --- a/app/activitypub/routes.py +++ b/app/activitypub/routes.py @@ -666,6 +666,24 @@ def process_inbox_request(request_json, activitypublog_id, ip_address): target_ap_id = request_json['object']['object']['object'] # object object object! post = undo_vote(activity_log, comment, post, target_ap_id, user) activity_log.result = 'success' + elif request_json['object']['type'] == 'Add': + activity_log.activity_type = request_json['object']['type'] + featured_url = Community.query.filter(Community.ap_public_url == request_json['actor']).first().ap_featured_url + if featured_url: + if 'target' in request_json['object'] and featured_url == request_json['object']['target']: + post = Post.query.filter(Post.ap_id == request_json['object']['object']).first() + if post: + post.sticky = True + activity_log.result = 'success' + elif request_json['object']['type'] == 'Remove': + activity_log.activity_type = request_json['object']['type'] + featured_url = Community.query.filter(Community.ap_public_url == request_json['actor']).first().ap_featured_url + if featured_url: + if 'target' in request_json['object'] and featured_url == request_json['object']['target']: + post = Post.query.filter(Post.ap_id == request_json['object']['object']).first() + if post: + post.sticky = False + activity_log.result = 'success' else: activity_log.exception_message = 'Invalid type for Announce' From 0210adafa5d55ff760eea88ba1508b788ef46d4b Mon Sep 17 00:00:00 2001 From: rimu <3310831+rimu@users.noreply.github.com> Date: Wed, 20 Mar 2024 20:41:45 +1300 Subject: [PATCH 3/3] show sticky posts at top of their community fixes #16 --- app/community/forms.py | 1 + app/community/routes.py | 11 ++++++++--- app/community/util.py | 1 + app/post/routes.py | 4 ++++ app/static/scss/_typography.scss | 13 +++++++++++++ app/static/structure.css | 12 ++++++++++++ app/static/styles.css | 12 ++++++++++++ app/templates/community/add_post.html | 3 +++ app/templates/post/_post_teaser.html | 3 ++- app/templates/post/post_edit.html | 3 +++ 10 files changed, 59 insertions(+), 4 deletions(-) diff --git a/app/community/forms.py b/app/community/forms.py index 4d293fa2..bb700ede 100644 --- a/app/community/forms.py +++ b/app/community/forms.py @@ -89,6 +89,7 @@ class CreatePostForm(FlaskForm): render_kw={'placeholder': 'Text (optional)'}) image_file = FileField(_('Image')) # flair = SelectField(_l('Flair'), coerce=int) + sticky = BooleanField(_l('Sticky')) nsfw = BooleanField(_l('NSFW')) nsfl = BooleanField(_l('Gore/gross')) notify_author = BooleanField(_l('Notify about replies')) diff --git a/app/community/routes.py b/app/community/routes.py index ce09ad64..ed350e22 100644 --- a/app/community/routes.py +++ b/app/community/routes.py @@ -120,6 +120,8 @@ def show_community(community: Community): page = request.args.get('page', 1, type=int) sort = request.args.get('sort', '' if current_user.is_anonymous else current_user.default_sort) + if sort is None: + sort = '' low_bandwidth = request.cookies.get('low_bandwidth', '0') == '1' if low_bandwidth: post_layout = None @@ -171,13 +173,13 @@ def show_community(community: Community): posts = posts.filter(or_(Post.instance_id.not_in(instance_ids), Post.instance_id == None)) if sort == '' or sort == 'hot': - posts = posts.order_by(desc(Post.ranking)).order_by(desc(Post.posted_at)) + posts = posts.order_by(desc(Post.sticky)).order_by(desc(Post.ranking)).order_by(desc(Post.posted_at)) elif sort == 'top': - posts = posts.filter(Post.posted_at > utcnow() - timedelta(days=7)).order_by(desc(Post.score)) + posts = posts.filter(Post.posted_at > utcnow() - timedelta(days=7)).order_by(desc(Post.sticky)).order_by(desc(Post.score)) elif sort == 'new': posts = posts.order_by(desc(Post.posted_at)) elif sort == 'active': - posts = posts.order_by(desc(Post.last_active)) + posts = posts.order_by(desc(Post.sticky)).order_by(desc(Post.last_active)) per_page = 100 if post_layout == 'masonry': per_page = 200 @@ -444,6 +446,8 @@ def add_post(actor): if community.nsfl: form.nsfl.data = True form.nsfw.render_kw = {'disabled': True} + if not(community.is_moderator() or community.is_owner() or current_user.is_admin()): + form.sticky.render_kw = {'disabled': True} form.communities.choices = [(c.id, c.display_name()) for c in current_user.communities()] @@ -485,6 +489,7 @@ def add_post(actor): 'commentsEnabled': post.comments_enabled, 'sensitive': post.nsfw, 'nsfl': post.nsfl, + 'stickied': post.sticky, 'published': ap_datetime(utcnow()), 'audience': community.ap_profile_id } diff --git a/app/community/util.py b/app/community/util.py index f96e915d..aedb3615 100644 --- a/app/community/util.py +++ b/app/community/util.py @@ -185,6 +185,7 @@ def url_to_thumbnail_file(filename) -> File: def save_post(form, post: Post): post.indexable = current_user.indexable + post.sticky = form.sticky.data post.nsfw = form.nsfw.data post.nsfl = form.nsfl.data post.notify_author = form.notify_author.data diff --git a/app/post/routes.py b/app/post/routes.py index 568a4518..f506c68f 100644 --- a/app/post/routes.py +++ b/app/post/routes.py @@ -649,6 +649,7 @@ def post_edit(post_id: int): 'commentsEnabled': post.comments_enabled, 'sensitive': post.nsfw, 'nsfl': post.nsfl, + 'stickied': post.sticky, 'published': ap_datetime(post.posted_at), 'updated': ap_datetime(post.edited_at), 'audience': post.community.ap_profile_id @@ -722,6 +723,9 @@ def post_edit(post_id: int): form.notify_author.data = post.notify_author form.nsfw.data = post.nsfw form.nsfl.data = post.nsfl + form.sticky.data = post.sticky + if not (post.community.is_moderator() or post.community.is_owner() or current_user.is_admin()): + form.sticky.render_kw = {'disabled': True} return render_template('post/post_edit.html', title=_('Edit post'), form=form, post=post, markdown_editor=current_user.markdown_editor, moderating_communities=moderating_communities(current_user.get_id()), diff --git a/app/static/scss/_typography.scss b/app/static/scss/_typography.scss index a5cdd97a..780e81d2 100644 --- a/app/static/scss/_typography.scss +++ b/app/static/scss/_typography.scss @@ -309,6 +309,19 @@ h1 { } } +.fe-sticky-left::before { + position: relative; + top: 2px; + content: "\e934"; +} + +.fe-sticky-right::before { + position: relative; + top: 2px; + content: "\e933"; +} + + a.no-underline { text-decoration: none; &:hover { diff --git a/app/static/structure.css b/app/static/structure.css index ec7d61e8..0500d613 100644 --- a/app/static/structure.css +++ b/app/static/structure.css @@ -333,6 +333,18 @@ h1 .fe-bell, h1 .fe-no-bell { /* possibly e985 */ } +.fe-sticky-left::before { + position: relative; + top: 2px; + content: "\e934"; +} + +.fe-sticky-right::before { + position: relative; + top: 2px; + content: "\e933"; +} + a.no-underline { text-decoration: none; } diff --git a/app/static/styles.css b/app/static/styles.css index abe90c6f..149e1db0 100644 --- a/app/static/styles.css +++ b/app/static/styles.css @@ -332,6 +332,18 @@ h1 .fe-bell, h1 .fe-no-bell { /* possibly e985 */ } +.fe-sticky-left::before { + position: relative; + top: 2px; + content: "\e934"; +} + +.fe-sticky-right::before { + position: relative; + top: 2px; + content: "\e933"; +} + a.no-underline { text-decoration: none; } diff --git a/app/templates/community/add_post.html b/app/templates/community/add_post.html index 1f0a07c3..f957d17a 100644 --- a/app/templates/community/add_post.html +++ b/app/templates/community/add_post.html @@ -98,6 +98,9 @@
{{ render_field(form.notify_author) }}
+
+ {{ render_field(form.sticky) }} +
{{ render_field(form.nsfw) }}
diff --git a/app/templates/post/_post_teaser.html b/app/templates/post/_post_teaser.html index 8905c4dc..410c5ba0 100644 --- a/app/templates/post/_post_teaser.html +++ b/app/templates/post/_post_teaser.html @@ -43,7 +43,7 @@ {% endif %} {% endif %} -

+

{% if post.sticky %}{% endif %} {% if post.type == POST_TYPE_IMAGE %}{% endif %} {% if post.type == POST_TYPE_LINK and post.domain_id %} {% if post.url and 'youtube.com' in post.url %} @@ -58,6 +58,7 @@ {% if post.reports and current_user.is_authenticated and post.community.is_moderator(current_user) %} {% endif %} + {% if post.sticky %}{% endif %}

{% if show_post_community %}c/{{ post.community.name }}{% endif %} diff --git a/app/templates/post/post_edit.html b/app/templates/post/post_edit.html index 6c5581aa..515993de 100644 --- a/app/templates/post/post_edit.html +++ b/app/templates/post/post_edit.html @@ -120,6 +120,9 @@
{{ render_field(form.notify_author) }}
+
+ {{ render_field(form.sticky) }} +
{{ render_field(form.nsfw) }}