diff --git a/app/community/forms.py b/app/community/forms.py index 89a6b537..c4c18162 100644 --- a/app/community/forms.py +++ b/app/community/forms.py @@ -44,7 +44,7 @@ class CreatePostForm(FlaskForm): # flair = SelectField(_l('Flair'), coerce=int) nsfw = BooleanField(_l('NSFW')) nsfl = BooleanField(_l('NSFL')) - notify_author = BooleanField(_l('Send me post reply notifications')) + notify_author = BooleanField(_l('Notify about replies')) submit = SubmitField(_l('Post')) def validate(self, extra_validators=None) -> bool: diff --git a/app/models.py b/app/models.py index 92dfc65e..f83d9fd0 100644 --- a/app/models.py +++ b/app/models.py @@ -180,6 +180,7 @@ class User(UserMixin, db.Model): indexable = db.Column(db.Boolean, default=False) bot = db.Column(db.Boolean, default=False) ignore_bots = db.Column(db.Boolean, default=False) + unread_notifications = db.Column(db.Integer, default=0) avatar = db.relationship('File', foreign_keys=[avatar_id], single_parent=True, cascade="all, delete-orphan") cover = db.relationship('File', foreign_keys=[cover_id], single_parent=True, cascade="all, delete-orphan") @@ -312,6 +313,9 @@ class User(UserMixin, db.Model): return User.query.get(id) def purge_content(self): + files = File.query.join(Post).filter(Post.user_id == self.id).all() + for file in files: + file.delete_from_disk() db.session.query(ActivityLog).filter(ActivityLog.user_id == self.id).delete() db.session.query(PostVote).filter(PostVote.user_id == self.id).delete() db.session.query(PostReplyVote).filter(PostReplyVote.user_id == self.id).delete() diff --git a/app/post/forms.py b/app/post/forms.py index 35d2e85e..1a21decb 100644 --- a/app/post/forms.py +++ b/app/post/forms.py @@ -1,9 +1,10 @@ from flask_wtf import FlaskForm -from wtforms import TextAreaField, SubmitField +from wtforms import TextAreaField, SubmitField, BooleanField from wtforms.validators import DataRequired, Length from flask_babel import _, lazy_gettext as _l class NewReplyForm(FlaskForm): body = TextAreaField(_l('Body'), render_kw={'placeholder': 'What are your thoughts?', 'rows': 3}, validators={DataRequired(), Length(min=3, max=5000)}) + notify_author = BooleanField(_l('Notify about replies')) submit = SubmitField(_l('Comment')) \ No newline at end of file diff --git a/app/post/routes.py b/app/post/routes.py index 9f7f1a6d..acc61dc2 100644 --- a/app/post/routes.py +++ b/app/post/routes.py @@ -12,7 +12,7 @@ from app.community.forms import CreatePostForm from app.post.util import post_replies, 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 Post, PostReply, \ - PostReplyVote, PostVote + PostReplyVote, PostVote, Notification 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, domain_from_url, validate_image, gibberish @@ -27,7 +27,12 @@ def show_post(post_id: int): if current_user.is_authenticated and current_user.verified and form.validate_on_submit(): reply = PostReply(user_id=current_user.id, post_id=post.id, community_id=post.community.id, body=form.body.data, body_html=markdown_to_html(form.body.data), body_html_safe=True, - from_bot=current_user.bot, up_votes=1, nsfw=post.nsfw, nsfl=post.nsfl) + from_bot=current_user.bot, up_votes=1, nsfw=post.nsfw, nsfl=post.nsfl, + notify_author=form.notify_author.data) + if post.notify_author and current_user.id != post.user_id: # todo: check if replier is blocked + notification = Notification(title=_('Reply: ') + shorten_string(form.body.data), user_id=post.user_id, + author_id=current_user.id, url=url_for('post.show_post', post_id=post.id)) + db.session.add(notification) db.session.add(reply) db.session.commit() reply_vote = PostReplyVote(user_id=current_user.id, author_id=current_user.id, post_reply_id=reply.id, @@ -42,6 +47,7 @@ def show_post(post_id: int): post_id=post_id)) # redirect to current page to avoid refresh resubmitting the form else: replies = post_replies(post.id, 'top') + form.notify_author.data = True og_image = post.image.source_url if post.image_id else None description = shorten_string(markdown_to_text(post.body), 150) if post.body else None @@ -178,8 +184,13 @@ def add_reply(post_id: int, comment_id: int): reply = PostReply(user_id=current_user.id, post_id=post.id, parent_id=comment.id, depth=comment.depth + 1, community_id=post.community.id, body=form.body.data, body_html=markdown_to_html(form.body.data), body_html_safe=True, - from_bot=current_user.bot, up_votes=1, nsfw=post.nsfw, nsfl=post.nsfl) + from_bot=current_user.bot, up_votes=1, nsfw=post.nsfw, nsfl=post.nsfl, + notify_author=form.notify_author.data) db.session.add(reply) + if comment.notify_author and current_user.id != comment.user_id: # todo: check if replier is blocked + notification = Notification(title=_('Reply: ') + shorten_string(form.body.data), user_id=comment.user_id, + author_id=current_user.id, url=url_for('post.show_post', post_id=post.id)) + db.session.add(notification) db.session.commit() reply_vote = PostReplyVote(user_id=current_user.id, author_id=current_user.id, post_reply_id=reply.id, effect=1.0) @@ -195,6 +206,7 @@ def add_reply(post_id: int, comment_id: int): else: return redirect(url_for('post.continue_discussion', post_id=post_id, comment_id=reply.parent_id)) else: + form.notify_author.data = True return render_template('post/add_reply.html', title=_('Discussing %(title)s', title=post.title), post=post, is_moderator=is_moderator, form=form, comment=comment) diff --git a/app/static/scss/_typography.scss b/app/static/scss/_typography.scss index fbf9d08e..b9a49587 100644 --- a/app/static/scss/_typography.scss +++ b/app/static/scss/_typography.scss @@ -182,6 +182,10 @@ content: "\e967"; } +.fe-bell::before { + content: "\e91e"; +} + .fe-image { position: relative; top: 2px; diff --git a/app/static/structure.css b/app/static/structure.css index e44bf55b..db796065 100644 --- a/app/static/structure.css +++ b/app/static/structure.css @@ -185,6 +185,10 @@ nav, etc which are used site-wide */ content: "\e967"; } +.fe-bell::before { + content: "\e91e"; +} + .fe-image { position: relative; top: 2px; @@ -403,6 +407,23 @@ fieldset legend { .post_reply_form label { display: none; } +.post_reply_form .form-check { + position: absolute; + bottom: -14px; + left: 122px; +} +.post_reply_form .form-check label { + display: inherit; +} + +.add_reply .form-control-label { + display: none; +} +.add_reply .form-check { + position: absolute; + bottom: -14px; + left: 122px; +} .post_list .post_teaser { border-bottom: solid 2px #ddd; @@ -548,8 +569,8 @@ fieldset legend { border-top: solid 1px #ddd; } -.add_reply .form-control-label { - display: none; +.table tr th { + vertical-align: middle; } /*# sourceMappingURL=structure.css.map */ diff --git a/app/static/structure.scss b/app/static/structure.scss index 5af70785..891e6df3 100644 --- a/app/static/structure.scss +++ b/app/static/structure.scss @@ -115,7 +115,30 @@ nav, etc which are used site-wide */ label { display: none; } + + .form-check { + position: absolute; + bottom: -14px; + left: 122px; + + label { + display: inherit; + } + } } + +.add_reply { + .form-control-label { + display: none; + } + + .form-check { + position: absolute; + bottom: -14px; + left: 122px; + } +} + .post_list { .post_teaser { @@ -294,8 +317,8 @@ nav, etc which are used site-wide */ } } -.add_reply { - .form-control-label { - display: none; +.table { + tr th { + vertical-align: middle; } } \ No newline at end of file diff --git a/app/static/styles.css b/app/static/styles.css index cfbb71a7..ccd1645a 100644 --- a/app/static/styles.css +++ b/app/static/styles.css @@ -184,6 +184,10 @@ content: "\e967"; } +.fe-bell::before { + content: "\e91e"; +} + .fe-image { position: relative; top: 2px; diff --git a/app/templates/base.html b/app/templates/base.html index 5c980ce1..f2941a8f 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -71,11 +71,13 @@ {% else %}
Notification | +When | +Mark all read | +
---|---|---|
{% if not notification.read %}{% endif %} + {{ notification.title }} + {% if not notification.read %}{% endif %} + | +{{ moment(notification.created_at).fromNow(refresh=True) }} | ++ Delete + | +
No notifications to show.
+ {% endif %} +