diff --git a/app/models.py b/app/models.py
index 1c8c3f26..7aae3641 100644
--- a/app/models.py
+++ b/app/models.py
@@ -710,6 +710,7 @@ class User(UserMixin, db.Model):
cover = db.relationship('File', lazy='joined', foreign_keys=[cover_id], single_parent=True, cascade="all, delete-orphan")
instance = db.relationship('Instance', lazy='joined', foreign_keys=[instance_id])
conversations = db.relationship('Conversation', lazy='dynamic', secondary=conversation_member, backref=db.backref('members', lazy='joined'))
+ user_notes = db.relationship('UserNote', lazy='dynamic', foreign_keys="UserNote.target_id")
ap_id = db.Column(db.String(255), index=True) # e.g. username@server
ap_profile_id = db.Column(db.String(255), index=True, unique=True) # e.g. https://server/u/username
@@ -1022,6 +1023,7 @@ class User(UserMixin, db.Model):
db.session.query(PostBookmark).filter(PostBookmark.user_id == self.id).delete()
db.session.query(PostReplyBookmark).filter(PostReplyBookmark.user_id == self.id).delete()
db.session.query(ModLog).filter(ModLog.user_id == self.id).delete()
+ db.session.query(UserNote).filter(or_(UserNote.user_id == self.id, UserNote.target_id == self.id)).delete()
def purge_content(self, soft=True):
files = File.query.join(Post).filter(Post.user_id == self.id).all()
@@ -1077,7 +1079,14 @@ class User(UserMixin, db.Model):
# returns true if the post has been read, false if not
def has_read_post(self, post):
return self.read_post.filter(read_posts.c.read_post_id == post.id).count() > 0
-
+
+ @cache.memoize(timeout=500)
+ def get_note(self, by_user):
+ user_note = self.user_notes.filter(UserNote.target_id == self.id, UserNote.user_id == by_user.id).first()
+ if user_note:
+ return user_note.body
+ else:
+ return None
class ActivityLog(db.Model):
diff --git a/app/templates/base.html b/app/templates/base.html
index ab2ad7b1..d48d00e8 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -27,6 +27,12 @@
{% endif -%}
{% endif -%}
+ {% if current_user.is_authenticated -%}
+ {% set user_note = user.get_note(current_user) %}
+ {% if user_note -%}
+ [{{ user_note | truncate(12, True) }}]
+ {% endif -%}
+ {% endif -%}
{% endif -%}
{% endmacro -%}
diff --git a/app/templates/user/edit_note.html b/app/templates/user/edit_note.html
new file mode 100644
index 00000000..5f003e0e
--- /dev/null
+++ b/app/templates/user/edit_note.html
@@ -0,0 +1,68 @@
+{% if theme() and file_exists('app/templates/themes/' + theme() + '/base.html') -%}
+ {% extends 'themes/' + theme() + '/base.html' %}
+{% else -%}
+ {% extends "base.html" %}
+{% endif -%}
+{% from 'bootstrap/form.html' import render_form %}
+
+{% block app_content %}
+
+
+
+
+
{{ _('Edit note for "%(user_name)s"', user_name=user.display_name()) }}
+
+
{{ _('Emoji quick access') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ render_form(form) }}
+
{{ _('This note appears next to their username. It\'s meant just for you and not displayed to anyone else.') }}
+
+
+
+
+
+
+{% endblock %}
diff --git a/app/templates/user/show_profile.html b/app/templates/user/show_profile.html
index 28a7304b..1afbddd2 100644
--- a/app/templates/user/show_profile.html
+++ b/app/templates/user/show_profile.html
@@ -100,6 +100,7 @@
{% endif -%}
{% endif -%}
{{ _('Report') }}
+ {{ _('Edit note') }}
{% endif %}
@@ -118,6 +119,7 @@
{% if current_user.is_authenticated and current_user.is_admin() and user.reputation %}{{ _('Reputation') }}: {{ user.reputation | round | int }}
{% endif %}
{{ _('Posts') }}: {{ user.post_count }}
{{ _('Comments') }}: {{ user.post_reply_count }}
+ {% if current_user.is_authenticated %}{{ _('User note') }}: {{ user.get_note(current_user) }}
{% endif %}
{{ user.about_html|safe }}
diff --git a/app/user/forms.py b/app/user/forms.py
index dc702ad2..e17407ce 100644
--- a/app/user/forms.py
+++ b/app/user/forms.py
@@ -147,3 +147,8 @@ class RemoteFollowForm(FlaskForm):
instance_type = SelectField(_l('Instance type'), choices=type_choices, render_kw={'class': 'form-select'})
submit = SubmitField(_l('View profile on remote instance'))
+
+
+class UserNoteForm(FlaskForm):
+ note = StringField(_l('User note'), validators=[Optional(), Length(max=50)])
+ submit = SubmitField(_l('Save note'))
diff --git a/app/user/routes.py b/app/user/routes.py
index 5bc30c38..fbde47f4 100644
--- a/app/user/routes.py
+++ b/app/user/routes.py
@@ -16,10 +16,10 @@ from app.constants import *
from app.email import send_verification_email
from app.models import Post, Community, CommunityMember, User, PostReply, PostVote, Notification, utcnow, File, Site, \
Instance, Report, UserBlock, CommunityBan, CommunityJoinRequest, CommunityBlock, Filter, Domain, DomainBlock, \
- InstanceBlock, NotificationSubscription, PostBookmark, PostReplyBookmark, read_posts, Topic
+ InstanceBlock, NotificationSubscription, PostBookmark, PostReplyBookmark, read_posts, Topic, UserNote
from app.user import bp
from app.user.forms import ProfileForm, SettingsForm, DeleteAccountForm, ReportUserForm, \
- FilterForm, KeywordFilterEditForm, RemoteFollowForm, ImportExportForm
+ FilterForm, KeywordFilterEditForm, RemoteFollowForm, ImportExportForm, UserNoteForm
from app.user.utils import purge_user_then_delete, unsubscribe_from_community
from app.utils import get_setting, render_template, markdown_to_html, user_access, markdown_to_text, shorten_string, \
is_image_url, ensure_directory_exists, gibberish, file_get_contents, community_membership, user_filters_home, \
@@ -1330,3 +1330,36 @@ def user_read_posts_delete():
db.session.commit()
flash(_('Reading history has been deleted'))
return redirect(url_for('user.user_read_posts'))
+
+
+@bp.route('/u/
/note', methods=['GET', 'POST'])
+@login_required
+def edit_user_note(actor):
+ actor = actor.strip()
+ if '@' in actor:
+ user: User = User.query.filter_by(ap_id=actor, deleted=False).first()
+ else:
+ user: User = User.query.filter_by(user_name=actor, deleted=False, ap_id=None).first()
+ if user is None:
+ abort(404)
+ form = UserNoteForm()
+ if form.validate_on_submit() and not current_user.banned:
+ text = form.note.data.strip()
+ usernote = UserNote.query.filter(UserNote.target_id == user.id, UserNote.user_id == current_user.id).first()
+ if usernote:
+ usernote.body = text
+ else:
+ usernote = UserNote(target_id=user.id, user_id=current_user.id, body=text)
+ db.session.add(usernote)
+ db.session.commit()
+ cache.delete_memoized(User.get_note, user, current_user)
+
+ flash(_('Your changes have been saved.'), 'success')
+ goto = request.args.get('redirect') if 'redirect' in request.args else f'/u/{actor}'
+ return redirect(goto)
+
+ elif request.method == 'GET':
+ form.note.data = user.get_note(current_user)
+
+ return render_template('user/edit_note.html', title=_('Edit note'), form=form, user=user,
+ menu_topics=menu_topics(), site=g.site)