diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index 408cf1c3..87d22085 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -11,6 +11,7 @@ from django.core.files.base import ContentFile from django.db import models from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from markdown import markdown from bookwyrm import activitypub from bookwyrm.sanitize_html import InputHtmlParser from bookwyrm.settings import DOMAIN @@ -25,6 +26,7 @@ def validate_remote_id(value): params={'value': value}, ) + def validate_username(value): ''' make sure usernames look okay ''' if not re.match(r'^[A-Za-z\-_\.]+$', value): @@ -399,6 +401,16 @@ class HtmlField(ActivitypubFieldMixin, models.TextField): sanitizer.feed(value) return sanitizer.get_output() + def to_python(self, value):# pylint: disable=no-self-use + ''' process markdown before save ''' + if not value: + return value + content = markdown(value) + # sanitize resulting html + sanitizer = InputHtmlParser() + sanitizer.feed(content) + return sanitizer.get_output() + class ArrayField(ActivitypubFieldMixin, DjangoArrayField): ''' activitypub-aware array field ''' def field_to_activity(self, value): diff --git a/bookwyrm/outgoing.py b/bookwyrm/outgoing.py index 209b5858..7ac2d0d6 100644 --- a/bookwyrm/outgoing.py +++ b/bookwyrm/outgoing.py @@ -211,18 +211,16 @@ def handle_status(user, form): ''' generic handler for statuses ''' status = form.save(commit=False) if not status.sensitive and status.content_warning: - # the cw text field remains populated hen you click "remove" + # the cw text field remains populated when you click "remove" status.content_warning = None status.save() # inspect the text for user tags - text = status.content - matches = re.finditer( - regex.username, - text - ) - for match in matches: + matches = [] + for match in re.finditer(regex.username, status.content): username = match.group().strip().split('@')[1:] + print(match.group()) + print(len(username)) if len(username) == 1: # this looks like a local user (@user), fill in the domain username.append(DOMAIN) @@ -232,6 +230,7 @@ def handle_status(user, form): if not mention_user: # we can ignore users we don't know about continue + matches.append((match.group(), mention_user.remote_id)) # add them to status mentions fk status.mention_users.add(mention_user) # create notification if the mentioned user is local @@ -242,6 +241,20 @@ def handle_status(user, form): related_user=user, related_status=status ) + # add links + content = status.content + content = re.sub( + r'([^(href=")])(https?:\/\/([A-Za-z\.\-_\/]+' \ + r'\.[A-Za-z]{2,}[A-Za-z\.\-_\/]+))', + r'\g<1>\g<3>', + content) + for (username, url) in matches: + content = re.sub( + r'%s([^@])' % username, + r'%s\g<1>' % (url, username), + content) + + status.content = content status.save() # notify reply parent or tagged users diff --git a/bookwyrm/sanitize_html.py b/bookwyrm/sanitize_html.py index 933fc43c..de13ede8 100644 --- a/bookwyrm/sanitize_html.py +++ b/bookwyrm/sanitize_html.py @@ -6,7 +6,11 @@ class InputHtmlParser(HTMLParser):#pylint: disable=abstract-method def __init__(self): HTMLParser.__init__(self) - self.allowed_tags = ['p', 'b', 'i', 'pre', 'a', 'span'] + self.allowed_tags = [ + 'p', 'br', + 'b', 'i', 'strong', 'em', 'pre', + 'a', 'span', 'ul', 'ol', 'li' + ] self.tag_stack = [] self.output = [] # if the html appears invalid, we just won't allow any at all diff --git a/bookwyrm/static/css/format.css b/bookwyrm/static/css/format.css index dd5c45eb..5b5ff08d 100644 --- a/bookwyrm/static/css/format.css +++ b/bookwyrm/static/css/format.css @@ -137,8 +137,3 @@ input.toggle-control:checked ~ .modal.toggle-content { content: "\e904"; right: 0; } - -/* --- BLOCKQUOTE --- */ -blockquote { - white-space: pre-line; -} diff --git a/bookwyrm/templates/book.html b/bookwyrm/templates/book.html index abed3598..506ee3db 100644 --- a/bookwyrm/templates/book.html +++ b/bookwyrm/templates/book.html @@ -103,14 +103,14 @@
{% for shelf in user_shelves %}

- This edition is on your {{ shelf.shelf.name }} shelf. + This edition is on your {{ shelf.shelf.name }} shelf. {% include 'snippets/shelf_selector.html' with current=shelf.shelf %}

{% endfor %} {% for shelf in other_edition_shelves %}

- A different edition of this book is on your {{ shelf.shelf.name }} shelf. + A different edition of this book is on your {{ shelf.shelf.name }} shelf. {% include 'snippets/switch_edition_button.html' with edition=book %}

{% endfor %} diff --git a/bookwyrm/templates/snippets/status_content.html b/bookwyrm/templates/snippets/status_content.html index 6e4a6b98..95d6f8ee 100644 --- a/bookwyrm/templates/snippets/status_content.html +++ b/bookwyrm/templates/snippets/status_content.html @@ -27,7 +27,7 @@ {% if status.quote %}
-
{{ status.quote }}
+
{{ status.quote | safe }}

— {% include 'snippets/book_titleby.html' with book=status.book %}

diff --git a/bookwyrm/tests/test_incoming.py b/bookwyrm/tests/test_incoming.py index 2cc4c1f4..e0c37648 100644 --- a/bookwyrm/tests/test_incoming.py +++ b/bookwyrm/tests/test_incoming.py @@ -262,8 +262,8 @@ class Incoming(TestCase): status = models.Quotation.objects.get() self.assertEqual( status.remote_id, 'https://example.com/user/mouse/quotation/13') - self.assertEqual(status.quote, 'quote body') - self.assertEqual(status.content, 'commentary') + self.assertEqual(status.quote, '

quote body

') + self.assertEqual(status.content, '

commentary

') self.assertEqual(status.user, self.local_user) self.assertEqual(models.Status.objects.count(), 2) @@ -284,7 +284,7 @@ class Incoming(TestCase): incoming.handle_create(activity) status = models.Status.objects.last() - self.assertEqual(status.content, 'test content in note') + self.assertEqual(status.content, '

test content in note

') self.assertEqual(status.mention_users.first(), self.local_user) self.assertTrue( models.Notification.objects.filter(user=self.local_user).exists()) @@ -306,7 +306,7 @@ class Incoming(TestCase): incoming.handle_create(activity) status = models.Status.objects.last() - self.assertEqual(status.content, 'test content in note') + self.assertEqual(status.content, '

test content in note

') self.assertEqual(status.reply_parent, self.status) self.assertTrue( models.Notification.objects.filter(user=self.local_user)) diff --git a/bookwyrm/views.py b/bookwyrm/views.py index 097c3577..c27391cc 100644 --- a/bookwyrm/views.py +++ b/bookwyrm/views.py @@ -615,18 +615,13 @@ def book_page(request, book_id): book__parent_work=book.parent_work, ) - rating = reviews.aggregate(Avg('rating')) - tags = models.UserTag.objects.filter( - book=book, - ) - data = { 'title': book.title, 'book': book, 'reviews': reviews_page, 'ratings': reviews.filter(content__isnull=True), - 'rating': rating['rating__avg'], - 'tags': tags, + 'rating': reviews.aggregate(Avg('rating'))['rating__avg'], + 'tags': models.UserTag.objects.filter(book=book), 'user_tags': user_tags, 'user_shelves': user_shelves, 'other_edition_shelves': other_edition_shelves, @@ -761,7 +756,7 @@ def shelf_page(request, username, shelf_identifier): return JsonResponse(shelf.to_activity(**request.GET)) data = { - 'title': user.name, + 'title': '%s\'s %s shelf' % (user.display_name, shelf.name), 'user': user, 'is_self': is_self, 'shelves': shelves.all(), diff --git a/requirements.txt b/requirements.txt index 0e17fcca..e5d7798d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ Django==3.0.7 django-model-utils==4.0.0 environs==7.2.0 flower==0.9.4 +Markdown==3.3.3 Pillow>=7.1.0 psycopg2==2.8.4 pycryptodome==3.9.4