diff --git a/app/models.py b/app/models.py index acc0316b..5a65324e 100644 --- a/app/models.py +++ b/app/models.py @@ -1442,6 +1442,20 @@ class PollChoiceVote(db.Model): created_at = db.Column(db.DateTime, default=utcnow) +class PostBookmark(db.Model): + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True) + post_id = db.Column(db.Integer, db.ForeignKey('post.id'), index=True) + created_at = db.Column(db.DateTime, default=utcnow) + + +class PostReplyBookmark(db.Model): + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True) + post_reply_id = db.Column(db.Integer, db.ForeignKey('post_reply.id'), index=True) + created_at = db.Column(db.DateTime, default=utcnow) + + class IpBan(db.Model): id = db.Column(db.Integer, primary_key=True) ip_address = db.Column(db.String(50), index=True) diff --git a/app/post/routes.py b/app/post/routes.py index 43f1b998..eb467f91 100644 --- a/app/post/routes.py +++ b/app/post/routes.py @@ -22,7 +22,8 @@ from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, SUBSCRIPTION_ POST_TYPE_ARTICLE, POST_TYPE_VIDEO, NOTIF_REPLY, NOTIF_POST, POST_TYPE_POLL from app.models import Post, PostReply, \ PostReplyVote, PostVote, Notification, utcnow, UserBlock, DomainBlock, InstanceBlock, Report, Site, Community, \ - Topic, User, Instance, NotificationSubscription, UserFollower, Poll, PollChoice, PollChoiceVote + Topic, User, Instance, NotificationSubscription, UserFollower, Poll, PollChoice, PollChoiceVote, PostBookmark, \ + PostReplyBookmark from app.post import bp from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \ shorten_string, markdown_to_text, gibberish, ap_datetime, return_304, \ @@ -1591,6 +1592,22 @@ def post_restore(post_id: int): return redirect(url_for('activitypub.post_ap', post_id=post.id)) +@bp.route('/post//bookmark', methods=['GET', 'POST']) +@login_required +def post_bookmark(post_id: int): + post = Post.query.get_or_404(post_id) + if post.deleted: + abort(404) + existing_bookmark = PostBookmark.query.filter(PostBookmark.post_id == post_id, PostBookmark.user_id == current_user.id).first() + if not existing_bookmark: + db.session.add(PostBookmark(post_id=post_id, user_id=current_user.id)) + db.session.commit() + flash(_('Bookmark added.')) + else: + flash(_('This post has already been bookmarked.')) + return redirect(url_for('activitypub.post_ap', post_id=post.id)) + + @bp.route('/post//report', methods=['GET', 'POST']) @login_required def post_report(post_id: int): @@ -1786,7 +1803,7 @@ def post_reply_report(post_id: int, comment_id: int): flash('Failed to send report to remote server', 'error') flash(_('Comment has been reported, thank you!')) - return redirect(post.community.local_url()) + return redirect(url_for('activitypub.post_ap', post_id=post.id)) elif request.method == 'GET': form.report_remote.data = True @@ -1797,6 +1814,25 @@ def post_reply_report(post_id: int, comment_id: int): ) +@bp.route('/post//comment//bookmark', methods=['GET']) +@login_required +def post_reply_bookmark(post_id: int, comment_id: int): + post = Post.query.get_or_404(post_id) + post_reply = PostReply.query.get_or_404(comment_id) + + if post.deleted or post_reply.deleted: + abort(404) + existing_bookmark = PostReplyBookmark.query.filter(PostReplyBookmark.post_reply_id == comment_id, + PostReplyBookmark.user_id == current_user.id).first() + if not existing_bookmark: + db.session.add(PostReplyBookmark(post_reply_id=comment_id, user_id=current_user.id)) + db.session.commit() + flash(_('Bookmark added.')) + else: + flash(_('This comment has already been bookmarked.')) + return redirect(url_for('activitypub.post_ap', post_id=post.id, _anchor=f'comment_{comment_id}')) + + @bp.route('/post//comment//block_user', methods=['GET', 'POST']) @login_required def post_reply_block_user(post_id: int, comment_id: int): diff --git a/app/static/scss/_typography.scss b/app/static/scss/_typography.scss index ae158733..f13d5408 100644 --- a/app/static/scss/_typography.scss +++ b/app/static/scss/_typography.scss @@ -243,6 +243,10 @@ content: "\e995"; } +.fe-bookmark::before { + content: "\e924"; +} + .fe-expand::before { content: "\e98e"; } diff --git a/app/static/structure.css b/app/static/structure.css index ea03e572..57f024de 100644 --- a/app/static/structure.css +++ b/app/static/structure.css @@ -275,6 +275,10 @@ nav, etc which are used site-wide */ content: "\e995"; } +.fe-bookmark::before { + content: "\e924"; +} + .fe-expand::before { content: "\e98e"; } diff --git a/app/static/styles.css b/app/static/styles.css index c269e001..67dc0161 100644 --- a/app/static/styles.css +++ b/app/static/styles.css @@ -274,6 +274,10 @@ content: "\e995"; } +.fe-bookmark::before { + content: "\e924"; +} + .fe-expand::before { content: "\e98e"; } @@ -687,6 +691,10 @@ div.navbar { .option_list li { margin-bottom: 15px; } +.option_list li .fe { + position: relative; + top: 1px; +} .field_hint { margin-top: -15px; diff --git a/app/static/styles.scss b/app/static/styles.scss index 07fc6499..f12ce699 100644 --- a/app/static/styles.scss +++ b/app/static/styles.scss @@ -272,6 +272,11 @@ div.navbar { li { margin-bottom: 15px; + + .fe { + position: relative; + top: 1px; + } } } diff --git a/app/templates/post/post_options.html b/app/templates/post/post_options.html index 2386e687..fb72f87f 100644 --- a/app/templates/post/post_options.html +++ b/app/templates/post/post_options.html @@ -26,6 +26,8 @@ {{ _('Delete') }} {% endif -%} {% endif -%} +
  • + {{ _('Bookmark') }}
  • {% if post.user_id == current_user.id and not post.mea_culpa -%}
  • {{ _("I made a mistake with this post and have changed my mind about the topic") }}
  • diff --git a/app/templates/post/post_reply_options.html b/app/templates/post/post_reply_options.html index 5dc8cb84..211f107a 100644 --- a/app/templates/post/post_reply_options.html +++ b/app/templates/post/post_reply_options.html @@ -26,6 +26,8 @@ {{ _('Delete') }} {% endif -%} {% endif -%} +
  • + {{ _('Bookmark') }}
  • {% if post_reply.user_id != current_user.id -%}
  • {{ _('Block author @%(author_name)s', author_name=post_reply.author.user_name) }}
  • diff --git a/migrations/versions/745e3e985199_save_posts.py b/migrations/versions/745e3e985199_save_posts.py new file mode 100644 index 00000000..c9af1efc --- /dev/null +++ b/migrations/versions/745e3e985199_save_posts.py @@ -0,0 +1,62 @@ +"""save posts + +Revision ID: 745e3e985199 +Revises: 1c51c3df2770 +Create Date: 2024-06-20 21:32:11.695162 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '745e3e985199' +down_revision = '1c51c3df2770' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('post_bookmark', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('post_id', sa.Integer(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['post_id'], ['post.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('post_bookmark', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_post_bookmark_post_id'), ['post_id'], unique=False) + batch_op.create_index(batch_op.f('ix_post_bookmark_user_id'), ['user_id'], unique=False) + + op.create_table('post_reply_bookmark', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('post_reply_id', sa.Integer(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['post_reply_id'], ['post_reply.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('post_reply_bookmark', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_post_reply_bookmark_post_reply_id'), ['post_reply_id'], unique=False) + batch_op.create_index(batch_op.f('ix_post_reply_bookmark_user_id'), ['user_id'], unique=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('post_reply_bookmark', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_post_reply_bookmark_user_id')) + batch_op.drop_index(batch_op.f('ix_post_reply_bookmark_post_reply_id')) + + op.drop_table('post_reply_bookmark') + with op.batch_alter_table('post_bookmark', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_post_bookmark_user_id')) + batch_op.drop_index(batch_op.f('ix_post_bookmark_post_id')) + + op.drop_table('post_bookmark') + # ### end Alembic commands ###