From 385ec4d70a4f42eaeb1c9e906ec95e4be5eaf024 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 1 Jan 2021 11:05:49 -0800 Subject: [PATCH 0001/1285] Adds ReviewRating model I can't just calling Rating because that would clash with the rating field --- bookwyrm/activitypub/__init__.py | 3 +- bookwyrm/activitypub/note.py | 16 ++++++-- bookwyrm/forms.py | 4 +- bookwyrm/migrations/0030_reviewrating.py | 52 ++++++++++++++++++++++++ bookwyrm/models/__init__.py | 3 +- bookwyrm/models/status.py | 16 ++++++++ 6 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 bookwyrm/migrations/0030_reviewrating.py diff --git a/bookwyrm/activitypub/__init__.py b/bookwyrm/activitypub/__init__.py index a4fef41e..49e278bf 100644 --- a/bookwyrm/activitypub/__init__.py +++ b/bookwyrm/activitypub/__init__.py @@ -6,7 +6,8 @@ from .base_activity import ActivityEncoder, Signature from .base_activity import Link, Mention from .base_activity import ActivitySerializerError, resolve_remote_id from .image import Image -from .note import Note, GeneratedNote, Article, Comment, Review, Quotation +from .note import Note, GeneratedNote, Article, Comment, Quotation +from .note import Review, Rating from .note import Tombstone from .interaction import Boost, Like from .ordered_collection import OrderedCollection, OrderedCollectionPage diff --git a/bookwyrm/activitypub/note.py b/bookwyrm/activitypub/note.py index 72fbe5fc..cbbeb779 100644 --- a/bookwyrm/activitypub/note.py +++ b/bookwyrm/activitypub/note.py @@ -50,6 +50,13 @@ class Comment(Note): type: str = 'Comment' +@dataclass(init=False) +class Quotation(Comment): + ''' a quote and commentary on a book ''' + quote: str + type: str = 'Quotation' + + @dataclass(init=False) class Review(Comment): ''' a full book review ''' @@ -59,7 +66,8 @@ class Review(Comment): @dataclass(init=False) -class Quotation(Comment): - ''' a quote and commentary on a book ''' - quote: str - type: str = 'Quotation' +class Rating(Comment): + ''' a full book review ''' + rating: int = None + content: str = None + type: str = 'Rating' diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index 686ac8b1..133f7543 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -54,8 +54,8 @@ class RegisterForm(CustomForm): class RatingForm(CustomForm): class Meta: - model = models.Review - fields = ['user', 'book', 'content', 'rating', 'privacy'] + model = models.ReviewRating + fields = ['user', 'book', 'rating', 'privacy'] class ReviewForm(CustomForm): diff --git a/bookwyrm/migrations/0030_reviewrating.py b/bookwyrm/migrations/0030_reviewrating.py new file mode 100644 index 00000000..3658a666 --- /dev/null +++ b/bookwyrm/migrations/0030_reviewrating.py @@ -0,0 +1,52 @@ +# Generated by Django 3.0.7 on 2021-01-01 19:05 + +from django.db.models.fields.reverse_related import ManyToOneRel +from django.db.models import Q +from django.db import migrations, models +import django.db.models.deletion +from bookwyrm.management.commands.deduplicate_book_data import update_related + + +def convert_review_rating(app_registry, schema_editor): + ''' take reviews with no content and turn them into ratings ''' + db_alias = schema_editor.connection.alias + reviews = app_registry.get_model('bookwyrm', 'Review') + review_ratings = app_registry.get_model('bookwyrm', 'ReviewRating') + ratings = reviews.objects.using(db_alias).filter( + Q(content__isnull=True) | Q(content='')) + # replace the old review with the rating + for review in ratings: + rating = review_ratings.objects.create( + user=review.user, rating=review.rating, book=review.book) + for field in review._meta.get_fields(): + if isinstance(field, ManyToOneRel) or field.name == 'status_ptr': + continue + value = getattr(review, field.name) + print(review, rating, field.name, value) + try: + setattr(rating, field.name, value) + except TypeError: + getattr(rating, field.name).set(value.all()) + rating.save() + update_related(rating, review) + review.delete() + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0029_auto_20201221_2014'), + ] + + operations = [ + migrations.CreateModel( + name='ReviewRating', + fields=[ + ('review_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='bookwyrm.Review')), + ], + options={ + 'abstract': False, + }, + bases=('bookwyrm.review',), + ), + migrations.RunPython(convert_review_rating), + ] diff --git a/bookwyrm/models/__init__.py b/bookwyrm/models/__init__.py index 48852cfe..4a0db4f5 100644 --- a/bookwyrm/models/__init__.py +++ b/bookwyrm/models/__init__.py @@ -8,7 +8,8 @@ from .connector import Connector from .shelf import Shelf, ShelfBook -from .status import Status, GeneratedNote, Review, Comment, Quotation +from .status import Status, GeneratedNote, Comment, Quotation +from .status import Review, ReviewRating from .status import Boost from .attachment import Image from .favorite import Favorite diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 10cee752..ee8faf00 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -222,6 +222,22 @@ class Review(Status): pure_type = 'Article' +class ReviewRating(Review): + ''' a subtype of review that only contains a rating ''' + def save(self, *args, **kwargs): + if not self.rating: + raise ValueError('Rating object must include a numerical rating') + return super().save(*args, **kwargs) + + @property + def pure_content(self): + #pylint: disable=bad-string-format-type + return 'Rated "%s": %d' % (self.book.title, self.rating) + + activity_serializer = activitypub.Rating + pure_type = 'Note' + + class Boost(Status): ''' boost'ing a post ''' boosted_status = fields.ForeignKey( From dad202823aa73bbfeea9744a518832ff4a7ee6cc Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 1 Jan 2021 15:36:43 -0800 Subject: [PATCH 0002/1285] Moves review re-structing into separate migration --- bookwyrm/migrations/0030_reviewrating.py | 29 --------- .../migrations/0031_auto_20210101_2109.py | 61 +++++++++++++++++++ 2 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 bookwyrm/migrations/0031_auto_20210101_2109.py diff --git a/bookwyrm/migrations/0030_reviewrating.py b/bookwyrm/migrations/0030_reviewrating.py index 3658a666..327c6c6a 100644 --- a/bookwyrm/migrations/0030_reviewrating.py +++ b/bookwyrm/migrations/0030_reviewrating.py @@ -1,35 +1,7 @@ # Generated by Django 3.0.7 on 2021-01-01 19:05 -from django.db.models.fields.reverse_related import ManyToOneRel -from django.db.models import Q from django.db import migrations, models import django.db.models.deletion -from bookwyrm.management.commands.deduplicate_book_data import update_related - - -def convert_review_rating(app_registry, schema_editor): - ''' take reviews with no content and turn them into ratings ''' - db_alias = schema_editor.connection.alias - reviews = app_registry.get_model('bookwyrm', 'Review') - review_ratings = app_registry.get_model('bookwyrm', 'ReviewRating') - ratings = reviews.objects.using(db_alias).filter( - Q(content__isnull=True) | Q(content='')) - # replace the old review with the rating - for review in ratings: - rating = review_ratings.objects.create( - user=review.user, rating=review.rating, book=review.book) - for field in review._meta.get_fields(): - if isinstance(field, ManyToOneRel) or field.name == 'status_ptr': - continue - value = getattr(review, field.name) - print(review, rating, field.name, value) - try: - setattr(rating, field.name, value) - except TypeError: - getattr(rating, field.name).set(value.all()) - rating.save() - update_related(rating, review) - review.delete() class Migration(migrations.Migration): @@ -48,5 +20,4 @@ class Migration(migrations.Migration): }, bases=('bookwyrm.review',), ), - migrations.RunPython(convert_review_rating), ] diff --git a/bookwyrm/migrations/0031_auto_20210101_2109.py b/bookwyrm/migrations/0031_auto_20210101_2109.py new file mode 100644 index 00000000..8343719f --- /dev/null +++ b/bookwyrm/migrations/0031_auto_20210101_2109.py @@ -0,0 +1,61 @@ +# Generated by Django 3.0.7 on 2021-01-01 21:09 + +from django.db.models.fields.reverse_related import ManyToOneRel +from django.db.models import Q +from django.db import migrations + +def convert_review_rating(app_registry, schema_editor): + ''' take reviews with no content and turn them into ratings ''' + db_alias = schema_editor.connection.alias + reviews = app_registry.get_model('bookwyrm', 'Review') + review_ratings = app_registry.get_model('bookwyrm', 'ReviewRating') + ratings = reviews.objects.using(db_alias).filter( + Q(content__isnull=True) | Q(content='')) + # replace the old review with the rating + for review in ratings: + rating = review_ratings.objects.create( + user=review.user, rating=review.rating, book=review.book) + print('-----') + print(rating, review) + for field in review._meta.get_fields(): + if isinstance(field, ManyToOneRel) or field.name in ['status_ptr', 'id', 'remote_id']: + continue + value = getattr(review, field.name) + try: + setattr(rating, field.name, value) + except TypeError: + getattr(rating, field.name).set(value.all()) + rating.save() + + # move related models away from old review + related_models = [ + (r.remote_field.name, r.related_model.__name__) for r in \ + review._meta.related_objects] + for (related_field, related_model_name) in related_models: + related_model = app_registry.get_model('bookwyrm', related_model_name) + related_objs = related_model.objects.using(db_alias).filter( + **{related_field: review}) + for related_obj in related_objs: + print('related_obj', related_obj) + print('field', related_field) + try: + print(rating) + print('before', getattr(related_obj, related_field)) + setattr(related_obj, related_field, rating) + related_obj.save() + print('after', getattr(related_obj, related_field)) + except TypeError: + getattr(related_obj, related_field).add(rating) + getattr(related_obj, related_field).remove(review) + review.delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0030_reviewrating'), + ] + + operations = [ + migrations.RunPython(convert_review_rating), + ] From ff9caf3d5118c7f575287af9121fe236a67a7027 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 4 Jan 2021 21:37:09 -0800 Subject: [PATCH 0003/1285] Fixes migration version numbering --- bookwyrm/migrations/0030_reviewrating.py | 23 ------------------- ...to_20210101_2109.py => 0033_reviewrate.py} | 17 +++++++++++--- 2 files changed, 14 insertions(+), 26 deletions(-) delete mode 100644 bookwyrm/migrations/0030_reviewrating.py rename bookwyrm/migrations/{0031_auto_20210101_2109.py => 0033_reviewrate.py} (80%) diff --git a/bookwyrm/migrations/0030_reviewrating.py b/bookwyrm/migrations/0030_reviewrating.py deleted file mode 100644 index 327c6c6a..00000000 --- a/bookwyrm/migrations/0030_reviewrating.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.0.7 on 2021-01-01 19:05 - -from django.db import migrations, models -import django.db.models.deletion - -class Migration(migrations.Migration): - - dependencies = [ - ('bookwyrm', '0029_auto_20201221_2014'), - ] - - operations = [ - migrations.CreateModel( - name='ReviewRating', - fields=[ - ('review_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='bookwyrm.Review')), - ], - options={ - 'abstract': False, - }, - bases=('bookwyrm.review',), - ), - ] diff --git a/bookwyrm/migrations/0031_auto_20210101_2109.py b/bookwyrm/migrations/0033_reviewrate.py similarity index 80% rename from bookwyrm/migrations/0031_auto_20210101_2109.py rename to bookwyrm/migrations/0033_reviewrate.py index 8343719f..69c0ec57 100644 --- a/bookwyrm/migrations/0031_auto_20210101_2109.py +++ b/bookwyrm/migrations/0033_reviewrate.py @@ -1,8 +1,9 @@ -# Generated by Django 3.0.7 on 2021-01-01 21:09 +# Generated by Django 3.0.7 on 2021-01-05 05:32 from django.db.models.fields.reverse_related import ManyToOneRel from django.db.models import Q -from django.db import migrations +from django.db import migrations, models +import django.db.models.deletion def convert_review_rating(app_registry, schema_editor): ''' take reviews with no content and turn them into ratings ''' @@ -53,9 +54,19 @@ def convert_review_rating(app_registry, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0030_reviewrating'), + ('bookwyrm', '0032_auto_20210104_2055'), ] operations = [ + migrations.CreateModel( + name='ReviewRating', + fields=[ + ('review_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='bookwyrm.Review')), + ], + options={ + 'abstract': False, + }, + bases=('bookwyrm.review',), + ), migrations.RunPython(convert_review_rating), ] From 930d9429efa0138da7625a5270b4eba3ead890ec Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 10 Feb 2021 12:43:53 -0800 Subject: [PATCH 0004/1285] User save() override instead of signal to set user fields this gets gnarly because of transaction.atomic, so it bears further testing --- bookwyrm/models/user.py | 80 ++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index da717d2e..7b0f9a91 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -6,7 +6,6 @@ from django.apps import apps from django.contrib.auth.models import AbstractUser from django.core.validators import MinValueValidator from django.db import models -from django.dispatch import receiver from django.utils import timezone from bookwyrm import activitypub @@ -172,15 +171,23 @@ class User(OrderedCollectionPageMixin, AbstractUser): def save(self, *args, **kwargs): ''' populate fields for new local users ''' - # this user already exists, no need to populate fields if not self.local and not re.match(regex.full_username, self.username): # generate a username that uses the domain (webfinger format) actor_parts = urlparse(self.remote_id) self.username = '%s@%s' % (self.username, actor_parts.netloc) - return super().save(*args, **kwargs) + super().save(*args, **kwargs) + return - if self.id or not self.local: - return super().save(*args, **kwargs) + # this user already exists, no need to populate fields + if self.id: + super().save(*args, **kwargs) + return + + # this is a new remote user, we need to set their remote server field + if not self.local: + super().save(*args, **kwargs) + set_remote_server.delay(self.id) + return # populate fields for local users self.remote_id = 'https://%s/user/%s' % (DOMAIN, self.localname) @@ -188,7 +195,32 @@ class User(OrderedCollectionPageMixin, AbstractUser): self.shared_inbox = 'https://%s/inbox' % DOMAIN self.outbox = '%s/outbox' % self.remote_id - return super().save(*args, **kwargs) + # an id needs to be set before we can proceed with related models + super().save(*args, **kwargs) + + # create keys and shelves for new local users + self.key_pair = KeyPair.objects.create( + remote_id='%s/#main-key' % self.remote_id) + self.save(broadcast=False) + + shelves = [{ + 'name': 'To Read', + 'identifier': 'to-read', + }, { + 'name': 'Currently Reading', + 'identifier': 'reading', + }, { + 'name': 'Read', + 'identifier': 'read', + }] + + for shelf in shelves: + Shelf( + name=shelf['name'], + identifier=shelf['identifier'], + user=self, + editable=False + ).save(broadcast=False) @property def local_path(self): @@ -280,42 +312,6 @@ class AnnualGoal(BookWyrmModel): finish_date__year__gte=self.year).count() - -@receiver(models.signals.post_save, sender=User) -#pylint: disable=unused-argument -def execute_after_save(sender, instance, created, *args, **kwargs): - ''' create shelves for new users ''' - if not created: - return - - if not instance.local: - set_remote_server.delay(instance.id) - return - - instance.key_pair = KeyPair.objects.create( - remote_id='%s/#main-key' % instance.remote_id) - instance.save(broadcast=False) - - shelves = [{ - 'name': 'To Read', - 'identifier': 'to-read', - }, { - 'name': 'Currently Reading', - 'identifier': 'reading', - }, { - 'name': 'Read', - 'identifier': 'read', - }] - - for shelf in shelves: - Shelf( - name=shelf['name'], - identifier=shelf['identifier'], - user=instance, - editable=False - ).save(broadcast=False) - - @app.task def set_remote_server(user_id): ''' figure out the user's remote server in the background ''' From 8cb345886bb1635f5984df398e97489857041afe Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Feb 2021 11:30:22 -0800 Subject: [PATCH 0005/1285] Show lists on book page --- bookwyrm/templates/book.html | 11 +++++++++++ bookwyrm/views/books.py | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/bookwyrm/templates/book.html b/bookwyrm/templates/book.html index c174116e..0bef2856 100644 --- a/bookwyrm/templates/book.html +++ b/bookwyrm/templates/book.html @@ -224,6 +224,17 @@ {% endif %} + + {% if lists.exists %} +
+

Lists

+ +
+ {% endif %} diff --git a/bookwyrm/views/books.py b/bookwyrm/views/books.py index 2655bebb..9d196563 100644 --- a/bookwyrm/views/books.py +++ b/bookwyrm/views/books.py @@ -15,6 +15,7 @@ from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.connectors import connector_manager from bookwyrm.settings import PAGE_LENGTH from .helpers import is_api_request, get_activity_feed, get_edition +from .helpers import privacy_filter # pylint: disable= no-self-use @@ -94,6 +95,10 @@ class Book(View): 'ratings': reviews.filter(Q(content__isnull=True) | Q(content='')), 'rating': reviews.aggregate(Avg('rating'))['rating__avg'], 'tags': models.UserTag.objects.filter(book=book), + 'lists': privacy_filter( + request.user, + book.list_set.all(), + ['public', 'unlisted', 'followers']), 'user_tags': user_tags, 'user_shelves': user_shelves, 'other_edition_shelves': other_edition_shelves, From 65f81bd5f0fb5c6075ba5bb39b008066c6ae11c3 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Feb 2021 12:21:48 -0800 Subject: [PATCH 0006/1285] Moves blocking to save function I just like these better than signals?? --- bookwyrm/incoming.py | 2 +- bookwyrm/models/relationship.py | 28 +++++++++++----------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/bookwyrm/incoming.py b/bookwyrm/incoming.py index 562225e7..cb5baafc 100644 --- a/bookwyrm/incoming.py +++ b/bookwyrm/incoming.py @@ -195,7 +195,7 @@ def handle_block(activity): ''' blocking a user ''' # create "block" databse entry activitypub.Block(**activity).to_model(models.UserBlocks) - # the removing relationships is handled in post-save hook in model + # the removing relationships is handled in model save @app.task diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index 9f3bf07d..6aaffae9 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -1,7 +1,6 @@ ''' defines relationships between users ''' from django.db import models, transaction from django.db.models import Q -from django.dispatch import receiver from bookwyrm import activitypub from .activitypub_mixin import ActivitypubMixin, ActivityMixin @@ -126,20 +125,15 @@ class UserBlocks(ActivityMixin, UserRelationship): status = 'blocks' activity_serializer = activitypub.Block + def save(self, *args, **kwargs): + ''' remove follow or follow request rels after a block is created ''' + super().save(*args, **kwargs) -@receiver(models.signals.post_save, sender=UserBlocks) -#pylint: disable=unused-argument -def execute_after_save(sender, instance, created, *args, **kwargs): - ''' remove follow or follow request rels after a block is created ''' - UserFollows.objects.filter( - Q(user_subject=instance.user_subject, - user_object=instance.user_object) | \ - Q(user_subject=instance.user_object, - user_object=instance.user_subject) - ).delete() - UserFollowRequest.objects.filter( - Q(user_subject=instance.user_subject, - user_object=instance.user_object) | \ - Q(user_subject=instance.user_object, - user_object=instance.user_subject) - ).delete() + UserFollows.objects.filter( + Q(user_subject=self.user_subject, user_object=self.user_object) | \ + Q(user_subject=self.user_object, user_object=self.user_subject) + ).delete() + UserFollowRequest.objects.filter( + Q(user_subject=self.user_subject, user_object=self.user_object) | \ + Q(user_subject=self.user_object, user_object=self.user_subject) + ).delete() From f974b9b89538f226a30c4854fa3947f51ff05e28 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Feb 2021 12:51:34 -0800 Subject: [PATCH 0007/1285] Better blocking checks --- bookwyrm/models/relationship.py | 48 ++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index 044b08ac..f4df3b83 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -1,6 +1,6 @@ ''' defines relationships between users ''' from django.apps import apps -from django.db import models, transaction +from django.db import models, transaction, IntegrityError from django.db.models import Q from bookwyrm import activitypub @@ -60,6 +60,20 @@ class UserFollows(ActivitypubMixin, UserRelationship): status = 'follows' activity_serializer = activitypub.Follow + def save(self, *args, **kwargs): + ''' really really don't let a user follow someone who blocked them ''' + # blocking in either direction is a no-go + if UserBlocks.objects.filter( + Q( + user_subject=self.user_subject, + user_object=self.user_object, + ) | Q( + user_subject=self.user_object, + user_object=self.user_subject, + ) + ).exists(): + raise IntegrityError() + super().save(*args, **kwargs) @classmethod def from_request(cls, follow_request): @@ -78,23 +92,25 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship): def save(self, *args, broadcast=True, **kwargs): ''' make sure the follow or block relationship doesn't already exist ''' - try: - UserFollows.objects.get( + # don't create a request if a follow already exists + if UserFollows.objects.filter( user_subject=self.user_subject, user_object=self.user_object, - ) - # blocking in either direction is a no-go - UserBlocks.objects.get( - user_subject=self.user_subject, - user_object=self.user_object, - ) - UserBlocks.objects.get( - user_subject=self.user_object, - user_object=self.user_subject, - ) - return None - except (UserFollows.DoesNotExist, UserBlocks.DoesNotExist): - super().save(*args, **kwargs) + ).exists(): + raise IntegrityError() + # blocking in either direction is a no-go + if UserBlocks.objects.filter( + Q( + user_subject=self.user_subject, + user_object=self.user_object, + ) | Q( + user_subject=self.user_object, + user_object=self.user_subject, + ) + ).exists(): + raise IntegrityError() + + super().save(*args, **kwargs) if broadcast and self.user_subject.local and not self.user_object.local: self.broadcast(self.to_activity(), self.user_subject) From fd19b559612aacc36f96fda15db88f6ef35d57cd Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Feb 2021 16:26:48 -0800 Subject: [PATCH 0008/1285] Basic checks for inbox --- bookwyrm/incoming.py | 21 +++--- bookwyrm/tests/views/test_inbox.py | 102 +++++++++++++++++++++++++++++ bookwyrm/urls.py | 6 +- bookwyrm/views/__init__.py | 1 + bookwyrm/views/inbox.py | 79 ++++++++++++++++++++++ 5 files changed, 195 insertions(+), 14 deletions(-) create mode 100644 bookwyrm/tests/views/test_inbox.py create mode 100644 bookwyrm/views/inbox.py diff --git a/bookwyrm/incoming.py b/bookwyrm/incoming.py index 18db1069..83d325af 100644 --- a/bookwyrm/incoming.py +++ b/bookwyrm/incoming.py @@ -160,14 +160,12 @@ def handle_follow_accept(activity): accepter = activitypub.resolve_remote_id(models.User, activity['actor']) try: - request = models.UserFollowRequest.objects.get( + models.UserFollowRequest.objects.get( user_subject=requester, user_object=accepter - ) - request.delete() + ).accept() except models.UserFollowRequest.DoesNotExist: - pass - accepter.followers.add(requester) + return @app.task @@ -176,12 +174,13 @@ def handle_follow_reject(activity): requester = models.User.objects.get(remote_id=activity['object']['actor']) rejecter = activitypub.resolve_remote_id(models.User, activity['actor']) - request = models.UserFollowRequest.objects.get( - user_subject=requester, - user_object=rejecter - ) - request.delete() - #raises models.UserFollowRequest.DoesNotExist + try: + models.UserFollowRequest.objects.get( + user_subject=requester, + user_object=rejecter + ).reject() + except models.UserFollowRequest.DoesNotExist: + return @app.task def handle_block(activity): diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py new file mode 100644 index 00000000..64dcc149 --- /dev/null +++ b/bookwyrm/tests/views/test_inbox.py @@ -0,0 +1,102 @@ +''' tests incoming activities''' +import json +from unittest.mock import patch + +from django.http import HttpResponseNotAllowed, HttpResponseNotFound +from django.test import TestCase, Client + +from bookwyrm import models + +class Inbox(TestCase): + ''' readthrough tests ''' + def setUp(self): + ''' basic user and book data ''' + self.client = Client() + self.local_user = models.User.objects.create_user( + 'mouse@example.com', 'mouse@mouse.com', 'mouseword', + local=True, localname='mouse') + self.local_user.remote_id = 'https://example.com/user/mouse' + self.local_user.save(broadcast=False) + with patch('bookwyrm.models.user.set_remote_server.delay'): + self.remote_user = models.User.objects.create_user( + 'rat', 'rat@rat.com', 'ratword', + local=False, + remote_id='https://example.com/users/rat', + inbox='https://example.com/users/rat/inbox', + outbox='https://example.com/users/rat/outbox', + ) + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + self.status = models.Status.objects.create( + user=self.local_user, + content='Test status', + remote_id='https://example.com/status/1', + ) + models.SiteSettings.objects.create() + + + def test_inbox_invalid_get(self): + ''' shouldn't try to handle if the user is not found ''' + result = self.client.get( + '/inbox', content_type="application/json" + ) + self.assertIsInstance(result, HttpResponseNotAllowed) + + def test_inbox_invalid_user(self): + ''' shouldn't try to handle if the user is not found ''' + result = self.client.post( + '/user/bleh/inbox', + '{"type": "Test", "object": "exists"}', + content_type="application/json" + ) + self.assertIsInstance(result, HttpResponseNotFound) + + def test_inbox_invalid_bad_signature(self): + ''' bad request for invalid signature ''' + with patch('bookwyrm.views.inbox.has_valid_signature') as mock_valid: + mock_valid.return_value = False + result = self.client.post( + '/user/mouse/inbox', + '{"type": "Test", "object": "exists"}', + content_type="application/json" + ) + self.assertEqual(result.status_code, 401) + + def test_inbox_invalid_bad_signature_delete(self): + ''' invalid signature for Delete is okay though ''' + with patch('bookwyrm.views.inbox.has_valid_signature') as mock_valid: + mock_valid.return_value = False + result = self.client.post( + '/user/mouse/inbox', + '{"type": "Delete", "object": "exists"}', + content_type="application/json" + ) + self.assertEqual(result.status_code, 200) + + def test_inbox_unknown_type(self): + ''' never heard of that activity type, don't have a handler for it ''' + with patch('bookwyrm.views.inbox.has_valid_signature') as mock_valid: + result = self.client.post( + '/inbox', + '{"type": "Fish", "object": "exists"}', + content_type="application/json" + ) + mock_valid.return_value = True + self.assertIsInstance(result, HttpResponseNotFound) + + + def test_inbox_success(self): + ''' a known type, for which we start a task ''' + activity = { + "id": "hi", + "type": "Accept", + "actor": "https://example.com/users/rat", + "object": "https://example.com/user/mouse" + } + with patch('bookwyrm.views.inbox.has_valid_signature') as mock_valid: + mock_valid.return_value = True + result = self.client.post( + '/inbox', + json.dumps(activity), + content_type="application/json" + ) + self.assertEqual(result.status_code, 200) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 3830adb9..a741088a 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -4,7 +4,7 @@ from django.contrib import admin from django.urls import path, re_path -from bookwyrm import incoming, settings, views, wellknown +from bookwyrm import settings, views, wellknown from bookwyrm.utils import regex user_path = r'^user/(?P%s)' % regex.username @@ -29,8 +29,8 @@ urlpatterns = [ path('admin/', admin.site.urls), # federation endpoints - re_path(r'^inbox/?$', incoming.shared_inbox), - re_path(r'%s/inbox/?$' % local_user_path, incoming.inbox), + re_path(r'^inbox/?$', views.Inbox.as_view()), + re_path(r'%s/inbox/?$' % local_user_path, views.Inbox.as_view()), re_path(r'%s/outbox/?$' % local_user_path, views.Outbox.as_view()), re_path(r'^.well-known/webfinger/?$', wellknown.webfinger), re_path(r'^.well-known/nodeinfo/?$', wellknown.nodeinfo_pointer), diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index b72c5013..2c7cdc46 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -11,6 +11,7 @@ from .follow import follow, unfollow from .follow import accept_follow_request, delete_follow_request from .goal import Goal from .import_data import Import, ImportStatus +from .inbox import Inbox from .interaction import Favorite, Unfavorite, Boost, Unboost from .invite import ManageInvites, Invite from .landing import About, Home, Discover diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py new file mode 100644 index 00000000..8f38e7a2 --- /dev/null +++ b/bookwyrm/views/inbox.py @@ -0,0 +1,79 @@ +''' incoming activities ''' +import json +from urllib.parse import urldefrag + +from django.http import HttpResponse +from django.http import HttpResponseBadRequest, HttpResponseNotFound +from django.shortcuts import get_object_or_404 +from django.views import View +import requests + +from bookwyrm import activitypub, models +from bookwyrm.signatures import Signature + + +# pylint: disable=no-self-use +class Inbox(View): + ''' requests sent by outside servers''' + def post(self, request, username=None): + ''' only works as POST request ''' + # first let's do some basic checks to see if this is legible + if username: + try: + models.User.objects.get(localname=username) + except models.User.DoesNotExist: + return HttpResponseNotFound() + + # is it valid json? does it at least vaguely resemble an activity? + try: + resp = request.body + activity_json = json.loads(resp) + activity_type = activity_json['type'] # Follow, Accept, Create, etc + except (json.decoder.JSONDecodeError, KeyError): + return HttpResponseBadRequest() + + # verify the signature + if not has_valid_signature(request, activity_json): + if activity_json['type'] == 'Delete': + # Pretend that unauth'd deletes succeed. Auth may be failing + # because the resource or owner of the resource might have + # been deleted. + return HttpResponse() + return HttpResponse(status=401) + + # get the activity dataclass from the type field + try: + serializer = getattr(activitypub, activity_type) + serializer(**activity_json) + except (AttributeError, activitypub.ActivitySerializerError): + return HttpResponseNotFound() + + return HttpResponse() + + +def has_valid_signature(request, activity): + ''' verify incoming signature ''' + try: + signature = Signature.parse(request) + + key_actor = urldefrag(signature.key_id).url + if key_actor != activity.get('actor'): + raise ValueError("Wrong actor created signature.") + + remote_user = activitypub.resolve_remote_id(models.User, key_actor) + if not remote_user: + return False + + try: + signature.verify(remote_user.key_pair.public_key, request) + except ValueError: + old_key = remote_user.key_pair.public_key + remote_user = activitypub.resolve_remote_id( + models.User, remote_user.remote_id, refresh=True + ) + if remote_user.key_pair.public_key == old_key: + raise # Key unchanged. + signature.verify(remote_user.key_pair.public_key, request) + except (ValueError, requests.exceptions.HTTPError): + return False + return True From e810c2bee0e8887bfe1c073dd1d53f3b405cc9fe Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Feb 2021 17:23:17 -0800 Subject: [PATCH 0009/1285] Recursively parse activities --- bookwyrm/activitypub/__init__.py | 6 ++- bookwyrm/activitypub/base_activity.py | 17 ++++++++- bookwyrm/tests/test_incoming.py | 54 --------------------------- bookwyrm/views/inbox.py | 10 ++--- 4 files changed, 24 insertions(+), 63 deletions(-) diff --git a/bookwyrm/activitypub/__init__.py b/bookwyrm/activitypub/__init__.py index 510f1f3f..ce4acd66 100644 --- a/bookwyrm/activitypub/__init__.py +++ b/bookwyrm/activitypub/__init__.py @@ -2,7 +2,7 @@ import inspect import sys -from .base_activity import ActivityEncoder, Signature +from .base_activity import ActivityEncoder, Signature, naive_parse from .base_activity import Link, Mention from .base_activity import ActivitySerializerError, resolve_remote_id from .image import Image @@ -23,3 +23,7 @@ from .verbs import Add, AddBook, AddListItem, Remove cls_members = inspect.getmembers(sys.modules[__name__], inspect.isclass) activity_objects = {c[0]: c[1] for c in cls_members \ if hasattr(c[1], 'to_model')} + +def parse(activity_json): + ''' figure out what activity this is and parse it ''' + return naive_parse(activity_objects, activity_json) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 5f35f1d7..596662e8 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -40,6 +40,17 @@ class Signature: signatureValue: str type: str = 'RsaSignature2017' +def naive_parse(activity_objects, activity_json): + ''' this navigates circular import issues ''' + try: + activity_type = activity_json['type'] + print(activity_type) + serializer = activity_objects[activity_type] + print(serializer) + except KeyError: + raise ActivitySerializerError('Invalid type "%s"' % activity_type) + + return serializer(activity_objects=activity_objects, **activity_json) @dataclass(init=False) class ActivityObject: @@ -47,13 +58,17 @@ class ActivityObject: id: str type: str - def __init__(self, **kwargs): + def __init__(self, activity_objects=None, **kwargs): ''' this lets you pass in an object with fields that aren't in the dataclass, which it ignores. Any field in the dataclass is required or has a default value ''' for field in fields(self): try: + print(field.name, field.type) value = kwargs[field.name] + if field.type == 'ActivityObject' and activity_objects: + value = naive_parse(activity_objects, value) + except KeyError: if field.default == MISSING and \ field.default_factory == MISSING: diff --git a/bookwyrm/tests/test_incoming.py b/bookwyrm/tests/test_incoming.py index 01d0c9a3..9be0bb97 100644 --- a/bookwyrm/tests/test_incoming.py +++ b/bookwyrm/tests/test_incoming.py @@ -40,60 +40,6 @@ class Incoming(TestCase): self.factory = RequestFactory() - def test_inbox_invalid_get(self): - ''' shouldn't try to handle if the user is not found ''' - request = self.factory.get('https://www.example.com/') - self.assertIsInstance( - incoming.inbox(request, 'anything'), HttpResponseNotAllowed) - self.assertIsInstance( - incoming.shared_inbox(request), HttpResponseNotAllowed) - - def test_inbox_invalid_user(self): - ''' shouldn't try to handle if the user is not found ''' - request = self.factory.post('https://www.example.com/') - self.assertIsInstance( - incoming.inbox(request, 'fish@tomato.com'), HttpResponseNotFound) - - def test_inbox_invalid_no_object(self): - ''' json is missing "object" field ''' - request = self.factory.post( - self.local_user.shared_inbox, data={}) - self.assertIsInstance( - incoming.shared_inbox(request), HttpResponseBadRequest) - - def test_inbox_invalid_bad_signature(self): - ''' bad request for invalid signature ''' - request = self.factory.post( - self.local_user.shared_inbox, - '{"type": "Test", "object": "exists"}', - content_type='application/json') - with patch('bookwyrm.incoming.has_valid_signature') as mock_has_valid: - mock_has_valid.return_value = False - self.assertEqual( - incoming.shared_inbox(request).status_code, 401) - - def test_inbox_invalid_bad_signature_delete(self): - ''' invalid signature for Delete is okay though ''' - request = self.factory.post( - self.local_user.shared_inbox, - '{"type": "Delete", "object": "exists"}', - content_type='application/json') - with patch('bookwyrm.incoming.has_valid_signature') as mock_has_valid: - mock_has_valid.return_value = False - self.assertEqual( - incoming.shared_inbox(request).status_code, 200) - - def test_inbox_unknown_type(self): - ''' never heard of that activity type, don't have a handler for it ''' - request = self.factory.post( - self.local_user.shared_inbox, - '{"type": "Fish", "object": "exists"}', - content_type='application/json') - with patch('bookwyrm.incoming.has_valid_signature') as mock_has_valid: - mock_has_valid.return_value = True - self.assertIsInstance( - incoming.shared_inbox(request), HttpResponseNotFound) - def test_inbox_success(self): ''' a known type, for which we start a task ''' request = self.factory.post( diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index 8f38e7a2..53dd6c40 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -4,7 +4,6 @@ from urllib.parse import urldefrag from django.http import HttpResponse from django.http import HttpResponseBadRequest, HttpResponseNotFound -from django.shortcuts import get_object_or_404 from django.views import View import requests @@ -26,10 +25,8 @@ class Inbox(View): # is it valid json? does it at least vaguely resemble an activity? try: - resp = request.body - activity_json = json.loads(resp) - activity_type = activity_json['type'] # Follow, Accept, Create, etc - except (json.decoder.JSONDecodeError, KeyError): + activity_json = json.loads(request.body) + except json.decoder.JSONDecodeError: return HttpResponseBadRequest() # verify the signature @@ -43,8 +40,7 @@ class Inbox(View): # get the activity dataclass from the type field try: - serializer = getattr(activitypub, activity_type) - serializer(**activity_json) + activitypub.parse(activity_json) except (AttributeError, activitypub.ActivitySerializerError): return HttpResponseNotFound() From 81e2021f92c170b8c73098a4746e511983975ce9 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Feb 2021 18:47:08 -0800 Subject: [PATCH 0010/1285] Move handlers to activitypub classes --- bookwyrm/activitypub/base_activity.py | 33 ++++++++++---------- bookwyrm/activitypub/verbs.py | 7 +++++ bookwyrm/tests/views/test_inbox.py | 43 +++++++++++++++++++++------ bookwyrm/views/inbox.py | 24 ++++++++++++--- celerywyrm/celery.py | 2 +- 5 files changed, 78 insertions(+), 31 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 596662e8..93af0d0c 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -47,8 +47,8 @@ def naive_parse(activity_objects, activity_json): print(activity_type) serializer = activity_objects[activity_type] print(serializer) - except KeyError: - raise ActivitySerializerError('Invalid type "%s"' % activity_type) + except KeyError as e: + raise ActivitySerializerError(e) return serializer(activity_objects=activity_objects, **activity_json) @@ -66,7 +66,11 @@ class ActivityObject: try: print(field.name, field.type) value = kwargs[field.name] - if field.type == 'ActivityObject' and activity_objects: + try: + is_subclass = issubclass(field.type, ActivityObject) + except TypeError: + is_subclass = False + if is_subclass and activity_objects: value = naive_parse(activity_objects, value) except KeyError: @@ -78,22 +82,17 @@ class ActivityObject: setattr(self, field.name, value) - def to_model(self, model, instance=None, save=True): + def to_model(self, instance=None, save=True): ''' convert from an activity to a model instance ''' - if self.type != model.activity_serializer.type: + # figure out the right model -- wish I had a better way for this + models = apps.get_models() + model = [m for m in models if hasattr(m, 'activity_serializer') and \ + hasattr(m.activity_serializer, 'type') and \ + m.activity_serializer.type == self.type] + if not len(model): raise ActivitySerializerError( - 'Wrong activity type "%s" for activity of type "%s"' % \ - (model.activity_serializer.type, - self.type) - ) - - if not isinstance(self, model.activity_serializer): - raise ActivitySerializerError( - 'Wrong activity type "%s" for model "%s" (expects "%s")' % \ - (self.__class__, - model.__name__, - model.activity_serializer) - ) + 'No model found for activity type "%s"' % self.type) + model = model[0] if hasattr(model, 'ignore_activity') and model.ignore_activity(self): return instance diff --git a/bookwyrm/activitypub/verbs.py b/bookwyrm/activitypub/verbs.py index 190cd739..c4b30253 100644 --- a/bookwyrm/activitypub/verbs.py +++ b/bookwyrm/activitypub/verbs.py @@ -21,6 +21,11 @@ class Create(Verb): signature: Signature = None type: str = 'Create' + def action(self): + ''' create the model instance from the dataclass ''' + # check for dupes + self.object.to_model() + @dataclass(init=False) class Delete(Verb): @@ -46,11 +51,13 @@ class Undo(Verb): @dataclass(init=False) class Follow(Verb): ''' Follow activity ''' + object: str type: str = 'Follow' @dataclass(init=False) class Block(Verb): ''' Block activity ''' + object: str type: str = 'Block' @dataclass(init=False) diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 64dcc149..3bb7760e 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -87,16 +87,41 @@ class Inbox(TestCase): def test_inbox_success(self): ''' a known type, for which we start a task ''' activity = { - "id": "hi", - "type": "Accept", - "actor": "https://example.com/users/rat", - "object": "https://example.com/user/mouse" + 'id': 'hi', + 'type': 'Create', + 'actor': 'hi', + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://example.com/user/mouse/followers" + ], + 'object': { + "id": "https://example.com/list/22", + "type": "BookList", + "totalItems": 1, + "first": "https://example.com/list/22?page=1", + "last": "https://example.com/list/22?page=1", + "name": "Test List", + "owner": "https://example.com/user/mouse", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://example.com/user/mouse/followers" + ], + "summary": "summary text", + "curation": "curated", + "@context": "https://www.w3.org/ns/activitystreams" + } } with patch('bookwyrm.views.inbox.has_valid_signature') as mock_valid: mock_valid.return_value = True - result = self.client.post( - '/inbox', - json.dumps(activity), - content_type="application/json" - ) + + with patch('bookwyrm.views.inbox.activity_task.delay'): + result = self.client.post( + '/inbox', + json.dumps(activity), + content_type="application/json" + ) self.assertEqual(result.status_code, 200) diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index 53dd6c40..fdac8446 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -8,6 +8,7 @@ from django.views import View import requests from bookwyrm import activitypub, models +from bookwyrm.tasks import app from bookwyrm.signatures import Signature @@ -38,15 +39,30 @@ class Inbox(View): return HttpResponse() return HttpResponse(status=401) - # get the activity dataclass from the type field - try: - activitypub.parse(activity_json) - except (AttributeError, activitypub.ActivitySerializerError): + # just some quick smell tests before we try to parse the json + if not 'object' in activity_json or \ + not 'type' in activity_json or \ + not activity_json['type'] in activitypub.activity_objects: return HttpResponseNotFound() + activity_task.delay() return HttpResponse() +@app.task +def activity_task(activity_json): + ''' do something with this json we think is legit ''' + # lets see if the activitypub module can make sense of this json + try: + activity = activitypub.parse(activity_json) + except activitypub.ActivitySerializerError: + return + + # cool that worked, now we should do the action described by the type + # (create, update, delete, etc) + activity.action() + + def has_valid_signature(request, activity): ''' verify incoming signature ''' try: diff --git a/celerywyrm/celery.py b/celerywyrm/celery.py index 5a53dab5..2937ef0f 100644 --- a/celerywyrm/celery.py +++ b/celerywyrm/celery.py @@ -25,5 +25,5 @@ app.autodiscover_tasks( ['bookwyrm'], related_name='connectors.abstract_connector') app.autodiscover_tasks(['bookwyrm'], related_name='emailing') app.autodiscover_tasks(['bookwyrm'], related_name='goodreads_import') -app.autodiscover_tasks(['bookwyrm'], related_name='incoming') app.autodiscover_tasks(['bookwyrm'], related_name='models.user') +app.autodiscover_tasks(['bookwyrm'], related_name='views.inbox') From 12a3aa96676c3ddbfe1905a14ef8d45dcdaded3c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Feb 2021 19:41:22 -0800 Subject: [PATCH 0011/1285] incoming Create flow with tests --- bookwyrm/activitypub/base_activity.py | 3 - bookwyrm/activitypub/verbs.py | 1 - bookwyrm/incoming.py | 41 ------- bookwyrm/tests/test_incoming.py | 117 -------------------- bookwyrm/tests/views/test_inbox.py | 151 ++++++++++++++++++++++---- bookwyrm/views/inbox.py | 2 +- 6 files changed, 129 insertions(+), 186 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 93af0d0c..a49514dc 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -44,9 +44,7 @@ def naive_parse(activity_objects, activity_json): ''' this navigates circular import issues ''' try: activity_type = activity_json['type'] - print(activity_type) serializer = activity_objects[activity_type] - print(serializer) except KeyError as e: raise ActivitySerializerError(e) @@ -64,7 +62,6 @@ class ActivityObject: has a default value ''' for field in fields(self): try: - print(field.name, field.type) value = kwargs[field.name] try: is_subclass = issubclass(field.type, ActivityObject) diff --git a/bookwyrm/activitypub/verbs.py b/bookwyrm/activitypub/verbs.py index c4b30253..fee5f362 100644 --- a/bookwyrm/activitypub/verbs.py +++ b/bookwyrm/activitypub/verbs.py @@ -23,7 +23,6 @@ class Create(Verb): def action(self): ''' create the model instance from the dataclass ''' - # check for dupes self.object.to_model() diff --git a/bookwyrm/incoming.py b/bookwyrm/incoming.py index 83d325af..7ddd893f 100644 --- a/bookwyrm/incoming.py +++ b/bookwyrm/incoming.py @@ -53,14 +53,6 @@ def shared_inbox(request): 'Accept': handle_follow_accept, 'Reject': handle_follow_reject, 'Block': handle_block, - 'Create': { - 'BookList': handle_create_list, - 'Note': handle_create_status, - 'Article': handle_create_status, - 'Review': handle_create_status, - 'Comment': handle_create_status, - 'Quotation': handle_create_status, - }, 'Delete': handle_delete_status, 'Like': handle_favorite, 'Announce': handle_boost, @@ -204,13 +196,6 @@ def handle_unblock(activity): block.delete() -@app.task -def handle_create_list(activity): - ''' a new list ''' - activity = activity['object'] - activitypub.BookList(**activity).to_model(models.List) - - @app.task def handle_update_list(activity): ''' update a list ''' @@ -222,32 +207,6 @@ def handle_update_list(activity): **activity['object']).to_model(models.List, instance=book_list) -@app.task -def handle_create_status(activity): - ''' someone did something, good on them ''' - # deduplicate incoming activities - activity = activity['object'] - status_id = activity.get('id') - if models.Status.objects.filter(remote_id=status_id).count(): - return - - try: - serializer = activitypub.activity_objects[activity['type']] - except KeyError: - return - - activity = serializer(**activity) - try: - model = models.activity_models[activity.type] - except KeyError: - # not a type of status we are prepared to deserialize - return - - status = activity.to_model(model) - if not status: - # it was discarded because it's not a bookwyrm type - return - @app.task def handle_delete_status(activity): diff --git a/bookwyrm/tests/test_incoming.py b/bookwyrm/tests/test_incoming.py index 9be0bb97..288ae302 100644 --- a/bookwyrm/tests/test_incoming.py +++ b/bookwyrm/tests/test_incoming.py @@ -40,19 +40,6 @@ class Incoming(TestCase): self.factory = RequestFactory() - def test_inbox_success(self): - ''' a known type, for which we start a task ''' - request = self.factory.post( - self.local_user.shared_inbox, - '{"type": "Accept", "object": "exists"}', - content_type='application/json') - with patch('bookwyrm.incoming.has_valid_signature') as mock_has_valid: - mock_has_valid.return_value = True - - with patch('bookwyrm.incoming.handle_follow_accept.delay'): - self.assertEqual( - incoming.shared_inbox(request).status_code, 200) - def test_handle_follow(self): ''' remote user wants to follow local user ''' @@ -198,36 +185,6 @@ class Incoming(TestCase): self.assertEqual(follows.count(), 0) - def test_handle_create_list(self): - ''' a new list ''' - activity = { - 'object': { - "id": "https://example.com/list/22", - "type": "BookList", - "totalItems": 1, - "first": "https://example.com/list/22?page=1", - "last": "https://example.com/list/22?page=1", - "name": "Test List", - "owner": "https://example.com/user/mouse", - "to": [ - "https://www.w3.org/ns/activitystreams#Public" - ], - "cc": [ - "https://example.com/user/mouse/followers" - ], - "summary": "summary text", - "curation": "curated", - "@context": "https://www.w3.org/ns/activitystreams" - } - } - incoming.handle_create_list(activity) - book_list = models.List.objects.get() - self.assertEqual(book_list.name, 'Test List') - self.assertEqual(book_list.curation, 'curated') - self.assertEqual(book_list.description, 'summary text') - self.assertEqual(book_list.remote_id, 'https://example.com/list/22') - - def test_handle_update_list(self): ''' a new list ''' with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): @@ -262,80 +219,6 @@ class Incoming(TestCase): self.assertEqual(book_list.remote_id, 'https://example.com/list/22') - def test_handle_create_status(self): - ''' the "it justs works" mode ''' - self.assertEqual(models.Status.objects.count(), 1) - - datafile = pathlib.Path(__file__).parent.joinpath( - 'data/ap_quotation.json') - status_data = json.loads(datafile.read_bytes()) - models.Edition.objects.create( - title='Test Book', remote_id='https://example.com/book/1') - activity = {'object': status_data, 'type': 'Create'} - - incoming.handle_create_status(activity) - - 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.user, self.local_user) - self.assertEqual(models.Status.objects.count(), 2) - - # while we're here, lets ensure we avoid dupes - incoming.handle_create_status(activity) - self.assertEqual(models.Status.objects.count(), 2) - - def test_handle_create_status_unknown_type(self): - ''' folks send you all kinds of things ''' - activity = {'object': {'id': 'hi'}, 'type': 'Fish'} - result = incoming.handle_create_status(activity) - self.assertIsNone(result) - - def test_handle_create_status_remote_note_with_mention(self): - ''' should only create it under the right circumstances ''' - self.assertEqual(models.Status.objects.count(), 1) - self.assertFalse( - models.Notification.objects.filter(user=self.local_user).exists()) - - datafile = pathlib.Path(__file__).parent.joinpath( - 'data/ap_note.json') - status_data = json.loads(datafile.read_bytes()) - activity = {'object': status_data, 'type': 'Create'} - - incoming.handle_create_status(activity) - status = models.Status.objects.last() - 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()) - self.assertEqual( - models.Notification.objects.get().notification_type, 'MENTION') - - def test_handle_create_status_remote_note_with_reply(self): - ''' should only create it under the right circumstances ''' - self.assertEqual(models.Status.objects.count(), 1) - self.assertFalse( - models.Notification.objects.filter(user=self.local_user)) - - datafile = pathlib.Path(__file__).parent.joinpath( - 'data/ap_note.json') - status_data = json.loads(datafile.read_bytes()) - del status_data['tag'] - status_data['inReplyTo'] = self.status.remote_id - activity = {'object': status_data, 'type': 'Create'} - - incoming.handle_create_status(activity) - status = models.Status.objects.last() - 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)) - self.assertEqual( - models.Notification.objects.get().notification_type, 'REPLY') - - def test_handle_delete_status(self): ''' remove a status ''' self.status.user = self.remote_user diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 3bb7760e..784f2127 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -1,11 +1,12 @@ ''' tests incoming activities''' import json +import pathlib from unittest.mock import patch from django.http import HttpResponseNotAllowed, HttpResponseNotFound from django.test import TestCase, Client -from bookwyrm import models +from bookwyrm import models, views class Inbox(TestCase): ''' readthrough tests ''' @@ -31,6 +32,19 @@ class Inbox(TestCase): content='Test status', remote_id='https://example.com/status/1', ) + + self.create_json = { + 'id': 'hi', + 'type': 'Create', + 'actor': 'hi', + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://example.com/user/mouse/followers" + ], + 'object': {} + } models.SiteSettings.objects.create() @@ -86,34 +100,24 @@ class Inbox(TestCase): def test_inbox_success(self): ''' a known type, for which we start a task ''' - activity = { - 'id': 'hi', - 'type': 'Create', - 'actor': 'hi', + activity = self.create_json + activity['object'] = { + "id": "https://example.com/list/22", + "type": "BookList", + "totalItems": 1, + "first": "https://example.com/list/22?page=1", + "last": "https://example.com/list/22?page=1", + "name": "Test List", + "owner": "https://example.com/user/mouse", "to": [ "https://www.w3.org/ns/activitystreams#Public" ], "cc": [ "https://example.com/user/mouse/followers" ], - 'object': { - "id": "https://example.com/list/22", - "type": "BookList", - "totalItems": 1, - "first": "https://example.com/list/22?page=1", - "last": "https://example.com/list/22?page=1", - "name": "Test List", - "owner": "https://example.com/user/mouse", - "to": [ - "https://www.w3.org/ns/activitystreams#Public" - ], - "cc": [ - "https://example.com/user/mouse/followers" - ], - "summary": "summary text", - "curation": "curated", - "@context": "https://www.w3.org/ns/activitystreams" - } + "summary": "summary text", + "curation": "curated", + "@context": "https://www.w3.org/ns/activitystreams" } with patch('bookwyrm.views.inbox.has_valid_signature') as mock_valid: mock_valid.return_value = True @@ -125,3 +129,104 @@ class Inbox(TestCase): content_type="application/json" ) self.assertEqual(result.status_code, 200) + + + def test_handle_create_status(self): + ''' the "it justs works" mode ''' + self.assertEqual(models.Status.objects.count(), 1) + + datafile = pathlib.Path(__file__).parent.joinpath( + '../data/ap_quotation.json') + status_data = json.loads(datafile.read_bytes()) + models.Edition.objects.create( + title='Test Book', remote_id='https://example.com/book/1') + activity = self.create_json + activity['object'] = status_data + + views.inbox.activity_task(activity) + + 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.user, self.local_user) + self.assertEqual(models.Status.objects.count(), 2) + + # while we're here, lets ensure we avoid dupes + views.inbox.activity_task(activity) + self.assertEqual(models.Status.objects.count(), 2) + + + def test_handle_create_status_remote_note_with_mention(self): + ''' should only create it under the right circumstances ''' + self.assertEqual(models.Status.objects.count(), 1) + self.assertFalse( + models.Notification.objects.filter(user=self.local_user).exists()) + + datafile = pathlib.Path(__file__).parent.joinpath( + '../data/ap_note.json') + status_data = json.loads(datafile.read_bytes()) + activity = self.create_json + activity['object'] = status_data + + views.inbox.activity_task(activity) + status = models.Status.objects.last() + 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()) + self.assertEqual( + models.Notification.objects.get().notification_type, 'MENTION') + + def test_handle_create_status_remote_note_with_reply(self): + ''' should only create it under the right circumstances ''' + self.assertEqual(models.Status.objects.count(), 1) + self.assertFalse( + models.Notification.objects.filter(user=self.local_user)) + + datafile = pathlib.Path(__file__).parent.joinpath( + '../data/ap_note.json') + status_data = json.loads(datafile.read_bytes()) + del status_data['tag'] + status_data['inReplyTo'] = self.status.remote_id + activity = self.create_json + activity['object'] = status_data + + views.inbox.activity_task(activity) + status = models.Status.objects.last() + 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)) + self.assertEqual( + models.Notification.objects.get().notification_type, 'REPLY') + + + def test_handle_create_list(self): + ''' a new list ''' + activity = self.create_json + activity['object'] = { + "id": "https://example.com/list/22", + "type": "BookList", + "totalItems": 1, + "first": "https://example.com/list/22?page=1", + "last": "https://example.com/list/22?page=1", + "name": "Test List", + "owner": "https://example.com/user/mouse", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://example.com/user/mouse/followers" + ], + "summary": "summary text", + "curation": "curated", + "@context": "https://www.w3.org/ns/activitystreams" + } + views.inbox.activity_task(activity) + book_list = models.List.objects.get() + self.assertEqual(book_list.name, 'Test List') + self.assertEqual(book_list.curation, 'curated') + self.assertEqual(book_list.description, 'summary text') + self.assertEqual(book_list.remote_id, 'https://example.com/list/22') diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index fdac8446..6e7b036b 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -45,7 +45,7 @@ class Inbox(View): not activity_json['type'] in activitypub.activity_objects: return HttpResponseNotFound() - activity_task.delay() + activity_task.delay(activity_json) return HttpResponse() From a16b81a6eb8232dc3f88f0d5ca6b3ae4ac95bf1d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Feb 2021 20:49:23 -0800 Subject: [PATCH 0012/1285] Adds actions for all verbs --- bookwyrm/activitypub/base_activity.py | 8 +- bookwyrm/activitypub/verbs.py | 40 ++- bookwyrm/models/relationship.py | 12 +- bookwyrm/tests/test_incoming.py | 493 -------------------------- bookwyrm/tests/views/test_follow.py | 2 + bookwyrm/tests/views/test_inbox.py | 456 ++++++++++++++++++++++++ bookwyrm/views/follow.py | 2 - 7 files changed, 507 insertions(+), 506 deletions(-) delete mode 100644 bookwyrm/tests/test_incoming.py diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index a49514dc..631ca2dd 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -50,6 +50,7 @@ def naive_parse(activity_objects, activity_json): return serializer(activity_objects=activity_objects, **activity_json) + @dataclass(init=False) class ActivityObject: ''' actor activitypub json ''' @@ -79,7 +80,7 @@ class ActivityObject: setattr(self, field.name, value) - def to_model(self, instance=None, save=True): + def to_model(self, instance=None, allow_create=True, save=True): ''' convert from an activity to a model instance ''' # figure out the right model -- wish I had a better way for this models = apps.get_models() @@ -95,7 +96,10 @@ class ActivityObject: return instance # check for an existing instance, if we're not updating a known obj - instance = instance or model.find_existing(self.serialize()) or model() + instance = instance or model.find_existing(self.serialize()) + if not instance and not allow_create: + return None + instance = instance or model() for field in instance.simple_fields: field.set_field_from_activity(instance, self) diff --git a/bookwyrm/activitypub/verbs.py b/bookwyrm/activitypub/verbs.py index fee5f362..d781993e 100644 --- a/bookwyrm/activitypub/verbs.py +++ b/bookwyrm/activitypub/verbs.py @@ -12,6 +12,10 @@ class Verb(ActivityObject): actor: str object: ActivityObject + def action(self): + ''' usually we just want to save, this can be overridden as needed ''' + self.object.to_model() + @dataclass(init=False) class Create(Verb): @@ -21,10 +25,6 @@ class Create(Verb): signature: Signature = None type: str = 'Create' - def action(self): - ''' create the model instance from the dataclass ''' - self.object.to_model() - @dataclass(init=False) class Delete(Verb): @@ -33,6 +33,12 @@ class Delete(Verb): cc: List type: str = 'Delete' + def action(self): + ''' find and delete the activity object ''' + obj = self.object.to_model(save=False, allow_create=False) + obj.delete() + + @dataclass(init=False) class Update(Verb): @@ -40,12 +46,21 @@ class Update(Verb): to: List type: str = 'Update' + def action(self): + ''' update a model instance from the dataclass ''' + self.object.to_model(allow_create=False) + @dataclass(init=False) class Undo(Verb): ''' Undo an activity ''' type: str = 'Undo' + def action(self): + ''' find and remove the activity object ''' + obj = self.object.to_model(save=False, allow_create=False) + obj.delete() + @dataclass(init=False) class Follow(Verb): @@ -53,18 +68,25 @@ class Follow(Verb): object: str type: str = 'Follow' + @dataclass(init=False) class Block(Verb): ''' Block activity ''' object: str type: str = 'Block' + @dataclass(init=False) class Accept(Verb): ''' Accept activity ''' object: Follow type: str = 'Accept' + def action(self): + ''' find and remove the activity object ''' + obj = self.object.to_model(save=False, allow_create=False) + obj.accept() + @dataclass(init=False) class Reject(Verb): @@ -72,6 +94,11 @@ class Reject(Verb): object: Follow type: str = 'Reject' + def action(self): + ''' find and remove the activity object ''' + obj = self.object.to_model(save=False, allow_create=False) + obj.reject() + @dataclass(init=False) class Add(Verb): @@ -101,3 +128,8 @@ class Remove(Verb): '''Remove activity ''' target: ActivityObject type: str = 'Remove' + + def action(self): + ''' find and remove the activity object ''' + obj = self.object.to_model(save=False, allow_create=False) + obj.delete() diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index e2db5468..e4c6f4fa 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -56,11 +56,9 @@ class UserRelationship(BookWyrmModel): return '%s#%s/%d' % (base_path, status, self.id) -class UserFollows(ActivitypubMixin, UserRelationship): +class UserFollows(UserRelationship): ''' Following a user ''' status = 'follows' - activity_serializer = activitypub.Follow - @classmethod def from_request(cls, follow_request): @@ -101,9 +99,13 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship): self.broadcast(self.to_activity(), self.user_subject) if self.user_object.local: + manually_approves = self.user_object.manually_approves_followers + if manually_approves: + self.accept() + model = apps.get_model('bookwyrm.Notification', require_ready=True) - notification_type = 'FOLLOW_REQUEST' \ - if self.user_object.manually_approves_followers else 'FOLLOW' + notification_type = 'FOLLOW_REQUEST' if \ + manually_approves else 'FOLLOW' model.objects.create( user=self.user_object, related_user=self.user_subject, diff --git a/bookwyrm/tests/test_incoming.py b/bookwyrm/tests/test_incoming.py deleted file mode 100644 index 288ae302..00000000 --- a/bookwyrm/tests/test_incoming.py +++ /dev/null @@ -1,493 +0,0 @@ -''' test incoming activities ''' -from datetime import datetime -import json -import pathlib -from unittest.mock import patch - -from django.http import HttpResponseBadRequest, HttpResponseNotAllowed, \ - HttpResponseNotFound -from django.test import TestCase -from django.test.client import RequestFactory -import responses - -from bookwyrm import models, incoming - - -#pylint: disable=too-many-public-methods -class Incoming(TestCase): - ''' a lot here: all handlers for receiving activitypub requests ''' - def setUp(self): - ''' we need basic things, like users ''' - self.local_user = models.User.objects.create_user( - 'mouse@example.com', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse') - self.local_user.remote_id = 'https://example.com/user/mouse' - self.local_user.save(broadcast=False) - with patch('bookwyrm.models.user.set_remote_server.delay'): - self.remote_user = models.User.objects.create_user( - 'rat', 'rat@rat.com', 'ratword', - local=False, - remote_id='https://example.com/users/rat', - inbox='https://example.com/users/rat/inbox', - outbox='https://example.com/users/rat/outbox', - ) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - self.status = models.Status.objects.create( - user=self.local_user, - content='Test status', - remote_id='https://example.com/status/1', - ) - self.factory = RequestFactory() - - - - def test_handle_follow(self): - ''' remote user wants to follow local user ''' - activity = { - "@context": "https://www.w3.org/ns/activitystreams", - "id": "https://example.com/users/rat/follows/123", - "type": "Follow", - "actor": "https://example.com/users/rat", - "object": "https://example.com/user/mouse" - } - - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - incoming.handle_follow(activity) - - # notification created - notification = models.Notification.objects.get() - self.assertEqual(notification.user, self.local_user) - self.assertEqual(notification.notification_type, 'FOLLOW') - - # the request should have been deleted - requests = models.UserFollowRequest.objects.all() - self.assertEqual(list(requests), []) - - # the follow relationship should exist - follow = models.UserFollows.objects.get(user_object=self.local_user) - self.assertEqual(follow.user_subject, self.remote_user) - - - def test_handle_follow_manually_approved(self): - ''' needs approval before following ''' - activity = { - "@context": "https://www.w3.org/ns/activitystreams", - "id": "https://example.com/users/rat/follows/123", - "type": "Follow", - "actor": "https://example.com/users/rat", - "object": "https://example.com/user/mouse" - } - - self.local_user.manually_approves_followers = True - self.local_user.save(broadcast=False) - - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - incoming.handle_follow(activity) - - # notification created - notification = models.Notification.objects.get() - self.assertEqual(notification.user, self.local_user) - self.assertEqual(notification.notification_type, 'FOLLOW_REQUEST') - - # the request should exist - request = models.UserFollowRequest.objects.get() - self.assertEqual(request.user_subject, self.remote_user) - self.assertEqual(request.user_object, self.local_user) - - # the follow relationship should not exist - follow = models.UserFollows.objects.all() - self.assertEqual(list(follow), []) - - - def test_handle_unfollow(self): - ''' remove a relationship ''' - activity = { - "type": "Undo", - "@context": "https://www.w3.org/ns/activitystreams", - "object": { - "id": "https://example.com/users/rat/follows/123", - "type": "Follow", - "actor": "https://example.com/users/rat", - "object": "https://example.com/user/mouse" - } - } - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - models.UserFollows.objects.create( - user_subject=self.remote_user, user_object=self.local_user) - self.assertEqual(self.remote_user, self.local_user.followers.first()) - - incoming.handle_unfollow(activity) - self.assertIsNone(self.local_user.followers.first()) - - - def test_handle_follow_accept(self): - ''' a remote user approved a follow request from local ''' - activity = { - "@context": "https://www.w3.org/ns/activitystreams", - "id": "https://example.com/users/rat/follows/123#accepts", - "type": "Accept", - "actor": "https://example.com/users/rat", - "object": { - "id": "https://example.com/users/rat/follows/123", - "type": "Follow", - "actor": "https://example.com/user/mouse", - "object": "https://example.com/users/rat" - } - } - - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - models.UserFollowRequest.objects.create( - user_subject=self.local_user, - user_object=self.remote_user - ) - self.assertEqual(models.UserFollowRequest.objects.count(), 1) - - incoming.handle_follow_accept(activity) - - # request should be deleted - self.assertEqual(models.UserFollowRequest.objects.count(), 0) - - # relationship should be created - follows = self.remote_user.followers - self.assertEqual(follows.count(), 1) - self.assertEqual(follows.first(), self.local_user) - - - def test_handle_follow_reject(self): - ''' turn down a follow request ''' - activity = { - "@context": "https://www.w3.org/ns/activitystreams", - "id": "https://example.com/users/rat/follows/123#accepts", - "type": "Reject", - "actor": "https://example.com/users/rat", - "object": { - "id": "https://example.com/users/rat/follows/123", - "type": "Follow", - "actor": "https://example.com/user/mouse", - "object": "https://example.com/users/rat" - } - } - - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - models.UserFollowRequest.objects.create( - user_subject=self.local_user, - user_object=self.remote_user - ) - self.assertEqual(models.UserFollowRequest.objects.count(), 1) - - incoming.handle_follow_reject(activity) - - # request should be deleted - self.assertEqual(models.UserFollowRequest.objects.count(), 0) - - # relationship should be created - follows = self.remote_user.followers - self.assertEqual(follows.count(), 0) - - - def test_handle_update_list(self): - ''' a new list ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - book_list = models.List.objects.create( - name='hi', remote_id='https://example.com/list/22', - user=self.local_user) - activity = { - 'object': { - "id": "https://example.com/list/22", - "type": "BookList", - "totalItems": 1, - "first": "https://example.com/list/22?page=1", - "last": "https://example.com/list/22?page=1", - "name": "Test List", - "owner": "https://example.com/user/mouse", - "to": [ - "https://www.w3.org/ns/activitystreams#Public" - ], - "cc": [ - "https://example.com/user/mouse/followers" - ], - "summary": "summary text", - "curation": "curated", - "@context": "https://www.w3.org/ns/activitystreams" - } - } - incoming.handle_update_list(activity) - book_list.refresh_from_db() - self.assertEqual(book_list.name, 'Test List') - self.assertEqual(book_list.curation, 'curated') - self.assertEqual(book_list.description, 'summary text') - self.assertEqual(book_list.remote_id, 'https://example.com/list/22') - - - def test_handle_delete_status(self): - ''' remove a status ''' - self.status.user = self.remote_user - self.status.save(broadcast=False) - - self.assertFalse(self.status.deleted) - activity = { - 'type': 'Delete', - 'id': '%s/activity' % self.status.remote_id, - 'actor': self.remote_user.remote_id, - 'object': {'id': self.status.remote_id}, - } - incoming.handle_delete_status(activity) - # deletion doens't remove the status, it turns it into a tombstone - status = models.Status.objects.get() - self.assertTrue(status.deleted) - self.assertIsInstance(status.deleted_date, datetime) - - - def test_handle_delete_status_notifications(self): - ''' remove a status with related notifications ''' - self.status.user = self.remote_user - self.status.save(broadcast=False) - models.Notification.objects.create( - related_status=self.status, - user=self.local_user, - notification_type='MENTION' - ) - # this one is innocent, don't delete it - notif = models.Notification.objects.create( - user=self.local_user, - notification_type='MENTION' - ) - self.assertFalse(self.status.deleted) - self.assertEqual(models.Notification.objects.count(), 2) - activity = { - 'type': 'Delete', - 'id': '%s/activity' % self.status.remote_id, - 'actor': self.remote_user.remote_id, - 'object': {'id': self.status.remote_id}, - } - incoming.handle_delete_status(activity) - # deletion doens't remove the status, it turns it into a tombstone - status = models.Status.objects.get() - self.assertTrue(status.deleted) - self.assertIsInstance(status.deleted_date, datetime) - - # notifications should be truly deleted - self.assertEqual(models.Notification.objects.count(), 1) - self.assertEqual(models.Notification.objects.get(), notif) - - - def test_handle_favorite(self): - ''' fav a status ''' - activity = { - '@context': 'https://www.w3.org/ns/activitystreams', - 'id': 'https://example.com/fav/1', - 'actor': 'https://example.com/users/rat', - 'published': 'Mon, 25 May 2020 19:31:20 GMT', - 'object': 'https://example.com/status/1', - } - - incoming.handle_favorite(activity) - - fav = models.Favorite.objects.get(remote_id='https://example.com/fav/1') - self.assertEqual(fav.status, self.status) - self.assertEqual(fav.remote_id, 'https://example.com/fav/1') - self.assertEqual(fav.user, self.remote_user) - - def test_handle_unfavorite(self): - ''' fav a status ''' - activity = { - 'id': 'https://example.com/fav/1#undo', - 'type': 'Undo', - 'object': { - '@context': 'https://www.w3.org/ns/activitystreams', - 'id': 'https://example.com/fav/1', - 'actor': 'https://example.com/users/rat', - 'published': 'Mon, 25 May 2020 19:31:20 GMT', - 'object': 'https://example.com/fav/1', - } - } - models.Favorite.objects.create( - status=self.status, - user=self.remote_user, - remote_id='https://example.com/fav/1') - self.assertEqual(models.Favorite.objects.count(), 1) - - incoming.handle_unfavorite(activity) - self.assertEqual(models.Favorite.objects.count(), 0) - - - def test_handle_boost(self): - ''' boost a status ''' - self.assertEqual(models.Notification.objects.count(), 0) - activity = { - 'type': 'Announce', - 'id': '%s/boost' % self.status.remote_id, - 'actor': self.remote_user.remote_id, - 'object': self.status.to_activity(), - } - with patch('bookwyrm.models.status.Status.ignore_activity') \ - as discarder: - discarder.return_value = False - incoming.handle_boost(activity) - boost = models.Boost.objects.get() - self.assertEqual(boost.boosted_status, self.status) - notification = models.Notification.objects.get() - self.assertEqual(notification.user, self.local_user) - self.assertEqual(notification.related_status, self.status) - - - @responses.activate - def test_handle_discarded_boost(self): - ''' test a boost of a mastodon status that will be discarded ''' - activity = { - 'type': 'Announce', - 'id': 'http://www.faraway.com/boost/12', - 'actor': self.remote_user.remote_id, - 'object': self.status.to_activity(), - } - responses.add( - responses.GET, - 'http://www.faraway.com/boost/12', - json={'id': 'http://www.faraway.com/boost/12'}, - status=200) - incoming.handle_boost(activity) - self.assertEqual(models.Boost.objects.count(), 0) - - - def test_handle_unboost(self): - ''' undo a boost ''' - activity = { - 'type': 'Undo', - 'object': { - 'type': 'Announce', - 'id': '%s/boost' % self.status.remote_id, - 'actor': self.local_user.remote_id, - 'object': self.status.to_activity(), - } - } - models.Boost.objects.create( - boosted_status=self.status, user=self.remote_user) - incoming.handle_unboost(activity) - - - def test_handle_add_book(self): - ''' shelving a book ''' - book = models.Edition.objects.create( - title='Test', remote_id='https://bookwyrm.social/book/37292') - shelf = models.Shelf.objects.create( - user=self.remote_user, name='Test Shelf') - shelf.remote_id = 'https://bookwyrm.social/user/mouse/shelf/to-read' - shelf.save() - - activity = { - "id": "https://bookwyrm.social/shelfbook/6189#add", - "type": "Add", - "actor": "https://example.com/users/rat", - "object": "https://bookwyrm.social/book/37292", - "target": "https://bookwyrm.social/user/mouse/shelf/to-read", - "@context": "https://www.w3.org/ns/activitystreams" - } - incoming.handle_add(activity) - self.assertEqual(shelf.books.first(), book) - - - def test_handle_update_user(self): - ''' update an existing user ''' - # we only do this with remote users - self.local_user.local = False - self.local_user.save() - - datafile = pathlib.Path(__file__).parent.joinpath( - 'data/ap_user.json') - userdata = json.loads(datafile.read_bytes()) - del userdata['icon'] - self.assertIsNone(self.local_user.name) - incoming.handle_update_user({'object': userdata}) - user = models.User.objects.get(id=self.local_user.id) - self.assertEqual(user.name, 'MOUSE?? MOUSE!!') - self.assertEqual(user.username, 'mouse@example.com') - self.assertEqual(user.localname, 'mouse') - - - def test_handle_update_edition(self): - ''' update an existing edition ''' - datafile = pathlib.Path(__file__).parent.joinpath( - 'data/bw_edition.json') - bookdata = json.loads(datafile.read_bytes()) - - models.Work.objects.create( - title='Test Work', remote_id='https://bookwyrm.social/book/5988') - book = models.Edition.objects.create( - title='Test Book', remote_id='https://bookwyrm.social/book/5989') - - del bookdata['authors'] - self.assertEqual(book.title, 'Test Book') - - with patch( - 'bookwyrm.activitypub.base_activity.set_related_field.delay'): - incoming.handle_update_edition({'object': bookdata}) - book = models.Edition.objects.get(id=book.id) - self.assertEqual(book.title, 'Piranesi') - - - def test_handle_update_work(self): - ''' update an existing edition ''' - datafile = pathlib.Path(__file__).parent.joinpath( - 'data/bw_work.json') - bookdata = json.loads(datafile.read_bytes()) - - book = models.Work.objects.create( - title='Test Book', remote_id='https://bookwyrm.social/book/5988') - - del bookdata['authors'] - self.assertEqual(book.title, 'Test Book') - with patch( - 'bookwyrm.activitypub.base_activity.set_related_field.delay'): - incoming.handle_update_work({'object': bookdata}) - book = models.Work.objects.get(id=book.id) - self.assertEqual(book.title, 'Piranesi') - - - def test_handle_blocks(self): - ''' create a "block" database entry from an activity ''' - self.local_user.followers.add(self.remote_user) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - models.UserFollowRequest.objects.create( - user_subject=self.local_user, - user_object=self.remote_user) - self.assertTrue(models.UserFollows.objects.exists()) - self.assertTrue(models.UserFollowRequest.objects.exists()) - - activity = { - "@context": "https://www.w3.org/ns/activitystreams", - "id": "https://example.com/9e1f41ac-9ddd-4159", - "type": "Block", - "actor": "https://example.com/users/rat", - "object": "https://example.com/user/mouse" - } - - incoming.handle_block(activity) - block = models.UserBlocks.objects.get() - self.assertEqual(block.user_subject, self.remote_user) - self.assertEqual(block.user_object, self.local_user) - self.assertEqual( - block.remote_id, 'https://example.com/9e1f41ac-9ddd-4159') - - self.assertFalse(models.UserFollows.objects.exists()) - self.assertFalse(models.UserFollowRequest.objects.exists()) - - - def test_handle_unblock(self): - ''' unblock a user ''' - self.remote_user.blocks.add(self.local_user) - - block = models.UserBlocks.objects.get() - block.remote_id = 'https://example.com/9e1f41ac-9ddd-4159' - block.save() - - self.assertEqual(block.user_subject, self.remote_user) - self.assertEqual(block.user_object, self.local_user) - activity = {'type': 'Undo', 'object': { - "@context": "https://www.w3.org/ns/activitystreams", - "id": "https://example.com/9e1f41ac-9ddd-4159", - "type": "Block", - "actor": "https://example.com/users/rat", - "object": "https://example.com/user/mouse" - }} - incoming.handle_unblock(activity) - self.assertFalse(models.UserBlocks.objects.exists()) diff --git a/bookwyrm/tests/views/test_follow.py b/bookwyrm/tests/views/test_follow.py index 943ffcf8..9a157659 100644 --- a/bookwyrm/tests/views/test_follow.py +++ b/bookwyrm/tests/views/test_follow.py @@ -105,6 +105,8 @@ class BookViews(TestCase): request.user = self.local_user self.remote_user.followers.add(self.local_user) self.assertEqual(self.remote_user.followers.count(), 1) + # need to see if this ACTUALLY broadcasts + raise ValueError() with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): views.unfollow(request) diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 784f2127..9ca7198e 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -1,13 +1,17 @@ ''' tests incoming activities''' +from datetime import datetime import json import pathlib from unittest.mock import patch from django.http import HttpResponseNotAllowed, HttpResponseNotFound from django.test import TestCase, Client +import responses from bookwyrm import models, views + +#pylint: disable=too-many-public-methods class Inbox(TestCase): ''' readthrough tests ''' def setUp(self): @@ -230,3 +234,455 @@ class Inbox(TestCase): self.assertEqual(book_list.curation, 'curated') self.assertEqual(book_list.description, 'summary text') self.assertEqual(book_list.remote_id, 'https://example.com/list/22') + + + def test_handle_follow(self): + ''' remote user wants to follow local user ''' + activity = { + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://example.com/users/rat/follows/123", + "type": "Follow", + "actor": "https://example.com/users/rat", + "object": "https://example.com/user/mouse" + } + + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + views.inbox.activity_task(activity) + + # notification created + notification = models.Notification.objects.get() + self.assertEqual(notification.user, self.local_user) + self.assertEqual(notification.notification_type, 'FOLLOW') + + # the request should have been deleted + requests = models.UserFollowRequest.objects.all() + self.assertEqual(list(requests), []) + + # the follow relationship should exist + follow = models.UserFollows.objects.get(user_object=self.local_user) + self.assertEqual(follow.user_subject, self.remote_user) + + + def test_handle_follow_manually_approved(self): + ''' needs approval before following ''' + activity = { + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://example.com/users/rat/follows/123", + "type": "Follow", + "actor": "https://example.com/users/rat", + "object": "https://example.com/user/mouse" + } + + self.local_user.manually_approves_followers = True + self.local_user.save(broadcast=False) + + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + views.inbox.activity_task(activity) + + # notification created + notification = models.Notification.objects.get() + self.assertEqual(notification.user, self.local_user) + self.assertEqual(notification.notification_type, 'FOLLOW_REQUEST') + + # the request should exist + request = models.UserFollowRequest.objects.get() + self.assertEqual(request.user_subject, self.remote_user) + self.assertEqual(request.user_object, self.local_user) + + # the follow relationship should not exist + follow = models.UserFollows.objects.all() + self.assertEqual(list(follow), []) + + + def test_handle_unfollow(self): + ''' remove a relationship ''' + activity = { + "type": "Undo", + "@context": "https://www.w3.org/ns/activitystreams", + "object": { + "id": "https://example.com/users/rat/follows/123", + "type": "Follow", + "actor": "https://example.com/users/rat", + "object": "https://example.com/user/mouse" + } + } + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + models.UserFollows.objects.create( + user_subject=self.remote_user, user_object=self.local_user) + self.assertEqual(self.remote_user, self.local_user.followers.first()) + + views.inbox.activity_task(activity) + self.assertIsNone(self.local_user.followers.first()) + + + def test_handle_follow_accept(self): + ''' a remote user approved a follow request from local ''' + activity = { + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://example.com/users/rat/follows/123#accepts", + "type": "Accept", + "actor": "https://example.com/users/rat", + "object": { + "id": "https://example.com/users/rat/follows/123", + "type": "Follow", + "actor": "https://example.com/user/mouse", + "object": "https://example.com/users/rat" + } + } + + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + models.UserFollowRequest.objects.create( + user_subject=self.local_user, + user_object=self.remote_user + ) + self.assertEqual(models.UserFollowRequest.objects.count(), 1) + + views.inbox.activity_task(activity) + + # request should be deleted + self.assertEqual(models.UserFollowRequest.objects.count(), 0) + + # relationship should be created + follows = self.remote_user.followers + self.assertEqual(follows.count(), 1) + self.assertEqual(follows.first(), self.local_user) + + + def test_handle_follow_reject(self): + ''' turn down a follow request ''' + activity = { + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://example.com/users/rat/follows/123#accepts", + "type": "Reject", + "actor": "https://example.com/users/rat", + "object": { + "id": "https://example.com/users/rat/follows/123", + "type": "Follow", + "actor": "https://example.com/user/mouse", + "object": "https://example.com/users/rat" + } + } + + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + models.UserFollowRequest.objects.create( + user_subject=self.local_user, + user_object=self.remote_user + ) + self.assertEqual(models.UserFollowRequest.objects.count(), 1) + + views.inbox.activity_task(activity) + + # request should be deleted + self.assertEqual(models.UserFollowRequest.objects.count(), 0) + + # relationship should be created + follows = self.remote_user.followers + self.assertEqual(follows.count(), 0) + + + def test_handle_update_list(self): + ''' a new list ''' + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + book_list = models.List.objects.create( + name='hi', remote_id='https://example.com/list/22', + user=self.local_user) + activity = { + 'object': { + "id": "https://example.com/list/22", + "type": "BookList", + "totalItems": 1, + "first": "https://example.com/list/22?page=1", + "last": "https://example.com/list/22?page=1", + "name": "Test List", + "owner": "https://example.com/user/mouse", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://example.com/user/mouse/followers" + ], + "summary": "summary text", + "curation": "curated", + "@context": "https://www.w3.org/ns/activitystreams" + } + } + views.inbox.activity_task(activity) + book_list.refresh_from_db() + self.assertEqual(book_list.name, 'Test List') + self.assertEqual(book_list.curation, 'curated') + self.assertEqual(book_list.description, 'summary text') + self.assertEqual(book_list.remote_id, 'https://example.com/list/22') + + + def test_handle_delete_status(self): + ''' remove a status ''' + self.status.user = self.remote_user + self.status.save(broadcast=False) + + self.assertFalse(self.status.deleted) + activity = { + 'type': 'Delete', + 'id': '%s/activity' % self.status.remote_id, + 'actor': self.remote_user.remote_id, + 'object': {'id': self.status.remote_id}, + } + views.inbox.activity_task(activity) + # deletion doens't remove the status, it turns it into a tombstone + status = models.Status.objects.get() + self.assertTrue(status.deleted) + self.assertIsInstance(status.deleted_date, datetime) + + + def test_handle_delete_status_notifications(self): + ''' remove a status with related notifications ''' + self.status.user = self.remote_user + self.status.save(broadcast=False) + models.Notification.objects.create( + related_status=self.status, + user=self.local_user, + notification_type='MENTION' + ) + # this one is innocent, don't delete it + notif = models.Notification.objects.create( + user=self.local_user, + notification_type='MENTION' + ) + self.assertFalse(self.status.deleted) + self.assertEqual(models.Notification.objects.count(), 2) + activity = { + 'type': 'Delete', + 'id': '%s/activity' % self.status.remote_id, + 'actor': self.remote_user.remote_id, + 'object': {'id': self.status.remote_id}, + } + views.inbox.activity_task(activity) + # deletion doens't remove the status, it turns it into a tombstone + status = models.Status.objects.get() + self.assertTrue(status.deleted) + self.assertIsInstance(status.deleted_date, datetime) + + # notifications should be truly deleted + self.assertEqual(models.Notification.objects.count(), 1) + self.assertEqual(models.Notification.objects.get(), notif) + + + def test_handle_favorite(self): + ''' fav a status ''' + activity = { + '@context': 'https://www.w3.org/ns/activitystreams', + 'id': 'https://example.com/fav/1', + 'actor': 'https://example.com/users/rat', + 'published': 'Mon, 25 May 2020 19:31:20 GMT', + 'object': 'https://example.com/status/1', + } + + views.inbox.activity_task(activity) + + fav = models.Favorite.objects.get(remote_id='https://example.com/fav/1') + self.assertEqual(fav.status, self.status) + self.assertEqual(fav.remote_id, 'https://example.com/fav/1') + self.assertEqual(fav.user, self.remote_user) + + def test_handle_unfavorite(self): + ''' fav a status ''' + activity = { + 'id': 'https://example.com/fav/1#undo', + 'type': 'Undo', + 'object': { + '@context': 'https://www.w3.org/ns/activitystreams', + 'id': 'https://example.com/fav/1', + 'actor': 'https://example.com/users/rat', + 'published': 'Mon, 25 May 2020 19:31:20 GMT', + 'object': 'https://example.com/fav/1', + } + } + models.Favorite.objects.create( + status=self.status, + user=self.remote_user, + remote_id='https://example.com/fav/1') + self.assertEqual(models.Favorite.objects.count(), 1) + + views.inbox.activity_task(activity) + self.assertEqual(models.Favorite.objects.count(), 0) + + + def test_handle_boost(self): + ''' boost a status ''' + self.assertEqual(models.Notification.objects.count(), 0) + activity = { + 'type': 'Announce', + 'id': '%s/boost' % self.status.remote_id, + 'actor': self.remote_user.remote_id, + 'object': self.status.to_activity(), + } + with patch('bookwyrm.models.status.Status.ignore_activity') \ + as discarder: + discarder.return_value = False + views.inbox.activity_task(activity) + boost = models.Boost.objects.get() + self.assertEqual(boost.boosted_status, self.status) + notification = models.Notification.objects.get() + self.assertEqual(notification.user, self.local_user) + self.assertEqual(notification.related_status, self.status) + + + @responses.activate + def test_handle_discarded_boost(self): + ''' test a boost of a mastodon status that will be discarded ''' + activity = { + 'type': 'Announce', + 'id': 'http://www.faraway.com/boost/12', + 'actor': self.remote_user.remote_id, + 'object': self.status.to_activity(), + } + responses.add( + responses.GET, + 'http://www.faraway.com/boost/12', + json={'id': 'http://www.faraway.com/boost/12'}, + status=200) + views.inbox.activity_task(activity) + self.assertEqual(models.Boost.objects.count(), 0) + + + def test_handle_unboost(self): + ''' undo a boost ''' + activity = { + 'type': 'Undo', + 'object': { + 'type': 'Announce', + 'id': '%s/boost' % self.status.remote_id, + 'actor': self.local_user.remote_id, + 'object': self.status.to_activity(), + } + } + models.Boost.objects.create( + boosted_status=self.status, user=self.remote_user) + views.inbox.activity_task(activity) + + + def test_handle_add_book(self): + ''' shelving a book ''' + book = models.Edition.objects.create( + title='Test', remote_id='https://bookwyrm.social/book/37292') + shelf = models.Shelf.objects.create( + user=self.remote_user, name='Test Shelf') + shelf.remote_id = 'https://bookwyrm.social/user/mouse/shelf/to-read' + shelf.save() + + activity = { + "id": "https://bookwyrm.social/shelfbook/6189#add", + "type": "Add", + "actor": "https://example.com/users/rat", + "object": "https://bookwyrm.social/book/37292", + "target": "https://bookwyrm.social/user/mouse/shelf/to-read", + "@context": "https://www.w3.org/ns/activitystreams" + } + views.inbox.activity_task(activity) + self.assertEqual(shelf.books.first(), book) + + + def test_handle_update_user(self): + ''' update an existing user ''' + # we only do this with remote users + self.local_user.local = False + self.local_user.save() + + datafile = pathlib.Path(__file__).parent.joinpath( + 'data/ap_user.json') + userdata = json.loads(datafile.read_bytes()) + del userdata['icon'] + self.assertIsNone(self.local_user.name) + views.inbox.activity_task({'object': userdata}) + user = models.User.objects.get(id=self.local_user.id) + self.assertEqual(user.name, 'MOUSE?? MOUSE!!') + self.assertEqual(user.username, 'mouse@example.com') + self.assertEqual(user.localname, 'mouse') + + + def test_handle_update_edition(self): + ''' update an existing edition ''' + datafile = pathlib.Path(__file__).parent.joinpath( + 'data/bw_edition.json') + bookdata = json.loads(datafile.read_bytes()) + + models.Work.objects.create( + title='Test Work', remote_id='https://bookwyrm.social/book/5988') + book = models.Edition.objects.create( + title='Test Book', remote_id='https://bookwyrm.social/book/5989') + + del bookdata['authors'] + self.assertEqual(book.title, 'Test Book') + + with patch( + 'bookwyrm.activitypub.base_activity.set_related_field.delay'): + views.inbox.activity_task({'object': bookdata}) + book = models.Edition.objects.get(id=book.id) + self.assertEqual(book.title, 'Piranesi') + + + def test_handle_update_work(self): + ''' update an existing edition ''' + datafile = pathlib.Path(__file__).parent.joinpath( + 'data/bw_work.json') + bookdata = json.loads(datafile.read_bytes()) + + book = models.Work.objects.create( + title='Test Book', remote_id='https://bookwyrm.social/book/5988') + + del bookdata['authors'] + self.assertEqual(book.title, 'Test Book') + with patch( + 'bookwyrm.activitypub.base_activity.set_related_field.delay'): + views.inbox.activity_task({'object': bookdata}) + book = models.Work.objects.get(id=book.id) + self.assertEqual(book.title, 'Piranesi') + + + def test_handle_blocks(self): + ''' create a "block" database entry from an activity ''' + self.local_user.followers.add(self.remote_user) + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + models.UserFollowRequest.objects.create( + user_subject=self.local_user, + user_object=self.remote_user) + self.assertTrue(models.UserFollows.objects.exists()) + self.assertTrue(models.UserFollowRequest.objects.exists()) + + activity = { + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://example.com/9e1f41ac-9ddd-4159", + "type": "Block", + "actor": "https://example.com/users/rat", + "object": "https://example.com/user/mouse" + } + + views.inbox.activity_task(activity) + block = models.UserBlocks.objects.get() + self.assertEqual(block.user_subject, self.remote_user) + self.assertEqual(block.user_object, self.local_user) + self.assertEqual( + block.remote_id, 'https://example.com/9e1f41ac-9ddd-4159') + + self.assertFalse(models.UserFollows.objects.exists()) + self.assertFalse(models.UserFollowRequest.objects.exists()) + + + def test_handle_unblock(self): + ''' unblock a user ''' + self.remote_user.blocks.add(self.local_user) + + block = models.UserBlocks.objects.get() + block.remote_id = 'https://example.com/9e1f41ac-9ddd-4159' + block.save() + + self.assertEqual(block.user_subject, self.remote_user) + self.assertEqual(block.user_object, self.local_user) + activity = {'type': 'Undo', 'object': { + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://example.com/9e1f41ac-9ddd-4159", + "type": "Block", + "actor": "https://example.com/users/rat", + "object": "https://example.com/user/mouse" + }} + views.inbox.activity_task(activity) + self.assertFalse(models.UserBlocks.objects.exists()) diff --git a/bookwyrm/views/follow.py b/bookwyrm/views/follow.py index c59f2e6d..e95355e6 100644 --- a/bookwyrm/views/follow.py +++ b/bookwyrm/views/follow.py @@ -22,8 +22,6 @@ def follow(request): user_object=to_follow, ) - if to_follow.local and not to_follow.manually_approves_followers: - rel.accept() return redirect(to_follow.local_path) From 606d89d3bde0d909993723fc3e9d9934c2dd7619 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Feb 2021 21:20:00 -0800 Subject: [PATCH 0013/1285] Fixes boost, recursive to_model calls --- bookwyrm/activitypub/__init__.py | 2 +- bookwyrm/activitypub/base_activity.py | 7 +- bookwyrm/activitypub/verbs.py | 34 +++ bookwyrm/incoming.py | 318 -------------------------- bookwyrm/models/status.py | 2 +- bookwyrm/tests/views/test_inbox.py | 9 +- bookwyrm/views/inbox.py | 2 +- 7 files changed, 47 insertions(+), 327 deletions(-) delete mode 100644 bookwyrm/incoming.py diff --git a/bookwyrm/activitypub/__init__.py b/bookwyrm/activitypub/__init__.py index ce4acd66..5303d1b2 100644 --- a/bookwyrm/activitypub/__init__.py +++ b/bookwyrm/activitypub/__init__.py @@ -8,7 +8,6 @@ from .base_activity import ActivitySerializerError, resolve_remote_id from .image import Image from .note import Note, GeneratedNote, Article, Comment, Review, Quotation from .note import Tombstone -from .interaction import Boost, Like from .ordered_collection import OrderedCollection, OrderedCollectionPage from .ordered_collection import BookList, Shelf from .person import Person, PublicKey @@ -17,6 +16,7 @@ from .book import Edition, Work, Author from .verbs import Create, Delete, Undo, Update from .verbs import Follow, Accept, Reject, Block from .verbs import Add, AddBook, AddListItem, Remove +from .verbs import Announce, Like # this creates a list of all the Activity types that we can serialize, # so when an Activity comes in from outside, we can check if it's known diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 631ca2dd..c20ab3b8 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -95,9 +95,10 @@ class ActivityObject: if hasattr(model, 'ignore_activity') and model.ignore_activity(self): return instance - # check for an existing instance, if we're not updating a known obj + # check for an existing instance instance = instance or model.find_existing(self.serialize()) if not instance and not allow_create: + # so that we don't create when we want to delete or update return None instance = instance or model() @@ -197,7 +198,7 @@ def set_related_field( getattr(model_field, 'activitypub_field'), instance.remote_id ) - item = activity.to_model(model) + item = activity.to_model() # if the related field isn't serialized (attachments on Status), then # we have to set it post-creation @@ -228,4 +229,4 @@ def resolve_remote_id(model, remote_id, refresh=False, save=True): item = model.activity_serializer(**data) # if we're refreshing, "result" will be set and we'll update it - return item.to_model(model, instance=result, save=save) + return item.to_model(instance=result, save=save) diff --git a/bookwyrm/activitypub/verbs.py b/bookwyrm/activitypub/verbs.py index d781993e..30eef448 100644 --- a/bookwyrm/activitypub/verbs.py +++ b/bookwyrm/activitypub/verbs.py @@ -68,6 +68,10 @@ class Follow(Verb): object: str type: str = 'Follow' + def action(self): + ''' relationship save ''' + self.to_model() + @dataclass(init=False) class Block(Verb): @@ -75,6 +79,10 @@ class Block(Verb): object: str type: str = 'Block' + def action(self): + ''' relationship save ''' + self.to_model() + @dataclass(init=False) class Accept(Verb): @@ -107,6 +115,10 @@ class Add(Verb): object: ActivityObject type: str = 'Add' + def action(self): + ''' add obj to collection ''' + self.to_model() + @dataclass(init=False) class AddBook(Add): @@ -133,3 +145,25 @@ class Remove(Verb): ''' find and remove the activity object ''' obj = self.object.to_model(save=False, allow_create=False) obj.delete() + + +@dataclass(init=False) +class Like(Verb): + ''' a user faving an object ''' + object: str + type: str = 'Like' + + def action(self): + ''' like ''' + self.to_model() + + +@dataclass(init=False) +class Announce(Verb): + ''' boosting a status ''' + object: str + type: str = 'Announce' + + def action(self): + ''' boost ''' + self.to_model() diff --git a/bookwyrm/incoming.py b/bookwyrm/incoming.py deleted file mode 100644 index 7ddd893f..00000000 --- a/bookwyrm/incoming.py +++ /dev/null @@ -1,318 +0,0 @@ -''' handles all of the activity coming in to the server ''' -import json -from urllib.parse import urldefrag - -import django.db.utils -from django.http import HttpResponse -from django.http import HttpResponseBadRequest, HttpResponseNotFound -from django.views.decorators.csrf import csrf_exempt -from django.views.decorators.http import require_POST -import requests - -from bookwyrm import activitypub, models -from bookwyrm import status as status_builder -from bookwyrm.tasks import app -from bookwyrm.signatures import Signature - - -@csrf_exempt -@require_POST -def inbox(request, username): - ''' incoming activitypub events ''' - try: - models.User.objects.get(localname=username) - except models.User.DoesNotExist: - return HttpResponseNotFound() - - return shared_inbox(request) - - -@csrf_exempt -@require_POST -def shared_inbox(request): - ''' incoming activitypub events ''' - try: - resp = request.body - activity = json.loads(resp) - if isinstance(activity, str): - activity = json.loads(activity) - activity_object = activity['object'] - except (json.decoder.JSONDecodeError, KeyError): - return HttpResponseBadRequest() - - if not has_valid_signature(request, activity): - if activity['type'] == 'Delete': - # Pretend that unauth'd deletes succeed. Auth may be failing because - # the resource or owner of the resource might have been deleted. - return HttpResponse() - return HttpResponse(status=401) - - # if this isn't a file ripe for refactor, I don't know what is. - handlers = { - 'Follow': handle_follow, - 'Accept': handle_follow_accept, - 'Reject': handle_follow_reject, - 'Block': handle_block, - 'Delete': handle_delete_status, - 'Like': handle_favorite, - 'Announce': handle_boost, - 'Add': { - 'Edition': handle_add, - }, - 'Undo': { - 'Follow': handle_unfollow, - 'Like': handle_unfavorite, - 'Announce': handle_unboost, - 'Block': handle_unblock, - }, - 'Update': { - 'Person': handle_update_user, - 'Edition': handle_update_edition, - 'Work': handle_update_work, - 'BookList': handle_update_list, - }, - } - activity_type = activity['type'] - - handler = handlers.get(activity_type, None) - if isinstance(handler, dict): - handler = handler.get(activity_object['type'], None) - - if not handler: - return HttpResponseNotFound() - - handler.delay(activity) - return HttpResponse() - - -def has_valid_signature(request, activity): - ''' verify incoming signature ''' - try: - signature = Signature.parse(request) - - key_actor = urldefrag(signature.key_id).url - if key_actor != activity.get('actor'): - raise ValueError("Wrong actor created signature.") - - remote_user = activitypub.resolve_remote_id(models.User, key_actor) - if not remote_user: - return False - - try: - signature.verify(remote_user.key_pair.public_key, request) - except ValueError: - old_key = remote_user.key_pair.public_key - remote_user = activitypub.resolve_remote_id( - models.User, remote_user.remote_id, refresh=True - ) - if remote_user.key_pair.public_key == old_key: - raise # Key unchanged. - signature.verify(remote_user.key_pair.public_key, request) - except (ValueError, requests.exceptions.HTTPError): - return False - return True - - -@app.task -def handle_follow(activity): - ''' someone wants to follow a local user ''' - try: - relationship = activitypub.Follow( - **activity - ).to_model(models.UserFollowRequest) - except django.db.utils.IntegrityError as err: - if err.__cause__.diag.constraint_name != 'userfollowrequest_unique': - raise - relationship = models.UserFollowRequest.objects.get( - remote_id=activity['id'] - ) - # send the accept normally for a duplicate request - - if not relationship.user_object.manually_approves_followers: - relationship.accept() - - -@app.task -def handle_unfollow(activity): - ''' unfollow a local user ''' - obj = activity['object'] - requester = activitypub.resolve_remote_id(models.User, obj['actor']) - to_unfollow = models.User.objects.get(remote_id=obj['object']) - # raises models.User.DoesNotExist - - to_unfollow.followers.remove(requester) - - -@app.task -def handle_follow_accept(activity): - ''' hurray, someone remote accepted a follow request ''' - # figure out who they want to follow - requester = models.User.objects.get(remote_id=activity['object']['actor']) - # figure out who they are - accepter = activitypub.resolve_remote_id(models.User, activity['actor']) - - try: - models.UserFollowRequest.objects.get( - user_subject=requester, - user_object=accepter - ).accept() - except models.UserFollowRequest.DoesNotExist: - return - - -@app.task -def handle_follow_reject(activity): - ''' someone is rejecting a follow request ''' - requester = models.User.objects.get(remote_id=activity['object']['actor']) - rejecter = activitypub.resolve_remote_id(models.User, activity['actor']) - - try: - models.UserFollowRequest.objects.get( - user_subject=requester, - user_object=rejecter - ).reject() - except models.UserFollowRequest.DoesNotExist: - return - -@app.task -def handle_block(activity): - ''' blocking a user ''' - # create "block" databse entry - activitypub.Block(**activity).to_model(models.UserBlocks) - # the removing relationships is handled in post-save hook in model - - -@app.task -def handle_unblock(activity): - ''' undoing a block ''' - try: - block_id = activity['object']['id'] - except KeyError: - return - try: - block = models.UserBlocks.objects.get(remote_id=block_id) - except models.UserBlocks.DoesNotExist: - return - block.delete() - - -@app.task -def handle_update_list(activity): - ''' update a list ''' - try: - book_list = models.List.objects.get(remote_id=activity['object']['id']) - except models.List.DoesNotExist: - book_list = None - activitypub.BookList( - **activity['object']).to_model(models.List, instance=book_list) - - - -@app.task -def handle_delete_status(activity): - ''' remove a status ''' - try: - status_id = activity['object']['id'] - except TypeError: - # this isn't a great fix, because you hit this when mastadon - # is trying to delete a user. - return - try: - status = models.Status.objects.get( - remote_id=status_id - ) - except models.Status.DoesNotExist: - return - models.Notification.objects.filter(related_status=status).all().delete() - status_builder.delete_status(status) - - -@app.task -def handle_favorite(activity): - ''' approval of your good good post ''' - fav = activitypub.Like(**activity) - # we dont know this status, we don't care about this status - if not models.Status.objects.filter(remote_id=fav.object).exists(): - return - - fav = fav.to_model(models.Favorite) - if fav.user.local: - return - - -@app.task -def handle_unfavorite(activity): - ''' approval of your good good post ''' - like = models.Favorite.objects.filter( - remote_id=activity['object']['id'] - ).first() - if not like: - return - like.delete() - - -@app.task -def handle_boost(activity): - ''' someone gave us a boost! ''' - try: - activitypub.Boost(**activity).to_model(models.Boost) - except activitypub.ActivitySerializerError: - # this probably just means we tried to boost an unknown status - return - - -@app.task -def handle_unboost(activity): - ''' someone gave us a boost! ''' - boost = models.Boost.objects.filter( - remote_id=activity['object']['id'] - ).first() - if boost: - boost.delete() - - -@app.task -def handle_add(activity): - ''' putting a book on a shelf ''' - #this is janky as heck but I haven't thought of a better solution - try: - activitypub.AddBook(**activity).to_model(models.ShelfBook) - return - except activitypub.ActivitySerializerError: - pass - try: - activitypub.AddListItem(**activity).to_model(models.ListItem) - return - except activitypub.ActivitySerializerError: - pass - try: - activitypub.AddBook(**activity).to_model(models.UserTag) - return - except activitypub.ActivitySerializerError: - pass - - -@app.task -def handle_update_user(activity): - ''' receive an updated user Person activity object ''' - try: - user = models.User.objects.get(remote_id=activity['object']['id']) - except models.User.DoesNotExist: - # who is this person? who cares - return - activitypub.Person( - **activity['object'] - ).to_model(models.User, instance=user) - # model save() happens in the to_model function - - -@app.task -def handle_update_edition(activity): - ''' a remote instance changed a book (Document) ''' - activitypub.Edition(**activity['object']).to_model(models.Edition) - - -@app.task -def handle_update_work(activity): - ''' a remote instance changed a book (Document) ''' - activitypub.Work(**activity['object']).to_model(models.Work) diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 62effeb8..716c1592 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -266,7 +266,7 @@ class Boost(ActivityMixin, Status): related_name='boosters', activitypub_field='object', ) - activity_serializer = activitypub.Boost + activity_serializer = activitypub.Announce def save(self, *args, **kwargs): ''' save and notify ''' diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 9ca7198e..92e5dace 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -573,12 +573,15 @@ class Inbox(TestCase): "id": "https://bookwyrm.social/shelfbook/6189#add", "type": "Add", "actor": "https://example.com/users/rat", - "object": "https://bookwyrm.social/book/37292", + "object": { + "type": "Edition", + "id": "https://bookwyrm.social/book/37292", + }, "target": "https://bookwyrm.social/user/mouse/shelf/to-read", "@context": "https://www.w3.org/ns/activitystreams" } - views.inbox.activity_task(activity) - self.assertEqual(shelf.books.first(), book) + #views.inbox.activity_task(activity) + #self.assertEqual(shelf.books.first(), book) def test_handle_update_user(self): diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index 6e7b036b..58356e1c 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -56,7 +56,7 @@ def activity_task(activity_json): try: activity = activitypub.parse(activity_json) except activitypub.ActivitySerializerError: - return + raise#return # cool that worked, now we should do the action described by the type # (create, update, delete, etc) From 08c1553e7141ce5afda44ada1113e5df18ddd842 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Feb 2021 21:41:08 -0800 Subject: [PATCH 0014/1285] Fixes Favs --- bookwyrm/activitypub/interaction.py | 20 -------------------- bookwyrm/activitypub/note.py | 2 -- bookwyrm/activitypub/verbs.py | 1 + bookwyrm/models/fields.py | 2 +- bookwyrm/models/user.py | 2 +- bookwyrm/tests/views/test_inbox.py | 6 ++++-- 6 files changed, 7 insertions(+), 26 deletions(-) delete mode 100644 bookwyrm/activitypub/interaction.py diff --git a/bookwyrm/activitypub/interaction.py b/bookwyrm/activitypub/interaction.py deleted file mode 100644 index 752b2fe3..00000000 --- a/bookwyrm/activitypub/interaction.py +++ /dev/null @@ -1,20 +0,0 @@ -''' boosting and liking posts ''' -from dataclasses import dataclass - -from .base_activity import ActivityObject - - -@dataclass(init=False) -class Like(ActivityObject): - ''' a user faving an object ''' - actor: str - object: str - type: str = 'Like' - - -@dataclass(init=False) -class Boost(ActivityObject): - ''' boosting a status ''' - actor: str - object: str - type: str = 'Announce' diff --git a/bookwyrm/activitypub/note.py b/bookwyrm/activitypub/note.py index 1e0bdcb7..5da2cced 100644 --- a/bookwyrm/activitypub/note.py +++ b/bookwyrm/activitypub/note.py @@ -8,8 +8,6 @@ from .image import Image @dataclass(init=False) class Tombstone(ActivityObject): ''' the placeholder for a deleted status ''' - published: str - deleted: str type: str = 'Tombstone' diff --git a/bookwyrm/activitypub/verbs.py b/bookwyrm/activitypub/verbs.py index 30eef448..c3de73f3 100644 --- a/bookwyrm/activitypub/verbs.py +++ b/bookwyrm/activitypub/verbs.py @@ -5,6 +5,7 @@ from typing import List from .base_activity import ActivityObject, Signature from .book import Edition + @dataclass(init=False) class Verb(ActivityObject): ''' generic fields for activities - maybe an unecessary level of diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index 55de1fab..880c7600 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -128,7 +128,7 @@ class ActivitypubRelatedFieldMixin(ActivitypubFieldMixin): return related_model.find_existing(value) # this is an activitypub object, which we can deserialize activity_serializer = related_model.activity_serializer - return activity_serializer(**value).to_model(related_model) + return activity_serializer(**value).to_model() try: # make sure the value looks like a remote id validate_remote_id(value) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index da717d2e..61d119a8 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -364,4 +364,4 @@ def get_remote_reviews(outbox): for activity in data['orderedItems']: if not activity['type'] == 'Review': continue - activitypub.Review(**activity).to_model(Review) + activitypub.Review(**activity).to_model() diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 92e5dace..af513609 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -424,7 +424,7 @@ class Inbox(TestCase): 'type': 'Delete', 'id': '%s/activity' % self.status.remote_id, 'actor': self.remote_user.remote_id, - 'object': {'id': self.status.remote_id}, + 'object': {'id': self.status.remote_id, 'type': 'Tombstone'}, } views.inbox.activity_task(activity) # deletion doens't remove the status, it turns it into a tombstone @@ -453,7 +453,7 @@ class Inbox(TestCase): 'type': 'Delete', 'id': '%s/activity' % self.status.remote_id, 'actor': self.remote_user.remote_id, - 'object': {'id': self.status.remote_id}, + 'object': {'id': self.status.remote_id, 'type': 'Tombstone'}, } views.inbox.activity_task(activity) # deletion doens't remove the status, it turns it into a tombstone @@ -472,6 +472,7 @@ class Inbox(TestCase): '@context': 'https://www.w3.org/ns/activitystreams', 'id': 'https://example.com/fav/1', 'actor': 'https://example.com/users/rat', + 'type': 'Like', 'published': 'Mon, 25 May 2020 19:31:20 GMT', 'object': 'https://example.com/status/1', } @@ -492,6 +493,7 @@ class Inbox(TestCase): '@context': 'https://www.w3.org/ns/activitystreams', 'id': 'https://example.com/fav/1', 'actor': 'https://example.com/users/rat', + 'type': 'Like', 'published': 'Mon, 25 May 2020 19:31:20 GMT', 'object': 'https://example.com/fav/1', } From b393df8cab781fcd35f8e5710af5c65bc41f5bc0 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 16 Feb 2021 09:35:00 -0800 Subject: [PATCH 0015/1285] Fixes deletion --- bookwyrm/activitypub/note.py | 6 ++++++ bookwyrm/models/status.py | 6 ++++++ bookwyrm/tests/views/test_inbox.py | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/bookwyrm/activitypub/note.py b/bookwyrm/activitypub/note.py index 5da2cced..705d6eed 100644 --- a/bookwyrm/activitypub/note.py +++ b/bookwyrm/activitypub/note.py @@ -1,6 +1,7 @@ ''' note serializer and children thereof ''' from dataclasses import dataclass, field from typing import Dict, List +from django.apps import apps from .base_activity import ActivityObject, Link from .image import Image @@ -10,6 +11,11 @@ class Tombstone(ActivityObject): ''' the placeholder for a deleted status ''' type: str = 'Tombstone' + def to_model(self, *args, **kwargs): + ''' this should never really get serialized, just searched for ''' + model = apps.get_model('bookwyrm.Status') + return model.find_existing_by_remote_id(self.id) + @dataclass(init=False) class Note(ActivityObject): diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 716c1592..029ca5a4 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -84,6 +84,12 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): related_status=self, ) + def delete(self, *args, **kwargs): + ''' "delete" a status ''' + self.deleted = True + self.deleted_date = timezone.now() + self.save() + @property def recipients(self): ''' tagged users who definitely need to get this status in broadcast ''' diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index af513609..cfd0e8c0 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -422,6 +422,8 @@ class Inbox(TestCase): self.assertFalse(self.status.deleted) activity = { 'type': 'Delete', + "to": ["https://www.w3.org/ns/activitystreams#Public"], + "cc": ["https://example.com/user/mouse/followers"], 'id': '%s/activity' % self.status.remote_id, 'actor': self.remote_user.remote_id, 'object': {'id': self.status.remote_id, 'type': 'Tombstone'}, @@ -451,6 +453,8 @@ class Inbox(TestCase): self.assertEqual(models.Notification.objects.count(), 2) activity = { 'type': 'Delete', + "to": ["https://www.w3.org/ns/activitystreams#Public"], + "cc": ["https://example.com/user/mouse/followers"], 'id': '%s/activity' % self.status.remote_id, 'actor': self.remote_user.remote_id, 'object': {'id': self.status.remote_id, 'type': 'Tombstone'}, From 3f1b62eb98a1acce321f9a0b31e4ee006f48cd65 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 16 Feb 2021 11:04:13 -0800 Subject: [PATCH 0016/1285] Fixes Add activity still janky --- bookwyrm/activitypub/base_activity.py | 51 +++++++++++++++++---------- bookwyrm/activitypub/verbs.py | 11 +++--- bookwyrm/models/fields.py | 13 ++++--- bookwyrm/tests/views/test_inbox.py | 12 ++++--- 4 files changed, 54 insertions(+), 33 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index c20ab3b8..db6cdc94 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -80,17 +80,10 @@ class ActivityObject: setattr(self, field.name, value) - def to_model(self, instance=None, allow_create=True, save=True): + def to_model(self, model=None, instance=None, allow_create=True, save=True): ''' convert from an activity to a model instance ''' # figure out the right model -- wish I had a better way for this - models = apps.get_models() - model = [m for m in models if hasattr(m, 'activity_serializer') and \ - hasattr(m.activity_serializer, 'type') and \ - m.activity_serializer.type == self.type] - if not len(model): - raise ActivitySerializerError( - 'No model found for activity type "%s"' % self.type) - model = model[0] + model = model or get_model_from_type(self.type) if hasattr(model, 'ignore_activity') and model.ignore_activity(self): return instance @@ -156,6 +149,14 @@ class ActivityObject: def serialize(self): ''' convert to dictionary with context attr ''' data = self.__dict__ + # recursively serialize + for (k, v) in data.items(): + try: + is_subclass = issubclass(v, ActivityObject) + except TypeError: + is_subclass = False + if is_subclass: + data[k] = v.serialize() data = {k:v for (k, v) in data.items() if v is not None} data['@context'] = 'https://www.w3.org/ns/activitystreams' return data @@ -207,11 +208,23 @@ def set_related_field( item.save() -def resolve_remote_id(model, remote_id, refresh=False, save=True): +def get_model_from_type(activity_type): + ''' given the activity, what type of model ''' + models = apps.get_models() + model = [m for m in models if hasattr(m, 'activity_serializer') and \ + hasattr(m.activity_serializer, 'type') and \ + m.activity_serializer.type == activity_type] + if not len(model): + raise ActivitySerializerError( + 'No model found for activity type "%s"' % activity_type) + return model[0] + +def resolve_remote_id(remote_id, model=None, refresh=False, save=True): ''' take a remote_id and return an instance, creating if necessary ''' - result = model.find_existing_by_remote_id(remote_id) - if result and not refresh: - return result + if model:# a bonus check we can do if we already know the model + result = model.find_existing_by_remote_id(remote_id) + if result and not refresh: + return result # load the data and create the object try: @@ -220,13 +233,15 @@ def resolve_remote_id(model, remote_id, refresh=False, save=True): raise ActivitySerializerError( 'Could not connect to host for remote_id in %s model: %s' % \ (model.__name__, remote_id)) + # determine the model implicitly, if not provided + if not model: + model = get_model_from_type(data.get('type')) # check for existing items with shared unique identifiers - if not result: - result = model.find_existing(data) - if result and not refresh: - return result + result = model.find_existing(data) + if result and not refresh: + return result item = model.activity_serializer(**data) # if we're refreshing, "result" will be set and we'll update it - return item.to_model(instance=result, save=save) + return item.to_model(model=model, instance=result, save=save) diff --git a/bookwyrm/activitypub/verbs.py b/bookwyrm/activitypub/verbs.py index c3de73f3..300e1bf3 100644 --- a/bookwyrm/activitypub/verbs.py +++ b/bookwyrm/activitypub/verbs.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from typing import List -from .base_activity import ActivityObject, Signature +from .base_activity import ActivityObject, Signature, resolve_remote_id from .book import Edition @@ -113,19 +113,22 @@ class Reject(Verb): class Add(Verb): '''Add activity ''' target: str - object: ActivityObject + object: Edition type: str = 'Add' def action(self): ''' add obj to collection ''' - self.to_model() + target = resolve_remote_id(self.target, refresh=False) + # we want to related field that isn't the book, this is janky af sorry + model = [t for t in type(target)._meta.related_objects \ + if t.name != 'edition'][0].related_model + self.to_model(model=model) @dataclass(init=False) class AddBook(Add): '''Add activity that's aware of the book obj ''' object: Edition - type: str = 'Add' @dataclass(init=False) diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index 880c7600..f2465fb6 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -122,13 +122,12 @@ class ActivitypubRelatedFieldMixin(ActivitypubFieldMixin): return None related_model = self.related_model - if isinstance(value, dict) and value.get('id'): + if hasattr(value, 'id') and value.id: if not self.load_remote: # only look in the local database - return related_model.find_existing(value) + return related_model.find_existing(value.serialize()) # this is an activitypub object, which we can deserialize - activity_serializer = related_model.activity_serializer - return activity_serializer(**value).to_model() + return value.to_model(model=related_model) try: # make sure the value looks like a remote id validate_remote_id(value) @@ -139,7 +138,7 @@ class ActivitypubRelatedFieldMixin(ActivitypubFieldMixin): if not self.load_remote: # only look in the local database return related_model.find_existing_by_remote_id(value) - return activitypub.resolve_remote_id(related_model, value) + return activitypub.resolve_remote_id(value, model=related_model) class RemoteIdField(ActivitypubFieldMixin, models.CharField): @@ -280,7 +279,7 @@ class ManyToManyField(ActivitypubFieldMixin, models.ManyToManyField): except ValidationError: continue items.append( - activitypub.resolve_remote_id(self.related_model, remote_id) + activitypub.resolve_remote_id(remote_id, model=self.related_model) ) return items @@ -317,7 +316,7 @@ class TagField(ManyToManyField): # tags can contain multiple types continue items.append( - activitypub.resolve_remote_id(self.related_model, link.href) + activitypub.resolve_remote_id(link.href, model=self.related_model) ) return items diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index cfd0e8c0..e7ce4c55 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -566,10 +566,12 @@ class Inbox(TestCase): views.inbox.activity_task(activity) - def test_handle_add_book(self): + def test_handle_add_book_to_shelf(self): ''' shelving a book ''' + work = models.Work.objects.create(title='work title') book = models.Edition.objects.create( - title='Test', remote_id='https://bookwyrm.social/book/37292') + title='Test', remote_id='https://bookwyrm.social/book/37292', + parent_work=work) shelf = models.Shelf.objects.create( user=self.remote_user, name='Test Shelf') shelf.remote_id = 'https://bookwyrm.social/user/mouse/shelf/to-read' @@ -581,13 +583,15 @@ class Inbox(TestCase): "actor": "https://example.com/users/rat", "object": { "type": "Edition", + "title": "Test Title", + "work": work.remote_id, "id": "https://bookwyrm.social/book/37292", }, "target": "https://bookwyrm.social/user/mouse/shelf/to-read", "@context": "https://www.w3.org/ns/activitystreams" } - #views.inbox.activity_task(activity) - #self.assertEqual(shelf.books.first(), book) + views.inbox.activity_task(activity) + self.assertEqual(shelf.books.first(), book) def test_handle_update_user(self): From f5a022184f71ca84db0d007b80f7f79afe0c6449 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 16 Feb 2021 12:31:27 -0800 Subject: [PATCH 0017/1285] Fixes discarding boosts --- bookwyrm/models/activitypub_mixin.py | 8 ++++++-- bookwyrm/models/status.py | 8 +++++++- bookwyrm/tests/views/test_inbox.py | 13 +++++++++---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 84293725..6ecf8d96 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -156,10 +156,14 @@ class ActivitypubMixin: return recipients - def to_activity(self): + def to_activity_dataclass(self): ''' convert from a model to an activity ''' activity = generate_activity(self) - return self.activity_serializer(**activity).serialize() + return self.activity_serializer(**activity) + + def to_activity(self): + ''' convert from a model to a json activity ''' + return self.to_activity_dataclass().serialize() class ObjectMixin(ActivitypubMixin): diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 029ca5a4..d2dd7d5b 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -102,6 +102,12 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): @classmethod def ignore_activity(cls, activity): ''' keep notes if they are replies to existing statuses ''' + if activity.type == 'Announce': + # keep it if the booster or the boosted are local + boosted = activitypub.resolve_remote_id(activity.object, save=False) + return cls.ignore_activity(boosted.to_activity_dataclass()) + + # keep if it if it's a custom type if activity.type != 'Note': return False if cls.objects.filter( @@ -112,8 +118,8 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): if activity.tag == MISSING or activity.tag is None: return True tags = [l['href'] for l in activity.tag if l['type'] == 'Mention'] + user_model = apps.get_model('bookwyrm.User', require_ready=True) for tag in tags: - user_model = apps.get_model('bookwyrm.User', require_ready=True) if user_model.objects.filter( remote_id=tag, local=True).exists(): # we found a mention of a known use boost diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index e7ce4c55..288dd056 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -519,7 +519,7 @@ class Inbox(TestCase): 'type': 'Announce', 'id': '%s/boost' % self.status.remote_id, 'actor': self.remote_user.remote_id, - 'object': self.status.to_activity(), + 'object': self.status.remote_id, } with patch('bookwyrm.models.status.Status.ignore_activity') \ as discarder: @@ -535,16 +535,21 @@ class Inbox(TestCase): @responses.activate def test_handle_discarded_boost(self): ''' test a boost of a mastodon status that will be discarded ''' + status = models.Status( + content='hi', + user=self.remote_user, + ) + status.save(broadcast=False) activity = { 'type': 'Announce', 'id': 'http://www.faraway.com/boost/12', 'actor': self.remote_user.remote_id, - 'object': self.status.to_activity(), + 'object': status.remote_id, } responses.add( responses.GET, - 'http://www.faraway.com/boost/12', - json={'id': 'http://www.faraway.com/boost/12'}, + status.remote_id, + json=status.to_activity(), status=200) views.inbox.activity_task(activity) self.assertEqual(models.Boost.objects.count(), 0) From b57a86d4e23232de5d59ab61319b3d77eefed744 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 16 Feb 2021 12:58:29 -0800 Subject: [PATCH 0018/1285] Fixes approving follow requests automatically --- bookwyrm/models/relationship.py | 2 +- bookwyrm/tests/views/test_inbox.py | 30 +++++++++++++++--------------- bookwyrm/views/inbox.py | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index e4c6f4fa..cdeebc86 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -100,7 +100,7 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship): if self.user_object.local: manually_approves = self.user_object.manually_approves_followers - if manually_approves: + if not manually_approves: self.accept() model = apps.get_model('bookwyrm.Notification', require_ready=True) diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 288dd056..d2e28380 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -246,8 +246,9 @@ class Inbox(TestCase): "object": "https://example.com/user/mouse" } - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') as mock: views.inbox.activity_task(activity) + self.assertEqual(mock.call_count, 1) # notification created notification = models.Notification.objects.get() @@ -255,8 +256,7 @@ class Inbox(TestCase): self.assertEqual(notification.notification_type, 'FOLLOW') # the request should have been deleted - requests = models.UserFollowRequest.objects.all() - self.assertEqual(list(requests), []) + self.assertFalse(models.UserFollowRequest.objects.exists()) # the follow relationship should exist follow = models.UserFollows.objects.get(user_object=self.local_user) @@ -317,24 +317,24 @@ class Inbox(TestCase): def test_handle_follow_accept(self): ''' a remote user approved a follow request from local ''' + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + rel = models.UserFollowRequest.objects.create( + user_subject=self.local_user, + user_object=self.remote_user + ) activity = { "@context": "https://www.w3.org/ns/activitystreams", "id": "https://example.com/users/rat/follows/123#accepts", "type": "Accept", "actor": "https://example.com/users/rat", "object": { - "id": "https://example.com/users/rat/follows/123", + "id": rel.remote_id, "type": "Follow", "actor": "https://example.com/user/mouse", "object": "https://example.com/users/rat" } } - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - models.UserFollowRequest.objects.create( - user_subject=self.local_user, - user_object=self.remote_user - ) self.assertEqual(models.UserFollowRequest.objects.count(), 1) views.inbox.activity_task(activity) @@ -350,24 +350,24 @@ class Inbox(TestCase): def test_handle_follow_reject(self): ''' turn down a follow request ''' + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + rel = models.UserFollowRequest.objects.create( + user_subject=self.local_user, + user_object=self.remote_user + ) activity = { "@context": "https://www.w3.org/ns/activitystreams", "id": "https://example.com/users/rat/follows/123#accepts", "type": "Reject", "actor": "https://example.com/users/rat", "object": { - "id": "https://example.com/users/rat/follows/123", + "id": rel.remote_id, "type": "Follow", "actor": "https://example.com/user/mouse", "object": "https://example.com/users/rat" } } - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - models.UserFollowRequest.objects.create( - user_subject=self.local_user, - user_object=self.remote_user - ) self.assertEqual(models.UserFollowRequest.objects.count(), 1) views.inbox.activity_task(activity) diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index 58356e1c..6e7b036b 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -56,7 +56,7 @@ def activity_task(activity_json): try: activity = activitypub.parse(activity_json) except activitypub.ActivitySerializerError: - raise#return + return # cool that worked, now we should do the action described by the type # (create, update, delete, etc) From d81bfb6573b8bca275e9dea578615d29abfe9fdc Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 16 Feb 2021 16:35:28 -0800 Subject: [PATCH 0019/1285] Fixes unfollow --- bookwyrm/activitypub/base_activity.py | 7 ++-- bookwyrm/activitypub/verbs.py | 8 ++++- bookwyrm/models/relationship.py | 2 +- bookwyrm/tests/views/test_inbox.py | 49 ++++++++++++++++++--------- bookwyrm/views/inbox.py | 2 +- 5 files changed, 47 insertions(+), 21 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index db6cdc94..008d3087 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -85,11 +85,14 @@ class ActivityObject: # figure out the right model -- wish I had a better way for this model = model or get_model_from_type(self.type) - if hasattr(model, 'ignore_activity') and model.ignore_activity(self): - return instance + # only reject statuses if we're potentially creating them + if allow_create and \ + hasattr(model, 'ignore_activity') and model.ignore_activity(self): + return None # check for an existing instance instance = instance or model.find_existing(self.serialize()) + if not instance and not allow_create: # so that we don't create when we want to delete or update return None diff --git a/bookwyrm/activitypub/verbs.py b/bookwyrm/activitypub/verbs.py index 300e1bf3..aa6e7eee 100644 --- a/bookwyrm/activitypub/verbs.py +++ b/bookwyrm/activitypub/verbs.py @@ -1,6 +1,7 @@ ''' undo wrapper activity ''' from dataclasses import dataclass from typing import List +from django.apps import apps from .base_activity import ActivityObject, Signature, resolve_remote_id from .book import Edition @@ -59,7 +60,12 @@ class Undo(Verb): def action(self): ''' find and remove the activity object ''' - obj = self.object.to_model(save=False, allow_create=False) + # this is so hacky but it does make it work.... + # (because you Reject a request and Undo a follow + model = None + if self.object.type == 'Follow': + model = apps.get_model('bookwyrm.UserFollows') + obj = self.object.to_model(model=model, save=False, allow_create=False) obj.delete() diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index cdeebc86..722f0fac 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -56,7 +56,7 @@ class UserRelationship(BookWyrmModel): return '%s#%s/%d' % (base_path, status, self.id) -class UserFollows(UserRelationship): +class UserFollows(ActivitypubMixin, UserRelationship): ''' Following a user ''' status = 'follows' diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index d2e28380..2e677cbf 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -42,7 +42,7 @@ class Inbox(TestCase): 'type': 'Create', 'actor': 'hi', "to": [ - "https://www.w3.org/ns/activitystreams#Public" + "https://www.w3.org/ns/activitystreams#public" ], "cc": [ "https://example.com/user/mouse/followers" @@ -296,19 +296,23 @@ class Inbox(TestCase): def test_handle_unfollow(self): ''' remove a relationship ''' + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + rel = models.UserFollows.objects.create( + user_subject=self.remote_user, user_object=self.local_user) activity = { "type": "Undo", + "id": "bleh", + "to": ["https://www.w3.org/ns/activitystreams#Public"], + "cc": ["https://example.com/user/mouse/followers"], + 'actor': self.remote_user.remote_id, "@context": "https://www.w3.org/ns/activitystreams", "object": { - "id": "https://example.com/users/rat/follows/123", + "id": rel.remote_id, "type": "Follow", "actor": "https://example.com/users/rat", "object": "https://example.com/user/mouse" } } - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - models.UserFollows.objects.create( - user_subject=self.remote_user, user_object=self.local_user) self.assertEqual(self.remote_user, self.local_user.followers.first()) views.inbox.activity_task(activity) @@ -493,13 +497,16 @@ class Inbox(TestCase): activity = { 'id': 'https://example.com/fav/1#undo', 'type': 'Undo', + "to": ["https://www.w3.org/ns/activitystreams#Public"], + "cc": ["https://example.com/user/mouse/followers"], + 'actor': self.remote_user.remote_id, 'object': { '@context': 'https://www.w3.org/ns/activitystreams', 'id': 'https://example.com/fav/1', 'actor': 'https://example.com/users/rat', 'type': 'Like', 'published': 'Mon, 25 May 2020 19:31:20 GMT', - 'object': 'https://example.com/fav/1', + 'object': self.status.remote_id, } } models.Favorite.objects.create( @@ -557,17 +564,21 @@ class Inbox(TestCase): def test_handle_unboost(self): ''' undo a boost ''' + boost = models.Boost.objects.create( + boosted_status=self.status, user=self.remote_user) activity = { 'type': 'Undo', + 'actor': 'hi', + 'id': 'bleh', + "to": ["https://www.w3.org/ns/activitystreams#public"], + "cc": ["https://example.com/user/mouse/followers"], 'object': { 'type': 'Announce', - 'id': '%s/boost' % self.status.remote_id, + 'id': boost.remote_id, 'actor': self.local_user.remote_id, - 'object': self.status.to_activity(), + 'object': self.status.remote_id, } } - models.Boost.objects.create( - boosted_status=self.status, user=self.remote_user) views.inbox.activity_task(activity) @@ -695,12 +706,18 @@ class Inbox(TestCase): self.assertEqual(block.user_subject, self.remote_user) self.assertEqual(block.user_object, self.local_user) - activity = {'type': 'Undo', 'object': { - "@context": "https://www.w3.org/ns/activitystreams", - "id": "https://example.com/9e1f41ac-9ddd-4159", - "type": "Block", - "actor": "https://example.com/users/rat", - "object": "https://example.com/user/mouse" + activity = { + 'type': 'Undo', + 'actor': 'hi', + 'id': 'bleh', + "to": ["https://www.w3.org/ns/activitystreams#public"], + "cc": ["https://example.com/user/mouse/followers"], + 'object': { + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://example.com/9e1f41ac-9ddd-4159", + "type": "Block", + "actor": "https://example.com/users/rat", + "object": "https://example.com/user/mouse" }} views.inbox.activity_task(activity) self.assertFalse(models.UserBlocks.objects.exists()) diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index 6e7b036b..58356e1c 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -56,7 +56,7 @@ def activity_task(activity_json): try: activity = activitypub.parse(activity_json) except activitypub.ActivitySerializerError: - return + raise#return # cool that worked, now we should do the action described by the type # (create, update, delete, etc) From 714202986dc859322764f749d32dfd00c06fe042 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 16 Feb 2021 17:47:53 -0800 Subject: [PATCH 0020/1285] Fixes person/author confusion and public keys --- bookwyrm/activitypub/base_activity.py | 4 +++- bookwyrm/activitypub/book.py | 2 +- bookwyrm/tests/views/test_inbox.py | 30 +++++++++++++++++++++------ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 008d3087..5c828858 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -42,6 +42,9 @@ class Signature: def naive_parse(activity_objects, activity_json): ''' this navigates circular import issues ''' + if activity_json.get('publicKeyPem'): + # ugh + activity_json['type'] = 'PublicKey' try: activity_type = activity_json['type'] serializer = activity_objects[activity_type] @@ -82,7 +85,6 @@ class ActivityObject: def to_model(self, model=None, instance=None, allow_create=True, save=True): ''' convert from an activity to a model instance ''' - # figure out the right model -- wish I had a better way for this model = model or get_model_from_type(self.type) # only reject statuses if we're potentially creating them diff --git a/bookwyrm/activitypub/book.py b/bookwyrm/activitypub/book.py index 68036559..87c40c90 100644 --- a/bookwyrm/activitypub/book.py +++ b/bookwyrm/activitypub/book.py @@ -67,4 +67,4 @@ class Author(ActivityObject): librarythingKey: str = '' goodreadsKey: str = '' wikipediaLink: str = '' - type: str = 'Person' + type: str = 'Author' diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 2e677cbf..0251d8a2 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -391,6 +391,9 @@ class Inbox(TestCase): name='hi', remote_id='https://example.com/list/22', user=self.local_user) activity = { + 'type': 'Update', + 'to': [], 'cc': [], 'actor': 'hi', + 'id': 'sdkjf', 'object': { "id": "https://example.com/list/22", "type": "BookList", @@ -617,11 +620,16 @@ class Inbox(TestCase): self.local_user.save() datafile = pathlib.Path(__file__).parent.joinpath( - 'data/ap_user.json') + '../data/ap_user.json') userdata = json.loads(datafile.read_bytes()) del userdata['icon'] self.assertIsNone(self.local_user.name) - views.inbox.activity_task({'object': userdata}) + views.inbox.activity_task({ + 'type': 'Update', + 'to': [], 'cc': [], 'actor': 'hi', + 'id': 'sdkjf', + 'object': userdata + }) user = models.User.objects.get(id=self.local_user.id) self.assertEqual(user.name, 'MOUSE?? MOUSE!!') self.assertEqual(user.username, 'mouse@example.com') @@ -631,7 +639,7 @@ class Inbox(TestCase): def test_handle_update_edition(self): ''' update an existing edition ''' datafile = pathlib.Path(__file__).parent.joinpath( - 'data/bw_edition.json') + '../data/bw_edition.json') bookdata = json.loads(datafile.read_bytes()) models.Work.objects.create( @@ -644,7 +652,12 @@ class Inbox(TestCase): with patch( 'bookwyrm.activitypub.base_activity.set_related_field.delay'): - views.inbox.activity_task({'object': bookdata}) + views.inbox.activity_task({ + 'type': 'Update', + 'to': [], 'cc': [], 'actor': 'hi', + 'id': 'sdkjf', + 'object': bookdata + }) book = models.Edition.objects.get(id=book.id) self.assertEqual(book.title, 'Piranesi') @@ -652,7 +665,7 @@ class Inbox(TestCase): def test_handle_update_work(self): ''' update an existing edition ''' datafile = pathlib.Path(__file__).parent.joinpath( - 'data/bw_work.json') + '../data/bw_work.json') bookdata = json.loads(datafile.read_bytes()) book = models.Work.objects.create( @@ -662,7 +675,12 @@ class Inbox(TestCase): self.assertEqual(book.title, 'Test Book') with patch( 'bookwyrm.activitypub.base_activity.set_related_field.delay'): - views.inbox.activity_task({'object': bookdata}) + views.inbox.activity_task({ + 'type': 'Update', + 'to': [], 'cc': [], 'actor': 'hi', + 'id': 'sdkjf', + 'object': bookdata + }) book = models.Work.objects.get(id=book.id) self.assertEqual(book.title, 'Piranesi') From a3b7063e4bca4aed6f3bf49460d5073b87d55a6f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 16 Feb 2021 18:07:57 -0800 Subject: [PATCH 0021/1285] makes inbox csrf exempt --- bookwyrm/tests/test_signing.py | 2 +- bookwyrm/views/inbox.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bookwyrm/tests/test_signing.py b/bookwyrm/tests/test_signing.py index 0d55893d..3ad5d233 100644 --- a/bookwyrm/tests/test_signing.py +++ b/bookwyrm/tests/test_signing.py @@ -76,7 +76,7 @@ class Signature(TestCase): digest = digest or make_digest(data) signature = make_signature( signer or sender, self.rat.inbox, now, digest) - with patch('bookwyrm.incoming.handle_follow.delay'): + with patch('bookwyrm.views.inbox.activity_task.delay'): with patch('bookwyrm.models.user.set_remote_server.delay'): return self.send(signature, now, send_data or data, digest) diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index 58356e1c..b4ff2736 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -4,7 +4,9 @@ from urllib.parse import urldefrag from django.http import HttpResponse from django.http import HttpResponseBadRequest, HttpResponseNotFound +from django.utils.decorators import method_decorator from django.views import View +from django.views.decorators.csrf import csrf_exempt import requests from bookwyrm import activitypub, models @@ -12,6 +14,7 @@ from bookwyrm.tasks import app from bookwyrm.signatures import Signature +@method_decorator(csrf_exempt, name='dispatch') # pylint: disable=no-self-use class Inbox(View): ''' requests sent by outside servers''' @@ -56,7 +59,7 @@ def activity_task(activity_json): try: activity = activitypub.parse(activity_json) except activitypub.ActivitySerializerError: - raise#return + return # cool that worked, now we should do the action described by the type # (create, update, delete, etc) From 91908eb1b6d7fa319e14992978c0efcd045fc029 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 16 Feb 2021 18:59:26 -0800 Subject: [PATCH 0022/1285] Smarter way of inferring serializers (which are explicitly present) --- bookwyrm/activitypub/base_activity.py | 30 +++++++++++++++------------ bookwyrm/tests/test_signing.py | 29 +++++++++++++++++--------- bookwyrm/views/inbox.py | 5 +++-- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 5c828858..7ee8ca45 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -40,16 +40,17 @@ class Signature: signatureValue: str type: str = 'RsaSignature2017' -def naive_parse(activity_objects, activity_json): +def naive_parse(activity_objects, activity_json, serializer=None): ''' this navigates circular import issues ''' - if activity_json.get('publicKeyPem'): - # ugh - activity_json['type'] = 'PublicKey' - try: - activity_type = activity_json['type'] - serializer = activity_objects[activity_type] - except KeyError as e: - raise ActivitySerializerError(e) + if not serializer: + if activity_json.get('publicKeyPem'): + # ugh + activity_json['type'] = 'PublicKey' + try: + activity_type = activity_json['type'] + serializer = activity_objects[activity_type] + except KeyError as e: + raise ActivitySerializerError(e) return serializer(activity_objects=activity_objects, **activity_json) @@ -71,8 +72,9 @@ class ActivityObject: is_subclass = issubclass(field.type, ActivityObject) except TypeError: is_subclass = False - if is_subclass and activity_objects: - value = naive_parse(activity_objects, value) + if is_subclass: + value = naive_parse( + activity_objects, value, serializer=field.type) except KeyError: if field.default == MISSING and \ @@ -89,7 +91,8 @@ class ActivityObject: # only reject statuses if we're potentially creating them if allow_create and \ - hasattr(model, 'ignore_activity') and model.ignore_activity(self): + hasattr(model, 'ignore_activity') and \ + model.ignore_activity(self): return None # check for an existing instance @@ -219,11 +222,12 @@ def get_model_from_type(activity_type): model = [m for m in models if hasattr(m, 'activity_serializer') and \ hasattr(m.activity_serializer, 'type') and \ m.activity_serializer.type == activity_type] - if not len(model): + if not model: raise ActivitySerializerError( 'No model found for activity type "%s"' % activity_type) return model[0] + def resolve_remote_id(remote_id, model=None, refresh=False, save=True): ''' take a remote_id and return an instance, creating if necessary ''' if model:# a bonus check we can do if we already know the model diff --git a/bookwyrm/tests/test_signing.py b/bookwyrm/tests/test_signing.py index 3ad5d233..f6de11e1 100644 --- a/bookwyrm/tests/test_signing.py +++ b/bookwyrm/tests/test_signing.py @@ -1,3 +1,4 @@ +''' getting and verifying signatures ''' import time from collections import namedtuple from urllib.parse import urlsplit @@ -12,31 +13,33 @@ import pytest from django.test import TestCase, Client from django.utils.http import http_date -from bookwyrm.models import User +from bookwyrm import models from bookwyrm.activitypub import Follow from bookwyrm.settings import DOMAIN from bookwyrm.signatures import create_key_pair, make_signature, make_digest -def get_follow_data(follower, followee): - follow_activity = Follow( +def get_follow_activity(follower, followee): + ''' generates a test activity ''' + return Follow( id='https://test.com/user/follow/id', actor=follower.remote_id, object=followee.remote_id, ).serialize() - return json.dumps(follow_activity) KeyPair = namedtuple('KeyPair', ('private_key', 'public_key')) Sender = namedtuple('Sender', ('remote_id', 'key_pair')) class Signature(TestCase): + ''' signature test ''' def setUp(self): - self.mouse = User.objects.create_user( + ''' create users and test data ''' + self.mouse = models.User.objects.create_user( 'mouse@%s' % DOMAIN, 'mouse@example.com', '', local=True, localname='mouse') - self.rat = User.objects.create_user( + self.rat = models.User.objects.create_user( 'rat@%s' % DOMAIN, 'rat@example.com', '', local=True, localname='rat') - self.cat = User.objects.create_user( + self.cat = models.User.objects.create_user( 'cat@%s' % DOMAIN, 'cat@example.com', '', local=True, localname='cat') @@ -47,6 +50,8 @@ class Signature(TestCase): KeyPair(private_key, public_key) ) + models.SiteSettings.objects.create() + def send(self, signature, now, data, digest): ''' test request ''' c = Client() @@ -63,7 +68,7 @@ class Signature(TestCase): } ) - def send_test_request( + def send_test_request(#pylint: disable=too-many-arguments self, sender, signer=None, @@ -72,7 +77,7 @@ class Signature(TestCase): date=None): ''' sends a follow request to the "rat" user ''' now = date or http_date() - data = json.dumps(get_follow_data(sender, self.rat)) + data = json.dumps(get_follow_activity(sender, self.rat)) digest = digest or make_digest(data) signature = make_signature( signer or sender, self.rat.inbox, now, digest) @@ -81,6 +86,7 @@ class Signature(TestCase): return self.send(signature, now, send_data or data, digest) def test_correct_signature(self): + ''' this one should just work ''' response = self.send_test_request(sender=self.mouse) self.assertEqual(response.status_code, 200) @@ -120,6 +126,7 @@ class Signature(TestCase): @responses.activate def test_key_needs_refresh(self): + ''' an out of date key should be updated and the new key work ''' datafile = pathlib.Path(__file__).parent.joinpath('data/ap_user.json') data = json.loads(datafile.read_bytes()) data['id'] = self.fake_remote.remote_id @@ -165,6 +172,7 @@ class Signature(TestCase): @responses.activate def test_nonexistent_signer(self): + ''' fail when unable to look up signer ''' responses.add( responses.GET, self.fake_remote.remote_id, @@ -180,11 +188,12 @@ class Signature(TestCase): with patch('bookwyrm.activitypub.resolve_remote_id'): response = self.send_test_request( self.mouse, - send_data=get_follow_data(self.mouse, self.cat)) + send_data=get_follow_activity(self.mouse, self.cat)) self.assertEqual(response.status_code, 401) @pytest.mark.integration def test_invalid_digest(self): + ''' signature digest must be valid ''' with patch('bookwyrm.activitypub.resolve_remote_id'): response = self.send_test_request( self.mouse, diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index b4ff2736..4da4e5b6 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -75,7 +75,8 @@ def has_valid_signature(request, activity): if key_actor != activity.get('actor'): raise ValueError("Wrong actor created signature.") - remote_user = activitypub.resolve_remote_id(models.User, key_actor) + remote_user = activitypub.resolve_remote_id( + key_actor, model=models.User) if not remote_user: return False @@ -84,7 +85,7 @@ def has_valid_signature(request, activity): except ValueError: old_key = remote_user.key_pair.public_key remote_user = activitypub.resolve_remote_id( - models.User, remote_user.remote_id, refresh=True + remote_user.remote_id, model=models.User, refresh=True ) if remote_user.key_pair.public_key == old_key: raise # Key unchanged. From e2f921b7f510f12856436bec203f3d92e11e5900 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 16 Feb 2021 19:28:23 -0800 Subject: [PATCH 0023/1285] better checking for empty values --- bookwyrm/activitypub/base_activity.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 7ee8ca45..933745e0 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -68,6 +68,8 @@ class ActivityObject: for field in fields(self): try: value = kwargs[field.name] + if value in (None, MISSING): + raise KeyError() try: is_subclass = issubclass(field.type, ActivityObject) except TypeError: From 3f61675a0aa9a7546d9aee3aeb240931785e4d28 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 16 Feb 2021 19:35:43 -0800 Subject: [PATCH 0024/1285] Updates usage of resolve_remote_id --- bookwyrm/connectors/bookwyrm_connector.py | 2 +- bookwyrm/models/fields.py | 6 ++++-- bookwyrm/models/status.py | 2 +- bookwyrm/tests/activitypub/test_base_activity.py | 4 ++-- bookwyrm/views/helpers.py | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/bookwyrm/connectors/bookwyrm_connector.py b/bookwyrm/connectors/bookwyrm_connector.py index 1f877993..00e6c62f 100644 --- a/bookwyrm/connectors/bookwyrm_connector.py +++ b/bookwyrm/connectors/bookwyrm_connector.py @@ -7,7 +7,7 @@ class Connector(AbstractMinimalConnector): ''' this is basically just for search ''' def get_or_create_book(self, remote_id): - edition = activitypub.resolve_remote_id(models.Edition, remote_id) + edition = activitypub.resolve_remote_id(remote_id, model=models.Edition) work = edition.parent_work work.default_edition = work.get_default_edition() work.save() diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index f2465fb6..029958bd 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -279,7 +279,8 @@ class ManyToManyField(ActivitypubFieldMixin, models.ManyToManyField): except ValidationError: continue items.append( - activitypub.resolve_remote_id(remote_id, model=self.related_model) + activitypub.resolve_remote_id( + remote_id, model=self.related_model) ) return items @@ -316,7 +317,8 @@ class TagField(ManyToManyField): # tags can contain multiple types continue items.append( - activitypub.resolve_remote_id(link.href, model=self.related_model) + activitypub.resolve_remote_id( + link.href, model=self.related_model) ) return items diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index d2dd7d5b..fbc94e9d 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -84,7 +84,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): related_status=self, ) - def delete(self, *args, **kwargs): + def delete(self, *args, **kwargs):#pylint: disable=unused-argument ''' "delete" a status ''' self.deleted = True self.deleted_date = timezone.now() diff --git a/bookwyrm/tests/activitypub/test_base_activity.py b/bookwyrm/tests/activitypub/test_base_activity.py index e84d7674..1d3e85d3 100644 --- a/bookwyrm/tests/activitypub/test_base_activity.py +++ b/bookwyrm/tests/activitypub/test_base_activity.py @@ -79,7 +79,7 @@ class BaseActivity(TestCase): def test_resolve_remote_id(self): ''' look up or load remote data ''' # existing item - result = resolve_remote_id(models.User, 'http://example.com/a/b') + result = resolve_remote_id('http://example.com/a/b', model=models.User) self.assertEqual(result, self.user) # remote item @@ -91,7 +91,7 @@ class BaseActivity(TestCase): with patch('bookwyrm.models.user.set_remote_server.delay'): result = resolve_remote_id( - models.User, 'https://example.com/user/mouse') + 'https://example.com/user/mouse', model=models.User) self.assertIsInstance(result, models.User) self.assertEqual(result.remote_id, 'https://example.com/user/mouse') self.assertEqual(result.name, 'MOUSE?? MOUSE!!') diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 842b8d1c..89d99501 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -162,7 +162,7 @@ def handle_remote_webfinger(query): if link.get('rel') == 'self': try: user = activitypub.resolve_remote_id( - models.User, link['href'] + link['href'], model=models.User ) except KeyError: return None From a9ca3a4290015d9c50f26dcaaf869d91fa82e156 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 16 Feb 2021 20:17:38 -0800 Subject: [PATCH 0025/1285] Fixes calls to to_model, init with activitypub partially serialized --- bookwyrm/activitypub/base_activity.py | 3 ++- bookwyrm/connectors/abstract_connector.py | 6 ++--- bookwyrm/models/fields.py | 4 ++-- .../tests/activitypub/test_base_activity.py | 24 ++++++++++++------- bookwyrm/tests/activitypub/test_person.py | 2 +- bookwyrm/tests/activitypub/test_quotation.py | 2 +- 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 933745e0..7292a0d7 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -74,7 +74,8 @@ class ActivityObject: is_subclass = issubclass(field.type, ActivityObject) except TypeError: is_subclass = False - if is_subclass: + # parse a dict into the appropriate activity + if is_subclass and isinstance(value, dict): value = naive_parse( activity_objects, value, serializer=field.type) diff --git a/bookwyrm/connectors/abstract_connector.py b/bookwyrm/connectors/abstract_connector.py index 527d2f42..61f553a4 100644 --- a/bookwyrm/connectors/abstract_connector.py +++ b/bookwyrm/connectors/abstract_connector.py @@ -127,7 +127,7 @@ class AbstractConnector(AbstractMinimalConnector): # create activitypub object work_activity = activitypub.Work(**work_data) # this will dedupe automatically - work = work_activity.to_model(models.Work) + work = work_activity.to_model(model=models.Work) for author in self.get_authors_from_data(data): work.authors.add(author) @@ -141,7 +141,7 @@ class AbstractConnector(AbstractMinimalConnector): mapped_data = dict_from_mappings(edition_data, self.book_mappings) mapped_data['work'] = work.remote_id edition_activity = activitypub.Edition(**mapped_data) - edition = edition_activity.to_model(models.Edition) + edition = edition_activity.to_model(model=models.Edition) edition.connector = self.connector edition.save() @@ -168,7 +168,7 @@ class AbstractConnector(AbstractMinimalConnector): mapped_data = dict_from_mappings(data, self.author_mappings) activity = activitypub.Author(**mapped_data) # this will dedupe - return activity.to_model(models.Author) + return activity.to_model(model=models.Author) @abstractmethod diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index 029958bd..4ea527eb 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -367,8 +367,8 @@ class ImageField(ActivitypubFieldMixin, models.ImageField): image_slug = value # when it's an inline image (User avatar/icon, Book cover), it's a json # blob, but when it's an attached image, it's just a url - if isinstance(image_slug, dict): - url = image_slug.get('url') + if hasattr(image_slug, 'url'): + url = image_slug.url elif isinstance(image_slug, str): url = image_slug else: diff --git a/bookwyrm/tests/activitypub/test_base_activity.py b/bookwyrm/tests/activitypub/test_base_activity.py index 1d3e85d3..ce294c1c 100644 --- a/bookwyrm/tests/activitypub/test_base_activity.py +++ b/bookwyrm/tests/activitypub/test_base_activity.py @@ -100,7 +100,7 @@ class BaseActivity(TestCase): ''' catch mismatch between activity type and model type ''' instance = ActivityObject(id='a', type='b') with self.assertRaises(ActivitySerializerError): - instance.to_model(models.User) + instance.to_model(model=models.User) def test_to_model_simple_fields(self): ''' test setting simple fields ''' @@ -118,7 +118,7 @@ class BaseActivity(TestCase): endpoints={}, ) - activity.to_model(models.User, self.user) + activity.to_model(model=models.User, instance=self.user) self.assertEqual(self.user.name, 'New Name') @@ -136,9 +136,9 @@ class BaseActivity(TestCase): endpoints={}, ) - activity.publicKey['publicKeyPem'] = 'hi im secure' + activity.publicKey.publicKeyPem = 'hi im secure' - activity.to_model(models.User, self.user) + activity.to_model(model=models.User, instance=self.user) self.assertEqual(self.user.key_pair.public_key, 'hi im secure') @responses.activate @@ -152,9 +152,15 @@ class BaseActivity(TestCase): outbox='http://www.com/', followers='', summary='', - publicKey=None, + publicKey={ + 'id': 'hi', + 'owner': self.user.remote_id, + 'publicKeyPem': 'hi'}, endpoints={}, - icon={'url': 'http://www.example.com/image.jpg'} + icon={ + 'type': 'Image', + 'url': 'http://www.example.com/image.jpg' + } ) responses.add( @@ -169,7 +175,7 @@ class BaseActivity(TestCase): # this would trigger a broadcast because it's a local user with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - activity.to_model(models.User, self.user) + activity.to_model(model=models.User, instance=self.user) self.assertIsNotNone(self.user.avatar.name) self.assertIsNotNone(self.user.avatar.file) @@ -202,7 +208,7 @@ class BaseActivity(TestCase): }, ] ) - update_data.to_model(models.Status, instance=status) + update_data.to_model(model=models.Status, instance=status) self.assertEqual(status.mention_users.first(), self.user) self.assertEqual(status.mention_books.first(), book) @@ -239,7 +245,7 @@ class BaseActivity(TestCase): # sets the celery task call to the function call with patch( 'bookwyrm.activitypub.base_activity.set_related_field.delay'): - update_data.to_model(models.Status, instance=status) + update_data.to_model(model=models.Status, instance=status) self.assertIsNone(status.attachments.first()) diff --git a/bookwyrm/tests/activitypub/test_person.py b/bookwyrm/tests/activitypub/test_person.py index c7a8221c..7548ed93 100644 --- a/bookwyrm/tests/activitypub/test_person.py +++ b/bookwyrm/tests/activitypub/test_person.py @@ -25,7 +25,7 @@ class Person(TestCase): def test_user_to_model(self): activity = activitypub.Person(**self.user_data) with patch('bookwyrm.models.user.set_remote_server.delay'): - user = activity.to_model(models.User) + user = activity.to_model(models=models.User) self.assertEqual(user.username, 'mouse@example.com') self.assertEqual(user.remote_id, 'https://example.com/user/mouse') self.assertFalse(user.local) diff --git a/bookwyrm/tests/activitypub/test_quotation.py b/bookwyrm/tests/activitypub/test_quotation.py index 60920889..1cd1f05d 100644 --- a/bookwyrm/tests/activitypub/test_quotation.py +++ b/bookwyrm/tests/activitypub/test_quotation.py @@ -46,7 +46,7 @@ class Quotation(TestCase): def test_activity_to_model(self): ''' create a model instance from an activity object ''' activity = activitypub.Quotation(**self.status_data) - quotation = activity.to_model(models.Quotation) + quotation = activity.to_model(model=models.Quotation) self.assertEqual(quotation.book, self.book) self.assertEqual(quotation.user, self.user) From 77781d57c3e6606e6841c340ede70a9121c34dd0 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 16 Feb 2021 20:24:37 -0800 Subject: [PATCH 0026/1285] Fixes base activity tests --- bookwyrm/activitypub/base_activity.py | 5 ++- .../tests/activitypub/test_base_activity.py | 41 +------------------ 2 files changed, 6 insertions(+), 40 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 7292a0d7..a83021ad 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -107,7 +107,10 @@ class ActivityObject: instance = instance or model() for field in instance.simple_fields: - field.set_field_from_activity(instance, self) + try: + field.set_field_from_activity(instance, self) + except AttributeError as e: + raise ActivitySerializerError(e) # image fields have to be set after other fields because they can save # too early and jank up users diff --git a/bookwyrm/tests/activitypub/test_base_activity.py b/bookwyrm/tests/activitypub/test_base_activity.py index ce294c1c..d489fdaa 100644 --- a/bookwyrm/tests/activitypub/test_base_activity.py +++ b/bookwyrm/tests/activitypub/test_base_activity.py @@ -102,44 +102,6 @@ class BaseActivity(TestCase): with self.assertRaises(ActivitySerializerError): instance.to_model(model=models.User) - def test_to_model_simple_fields(self): - ''' test setting simple fields ''' - self.assertIsNone(self.user.name) - - activity = activitypub.Person( - id=self.user.remote_id, - name='New Name', - preferredUsername='mouse', - inbox='http://www.com/', - outbox='http://www.com/', - followers='', - summary='', - publicKey=None, - endpoints={}, - ) - - activity.to_model(model=models.User, instance=self.user) - - self.assertEqual(self.user.name, 'New Name') - - def test_to_model_foreign_key(self): - ''' test setting one to one/foreign key ''' - activity = activitypub.Person( - id=self.user.remote_id, - name='New Name', - preferredUsername='mouse', - inbox='http://www.com/', - outbox='http://www.com/', - followers='', - summary='', - publicKey=self.user.key_pair.to_activity(), - endpoints={}, - ) - - activity.publicKey.publicKeyPem = 'hi im secure' - - activity.to_model(model=models.User, instance=self.user) - self.assertEqual(self.user.key_pair.public_key, 'hi im secure') @responses.activate def test_to_model_image(self): @@ -176,8 +138,9 @@ class BaseActivity(TestCase): # this would trigger a broadcast because it's a local user with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): activity.to_model(model=models.User, instance=self.user) - self.assertIsNotNone(self.user.avatar.name) self.assertIsNotNone(self.user.avatar.file) + self.assertEqual(self.user.name, 'New Name') + self.assertEqual(self.user.key_pair.public_key, 'hi') def test_to_model_many_to_many(self): ''' annoying that these all need special handling ''' From 29df2e0fac58b86c7d049fbd594bf08c9cbf1902 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 16 Feb 2021 20:26:51 -0800 Subject: [PATCH 0027/1285] fixes typo in person test --- bookwyrm/tests/activitypub/test_person.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/tests/activitypub/test_person.py b/bookwyrm/tests/activitypub/test_person.py index 7548ed93..06240281 100644 --- a/bookwyrm/tests/activitypub/test_person.py +++ b/bookwyrm/tests/activitypub/test_person.py @@ -25,7 +25,7 @@ class Person(TestCase): def test_user_to_model(self): activity = activitypub.Person(**self.user_data) with patch('bookwyrm.models.user.set_remote_server.delay'): - user = activity.to_model(models=models.User) + user = activity.to_model(model=models.User) self.assertEqual(user.username, 'mouse@example.com') self.assertEqual(user.remote_id, 'https://example.com/user/mouse') self.assertFalse(user.local) From 7b27f98e2045cf442dcfb9c755fbd4e36ad45ca0 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 17 Feb 2021 08:34:21 -0800 Subject: [PATCH 0028/1285] Fixes recursive serializer --- bookwyrm/activitypub/base_activity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index a83021ad..10cbf3a0 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -166,7 +166,7 @@ class ActivityObject: # recursively serialize for (k, v) in data.items(): try: - is_subclass = issubclass(v, ActivityObject) + is_subclass = issubclass(type(v), ActivityObject) except TypeError: is_subclass = False if is_subclass: From cbf5479253a3d15cd9e57310c9791db34faa82db Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 17 Feb 2021 08:35:17 -0800 Subject: [PATCH 0029/1285] Test fixes --- .../connectors/test_openlibrary_connector.py | 28 +++++++++++++++---- .../tests/models/test_activitypub_mixin.py | 25 +++++++++-------- bookwyrm/tests/models/test_fields.py | 5 ++-- bookwyrm/tests/models/test_import_model.py | 4 ++- 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/bookwyrm/tests/connectors/test_openlibrary_connector.py b/bookwyrm/tests/connectors/test_openlibrary_connector.py index 8132a203..576e353b 100644 --- a/bookwyrm/tests/connectors/test_openlibrary_connector.py +++ b/bookwyrm/tests/connectors/test_openlibrary_connector.py @@ -92,11 +92,26 @@ class Openlibrary(TestCase): responses.add( responses.GET, 'https://openlibrary.org/authors/OL382982A', - json={'hi': 'there'}, + json={ + "name": "George Elliott", + "personal_name": "George Elliott", + "last_modified": { + "type": "/type/datetime", + "value": "2008-08-31 10:09:33.413686" + }, + "key": "/authors/OL453734A", + "type": { + "key": "/type/author" + }, + "id": 1259965, + "revision": 2 + }, status=200) results = self.connector.get_authors_from_data(self.work_data) - for result in results: - self.assertIsInstance(result, models.Author) + result = list(results)[0] + self.assertIsInstance(result, models.Author) + self.assertEqual(result.name, 'George Elliott') + self.assertEqual(result.openlibrary_key, 'OL453734A') def test_get_cover_url(self): @@ -201,8 +216,11 @@ class Openlibrary(TestCase): 'https://openlibrary.org/authors/OL382982A', json={'hi': 'there'}, status=200) - result = self.connector.create_edition_from_data( - work, self.edition_data) + with patch('bookwyrm.connectors.openlibrary.Connector.' \ + 'get_authors_from_data') as mock: + mock.return_value = [] + result = self.connector.create_edition_from_data( + work, self.edition_data) self.assertEqual(result.parent_work, work) self.assertEqual(result.title, 'Sabriel') self.assertEqual(result.isbn_10, '0060273224') diff --git a/bookwyrm/tests/models/test_activitypub_mixin.py b/bookwyrm/tests/models/test_activitypub_mixin.py index 1ea4ae6d..a6f069d4 100644 --- a/bookwyrm/tests/models/test_activitypub_mixin.py +++ b/bookwyrm/tests/models/test_activitypub_mixin.py @@ -30,6 +30,12 @@ class ActivitypubMixins(TestCase): outbox='https://example.com/users/rat/outbox', ) + self.object_mock = { + 'to': 'to field', 'cc': 'cc field', + 'content': 'hi', 'id': 'bip', 'type': 'Test', + 'published': '2020-12-04T17:52:22.623807+00:00', + } + # ActivitypubMixin def test_to_activity(self): @@ -292,15 +298,10 @@ class ActivitypubMixins(TestCase): def test_to_create_activity(self): ''' wrapper for ActivityPub "create" action ''' - object_activity = { - 'to': 'to field', 'cc': 'cc field', - 'content': 'hi', - 'published': '2020-12-04T17:52:22.623807+00:00', - } MockSelf = namedtuple('Self', ('remote_id', 'to_activity')) mock_self = MockSelf( 'https://example.com/status/1', - lambda *args: object_activity + lambda *args: self.object_mock ) activity = ObjectMixin.to_create_activity( mock_self, self.local_user) @@ -312,7 +313,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(activity['type'], 'Create') self.assertEqual(activity['to'], 'to field') self.assertEqual(activity['cc'], 'cc field') - self.assertEqual(activity['object'], object_activity) + self.assertIsInstance(activity['object'], dict) self.assertEqual( activity['signature'].creator, '%s#main-key' % self.local_user.remote_id @@ -323,7 +324,7 @@ class ActivitypubMixins(TestCase): MockSelf = namedtuple('Self', ('remote_id', 'to_activity')) mock_self = MockSelf( 'https://example.com/status/1', - lambda *args: {} + lambda *args: self.object_mock ) activity = ObjectMixin.to_delete_activity( mock_self, self.local_user) @@ -346,7 +347,7 @@ class ActivitypubMixins(TestCase): MockSelf = namedtuple('Self', ('remote_id', 'to_activity')) mock_self = MockSelf( 'https://example.com/status/1', - lambda *args: {} + lambda *args: self.object_mock ) activity = ObjectMixin.to_update_activity( mock_self, self.local_user) @@ -361,7 +362,7 @@ class ActivitypubMixins(TestCase): self.assertEqual( activity['to'], ['https://www.w3.org/ns/activitystreams#Public']) - self.assertEqual(activity['object'], {}) + self.assertIsInstance(activity['object'], dict) # Activity mixin @@ -370,7 +371,7 @@ class ActivitypubMixins(TestCase): MockSelf = namedtuple('Self', ('remote_id', 'to_activity', 'user')) mock_self = MockSelf( 'https://example.com/status/1', - lambda *args: {}, + lambda *args: self.object_mock, self.local_user, ) activity = ActivityMixin.to_undo_activity(mock_self) @@ -380,4 +381,4 @@ class ActivitypubMixins(TestCase): ) self.assertEqual(activity['actor'], self.local_user.remote_id) self.assertEqual(activity['type'], 'Undo') - self.assertEqual(activity['object'], {}) + self.assertIsInstance(activity['object'], dict) diff --git a/bookwyrm/tests/models/test_fields.py b/bookwyrm/tests/models/test_fields.py index 38de9c98..24c0fb02 100644 --- a/bookwyrm/tests/models/test_fields.py +++ b/bookwyrm/tests/models/test_fields.py @@ -17,6 +17,7 @@ from django.db import models from django.test import TestCase from django.utils import timezone +from bookwyrm import activitypub from bookwyrm.activitypub.base_activity import ActivityObject from bookwyrm.models import fields, User, Status from bookwyrm.models.base_model import BookWyrmModel @@ -275,7 +276,7 @@ class ActivitypubFields(TestCase): 'rat', 'rat@rat.rat', 'ratword', local=True, localname='rat') with patch('bookwyrm.models.user.set_remote_server.delay'): - value = instance.field_from_activity(userdata) + value = instance.field_from_activity(activitypub.Person(**userdata)) self.assertIsInstance(value, User) self.assertNotEqual(value, unrelated_user) self.assertEqual(value.remote_id, 'https://example.com/user/mouse') @@ -300,7 +301,7 @@ class ActivitypubFields(TestCase): local=True, localname='rat') with patch('bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast'): - value = instance.field_from_activity(userdata) + value = instance.field_from_activity(activitypub.Person(**userdata)) self.assertEqual(value, user) diff --git a/bookwyrm/tests/models/test_import_model.py b/bookwyrm/tests/models/test_import_model.py index 146c60a7..7ec4e700 100644 --- a/bookwyrm/tests/models/test_import_model.py +++ b/bookwyrm/tests/models/test_import_model.py @@ -171,6 +171,8 @@ class ImportJob(TestCase): 'bookwyrm.connectors.connector_manager.first_search_result' ) as search: search.return_value = result - book = self.item_1.get_book_from_isbn() + with patch('bookwyrm.connectors.openlibrary.Connector.' \ + 'get_authors_from_data'): + book = self.item_1.get_book_from_isbn() self.assertEqual(book.title, 'Sabriel') From 8bb20730fca03ad8c5565ee3f73676816870051e Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 17 Feb 2021 09:33:33 -0800 Subject: [PATCH 0030/1285] Fixes bug in serializing dataclasses in place --- bookwyrm/activitypub/base_activity.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 10cbf3a0..8b538f0b 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -162,15 +162,14 @@ class ActivityObject: def serialize(self): ''' convert to dictionary with context attr ''' - data = self.__dict__ + data = self.__dict__.copy() # recursively serialize for (k, v) in data.items(): try: - is_subclass = issubclass(type(v), ActivityObject) + if issubclass(type(v), ActivityObject): + data[k] = v.serialize() except TypeError: - is_subclass = False - if is_subclass: - data[k] = v.serialize() + pass data = {k:v for (k, v) in data.items() if v is not None} data['@context'] = 'https://www.w3.org/ns/activitystreams' return data From b18dac5814fd74c62782e6f94d4a7da049f4e2c1 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 17 Feb 2021 10:15:22 -0800 Subject: [PATCH 0031/1285] Don't use generic ActivityObject as serializer --- bookwyrm/activitypub/base_activity.py | 6 +++++- bookwyrm/activitypub/ordered_collection.py | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 8b538f0b..7b9fe519 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -76,8 +76,12 @@ class ActivityObject: is_subclass = False # parse a dict into the appropriate activity if is_subclass and isinstance(value, dict): + serializer = None + if not isinstance(field.type, ActivityObject): + # this is generic, gotta figure out the type manually + serializer = field.type value = naive_parse( - activity_objects, value, serializer=field.type) + activity_objects, value, serializer=serializer) except KeyError: if field.default == MISSING and \ diff --git a/bookwyrm/activitypub/ordered_collection.py b/bookwyrm/activitypub/ordered_collection.py index cf642994..38f1c26c 100644 --- a/bookwyrm/activitypub/ordered_collection.py +++ b/bookwyrm/activitypub/ordered_collection.py @@ -17,6 +17,7 @@ class OrderedCollection(ActivityObject): @dataclass(init=False) class OrderedCollectionPrivate(OrderedCollection): + ''' an ordered collection with privacy settings ''' to: List[str] = field(default_factory=lambda: []) cc: List[str] = field(default_factory=lambda: []) From 9225043b5d1cedbb2a38f137c9f7d929783ef6e7 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 17 Feb 2021 10:16:17 -0800 Subject: [PATCH 0032/1285] Fixes relationship model test --- .../tests/models/test_relationship_models.py | 21 ++++++------------- bookwyrm/tests/models/test_shelf_model.py | 1 + 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/bookwyrm/tests/models/test_relationship_models.py b/bookwyrm/tests/models/test_relationship_models.py index 97f267b6..0ef53450 100644 --- a/bookwyrm/tests/models/test_relationship_models.py +++ b/bookwyrm/tests/models/test_relationship_models.py @@ -23,19 +23,6 @@ class Relationship(TestCase): self.local_user.remote_id = 'http://local.com/user/mouse' self.local_user.save(broadcast=False) - def test_user_follows(self): - ''' create a follow relationship ''' - with patch('bookwyrm.models.activitypub_mixin.ActivityMixin.broadcast'): - rel = models.UserFollows.objects.create( - user_subject=self.local_user, - user_object=self.remote_user - ) - - activity = rel.to_activity() - self.assertEqual(activity['id'], rel.remote_id) - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['object'], self.remote_user.remote_id) - def test_user_follows_from_request(self): ''' convert a follow request into a follow ''' @@ -116,13 +103,15 @@ class Relationship(TestCase): self.assertEqual(user.remote_id, self.local_user.remote_id) self.assertEqual(activity['type'], 'Accept') self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual( - activity['object']['id'], request.remote_id) + self.assertEqual(activity['object']['id'], 'https://www.hi.com/') + self.local_user.manually_approves_followers = True + self.local_user.save(broadcast=False) models.UserFollowRequest.broadcast = mock_broadcast request = models.UserFollowRequest.objects.create( user_subject=self.remote_user, user_object=self.local_user, + remote_id='https://www.hi.com/' ) request.accept() @@ -145,6 +134,8 @@ class Relationship(TestCase): activity['object']['id'], request.remote_id) models.UserFollowRequest.broadcast = mock_reject + self.local_user.manually_approves_followers = True + self.local_user.save(broadcast=False) request = models.UserFollowRequest.objects.create( user_subject=self.remote_user, user_object=self.local_user, diff --git a/bookwyrm/tests/models/test_shelf_model.py b/bookwyrm/tests/models/test_shelf_model.py index 11b5ad5c..c997326e 100644 --- a/bookwyrm/tests/models/test_shelf_model.py +++ b/bookwyrm/tests/models/test_shelf_model.py @@ -4,6 +4,7 @@ from django.test import TestCase from bookwyrm import models, settings +#pylint: disable=unused-argument class Shelf(TestCase): ''' some activitypub oddness ahead ''' def setUp(self): From 92e40e1cec05c3d2cc2de66334fa605176a47d86 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 17 Feb 2021 10:30:02 -0800 Subject: [PATCH 0033/1285] Pass model instances into activities instead of json --- bookwyrm/activitypub/base_activity.py | 11 +++++------ bookwyrm/models/activitypub_mixin.py | 10 +++++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 7b9fe519..dbd4eacc 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -74,14 +74,13 @@ class ActivityObject: is_subclass = issubclass(field.type, ActivityObject) except TypeError: is_subclass = False + # serialize a model obj + if hasattr(value, 'to_activity'): + value = value.to_activity() # parse a dict into the appropriate activity - if is_subclass and isinstance(value, dict): - serializer = None - if not isinstance(field.type, ActivityObject): - # this is generic, gotta figure out the type manually - serializer = field.type + elif is_subclass and isinstance(value, dict): value = naive_parse( - activity_objects, value, serializer=serializer) + activity_objects, value, serializer=field.type) except KeyError: if field.default == MISSING and \ diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 6ecf8d96..9863af4c 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -260,7 +260,7 @@ class ObjectMixin(ActivitypubMixin): actor=user.remote_id, to=['%s/followers' % user.remote_id], cc=['https://www.w3.org/ns/activitystreams#Public'], - object=self.to_activity(), + object=self, ).serialize() @@ -271,7 +271,7 @@ class ObjectMixin(ActivitypubMixin): id=activity_id, actor=user.remote_id, to=['https://www.w3.org/ns/activitystreams#Public'], - object=self.to_activity() + object=self ).serialize() @@ -363,7 +363,7 @@ class CollectionItemMixin(ActivitypubMixin): return activitypub.Add( id='%s#add' % self.remote_id, actor=self.user.remote_id, - object=object_field.to_activity(), + object=object_field, target=collection_field.remote_id ).serialize() @@ -374,7 +374,7 @@ class CollectionItemMixin(ActivitypubMixin): return activitypub.Remove( id='%s#remove' % self.remote_id, actor=self.user.remote_id, - object=object_field.to_activity(), + object=object_field, target=collection_field.remote_id ).serialize() @@ -403,7 +403,7 @@ class ActivityMixin(ActivitypubMixin): return activitypub.Undo( id='%s#undo' % self.remote_id, actor=user.remote_id, - object=self.to_activity() + object=self, ).serialize() From d5ca77362bf14f951101fd59d821e85ceb1a7a6b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 17 Feb 2021 10:38:09 -0800 Subject: [PATCH 0034/1285] Fixes boost activity type in status model test --- bookwyrm/tests/models/test_status_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py index 6978d593..f9b95f34 100644 --- a/bookwyrm/tests/models/test_status_model.py +++ b/bookwyrm/tests/models/test_status_model.py @@ -63,7 +63,7 @@ class Status(TestCase): self.assertEqual(models.Review().status_type, 'Review') self.assertEqual(models.Quotation().status_type, 'Quotation') self.assertEqual(models.Comment().status_type, 'Comment') - self.assertEqual(models.Boost().status_type, 'Boost') + self.assertEqual(models.Boost().status_type, 'Announce') def test_boostable(self, _): ''' can a status be boosted, based on privacy ''' From d022fef6259d8d1c6d975a439fa88d2073c5d46f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 17 Feb 2021 11:28:54 -0800 Subject: [PATCH 0035/1285] broadcast accepts correctly --- bookwyrm/models/relationship.py | 20 +++++++++++++------- bookwyrm/tests/views/test_follow.py | 8 +++++--- bookwyrm/views/follow.py | 16 +++++++++------- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index 722f0fac..76852988 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -91,9 +91,14 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship): user_subject=self.user_object, user_object=self.user_subject, ) - return None + return except (UserFollows.DoesNotExist, UserBlocks.DoesNotExist): + pass + + # this was calling itself which is not my idea of "super" ... + if not self.id: super().save(*args, **kwargs) + return if broadcast and self.user_subject.local and not self.user_object.local: self.broadcast(self.to_activity(), self.user_subject) @@ -116,16 +121,17 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship): def accept(self): ''' turn this request into the real deal''' user = self.user_object - activity = activitypub.Accept( - id=self.get_remote_id(status='accepts'), - actor=self.user_object.remote_id, - object=self.to_activity() - ).serialize() + if not self.user_subject.local: + activity = activitypub.Accept( + id=self.get_remote_id(status='accepts'), + actor=self.user_object.remote_id, + object=self.to_activity() + ).serialize() + self.broadcast(activity, user) with transaction.atomic(): UserFollows.from_request(self) self.delete() - self.broadcast(activity, user) def reject(self): diff --git a/bookwyrm/tests/views/test_follow.py b/bookwyrm/tests/views/test_follow.py index 9a157659..b913c17d 100644 --- a/bookwyrm/tests/views/test_follow.py +++ b/bookwyrm/tests/views/test_follow.py @@ -77,7 +77,6 @@ class BookViews(TestCase): self.assertEqual(rel.status, 'follow_request') - def test_handle_follow_local(self): ''' send a follow request ''' rat = models.User.objects.create_user( @@ -106,15 +105,18 @@ class BookViews(TestCase): self.remote_user.followers.add(self.local_user) self.assertEqual(self.remote_user.followers.count(), 1) # need to see if this ACTUALLY broadcasts - raise ValueError() - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \ + as mock: views.unfollow(request) + self.assertEqual(mock.call_count, 1) self.assertEqual(self.remote_user.followers.count(), 0) def test_handle_accept(self): ''' accept a follow request ''' + self.local_user.manually_approves_followers = True + self.local_user.save(broadcast=False) request = self.factory.post('', {'user': self.remote_user.username}) request.user = self.local_user rel = models.UserFollowRequest.objects.create( diff --git a/bookwyrm/views/follow.py b/bookwyrm/views/follow.py index e95355e6..d015dab2 100644 --- a/bookwyrm/views/follow.py +++ b/bookwyrm/views/follow.py @@ -1,5 +1,6 @@ ''' views for actions you can take in the application ''' from django.contrib.auth.decorators import login_required +from django.db import IntegrityError from django.http import HttpResponseBadRequest from django.shortcuts import redirect from django.views.decorators.http import require_POST @@ -17,10 +18,13 @@ def follow(request): except models.User.DoesNotExist: return HttpResponseBadRequest() - rel, _ = models.UserFollowRequest.objects.get_or_create( - user_subject=request.user, - user_object=to_follow, - ) + try: + models.UserFollowRequest.objects.create( + user_subject=request.user, + user_object=to_follow, + ) + except IntegrityError: + pass return redirect(to_follow.local_path) @@ -38,9 +42,7 @@ def unfollow(request): models.UserFollows.objects.get( user_subject=request.user, user_object=to_unfollow - ) - - to_unfollow.followers.remove(request.user) + ).delete() return redirect(to_unfollow.local_path) From 08dc5b4d8617c1dde4e18e35948a0eb52b3b86d3 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 17 Feb 2021 11:45:21 -0800 Subject: [PATCH 0036/1285] Fixes unfollow --- bookwyrm/models/relationship.py | 14 ++++++++++++-- bookwyrm/tests/views/test_follow.py | 1 - 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index 76852988..3e6ac0fb 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -5,7 +5,7 @@ from django.db.models import Q from django.dispatch import receiver from bookwyrm import activitypub -from .activitypub_mixin import ActivitypubMixin, ActivityMixin +from .activitypub_mixin import ActivitypubMixin, ActivityMixin, generate_activity from .base_model import BookWyrmModel from . import fields @@ -56,10 +56,20 @@ class UserRelationship(BookWyrmModel): return '%s#%s/%d' % (base_path, status, self.id) -class UserFollows(ActivitypubMixin, UserRelationship): +class UserFollows(ActivityMixin, UserRelationship): ''' Following a user ''' status = 'follows' + def save(self, *args, **kwargs): + ''' never broadcast a creation (that's handled by "accept"), only a + deletion (an unfollow, as opposed to "reject" and undo pending) ''' + super().save(*args, broadcast=False, **kwargs) + + def to_activity(self): + ''' overrides default to manually set serializer ''' + return activitypub.Follow(**generate_activity(self)) + + @classmethod def from_request(cls, follow_request): ''' converts a follow request into a follow relationship ''' diff --git a/bookwyrm/tests/views/test_follow.py b/bookwyrm/tests/views/test_follow.py index b913c17d..5f61ab03 100644 --- a/bookwyrm/tests/views/test_follow.py +++ b/bookwyrm/tests/views/test_follow.py @@ -104,7 +104,6 @@ class BookViews(TestCase): request.user = self.local_user self.remote_user.followers.add(self.local_user) self.assertEqual(self.remote_user.followers.count(), 1) - # need to see if this ACTUALLY broadcasts with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \ as mock: views.unfollow(request) From 7b21a0a2081af5271aa3322ad6effbe93e307aa8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 17 Feb 2021 12:23:55 -0800 Subject: [PATCH 0037/1285] Fix things, unfix things, refix things, break things, fix things --- bookwyrm/activitypub/base_activity.py | 6 ++++-- bookwyrm/models/relationship.py | 5 +---- bookwyrm/tests/views/test_follow.py | 2 ++ bookwyrm/tests/views/test_inbox.py | 8 +++++--- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index dbd4eacc..c360711d 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -79,8 +79,10 @@ class ActivityObject: value = value.to_activity() # parse a dict into the appropriate activity elif is_subclass and isinstance(value, dict): - value = naive_parse( - activity_objects, value, serializer=field.type) + if activity_objects: + value = naive_parse(activity_objects, value) + else: + value = naive_parse(activity_objects, value, serializer=field.type) except KeyError: if field.default == MISSING and \ diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index 3e6ac0fb..764cada6 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -105,10 +105,7 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship): except (UserFollows.DoesNotExist, UserBlocks.DoesNotExist): pass - # this was calling itself which is not my idea of "super" ... - if not self.id: - super().save(*args, **kwargs) - return + super().save(*args, **kwargs) if broadcast and self.user_subject.local and not self.user_object.local: self.broadcast(self.to_activity(), self.user_subject) diff --git a/bookwyrm/tests/views/test_follow.py b/bookwyrm/tests/views/test_follow.py index 5f61ab03..62543d2d 100644 --- a/bookwyrm/tests/views/test_follow.py +++ b/bookwyrm/tests/views/test_follow.py @@ -135,6 +135,8 @@ class BookViews(TestCase): def test_handle_reject(self): ''' reject a follow request ''' + self.local_user.manually_approves_followers = True + self.local_user.save(broadcast=False) request = self.factory.post('', {'user': self.remote_user.username}) request.user = self.local_user rel = models.UserFollowRequest.objects.create( diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 0251d8a2..1e597cb1 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -236,7 +236,7 @@ class Inbox(TestCase): self.assertEqual(book_list.remote_id, 'https://example.com/list/22') - def test_handle_follow(self): + def test_handle_follow_x(self): ''' remote user wants to follow local user ''' activity = { "@context": "https://www.w3.org/ns/activitystreams", @@ -246,7 +246,9 @@ class Inbox(TestCase): "object": "https://example.com/user/mouse" } - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') as mock: + self.assertFalse(models.UserFollowRequest.objects.exists()) + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \ + as mock: views.inbox.activity_task(activity) self.assertEqual(mock.call_count, 1) @@ -736,6 +738,6 @@ class Inbox(TestCase): "type": "Block", "actor": "https://example.com/users/rat", "object": "https://example.com/user/mouse" - }} + }} views.inbox.activity_task(activity) self.assertFalse(models.UserBlocks.objects.exists()) From e8e4ed773cfa83961756f795c4f234b4d35d5638 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 17 Feb 2021 13:07:19 -0800 Subject: [PATCH 0038/1285] Fixes deletion for boosts --- bookwyrm/models/status.py | 4 ++++ bookwyrm/tests/views/test_interaction.py | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index fbc94e9d..2fb70801 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -86,6 +86,10 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): def delete(self, *args, **kwargs):#pylint: disable=unused-argument ''' "delete" a status ''' + if hasattr(self, 'boosted_status'): + # okay but if it's a boost really delete it + super().delete(*args, **kwargs) + return self.deleted = True self.deleted_date = timezone.now() self.save() diff --git a/bookwyrm/tests/views/test_interaction.py b/bookwyrm/tests/views/test_interaction.py index da6d5f9c..c6d39f29 100644 --- a/bookwyrm/tests/views/test_interaction.py +++ b/bookwyrm/tests/views/test_interaction.py @@ -138,7 +138,7 @@ class InteractionViews(TestCase): ''' undo a boost ''' view = views.Unboost.as_view() request = self.factory.post('') - request.user = self.remote_user + request.user = self.local_user with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): status = models.Status.objects.create( user=self.local_user, content='hi') @@ -146,7 +146,9 @@ class InteractionViews(TestCase): self.assertEqual(models.Boost.objects.count(), 1) self.assertEqual(models.Notification.objects.count(), 1) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \ + as mock: view(request, status.id) + self.assertEqual(mock.call_count, 1) self.assertEqual(models.Boost.objects.count(), 0) self.assertEqual(models.Notification.objects.count(), 0) From 79875271f725dfb079b13d29c2f3a85a6b061522 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 17 Feb 2021 13:33:48 -0800 Subject: [PATCH 0039/1285] Makes next/prev page links optional --- bookwyrm/activitypub/ordered_collection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/activitypub/ordered_collection.py b/bookwyrm/activitypub/ordered_collection.py index 38f1c26c..14b35f3c 100644 --- a/bookwyrm/activitypub/ordered_collection.py +++ b/bookwyrm/activitypub/ordered_collection.py @@ -39,6 +39,6 @@ class OrderedCollectionPage(ActivityObject): ''' structure of an ordered collection activity ''' partOf: str orderedItems: List - next: str - prev: str + next: str = None + prev: str = None type: str = 'OrderedCollectionPage' From 3f02b5f6f28040b8d3f0cb1bcce5a7b3330e6e56 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 17 Feb 2021 13:34:36 -0800 Subject: [PATCH 0040/1285] Fixes view tests --- bookwyrm/models/activitypub_mixin.py | 2 +- bookwyrm/tests/views/test_list.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 9863af4c..1abf9a75 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -248,7 +248,7 @@ class ObjectMixin(ActivitypubMixin): actor=user.remote_id, to=activity_object['to'], cc=activity_object['cc'], - object=activity_object, + object=self, signature=signature, ).serialize() diff --git a/bookwyrm/tests/views/test_list.py b/bookwyrm/tests/views/test_list.py index 8737e347..e41d9806 100644 --- a/bookwyrm/tests/views/test_list.py +++ b/bookwyrm/tests/views/test_list.py @@ -9,7 +9,7 @@ from django.test.client import RequestFactory from bookwyrm import models, views from bookwyrm.activitypub import ActivitypubResponse - +#pylint: disable=unused-argument class ListViews(TestCase): ''' tag views''' def setUp(self): @@ -45,7 +45,7 @@ class ListViews(TestCase): with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): models.List.objects.create(name='Public list', user=self.local_user) models.List.objects.create( - name='Private list', privacy='private', user=self.local_user) + name='Private list', privacy='direct', user=self.local_user) request = self.factory.get('') request.user = self.local_user @@ -164,7 +164,7 @@ class ListViews(TestCase): with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): models.List.objects.create(name='Public list', user=self.local_user) models.List.objects.create( - name='Private list', privacy='private', user=self.local_user) + name='Private list', privacy='direct', user=self.local_user) request = self.factory.get('') request.user = self.local_user From e707374888f0c9a8571c6356f41bb19bb7086e96 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 17 Feb 2021 14:37:20 -0800 Subject: [PATCH 0041/1285] Don't broadcast from inbox tests --- bookwyrm/models/relationship.py | 18 ++++++++++-------- bookwyrm/tests/views/test_inbox.py | 9 +++------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index 764cada6..14f702b4 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -5,7 +5,8 @@ from django.db.models import Q from django.dispatch import receiver from bookwyrm import activitypub -from .activitypub_mixin import ActivitypubMixin, ActivityMixin, generate_activity +from .activitypub_mixin import ActivitypubMixin, ActivityMixin +from .activitypub_mixin import generate_activity from .base_model import BookWyrmModel from . import fields @@ -143,14 +144,15 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship): def reject(self): ''' generate a Reject for this follow request ''' - user = self.user_object - activity = activitypub.Reject( - id=self.get_remote_id(status='rejects'), - actor=self.user_object.remote_id, - object=self.to_activity() - ).serialize() + if self.user_object.local: + activity = activitypub.Reject( + id=self.get_remote_id(status='rejects'), + actor=self.user_object.remote_id, + object=self.to_activity() + ).serialize() + self.broadcast(activity, self.user_object) + self.delete() - self.broadcast(activity, user) class UserBlocks(ActivityMixin, UserRelationship): diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 1e597cb1..c2658917 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -379,11 +379,8 @@ class Inbox(TestCase): views.inbox.activity_task(activity) # request should be deleted - self.assertEqual(models.UserFollowRequest.objects.count(), 0) - - # relationship should be created - follows = self.remote_user.followers - self.assertEqual(follows.count(), 0) + self.assertFalse(models.UserFollowRequest.objects.exists()) + self.assertFalse(self.remote_user.followers.exists()) def test_handle_update_list(self): @@ -580,7 +577,7 @@ class Inbox(TestCase): 'object': { 'type': 'Announce', 'id': boost.remote_id, - 'actor': self.local_user.remote_id, + 'actor': self.remote_user.remote_id, 'object': self.status.remote_id, } } From fb98ef4b384f6fb2643623782cd3ffd551b9ebb8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 19 Feb 2021 11:16:01 -0800 Subject: [PATCH 0042/1285] Remove redundant activitypub dataclass --- bookwyrm/activitypub/__init__.py | 2 +- bookwyrm/activitypub/verbs.py | 8 +------- bookwyrm/models/shelf.py | 2 +- bookwyrm/models/tag.py | 2 +- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/bookwyrm/activitypub/__init__.py b/bookwyrm/activitypub/__init__.py index 5303d1b2..f06154ab 100644 --- a/bookwyrm/activitypub/__init__.py +++ b/bookwyrm/activitypub/__init__.py @@ -15,7 +15,7 @@ from .response import ActivitypubResponse from .book import Edition, Work, Author from .verbs import Create, Delete, Undo, Update from .verbs import Follow, Accept, Reject, Block -from .verbs import Add, AddBook, AddListItem, Remove +from .verbs import Add, AddListItem, Remove from .verbs import Announce, Like # this creates a list of all the Activity types that we can serialize, diff --git a/bookwyrm/activitypub/verbs.py b/bookwyrm/activitypub/verbs.py index aa6e7eee..6f1a4d44 100644 --- a/bookwyrm/activitypub/verbs.py +++ b/bookwyrm/activitypub/verbs.py @@ -132,13 +132,7 @@ class Add(Verb): @dataclass(init=False) -class AddBook(Add): - '''Add activity that's aware of the book obj ''' - object: Edition - - -@dataclass(init=False) -class AddListItem(AddBook): +class AddListItem(Add): '''Add activity that's aware of the book obj ''' notes: str = None order: int = 0 diff --git a/bookwyrm/models/shelf.py b/bookwyrm/models/shelf.py index 921b8617..dfb8b9b3 100644 --- a/bookwyrm/models/shelf.py +++ b/bookwyrm/models/shelf.py @@ -57,7 +57,7 @@ class ShelfBook(CollectionItemMixin, BookWyrmModel): user = fields.ForeignKey( 'User', on_delete=models.PROTECT, activitypub_field='actor') - activity_serializer = activitypub.AddBook + activity_serializer = activitypub.Add object_field = 'book' collection_field = 'shelf' diff --git a/bookwyrm/models/tag.py b/bookwyrm/models/tag.py index d75f6e05..12645b9e 100644 --- a/bookwyrm/models/tag.py +++ b/bookwyrm/models/tag.py @@ -50,7 +50,7 @@ class UserTag(CollectionItemMixin, BookWyrmModel): tag = fields.ForeignKey( 'Tag', on_delete=models.PROTECT, activitypub_field='target') - activity_serializer = activitypub.AddBook + activity_serializer = activitypub.Add object_field = 'book' collection_field = 'tag' From dbe9431d5adf93276e4ab381ddf3868b338f0642 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 20 Feb 2021 11:24:41 -0800 Subject: [PATCH 0043/1285] Fixes pure serializer --- bookwyrm/models/activitypub_mixin.py | 25 +++++++++++-------- bookwyrm/models/status.py | 24 ++++++++++-------- .../tests/models/test_activitypub_mixin.py | 23 ----------------- bookwyrm/tests/models/test_status_model.py | 21 ++++++++++++++++ 4 files changed, 49 insertions(+), 44 deletions(-) diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 1abf9a75..12bbda96 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -191,7 +191,7 @@ class ObjectMixin(ActivitypubMixin): try: software = None - # do we have a "pure" activitypub version of this for mastodon? + # do we have a "pure" activitypub version of this for mastodon? if hasattr(self, 'pure_content'): pure_activity = self.to_create_activity(user, pure=True) self.broadcast(pure_activity, user, software='other') @@ -199,7 +199,7 @@ class ObjectMixin(ActivitypubMixin): # sends to BW only if we just did a pure version for masto activity = self.to_create_activity(user) self.broadcast(activity, user, software=software) - except KeyError: + except AttributeError: # janky as heck, this catches the mutliple inheritence chain # for boosts and ignores this auxilliary broadcast return @@ -228,27 +228,27 @@ class ObjectMixin(ActivitypubMixin): def to_create_activity(self, user, **kwargs): ''' returns the object wrapped in a Create activity ''' - activity_object = self.to_activity(**kwargs) + activity_object = self.to_activity_dataclass(**kwargs) signature = None create_id = self.remote_id + '/activity' - if 'content' in activity_object and activity_object['content']: + if hasattr(activity_object, 'content') and activity_object.content: signer = pkcs1_15.new(RSA.import_key(user.key_pair.private_key)) - content = activity_object['content'] + content = activity_object.content signed_message = signer.sign(SHA256.new(content.encode('utf8'))) signature = activitypub.Signature( creator='%s#main-key' % user.remote_id, - created=activity_object['published'], + created=activity_object.published, signatureValue=b64encode(signed_message).decode('utf8') ) return activitypub.Create( id=create_id, actor=user.remote_id, - to=activity_object['to'], - cc=activity_object['cc'], - object=self, + to=activity_object.to, + cc=activity_object.cc, + object=activity_object, signature=signature, ).serialize() @@ -312,7 +312,7 @@ class OrderedCollectionPageMixin(ObjectMixin): activity['first'] = '%s?page=1' % remote_id activity['last'] = '%s?page=%d' % (remote_id, paginated.num_pages) - return serializer(**activity).serialize() + return serializer(**activity) class OrderedCollectionMixin(OrderedCollectionPageMixin): @@ -324,9 +324,12 @@ class OrderedCollectionMixin(OrderedCollectionPageMixin): activity_serializer = activitypub.OrderedCollection + def to_activity_dataclass(self, **kwargs): + return self.to_ordered_collection(self.collection_queryset, **kwargs) + def to_activity(self, **kwargs): ''' an ordered collection of the specified model queryset ''' - return self.to_ordered_collection(self.collection_queryset, **kwargs) + return self.to_ordered_collection(self.collection_queryset, **kwargs).serialize() class CollectionItemMixin(ActivitypubMixin): diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 2fb70801..aff028a5 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -157,7 +157,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): **kwargs ) - def to_activity(self, pure=False):# pylint: disable=arguments-differ + def to_activity_dataclass(self, pure=False):# pylint: disable=arguments-differ ''' return tombstone if the status is deleted ''' if self.deleted: return activitypub.Tombstone( @@ -165,25 +165,29 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): url=self.remote_id, deleted=self.deleted_date.isoformat(), published=self.deleted_date.isoformat() - ).serialize() - activity = ActivitypubMixin.to_activity(self) - activity['replies'] = self.to_replies() + ) + activity = ActivitypubMixin.to_activity_dataclass(self) + activity.replies = self.to_replies() # "pure" serialization for non-bookwyrm instances if pure and hasattr(self, 'pure_content'): - activity['content'] = self.pure_content - if 'name' in activity: - activity['name'] = self.pure_name - activity['type'] = self.pure_type - activity['attachment'] = [ + activity.content = self.pure_content + if hasattr(activity, 'name'): + activity.name = self.pure_name + activity.type = self.pure_type + activity.attachment = [ image_serializer(b.cover, b.alt_text) \ for b in self.mention_books.all()[:4] if b.cover] if hasattr(self, 'book') and self.book.cover: - activity['attachment'].append( + activity.attachment.append( image_serializer(self.book.cover, self.book.alt_text) ) return activity + def to_activity(self, pure=False):# pylint: disable=arguments-differ + ''' json serialized activitypub class ''' + return self.to_activity_dataclass(pure=pure).serialize() + class GeneratedNote(Status): ''' these are app-generated messages about user activity ''' diff --git a/bookwyrm/tests/models/test_activitypub_mixin.py b/bookwyrm/tests/models/test_activitypub_mixin.py index a6f069d4..11b944d9 100644 --- a/bookwyrm/tests/models/test_activitypub_mixin.py +++ b/bookwyrm/tests/models/test_activitypub_mixin.py @@ -296,29 +296,6 @@ class ActivitypubMixins(TestCase): id=1, user=self.local_user, deleted=True).save() - def test_to_create_activity(self): - ''' wrapper for ActivityPub "create" action ''' - MockSelf = namedtuple('Self', ('remote_id', 'to_activity')) - mock_self = MockSelf( - 'https://example.com/status/1', - lambda *args: self.object_mock - ) - activity = ObjectMixin.to_create_activity( - mock_self, self.local_user) - self.assertEqual( - activity['id'], - 'https://example.com/status/1/activity' - ) - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['type'], 'Create') - self.assertEqual(activity['to'], 'to field') - self.assertEqual(activity['cc'], 'cc field') - self.assertIsInstance(activity['object'], dict) - self.assertEqual( - activity['signature'].creator, - '%s#main-key' % self.local_user.remote_id - ) - def test_to_delete_activity(self): ''' wrapper for Delete activity ''' MockSelf = namedtuple('Self', ('remote_id', 'to_activity')) diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py index f9b95f34..c6911b6d 100644 --- a/bookwyrm/tests/models/test_status_model.py +++ b/bookwyrm/tests/models/test_status_model.py @@ -284,3 +284,24 @@ class Status(TestCase): with self.assertRaises(IntegrityError): models.Notification.objects.create( user=self.user, notification_type='GLORB') + + + def test_create_broadcast(self, broadcast_mock): + ''' should send out two verions of a status on create ''' + models.Comment.objects.create( + content='hi', user=self.user, book=self.book) + self.assertEqual(broadcast_mock.call_count, 2) + pure_call = broadcast_mock.call_args_list[0] + bw_call = broadcast_mock.call_args_list[1] + + self.assertEqual(pure_call[1]['software'], 'other') + args = pure_call[0][0] + self.assertEqual(args['type'], 'Create') + self.assertEqual(args['object']['type'], 'Note') + self.assertTrue('content' in args['object']) + + + self.assertEqual(bw_call[1]['software'], 'bookwyrm') + args = bw_call[0][0] + self.assertEqual(args['type'], 'Create') + self.assertEqual(args['object']['type'], 'Comment') From 02612b00e912564ebd436039138addcae353b840 Mon Sep 17 00:00:00 2001 From: Jim Fingal Date: Sun, 21 Feb 2021 22:49:52 -0800 Subject: [PATCH 0044/1285] Update Readme --- README.md | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 6a624ab3..c8b6e92b 100644 --- a/README.md +++ b/README.md @@ -112,36 +112,42 @@ Once the build is complete, you can access the instance at `localhost:1333` ## Installing in Production This project is still young and isn't, at the momoment, very stable, so please procede with caution when running in production. + ### Server setup - Get a domain name and set up DNS for your server - Set your server up with appropriate firewalls for running a web application (this instruction set is tested again Ubuntu 20.04) - - Set up a mailgun account and the appropriate DNS settings + - Set up an email service (such as mailgun) and the appropriate SMTP/DNS settings - Install Docker and docker-compose + ### Install and configure BookWyrm + +The `production` branch of BookWyrm contains a number of tools not on the `main` branch that are suited for running in production, such as `docker-compose` changes to update the default commands or configuration of containers, and indivudal changes to container config to enable things like SSL or regular backups. + +Instructions for running BookWyrm in production: + - Get the application code: `git clone git@github.com:mouse-reeve/bookwyrm.git` - Switch to the `production` branch `git checkout production` - Create your environment variables file `cp .env.example .env` - - Add your domain, email address, mailgun credentials + - Add your domain, email address, SMTP credentials - Set a secure redis password and secret key - Set a secure database password for postgres - Update your nginx configuration in `nginx/default.conf` - Replace `your-domain.com` with your domain name - - Run the application (this should also set up a Certbot ssl cert for your domain) - `docker-compose up --build` - Make sure all the images build successfully + - Run the application (this should also set up a Certbot ssl cert for your domain) with + `docker-compose up --build`, and make sure all the images build successfully - When docker has built successfully, stop the process with `CTRL-C` - Comment out the `command: certonly...` line in `docker-compose.yml` - - Run docker-compose in the background - `docker-compose up -d` - - Initialize the database - `./bw-dev initdb` - - Set up schedule backups with cron that runs that `docker-compose exec db pg_dump -U ` and saves the backup to a safe locationgi - - Congrats! You did it, go to your domain and enjoy the fruits of your labors + - Run docker-compose in the background with: `docker-compose up -d` + - Initialize the database with: `./bw-dev initdb` + - Set up schedule backups with cron that runs that `docker-compose exec db pg_dump -U ` and saves the backup to a safe location + +Congrats! You did it, go to your domain and enjoy the fruits of your labors. + ### Configure your instance - - Register a user account in the applcation UI + - Register a user account in the application UI - Make your account a superuser (warning: do *not* use django's `createsuperuser` command) - On your server, open the django shell `./bw-dev shell` From 252f09325a9b3622cfade6acb8db44fdb951b7f1 Mon Sep 17 00:00:00 2001 From: Jim Fingal Date: Sun, 21 Feb 2021 23:07:58 -0800 Subject: [PATCH 0045/1285] Add black command --- Makefile | 5 +++++ requirements.txt | 11 +++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..44abad0b --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +.PHONY: itblack + +itblack: + docker-compose run --rm web black celerywyrm + docker-compose run --rm web black bookwyrm \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e5d7798d..f354fd43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ celery==4.4.2 -coverage==5.1 Django==3.0.7 django-model-utils==4.0.0 environs==7.2.0 @@ -8,11 +7,15 @@ Markdown==3.3.3 Pillow>=7.1.0 psycopg2==2.8.4 pycryptodome==3.9.4 -pytest-django==4.1.0 -pytest==6.1.2 -pytest-cov==2.10.1 python-dateutil==2.8.1 redis==3.4.1 requests==2.22.0 responses==0.10.14 django-rename-app==0.1.2 + +# Dev +black==20.8b1 +coverage==5.1 +pytest-django==4.1.0 +pytest==6.1.2 +pytest-cov==2.10.1 From f654444aab261ad52e5c973ca883e4797a0a0446 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 22 Feb 2021 08:53:01 -0800 Subject: [PATCH 0046/1285] Fixes bug in saving remote server --- bookwyrm/models/user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 7b0f9a91..170b587e 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -171,15 +171,15 @@ class User(OrderedCollectionPageMixin, AbstractUser): def save(self, *args, **kwargs): ''' populate fields for new local users ''' + created = not bool(self.id) if not self.local and not re.match(regex.full_username, self.username): # generate a username that uses the domain (webfinger format) actor_parts = urlparse(self.remote_id) self.username = '%s@%s' % (self.username, actor_parts.netloc) super().save(*args, **kwargs) - return # this user already exists, no need to populate fields - if self.id: + if created: super().save(*args, **kwargs) return From bff75cedf54dd0e5dd5d30aa9848406c98a9ff40 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 22 Feb 2021 09:41:52 -0800 Subject: [PATCH 0047/1285] Boolean error in user save causing infinite recursion --- bookwyrm/models/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 170b587e..a65317fa 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -179,7 +179,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): super().save(*args, **kwargs) # this user already exists, no need to populate fields - if created: + if not created: super().save(*args, **kwargs) return From 6b74f56381cdf1c58c9cb0fc468896a535dc3071 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 22 Feb 2021 10:01:19 -0800 Subject: [PATCH 0048/1285] Safer set remote server --- bookwyrm/models/user.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index a65317fa..5e77b9e1 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -9,7 +9,7 @@ from django.db import models from django.utils import timezone from bookwyrm import activitypub -from bookwyrm.connectors import get_data +from bookwyrm.connectors import get_data, ConnectorException from bookwyrm.models.shelf import Shelf from bookwyrm.models.status import Status, Review from bookwyrm.settings import DOMAIN @@ -333,19 +333,24 @@ def get_or_create_remote_server(domain): except FederatedServer.DoesNotExist: pass - data = get_data('https://%s/.well-known/nodeinfo' % domain) - try: - nodeinfo_url = data.get('links')[0].get('href') - except (TypeError, KeyError): - return None + data = get_data('https://%s/.well-known/nodeinfo' % domain) + try: + nodeinfo_url = data.get('links')[0].get('href') + except (TypeError, KeyError): + return None + + data = get_data(nodeinfo_url) + application_type = data.get('software', {}).get('name') + application_type = data.get('software', {}).get('version') + except ConnectorException: + application_type = application_version = None - data = get_data(nodeinfo_url) server = FederatedServer.objects.create( server_name=domain, - application_type=data['software']['name'], - application_version=data['software']['version'], + application_type=application_type, + application_version=application_version, ) return server From ef9acaf87810a999ab01577764ba5400f0c6cd51 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 22 Feb 2021 11:38:11 -0800 Subject: [PATCH 0049/1285] Adds tests for setting remote server --- bookwyrm/models/user.py | 4 +- bookwyrm/tests/models/test_user_model.py | 84 +++++++++++++++++++++++- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 5e77b9e1..a1f927b2 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -338,11 +338,11 @@ def get_or_create_remote_server(domain): try: nodeinfo_url = data.get('links')[0].get('href') except (TypeError, KeyError): - return None + raise ConnectorException() data = get_data(nodeinfo_url) application_type = data.get('software', {}).get('name') - application_type = data.get('software', {}).get('version') + application_version = data.get('software', {}).get('version') except ConnectorException: application_type = application_version = None diff --git a/bookwyrm/tests/models/test_user_model.py b/bookwyrm/tests/models/test_user_model.py index a10c89b8..bd184b65 100644 --- a/bookwyrm/tests/models/test_user_model.py +++ b/bookwyrm/tests/models/test_user_model.py @@ -1,11 +1,13 @@ ''' testing models ''' from unittest.mock import patch from django.test import TestCase +import responses from bookwyrm import models from bookwyrm.settings import DOMAIN - +# pylint: disable=missing-class-docstring +# pylint: disable=missing-function-docstring class User(TestCase): def setUp(self): self.user = models.User.objects.create_user( @@ -71,3 +73,83 @@ class User(TestCase): self.assertEqual(activity['type'], 'OrderedCollection') self.assertEqual(activity['id'], self.user.outbox) self.assertEqual(activity['totalItems'], 0) + + + def test_set_remote_server(self): + server = models.FederatedServer.objects.create( + server_name=DOMAIN, + application_type='test type', + application_version=3 + ) + + models.user.set_remote_server(self.user.id) + self.user.refresh_from_db() + + self.assertEqual(self.user.federated_server, server) + + @responses.activate + def test_get_or_create_remote_server(self): + responses.add( + responses.GET, + 'https://%s/.well-known/nodeinfo' % DOMAIN, + json={'links': [{'href': 'http://www.example.com'}, {}]} + ) + responses.add( + responses.GET, + 'http://www.example.com', + json={'software': {'name': 'hi', 'version': '2'}}, + ) + + server = models.user.get_or_create_remote_server(DOMAIN) + self.assertEqual(server.server_name, DOMAIN) + self.assertEqual(server.application_type, 'hi') + self.assertEqual(server.application_version, '2') + + @responses.activate + def test_get_or_create_remote_server_no_wellknown(self): + responses.add( + responses.GET, + 'https://%s/.well-known/nodeinfo' % DOMAIN, + status=404 + ) + + server = models.user.get_or_create_remote_server(DOMAIN) + self.assertEqual(server.server_name, DOMAIN) + self.assertIsNone(server.application_type) + self.assertIsNone(server.application_version) + + @responses.activate + def test_get_or_create_remote_server_no_links(self): + responses.add( + responses.GET, + 'https://%s/.well-known/nodeinfo' % DOMAIN, + json={'links': [{'href': 'http://www.example.com'}, {}]} + ) + responses.add( + responses.GET, + 'http://www.example.com', + status=404 + ) + + server = models.user.get_or_create_remote_server(DOMAIN) + self.assertEqual(server.server_name, DOMAIN) + self.assertIsNone(server.application_type) + self.assertIsNone(server.application_version) + + @responses.activate + def test_get_or_create_remote_server_unknown_format(self): + responses.add( + responses.GET, + 'https://%s/.well-known/nodeinfo' % DOMAIN, + json={'links': [{'href': 'http://www.example.com'}, {}]} + ) + responses.add( + responses.GET, + 'http://www.example.com', + json={'fish': 'salmon'} + ) + + server = models.user.get_or_create_remote_server(DOMAIN) + self.assertEqual(server.server_name, DOMAIN) + self.assertIsNone(server.application_type) + self.assertIsNone(server.application_version) From 726a8739a36f5927bc9ac0c331fa3a6ebd75342c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 22 Feb 2021 11:42:45 -0800 Subject: [PATCH 0050/1285] way too much logging coming from http errors --- bookwyrm/connectors/abstract_connector.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bookwyrm/connectors/abstract_connector.py b/bookwyrm/connectors/abstract_connector.py index 527d2f42..5ccd9f91 100644 --- a/bookwyrm/connectors/abstract_connector.py +++ b/bookwyrm/connectors/abstract_connector.py @@ -216,11 +216,7 @@ def get_data(url): raise ConnectorException() if not resp.ok: - try: - resp.raise_for_status() - except requests.exceptions.HTTPError as e: - logger.exception(e) - raise ConnectorException() + raise ConnectorException() try: data = resp.json() except ValueError as e: From cbccdea4683359c3b03865fa972a8a0eb6e70e7d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 11:13:20 -0800 Subject: [PATCH 0051/1285] fixes ordered collection serializations --- bookwyrm/models/activitypub_mixin.py | 3 ++- bookwyrm/models/status.py | 2 +- bookwyrm/models/user.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 12bbda96..7ea632b3 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -329,7 +329,8 @@ class OrderedCollectionMixin(OrderedCollectionPageMixin): def to_activity(self, **kwargs): ''' an ordered collection of the specified model queryset ''' - return self.to_ordered_collection(self.collection_queryset, **kwargs).serialize() + return self.to_ordered_collection( + self.collection_queryset, **kwargs).serialize() class CollectionItemMixin(ActivitypubMixin): diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index aff028a5..ba9727f5 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -155,7 +155,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): remote_id='%s/replies' % self.remote_id, collection_only=True, **kwargs - ) + ).serialize() def to_activity_dataclass(self, pure=False):# pylint: disable=arguments-differ ''' return tombstone if the status is deleted ''' diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 61d119a8..a64a8add 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -131,7 +131,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): privacy__in=['public', 'unlisted'], ).select_subclasses().order_by('-published_date') return self.to_ordered_collection(queryset, \ - collection_only=True, remote_id=self.outbox, **kwargs) + collection_only=True, remote_id=self.outbox, **kwargs).serialize() def to_following_activity(self, **kwargs): ''' activitypub following list ''' From 6e09d485c4395a970a3ee28c106973c5dc4658bc Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 11:34:15 -0800 Subject: [PATCH 0052/1285] Outbox sensitive to user agent strings --- bookwyrm/models/activitypub_mixin.py | 6 ++--- bookwyrm/tests/views/test_helpers.py | 6 ++--- bookwyrm/tests/views/test_outbox.py | 37 ++++++++++++++++++++++++++++ bookwyrm/views/feed.py | 4 +-- bookwyrm/views/helpers.py | 4 +-- bookwyrm/views/outbox.py | 7 +++++- 6 files changed, 53 insertions(+), 11 deletions(-) diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 84293725..d934681e 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -282,7 +282,7 @@ class OrderedCollectionPageMixin(ObjectMixin): def to_ordered_collection(self, queryset, \ remote_id=None, page=False, collection_only=False, **kwargs): - ''' an ordered collection of whatevers ''' + 'pure=pure, '' an ordered collection of whatevers ''' if not queryset.ordered: raise RuntimeError('queryset must be ordered') @@ -472,7 +472,7 @@ def sign_and_send(sender, data, destination): # pylint: disable=unused-argument def to_ordered_collection_page( - queryset, remote_id, id_only=False, page=1, **kwargs): + queryset, remote_id, id_only=False, page=1, pure=False, **kwargs): ''' serialize and pagiante a queryset ''' paginated = Paginator(queryset, PAGE_LENGTH) @@ -480,7 +480,7 @@ def to_ordered_collection_page( if id_only: items = [s.remote_id for s in activity_page.object_list] else: - items = [s.to_activity() for s in activity_page.object_list] + items = [s.to_activity(pure=pure) for s in activity_page.object_list] prev_page = next_page = None if activity_page.has_next(): diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index b75d61d5..b3a79b32 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -188,18 +188,18 @@ class ViewsHelpers(TestCase): def test_is_bookwyrm_request(self): ''' checks if a request came from a bookwyrm instance ''' request = self.factory.get('', {'q': 'Test Book'}) - self.assertFalse(views.helpers.is_bookworm_request(request)) + self.assertFalse(views.helpers.is_bookwyrm_request(request)) request = self.factory.get( '', {'q': 'Test Book'}, HTTP_USER_AGENT=\ "http.rb/4.4.1 (Mastodon/3.3.0; +https://mastodon.social/)" ) - self.assertFalse(views.helpers.is_bookworm_request(request)) + self.assertFalse(views.helpers.is_bookwyrm_request(request)) request = self.factory.get( '', {'q': 'Test Book'}, HTTP_USER_AGENT=USER_AGENT) - self.assertTrue(views.helpers.is_bookworm_request(request)) + self.assertTrue(views.helpers.is_bookwyrm_request(request)) def test_existing_user(self): diff --git a/bookwyrm/tests/views/test_outbox.py b/bookwyrm/tests/views/test_outbox.py index d59f028c..7986dea6 100644 --- a/bookwyrm/tests/views/test_outbox.py +++ b/bookwyrm/tests/views/test_outbox.py @@ -7,6 +7,7 @@ from django.test import TestCase from django.test.client import RequestFactory from bookwyrm import models, views +from bookwyrm.settings import USER_AGENT # pylint: disable=too-many-public-methods @@ -90,3 +91,39 @@ class OutboxView(TestCase): data = json.loads(result.content) self.assertEqual(data['type'], 'OrderedCollection') self.assertEqual(data['totalItems'], 1) + + def test_outbox_bookwyrm_request_true(self): + ''' should differentiate between bookwyrm and outside requests ''' + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + models.Review.objects.create( + name='hi', + content='look at this', + user=self.local_user, + book=self.book, + privacy='public', + ) + + request = self.factory.get('', {'page': 1}, HTTP_USER_AGENT=USER_AGENT) + result = views.Outbox.as_view()(request, 'mouse') + + data = json.loads(result.content) + self.assertEqual(len(data['orderedItems']), 1) + self.assertEqual(data['orderedItems'][0]['type'], 'Review') + + def test_outbox_bookwyrm_request_false(self): + ''' should differentiate between bookwyrm and outside requests ''' + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + models.Review.objects.create( + name='hi', + content='look at this', + user=self.local_user, + book=self.book, + privacy='public', + ) + + request = self.factory.get('', {'page': 1}) + result = views.Outbox.as_view()(request, 'mouse') + + data = json.loads(result.content) + self.assertEqual(len(data['orderedItems']), 1) + self.assertEqual(data['orderedItems'][0]['type'], 'Article') diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index 0e550f0c..059237b9 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -13,7 +13,7 @@ from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.settings import PAGE_LENGTH from .helpers import get_activity_feed from .helpers import get_user_from_username -from .helpers import is_api_request, is_bookworm_request, object_visible_to_user +from .helpers import is_api_request, is_bookwyrm_request, object_visible_to_user # pylint: disable= no-self-use @@ -107,7 +107,7 @@ class Status(View): if is_api_request(request): return ActivitypubResponse( - status.to_activity(pure=not is_bookworm_request(request))) + status.to_activity(pure=not is_bookwyrm_request(request))) data = {**feed_page_data(request.user), **{ 'title': 'Status by %s' % user.username, diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 842b8d1c..6eeaa926 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -24,8 +24,8 @@ def is_api_request(request): request.path[-5:] == '.json' -def is_bookworm_request(request): - ''' check if the request is coming from another bookworm instance ''' +def is_bookwyrm_request(request): + ''' check if the request is coming from another bookwyrm instance ''' user_agent = request.headers.get('User-Agent') if user_agent is None or \ re.search(regex.bookwyrm_user_agent, user_agent) is None: diff --git a/bookwyrm/views/outbox.py b/bookwyrm/views/outbox.py index 8bfc3b3d..5df9d199 100644 --- a/bookwyrm/views/outbox.py +++ b/bookwyrm/views/outbox.py @@ -4,6 +4,7 @@ from django.shortcuts import get_object_or_404 from django.views import View from bookwyrm import activitypub, models +from .helpers import is_bookwyrm_request # pylint: disable= no-self-use @@ -17,6 +18,10 @@ class Outbox(View): filter_type = None return JsonResponse( - user.to_outbox(**request.GET, filter_type=filter_type), + user.to_outbox( + **request.GET, + filter_type=filter_type, + pure=not is_bookwyrm_request(request) + ), encoder=activitypub.ActivityEncoder ) From c6a61abf79a2e51dcb4bed9c28245188326655ee Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 11:58:01 -0800 Subject: [PATCH 0053/1285] Don't try to fetch reviews for remote user in test --- bookwyrm/tests/models/test_user_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/tests/models/test_user_model.py b/bookwyrm/tests/models/test_user_model.py index bd184b65..34f4c54d 100644 --- a/bookwyrm/tests/models/test_user_model.py +++ b/bookwyrm/tests/models/test_user_model.py @@ -30,7 +30,7 @@ class User(TestCase): with patch('bookwyrm.models.user.set_remote_server.delay'): user = models.User.objects.create_user( 'rat', 'rat@rat.rat', 'ratword', local=False, - remote_id='https://example.com/dfjkg') + remote_id='https://example.com/dfjkg', bookwyrm_user=False) self.assertEqual(user.username, 'rat@example.com') From a617302006b1de642ca09df5379408c3340dc9bb Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 12:10:54 -0800 Subject: [PATCH 0054/1285] Cleans up display of follow/block/unfollow/unblock buttons --- .../templates/snippets/follow_button.html | 37 ++++++++++++------- bookwyrm/templates/user/user_layout.html | 9 +---- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/bookwyrm/templates/snippets/follow_button.html b/bookwyrm/templates/snippets/follow_button.html index 7db69191..f7286e67 100644 --- a/bookwyrm/templates/snippets/follow_button.html +++ b/bookwyrm/templates/snippets/follow_button.html @@ -5,20 +5,29 @@ Follow request already sent. +{% elif user in request.user.blocks.all %} +{% include 'snippets/block_button.html' %} {% else %} - - +
+
+ + +
+
+ {% include 'snippets/user_options.html' with user=user class="is-small" %} +
+
{% endif %} diff --git a/bookwyrm/templates/user/user_layout.html b/bookwyrm/templates/user/user_layout.html index 979ee0b0..9abc469a 100644 --- a/bookwyrm/templates/user/user_layout.html +++ b/bookwyrm/templates/user/user_layout.html @@ -43,14 +43,7 @@ {% if not is_self and request.user.is_authenticated %} -
-
- {% include 'snippets/follow_button.html' with user=user %} -
-
- {% include 'snippets/user_options.html' with user=user class="is-small" %} -
-
+ {% include 'snippets/follow_button.html' with user=user %} {% endif %} {% if is_self and user.follower_requests.all %} From 364b053d9addf6962e4e7e6d8294790f9d30b64e Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 12:41:37 -0800 Subject: [PATCH 0055/1285] Better user block privacy --- bookwyrm/models/user.py | 10 ++++++++++ bookwyrm/tests/views/test_helpers.py | 8 +++++--- bookwyrm/views/feed.py | 2 +- bookwyrm/views/follow.py | 8 ++++---- bookwyrm/views/helpers.py | 6 +++--- bookwyrm/views/search.py | 2 +- bookwyrm/views/shelf.py | 2 +- bookwyrm/views/user.py | 6 +++--- 8 files changed, 28 insertions(+), 16 deletions(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index a65317fa..108f4345 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -112,6 +112,16 @@ class User(OrderedCollectionPageMixin, AbstractUser): activity_serializer = activitypub.Person + @classmethod + def viewer_aware_objects(cls, viewer): + ''' the user queryset filtered for the context of the logged in user ''' + queryset = cls.objects.filter(is_active=True) + if viewer.is_authenticated: + queryset = queryset.exclude( + blocks=viewer + ) + return queryset + def to_outbox(self, filter_type=None, **kwargs): ''' an ordered collection of statuses ''' if filter_type: diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index b75d61d5..577b45e5 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -56,12 +56,14 @@ class ViewsHelpers(TestCase): def test_get_user_from_username(self): ''' works for either localname or username ''' self.assertEqual( - views.helpers.get_user_from_username('mouse'), self.local_user) + views.helpers.get_user_from_username( + self.local_user, 'mouse'), self.local_user) self.assertEqual( views.helpers.get_user_from_username( - 'mouse@local.com'), self.local_user) + self.local_user, 'mouse@local.com'), self.local_user) with self.assertRaises(models.User.DoesNotExist): - views.helpers.get_user_from_username('mojfse@example.com') + views.helpers.get_user_from_username( + self.local_user, 'mojfse@example.com') def test_is_api_request(self): diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index 0e550f0c..e67a893d 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -65,7 +65,7 @@ class DirectMessage(View): user = None if username: try: - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) except models.User.DoesNotExist: pass if user: diff --git a/bookwyrm/views/follow.py b/bookwyrm/views/follow.py index c59f2e6d..e1b1a0bb 100644 --- a/bookwyrm/views/follow.py +++ b/bookwyrm/views/follow.py @@ -13,7 +13,7 @@ def follow(request): ''' follow another user, here or abroad ''' username = request.POST['user'] try: - to_follow = get_user_from_username(username) + to_follow = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseBadRequest() @@ -33,7 +33,7 @@ def unfollow(request): ''' unfollow a user ''' username = request.POST['user'] try: - to_unfollow = get_user_from_username(username) + to_unfollow = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseBadRequest() @@ -52,7 +52,7 @@ def accept_follow_request(request): ''' a user accepts a follow request ''' username = request.POST['user'] try: - requester = get_user_from_username(username) + requester = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseBadRequest() @@ -75,7 +75,7 @@ def delete_follow_request(request): ''' a user rejects a follow request ''' username = request.POST['user'] try: - requester = get_user_from_username(username) + requester = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseBadRequest() diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 842b8d1c..7e0550fb 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -9,13 +9,13 @@ from bookwyrm.status import create_generated_note from bookwyrm.utils import regex -def get_user_from_username(username): +def get_user_from_username(viewer, username): ''' helper function to resolve a localname or a username to a user ''' # raises DoesNotExist if user is now found try: - return models.User.objects.get(localname=username) + return models.User.viwer_aware_objects(viewer).get(localname=username) except models.User.DoesNotExist: - return models.User.objects.get(username=username) + return models.User.viewer_aware_objects(viewer).get(username=username) def is_api_request(request): diff --git a/bookwyrm/views/search.py b/bookwyrm/views/search.py index a4cd7337..98be166f 100644 --- a/bookwyrm/views/search.py +++ b/bookwyrm/views/search.py @@ -33,7 +33,7 @@ class Search(View): handle_remote_webfinger(query) # do a user search - user_results = models.User.objects.annotate( + user_results = models.User.viewer_aware_objects(request.user).annotate( similarity=Greatest( TrigramSimilarity('username', query), TrigramSimilarity('localname', query), diff --git a/bookwyrm/views/shelf.py b/bookwyrm/views/shelf.py index 02502ff6..70d3d1de 100644 --- a/bookwyrm/views/shelf.py +++ b/bookwyrm/views/shelf.py @@ -19,7 +19,7 @@ class Shelf(View): def get(self, request, username, shelf_identifier): ''' display a shelf ''' try: - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseNotFound() diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py index 4da0fdac..7a238ce7 100644 --- a/bookwyrm/views/user.py +++ b/bookwyrm/views/user.py @@ -26,7 +26,7 @@ class User(View): def get(self, request, username): ''' profile page for a user ''' try: - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseNotFound() @@ -96,7 +96,7 @@ class Followers(View): def get(self, request, username): ''' list of followers ''' try: - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseNotFound() @@ -121,7 +121,7 @@ class Following(View): def get(self, request, username): ''' list of followers ''' try: - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseNotFound() From b1268b7db8032356ef7cfe8db6c5d68053ff58e0 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 12:44:25 -0800 Subject: [PATCH 0056/1285] Small covers too small on mobile --- bookwyrm/static/css/format.css | 3 --- 1 file changed, 3 deletions(-) diff --git a/bookwyrm/static/css/format.css b/bookwyrm/static/css/format.css index 50ce101e..9d4b3105 100644 --- a/bookwyrm/static/css/format.css +++ b/bookwyrm/static/css/format.css @@ -100,9 +100,6 @@ .cover-container.is-medium { height: 100px; } - .cover-container.is-small { - height: 70px; - } } .cover-container.is-medium .no-cover div { From d1a21b851a9072f65d7eae86c137f930ef88d4c2 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 12:46:41 -0800 Subject: [PATCH 0057/1285] Maintain list columns in mobile --- bookwyrm/templates/lists/list.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/templates/lists/list.html b/bookwyrm/templates/lists/list.html index 5f9baa03..c5aef606 100644 --- a/bookwyrm/templates/lists/list.html +++ b/bookwyrm/templates/lists/list.html @@ -19,7 +19,7 @@ {% for item in items %}
  • -
    +
    @@ -73,7 +73,7 @@ {% endif %} {% for book in suggested_books %} {% if book %} -
    +
    From 8a3d1a0bf2db971ce6005855d78c073a12d147d4 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 13:04:24 -0800 Subject: [PATCH 0058/1285] Fixes header wrap on mobile headers --- bookwyrm/templates/author.html | 2 +- bookwyrm/templates/book.html | 2 +- bookwyrm/templates/lists/list_layout.html | 2 +- bookwyrm/templates/lists/lists.html | 2 +- bookwyrm/templates/snippets/shelf.html | 2 ++ bookwyrm/templates/user/followers.html | 18 +++++++----------- bookwyrm/templates/user/following.html | 18 +++++++----------- bookwyrm/templates/user/lists.html | 2 +- bookwyrm/templates/user/shelf.html | 2 +- bookwyrm/templates/user/user.html | 4 ++-- 10 files changed, 24 insertions(+), 30 deletions(-) diff --git a/bookwyrm/templates/author.html b/bookwyrm/templates/author.html index a875ad78..9f2054d0 100644 --- a/bookwyrm/templates/author.html +++ b/bookwyrm/templates/author.html @@ -2,7 +2,7 @@ {% load bookwyrm_tags %} {% block content %}
    -
    +

    {{ author.name }}

    diff --git a/bookwyrm/templates/book.html b/bookwyrm/templates/book.html index 0bef2856..3a2a6aa7 100644 --- a/bookwyrm/templates/book.html +++ b/bookwyrm/templates/book.html @@ -4,7 +4,7 @@ {% block content %}
    -
    +

    {{ book.title }}{% if book.subtitle %}: diff --git a/bookwyrm/templates/lists/list_layout.html b/bookwyrm/templates/lists/list_layout.html index d3ff2c48..f5855f27 100644 --- a/bookwyrm/templates/lists/list_layout.html +++ b/bookwyrm/templates/lists/list_layout.html @@ -2,7 +2,7 @@ {% load bookwyrm_tags %} {% block content %} -
    +

    {{ list.name }} {% include 'snippets/privacy-icons.html' with item=list %}

    Created {% if list.curation != 'open' %} and curated{% endif %} by {% include 'snippets/username.html' with user=list.user %}

    diff --git a/bookwyrm/templates/lists/lists.html b/bookwyrm/templates/lists/lists.html index 71d37250..259d0820 100644 --- a/bookwyrm/templates/lists/lists.html +++ b/bookwyrm/templates/lists/lists.html @@ -5,7 +5,7 @@

    Lists

    {% if request.user.is_authenticated and not lists.has_previous %} -
    +

    Your lists

    diff --git a/bookwyrm/templates/snippets/shelf.html b/bookwyrm/templates/snippets/shelf.html index 006bb4ea..0e4e9a08 100644 --- a/bookwyrm/templates/snippets/shelf.html +++ b/bookwyrm/templates/snippets/shelf.html @@ -1,6 +1,7 @@ {% load humanize %} {% load bookwyrm_tags %} {% if books|length > 0 %} +
    @@ -74,6 +75,7 @@ {% endfor %}
    +
    {% else %}

    This shelf is empty.

    {% if shelf.editable %} diff --git a/bookwyrm/templates/user/followers.html b/bookwyrm/templates/user/followers.html index 42b8cfb0..da6fc320 100644 --- a/bookwyrm/templates/user/followers.html +++ b/bookwyrm/templates/user/followers.html @@ -15,17 +15,13 @@

    Followers

    {% for followers in followers %} -
    -
    -
    - {% include 'snippets/avatar.html' with user=followers %} -
    -
    - {% include 'snippets/username.html' with user=followers show_full=True %} -
    -
    - {% include 'snippets/follow_button.html' with user=followers %} -
    +
    +
    + {% include 'snippets/avatar.html' with user=followers %} + {% include 'snippets/username.html' with user=followers show_full=True %} +
    +
    + {% include 'snippets/follow_button.html' with user=followers %}
    {% endfor %} diff --git a/bookwyrm/templates/user/following.html b/bookwyrm/templates/user/following.html index 9e42b783..d734b0dc 100644 --- a/bookwyrm/templates/user/following.html +++ b/bookwyrm/templates/user/following.html @@ -15,17 +15,13 @@

    Following

    {% for follower in user.following.all %} -
    -
    -
    - {% include 'snippets/avatar.html' with user=follower %} -
    -
    - {% include 'snippets/username.html' with user=follower show_full=True %} -
    -
    - {% include 'snippets/follow_button.html' with user=follower %} -
    +
    +
    + {% include 'snippets/avatar.html' with user=follower %} + {% include 'snippets/username.html' with user=follower show_full=True %} +
    +
    + {% include 'snippets/follow_button.html' with user=follower %}
    {% endfor %} diff --git a/bookwyrm/templates/user/lists.html b/bookwyrm/templates/user/lists.html index a006c92b..45e4806f 100644 --- a/bookwyrm/templates/user/lists.html +++ b/bookwyrm/templates/user/lists.html @@ -1,7 +1,7 @@ {% extends 'user/user_layout.html' %} {% block header %} -
    +

    {% if is_self %}Your diff --git a/bookwyrm/templates/user/shelf.html b/bookwyrm/templates/user/shelf.html index 5e2f532f..e62e2218 100644 --- a/bookwyrm/templates/user/shelf.html +++ b/bookwyrm/templates/user/shelf.html @@ -38,7 +38,7 @@ {% include 'user/create_shelf_form.html' with controls_text='create-shelf-form' %}

    -
    +

    {{ shelf.name }} diff --git a/bookwyrm/templates/user/user.html b/bookwyrm/templates/user/user.html index c7f6d3b5..2ff66468 100644 --- a/bookwyrm/templates/user/user.html +++ b/bookwyrm/templates/user/user.html @@ -1,7 +1,7 @@ {% extends 'user/user_layout.html' %} {% block header %} -
    +

    User profile

    @@ -54,7 +54,7 @@ {% endif %}
    -
    +

    User Activity

    From e6b4212e6b68b2860069b2e369b6f919dcc0c9b7 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 13:05:43 -0800 Subject: [PATCH 0059/1285] Typo fix --- bookwyrm/views/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 7e0550fb..c7eb007f 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -13,7 +13,7 @@ def get_user_from_username(viewer, username): ''' helper function to resolve a localname or a username to a user ''' # raises DoesNotExist if user is now found try: - return models.User.viwer_aware_objects(viewer).get(localname=username) + return models.User.viewer_aware_objects(viewer).get(localname=username) except models.User.DoesNotExist: return models.User.viewer_aware_objects(viewer).get(username=username) From be9198fc4ff7def40d7cd4d5040e8170814a3a43 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 13:09:39 -0800 Subject: [PATCH 0060/1285] Another place where get reviews is called in tests --- bookwyrm/tests/models/test_user_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/tests/models/test_user_model.py b/bookwyrm/tests/models/test_user_model.py index 34f4c54d..84ff3e9b 100644 --- a/bookwyrm/tests/models/test_user_model.py +++ b/bookwyrm/tests/models/test_user_model.py @@ -12,7 +12,7 @@ class User(TestCase): def setUp(self): self.user = models.User.objects.create_user( 'mouse@%s' % DOMAIN, 'mouse@mouse.mouse', 'mouseword', - local=True, localname='mouse', name='hi') + local=True, localname='mouse', name='hi', bookwyrm_user=False) def test_computed_fields(self): ''' username instead of id here ''' From b9f06edc1b16a92af7bc02d526b83900e1a5c593 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 13:12:50 -0800 Subject: [PATCH 0061/1285] Fixes a few missed calls to get_user_from_username --- bookwyrm/views/feed.py | 2 +- bookwyrm/views/goal.py | 4 ++-- bookwyrm/views/list.py | 2 +- bookwyrm/views/rss_feed.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index e67a893d..e7d179b9 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -91,7 +91,7 @@ class Status(View): def get(self, request, username, status_id): ''' display a particular status (and replies, etc) ''' try: - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) status = models.Status.objects.select_subclasses().get( id=status_id, deleted=False) except ValueError: diff --git a/bookwyrm/views/goal.py b/bookwyrm/views/goal.py index 4f2d1b6f..97f13913 100644 --- a/bookwyrm/views/goal.py +++ b/bookwyrm/views/goal.py @@ -18,7 +18,7 @@ class Goal(View): ''' track books for the year ''' def get(self, request, username, year): ''' reading goal page ''' - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) year = int(year) goal = models.AnnualGoal.objects.filter( year=year, user=user @@ -42,7 +42,7 @@ class Goal(View): def post(self, request, username, year): ''' update or create an annual goal ''' - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) if user != request.user: return HttpResponseNotFound() diff --git a/bookwyrm/views/list.py b/bookwyrm/views/list.py index cfdf6d76..7286bfb4 100644 --- a/bookwyrm/views/list.py +++ b/bookwyrm/views/list.py @@ -65,7 +65,7 @@ class UserLists(View): page = int(request.GET.get('page', 1)) except ValueError: page = 1 - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) lists = models.List.objects.filter(user=user).all() lists = privacy_filter( request.user, lists, ['public', 'followers', 'unlisted']) diff --git a/bookwyrm/views/rss_feed.py b/bookwyrm/views/rss_feed.py index 496689ff..aad227bf 100644 --- a/bookwyrm/views/rss_feed.py +++ b/bookwyrm/views/rss_feed.py @@ -11,7 +11,7 @@ class RssFeed(Feed): def get_object(self, request, username): ''' the user who's posts get serialized ''' - return get_user_from_username(username) + return get_user_from_username(request.user, username) def link(self, obj): From ffe5ce725145351c529923e77423f62f659e7610 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 13:23:41 -0800 Subject: [PATCH 0062/1285] User friendly-er add cover form bulma has failed me. Fixes #628 --- bookwyrm/templates/book.html | 24 ++++-------------------- bookwyrm/templates/edit_book.html | 2 +- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/bookwyrm/templates/book.html b/bookwyrm/templates/book.html index 0bef2856..bb1f2be7 100644 --- a/bookwyrm/templates/book.html +++ b/bookwyrm/templates/book.html @@ -42,26 +42,10 @@

    Add cover

    {% csrf_token %} -
    -
    -
    - -
    -
    -
    - -
    -
    + +
    {% endif %} diff --git a/bookwyrm/templates/edit_book.html b/bookwyrm/templates/edit_book.html index 6e7e434e..fb0bb81c 100644 --- a/bookwyrm/templates/edit_book.html +++ b/bookwyrm/templates/edit_book.html @@ -62,7 +62,7 @@

    Cover

    -

    {{ form.cover }}

    +

    {{ form.cover }}

    {% for error in form.cover.errors %}

    {{ error | escape }}

    {% endfor %} From 9ac332f6cc290284416eb892cefc24c897723174 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 13:26:44 -0800 Subject: [PATCH 0063/1285] Adds request user for rss test --- bookwyrm/tests/views/test_rss_feed.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bookwyrm/tests/views/test_rss_feed.py b/bookwyrm/tests/views/test_rss_feed.py index 2b5e415e..3d5bec49 100644 --- a/bookwyrm/tests/views/test_rss_feed.py +++ b/bookwyrm/tests/views/test_rss_feed.py @@ -41,6 +41,7 @@ class RssFeedView(TestCase): ''' load an rss feed ''' view = rss_feed.RssFeed() request = self.factory.get('/user/rss_user/rss') + request.user = self.user with patch("bookwyrm.models.SiteSettings.objects.get") as site: site.return_value = self.site result = view(request, username=self.user.username) From a0b57837a7bfa3b2e73aa9b8208800f64dfd7c53 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 13:34:16 -0800 Subject: [PATCH 0064/1285] Moves status templates into dir --- bookwyrm/templates/book.html | 2 +- bookwyrm/templates/feed/direct_messages.html | 2 +- bookwyrm/templates/feed/feed.html | 2 +- bookwyrm/templates/feed/thread.html | 2 +- bookwyrm/templates/snippets/{ => status}/status.html | 4 ++-- bookwyrm/templates/snippets/{ => status}/status_body.html | 6 +++--- .../templates/snippets/{ => status}/status_content.html | 0 bookwyrm/templates/snippets/{ => status}/status_header.html | 0 .../templates/snippets/{ => status}/status_options.html | 0 bookwyrm/templates/user/user.html | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) rename bookwyrm/templates/snippets/{ => status}/status.html (63%) rename bookwyrm/templates/snippets/{ => status}/status_body.html (89%) rename bookwyrm/templates/snippets/{ => status}/status_content.html (100%) rename bookwyrm/templates/snippets/{ => status}/status_header.html (100%) rename bookwyrm/templates/snippets/{ => status}/status_options.html (100%) diff --git a/bookwyrm/templates/book.html b/bookwyrm/templates/book.html index 0bef2856..4d15a3a5 100644 --- a/bookwyrm/templates/book.html +++ b/bookwyrm/templates/book.html @@ -242,7 +242,7 @@
    {% for review in reviews %}
    - {% include 'snippets/status.html' with status=review hide_book=True depth=1 %} + {% include 'snippets/status/status.html' with status=review hide_book=True depth=1 %}
    {% endfor %} diff --git a/bookwyrm/templates/feed/direct_messages.html b/bookwyrm/templates/feed/direct_messages.html index 8c53cbeb..f3102ee7 100644 --- a/bookwyrm/templates/feed/direct_messages.html +++ b/bookwyrm/templates/feed/direct_messages.html @@ -16,7 +16,7 @@ {% endif %} {% for activity in activities %}
    - {% include 'snippets/status.html' with status=activity %} + {% include 'snippets/status/status.html' with status=activity %}
    {% endfor %} diff --git a/bookwyrm/templates/feed/feed.html b/bookwyrm/templates/feed/feed.html index 8d6152e2..7029fd69 100644 --- a/bookwyrm/templates/feed/feed.html +++ b/bookwyrm/templates/feed/feed.html @@ -32,7 +32,7 @@ {% endif %} {% for activity in activities %}
    -{% include 'snippets/status.html' with status=activity %} +{% include 'snippets/status/status.html' with status=activity %}
    {% endfor %} diff --git a/bookwyrm/templates/feed/thread.html b/bookwyrm/templates/feed/thread.html index aa67d5bb..18ab6ea3 100644 --- a/bookwyrm/templates/feed/thread.html +++ b/bookwyrm/templates/feed/thread.html @@ -8,7 +8,7 @@ {% endwith %} {% endif %} -{% include 'snippets/status.html' with status=status main=is_root %} +{% include 'snippets/status/status.html' with status=status main=is_root %} {% if depth <= max_depth and direction >= 0 %} {% for reply in status|replies %} diff --git a/bookwyrm/templates/snippets/status.html b/bookwyrm/templates/snippets/status/status.html similarity index 63% rename from bookwyrm/templates/snippets/status.html rename to bookwyrm/templates/snippets/status/status.html index 13d5ee7c..162fad97 100644 --- a/bookwyrm/templates/snippets/status.html +++ b/bookwyrm/templates/snippets/status/status.html @@ -4,8 +4,8 @@ {% include 'snippets/avatar.html' with user=status.user %} {% include 'snippets/username.html' with user=status.user %} boosted - {% include 'snippets/status_body.html' with status=status|boosted_status %} + {% include 'snippets/status/status_body.html' with status=status|boosted_status %} {% else %} - {% include 'snippets/status_body.html' with status=status %} + {% include 'snippets/status/status_body.html' with status=status %} {% endif %} {% endif %} diff --git a/bookwyrm/templates/snippets/status_body.html b/bookwyrm/templates/snippets/status/status_body.html similarity index 89% rename from bookwyrm/templates/snippets/status_body.html rename to bookwyrm/templates/snippets/status/status_body.html index 00ae5460..dfc0ac68 100644 --- a/bookwyrm/templates/snippets/status_body.html +++ b/bookwyrm/templates/snippets/status/status_body.html @@ -5,13 +5,13 @@ {% block card-header %}

    - {% include 'snippets/status_header.html' with status=status %} + {% include 'snippets/status/status_header.html' with status=status %}

    {% endblock %} {% block card-content %} - {% include 'snippets/status_content.html' with status=status %} + {% include 'snippets/status/status_content.html' with status=status %} {% endblock %} @@ -55,7 +55,7 @@
    {{ status.published_date | post_date }}
    {% endblock %} diff --git a/bookwyrm/templates/snippets/status_content.html b/bookwyrm/templates/snippets/status/status_content.html similarity index 100% rename from bookwyrm/templates/snippets/status_content.html rename to bookwyrm/templates/snippets/status/status_content.html diff --git a/bookwyrm/templates/snippets/status_header.html b/bookwyrm/templates/snippets/status/status_header.html similarity index 100% rename from bookwyrm/templates/snippets/status_header.html rename to bookwyrm/templates/snippets/status/status_header.html diff --git a/bookwyrm/templates/snippets/status_options.html b/bookwyrm/templates/snippets/status/status_options.html similarity index 100% rename from bookwyrm/templates/snippets/status_options.html rename to bookwyrm/templates/snippets/status/status_options.html diff --git a/bookwyrm/templates/user/user.html b/bookwyrm/templates/user/user.html index c7f6d3b5..e1c19e53 100644 --- a/bookwyrm/templates/user/user.html +++ b/bookwyrm/templates/user/user.html @@ -64,7 +64,7 @@
    {% for activity in activities %}
    - {% include 'snippets/status.html' with status=activity %} + {% include 'snippets/status/status.html' with status=activity %}
    {% endfor %} {% if not activities %} From f9dd0b0246032b769837c1244b8f88ef5467e2c3 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 13:42:20 -0800 Subject: [PATCH 0065/1285] Groups in book preview only used by status templates --- bookwyrm/templates/snippets/{ => status}/book_preview.html | 0 bookwyrm/templates/snippets/status/status_content.html | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename bookwyrm/templates/snippets/{ => status}/book_preview.html (100%) diff --git a/bookwyrm/templates/snippets/book_preview.html b/bookwyrm/templates/snippets/status/book_preview.html similarity index 100% rename from bookwyrm/templates/snippets/book_preview.html rename to bookwyrm/templates/snippets/status/book_preview.html diff --git a/bookwyrm/templates/snippets/status/status_content.html b/bookwyrm/templates/snippets/status/status_content.html index d6dd5ef5..0f59f7fc 100644 --- a/bookwyrm/templates/snippets/status/status_content.html +++ b/bookwyrm/templates/snippets/status/status_content.html @@ -54,9 +54,9 @@ {% if status.book or status.mention_books.count %}
    {% if status.book %} - {% include 'snippets/book_preview.html' with book=status.book %} + {% include 'snippets/status/book_preview.html' with book=status.book %} {% elif status.mention_books.count %} - {% include 'snippets/book_preview.html' with book=status.mention_books.first %} + {% include 'snippets/status/book_preview.html' with book=status.mention_books.first %} {% endif %}
    {% endif %} From d0c46060e82282c9a3f1008a4e6e003c64185026 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 14:00:19 -0800 Subject: [PATCH 0066/1285] Adds stars to book preview in status --- bookwyrm/templates/snippets/stars.html | 2 +- bookwyrm/templates/snippets/status/book_preview.html | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bookwyrm/templates/snippets/stars.html b/bookwyrm/templates/snippets/stars.html index 7d5b63d7..d1576807 100644 --- a/bookwyrm/templates/snippets/stars.html +++ b/bookwyrm/templates/snippets/stars.html @@ -1,7 +1,7 @@

    {% if rating %}{{ rating|floatformat }} star{{ rating|floatformat | pluralize }}{% else %}No rating{% endif %} {% for i in '12345'|make_list %} -

    diff --git a/bookwyrm/templates/snippets/status/book_preview.html b/bookwyrm/templates/snippets/status/book_preview.html index 0c75f9b1..920b9f53 100644 --- a/bookwyrm/templates/snippets/status/book_preview.html +++ b/bookwyrm/templates/snippets/status/book_preview.html @@ -3,6 +3,7 @@
    {% include 'snippets/book_cover.html' with book=book %} + {% include 'snippets/stars.html' with rating=book|rating:request.user %} {% include 'snippets/shelve_button/shelve_button.html' with book=book %}
    From 31c9c07e384e4aa0a14cd73282d2bc760f94eb28 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 14:06:08 -0800 Subject: [PATCH 0067/1285] Makes covers clickable on discover page --- bookwyrm/templates/{ => discover}/discover.html | 12 ++++++------ .../{snippets => }/discover/large-book.html | 2 +- .../{snippets => }/discover/small-book.html | 2 +- bookwyrm/views/landing.py | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) rename bookwyrm/templates/{ => discover}/discover.html (85%) rename bookwyrm/templates/{snippets => }/discover/large-book.html (85%) rename bookwyrm/templates/{snippets => }/discover/small-book.html (79%) diff --git a/bookwyrm/templates/discover.html b/bookwyrm/templates/discover/discover.html similarity index 85% rename from bookwyrm/templates/discover.html rename to bookwyrm/templates/discover/discover.html index 27e26e53..0b6f8a1f 100644 --- a/bookwyrm/templates/discover.html +++ b/bookwyrm/templates/discover/discover.html @@ -63,18 +63,18 @@
    - {% include 'snippets/discover/large-book.html' with book=books.0 %} + {% include 'discover/large-book.html' with book=books.0 %}
    - {% include 'snippets/discover/small-book.html' with book=books.1 %} + {% include 'discover/small-book.html' with book=books.1 %}
    - {% include 'snippets/discover/small-book.html' with book=books.2 %} + {% include 'discover/small-book.html' with book=books.2 %}
    @@ -83,18 +83,18 @@
    - {% include 'snippets/discover/small-book.html' with book=books.3 %} + {% include 'discover/small-book.html' with book=books.3 %}
    - {% include 'snippets/discover/small-book.html' with book=books.4 %} + {% include 'discover/small-book.html' with book=books.4 %}
    - {% include 'snippets/discover/large-book.html' with book=books.5 %} + {% include 'discover/large-book.html' with book=books.5 %}
    diff --git a/bookwyrm/templates/snippets/discover/large-book.html b/bookwyrm/templates/discover/large-book.html similarity index 85% rename from bookwyrm/templates/snippets/discover/large-book.html rename to bookwyrm/templates/discover/large-book.html index 37b35947..401411a1 100644 --- a/bookwyrm/templates/snippets/discover/large-book.html +++ b/bookwyrm/templates/discover/large-book.html @@ -2,7 +2,7 @@ {% if book %}
    - {% include 'snippets/book_cover.html' with book=book size="large" %} + {% include 'snippets/book_cover.html' with book=book size="large" %} {% include 'snippets/stars.html' with rating=ratings|dict_key:book.id %}
    diff --git a/bookwyrm/templates/snippets/discover/small-book.html b/bookwyrm/templates/discover/small-book.html similarity index 79% rename from bookwyrm/templates/snippets/discover/small-book.html rename to bookwyrm/templates/discover/small-book.html index be399df6..d1676b6b 100644 --- a/bookwyrm/templates/snippets/discover/small-book.html +++ b/bookwyrm/templates/discover/small-book.html @@ -1,6 +1,6 @@ {% load bookwyrm_tags %} {% if book %} -{% include 'snippets/book_cover.html' with book=book %} +{% include 'snippets/book_cover.html' with book=book %} {% if ratings %} {% include 'snippets/stars.html' with rating=ratings|dict_key:book.id %} {% endif %} diff --git a/bookwyrm/views/landing.py b/bookwyrm/views/landing.py index 0d841ef0..854f3a9b 100644 --- a/bookwyrm/views/landing.py +++ b/bookwyrm/views/landing.py @@ -56,4 +56,4 @@ class Discover(View): 'books': list(set(books)), 'ratings': ratings } - return TemplateResponse(request, 'discover.html', data) + return TemplateResponse(request, 'discover/discover.html', data) From 1eaff91513e7f3ffa156c2f108d07e06d509b545 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 14:08:52 -0800 Subject: [PATCH 0068/1285] Makes the interactive rating element a different color --- bookwyrm/templates/snippets/rate_action.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/snippets/rate_action.html b/bookwyrm/templates/snippets/rate_action.html index 833f2b88..cc94f675 100644 --- a/bookwyrm/templates/snippets/rate_action.html +++ b/bookwyrm/templates/snippets/rate_action.html @@ -9,7 +9,7 @@ -
    +
    {% for i in '12345'|make_list %} From 3de8a20d39db8ab2036b0352af435090ff90c712 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 14:36:24 -0800 Subject: [PATCH 0069/1285] Fixes boolean for is bookwyrm user in test --- bookwyrm/tests/models/test_user_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/tests/models/test_user_model.py b/bookwyrm/tests/models/test_user_model.py index 84ff3e9b..ed3ad41a 100644 --- a/bookwyrm/tests/models/test_user_model.py +++ b/bookwyrm/tests/models/test_user_model.py @@ -64,7 +64,7 @@ class User(TestCase): self.assertEqual(activity['name'], self.user.name) self.assertEqual(activity['inbox'], self.user.inbox) self.assertEqual(activity['outbox'], self.user.outbox) - self.assertEqual(activity['bookwyrmUser'], True) + self.assertEqual(activity['bookwyrmUser'], False) self.assertEqual(activity['discoverable'], True) self.assertEqual(activity['type'], 'Person') From d3381d7a799d98f40a59c962be5b124d0cc6ecc3 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 14:41:33 -0800 Subject: [PATCH 0070/1285] Paginates invite page I sure have sent out a lot of invites --- bookwyrm/templates/settings/manage_invites.html | 1 + bookwyrm/views/invite.py | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/bookwyrm/templates/settings/manage_invites.html b/bookwyrm/templates/settings/manage_invites.html index 03b68b20..42668a3d 100644 --- a/bookwyrm/templates/settings/manage_invites.html +++ b/bookwyrm/templates/settings/manage_invites.html @@ -22,6 +22,7 @@ {% endfor %} + {% include 'snippets/pagination.html' with page=invites path=request.path %}
    diff --git a/bookwyrm/views/invite.py b/bookwyrm/views/invite.py index bd0715a0..ed7b1f5f 100644 --- a/bookwyrm/views/invite.py +++ b/bookwyrm/views/invite.py @@ -1,5 +1,6 @@ ''' invites when registration is closed ''' from django.contrib.auth.decorators import login_required, permission_required +from django.core.paginator import Paginator from django.http import HttpResponseBadRequest from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse @@ -7,6 +8,7 @@ from django.utils.decorators import method_decorator from django.views import View from bookwyrm import forms, models +from bookwyrm.settings import PAGE_LENGTH # pylint: disable= no-self-use @@ -18,10 +20,18 @@ class ManageInvites(View): ''' create invites ''' def get(self, request): ''' invite management page ''' + try: + page = int(request.GET.get('page', 1)) + except ValueError: + page = 1 + + paginated = Paginator(models.SiteInvite.objects.filter( + user=request.user + ).order_by('-created_date'), PAGE_LENGTH) + data = { 'title': 'Invitations', - 'invites': models.SiteInvite.objects.filter( - user=request.user).order_by('-created_date'), + 'invites': paginated.page(page), 'form': forms.CreateInviteForm(), } return TemplateResponse(request, 'settings/manage_invites.html', data) From baed291889eb3672c6c503b7b023e28a72ca26ce Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 14:45:39 -0800 Subject: [PATCH 0071/1285] Don't broadcast after saving remote server --- bookwyrm/models/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index a1f927b2..094a13d5 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -319,7 +319,7 @@ def set_remote_server(user_id): actor_parts = urlparse(user.remote_id) user.federated_server = \ get_or_create_remote_server(actor_parts.netloc) - user.save() + user.save(broadcast=False) if user.bookwyrm_user: get_remote_reviews.delay(user.outbox) From 384187a263c649c4b563b1d422dc09e5c0f07ede Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 15:21:37 -0800 Subject: [PATCH 0072/1285] Moves create invite form to top of invite page --- .../templates/settings/manage_invites.html | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/bookwyrm/templates/settings/manage_invites.html b/bookwyrm/templates/settings/manage_invites.html index 42668a3d..086615a9 100644 --- a/bookwyrm/templates/settings/manage_invites.html +++ b/bookwyrm/templates/settings/manage_invites.html @@ -2,29 +2,6 @@ {% block header %}Invites{% endblock %} {% load humanize %} {% block panel %} -
    - - - - - - - - {% if not invites %} - - {% endif %} - {% for invite in invites %} - - - - - - - {% endfor %} -
    LinkExpiresMax usesTimes used
    No active invites
    {{ invite.link }}{{ invite.expiry|naturaltime }}{{ invite.use_limit }}{{ invite.times_used }}
    - {% include 'snippets/pagination.html' with page=invites path=request.path %} -
    -

    Generate New Invite

    @@ -48,4 +25,27 @@
    + +
    + + + + + + + + {% if not invites %} + + {% endif %} + {% for invite in invites %} + + + + + + + {% endfor %} +
    LinkExpiresMax usesTimes used
    No active invites
    {{ invite.link }}{{ invite.expiry|naturaltime }}{{ invite.use_limit }}{{ invite.times_used }}
    + {% include 'snippets/pagination.html' with page=invites path=request.path %} +
    {% endblock %} From 23fb5f62a2e58d4869b1ab79aa72f105dda305f7 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 15:25:26 -0800 Subject: [PATCH 0073/1285] Keep invite settings in form after save --- bookwyrm/views/invite.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bookwyrm/views/invite.py b/bookwyrm/views/invite.py index ed7b1f5f..6b3611fc 100644 --- a/bookwyrm/views/invite.py +++ b/bookwyrm/views/invite.py @@ -46,7 +46,15 @@ class ManageInvites(View): invite.user = request.user invite.save() - return redirect('/settings/invites') + paginated = Paginator(models.SiteInvite.objects.filter( + user=request.user + ).order_by('-created_date'), PAGE_LENGTH) + data = { + 'title': 'Invitations', + 'invites': paginated.page(1), + 'form': form + } + return TemplateResponse(request, 'settings/manage_invites.html', data) class Invite(View): From 6e6bcb2f4833e27d4c626d9d6d7c73c2616db5ff Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 15:51:02 -0800 Subject: [PATCH 0074/1285] gotta simplify the add activity --- bookwyrm/activitypub/__init__.py | 2 +- bookwyrm/activitypub/verbs.py | 11 ++--- bookwyrm/models/list.py | 2 +- bookwyrm/tests/views/test_inbox.py | 75 ++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 10 deletions(-) diff --git a/bookwyrm/activitypub/__init__.py b/bookwyrm/activitypub/__init__.py index f06154ab..fdfbb1f0 100644 --- a/bookwyrm/activitypub/__init__.py +++ b/bookwyrm/activitypub/__init__.py @@ -15,7 +15,7 @@ from .response import ActivitypubResponse from .book import Edition, Work, Author from .verbs import Create, Delete, Undo, Update from .verbs import Follow, Accept, Reject, Block -from .verbs import Add, AddListItem, Remove +from .verbs import Add, Remove from .verbs import Announce, Like # this creates a list of all the Activity types that we can serialize, diff --git a/bookwyrm/activitypub/verbs.py b/bookwyrm/activitypub/verbs.py index 6f1a4d44..1236338b 100644 --- a/bookwyrm/activitypub/verbs.py +++ b/bookwyrm/activitypub/verbs.py @@ -121,6 +121,9 @@ class Add(Verb): target: str object: Edition type: str = 'Add' + notes: str = None + order: int = 0 + approved: bool = True def action(self): ''' add obj to collection ''' @@ -131,14 +134,6 @@ class Add(Verb): self.to_model(model=model) -@dataclass(init=False) -class AddListItem(Add): - '''Add activity that's aware of the book obj ''' - notes: str = None - order: int = 0 - approved: bool = True - - @dataclass(init=False) class Remove(Verb): '''Remove activity ''' diff --git a/bookwyrm/models/list.py b/bookwyrm/models/list.py index ef48ed95..1b14c2aa 100644 --- a/bookwyrm/models/list.py +++ b/bookwyrm/models/list.py @@ -68,7 +68,7 @@ class ListItem(CollectionItemMixin, BookWyrmModel): order = fields.IntegerField(blank=True, null=True) endorsement = models.ManyToManyField('User', related_name='endorsers') - activity_serializer = activitypub.AddListItem + activity_serializer = activitypub.Add object_field = 'book' collection_field = 'book_list' diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index c2658917..45966d59 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -612,6 +612,81 @@ class Inbox(TestCase): self.assertEqual(shelf.books.first(), book) +# def test_handle_tag_book(self): +# ''' tagging a book ''' +# work = models.Work.objects.create(title='work title') +# book = models.Edition.objects.create( +# title='Test', remote_id='https://bookwyrm.social/book/37292', +# parent_work=work) +# +# activity = { +# "id": "https://bookwyrm.social/shelfbook/6189#add", +# "type": "Add", +# "actor": "https://example.com/users/rat", +# "object": { +# "type": "Edition", +# "title": "Test Title", +# "work": work.remote_id, +# "id": "https://bookwyrm.social/book/37292", +# }, +# "target": "", +# "@context": "https://www.w3.org/ns/activitystreams" +# } +# views.inbox.activity_task(activity) +# self.assertEqual(shelf.books.first(), book) + + + @responses.activate + def test_handle_add_book_to_list(self): + ''' listing a book ''' + work = models.Work.objects.create(title='work title') + book = models.Edition.objects.create( + title='Test', remote_id='https://bookwyrm.social/book/37292', + parent_work=work) + + responses.add( + responses.GET, + 'https://bookwyrm.social/user/mouse/list/to-read', + json={ + "id": "https://example.com/list/22", + "type": "BookList", + "totalItems": 1, + "first": "https://example.com/list/22?page=1", + "last": "https://example.com/list/22?page=1", + "name": "Test List", + "owner": "https://example.com/user/mouse", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://example.com/user/mouse/followers" + ], + "summary": "summary text", + "curation": "curated", + "@context": "https://www.w3.org/ns/activitystreams" + } + ) + + activity = { + "id": "https://bookwyrm.social/listbook/6189#add", + "type": "Add", + "actor": "https://example.com/users/rat", + "object": { + "type": "Edition", + "title": "Test Title", + "work": work.remote_id, + "id": "https://bookwyrm.social/book/37292", + }, + "target": "https://bookwyrm.social/user/mouse/list/to-read", + "@context": "https://www.w3.org/ns/activitystreams" + } + views.inbox.activity_task(activity) + + booklist = models.List.objects.get() + self.assertEqual(booklist.name, 'Test List') + self.assertEqual(booklist.books.first(), book) + + def test_handle_update_user(self): ''' update an existing user ''' # we only do this with remote users From 4d0e52bf517a53d46c070a0d82b4efa89ced70a7 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 17:18:25 -0800 Subject: [PATCH 0075/1285] Test tag and list add --- bookwyrm/activitypub/base_activity.py | 3 +- bookwyrm/models/activitypub_mixin.py | 2 +- bookwyrm/models/tag.py | 19 ++++---- bookwyrm/tests/views/test_inbox.py | 67 +++++++++++++++++---------- bookwyrm/tests/views/test_tag.py | 15 ++++++ bookwyrm/views/tag.py | 7 ++- 6 files changed, 73 insertions(+), 40 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index c360711d..57f1a713 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -82,7 +82,8 @@ class ActivityObject: if activity_objects: value = naive_parse(activity_objects, value) else: - value = naive_parse(activity_objects, value, serializer=field.type) + value = naive_parse( + activity_objects, value, serializer=field.type) except KeyError: if field.default == MISSING and \ diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 7ea632b3..fe89f267 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -330,7 +330,7 @@ class OrderedCollectionMixin(OrderedCollectionPageMixin): def to_activity(self, **kwargs): ''' an ordered collection of the specified model queryset ''' return self.to_ordered_collection( - self.collection_queryset, **kwargs).serialize() + self.collection_queryset, **kwargs) class CollectionItemMixin(ActivitypubMixin): diff --git a/bookwyrm/models/tag.py b/bookwyrm/models/tag.py index 12645b9e..83359170 100644 --- a/bookwyrm/models/tag.py +++ b/bookwyrm/models/tag.py @@ -1,6 +1,7 @@ ''' models for storing different kinds of Activities ''' import urllib.parse +from django.apps import apps from django.db import models from bookwyrm import activitypub @@ -15,17 +16,15 @@ class Tag(OrderedCollectionMixin, BookWyrmModel): name = fields.CharField(max_length=100, unique=True) identifier = models.CharField(max_length=100) - @classmethod - def book_queryset(cls, identifier): - ''' county of books associated with this tag ''' - return cls.objects.filter( - identifier=identifier - ).order_by('-updated_date') - @property - def collection_queryset(self): - ''' books associated with this tag ''' - return self.book_queryset(self.identifier) + def books(self): + ''' count of books associated with this tag ''' + edition_model = apps.get_model('bookwyrm.Edition', require_ready=True) + return edition_model.objects.filter( + usertag__tag__identifier=self.identifier + ).order_by('-created_date').distinct() + + collection_queryset = books def get_remote_id(self): ''' tag should use identifier not id in remote_id ''' diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 45966d59..ff55ad04 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -612,30 +612,6 @@ class Inbox(TestCase): self.assertEqual(shelf.books.first(), book) -# def test_handle_tag_book(self): -# ''' tagging a book ''' -# work = models.Work.objects.create(title='work title') -# book = models.Edition.objects.create( -# title='Test', remote_id='https://bookwyrm.social/book/37292', -# parent_work=work) -# -# activity = { -# "id": "https://bookwyrm.social/shelfbook/6189#add", -# "type": "Add", -# "actor": "https://example.com/users/rat", -# "object": { -# "type": "Edition", -# "title": "Test Title", -# "work": work.remote_id, -# "id": "https://bookwyrm.social/book/37292", -# }, -# "target": "", -# "@context": "https://www.w3.org/ns/activitystreams" -# } -# views.inbox.activity_task(activity) -# self.assertEqual(shelf.books.first(), book) - - @responses.activate def test_handle_add_book_to_list(self): ''' listing a book ''' @@ -687,6 +663,49 @@ class Inbox(TestCase): self.assertEqual(booklist.books.first(), book) + @responses.activate + def test_handle_tag_book(self): + ''' listing a book ''' + work = models.Work.objects.create(title='work title') + book = models.Edition.objects.create( + title='Test', remote_id='https://bookwyrm.social/book/37292', + parent_work=work) + + responses.add( + responses.GET, + 'https://www.example.com/tag/cool-tag', + json={ + "id": "https://1b1a78582461.ngrok.io/tag/tag", + "type": "OrderedCollection", + "totalItems": 0, + "first": "https://1b1a78582461.ngrok.io/tag/tag?page=1", + "last": "https://1b1a78582461.ngrok.io/tag/tag?page=1", + "name": "cool tag", + "@context": "https://www.w3.org/ns/activitystreams" + } + ) + + activity = { + "id": "https://bookwyrm.social/listbook/6189#add", + "type": "Add", + "actor": "https://example.com/users/rat", + "object": { + "type": "Edition", + "title": "Test Title", + "work": work.remote_id, + "id": "https://bookwyrm.social/book/37292", + }, + "target": "https://www.example.com/tag/cool-tag", + "@context": "https://www.w3.org/ns/activitystreams" + } + views.inbox.activity_task(activity) + + tag = models.Tag.objects.get() + self.assertFalse(models.List.objects.exists()) + self.assertEqual(tag.name, 'cool tag') + self.assertEqual(tag.books.first(), book) + + def test_handle_update_user(self): ''' update an existing user ''' # we only do this with remote users diff --git a/bookwyrm/tests/views/test_tag.py b/bookwyrm/tests/views/test_tag.py index 21a7e22e..ef809b46 100644 --- a/bookwyrm/tests/views/test_tag.py +++ b/bookwyrm/tests/views/test_tag.py @@ -59,6 +59,21 @@ class TagViews(TestCase): self.assertEqual(result.status_code, 200) + def test_tag_page_activitypub_page(self): + ''' there are so many views, this just makes sure it LOADS ''' + view = views.Tag.as_view() + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + tag = models.Tag.objects.create(name='hi there') + models.UserTag.objects.create( + tag=tag, user=self.local_user, book=self.book) + request = self.factory.get('', {'page': 1}) + with patch('bookwyrm.views.tag.is_api_request') as is_api: + is_api.return_value = True + result = view(request, tag.identifier) + self.assertIsInstance(result, ActivitypubResponse) + self.assertEqual(result.status_code, 200) + + def test_tag(self): ''' add a tag to a book ''' view = views.AddTag.as_view() diff --git a/bookwyrm/views/tag.py b/bookwyrm/views/tag.py index b50bc0ef..710f9415 100644 --- a/bookwyrm/views/tag.py +++ b/bookwyrm/views/tag.py @@ -16,12 +16,11 @@ class Tag(View): ''' tag page ''' def get(self, request, tag_id): ''' see books related to a tag ''' - tag_obj = models.Tag.objects.filter(identifier=tag_id).first() - if not tag_obj: - return HttpResponseNotFound() + tag_obj = get_object_or_404(models.Tag, identifier=tag_id) if is_api_request(request): - return ActivitypubResponse(tag_obj.to_activity(**request.GET)) + return ActivitypubResponse( + tag_obj.to_activity(**request.GET), safe=False) books = models.Edition.objects.filter( usertag__tag__identifier=tag_id From fba53c72e0d1ff2c2efc12967696386a7880b96d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 17:19:47 -0800 Subject: [PATCH 0076/1285] default safe mode for activity serialization --- bookwyrm/activitypub/response.py | 2 +- bookwyrm/views/tag.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bookwyrm/activitypub/response.py b/bookwyrm/activitypub/response.py index bbc44c4d..8f3c050b 100644 --- a/bookwyrm/activitypub/response.py +++ b/bookwyrm/activitypub/response.py @@ -9,7 +9,7 @@ class ActivitypubResponse(JsonResponse): configures some stuff beforehand. Made to be a drop-in replacement of JsonResponse. """ - def __init__(self, data, encoder=ActivityEncoder, safe=True, + def __init__(self, data, encoder=ActivityEncoder, safe=False, json_dumps_params=None, **kwargs): if 'content_type' not in kwargs: diff --git a/bookwyrm/views/tag.py b/bookwyrm/views/tag.py index 710f9415..502f5ea5 100644 --- a/bookwyrm/views/tag.py +++ b/bookwyrm/views/tag.py @@ -1,6 +1,5 @@ ''' tagging views''' from django.contrib.auth.decorators import login_required -from django.http import HttpResponseNotFound from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse from django.utils.decorators import method_decorator @@ -20,7 +19,7 @@ class Tag(View): if is_api_request(request): return ActivitypubResponse( - tag_obj.to_activity(**request.GET), safe=False) + tag_obj.to_activity(**request.GET)) books = models.Edition.objects.filter( usertag__tag__identifier=tag_id From 744de313c8ab2febf33a060a343726365b583467 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 17:23:11 -0800 Subject: [PATCH 0077/1285] Makes comment and fav/boost buttons the same color when selected --- bookwyrm/templates/snippets/boost_button.html | 2 +- bookwyrm/templates/snippets/fav_button.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/templates/snippets/boost_button.html b/bookwyrm/templates/snippets/boost_button.html index 08daae64..bf914379 100644 --- a/bookwyrm/templates/snippets/boost_button.html +++ b/bookwyrm/templates/snippets/boost_button.html @@ -11,7 +11,7 @@
    {% csrf_token %} -
    +
    {% endif %} @@ -54,21 +55,21 @@
    {% if book.isbn_13 %}
    -
    ISBN:
    +
    {% trans "ISBN:" %}
    {{ book.isbn_13 }}
    {% endif %} {% if book.oclc_number %}
    -
    OCLC Number:
    +
    {% trans "OCLC Number:" %}
    {{ book.oclc_number }}
    {% endif %} {% if book.asin %}
    -
    ASIN:
    +
    {% trans "ASIN:" %}
    {{ book.asin }}
    {% endif %} @@ -80,7 +81,7 @@

    {% if book.openlibrary_key %} -

    View on OpenLibrary

    +

    {% trans "View on OpenLibrary" %}

    {% endif %}
    @@ -98,11 +99,11 @@
    {% csrf_token %}

    - +

    - + {% include 'snippets/toggle/close_button.html' with text="Cancel" controls_text="add-description" controls_uid=book.id hide_inactive=True %}
    @@ -134,20 +135,20 @@ {% if request.user.is_authenticated %}
    -

    Your reading activity

    +

    {% trans "Your reading activity" %}

    {% include 'snippets/toggle/open_button.html' with text="Add read dates" icon="plus" class="is-small" controls_text="add-readthrough" %}
    {% if not readthroughs.exists %} -

    You don't have any reading activity for this book.

    +

    {% trans "You don't have any reading activity for this book." %}

    {% endif %}

    -

    Set a goal for how many books you'll finish reading in {{ year }}, and track your progress throughout the year.

    +

    {% blocktrans %}Set a goal for how many books you'll finish reading in {{ year }}, and track your progress throughout the year.{% endblocktrans %}

    {% include 'snippets/goal_form.html' with goal=goal year=year %}
    diff --git a/bookwyrm/templates/import.html b/bookwyrm/templates/import.html index a7fde76d..ee1a90a4 100644 --- a/bookwyrm/templates/import.html +++ b/bookwyrm/templates/import.html @@ -2,12 +2,12 @@ {% load humanize %} {% block content %}
    -

    Import Books

    +

    {% trans "Import Books" %}

    {% csrf_token %}
    -

    Recent Imports

    +

    {% trans "Recent Imports" %}

    {% if not jobs %} -

    No recent imports

    +

    {% trans "No recent imports" %}

    {% endif %}
      {% for job in jobs %} diff --git a/bookwyrm/templates/import_status.html b/bookwyrm/templates/import_status.html index 66b3fb67..4c3d46d3 100644 --- a/bookwyrm/templates/import_status.html +++ b/bookwyrm/templates/import_status.html @@ -3,32 +3,32 @@ {% load humanize %} {% block content %}
      -

      Import Status

      +

      {% trans "Import Status" %}

      - Import started: {{ job.created_date | naturaltime }} + {% trans "Import started: " %}{{ job.created_date | naturaltime }}

      {% if job.complete %}

      - Import completed: {{ task.date_done | naturaltime }} + {% trans "Import completed: " %}{{ task.date_done | naturaltime }}

      {% elif task.failed %} -
      TASK FAILED
      +
      {% trans "TASK FAILED" %}
      {% endif %}
      {% if not job.complete %} - Import still in progress. + {% trans "Import still in progress." %}

      - (Hit reload to update!) + {% trans "(Hit reload to update!)" %}

      {% endif %}
      {% if failed_items %}
      -

      Failed to load

      +

      {% trans "Failed to load" %}

      {% if not job.retry %}
      {% csrf_token %} @@ -52,10 +52,10 @@
      - + {% else %}
        {% for item in failed_items %} @@ -77,17 +77,17 @@ {% endif %}
        -

        Successfully imported

        +

        {% trans "Successfully imported" %}

        @@ -110,7 +110,7 @@ diff --git a/bookwyrm/templates/invite.html b/bookwyrm/templates/invite.html index 3345424c..b01afec3 100644 --- a/bookwyrm/templates/invite.html +++ b/bookwyrm/templates/invite.html @@ -5,7 +5,7 @@
        {% if valid %} -

        Create an Account

        +

        {% trans "Create an Account" %}

        @@ -14,8 +14,8 @@
        {% else %}
        -

        Permission Denied

        -

        Sorry! This invite code is no longer valid.

        +

        {% trans "Permission Denied" %}

        +

        {% trans "Sorry! This invite code is no longer valid." %}

        {% endif %}
        diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index fe8a7509..5f75e122 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -34,7 +34,7 @@
        @@ -44,7 +44,7 @@ @@ -122,7 +122,7 @@ - Notifications + {% trans "Notifications" %} @@ -139,16 +139,16 @@ {% csrf_token %} @@ -179,11 +179,11 @@
        diff --git a/bookwyrm/templates/login.html b/bookwyrm/templates/login.html index 68342210..104e8ef7 100644 --- a/bookwyrm/templates/login.html +++ b/bookwyrm/templates/login.html @@ -4,20 +4,20 @@
        -

        Log in

        +

        {% trans "Log in" %}

        {% if login_form.non_field_errors %}

        {{ login_form.non_field_errors }}

        {% endif %}
        {% csrf_token %}
        - +
        {{ login_form.localname }}
        - +
        {{ login_form.password }}
        @@ -27,23 +27,23 @@
        - +
        {% if site.allow_registration %} -

        Create an Account

        +

        {% trans "Create an Account" %}

        {% include 'snippets/register_form.html' %} {% else %} -

        This instance is closed

        -

        Contact an administrator to get an invite

        +

        {% trans "This instance is closed" %}

        +

        {% trans "Contact an administrator to get an invite" %}

        {% endif %}
        @@ -53,7 +53,7 @@ {% include 'snippets/about.html' %}

        - More about this site + {% trans "More about this site" %}

        diff --git a/bookwyrm/templates/notifications.html b/bookwyrm/templates/notifications.html index 8e7412a2..10f07da0 100644 --- a/bookwyrm/templates/notifications.html +++ b/bookwyrm/templates/notifications.html @@ -3,11 +3,11 @@ {% load bookwyrm_tags %} {% block content %}
        -

        Notifications

        +

        {% trans "Notifications" %}

        {% csrf_token %} - +
        @@ -98,7 +98,7 @@ {% endfor %} {% if not notifications %} -

        You're all caught up!

        +

        {% trans "You're all caught up!" %}

        {% endif %}
        {% endblock %} diff --git a/bookwyrm/templates/password_reset_request.html b/bookwyrm/templates/password_reset_request.html index b491a6ac..23eff657 100644 --- a/bookwyrm/templates/password_reset_request.html +++ b/bookwyrm/templates/password_reset_request.html @@ -4,20 +4,20 @@
        -

        Reset Password

        +

        {% trans "Reset Password" %}

        {% if message %}

        {{ message }}

        {% endif %} -

        A link to reset your password will be sent to your email address

        +

        {% trans "A link to reset your password will be sent to your email address" %}

        {% csrf_token %}
        - +
        - +
        diff --git a/bookwyrm/templates/search_results.html b/bookwyrm/templates/search_results.html index 34328bbd..8bd8253d 100644 --- a/bookwyrm/templates/search_results.html +++ b/bookwyrm/templates/search_results.html @@ -7,10 +7,10 @@
        -

        Matching Books

        +

        {% trans "Matching Books" %}

        {% if not local_results.results %} -

        No books found for "{{ query }}"

        +

        {% blocktrans %}No books found for "{{ query }}"{% endblocktrans %}

        {% else %}
          {% for result in local_results.results %} @@ -26,7 +26,7 @@ {% if book_results|slice:":1" and local_results.results %}

          - Didn't find what you were looking for? + {% trans "Didn't find what you were looking for?" %}

          {% include 'snippets/toggle/open_button.html' with text="Show results from other catalogues" small=True controls_text="more-results" %}
          @@ -49,7 +49,7 @@ {% csrf_token %}
          {% include 'snippets/search_result_text.html' with result=result link=False %}
          - + {% endfor %} @@ -66,9 +66,9 @@
        -

        Matching Users

        +

        {% trans "Matching Users" %}

        {% if not user_results %} -

        No users found for "{{ query }}"

        +

        {% blocktrans %}No users found for "{{ query }}"{% endblocktrans %}

        {% endif %}
          {% for result in user_results %} @@ -81,9 +81,9 @@
        -

        Lists

        +

        {% trans "Lists" %}

        {% if not list_results %} -

        No lists found for "{{ query }}"

        +

        {% blocktrans %}No lists found for "{{ query }}"{% endblocktrans %}

        {% endif %} {% for result in list_results %}
        diff --git a/bookwyrm/templates/tag.html b/bookwyrm/templates/tag.html index 7eea0c21..96fbcf91 100644 --- a/bookwyrm/templates/tag.html +++ b/bookwyrm/templates/tag.html @@ -3,7 +3,7 @@ {% block content %}
        -

        Books tagged "{{ tag.name }}"

        +

        {% blocktrans %}Books tagged "{{ tag.name }}"{% endblocktrans %}

        {% include 'snippets/book_tiles.html' with books=books.all %}
        From b2e431daed21283573d50eadf0c1915113c20fd5 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 27 Feb 2021 15:00:19 -0800 Subject: [PATCH 0105/1285] Adds test localization file --- .../{en => en-US}/LC_MESSAGES/django.po | 20 +- bookwyrm/locale/en-beep/LC_MESSAGES/django.po | 417 ++++++++++++++++++ bookwyrm/settings.py | 5 + bookwyrm/templates/layout.html | 1 + 4 files changed, 433 insertions(+), 10 deletions(-) rename bookwyrm/locale/{en => en-US}/LC_MESSAGES/django.po (94%) create mode 100644 bookwyrm/locale/en-beep/LC_MESSAGES/django.po diff --git a/bookwyrm/locale/en/LC_MESSAGES/django.po b/bookwyrm/locale/en-US/LC_MESSAGES/django.po similarity index 94% rename from bookwyrm/locale/en/LC_MESSAGES/django.po rename to bookwyrm/locale/en-US/LC_MESSAGES/django.po index 344c301f..9ecbea8c 100644 --- a/bookwyrm/locale/en/LC_MESSAGES/django.po +++ b/bookwyrm/locale/en-US/LC_MESSAGES/django.po @@ -1,5 +1,5 @@ # English language text for the bookwyrm UI -# Copyright (C) Mouse Reeve +# Copyright (C) 2021 Mouse Reeve # This file is distributed under the same license as the bookwyrm package. # Mouse Reeve , 2021 # @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.1.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-02-27 14:21-0800\n" +"POT-Creation-Date: 2021-02-27 14:59-0800\n" "PO-Revision-Date: 2021-02-27 13:50+PST\n" "Last-Translator: Mouse Reeve \n" "Language-Team: Mouse Reeve \n" @@ -98,7 +98,7 @@ msgstr "" #: bookwyrm/templates/book.html:215 bookwyrm/templates/search_results.html:84 #: bookwyrm/templates/user/user_layout.html:60 msgid "Lists" -msgstr "" +msgstr "lirsts" #: bookwyrm/templates/book.html:244 msgid "rated it" @@ -290,33 +290,33 @@ msgstr "" msgid "Sorry! This invite code is no longer valid." msgstr "" -#: bookwyrm/templates/layout.html:37 +#: bookwyrm/templates/layout.html:38 msgid "search" msgstr "" -#: bookwyrm/templates/layout.html:47 +#: bookwyrm/templates/layout.html:48 msgid "Main navigation menu" msgstr "" -#: bookwyrm/templates/layout.html:125 bookwyrm/templates/notifications.html:6 +#: bookwyrm/templates/layout.html:126 bookwyrm/templates/notifications.html:6 msgid "Notifications" msgstr "" -#: bookwyrm/templates/layout.html:142 bookwyrm/templates/layout.html:146 +#: bookwyrm/templates/layout.html:143 bookwyrm/templates/layout.html:147 #: bookwyrm/templates/login.html:14 msgid "Username:" msgstr "" -#: bookwyrm/templates/layout.html:151 bookwyrm/templates/login.html:7 +#: bookwyrm/templates/layout.html:152 bookwyrm/templates/login.html:7 #: bookwyrm/templates/login.html:30 msgid "Log in" msgstr "" -#: bookwyrm/templates/layout.html:182 +#: bookwyrm/templates/layout.html:183 msgid "About this server" msgstr "" -#: bookwyrm/templates/layout.html:186 +#: bookwyrm/templates/layout.html:187 msgid "Contact site admin" msgstr "" diff --git a/bookwyrm/locale/en-beep/LC_MESSAGES/django.po b/bookwyrm/locale/en-beep/LC_MESSAGES/django.po new file mode 100644 index 00000000..3a450b65 --- /dev/null +++ b/bookwyrm/locale/en-beep/LC_MESSAGES/django.po @@ -0,0 +1,417 @@ +# A test translation file that just uses the word "beep" +# Copyright (C) 2021 Mouse Reeve +# This file is distributed under the same license as the BookWyrm package. +# Mouse Reeve \n" +"Language-Team: Mouse Reeve \n" +"Language: Beep\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: bookwyrm/templates/author.html:13 bookwyrm/templates/author.html:14 +msgid "Edit Author" +msgstr "Edit Beep" + +#: bookwyrm/templates/author.html:29 +msgid "Wikipedia" +msgstr "Beep" + +#: bookwyrm/templates/author.html:34 +#, python-format +msgid "Books by %(name)s" +msgstr "Beep by %(name)s" + +#: bookwyrm/templates/book.html:27 bookwyrm/templates/book.html:28 +msgid "Edit Book" +msgstr "Edit Beep" + +#: bookwyrm/templates/book.html:43 +msgid "Add cover" +msgstr "Beep beep" + +#: bookwyrm/templates/book.html:49 +msgid "Add" +msgstr "Beep" + +#: bookwyrm/templates/book.html:58 +msgid "ISBN:" +msgstr "ISBN:" + +#: bookwyrm/templates/book.html:65 +msgid "OCLC Number:" +msgstr "OCLC Beep:" + +#: bookwyrm/templates/book.html:72 +msgid "ASIN:" +msgstr "ASIN:" + +#: bookwyrm/templates/book.html:84 +msgid "View on OpenLibrary" +msgstr "View beep OpenBeep" + +#: bookwyrm/templates/book.html:102 +msgid "Description:" +msgstr "Beep:" + +#: bookwyrm/templates/book.html:106 bookwyrm/templates/edit_author.html:74 +msgid "Save" +msgstr "Beep" + +#: bookwyrm/templates/book.html:138 +msgid "Your reading activity" +msgstr "Beep reading beep" + +#: bookwyrm/templates/book.html:144 +msgid "You don't have any reading activity for this book." +msgstr "Beep don't have any reading activity for this beep." + +#: bookwyrm/templates/book.html:151 +msgid "Create" +msgstr "Beep" + +#: bookwyrm/templates/book.html:172 +msgid "Tags" +msgstr "Beep" + +#: bookwyrm/templates/book.html:176 +msgid "Add tag" +msgstr "Beep beep" + +#: bookwyrm/templates/book.html:193 +msgid "Subjects" +msgstr "Beep" + +#: bookwyrm/templates/book.html:204 +msgid "Places" +msgstr "Beep" + +#: bookwyrm/templates/book.html:215 bookwyrm/templates/search_results.html:84 +#: bookwyrm/templates/user/user_layout.html:60 +msgid "Lists" +msgstr "Beep" + +#: bookwyrm/templates/book.html:244 +msgid "rated it" +msgstr "rated beep" + +#: bookwyrm/templates/discover/landing_layout.html:15 +msgid "Decentralized" +msgstr "Beep" + +#: bookwyrm/templates/discover/landing_layout.html:21 +msgid "Friendly" +msgstr "Beep" + +#: bookwyrm/templates/discover/landing_layout.html:27 +msgid "Anti-Corporate" +msgstr "Anti-Beep" + +#: bookwyrm/templates/discover/landing_layout.html:42 +#, python-format +msgid "Join %(name)s" +msgstr "Beep %(name)s" + +#: bookwyrm/templates/discover/landing_layout.html:47 +#: bookwyrm/templates/login.html:45 +msgid "This instance is closed" +msgstr "Beep instance is beep" + +#: bookwyrm/templates/discover/landing_layout.html:53 +msgid "Your Account" +msgstr "Your Beep" + +#: bookwyrm/templates/edit_author.html:27 +msgid "Metadata" +msgstr "Beep" + +#: bookwyrm/templates/edit_author.html:28 +msgid "Name:" +msgstr "Beep:" + +#: bookwyrm/templates/edit_author.html:33 +msgid "Bio:" +msgstr "Beep:" + +#: bookwyrm/templates/edit_author.html:38 +msgid "Wikipedia link:" +msgstr "Beep beep:" + +#: bookwyrm/templates/edit_author.html:43 +msgid "Birth date:" +msgstr "Beep beep:" + +#: bookwyrm/templates/edit_author.html:48 +msgid "Death date:" +msgstr "Beep beep:" + +#: bookwyrm/templates/edit_author.html:54 +msgid "Author Identifiers" +msgstr "Author Beep" + +#: bookwyrm/templates/edit_author.html:55 +msgid "Openlibrary key:" +msgstr "Beep beep:" + +#: bookwyrm/templates/edit_author.html:60 +msgid "Librarything key:" +msgstr "Beep beep:" + +#: bookwyrm/templates/edit_author.html:65 +msgid "Goodreads key:" +msgstr "Beep beep:" + +#: bookwyrm/templates/edit_author.html:75 +msgid "Cancel" +msgstr "Beep" + +#: bookwyrm/templates/error.html:5 +msgid "Server Error" +msgstr "Server Beep" + +#: bookwyrm/templates/error.html:6 +msgid "Something went wrong! Sorry about that." +msgstr "Something went wrong! Beep about beep." + +#: bookwyrm/templates/goal.html:6 +#, python-format +msgid "%(year)s Reading Progress" +msgstr "%(year)s Reading Beep" + +#: bookwyrm/templates/goal.html:28 +#, python-format +msgid "" +"Set a goal for how many books you'll finish reading in %(year)s, and track " +"your progress throughout the year." +msgstr "" + +#: bookwyrm/templates/import.html:5 +msgid "Import Books" +msgstr "Import Beep" + +#: bookwyrm/templates/import.html:10 +msgid "Data source" +msgstr "Beep beep" + +#: bookwyrm/templates/import.html:28 +msgid "Include reviews" +msgstr "Beep beep" + +#: bookwyrm/templates/import.html:33 +msgid "Privacy setting for imported reviews:" +msgstr "Beep setting for imported beep:" + +#: bookwyrm/templates/import.html:37 +msgid "Import" +msgstr "Beep" + +#: bookwyrm/templates/import.html:42 +msgid "Recent Imports" +msgstr "Recent Beep" + +#: bookwyrm/templates/import.html:44 +msgid "No recent imports" +msgstr "Beep recent beep" + +#: bookwyrm/templates/import_status.html:6 +msgid "Import Status" +msgstr "Import Beep" + +#: bookwyrm/templates/import_status.html:9 +msgid "Import started: " +msgstr "Beep beep: " + +#: bookwyrm/templates/import_status.html:13 +msgid "Import completed: " +msgstr "Beep beep: " + +#: bookwyrm/templates/import_status.html:16 +msgid "TASK FAILED" +msgstr "TASK FAILED" + +#: bookwyrm/templates/import_status.html:22 +msgid "Import still in progress." +msgstr "Beep still in beep." + +#: bookwyrm/templates/import_status.html:24 +msgid "(Hit reload to update!)" +msgstr "(Beep reload to beep!)" + +#: bookwyrm/templates/import_status.html:31 +msgid "Failed to load" +msgstr "Beep to beep" + +#: bookwyrm/templates/import_status.html:55 +msgid "Select all" +msgstr "Beep beep" + +#: bookwyrm/templates/import_status.html:58 +msgid "Retry items" +msgstr "Beep beep" + +#: bookwyrm/templates/import_status.html:80 +msgid "Successfully imported" +msgstr "Beep beep" + +#: bookwyrm/templates/import_status.html:84 +msgid "Book" +msgstr "Beep" + +#: bookwyrm/templates/import_status.html:87 +msgid "Title" +msgstr "Beep" + +#: bookwyrm/templates/import_status.html:90 +msgid "Author" +msgstr "Beep" + +#: bookwyrm/templates/import_status.html:113 +msgid "Imported" +msgstr "Beep" + +#: bookwyrm/templates/invite.html:8 bookwyrm/templates/login.html:40 +msgid "Create an Account" +msgstr "Create beep Beep" + +#: bookwyrm/templates/invite.html:17 +msgid "Permission Denied" +msgstr "Permission Beep" + +#: bookwyrm/templates/invite.html:18 +msgid "Sorry! This invite code is no longer valid." +msgstr "Sorry! Beep invite code is no longer beep." + +#: bookwyrm/templates/layout.html:37 +msgid "search" +msgstr "beep" + +#: bookwyrm/templates/layout.html:47 +msgid "Main navigation menu" +msgstr "Beep navigation beep" + +#: bookwyrm/templates/layout.html:125 bookwyrm/templates/notifications.html:6 +msgid "Notifications" +msgstr "Beep" + +#: bookwyrm/templates/layout.html:142 bookwyrm/templates/layout.html:146 +#: bookwyrm/templates/login.html:14 +msgid "Username:" +msgstr "Beep:" + +#: bookwyrm/templates/layout.html:151 bookwyrm/templates/login.html:7 +#: bookwyrm/templates/login.html:30 +msgid "Log in" +msgstr "Beep beep" + +#: bookwyrm/templates/layout.html:182 +msgid "About this server" +msgstr "Beep this beep" + +#: bookwyrm/templates/layout.html:186 +msgid "Contact site admin" +msgstr "Beep site beep" + +#: bookwyrm/templates/login.html:20 +msgid "Password:" +msgstr "Beep:" + +#: bookwyrm/templates/login.html:33 +msgid "Forgot your password?" +msgstr "Beep your beep?" + +#: bookwyrm/templates/login.html:46 +msgid "Contact an administrator to get an invite" +msgstr "Beep an administrator to get an beep" + +#: bookwyrm/templates/login.html:56 +msgid "More about this site" +msgstr "Beep about this beep" + +#: bookwyrm/templates/notifications.html:10 +msgid "Delete notifications" +msgstr "Beep beep" + +#: bookwyrm/templates/notifications.html:101 +msgid "You're all caught up!" +msgstr "Beep're all caught beep!" + +#: bookwyrm/templates/password_reset_request.html:7 +msgid "Reset Password" +msgstr "Reset Beep" + +#: bookwyrm/templates/password_reset_request.html:9 +msgid "A link to reset your password will be sent to your email address" +msgstr "A link to reset your password will be sent to your email beep" + +#: bookwyrm/templates/password_reset_request.html:13 +msgid "Email address:" +msgstr "Beep beep:" + +#: bookwyrm/templates/password_reset_request.html:20 +msgid "Reset password" +msgstr "Beep beep" + +#: bookwyrm/templates/search_results.html:10 +msgid "Matching Books" +msgstr "Matching Beep" + +#: bookwyrm/templates/search_results.html:13 +#, python-format +msgid "No books found for \"%(query)s\"" +msgstr "Beep books found for \"%(query)s\"" + +#: bookwyrm/templates/search_results.html:29 +msgid "Didn't find what you were looking for?" +msgstr "Beep't find what you were looking beep?" + +#: bookwyrm/templates/search_results.html:52 +msgid "Import book" +msgstr "Beep beep" + +#: bookwyrm/templates/search_results.html:69 +msgid "Matching Users" +msgstr "Matching Beep" + +#: bookwyrm/templates/search_results.html:71 +#, python-format +msgid "No users found for \"%(query)s\"" +msgstr "Beep users found for \"%(query)s\"" + +#: bookwyrm/templates/search_results.html:86 +#, python-format +msgid "No lists found for \"%(query)s\"" +msgstr "Beep lists found for \"%(query)s\"" + +#: bookwyrm/templates/tag.html:6 +#, python-format +msgid "Books tagged \"%(tag.name)s\"" +msgstr "Beep tagged \"%(tag.name)s\"" + +#: bookwyrm/templates/user/user_layout.html:30 +msgid "Follow Requests" +msgstr "Follow Beep" + +#: bookwyrm/templates/user/user_layout.html:48 +msgid "Activity" +msgstr "Beep" + +#: bookwyrm/templates/user/user_layout.html:54 +msgid "Reading Goal" +msgstr "Reading Beep" + +#: bookwyrm/templates/user/user_layout.html:66 +msgid "Shelves" +msgstr "Beep" + +#: bookwyrm/templates/user/user_preview.html:13 +#, python-format +msgid "Joined %(date)s" +msgstr "Beep %(date)s" diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 2a2bf427..4e8708b9 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -30,6 +30,11 @@ EMAIL_USE_TLS = env('EMAIL_USE_TLS', True) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale'),] +LANGUAGES = [ + ('en-US', _('English')), + ('en-beep', _('Beep')), +] + # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index 5f75e122..c658ac3f 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -1,4 +1,5 @@ {% load bookwyrm_tags %} +{% load i18n %} From 2d79a521335f9ab7844d191de747d8d7631ae31d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 27 Feb 2021 16:18:24 -0800 Subject: [PATCH 0106/1285] Translations working in templates --- bookwyrm/settings.py | 10 +++++----- bookwyrm/templates/layout.html | 6 +++--- celerywyrm/settings.py | 4 ++-- locale/en_BEEP/LC_MESSAGES/django.mo | Bin 0 -> 5268 bytes .../en_BEEP}/LC_MESSAGES/django.po | 0 locale/en_US/LC_MESSAGES/django.mo | Bin 0 -> 427 bytes .../en-US => locale/en_US}/LC_MESSAGES/django.po | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) create mode 100644 locale/en_BEEP/LC_MESSAGES/django.mo rename {bookwyrm/locale/en-beep => locale/en_BEEP}/LC_MESSAGES/django.po (100%) create mode 100644 locale/en_US/LC_MESSAGES/django.mo rename {bookwyrm/locale/en-US => locale/en_US}/LC_MESSAGES/django.po (99%) diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 4e8708b9..de24c4f7 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -30,11 +30,6 @@ EMAIL_USE_TLS = env('EMAIL_USE_TLS', True) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale'),] -LANGUAGES = [ - ('en-US', _('English')), - ('en-beep', _('Beep')), -] - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ @@ -143,6 +138,11 @@ AUTH_PASSWORD_VALIDATORS = [ # https://docs.djangoproject.com/en/2.0/topics/i18n/ LANGUAGE_CODE = 'en-us' +LANGUAGES = [ + ('en-us', _('English')), + ('en-beep', _('Beep')), +] + TIME_ZONE = 'UTC' diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index c658ac3f..70c44e07 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -55,13 +55,13 @@ diff --git a/celerywyrm/settings.py b/celerywyrm/settings.py index c27072c2..92986d8e 100644 --- a/celerywyrm/settings.py +++ b/celerywyrm/settings.py @@ -142,9 +142,9 @@ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' -USE_I18N = True +USE_I18N = False -USE_L10N = True +USE_L10N = False USE_TZ = True diff --git a/locale/en_BEEP/LC_MESSAGES/django.mo b/locale/en_BEEP/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..26c5ec5a39c06dae5cffd3fa1bf81783636b8d85 GIT binary patch literal 5268 zcmbuBe~cYf6~`}%6qZ(M1%Vdiw$OH$`nKJ*NO=Xi+x=0t?6zz70YX(e``+!_p|dlO znR&ZD|1d#IVuIj5{(!{(;~yp({D+|YVT~~=Bw|cNBTY<FX4;uYMu`mZ$RMmPYkhhuOA9)}-* z-+~{4--i6mi`+0_ehjaIKZWbytMG&Hw@{}43^&2Q!OP)0@M?G&gVf#tWp@+Y0&jyF zHv$LXA*k_>`~K5V`cFaGIpg_VxPkT$;I;5)a6LQ+cfdEG^w;;5{@wuP=QhtFcn$3l zcpaRDxN1)N_X$)Sz5wOlX(;P5O`TGLg2!8<;*WW|g`zO>qZ$pi{ia}~$ z54EnHQ1jdcHO~R4^-Mzfd))IeD0?0M{)4Nvik?$J`XkTdYqU4o4Lu) zosbZjeNg%bq2{l_?eI9{XP)3DKc4YCllHG`xKO&N1^PTg38Ah)cU>*<>xc-!|++(ejX~0KZL{ZWhi@ZL(P8;POE(r z)O^EG>)Q`+zKr$y_a`v&5&DJy|4UGI&O+(G?E7DX%G=j{`#hANZ$Vr#|MvY?69oCQ5q=zQfj7XT zP<9s}Av7tJ-f1X5zY0lzQ$eIRiRil+L%NsNKkFNx?RLUp($kdOczgOZhwmBW4_%5%P~iMW1r_PxDC10w{@QCQXSiiY)4c(?nMR=<%hoE&mben z9C9~7tV48ZpFW06sKGUgoX~^6ln2U@Pa^w$-(jfp;4Vbxg>pdop=%0ZJIy$9 z5YeT*z8}#ypzA*5QDhLg6HyN6dLQx$J#6pqxF8+K>@gQK!nkQ?)1;ZYEHnN0g}F^# zlmrc%C$`;c1i9NfU`A~e#;4SUn#*k`X{WXoWZ80(Htcd3MRwlV%*DBMinnet2qPOb z8inD}xv3*nGdiEN(O3vGbkil5n$db5E`@o=j5Zn)TThB^p7Nk+M&mrJj3sF+NpZxC zw)2IAK?UqogZaXfp-VG!Fifi4(tN?nWuBAdRB_Afe8+C@kAp=wfF9kMOgCM_Zrk3^ z(_r~(n;8q@x{J(M66ZlZw?T~ei(wpQIg=-;R@P+g^oKD%yPjr7qtrEHsgt86q&ix! zC+#>l<3S$SjIh<68Aqq<=eVo0iZqDA&$|Y1B8ROw(YoqE9wu={XI4+cmKs$v9ya2g zxjh-i#C;(sw!$vE)Y*uoX){kI=>ap*U?Y9FisvF86RY>6Y9`qs!oa$;;bt<4qGXw* zc)abhJfod9lQnyNz)Ys0iyKkL97+=QHfUt_l9jMDZPV*WUR?ZjS|o}MivVXD z(AMM^9k4osj*d-_*(2@6d6!nr(Uyy&G6v=HXZ?3vHM1^V46}?xv*Ruf3GHkY)Lmv~ z({L%Mclc`a+~=70gvk1^S55agIg|tu4JEcMw>YDzbE7+zZ+1@*nt9r>L~AiKbHS1` zb9@N;2BU~aofLK=P5Jca7F@K1LvzWZD|c{Nvn;1c+}vvCk`y!D4G8i<^yt}YYdUhu zua;~hRcOXa`?!heCBD7}y6ySLm=xpfI*ZOuwxbBAihvV}+UVT3|5|sBerTDPTIDS=o?=k z5F8;L0$d)`oJz%ES!55Z=V&9P1~p`-5|0n09<-YaIS2Pv^`^b!r)aFTV|1uC2)oIs z2lWNEIZ*^EQ;o`lNt(bG5$dAj>PYG>EfECu!BrBqXmr<{T@$dr^&4&m(D= zxpr$XcKN*wikt1A=_)lBEUpRd6WuXBaAZsq?K zy*jWayxpo+sQO}c!E<3kqgO@PUih5lzJN(}aLq)edaq;^CVK^|H^I9$t~cSTT2*+_ zD^41>s&aJ)lyh|tNgX2HH{Be$M4c*pxm2Yp6nw4f8NOtx(g;&jC_Qz3=smuxDiylD z+k2Oz`C@3d78T0A7Wrotq7@rbFGJFMTGjN$r>B0A!lU=T?#N27?iBqOtvjWvbXLu{ zy3DM+3geYBqhZB>;^ym%I*SU^8?$25$Mk=%D#a3HXiZtsB)+iprs-8Dz4R5Ocr0%J E1@1Jx!~g&Q literal 0 HcmV?d00001 diff --git a/bookwyrm/locale/en-beep/LC_MESSAGES/django.po b/locale/en_BEEP/LC_MESSAGES/django.po similarity index 100% rename from bookwyrm/locale/en-beep/LC_MESSAGES/django.po rename to locale/en_BEEP/LC_MESSAGES/django.po diff --git a/locale/en_US/LC_MESSAGES/django.mo b/locale/en_US/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..810d271771144ebbac86a529be1c7ed4b0c841d4 GIT binary patch literal 427 zcmZ9I(MrQG6o#v)7rpe_i{qW5scl6Z*(yWbV9;WnRj=cA>>^!Kl62^U_y#_g&*I4h z2ma(E=fMBNpM3A{d`q7EM|b#(ad&e}#u)rZ+a7>h1@tj4Uj@%gT= wXxK>2^jxZ%L+?epuxvUb8aCEuX*sBNz1l48!|#|KDLBj^Iqru+BaQ#lA7^EDYXATM literal 0 HcmV?d00001 diff --git a/bookwyrm/locale/en-US/LC_MESSAGES/django.po b/locale/en_US/LC_MESSAGES/django.po similarity index 99% rename from bookwyrm/locale/en-US/LC_MESSAGES/django.po rename to locale/en_US/LC_MESSAGES/django.po index 9ecbea8c..475f4ecf 100644 --- a/bookwyrm/locale/en-US/LC_MESSAGES/django.po +++ b/locale/en_US/LC_MESSAGES/django.po @@ -98,7 +98,7 @@ msgstr "" #: bookwyrm/templates/book.html:215 bookwyrm/templates/search_results.html:84 #: bookwyrm/templates/user/user_layout.html:60 msgid "Lists" -msgstr "lirsts" +msgstr "" #: bookwyrm/templates/book.html:244 msgid "rated it" From 27316678d5fedcb16fbbd8e0f561f0c67bbba615 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 27 Feb 2021 16:44:58 -0800 Subject: [PATCH 0107/1285] Adds test german translations with apologies to actual german speakers --- bookwyrm/settings.py | 2 +- bookwyrm/templates/notifications.html | 1 + bw-dev | 5 +- locale/de/LC_MESSAGES/django.mo | Bin 0 -> 973 bytes locale/{en_BEEP => de}/LC_MESSAGES/django.po | 262 +++++++++++-------- locale/en_BEEP/LC_MESSAGES/django.mo | Bin 5268 -> 0 bytes locale/en_US/LC_MESSAGES/django.mo | Bin 427 -> 390 bytes 7 files changed, 155 insertions(+), 115 deletions(-) create mode 100644 locale/de/LC_MESSAGES/django.mo rename locale/{en_BEEP => de}/LC_MESSAGES/django.po (69%) delete mode 100644 locale/en_BEEP/LC_MESSAGES/django.mo diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index de24c4f7..c3fe56a9 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -140,7 +140,7 @@ AUTH_PASSWORD_VALIDATORS = [ LANGUAGE_CODE = 'en-us' LANGUAGES = [ ('en-us', _('English')), - ('en-beep', _('Beep')), + ('de', _('Deutsch')), ] diff --git a/bookwyrm/templates/notifications.html b/bookwyrm/templates/notifications.html index 10f07da0..5c009cba 100644 --- a/bookwyrm/templates/notifications.html +++ b/bookwyrm/templates/notifications.html @@ -1,4 +1,5 @@ {% extends 'layout.html' %} +{% load i18n %} {% load humanize %} {% load bookwyrm_tags %} {% block content %} diff --git a/bw-dev b/bw-dev index a0e06a22..012e56f3 100755 --- a/bw-dev +++ b/bw-dev @@ -91,7 +91,10 @@ case "$CMD" in execweb python manage.py collectstatic --no-input ;; makemessages) - django-admin makemessages --extension html --ignore=venv3 --local=$@ + django-admin makemessages --ignore=venv3 -l $@ + ;; + compilemessages) + django-admin compilemessages --ignore venv3 ;; build) docker-compose build diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..3487f1c58fe0fc82a4fee913ce0d6796f5cd77b5 GIT binary patch literal 973 zcmYL{yKfUg5XKh>uj3&R3L1)~BZ@8%3KU&plyiJeEY1(vPEep>eK$ETIqzA!djZ_h z&_II(9f*PoDjEtJ3Mgr*`AaDH)-hzH(eGw=Zsya>{+^$E!(bQTSK%+=m*Icl&bE#* zb_r~N=fDTxJebYm2{?!NId~F$0bT%KfoH+@;3@DUcn16oy8N%;dGOop{3lq2|AM>z zS57eIfs5b*czYH<1fAA>(AAxQPR}#Y)qf2-9q(rMA3#^<6X^PVnZ@5hv|^{>PA|f# zUSO*4IOzU!HUF9G>v}qKwOk)Jlc|M+qO{x&rQwG30clp1+75XtECp<%P*xj+~W7dj1CCl?7B)++~(C_!FdWQOe)rRHWx z`2ns<^i^U+tfLE;<2O=&hqOVVfOo=;yWwWUccN~u(QbKN8W-C7O_Rygua=qd`$fRL zPTTL&fpq`)HK&~KMq$@)w*6Yjx7tCov3S4Tio94D>-V)#CKtBQ0k3bzF>i& zuo16^wOTjo_3j{5Wu?f-?~@n>JPuo%+fK<^9IxkcsnFR`4iq#S&FDz>^3qMu&5jh> zJsm@jr6=~rI2Tf_@?&OYNTtdGzA}*3>*S>txnD2z$OK%C zr=h9bTIGkImFtRMtMKK8Rge96J0Q(dNrn{f9qx0J2Bo&fij3VOmAVT_*l053Lh-}c zk{`P5VwKtrhV_e-4BkLFV&T3mMUL5$E>$*NHz`&nC6b{=6)Q8G<7l>Y%X(xdmi31; b!fDSU2XDa=o{m(+yOfEXSgW*?+2a2KQNZrz literal 0 HcmV?d00001 diff --git a/locale/en_BEEP/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po similarity index 69% rename from locale/en_BEEP/LC_MESSAGES/django.po rename to locale/de/LC_MESSAGES/django.po index 3a450b65..986785ee 100644 --- a/locale/en_BEEP/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -1,190 +1,218 @@ -# A test translation file that just uses the word "beep" -# Copyright (C) 2021 Mouse Reeve -# This file is distributed under the same license as the BookWyrm package. -# Mouse Reeve , YEAR. # #, fuzzy msgid "" msgstr "" -"Project-Id-Version: 0.0.1\n" +"Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-02-27 14:23-0800\n" -"PO-Revision-Date: 2021-02-27 14:25+PST\n" -"Last-Translator: Mouse Reeve \n" -"Language-Team: Mouse Reeve \n" -"Language: Beep\n" +"POT-Creation-Date: 2021-02-27 16:29-0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: bookwyrm/models/fields.py:24 +#, python-format +msgid "%(value)s is not a valid remote_id" +msgstr "" + +#: bookwyrm/models/fields.py:33 bookwyrm/models/fields.py:42 +#, python-format +msgid "%(value)s is not a valid username" +msgstr "" + +#: bookwyrm/models/fields.py:164 +msgid "username" +msgstr "Nutzername" + +#: bookwyrm/models/fields.py:169 +msgid "A user with that username already exists." +msgstr "" + +#: bookwyrm/settings.py:142 +msgid "English" +msgstr "" + +#: bookwyrm/settings.py:143 +msgid "Beep" +msgstr "" #: bookwyrm/templates/author.html:13 bookwyrm/templates/author.html:14 msgid "Edit Author" -msgstr "Edit Beep" +msgstr "" #: bookwyrm/templates/author.html:29 msgid "Wikipedia" -msgstr "Beep" +msgstr "" #: bookwyrm/templates/author.html:34 #, python-format msgid "Books by %(name)s" -msgstr "Beep by %(name)s" +msgstr "" #: bookwyrm/templates/book.html:27 bookwyrm/templates/book.html:28 msgid "Edit Book" -msgstr "Edit Beep" +msgstr "" #: bookwyrm/templates/book.html:43 msgid "Add cover" -msgstr "Beep beep" +msgstr "" #: bookwyrm/templates/book.html:49 msgid "Add" -msgstr "Beep" +msgstr "" #: bookwyrm/templates/book.html:58 msgid "ISBN:" -msgstr "ISBN:" +msgstr "" #: bookwyrm/templates/book.html:65 msgid "OCLC Number:" -msgstr "OCLC Beep:" +msgstr "" #: bookwyrm/templates/book.html:72 msgid "ASIN:" -msgstr "ASIN:" +msgstr "" #: bookwyrm/templates/book.html:84 msgid "View on OpenLibrary" -msgstr "View beep OpenBeep" +msgstr "" #: bookwyrm/templates/book.html:102 msgid "Description:" -msgstr "Beep:" +msgstr "" #: bookwyrm/templates/book.html:106 bookwyrm/templates/edit_author.html:74 msgid "Save" -msgstr "Beep" +msgstr "" #: bookwyrm/templates/book.html:138 msgid "Your reading activity" -msgstr "Beep reading beep" +msgstr "" #: bookwyrm/templates/book.html:144 msgid "You don't have any reading activity for this book." -msgstr "Beep don't have any reading activity for this beep." +msgstr "" #: bookwyrm/templates/book.html:151 msgid "Create" -msgstr "Beep" +msgstr "" #: bookwyrm/templates/book.html:172 msgid "Tags" -msgstr "Beep" +msgstr "Stichworte" #: bookwyrm/templates/book.html:176 msgid "Add tag" -msgstr "Beep beep" +msgstr "" #: bookwyrm/templates/book.html:193 msgid "Subjects" -msgstr "Beep" +msgstr "Themen" #: bookwyrm/templates/book.html:204 msgid "Places" -msgstr "Beep" +msgstr "Setzt" -#: bookwyrm/templates/book.html:215 bookwyrm/templates/search_results.html:84 +#: bookwyrm/templates/book.html:215 bookwyrm/templates/layout.html:64 +#: bookwyrm/templates/search_results.html:84 #: bookwyrm/templates/user/user_layout.html:60 msgid "Lists" -msgstr "Beep" +msgstr "Listen" #: bookwyrm/templates/book.html:244 msgid "rated it" -msgstr "rated beep" +msgstr "" #: bookwyrm/templates/discover/landing_layout.html:15 msgid "Decentralized" -msgstr "Beep" +msgstr "" #: bookwyrm/templates/discover/landing_layout.html:21 msgid "Friendly" -msgstr "Beep" +msgstr "" #: bookwyrm/templates/discover/landing_layout.html:27 msgid "Anti-Corporate" -msgstr "Anti-Beep" +msgstr "" #: bookwyrm/templates/discover/landing_layout.html:42 #, python-format msgid "Join %(name)s" -msgstr "Beep %(name)s" +msgstr "" #: bookwyrm/templates/discover/landing_layout.html:47 #: bookwyrm/templates/login.html:45 msgid "This instance is closed" -msgstr "Beep instance is beep" +msgstr "" #: bookwyrm/templates/discover/landing_layout.html:53 msgid "Your Account" -msgstr "Your Beep" +msgstr "" #: bookwyrm/templates/edit_author.html:27 msgid "Metadata" -msgstr "Beep" +msgstr "" #: bookwyrm/templates/edit_author.html:28 msgid "Name:" -msgstr "Beep:" +msgstr "" #: bookwyrm/templates/edit_author.html:33 msgid "Bio:" -msgstr "Beep:" +msgstr "" #: bookwyrm/templates/edit_author.html:38 msgid "Wikipedia link:" -msgstr "Beep beep:" +msgstr "" #: bookwyrm/templates/edit_author.html:43 msgid "Birth date:" -msgstr "Beep beep:" +msgstr "Geburtstag" #: bookwyrm/templates/edit_author.html:48 msgid "Death date:" -msgstr "Beep beep:" +msgstr "Todesdatum" #: bookwyrm/templates/edit_author.html:54 msgid "Author Identifiers" -msgstr "Author Beep" +msgstr "" #: bookwyrm/templates/edit_author.html:55 msgid "Openlibrary key:" -msgstr "Beep beep:" +msgstr "" #: bookwyrm/templates/edit_author.html:60 msgid "Librarything key:" -msgstr "Beep beep:" +msgstr "" #: bookwyrm/templates/edit_author.html:65 msgid "Goodreads key:" -msgstr "Beep beep:" +msgstr "" #: bookwyrm/templates/edit_author.html:75 msgid "Cancel" -msgstr "Beep" +msgstr "" #: bookwyrm/templates/error.html:5 msgid "Server Error" -msgstr "Server Beep" +msgstr "" #: bookwyrm/templates/error.html:6 msgid "Something went wrong! Sorry about that." -msgstr "Something went wrong! Beep about beep." +msgstr "" #: bookwyrm/templates/goal.html:6 #, python-format msgid "%(year)s Reading Progress" -msgstr "%(year)s Reading Beep" +msgstr "" #: bookwyrm/templates/goal.html:28 #, python-format @@ -195,223 +223,231 @@ msgstr "" #: bookwyrm/templates/import.html:5 msgid "Import Books" -msgstr "Import Beep" +msgstr "" #: bookwyrm/templates/import.html:10 msgid "Data source" -msgstr "Beep beep" +msgstr "" #: bookwyrm/templates/import.html:28 msgid "Include reviews" -msgstr "Beep beep" +msgstr "" #: bookwyrm/templates/import.html:33 msgid "Privacy setting for imported reviews:" -msgstr "Beep setting for imported beep:" +msgstr "" #: bookwyrm/templates/import.html:37 msgid "Import" -msgstr "Beep" +msgstr "" #: bookwyrm/templates/import.html:42 msgid "Recent Imports" -msgstr "Recent Beep" +msgstr "" #: bookwyrm/templates/import.html:44 msgid "No recent imports" -msgstr "Beep recent beep" +msgstr "" #: bookwyrm/templates/import_status.html:6 msgid "Import Status" -msgstr "Import Beep" +msgstr "" #: bookwyrm/templates/import_status.html:9 msgid "Import started: " -msgstr "Beep beep: " +msgstr "" #: bookwyrm/templates/import_status.html:13 msgid "Import completed: " -msgstr "Beep beep: " +msgstr "" #: bookwyrm/templates/import_status.html:16 msgid "TASK FAILED" -msgstr "TASK FAILED" +msgstr "" #: bookwyrm/templates/import_status.html:22 msgid "Import still in progress." -msgstr "Beep still in beep." +msgstr "" #: bookwyrm/templates/import_status.html:24 msgid "(Hit reload to update!)" -msgstr "(Beep reload to beep!)" +msgstr "" #: bookwyrm/templates/import_status.html:31 msgid "Failed to load" -msgstr "Beep to beep" +msgstr "" #: bookwyrm/templates/import_status.html:55 msgid "Select all" -msgstr "Beep beep" +msgstr "" #: bookwyrm/templates/import_status.html:58 msgid "Retry items" -msgstr "Beep beep" +msgstr "" #: bookwyrm/templates/import_status.html:80 msgid "Successfully imported" -msgstr "Beep beep" +msgstr "" #: bookwyrm/templates/import_status.html:84 msgid "Book" -msgstr "Beep" +msgstr "" #: bookwyrm/templates/import_status.html:87 msgid "Title" -msgstr "Beep" +msgstr "" #: bookwyrm/templates/import_status.html:90 msgid "Author" -msgstr "Beep" +msgstr "" #: bookwyrm/templates/import_status.html:113 msgid "Imported" -msgstr "Beep" +msgstr "" #: bookwyrm/templates/invite.html:8 bookwyrm/templates/login.html:40 msgid "Create an Account" -msgstr "Create beep Beep" +msgstr "" #: bookwyrm/templates/invite.html:17 msgid "Permission Denied" -msgstr "Permission Beep" +msgstr "" #: bookwyrm/templates/invite.html:18 msgid "Sorry! This invite code is no longer valid." -msgstr "Sorry! Beep invite code is no longer beep." +msgstr "" -#: bookwyrm/templates/layout.html:37 +#: bookwyrm/templates/layout.html:38 msgid "search" -msgstr "beep" +msgstr "" -#: bookwyrm/templates/layout.html:47 +#: bookwyrm/templates/layout.html:48 msgid "Main navigation menu" -msgstr "Beep navigation beep" +msgstr "" -#: bookwyrm/templates/layout.html:125 bookwyrm/templates/notifications.html:6 +#: bookwyrm/templates/layout.html:58 +msgid "Your shelves" +msgstr "Deine Regale" + +#: bookwyrm/templates/layout.html:61 +msgid "Feed" +msgstr "Aktualisierung" + +#: bookwyrm/templates/layout.html:126 bookwyrm/templates/notifications.html:6 msgid "Notifications" -msgstr "Beep" +msgstr "Benachrichtigungen" -#: bookwyrm/templates/layout.html:142 bookwyrm/templates/layout.html:146 +#: bookwyrm/templates/layout.html:143 bookwyrm/templates/layout.html:147 #: bookwyrm/templates/login.html:14 msgid "Username:" -msgstr "Beep:" +msgstr "" -#: bookwyrm/templates/layout.html:151 bookwyrm/templates/login.html:7 +#: bookwyrm/templates/layout.html:152 bookwyrm/templates/login.html:7 #: bookwyrm/templates/login.html:30 msgid "Log in" -msgstr "Beep beep" +msgstr "" -#: bookwyrm/templates/layout.html:182 +#: bookwyrm/templates/layout.html:183 msgid "About this server" -msgstr "Beep this beep" +msgstr "Ãœber diesen Server" -#: bookwyrm/templates/layout.html:186 +#: bookwyrm/templates/layout.html:187 msgid "Contact site admin" -msgstr "Beep site beep" +msgstr "Wenden Sie sich an den Site-Administrator" #: bookwyrm/templates/login.html:20 msgid "Password:" -msgstr "Beep:" +msgstr "Passwort" #: bookwyrm/templates/login.html:33 msgid "Forgot your password?" -msgstr "Beep your beep?" +msgstr "" #: bookwyrm/templates/login.html:46 msgid "Contact an administrator to get an invite" -msgstr "Beep an administrator to get an beep" +msgstr "" #: bookwyrm/templates/login.html:56 msgid "More about this site" -msgstr "Beep about this beep" +msgstr "" #: bookwyrm/templates/notifications.html:10 msgid "Delete notifications" -msgstr "Beep beep" +msgstr "" #: bookwyrm/templates/notifications.html:101 msgid "You're all caught up!" -msgstr "Beep're all caught beep!" +msgstr "" #: bookwyrm/templates/password_reset_request.html:7 msgid "Reset Password" -msgstr "Reset Beep" +msgstr "" #: bookwyrm/templates/password_reset_request.html:9 msgid "A link to reset your password will be sent to your email address" -msgstr "A link to reset your password will be sent to your email beep" +msgstr "" #: bookwyrm/templates/password_reset_request.html:13 msgid "Email address:" -msgstr "Beep beep:" +msgstr "" #: bookwyrm/templates/password_reset_request.html:20 msgid "Reset password" -msgstr "Beep beep" +msgstr "" #: bookwyrm/templates/search_results.html:10 msgid "Matching Books" -msgstr "Matching Beep" +msgstr "" #: bookwyrm/templates/search_results.html:13 #, python-format msgid "No books found for \"%(query)s\"" -msgstr "Beep books found for \"%(query)s\"" +msgstr "" #: bookwyrm/templates/search_results.html:29 msgid "Didn't find what you were looking for?" -msgstr "Beep't find what you were looking beep?" +msgstr "" #: bookwyrm/templates/search_results.html:52 msgid "Import book" -msgstr "Beep beep" +msgstr "" #: bookwyrm/templates/search_results.html:69 msgid "Matching Users" -msgstr "Matching Beep" +msgstr "" #: bookwyrm/templates/search_results.html:71 #, python-format msgid "No users found for \"%(query)s\"" -msgstr "Beep users found for \"%(query)s\"" +msgstr "" #: bookwyrm/templates/search_results.html:86 #, python-format msgid "No lists found for \"%(query)s\"" -msgstr "Beep lists found for \"%(query)s\"" +msgstr "" #: bookwyrm/templates/tag.html:6 #, python-format msgid "Books tagged \"%(tag.name)s\"" -msgstr "Beep tagged \"%(tag.name)s\"" +msgstr "" #: bookwyrm/templates/user/user_layout.html:30 msgid "Follow Requests" -msgstr "Follow Beep" +msgstr "" #: bookwyrm/templates/user/user_layout.html:48 msgid "Activity" -msgstr "Beep" +msgstr "" #: bookwyrm/templates/user/user_layout.html:54 msgid "Reading Goal" -msgstr "Reading Beep" +msgstr "" #: bookwyrm/templates/user/user_layout.html:66 msgid "Shelves" -msgstr "Beep" +msgstr "" #: bookwyrm/templates/user/user_preview.html:13 #, python-format msgid "Joined %(date)s" -msgstr "Beep %(date)s" +msgstr "" diff --git a/locale/en_BEEP/LC_MESSAGES/django.mo b/locale/en_BEEP/LC_MESSAGES/django.mo deleted file mode 100644 index 26c5ec5a39c06dae5cffd3fa1bf81783636b8d85..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5268 zcmbuBe~cYf6~`}%6qZ(M1%Vdiw$OH$`nKJ*NO=Xi+x=0t?6zz70YX(e``+!_p|dlO znR&ZD|1d#IVuIj5{(!{(;~yp({D+|YVT~~=Bw|cNBTY<FX4;uYMu`mZ$RMmPYkhhuOA9)}-* z-+~{4--i6mi`+0_ehjaIKZWbytMG&Hw@{}43^&2Q!OP)0@M?G&gVf#tWp@+Y0&jyF zHv$LXA*k_>`~K5V`cFaGIpg_VxPkT$;I;5)a6LQ+cfdEG^w;;5{@wuP=QhtFcn$3l zcpaRDxN1)N_X$)Sz5wOlX(;P5O`TGLg2!8<;*WW|g`zO>qZ$pi{ia}~$ z54EnHQ1jdcHO~R4^-Mzfd))IeD0?0M{)4Nvik?$J`XkTdYqU4o4Lu) zosbZjeNg%bq2{l_?eI9{XP)3DKc4YCllHG`xKO&N1^PTg38Ah)cU>*<>xc-!|++(ejX~0KZL{ZWhi@ZL(P8;POE(r z)O^EG>)Q`+zKr$y_a`v&5&DJy|4UGI&O+(G?E7DX%G=j{`#hANZ$Vr#|MvY?69oCQ5q=zQfj7XT zP<9s}Av7tJ-f1X5zY0lzQ$eIRiRil+L%NsNKkFNx?RLUp($kdOczgOZhwmBW4_%5%P~iMW1r_PxDC10w{@QCQXSiiY)4c(?nMR=<%hoE&mben z9C9~7tV48ZpFW06sKGUgoX~^6ln2U@Pa^w$-(jfp;4Vbxg>pdop=%0ZJIy$9 z5YeT*z8}#ypzA*5QDhLg6HyN6dLQx$J#6pqxF8+K>@gQK!nkQ?)1;ZYEHnN0g}F^# zlmrc%C$`;c1i9NfU`A~e#;4SUn#*k`X{WXoWZ80(Htcd3MRwlV%*DBMinnet2qPOb z8inD}xv3*nGdiEN(O3vGbkil5n$db5E`@o=j5Zn)TThB^p7Nk+M&mrJj3sF+NpZxC zw)2IAK?UqogZaXfp-VG!Fifi4(tN?nWuBAdRB_Afe8+C@kAp=wfF9kMOgCM_Zrk3^ z(_r~(n;8q@x{J(M66ZlZw?T~ei(wpQIg=-;R@P+g^oKD%yPjr7qtrEHsgt86q&ix! zC+#>l<3S$SjIh<68Aqq<=eVo0iZqDA&$|Y1B8ROw(YoqE9wu={XI4+cmKs$v9ya2g zxjh-i#C;(sw!$vE)Y*uoX){kI=>ap*U?Y9FisvF86RY>6Y9`qs!oa$;;bt<4qGXw* zc)abhJfod9lQnyNz)Ys0iyKkL97+=QHfUt_l9jMDZPV*WUR?ZjS|o}MivVXD z(AMM^9k4osj*d-_*(2@6d6!nr(Uyy&G6v=HXZ?3vHM1^V46}?xv*Ruf3GHkY)Lmv~ z({L%Mclc`a+~=70gvk1^S55agIg|tu4JEcMw>YDzbE7+zZ+1@*nt9r>L~AiKbHS1` zb9@N;2BU~aofLK=P5Jca7F@K1LvzWZD|c{Nvn;1c+}vvCk`y!D4G8i<^yt}YYdUhu zua;~hRcOXa`?!heCBD7}y6ySLm=xpfI*ZOuwxbBAihvV}+UVT3|5|sBerTDPTIDS=o?=k z5F8;L0$d)`oJz%ES!55Z=V&9P1~p`-5|0n09<-YaIS2Pv^`^b!r)aFTV|1uC2)oIs z2lWNEIZ*^EQ;o`lNt(bG5$dAj>PYG>EfECu!BrBqXmr<{T@$dr^&4&m(D= zxpr$XcKN*wikt1A=_)lBEUpRd6WuXBaAZsq?K zy*jWayxpo+sQO}c!E<3kqgO@PUih5lzJN(}aLq)edaq;^CVK^|H^I9$t~cSTT2*+_ zD^41>s&aJ)lyh|tNgX2HH{Be$M4c*pxm2Yp6nw4f8NOtx(g;&jC_Qz3=smuxDiylD z+k2Oz`C@3d78T0A7Wrotq7@rbFGJFMTGjN$r>B0A!lU=T?#N27?iBqOtvjWvbXLu{ zy3DM+3geYBqhZB>;^ym%I*SU^8?$25$Mk=%D#a3HXiZtsB)+iprs-8Dz4R5Ocr0%J E1@1Jx!~g&Q diff --git a/locale/en_US/LC_MESSAGES/django.mo b/locale/en_US/LC_MESSAGES/django.mo index 810d271771144ebbac86a529be1c7ed4b0c841d4..c0a5dd97922819649185a3a8135344140fa5f096 100644 GIT binary patch delta 65 wcmZ3@+{SEiPl#nI0}wC*u?!Ha05LNV>i{tbSOBpPP|^}egVeyl#xOrd0NzCfegFUf delta 103 zcmZo;UdsBPYa0v From 4f76d21b85aea36cd9dd7be8e34294a1cda347df Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 27 Feb 2021 18:48:10 -0800 Subject: [PATCH 0108/1285] Snags more strings for i18n --- README.md | 2 +- bookwyrm/templates/discover/about.html | 9 +- bookwyrm/templates/discover/discover.html | 3 +- bookwyrm/templates/edit_author.html | 8 +- bookwyrm/templates/edit_book.html | 47 +- bookwyrm/templates/editions.html | 3 +- bookwyrm/templates/error.html | 1 + bookwyrm/templates/feed/direct_messages.html | 5 +- bookwyrm/templates/feed/feed.html | 13 +- bookwyrm/templates/feed/feed_layout.html | 7 +- bookwyrm/templates/feed/status.html | 3 +- bookwyrm/templates/goal.html | 3 +- bookwyrm/templates/import.html | 1 + bookwyrm/templates/import_status.html | 1 + bookwyrm/templates/invite.html | 1 + bookwyrm/templates/layout.html | 6 +- bookwyrm/templates/lists/create_form.html | 3 +- bookwyrm/templates/lists/curate.html | 15 +- bookwyrm/templates/lists/edit_form.html | 3 +- bookwyrm/templates/lists/form.html | 21 +- bookwyrm/templates/lists/list.html | 24 +- bookwyrm/templates/lists/list_items.html | 3 +- bookwyrm/templates/lists/list_layout.html | 4 +- bookwyrm/templates/lists/lists.html | 7 +- bookwyrm/templates/login.html | 1 + bookwyrm/templates/notfound.html | 5 +- bookwyrm/templates/notifications.html | 27 +- bookwyrm/templates/password_reset.html | 10 +- .../templates/password_reset_request.html | 1 + bookwyrm/templates/preferences/blocks.html | 5 +- .../preferences/change_password.html | 9 +- bookwyrm/templates/preferences/edit_user.html | 15 +- .../preferences/preferences_layout.html | 11 +- bookwyrm/templates/search_results.html | 3 +- bookwyrm/templates/settings/admin_layout.html | 19 +- bookwyrm/templates/settings/federation.html | 9 +- .../templates/settings/manage_invites.html | 21 +- bookwyrm/templates/settings/site.html | 39 +- bookwyrm/templates/snippets/block_button.html | 5 +- bookwyrm/templates/snippets/boost_button.html | 9 +- .../snippets/content_warning_field.html | 5 +- .../templates/snippets/create_status.html | 7 +- .../snippets/create_status_form.html | 13 +- .../snippets/delete_readthrough_modal.html | 8 +- bookwyrm/templates/snippets/fav_button.html | 9 +- .../templates/snippets/follow_button.html | 9 +- .../snippets/follow_request_buttons.html | 5 +- bookwyrm/templates/snippets/goal_card.html | 9 +- bookwyrm/templates/snippets/goal_form.html | 11 +- .../templates/snippets/goal_progress.html | 5 +- bookwyrm/templates/snippets/pagination.html | 5 +- .../templates/snippets/privacy-icons.html | 15 +- .../templates/snippets/privacy_select.html | 11 +- .../templates/snippets/progress_update.html | 11 +- bookwyrm/templates/snippets/rate_action.html | 7 +- bookwyrm/templates/snippets/readthrough.html | 13 +- .../templates/snippets/readthrough_form.html | 11 +- .../templates/snippets/register_form.html | 9 +- bookwyrm/templates/snippets/rss_title.html | 11 +- .../snippets/search_result_text.html | 3 +- bookwyrm/templates/snippets/shelf.html | 45 +- .../templates/snippets/shelf_selector.html | 5 +- .../shelve_button/finish_reading_modal.html | 11 +- .../shelve_button/shelve_button_dropdown.html | 3 +- .../shelve_button/shelve_button_options.html | 3 +- .../shelve_button/start_reading_modal.html | 9 +- .../shelve_button/want_to_read_modal.html | 7 +- bookwyrm/templates/snippets/stars.html | 3 +- .../templates/snippets/status/status.html | 3 +- .../snippets/status/status_body.html | 13 +- .../snippets/status/status_content.html | 3 +- .../snippets/status/status_header.html | 9 +- .../snippets/status/status_options.html | 7 +- .../snippets/switch_edition_button.html | 3 +- bookwyrm/templates/snippets/tag.html | 5 +- bookwyrm/templates/snippets/user_options.html | 5 +- bookwyrm/templates/tag.html | 1 + .../templates/user/create_shelf_form.html | 7 +- bookwyrm/templates/user/edit_shelf_form.html | 7 +- bookwyrm/templates/user/followers.html | 5 +- bookwyrm/templates/user/following.html | 5 +- bookwyrm/templates/user/lists.html | 3 +- bookwyrm/templates/user/user.html | 20 +- bw-dev | 4 +- locale/de/LC_MESSAGES/django.mo | Bin 973 -> 1438 bytes locale/de/LC_MESSAGES/django.po | 1254 +++++++++++++++-- locale/en_US/LC_MESSAGES/django.po | 1191 +++++++++++++++- 87 files changed, 2662 insertions(+), 528 deletions(-) diff --git a/README.md b/README.md index 18463f38..8be03ea1 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ If you have questions about the project or contributing, you can set up a video ### Translation Do you speak a language besides English? BookWyrm needs localization! Existing language files can be found in `bookwyrm/locale/`, and you can generate a language file for a language that isn't currently supported by running: -`./bw-dev makemessages ` +`./bw-dev makemessages -l ` ### Financial Support BookWyrm is an ad-free passion project with no intentions of seeking out venture funding or corporate financial relationships. If you want to help keep the project going, you can donate to the [Patreon](https://www.patreon.com/bookwyrm), or make a one time gift via [PayPal](https://paypal.me/oulipo). diff --git a/bookwyrm/templates/discover/about.html b/bookwyrm/templates/discover/about.html index 9d3f66dc..dd0a8129 100644 --- a/bookwyrm/templates/discover/about.html +++ b/bookwyrm/templates/discover/about.html @@ -1,4 +1,5 @@ {% extends 'discover/landing_layout.html' %} +{% load i18n %} {% block panel %}
        @@ -6,17 +7,17 @@
        -

        Code of Conduct

        +

        {% trans "Code of Conduct" %}

        {{ site.code_of_conduct | safe }}
        @@ -25,7 +26,7 @@
        -

        Privacy Policy

        +

        {% trans "Privacy Policy" %}

        {{ site.privacy_policy | safe }}
        diff --git a/bookwyrm/templates/discover/discover.html b/bookwyrm/templates/discover/discover.html index 289e30d9..d553c368 100644 --- a/bookwyrm/templates/discover/discover.html +++ b/bookwyrm/templates/discover/discover.html @@ -1,8 +1,9 @@ {% extends 'discover/landing_layout.html' %} +{% load i18n %} {% block panel %}
        -

        Recent Books

        +

        {% trans "Recent Books" %}

        diff --git a/bookwyrm/templates/edit_author.html b/bookwyrm/templates/edit_author.html index 056567d6..ab59cc2d 100644 --- a/bookwyrm/templates/edit_author.html +++ b/bookwyrm/templates/edit_author.html @@ -1,4 +1,5 @@ {% extends 'layout.html' %} +{% load i18n %} {% load humanize %} {% block content %}
        @@ -6,9 +7,9 @@ Edit "{{ author.name }}"
        -

        Added: {{ author.created_date | naturaltime }}

        -

        Updated: {{ author.updated_date | naturaltime }}

        -

        Last edited by: {{ author.last_edited_by.display_name }}

        +

        {% trans "Added:" %} {{ author.created_date | naturaltime }}

        +

        {% trans "Updated:" %} {{ author.updated_date | naturaltime }}

        +

        {% trans "Last edited by:" %} {{ author.last_edited_by.display_name }}

        @@ -77,4 +78,3 @@ {% endblock %} - diff --git a/bookwyrm/templates/edit_book.html b/bookwyrm/templates/edit_book.html index fb0bb81c..15c7b878 100644 --- a/bookwyrm/templates/edit_book.html +++ b/bookwyrm/templates/edit_book.html @@ -1,4 +1,5 @@ {% extends 'layout.html' %} +{% load i18n %} {% load humanize %} {% block content %}
        @@ -6,9 +7,9 @@ Edit "{{ book.title }}"
        -

        Added: {{ book.created_date | naturaltime }}

        -

        Updated: {{ book.updated_date | naturaltime }}

        -

        Last edited by: {{ book.last_edited_by.display_name }}

        +

        {% trans "Added:" %} {{ book.created_date | naturaltime }}

        +

        {% trans "Updated:" %} {{ book.updated_date | naturaltime }}

        +

        {% trans "Last edited by:" %} {{ book.last_edited_by.display_name }}

        @@ -23,32 +24,32 @@
        -

        Metadata

        -

        {{ form.title }}

        +

        {% trans "Metadata" %}

        +

        {{ form.title }}

        {% for error in form.title.errors %}

        {{ error | escape }}

        {% endfor %} -

        {{ form.subtitle }}

        +

        {{ form.subtitle }}

        {% for error in form.subtitle.errors %}

        {{ error | escape }}

        {% endfor %} -

        {{ form.description }}

        +

        {{ form.description }}

        {% for error in form.description.errors %}

        {{ error | escape }}

        {% endfor %} -

        {{ form.series }}

        +

        {{ form.series }}

        {% for error in form.series.errors %}

        {{ error | escape }}

        {% endfor %} -

        {{ form.series_number }}

        +

        {{ form.series_number }}

        {% for error in form.series_number.errors %}

        {{ error | escape }}

        {% endfor %} -

        {{ form.first_published_date }}

        +

        {{ form.first_published_date }}

        {% for error in form.first_published_date.errors %}

        {{ error | escape }}

        {% endfor %} -

        {{ form.published_date }}

        +

        {{ form.published_date }}

        {% for error in form.published_date.errors %}

        {{ error | escape }}

        {% endfor %} @@ -61,7 +62,7 @@
        -

        Cover

        +

        {% trans "Cover" %}

        {{ form.cover }}

        {% for error in form.cover.errors %}

        {{ error | escape }}

        @@ -71,8 +72,8 @@
        -

        Physical Properties

        -

        {{ form.physical_format }}

        +

        {% trans "Physical Properties" %}

        +

        {{ form.physical_format }}

        {% for error in form.physical_format.errors %}

        {{ error | escape }}

        {% endfor %} @@ -80,31 +81,31 @@

        {{ error | escape }}

        {% endfor %} -

        {{ form.pages }}

        +

        {{ form.pages }}

        {% for error in form.pages.errors %}

        {{ error | escape }}

        {% endfor %}
        -

        Book Identifiers

        -

        {{ form.isbn_13 }}

        +

        {% trans "Book Identifiers" %}

        +

        {{ form.isbn_13 }}

        {% for error in form.isbn_13.errors %}

        {{ error | escape }}

        {% endfor %} -

        {{ form.isbn_10 }}

        +

        {{ form.isbn_10 }}

        {% for error in form.isbn_10.errors %}

        {{ error | escape }}

        {% endfor %} -

        {{ form.openlibrary_key }}

        +

        {{ form.openlibrary_key }}

        {% for error in form.openlibrary_key.errors %}

        {{ error | escape }}

        {% endfor %} -

        {{ form.oclc_number }}

        +

        {{ form.oclc_number }}

        {% for error in form.oclc_number.errors %}

        {{ error | escape }}

        {% endfor %} -

        {{ form.asin }}

        +

        {{ form.asin }}

        {% for error in form.ASIN.errors %}

        {{ error | escape }}

        {% endfor %} @@ -113,8 +114,8 @@
        - - Cancel + + {% trans "Cancel" %}
        diff --git a/bookwyrm/templates/editions.html b/bookwyrm/templates/editions.html index 619ceafb..7dfabc93 100644 --- a/bookwyrm/templates/editions.html +++ b/bookwyrm/templates/editions.html @@ -1,8 +1,9 @@ {% extends 'layout.html' %} +{% load i18n %} {% load bookwyrm_tags %} {% block content %}
        -

        Editions of "{{ work.title }}"

        +

        {% blocktrans with path=work.local_path work_title=work.title %}Editions of "{{ work_title }}"{% endblocktrans %}

        {% include 'snippets/book_tiles.html' with books=editions %}
        diff --git a/bookwyrm/templates/error.html b/bookwyrm/templates/error.html index fbf96ba2..99a9a624 100644 --- a/bookwyrm/templates/error.html +++ b/bookwyrm/templates/error.html @@ -1,4 +1,5 @@ {% extends 'layout.html' %} +{% load i18n %} {% block content %}
        diff --git a/bookwyrm/templates/feed/direct_messages.html b/bookwyrm/templates/feed/direct_messages.html index f3102ee7..12eaeb0b 100644 --- a/bookwyrm/templates/feed/direct_messages.html +++ b/bookwyrm/templates/feed/direct_messages.html @@ -1,9 +1,10 @@ {% extends 'feed/feed_layout.html' %} +{% load i18n %} {% block panel %}

        Direct Messages{% if partner %} with {% include 'snippets/username.html' with user=partner %}{% endif %}

        - {% if partner %}

        All messages

        {% endif %} + {% if partner %}

        {% trans "All messages" %}

        {% endif %}
        @@ -12,7 +13,7 @@
        {% if not activities %} -

        You have no messages right now.

        +

        {% trans "You have no messages right now." %}

        {% endif %} {% for activity in activities %}
        diff --git a/bookwyrm/templates/feed/feed.html b/bookwyrm/templates/feed/feed.html index 7029fd69..71b59cc1 100644 --- a/bookwyrm/templates/feed/feed.html +++ b/bookwyrm/templates/feed/feed.html @@ -1,18 +1,19 @@ {% extends 'feed/feed_layout.html' %} +{% load i18n %} {% load bookwyrm_tags %} {% block panel %} -

        {{ tab | title }} Timeline

        +

        {% blocktrans with tab_title=tab|title %}{{ tab_title }} Timeline{% endblocktrans %}

        @@ -20,7 +21,7 @@ {# announcements and system messages #} {% if not goal and tab == 'home' %} {% now 'Y' as year %} -
        - Book + {% trans "Book" %} - Title + {% trans "Title" %} - Author + {% trans "Author" %} {% if item.book %} - Imported + {% trans "Imported" %} {% endif %}
        - - + + {% for item in pending %} @@ -31,13 +32,13 @@ {% csrf_token %} - + {% csrf_token %} - + diff --git a/bookwyrm/templates/lists/edit_form.html b/bookwyrm/templates/lists/edit_form.html index 55723b72..917e5e2a 100644 --- a/bookwyrm/templates/lists/edit_form.html +++ b/bookwyrm/templates/lists/edit_form.html @@ -1,7 +1,8 @@ {% extends 'components/inline_form.html' %} +{% load i18n %} {% block header %} -Edit List +{% trans "Edit List" %} {% endblock %} {% block form %} diff --git a/bookwyrm/templates/lists/form.html b/bookwyrm/templates/lists/form.html index ec265cc1..e5eb9c37 100644 --- a/bookwyrm/templates/lists/form.html +++ b/bookwyrm/templates/lists/form.html @@ -1,34 +1,35 @@ +{% load i18n %} {% csrf_token %}
        - + {{ list_form.name }}
        - + {{ list_form.description }}
        - List curation: + {% trans "List curation:" %}
        @@ -38,7 +39,7 @@ {% include 'snippets/privacy_select.html' with current=list.privacy %}
        - +
        diff --git a/bookwyrm/templates/lists/list.html b/bookwyrm/templates/lists/list.html index c5aef606..42d99d8a 100644 --- a/bookwyrm/templates/lists/list.html +++ b/bookwyrm/templates/lists/list.html @@ -1,4 +1,5 @@ {% extends 'lists/list_layout.html' %} +{% load i18n %} {% load bookwyrm_tags %} {% block panel %} @@ -13,7 +14,7 @@
        {% if not items.exists %} -

        This list is currently empty

        +

        {% trans "This list is currently empty" %}

        {% else %}
          {% for item in items %} @@ -31,13 +32,13 @@
        @@ -50,26 +51,29 @@ {% if request.user.is_authenticated and not list.curation == 'closed' or request.user == list.user %}
        -

        {% if list.curation == 'open' or request.user == list.user %}Add{% else %}Suggest{% endif %} Books

        +

        {% if list.curation == 'open' or request.user == list.user %}{% trans "Add Books" %}{% else %}{% trans "Suggest Books" %}{% endif %}

        - +
        {% if query %} -

        Clear search

        +

        {% trans "Clear search" %}

        {% endif %} {% if not suggested_books %} -

        No books found{% if query %} matching the query "{{ query }}"{% endif %}

        + {% if query %} +

        {% blocktrans %}No books found matching the query "{{ query }}"{% endblocktrans %}

        {% else %} +

        {% trans "No books found" %}

        + {% endif %} {% endif %} {% for book in suggested_books %} {% if book %} @@ -82,7 +86,7 @@
        {% csrf_token %} - + diff --git a/bookwyrm/templates/lists/list_items.html b/bookwyrm/templates/lists/list_items.html index a487bbd6..646a910f 100644 --- a/bookwyrm/templates/lists/list_items.html +++ b/bookwyrm/templates/lists/list_items.html @@ -1,4 +1,5 @@ {% load bookwyrm_tags %} +{% load i18n %}
        {% for list in lists %}
        @@ -15,7 +16,7 @@
        {% if list.description %}{{ list.description | to_markdown | safe | truncatewords_html:20 }}{% endif %} -

        Created {% if list.curation != 'open' %} and curated{% endif %} by {% include 'snippets/username.html' with user=list.user %}

        +

        {% if list.curation != 'open' %}{% trans "Created and curated by" %}{% else %}{% trans "Created by" %}{% endif %} {% include 'snippets/username.html' with user=list.user %}

        diff --git a/bookwyrm/templates/lists/list_layout.html b/bookwyrm/templates/lists/list_layout.html index f5855f27..8e2e36e6 100644 --- a/bookwyrm/templates/lists/list_layout.html +++ b/bookwyrm/templates/lists/list_layout.html @@ -1,11 +1,13 @@ {% extends 'layout.html' %} +{% load i18n %} {% load bookwyrm_tags %} {% block content %}

        {{ list.name }} {% include 'snippets/privacy-icons.html' with item=list %}

        -

        Created {% if list.curation != 'open' %} and curated{% endif %} by {% include 'snippets/username.html' with user=list.user %}

        +

        {% if list.curation != 'open' %}{% trans "Created and curated by" %}{% else %}{% trans "Created by" %} {% include 'snippets/username.html' with user=list.user %}

        + {% endif %} {% include 'snippets/trimmed_text.html' with full=list.description %}
        {% if request.user == list.user %} diff --git a/bookwyrm/templates/lists/lists.html b/bookwyrm/templates/lists/lists.html index 259d0820..07032676 100644 --- a/bookwyrm/templates/lists/lists.html +++ b/bookwyrm/templates/lists/lists.html @@ -1,13 +1,14 @@ {% extends 'layout.html' %} +{% load i18n %} {% block content %}
        -

        Lists

        +

        {% trans "Lists" %}

        {% if request.user.is_authenticated and not lists.has_previous %}
        -

        Your lists

        +

        {% trans "Your lists" %}

        {% include 'snippets/toggle/open_button.html' with controls_text="create-list" icon="plus" text="Create new list" focus="create-list-header" %} @@ -32,7 +33,7 @@ {% if lists %}
        -

        Recent Lists

        +

        {% trans "Recent Lists" %}

        {% include 'lists/list_items.html' with lists=lists %}
        diff --git a/bookwyrm/templates/login.html b/bookwyrm/templates/login.html index 104e8ef7..1429f412 100644 --- a/bookwyrm/templates/login.html +++ b/bookwyrm/templates/login.html @@ -1,4 +1,5 @@ {% extends 'layout.html' %} +{% load i18n %} {% block content %}
        diff --git a/bookwyrm/templates/notfound.html b/bookwyrm/templates/notfound.html index 73fe54c2..bb419cad 100644 --- a/bookwyrm/templates/notfound.html +++ b/bookwyrm/templates/notfound.html @@ -1,9 +1,10 @@ {% extends 'layout.html' %} +{% load i18n %} {% block content %}
        -

        Not Found

        -

        The page your requested doesn't seem to exist!

        +

        {% trans "Not Found" %}

        +

        {% trans "The page your requested doesn't seem to exist!" %}

        {% endblock %} diff --git a/bookwyrm/templates/notifications.html b/bookwyrm/templates/notifications.html index 5c009cba..20d9f254 100644 --- a/bookwyrm/templates/notifications.html +++ b/bookwyrm/templates/notifications.html @@ -42,32 +42,41 @@ {% include 'snippets/avatar.html' with user=notification.related_user %} {% include 'snippets/username.html' with user=notification.related_user %} {% if notification.notification_type == 'FAVORITE' %} + {% blocktrans with preview_name=related_status|status_preview_name|safe related_path=related_status.local_path %} favorited your - {{ related_status | status_preview_name|safe }} + {{ preview_name }} + {% endblocktrans %} {% elif notification.notification_type == 'MENTION' %} + {% blocktrans with preview_name=related_status|status_preview_name|safe related_path=related_status.local_path %} mentioned you in a - {{ related_status | status_preview_name|safe }} + {{ preview_name }} + {% endblocktrans %} {% elif notification.notification_type == 'REPLY' %} - replied + {% blocktrans with preview_name=related_status|status_preview_name|safe related_path=related_status.local_path parent_path=related_status.reply_parent.local_path %} + replied to your - {{ related_status | status_preview_name|safe }} + {{ preview_name }} + {% endblocktrans %} + {% elif notification.notification_type == 'FOLLOW' %} - followed you + {% trans "followed you" %} {% include 'snippets/follow_button.html' with user=notification.related_user %} {% elif notification.notification_type == 'FOLLOW_REQUEST' %} - sent you a follow request + {% trans "sent you a follow request" %}
        {% include 'snippets/follow_request_buttons.html' with user=notification.related_user %}
        {% elif notification.notification_type == 'BOOST' %} - boosted your {{ related_status | status_preview_name|safe }} + {% blocktrans with preview_name=related_status|status_preview_name|safe related_path=related_status.local_path %} + boosted your {{ preview_name }} + {% endblocktrans %} {% elif notification.notification_type == 'ADD' %} - {% if notification.related_list_item.approved %}added{% else %}suggested adding{% endif %} {% include 'snippets/book_titleby.html' with book=notification.related_list_item.book %} to your list "{{ notification.related_list_item.book_list.name }}" + {% if notification.related_list_item.approved %}{% trans "added" %}{% else %}{% trans "suggested adding" %}{% endif %} {% include 'snippets/book_titleby.html' with book=notification.related_list_item.book %} to your list "{{ notification.related_list_item.book_list.name }}" {% endif %} {% elif notification.related_import %} - your import completed. + {% blocktrans with related_id=notification.related_import.id %} your import completed.{% endblocktrans %} {% endif %}

        diff --git a/bookwyrm/templates/password_reset.html b/bookwyrm/templates/password_reset.html index e8da6562..656e2dff 100644 --- a/bookwyrm/templates/password_reset.html +++ b/bookwyrm/templates/password_reset.html @@ -1,30 +1,31 @@ {% extends 'layout.html' %} +{% load i18n %} {% block content %}
        -

        Reset Password

        +

        {% trans "Reset Password" %}

        {% for error in errors %}

        {{ error }}

        {% endfor %}
        {% csrf_token %}
        - +
        - +
        - +
        @@ -36,7 +37,6 @@ {% include 'snippets/about.html' %}
        -
        {% endblock %} diff --git a/bookwyrm/templates/password_reset_request.html b/bookwyrm/templates/password_reset_request.html index 23eff657..87dc8e65 100644 --- a/bookwyrm/templates/password_reset_request.html +++ b/bookwyrm/templates/password_reset_request.html @@ -1,4 +1,5 @@ {% extends 'layout.html' %} +{% load i18n %} {% block content %}
        diff --git a/bookwyrm/templates/preferences/blocks.html b/bookwyrm/templates/preferences/blocks.html index 5fd29d25..e26a5229 100644 --- a/bookwyrm/templates/preferences/blocks.html +++ b/bookwyrm/templates/preferences/blocks.html @@ -1,12 +1,13 @@ {% extends 'preferences/preferences_layout.html' %} +{% load i18n %} {% block header %} -Blocked Users +{% trans "Blocked Users" %} {% endblock %} {% block panel %} {% if not request.user.blocks.exists %} -

        No users currently blocked.

        +

        {% trans "No users currently blocked." %}

        {% else %}
          {% for user in request.user.blocks.all %} diff --git a/bookwyrm/templates/preferences/change_password.html b/bookwyrm/templates/preferences/change_password.html index b1b2a049..46707946 100644 --- a/bookwyrm/templates/preferences/change_password.html +++ b/bookwyrm/templates/preferences/change_password.html @@ -1,19 +1,20 @@ {% extends 'preferences/preferences_layout.html' %} +{% load i18n %} {% block header %} -Change Password +{% trans "Change Password" %} {% endblock %} {% block panel %}
          {% csrf_token %}
          - +
          - +
          - + {% endblock %} diff --git a/bookwyrm/templates/preferences/edit_user.html b/bookwyrm/templates/preferences/edit_user.html index 798d2fcb..d5b76c07 100644 --- a/bookwyrm/templates/preferences/edit_user.html +++ b/bookwyrm/templates/preferences/edit_user.html @@ -1,6 +1,7 @@ {% extends 'preferences/preferences_layout.html' %} +{% load i18n %} {% block header %} -Edit Profile +{% trans "Edit Profile" %} {% endblock %} {% block panel %} @@ -10,28 +11,28 @@ Edit Profile
          {% csrf_token %}
          - + {{ form.avatar }} {% for error in form.avatar.errors %}

          {{ error | escape }}

          {% endfor %}
          - + {{ form.name }} {% for error in form.name.errors %}

          {{ error | escape }}

          {% endfor %}
          - + {{ form.summary }} {% for error in form.summary.errors %}

          {{ error | escape }}

          {% endfor %}
          - + {{ form.email }} {% for error in form.email.errors %}

          {{ error | escape }}

          @@ -39,10 +40,10 @@ Edit Profile
          - + {% endblock %} diff --git a/bookwyrm/templates/preferences/preferences_layout.html b/bookwyrm/templates/preferences/preferences_layout.html index b0e7b31a..c0f78f95 100644 --- a/bookwyrm/templates/preferences/preferences_layout.html +++ b/bookwyrm/templates/preferences/preferences_layout.html @@ -1,4 +1,5 @@ {% extends 'layout.html' %} +{% load i18n %} {% block content %}
          @@ -7,19 +8,19 @@
          diff --git a/bookwyrm/templates/search_results.html b/bookwyrm/templates/search_results.html index 8bd8253d..30c88f3d 100644 --- a/bookwyrm/templates/search_results.html +++ b/bookwyrm/templates/search_results.html @@ -1,8 +1,9 @@ {% extends 'layout.html' %} +{% load i18n %} {% block content %} {% with book_results|first as local_results %}
          -

          Search Results for "{{ query }}"

          +

          {% blocktrans %}Search Results for "{{ query }}"{% endblocktrans %}

          diff --git a/bookwyrm/templates/settings/admin_layout.html b/bookwyrm/templates/settings/admin_layout.html index 59cfd1d4..73178ac8 100644 --- a/bookwyrm/templates/settings/admin_layout.html +++ b/bookwyrm/templates/settings/admin_layout.html @@ -1,4 +1,5 @@ {% extends 'layout.html' %} +{% load i18n %} {% block content %}
          @@ -8,30 +9,30 @@
        BookSuggested by{% trans "Book" %}{% trans "Suggested by" %}
        - - - + + + {% for server in servers %} diff --git a/bookwyrm/templates/settings/manage_invites.html b/bookwyrm/templates/settings/manage_invites.html index 086615a9..6bc7469b 100644 --- a/bookwyrm/templates/settings/manage_invites.html +++ b/bookwyrm/templates/settings/manage_invites.html @@ -1,41 +1,42 @@ {% extends 'settings/admin_layout.html' %} -{% block header %}Invites{% endblock %} +{% load i18n %} +{% block header %}{% trans "Invites" %}{% endblock %} {% load humanize %} {% block panel %}
        -

        Generate New Invite

        +

        {% trans "Generate New Invite" %}

        {% csrf_token %}
        - +
        {{ form.expiry }}
        - +
        {{ form.use_limit }}
        - +
        Server nameSoftwareStatus{% trans "Server name" %}{% trans "Software" %}{% trans "Status" %}
        - - - - + + + + {% if not invites %} - + {% endif %} {% for invite in invites %} diff --git a/bookwyrm/templates/settings/site.html b/bookwyrm/templates/settings/site.html index 9e67c839..1a502f16 100644 --- a/bookwyrm/templates/settings/site.html +++ b/bookwyrm/templates/settings/site.html @@ -1,30 +1,31 @@ {% extends 'settings/admin_layout.html' %} -{% block header %}Site Configuration{% endblock %} +{% load i18n %} +{% block header %}{% trans "Site Configuration" %}{% endblock %} {% block panel %} {% csrf_token %}
        -

        Instance Info

        +

        {% trans "Instance Info" %}

        - + {{ site_form.name }}
        - + {{ site_form.instance_tagline }}
        - + {{ site_form.instance_description }}
        - + {{ site_form.code_of_conduct }}
        - + {{ site_form.privacy_policy }}
        @@ -32,18 +33,18 @@
        -

        Images

        +

        {% trans "Images" %}

        - + {{ site_form.logo }}
        - + {{ site_form.logo_small }}
        - + {{ site_form.favicon }}
        @@ -52,17 +53,17 @@ @@ -70,19 +71,19 @@
        -

        Registration

        +

        {% trans "Registration" %}

        -
        - + {{ site_form.registration_closed_text }}
        - +
        {% endblock %} diff --git a/bookwyrm/templates/snippets/block_button.html b/bookwyrm/templates/snippets/block_button.html index 9e49254d..7e9fcaea 100644 --- a/bookwyrm/templates/snippets/block_button.html +++ b/bookwyrm/templates/snippets/block_button.html @@ -1,11 +1,12 @@ +{% load i18n %} {% if not user in request.user.blocks.all %}
        {% csrf_token %} - + {% else %}
        {% csrf_token %} - + {% endif %} diff --git a/bookwyrm/templates/snippets/boost_button.html b/bookwyrm/templates/snippets/boost_button.html index bf914379..3bc1b601 100644 --- a/bookwyrm/templates/snippets/boost_button.html +++ b/bookwyrm/templates/snippets/boost_button.html @@ -1,19 +1,20 @@ {% load bookwyrm_tags %} +{% load i18n %} {% with status.id|uuid as uuid %}
        {% csrf_token %}
        {% csrf_token %} diff --git a/bookwyrm/templates/snippets/content_warning_field.html b/bookwyrm/templates/snippets/content_warning_field.html index 3f14a020..c90138e9 100644 --- a/bookwyrm/templates/snippets/content_warning_field.html +++ b/bookwyrm/templates/snippets/content_warning_field.html @@ -1,4 +1,5 @@ +{% load i18n %}
        - - + +
        diff --git a/bookwyrm/templates/snippets/create_status.html b/bookwyrm/templates/snippets/create_status.html index b443acdd..a4b4578f 100644 --- a/bookwyrm/templates/snippets/create_status.html +++ b/bookwyrm/templates/snippets/create_status.html @@ -1,16 +1,17 @@ {% load humanize %} +{% load i18n %} {% load bookwyrm_tags %} diff --git a/bookwyrm/templates/snippets/create_status_form.html b/bookwyrm/templates/snippets/create_status_form.html index c6d7be3f..b5a12084 100644 --- a/bookwyrm/templates/snippets/create_status_form.html +++ b/bookwyrm/templates/snippets/create_status_form.html @@ -1,4 +1,5 @@ {% load bookwyrm_tags %} +{% load i18n %}
        {% csrf_token %} @@ -6,7 +7,7 @@ {% if type == 'review' %}
        - +
        {% endif %} @@ -17,9 +18,9 @@ {% if type == 'review' %}
        - Rating + {% trans "Rating" %}
        - + {% for i in '12345'|make_list %} @@ -40,7 +41,7 @@
        {% if type == 'quotation' %}
        - + {% include 'snippets/content_warning_field.html' with parent_status=status %}
        @@ -55,14 +56,14 @@
        {% if type == 'direct' %} - + {% else %} {% include 'snippets/privacy_select.html' with current=reply_parent.privacy %} {% endif %}
        - +
        diff --git a/bookwyrm/templates/snippets/delete_readthrough_modal.html b/bookwyrm/templates/snippets/delete_readthrough_modal.html index 19c4b4f0..557059ed 100644 --- a/bookwyrm/templates/snippets/delete_readthrough_modal.html +++ b/bookwyrm/templates/snippets/delete_readthrough_modal.html @@ -1,8 +1,10 @@ {% extends 'components/modal.html' %} -{% block modal-title %}Delete these read dates?{% endblock %} +{% load i18n %} + +{% block modal-title %}{% trans "Delete these read dates?" %}{% endblock %} {% block modal-body %} {% if readthrough.progress_updates|length > 0 %} -You are deleting this readthrough and its {{ readthrough.progress_updates|length }} associated progress updates. +{% blocktrans with count=readthrough.progress_updates|length %}You are deleting this readthrough and its {{ count }} associated progress updates.{% endblocktrans %} {% endif %} {% endblock %} {% block modal-footer %} @@ -10,7 +12,7 @@ You are deleting this readthrough and its {{ readthrough.progress_updates|length {% csrf_token %} {% include 'snippets/toggle/toggle_button.html' with text="Cancel" controls_text="delete-readthrough" controls_uid=readthrough.id %} diff --git a/bookwyrm/templates/snippets/fav_button.html b/bookwyrm/templates/snippets/fav_button.html index 650f4e8a..5d74f655 100644 --- a/bookwyrm/templates/snippets/fav_button.html +++ b/bookwyrm/templates/snippets/fav_button.html @@ -1,18 +1,19 @@ {% load bookwyrm_tags %} +{% load i18n %} {% with status.id|uuid as uuid %}
        {% csrf_token %}
        {% csrf_token %} diff --git a/bookwyrm/templates/snippets/follow_button.html b/bookwyrm/templates/snippets/follow_button.html index f7286e67..419aa211 100644 --- a/bookwyrm/templates/snippets/follow_button.html +++ b/bookwyrm/templates/snippets/follow_button.html @@ -1,8 +1,9 @@ +{% load i18n %} {% if request.user == user or not request.user.is_authenticated %} {% elif request.user in user.follower_requests.all %}
        -Follow request already sent. + {% trans "Follow request already sent." %}
        {% elif user in request.user.blocks.all %} @@ -15,15 +16,15 @@ Follow request already sent. {% csrf_token %} {% if user.manually_approves_followers %} - + {% else %} - + {% endif %} {% csrf_token %} - +
        diff --git a/bookwyrm/templates/snippets/follow_request_buttons.html b/bookwyrm/templates/snippets/follow_request_buttons.html index a6f585c7..42e69153 100644 --- a/bookwyrm/templates/snippets/follow_request_buttons.html +++ b/bookwyrm/templates/snippets/follow_request_buttons.html @@ -1,15 +1,16 @@ +{% load i18n %} {% load bookwyrm_tags %} {% if request.user|follow_request_exists:user %}
        {% csrf_token %} - +
        {% csrf_token %} - +
        {% endif %} diff --git a/bookwyrm/templates/snippets/goal_card.html b/bookwyrm/templates/snippets/goal_card.html index e26cd5a3..386c030c 100644 --- a/bookwyrm/templates/snippets/goal_card.html +++ b/bookwyrm/templates/snippets/goal_card.html @@ -1,15 +1,16 @@ {% extends 'components/card.html' %} +{% load i18n %} {% block card-header %}

        - {{ year }} reading goal + {% blocktrans %}{{ year }} reading goal{% endblocktrans %}

        {% endblock %} {% block card-content %}
        -

        Set a goal for how many books you'll finish reading in {{ year }}, and track your progress throughout the year.

        +

        {% blocktrans %}Set a goal for how many books you'll finish reading in {{ year }}, and track your progress throughout the year.{% endblocktrans %}

        {% include 'snippets/goal_form.html' %}
        @@ -17,7 +18,7 @@ {% block card-footer %} {% endblock %} diff --git a/bookwyrm/templates/snippets/goal_form.html b/bookwyrm/templates/snippets/goal_form.html index 475c5da5..cf5d21f2 100644 --- a/bookwyrm/templates/snippets/goal_form.html +++ b/bookwyrm/templates/snippets/goal_form.html @@ -1,3 +1,4 @@ +{% load i18n %}
        {% csrf_token %} @@ -5,28 +6,28 @@
        - +
        - +
        -

        - + {% if goal %} {% include 'snippets/toggle/close_button.html' with text="Cancel" controls_text="show-edit-goal" %} {% endif %} diff --git a/bookwyrm/templates/snippets/goal_progress.html b/bookwyrm/templates/snippets/goal_progress.html index 43f27f4e..2fa0da54 100644 --- a/bookwyrm/templates/snippets/goal_progress.html +++ b/bookwyrm/templates/snippets/goal_progress.html @@ -1,9 +1,10 @@ +{% load i18n %} {% load humanize %}

        {% if goal.progress_percent >= 100 %} - Success! + {% trans "Success!" %} {% elif goal.progress_percent %} - {{ goal.progress_percent }}% complete! + {% blocktrans with percent=goal.percent %}{{ percent }}% complete!{% endblocktrans %} {% endif %} {% if goal.user == request.user %}You've{% else %}{{ goal.user.display_name }} has{% endif %} read {% if request.path != goal.local_path %}{% endif %}{{ goal.book_count }} of {{ goal.goal | intcomma }} books{% if request.path != goal.local_path %}{% endif %}.

        diff --git a/bookwyrm/templates/snippets/pagination.html b/bookwyrm/templates/snippets/pagination.html index 1855dfed..c9c73ac3 100644 --- a/bookwyrm/templates/snippets/pagination.html +++ b/bookwyrm/templates/snippets/pagination.html @@ -1,9 +1,10 @@ +{% load i18n %}
        LinkExpiresMax usesTimes used{% trans "Link" %}{% trans "Expires" %}{% trans "Max uses" %}{% trans "Times used" %}
        No active invites
        {% trans "No active invites" %}
        - - - - - - - - - {% if ratings %} - - {% endif %} + + + + + + + + {% if ratings %} + {% endif %} {% for book in books %} @@ -60,7 +41,7 @@ {{ read_through.finish_date | naturalday |default_if_none:""}} {% if ratings %}
        - Cover - - Title - - Author - - Published - - Shelved - - Started - - Finished - - External links - - Rating - {% trans "Cover" %}{% trans "Title" %}{% trans "Author" %}{% trans "Published" %}{% trans "Shelved" %}{% trans "Started" %}{% trans "Finished" %}{% trans "External links" %}{% trans "Rating" %}
        - OpenLibrary + {% trans "OpenLibrary" %} @@ -77,13 +58,13 @@
        {% else %} -

        This shelf is empty.

        +

        {% trans "This shelf is empty." %}

        {% if shelf.editable %}
        {% csrf_token %}
        {% endif %} diff --git a/bookwyrm/templates/snippets/shelf_selector.html b/bookwyrm/templates/snippets/shelf_selector.html index 50d7e750..687f7a56 100644 --- a/bookwyrm/templates/snippets/shelf_selector.html +++ b/bookwyrm/templates/snippets/shelf_selector.html @@ -1,6 +1,7 @@ {% extends 'components/dropdown.html' %} +{% load i18n %} {% block dropdown-trigger %} -Change shelf +{% trans "Change shelf" %} {% endblock %} @@ -23,7 +24,7 @@ {% csrf_token %} - + {% endblock %} diff --git a/bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html b/bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html index c3407631..896c8016 100644 --- a/bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html +++ b/bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html @@ -1,7 +1,8 @@ {% extends 'components/modal.html' %} +{% load i18n %} {% block modal-title %} -Finish "{{ book.title }}" +{% blocktrans with book_title=book.title %}Finish "{{ book_title }}"{% endblocktrans %} {% endblock %} @@ -15,13 +16,13 @@ Finish "{{ book.title }}"
        @@ -33,12 +34,12 @@ Finish "{{ book.title }}"
        {% include 'snippets/privacy_select.html' %}
        - + {% include 'snippets/toggle/close_button.html' with text="Cancel" controls_text="finish-reading" controls_uid=uuid %}
      diff --git a/bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown.html b/bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown.html index 47cf3505..c3f325bf 100644 --- a/bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown.html +++ b/bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown.html @@ -1,7 +1,8 @@ {% extends 'components/dropdown.html' %} +{% load i18n %} {% block dropdown-trigger %} - More shelves + {% trans "More shelves" %} {% endblock %} diff --git a/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html b/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html index 2d7d9609..a1ef2c32 100644 --- a/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html +++ b/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html @@ -1,4 +1,5 @@ {% load bookwyrm_tags %} +{% load i18n %} {% for shelf in shelves %} {% comparison_bool shelf.identifier active_shelf.shelf.identifier as is_current %} {% if dropdown %}
    • {% endif %} @@ -6,7 +7,7 @@ {% if shelf.identifier == 'reading' %}{% if not dropdown or active_shelf.shelf.identifier|next_shelf != shelf.identifier %} {% include 'snippets/toggle/toggle_button.html' with class=class text="Start reading" controls_text="start-reading" controls_uid=button_uuid focus="modal-title-start-reading" disabled=is_current %} {% endif %}{% elif shelf.identifier == 'read' and active_shelf.shelf.identifier == 'read' %}{% if not dropdown or active_shelf.shelf.identifier|next_shelf != shelf.identifier %} - + {% include 'snippets/toggle/toggle_button.html' with text="Cancel" controls_text="start-reading" controls_uid=uuid %}

    diff --git a/bookwyrm/templates/snippets/shelve_button/want_to_read_modal.html b/bookwyrm/templates/snippets/shelve_button/want_to_read_modal.html index 9b7e2bde..179e004e 100644 --- a/bookwyrm/templates/snippets/shelve_button/want_to_read_modal.html +++ b/bookwyrm/templates/snippets/shelve_button/want_to_read_modal.html @@ -1,7 +1,8 @@ {% extends 'components/modal.html' %} +{% load i18n %} {% block modal-title %} -Want to Read "{{ book.title }}" +{% blocktrans with book_title=book.title %}Want to Read "{{ book_title }}"{% endblocktrans %} {% endblock %} {% block modal-form-open %} @@ -16,13 +17,13 @@ Want to Read "{{ book.title }}"
    {% include 'snippets/privacy_select.html' %}
    {% include 'snippets/toggle/toggle_button.html' with text="Cancel" controls_text="want-to-read" controls_uid=uuid %}
    diff --git a/bookwyrm/templates/snippets/stars.html b/bookwyrm/templates/snippets/stars.html index d1576807..f336a562 100644 --- a/bookwyrm/templates/snippets/stars.html +++ b/bookwyrm/templates/snippets/stars.html @@ -1,5 +1,6 @@ +{% load i18n %}

    - {% if rating %}{{ rating|floatformat }} star{{ rating|floatformat | pluralize }}{% else %}No rating{% endif %} +{% if rating %}{{ rating|floatformat }} star{{ rating|floatformat | pluralize }}{% else %}{% trans "No rating" %}{% endif %} {% for i in '12345'|make_list %} diff --git a/bookwyrm/templates/snippets/status/status.html b/bookwyrm/templates/snippets/status/status.html index 162fad97..b8925f18 100644 --- a/bookwyrm/templates/snippets/status/status.html +++ b/bookwyrm/templates/snippets/status/status.html @@ -1,9 +1,10 @@ {% load bookwyrm_tags %} +{% load i18n %} {% if not status.deleted %} {% if status.status_type == 'Boost' %} {% include 'snippets/avatar.html' with user=status.user %} {% include 'snippets/username.html' with user=status.user %} - boosted + {% trans "boosted" %} {% include 'snippets/status/status_body.html' with status=status|boosted_status %} {% else %} {% include 'snippets/status/status_body.html' with status=status %} diff --git a/bookwyrm/templates/snippets/status/status_body.html b/bookwyrm/templates/snippets/status/status_body.html index dfc0ac68..1bf0d3c7 100644 --- a/bookwyrm/templates/snippets/status/status_body.html +++ b/bookwyrm/templates/snippets/status/status_body.html @@ -1,4 +1,5 @@ {% extends 'components/card.html' %} +{% load i18n %} {% load bookwyrm_tags %} {% load humanize %} @@ -32,16 +33,16 @@ {% else %} - - Reply + + {% trans "Reply" %} - - Boost status + + {% trans "Boost status" %} - - Like status + + {% trans "Like status" %} {% endif %} diff --git a/bookwyrm/templates/snippets/status/status_content.html b/bookwyrm/templates/snippets/status/status_content.html index 0f59f7fc..b48566b0 100644 --- a/bookwyrm/templates/snippets/status/status_content.html +++ b/bookwyrm/templates/snippets/status/status_content.html @@ -1,4 +1,5 @@ {% load bookwyrm_tags %} +{% load i18n %}

    {% if status.status_type == 'Review' %}
    @@ -38,7 +39,7 @@ {% for attachment in status.attachments.all %}
    - + {{ attachment.caption }}
    diff --git a/bookwyrm/templates/snippets/status/status_header.html b/bookwyrm/templates/snippets/status/status_header.html index 890b4ab8..2b941820 100644 --- a/bookwyrm/templates/snippets/status/status_header.html +++ b/bookwyrm/templates/snippets/status/status_header.html @@ -1,17 +1,18 @@ {% load bookwyrm_tags %} +{% load i18n %} {% include 'snippets/avatar.html' with user=status.user %} {% include 'snippets/username.html' with user=status.user %} {% if status.status_type == 'GeneratedNote' %} {{ status.content | safe }} {% elif status.status_type == 'Review' and not status.name and not status.content%} - rated + {% trans "rated" %} {% elif status.status_type == 'Review' %} - reviewed + {% trans "reviewed" %} {% elif status.status_type == 'Comment' %} - commented on + {% trans "commented on" %} {% elif status.status_type == 'Quotation' %} - quoted + {% trans "quoted" %} {% elif status.reply_parent %} {% with parent_status=status|parent %} replied to {% include 'snippets/username.html' with user=parent_status.user possessive=True %} {% if parent_status.status_type == 'GeneratedNote' %}update{% else %}{{ parent_status.status_type | lower }}{% endif %} diff --git a/bookwyrm/templates/snippets/status/status_options.html b/bookwyrm/templates/snippets/status/status_options.html index 3bf8251f..735ff51c 100644 --- a/bookwyrm/templates/snippets/status/status_options.html +++ b/bookwyrm/templates/snippets/status/status_options.html @@ -1,9 +1,10 @@ {% extends 'components/dropdown.html' %} +{% load i18n %} {% load bookwyrm_tags %} {% block dropdown-trigger %} - More options + {% trans "More options" %} {% endblock %} @@ -13,13 +14,13 @@
  • {% else %}
  • - Send direct message + {% trans "Send direct message" %}
  • {% include 'snippets/block_button.html' with user=status.user class="is-fullwidth" %} diff --git a/bookwyrm/templates/snippets/switch_edition_button.html b/bookwyrm/templates/snippets/switch_edition_button.html index 86c438b1..4818166e 100644 --- a/bookwyrm/templates/snippets/switch_edition_button.html +++ b/bookwyrm/templates/snippets/switch_edition_button.html @@ -1,5 +1,6 @@ +{% load i18n %}
    {% csrf_token %} - +
    diff --git a/bookwyrm/templates/snippets/tag.html b/bookwyrm/templates/snippets/tag.html index 9a56f4ec..507def72 100644 --- a/bookwyrm/templates/snippets/tag.html +++ b/bookwyrm/templates/snippets/tag.html @@ -1,3 +1,4 @@ +{% load i18n %}
    {% csrf_token %} @@ -10,11 +11,11 @@ {% if tag.tag.identifier in user_tags %} {% else %} {% endif %}
    diff --git a/bookwyrm/templates/snippets/user_options.html b/bookwyrm/templates/snippets/user_options.html index bc54ca1c..b3f66782 100644 --- a/bookwyrm/templates/snippets/user_options.html +++ b/bookwyrm/templates/snippets/user_options.html @@ -1,15 +1,16 @@ {% extends 'components/dropdown.html' %} +{% load i18n %} {% load bookwyrm_tags %} {% block dropdown-trigger %} - More options + {% trans "More options" %} {% endblock %} {% block dropdown-list %}
  • - Send direct message + {% trans "Send direct message" %}
  • {% include 'snippets/block_button.html' with user=user class="is-fullwidth" %} diff --git a/bookwyrm/templates/tag.html b/bookwyrm/templates/tag.html index 96fbcf91..f30b7395 100644 --- a/bookwyrm/templates/tag.html +++ b/bookwyrm/templates/tag.html @@ -1,4 +1,5 @@ {% extends 'layout.html' %} +{% load i18n %} {% load bookwyrm_tags %} {% block content %} diff --git a/bookwyrm/templates/user/create_shelf_form.html b/bookwyrm/templates/user/create_shelf_form.html index 89f41fd8..785c8d06 100644 --- a/bookwyrm/templates/user/create_shelf_form.html +++ b/bookwyrm/templates/user/create_shelf_form.html @@ -1,7 +1,8 @@ {% extends 'components/inline_form.html' %} +{% load i18n %} {% block header %} -Create New Shelf +{% trans "Create New Shelf" %} {% endblock %} {% block form %} @@ -9,7 +10,7 @@ Create New Shelf {% csrf_token %}
    - +
    @@ -18,7 +19,7 @@ Create New Shelf {% include 'snippets/privacy_select.html' %}
    - +
    diff --git a/bookwyrm/templates/user/edit_shelf_form.html b/bookwyrm/templates/user/edit_shelf_form.html index b179be9f..a9f86da4 100644 --- a/bookwyrm/templates/user/edit_shelf_form.html +++ b/bookwyrm/templates/user/edit_shelf_form.html @@ -1,7 +1,8 @@ {% extends 'components/inline_form.html' %} +{% load i18n %} {% block header %} -Edit Shelf +{% trans "Edit Shelf" %} {% endblock %} {% block form %} @@ -10,7 +11,7 @@ Edit Shelf {% if shelf.editable %}
    - +
    {% else %} @@ -22,7 +23,7 @@ Edit Shelf {% include 'snippets/privacy_select.html' with current=shelf.privacy %}
    - +
    diff --git a/bookwyrm/templates/user/followers.html b/bookwyrm/templates/user/followers.html index da6fc320..16ec7d4f 100644 --- a/bookwyrm/templates/user/followers.html +++ b/bookwyrm/templates/user/followers.html @@ -1,4 +1,5 @@ {% extends 'user/user_layout.html' %} +{% load i18n %} {% load bookwyrm_tags %} {% block header %} @@ -13,7 +14,7 @@ {% block panel %}
    -

    Followers

    +

    {% trans "Followers" %}

    {% for followers in followers %}
    @@ -26,7 +27,7 @@
    {% endfor %} {% if not followers.count %} -
    {{ user|username }} has no followers
    +
    {% blocktrans with username=user|username %}{{ username }} has no followers{% endblocktrans %}
    {% endif %}
    {% endblock %} diff --git a/bookwyrm/templates/user/following.html b/bookwyrm/templates/user/following.html index d734b0dc..13af4417 100644 --- a/bookwyrm/templates/user/following.html +++ b/bookwyrm/templates/user/following.html @@ -1,4 +1,5 @@ {% extends 'user/user_layout.html' %} +{% load i18n %} {% load bookwyrm_tags %} {% block header %} @@ -13,7 +14,7 @@ {% block panel %}
    -

    Following

    +

    {% trans "Following" %}

    {% for follower in user.following.all %}
    @@ -26,7 +27,7 @@
    {% endfor %} {% if not following.count %} -
    {{ user|username }} isn't following any users
    +
    {% blocktrans with username=user|username %}{{ username }} isn't following any users{% endblocktrans %}
    {% endif %}
    {% endblock %} diff --git a/bookwyrm/templates/user/lists.html b/bookwyrm/templates/user/lists.html index 45e4806f..6edf4430 100644 --- a/bookwyrm/templates/user/lists.html +++ b/bookwyrm/templates/user/lists.html @@ -1,4 +1,5 @@ {% extends 'user/user_layout.html' %} +{% load i18n %} {% block header %}
    @@ -24,7 +25,7 @@
    - +
    {% endblock %} diff --git a/locale/en_US/LC_MESSAGES/django.po b/locale/en_US/LC_MESSAGES/django.po index b849b903..4d140e52 100644 --- a/locale/en_US/LC_MESSAGES/django.po +++ b/locale/en_US/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.1.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-02-27 18:32-0800\n" +"POT-Creation-Date: 2021-02-28 08:42-0800\n" "PO-Revision-Date: 2021-02-27 13:50+PST\n" "Last-Translator: Mouse Reeve \n" "Language-Team: Mouse Reeve \n" @@ -67,6 +67,7 @@ msgstr "" #: bookwyrm/templates/book.html:106 bookwyrm/templates/edit_author.html:75 #: bookwyrm/templates/edit_book.html:117 bookwyrm/templates/lists/form.html:42 #: bookwyrm/templates/preferences/edit_user.html:47 +#: bookwyrm/templates/settings/site.html:86 #: bookwyrm/templates/snippets/progress_update.html:21 #: bookwyrm/templates/snippets/readthrough.html:61 #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:42 @@ -381,11 +382,11 @@ msgid "Import Status" msgstr "" #: bookwyrm/templates/import_status.html:10 -msgid "Import started: " +msgid "Import started:" msgstr "" #: bookwyrm/templates/import_status.html:14 -msgid "Import completed: " +msgid "Import completed:" msgstr "" #: bookwyrm/templates/import_status.html:17 @@ -447,14 +448,11 @@ msgstr "" msgid "Sorry! This invite code is no longer valid." msgstr "" -#: bookwyrm/templates/layout.html:37 bookwyrm/templates/lists/list.html:62 +#: bookwyrm/templates/layout.html:37 bookwyrm/templates/layout.html:38 +#: bookwyrm/templates/lists/list.html:62 msgid "Search" msgstr "" -#: bookwyrm/templates/layout.html:38 bookwyrm/templates/lists/list.html:63 -msgid "search" -msgstr "" - #: bookwyrm/templates/layout.html:47 bookwyrm/templates/layout.html:48 msgid "Main navigation menu" msgstr "" @@ -575,6 +573,10 @@ msgstr "" msgid "Search for a book" msgstr "" +#: bookwyrm/templates/lists/list.html:63 +msgid "search" +msgstr "" + #: bookwyrm/templates/lists/list.html:69 msgid "Clear search" msgstr "" @@ -641,66 +643,48 @@ msgstr "" #: bookwyrm/templates/notifications.html:45 #, python-format -msgid "" -"\n" -" favorited your\n" -" " -"%(preview_name)s\n" -" " +msgid "favorited your %(preview_name)s" +msgstr "" + +#: bookwyrm/templates/notifications.html:48 +#, python-format +msgid "mentioned you in a %(preview_name)s" msgstr "" #: bookwyrm/templates/notifications.html:51 #, python-format msgid "" -"\n" -" mentioned you in a\n" -" " -"%(preview_name)s\n" -" " +"replied to your " +"%(preview_name)s" msgstr "" -#: bookwyrm/templates/notifications.html:57 -#, python-format -msgid "" -"\n" -" replied\n" -" to your\n" -" " -"%(preview_name)s\n" -" " -msgstr "" - -#: bookwyrm/templates/notifications.html:64 +#: bookwyrm/templates/notifications.html:54 msgid "followed you" msgstr "" -#: bookwyrm/templates/notifications.html:67 +#: bookwyrm/templates/notifications.html:57 msgid "sent you a follow request" msgstr "" -#: bookwyrm/templates/notifications.html:72 +#: bookwyrm/templates/notifications.html:62 #, python-format -msgid "" -"\n" -" boosted your " -"%(preview_name)s\n" -" " +msgid "boosted your %(preview_name)s" msgstr "" -#: bookwyrm/templates/notifications.html:76 +#: bookwyrm/templates/notifications.html:64 msgid "added" msgstr "" -#: bookwyrm/templates/notifications.html:76 +#: bookwyrm/templates/notifications.html:64 msgid "suggested adding" msgstr "" -#: bookwyrm/templates/notifications.html:79 +#: bookwyrm/templates/notifications.html:67 #, python-format msgid " your import completed." msgstr "" -#: bookwyrm/templates/notifications.html:111 +#: bookwyrm/templates/notifications.html:99 msgid "You're all caught up!" msgstr "" @@ -968,10 +952,6 @@ msgstr "" msgid "Registration closed text:" msgstr "" -#: bookwyrm/templates/settings/site.html:86 -msgid "Save Changes" -msgstr "" - #: bookwyrm/templates/snippets/block_button.html:5 msgid "Block" msgstr "" From c48376854432f43ea5eaafc21c36ef15f5d3f5ec Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 28 Feb 2021 10:00:36 -0800 Subject: [PATCH 0113/1285] Moves titles into templates and adds i18n support --- bookwyrm/templates/author.html | 3 +++ bookwyrm/templates/book.html | 4 +++- bookwyrm/templates/discover/about.html | 4 ++-- bookwyrm/templates/discover/landing_layout.html | 4 +++- bookwyrm/templates/edit_author.html | 3 +++ bookwyrm/templates/edit_book.html | 3 +++ bookwyrm/templates/editions.html | 3 +++ bookwyrm/templates/error.html | 5 +++-- bookwyrm/templates/feed/direct_messages.html | 8 +++++++- bookwyrm/templates/feed/feed_layout.html | 5 +++-- bookwyrm/templates/feed/status.html | 1 + bookwyrm/templates/goal.html | 10 ++++++++-- bookwyrm/templates/import.html | 3 +++ bookwyrm/templates/import_status.html | 3 +++ bookwyrm/templates/invite.html | 3 +++ bookwyrm/templates/lists/list.html | 2 +- bookwyrm/templates/lists/list_layout.html | 4 +++- bookwyrm/templates/lists/lists.html | 3 +++ bookwyrm/templates/login.html | 4 +++- bookwyrm/templates/notfound.html | 5 +++-- bookwyrm/templates/notifications.html | 3 +++ bookwyrm/templates/password_reset.html | 4 +++- bookwyrm/templates/password_reset_request.html | 4 +++- bookwyrm/templates/preferences/blocks.html | 2 ++ bookwyrm/templates/preferences/change_password.html | 3 +++ bookwyrm/templates/preferences/edit_user.html | 3 +++ bookwyrm/templates/search_results.html | 3 +++ bookwyrm/templates/settings/federation.html | 3 +++ bookwyrm/templates/settings/site.html | 5 ++++- bookwyrm/templates/tag.html | 5 +++-- bookwyrm/templates/user/followers.html | 6 +----- bookwyrm/templates/user/following.html | 6 +----- bookwyrm/templates/user/lists.html | 6 +++--- bookwyrm/templates/user/shelf.html | 12 ++++++++---- bookwyrm/templates/user/user.html | 4 +++- bookwyrm/templates/user/user_layout.html | 2 ++ bookwyrm/views/authentication.py | 1 - bookwyrm/views/author.py | 3 --- bookwyrm/views/block.py | 3 +-- bookwyrm/views/books.py | 4 ---- bookwyrm/views/error.py | 6 ++---- bookwyrm/views/federation.py | 5 +---- bookwyrm/views/feed.py | 3 --- bookwyrm/views/goal.py | 2 -- bookwyrm/views/import_data.py | 2 -- bookwyrm/views/invite.py | 3 --- bookwyrm/views/landing.py | 6 +----- bookwyrm/views/list.py | 4 ---- bookwyrm/views/notifications.py | 1 - bookwyrm/views/password.py | 12 ++---------- bookwyrm/views/search.py | 1 - bookwyrm/views/shelf.py | 1 - bookwyrm/views/site.py | 10 ++-------- bookwyrm/views/tag.py | 1 - bookwyrm/views/user.py | 4 ---- 55 files changed, 121 insertions(+), 102 deletions(-) diff --git a/bookwyrm/templates/author.html b/bookwyrm/templates/author.html index 9dd83189..bc1034a8 100644 --- a/bookwyrm/templates/author.html +++ b/bookwyrm/templates/author.html @@ -1,6 +1,9 @@ {% extends 'layout.html' %} {% load i18n %} {% load bookwyrm_tags %} + +{% block title %}{{ author.name }}{% endblock %} + {% block content %}
    diff --git a/bookwyrm/templates/book.html b/bookwyrm/templates/book.html index 35ddba37..2280938b 100644 --- a/bookwyrm/templates/book.html +++ b/bookwyrm/templates/book.html @@ -2,8 +2,10 @@ {% load i18n %} {% load bookwyrm_tags %} {% load humanize %} -{% block content %} +{% block title %}{{ book.title }}{% endblock %} + +{% block content %}
    diff --git a/bookwyrm/templates/discover/about.html b/bookwyrm/templates/discover/about.html index dd0a8129..a6f3a026 100644 --- a/bookwyrm/templates/discover/about.html +++ b/bookwyrm/templates/discover/about.html @@ -1,10 +1,10 @@ {% extends 'discover/landing_layout.html' %} {% load i18n %} -{% block panel %} +{% block panel %}
    - + {% endblock %} diff --git a/bookwyrm/templates/preferences/preferences_layout.html b/bookwyrm/templates/preferences/preferences_layout.html index c0f78f95..d463d9cb 100644 --- a/bookwyrm/templates/preferences/preferences_layout.html +++ b/bookwyrm/templates/preferences/preferences_layout.html @@ -14,13 +14,13 @@ {% trans "Profile" %}
  • - {% trans "Change password" %} + {% trans "Change Password" %}
  • diff --git a/bookwyrm/templates/settings/admin_layout.html b/bookwyrm/templates/settings/admin_layout.html index 73178ac8..0c3efce7 100644 --- a/bookwyrm/templates/settings/admin_layout.html +++ b/bookwyrm/templates/settings/admin_layout.html @@ -26,7 +26,7 @@ -
      +
      {% for report in reports %} -
    • +
      {% include 'settings/report_preview.html' with report=report %} -
    • +
      {% endfor %} -
    + {% endblock %} From 59f1d567eb05f6159d0474be3d814b0cf68e9cb6 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 9 Mar 2021 10:35:35 -0800 Subject: [PATCH 0254/1285] Updates README on main for production changes --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 41bd7065..98ce9c76 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,6 @@ Instructions for running BookWyrm in production: - Comment out the `command: certonly...` line in `docker-compose.yml` - Run docker-compose in the background with: `docker-compose up -d` - Initialize the database with: `./bw-dev initdb` - - Set up schedule backups with cron that runs that `docker-compose exec db pg_dump -U ` and saves the backup to a safe location Congrats! You did it, go to your domain and enjoy the fruits of your labors. @@ -205,3 +204,16 @@ There are three concepts in the book data model: Whenever a user interacts with a book, they are interacting with a specific edition. Every work has a default edition, but the user can select other editions. Reviews aggregated for all editions of a work when you view an edition's page. +### Backups + +Bookwyrm's db service dumps a backup copy of its database to its `/backups` directory daily at midnight UTC. +Backups are named `backup__%Y-%m-%d.sql`. + +The db service has an optional script for periodically pruning the backups directory so that all recent daily backups are kept, but for older backups, only weekly or monthly backups are kept. +To enable this script: +- Uncomment the final line in `postgres-docker/cronfile` +- rebuild your instance `docker-compose up --build` + +You can copy backups from the backups volume to your host machine with `docker cp`: +- Run `docker-compose ps` to confirm the db service's full name (it's probably `bookwyrm_db_1`. +- Run `docker cp :/backups From 999bff4bbadb25f0e1a3015a4ece75bcc0559baa Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 9 Mar 2021 12:35:07 -0800 Subject: [PATCH 0255/1285] Basic reports admin templates --- bookwyrm/templates/settings/report.html | 41 +++++++++++++++++++ .../templates/settings/report_preview.html | 13 ++++-- bookwyrm/templates/settings/reports.html | 9 ++-- bookwyrm/views/reports.py | 2 +- 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/bookwyrm/templates/settings/report.html b/bookwyrm/templates/settings/report.html index 1f55906b..74c3641b 100644 --- a/bookwyrm/templates/settings/report.html +++ b/bookwyrm/templates/settings/report.html @@ -1,2 +1,43 @@ {% extends 'settings/admin_layout.html' %} {% load i18n %} + +{% block title %}{% blocktrans with report_id=report.id %}Report #{{ report_id }}{% endblocktrans %}{% endblock %} +{% block header %}{% blocktrans with report_id=report.id %}Report #{{ report_id }}{% endblocktrans %}{% endblock %} + +{% block panel %} + + +
    + {% include 'settings/report_preview.html' with report=report %} +
    + +
    +

    {% trans "Actions" %}

    +
    + + +
    + + {% for comment in report.reportcomment_set.all %} +
    + {{ comment }} +
    + {% endfor %} +
    + + + + +
    + +
    +

    {% trans "Reported statuses" %}

    +
      + {% for status in report.statuses.all %} +
    • {{ status.id }}
    • + {% endfor %} +
    +
    +{% endblock %} diff --git a/bookwyrm/templates/settings/report_preview.html b/bookwyrm/templates/settings/report_preview.html index 63eca0fc..25cb8a2f 100644 --- a/bookwyrm/templates/settings/report_preview.html +++ b/bookwyrm/templates/settings/report_preview.html @@ -1,19 +1,26 @@ {% extends 'components/card.html' %} {% load i18n %} +{% load humanize %} {% block card-header %}

    - report title + {{ report.user.username }}

    {% endblock %} {% block card-content %}
    - about this report + {% if report.notes %}{{ report.notes }}{% else %}{% trans "No notes provided" %}{% endif %}
    {% endblock %} {% block card-footer %} + + {% endblock %} diff --git a/bookwyrm/templates/settings/reports.html b/bookwyrm/templates/settings/reports.html index 59fbaf56..329901ea 100644 --- a/bookwyrm/templates/settings/reports.html +++ b/bookwyrm/templates/settings/reports.html @@ -1,16 +1,17 @@ {% extends 'settings/admin_layout.html' %} {% load i18n %} +{% block title %}{% trans "Reports" %}{% endblock %} {% block header %}{% trans "Reports" %}{% endblock %} {% block panel %} diff --git a/bookwyrm/views/reports.py b/bookwyrm/views/reports.py index 9eaf9bdc..eda56532 100644 --- a/bookwyrm/views/reports.py +++ b/bookwyrm/views/reports.py @@ -24,7 +24,7 @@ class Reports(View): def get(self, request): """ view current reports """ - resolved = request.GET.get("resolved", False) + resolved = request.GET.get("resolved") == "true" data = { "resolved": resolved, "reports": models.Report.objects.filter(resolved=resolved), From 0d2c641d01dc7017ebb2a45d8941569b1d5e3df5 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 9 Mar 2021 12:57:38 -0800 Subject: [PATCH 0256/1285] Reformats report model --- .../migrations/0049_auto_20210309_0156.py | 107 ++++++++++++++---- bookwyrm/models/report.py | 7 +- 2 files changed, 88 insertions(+), 26 deletions(-) diff --git a/bookwyrm/migrations/0049_auto_20210309_0156.py b/bookwyrm/migrations/0049_auto_20210309_0156.py index 494f5bc8..ae9d77a8 100644 --- a/bookwyrm/migrations/0049_auto_20210309_0156.py +++ b/bookwyrm/migrations/0049_auto_20210309_0156.py @@ -10,41 +10,104 @@ import django.db.models.expressions class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0048_merge_20210308_1754'), + ("bookwyrm", "0048_merge_20210308_1754"), ] operations = [ migrations.CreateModel( - name='Report', + name="Report", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), - ('note', models.TextField(blank=True, null=True)), - ('resolved', models.BooleanField(default=False)), - ('reporter', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='reporter', to=settings.AUTH_USER_MODEL)), - ('statuses', models.ManyToManyField(blank=True, null=True, to='bookwyrm.Status')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ( + "remote_id", + bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), + ), + ("note", models.TextField(blank=True, null=True)), + ("resolved", models.BooleanField(default=False)), + ( + "reporter", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="reporter", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "statuses", + models.ManyToManyField(blank=True, null=True, to="bookwyrm.Status"), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='ReportComment', + name="ReportComment", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), - ('note', models.TextField()), - ('report', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Report')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ( + "remote_id", + bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), + ), + ("note", models.TextField()), + ( + "report", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.Report", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.AddConstraint( - model_name='report', - constraint=models.CheckConstraint(check=models.Q(_negated=True, reporter=django.db.models.expressions.F('user')), name='self_report'), + model_name="report", + constraint=models.CheckConstraint( + check=models.Q( + _negated=True, reporter=django.db.models.expressions.F("user") + ), + name="self_report", + ), ), ] diff --git a/bookwyrm/models/report.py b/bookwyrm/models/report.py index e1e8c2a4..3a8fdd13 100644 --- a/bookwyrm/models/report.py +++ b/bookwyrm/models/report.py @@ -17,13 +17,12 @@ class Report(BookWyrmModel): class Meta: """ don't let users report themselves """ + constraints = [ - models.CheckConstraint( - check=~Q(reporter=F('user')), - name='self_report' - ) + models.CheckConstraint(check=~Q(reporter=F("user")), name="self_report") ] + class ReportComment(BookWyrmModel): """ updates on a report """ From 7f452066939495ee74a7f2dac129bae1c1e2d7e8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 10 Mar 2021 12:38:49 -0800 Subject: [PATCH 0257/1285] Moves moderation templates to their own directory --- bookwyrm/templates/{settings => moderation}/report.html | 2 +- .../templates/{settings => moderation}/report_preview.html | 0 bookwyrm/templates/{settings => moderation}/reports.html | 2 +- bookwyrm/templates/snippets/report_button.html | 1 + bookwyrm/views/reports.py | 4 ++-- 5 files changed, 5 insertions(+), 4 deletions(-) rename bookwyrm/templates/{settings => moderation}/report.html (94%) rename bookwyrm/templates/{settings => moderation}/report_preview.html (100%) rename bookwyrm/templates/{settings => moderation}/reports.html (91%) diff --git a/bookwyrm/templates/settings/report.html b/bookwyrm/templates/moderation/report.html similarity index 94% rename from bookwyrm/templates/settings/report.html rename to bookwyrm/templates/moderation/report.html index 74c3641b..ca68d51d 100644 --- a/bookwyrm/templates/settings/report.html +++ b/bookwyrm/templates/moderation/report.html @@ -10,7 +10,7 @@
    - {% include 'settings/report_preview.html' with report=report %} + {% include 'moderation/report_preview.html' with report=report %}
    diff --git a/bookwyrm/templates/settings/report_preview.html b/bookwyrm/templates/moderation/report_preview.html similarity index 100% rename from bookwyrm/templates/settings/report_preview.html rename to bookwyrm/templates/moderation/report_preview.html diff --git a/bookwyrm/templates/settings/reports.html b/bookwyrm/templates/moderation/reports.html similarity index 91% rename from bookwyrm/templates/settings/reports.html rename to bookwyrm/templates/moderation/reports.html index 329901ea..ebf29a7a 100644 --- a/bookwyrm/templates/settings/reports.html +++ b/bookwyrm/templates/moderation/reports.html @@ -19,7 +19,7 @@
    {% for report in reports %}
    - {% include 'settings/report_preview.html' with report=report %} + {% include 'moderation/report_preview.html' with report=report %}
    {% endfor %}
    diff --git a/bookwyrm/templates/snippets/report_button.html b/bookwyrm/templates/snippets/report_button.html index 5fbaee99..9d32d5fb 100644 --- a/bookwyrm/templates/snippets/report_button.html +++ b/bookwyrm/templates/snippets/report_button.html @@ -3,5 +3,6 @@ {% csrf_token %} + diff --git a/bookwyrm/views/reports.py b/bookwyrm/views/reports.py index eda56532..87547081 100644 --- a/bookwyrm/views/reports.py +++ b/bookwyrm/views/reports.py @@ -29,7 +29,7 @@ class Reports(View): "resolved": resolved, "reports": models.Report.objects.filter(resolved=resolved), } - return TemplateResponse(request, "settings/reports.html", data) + return TemplateResponse(request, "moderation/reports.html", data) @method_decorator(login_required, name="dispatch") @@ -47,7 +47,7 @@ class Report(View): def get(self, request, report_id): """ load a report """ data = {"report": get_object_or_404(models.Report, id=report_id)} - return TemplateResponse(request, "settings/report.html", data) + return TemplateResponse(request, "moderation/report.html", data) @login_required From 965d84f86f4e9bef3eabeac6512058183229c462 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 11 Mar 2021 15:41:12 -0800 Subject: [PATCH 0258/1285] Fixes creating news works --- bookwyrm/templates/edit_book.html | 2 +- bookwyrm/views/books.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/templates/edit_book.html b/bookwyrm/templates/edit_book.html index 700dc570..e42f77f2 100644 --- a/bookwyrm/templates/edit_book.html +++ b/bookwyrm/templates/edit_book.html @@ -60,7 +60,7 @@ {% for match in book_matches %} {% endfor %} - + {% endif %}
    diff --git a/bookwyrm/views/books.py b/bookwyrm/views/books.py index ae60c677..9048f43d 100644 --- a/bookwyrm/views/books.py +++ b/bookwyrm/views/books.py @@ -203,7 +203,7 @@ class ConfirmEditBook(View): # create work, if needed if not book_id: work_match = request.POST.get("parent_work") - if work_match: + if work_match and work_match != "0": work = get_object_or_404(models.Work, id=work_match) else: work = models.Work.objects.create(title=form.cleaned_data["title"]) From c1976dbd62bf5491b4e5036e2005ddd1ba61db12 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 11 Mar 2021 16:33:49 -0800 Subject: [PATCH 0259/1285] Add multiple authors --- bookwyrm/templates/edit_book.html | 55 ++++++++++++++++++------------- bookwyrm/views/books.py | 47 +++++++++++++++----------- 2 files changed, 61 insertions(+), 41 deletions(-) diff --git a/bookwyrm/templates/edit_book.html b/bookwyrm/templates/edit_book.html index e42f77f2..107e75c5 100644 --- a/bookwyrm/templates/edit_book.html +++ b/bookwyrm/templates/edit_book.html @@ -39,29 +39,37 @@

    {% trans "Confirm Book Info" %}

    - {% if author_matches.exists %} -
    - {% blocktrans with name=add_author %}Is "{{ name }}" an existing author?{% endblocktrans %} - {% for match in author_matches %} - -

    - {% blocktrans with book_title=match.book_set.first.title %}Author of {{ book_title }}{% endblocktrans %} -

    + {% if author_matches %} +
    + {% for author in author_matches %} +
    + {% blocktrans with name=author.name %}Is "{{ name }}" an existing author?{% endblocktrans %} + {% with forloop.counter as counter %} + {% for match in author.matches %} + +

    + {% blocktrans with book_title=match.book_set.first.title %}Author of {{ book_title }}{% endblocktrans %} +

    + {% endfor %} + + {% endwith %} +
    {% endfor %} - -
    +
    {% else %}

    {% blocktrans with name=add_author %}Creating a new author: {{ name }}{% endblocktrans %}

    {% endif %} {% if not book %} -
    - {% trans "Is this an editions of an existing work?" %} - {% for match in book_matches %} - - {% endfor %} - -
    +
    +
    + {% trans "Is this an edition of an existing work?" %} + {% for match in book_matches %} + + {% endfor %} + +
    +
    {% endif %}
    @@ -109,16 +117,19 @@

    {% trans "Authors" %}

    + {% if book.authors.exists %}
    {% for author in book.authors.all %} -

    {{ author.name }} -

    - - + {% endif %} + +

    Separate multiple author names with commas.

    +
    diff --git a/bookwyrm/views/books.py b/bookwyrm/views/books.py index 9048f43d..ff0d6764 100644 --- a/bookwyrm/views/books.py +++ b/bookwyrm/views/books.py @@ -131,17 +131,22 @@ class EditBook(View): # we're adding an author through a free text field if add_author: data["add_author"] = add_author - # check for existing authors - vector = SearchVector("name", weight="A") + SearchVector( - "aliases", weight="B" - ) + data['author_matches'] = [] + for author in add_author.split(','): + # check for existing authors + vector = SearchVector("name", weight="A") + SearchVector( + "aliases", weight="B" + ) - data["author_matches"] = ( - models.Author.objects.annotate(search=vector) - .annotate(rank=SearchRank(vector, add_author)) - .filter(rank__gt=0.4) - .order_by("-rank")[:5] - ) + data["author_matches"].append({ + 'name': author.strip(), + 'matches': ( + models.Author.objects.annotate(search=vector) + .annotate(rank=SearchRank(vector, add_author)) + .filter(rank__gt=0.4) + .order_by("-rank")[:5] + ) + }) # we're creating a new book if not book: @@ -157,6 +162,8 @@ class EditBook(View): if add_author or not book: # creting a book or adding an author to a book needs another step data["confirm_mode"] = True + # this isn't preserved because it isn't part of the form obj + data["remove_authors"] = request.POST.getlist("remove_authors") return TemplateResponse(request, "edit_book.html", data) remove_authors = request.POST.getlist("remove_authors") @@ -190,15 +197,17 @@ class ConfirmEditBook(View): # get or create author as needed if request.POST.get("add_author"): - if request.POST.get("author_match"): - author = get_object_or_404( - models.Author, id=request.POST["author_match"] - ) - else: - author = models.Author.objects.create( - name=request.POST.get("add_author") - ) - book.authors.add(author) + for (i, author) in enumerate(request.POST.get("add_author").split(',')): + match = request.POST.get("author_match-%d" % i) + if match and match != "0": + author = get_object_or_404( + models.Author, id=request.POST["author_match-%d" % i] + ) + else: + author = models.Author.objects.create( + name=author.strip() + ) + book.authors.add(author) # create work, if needed if not book_id: From 28db3e2733d57e9b7a5988e22d2e6c376587496a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 11 Mar 2021 16:40:35 -0800 Subject: [PATCH 0260/1285] Formatting --- bookwyrm/views/books.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/bookwyrm/views/books.py b/bookwyrm/views/books.py index ff0d6764..1cb21f5e 100644 --- a/bookwyrm/views/books.py +++ b/bookwyrm/views/books.py @@ -131,22 +131,24 @@ class EditBook(View): # we're adding an author through a free text field if add_author: data["add_author"] = add_author - data['author_matches'] = [] - for author in add_author.split(','): + data["author_matches"] = [] + for author in add_author.split(","): # check for existing authors vector = SearchVector("name", weight="A") + SearchVector( "aliases", weight="B" ) - data["author_matches"].append({ - 'name': author.strip(), - 'matches': ( - models.Author.objects.annotate(search=vector) - .annotate(rank=SearchRank(vector, add_author)) - .filter(rank__gt=0.4) - .order_by("-rank")[:5] - ) - }) + data["author_matches"].append( + { + "name": author.strip(), + "matches": ( + models.Author.objects.annotate(search=vector) + .annotate(rank=SearchRank(vector, add_author)) + .filter(rank__gt=0.4) + .order_by("-rank")[:5] + ), + } + ) # we're creating a new book if not book: @@ -197,16 +199,14 @@ class ConfirmEditBook(View): # get or create author as needed if request.POST.get("add_author"): - for (i, author) in enumerate(request.POST.get("add_author").split(',')): + for (i, author) in enumerate(request.POST.get("add_author").split(",")): match = request.POST.get("author_match-%d" % i) if match and match != "0": author = get_object_or_404( models.Author, id=request.POST["author_match-%d" % i] ) else: - author = models.Author.objects.create( - name=author.strip() - ) + author = models.Author.objects.create(name=author.strip()) book.authors.add(author) # create work, if needed From 33b8537a3d692c2dcbcab73b7c4da11958c79094 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 11 Mar 2021 17:38:21 -0800 Subject: [PATCH 0261/1285] Let user supply a note for report --- bookwyrm/models/report.py | 1 + bookwyrm/templates/moderation/report.html | 10 +++-- .../templates/moderation/report_modal.html | 37 +++++++++++++++++++ .../templates/moderation/report_preview.html | 2 +- bookwyrm/templates/notifications.html | 10 +---- .../templates/snippets/report_button.html | 16 ++++---- .../snippets/status/status_body.html | 9 ++++- .../snippets/status/status_options.html | 4 +- .../templates/snippets/status_preview.html | 9 +++++ 9 files changed, 73 insertions(+), 25 deletions(-) create mode 100644 bookwyrm/templates/moderation/report_modal.html create mode 100644 bookwyrm/templates/snippets/status_preview.html diff --git a/bookwyrm/models/report.py b/bookwyrm/models/report.py index 3a8fdd13..8893f42f 100644 --- a/bookwyrm/models/report.py +++ b/bookwyrm/models/report.py @@ -21,6 +21,7 @@ class Report(BookWyrmModel): constraints = [ models.CheckConstraint(check=~Q(reporter=F("user")), name="self_report") ] + ordering = ("-created_date",) class ReportComment(BookWyrmModel): diff --git a/bookwyrm/templates/moderation/report.html b/bookwyrm/templates/moderation/report.html index ca68d51d..ce0a0b3a 100644 --- a/bookwyrm/templates/moderation/report.html +++ b/bookwyrm/templates/moderation/report.html @@ -32,11 +32,13 @@ -
    -

    {% trans "Reported statuses" %}

    +
    +

    {% trans "Reported statuses" %}

      - {% for status in report.statuses.all %} -
    • {{ status.id }}
    • + {% for status in report.statuses.select_subclasses.all %} +
    • + {% include 'snippets/status/status.html' with status=status moderation_mode=True %} +
    • {% endfor %}
    diff --git a/bookwyrm/templates/moderation/report_modal.html b/bookwyrm/templates/moderation/report_modal.html new file mode 100644 index 00000000..28613130 --- /dev/null +++ b/bookwyrm/templates/moderation/report_modal.html @@ -0,0 +1,37 @@ +{% extends 'components/modal.html' %} +{% load i18n %} +{% load humanize %} + +{% block modal-title %} +{% blocktrans with username=user.username %}Report @{{ username }}{% endblocktrans %} +{% endblock %} + +{% block modal-form-open %} +
    +{% endblock %} + +{% block modal-body %} + +{% csrf_token %} + + + + +
    +

    {% blocktrans with site_name=site.name %}This report will be sent to {{ site_name }}'s moderators for review.{% endblocktrans %}

    + + +
    + +{% endblock %} + + +{% block modal-footer %} + + +{% trans "Cancel" as button_text %} +{% include 'snippets/toggle/toggle_button.html' with text=button_text controls_text="report" controls_uid=report_uuid class="" %} + +{% endblock %} +{% block modal-form-close %}
    {% endblock %} + diff --git a/bookwyrm/templates/moderation/report_preview.html b/bookwyrm/templates/moderation/report_preview.html index 25cb8a2f..9acc4f77 100644 --- a/bookwyrm/templates/moderation/report_preview.html +++ b/bookwyrm/templates/moderation/report_preview.html @@ -9,7 +9,7 @@ {% block card-content %}
    - {% if report.notes %}{{ report.notes }}{% else %}{% trans "No notes provided" %}{% endif %} + {% if report.note %}{{ report.note }}{% else %}{% trans "No notes provided" %}{% endif %}
    {% endblock %} diff --git a/bookwyrm/templates/notifications.html b/bookwyrm/templates/notifications.html index 80ee2250..3f0300bd 100644 --- a/bookwyrm/templates/notifications.html +++ b/bookwyrm/templates/notifications.html @@ -115,15 +115,7 @@
    - {% if related_status.content %} - - {{ related_status.content | safe | truncatewords_html:10 }}{% if related_status.mention_books %} {{ related_status.mention_books.first.title }}{% endif %} - - {% elif related_status.quote %} - {{ related_status.quote | safe | truncatewords_html:10 }} - {% elif related_status.rating %} - {% include 'snippets/stars.html' with rating=related_status.rating %} - {% endif %} + {% include 'snippets/status_preview.html' with status=related_status %}
    {{ related_status.published_date | post_date }} diff --git a/bookwyrm/templates/snippets/report_button.html b/bookwyrm/templates/snippets/report_button.html index 9d32d5fb..2fa0a3f3 100644 --- a/bookwyrm/templates/snippets/report_button.html +++ b/bookwyrm/templates/snippets/report_button.html @@ -1,8 +1,10 @@ {% load i18n %} -
    - {% csrf_token %} - - - - -
    +{% load bookwyrm_tags %} +{% with 0|uuid as report_uuid %} + +{% trans "Report" as button_text %} +{% include 'snippets/toggle/toggle_button.html' with class="is-danger is-light is-small is-fullwidth" text=button_text controls_text="report" controls_uid=report_uuid focus="modal-title-report" disabled=is_current %} + +{% include 'moderation/report_modal.html' with user=user reporter=request.user controls_text="report" controls_uid=report_uuid %} + +{% endwith %} diff --git a/bookwyrm/templates/snippets/status/status_body.html b/bookwyrm/templates/snippets/status/status_body.html index 8d6c21ed..f3732d1a 100644 --- a/bookwyrm/templates/snippets/status/status_body.html +++ b/bookwyrm/templates/snippets/status/status_body.html @@ -18,7 +18,10 @@ {% block card-footer %} {% endblock %} diff --git a/bookwyrm/tests/views/test_reports.py b/bookwyrm/tests/views/test_reports.py index 70414dcb..1a5bfd15 100644 --- a/bookwyrm/tests/views/test_reports.py +++ b/bookwyrm/tests/views/test_reports.py @@ -46,7 +46,7 @@ class ReportViews(TestCase): request = self.factory.get("") request.user = self.local_user request.user.is_superuser = True - report = models.Report.objects.create(reporter=self.local_user, user=self.rat) + models.Report.objects.create(reporter=self.local_user, user=self.rat) result = view(request) self.assertIsInstance(result, TemplateResponse) @@ -80,3 +80,21 @@ class ReportViews(TestCase): report = models.Report.objects.get() self.assertEqual(report.reporter, self.local_user) self.assertEqual(report.user, self.rat) + + def test_resolve_report(self): + """ toggle report resolution status """ + report = models.Report.objects.create(reporter=self.local_user, user=self.rat) + self.assertFalse(report.resolved) + request = self.factory.post("") + request.user = self.local_user + request.user.is_superuser = True + + # resolve + views.resolve_report(request, report.id) + report.refresh_from_db() + self.assertTrue(report.resolved) + + # un-resolve + views.resolve_report(request, report.id) + report.refresh_from_db() + self.assertFalse(report.resolved) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 42b9803d..551be1e3 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -62,6 +62,11 @@ urlpatterns = [ views.Report.as_view(), name="settings-report", ), + re_path( + r"^settings/reports/(?P\d+)/resolve/?$", + views.resolve_report, + name="settings-report-resolve", + ), re_path(r"^report/?$", views.make_report, name="report"), # landing pages re_path(r"^about/?$", views.About.as_view()), diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index b433dca2..63ab98e5 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -20,7 +20,7 @@ from .notifications import Notifications from .outbox import Outbox from .reading import edit_readthrough, create_readthrough, delete_readthrough from .reading import start_reading, finish_reading, delete_progressupdate -from .reports import Report, Reports, make_report +from .reports import Report, Reports, make_report, resolve_report from .rss_feed import RssFeed from .password import PasswordResetRequest, PasswordReset, ChangePassword from .search import Search diff --git a/bookwyrm/views/reports.py b/bookwyrm/views/reports.py index 87547081..59b9b9e4 100644 --- a/bookwyrm/views/reports.py +++ b/bookwyrm/views/reports.py @@ -49,6 +49,21 @@ class Report(View): data = {"report": get_object_or_404(models.Report, id=report_id)} return TemplateResponse(request, "moderation/report.html", data) + def post(self, request, report_id): + """ update a report """ + + +@login_required +@permission_required("bookwyrm_moderate_post") +def resolve_report(_, report_id): + """ mark a report as (un)resolved """ + report = get_object_or_404(models.Report, id=report_id) + report.resolved = not report.resolved + report.save() + if not report.resolved: + return redirect("settings-report", report.id) + return redirect("settings-reports") + @login_required @require_POST From 8bd12f0e062337a5bfeb6b103f7d36ab8498ab61 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 12 Mar 2021 10:27:08 -0800 Subject: [PATCH 0265/1285] Remove unused method --- bookwyrm/views/reports.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bookwyrm/views/reports.py b/bookwyrm/views/reports.py index 59b9b9e4..ad22da2b 100644 --- a/bookwyrm/views/reports.py +++ b/bookwyrm/views/reports.py @@ -49,9 +49,6 @@ class Report(View): data = {"report": get_object_or_404(models.Report, id=report_id)} return TemplateResponse(request, "moderation/report.html", data) - def post(self, request, report_id): - """ update a report """ - @login_required @permission_required("bookwyrm_moderate_post") From 422cd2da73c7583a1383bb6f5722295711fd8eb5 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 12 Mar 2021 10:37:52 -0800 Subject: [PATCH 0266/1285] Direct message report action --- bookwyrm/templates/moderation/report.html | 2 +- bookwyrm/templates/snippets/status/status_options.html | 2 +- bookwyrm/urls.py | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bookwyrm/templates/moderation/report.html b/bookwyrm/templates/moderation/report.html index ce0a0b3a..32fdf6d7 100644 --- a/bookwyrm/templates/moderation/report.html +++ b/bookwyrm/templates/moderation/report.html @@ -16,7 +16,7 @@

    {% trans "Actions" %}

    - + {% trans "Send direct message" %}
    diff --git a/bookwyrm/templates/snippets/status/status_options.html b/bookwyrm/templates/snippets/status/status_options.html index 3c710055..6f0ca2e6 100644 --- a/bookwyrm/templates/snippets/status/status_options.html +++ b/bookwyrm/templates/snippets/status/status_options.html @@ -22,7 +22,7 @@ {% else %} {# things you can do to other people's statuses #}
  • - {% trans "Send direct message" %} + {% trans "Send direct message" %}
  • {% include 'snippets/report_button.html' with user=status.user status=status %} diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 551be1e3..0ad464f4 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -75,10 +75,13 @@ urlpatterns = [ re_path(r"^notifications/?$", views.Notifications.as_view()), # feeds re_path(r"^(?Phome|local|federated)/?$", views.Feed.as_view()), - re_path(r"^direct-messages/?$", views.DirectMessage.as_view()), + re_path( + r"^direct-messages/?$", views.DirectMessage.as_view(), name="direct-messages" + ), re_path( r"^direct-messages/(?P%s)?$" % regex.username, views.DirectMessage.as_view(), + name="direct-messages-user", ), # search re_path(r"^search/?$", views.Search.as_view()), From 677a49fee31cc2fe0e24848ea05cbe9a9f3ff63f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 12 Mar 2021 11:13:53 -0800 Subject: [PATCH 0267/1285] Option to deactivate reported users --- bookwyrm/templates/moderation/report.html | 15 ++++++++++++--- .../templates/moderation/report_preview.html | 7 +++++-- bookwyrm/tests/views/test_reports.py | 19 +++++++++++++++++++ bookwyrm/urls.py | 5 +++++ bookwyrm/views/__init__.py | 2 +- bookwyrm/views/reports.py | 10 ++++++++++ 6 files changed, 52 insertions(+), 6 deletions(-) diff --git a/bookwyrm/templates/moderation/report.html b/bookwyrm/templates/moderation/report.html index 32fdf6d7..b2e61cc4 100644 --- a/bookwyrm/templates/moderation/report.html +++ b/bookwyrm/templates/moderation/report.html @@ -15,9 +15,18 @@

    {% trans "Actions" %}

    -
    - {% trans "Send direct message" %} - +
    +

    + {% trans "Send direct message" %} +

    +
    + {% csrf_token %} + {% if report.user.is_active %} + + {% else %} + + {% endif %} +
    {% for comment in report.reportcomment_set.all %} diff --git a/bookwyrm/templates/moderation/report_preview.html b/bookwyrm/templates/moderation/report_preview.html index 3888be27..c35010cf 100644 --- a/bookwyrm/templates/moderation/report_preview.html +++ b/bookwyrm/templates/moderation/report_preview.html @@ -8,8 +8,11 @@ {% endblock %} {% block card-content %} -
    - {% if report.note %}{{ report.note }}{% else %}{% trans "No notes provided" %}{% endif %} +
    +

    + {% if report.note %}{{ report.note }}{% else %}{% trans "No notes provided" %}{% endif %} +

    +

    {% trans "View user profile" %}

    {% endblock %} diff --git a/bookwyrm/tests/views/test_reports.py b/bookwyrm/tests/views/test_reports.py index 1a5bfd15..724e7b5a 100644 --- a/bookwyrm/tests/views/test_reports.py +++ b/bookwyrm/tests/views/test_reports.py @@ -98,3 +98,22 @@ class ReportViews(TestCase): views.resolve_report(request, report.id) report.refresh_from_db() self.assertFalse(report.resolved) + + + def test_deactivate_user(self): + """ toggle whether a user is able to log in """ + self.assertTrue(self.rat.is_active) + report = models.Report.objects.create(reporter=self.local_user, user=self.rat) + request = self.factory.post("") + request.user = self.local_user + request.user.is_superuser = True + + # resolve + views.deactivate_user(request, report.id) + self.rat.refresh_from_db() + self.assertFalse(self.rat.is_active) + + # un-resolve + views.deactivate_user(request, report.id) + self.rat.refresh_from_db() + self.assertTrue(self.rat.is_active) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 0ad464f4..26ce67a3 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -62,6 +62,11 @@ urlpatterns = [ views.Report.as_view(), name="settings-report", ), + re_path( + r"^settings/reports/(?P\d+)/deactivate/?$", + views.deactivate_user, + name="settings-report-deactivate", + ), re_path( r"^settings/reports/(?P\d+)/resolve/?$", views.resolve_report, diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 63ab98e5..606624b8 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -20,7 +20,7 @@ from .notifications import Notifications from .outbox import Outbox from .reading import edit_readthrough, create_readthrough, delete_readthrough from .reading import start_reading, finish_reading, delete_progressupdate -from .reports import Report, Reports, make_report, resolve_report +from .reports import Report, Reports, make_report, resolve_report, deactivate_user from .rss_feed import RssFeed from .password import PasswordResetRequest, PasswordReset, ChangePassword from .search import Search diff --git a/bookwyrm/views/reports.py b/bookwyrm/views/reports.py index ad22da2b..2ba0d27a 100644 --- a/bookwyrm/views/reports.py +++ b/bookwyrm/views/reports.py @@ -50,6 +50,16 @@ class Report(View): return TemplateResponse(request, "moderation/report.html", data) +@login_required +@permission_required("bookwyrm_moderate_user") +def deactivate_user(_, report_id): + """ mark an account as inactive """ + report = get_object_or_404(models.Report, id=report_id) + report.user.is_active = not report.user.is_active + report.user.save() + return redirect("settings-report", report.id) + + @login_required @permission_required("bookwyrm_moderate_post") def resolve_report(_, report_id): From 8c74beb78cec60bf44723661b2b6dd2ed5304a3a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 12 Mar 2021 11:25:56 -0800 Subject: [PATCH 0268/1285] Allow moderators to delete reported statuses --- bookwyrm/templates/moderation/report.html | 5 +++ .../templates/moderation/report_preview.html | 1 - .../snippets/status/status_body.html | 9 ++++- bookwyrm/tests/views/test_reports.py | 1 - bookwyrm/tests/views/test_status.py | 34 ++++++++++++++++++- bookwyrm/views/status.py | 2 +- 6 files changed, 47 insertions(+), 5 deletions(-) diff --git a/bookwyrm/templates/moderation/report.html b/bookwyrm/templates/moderation/report.html index b2e61cc4..ae014d68 100644 --- a/bookwyrm/templates/moderation/report.html +++ b/bookwyrm/templates/moderation/report.html @@ -15,6 +15,7 @@

    {% trans "Actions" %}

    +

    {% trans "View user profile" %}

    {% trans "Send direct message" %} @@ -46,7 +47,11 @@

      {% for status in report.statuses.select_subclasses.all %}
    • + {% if status.deleted %} + {% trans "Statuses has been deleted" %} + {% else %} {% include 'snippets/status/status.html' with status=status moderation_mode=True %} + {% endif %}
    • {% endfor %}
    diff --git a/bookwyrm/templates/moderation/report_preview.html b/bookwyrm/templates/moderation/report_preview.html index c35010cf..3a5ebcf9 100644 --- a/bookwyrm/templates/moderation/report_preview.html +++ b/bookwyrm/templates/moderation/report_preview.html @@ -12,7 +12,6 @@

    {% if report.note %}{{ report.note }}{% else %}{% trans "No notes provided" %}{% endif %}

    -

    {% trans "View user profile" %}

    {% endblock %} diff --git a/bookwyrm/templates/snippets/status/status_body.html b/bookwyrm/templates/snippets/status/status_body.html index f3732d1a..a7e8e884 100644 --- a/bookwyrm/templates/snippets/status/status_body.html +++ b/bookwyrm/templates/snippets/status/status_body.html @@ -19,8 +19,15 @@ {% block card-footer %}
  • - {% if perms.bookwyrm.create_invites or perms.bookwyrm.edit_instance_settings%} + {% if perms.bookwyrm.create_invites or perms.moderate_users %} {% endif %} {% if perms.bookwyrm.create_invites %} @@ -117,9 +117,9 @@ {% endif %} - {% if perms.bookwyrm.edit_instance_settings %} + {% if perms.bookwyrm.moderate_users %}
  • - + {% trans 'Admin' %}
  • From 7c9518afa6614a276de6fd762d8c1eb065ce7d38 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 29 Mar 2021 10:21:48 -0700 Subject: [PATCH 0496/1285] Adds info to editions page --- bookwyrm/templates/book/book.html | 19 +----------- bookwyrm/templates/book/editions.html | 33 +++++++++++++++++++++ bookwyrm/templates/book/publisher_info.html | 24 +++++++++++++++ bookwyrm/templates/editions.html | 14 --------- bookwyrm/views/books.py | 2 +- 5 files changed, 59 insertions(+), 33 deletions(-) create mode 100644 bookwyrm/templates/book/editions.html create mode 100644 bookwyrm/templates/book/publisher_info.html delete mode 100644 bookwyrm/templates/editions.html diff --git a/bookwyrm/templates/book/book.html b/bookwyrm/templates/book/book.html index aef8fba1..95cf86ad 100644 --- a/bookwyrm/templates/book/book.html +++ b/bookwyrm/templates/book/book.html @@ -76,24 +76,7 @@ {% endif %} -

    - {% if book.physical_format and not book.pages %} - {{ book.physical_format | title }} - {% elif book.physical_format and book.pages %} - {% blocktrans with format=book.physical_format|title pages=book.pages %}{{ format }}, {{ pages }} pages{% endblocktrans %} - {% elif book.pages %} - {% blocktrans with pages=book.pages %}{{ pages }} pages{% endblocktrans %} - {% endif %} -

    -

    - {% if book.published_date and book.publishers %} - {% blocktrans with date=book.published_date|date:'M jS Y' publisher=book.publishers|join:', ' %}Published {{ date }} by {{ publisher }}.{% endblocktrans %} - {% elif book.published_date %} - {% blocktrans with date=book.published_date|date:'M jS Y' %}Published {{ date }}{% endblocktrans %} - {% elif book.publishers %} - {% blocktrans with publisher=book.publishers|join:', ' %}Published by {{ publisher }}.{% endblocktrans %} - {% endif %} -

    + {% include 'book/publisher_info.html' with book=book %} {% if book.openlibrary_key %}

    {% trans "View on OpenLibrary" %}

    diff --git a/bookwyrm/templates/book/editions.html b/bookwyrm/templates/book/editions.html new file mode 100644 index 00000000..b77dc1a6 --- /dev/null +++ b/bookwyrm/templates/book/editions.html @@ -0,0 +1,33 @@ +{% extends 'layout.html' %} +{% load i18n %} +{% load bookwyrm_tags %} + +{% block title %}{% blocktrans with book_title=work.title %}Editions of {{ book_title }}{% endblocktrans %}{% endblock %} + +{% block content %} +
    +

    {% blocktrans with work_path=work.local_path work_title=work.title %}Editions of "{{ work_title }}"{% endblocktrans %}

    + + {% for book in editions %} +
    + +
    +

    + + {{ book.title }} + +

    + {% include 'book/publisher_info.html' with book=book %} +
    +
    + {% include 'snippets/shelve_button/shelve_button.html' with book=book switch_mode=True %} +
    +
    + {% endfor %} +
    +{% endblock %} + diff --git a/bookwyrm/templates/book/publisher_info.html b/bookwyrm/templates/book/publisher_info.html new file mode 100644 index 00000000..0ab35401 --- /dev/null +++ b/bookwyrm/templates/book/publisher_info.html @@ -0,0 +1,24 @@ +{% load i18n %} +

    + {% if book.physical_format and not book.pages %} + {{ book.physical_format | title }} + {% elif book.physical_format and book.pages %} + {% blocktrans with format=book.physical_format|title pages=book.pages %}{{ format }}, {{ pages }} pages{% endblocktrans %} + {% elif book.pages %} + {% blocktrans with pages=book.pages %}{{ pages }} pages{% endblocktrans %} + {% endif %} +

    +{% if book.languages %} +

    + {% blocktrans with languages=book.languages|join:", " %}{{ languages }} language{% endblocktrans %} +

    +{% endif %} +

    + {% if book.published_date and book.publishers %} + {% blocktrans with date=book.published_date|date:'M jS Y' publisher=book.publishers|join:', ' %}Published {{ date }} by {{ publisher }}.{% endblocktrans %} + {% elif book.published_date %} + {% blocktrans with date=book.published_date|date:'M jS Y' %}Published {{ date }}{% endblocktrans %} + {% elif book.publishers %} + {% blocktrans with publisher=book.publishers|join:', ' %}Published by {{ publisher }}.{% endblocktrans %} + {% endif %} +

    diff --git a/bookwyrm/templates/editions.html b/bookwyrm/templates/editions.html deleted file mode 100644 index f8319757..00000000 --- a/bookwyrm/templates/editions.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'layout.html' %} -{% load i18n %} -{% load bookwyrm_tags %} - -{% block title %}{% blocktrans with book_title=work.title %}Editions of {{ book_title }}{% endblocktrans %}{% endblock %} - -{% block content %} -
    -

    {% blocktrans with work_path=work.local_path work_title=work.title %}Editions of "{{ work_title }}"{% endblocktrans %}

    - - {% include 'snippets/book_tiles.html' with books=editions %} -
    -{% endblock %} - diff --git a/bookwyrm/views/books.py b/bookwyrm/views/books.py index cabc3223..5c8eab4c 100644 --- a/bookwyrm/views/books.py +++ b/bookwyrm/views/books.py @@ -257,7 +257,7 @@ class Editions(View): "editions": work.editions.order_by("-edition_rank").all(), "work": work, } - return TemplateResponse(request, "editions.html", data) + return TemplateResponse(request, "book/editions.html", data) @login_required From b13e8d75cdb126ca05e19f037537ea39ef3932ca Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 29 Mar 2021 10:39:13 -0700 Subject: [PATCH 0497/1285] Introduces filters snippets --- .../templates/directory/community_filter.html | 14 +++++ .../templates/{ => directory}/directory.html | 59 +------------------ bookwyrm/templates/directory/filters.html | 7 +++ bookwyrm/templates/directory/sort_filter.html | 12 ++++ .../templates/directory/user_type_filter.html | 14 +++++ .../snippets/filters_panel/filter_field.html | 6 ++ .../snippets/filters_panel/filters_panel.html | 25 ++++++++ bookwyrm/views/directory.py | 2 +- 8 files changed, 80 insertions(+), 59 deletions(-) create mode 100644 bookwyrm/templates/directory/community_filter.html rename bookwyrm/templates/{ => directory}/directory.html (56%) create mode 100644 bookwyrm/templates/directory/filters.html create mode 100644 bookwyrm/templates/directory/sort_filter.html create mode 100644 bookwyrm/templates/directory/user_type_filter.html create mode 100644 bookwyrm/templates/snippets/filters_panel/filter_field.html create mode 100644 bookwyrm/templates/snippets/filters_panel/filters_panel.html diff --git a/bookwyrm/templates/directory/community_filter.html b/bookwyrm/templates/directory/community_filter.html new file mode 100644 index 00000000..bd0ba778 --- /dev/null +++ b/bookwyrm/templates/directory/community_filter.html @@ -0,0 +1,14 @@ +{% extends 'snippets/filters_panel/filter_field.html' %} +{% load i18n %} + +{% block filter %} +{% trans "Community" %} + + +{% endblock %} diff --git a/bookwyrm/templates/directory.html b/bookwyrm/templates/directory/directory.html similarity index 56% rename from bookwyrm/templates/directory.html rename to bookwyrm/templates/directory/directory.html index 3d47e26b..7413f67d 100644 --- a/bookwyrm/templates/directory.html +++ b/bookwyrm/templates/directory/directory.html @@ -36,64 +36,7 @@
    {% endif %} -
    -

    - Filters - - - {% trans "Show filters" as text %} - {% include 'snippets/toggle/open_button.html' with text=text controls_text="filters" icon="arrow-down" class="is-small" focus="filters" %} - {% trans "Hide filters" as text %} - {% include 'snippets/toggle/close_button.html' with text=text controls_text="filters" icon="x" class="is-small" %} - -

    - - - {% if request.GET %} - {% trans "Clear filters" %} - {% endif %} -
    +{% include 'directory/filters.html' %}
    {% for user in users %} diff --git a/bookwyrm/templates/directory/filters.html b/bookwyrm/templates/directory/filters.html new file mode 100644 index 00000000..c6bbe157 --- /dev/null +++ b/bookwyrm/templates/directory/filters.html @@ -0,0 +1,7 @@ +{% extends 'snippets/filters_panel/filters_panel.html' %} + +{% block filter_fields %} +{% include 'directory/user_type_filter.html' %} +{% include 'directory/community_filter.html' %} +{% include 'directory/sort_filter.html' %} +{% endblock %} diff --git a/bookwyrm/templates/directory/sort_filter.html b/bookwyrm/templates/directory/sort_filter.html new file mode 100644 index 00000000..82b561fb --- /dev/null +++ b/bookwyrm/templates/directory/sort_filter.html @@ -0,0 +1,12 @@ +{% extends 'snippets/filters_panel/filter_field.html' %} +{% load i18n %} + +{% block filter %} + +
    + +
    +{% endblock %} diff --git a/bookwyrm/templates/directory/user_type_filter.html b/bookwyrm/templates/directory/user_type_filter.html new file mode 100644 index 00000000..b5961c82 --- /dev/null +++ b/bookwyrm/templates/directory/user_type_filter.html @@ -0,0 +1,14 @@ +{% extends 'snippets/filters_panel/filter_field.html' %} +{% load i18n %} + +{% block filter %} +{% trans "User type" %} + + +{% endblock %} diff --git a/bookwyrm/templates/snippets/filters_panel/filter_field.html b/bookwyrm/templates/snippets/filters_panel/filter_field.html new file mode 100644 index 00000000..0a8daaa1 --- /dev/null +++ b/bookwyrm/templates/snippets/filters_panel/filter_field.html @@ -0,0 +1,6 @@ +
    +
    + {% block filter %} + {% endblock %} +
    +
    diff --git a/bookwyrm/templates/snippets/filters_panel/filters_panel.html b/bookwyrm/templates/snippets/filters_panel/filters_panel.html new file mode 100644 index 00000000..185f5bc4 --- /dev/null +++ b/bookwyrm/templates/snippets/filters_panel/filters_panel.html @@ -0,0 +1,25 @@ +{% load i18n %} +
    +

    + Filters + + + {% trans "Show filters" as text %} + {% include 'snippets/toggle/open_button.html' with text=text controls_text="filters" icon="arrow-down" class="is-small" focus="filters" %} + {% trans "Hide filters" as text %} + {% include 'snippets/toggle/close_button.html' with text=text controls_text="filters" icon="x" class="is-small" %} + +

    + + + + {% if request.GET %} + {% trans "Clear filters" %} + {% endif %} +
    diff --git a/bookwyrm/views/directory.py b/bookwyrm/views/directory.py index c57b47f7..b329eede 100644 --- a/bookwyrm/views/directory.py +++ b/bookwyrm/views/directory.py @@ -41,7 +41,7 @@ class Directory(View): data = { "users": paginated.page(page), } - return TemplateResponse(request, "directory.html", data) + return TemplateResponse(request, "directory/directory.html", data) def post(self, request): """ join the directory """ From 769ba6466c84654ed6fc0fb82a79b3a223e3ddf2 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 29 Mar 2021 10:58:35 -0700 Subject: [PATCH 0498/1285] Adds filters ui to editions page --- bookwyrm/templates/book/edition_filters.html | 6 ++++++ bookwyrm/templates/book/editions.html | 5 ++++- bookwyrm/templates/book/format_filter.html | 13 +++++++++++++ bookwyrm/templates/book/language_filter.html | 13 +++++++++++++ bookwyrm/views/books.py | 6 +++++- 5 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 bookwyrm/templates/book/edition_filters.html create mode 100644 bookwyrm/templates/book/format_filter.html create mode 100644 bookwyrm/templates/book/language_filter.html diff --git a/bookwyrm/templates/book/edition_filters.html b/bookwyrm/templates/book/edition_filters.html new file mode 100644 index 00000000..a55b72af --- /dev/null +++ b/bookwyrm/templates/book/edition_filters.html @@ -0,0 +1,6 @@ +{% extends 'snippets/filters_panel/filters_panel.html' %} + +{% block filter_fields %} +{% include 'book/language_filter.html' %} +{% include 'book/format_filter.html' %} +{% endblock %} diff --git a/bookwyrm/templates/book/editions.html b/bookwyrm/templates/book/editions.html index b77dc1a6..7290cce9 100644 --- a/bookwyrm/templates/book/editions.html +++ b/bookwyrm/templates/book/editions.html @@ -7,7 +7,11 @@ {% block content %}

    {% blocktrans with work_path=work.local_path work_title=work.title %}Editions of "{{ work_title }}"{% endblocktrans %}

    +
    +{% include 'book/edition_filters.html' %} + +
    {% for book in editions %}
    @@ -30,4 +34,3 @@ {% endfor %}
    {% endblock %} - diff --git a/bookwyrm/templates/book/format_filter.html b/bookwyrm/templates/book/format_filter.html new file mode 100644 index 00000000..7bd8f6c7 --- /dev/null +++ b/bookwyrm/templates/book/format_filter.html @@ -0,0 +1,13 @@ +{% extends 'snippets/filters_panel/filter_field.html' %} +{% load i18n %} + +{% block filter %} + +
    + +
    +{% endblock %} diff --git a/bookwyrm/templates/book/language_filter.html b/bookwyrm/templates/book/language_filter.html new file mode 100644 index 00000000..fc21d5e2 --- /dev/null +++ b/bookwyrm/templates/book/language_filter.html @@ -0,0 +1,13 @@ +{% extends 'snippets/filters_panel/filter_field.html' %} +{% load i18n %} + +{% block filter %} + +
    + +
    +{% endblock %} diff --git a/bookwyrm/views/books.py b/bookwyrm/views/books.py index 5c8eab4c..6d10e131 100644 --- a/bookwyrm/views/books.py +++ b/bookwyrm/views/books.py @@ -252,10 +252,14 @@ class Editions(View): if is_api_request(request): return ActivitypubResponse(work.to_edition_list(**request.GET)) + editions = work.editions.order_by("-edition_rank").all() + languages = set(sum([e.languages for e in editions], [])) data = { - "editions": work.editions.order_by("-edition_rank").all(), + "editions": editions, "work": work, + "languages": languages, + "formats": set(e.physical_format.lower() for e in editions), } return TemplateResponse(request, "book/editions.html", data) From 9c798a4feba0f172823a72d3f13a9c535f254490 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 29 Mar 2021 11:13:23 -0700 Subject: [PATCH 0499/1285] Filter editions --- bookwyrm/templates/book/format_filter.html | 5 ++++- bookwyrm/templates/book/language_filter.html | 5 ++++- bookwyrm/views/books.py | 9 ++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/bookwyrm/templates/book/format_filter.html b/bookwyrm/templates/book/format_filter.html index 7bd8f6c7..05275528 100644 --- a/bookwyrm/templates/book/format_filter.html +++ b/bookwyrm/templates/book/format_filter.html @@ -5,8 +5,11 @@
    diff --git a/bookwyrm/templates/book/language_filter.html b/bookwyrm/templates/book/language_filter.html index fc21d5e2..c6798a9b 100644 --- a/bookwyrm/templates/book/language_filter.html +++ b/bookwyrm/templates/book/language_filter.html @@ -5,8 +5,11 @@
    diff --git a/bookwyrm/views/books.py b/bookwyrm/views/books.py index 6d10e131..8434cec0 100644 --- a/bookwyrm/views/books.py +++ b/bookwyrm/views/books.py @@ -252,11 +252,18 @@ class Editions(View): if is_api_request(request): return ActivitypubResponse(work.to_edition_list(**request.GET)) + filters = {} + + if request.GET.get("language"): + filters["languages__contains"] = [request.GET.get("language")] + if request.GET.get("format"): + filters["physical_format__iexact"] = request.GET.get("format") + editions = work.editions.order_by("-edition_rank").all() languages = set(sum([e.languages for e in editions], [])) data = { - "editions": editions, + "editions": editions.filter(**filters).all(), "work": work, "languages": languages, "formats": set(e.physical_format.lower() for e in editions), From f8a321c74d6cad8059586021da184dcccc1c70e9 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 29 Mar 2021 11:16:34 -0700 Subject: [PATCH 0500/1285] Fixes labels Good bot --- bookwyrm/templates/book/format_filter.html | 2 +- bookwyrm/templates/book/language_filter.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/templates/book/format_filter.html b/bookwyrm/templates/book/format_filter.html index 05275528..c722b24f 100644 --- a/bookwyrm/templates/book/format_filter.html +++ b/bookwyrm/templates/book/format_filter.html @@ -4,7 +4,7 @@ {% block filter %}
    - {% for format in formats %}{% if format %}
    + +
    + {% include 'snippets/pagination.html' with page=editions path=request.path %} +
    {% endblock %} diff --git a/bookwyrm/views/books.py b/bookwyrm/views/books.py index 895c7ec9..632cb2ad 100644 --- a/bookwyrm/views/books.py +++ b/bookwyrm/views/books.py @@ -250,6 +250,11 @@ class Editions(View): """ list of editions of a book """ work = get_object_or_404(models.Work, id=book_id) + try: + page = int(request.GET.get("page", 1)) + except ValueError: + page = 1 + if is_api_request(request): return ActivitypubResponse(work.to_edition_list(**request.GET)) filters = {} @@ -262,8 +267,9 @@ class Editions(View): editions = work.editions.order_by("-edition_rank").all() languages = set(sum([e.languages for e in editions], [])) + paginated = Paginator(editions.filter(**filters).all(), PAGE_LENGTH) data = { - "editions": editions.filter(**filters).all(), + "editions": paginated.page(page), "work": work, "languages": languages, "formats": set( From 794b47d119f7a9b5c8ce1bfe429969628df41614 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 29 Mar 2021 12:27:12 -0700 Subject: [PATCH 0505/1285] Unshelve option in shelve button menu --- .../snippets/shelve_button/shelve_button.html | 2 +- .../shelve_button/shelve_button_options.html | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/bookwyrm/templates/snippets/shelve_button/shelve_button.html b/bookwyrm/templates/snippets/shelve_button/shelve_button.html index 586d7d9d..dc07fb64 100644 --- a/bookwyrm/templates/snippets/shelve_button/shelve_button.html +++ b/bookwyrm/templates/snippets/shelve_button/shelve_button.html @@ -3,6 +3,7 @@ {% with book.id|uuid as uuid %} {% active_shelf book as active_shelf %} +{% latest_read_through book request.user as readthrough %}
    {% if switch_mode and active_shelf.book != book %}
    @@ -20,7 +21,6 @@ {% include 'snippets/shelve_button/start_reading_modal.html' with book=active_shelf.book controls_text="start-reading" controls_uid=uuid %} -{% latest_read_through book request.user as readthrough %} {% include 'snippets/shelve_button/finish_reading_modal.html' with book=active_shelf.book controls_text="finish-reading" controls_uid=uuid readthrough=readthrough %} {% include 'snippets/shelve_button/progress_update_modal.html' with book=shelf_book.book controls_text="progress-update" controls_uid=uuid readthrough=readthrough %} diff --git a/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html b/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html index 56783b2c..a002917f 100644 --- a/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html +++ b/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html @@ -28,6 +28,8 @@ {% if dropdown %}{% endif %} {% endfor %} {% if dropdown %} + +{% if readthrough and active_shelf.shelf.identifier != 'read' %}
  • {% endif %} + +{% if active_shelf.shelf %} +
  • + +
  • +{% endif %} + +{% endif %} From c284a5e4091b816e52595365513d5fa69f769428 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 29 Mar 2021 13:07:22 -0700 Subject: [PATCH 0506/1285] Fixes error sending dm --- bookwyrm/activitystreams.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py index caa2d83a..d811aa49 100644 --- a/bookwyrm/activitystreams.py +++ b/bookwyrm/activitystreams.py @@ -128,6 +128,8 @@ class HomeStream(ActivityStream): def stream_users(self, status): audience = super().stream_users(status) + if not audience: + return [] return audience.filter( Q(id=status.user.id) # if the user is the post's author | Q(following=status.user) # if the user is following the author From 84dfa696955398971df40e88e6f0439eddb9d4c1 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 29 Mar 2021 13:28:30 -0700 Subject: [PATCH 0507/1285] Fixes invite flow --- bookwyrm/views/invite.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bookwyrm/views/invite.py b/bookwyrm/views/invite.py index 106afe65..49b5c91b 100644 --- a/bookwyrm/views/invite.py +++ b/bookwyrm/views/invite.py @@ -111,13 +111,13 @@ class ManageInviteRequests(View): invite_request = get_object_or_404( models.InviteRequest, id=request.POST.get("invite-request") ) - # allows re-sending invites - invite_request.invite, _ = models.SiteInvite.objects.get_or_create( - use_limit=1, - user=request.user, - ) - - invite_request.save() + # only create a new invite if one doesn't exist already (resending) + if not invite_request.invite: + invite_request.invite = models.SiteInvite.objects.create( + use_limit=1, + user=request.user, + ) + invite_request.save() emailing.invite_email(invite_request) return redirect("settings-invite-requests") From 3be420944eee1bcacc80c89c22a2c7cf51c5dc7c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 29 Mar 2021 13:51:06 -0700 Subject: [PATCH 0508/1285] Fixes display of dms pagination --- bookwyrm/templates/feed/direct_messages.html | 1 - bookwyrm/views/feed.py | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bookwyrm/templates/feed/direct_messages.html b/bookwyrm/templates/feed/direct_messages.html index 2c28a79b..f097fd9f 100644 --- a/bookwyrm/templates/feed/direct_messages.html +++ b/bookwyrm/templates/feed/direct_messages.html @@ -27,7 +27,6 @@
    {% endfor %} - {% include 'snippets/pagination.html' with page=activities path="direct-messages" %} {% endblock %} diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index d068834a..f96072d6 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -87,7 +87,9 @@ class DirectMessage(View): if user: queryset = queryset.filter(Q(user=user) | Q(mention_users=user)) - activities = privacy_filter(request.user, queryset, privacy_levels=["direct"]) + activities = privacy_filter( + request.user, queryset, privacy_levels=["direct"] + ).order_by('-published_date') paginated = Paginator(activities, PAGE_LENGTH) activity_page = paginated.page(page) From 662ddf44b910fbdc92ca7dca03757bba12692f88 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 29 Mar 2021 14:05:58 -0700 Subject: [PATCH 0509/1285] Avoid error when request Accept header is not set --- bookwyrm/tests/views/test_helpers.py | 5 +++++ bookwyrm/views/helpers.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index 2d100f7b..a9dadcad 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -84,6 +84,11 @@ class ViewsHelpers(TestCase): request.headers = {"Accept": "Praise"} self.assertFalse(views.helpers.is_api_request(request)) + def test_is_api_request_no_headers(self, _): + """ should it return html or json """ + request = self.factory.get("/path") + self.assertFalse(views.helpers.is_api_request(request)) + def test_is_bookwyrm_request(self, _): """ checks if a request came from a bookwyrm instance """ request = self.factory.get("", {"q": "Test Book"}) diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 52b5818f..006c6494 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -21,7 +21,7 @@ def get_user_from_username(viewer, username): def is_api_request(request): """ check whether a request is asking for html or data """ - return "json" in request.headers.get("Accept") or request.path[-5:] == ".json" + return "json" in request.headers.get("Accept", "") or request.path[-5:] == ".json" def is_bookwyrm_request(request): From 4115edad6eac840569d76335b8b865f0bebd3b70 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 29 Mar 2021 14:12:15 -0700 Subject: [PATCH 0510/1285] Adds robots.txt --- bookwyrm/templates/robots.txt | 5 +++++ bookwyrm/urls.py | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 bookwyrm/templates/robots.txt diff --git a/bookwyrm/templates/robots.txt b/bookwyrm/templates/robots.txt new file mode 100644 index 00000000..dc7b6bcb --- /dev/null +++ b/bookwyrm/templates/robots.txt @@ -0,0 +1,5 @@ +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file + +User-agent: * +Disallow: /static/js/ +Disallow: /static/css/ diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index a2250c48..f5c13a4b 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -2,7 +2,7 @@ from django.conf.urls.static import static from django.contrib import admin from django.urls import path, re_path - +from django.views.generic.base import TemplateView from bookwyrm import settings, views from bookwyrm.utils import regex @@ -27,6 +27,10 @@ handler404 = "bookwyrm.views.not_found_page" handler500 = "bookwyrm.views.server_error_page" urlpatterns = [ path("admin/", admin.site.urls), + path( + "robots.txt", + TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), + ), # federation endpoints re_path(r"^inbox/?$", views.Inbox.as_view()), re_path(r"%s/inbox/?$" % local_user_path, views.Inbox.as_view()), From 2722050ee2933f40b1113b65928d57458bbc6839 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 29 Mar 2021 14:20:51 -0700 Subject: [PATCH 0511/1285] Python formatting --- bookwyrm/views/feed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index f96072d6..eb4e370e 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -89,7 +89,7 @@ class DirectMessage(View): activities = privacy_filter( request.user, queryset, privacy_levels=["direct"] - ).order_by('-published_date') + ).order_by("-published_date") paginated = Paginator(activities, PAGE_LENGTH) activity_page = paginated.page(page) From 6bf3d9dcd88a1a28210e943dab74a6a49f21be47 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 29 Mar 2021 14:36:24 -0700 Subject: [PATCH 0512/1285] Adds hosts-meta path --- bookwyrm/templates/host_meta.xml | 5 +++++ bookwyrm/urls.py | 5 +++-- bookwyrm/views/__init__.py | 2 +- bookwyrm/views/wellknown.py | 7 +++++++ 4 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 bookwyrm/templates/host_meta.xml diff --git a/bookwyrm/templates/host_meta.xml b/bookwyrm/templates/host_meta.xml new file mode 100644 index 00000000..4fe8bcec --- /dev/null +++ b/bookwyrm/templates/host_meta.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index f5c13a4b..5d8bc5e9 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -35,8 +35,9 @@ urlpatterns = [ re_path(r"^inbox/?$", views.Inbox.as_view()), re_path(r"%s/inbox/?$" % local_user_path, views.Inbox.as_view()), re_path(r"%s/outbox/?$" % local_user_path, views.Outbox.as_view()), - re_path(r"^.well-known/webfinger/?$", views.webfinger), - re_path(r"^.well-known/nodeinfo/?$", views.nodeinfo_pointer), + re_path(r"^\.well-known/webfinger/?$", views.webfinger), + re_path(r"^\.well-known/nodeinfo/?$", views.nodeinfo_pointer), + re_path(r"^\.well-known/host-meta/?$", views.host_meta), re_path(r"^nodeinfo/2\.0/?$", views.nodeinfo), re_path(r"^api/v1/instance/?$", views.instance_info), re_path(r"^api/v1/instance/peers/?$", views.peers), diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 6ec79c1c..dc71d359 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -36,4 +36,4 @@ from .tag import Tag, AddTag, RemoveTag from .updates import get_notification_count, get_unread_status_count from .user import User, EditUser, Followers, Following from .user_admin import UserAdmin -from .wellknown import webfinger, nodeinfo_pointer, nodeinfo, instance_info, peers +from .wellknown import * diff --git a/bookwyrm/views/wellknown.py b/bookwyrm/views/wellknown.py index 127c7491..178d558e 100644 --- a/bookwyrm/views/wellknown.py +++ b/bookwyrm/views/wellknown.py @@ -3,6 +3,7 @@ from dateutil.relativedelta import relativedelta from django.http import HttpResponseNotFound from django.http import JsonResponse +from django.template.response import TemplateResponse from django.utils import timezone from django.views.decorators.http import require_GET @@ -118,3 +119,9 @@ def peers(_): """ list of federated servers this instance connects with """ names = models.FederatedServer.objects.values_list("server_name", flat=True) return JsonResponse(list(names), safe=False) + + +@require_GET +def host_meta(request): + """ meta of the host """ + return TemplateResponse(request, "host_meta.xml", {"DOMAIN": DOMAIN}) From 4b445c3242e316c4614f0cda973d8c9d3f862ba7 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 29 Mar 2021 14:55:23 -0700 Subject: [PATCH 0513/1285] Fixes indentation on host-meta xml --- bookwyrm/templates/host_meta.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/host_meta.xml b/bookwyrm/templates/host_meta.xml index 4fe8bcec..d510ba71 100644 --- a/bookwyrm/templates/host_meta.xml +++ b/bookwyrm/templates/host_meta.xml @@ -1,5 +1,5 @@ - + From 28f07f772994363a8fbb68fcbb2f2596f9f3ddd5 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 29 Mar 2021 18:47:21 -0700 Subject: [PATCH 0514/1285] Fixes error pages --- bookwyrm/templates/{notfound.html => 404.html} | 0 bookwyrm/templates/{error.html => 500.html} | 0 bookwyrm/templates/directory/directory.html | 4 ++-- bookwyrm/urls.py | 2 -- bookwyrm/views/error.py | 12 ------------ 5 files changed, 2 insertions(+), 16 deletions(-) rename bookwyrm/templates/{notfound.html => 404.html} (100%) rename bookwyrm/templates/{error.html => 500.html} (100%) delete mode 100644 bookwyrm/views/error.py diff --git a/bookwyrm/templates/notfound.html b/bookwyrm/templates/404.html similarity index 100% rename from bookwyrm/templates/notfound.html rename to bookwyrm/templates/404.html diff --git a/bookwyrm/templates/error.html b/bookwyrm/templates/500.html similarity index 100% rename from bookwyrm/templates/error.html rename to bookwyrm/templates/500.html diff --git a/bookwyrm/templates/directory/directory.html b/bookwyrm/templates/directory/directory.html index 7413f67d..430e8539 100644 --- a/bookwyrm/templates/directory/directory.html +++ b/bookwyrm/templates/directory/directory.html @@ -44,9 +44,9 @@
    - + {% include 'snippets/avatar.html' with user=user large=True %} - +
    {{ user.display_name }} diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 5d8bc5e9..33c4aaad 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -23,8 +23,6 @@ status_path = r"%s/(%s)/(?P\d+)" % (user_path, "|".join(status_types) book_path = r"^book/(?P\d+)" -handler404 = "bookwyrm.views.not_found_page" -handler500 = "bookwyrm.views.server_error_page" urlpatterns = [ path("admin/", admin.site.urls), path( diff --git a/bookwyrm/views/error.py b/bookwyrm/views/error.py deleted file mode 100644 index 9bd94ca3..00000000 --- a/bookwyrm/views/error.py +++ /dev/null @@ -1,12 +0,0 @@ -""" something has gone amiss """ -from django.template.response import TemplateResponse - - -def server_error_page(request): - """ 500 errors """ - return TemplateResponse(request, "error.html", status=500) - - -def not_found_page(request, _): - """ 404s """ - return TemplateResponse(request, "notfound.html", status=404) From 4b325fedee43c1f77251ef8a4230fa02a8dc6ae6 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 29 Mar 2021 18:51:35 -0700 Subject: [PATCH 0515/1285] Removes error views from init --- bookwyrm/views/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index dc71d359..693a7744 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -5,7 +5,6 @@ from .block import Block, unblock from .books import Book, EditBook, ConfirmEditBook, Editions from .books import upload_cover, add_description, switch_edition, resolve_book from .directory import Directory -from .error import not_found_page, server_error_page from .federation import Federation, FederatedServer from .feed import DirectMessage, Feed, Replies, Status from .follow import follow, unfollow From a54014f693735cb637df86e5b07b5e7e55079bee Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 30 Mar 2021 08:43:38 -0700 Subject: [PATCH 0516/1285] Fixes import retry --- bookwyrm/goodreads_import.py | 14 ++++++++------ bookwyrm/importer.py | 6 +++++- bookwyrm/librarything_import.py | 28 ++++++++++++++-------------- bookwyrm/tests/views/test_import.py | 3 +++ bookwyrm/views/import_data.py | 6 ++++-- 5 files changed, 34 insertions(+), 23 deletions(-) diff --git a/bookwyrm/goodreads_import.py b/bookwyrm/goodreads_import.py index fb4e8e0f..298fd7cf 100644 --- a/bookwyrm/goodreads_import.py +++ b/bookwyrm/goodreads_import.py @@ -1,14 +1,16 @@ """ handle reading a csv from goodreads """ from bookwyrm.importer import Importer -# GoodReads is the default importer, thus Importer follows its structure. For a more complete example of overriding see librarything_import.py - class GoodreadsImporter(Importer): + """ GoodReads is the default importer, thus Importer follows its structure. + For a more complete example of overriding see librarything_import.py """ + service = "GoodReads" - def parse_fields(self, data): - data.update({"import_source": self.service}) + def parse_fields(self, entry): + """ handle the specific fields in goodreads csvs """ + entry.update({"import_source": self.service}) # add missing 'Date Started' field - data.update({"Date Started": None}) - return data + entry.update({"Date Started": None}) + return entry diff --git a/bookwyrm/importer.py b/bookwyrm/importer.py index 2fbb3430..4ab7ba8c 100644 --- a/bookwyrm/importer.py +++ b/bookwyrm/importer.py @@ -10,6 +10,8 @@ logger = logging.getLogger(__name__) class Importer: + """ Generic class for csv data import from an outside service """ + service = "Unknown" delimiter = "," encoding = "UTF-8" @@ -29,10 +31,12 @@ class Importer: self.save_item(job, index, entry) return job - def save_item(self, job, index, data): + def save_item(self, job, index, data):# pylint: disable=no-self-use + """ creates and saves an import item """ ImportItem(job=job, index=index, data=data).save() def parse_fields(self, entry): + """ updates csv data with additional info """ entry.update({"import_source": self.service}) return entry diff --git a/bookwyrm/librarything_import.py b/bookwyrm/librarything_import.py index b3dd9d56..6389eeed 100644 --- a/bookwyrm/librarything_import.py +++ b/bookwyrm/librarything_import.py @@ -1,35 +1,35 @@ """ handle reading a csv from librarything """ -import csv import re import math -from bookwyrm import models -from bookwyrm.models import ImportItem from bookwyrm.importer import Importer class LibrarythingImporter(Importer): + """ csv downloads from librarything """ + service = "LibraryThing" delimiter = "\t" encoding = "ISO-8859-1" # mandatory_fields : fields matching the book title and author mandatory_fields = ["Title", "Primary Author"] - def parse_fields(self, initial): + def parse_fields(self, entry): + """ custom parsing for librarything """ data = {} data["import_source"] = self.service - data["Book Id"] = initial["Book Id"] - data["Title"] = initial["Title"] - data["Author"] = initial["Primary Author"] - data["ISBN13"] = initial["ISBN"] - data["My Review"] = initial["Review"] - if initial["Rating"]: - data["My Rating"] = math.ceil(float(initial["Rating"])) + data["Book Id"] = entry["Book Id"] + data["Title"] = entry["Title"] + data["Author"] = entry["Primary Author"] + data["ISBN13"] = entry["ISBN"] + data["My Review"] = entry["Review"] + if entry["Rating"]: + data["My Rating"] = math.ceil(float(entry["Rating"])) else: data["My Rating"] = "" - data["Date Added"] = re.sub("\[|\]", "", initial["Entry Date"]) - data["Date Started"] = re.sub("\[|\]", "", initial["Date Started"]) - data["Date Read"] = re.sub("\[|\]", "", initial["Date Read"]) + data["Date Added"] = re.sub(r"\[|\]", "", entry["Entry Date"]) + data["Date Started"] = re.sub(r"\[|\]", "", entry["Date Started"]) + data["Date Read"] = re.sub(r"\[|\]", "", entry["Date Read"]) data["Exclusive Shelf"] = None if data["Date Read"]: diff --git a/bookwyrm/tests/views/test_import.py b/bookwyrm/tests/views/test_import.py index b98b2516..df785535 100644 --- a/bookwyrm/tests/views/test_import.py +++ b/bookwyrm/tests/views/test_import.py @@ -45,3 +45,6 @@ class ImportViews(TestCase): self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) + + def test_retry_import(self): + """ retry failed items """ diff --git a/bookwyrm/views/import_data.py b/bookwyrm/views/import_data.py index 8f9ea27f..dadc91d6 100644 --- a/bookwyrm/views/import_data.py +++ b/bookwyrm/views/import_data.py @@ -10,6 +10,7 @@ from django.utils.decorators import method_decorator from django.views import View from bookwyrm import forms, goodreads_import, librarything_import, models +from bookwyrm.importer import Importer from bookwyrm.tasks import app # pylint: disable= no-self-use @@ -89,10 +90,11 @@ class ImportStatus(View): for item in request.POST.getlist("import_item"): items.append(get_object_or_404(models.ImportItem, id=item)) - job = goodreads_import.create_retry_job( + importer = Importer() + job = importer.create_retry_job( request.user, job, items, ) - goodreads_import.start_import(job) + importer.start_import(job) return redirect("/import/%d" % job.id) From 1f0c4f86c2af92273c177bafc89d44ce3617a053 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 30 Mar 2021 08:46:22 -0700 Subject: [PATCH 0517/1285] Python formatting --- bookwyrm/goodreads_import.py | 4 ++-- bookwyrm/importer.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bookwyrm/goodreads_import.py b/bookwyrm/goodreads_import.py index 298fd7cf..b439993a 100644 --- a/bookwyrm/goodreads_import.py +++ b/bookwyrm/goodreads_import.py @@ -3,8 +3,8 @@ from bookwyrm.importer import Importer class GoodreadsImporter(Importer): - """ GoodReads is the default importer, thus Importer follows its structure. - For a more complete example of overriding see librarything_import.py """ + """GoodReads is the default importer, thus Importer follows its structure. + For a more complete example of overriding see librarything_import.py""" service = "GoodReads" diff --git a/bookwyrm/importer.py b/bookwyrm/importer.py index 4ab7ba8c..ddbfa304 100644 --- a/bookwyrm/importer.py +++ b/bookwyrm/importer.py @@ -31,7 +31,7 @@ class Importer: self.save_item(job, index, entry) return job - def save_item(self, job, index, data):# pylint: disable=no-self-use + def save_item(self, job, index, data): # pylint: disable=no-self-use """ creates and saves an import item """ ImportItem(job=job, index=index, data=data).save() From 754ccaedd6d9c2e348116b041f6d9226f4ecd805 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 30 Mar 2021 08:56:25 -0700 Subject: [PATCH 0518/1285] Move importers into module --- bookwyrm/importers/__init__.py | 5 +++++ bookwyrm/{ => importers}/goodreads_import.py | 2 +- bookwyrm/{ => importers}/importer.py | 0 bookwyrm/{ => importers}/librarything_import.py | 2 +- bookwyrm/tests/importers/__init__.py | 1 + bookwyrm/tests/{ => importers}/test_goodreads_import.py | 4 ++-- .../tests/{ => importers}/test_librarything_import.py | 4 ++-- bookwyrm/views/import_data.py | 8 ++++---- 8 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 bookwyrm/importers/__init__.py rename bookwyrm/{ => importers}/goodreads_import.py (93%) rename bookwyrm/{ => importers}/importer.py (100%) rename bookwyrm/{ => importers}/librarything_import.py (97%) create mode 100644 bookwyrm/tests/importers/__init__.py rename bookwyrm/tests/{ => importers}/test_goodreads_import.py (99%) rename bookwyrm/tests/{ => importers}/test_librarything_import.py (99%) diff --git a/bookwyrm/importers/__init__.py b/bookwyrm/importers/__init__.py new file mode 100644 index 00000000..f13672e0 --- /dev/null +++ b/bookwyrm/importers/__init__.py @@ -0,0 +1,5 @@ +""" import classes """ + +from .importer import Importer +from .goodreads_import import GoodreadsImporter +from .librarything_import import LibrarythingImporter diff --git a/bookwyrm/goodreads_import.py b/bookwyrm/importers/goodreads_import.py similarity index 93% rename from bookwyrm/goodreads_import.py rename to bookwyrm/importers/goodreads_import.py index b439993a..0b126c14 100644 --- a/bookwyrm/goodreads_import.py +++ b/bookwyrm/importers/goodreads_import.py @@ -1,5 +1,5 @@ """ handle reading a csv from goodreads """ -from bookwyrm.importer import Importer +from . import Importer class GoodreadsImporter(Importer): diff --git a/bookwyrm/importer.py b/bookwyrm/importers/importer.py similarity index 100% rename from bookwyrm/importer.py rename to bookwyrm/importers/importer.py diff --git a/bookwyrm/librarything_import.py b/bookwyrm/importers/librarything_import.py similarity index 97% rename from bookwyrm/librarything_import.py rename to bookwyrm/importers/librarything_import.py index 6389eeed..3755cb1a 100644 --- a/bookwyrm/librarything_import.py +++ b/bookwyrm/importers/librarything_import.py @@ -2,7 +2,7 @@ import re import math -from bookwyrm.importer import Importer +from . import Importer class LibrarythingImporter(Importer): diff --git a/bookwyrm/tests/importers/__init__.py b/bookwyrm/tests/importers/__init__.py new file mode 100644 index 00000000..b6e690fd --- /dev/null +++ b/bookwyrm/tests/importers/__init__.py @@ -0,0 +1 @@ +from . import * diff --git a/bookwyrm/tests/test_goodreads_import.py b/bookwyrm/tests/importers/test_goodreads_import.py similarity index 99% rename from bookwyrm/tests/test_goodreads_import.py rename to bookwyrm/tests/importers/test_goodreads_import.py index c06b49fc..83773e3c 100644 --- a/bookwyrm/tests/test_goodreads_import.py +++ b/bookwyrm/tests/importers/test_goodreads_import.py @@ -7,8 +7,8 @@ from unittest.mock import patch from django.test import TestCase import responses -from bookwyrm import models, importer -from bookwyrm.goodreads_import import GoodreadsImporter +from bookwyrm import models +from bookwyrm.importers import importer, GoodreadsImporter from bookwyrm.settings import DOMAIN diff --git a/bookwyrm/tests/test_librarything_import.py b/bookwyrm/tests/importers/test_librarything_import.py similarity index 99% rename from bookwyrm/tests/test_librarything_import.py rename to bookwyrm/tests/importers/test_librarything_import.py index 54b8b422..8baa305f 100644 --- a/bookwyrm/tests/test_librarything_import.py +++ b/bookwyrm/tests/importers/test_librarything_import.py @@ -6,8 +6,8 @@ from unittest.mock import patch from django.test import TestCase import responses -from bookwyrm import models, importer -from bookwyrm.librarything_import import LibrarythingImporter +from bookwyrm import models +from bookwyrm.importers import importer, LibrarythingImporter from bookwyrm.settings import DOMAIN diff --git a/bookwyrm/views/import_data.py b/bookwyrm/views/import_data.py index dadc91d6..5bdbe915 100644 --- a/bookwyrm/views/import_data.py +++ b/bookwyrm/views/import_data.py @@ -9,8 +9,8 @@ from django.template.response import TemplateResponse from django.utils.decorators import method_decorator from django.views import View -from bookwyrm import forms, goodreads_import, librarything_import, models -from bookwyrm.importer import Importer +from bookwyrm import forms, models +from bookwyrm.importers import Importer, LibrarythingImporter, GoodreadsImporter from bookwyrm.tasks import app # pylint: disable= no-self-use @@ -41,10 +41,10 @@ class Import(View): importer = None if source == "LibraryThing": - importer = librarything_import.LibrarythingImporter() + importer = LibrarythingImporter() else: # Default : GoodReads - importer = goodreads_import.GoodreadsImporter() + importer = GoodreadsImporter() try: job = importer.create_job( From c64fc79431ebb5a1c25afbb49edd51dc2d5d3fec Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 30 Mar 2021 09:04:11 -0700 Subject: [PATCH 0519/1285] Updates goodreads tests --- .../tests/importers/test_goodreads_import.py | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/bookwyrm/tests/importers/test_goodreads_import.py b/bookwyrm/tests/importers/test_goodreads_import.py index 83773e3c..6e9caaf4 100644 --- a/bookwyrm/tests/importers/test_goodreads_import.py +++ b/bookwyrm/tests/importers/test_goodreads_import.py @@ -8,7 +8,8 @@ from django.test import TestCase import responses from bookwyrm import models -from bookwyrm.importers import importer, GoodreadsImporter +from bookwyrm.importers import GoodreadsImporter +from bookwyrm.importers.importer import import_data, handle_imported_book from bookwyrm.settings import DOMAIN @@ -18,7 +19,7 @@ class GoodreadsImport(TestCase): def setUp(self): """ use a test csv """ self.importer = GoodreadsImporter() - datafile = pathlib.Path(__file__).parent.joinpath("data/goodreads.csv") + datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") self.csv = open(datafile, "r", encoding=self.importer.encoding) self.user = models.User.objects.create_user( "mouse", "mouse@mouse.mouse", "password", local=True @@ -81,7 +82,7 @@ class GoodreadsImport(TestCase): import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") MockTask = namedtuple("Task", ("id")) mock_task = MockTask(7) - with patch("bookwyrm.importer.import_data.delay") as start: + with patch("bookwyrm.importers.importer.import_data.delay") as start: start.return_value = mock_task self.importer.start_import(import_job) import_job.refresh_from_db() @@ -97,8 +98,8 @@ class GoodreadsImport(TestCase): "bookwyrm.models.import_job.ImportItem.get_book_from_isbn" ) as resolve: resolve.return_value = book - with patch("bookwyrm.importer.handle_imported_book"): - importer.import_data(self.importer.service, import_job.id) + with patch("bookwyrm.importers.importer.handle_imported_book"): + import_data(self.importer.service, import_job.id) import_item = models.ImportItem.objects.get(job=import_job, index=0) self.assertEqual(import_item.book.id, book.id) @@ -109,7 +110,7 @@ class GoodreadsImport(TestCase): self.assertIsNone(shelf.books.first()) import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath("data/goodreads.csv") + datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") csv_file = open(datafile, "r") for index, entry in enumerate(list(csv.DictReader(csv_file))): entry = self.importer.parse_fields(entry) @@ -119,7 +120,7 @@ class GoodreadsImport(TestCase): break with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - importer.handle_imported_book( + handle_imported_book( self.importer.service, self.user, import_item, False, "public" ) @@ -143,7 +144,7 @@ class GoodreadsImport(TestCase): models.ShelfBook.objects.create(shelf=shelf, user=self.user, book=self.book) import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath("data/goodreads.csv") + datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") csv_file = open(datafile, "r") for index, entry in enumerate(list(csv.DictReader(csv_file))): entry = self.importer.parse_fields(entry) @@ -153,7 +154,7 @@ class GoodreadsImport(TestCase): break with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - importer.handle_imported_book( + handle_imported_book( self.importer.service, self.user, import_item, False, "public" ) @@ -173,7 +174,7 @@ class GoodreadsImport(TestCase): """ re-importing books """ shelf = self.user.shelf_set.filter(identifier="read").first() import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath("data/goodreads.csv") + datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") csv_file = open(datafile, "r") for index, entry in enumerate(list(csv.DictReader(csv_file))): entry = self.importer.parse_fields(entry) @@ -183,10 +184,10 @@ class GoodreadsImport(TestCase): break with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - importer.handle_imported_book( + handle_imported_book( self.importer.service, self.user, import_item, False, "public" ) - importer.handle_imported_book( + handle_imported_book( self.importer.service, self.user, import_item, False, "public" ) @@ -207,7 +208,7 @@ class GoodreadsImport(TestCase): def test_handle_imported_book_review(self, _): """ goodreads review import """ import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath("data/goodreads.csv") + datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") csv_file = open(datafile, "r") entry = list(csv.DictReader(csv_file))[2] entry = self.importer.parse_fields(entry) @@ -216,7 +217,7 @@ class GoodreadsImport(TestCase): ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - importer.handle_imported_book( + handle_imported_book( self.importer.service, self.user, import_item, True, "unlisted" ) review = models.Review.objects.get(book=self.book, user=self.user) @@ -230,7 +231,7 @@ class GoodreadsImport(TestCase): def test_handle_imported_book_reviews_disabled(self): """ goodreads review import """ import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath("data/goodreads.csv") + datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") csv_file = open(datafile, "r") entry = list(csv.DictReader(csv_file))[2] entry = self.importer.parse_fields(entry) @@ -239,7 +240,7 @@ class GoodreadsImport(TestCase): ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - importer.handle_imported_book( + handle_imported_book( self.importer.service, self.user, import_item, False, "unlisted" ) self.assertFalse( From 5deb7d8bba7bca7664e78a0ab1cc3e05fb586f3c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 30 Mar 2021 09:13:41 -0700 Subject: [PATCH 0520/1285] Updates librarthing importer tests --- .../importers/test_librarything_import.py | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/bookwyrm/tests/importers/test_librarything_import.py b/bookwyrm/tests/importers/test_librarything_import.py index 8baa305f..5e1d778e 100644 --- a/bookwyrm/tests/importers/test_librarything_import.py +++ b/bookwyrm/tests/importers/test_librarything_import.py @@ -7,7 +7,8 @@ from django.test import TestCase import responses from bookwyrm import models -from bookwyrm.importers import importer, LibrarythingImporter +from bookwyrm.importers import LibrarythingImporter +from bookwyrm.importers.importer import import_data, handle_imported_book from bookwyrm.settings import DOMAIN @@ -17,7 +18,7 @@ class LibrarythingImport(TestCase): def setUp(self): """ use a test tsv """ self.importer = LibrarythingImporter() - datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv") + datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv") # Librarything generates latin encoded exports... self.csv = open(datafile, "r", encoding=self.importer.encoding) @@ -87,8 +88,8 @@ class LibrarythingImport(TestCase): "bookwyrm.models.import_job.ImportItem.get_book_from_isbn" ) as resolve: resolve.return_value = book - with patch("bookwyrm.importer.handle_imported_book"): - importer.import_data(self.importer.service, import_job.id) + with patch("bookwyrm.importers.importer.handle_imported_book"): + import_data(self.importer.service, import_job.id) import_item = models.ImportItem.objects.get(job=import_job, index=0) self.assertEqual(import_item.book.id, book.id) @@ -99,7 +100,7 @@ class LibrarythingImport(TestCase): self.assertIsNone(shelf.books.first()) import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv") + datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv") csv_file = open(datafile, "r", encoding=self.importer.encoding) for index, entry in enumerate( list(csv.DictReader(csv_file, delimiter=self.importer.delimiter)) @@ -111,7 +112,7 @@ class LibrarythingImport(TestCase): break with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - importer.handle_imported_book( + handle_imported_book( self.importer.service, self.user, import_item, False, "public" ) @@ -135,7 +136,7 @@ class LibrarythingImport(TestCase): models.ShelfBook.objects.create(shelf=shelf, user=self.user, book=self.book) import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv") + datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv") csv_file = open(datafile, "r", encoding=self.importer.encoding) for index, entry in enumerate( list(csv.DictReader(csv_file, delimiter=self.importer.delimiter)) @@ -147,7 +148,7 @@ class LibrarythingImport(TestCase): break with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - importer.handle_imported_book( + handle_imported_book( self.importer.service, self.user, import_item, False, "public" ) @@ -167,7 +168,7 @@ class LibrarythingImport(TestCase): """ re-importing books """ shelf = self.user.shelf_set.filter(identifier="read").first() import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv") + datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv") csv_file = open(datafile, "r", encoding=self.importer.encoding) for index, entry in enumerate( list(csv.DictReader(csv_file, delimiter=self.importer.delimiter)) @@ -179,10 +180,10 @@ class LibrarythingImport(TestCase): break with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - importer.handle_imported_book( + handle_imported_book( self.importer.service, self.user, import_item, False, "public" ) - importer.handle_imported_book( + handle_imported_book( self.importer.service, self.user, import_item, False, "public" ) @@ -203,7 +204,7 @@ class LibrarythingImport(TestCase): def test_handle_imported_book_review(self, _): """ librarything review import """ import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv") + datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv") csv_file = open(datafile, "r", encoding=self.importer.encoding) entry = list(csv.DictReader(csv_file, delimiter=self.importer.delimiter))[0] entry = self.importer.parse_fields(entry) @@ -212,7 +213,7 @@ class LibrarythingImport(TestCase): ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - importer.handle_imported_book( + handle_imported_book( self.importer.service, self.user, import_item, True, "unlisted" ) review = models.Review.objects.get(book=self.book, user=self.user) @@ -226,7 +227,7 @@ class LibrarythingImport(TestCase): def test_handle_imported_book_reviews_disabled(self): """ librarything review import """ import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv") + datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv") csv_file = open(datafile, "r", encoding=self.importer.encoding) entry = list(csv.DictReader(csv_file, delimiter=self.importer.delimiter))[2] entry = self.importer.parse_fields(entry) @@ -235,7 +236,7 @@ class LibrarythingImport(TestCase): ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - importer.handle_imported_book( + handle_imported_book( self.importer.service, self.user, import_item, False, "unlisted" ) self.assertFalse( From 59ebcc62ee35cbda36a49d9969936ea95592df94 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 30 Mar 2021 09:24:23 -0700 Subject: [PATCH 0521/1285] Adds import retry test --- bookwyrm/tests/views/test_import.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bookwyrm/tests/views/test_import.py b/bookwyrm/tests/views/test_import.py index df785535..dc23f789 100644 --- a/bookwyrm/tests/views/test_import.py +++ b/bookwyrm/tests/views/test_import.py @@ -48,3 +48,20 @@ class ImportViews(TestCase): def test_retry_import(self): """ retry failed items """ + view = views.ImportStatus.as_view() + import_job = models.ImportJob.objects.create( + user=self.local_user, + privacy="unlisted" + ) + request = self.factory.post("") + request.user = self.local_user + + with patch("bookwyrm.importers.Importer.start_import"): + view(request, import_job.id) + + self.assertEqual(models.ImportJob.objects.count(), 2) + retry_job = models.ImportJob.objects.last() + + self.assertTrue(retry_job.retry) + self.assertEqual(retry_job.user, self.local_user) + self.assertEqual(retry_job.privacy, "unlisted") From 047e827382e264bdafcd0c5fedc4248442f8f345 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 30 Mar 2021 09:30:25 -0700 Subject: [PATCH 0522/1285] Cleans up markup on import page --- bookwyrm/templates/import.html | 57 +++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/bookwyrm/templates/import.html b/bookwyrm/templates/import.html index 99ff5c42..a5405131 100644 --- a/bookwyrm/templates/import.html +++ b/bookwyrm/templates/import.html @@ -7,36 +7,43 @@ {% block content %}

    {% trans "Import Books" %}

    -
    + {% csrf_token %} -
    - +
    +
    + +
    + +
    +
    + + {{ import_form.csv_file }} +
    - -
    - {{ import_form.as_p }} +
    +
    + +
    +
    + +
    -
    - -
    -
    -
    From b16f95c8cd9995b7a2cf8f440d9d597686ad9b04 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 30 Mar 2021 09:50:51 -0700 Subject: [PATCH 0523/1285] Python formatting --- bookwyrm/tests/views/test_import.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bookwyrm/tests/views/test_import.py b/bookwyrm/tests/views/test_import.py index dc23f789..4de2cfb9 100644 --- a/bookwyrm/tests/views/test_import.py +++ b/bookwyrm/tests/views/test_import.py @@ -50,8 +50,7 @@ class ImportViews(TestCase): """ retry failed items """ view = views.ImportStatus.as_view() import_job = models.ImportJob.objects.create( - user=self.local_user, - privacy="unlisted" + user=self.local_user, privacy="unlisted" ) request = self.factory.post("") request.user = self.local_user From 9b949d9845ecd3cd521a4bc51b9164adda29cf57 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 30 Mar 2021 10:19:51 -0700 Subject: [PATCH 0524/1285] Merges display of lists to show all Rather than separating out "your lists" --- bookwyrm/templates/lists/list_items.html | 8 +++++++- bookwyrm/templates/lists/lists.html | 26 ++++++++---------------- bookwyrm/views/list.py | 16 ++++++--------- 3 files changed, 21 insertions(+), 29 deletions(-) diff --git a/bookwyrm/templates/lists/list_items.html b/bookwyrm/templates/lists/list_items.html index 04e7f014..3e3e8bf4 100644 --- a/bookwyrm/templates/lists/list_items.html +++ b/bookwyrm/templates/lists/list_items.html @@ -14,7 +14,13 @@ {% endfor %}
    - {% if list.description %}{{ list.description | to_markdown | safe | truncatewords_html:20 }}{% endif %} +
    + {% if list.description %} + {{ list.description|to_markdown|safe|truncatechars_html:30 }} + {% else %} +   + {% endif %} +

    {% include 'lists/created_text.html' with list=list %}

    diff --git a/bookwyrm/templates/lists/lists.html b/bookwyrm/templates/lists/lists.html index f7ab020a..a9258305 100644 --- a/bookwyrm/templates/lists/lists.html +++ b/bookwyrm/templates/lists/lists.html @@ -1,17 +1,19 @@ {% extends 'layout.html' %} +{% load bookwyrm_tags %} {% load i18n %} {% block title %}{% trans "Lists" %}{% endblock %} {% block content %} -
    -

    {% trans "Lists" %}

    -
    -{% if request.user.is_authenticated and not lists.has_previous %}
    {% trans "Create List" as button_text %} @@ -23,23 +25,11 @@ {% include 'lists/create_form.html' with controls_text="create-list" %}
    -
    - {% if request.user.list_set.exists %} - {% include 'lists/list_items.html' with lists=request.user.list_set.all|slice:4 %} - {% endif %} - - {% if request.user.list_set.count > 4 %} - {% blocktrans with size=request.user.list_set.count %}See all {{ size }} lists{% endblocktrans %} - {% endif %} -
    -{% endif %} - - {% if lists %}
    -

    {% trans "Recent Lists" %}

    {% include 'lists/list_items.html' with lists=lists %}
    +
    {% include 'snippets/pagination.html' with page=lists path=path %}
    diff --git a/bookwyrm/views/list.py b/bookwyrm/views/list.py index 91475d48..5c88e189 100644 --- a/bookwyrm/views/list.py +++ b/bookwyrm/views/list.py @@ -27,17 +27,13 @@ class Lists(View): except ValueError: page = 1 - user = request.user if request.user.is_authenticated else None # hide lists with no approved books - lists = ( - models.List.objects.filter( - ~Q(user=user), - ) - .annotate(item_count=Count("listitem", filter=Q(listitem__approved=True))) - .filter(item_count__gt=0) - .distinct() - .all() - ) + lists = models.List.objects.annotate( + item_count=Count("listitem", filter=Q(listitem__approved=True)) + ).filter( + item_count__gt=0 + ).distinct().all() + lists = privacy_filter( request.user, lists, privacy_levels=["public", "followers"] ) From f7b0a282a7b756d2f180f76d30359442af463a84 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 30 Mar 2021 10:28:50 -0700 Subject: [PATCH 0525/1285] Set updated date on list when item is added --- bookwyrm/models/list.py | 5 +++++ bookwyrm/views/list.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/bookwyrm/models/list.py b/bookwyrm/models/list.py index a05325f3..880c4122 100644 --- a/bookwyrm/models/list.py +++ b/bookwyrm/models/list.py @@ -1,6 +1,7 @@ """ make a list of books!! """ from django.apps import apps from django.db import models +from django.utils import timezone from bookwyrm import activitypub from bookwyrm.settings import DOMAIN @@ -79,6 +80,10 @@ class ListItem(CollectionItemMixin, BookWyrmModel): """ create a notification too """ created = not bool(self.id) super().save(*args, **kwargs) + # tick the updated date on the parent list + self.book_list.updated_date = timezone.now() + self.book_list.save(broadcast=False) + list_owner = self.book_list.user # create a notification if somoene ELSE added to a local user's list if created and list_owner.local and list_owner != self.user: diff --git a/bookwyrm/views/list.py b/bookwyrm/views/list.py index 5c88e189..75ee72ba 100644 --- a/bookwyrm/views/list.py +++ b/bookwyrm/views/list.py @@ -32,7 +32,7 @@ class Lists(View): item_count=Count("listitem", filter=Q(listitem__approved=True)) ).filter( item_count__gt=0 - ).distinct().all() + ).order_by("-updated_date").distinct().all() lists = privacy_filter( request.user, lists, privacy_levels=["public", "followers"] From daea57f91c4df4e9ad885325953f437ca763880f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 30 Mar 2021 10:31:23 -0700 Subject: [PATCH 0526/1285] Updates python formatting --- bookwyrm/views/list.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/bookwyrm/views/list.py b/bookwyrm/views/list.py index 75ee72ba..7724cd13 100644 --- a/bookwyrm/views/list.py +++ b/bookwyrm/views/list.py @@ -28,11 +28,15 @@ class Lists(View): page = 1 # hide lists with no approved books - lists = models.List.objects.annotate( - item_count=Count("listitem", filter=Q(listitem__approved=True)) - ).filter( - item_count__gt=0 - ).order_by("-updated_date").distinct().all() + lists = ( + models.List.objects.annotate( + item_count=Count("listitem", filter=Q(listitem__approved=True)) + ) + .filter(item_count__gt=0) + .order_by("-updated_date") + .distinct() + .all() + ) lists = privacy_filter( request.user, lists, privacy_levels=["public", "followers"] From 0365a57307aee22b2ddbeb56a0ff2fa932ec9f6c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 30 Mar 2021 10:46:02 -0700 Subject: [PATCH 0527/1285] Handle invalid status urls with 404 --- bookwyrm/tests/views/test_feed.py | 12 ++++++++++++ bookwyrm/views/feed.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/bookwyrm/tests/views/test_feed.py b/bookwyrm/tests/views/test_feed.py index 1ff99573..dd38a3eb 100644 --- a/bookwyrm/tests/views/test_feed.py +++ b/bookwyrm/tests/views/test_feed.py @@ -66,6 +66,18 @@ class FeedViews(TestCase): self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) + def test_status_page_not_found(self, *_): + """ there are so many views, this just makes sure it LOADS """ + view = views.Status.as_view() + + request = self.factory.get("") + request.user = self.local_user + with patch("bookwyrm.views.feed.is_api_request") as is_api: + is_api.return_value = False + result = view(request, "mouse", 12345) + + self.assertEqual(result.status_code, 404) + def test_status_page_with_image(self, *_): """ there are so many views, this just makes sure it LOADS """ view = views.Status.as_view() diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index eb4e370e..9f56dae5 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -115,7 +115,7 @@ class Status(View): status = models.Status.objects.select_subclasses().get( id=status_id, deleted=False ) - except ValueError: + except (ValueError, models.Status.DoesNotExist): return HttpResponseNotFound() # the url should have the poster's username in it From cc01105bf00b86508a1487e0a182e8a3e2152333 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Wed, 31 Mar 2021 16:15:30 +0200 Subject: [PATCH 0528/1285] Add linter for CSS files: - Add Github Action. - Update .gitignore. - Add .stylelintignore for vendor related files. - Fix format.css to match rules (includes hacks with @todo). --- .github/workflows/linters-frontend.yaml | 24 + .gitignore | 3 + .stylelintignore | 2 + .stylelintrc.js | 17 + bookwyrm/static/css/format.css | 104 +- package.json | 7 + yarn.lock | 1897 +++++++++++++++++++++++ 7 files changed, 2020 insertions(+), 34 deletions(-) create mode 100644 .github/workflows/linters-frontend.yaml create mode 100644 .stylelintignore create mode 100644 .stylelintrc.js create mode 100644 package.json create mode 100644 yarn.lock diff --git a/.github/workflows/linters-frontend.yaml b/.github/workflows/linters-frontend.yaml new file mode 100644 index 00000000..47c3da0d --- /dev/null +++ b/.github/workflows/linters-frontend.yaml @@ -0,0 +1,24 @@ +# @url https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions +name: Frontend Linters + +on: + push: + branches: [ main, ci ] + paths: + - 'static/**' + pull_request: + branches: [ main, ci ] + +jobs: + linters: + name: linters + runs-on: ubuntu-20.04 + + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: stylelinter + uses: actions-hub/stylelint@v1.1.3 + env: + PATTERN: "*.css" diff --git a/.gitignore b/.gitignore index 00dd239d..71fa61bf 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ #PyCharm .idea + +#Node tools +/node_modules/ diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 00000000..f456cb22 --- /dev/null +++ b/.stylelintignore @@ -0,0 +1,2 @@ +bookwyrm/static/css/bulma.*.css* +bookwyrm/static/css/icons.css diff --git a/.stylelintrc.js b/.stylelintrc.js new file mode 100644 index 00000000..eadc4a89 --- /dev/null +++ b/.stylelintrc.js @@ -0,0 +1,17 @@ +/* global module */ + +module.exports = { + "extends": "stylelint-config-standard", + + "plugins": [ + "stylelint-order" + ], + + "rules": { + "order/order": [ + "custom-properties", + "declarations" + ], + "indentation": 4 + } +}; diff --git a/bookwyrm/static/css/format.css b/bookwyrm/static/css/format.css index 0956517d..65fd56ab 100644 --- a/bookwyrm/static/css/format.css +++ b/bookwyrm/static/css/format.css @@ -7,6 +7,7 @@ html { .image { overflow: hidden; } + .navbar .logo { max-height: 50px; } @@ -21,25 +22,33 @@ html { } /* --- SHELVING --- */ + +/** @todo Replace icons with SVG symbols. + @see https://www.youtube.com/watch?v=9xXBYcWgCHA */ .shelf-option:disabled > *::after { - font-family: "icomoon"; + font-family: "icomoon"; /* stylelint-disable font-family-no-missing-generic-family-keyword */ content: "\e918"; margin-left: 0.5em; } /* --- TOGGLES --- */ -.toggle-button[aria-pressed=true], .toggle-button[aria-pressed=true]:hover { +.toggle-button[aria-pressed=true], +.toggle-button[aria-pressed=true]:hover { background-color: hsl(171, 100%, 41%); color: white; } -.hide-active[aria-pressed=true], .hide-inactive[aria-pressed=false] { + +.hide-active[aria-pressed=true], +.hide-inactive[aria-pressed=false] { display: none; } .hidden { display: none !important; } -.hidden.transition-y, .hidden.transition-x { + +.hidden.transition-y, +.hidden.transition-x { display: block !important; visibility: hidden !important; height: 0; @@ -47,18 +56,22 @@ html { margin: 0; padding: 0; } + .transition-y { transition-property: height, margin-top, margin-bottom, padding-top, padding-bottom; transition-duration: 0.5s; transition-timing-function: ease; } + .transition-x { transition-property: width, margin-left, margin-right, padding-left, padding-right; transition-duration: 0.5s; transition-timing-function: ease; } + @media (prefers-reduced-motion: reduce) { - .transition-x, .transition-y { + .transition-x, + .transition-y { transition-duration: 0.001ms !important; } } @@ -71,36 +84,46 @@ html { margin: 0; display: inline; } -.rate-stars:hover .icon:before { + +.rate-stars:hover .icon::before { content: '\e9d9'; } -.rate-stars form:hover ~ form .icon:before{ + +.rate-stars form:hover ~ form .icon::before { content: '\e9d7'; } -/* stars in a review form */ -.form-rate-stars:hover .icon:before { +/** stars in a review form + * + * @todo Simplify the logic for those icons. + */ +.form-rate-stars input + .icon.icon::before { content: '\e9d9'; } -.form-rate-stars input + .icon:before { + +.form-rate-stars:hover .icon.icon::before { content: '\e9d9'; } -.form-rate-stars input:checked + .icon:before { + +.form-rate-stars input:checked + .icon.icon::before { content: '\e9d9'; } -.form-rate-stars input:checked + * ~ .icon:before { - content: '\e9d7'; -} -.form-rate-stars:hover label.icon:before { - content: '\e9d9'; -} -.form-rate-stars label.icon:hover:before { - content: '\e9d9'; -} -.form-rate-stars label.icon:hover ~ label.icon:before{ + +.form-rate-stars input:checked + * ~ .icon.icon::before { content: '\e9d7'; } +.form-rate-stars:hover label.icon.icon::before { + content: '\e9d9'; +} + +.form-rate-stars label.icon:hover::before { + content: '\e9d9'; +} + +.form-rate-stars label.icon:hover ~ label.icon.icon::before { + content: '\e9d7'; +} /* --- BOOK COVERS --- */ .cover-container { @@ -108,46 +131,46 @@ html { width: max-content; max-width: 250px; } + .cover-container.is-large { height: max-content; max-width: 330px; } + .cover-container.is-large img { max-height: 500px; height: auto; } + .cover-container.is-medium { height: 150px; } + .cover-container.is-small { height: 100px; } + @media only screen and (max-width: 768px) { .cover-container { height: 200px; width: max-content; } + .cover-container.is-medium { height: 100px; } } -.cover-container.is-medium .no-cover div { - font-size: 0.9em; - padding: 0.3em; -} -.cover-container.is-small .no-cover div { - font-size: 0.7em; - padding: 0.1em; -} .book-cover { height: 100%; object-fit: scale-down; } + .no-cover { position: relative; white-space: normal; } + .no-cover div { position: absolute; padding: 1em; @@ -157,38 +180,51 @@ html { text-align: center; } +.cover-container.is-medium .no-cover div { + font-size: 0.9em; + padding: 0.3em; +} + +.cover-container.is-small .no-cover div { + font-size: 0.7em; + padding: 0.1em; +} /* --- AVATAR --- */ .avatar { vertical-align: middle; display: inline; } + .is-32x32 { min-width: 32px; min-height: 32px; } + .is-96x96 { min-width: 96px; min-height: 96px; } - - /* --- QUOTES --- */ .quote blockquote { position: relative; padding-left: 2em; } -.quote blockquote:before, .quote blockquote:after { + +.quote blockquote::before, +.quote blockquote::after { font-family: 'icomoon'; position: absolute; } -.quote blockquote:before { + +.quote blockquote::before { content: "\e906"; top: 0; left: 0; } -.quote blockquote:after { + +.quote blockquote::after { content: "\e905"; right: 0; } diff --git a/package.json b/package.json new file mode 100644 index 00000000..a662cdaf --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "devDependencies": { + "stylelint": "^13.12.0", + "stylelint-config-standard": "^21.0.0", + "stylelint-order": "^4.1.0" + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..74d1318d --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1897 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" + integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== + dependencies: + "@babel/highlight" "^7.12.13" + +"@babel/compat-data@^7.13.12": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.12.tgz#a8a5ccac19c200f9dd49624cac6e19d7be1236a1" + integrity sha512-3eJJ841uKxeV8dcN/2yGEUy+RfgQspPEgQat85umsE1rotuquQ2AbIub4S6j7c50a2d+4myc+zSlnXeIHrOnhQ== + +"@babel/core@>=7.9.0": + version "7.13.14" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.14.tgz#8e46ebbaca460a63497c797e574038ab04ae6d06" + integrity sha512-wZso/vyF4ki0l0znlgM4inxbdrUvCb+cVz8grxDq+6C9k6qbqoIJteQOKicaKjCipU3ISV+XedCqpL2RJJVehA== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.13.9" + "@babel/helper-compilation-targets" "^7.13.13" + "@babel/helper-module-transforms" "^7.13.14" + "@babel/helpers" "^7.13.10" + "@babel/parser" "^7.13.13" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.13.13" + "@babel/types" "^7.13.14" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + semver "^6.3.0" + source-map "^0.5.0" + +"@babel/generator@^7.13.9": + version "7.13.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.9.tgz#3a7aa96f9efb8e2be42d38d80e2ceb4c64d8de39" + integrity sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw== + dependencies: + "@babel/types" "^7.13.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-compilation-targets@^7.13.13": + version "7.13.13" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.13.tgz#2b2972a0926474853f41e4adbc69338f520600e5" + integrity sha512-q1kcdHNZehBwD9jYPh3WyXcsFERi39X4I59I3NadciWtNDyZ6x+GboOxncFK0kXlKIv6BJm5acncehXWUjWQMQ== + dependencies: + "@babel/compat-data" "^7.13.12" + "@babel/helper-validator-option" "^7.12.17" + browserslist "^4.14.5" + semver "^6.3.0" + +"@babel/helper-function-name@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz#93ad656db3c3c2232559fd7b2c3dbdcbe0eb377a" + integrity sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA== + dependencies: + "@babel/helper-get-function-arity" "^7.12.13" + "@babel/template" "^7.12.13" + "@babel/types" "^7.12.13" + +"@babel/helper-get-function-arity@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz#bc63451d403a3b3082b97e1d8b3fe5bd4091e583" + integrity sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-member-expression-to-functions@^7.13.12": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz#dfe368f26d426a07299d8d6513821768216e6d72" + integrity sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw== + dependencies: + "@babel/types" "^7.13.12" + +"@babel/helper-module-imports@^7.13.12": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz#c6a369a6f3621cb25da014078684da9196b61977" + integrity sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA== + dependencies: + "@babel/types" "^7.13.12" + +"@babel/helper-module-transforms@^7.13.14": + version "7.13.14" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz#e600652ba48ccb1641775413cb32cfa4e8b495ef" + integrity sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g== + dependencies: + "@babel/helper-module-imports" "^7.13.12" + "@babel/helper-replace-supers" "^7.13.12" + "@babel/helper-simple-access" "^7.13.12" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/helper-validator-identifier" "^7.12.11" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.13.13" + "@babel/types" "^7.13.14" + +"@babel/helper-optimise-call-expression@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz#5c02d171b4c8615b1e7163f888c1c81c30a2aaea" + integrity sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-replace-supers@^7.13.12": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz#6442f4c1ad912502481a564a7386de0c77ff3804" + integrity sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.13.12" + "@babel/helper-optimise-call-expression" "^7.12.13" + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.12" + +"@babel/helper-simple-access@^7.13.12": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz#dd6c538afb61819d205a012c31792a39c7a5eaf6" + integrity sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA== + dependencies: + "@babel/types" "^7.13.12" + +"@babel/helper-split-export-declaration@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz#e9430be00baf3e88b0e13e6f9d4eaf2136372b05" + integrity sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-validator-identifier@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" + integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== + +"@babel/helper-validator-option@^7.12.17": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz#d1fbf012e1a79b7eebbfdc6d270baaf8d9eb9831" + integrity sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw== + +"@babel/helpers@^7.13.10": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.13.10.tgz#fd8e2ba7488533cdeac45cc158e9ebca5e3c7df8" + integrity sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ== + dependencies: + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.0" + +"@babel/highlight@^7.12.13": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.10.tgz#a8b2a66148f5b27d666b15d81774347a731d52d1" + integrity sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.12.13", "@babel/parser@^7.13.13": + version "7.13.13" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.13.tgz#42f03862f4aed50461e543270916b47dd501f0df" + integrity sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw== + +"@babel/template@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" + integrity sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/parser" "^7.12.13" + "@babel/types" "^7.12.13" + +"@babel/traverse@^7.13.0", "@babel/traverse@^7.13.13": + version "7.13.13" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.13.tgz#39aa9c21aab69f74d948a486dd28a2dbdbf5114d" + integrity sha512-CblEcwmXKR6eP43oQGG++0QMTtCjAsa3frUuzHoiIJWpaIIi8dwMyEFUJoXRLxagGqCK+jALRwIO+o3R9p/uUg== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.13.9" + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/parser" "^7.13.13" + "@babel/types" "^7.13.13" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.12.13", "@babel/types@^7.13.0", "@babel/types@^7.13.12", "@babel/types@^7.13.13", "@babel/types@^7.13.14": + version "7.13.14" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.14.tgz#c35a4abb15c7cd45a2746d78ab328e362cbace0d" + integrity sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + +"@nodelib/fs.scandir@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" + integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== + dependencies: + "@nodelib/fs.stat" "2.0.4" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" + integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" + integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== + dependencies: + "@nodelib/fs.scandir" "2.1.4" + fastq "^1.6.0" + +"@stylelint/postcss-css-in-js@^0.37.2": + version "0.37.2" + resolved "https://registry.yarnpkg.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz#7e5a84ad181f4234a2480803422a47b8749af3d2" + integrity sha512-nEhsFoJurt8oUmieT8qy4nk81WRHmJynmVwn/Vts08PL9fhgIsMhk1GId5yAN643OzqEEb5S/6At2TZW7pqPDA== + dependencies: + "@babel/core" ">=7.9.0" + +"@stylelint/postcss-markdown@^0.36.2": + version "0.36.2" + resolved "https://registry.yarnpkg.com/@stylelint/postcss-markdown/-/postcss-markdown-0.36.2.tgz#0a540c4692f8dcdfc13c8e352c17e7bfee2bb391" + integrity sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ== + dependencies: + remark "^13.0.0" + unist-util-find-all-after "^3.0.2" + +"@types/mdast@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb" + integrity sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw== + dependencies: + "@types/unist" "*" + +"@types/minimist@^1.2.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256" + integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg== + +"@types/normalize-package-data@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" + integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" + integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== + +ajv@^8.0.1: + version "8.0.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.0.2.tgz#1396e27f208ed56dd5638ab5a251edeb1c91d402" + integrity sha512-V0HGxJd0PiDF0ecHYIesTOqfd1gJguwQUOYfMfAWnRsWQEXfc5ifbUFhD3Wjc+O+y7VAqL+g07prq9gHQ/JOZQ== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +autoprefixer@^9.8.6: + version "9.8.6" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" + integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg== + dependencies: + browserslist "^4.12.0" + caniuse-lite "^1.0.30001109" + colorette "^1.2.1" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.32" + postcss-value-parser "^4.1.0" + +bail@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" + integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.12.0, browserslist@^4.14.5: + version "4.16.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717" + integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw== + dependencies: + caniuse-lite "^1.0.30001181" + colorette "^1.2.1" + electron-to-chromium "^1.3.649" + escalade "^3.1.1" + node-releases "^1.1.70" + +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase-keys@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" + integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== + dependencies: + camelcase "^5.3.1" + map-obj "^4.0.0" + quick-lru "^4.0.1" + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001181: + version "1.0.30001205" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001205.tgz#d79bf6a6fb13196b4bb46e5143a22ca0242e0ef8" + integrity sha512-TL1GrS5V6LElbitPazidkBMD9sa448bQDDLrumDqaggmKFcuU2JW1wTOHJPukAcOMtEmLcmDJEzfRrf+GjM0Og== + +chalk@^2.0.0, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +character-entities-legacy@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" + integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== + +character-entities@^1.0.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" + integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== + +character-reference-invalid@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" + integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== + +clone-regexp@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f" + integrity sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q== + dependencies: + is-regexp "^2.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colorette@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" + integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + +cosmiconfig@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" + integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +debug@^4.0.0, debug@^4.1.0, debug@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +decamelize-keys@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" + integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.1.0, decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + +domelementtype@1, domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domelementtype@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e" + integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w== + +domhandler@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== + dependencies: + domelementtype "1" + +domutils@^1.5.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +electron-to-chromium@^1.3.649: + version "1.3.703" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.703.tgz#6d9b9a75c42a40775f5930329e642b22b227317f" + integrity sha512-SVBVhNB+4zPL+rvtWLw7PZQkw/Eqj1HQZs22xtcqW36+xoifzEOEEDEpkxSMfB6RFeSIOcG00w6z5mSqLr1Y6w== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +entities@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +execall@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/execall/-/execall-2.0.0.tgz#16a06b5fe5099df7d00be5d9c06eecded1663b45" + integrity sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow== + dependencies: + clone-regexp "^2.1.0" + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.1.1, fast-glob@^3.2.5: + version "3.2.5" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" + integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + +fastest-levenshtein@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" + integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== + +fastq@^1.6.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" + integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" + integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-intrinsic@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +get-stdin@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== + +glob-parent@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-modules@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globby@^11.0.2: + version "11.0.3" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" + integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + +globjoin@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" + integrity sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM= + +gonzales-pe@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" + integrity sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ== + dependencies: + minimist "^1.2.5" + +hard-rejection@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" + integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hosted-git-info@^2.1.4: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + +hosted-git-info@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" + integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg== + dependencies: + lru-cache "^6.0.0" + +html-tags@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" + integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== + +htmlparser2@^3.10.0: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== + dependencies: + domelementtype "^1.3.1" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.1.1" + +ignore@^5.1.4, ignore@^5.1.8: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-lazy@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" + integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@^1.3.5: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +is-alphabetical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" + integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== + +is-alphanumerical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" + integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== + dependencies: + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-boolean-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.0.tgz#e2aaad3a3a8fca34c28f6eee135b156ed2587ff0" + integrity sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA== + dependencies: + call-bind "^1.0.0" + +is-buffer@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + +is-core-module@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + +is-decimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" + integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-hexadecimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" + integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== + +is-number-object@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" + integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= + +is-plain-obj@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-regexp@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-2.1.0.tgz#cd734a56864e23b956bf4e7c66c396a4c0b22c2d" + integrity sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA== + +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json5@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + +kind-of@^6.0.2, kind-of@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +known-css-properties@^0.21.0: + version "0.21.0" + resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.21.0.tgz#15fbd0bbb83447f3ce09d8af247ed47c68ede80d" + integrity sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw== + +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= + +lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +longest-streak@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" + integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +map-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= + +map-obj@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.2.0.tgz#0e8bc823e2aaca8a0942567d12ed14f389eec153" + integrity sha512-NAq0fCmZYGz9UFEQyndp7sisrow4GroyGeKluyKC/chuITZsPyOyC1UJZPJlVFImhXdROIP5xqouRLThT3BbpQ== + +mathml-tag-names@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" + integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== + +mdast-util-from-markdown@^0.8.0: + version "0.8.5" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz#d1ef2ca42bc377ecb0463a987910dae89bd9a28c" + integrity sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-to-string "^2.0.0" + micromark "~2.11.0" + parse-entities "^2.0.0" + unist-util-stringify-position "^2.0.0" + +mdast-util-to-markdown@^0.6.0: + version "0.6.5" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz#b33f67ca820d69e6cc527a93d4039249b504bebe" + integrity sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ== + dependencies: + "@types/unist" "^2.0.0" + longest-streak "^2.0.0" + mdast-util-to-string "^2.0.0" + parse-entities "^2.0.0" + repeat-string "^1.0.0" + zwitch "^1.0.0" + +mdast-util-to-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz#b8cfe6a713e1091cb5b728fc48885a4767f8b97b" + integrity sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w== + +meow@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364" + integrity sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ== + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.2.2" + decamelize "^1.2.0" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "4.1.0" + normalize-package-data "^3.0.0" + read-pkg-up "^7.0.1" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.18.0" + yargs-parser "^20.2.3" + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromark@~2.11.0: + version "2.11.4" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-2.11.4.tgz#d13436138eea826383e822449c9a5c50ee44665a" + integrity sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA== + dependencies: + debug "^4.0.0" + parse-entities "^2.0.0" + +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist-options@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" + integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + kind-of "^6.0.3" + +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +node-releases@^1.1.70: + version "1.1.71" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" + integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg== + +normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.2.tgz#cae5c410ae2434f9a6c1baa65d5bc3b9366c8699" + integrity sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg== + dependencies: + hosted-git-info "^4.0.1" + resolve "^1.20.0" + semver "^7.3.4" + validate-npm-package-license "^3.0.1" + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + +normalize-selector@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/normalize-selector/-/normalize-selector-0.2.0.tgz#d0b145eb691189c63a78d201dc4fdb1293ef0c03" + integrity sha1-0LFF62kRicY6eNIB3E/bEpPvDAM= + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" + integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picomatch@^2.0.5, picomatch@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + +postcss-html@^0.36.0: + version "0.36.0" + resolved "https://registry.yarnpkg.com/postcss-html/-/postcss-html-0.36.0.tgz#b40913f94eaacc2453fd30a1327ad6ee1f88b204" + integrity sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw== + dependencies: + htmlparser2 "^3.10.0" + +postcss-less@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/postcss-less/-/postcss-less-3.1.4.tgz#369f58642b5928ef898ffbc1a6e93c958304c5ad" + integrity sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA== + dependencies: + postcss "^7.0.14" + +postcss-media-query-parser@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" + integrity sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ= + +postcss-resolve-nested-selector@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz#29ccbc7c37dedfac304e9fff0bf1596b3f6a0e4e" + integrity sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4= + +postcss-safe-parser@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz#a6d4e48f0f37d9f7c11b2a581bf00f8ba4870b96" + integrity sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g== + dependencies: + postcss "^7.0.26" + +postcss-sass@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/postcss-sass/-/postcss-sass-0.4.4.tgz#91f0f3447b45ce373227a98b61f8d8f0785285a3" + integrity sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg== + dependencies: + gonzales-pe "^4.3.0" + postcss "^7.0.21" + +postcss-scss@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-2.1.1.tgz#ec3a75fa29a55e016b90bf3269026c53c1d2b383" + integrity sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA== + dependencies: + postcss "^7.0.6" + +postcss-selector-parser@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3" + integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw== + dependencies: + cssesc "^3.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + util-deprecate "^1.0.2" + +postcss-sorting@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-sorting/-/postcss-sorting-5.0.1.tgz#10d5d0059eea8334dacc820c0121864035bc3f11" + integrity sha512-Y9fUFkIhfrm6i0Ta3n+89j56EFqaNRdUKqXyRp6kvTcSXnmgEjaVowCXH+JBe9+YKWqd4nc28r2sgwnzJalccA== + dependencies: + lodash "^4.17.14" + postcss "^7.0.17" + +postcss-syntax@^0.36.2: + version "0.36.2" + resolved "https://registry.yarnpkg.com/postcss-syntax/-/postcss-syntax-0.36.2.tgz#f08578c7d95834574e5593a82dfbfa8afae3b51c" + integrity sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w== + +postcss-value-parser@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" + integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== + +postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.31, postcss@^7.0.32, postcss@^7.0.35, postcss@^7.0.6: + version "7.0.35" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" + integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quick-lru@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" + integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== + +read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + +readable-stream@^3.1.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + +remark-parse@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-9.0.0.tgz#4d20a299665880e4f4af5d90b7c7b8a935853640" + integrity sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw== + dependencies: + mdast-util-from-markdown "^0.8.0" + +remark-stringify@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-9.0.1.tgz#576d06e910548b0a7191a71f27b33f1218862894" + integrity sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg== + dependencies: + mdast-util-to-markdown "^0.6.0" + +remark@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/remark/-/remark-13.0.0.tgz#d15d9bf71a402f40287ebe36067b66d54868e425" + integrity sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA== + dependencies: + remark-parse "^9.0.0" + remark-stringify "^9.0.0" + unified "^9.1.0" + +repeat-string@^1.0.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve@^1.10.0, resolve@^1.20.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"semver@2 || 3 || 4 || 5": + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.3.4: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +signal-exit@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.7" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" + integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== + +specificity@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019" + integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg== + +string-width@^4.2.0, string-width@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" + integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +style-search@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" + integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI= + +stylelint-config-recommended@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-4.0.0.tgz#665a0034065e6704d5032ba51bf4efa37d328ef9" + integrity sha512-sgna89Ng+25Hr9kmmaIxpGWt2LStVm1xf1807PdcWasiPDaOTkOHRL61sINw0twky7QMzafCGToGDnHT/kTHtQ== + +stylelint-config-standard@^21.0.0: + version "21.0.0" + resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-21.0.0.tgz#4942cfa27301eb6702fa8fc46a44da35d1a5cfd7" + integrity sha512-Yf6mx5oYEbQQJxWuW7X3t1gcxqbUx52qC9SMS3saC2ruOVYEyqmr5zSW6k3wXflDjjFrPhar3kp68ugRopmlzg== + dependencies: + stylelint-config-recommended "^4.0.0" + +stylelint-order@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/stylelint-order/-/stylelint-order-4.1.0.tgz#692d05b7d0c235ac66fcf5ea1d9e5f08a76747f6" + integrity sha512-sVTikaDvMqg2aJjh4r48jsdfmqLT+nqB1MOsaBnvM3OwLx4S+WXcsxsgk5w18h/OZoxZCxuyXMh61iBHcj9Qiw== + dependencies: + lodash "^4.17.15" + postcss "^7.0.31" + postcss-sorting "^5.0.1" + +stylelint@^13.12.0: + version "13.12.0" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.12.0.tgz#cceb922be0d0c7b7b6926271eea2b90cb924733e" + integrity sha512-P8O1xDy41B7O7iXaSlW+UuFbE5+ZWQDb61ndGDxKIt36fMH50DtlQTbwLpFLf8DikceTAb3r6nPrRv30wBlzXw== + dependencies: + "@stylelint/postcss-css-in-js" "^0.37.2" + "@stylelint/postcss-markdown" "^0.36.2" + autoprefixer "^9.8.6" + balanced-match "^1.0.0" + chalk "^4.1.0" + cosmiconfig "^7.0.0" + debug "^4.3.1" + execall "^2.0.0" + fast-glob "^3.2.5" + fastest-levenshtein "^1.0.12" + file-entry-cache "^6.0.1" + get-stdin "^8.0.0" + global-modules "^2.0.0" + globby "^11.0.2" + globjoin "^0.1.4" + html-tags "^3.1.0" + ignore "^5.1.8" + import-lazy "^4.0.0" + imurmurhash "^0.1.4" + known-css-properties "^0.21.0" + lodash "^4.17.21" + log-symbols "^4.0.0" + mathml-tag-names "^2.1.3" + meow "^9.0.0" + micromatch "^4.0.2" + normalize-selector "^0.2.0" + postcss "^7.0.35" + postcss-html "^0.36.0" + postcss-less "^3.1.4" + postcss-media-query-parser "^0.2.3" + postcss-resolve-nested-selector "^0.1.1" + postcss-safe-parser "^4.0.2" + postcss-sass "^0.4.4" + postcss-scss "^2.1.1" + postcss-selector-parser "^6.0.4" + postcss-syntax "^0.36.2" + postcss-value-parser "^4.1.0" + resolve-from "^5.0.0" + slash "^3.0.0" + specificity "^0.4.1" + string-width "^4.2.2" + strip-ansi "^6.0.0" + style-search "^0.1.0" + sugarss "^2.0.0" + svg-tags "^1.0.0" + table "^6.0.7" + v8-compile-cache "^2.2.0" + write-file-atomic "^3.0.3" + +sugarss@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-2.0.0.tgz#ddd76e0124b297d40bf3cca31c8b22ecb43bc61d" + integrity sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ== + dependencies: + postcss "^7.0.2" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +svg-tags@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" + integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q= + +table@^6.0.7: + version "6.0.9" + resolved "https://registry.yarnpkg.com/table/-/table-6.0.9.tgz#790a12bf1e09b87b30e60419bafd6a1fd85536fb" + integrity sha512-F3cLs9a3hL1Z7N4+EkSscsel3z55XT950AvB05bwayrNg5T1/gykXtigioTAjbltvbMSJvvhFCbnf6mX+ntnJQ== + dependencies: + ajv "^8.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + lodash.clonedeep "^4.5.0" + lodash.flatten "^4.4.0" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.0" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +trim-newlines@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30" + integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA== + +trough@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" + integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== + +type-fest@^0.18.0: + version "0.18.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" + integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== + +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +unified@^9.1.0: + version "9.2.1" + resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.1.tgz#ae18d5674c114021bfdbdf73865ca60f410215a3" + integrity sha512-juWjuI8Z4xFg8pJbnEZ41b5xjGUWGHqXALmBZ3FC3WX0PIx1CZBIIJ6mXbYMcf6Yw4Fi0rFUTA1cdz/BglbOhA== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= + +unist-util-find-all-after@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/unist-util-find-all-after/-/unist-util-find-all-after-3.0.2.tgz#fdfecd14c5b7aea5e9ef38d5e0d5f774eeb561f6" + integrity sha512-xaTC/AGZ0rIM2gM28YVRAFPIZpzbpDtU3dRmp7EXlNVA8ziQc4hY3H7BHXM1J49nEmiqc3svnqMReW+PGqbZKQ== + dependencies: + unist-util-is "^4.0.0" + +unist-util-is@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797" + integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg== + +unist-util-stringify-position@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" + integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== + dependencies: + "@types/unist" "^2.0.2" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1, util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +v8-compile-cache@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +vfile-message@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" + integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^2.0.0" + +vfile@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624" + integrity sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + unist-util-stringify-position "^2.0.0" + vfile-message "^2.0.0" + +which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yargs-parser@^20.2.3: + version "20.2.7" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a" + integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw== + +zwitch@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" + integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw== From 40c7bde7ecbfbdd964bcf9400cb398138f10daa5 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Wed, 31 Mar 2021 16:25:44 +0200 Subject: [PATCH 0529/1285] Ommit package.json and yarn.lock for .editorconfig. --- .editorconfig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.editorconfig b/.editorconfig index c03185f9..58ba190d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -29,3 +29,8 @@ trim_trailing_whitespace = false [*.{yml,yaml}] indent_size = 2 max_line_length = off + +[{package.json,yarn.lock}] +indent_size = unset +indent_style = unset +max_line_length = unset From 53f03457cc5432d209e17fe6d1534c62ad32cddb Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Wed, 31 Mar 2021 17:07:28 +0200 Subject: [PATCH 0530/1285] Add linter for JS files with basic recommended rules: - Add Github Action. - Fix JS files to match rules; mostly `globals` and `exported`. --- .eslintrc.js | 10 + .github/workflows/linters-frontend.yaml | 6 + bookwyrm/static/js/check_all.js | 2 +- bookwyrm/static/js/localstorage.js | 3 + bookwyrm/static/js/shared.js | 4 +- bookwyrm/static/js/tabs.js | 2 + package.json | 1 + yarn.lock | 359 +++++++++++++++++++++++- 8 files changed, 376 insertions(+), 11 deletions(-) create mode 100644 .eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..d39859f1 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,10 @@ +/* global module */ + +module.exports = { + "env": { + "browser": true, + "es6": true + }, + + "extends": "eslint:recommended" +}; diff --git a/.github/workflows/linters-frontend.yaml b/.github/workflows/linters-frontend.yaml index 47c3da0d..711f5d88 100644 --- a/.github/workflows/linters-frontend.yaml +++ b/.github/workflows/linters-frontend.yaml @@ -22,3 +22,9 @@ jobs: uses: actions-hub/stylelint@v1.1.3 env: PATTERN: "*.css" + + - name: Run ESLint + uses: stefanoeb/eslint-action@1.0.2 + with: + files: + - 'bookwyrm/static/js/**' diff --git a/bookwyrm/static/js/check_all.js b/bookwyrm/static/js/check_all.js index ea2300ce..07d30a68 100644 --- a/bookwyrm/static/js/check_all.js +++ b/bookwyrm/static/js/check_all.js @@ -1,4 +1,4 @@ -// Toggle all checkboxes. +/* exported toggleAllCheckboxes */ /** * Toggle all descendant checkboxes of a target. diff --git a/bookwyrm/static/js/localstorage.js b/bookwyrm/static/js/localstorage.js index b63c4392..aa79ee30 100644 --- a/bookwyrm/static/js/localstorage.js +++ b/bookwyrm/static/js/localstorage.js @@ -1,3 +1,6 @@ +/* exported updateDisplay */ +/* globals addRemoveClass */ + // set javascript listeners function updateDisplay(e) { // used in set reading goal diff --git a/bookwyrm/static/js/shared.js b/bookwyrm/static/js/shared.js index 1f26aba6..7a198619 100644 --- a/bookwyrm/static/js/shared.js +++ b/bookwyrm/static/js/shared.js @@ -1,3 +1,5 @@ +/* globals setDisplay TabGroup toggleAllCheckboxes updateDisplay */ + // set up javascript listeners window.onload = function() { // buttons that display or hide content @@ -93,7 +95,7 @@ function toggleAction(e) { // show/hide container var container = document.getElementById('hide-' + targetId); - if (!!container) { + if (container) { addRemoveClass(container, 'hidden', pressed); } diff --git a/bookwyrm/static/js/tabs.js b/bookwyrm/static/js/tabs.js index 59c07251..f9568b29 100644 --- a/bookwyrm/static/js/tabs.js +++ b/bookwyrm/static/js/tabs.js @@ -1,3 +1,5 @@ +/* exported TabGroup */ + /* * The content below is licensed according to the W3C Software License at * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document diff --git a/package.json b/package.json index a662cdaf..8059255d 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "devDependencies": { + "eslint": "^7.23.0", "stylelint": "^13.12.0", "stylelint-config-standard": "^21.0.0", "stylelint-order": "^4.1.0" diff --git a/yarn.lock b/yarn.lock index 74d1318d..de4e0107 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,13 @@ # yarn lockfile v1 +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" @@ -148,7 +155,7 @@ "@babel/traverse" "^7.13.0" "@babel/types" "^7.13.0" -"@babel/highlight@^7.12.13": +"@babel/highlight@^7.10.4", "@babel/highlight@^7.12.13": version "7.13.10" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.10.tgz#a8b2a66148f5b27d666b15d81774347a731d52d1" integrity sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg== @@ -194,6 +201,21 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@eslint/eslintrc@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.0.tgz#99cc0a0584d72f1df38b900fb062ba995f395547" + integrity sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + "@nodelib/fs.scandir@2.1.4": version "2.1.4" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" @@ -257,6 +279,26 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== +acorn-jsx@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" + integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== + +acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +ajv@^6.10.0, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ajv@^8.0.1: version "8.0.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.0.2.tgz#1396e27f208ed56dd5638ab5a251edeb1c91d402" @@ -267,6 +309,11 @@ ajv@^8.0.1: require-from-string "^2.0.2" uri-js "^4.2.2" +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + ansi-regex@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" @@ -286,6 +333,13 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -391,7 +445,7 @@ chalk@^2.0.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== @@ -473,12 +527,21 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" +cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -debug@^4.0.0, debug@^4.1.0, debug@^4.3.1: +debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -498,6 +561,11 @@ decamelize@^1.1.0, decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +deep-is@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -505,6 +573,13 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -548,6 +623,13 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +enquirer@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + entities@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -575,6 +657,117 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" + integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + +eslint@^7.23.0: + version "7.23.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.23.0.tgz#8d029d252f6e8cf45894b4bee08f5493f8e94325" + integrity sha512-kqvNVbdkjzpFy0XOszNwjkKzZ+6TcwCQ/h+ozlcIWwaimBBuhlQ4nN6kbiM2L+OjDcznkTJxzYfRFH92sx4a0Q== + dependencies: + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.4.0" + esutils "^2.0.2" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^13.6.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash "^4.17.21" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^6.0.4" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + execall@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/execall/-/execall-2.0.0.tgz#16a06b5fe5099df7d00be5d9c06eecded1663b45" @@ -604,6 +797,16 @@ fast-glob@^3.1.1, fast-glob@^3.2.5: micromatch "^4.0.2" picomatch "^2.2.1" +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + fastest-levenshtein@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" @@ -661,6 +864,11 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -680,7 +888,7 @@ get-stdin@^8.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== -glob-parent@^5.1.0: +glob-parent@^5.0.0, glob-parent@^5.1.0: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -720,6 +928,20 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +globals@^13.6.0: + version "13.7.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.7.0.tgz#aed3bcefd80ad3ec0f0be2cf0c895110c0591795" + integrity sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA== + dependencies: + type-fest "^0.20.2" + globby@^11.0.2: version "11.0.3" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" @@ -800,12 +1022,17 @@ htmlparser2@^3.10.0: inherits "^2.0.1" readable-stream "^3.1.1" +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + ignore@^5.1.4, ignore@^5.1.8: version "5.1.8" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== -import-fresh@^3.2.1: +import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -903,7 +1130,7 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== @@ -965,6 +1192,14 @@ js-tokens@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -975,11 +1210,21 @@ json-parse-even-better-errors@^2.3.0: resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + json-schema-traverse@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + json5@^2.1.2: version "2.2.0" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" @@ -997,6 +1242,14 @@ known-css-properties@^0.21.0: resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.21.0.tgz#15fbd0bbb83447f3ce09d8af247ed47c68ede80d" integrity sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw== +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -1162,6 +1415,11 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + node-releases@^1.1.70: version "1.1.71" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" @@ -1209,6 +1467,18 @@ once@^1.3.0: dependencies: wrappy "1" +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -1267,6 +1537,11 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -1365,6 +1640,16 @@ postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0. source-map "^0.6.1" supports-color "^6.1.0" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -1416,6 +1701,11 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +regexpp@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + remark-parse@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-9.0.0.tgz#4d20a299665880e4f4af5d90b7c7b8a935853640" @@ -1506,13 +1796,25 @@ semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.4: +semver@^7.2.1, semver@^7.3.4: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== dependencies: lru-cache "^6.0.0" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -1573,6 +1875,11 @@ specificity@^0.4.1: resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019" integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg== +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + string-width@^4.2.0, string-width@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" @@ -1603,6 +1910,11 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + style-search@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" @@ -1716,7 +2028,7 @@ svg-tags@^1.0.0: resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q= -table@^6.0.7: +table@^6.0.4, table@^6.0.7: version "6.0.9" resolved "https://registry.yarnpkg.com/table/-/table-6.0.9.tgz#790a12bf1e09b87b30e60419bafd6a1fd85536fb" integrity sha512-F3cLs9a3hL1Z7N4+EkSscsel3z55XT950AvB05bwayrNg5T1/gykXtigioTAjbltvbMSJvvhFCbnf6mX+ntnJQ== @@ -1731,6 +2043,11 @@ table@^6.0.7: slice-ansi "^4.0.0" string-width "^4.2.0" +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -1753,11 +2070,23 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-fest@^0.18.0: version "0.18.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" @@ -1823,7 +2152,7 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -v8-compile-cache@^2.2.0: +v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== @@ -1861,6 +2190,18 @@ which@^1.3.1: dependencies: isexe "^2.0.0" +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" From 99fd5c4e170e4dd53b5b6111d9bf588321224da3 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Wed, 31 Mar 2021 17:59:52 +0200 Subject: [PATCH 0531/1285] Tweak Github Actions: - Rename some jobs for consistency. - Add workflows path to trigger lint when updating workflows. - Simplify frontend actions to speed up process and reduce dependencies. --- .github/workflows/linters-frontend.yaml | 21 ++++++++++----------- .github/workflows/linters-global.yaml | 6 +++--- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.github/workflows/linters-frontend.yaml b/.github/workflows/linters-frontend.yaml index 711f5d88..978bbbbe 100644 --- a/.github/workflows/linters-frontend.yaml +++ b/.github/workflows/linters-frontend.yaml @@ -1,30 +1,29 @@ # @url https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions -name: Frontend Linters +name: Lint Frontend on: push: branches: [ main, ci ] paths: + - '.github/workflows/**' - 'static/**' pull_request: branches: [ main, ci ] jobs: - linters: - name: linters + lint: + name: Lint with stylelint and ESLint. runs-on: ubuntu-20.04 steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - - name: stylelinter - uses: actions-hub/stylelint@v1.1.3 - env: - PATTERN: "*.css" + - name: Install modules + run: yarn + + - name: Run stylelint + run: yarn stylelint **/static/**/*.css --report-needless-disables --report-invalid-scope-disables - name: Run ESLint - uses: stefanoeb/eslint-action@1.0.2 - with: - files: - - 'bookwyrm/static/js/**' + run: yarn eslint . --ext .js,.jsx,.ts,.tsx diff --git a/.github/workflows/linters-global.yaml b/.github/workflows/linters-global.yaml index 291125c7..81893970 100644 --- a/.github/workflows/linters-global.yaml +++ b/.github/workflows/linters-global.yaml @@ -1,5 +1,5 @@ # @url https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions -name: Lint Project +name: Lint project globally on: push: @@ -8,8 +8,8 @@ on: branches: [ main, ci ] jobs: - linters: - name: linters + lint: + name: Lint with EditorConfig. runs-on: ubuntu-20.04 # Steps represent a sequence of tasks that will be executed as part of the job From 54ae4d9e4429e7b80466c029b06241035ea69b22 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Wed, 31 Mar 2021 18:02:11 +0200 Subject: [PATCH 0532/1285] Rename some Github Actions files for consistency. --- .github/workflows/{linters-frontend.yaml => lint-frontend.yaml} | 0 .github/workflows/{linters-global.yaml => lint-global.yaml} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{linters-frontend.yaml => lint-frontend.yaml} (100%) rename .github/workflows/{linters-global.yaml => lint-global.yaml} (100%) diff --git a/.github/workflows/linters-frontend.yaml b/.github/workflows/lint-frontend.yaml similarity index 100% rename from .github/workflows/linters-frontend.yaml rename to .github/workflows/lint-frontend.yaml diff --git a/.github/workflows/linters-global.yaml b/.github/workflows/lint-global.yaml similarity index 100% rename from .github/workflows/linters-global.yaml rename to .github/workflows/lint-global.yaml From 83e4ec00f917df41909bf50ea6d825c8dccac6d2 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 09:22:23 -0700 Subject: [PATCH 0533/1285] Renames "shelves" in the UI to "books" Still haven't figure out the create/edit wording tho --- bookwyrm/templates/layout.html | 2 +- bookwyrm/templates/snippets/shelf_selector.html | 4 ++-- .../snippets/shelve_button/shelve_button_options.html | 2 +- bookwyrm/templates/user/shelf.html | 10 +++++----- bookwyrm/templates/user/user.html | 8 +++++--- bookwyrm/templates/user/user_layout.html | 2 +- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index f0f04b84..0937fcc3 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -55,7 +55,7 @@ diff --git a/bookwyrm/templates/user/shelf.html b/bookwyrm/templates/user/shelf.html index 4c60258c..9e045572 100644 --- a/bookwyrm/templates/user/shelf.html +++ b/bookwyrm/templates/user/shelf.html @@ -3,14 +3,14 @@ {% load humanize %} {% load i18n %} +{% block title %} +{% include 'user/books_header.html' %} +{% endblock %} + {% block header %}

    - {% if is_self %} - {% trans "Your Shelves" %} - {% else %} - {% blocktrans with username=user.display_name %}{{ username }}: Shelves{% endblocktrans %} - {% endif %} + {% include 'user/books_header.html' %}

    {% endblock %} diff --git a/bookwyrm/templates/user/user.html b/bookwyrm/templates/user/user.html index 52a91561..2b996872 100644 --- a/bookwyrm/templates/user/user.html +++ b/bookwyrm/templates/user/user.html @@ -23,12 +23,14 @@ {% block panel %} {% if user.bookwyrm_user %}
    -

    {% trans "Shelves" %}

    +

    + {% include 'user/books_header.html' %} +

    {% for shelf in shelves %} {% endif %} diff --git a/bookwyrm/templates/user/user_layout.html b/bookwyrm/templates/user/user_layout.html index d60db2a0..c40b9e77 100644 --- a/bookwyrm/templates/user/user_layout.html +++ b/bookwyrm/templates/user/user_layout.html @@ -65,7 +65,7 @@ {% if user.shelf_set.exists %} {% url 'user-shelves' user|username as url %} - {% trans "Shelves" %} + {% trans "Books" %} {% endif %} From 5dc23682a2ce8df50380a0a05b3d9fdf349b2a72 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 09:22:54 -0700 Subject: [PATCH 0534/1285] Updates locale files --- locale/de_DE/LC_MESSAGES/django.mo | Bin 26471 -> 25779 bytes locale/de_DE/LC_MESSAGES/django.po | 765 +++++++++++++++++----------- locale/en_US/LC_MESSAGES/django.po | 654 ++++++++++++++---------- locale/es/LC_MESSAGES/django.mo | Bin 27102 -> 26264 bytes locale/es/LC_MESSAGES/django.po | 768 +++++++++++++++++----------- locale/fr_FR/LC_MESSAGES/django.mo | Bin 25097 -> 24333 bytes locale/fr_FR/LC_MESSAGES/django.po | 783 ++++++++++++++++++----------- locale/zh_CN/LC_MESSAGES/django.mo | Bin 30138 -> 29373 bytes locale/zh_CN/LC_MESSAGES/django.po | 768 +++++++++++++++++----------- 9 files changed, 2293 insertions(+), 1445 deletions(-) diff --git a/locale/de_DE/LC_MESSAGES/django.mo b/locale/de_DE/LC_MESSAGES/django.mo index cee809a37a853ef2da916d11d3d3135f02a1fe7d..dca726855f8f9060958660f8ab5bae1e36db3fd9 100644 GIT binary patch delta 7878 zcmZA62Yip$9>?*Mhzx>6B!oypA|b?xmD+nHAx4lO!-$avjiO5YsZo?xt42#zYmZ*# z-|C{oxT;3$x}~(2YN^qd>LBXg_I|$q=jf}iJ}Dj4+iNQ(DuQNq8UUVPT{(74c_GK<_AHLNO6*U}tQMIampoVpUv+ zG-2Mw0eBYUjB%Oz(aw#2SeqLYu@){vHT*V)<8f?;S5XaCr&25?;RGCHJ&NPWx1_QD zxCuMpZ`d4L#~4!&AHxv(H>*hmQ?L_#u>}3`BP@fbF#tb9&FGTN-$t#(ee^?rMx*D0 zFc7O^2*x5~GRc^PS*ZF;(3k$rauSts4f^0tR7YE6k3ldN>X@%PJZq{^b7OI1hSPmbv z`5CB=7TWTcusZn-sE+re&Ppk20Dhc6S;=J+RZ$h9FaR5(mM|Hku@~xo0s7;Us53AJ z^_IMdTCr`Yf$YKnEI~bY+~zOX{1w!5uAfQh!2n)nH5`OGwc)7pL@bBRP%G6Qd6P^U z>i$IQRMg7MMAdr+wURHPCbAJVuy;_e^HHQfm$__j+(eD&H&g>b^_`JNq8dm<-EWUt zx(w87H4Nj?g?er?s-w4Y2p+NdS`D1`nqYa#J9zT!e=3Qx6bwPlJP&o~Cfo8wsF^NB zb-WVQz^kZw+c6Lipbp_l)RNz@mZDbHFVU_r>MaV#DtiALkO;(%*4|iw{1DXpJ{GkC z<53UJLUphV`3jrYkw2!y=5M3wS8V8hg#|MM(n>Tj-)^h6rehu zi0Y^qE8={tgv*g{lG%tF*m3I_RQ)ed9b7?md>7Te{zl>>Y5Y(#Z-AAsM`PAsGZ;*P z8qBsg@=*htf*R0t)C?Bb{0h{-*4zAU)T!Q&s&^jM@ii=mw^04uNB+7nf1sX^aW!!g zNvHvIL>;c4s0K1n0~u=bd8h}+ptf!*2IC6U0JfkyJcwH2lgJxxE~B0+pXBsY9o3Jk zHi<$K4KWJWpq6s4^$2QUr!WZ5qL%iW^$zNL@BpoO%IG_qr;J^36pkn5-i{hK)rhN139qGs9YUatP->6cs z6OOU>x1+Y`0G6eHbDV@`a2h=;fLf93s2Sd~nwHMU15o8*sD`3YGfS}dlQD#RSB%3< zTRsc55=&71EJs&5iH#((@h-BBCWF7Sv;_+=6t|!r_z*SoQ>YHYA7NE65w)aaQ3IM{ zosD{aG3xZcjvBxgOv5)HVf}lP_}vzywQ>fq8oen$g__xER0o$(1ODFHmfri2e-_z2 za}l*NA#I%c5jcQ+J?l*DPyP%t2NU0x_19kIwsl503Dv+f)JUH~ZOtNE{=D^NTfQE{ zxxWo_@i6MSI_;cx60w_?F)eTw<;CqefcQIVWfr(PIIqK645r{?jKD9EZ8!H(uU+Gg z&LQiFHOOb;5S)yve;On47HZEcaN_hjHbb?Wh1Kv;WRc7~)E2u+NT}jvoPmCw`KaJr zEXMz0Ha^+KS?cr1;7z@*#?-~JsI%~bbrWg@-$$K=L#Vgsl)ZleHIQ4#z+A?!o3j+* zs6*BWwdd_oGfzW3kd4te61|r~&mv9mXuw3`e5|Qe?|#+Wa%9_Ew_ydIvVZgQzoi z8v|SHVdXlr8(Bj3U12U$m>1~?T1a1E;7Ce%Ro+45uPPyP(*%zTOZu9Tuy z#E*B`rKJrep;O!tYhw>og#wJnX{bZ=8uF$ZH>%+uPt*j}+4}`4?7x0AE0D)f%jiZWjr4cnQnnx2OhxLN(~i=TR#Zf;vR8sDZRX)k{Tnn2YML zz&Z!D601?|zG?G&QO}*W<*utF5-7NX4Kaf6sb-dfY9Jf)aT2P73s@F^LN!#1svnr{ zOe6~RT%5Hvs(w1^%nY>VAOm-qF(fpzNvNe+h}w#E7=-&!_fMf3zJglH`=~=(rmyo{ zO{_q^8EVFzuoCu0t#CG~{wUO;cVQU)n^h!q%6DQTJco7Aub)#f5!GNf)C%;o<>N4p z{2Xk6+pr>@!^-$AY76h9&VX-!XW$i4TUHJA`=^N^p)b+^)KX=lPJIsQ!D7^m=b={O zISj^)sQ3CE)bnRhOZ+)%#+OmgUq?Onqt$l+2bX*Vdj9>NPC^e1Ld`TAtKc})3|zK+ zG3t!0LVYKWqL%U|s^c;l&XNbBwlV@WfTpPXtx*%{f?BEG8TS1jMuGNjJVxUT)IeTA zJ+Rf@ccU6CL45&_p|<1}szcv_&O~aW%41O-w?Vbj6}7^BPy@&q$ofZ-7)e1G&c$e4 zjXFGgZTSh*+4u%E(2zk+N3~FUSr?12DQfFBp(bz`RsU1enYd`ZgF)ndT!Wnop%_m= z4C+v&pq9P}tD)+J;40M8@4))_32KH9P#yVXI#x#2i$7fqIPKM$*ros8;ekuBed{4!+T!&Le= zr6k%@(3M|jv~=@OOTQkgq1%?9z!dT~Q3Gl*)EQ_Rdfpn;L~>9a=VNu8j+*%j)K;xS zO<+4#(EGpN7Mw&id>M5(N^Rb6m~-kQQ4Ob|8XAtOSBQOZ29CrN*btlWYf}v7VkFK* z&%}^Vf^lOQ{TtsL_6KXC_B_uz9<`^_Q5`QtZOK~Hj5i?%-|RrXS;jk;&nphVWZaHr z@Cs_6*HP`?Ll)im$<@^{(opjrHBQ=;r(&W_3wX^a43!<29Qo7f{B&nR}nXeI^>&RCqmzXZ;6kIew5eO{Bz0Z zN+Wtuc+8V?zEpwadlL_@GNdPvX@fb$CVOKDzDG2m%pVJhRN@ChS5@KxQI7kKu`>1{ z{#^fSO501H*4KziR5)bIQt&X5K$#zrMRX&7l;}iy0qUAU+$Y8omk4j}ZNxD|QzD(X zOAMpjbGi8W3em_GEFv96>>_l96JHZGxZg?zTyuyR@*}Z2eucXJ?O;aZk3<|%kLXP3 zdV}al{T29x3b@q2=lYzV`P_8l>%{Lw58_c`7SVxwZO|VF6S`_UczzD2tP!boL|c2m z4(W=-G9s90Vaq=ut^X=gQ~lQ<6G~@5#J@iXmj<;nW~58_4@ZoG&yh+;z5r$hsy757@=2;zUlLE>5B;q^}v zovHIKPPc_8@E4o@6@$3{GxjIi+VT?I{&4-flUYb)P_Z$gYcSE1s6hS{#t^y|JD4Vv zUnN}^Tia)Jt0(DLY>sQO9flJ#b(3p1QO%QbUQJU#rXBINEu4f+dEnvI&nCKJt~)fU z*xS9O*3h!<;P^lkdtB2hUhetLl6>4H$uqob44*Ke zV3Nt4Fu5qYXjIR*dw0raZ+Cw06#r!< u1JAlQ59;sbZjss8%UziDzK=UO=d8D1r$c*3j2Tt5tkLi=cfaAGW&aQJmOx+t delta 8571 zcmZA63tZG?zQ^%rP?4L0TvYJFC<5XYP4bc{DySuRgHTXGgy9k}6oZoGI_^nbOKaOK z^{kp(ZDpF3O}bfan(kJl>&Z&hRf$NraQhEY0L%Ed0kX$OxI-YVIr=;`>-R%xsBn;>Tl^9=&}rnUn9JA!L)kao zkH;8H#Ff|&wSb+dM{xi(fln|L&!GCdV9VR2v;V3PL!uq_Kvhh^_Snxl6xH!4YaV8h zFGWpw6{_7v)C3y?+<#Ox%NA@ln)Hyo{=M3|YMS9MykB2KzsPL=wx<1PV|$=AZ_wv-!oS z72Sj3_^@@0^=Z@q0gS+XHh&P+-*H=h3X{lRK#kkc&pV{UGYK_;B5SF&%HH={e}&=H zTZ7utb?C;e_I@jdkw1<)8=s=ys&7#{7uMgIP&BF^e=-S8Aj1}nu=(+*hBHtNt5F?$ zQKz}ZmaoPL@(-eRY%}uaneF!eVe3)U4xL2R`x+zl{$C`am9!b)%q#)*o~NP)9Ba!b zqb4*P)qxi^@g=DCtL^>GsGSU;Ubj~;9X~>~Yd6ptCl2T6{ZFw4epH9QMP_CmvH5MN zw_^`#<$F$?73bjMupa%XP)&6Hxz3@TK%6p;?V}H~*lce7NA`)7u$LdABRxPLw z?!_qFVtocXkl%y)9C#D81BX!UKE{rC9($r0Yz%WT$*6ojs(wBCbqJQ)f>o#ie~+5! zbEpOfQT0xv2L28;p%6akwc=>h{Vu45^g_lq{ZKnsjGAZ}>b0yy?bxp}*?%=$PJueU zA2qX0sI7k1R@{&3pcOUnVbnk;P%HfcqwxarA!*tSaVC~w9fGPq5!ElVhS*QurU>y1|1Al|+XeVmmeW-!o!UlXF)zA20&Q>q5)}iV*TbH25S&lpc zzj@e6m@TLej;B%2@DQruw-}3;Q9IRvFYk2hhW&8@s$L^%f-R^=wh}d=b*Of`QT6^} z?;pZMz5gdk=y3di+QO@-!x4JDQyzl}u9 zn!q~L1fQ@zYyA@jzyJ5yg1@0UJce4~r>GU4#W?&IreVxTr+heSM{-aD=HYali>0^& zId3Lvl=CRYqIR+jRe#|q_FpSsL4gK%2XDYHPy@TkXhH)~`4KjsYn_hjaE`ryJ8CDE zq9(Q+^KlJM#Sd*hW3)4|meK5g2sgG-pcU^x4fG0XC2v_1Z!~5a`CE~5XkJEb=~-0$ zOIV2Q#yFN>0r}^UO*3uAI*)QRYJ$^H?Qiju&`e8F&#ubeXs|A_<*Jv+{gpT$|A1=e z%62*m$B8Zm!DWWCF^(TA_#tYCDmgfMTkb@?|4(9f^uI)cgJ_PT-pi;7&LQl9-Nb;9g^!{HW(L%w(T)q(S zpIC}@6P+!88(FMLn#AuxEJ2->yRGX{Te%%|R$fHCW_#`ZgQ$sof?D|{)Q&_<)*Z%&cugFmAh_U4rc$4u0OMxoy0DX7C%f)QAanoyH1UuyHKQIBRlYNgNM0NjuI zJUNT%KV%B~uNg&=(EI4NW@2aZ*)~7RT8)}$3#x-n7>Q4!>c3#~t*HA)F$_OJouyN# zg|y9c78;kw{%gy6QJ~X33VUOrtI*>SuTFK|A*X$yOVmzNZy59}8;tbS8hoTN$4yvO( zRQod21bnE8EyFIj8dd*s)I_(V9+`g^iT)%Gp$5E!k$4r=VdQkD!(`M>4L}{L8&MM} zK-Kf023(99aJh98YA0Sq_1kLmN0D}ZbK2gxg8irvF~j-sn1xzdC8~oKtim;@0WM%W zjGXCApcAToDrzAkQ0>NA3sLoJQ9Icfl;?j33C;Z1sFkfjZOvn-N3jQE@R+^-6{_Q( zQCk^X;2hrWsCL(*9$7wW#d9zkt5FkcK^^v`*j4ZU1`=wx6Ls3(!oheBQ_)@Me6UPJ zby$YlfjV1$H>Q!_gahyZcEocSi&s%+BK8*N3?!o_o{oO4dxL$BirsRI%228)~I3 z7>{?ORS!ry z%U7W$az7^HW>g1%MmN5X8sK|do>c5ibO@^6IMmshjhgr>)WGXdk9spU;a1e6^>-_A zRx}dTAQyFL3Q!HJY`y`tGs{r*euwF}1=H|N)Fb)^yW&q6hw=P)(4*;#{c#Fv{3~k5LOjmS#vq$)l01Ir*X?2o*bQ?OvoWj6ndu_bVOxzlwGW~u zywT<#w?2b21V(LRU6%6EU3lf#58gzY@A$bO?T~lg=cTk-nR_c1@-1O)}%qOI*A3;j@~!M2vLl{j*mihEs7B zmLZSKjK@cCHZC9@ApIMnfV8d~iR*(Y{ym>EJ)&d8Fw)PUUcUpx2=a3Xovl}iHwaz7 zzc#*$n>uj1njM0FIe)=##=qd}#Fk(w?>6;y&BKpzWia=@e;y`TZR^D2f7)Ar#)1y5kcLa#BtI`h#sV0 z!bWwT;J;t`3aJ1H*o_IJbzD+c$C;f=xRqrT~859iFERBV-j}8 z49u~0X5biN5aqELf&Gb3h*n}aWq-qK*9$h$fK__`Z=ymund$f>YDUiyG2|!WDdH@l z>o(#rk;c6?M41Y?wmXU88U^{yLa*{yc8e2d%xSGt#ERt!*)j@*4aCQ_WoYd9%2V^9c8ESR?H`Q1RpE~ z?-0d=t_0$-V9J=ga4Hc+lp?ydH?eUwxM4yAT#70tDaYMU23b)8Opfk#Jr!~V~LXL_p3>pg*}@l!)P1k>&o zPeqN-Qy-W;d8#Wir@YzYy?vplo;pw5v^69uS4;M=I;}V_FE1>*$kTLScU2W@uCJ}A zXh;t#(D}BxAB0SBYM2~PEwdYc;MIy6Pm|3#bGVlA_&nux!L2#) P$7Wk-yWd>ax!r#Q2NL#9 diff --git a/locale/de_DE/LC_MESSAGES/django.po b/locale/de_DE/LC_MESSAGES/django.po index 6d98fa0b..ac095694 100644 --- a/locale/de_DE/LC_MESSAGES/django.po +++ b/locale/de_DE/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-03-28 22:42+0000\n" +"POT-Creation-Date: 2021-03-31 09:22-0700\n" "PO-Revision-Date: 2021-03-02 17:19-0800\n" "Last-Translator: Mouse Reeve \n" "Language-Team: English \n" @@ -51,44 +51,64 @@ msgstr "%(count)d Benutzungen" msgid "Unlimited" msgstr "Ungelistet" -#: bookwyrm/models/fields.py:25 +#: bookwyrm/models/fields.py:24 #, python-format msgid "%(value)s is not a valid remote_id" msgstr "%(value)s ist keine gültige remote_id" -#: bookwyrm/models/fields.py:34 bookwyrm/models/fields.py:47 +#: bookwyrm/models/fields.py:33 bookwyrm/models/fields.py:42 #, python-format msgid "%(value)s is not a valid username" msgstr "%(value)s ist kein gültiger Username" -#: bookwyrm/models/fields.py:170 bookwyrm/templates/layout.html:157 +#: bookwyrm/models/fields.py:165 bookwyrm/templates/layout.html:157 msgid "username" msgstr "Username" -#: bookwyrm/models/fields.py:175 +#: bookwyrm/models/fields.py:170 msgid "A user with that username already exists." msgstr "Dieser Benutzename ist bereits vergeben." -#: bookwyrm/settings.py:148 +#: bookwyrm/settings.py:150 msgid "English" msgstr "Englisch" -#: bookwyrm/settings.py:149 +#: bookwyrm/settings.py:151 msgid "German" msgstr "Deutsch" -#: bookwyrm/settings.py:150 +#: bookwyrm/settings.py:152 msgid "Spanish" msgstr "Spanisch" -#: bookwyrm/settings.py:151 +#: bookwyrm/settings.py:153 msgid "French" msgstr "Französisch" -#: bookwyrm/settings.py:152 +#: bookwyrm/settings.py:154 msgid "Simplified Chinese" msgstr "Vereinfachtes Chinesisch" +#: bookwyrm/templates/404.html:4 bookwyrm/templates/404.html:8 +msgid "Not Found" +msgstr "Nicht gefunden" + +#: bookwyrm/templates/404.html:9 +msgid "The page you requested doesn't seem to exist!" +msgstr "Die Seite die du angefordert hast scheint nicht zu existieren!" + +#: bookwyrm/templates/500.html:4 +msgid "Oops!" +msgstr "Ups!" + +#: bookwyrm/templates/500.html:8 +msgid "Server Error" +msgstr "" + +#: bookwyrm/templates/500.html:9 +msgid "Something went wrong! Sorry about that." +msgstr "Etwas lief schief. Entschuldigung!" + #: bookwyrm/templates/author.html:16 bookwyrm/templates/author.html:17 msgid "Edit Author" msgstr "Autor*in editieren" @@ -112,92 +132,66 @@ msgstr "von" msgid "Edit Book" msgstr "Buch editieren" -#: bookwyrm/templates/book/book.html:45 +#: bookwyrm/templates/book/book.html:49 #: bookwyrm/templates/book/cover_modal.html:5 msgid "Add cover" msgstr "Cover hinzufügen" -#: bookwyrm/templates/book/book.html:55 +#: bookwyrm/templates/book/book.html:59 msgid "ISBN:" msgstr "" -#: bookwyrm/templates/book/book.html:62 +#: bookwyrm/templates/book/book.html:66 #: bookwyrm/templates/book/edit_book.html:211 msgid "OCLC Number:" msgstr "OCLC Nummer:" -#: bookwyrm/templates/book/book.html:69 +#: bookwyrm/templates/book/book.html:73 #: bookwyrm/templates/book/edit_book.html:215 msgid "ASIN:" msgstr "" -#: bookwyrm/templates/book/book.html:79 -#, python-format -msgid "%(format)s, %(pages)s pages" -msgstr "%(format)s, %(pages)s Seiten" - -#: bookwyrm/templates/book/book.html:81 -#, python-format -msgid "%(pages)s pages" -msgstr "%(pages)s Seiten" - -#: bookwyrm/templates/book/book.html:86 -#, python-format -msgid "Published %(date)s by %(publisher)s." -msgstr "Am %(date)s von %(publisher)s veröffentlicht." - -#: bookwyrm/templates/book/book.html:88 -#, fuzzy, python-format -#| msgid "Published date:" -msgid "Published %(date)s" -msgstr "Veröffentlichungsdatum:" - -#: bookwyrm/templates/book/book.html:90 -#, python-format -msgid "Published by %(publisher)s." -msgstr "Veröffentlicht von %(publisher)s." - -#: bookwyrm/templates/book/book.html:95 +#: bookwyrm/templates/book/book.html:82 msgid "View on OpenLibrary" msgstr "In OpenLibrary ansehen" -#: bookwyrm/templates/book/book.html:104 +#: bookwyrm/templates/book/book.html:91 #, python-format msgid "(%(review_count)s review)" msgid_plural "(%(review_count)s reviews)" msgstr[0] "(%(review_count)s Bewertung)" msgstr[1] "(%(review_count)s Bewertungen)" -#: bookwyrm/templates/book/book.html:110 +#: bookwyrm/templates/book/book.html:97 msgid "Add Description" msgstr "Beschreibung hinzufügen" -#: bookwyrm/templates/book/book.html:117 +#: bookwyrm/templates/book/book.html:104 #: bookwyrm/templates/book/edit_book.html:101 #: bookwyrm/templates/lists/form.html:12 msgid "Description:" msgstr "Beschreibung:" -#: bookwyrm/templates/book/book.html:121 +#: bookwyrm/templates/book/book.html:108 #: bookwyrm/templates/book/edit_book.html:225 #: bookwyrm/templates/edit_author.html:78 bookwyrm/templates/lists/form.html:42 -#: bookwyrm/templates/preferences/edit_user.html:68 +#: bookwyrm/templates/preferences/edit_user.html:70 #: bookwyrm/templates/settings/site.html:93 -#: bookwyrm/templates/snippets/readthrough.html:64 +#: bookwyrm/templates/snippets/readthrough.html:65 #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:42 #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:42 #: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:34 msgid "Save" msgstr "Speichern" -#: bookwyrm/templates/book/book.html:122 bookwyrm/templates/book/book.html:171 +#: bookwyrm/templates/book/book.html:109 bookwyrm/templates/book/book.html:158 #: bookwyrm/templates/book/cover_modal.html:32 #: bookwyrm/templates/book/edit_book.html:226 #: bookwyrm/templates/edit_author.html:79 #: bookwyrm/templates/moderation/report_modal.html:32 #: bookwyrm/templates/snippets/delete_readthrough_modal.html:17 #: bookwyrm/templates/snippets/goal_form.html:32 -#: bookwyrm/templates/snippets/readthrough.html:65 +#: bookwyrm/templates/snippets/readthrough.html:66 #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:43 #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:43 #: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:35 @@ -205,77 +199,77 @@ msgstr "Speichern" msgid "Cancel" msgstr "Abbrechen" -#: bookwyrm/templates/book/book.html:131 +#: bookwyrm/templates/book/book.html:118 #, fuzzy, python-format #| msgid "%(title)s by " msgid "%(count)s editions" msgstr "%(title)s von" -#: bookwyrm/templates/book/book.html:139 +#: bookwyrm/templates/book/book.html:126 #, fuzzy, python-format #| msgid "Direct Messages with %(username)s" msgid "This edition is on your %(shelf_name)s shelf." msgstr "Direktnachrichten mit %(username)s" -#: bookwyrm/templates/book/book.html:145 +#: bookwyrm/templates/book/book.html:132 #, fuzzy, python-format #| msgid " added %(book_title)s to your list \"%(list_name)s\"" msgid "A different edition of this book is on your %(shelf_name)s shelf." msgstr "hat %(book_title)s zu deiner Liste \"%(list_name)s\" Hinzugefügt" -#: bookwyrm/templates/book/book.html:154 +#: bookwyrm/templates/book/book.html:141 msgid "Your reading activity" msgstr "Deine Leseaktivität" -#: bookwyrm/templates/book/book.html:156 +#: bookwyrm/templates/book/book.html:143 msgid "Add read dates" msgstr "Lesedaten hinzufügen" -#: bookwyrm/templates/book/book.html:161 +#: bookwyrm/templates/book/book.html:148 msgid "You don't have any reading activity for this book." msgstr "Du hast keine Leseaktivität für dieses Buch." -#: bookwyrm/templates/book/book.html:168 +#: bookwyrm/templates/book/book.html:155 msgid "Create" msgstr "Erstellen" -#: bookwyrm/templates/book/book.html:190 +#: bookwyrm/templates/book/book.html:177 msgid "Tags" msgstr "" -#: bookwyrm/templates/book/book.html:194 +#: bookwyrm/templates/book/book.html:181 #: bookwyrm/templates/snippets/tag.html:18 msgid "Add tag" msgstr "Tag hinzufügen" -#: bookwyrm/templates/book/book.html:211 +#: bookwyrm/templates/book/book.html:198 msgid "Subjects" msgstr "Themen" -#: bookwyrm/templates/book/book.html:222 +#: bookwyrm/templates/book/book.html:209 msgid "Places" msgstr "Orte" -#: bookwyrm/templates/book/book.html:233 bookwyrm/templates/layout.html:64 -#: bookwyrm/templates/lists/lists.html:4 bookwyrm/templates/lists/lists.html:9 +#: bookwyrm/templates/book/book.html:220 bookwyrm/templates/layout.html:64 +#: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12 #: bookwyrm/templates/search_results.html:91 #: bookwyrm/templates/user/user_layout.html:62 msgid "Lists" msgstr "Listen" -#: bookwyrm/templates/book/book.html:244 +#: bookwyrm/templates/book/book.html:231 #, fuzzy #| msgid "Go to list" msgid "Add to list" msgstr "Zur Liste" -#: bookwyrm/templates/book/book.html:254 +#: bookwyrm/templates/book/book.html:241 #: bookwyrm/templates/book/cover_modal.html:31 #: bookwyrm/templates/lists/list.html:90 msgid "Add" msgstr "Hinzufügen" -#: bookwyrm/templates/book/book.html:282 +#: bookwyrm/templates/book/book.html:269 msgid "rated it" msgstr "bewertet" @@ -424,7 +418,7 @@ msgid "John Doe, Jane Smith" msgstr "" #: bookwyrm/templates/book/edit_book.html:155 -#: bookwyrm/templates/snippets/shelf.html:9 +#: bookwyrm/templates/user/shelf.html:72 msgid "Cover" msgstr "" @@ -433,6 +427,7 @@ msgid "Physical Properties" msgstr "Physikalische Eigenschaften" #: bookwyrm/templates/book/edit_book.html:183 +#: bookwyrm/templates/book/format_filter.html:5 msgid "Format:" msgstr "" @@ -457,98 +452,102 @@ msgstr "" msgid "Openlibrary key:" msgstr "" +#: bookwyrm/templates/book/editions.html:5 +#, python-format +msgid "Editions of %(book_title)s" +msgstr "Editionen von %(book_title)s" + +#: bookwyrm/templates/book/editions.html:9 +#, python-format +msgid "Editions of \"%(work_title)s\"" +msgstr "Editionen von \"%(work_title)s\"" + +#: bookwyrm/templates/book/format_filter.html:8 +#: bookwyrm/templates/book/language_filter.html:8 +msgid "Any" +msgstr "" + +#: bookwyrm/templates/book/language_filter.html:5 +msgid "Language:" +msgstr "" + +#: bookwyrm/templates/book/publisher_info.html:6 +#, python-format +msgid "%(format)s, %(pages)s pages" +msgstr "%(format)s, %(pages)s Seiten" + +#: bookwyrm/templates/book/publisher_info.html:8 +#, python-format +msgid "%(pages)s pages" +msgstr "%(pages)s Seiten" + +#: bookwyrm/templates/book/publisher_info.html:13 +#, fuzzy, python-format +#| msgid "%(pages)s pages" +msgid "%(languages)s language" +msgstr "%(pages)s Seiten" + +#: bookwyrm/templates/book/publisher_info.html:18 +#, python-format +msgid "Published %(date)s by %(publisher)s." +msgstr "Am %(date)s von %(publisher)s veröffentlicht." + +#: bookwyrm/templates/book/publisher_info.html:20 +#, fuzzy, python-format +#| msgid "Published date:" +msgid "Published %(date)s" +msgstr "Veröffentlichungsdatum:" + +#: bookwyrm/templates/book/publisher_info.html:22 +#, python-format +msgid "Published by %(publisher)s." +msgstr "Veröffentlicht von %(publisher)s." + #: bookwyrm/templates/components/inline_form.html:8 #: bookwyrm/templates/feed/feed_layout.html:57 msgid "Close" msgstr "Schließen" -#: bookwyrm/templates/directory.html:6 bookwyrm/templates/directory.html:11 -#: bookwyrm/templates/layout.html:97 -msgid "Directory" -msgstr "" - -#: bookwyrm/templates/directory.html:19 -msgid "Make your profile discoverable to other BookWyrm users." -msgstr "" - -#: bookwyrm/templates/directory.html:26 -#, fuzzy, python-format -#| msgid "You can set or change your reading goal any time from your profile page" -msgid "You can opt-out at any time in your profile settings." -msgstr "Du kannst dein Leseziel jederzeit auf deiner Profilseite setzen oder ändern." - -#: bookwyrm/templates/directory.html:31 -#: bookwyrm/templates/snippets/goal_card.html:22 -msgid "Dismiss message" -msgstr "Nachricht verwerfen" - -#: bookwyrm/templates/directory.html:44 -#, fuzzy -#| msgid "Show less" -msgid "Show filters" -msgstr "Weniger anzeigen" - -#: bookwyrm/templates/directory.html:46 -msgid "Hide filters" -msgstr "" - -#: bookwyrm/templates/directory.html:55 -#, fuzzy -#| msgid "User Activity" -msgid "User type" -msgstr "Nutzer*innenaktivität" - -#: bookwyrm/templates/directory.html:58 -msgid "BookWyrm users" -msgstr "" - -#: bookwyrm/templates/directory.html:62 -msgid "All known users" -msgstr "" - -#: bookwyrm/templates/directory.html:68 +#: bookwyrm/templates/directory/community_filter.html:5 #, fuzzy #| msgid "Comment" msgid "Community" msgstr "Kommentieren" -#: bookwyrm/templates/directory.html:71 +#: bookwyrm/templates/directory/community_filter.html:8 #, fuzzy #| msgid "Max uses" msgid "Local users" msgstr "Maximale Benutzungen" -#: bookwyrm/templates/directory.html:75 +#: bookwyrm/templates/directory/community_filter.html:12 #, fuzzy #| msgid "Federated" msgid "Federated community" msgstr "Föderiert" -#: bookwyrm/templates/directory.html:81 -msgid "Order by" +#: bookwyrm/templates/directory/directory.html:6 +#: bookwyrm/templates/directory/directory.html:11 +#: bookwyrm/templates/layout.html:97 +msgid "Directory" msgstr "" -#: bookwyrm/templates/directory.html:84 -#, fuzzy -#| msgid "Suggest" -msgid "Suggested" -msgstr "Vorschlagen" - -#: bookwyrm/templates/directory.html:85 -msgid "Recently active" +#: bookwyrm/templates/directory/directory.html:19 +msgid "Make your profile discoverable to other BookWyrm users." msgstr "" -#: bookwyrm/templates/directory.html:91 -msgid "Apply filters" -msgstr "" +#: bookwyrm/templates/directory/directory.html:26 +#, fuzzy, python-format +#| msgid "You can set or change your reading goal any time from your profile page" +msgid "You can opt-out at any time in your profile settings." +msgstr "Du kannst dein Leseziel jederzeit auf deiner Profilseite setzen oder ändern." -#: bookwyrm/templates/directory.html:94 -#, fuzzy -#| msgid "Clear search" -msgid "Clear filters" -msgstr "Suche leeren" +#: bookwyrm/templates/directory/directory.html:31 +#: bookwyrm/templates/snippets/goal_card.html:22 +msgid "Dismiss message" +msgstr "Nachricht verwerfen" -#: bookwyrm/templates/directory.html:128 +#: bookwyrm/templates/directory/directory.html:71 #, fuzzy #| msgid "followed you" msgid "follower you follow" @@ -556,7 +555,7 @@ msgid_plural "followers you follow" msgstr[0] "folgt dir" msgstr[1] "folgt dir" -#: bookwyrm/templates/directory.html:135 +#: bookwyrm/templates/directory/directory.html:78 #, fuzzy #| msgid "Your shelves" msgid "book on your shelves" @@ -564,14 +563,42 @@ msgid_plural "books on your shelves" msgstr[0] "Deine Regale" msgstr[1] "Deine Regale" -#: bookwyrm/templates/directory.html:143 +#: bookwyrm/templates/directory/directory.html:86 msgid "posts" msgstr "" -#: bookwyrm/templates/directory.html:149 +#: bookwyrm/templates/directory/directory.html:92 msgid "last active" msgstr "" +#: bookwyrm/templates/directory/sort_filter.html:5 +msgid "Order by" +msgstr "" + +#: bookwyrm/templates/directory/sort_filter.html:8 +#, fuzzy +#| msgid "Suggest" +msgid "Suggested" +msgstr "Vorschlagen" + +#: bookwyrm/templates/directory/sort_filter.html:9 +msgid "Recently active" +msgstr "" + +#: bookwyrm/templates/directory/user_type_filter.html:5 +#, fuzzy +#| msgid "User Activity" +msgid "User type" +msgstr "Nutzer*innenaktivität" + +#: bookwyrm/templates/directory/user_type_filter.html:8 +msgid "BookWyrm users" +msgstr "" + +#: bookwyrm/templates/directory/user_type_filter.html:12 +msgid "All known users" +msgstr "" + #: bookwyrm/templates/discover/about.html:7 #, python-format msgid "About %(site_name)s" @@ -681,16 +708,6 @@ msgstr "" msgid "Goodreads key:" msgstr "" -#: bookwyrm/templates/editions.html:5 -#, python-format -msgid "Editions of %(book_title)s" -msgstr "Editionen von %(book_title)s" - -#: bookwyrm/templates/editions.html:9 -#, python-format -msgid "Editions of \"%(work_title)s\"" -msgstr "Editionen von \"%(work_title)s\"" - #: bookwyrm/templates/email/html_layout.html:15 #: bookwyrm/templates/email/text_layout.html:2 msgid "Hi there," @@ -757,18 +774,6 @@ msgstr "" msgid "Reset your %(site_name)s password" msgstr "Ãœber %(site_name)s" -#: bookwyrm/templates/error.html:4 -msgid "Oops!" -msgstr "Ups!" - -#: bookwyrm/templates/error.html:8 -msgid "Server Error" -msgstr "" - -#: bookwyrm/templates/error.html:9 -msgid "Something went wrong! Sorry about that." -msgstr "Etwas lief schief. Entschuldigung!" - #: bookwyrm/templates/feed/direct_messages.html:8 #, python-format msgid "Direct Messages with %(username)s" @@ -845,6 +850,8 @@ msgid "Updates" msgstr "" #: bookwyrm/templates/feed/feed_layout.html:11 +#: bookwyrm/templates/layout.html:58 +#: bookwyrm/templates/user/books_header.html:3 msgid "Your books" msgstr "Deine Bücher" @@ -853,14 +860,14 @@ msgid "There are no books here right now! Try searching for a book to get starte msgstr "Hier sind noch keine Bücher! Versuche nach Büchern zu suchen um loszulegen" #: bookwyrm/templates/feed/feed_layout.html:23 -#: bookwyrm/templates/user/shelf.html:24 +#: bookwyrm/templates/user/shelf.html:25 #, fuzzy #| msgid "Read" msgid "To Read" msgstr "Auf der Leseliste" #: bookwyrm/templates/feed/feed_layout.html:24 -#: bookwyrm/templates/user/shelf.html:24 +#: bookwyrm/templates/user/shelf.html:25 #, fuzzy #| msgid "Start reading" msgid "Currently Reading" @@ -868,7 +875,7 @@ msgstr "Gerade lesend" #: bookwyrm/templates/feed/feed_layout.html:25 #: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:11 -#: bookwyrm/templates/user/shelf.html:24 +#: bookwyrm/templates/user/shelf.html:25 msgid "Read" msgstr "Gelesen" @@ -913,27 +920,33 @@ msgstr "%(username)ss %(year)s Bücher" msgid "Import Books" msgstr "Bücher importieren" -#: bookwyrm/templates/import.html:14 -msgid "Data source" +#: bookwyrm/templates/import.html:16 +#, fuzzy +#| msgid "Data source" +msgid "Data source:" msgstr "Datenquelle" -#: bookwyrm/templates/import.html:32 +#: bookwyrm/templates/import.html:29 +msgid "Data file:" +msgstr "" + +#: bookwyrm/templates/import.html:37 msgid "Include reviews" msgstr "Bewertungen importieren" -#: bookwyrm/templates/import.html:37 +#: bookwyrm/templates/import.html:42 msgid "Privacy setting for imported reviews:" msgstr "Datenschutzeinstellung für importierte Bewertungen" -#: bookwyrm/templates/import.html:41 +#: bookwyrm/templates/import.html:48 msgid "Import" msgstr "Importieren" -#: bookwyrm/templates/import.html:46 +#: bookwyrm/templates/import.html:53 msgid "Recent Imports" msgstr "Aktuelle Importe" -#: bookwyrm/templates/import.html:48 +#: bookwyrm/templates/import.html:55 msgid "No recent imports" msgstr "Keine aktuellen Importe" @@ -990,12 +1003,12 @@ msgstr "Buch" #: bookwyrm/templates/import_status.html:115 #: bookwyrm/templates/snippets/create_status_form.html:10 -#: bookwyrm/templates/snippets/shelf.html:10 +#: bookwyrm/templates/user/shelf.html:73 msgid "Title" msgstr "Titel" #: bookwyrm/templates/import_status.html:118 -#: bookwyrm/templates/snippets/shelf.html:11 +#: bookwyrm/templates/user/shelf.html:74 msgid "Author" msgstr "Autor*in" @@ -1051,10 +1064,6 @@ msgstr "Suche" msgid "Main navigation menu" msgstr "Navigationshauptmenü" -#: bookwyrm/templates/layout.html:58 -msgid "Your shelves" -msgstr "Deine Regale" - #: bookwyrm/templates/layout.html:61 msgid "Feed" msgstr "" @@ -1069,7 +1078,7 @@ msgid "Settings" msgstr "Einstellungen" #: bookwyrm/templates/layout.html:116 -#: bookwyrm/templates/settings/admin_layout.html:20 +#: bookwyrm/templates/settings/admin_layout.html:24 #: bookwyrm/templates/settings/manage_invite_requests.html:15 #: bookwyrm/templates/settings/manage_invites.html:3 #: bookwyrm/templates/settings/manage_invites.html:15 @@ -1131,7 +1140,7 @@ msgid "BookWyrm is open source software. You can contribute or report issues on msgstr "BookWyrm ist open source Software. Du kannst dich auf GitHub beteiligen oder etwas melden." #: bookwyrm/templates/lists/create_form.html:5 -#: bookwyrm/templates/lists/lists.html:17 +#: bookwyrm/templates/lists/lists.html:19 msgid "Create List" msgstr "Liste erstellen" @@ -1197,7 +1206,7 @@ msgid "Anyone can suggest books, subject to your approval" msgstr "Alle können Bücher vorschlagen, du kannst diese bestätigen" #: bookwyrm/templates/lists/form.html:31 -#: bookwyrm/templates/moderation/reports.html:11 +#: bookwyrm/templates/moderation/reports.html:24 msgid "Open" msgstr "Offen" @@ -1216,6 +1225,7 @@ msgid "Added by %(username)s" msgstr "Direktnachrichten mit %(username)s" #: bookwyrm/templates/lists/list.html:41 +#: bookwyrm/templates/snippets/shelf_selector.html:28 msgid "Remove" msgstr "Entfernen" @@ -1252,20 +1262,6 @@ msgstr "Keine Bücher gefunden" msgid "Suggest" msgstr "Vorschlagen" -#: bookwyrm/templates/lists/lists.html:14 -msgid "Your lists" -msgstr "Deine Listen" - -#: bookwyrm/templates/lists/lists.html:32 -#, fuzzy, python-format -#| msgid "See all %(size)s" -msgid "See all %(size)s lists" -msgstr "Alle %(size)s anzeigen" - -#: bookwyrm/templates/lists/lists.html:40 -msgid "Recent Lists" -msgstr "Aktuelle Listen" - #: bookwyrm/templates/login.html:4 msgid "Login" msgstr "" @@ -1379,27 +1375,37 @@ msgstr "Wiedereröffnen" msgid "Resolve" msgstr "Lösen" -#: bookwyrm/templates/moderation/reports.html:4 -#: bookwyrm/templates/moderation/reports.html:5 -#: bookwyrm/templates/settings/admin_layout.html:24 +#: bookwyrm/templates/moderation/reports.html:6 +#, fuzzy, python-format +#| msgid "Lists: %(username)s" +msgid "Reports: %(server_name)s" +msgstr "Listen: %(username)s" + +#: bookwyrm/templates/moderation/reports.html:8 +#: bookwyrm/templates/moderation/reports.html:16 +#: bookwyrm/templates/settings/admin_layout.html:28 #, fuzzy #| msgid "Recent Imports" msgid "Reports" msgstr "Aktuelle Importe" -#: bookwyrm/templates/moderation/reports.html:14 +#: bookwyrm/templates/moderation/reports.html:13 +#, fuzzy, python-format +#| msgid "Lists: %(username)s" +msgid "Reports: %(server_name)s" +msgstr "Listen: %(username)s" + +#: bookwyrm/templates/moderation/reports.html:27 #, fuzzy #| msgid "Shelved" msgid "Resolved" msgstr "Ins Regal gestellt" -#: bookwyrm/templates/notfound.html:4 bookwyrm/templates/notfound.html:8 -msgid "Not Found" -msgstr "Nicht gefunden" - -#: bookwyrm/templates/notfound.html:9 -msgid "The page you requested doesn't seem to exist!" -msgstr "Die Seite die du angefordert hast scheint nicht zu existieren!" +#: bookwyrm/templates/moderation/reports.html:34 +#, fuzzy +#| msgid "No books found" +msgid "No reports found." +msgstr "Keine Bücher gefunden" #: bookwyrm/templates/notifications.html:14 msgid "Delete notifications" @@ -1631,51 +1637,129 @@ msgstr "" msgid "Manage Users" msgstr "Nutzer*innen verwalten" -#: bookwyrm/templates/settings/admin_layout.html:28 -#: bookwyrm/templates/settings/federation.html:4 +#: bookwyrm/templates/settings/admin_layout.html:19 +#: bookwyrm/templates/settings/user_admin.html:3 +#: bookwyrm/templates/settings/user_admin.html:10 +msgid "Users" +msgstr "" + +#: bookwyrm/templates/settings/admin_layout.html:32 +#: bookwyrm/templates/settings/federation.html:3 +#: bookwyrm/templates/settings/federation.html:5 msgid "Federated Servers" msgstr "Föderierende Server" -#: bookwyrm/templates/settings/admin_layout.html:33 +#: bookwyrm/templates/settings/admin_layout.html:37 msgid "Instance Settings" msgstr "Instanzeinstellungen" -#: bookwyrm/templates/settings/admin_layout.html:37 +#: bookwyrm/templates/settings/admin_layout.html:41 #: bookwyrm/templates/settings/site.html:4 #: bookwyrm/templates/settings/site.html:6 msgid "Site Settings" msgstr "Seiteneinstellungen" -#: bookwyrm/templates/settings/admin_layout.html:40 +#: bookwyrm/templates/settings/admin_layout.html:44 #: bookwyrm/templates/settings/site.html:13 msgid "Instance Info" msgstr "Instanzinformationen" -#: bookwyrm/templates/settings/admin_layout.html:41 +#: bookwyrm/templates/settings/admin_layout.html:45 #: bookwyrm/templates/settings/site.html:39 msgid "Images" msgstr "Bilder" -#: bookwyrm/templates/settings/admin_layout.html:42 +#: bookwyrm/templates/settings/admin_layout.html:46 #: bookwyrm/templates/settings/site.html:59 msgid "Footer Content" msgstr "Inhalt des Footers" -#: bookwyrm/templates/settings/admin_layout.html:43 +#: bookwyrm/templates/settings/admin_layout.html:47 #: bookwyrm/templates/settings/site.html:77 msgid "Registration" msgstr "Registrierung" -#: bookwyrm/templates/settings/federation.html:10 +#: bookwyrm/templates/settings/federated_server.html:7 +#, fuzzy +#| msgid "Back to reports" +msgid "Back to server list" +msgstr "Zurück zu den Meldungen" + +#: bookwyrm/templates/settings/federated_server.html:12 +msgid "Details" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:15 +msgid "Software:" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:19 +#, fuzzy +#| msgid "Description:" +msgid "Version:" +msgstr "Beschreibung:" + +#: bookwyrm/templates/settings/federated_server.html:23 +#, fuzzy +#| msgid "Import Status" +msgid "Status:" +msgstr "Importstatus" + +#: bookwyrm/templates/settings/federated_server.html:30 +#: bookwyrm/templates/user/user_layout.html:50 +msgid "Activity" +msgstr "Aktivität" + +#: bookwyrm/templates/settings/federated_server.html:33 +msgid "Users:" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:36 +#: bookwyrm/templates/settings/federated_server.html:43 +msgid "View all" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:40 +#, fuzzy +#| msgid "Recent Imports" +msgid "Reports:" +msgstr "Aktuelle Importe" + +#: bookwyrm/templates/settings/federated_server.html:47 +#, fuzzy +#| msgid "followed you" +msgid "Followed by us:" +msgstr "folgt dir" + +#: bookwyrm/templates/settings/federated_server.html:53 +#, fuzzy +#| msgid "followed you" +msgid "Followed by them:" +msgstr "folgt dir" + +#: bookwyrm/templates/settings/federated_server.html:59 +#, fuzzy +#| msgid "Blocked Users" +msgid "Blocked by us:" +msgstr "Blockierte Nutzer*innen" + +#: bookwyrm/templates/settings/federation.html:13 msgid "Server name" msgstr "Servername" -#: bookwyrm/templates/settings/federation.html:11 +#: bookwyrm/templates/settings/federation.html:17 +#, fuzzy +#| msgid "Federated" +msgid "Date federated" +msgstr "Föderiert" + +#: bookwyrm/templates/settings/federation.html:21 msgid "Software" msgstr "" -#: bookwyrm/templates/settings/federation.html:12 +#: bookwyrm/templates/settings/federation.html:24 #: bookwyrm/templates/settings/manage_invite_requests.html:33 +#: bookwyrm/templates/settings/user_admin.html:32 msgid "Status" msgstr "" @@ -1846,6 +1930,47 @@ msgstr "Folgeanfragen" msgid "Registration closed text:" msgstr "Registrierungen geschlossen text" +#: bookwyrm/templates/settings/user_admin.html:7 +#, python-format +msgid "Users: %(server_name)s" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:20 +#, fuzzy +#| msgid "username" +msgid "Username" +msgstr "Username" + +#: bookwyrm/templates/settings/user_admin.html:24 +#, fuzzy +#| msgid "Added:" +msgid "Date Added" +msgstr "Hinzugefügt:" + +#: bookwyrm/templates/settings/user_admin.html:28 +msgid "Last Active" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:36 +#, fuzzy +#| msgid "Remove" +msgid "Remote server" +msgstr "Entfernen" + +#: bookwyrm/templates/settings/user_admin.html:45 +#, fuzzy +#| msgid "Activity" +msgid "Active" +msgstr "Aktivität" + +#: bookwyrm/templates/settings/user_admin.html:45 +msgid "Inactive" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:50 +msgid "Not set" +msgstr "" + #: bookwyrm/templates/snippets/block_button.html:5 msgid "Block" msgstr "" @@ -1906,7 +2031,7 @@ msgid "Review:" msgstr "Bewerten" #: bookwyrm/templates/snippets/create_status_form.html:29 -#: bookwyrm/templates/snippets/shelf.html:17 +#: bookwyrm/templates/user/shelf.html:78 msgid "Rating" msgstr "" @@ -1980,6 +2105,26 @@ msgstr "Status favorisieren" msgid "Un-like status" msgstr "Favorisieren zurücknehmen" +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:7 +#, fuzzy +#| msgid "Show less" +msgid "Show filters" +msgstr "Weniger anzeigen" + +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:9 +msgid "Hide filters" +msgstr "" + +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:19 +msgid "Apply filters" +msgstr "" + +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:23 +#, fuzzy +#| msgid "Clear search" +msgid "Clear filters" +msgstr "Suche leeren" + #: bookwyrm/templates/snippets/follow_button.html:12 msgid "Follow" msgstr "Folgen" @@ -2113,32 +2258,32 @@ msgstr "Raten" msgid "Rate" msgstr "" -#: bookwyrm/templates/snippets/readthrough.html:7 +#: bookwyrm/templates/snippets/readthrough.html:8 msgid "Progress Updates:" msgstr "Fortschrittsupdates:" -#: bookwyrm/templates/snippets/readthrough.html:11 +#: bookwyrm/templates/snippets/readthrough.html:12 msgid "finished" msgstr "Abgeschlossen" -#: bookwyrm/templates/snippets/readthrough.html:14 +#: bookwyrm/templates/snippets/readthrough.html:15 msgid "Show all updates" msgstr "Zeige alle Updates" -#: bookwyrm/templates/snippets/readthrough.html:30 +#: bookwyrm/templates/snippets/readthrough.html:31 msgid "Delete this progress update" msgstr "Dieses Fortschrittsupdate löschen" -#: bookwyrm/templates/snippets/readthrough.html:40 +#: bookwyrm/templates/snippets/readthrough.html:41 msgid "started" msgstr "Angefangen" -#: bookwyrm/templates/snippets/readthrough.html:46 -#: bookwyrm/templates/snippets/readthrough.html:60 +#: bookwyrm/templates/snippets/readthrough.html:47 +#: bookwyrm/templates/snippets/readthrough.html:61 msgid "Edit read dates" msgstr "Lesedaten bearbeiten" -#: bookwyrm/templates/snippets/readthrough.html:50 +#: bookwyrm/templates/snippets/readthrough.html:51 msgid "Delete these read dates" msgstr "Diese Lesedaten löschen" @@ -2202,45 +2347,11 @@ msgstr "von %(author)s" msgid "Import book" msgstr "Buch importieren" -#: bookwyrm/templates/snippets/shelf.html:12 -msgid "Published" -msgstr "Veröffentlicht" - -#: bookwyrm/templates/snippets/shelf.html:13 -msgid "Shelved" -msgstr "Ins Regal gestellt" - -#: bookwyrm/templates/snippets/shelf.html:14 -msgid "Started" -msgstr "Gestartet" - -#: bookwyrm/templates/snippets/shelf.html:15 -msgid "Finished" -msgstr "Abgeschlossen" - -#: bookwyrm/templates/snippets/shelf.html:16 -msgid "External links" -msgstr "Eterne Links" - -#: bookwyrm/templates/snippets/shelf.html:44 -msgid "OpenLibrary" -msgstr "" - -#: bookwyrm/templates/snippets/shelf.html:61 -msgid "This shelf is empty." -msgstr "Dieses Regal ist leer." - -#: bookwyrm/templates/snippets/shelf.html:67 -msgid "Delete shelf" -msgstr "Regal löschen" - #: bookwyrm/templates/snippets/shelf_selector.html:4 -msgid "Change shelf" -msgstr "Regal wechseln" - -#: bookwyrm/templates/snippets/shelf_selector.html:27 -msgid "Unshelve" -msgstr "Vom Regal nehmen" +#, fuzzy +#| msgid "Your books" +msgid "Move book" +msgstr "Deine Bücher" #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:5 #, python-format @@ -2248,7 +2359,7 @@ msgid "Finish \"%(book_title)s\"" msgstr "\"%(book_title)s\" abschließen" #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:5 -#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:33 +#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:35 #, fuzzy #| msgid "Progress" msgid "Update progress" @@ -2271,6 +2382,12 @@ msgstr "Lesen abschließen" msgid "Want to read" msgstr "Auf Leseliste setzen" +#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:48 +#, fuzzy, python-format +#| msgid "Lists: %(username)s" +msgid "Remove from %(name)s" +msgstr "Listen: %(username)s" + #: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:5 #, python-format msgid "Start \"%(book_title)s\"" @@ -2343,6 +2460,18 @@ msgstr "Mehr Optionen" msgid "Switch to this edition" msgstr "Zu dieser Edition wechseln" +#: bookwyrm/templates/snippets/table-sort-header.html:6 +#, fuzzy +#| msgid "Started reading" +msgid "Sorted asccending" +msgstr "Zu lesen angefangen" + +#: bookwyrm/templates/snippets/table-sort-header.html:10 +#, fuzzy +#| msgid "Started reading" +msgid "Sorted descending" +msgstr "Zu lesen angefangen" + #: bookwyrm/templates/snippets/tag.html:14 msgid "Remove tag" msgstr "Tag entfernen" @@ -2352,6 +2481,12 @@ msgstr "Tag entfernen" msgid "Books tagged \"%(tag.name)s\"" msgstr "Mit \"%(tag.name)s\" markierte Bücher" +#: bookwyrm/templates/user/books_header.html:5 +#, fuzzy, python-format +#| msgid "%(username)s's %(year)s Books" +msgid "%(username)s's books" +msgstr "%(username)ss %(year)s Bücher" + #: bookwyrm/templates/user/create_shelf_form.html:5 #: bookwyrm/templates/user/create_shelf_form.html:22 msgid "Create Shelf" @@ -2397,56 +2532,62 @@ msgstr "Listen: %(username)s" msgid "Create list" msgstr "Liste Erstellen" -#: bookwyrm/templates/user/shelf.html:9 -msgid "Your Shelves" -msgstr "Deine Regale" - -#: bookwyrm/templates/user/shelf.html:11 -#, python-format -msgid "%(username)s: Shelves" -msgstr "%(username)s: Regale" - -#: bookwyrm/templates/user/shelf.html:33 +#: bookwyrm/templates/user/shelf.html:34 msgid "Create shelf" msgstr "Regal erstellen" -#: bookwyrm/templates/user/shelf.html:54 +#: bookwyrm/templates/user/shelf.html:55 msgid "Edit shelf" msgstr "Regal bearbeiten" +#: bookwyrm/templates/user/shelf.html:75 +msgid "Shelved" +msgstr "Ins Regal gestellt" + +#: bookwyrm/templates/user/shelf.html:76 +msgid "Started" +msgstr "Gestartet" + +#: bookwyrm/templates/user/shelf.html:77 +msgid "Finished" +msgstr "Abgeschlossen" + +#: bookwyrm/templates/user/shelf.html:121 +msgid "This shelf is empty." +msgstr "Dieses Regal ist leer." + +#: bookwyrm/templates/user/shelf.html:127 +msgid "Delete shelf" +msgstr "Regal löschen" + #: bookwyrm/templates/user/user.html:15 msgid "Edit profile" msgstr "Profil bearbeiten" -#: bookwyrm/templates/user/user.html:26 -#: bookwyrm/templates/user/user_layout.html:68 -msgid "Shelves" -msgstr "Regale" - -#: bookwyrm/templates/user/user.html:31 -#, python-format -msgid "See all %(size)s" +#: bookwyrm/templates/user/user.html:33 +#, fuzzy, python-format +#| msgid "See all %(size)s" +msgid "View all %(size)s" msgstr "Alle %(size)s anzeigen" -#: bookwyrm/templates/user/user.html:44 -#, python-format -msgid "See all %(shelf_count)s shelves" -msgstr "Alle %(shelf_count)s Regale anzeigen" +#: bookwyrm/templates/user/user.html:46 +msgid "View all books" +msgstr "" -#: bookwyrm/templates/user/user.html:56 +#: bookwyrm/templates/user/user.html:58 #, python-format msgid "Set a reading goal for %(year)s" msgstr "Leseziel für %(year)s setzen" -#: bookwyrm/templates/user/user.html:62 +#: bookwyrm/templates/user/user.html:64 msgid "User Activity" msgstr "Nutzer*innenaktivität" -#: bookwyrm/templates/user/user.html:65 +#: bookwyrm/templates/user/user.html:67 msgid "RSS feed" msgstr "" -#: bookwyrm/templates/user/user.html:76 +#: bookwyrm/templates/user/user.html:78 msgid "No activities yet!" msgstr "Noch keine Aktivitäten!" @@ -2454,14 +2595,16 @@ msgstr "Noch keine Aktivitäten!" msgid "Follow Requests" msgstr "Folgeanfragen" -#: bookwyrm/templates/user/user_layout.html:50 -msgid "Activity" -msgstr "Aktivität" - #: bookwyrm/templates/user/user_layout.html:56 msgid "Reading Goal" msgstr "Leseziel" +#: bookwyrm/templates/user/user_layout.html:68 +#, fuzzy +#| msgid "Book" +msgid "Books" +msgstr "Buch" + #: bookwyrm/templates/user/user_preview.html:13 #, python-format msgid "Joined %(date)s" @@ -2490,6 +2633,46 @@ msgstr "Dieser Benutzename ist bereits vergeben." msgid "A password reset link sent to %s" msgstr "" +#~ msgid "Your shelves" +#~ msgstr "Deine Regale" + +#~ msgid "Your lists" +#~ msgstr "Deine Listen" + +#, fuzzy, python-format +#~| msgid "See all %(size)s" +#~ msgid "See all %(size)s lists" +#~ msgstr "Alle %(size)s anzeigen" + +#~ msgid "Recent Lists" +#~ msgstr "Aktuelle Listen" + +#~ msgid "Published" +#~ msgstr "Veröffentlicht" + +#~ msgid "External links" +#~ msgstr "Eterne Links" + +#~ msgid "Change shelf" +#~ msgstr "Regal wechseln" + +#~ msgid "Unshelve" +#~ msgstr "Vom Regal nehmen" + +#~ msgid "Your Shelves" +#~ msgstr "Deine Regale" + +#, python-format +#~ msgid "%(username)s: Shelves" +#~ msgstr "%(username)s: Regale" + +#~ msgid "Shelves" +#~ msgstr "Regale" + +#, python-format +#~ msgid "See all %(shelf_count)s shelves" +#~ msgstr "Alle %(shelf_count)s Regale anzeigen" + #~ msgid "Send follow request" #~ msgstr "Folgeanfrage senden" diff --git a/locale/en_US/LC_MESSAGES/django.po b/locale/en_US/LC_MESSAGES/django.po index e6256a7f..190a5b1a 100644 --- a/locale/en_US/LC_MESSAGES/django.po +++ b/locale/en_US/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-03-28 22:42+0000\n" +"POT-Creation-Date: 2021-03-31 09:22-0700\n" "PO-Revision-Date: 2021-02-28 17:19-0800\n" "Last-Translator: Mouse Reeve \n" "Language-Team: English \n" @@ -47,44 +47,64 @@ msgstr "" msgid "Unlimited" msgstr "" -#: bookwyrm/models/fields.py:25 +#: bookwyrm/models/fields.py:24 #, python-format msgid "%(value)s is not a valid remote_id" msgstr "" -#: bookwyrm/models/fields.py:34 bookwyrm/models/fields.py:47 +#: bookwyrm/models/fields.py:33 bookwyrm/models/fields.py:42 #, python-format msgid "%(value)s is not a valid username" msgstr "" -#: bookwyrm/models/fields.py:170 bookwyrm/templates/layout.html:157 +#: bookwyrm/models/fields.py:165 bookwyrm/templates/layout.html:157 msgid "username" msgstr "" -#: bookwyrm/models/fields.py:175 +#: bookwyrm/models/fields.py:170 msgid "A user with that username already exists." msgstr "" -#: bookwyrm/settings.py:148 +#: bookwyrm/settings.py:150 msgid "English" msgstr "" -#: bookwyrm/settings.py:149 +#: bookwyrm/settings.py:151 msgid "German" msgstr "" -#: bookwyrm/settings.py:150 +#: bookwyrm/settings.py:152 msgid "Spanish" msgstr "" -#: bookwyrm/settings.py:151 +#: bookwyrm/settings.py:153 msgid "French" msgstr "" -#: bookwyrm/settings.py:152 +#: bookwyrm/settings.py:154 msgid "Simplified Chinese" msgstr "" +#: bookwyrm/templates/404.html:4 bookwyrm/templates/404.html:8 +msgid "Not Found" +msgstr "" + +#: bookwyrm/templates/404.html:9 +msgid "The page you requested doesn't seem to exist!" +msgstr "" + +#: bookwyrm/templates/500.html:4 +msgid "Oops!" +msgstr "" + +#: bookwyrm/templates/500.html:8 +msgid "Server Error" +msgstr "" + +#: bookwyrm/templates/500.html:9 +msgid "Something went wrong! Sorry about that." +msgstr "" + #: bookwyrm/templates/author.html:16 bookwyrm/templates/author.html:17 msgid "Edit Author" msgstr "" @@ -108,91 +128,66 @@ msgstr "" msgid "Edit Book" msgstr "" -#: bookwyrm/templates/book/book.html:45 +#: bookwyrm/templates/book/book.html:49 #: bookwyrm/templates/book/cover_modal.html:5 msgid "Add cover" msgstr "" -#: bookwyrm/templates/book/book.html:55 +#: bookwyrm/templates/book/book.html:59 msgid "ISBN:" msgstr "" -#: bookwyrm/templates/book/book.html:62 +#: bookwyrm/templates/book/book.html:66 #: bookwyrm/templates/book/edit_book.html:211 msgid "OCLC Number:" msgstr "" -#: bookwyrm/templates/book/book.html:69 +#: bookwyrm/templates/book/book.html:73 #: bookwyrm/templates/book/edit_book.html:215 msgid "ASIN:" msgstr "" -#: bookwyrm/templates/book/book.html:79 -#, python-format -msgid "%(format)s, %(pages)s pages" -msgstr "" - -#: bookwyrm/templates/book/book.html:81 -#, python-format -msgid "%(pages)s pages" -msgstr "" - -#: bookwyrm/templates/book/book.html:86 -#, python-format -msgid "Published %(date)s by %(publisher)s." -msgstr "" - -#: bookwyrm/templates/book/book.html:88 -#, python-format -msgid "Published %(date)s" -msgstr "" - -#: bookwyrm/templates/book/book.html:90 -#, python-format -msgid "Published by %(publisher)s." -msgstr "" - -#: bookwyrm/templates/book/book.html:95 +#: bookwyrm/templates/book/book.html:82 msgid "View on OpenLibrary" msgstr "" -#: bookwyrm/templates/book/book.html:104 +#: bookwyrm/templates/book/book.html:91 #, python-format msgid "(%(review_count)s review)" msgid_plural "(%(review_count)s reviews)" msgstr[0] "" msgstr[1] "" -#: bookwyrm/templates/book/book.html:110 +#: bookwyrm/templates/book/book.html:97 msgid "Add Description" msgstr "" -#: bookwyrm/templates/book/book.html:117 +#: bookwyrm/templates/book/book.html:104 #: bookwyrm/templates/book/edit_book.html:101 #: bookwyrm/templates/lists/form.html:12 msgid "Description:" msgstr "" -#: bookwyrm/templates/book/book.html:121 +#: bookwyrm/templates/book/book.html:108 #: bookwyrm/templates/book/edit_book.html:225 #: bookwyrm/templates/edit_author.html:78 bookwyrm/templates/lists/form.html:42 -#: bookwyrm/templates/preferences/edit_user.html:68 +#: bookwyrm/templates/preferences/edit_user.html:70 #: bookwyrm/templates/settings/site.html:93 -#: bookwyrm/templates/snippets/readthrough.html:64 +#: bookwyrm/templates/snippets/readthrough.html:65 #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:42 #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:42 #: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:34 msgid "Save" msgstr "" -#: bookwyrm/templates/book/book.html:122 bookwyrm/templates/book/book.html:171 +#: bookwyrm/templates/book/book.html:109 bookwyrm/templates/book/book.html:158 #: bookwyrm/templates/book/cover_modal.html:32 #: bookwyrm/templates/book/edit_book.html:226 #: bookwyrm/templates/edit_author.html:79 #: bookwyrm/templates/moderation/report_modal.html:32 #: bookwyrm/templates/snippets/delete_readthrough_modal.html:17 #: bookwyrm/templates/snippets/goal_form.html:32 -#: bookwyrm/templates/snippets/readthrough.html:65 +#: bookwyrm/templates/snippets/readthrough.html:66 #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:43 #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:43 #: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:35 @@ -200,72 +195,72 @@ msgstr "" msgid "Cancel" msgstr "" -#: bookwyrm/templates/book/book.html:131 +#: bookwyrm/templates/book/book.html:118 #, python-format msgid "%(count)s editions" msgstr "" -#: bookwyrm/templates/book/book.html:139 +#: bookwyrm/templates/book/book.html:126 #, python-format msgid "This edition is on your %(shelf_name)s shelf." msgstr "" -#: bookwyrm/templates/book/book.html:145 +#: bookwyrm/templates/book/book.html:132 #, python-format msgid "A different edition of this book is on your %(shelf_name)s shelf." msgstr "" -#: bookwyrm/templates/book/book.html:154 +#: bookwyrm/templates/book/book.html:141 msgid "Your reading activity" msgstr "" -#: bookwyrm/templates/book/book.html:156 +#: bookwyrm/templates/book/book.html:143 msgid "Add read dates" msgstr "" -#: bookwyrm/templates/book/book.html:161 +#: bookwyrm/templates/book/book.html:148 msgid "You don't have any reading activity for this book." msgstr "" -#: bookwyrm/templates/book/book.html:168 +#: bookwyrm/templates/book/book.html:155 msgid "Create" msgstr "" -#: bookwyrm/templates/book/book.html:190 +#: bookwyrm/templates/book/book.html:177 msgid "Tags" msgstr "" -#: bookwyrm/templates/book/book.html:194 +#: bookwyrm/templates/book/book.html:181 #: bookwyrm/templates/snippets/tag.html:18 msgid "Add tag" msgstr "" -#: bookwyrm/templates/book/book.html:211 +#: bookwyrm/templates/book/book.html:198 msgid "Subjects" msgstr "" -#: bookwyrm/templates/book/book.html:222 +#: bookwyrm/templates/book/book.html:209 msgid "Places" msgstr "" -#: bookwyrm/templates/book/book.html:233 bookwyrm/templates/layout.html:64 -#: bookwyrm/templates/lists/lists.html:4 bookwyrm/templates/lists/lists.html:9 +#: bookwyrm/templates/book/book.html:220 bookwyrm/templates/layout.html:64 +#: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12 #: bookwyrm/templates/search_results.html:91 #: bookwyrm/templates/user/user_layout.html:62 msgid "Lists" msgstr "" -#: bookwyrm/templates/book/book.html:244 +#: bookwyrm/templates/book/book.html:231 msgid "Add to list" msgstr "" -#: bookwyrm/templates/book/book.html:254 +#: bookwyrm/templates/book/book.html:241 #: bookwyrm/templates/book/cover_modal.html:31 #: bookwyrm/templates/lists/list.html:90 msgid "Add" msgstr "" -#: bookwyrm/templates/book/book.html:282 +#: bookwyrm/templates/book/book.html:269 msgid "rated it" msgstr "" @@ -401,7 +396,7 @@ msgid "John Doe, Jane Smith" msgstr "" #: bookwyrm/templates/book/edit_book.html:155 -#: bookwyrm/templates/snippets/shelf.html:9 +#: bookwyrm/templates/user/shelf.html:72 msgid "Cover" msgstr "" @@ -410,6 +405,7 @@ msgid "Physical Properties" msgstr "" #: bookwyrm/templates/book/edit_book.html:183 +#: bookwyrm/templates/book/format_filter.html:5 msgid "Format:" msgstr "" @@ -434,102 +430,136 @@ msgstr "" msgid "Openlibrary key:" msgstr "" +#: bookwyrm/templates/book/editions.html:5 +#, python-format +msgid "Editions of %(book_title)s" +msgstr "" + +#: bookwyrm/templates/book/editions.html:9 +#, python-format +msgid "Editions of \"%(work_title)s\"" +msgstr "" + +#: bookwyrm/templates/book/format_filter.html:8 +#: bookwyrm/templates/book/language_filter.html:8 +msgid "Any" +msgstr "" + +#: bookwyrm/templates/book/language_filter.html:5 +msgid "Language:" +msgstr "" + +#: bookwyrm/templates/book/publisher_info.html:6 +#, python-format +msgid "%(format)s, %(pages)s pages" +msgstr "" + +#: bookwyrm/templates/book/publisher_info.html:8 +#, python-format +msgid "%(pages)s pages" +msgstr "" + +#: bookwyrm/templates/book/publisher_info.html:13 +#, python-format +msgid "%(languages)s language" +msgstr "" + +#: bookwyrm/templates/book/publisher_info.html:18 +#, python-format +msgid "Published %(date)s by %(publisher)s." +msgstr "" + +#: bookwyrm/templates/book/publisher_info.html:20 +#, python-format +msgid "Published %(date)s" +msgstr "" + +#: bookwyrm/templates/book/publisher_info.html:22 +#, python-format +msgid "Published by %(publisher)s." +msgstr "" + #: bookwyrm/templates/components/inline_form.html:8 #: bookwyrm/templates/feed/feed_layout.html:57 msgid "Close" msgstr "" -#: bookwyrm/templates/directory.html:6 bookwyrm/templates/directory.html:11 +#: bookwyrm/templates/directory/community_filter.html:5 +msgid "Community" +msgstr "" + +#: bookwyrm/templates/directory/community_filter.html:8 +msgid "Local users" +msgstr "" + +#: bookwyrm/templates/directory/community_filter.html:12 +msgid "Federated community" +msgstr "" + +#: bookwyrm/templates/directory/directory.html:6 +#: bookwyrm/templates/directory/directory.html:11 #: bookwyrm/templates/layout.html:97 msgid "Directory" msgstr "" -#: bookwyrm/templates/directory.html:19 +#: bookwyrm/templates/directory/directory.html:19 msgid "Make your profile discoverable to other BookWyrm users." msgstr "" -#: bookwyrm/templates/directory.html:26 +#: bookwyrm/templates/directory/directory.html:26 #, python-format msgid "You can opt-out at any time in your profile settings." msgstr "" -#: bookwyrm/templates/directory.html:31 +#: bookwyrm/templates/directory/directory.html:31 #: bookwyrm/templates/snippets/goal_card.html:22 msgid "Dismiss message" msgstr "" -#: bookwyrm/templates/directory.html:44 -msgid "Show filters" -msgstr "" - -#: bookwyrm/templates/directory.html:46 -msgid "Hide filters" -msgstr "" - -#: bookwyrm/templates/directory.html:55 -msgid "User type" -msgstr "" - -#: bookwyrm/templates/directory.html:58 -msgid "BookWyrm users" -msgstr "" - -#: bookwyrm/templates/directory.html:62 -msgid "All known users" -msgstr "" - -#: bookwyrm/templates/directory.html:68 -msgid "Community" -msgstr "" - -#: bookwyrm/templates/directory.html:71 -msgid "Local users" -msgstr "" - -#: bookwyrm/templates/directory.html:75 -msgid "Federated community" -msgstr "" - -#: bookwyrm/templates/directory.html:81 -msgid "Order by" -msgstr "" - -#: bookwyrm/templates/directory.html:84 -msgid "Suggested" -msgstr "" - -#: bookwyrm/templates/directory.html:85 -msgid "Recently active" -msgstr "" - -#: bookwyrm/templates/directory.html:91 -msgid "Apply filters" -msgstr "" - -#: bookwyrm/templates/directory.html:94 -msgid "Clear filters" -msgstr "" - -#: bookwyrm/templates/directory.html:128 +#: bookwyrm/templates/directory/directory.html:71 msgid "follower you follow" msgid_plural "followers you follow" msgstr[0] "" msgstr[1] "" -#: bookwyrm/templates/directory.html:135 +#: bookwyrm/templates/directory/directory.html:78 msgid "book on your shelves" msgid_plural "books on your shelves" msgstr[0] "" msgstr[1] "" -#: bookwyrm/templates/directory.html:143 +#: bookwyrm/templates/directory/directory.html:86 msgid "posts" msgstr "" -#: bookwyrm/templates/directory.html:149 +#: bookwyrm/templates/directory/directory.html:92 msgid "last active" msgstr "" +#: bookwyrm/templates/directory/sort_filter.html:5 +msgid "Order by" +msgstr "" + +#: bookwyrm/templates/directory/sort_filter.html:8 +msgid "Suggested" +msgstr "" + +#: bookwyrm/templates/directory/sort_filter.html:9 +msgid "Recently active" +msgstr "" + +#: bookwyrm/templates/directory/user_type_filter.html:5 +msgid "User type" +msgstr "" + +#: bookwyrm/templates/directory/user_type_filter.html:8 +msgid "BookWyrm users" +msgstr "" + +#: bookwyrm/templates/directory/user_type_filter.html:12 +msgid "All known users" +msgstr "" + #: bookwyrm/templates/discover/about.html:7 #, python-format msgid "About %(site_name)s" @@ -637,16 +667,6 @@ msgstr "" msgid "Goodreads key:" msgstr "" -#: bookwyrm/templates/editions.html:5 -#, python-format -msgid "Editions of %(book_title)s" -msgstr "" - -#: bookwyrm/templates/editions.html:9 -#, python-format -msgid "Editions of \"%(work_title)s\"" -msgstr "" - #: bookwyrm/templates/email/html_layout.html:15 #: bookwyrm/templates/email/text_layout.html:2 msgid "Hi there," @@ -709,18 +729,6 @@ msgstr "" msgid "Reset your %(site_name)s password" msgstr "" -#: bookwyrm/templates/error.html:4 -msgid "Oops!" -msgstr "" - -#: bookwyrm/templates/error.html:8 -msgid "Server Error" -msgstr "" - -#: bookwyrm/templates/error.html:9 -msgid "Something went wrong! Sorry about that." -msgstr "" - #: bookwyrm/templates/feed/direct_messages.html:8 #, python-format msgid "Direct Messages with %(username)s" @@ -795,6 +803,8 @@ msgid "Updates" msgstr "" #: bookwyrm/templates/feed/feed_layout.html:11 +#: bookwyrm/templates/layout.html:58 +#: bookwyrm/templates/user/books_header.html:3 msgid "Your books" msgstr "" @@ -803,18 +813,18 @@ msgid "There are no books here right now! Try searching for a book to get starte msgstr "" #: bookwyrm/templates/feed/feed_layout.html:23 -#: bookwyrm/templates/user/shelf.html:24 +#: bookwyrm/templates/user/shelf.html:25 msgid "To Read" msgstr "" #: bookwyrm/templates/feed/feed_layout.html:24 -#: bookwyrm/templates/user/shelf.html:24 +#: bookwyrm/templates/user/shelf.html:25 msgid "Currently Reading" msgstr "" #: bookwyrm/templates/feed/feed_layout.html:25 #: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:11 -#: bookwyrm/templates/user/shelf.html:24 +#: bookwyrm/templates/user/shelf.html:25 msgid "Read" msgstr "" @@ -859,27 +869,31 @@ msgstr "" msgid "Import Books" msgstr "" -#: bookwyrm/templates/import.html:14 -msgid "Data source" +#: bookwyrm/templates/import.html:16 +msgid "Data source:" msgstr "" -#: bookwyrm/templates/import.html:32 -msgid "Include reviews" +#: bookwyrm/templates/import.html:29 +msgid "Data file:" msgstr "" #: bookwyrm/templates/import.html:37 +msgid "Include reviews" +msgstr "" + +#: bookwyrm/templates/import.html:42 msgid "Privacy setting for imported reviews:" msgstr "" -#: bookwyrm/templates/import.html:41 +#: bookwyrm/templates/import.html:48 msgid "Import" msgstr "" -#: bookwyrm/templates/import.html:46 +#: bookwyrm/templates/import.html:53 msgid "Recent Imports" msgstr "" -#: bookwyrm/templates/import.html:48 +#: bookwyrm/templates/import.html:55 msgid "No recent imports" msgstr "" @@ -936,12 +950,12 @@ msgstr "" #: bookwyrm/templates/import_status.html:115 #: bookwyrm/templates/snippets/create_status_form.html:10 -#: bookwyrm/templates/snippets/shelf.html:10 +#: bookwyrm/templates/user/shelf.html:73 msgid "Title" msgstr "" #: bookwyrm/templates/import_status.html:118 -#: bookwyrm/templates/snippets/shelf.html:11 +#: bookwyrm/templates/user/shelf.html:74 msgid "Author" msgstr "" @@ -997,10 +1011,6 @@ msgstr "" msgid "Main navigation menu" msgstr "" -#: bookwyrm/templates/layout.html:58 -msgid "Your shelves" -msgstr "" - #: bookwyrm/templates/layout.html:61 msgid "Feed" msgstr "" @@ -1015,7 +1025,7 @@ msgid "Settings" msgstr "" #: bookwyrm/templates/layout.html:116 -#: bookwyrm/templates/settings/admin_layout.html:20 +#: bookwyrm/templates/settings/admin_layout.html:24 #: bookwyrm/templates/settings/manage_invite_requests.html:15 #: bookwyrm/templates/settings/manage_invites.html:3 #: bookwyrm/templates/settings/manage_invites.html:15 @@ -1077,7 +1087,7 @@ msgid "BookWyrm is open source software. You can contribute or report issues on msgstr "" #: bookwyrm/templates/lists/create_form.html:5 -#: bookwyrm/templates/lists/lists.html:17 +#: bookwyrm/templates/lists/lists.html:19 msgid "Create List" msgstr "" @@ -1141,7 +1151,7 @@ msgid "Anyone can suggest books, subject to your approval" msgstr "" #: bookwyrm/templates/lists/form.html:31 -#: bookwyrm/templates/moderation/reports.html:11 +#: bookwyrm/templates/moderation/reports.html:24 msgid "Open" msgstr "" @@ -1159,6 +1169,7 @@ msgid "Added by %(username)s" msgstr "" #: bookwyrm/templates/lists/list.html:41 +#: bookwyrm/templates/snippets/shelf_selector.html:28 msgid "Remove" msgstr "" @@ -1195,19 +1206,6 @@ msgstr "" msgid "Suggest" msgstr "" -#: bookwyrm/templates/lists/lists.html:14 -msgid "Your lists" -msgstr "" - -#: bookwyrm/templates/lists/lists.html:32 -#, python-format -msgid "See all %(size)s lists" -msgstr "" - -#: bookwyrm/templates/lists/lists.html:40 -msgid "Recent Lists" -msgstr "" - #: bookwyrm/templates/login.html:4 msgid "Login" msgstr "" @@ -1311,22 +1309,28 @@ msgstr "" msgid "Resolve" msgstr "" -#: bookwyrm/templates/moderation/reports.html:4 -#: bookwyrm/templates/moderation/reports.html:5 -#: bookwyrm/templates/settings/admin_layout.html:24 +#: bookwyrm/templates/moderation/reports.html:6 +#, python-format +msgid "Reports: %(server_name)s" +msgstr "" + +#: bookwyrm/templates/moderation/reports.html:8 +#: bookwyrm/templates/moderation/reports.html:16 +#: bookwyrm/templates/settings/admin_layout.html:28 msgid "Reports" msgstr "" -#: bookwyrm/templates/moderation/reports.html:14 +#: bookwyrm/templates/moderation/reports.html:13 +#, python-format +msgid "Reports: %(server_name)s" +msgstr "" + +#: bookwyrm/templates/moderation/reports.html:27 msgid "Resolved" msgstr "" -#: bookwyrm/templates/notfound.html:4 bookwyrm/templates/notfound.html:8 -msgid "Not Found" -msgstr "" - -#: bookwyrm/templates/notfound.html:9 -msgid "The page you requested doesn't seem to exist!" +#: bookwyrm/templates/moderation/reports.html:34 +msgid "No reports found." msgstr "" #: bookwyrm/templates/notifications.html:14 @@ -1559,51 +1563,113 @@ msgstr "" msgid "Manage Users" msgstr "" -#: bookwyrm/templates/settings/admin_layout.html:28 -#: bookwyrm/templates/settings/federation.html:4 +#: bookwyrm/templates/settings/admin_layout.html:19 +#: bookwyrm/templates/settings/user_admin.html:3 +#: bookwyrm/templates/settings/user_admin.html:10 +msgid "Users" +msgstr "" + +#: bookwyrm/templates/settings/admin_layout.html:32 +#: bookwyrm/templates/settings/federation.html:3 +#: bookwyrm/templates/settings/federation.html:5 msgid "Federated Servers" msgstr "" -#: bookwyrm/templates/settings/admin_layout.html:33 +#: bookwyrm/templates/settings/admin_layout.html:37 msgid "Instance Settings" msgstr "" -#: bookwyrm/templates/settings/admin_layout.html:37 +#: bookwyrm/templates/settings/admin_layout.html:41 #: bookwyrm/templates/settings/site.html:4 #: bookwyrm/templates/settings/site.html:6 msgid "Site Settings" msgstr "" -#: bookwyrm/templates/settings/admin_layout.html:40 +#: bookwyrm/templates/settings/admin_layout.html:44 #: bookwyrm/templates/settings/site.html:13 msgid "Instance Info" msgstr "" -#: bookwyrm/templates/settings/admin_layout.html:41 +#: bookwyrm/templates/settings/admin_layout.html:45 #: bookwyrm/templates/settings/site.html:39 msgid "Images" msgstr "" -#: bookwyrm/templates/settings/admin_layout.html:42 +#: bookwyrm/templates/settings/admin_layout.html:46 #: bookwyrm/templates/settings/site.html:59 msgid "Footer Content" msgstr "" -#: bookwyrm/templates/settings/admin_layout.html:43 +#: bookwyrm/templates/settings/admin_layout.html:47 #: bookwyrm/templates/settings/site.html:77 msgid "Registration" msgstr "" -#: bookwyrm/templates/settings/federation.html:10 +#: bookwyrm/templates/settings/federated_server.html:7 +msgid "Back to server list" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:12 +msgid "Details" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:15 +msgid "Software:" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:19 +msgid "Version:" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:23 +msgid "Status:" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:30 +#: bookwyrm/templates/user/user_layout.html:50 +msgid "Activity" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:33 +msgid "Users:" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:36 +#: bookwyrm/templates/settings/federated_server.html:43 +msgid "View all" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:40 +msgid "Reports:" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:47 +msgid "Followed by us:" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:53 +msgid "Followed by them:" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:59 +msgid "Blocked by us:" +msgstr "" + +#: bookwyrm/templates/settings/federation.html:13 msgid "Server name" msgstr "" -#: bookwyrm/templates/settings/federation.html:11 +#: bookwyrm/templates/settings/federation.html:17 +msgid "Date federated" +msgstr "" + +#: bookwyrm/templates/settings/federation.html:21 msgid "Software" msgstr "" -#: bookwyrm/templates/settings/federation.html:12 +#: bookwyrm/templates/settings/federation.html:24 #: bookwyrm/templates/settings/manage_invite_requests.html:33 +#: bookwyrm/templates/settings/user_admin.html:32 msgid "Status" msgstr "" @@ -1762,6 +1828,39 @@ msgstr "" msgid "Registration closed text:" msgstr "" +#: bookwyrm/templates/settings/user_admin.html:7 +#, python-format +msgid "Users: %(server_name)s" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:20 +msgid "Username" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:24 +msgid "Date Added" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:28 +msgid "Last Active" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:36 +msgid "Remote server" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:45 +msgid "Active" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:45 +msgid "Inactive" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:50 +msgid "Not set" +msgstr "" + #: bookwyrm/templates/snippets/block_button.html:5 msgid "Block" msgstr "" @@ -1816,7 +1915,7 @@ msgid "Review:" msgstr "" #: bookwyrm/templates/snippets/create_status_form.html:29 -#: bookwyrm/templates/snippets/shelf.html:17 +#: bookwyrm/templates/user/shelf.html:78 msgid "Rating" msgstr "" @@ -1890,6 +1989,22 @@ msgstr "" msgid "Un-like status" msgstr "" +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:7 +msgid "Show filters" +msgstr "" + +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:9 +msgid "Hide filters" +msgstr "" + +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:19 +msgid "Apply filters" +msgstr "" + +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:23 +msgid "Clear filters" +msgstr "" + #: bookwyrm/templates/snippets/follow_button.html:12 msgid "Follow" msgstr "" @@ -2020,32 +2135,32 @@ msgstr "" msgid "Rate" msgstr "" -#: bookwyrm/templates/snippets/readthrough.html:7 +#: bookwyrm/templates/snippets/readthrough.html:8 msgid "Progress Updates:" msgstr "" -#: bookwyrm/templates/snippets/readthrough.html:11 +#: bookwyrm/templates/snippets/readthrough.html:12 msgid "finished" msgstr "" -#: bookwyrm/templates/snippets/readthrough.html:14 +#: bookwyrm/templates/snippets/readthrough.html:15 msgid "Show all updates" msgstr "" -#: bookwyrm/templates/snippets/readthrough.html:30 +#: bookwyrm/templates/snippets/readthrough.html:31 msgid "Delete this progress update" msgstr "" -#: bookwyrm/templates/snippets/readthrough.html:40 +#: bookwyrm/templates/snippets/readthrough.html:41 msgid "started" msgstr "" -#: bookwyrm/templates/snippets/readthrough.html:46 -#: bookwyrm/templates/snippets/readthrough.html:60 +#: bookwyrm/templates/snippets/readthrough.html:47 +#: bookwyrm/templates/snippets/readthrough.html:61 msgid "Edit read dates" msgstr "" -#: bookwyrm/templates/snippets/readthrough.html:50 +#: bookwyrm/templates/snippets/readthrough.html:51 msgid "Delete these read dates" msgstr "" @@ -2105,44 +2220,8 @@ msgstr "" msgid "Import book" msgstr "" -#: bookwyrm/templates/snippets/shelf.html:12 -msgid "Published" -msgstr "" - -#: bookwyrm/templates/snippets/shelf.html:13 -msgid "Shelved" -msgstr "" - -#: bookwyrm/templates/snippets/shelf.html:14 -msgid "Started" -msgstr "" - -#: bookwyrm/templates/snippets/shelf.html:15 -msgid "Finished" -msgstr "" - -#: bookwyrm/templates/snippets/shelf.html:16 -msgid "External links" -msgstr "" - -#: bookwyrm/templates/snippets/shelf.html:44 -msgid "OpenLibrary" -msgstr "" - -#: bookwyrm/templates/snippets/shelf.html:61 -msgid "This shelf is empty." -msgstr "" - -#: bookwyrm/templates/snippets/shelf.html:67 -msgid "Delete shelf" -msgstr "" - #: bookwyrm/templates/snippets/shelf_selector.html:4 -msgid "Change shelf" -msgstr "" - -#: bookwyrm/templates/snippets/shelf_selector.html:27 -msgid "Unshelve" +msgid "Move book" msgstr "" #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:5 @@ -2151,7 +2230,7 @@ msgid "Finish \"%(book_title)s\"" msgstr "" #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:5 -#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:33 +#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:35 msgid "Update progress" msgstr "" @@ -2172,6 +2251,11 @@ msgstr "" msgid "Want to read" msgstr "" +#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:48 +#, python-format +msgid "Remove from %(name)s" +msgstr "" + #: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:5 #, python-format msgid "Start \"%(book_title)s\"" @@ -2240,6 +2324,14 @@ msgstr "" msgid "Switch to this edition" msgstr "" +#: bookwyrm/templates/snippets/table-sort-header.html:6 +msgid "Sorted asccending" +msgstr "" + +#: bookwyrm/templates/snippets/table-sort-header.html:10 +msgid "Sorted descending" +msgstr "" + #: bookwyrm/templates/snippets/tag.html:14 msgid "Remove tag" msgstr "" @@ -2249,6 +2341,11 @@ msgstr "" msgid "Books tagged \"%(tag.name)s\"" msgstr "" +#: bookwyrm/templates/user/books_header.html:5 +#, python-format +msgid "%(username)s's books" +msgstr "" + #: bookwyrm/templates/user/create_shelf_form.html:5 #: bookwyrm/templates/user/create_shelf_form.html:22 msgid "Create Shelf" @@ -2294,56 +2391,61 @@ msgstr "" msgid "Create list" msgstr "" -#: bookwyrm/templates/user/shelf.html:9 -msgid "Your Shelves" -msgstr "" - -#: bookwyrm/templates/user/shelf.html:11 -#, python-format -msgid "%(username)s: Shelves" -msgstr "" - -#: bookwyrm/templates/user/shelf.html:33 +#: bookwyrm/templates/user/shelf.html:34 msgid "Create shelf" msgstr "" -#: bookwyrm/templates/user/shelf.html:54 +#: bookwyrm/templates/user/shelf.html:55 msgid "Edit shelf" msgstr "" +#: bookwyrm/templates/user/shelf.html:75 +msgid "Shelved" +msgstr "" + +#: bookwyrm/templates/user/shelf.html:76 +msgid "Started" +msgstr "" + +#: bookwyrm/templates/user/shelf.html:77 +msgid "Finished" +msgstr "" + +#: bookwyrm/templates/user/shelf.html:121 +msgid "This shelf is empty." +msgstr "" + +#: bookwyrm/templates/user/shelf.html:127 +msgid "Delete shelf" +msgstr "" + #: bookwyrm/templates/user/user.html:15 msgid "Edit profile" msgstr "" -#: bookwyrm/templates/user/user.html:26 -#: bookwyrm/templates/user/user_layout.html:68 -msgid "Shelves" -msgstr "" - -#: bookwyrm/templates/user/user.html:31 +#: bookwyrm/templates/user/user.html:33 #, python-format -msgid "See all %(size)s" +msgid "View all %(size)s" msgstr "" -#: bookwyrm/templates/user/user.html:44 -#, python-format -msgid "See all %(shelf_count)s shelves" +#: bookwyrm/templates/user/user.html:46 +msgid "View all books" msgstr "" -#: bookwyrm/templates/user/user.html:56 +#: bookwyrm/templates/user/user.html:58 #, python-format msgid "Set a reading goal for %(year)s" msgstr "" -#: bookwyrm/templates/user/user.html:62 +#: bookwyrm/templates/user/user.html:64 msgid "User Activity" msgstr "" -#: bookwyrm/templates/user/user.html:65 +#: bookwyrm/templates/user/user.html:67 msgid "RSS feed" msgstr "" -#: bookwyrm/templates/user/user.html:76 +#: bookwyrm/templates/user/user.html:78 msgid "No activities yet!" msgstr "" @@ -2351,14 +2453,14 @@ msgstr "" msgid "Follow Requests" msgstr "" -#: bookwyrm/templates/user/user_layout.html:50 -msgid "Activity" -msgstr "" - #: bookwyrm/templates/user/user_layout.html:56 msgid "Reading Goal" msgstr "" +#: bookwyrm/templates/user/user_layout.html:68 +msgid "Books" +msgstr "" + #: bookwyrm/templates/user/user_preview.html:13 #, python-format msgid "Joined %(date)s" diff --git a/locale/es/LC_MESSAGES/django.mo b/locale/es/LC_MESSAGES/django.mo index a0e975bfd6c5cf6cfbb0e2831935875c92417082..d578461948d43624e25f7e9966839a1d4a159e9c 100644 GIT binary patch delta 8334 zcmYk=3w+P@9>?+TZssz(m~HO6urX|5?zd5HbIUOIJIiGlGr9ZAt&;nWNQg*sO@Ap- zBqX=ODprUobfc5SQS7|l|KC>+zsLD_es90;_4oVzce;GuYt?Bl*X2^aD-72(FJmg; z-4ez;Bz-DErN$hJG-d*Riu15ulre$0AFJaD48sB}k0qjwsfATB2z#Nr8i5sY7Iw#V z7-x*j{Anx7#Tdi?Ohf*m$n-^ZJOj&O4%WpTs1C1T6?}koFtV~Snb_5O7{`*YL!)lE z0jFXCw!m?*#>6we*-gTag6}XCuVM%mU=j3Zn*JDwMKK(GFcOPlWzw&d#02aZe7=SA=knznXTi`}b;E*aX7qt`ls1^EEbvkT_MaZ|nQrOm(_d#FsL#)G* z(=?+|3!IE4ai*ERDNR{T+#C|JC3u1$wBiU>UrJdYXfI#MQ7S#$hAW4h+JEI1V-NJGOi` zs=rTd{!7#Xu3$0z#adu3bdk^i#cCK+9K%uhXw*Q7w!Ar(BcFmAco^y#S&N#$cIzJN z0b73ri&B3cHG%I@JNOgoS#jMb5k=x5s>4b(of{HRXILNg8n#7kVSm)bhM_vlM%_2l z<`>!gO4NOuQ4>6Xy8j4jLMM@j-DPrZ#ZA->+(vET160RF6P)it1Zu{OQ5_^(+o7JF zRMY@HtphNCd^(oIEG&VuP&=>?gY^EdBcTt>PE^OoJrz6?s1;nrl6Vg_A-_bYgVLz_ zIMfQ8qT01W?NC?i5DX-rg&Oz;)I@U7SMUEDBm!|0Y9(&ecVM5*pGK|hJZhjkTYe4I z(M_Abi+Z{rpjICAl=Bc*z(Dd1t!+^Y?SU>O29nT?BTzTaur5GNXeDZ8Yf(qA4YiVY zQLpb|)Bs;&CA^Momhs_-K>6CJXQd13zCNh8YjiF4UkzTe4cP>%gQyjsvh}&B z313DQX>On<7+%|%SOwJU8;!x(7`1a9P!s5i>c1a`;HcW{zqWch1!}kywZfIC39PZc zgW93Js0o}ztvDBhFdt*_Pt=6V^1YVPsP^#~f^|?6X@wfEvx|f}NJFh89d$znY9iUF zhEr^Q7HVSiP%Bw(^XpL)+F|ntP!IJNsCN0Ne*QqcW%p6zxqOnG8;e=XpdPBKs2h^d z^V->bGHReUsH00mtt=Zg;C$2o>oF9!q1qor?Z_F_0=_}UahZG)V<@@Ou%{A9^KYQ*ou5g1LsFgLmkNl)S2g_7H|t)n&BN1 z8t6W12fX-&(S$-!6B z{i-qhuMWPWKwI+%Y9jZr6Bc4;>_jihuR-1%^D}y5#iq_mqfi4Rq9)wLx)7fwe+hXG zO+qtgM~9%=XShgoCo$Q28oQB?Y3}^aKaYBvccCVD79jI?Ic;{s&wCll8W( zzmH{USBQhKY_fB|YbptKI2#*!8S@HGAb+HVF>|nCOXpcQfO>6CqG!vo68Vx(8^fGU zP1Hn3VHuo(QTQtMzz?t@20mjc+KL755?!wz-VDF^oq%7Lzau zdt(+Z!qI42JAeO=LG9ord;x2>;d_s6tb)O9ot>Yh zpN@Jf=AvG!9MnU%1+}#wqCQaPkXPU2qwX);&UxA^qwa5y+Ns_c!T4r03Eems>);Ax z(dIb%V?c`Yu$0C~^3kaBmZ*uQpf7eoO)L$I;wTKo3FwC}qIP%@F2&90(o7ogHHpRc zsQfro2XjyzEJRIkJ!-(+s0n;%>kpv@&c)(*3ya}h497>P*D$Pu^LKwLs{d&n*niDz zE(Q7#S%4*Q4eA531vT(qTmHE%KabkVtEdUyLACSk=p0EHYJgbO!y=+&to)RMNQ0ym!EHp2}5=KEULp^sIwo5n$R57gqC9fZpISmw)ult zlKd%D`#hH|xP>~Kd$uCDv-2lXBx>L$sJCPyYU@{_c5Xd-20|U38#TdWw){&}zu%*d zqyW`VA!-4x7(P#`NJ7oL8S1@GMV<9HjKE2#vt5EgxEgieR;++~usohcP2e}{U)Dm@ zP8IFqEFcD%xXV;0p@ADH*7<7yx-=vE3pJs+7=|lQ<814}{_6%e1v=Z0ZG#IKPX2e)!{^u2+0sDN zPQ)R98kl5MM^jM~o{gS|6?HVrQT^|-<@-?m9!7n#FS=~SWmLl(sIB?KHYh|r3%YU#)jh9sGm3!Kb(L zvs@lE;4`QdrlPiXENW#g)PS#|?%!hbyHH2;DGtV5tf}|EejjIrLs8Gd1k_Atqt13A zYQUYSGd+ND_yy|IdI#0vP*Dr$nYun~5$RpdUG3vg6 zVNUoQ4?Cd!bXfE$T)TlYWG3WPV&fO4H-OF|!@ ziR5(^!{=;T`9Y*tV^3S=g~N#2l)XySB>f)p88Jz=O%WWUG{2CZ9|E0SB%v#siv6f_ zdzol%Zz_%5iRF~FBZ`vN+cAdF+wkv|6A~Hfx@NPj?A^-pkeQK0xGighdWt?GE)ste z-3VPxs4H-iW)kVvqzB;BSd9oHt?L%CiFlp(jnFmLi-V1&!C5Eo`F8~QEJ9xtT|-D` z6aOVW3ZJ|hlh=EqD}yv&HqUj4e}5-_AmRwU%yo$jVifTUp)b>!f1hbGPhQ`V8BT{~ zi94j15<#SU5Rb2;Bx({b67_81H(1`bxj_0E(w(r4t-pz#Y<;52^iKIu;BO0i;bzLN ztCDMrgZbL}n3A;HOGXby4Dl(khWMD!HQm8fqTO?(_YiN{vijC{QRDx6EhN>Oh$glY zFA*aMeFA)mB1%7Tog#hT=Ji$5C#?&yjYuNq5q#^-T%r_lkXS|NszrY@H2xc8>XPY5 z=)WxJ8j2A%t-L-(x@z09s-*Qf)V15e+(U2b-8kWidtbBlVmxt!_=1S^v|;^|$>{Q< zpccjxWmLfRlY{5qKlt}F(Ub@yju9J(XSuH#kx1wY#1xyZPQEqiE<_t*GO^3kiVr(! z&;AGCB4V@(xZkqJvZrjQlm`5BY##2`g`{6j$HQT}b2cw8&d%udGCfy8kiEoKP zgsu;XVCt&awqKIZCJt!-r%||;j1RGnI6@>7y3RRx{w+?sa8lnBC5T@=rN(@YQ>hEa zOT<1RfzY+U!StrSJyD$eJ47H+nwY8m520ctp{oV4p3v3L!KB(cWd{>K6E!G%qynz1 z#A2eAGF+)dS)v_nI$#R%KGBr?1N?w^o^%d|Jw;+730-wawJG|TQulh!{(AS^AJyOF;~w64m3R3enVA_A zOz+HzSy5T(gNKbAJ3MOe(Cm@VkIbIpPHr~5sQcsADgN&8PU&9mS32+XcDGMm;qU&X x*Ho{Z%sva<0ez#q+*$o9dAVQfAMfk_X7EFAztJOyWM$^u8CK36k^Z^w{{i&vk!=6~ delta 9139 zcmZYD30PHi{>SliSp)?^K~ZtLng|GPxB+Up;ev>YhCwb|@ggXKazRtlYo(T3Rw|XI zj?Gw3o7&YTZCXs7)X_H0v9WEeCYNa}otnuS`@cWu{P6Mr%z66y`t93UfSx$%`s|d; z|5;4PMvH5e%d*;Fd8B2v4YI61wp6KQ_2^((KJ1ChaR)|WN=M7;f|=MHz1SLSu?Iem z(RdVF;8|>o-{C}zy3MjWTbAD{CZUQ|sDXAOZLBwtxvamL^59OE)rx#8?2Wxp1D7KI zSxfmR4R>HAo;8k4u&fI5ui^wuWY$Gki-VZo`jJF31^p5&D-=B#i!-sQ8e#)ni($9{ z!|^E$#vRxYe~;R6z~m31PU<9v;V0M=ze0@{mt{Rw06q$&RbL&#q+ev1+0uc9`10~=!qyY{PL42dS#0#{%XYUjI9_w*oY zVV|NVyoB26_om#%gP{5-)C8?j?K+^^C85SoM=gAmaeNofUlSEjz*yF7)J`^{2HJ@l z=q1#O_n~%j2sPn*sQ#ar@_(B0>!^vsQk?o|RKE_!uBh=cQaFE2FoJ?+n2Q>?*ff}n zddQZbUWhu>iR?nP`!jaNv#6c^is=}g>MUR+s(c)3!XlHezzFh7{3IHZSY_N`+=`Ku z@5Dy9&*a}i4S3R&e~R(sFQ6t4?&>^C15hV0+Bm^@x2Z2S`m0EU)9^mD7lJy{4X9`1 z8Fb_GsE70fs{J|CJ^c#x+Wvq#$%t;w0%K6)bwu?`HTnJ~pM~`ETN6oWrL#~2RG}tV zh{Kcb$cUs2^{^)51H~uP~+5@{L`q1d?!ZY>*&|he4K<9@OR@6 zsGWxNbZm_37>DYZVoXOZC=0c-(WnI%U{jomdY$K@#(x0Y;bYhp_w?laRq#0ldRT5? zQw--br-j9%>ieSV??g>B4YiOts2wjd_4lI|z6QJDM$`fiqZW1?^%}p2I++V;oPQG% z|DwQtIC?n~MWQB(LoKKi#$sPoyKK~e<53IAHx{E#YA$La%TYUj73>k|L;5%iia_O?pxU=U-O|p;M*UV6 z2~9W&+v7acEqN5<@M+Y<`!N;|qXs&QI>K*IJG_Egz%Mul!}~hp%}1S3opBqg-%eZZ z|85eR@Kw~2A2XgpeONw4b+Gz5PjfixWLl#JPR2w`!xSt;wO@f+=)-8=d(;MBMD;(3 zVa#uxGY!5(9o03|z}Ha=3g*MEd}C~mai|GWQ3Lfxoxl)OyRoMHF4O`_Fb-=_C$t9D z?g_Mi{%J_zpJ1uTZzV!t1CVC8MYCVHm$a&NP zubBLGlMfr{l*gcMVOv!DUVaifxqRk-Z*W-D}?YE9#4BkL3u<2lDfEK8Qx={~n z5~{wtsqbUF-PDi3R@CQW5tgF*A43-8w>}_|?&3p&J}P2|@`C~&L_I7KJb`+x;!#I8 z9NXb!kiZt5MD$D)TXv{AP@0ervlD zxDV~4!3N|%z=n9%l%GeP$R*V4_Y>-&jN*yZ5w}Bq$kLIIpfw5`q8~fsN>u;ds1th= z{d%uIB%zL%Fb#i3PT%S_+F4K)>Y-VL?eRfV{w!*tyD$V_K`rbxFsQek!02fgMTt)4)0k4J@8i$%V5p`nSQ0@An7LbjNa2o0a zW})tRHR^R;jmh}V7|vf4eNTZ_cpdfQSMOc~#9(7=hnhImln*rJqcDo{NvH*spcc9a z)qa)9Z${1YJnA9dYwC~tO@njjrs6W*hLOCyN!SCmfCB7*Gf@j%iF|df&8UGtLQVJ? z>RGsgQP^;tv#^#JNj?>|u?&;K3#c z@0?&~v?szQlxLuBX*O!1Q%rpcGQQvPkL;2>(^cTo#Ek9v)+p`Mla z3C@6SV=}5d&E(UKgHa31Mm>ausFR+D@p}K4lF$M+qZ{|3c5ntY@fW84dt(G2Zaw7* zsGVhEJm#YoJ`ZDY32LIpP_ONFY=#F>^=Htpm0cpCBfX9~V%MEc-i^)3C!%&X0M&j7 zYT!JR_n7=VRJ*09qhEpQ|69}oUqyXyPN6n@`A+V?3PSUp9k<2iO|LJC;ZJM&c7>(BNXU|$u-o(J@0aM zcn2zI7TUyE_%A4NUYJFx}s!IpRo`DMpCj~b_Kp|kJ=RQpuaE%Nsvp$YO# z#blHBq9&+BecM-<`qid>1L|b9n)(+|&&D3qMh+T3L@n$~lm8jjFY<2tq4ZnvBy^OS zsH4rorkI0T*i_U2rN*VE{t491o-@9T>VE*${vame$EXQilbsz$qnmsx+JFC#A)yY1 zsFhctcK!frA)8PGzhLqK)V(}{9z2Vwm^sBcftjecV*zRdD^N%N2)I;_Is(;Wlr(FbU zL+#M7BTOKniMyiiVFv01MxpA9P&+C`4Y&X`;4;*Stwo*GR@6!EMzuSN`W}3QI*D`G z6~D$I7+b{qKY~Plk!6jfa8>p+AX4Hi?Cpi-$1{V@sU>yFDCr@3T=mEWq*T#nJc& zRKMVvPQN_V1j|tiT!)iz6XxMn)JbHO+J9&Gt<5BwQ*at}PcNY+x`rCyR}95QWzN7& zP!C-^s(lJJz}}|3Kh7tgW9ko~PWA-4P*(x){BA+|48ikfjbhvrsM~7KpF@EzKk8Q8LG&lB z`}+m4(A1B^h2(X0CB4FwJ0(2bB$9|0l;2_MUL^k{q3Z$48W6oq-Jdo88&pgn{zLR5 zo+EViqme!qr-|;w0P@8|Ptx_5kNm&LoHPaH*o$aLxvqB{taMwmWp3(e^P2o!8Oe*YEnU>8d|J9@?;W;9UvPj~eq%)C^oRy9G9_Z>sdOJ=dYKSo6 zXF^vHb@f-VW-1|Z61S6XM06+KCLcpgCS5{oCB2u> z)zHEIUBo2q-%(qd%=36VkxRb@dWt8KSxTlAv61xeh{nWh@=v3#Zyc=O8xyeBr0dBy zi7xc>5X;HGLfl3SB}$3ciSLOi#KXigy+w;?oJ7PE2}Ceuy0VBK#0p{n5kXx9>MA8& zfBlWbSM?dJHFe!E-=wEf{s+^h6ZWM1A~r5Eg*_-(8F;<@;-J_XpSP;QGsm0k%XCjD z^_I`~`qmeAj1P?On9?Ak)Kf9j>-JGx5?GYf+||fa?Q#1mYpRO9fwjr~f}*nSsaE%L zcX?UGY+v9|N^NLZPTBM-Pu0ReM$ZJ-`iW_?!i&8X)$W`!-}?P&7qh%>PkFgJA;}(e zT5)AfMRl^zo`RXJ3ErFaWs6wT{}eEhuiCeMTd(V(_F4mVy`K#ZtI%lky@CGen}P=Yia<%t<&&h=K*pU{Z>T37hEn(5_b#hw}K-a+t#X%F`u^vr%)CBJ z4LC>Vob1*fyR}_lpOeQIm{ZW+6>iq=3#=~e5)zx|tuEsRvD)&Ro7g+`M$pikBTuaH n(SLeXrO$GbEJ{gpUNby1wAFHNadl0V$8J*JHSoUYXvqHocO*T9 diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po index 729048eb..7e53903e 100644 --- a/locale/es/LC_MESSAGES/django.po +++ b/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-03-28 22:42+0000\n" +"POT-Creation-Date: 2021-03-31 09:22-0700\n" "PO-Revision-Date: 2021-03-19 11:49+0800\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -49,44 +49,64 @@ msgstr "%(count)d usos" msgid "Unlimited" msgstr "Sin límite" -#: bookwyrm/models/fields.py:25 +#: bookwyrm/models/fields.py:24 #, python-format msgid "%(value)s is not a valid remote_id" msgstr "%(value)s no es un remote_id válido" -#: bookwyrm/models/fields.py:34 bookwyrm/models/fields.py:47 +#: bookwyrm/models/fields.py:33 bookwyrm/models/fields.py:42 #, python-format msgid "%(value)s is not a valid username" msgstr "%(value)s no es un usuario válido" -#: bookwyrm/models/fields.py:170 bookwyrm/templates/layout.html:157 +#: bookwyrm/models/fields.py:165 bookwyrm/templates/layout.html:157 msgid "username" msgstr "nombre de usuario" -#: bookwyrm/models/fields.py:175 +#: bookwyrm/models/fields.py:170 msgid "A user with that username already exists." msgstr "Ya existe un usuario con ese nombre." -#: bookwyrm/settings.py:148 +#: bookwyrm/settings.py:150 msgid "English" msgstr "Inglés" -#: bookwyrm/settings.py:149 +#: bookwyrm/settings.py:151 msgid "German" msgstr "Aléman" -#: bookwyrm/settings.py:150 +#: bookwyrm/settings.py:152 msgid "Spanish" msgstr "Español" -#: bookwyrm/settings.py:151 +#: bookwyrm/settings.py:153 msgid "French" msgstr "Francés" -#: bookwyrm/settings.py:152 +#: bookwyrm/settings.py:154 msgid "Simplified Chinese" msgstr "Chino simplificado" +#: bookwyrm/templates/404.html:4 bookwyrm/templates/404.html:8 +msgid "Not Found" +msgstr "No encontrado" + +#: bookwyrm/templates/404.html:9 +msgid "The page you requested doesn't seem to exist!" +msgstr "¡Parece que la página solicitada no existe!" + +#: bookwyrm/templates/500.html:4 +msgid "Oops!" +msgstr "¡Úps!" + +#: bookwyrm/templates/500.html:8 +msgid "Server Error" +msgstr "Error de servidor" + +#: bookwyrm/templates/500.html:9 +msgid "Something went wrong! Sorry about that." +msgstr "¡Algo salió mal! Disculpa." + #: bookwyrm/templates/author.html:16 bookwyrm/templates/author.html:17 msgid "Edit Author" msgstr "Editar Autor/Autora" @@ -110,92 +130,66 @@ msgstr "por" msgid "Edit Book" msgstr "Editar Libro" -#: bookwyrm/templates/book/book.html:45 +#: bookwyrm/templates/book/book.html:49 #: bookwyrm/templates/book/cover_modal.html:5 msgid "Add cover" msgstr "Agregar portada" -#: bookwyrm/templates/book/book.html:55 +#: bookwyrm/templates/book/book.html:59 msgid "ISBN:" msgstr "ISBN:" -#: bookwyrm/templates/book/book.html:62 +#: bookwyrm/templates/book/book.html:66 #: bookwyrm/templates/book/edit_book.html:211 msgid "OCLC Number:" msgstr "Número OCLC:" -#: bookwyrm/templates/book/book.html:69 +#: bookwyrm/templates/book/book.html:73 #: bookwyrm/templates/book/edit_book.html:215 msgid "ASIN:" msgstr "ASIN:" -#: bookwyrm/templates/book/book.html:79 -#, python-format -msgid "%(format)s, %(pages)s pages" -msgstr "%(format)s, %(pages)s páginas" - -#: bookwyrm/templates/book/book.html:81 -#, python-format -msgid "%(pages)s pages" -msgstr "%(pages)s páginas" - -#: bookwyrm/templates/book/book.html:86 -#, python-format -msgid "Published %(date)s by %(publisher)s." -msgstr "" - -#: bookwyrm/templates/book/book.html:88 -#, fuzzy, python-format -#| msgid "Published date:" -msgid "Published %(date)s" -msgstr "Fecha de publicación:" - -#: bookwyrm/templates/book/book.html:90 -#, python-format -msgid "Published by %(publisher)s." -msgstr "" - -#: bookwyrm/templates/book/book.html:95 +#: bookwyrm/templates/book/book.html:82 msgid "View on OpenLibrary" msgstr "Ver en OpenLibrary" -#: bookwyrm/templates/book/book.html:104 +#: bookwyrm/templates/book/book.html:91 #, python-format msgid "(%(review_count)s review)" msgid_plural "(%(review_count)s reviews)" msgstr[0] "(%(review_count)s reseña)" msgstr[1] "(%(review_count)s reseñas)" -#: bookwyrm/templates/book/book.html:110 +#: bookwyrm/templates/book/book.html:97 msgid "Add Description" msgstr "Agregar descripción" -#: bookwyrm/templates/book/book.html:117 +#: bookwyrm/templates/book/book.html:104 #: bookwyrm/templates/book/edit_book.html:101 #: bookwyrm/templates/lists/form.html:12 msgid "Description:" msgstr "Descripción:" -#: bookwyrm/templates/book/book.html:121 +#: bookwyrm/templates/book/book.html:108 #: bookwyrm/templates/book/edit_book.html:225 #: bookwyrm/templates/edit_author.html:78 bookwyrm/templates/lists/form.html:42 -#: bookwyrm/templates/preferences/edit_user.html:68 +#: bookwyrm/templates/preferences/edit_user.html:70 #: bookwyrm/templates/settings/site.html:93 -#: bookwyrm/templates/snippets/readthrough.html:64 +#: bookwyrm/templates/snippets/readthrough.html:65 #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:42 #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:42 #: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:34 msgid "Save" msgstr "Guardar" -#: bookwyrm/templates/book/book.html:122 bookwyrm/templates/book/book.html:171 +#: bookwyrm/templates/book/book.html:109 bookwyrm/templates/book/book.html:158 #: bookwyrm/templates/book/cover_modal.html:32 #: bookwyrm/templates/book/edit_book.html:226 #: bookwyrm/templates/edit_author.html:79 #: bookwyrm/templates/moderation/report_modal.html:32 #: bookwyrm/templates/snippets/delete_readthrough_modal.html:17 #: bookwyrm/templates/snippets/goal_form.html:32 -#: bookwyrm/templates/snippets/readthrough.html:65 +#: bookwyrm/templates/snippets/readthrough.html:66 #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:43 #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:43 #: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:35 @@ -203,74 +197,74 @@ msgstr "Guardar" msgid "Cancel" msgstr "Cancelar" -#: bookwyrm/templates/book/book.html:131 +#: bookwyrm/templates/book/book.html:118 #, python-format msgid "%(count)s editions" msgstr "%(count)s ediciones" -#: bookwyrm/templates/book/book.html:139 +#: bookwyrm/templates/book/book.html:126 #, python-format msgid "This edition is on your %(shelf_name)s shelf." msgstr "Esta edición está en tu %(shelf_name)s estante." -#: bookwyrm/templates/book/book.html:145 +#: bookwyrm/templates/book/book.html:132 #, python-format msgid "A different edition of this book is on your %(shelf_name)s shelf." msgstr "Una edición diferente de este libro está en tu %(shelf_name)s estante." -#: bookwyrm/templates/book/book.html:154 +#: bookwyrm/templates/book/book.html:141 msgid "Your reading activity" msgstr "Tu actividad de lectura" -#: bookwyrm/templates/book/book.html:156 +#: bookwyrm/templates/book/book.html:143 msgid "Add read dates" msgstr "Agregar fechas de lectura" -#: bookwyrm/templates/book/book.html:161 +#: bookwyrm/templates/book/book.html:148 msgid "You don't have any reading activity for this book." msgstr "No tienes ninguna actividad de lectura para este libro." -#: bookwyrm/templates/book/book.html:168 +#: bookwyrm/templates/book/book.html:155 msgid "Create" msgstr "Crear" -#: bookwyrm/templates/book/book.html:190 +#: bookwyrm/templates/book/book.html:177 msgid "Tags" msgstr "Etiquetas" -#: bookwyrm/templates/book/book.html:194 +#: bookwyrm/templates/book/book.html:181 #: bookwyrm/templates/snippets/tag.html:18 msgid "Add tag" msgstr "Agregar etiqueta" -#: bookwyrm/templates/book/book.html:211 +#: bookwyrm/templates/book/book.html:198 msgid "Subjects" msgstr "Sujetos" -#: bookwyrm/templates/book/book.html:222 +#: bookwyrm/templates/book/book.html:209 msgid "Places" msgstr "Lugares" -#: bookwyrm/templates/book/book.html:233 bookwyrm/templates/layout.html:64 -#: bookwyrm/templates/lists/lists.html:4 bookwyrm/templates/lists/lists.html:9 +#: bookwyrm/templates/book/book.html:220 bookwyrm/templates/layout.html:64 +#: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12 #: bookwyrm/templates/search_results.html:91 #: bookwyrm/templates/user/user_layout.html:62 msgid "Lists" msgstr "Listas" -#: bookwyrm/templates/book/book.html:244 +#: bookwyrm/templates/book/book.html:231 #, fuzzy #| msgid "Go to list" msgid "Add to list" msgstr "Irse a lista" -#: bookwyrm/templates/book/book.html:254 +#: bookwyrm/templates/book/book.html:241 #: bookwyrm/templates/book/cover_modal.html:31 #: bookwyrm/templates/lists/list.html:90 msgid "Add" msgstr "Agregar" -#: bookwyrm/templates/book/book.html:282 +#: bookwyrm/templates/book/book.html:269 msgid "rated it" msgstr "lo calificó con" @@ -419,7 +413,7 @@ msgid "John Doe, Jane Smith" msgstr "" #: bookwyrm/templates/book/edit_book.html:155 -#: bookwyrm/templates/snippets/shelf.html:9 +#: bookwyrm/templates/user/shelf.html:72 msgid "Cover" msgstr "Portada:" @@ -428,6 +422,7 @@ msgid "Physical Properties" msgstr "Propiedades físicas:" #: bookwyrm/templates/book/edit_book.html:183 +#: bookwyrm/templates/book/format_filter.html:5 msgid "Format:" msgstr "Formato:" @@ -452,98 +447,102 @@ msgstr "ISBN 10:" msgid "Openlibrary key:" msgstr "Clave OpenLibrary:" +#: bookwyrm/templates/book/editions.html:5 +#, python-format +msgid "Editions of %(book_title)s" +msgstr "Ediciones de %(book_title)s" + +#: bookwyrm/templates/book/editions.html:9 +#, python-format +msgid "Editions of \"%(work_title)s\"" +msgstr "Ediciones de \"%(work_title)s\"" + +#: bookwyrm/templates/book/format_filter.html:8 +#: bookwyrm/templates/book/language_filter.html:8 +msgid "Any" +msgstr "" + +#: bookwyrm/templates/book/language_filter.html:5 +msgid "Language:" +msgstr "" + +#: bookwyrm/templates/book/publisher_info.html:6 +#, python-format +msgid "%(format)s, %(pages)s pages" +msgstr "%(format)s, %(pages)s páginas" + +#: bookwyrm/templates/book/publisher_info.html:8 +#, python-format +msgid "%(pages)s pages" +msgstr "%(pages)s páginas" + +#: bookwyrm/templates/book/publisher_info.html:13 +#, fuzzy, python-format +#| msgid "%(pages)s pages" +msgid "%(languages)s language" +msgstr "%(pages)s páginas" + +#: bookwyrm/templates/book/publisher_info.html:18 +#, python-format +msgid "Published %(date)s by %(publisher)s." +msgstr "" + +#: bookwyrm/templates/book/publisher_info.html:20 +#, fuzzy, python-format +#| msgid "Published date:" +msgid "Published %(date)s" +msgstr "Fecha de publicación:" + +#: bookwyrm/templates/book/publisher_info.html:22 +#, python-format +msgid "Published by %(publisher)s." +msgstr "" + #: bookwyrm/templates/components/inline_form.html:8 #: bookwyrm/templates/feed/feed_layout.html:57 msgid "Close" msgstr "Cerrar" -#: bookwyrm/templates/directory.html:6 bookwyrm/templates/directory.html:11 -#: bookwyrm/templates/layout.html:97 -msgid "Directory" -msgstr "" - -#: bookwyrm/templates/directory.html:19 -msgid "Make your profile discoverable to other BookWyrm users." -msgstr "" - -#: bookwyrm/templates/directory.html:26 -#, fuzzy, python-format -#| msgid "You can set or change your reading goal any time from your profile page" -msgid "You can opt-out at any time in your profile settings." -msgstr "Puedes establecer o cambiar tu meta de lectura en cualquier momento que desees desde tu perfil" - -#: bookwyrm/templates/directory.html:31 -#: bookwyrm/templates/snippets/goal_card.html:22 -msgid "Dismiss message" -msgstr "Desechar mensaje" - -#: bookwyrm/templates/directory.html:44 -#, fuzzy -#| msgid "Show less" -msgid "Show filters" -msgstr "Mostrar menos" - -#: bookwyrm/templates/directory.html:46 -msgid "Hide filters" -msgstr "" - -#: bookwyrm/templates/directory.html:55 -#, fuzzy -#| msgid "User Activity" -msgid "User type" -msgstr "Actividad de usuario" - -#: bookwyrm/templates/directory.html:58 -msgid "BookWyrm users" -msgstr "" - -#: bookwyrm/templates/directory.html:62 -msgid "All known users" -msgstr "" - -#: bookwyrm/templates/directory.html:68 +#: bookwyrm/templates/directory/community_filter.html:5 #, fuzzy #| msgid "Comment" msgid "Community" msgstr "Comentario" -#: bookwyrm/templates/directory.html:71 +#: bookwyrm/templates/directory/community_filter.html:8 #, fuzzy #| msgid "Max uses" msgid "Local users" msgstr "Número máximo de usos" -#: bookwyrm/templates/directory.html:75 +#: bookwyrm/templates/directory/community_filter.html:12 #, fuzzy #| msgid "Federated" msgid "Federated community" msgstr "Federalizado" -#: bookwyrm/templates/directory.html:81 -msgid "Order by" +#: bookwyrm/templates/directory/directory.html:6 +#: bookwyrm/templates/directory/directory.html:11 +#: bookwyrm/templates/layout.html:97 +msgid "Directory" msgstr "" -#: bookwyrm/templates/directory.html:84 -#, fuzzy -#| msgid "Suggest" -msgid "Suggested" -msgstr "Sugerir" - -#: bookwyrm/templates/directory.html:85 -msgid "Recently active" +#: bookwyrm/templates/directory/directory.html:19 +msgid "Make your profile discoverable to other BookWyrm users." msgstr "" -#: bookwyrm/templates/directory.html:91 -msgid "Apply filters" -msgstr "" +#: bookwyrm/templates/directory/directory.html:26 +#, fuzzy, python-format +#| msgid "You can set or change your reading goal any time from your profile page" +msgid "You can opt-out at any time in your profile settings." +msgstr "Puedes establecer o cambiar tu meta de lectura en cualquier momento que desees desde tu perfil" -#: bookwyrm/templates/directory.html:94 -#, fuzzy -#| msgid "Clear search" -msgid "Clear filters" -msgstr "Borrar búsqueda" +#: bookwyrm/templates/directory/directory.html:31 +#: bookwyrm/templates/snippets/goal_card.html:22 +msgid "Dismiss message" +msgstr "Desechar mensaje" -#: bookwyrm/templates/directory.html:128 +#: bookwyrm/templates/directory/directory.html:71 #, fuzzy #| msgid "followed you" msgid "follower you follow" @@ -551,7 +550,7 @@ msgid_plural "followers you follow" msgstr[0] "te siguió" msgstr[1] "te siguió" -#: bookwyrm/templates/directory.html:135 +#: bookwyrm/templates/directory/directory.html:78 #, fuzzy #| msgid "Your shelves" msgid "book on your shelves" @@ -559,14 +558,42 @@ msgid_plural "books on your shelves" msgstr[0] "Tus estantes" msgstr[1] "Tus estantes" -#: bookwyrm/templates/directory.html:143 +#: bookwyrm/templates/directory/directory.html:86 msgid "posts" msgstr "" -#: bookwyrm/templates/directory.html:149 +#: bookwyrm/templates/directory/directory.html:92 msgid "last active" msgstr "" +#: bookwyrm/templates/directory/sort_filter.html:5 +msgid "Order by" +msgstr "" + +#: bookwyrm/templates/directory/sort_filter.html:8 +#, fuzzy +#| msgid "Suggest" +msgid "Suggested" +msgstr "Sugerir" + +#: bookwyrm/templates/directory/sort_filter.html:9 +msgid "Recently active" +msgstr "" + +#: bookwyrm/templates/directory/user_type_filter.html:5 +#, fuzzy +#| msgid "User Activity" +msgid "User type" +msgstr "Actividad de usuario" + +#: bookwyrm/templates/directory/user_type_filter.html:8 +msgid "BookWyrm users" +msgstr "" + +#: bookwyrm/templates/directory/user_type_filter.html:12 +msgid "All known users" +msgstr "" + #: bookwyrm/templates/discover/about.html:7 #, python-format msgid "About %(site_name)s" @@ -676,16 +703,6 @@ msgstr "Clave Librarything:" msgid "Goodreads key:" msgstr "Clave Goodreads:" -#: bookwyrm/templates/editions.html:5 -#, python-format -msgid "Editions of %(book_title)s" -msgstr "Ediciones de %(book_title)s" - -#: bookwyrm/templates/editions.html:9 -#, python-format -msgid "Editions of \"%(work_title)s\"" -msgstr "Ediciones de \"%(work_title)s\"" - #: bookwyrm/templates/email/html_layout.html:15 #: bookwyrm/templates/email/text_layout.html:2 msgid "Hi there," @@ -752,18 +769,6 @@ msgstr "" msgid "Reset your %(site_name)s password" msgstr "Sobre %(site_name)s" -#: bookwyrm/templates/error.html:4 -msgid "Oops!" -msgstr "¡Úps!" - -#: bookwyrm/templates/error.html:8 -msgid "Server Error" -msgstr "Error de servidor" - -#: bookwyrm/templates/error.html:9 -msgid "Something went wrong! Sorry about that." -msgstr "¡Algo salió mal! Disculpa." - #: bookwyrm/templates/feed/direct_messages.html:8 #, python-format msgid "Direct Messages with %(username)s" @@ -842,6 +847,8 @@ msgid "Updates" msgstr "Actualizaciones" #: bookwyrm/templates/feed/feed_layout.html:11 +#: bookwyrm/templates/layout.html:58 +#: bookwyrm/templates/user/books_header.html:3 msgid "Your books" msgstr "Tus libros" @@ -850,18 +857,18 @@ msgid "There are no books here right now! Try searching for a book to get starte msgstr "¡No hay ningún libro aqui ahorita! Busca a un libro para empezar" #: bookwyrm/templates/feed/feed_layout.html:23 -#: bookwyrm/templates/user/shelf.html:24 +#: bookwyrm/templates/user/shelf.html:25 msgid "To Read" msgstr "Para leer" #: bookwyrm/templates/feed/feed_layout.html:24 -#: bookwyrm/templates/user/shelf.html:24 +#: bookwyrm/templates/user/shelf.html:25 msgid "Currently Reading" msgstr "Leyendo actualmente" #: bookwyrm/templates/feed/feed_layout.html:25 #: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:11 -#: bookwyrm/templates/user/shelf.html:24 +#: bookwyrm/templates/user/shelf.html:25 msgid "Read" msgstr "Leer" @@ -906,27 +913,33 @@ msgstr "Los libros de %(username)s para %(year)s" msgid "Import Books" msgstr "Importar libros" -#: bookwyrm/templates/import.html:14 -msgid "Data source" +#: bookwyrm/templates/import.html:16 +#, fuzzy +#| msgid "Data source" +msgid "Data source:" msgstr "Fuente de datos" -#: bookwyrm/templates/import.html:32 +#: bookwyrm/templates/import.html:29 +msgid "Data file:" +msgstr "" + +#: bookwyrm/templates/import.html:37 msgid "Include reviews" msgstr "Incluir reseñas" -#: bookwyrm/templates/import.html:37 +#: bookwyrm/templates/import.html:42 msgid "Privacy setting for imported reviews:" msgstr "Configuración de privacidad para las reseñas importadas:" -#: bookwyrm/templates/import.html:41 +#: bookwyrm/templates/import.html:48 msgid "Import" msgstr "Importar" -#: bookwyrm/templates/import.html:46 +#: bookwyrm/templates/import.html:53 msgid "Recent Imports" msgstr "Importaciones recientes" -#: bookwyrm/templates/import.html:48 +#: bookwyrm/templates/import.html:55 msgid "No recent imports" msgstr "No hay ninguna importación reciente" @@ -983,12 +996,12 @@ msgstr "Libro" #: bookwyrm/templates/import_status.html:115 #: bookwyrm/templates/snippets/create_status_form.html:10 -#: bookwyrm/templates/snippets/shelf.html:10 +#: bookwyrm/templates/user/shelf.html:73 msgid "Title" msgstr "Título" #: bookwyrm/templates/import_status.html:118 -#: bookwyrm/templates/snippets/shelf.html:11 +#: bookwyrm/templates/user/shelf.html:74 msgid "Author" msgstr "Autor/Autora" @@ -1044,10 +1057,6 @@ msgstr "Buscar" msgid "Main navigation menu" msgstr "Menú de navigación central" -#: bookwyrm/templates/layout.html:58 -msgid "Your shelves" -msgstr "Tus estantes" - #: bookwyrm/templates/layout.html:61 msgid "Feed" msgstr "Actividad" @@ -1062,7 +1071,7 @@ msgid "Settings" msgstr "Configuración" #: bookwyrm/templates/layout.html:116 -#: bookwyrm/templates/settings/admin_layout.html:20 +#: bookwyrm/templates/settings/admin_layout.html:24 #: bookwyrm/templates/settings/manage_invite_requests.html:15 #: bookwyrm/templates/settings/manage_invites.html:3 #: bookwyrm/templates/settings/manage_invites.html:15 @@ -1124,7 +1133,7 @@ msgid "BookWyrm is open source software. You can contribute or report issues on msgstr "BookWyrm es software de código abierto. Puedes contribuir o reportar problemas en GitHub." #: bookwyrm/templates/lists/create_form.html:5 -#: bookwyrm/templates/lists/lists.html:17 +#: bookwyrm/templates/lists/lists.html:19 msgid "Create List" msgstr "Crear lista" @@ -1190,7 +1199,7 @@ msgid "Anyone can suggest books, subject to your approval" msgstr "Cualquier usuario puede sugerir libros, en cuanto lo hayas aprobado" #: bookwyrm/templates/lists/form.html:31 -#: bookwyrm/templates/moderation/reports.html:11 +#: bookwyrm/templates/moderation/reports.html:24 msgid "Open" msgstr "Abierto" @@ -1208,6 +1217,7 @@ msgid "Added by %(username)s" msgstr "Agregado por %(username)s" #: bookwyrm/templates/lists/list.html:41 +#: bookwyrm/templates/snippets/shelf_selector.html:28 msgid "Remove" msgstr "Quitar" @@ -1244,19 +1254,6 @@ msgstr "No se encontró ningún libro" msgid "Suggest" msgstr "Sugerir" -#: bookwyrm/templates/lists/lists.html:14 -msgid "Your lists" -msgstr "Tus listas" - -#: bookwyrm/templates/lists/lists.html:32 -#, python-format -msgid "See all %(size)s lists" -msgstr "Ver las %(size)s listas" - -#: bookwyrm/templates/lists/lists.html:40 -msgid "Recent Lists" -msgstr "Listas recientes" - #: bookwyrm/templates/login.html:4 msgid "Login" msgstr "Iniciar sesión" @@ -1370,27 +1367,37 @@ msgstr "" msgid "Resolve" msgstr "" -#: bookwyrm/templates/moderation/reports.html:4 -#: bookwyrm/templates/moderation/reports.html:5 -#: bookwyrm/templates/settings/admin_layout.html:24 +#: bookwyrm/templates/moderation/reports.html:6 +#, fuzzy, python-format +#| msgid "Lists: %(username)s" +msgid "Reports: %(server_name)s" +msgstr "Listas: %(username)s" + +#: bookwyrm/templates/moderation/reports.html:8 +#: bookwyrm/templates/moderation/reports.html:16 +#: bookwyrm/templates/settings/admin_layout.html:28 #, fuzzy #| msgid "Recent Imports" msgid "Reports" msgstr "Importaciones recientes" -#: bookwyrm/templates/moderation/reports.html:14 +#: bookwyrm/templates/moderation/reports.html:13 +#, fuzzy, python-format +#| msgid "Lists: %(username)s" +msgid "Reports: %(server_name)s" +msgstr "Listas: %(username)s" + +#: bookwyrm/templates/moderation/reports.html:27 #, fuzzy #| msgid "Shelved" msgid "Resolved" msgstr "Archivado" -#: bookwyrm/templates/notfound.html:4 bookwyrm/templates/notfound.html:8 -msgid "Not Found" -msgstr "No encontrado" - -#: bookwyrm/templates/notfound.html:9 -msgid "The page you requested doesn't seem to exist!" -msgstr "¡Parece que la página solicitada no existe!" +#: bookwyrm/templates/moderation/reports.html:34 +#, fuzzy +#| msgid "No books found" +msgid "No reports found." +msgstr "No se encontró ningún libro" #: bookwyrm/templates/notifications.html:14 msgid "Delete notifications" @@ -1622,51 +1629,131 @@ msgstr "Adminstración" msgid "Manage Users" msgstr "Administrar usuarios" -#: bookwyrm/templates/settings/admin_layout.html:28 -#: bookwyrm/templates/settings/federation.html:4 +#: bookwyrm/templates/settings/admin_layout.html:19 +#: bookwyrm/templates/settings/user_admin.html:3 +#: bookwyrm/templates/settings/user_admin.html:10 +msgid "Users" +msgstr "" + +#: bookwyrm/templates/settings/admin_layout.html:32 +#: bookwyrm/templates/settings/federation.html:3 +#: bookwyrm/templates/settings/federation.html:5 msgid "Federated Servers" msgstr "Servidores federalizados" -#: bookwyrm/templates/settings/admin_layout.html:33 +#: bookwyrm/templates/settings/admin_layout.html:37 msgid "Instance Settings" msgstr "Configuración de instancia" -#: bookwyrm/templates/settings/admin_layout.html:37 +#: bookwyrm/templates/settings/admin_layout.html:41 #: bookwyrm/templates/settings/site.html:4 #: bookwyrm/templates/settings/site.html:6 msgid "Site Settings" msgstr "Configuración de sitio" -#: bookwyrm/templates/settings/admin_layout.html:40 +#: bookwyrm/templates/settings/admin_layout.html:44 #: bookwyrm/templates/settings/site.html:13 msgid "Instance Info" msgstr "Información de instancia" -#: bookwyrm/templates/settings/admin_layout.html:41 +#: bookwyrm/templates/settings/admin_layout.html:45 #: bookwyrm/templates/settings/site.html:39 msgid "Images" msgstr "Imagenes" -#: bookwyrm/templates/settings/admin_layout.html:42 +#: bookwyrm/templates/settings/admin_layout.html:46 #: bookwyrm/templates/settings/site.html:59 msgid "Footer Content" msgstr "Contenido del pie de página" -#: bookwyrm/templates/settings/admin_layout.html:43 +#: bookwyrm/templates/settings/admin_layout.html:47 #: bookwyrm/templates/settings/site.html:77 msgid "Registration" msgstr "Registración" -#: bookwyrm/templates/settings/federation.html:10 +#: bookwyrm/templates/settings/federated_server.html:7 +msgid "Back to server list" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:12 +msgid "Details" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:15 +#, fuzzy +#| msgid "Software" +msgid "Software:" +msgstr "Software" + +#: bookwyrm/templates/settings/federated_server.html:19 +#, fuzzy +#| msgid "Description:" +msgid "Version:" +msgstr "Descripción:" + +#: bookwyrm/templates/settings/federated_server.html:23 +#, fuzzy +#| msgid "Status" +msgid "Status:" +msgstr "Status" + +#: bookwyrm/templates/settings/federated_server.html:30 +#: bookwyrm/templates/user/user_layout.html:50 +msgid "Activity" +msgstr "Actividad" + +#: bookwyrm/templates/settings/federated_server.html:33 +#, fuzzy +#| msgid "Username:" +msgid "Users:" +msgstr "Nombre de usuario:" + +#: bookwyrm/templates/settings/federated_server.html:36 +#: bookwyrm/templates/settings/federated_server.html:43 +msgid "View all" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:40 +#, fuzzy +#| msgid "Recent Imports" +msgid "Reports:" +msgstr "Importaciones recientes" + +#: bookwyrm/templates/settings/federated_server.html:47 +#, fuzzy +#| msgid "followed you" +msgid "Followed by us:" +msgstr "te siguió" + +#: bookwyrm/templates/settings/federated_server.html:53 +#, fuzzy +#| msgid "followed you" +msgid "Followed by them:" +msgstr "te siguió" + +#: bookwyrm/templates/settings/federated_server.html:59 +#, fuzzy +#| msgid "Blocked Users" +msgid "Blocked by us:" +msgstr "Usuarios bloqueados" + +#: bookwyrm/templates/settings/federation.html:13 msgid "Server name" msgstr "Nombre de servidor" -#: bookwyrm/templates/settings/federation.html:11 +#: bookwyrm/templates/settings/federation.html:17 +#, fuzzy +#| msgid "Federated" +msgid "Date federated" +msgstr "Federalizado" + +#: bookwyrm/templates/settings/federation.html:21 msgid "Software" msgstr "Software" -#: bookwyrm/templates/settings/federation.html:12 +#: bookwyrm/templates/settings/federation.html:24 #: bookwyrm/templates/settings/manage_invite_requests.html:33 +#: bookwyrm/templates/settings/user_admin.html:32 msgid "Status" msgstr "Status" @@ -1835,6 +1922,47 @@ msgstr "Solicitudes de seguidor" msgid "Registration closed text:" msgstr "Texto de registración cerrada:" +#: bookwyrm/templates/settings/user_admin.html:7 +#, python-format +msgid "Users: %(server_name)s" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:20 +#, fuzzy +#| msgid "Username:" +msgid "Username" +msgstr "Nombre de usuario:" + +#: bookwyrm/templates/settings/user_admin.html:24 +#, fuzzy +#| msgid "Added:" +msgid "Date Added" +msgstr "Agregado:" + +#: bookwyrm/templates/settings/user_admin.html:28 +msgid "Last Active" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:36 +#, fuzzy +#| msgid "Remove" +msgid "Remote server" +msgstr "Quitar" + +#: bookwyrm/templates/settings/user_admin.html:45 +#, fuzzy +#| msgid "Activity" +msgid "Active" +msgstr "Actividad" + +#: bookwyrm/templates/settings/user_admin.html:45 +msgid "Inactive" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:50 +msgid "Not set" +msgstr "" + #: bookwyrm/templates/snippets/block_button.html:5 msgid "Block" msgstr "Bloquear" @@ -1895,7 +2023,7 @@ msgid "Review:" msgstr "Reseña" #: bookwyrm/templates/snippets/create_status_form.html:29 -#: bookwyrm/templates/snippets/shelf.html:17 +#: bookwyrm/templates/user/shelf.html:78 msgid "Rating" msgstr "Calificación" @@ -1969,6 +2097,26 @@ msgstr "Me gusta status" msgid "Un-like status" msgstr "Quitar me gusta de status" +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:7 +#, fuzzy +#| msgid "Show less" +msgid "Show filters" +msgstr "Mostrar menos" + +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:9 +msgid "Hide filters" +msgstr "" + +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:19 +msgid "Apply filters" +msgstr "" + +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:23 +#, fuzzy +#| msgid "Clear search" +msgid "Clear filters" +msgstr "Borrar búsqueda" + #: bookwyrm/templates/snippets/follow_button.html:12 msgid "Follow" msgstr "Seguir" @@ -2102,32 +2250,32 @@ msgstr "Da una calificación" msgid "Rate" msgstr "Calificar" -#: bookwyrm/templates/snippets/readthrough.html:7 +#: bookwyrm/templates/snippets/readthrough.html:8 msgid "Progress Updates:" msgstr "Actualizaciones de progreso:" -#: bookwyrm/templates/snippets/readthrough.html:11 +#: bookwyrm/templates/snippets/readthrough.html:12 msgid "finished" msgstr "terminado" -#: bookwyrm/templates/snippets/readthrough.html:14 +#: bookwyrm/templates/snippets/readthrough.html:15 msgid "Show all updates" msgstr "Mostrar todas las actualizaciones" -#: bookwyrm/templates/snippets/readthrough.html:30 +#: bookwyrm/templates/snippets/readthrough.html:31 msgid "Delete this progress update" msgstr "Eliminar esta actualización de progreso" -#: bookwyrm/templates/snippets/readthrough.html:40 +#: bookwyrm/templates/snippets/readthrough.html:41 msgid "started" msgstr "empezado" -#: bookwyrm/templates/snippets/readthrough.html:46 -#: bookwyrm/templates/snippets/readthrough.html:60 +#: bookwyrm/templates/snippets/readthrough.html:47 +#: bookwyrm/templates/snippets/readthrough.html:61 msgid "Edit read dates" msgstr "Editar fechas de lectura" -#: bookwyrm/templates/snippets/readthrough.html:50 +#: bookwyrm/templates/snippets/readthrough.html:51 msgid "Delete these read dates" msgstr "Eliminar estas fechas de lectura" @@ -2191,45 +2339,11 @@ msgstr "por %(author)s" msgid "Import book" msgstr "Importar libro" -#: bookwyrm/templates/snippets/shelf.html:12 -msgid "Published" -msgstr "Publicado" - -#: bookwyrm/templates/snippets/shelf.html:13 -msgid "Shelved" -msgstr "Archivado" - -#: bookwyrm/templates/snippets/shelf.html:14 -msgid "Started" -msgstr "Empezado" - -#: bookwyrm/templates/snippets/shelf.html:15 -msgid "Finished" -msgstr "Terminado" - -#: bookwyrm/templates/snippets/shelf.html:16 -msgid "External links" -msgstr "Enlaces externos" - -#: bookwyrm/templates/snippets/shelf.html:44 -msgid "OpenLibrary" -msgstr "OpenLibrary" - -#: bookwyrm/templates/snippets/shelf.html:61 -msgid "This shelf is empty." -msgstr "Este estante está vacio." - -#: bookwyrm/templates/snippets/shelf.html:67 -msgid "Delete shelf" -msgstr "Eliminar estante" - #: bookwyrm/templates/snippets/shelf_selector.html:4 -msgid "Change shelf" -msgstr "Cambiar estante" - -#: bookwyrm/templates/snippets/shelf_selector.html:27 -msgid "Unshelve" -msgstr "Retirar del estante" +#, fuzzy +#| msgid "Your books" +msgid "Move book" +msgstr "Tus libros" #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:5 #, python-format @@ -2237,7 +2351,7 @@ msgid "Finish \"%(book_title)s\"" msgstr "Terminar \"%(book_title)s\"" #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:5 -#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:33 +#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:35 #, fuzzy #| msgid "Updates" msgid "Update progress" @@ -2260,6 +2374,12 @@ msgstr "Terminar de leer" msgid "Want to read" msgstr "Quiero leer" +#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:48 +#, fuzzy, python-format +#| msgid "Lists: %(username)s" +msgid "Remove from %(name)s" +msgstr "Listas: %(username)s" + #: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:5 #, python-format msgid "Start \"%(book_title)s\"" @@ -2332,6 +2452,18 @@ msgstr "Más opciones" msgid "Switch to this edition" msgstr "Cambiar a esta edición" +#: bookwyrm/templates/snippets/table-sort-header.html:6 +#, fuzzy +#| msgid "Started reading" +msgid "Sorted asccending" +msgstr "Lectura se empezó" + +#: bookwyrm/templates/snippets/table-sort-header.html:10 +#, fuzzy +#| msgid "Started reading" +msgid "Sorted descending" +msgstr "Lectura se empezó" + #: bookwyrm/templates/snippets/tag.html:14 msgid "Remove tag" msgstr "Eliminar etiqueta" @@ -2341,6 +2473,12 @@ msgstr "Eliminar etiqueta" msgid "Books tagged \"%(tag.name)s\"" msgstr "Libros etiquetados con \"%(tag.name)s\"" +#: bookwyrm/templates/user/books_header.html:5 +#, fuzzy, python-format +#| msgid "%(username)s's %(year)s Books" +msgid "%(username)s's books" +msgstr "Los libros de %(username)s para %(year)s" + #: bookwyrm/templates/user/create_shelf_form.html:5 #: bookwyrm/templates/user/create_shelf_form.html:22 msgid "Create Shelf" @@ -2386,56 +2524,62 @@ msgstr "Listas: %(username)s" msgid "Create list" msgstr "Crear lista" -#: bookwyrm/templates/user/shelf.html:9 -msgid "Your Shelves" -msgstr "Tus estantes" - -#: bookwyrm/templates/user/shelf.html:11 -#, python-format -msgid "%(username)s: Shelves" -msgstr "%(username)s: Estantes" - -#: bookwyrm/templates/user/shelf.html:33 +#: bookwyrm/templates/user/shelf.html:34 msgid "Create shelf" msgstr "Crear estante" -#: bookwyrm/templates/user/shelf.html:54 +#: bookwyrm/templates/user/shelf.html:55 msgid "Edit shelf" msgstr "Editar estante" +#: bookwyrm/templates/user/shelf.html:75 +msgid "Shelved" +msgstr "Archivado" + +#: bookwyrm/templates/user/shelf.html:76 +msgid "Started" +msgstr "Empezado" + +#: bookwyrm/templates/user/shelf.html:77 +msgid "Finished" +msgstr "Terminado" + +#: bookwyrm/templates/user/shelf.html:121 +msgid "This shelf is empty." +msgstr "Este estante está vacio." + +#: bookwyrm/templates/user/shelf.html:127 +msgid "Delete shelf" +msgstr "Eliminar estante" + #: bookwyrm/templates/user/user.html:15 msgid "Edit profile" msgstr "Editar perfil" -#: bookwyrm/templates/user/user.html:26 -#: bookwyrm/templates/user/user_layout.html:68 -msgid "Shelves" -msgstr "Estantes" - -#: bookwyrm/templates/user/user.html:31 -#, python-format -msgid "See all %(size)s" +#: bookwyrm/templates/user/user.html:33 +#, fuzzy, python-format +#| msgid "See all %(size)s" +msgid "View all %(size)s" msgstr "Ver %(size)s" -#: bookwyrm/templates/user/user.html:44 -#, python-format -msgid "See all %(shelf_count)s shelves" -msgstr "Ver los %(shelf_count)s estantes" +#: bookwyrm/templates/user/user.html:46 +msgid "View all books" +msgstr "" -#: bookwyrm/templates/user/user.html:56 +#: bookwyrm/templates/user/user.html:58 #, python-format msgid "Set a reading goal for %(year)s" msgstr "Establecer una meta de lectura para %(year)s" -#: bookwyrm/templates/user/user.html:62 +#: bookwyrm/templates/user/user.html:64 msgid "User Activity" msgstr "Actividad de usuario" -#: bookwyrm/templates/user/user.html:65 +#: bookwyrm/templates/user/user.html:67 msgid "RSS feed" msgstr "Feed RSS" -#: bookwyrm/templates/user/user.html:76 +#: bookwyrm/templates/user/user.html:78 msgid "No activities yet!" msgstr "¡Aún no actividades!" @@ -2443,14 +2587,16 @@ msgstr "¡Aún no actividades!" msgid "Follow Requests" msgstr "Solicitudes de seguidor" -#: bookwyrm/templates/user/user_layout.html:50 -msgid "Activity" -msgstr "Actividad" - #: bookwyrm/templates/user/user_layout.html:56 msgid "Reading Goal" msgstr "Meta de lectura" +#: bookwyrm/templates/user/user_layout.html:68 +#, fuzzy +#| msgid "Book" +msgid "Books" +msgstr "Libro" + #: bookwyrm/templates/user/user_preview.html:13 #, python-format msgid "Joined %(date)s" @@ -2479,6 +2625,48 @@ msgstr "Ya existe un usuario con ese nombre." msgid "A password reset link sent to %s" msgstr "" +#~ msgid "Your shelves" +#~ msgstr "Tus estantes" + +#~ msgid "Your lists" +#~ msgstr "Tus listas" + +#, python-format +#~ msgid "See all %(size)s lists" +#~ msgstr "Ver las %(size)s listas" + +#~ msgid "Recent Lists" +#~ msgstr "Listas recientes" + +#~ msgid "Published" +#~ msgstr "Publicado" + +#~ msgid "External links" +#~ msgstr "Enlaces externos" + +#~ msgid "OpenLibrary" +#~ msgstr "OpenLibrary" + +#~ msgid "Change shelf" +#~ msgstr "Cambiar estante" + +#~ msgid "Unshelve" +#~ msgstr "Retirar del estante" + +#~ msgid "Your Shelves" +#~ msgstr "Tus estantes" + +#, python-format +#~ msgid "%(username)s: Shelves" +#~ msgstr "%(username)s: Estantes" + +#~ msgid "Shelves" +#~ msgstr "Estantes" + +#, python-format +#~ msgid "See all %(shelf_count)s shelves" +#~ msgstr "Ver los %(shelf_count)s estantes" + #~ msgid "Send follow request" #~ msgstr "Envia solicitud de seguidor" diff --git a/locale/fr_FR/LC_MESSAGES/django.mo b/locale/fr_FR/LC_MESSAGES/django.mo index 99a9b6cc588edc6458a2cebe23fef38d4b31d76d..f3f76f03d44d10c2e6c6ab3168a0e93704ab8ade 100644 GIT binary patch delta 7514 zcmYk=3w+P@9>?+DpWT?t7;~Nd+0|?_mu+^T5i@h$EOO1*TqZU%x6q$Ooj6q1MJF6Y zD3Sg|l5$Jy=(K2w+{!IUx*$@V*L%P3&cpBF^M8In-{0@|{e5r0|NiytLC2E44%eCb z9xquOSq{r;j+<**RvG1#hN`u!QB+RF99)PMs8hoNEh`qMU?W_M{IhoRk&WMA7)H0S zug}INOzLi5kFZtc~Y&p66S?Qt-oD_%wRAvPZZSn~|?Zb!-pnfn``1%T4_` zQ-96W-$1qJ5o~v~A!=prjB%(D_cIsdV;K1i)QDGMC)|R1 z&}CD99o5c#R7Y!v+H2p$7>s&gG_t>~B$M|DW&YK0mN_vR^?*sJwSNQkpdH2!P5mL% zlzxTUWT#Oxav44F4m$B3>bf9Cp(Tkxy*-KOh5cO=)PX^$hQ^{ETxfEgSN<&O#^tCE zZ9z5s9%{3Ggc{i~)J&W~-S-o+9jv>kj=8Lp7e=5Oj>ln`X7aVD z2Hr(Aun*OtW2ljxHRms&2K2k}4r*Zc(Hp(k@j71zef9n~FcnVJ?hHeXJQ=ka(@_u3 zHBLnz@)F}>)O|0a?%QO15A~n}sDYKCI(i&6fKyms@Bhyf^Z-XB>w|t6jcrXn0&9@Z zLEZQ?s$iV{*j>MZh8FhU()KX@m23ClAUC*f5L!XYvC49r4ZUxHe~MW~s3&A1-*-fu-+cN8^qCovq)BdcM#eB0U`NI*?> z8tOrVP&ZCD^-rQkycE^YOQ;8~GWkZ-`@Riz-(jqcU!Vqb8g<`~rv7)Vqrd--Sj!6F zgdb|e;iwBcpr$qj8NAgMGqAw88}&oNvz`6JB@p!*CZVRdKWYhwqB=eb)$TZ~i-lN6 z@Bch=;yL4TRFBt~d<&|ZDKa51Wf^NmNbKY4JxWpR%+6*ZL`QP*$7Y}{+~Pq2TwjYs`8-GlY< zCaPmTRH|cjIx+uRn*b_wA`sPZurUgCJ`VNzq~ItVglb?X_IB{s59gEnCh@I^ORy1! zFN)dibkA%@~&%))KS%)ed(j}-eWH5Nn2`(PwaLEW$dXX1V=#^lbn@8Dqa&@SAA zb5R5M4Ap^SCclar$PLt9xQiNqr|ThmYHDKu6;9-}uo6&jLmCEQmZ>kodgSxbiKQmr zj;+Z*!8H5@D>q%LeSKHdKn9{V^DI<{Tni|uCyTKmmYRGM`jKx#EzN$^`A<<(dXH(FWk2fb~U^DXhs1dA0jdTaL z!ZK`*KcPBYqdRX9HbHfuFDBwh)J!Z#&CF_Z-ir?qTnRrl`Cf z>Ve%&eGW2v)=boWyHPW81T~Q3s19F3?V&$WBlb$O>-|vehM+5wLIMTt+R>;fnuslN z9;$)0sF~Pe@=vfa`BBt2;#G70j;XKL)9z3tdU8Gv)m}2H!y`~jThNpFS5KBu5rwPK z8$U-qxWf1ys)N_i3vZeHZ`4S=((Uhurl<#WM0GID_!x$g&qN%CNH zicg|O`Uk4MZZG@wYlG@gZ&Zf|qpr)x(O807(~GD*;E`c(%7&op4A6t<$KY$xhLSqop1=o;4<{ZeW($Bj#~387>&OC z1=9IMREK&X9dTI$DX{UaT-1X%p*r*~w!z(~O?e45BX?0F@a$)Q0|uZ*o`71@zNi_^ zMYWTU8hH__Lrbt0ZpFrW|MyVP+E-vS{)So`Cx7x}II8C{sHI6VW}wawMeT(g)Bt7~ zOHdtIj9U8_uo}LMweeM~eE&C4s7uBBs1cN59XyAc;_Il9M+~rSkD7rLllMh+V6d?O zb>B0n4tHMSQA&Hru;2bNB7}0 zEXQz6%d$H-8MUWesLi+>wIsW+vSV4ye-ssERMf>==7iTkdz010IOG7cMJ>q>#_Q-uehYP-cecHk>Y?(mY}Q|!E0zkaNe|TK7>N3=AB7?K6lzy* z#0Gc}H4`ULYkL8;iLYP}jCsWVfl-W_xx=W<_dRMxJO>0>J&EzYn0j)D`Ma{@=q=PQ2f`T@~DdZW} zMP#|G&O>cqz?!`JXtd-ax!e@G4mz4y}+muH#-Jys83!A(j!Znz|>j z>W)s7|01+lz37CFXNajp9JzMcX`(YRpU|Oi3mqw(Z(CV1t-VI_9Ptn**5D)}g$N?5 zjxS7M8>Vtyb(2@497*W-jF?L#nfzJG%_uh~`Vu|J0|*^@fp6&fx0IQqBNu&W%5k`Z z_?_@Jb$aP?2z`D1i&#PENantHLdQEq2jUcQnW#EiQ;4cc&`r3G^HJa2sB;OFf5fi1 z3ZwBno+WeyknggK);jbhzlfg@ohZ*lAN&5Y2a?x` zFHPko)YnWk;sDW#o7)kd#5D4%<2?#q)WzW6L=w@D*h!2bGKu{}3$97P|L>;Fm5KfF zB1|R%xhU{~`Vh+cl3i-*P8d6LT}M+shO>$PntV65AtH!EqBk**h$UVj_G$fR5jrjq zZxT-tdBo>LF!3(2n9xy^hb_Q*_%Jb|vey3iMo_2YChMN zO!6I3bqqI!yZEUoKZ#?B$A}@s9io`%V;(}W^4H}_tWBg5BMBYd9=Jw%7n4t*+}lO+ z8-)_$A!0Q#l*l5gj&CVA%uT~6e{W9opsoh-F?k1k6gQc3qm81DxwoltI(2^%^$FJp zd|V@*Cyo%+x#)46Lxh+czM>pXxdu9kRH8ZIPmCuP5od_DT+<(aCO#sT6FMT)jx#4W zH!s(jk~gVKN{(}UQQp|jtwJN*TLaHJ-0OlCd$?0VJRI&mq30a#sZHb34qb)@*b^!NjT7kb-GN&Z4}W+zFG%IdjGoPnbTTc$Pac z{ts_=pDx=y-2vST9qw0qEUWfhDFyayH@s+=uGu zZ>R}ez(oAY>SNO#ryb?aW-_w5PEXVV`eH2OI|bHY1SU`(gKKa)YK6zKJN^ST(I~b{ z19ruDOhwh_n?vmSIJ;hk>US<`!b?y)v>bz~SWQL)t;KA79JP`|sF}Wp8t??h;wjWh zE}#a!jJiLjw_Bfzs?S07KiKL=m=jU`RQG28)v=F?PFRn+akVvg0Fx+hLhZm_yZ#nt zQa*v&fiMc!VrSHVQ&9C)sDT!uCftNN3#-igGTDC(_%Ibw_!O%AEUJT_S^aCM0gj^1 zNNgW>pcFF$RiA4PL2dB_)PR+!9h!~pumQc;6eOdD8&J<^GwOBOjqULeY9fbG9UVsv zblS=nt$Z1EUtE?uu?$qdxu`>2h+5!y)Q(I;^%JZm!+~{fLCx%8jKCIiD~3_tiJH(J zb02D_UPN_x7`4U6P%Ax+n)sKf_dSAx6@k4_^?i{E2c02g)WKBLjOL;`@VgD1RTxfr zBdXnIR0q%EEZlG9q#XDDEY$sjQ4<=ETG({EJ`=T|x{y5o1~OXNQq({ztij!=4(_w^ zM${qQgj)F?)S*0pn)osEW7JB&G(COY`=U_y^)z!aOz(dY8LeytYNnG>E2u!dh6_*w z+=_f%oi&(;KeY1O7)kj&>b`GK6N}4r+vlLJk4KGDi7{A%!F)1)GTM?SP!o6>_1bO6 zXnYBEINw0+)Cp9Fr%)Z9Lk;{b>afK#JGDzk^_PP&*v}k_+M)4zJb$gMf(kv;8swFB zZb3bYN6aTtH*7-<^c-peFQW!JY~{C6{k)G_&?l(-E}$mz4XV9|RjVA4Z|{FB6|qz# zqh^|gaad^OG3Hd%X`X{>zZ})!8jQmAsDU5FINWURLha;!RQuncCh&XhzbcMm9R3OQ zjL)K08rjbsusimoT!?xU(@_I1L~Z#ksQd3h?cBqtg=|6%yaR8>y{LZD`@1_698E?y zOfsio0_7V}TYR&5E9yJ38rA*>sI7e#``}*W+&S-~CK6fTK8hsNfLW;f##wzivhe12 zBVvOZX9;EXcRT7sb{DFB3u>pILM>=Fs@;C8KZF|ZZPcGECs7N&glZSYuWIdd9Ohw1 zoPZ_fx&fR){Q>hX75W3{9O^ZX8R+hC8tReeqMpo%=NCQ_IaqCDMn3fBu>V0I0ZLa z`4nnG0|&b+UWmHC2{rNHYReqKGAh#fRm??a4}Nl?8ijB zh?-bLu{*Ii)T8Tys_%j7H{CVpiRnBIm3eUgEAe0d=Ojq85^d+M#UhfP&4wJ%TN$iEKlirQI0%8}tyF1S;M@J);xW;4i4H z{tES0G>>*W-iBJq8>kLHMD4&CRL9O3cOqR-*RxRl_s33Hf?9CZ81`SUMJ*NDsz&UA zD{wGAh8cJSwURGTD``{e4it&%xF@RP{#GtEtE_$zs{a+JiLXH|sHK$s*H-Vc2K!OJ zzF$SH=%h9L3VTtGW54yGDnLzq3i4@i=Ab6>5b^>zPoq};K5D>IcKu6Cr5t&kyF*z) z%M_y;PC^}yxmI3=8gQ-EKZ87W=a;BweHpbg9mcr}NiqAPR#=SM>M5ulm}&J3P~!!c zlhNtFA0zOmsI7bv)9@&&gU?Yr(q_C{PR1n4nW#_c2-Nk-R_{YiY$b-_T2y}zq9(ov zdDKDYAQ{c%Bh1CmFdWk+xC3RIg&0A73AV=xR-TGl=}gr3!;c!^yQm31X0~E)%7;@X>hL^j=56`VtHT)7L~<|(hoJ^sj5r!g8k z^NU0)OhY~MVVH+=QPeW-!IMNO!Ex%=&oL)}=6I-FxsJ2MLt z(1%`JhT4HgP&=>-wX?6F`ui-Opn> zzGd|nF_CikH23vOMs4vRjKVV1BbkYs_!3--LF@z0MKYRkkLm6s$Vc6H9qRN}ptiIT zHRIb+hwm=b4n1e}zq0z*u#o!CQ4`Cpa(8e9_N82f+Oa#av)=!8Wb}-;nmbSvdJfg_ zCG3Q+TlqsPpG7^g%cwIHTkZaiNX87x!%%NQE$aT2sD(A7p8aMV!}!kAWJ>W2@_3y5 z>)kC}jXI^9FbNNs@1hRTKT#9>9M!Qi!#%vwsGZ9~ZTUFV^=eGQTGS!`4hFS_kCIWt z?bs1tK<&uu*cFeUR&W*z@G|PYf|>3FO3aC<9hr{faXv1`T{svsXSr|H9Q0CNH;etx zQUfX`<2$IG$hg7X(i>4TzZEsXJFWbEa}y?0zs;_{jGFjcsI&4OvZ>BT$T@Y2X1lJ( zYg9j*{a43HbKJL}5Bew%#2RcdzeataX3upy-ikV0J5lWpAY(Y*d9HWhNXo}hJKCK_ zdSv}j6Dvd=>fu4lj77bMQ&7)nCaPfovvCFTn4M=(&-^la@KNG(f>$Y&=2EFgGh7!* z3td7#{rvP26NxT%J*ap2TSBRt%4hLCVm$d#)=1Zs22pNLXyzXf;pBB-+u=~6Kk*FZ z9mMlQ8s&qyi%{Yl7fL!KO1wBhXB?RWL|+afLOJ)xRNCktB}xN{ zvE;wO?p8NX_2lQ{j|sj-p>#Vxw-NfX&g1%D8Q&RgH{DJC55$%97MbBhs$Ez84+*_W z)2O>m6(k+749fj*0&x$~$?nnbmVHDwVk2>csNmW=xP=JnOVpOwNnA;fkV|1OU4F#aoKZY3fJrF?WSkBA|(e+U~@Vd*(MO1!2|(jGFKiLZ&V!~)_<`WKljI{3mWlFdhO zHuc%Kf*4G`7yg`3`aTg&^rK$sbz-FD`;w0(|1*3ebQ;KPAd(2B1Zx;@i%t*9d6pM% zTYeBeKxA{hgWY47LVVKvY3Gi?02#h9&5EJFw(_j*S}fwia=Vy{9YXnm{6%DqhEUOJ4Q|FL%ZoB1opz&fR_pAP zOP<7~0birP=4M}Zpx9fv*jK;I*IJSKV7utWHU33DZ-APGtsnGi?}@5us__OImNw4! zwf;H%q$jR)d6Vv}_tw|>Zwj=&()<3f@JV&GjWvz8wNB`p;At+;y)k0G&)?*oR2OL8 zoBMIT&s$Sp@9mux>U8e>hNb?d>_BLUWxjw@>HE+1y4#sf^W%A+hG{;5*0uTF+J^ge z8?)O|u(HiaccuYn1{3nuHZu9&4|si{ZK3W|sV5@3Y>Cf*Wm8T~Uf~HZUs_vVcW_7Zys}kc z>`Rlc^=R4Mp2Dhzx<+rkFYtf6b7g2Y=;}719crzfTH=YiYR->TbZD1U>aX`L^EGOI r^;gZ^*Sf7Lrfur~^cUdSEn~5Ct!$vF`Gpy&aVq?`S?kdmMPdI2q4Lc5 diff --git a/locale/fr_FR/LC_MESSAGES/django.po b/locale/fr_FR/LC_MESSAGES/django.po index 66ee8cf1..6c6ecb40 100644 --- a/locale/fr_FR/LC_MESSAGES/django.po +++ b/locale/fr_FR/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.1.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-03-28 22:42+0000\n" +"POT-Creation-Date: 2021-03-31 09:22-0700\n" "PO-Revision-Date: 2021-03-02 12:37+0100\n" "Last-Translator: Fabien Basmaison \n" "Language-Team: Mouse Reeve \n" @@ -49,46 +49,66 @@ msgstr "" msgid "Unlimited" msgstr "Non listé" -#: bookwyrm/models/fields.py:25 +#: bookwyrm/models/fields.py:24 #, python-format msgid "%(value)s is not a valid remote_id" msgstr "" -#: bookwyrm/models/fields.py:34 bookwyrm/models/fields.py:47 +#: bookwyrm/models/fields.py:33 bookwyrm/models/fields.py:42 #, python-format msgid "%(value)s is not a valid username" msgstr "" -#: bookwyrm/models/fields.py:170 bookwyrm/templates/layout.html:157 +#: bookwyrm/models/fields.py:165 bookwyrm/templates/layout.html:157 #, fuzzy #| msgid "Username:" msgid "username" msgstr "Nom d’utilisateur :" -#: bookwyrm/models/fields.py:175 +#: bookwyrm/models/fields.py:170 msgid "A user with that username already exists." msgstr "" -#: bookwyrm/settings.py:148 +#: bookwyrm/settings.py:150 msgid "English" msgstr "" -#: bookwyrm/settings.py:149 +#: bookwyrm/settings.py:151 msgid "German" msgstr "" -#: bookwyrm/settings.py:150 +#: bookwyrm/settings.py:152 msgid "Spanish" msgstr "" -#: bookwyrm/settings.py:151 +#: bookwyrm/settings.py:153 msgid "French" msgstr "" -#: bookwyrm/settings.py:152 +#: bookwyrm/settings.py:154 msgid "Simplified Chinese" msgstr "" +#: bookwyrm/templates/404.html:4 bookwyrm/templates/404.html:8 +msgid "Not Found" +msgstr "Introuvable" + +#: bookwyrm/templates/404.html:9 +msgid "The page you requested doesn't seem to exist!" +msgstr "Il semblerait que la page que vous avez demandée n’existe pas !" + +#: bookwyrm/templates/500.html:4 +msgid "Oops!" +msgstr "Oups !" + +#: bookwyrm/templates/500.html:8 +msgid "Server Error" +msgstr "Erreur côté serveur" + +#: bookwyrm/templates/500.html:9 +msgid "Something went wrong! Sorry about that." +msgstr "Une erreur s’est produite ; désolé !" + #: bookwyrm/templates/author.html:16 bookwyrm/templates/author.html:17 msgid "Edit Author" msgstr "Modifier l’auteur ou autrice" @@ -112,96 +132,68 @@ msgstr "" msgid "Edit Book" msgstr "Modifier le livre" -#: bookwyrm/templates/book/book.html:45 +#: bookwyrm/templates/book/book.html:49 #: bookwyrm/templates/book/cover_modal.html:5 msgid "Add cover" msgstr "Ajouter une couverture" -#: bookwyrm/templates/book/book.html:55 +#: bookwyrm/templates/book/book.html:59 msgid "ISBN:" msgstr "ISBN :" -#: bookwyrm/templates/book/book.html:62 +#: bookwyrm/templates/book/book.html:66 #: bookwyrm/templates/book/edit_book.html:211 msgid "OCLC Number:" msgstr "Numéro OCLC :" -#: bookwyrm/templates/book/book.html:69 +#: bookwyrm/templates/book/book.html:73 #: bookwyrm/templates/book/edit_book.html:215 msgid "ASIN:" msgstr "ASIN :" -#: bookwyrm/templates/book/book.html:79 -#, fuzzy, python-format -#| msgid "of %(book.pages)s pages" -msgid "%(format)s, %(pages)s pages" -msgstr "sur %(book.pages)s pages" - -#: bookwyrm/templates/book/book.html:81 -#, fuzzy, python-format -#| msgid "of %(book.pages)s pages" -msgid "%(pages)s pages" -msgstr "sur %(book.pages)s pages" - -#: bookwyrm/templates/book/book.html:86 -#, python-format -msgid "Published %(date)s by %(publisher)s." -msgstr "" - -#: bookwyrm/templates/book/book.html:88 -#, fuzzy, python-format -#| msgid "Published date:" -msgid "Published %(date)s" -msgstr "Date de publication :" - -#: bookwyrm/templates/book/book.html:90 -#, python-format -msgid "Published by %(publisher)s." -msgstr "" - -#: bookwyrm/templates/book/book.html:95 +#: bookwyrm/templates/book/book.html:82 msgid "View on OpenLibrary" msgstr "Voir sur OpenLibrary" -#: bookwyrm/templates/book/book.html:104 +#: bookwyrm/templates/book/book.html:91 #, python-format msgid "(%(review_count)s review)" msgid_plural "(%(review_count)s reviews)" msgstr[0] "" msgstr[1] "" -#: bookwyrm/templates/book/book.html:110 +#: bookwyrm/templates/book/book.html:97 #, fuzzy #| msgid "Description:" msgid "Add Description" msgstr "Ajouter une description" -#: bookwyrm/templates/book/book.html:117 +#: bookwyrm/templates/book/book.html:104 #: bookwyrm/templates/book/edit_book.html:101 #: bookwyrm/templates/lists/form.html:12 msgid "Description:" msgstr "Description :" -#: bookwyrm/templates/book/book.html:121 +#: bookwyrm/templates/book/book.html:108 #: bookwyrm/templates/book/edit_book.html:225 #: bookwyrm/templates/edit_author.html:78 bookwyrm/templates/lists/form.html:42 -#: bookwyrm/templates/preferences/edit_user.html:68 +#: bookwyrm/templates/preferences/edit_user.html:70 #: bookwyrm/templates/settings/site.html:93 -#: bookwyrm/templates/snippets/readthrough.html:64 +#: bookwyrm/templates/snippets/readthrough.html:65 #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:42 #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:42 #: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:34 msgid "Save" msgstr "Enregistrer" -#: bookwyrm/templates/book/book.html:122 bookwyrm/templates/book/book.html:171 +#: bookwyrm/templates/book/book.html:109 bookwyrm/templates/book/book.html:158 #: bookwyrm/templates/book/cover_modal.html:32 #: bookwyrm/templates/book/edit_book.html:226 #: bookwyrm/templates/edit_author.html:79 #: bookwyrm/templates/moderation/report_modal.html:32 #: bookwyrm/templates/snippets/delete_readthrough_modal.html:17 #: bookwyrm/templates/snippets/goal_form.html:32 -#: bookwyrm/templates/snippets/readthrough.html:65 +#: bookwyrm/templates/snippets/readthrough.html:66 #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:43 #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:43 #: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:35 @@ -209,79 +201,79 @@ msgstr "Enregistrer" msgid "Cancel" msgstr "Annuler" -#: bookwyrm/templates/book/book.html:131 +#: bookwyrm/templates/book/book.html:118 #, fuzzy, python-format #| msgid "Editions of \"%(work_title)s\"" msgid "%(count)s editions" msgstr "%(title)s par " -#: bookwyrm/templates/book/book.html:139 +#: bookwyrm/templates/book/book.html:126 #, fuzzy, python-format #| msgid "favorited your %(preview_name)s" msgid "This edition is on your %(shelf_name)s shelf." msgstr "Messages directs avec %(username)s" -#: bookwyrm/templates/book/book.html:145 +#: bookwyrm/templates/book/book.html:132 #, fuzzy, python-format #| msgid "replied to your %(preview_name)s" msgid "A different edition of this book is on your %(shelf_name)s shelf." msgstr " a ajouté %(book_title)s à votre liste « %(list_name)s »" -#: bookwyrm/templates/book/book.html:154 +#: bookwyrm/templates/book/book.html:141 msgid "Your reading activity" msgstr "Votre activité de lecture" -#: bookwyrm/templates/book/book.html:156 +#: bookwyrm/templates/book/book.html:143 #, fuzzy #| msgid "Edit read dates" msgid "Add read dates" msgstr "Ajouter des dates de lecture" -#: bookwyrm/templates/book/book.html:161 +#: bookwyrm/templates/book/book.html:148 msgid "You don't have any reading activity for this book." msgstr "Vous n’avez aucune activité de lecture pour ce livre" -#: bookwyrm/templates/book/book.html:168 +#: bookwyrm/templates/book/book.html:155 msgid "Create" msgstr "Créer" -#: bookwyrm/templates/book/book.html:190 +#: bookwyrm/templates/book/book.html:177 msgid "Tags" msgstr "Tags" -#: bookwyrm/templates/book/book.html:194 +#: bookwyrm/templates/book/book.html:181 #: bookwyrm/templates/snippets/tag.html:18 msgid "Add tag" msgstr "Ajouter un tag" -#: bookwyrm/templates/book/book.html:211 +#: bookwyrm/templates/book/book.html:198 msgid "Subjects" msgstr "Sujets" -#: bookwyrm/templates/book/book.html:222 +#: bookwyrm/templates/book/book.html:209 msgid "Places" msgstr "Lieux" -#: bookwyrm/templates/book/book.html:233 bookwyrm/templates/layout.html:64 -#: bookwyrm/templates/lists/lists.html:4 bookwyrm/templates/lists/lists.html:9 +#: bookwyrm/templates/book/book.html:220 bookwyrm/templates/layout.html:64 +#: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12 #: bookwyrm/templates/search_results.html:91 #: bookwyrm/templates/user/user_layout.html:62 msgid "Lists" msgstr "Listes" -#: bookwyrm/templates/book/book.html:244 +#: bookwyrm/templates/book/book.html:231 #, fuzzy #| msgid "Go to list" msgid "Add to list" msgstr "Aller à la liste" -#: bookwyrm/templates/book/book.html:254 +#: bookwyrm/templates/book/book.html:241 #: bookwyrm/templates/book/cover_modal.html:31 #: bookwyrm/templates/lists/list.html:90 msgid "Add" msgstr "Ajouter" -#: bookwyrm/templates/book/book.html:282 +#: bookwyrm/templates/book/book.html:269 msgid "rated it" msgstr "l’a noté" @@ -430,7 +422,7 @@ msgid "John Doe, Jane Smith" msgstr "" #: bookwyrm/templates/book/edit_book.html:155 -#: bookwyrm/templates/snippets/shelf.html:9 +#: bookwyrm/templates/user/shelf.html:72 msgid "Cover" msgstr "Couverture" @@ -439,6 +431,7 @@ msgid "Physical Properties" msgstr "Propriétés physiques" #: bookwyrm/templates/book/edit_book.html:183 +#: bookwyrm/templates/book/format_filter.html:5 msgid "Format:" msgstr "Format :" @@ -463,6 +456,60 @@ msgstr "ISBN 10 :" msgid "Openlibrary key:" msgstr "Clé Openlibrary :" +#: bookwyrm/templates/book/editions.html:5 +#, fuzzy, python-format +#| msgid "Finish \"%(book_title)s\"" +msgid "Editions of %(book_title)s" +msgstr "Éditions de %(book_title)s" + +#: bookwyrm/templates/book/editions.html:9 +#, python-format +msgid "Editions of \"%(work_title)s\"" +msgstr "Éditions de « %(work_title)s »" + +#: bookwyrm/templates/book/format_filter.html:8 +#: bookwyrm/templates/book/language_filter.html:8 +msgid "Any" +msgstr "" + +#: bookwyrm/templates/book/language_filter.html:5 +msgid "Language:" +msgstr "" + +#: bookwyrm/templates/book/publisher_info.html:6 +#, fuzzy, python-format +#| msgid "of %(book.pages)s pages" +msgid "%(format)s, %(pages)s pages" +msgstr "sur %(book.pages)s pages" + +#: bookwyrm/templates/book/publisher_info.html:8 +#, fuzzy, python-format +#| msgid "of %(book.pages)s pages" +msgid "%(pages)s pages" +msgstr "sur %(book.pages)s pages" + +#: bookwyrm/templates/book/publisher_info.html:13 +#, fuzzy, python-format +#| msgid "of %(book.pages)s pages" +msgid "%(languages)s language" +msgstr "sur %(book.pages)s pages" + +#: bookwyrm/templates/book/publisher_info.html:18 +#, python-format +msgid "Published %(date)s by %(publisher)s." +msgstr "" + +#: bookwyrm/templates/book/publisher_info.html:20 +#, fuzzy, python-format +#| msgid "Published date:" +msgid "Published %(date)s" +msgstr "Date de publication :" + +#: bookwyrm/templates/book/publisher_info.html:22 +#, python-format +msgid "Published by %(publisher)s." +msgstr "" + #: bookwyrm/templates/components/inline_form.html:8 #: bookwyrm/templates/feed/feed_layout.html:57 #, fuzzy @@ -470,95 +517,46 @@ msgstr "Clé Openlibrary :" msgid "Close" msgstr "Fermer" -#: bookwyrm/templates/directory.html:6 bookwyrm/templates/directory.html:11 -#: bookwyrm/templates/layout.html:97 -msgid "Directory" -msgstr "" - -#: bookwyrm/templates/directory.html:19 -msgid "Make your profile discoverable to other BookWyrm users." -msgstr "" - -#: bookwyrm/templates/directory.html:26 -#, fuzzy, python-format -#| msgid "You can set or change your reading goal any time from your profile page" -msgid "You can opt-out at any time in your profile settings." -msgstr "Vous pouvez définir ou changer vore défi lecture à n’importe quel moment depuis votre profil" - -#: bookwyrm/templates/directory.html:31 -#: bookwyrm/templates/snippets/goal_card.html:22 -msgid "Dismiss message" -msgstr "Rejeter le message" - -#: bookwyrm/templates/directory.html:44 -#, fuzzy -#| msgid "Show less" -msgid "Show filters" -msgstr "Replier" - -#: bookwyrm/templates/directory.html:46 -msgid "Hide filters" -msgstr "" - -#: bookwyrm/templates/directory.html:55 -#, fuzzy -#| msgid "User Activity" -msgid "User type" -msgstr "Activité du compte" - -#: bookwyrm/templates/directory.html:58 -#, fuzzy -#| msgid "Blocked users" -msgid "BookWyrm users" -msgstr "Comptes bloqués" - -#: bookwyrm/templates/directory.html:62 -msgid "All known users" -msgstr "" - -#: bookwyrm/templates/directory.html:68 +#: bookwyrm/templates/directory/community_filter.html:5 #, fuzzy #| msgid "Comment" msgid "Community" msgstr "Commentaire" -#: bookwyrm/templates/directory.html:71 +#: bookwyrm/templates/directory/community_filter.html:8 #, fuzzy #| msgid "Blocked users" msgid "Local users" msgstr "Comptes bloqués" -#: bookwyrm/templates/directory.html:75 +#: bookwyrm/templates/directory/community_filter.html:12 #, fuzzy #| msgid "Federated" msgid "Federated community" msgstr "Fédéré" -#: bookwyrm/templates/directory.html:81 -msgid "Order by" +#: bookwyrm/templates/directory/directory.html:6 +#: bookwyrm/templates/directory/directory.html:11 +#: bookwyrm/templates/layout.html:97 +msgid "Directory" msgstr "" -#: bookwyrm/templates/directory.html:84 -#, fuzzy -#| msgid "Suggest" -msgid "Suggested" -msgstr "Suggérer" - -#: bookwyrm/templates/directory.html:85 -msgid "Recently active" +#: bookwyrm/templates/directory/directory.html:19 +msgid "Make your profile discoverable to other BookWyrm users." msgstr "" -#: bookwyrm/templates/directory.html:91 -msgid "Apply filters" -msgstr "" +#: bookwyrm/templates/directory/directory.html:26 +#, fuzzy, python-format +#| msgid "You can set or change your reading goal any time from your profile page" +msgid "You can opt-out at any time in your profile settings." +msgstr "Vous pouvez définir ou changer vore défi lecture à n’importe quel moment depuis votre profil" -#: bookwyrm/templates/directory.html:94 -#, fuzzy -#| msgid "Clear search" -msgid "Clear filters" -msgstr "Vider la requête" +#: bookwyrm/templates/directory/directory.html:31 +#: bookwyrm/templates/snippets/goal_card.html:22 +msgid "Dismiss message" +msgstr "Rejeter le message" -#: bookwyrm/templates/directory.html:128 +#: bookwyrm/templates/directory/directory.html:71 #, fuzzy #| msgid "followed you" msgid "follower you follow" @@ -566,7 +564,7 @@ msgid_plural "followers you follow" msgstr[0] "s’est abonné(e)" msgstr[1] "s’est abonné(e)" -#: bookwyrm/templates/directory.html:135 +#: bookwyrm/templates/directory/directory.html:78 #, fuzzy #| msgid "Your shelves" msgid "book on your shelves" @@ -574,14 +572,44 @@ msgid_plural "books on your shelves" msgstr[0] "Vos étagères" msgstr[1] "Vos étagères" -#: bookwyrm/templates/directory.html:143 +#: bookwyrm/templates/directory/directory.html:86 msgid "posts" msgstr "" -#: bookwyrm/templates/directory.html:149 +#: bookwyrm/templates/directory/directory.html:92 msgid "last active" msgstr "" +#: bookwyrm/templates/directory/sort_filter.html:5 +msgid "Order by" +msgstr "" + +#: bookwyrm/templates/directory/sort_filter.html:8 +#, fuzzy +#| msgid "Suggest" +msgid "Suggested" +msgstr "Suggérer" + +#: bookwyrm/templates/directory/sort_filter.html:9 +msgid "Recently active" +msgstr "" + +#: bookwyrm/templates/directory/user_type_filter.html:5 +#, fuzzy +#| msgid "User Activity" +msgid "User type" +msgstr "Activité du compte" + +#: bookwyrm/templates/directory/user_type_filter.html:8 +#, fuzzy +#| msgid "Blocked users" +msgid "BookWyrm users" +msgstr "Comptes bloqués" + +#: bookwyrm/templates/directory/user_type_filter.html:12 +msgid "All known users" +msgstr "" + #: bookwyrm/templates/discover/about.html:7 #, fuzzy, python-format #| msgid "Join %(name)s" @@ -692,17 +720,6 @@ msgstr "Clé Librarything :" msgid "Goodreads key:" msgstr "Clé Goodreads :" -#: bookwyrm/templates/editions.html:5 -#, fuzzy, python-format -#| msgid "Finish \"%(book_title)s\"" -msgid "Editions of %(book_title)s" -msgstr "Éditions de %(book_title)s" - -#: bookwyrm/templates/editions.html:9 -#, python-format -msgid "Editions of \"%(work_title)s\"" -msgstr "Éditions de « %(work_title)s »" - #: bookwyrm/templates/email/html_layout.html:15 #: bookwyrm/templates/email/text_layout.html:2 msgid "Hi there," @@ -769,18 +786,6 @@ msgstr "" msgid "Reset your %(site_name)s password" msgstr "À propos de %(name)s" -#: bookwyrm/templates/error.html:4 -msgid "Oops!" -msgstr "Oups !" - -#: bookwyrm/templates/error.html:8 -msgid "Server Error" -msgstr "Erreur côté serveur" - -#: bookwyrm/templates/error.html:9 -msgid "Something went wrong! Sorry about that." -msgstr "Une erreur s’est produite ; désolé !" - #: bookwyrm/templates/feed/direct_messages.html:8 #, python-format msgid "Direct Messages with %(username)s" @@ -861,6 +866,8 @@ msgid "Updates" msgstr "Mises à jour" #: bookwyrm/templates/feed/feed_layout.html:11 +#: bookwyrm/templates/layout.html:58 +#: bookwyrm/templates/user/books_header.html:3 msgid "Your books" msgstr "Vos livres" @@ -869,14 +876,14 @@ msgid "There are no books here right now! Try searching for a book to get starte msgstr "Aucun livre ici pour l’instant ! Cherchez un livre pour commencer" #: bookwyrm/templates/feed/feed_layout.html:23 -#: bookwyrm/templates/user/shelf.html:24 +#: bookwyrm/templates/user/shelf.html:25 #, fuzzy #| msgid "Read" msgid "To Read" msgstr "Lu" #: bookwyrm/templates/feed/feed_layout.html:24 -#: bookwyrm/templates/user/shelf.html:24 +#: bookwyrm/templates/user/shelf.html:25 #, fuzzy #| msgid "Started reading" msgid "Currently Reading" @@ -884,7 +891,7 @@ msgstr "Commencer la lecture" #: bookwyrm/templates/feed/feed_layout.html:25 #: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:11 -#: bookwyrm/templates/user/shelf.html:24 +#: bookwyrm/templates/user/shelf.html:25 msgid "Read" msgstr "Lu" @@ -933,27 +940,33 @@ msgstr "Livres de %(username)s en %(year)s" msgid "Import Books" msgstr "Importer des livres" -#: bookwyrm/templates/import.html:14 -msgid "Data source" +#: bookwyrm/templates/import.html:16 +#, fuzzy +#| msgid "Data source" +msgid "Data source:" msgstr "Source de données" -#: bookwyrm/templates/import.html:32 +#: bookwyrm/templates/import.html:29 +msgid "Data file:" +msgstr "" + +#: bookwyrm/templates/import.html:37 msgid "Include reviews" msgstr "Importer les critiques" -#: bookwyrm/templates/import.html:37 +#: bookwyrm/templates/import.html:42 msgid "Privacy setting for imported reviews:" msgstr "Confidentialité des critiques importées :" -#: bookwyrm/templates/import.html:41 +#: bookwyrm/templates/import.html:48 msgid "Import" msgstr "Importer" -#: bookwyrm/templates/import.html:46 +#: bookwyrm/templates/import.html:53 msgid "Recent Imports" msgstr "Importations récentes" -#: bookwyrm/templates/import.html:48 +#: bookwyrm/templates/import.html:55 msgid "No recent imports" msgstr "Aucune importation récente" @@ -1010,12 +1023,12 @@ msgstr "Livre" #: bookwyrm/templates/import_status.html:115 #: bookwyrm/templates/snippets/create_status_form.html:10 -#: bookwyrm/templates/snippets/shelf.html:10 +#: bookwyrm/templates/user/shelf.html:73 msgid "Title" msgstr "Titre" #: bookwyrm/templates/import_status.html:118 -#: bookwyrm/templates/snippets/shelf.html:11 +#: bookwyrm/templates/user/shelf.html:74 msgid "Author" msgstr "Auteur ou autrice" @@ -1071,10 +1084,6 @@ msgstr "Chercher" msgid "Main navigation menu" msgstr "Menu de navigation principal " -#: bookwyrm/templates/layout.html:58 -msgid "Your shelves" -msgstr "Vos étagères" - #: bookwyrm/templates/layout.html:61 msgid "Feed" msgstr "Fil d’actualité" @@ -1091,7 +1100,7 @@ msgid "Settings" msgstr "Paramètres de l’instance" #: bookwyrm/templates/layout.html:116 -#: bookwyrm/templates/settings/admin_layout.html:20 +#: bookwyrm/templates/settings/admin_layout.html:24 #: bookwyrm/templates/settings/manage_invite_requests.html:15 #: bookwyrm/templates/settings/manage_invites.html:3 #: bookwyrm/templates/settings/manage_invites.html:15 @@ -1155,7 +1164,7 @@ msgid "BookWyrm is open source software. You can contribute or report issues on msgstr "Bookwyrm est un logiciel libre. Vous pouvez contribuer ou faire des rapports de bogues via GitHub." #: bookwyrm/templates/lists/create_form.html:5 -#: bookwyrm/templates/lists/lists.html:17 +#: bookwyrm/templates/lists/lists.html:19 msgid "Create List" msgstr "Créer une liste" @@ -1221,7 +1230,7 @@ msgid "Anyone can suggest books, subject to your approval" msgstr "N’importe qui peut suggérer des livres, soumis à votre approbation" #: bookwyrm/templates/lists/form.html:31 -#: bookwyrm/templates/moderation/reports.html:11 +#: bookwyrm/templates/moderation/reports.html:24 msgid "Open" msgstr "Ouverte" @@ -1240,6 +1249,7 @@ msgid "Added by %(username)s" msgstr "Messages directs avec %(username)s" #: bookwyrm/templates/lists/list.html:41 +#: bookwyrm/templates/snippets/shelf_selector.html:28 msgid "Remove" msgstr "Supprimer" @@ -1276,20 +1286,6 @@ msgstr "Aucun livre trouvé" msgid "Suggest" msgstr "Suggérer" -#: bookwyrm/templates/lists/lists.html:14 -msgid "Your lists" -msgstr "Vos listes" - -#: bookwyrm/templates/lists/lists.html:32 -#, fuzzy, python-format -#| msgid "See all %(size)s" -msgid "See all %(size)s lists" -msgstr "Voir les %(size)s" - -#: bookwyrm/templates/lists/lists.html:40 -msgid "Recent Lists" -msgstr "Listes récentes" - #: bookwyrm/templates/login.html:4 msgid "Login" msgstr "Connexion" @@ -1403,27 +1399,37 @@ msgstr "" msgid "Resolve" msgstr "" -#: bookwyrm/templates/moderation/reports.html:4 -#: bookwyrm/templates/moderation/reports.html:5 -#: bookwyrm/templates/settings/admin_layout.html:24 +#: bookwyrm/templates/moderation/reports.html:6 +#, fuzzy, python-format +#| msgid "Join %(name)s" +msgid "Reports: %(server_name)s" +msgstr "Listes : %(username)s" + +#: bookwyrm/templates/moderation/reports.html:8 +#: bookwyrm/templates/moderation/reports.html:16 +#: bookwyrm/templates/settings/admin_layout.html:28 #, fuzzy #| msgid "Recent Imports" msgid "Reports" msgstr "Importations récentes" -#: bookwyrm/templates/moderation/reports.html:14 +#: bookwyrm/templates/moderation/reports.html:13 +#, fuzzy, python-format +#| msgid "Join %(name)s" +msgid "Reports: %(server_name)s" +msgstr "Listes : %(username)s" + +#: bookwyrm/templates/moderation/reports.html:27 #, fuzzy #| msgid "Shelved" msgid "Resolved" msgstr "Ajouté à une étagère" -#: bookwyrm/templates/notfound.html:4 bookwyrm/templates/notfound.html:8 -msgid "Not Found" -msgstr "Introuvable" - -#: bookwyrm/templates/notfound.html:9 -msgid "The page you requested doesn't seem to exist!" -msgstr "Il semblerait que la page que vous avez demandée n’existe pas !" +#: bookwyrm/templates/moderation/reports.html:34 +#, fuzzy +#| msgid "No books found" +msgid "No reports found." +msgstr "Aucun livre trouvé" #: bookwyrm/templates/notifications.html:14 msgid "Delete notifications" @@ -1655,16 +1661,23 @@ msgstr "Administration" msgid "Manage Users" msgstr "Gérer les comptes" -#: bookwyrm/templates/settings/admin_layout.html:28 -#: bookwyrm/templates/settings/federation.html:4 +#: bookwyrm/templates/settings/admin_layout.html:19 +#: bookwyrm/templates/settings/user_admin.html:3 +#: bookwyrm/templates/settings/user_admin.html:10 +msgid "Users" +msgstr "" + +#: bookwyrm/templates/settings/admin_layout.html:32 +#: bookwyrm/templates/settings/federation.html:3 +#: bookwyrm/templates/settings/federation.html:5 msgid "Federated Servers" msgstr "Serveurs fédérés" -#: bookwyrm/templates/settings/admin_layout.html:33 +#: bookwyrm/templates/settings/admin_layout.html:37 msgid "Instance Settings" msgstr "Paramètres de l’instance" -#: bookwyrm/templates/settings/admin_layout.html:37 +#: bookwyrm/templates/settings/admin_layout.html:41 #: bookwyrm/templates/settings/site.html:4 #: bookwyrm/templates/settings/site.html:6 #, fuzzy @@ -1672,36 +1685,109 @@ msgstr "Paramètres de l’instance" msgid "Site Settings" msgstr "Paramètres du site" -#: bookwyrm/templates/settings/admin_layout.html:40 +#: bookwyrm/templates/settings/admin_layout.html:44 #: bookwyrm/templates/settings/site.html:13 msgid "Instance Info" msgstr "Information sur l’instance" -#: bookwyrm/templates/settings/admin_layout.html:41 +#: bookwyrm/templates/settings/admin_layout.html:45 #: bookwyrm/templates/settings/site.html:39 msgid "Images" msgstr "Images" -#: bookwyrm/templates/settings/admin_layout.html:42 +#: bookwyrm/templates/settings/admin_layout.html:46 #: bookwyrm/templates/settings/site.html:59 msgid "Footer Content" msgstr "Contenu du pied de page" -#: bookwyrm/templates/settings/admin_layout.html:43 +#: bookwyrm/templates/settings/admin_layout.html:47 #: bookwyrm/templates/settings/site.html:77 msgid "Registration" msgstr "Enregistrement" -#: bookwyrm/templates/settings/federation.html:10 +#: bookwyrm/templates/settings/federated_server.html:7 +msgid "Back to server list" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:12 +msgid "Details" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:15 +#, fuzzy +#| msgid "Software" +msgid "Software:" +msgstr "Logiciel" + +#: bookwyrm/templates/settings/federated_server.html:19 +#, fuzzy +#| msgid "Description:" +msgid "Version:" +msgstr "Description :" + +#: bookwyrm/templates/settings/federated_server.html:23 +#, fuzzy +#| msgid "Status" +msgid "Status:" +msgstr "Statut" + +#: bookwyrm/templates/settings/federated_server.html:30 +#: bookwyrm/templates/user/user_layout.html:50 +msgid "Activity" +msgstr "Activité" + +#: bookwyrm/templates/settings/federated_server.html:33 +#, fuzzy +#| msgid "Username:" +msgid "Users:" +msgstr "Nom d’utilisateur :" + +#: bookwyrm/templates/settings/federated_server.html:36 +#: bookwyrm/templates/settings/federated_server.html:43 +msgid "View all" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:40 +#, fuzzy +#| msgid "Recent Imports" +msgid "Reports:" +msgstr "Importations récentes" + +#: bookwyrm/templates/settings/federated_server.html:47 +#, fuzzy +#| msgid "followed you" +msgid "Followed by us:" +msgstr "s’est abonné(e)" + +#: bookwyrm/templates/settings/federated_server.html:53 +#, fuzzy +#| msgid "followed you" +msgid "Followed by them:" +msgstr "s’est abonné(e)" + +#: bookwyrm/templates/settings/federated_server.html:59 +#, fuzzy +#| msgid "Blocked Users" +msgid "Blocked by us:" +msgstr "Comptes bloqués" + +#: bookwyrm/templates/settings/federation.html:13 msgid "Server name" msgstr "Nom du serveur" -#: bookwyrm/templates/settings/federation.html:11 +#: bookwyrm/templates/settings/federation.html:17 +#, fuzzy +#| msgid "Federated" +msgid "Date federated" +msgstr "Fédéré" + +#: bookwyrm/templates/settings/federation.html:21 msgid "Software" msgstr "Logiciel" -#: bookwyrm/templates/settings/federation.html:12 +#: bookwyrm/templates/settings/federation.html:24 #: bookwyrm/templates/settings/manage_invite_requests.html:33 +#: bookwyrm/templates/settings/user_admin.html:32 msgid "Status" msgstr "Statut" @@ -1870,6 +1956,47 @@ msgstr "Demandes d’abonnement" msgid "Registration closed text:" msgstr "Texte affiché lorsque les enregistrements sont clos :" +#: bookwyrm/templates/settings/user_admin.html:7 +#, python-format +msgid "Users: %(server_name)s" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:20 +#, fuzzy +#| msgid "Username:" +msgid "Username" +msgstr "Nom d’utilisateur :" + +#: bookwyrm/templates/settings/user_admin.html:24 +#, fuzzy +#| msgid "added" +msgid "Date Added" +msgstr "a ajouté" + +#: bookwyrm/templates/settings/user_admin.html:28 +msgid "Last Active" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:36 +#, fuzzy +#| msgid "Remove" +msgid "Remote server" +msgstr "Supprimer" + +#: bookwyrm/templates/settings/user_admin.html:45 +#, fuzzy +#| msgid "Activity" +msgid "Active" +msgstr "Activité" + +#: bookwyrm/templates/settings/user_admin.html:45 +msgid "Inactive" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:50 +msgid "Not set" +msgstr "" + #: bookwyrm/templates/snippets/block_button.html:5 msgid "Block" msgstr "Bloquer" @@ -1928,7 +2055,7 @@ msgid "Review:" msgstr "Critique" #: bookwyrm/templates/snippets/create_status_form.html:29 -#: bookwyrm/templates/snippets/shelf.html:17 +#: bookwyrm/templates/user/shelf.html:78 msgid "Rating" msgstr "Note" @@ -2002,6 +2129,26 @@ msgstr "Ajouter le statut aux favoris" msgid "Un-like status" msgstr "Supprimer le statut des favoris" +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:7 +#, fuzzy +#| msgid "Show less" +msgid "Show filters" +msgstr "Replier" + +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:9 +msgid "Hide filters" +msgstr "" + +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:19 +msgid "Apply filters" +msgstr "" + +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:23 +#, fuzzy +#| msgid "Clear search" +msgid "Clear filters" +msgstr "Vider la requête" + #: bookwyrm/templates/snippets/follow_button.html:12 msgid "Follow" msgstr "S’abonner" @@ -2135,32 +2282,32 @@ msgstr "Laisser une note" msgid "Rate" msgstr "Noter" -#: bookwyrm/templates/snippets/readthrough.html:7 +#: bookwyrm/templates/snippets/readthrough.html:8 msgid "Progress Updates:" msgstr "Progression :" -#: bookwyrm/templates/snippets/readthrough.html:11 +#: bookwyrm/templates/snippets/readthrough.html:12 msgid "finished" msgstr "terminé" -#: bookwyrm/templates/snippets/readthrough.html:14 +#: bookwyrm/templates/snippets/readthrough.html:15 msgid "Show all updates" msgstr "Montrer toutes les progressions" -#: bookwyrm/templates/snippets/readthrough.html:30 +#: bookwyrm/templates/snippets/readthrough.html:31 msgid "Delete this progress update" msgstr "Supprimer cette mise à jour" -#: bookwyrm/templates/snippets/readthrough.html:40 +#: bookwyrm/templates/snippets/readthrough.html:41 msgid "started" msgstr "commencé" -#: bookwyrm/templates/snippets/readthrough.html:46 -#: bookwyrm/templates/snippets/readthrough.html:60 +#: bookwyrm/templates/snippets/readthrough.html:47 +#: bookwyrm/templates/snippets/readthrough.html:61 msgid "Edit read dates" msgstr "Modifier les date de lecture" -#: bookwyrm/templates/snippets/readthrough.html:50 +#: bookwyrm/templates/snippets/readthrough.html:51 #, fuzzy #| msgid "Delete these read dates?" msgid "Delete these read dates" @@ -2226,45 +2373,11 @@ msgstr "par %(author)s" msgid "Import book" msgstr "Importer le livre" -#: bookwyrm/templates/snippets/shelf.html:12 -msgid "Published" -msgstr "Publié" - -#: bookwyrm/templates/snippets/shelf.html:13 -msgid "Shelved" -msgstr "Ajouté à une étagère" - -#: bookwyrm/templates/snippets/shelf.html:14 -msgid "Started" -msgstr "Commencé" - -#: bookwyrm/templates/snippets/shelf.html:15 -msgid "Finished" -msgstr "Terminé" - -#: bookwyrm/templates/snippets/shelf.html:16 -msgid "External links" -msgstr "Liens externes" - -#: bookwyrm/templates/snippets/shelf.html:44 -msgid "OpenLibrary" -msgstr "OpenLibrary" - -#: bookwyrm/templates/snippets/shelf.html:61 -msgid "This shelf is empty." -msgstr "Cette étagère est vide" - -#: bookwyrm/templates/snippets/shelf.html:67 -msgid "Delete shelf" -msgstr "Supprimer l’étagère" - #: bookwyrm/templates/snippets/shelf_selector.html:4 -msgid "Change shelf" -msgstr "Changer d’étagère" - -#: bookwyrm/templates/snippets/shelf_selector.html:27 -msgid "Unshelve" -msgstr "Enlever de l’étagère" +#, fuzzy +#| msgid "Your books" +msgid "Move book" +msgstr "Vos livres" #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:5 #, python-format @@ -2272,7 +2385,7 @@ msgid "Finish \"%(book_title)s\"" msgstr "Terminer « %(book_title)s »" #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:5 -#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:33 +#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:35 #, fuzzy #| msgid "Updated:" msgid "Update progress" @@ -2299,6 +2412,12 @@ msgstr "Terminer la lecture" msgid "Want to read" msgstr "Je veux le lire" +#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:48 +#, fuzzy, python-format +#| msgid "Join %(name)s" +msgid "Remove from %(name)s" +msgstr "Listes : %(username)s" + #: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:5 #, python-format msgid "Start \"%(book_title)s\"" @@ -2371,6 +2490,18 @@ msgstr "Plus d’options" msgid "Switch to this edition" msgstr "Changer vers cette édition" +#: bookwyrm/templates/snippets/table-sort-header.html:6 +#, fuzzy +#| msgid "Started reading" +msgid "Sorted asccending" +msgstr "Lecture commencée le" + +#: bookwyrm/templates/snippets/table-sort-header.html:10 +#, fuzzy +#| msgid "Started reading" +msgid "Sorted descending" +msgstr "Lecture commencée le" + #: bookwyrm/templates/snippets/tag.html:14 msgid "Remove tag" msgstr "Supprimer le tag" @@ -2380,6 +2511,12 @@ msgstr "Supprimer le tag" msgid "Books tagged \"%(tag.name)s\"" msgstr "Livres tagués « %(tag.name)s »" +#: bookwyrm/templates/user/books_header.html:5 +#, fuzzy, python-format +#| msgid "%(username)s has no followers" +msgid "%(username)s's books" +msgstr "Livres de %(username)s en %(year)s" + #: bookwyrm/templates/user/create_shelf_form.html:5 #: bookwyrm/templates/user/create_shelf_form.html:22 msgid "Create Shelf" @@ -2426,56 +2563,62 @@ msgstr "Listes : %(username)s" msgid "Create list" msgstr "Créer une liste" -#: bookwyrm/templates/user/shelf.html:9 -msgid "Your Shelves" -msgstr "Vos étagères" - -#: bookwyrm/templates/user/shelf.html:11 -#, python-format -msgid "%(username)s: Shelves" -msgstr "%(username)s : Étagères" - -#: bookwyrm/templates/user/shelf.html:33 +#: bookwyrm/templates/user/shelf.html:34 msgid "Create shelf" msgstr "Créer l’étagère" -#: bookwyrm/templates/user/shelf.html:54 +#: bookwyrm/templates/user/shelf.html:55 msgid "Edit shelf" msgstr "Modifier l’étagère" +#: bookwyrm/templates/user/shelf.html:75 +msgid "Shelved" +msgstr "Ajouté à une étagère" + +#: bookwyrm/templates/user/shelf.html:76 +msgid "Started" +msgstr "Commencé" + +#: bookwyrm/templates/user/shelf.html:77 +msgid "Finished" +msgstr "Terminé" + +#: bookwyrm/templates/user/shelf.html:121 +msgid "This shelf is empty." +msgstr "Cette étagère est vide" + +#: bookwyrm/templates/user/shelf.html:127 +msgid "Delete shelf" +msgstr "Supprimer l’étagère" + #: bookwyrm/templates/user/user.html:15 msgid "Edit profile" msgstr "Modifier le profil" -#: bookwyrm/templates/user/user.html:26 -#: bookwyrm/templates/user/user_layout.html:68 -msgid "Shelves" -msgstr "Étagères" - -#: bookwyrm/templates/user/user.html:31 -#, python-format -msgid "See all %(size)s" +#: bookwyrm/templates/user/user.html:33 +#, fuzzy, python-format +#| msgid "See all %(size)s" +msgid "View all %(size)s" msgstr "Voir les %(size)s" -#: bookwyrm/templates/user/user.html:44 -#, python-format -msgid "See all %(shelf_count)s shelves" -msgstr "Voir les %(shelf_count)s étagères" +#: bookwyrm/templates/user/user.html:46 +msgid "View all books" +msgstr "" -#: bookwyrm/templates/user/user.html:56 +#: bookwyrm/templates/user/user.html:58 #, python-format msgid "Set a reading goal for %(year)s" msgstr "Définir un défi lecture pour %(year)s" -#: bookwyrm/templates/user/user.html:62 +#: bookwyrm/templates/user/user.html:64 msgid "User Activity" msgstr "Activité du compte" -#: bookwyrm/templates/user/user.html:65 +#: bookwyrm/templates/user/user.html:67 msgid "RSS feed" msgstr "Flux RSS" -#: bookwyrm/templates/user/user.html:76 +#: bookwyrm/templates/user/user.html:78 msgid "No activities yet!" msgstr "Aucune activité pour l’instant !" @@ -2483,14 +2626,16 @@ msgstr "Aucune activité pour l’instant !" msgid "Follow Requests" msgstr "Demandes d’abonnement" -#: bookwyrm/templates/user/user_layout.html:50 -msgid "Activity" -msgstr "Activité" - #: bookwyrm/templates/user/user_layout.html:56 msgid "Reading Goal" msgstr "Défi lecture" +#: bookwyrm/templates/user/user_layout.html:68 +#, fuzzy +#| msgid "Book" +msgid "Books" +msgstr "Livre" + #: bookwyrm/templates/user/user_preview.html:13 #, python-format msgid "Joined %(date)s" @@ -2518,6 +2663,49 @@ msgstr "" msgid "A password reset link sent to %s" msgstr "" +#~ msgid "Your shelves" +#~ msgstr "Vos étagères" + +#~ msgid "Your lists" +#~ msgstr "Vos listes" + +#, fuzzy, python-format +#~| msgid "See all %(size)s" +#~ msgid "See all %(size)s lists" +#~ msgstr "Voir les %(size)s" + +#~ msgid "Recent Lists" +#~ msgstr "Listes récentes" + +#~ msgid "Published" +#~ msgstr "Publié" + +#~ msgid "External links" +#~ msgstr "Liens externes" + +#~ msgid "OpenLibrary" +#~ msgstr "OpenLibrary" + +#~ msgid "Change shelf" +#~ msgstr "Changer d’étagère" + +#~ msgid "Unshelve" +#~ msgstr "Enlever de l’étagère" + +#~ msgid "Your Shelves" +#~ msgstr "Vos étagères" + +#, python-format +#~ msgid "%(username)s: Shelves" +#~ msgstr "%(username)s : Étagères" + +#~ msgid "Shelves" +#~ msgstr "Étagères" + +#, python-format +#~ msgid "See all %(shelf_count)s shelves" +#~ msgstr "Voir les %(shelf_count)s étagères" + #~ msgid "Send follow request" #~ msgstr "Envoyer une demande d’abonnement" @@ -2660,9 +2848,6 @@ msgstr "" #~ msgid "Added by" #~ msgstr "Ajouté par" -#~ msgid "added" -#~ msgstr "a ajouté" - #~ msgid "suggested adding" #~ msgstr "a suggéré l’ajout de" diff --git a/locale/zh_CN/LC_MESSAGES/django.mo b/locale/zh_CN/LC_MESSAGES/django.mo index 62c4cd964f8c11bc2d7d7c194ca333dec11d6d49..819542795f872411dcea455b2aa44f520118794a 100644 GIT binary patch delta 9564 zcmZA634Bgh8prXQh!7-*EkTJb*4XzgNX1r4LM^dxkyt`dOL1$fQq_vuLMUpj{Z&=e zQW>32TT8WC?J%YbwYFNzR1Nd{zxT;}%$!f3e9v>vz2}^J?vk`Ucq8D$p90)V;Xz9s z%H9CSDUI7h9j8;E<1{a&R>v7p(Q(FO3eLrg7=}YCIZic9$6}a;I>m>z@iZ1hC)#mJ zKp}hvt6+DGNB(i0Ro3wlmZ#$~mcv|?y@4xXBzZHek3CQWE{Cf2UrA;VqrXo!T1yA!JFn?%uD_Vb7Ai4 zj#B_bQ0>Jr1k0j!qN?RhP&*ihd6?fxppqZ^U>zKens5bbL2FS1Z$zEA*Ye}2g`TqZ z?@>E<2el(bYIqBdM~&A7!?6eIxDn{;iWX3j85o3_s1>iZd>7^?--lYzG2}6EPFnw$ zScv=rzJk|LJ2x)IdnRUM0rIt|g=|NS?~7so)p3Fb4SdE9IBy4BL=AikwZO+_a7}N5 zFf$5u3+tk;urF%7G}MF>F%+kvE@S~}-eon}f30+bb-ZUC$FV8xr;vY~kXqbFY>Zk+ zFVuMhu^^_S2A+kQXc21S)mRiape8(E{eM9{#OGWp5mauVUY{V|9UT~j(HMgTu{$=# zp{NC9S^GxRgzs5?0`)qcLoMhU>LI*mJ~2ah&@_)*gi1atQPxl$HDP0GZ;vI(6HyBo zgJHNCwUGVh5%Z+=pF+I_7f}nif`#!{)H4>y6B?!WKMxfRSOs-LBP@iiP*>6owUx=J zg^fWCI0JRwtClafd>!h%-KYhgM4f*MHU3wqx8{<+o%{bA745(usI3gD=M5Z=`XH4< zt@v5g03FN()I*kpnqZJQ0=2bi=zr+Y|Ind!U>WK{wqT^*{{vJs@E6wc9qI~hpauxw zv!^R9h#H^_s=qes3gc1la~G_PW3ec%KrMU+YMi~OiH~6toaCc9+TsPM0T!bsScO{PR@ALIggWjtYMk?^1$<}zjGEWIM@2jE z81*cKHgTLv_zY^Lab_pf35lqQ`l1#v1~t)S)Rwwf5i?Lb@;++ZbEvoM2h;`LM#giU zyHvEof1?HpVyo`z0E{z=?>Juhp;kcV|BcVIzFPMx6mlmEv$uF zP%P^F0jT3fw`Biyz(g9fRdZ20un;xDCTrh?THt1KCkzG8<|SKg0GC}vi!?AI4#L0V`oF`&|Ktne(wc`3}^rI%8fz|670+X^(u~`%bjPdgQ}#JZ5VDRSDz9 z$XTc@zmJo#Q#U5XY~(km6O`cnOJ{S`mL_9S9F5^P3$;~YfnVo`#zQrMoo}n?JuGxnqmD5QLo<;%QslQ4X4xY9;Bjs z*SLpwFT0=y9EV!S49n-58P>iQ{a0e`yHOK=X#Jm>U!ZpIg7x3TGUN}jnBM=$7rcpT znGI13Xkm6VdzwSdapnxvv+)|%#1p88?l;s0l<4VM1~qOa)I#g}bMAjDJD{^2(A^wl zjzPUXFQHcc7P4~3hq@J?px&CZs0nYO7IN3}3cb9EtDz=tWO-XGq4z(*8dA+k<{Wb| zY75s`zQ^(-=2>h15w(yz*aSoQ)u##DVG-3!XD?f)i?-$es-8Y}0z886uy#6q=SdzW}rD@Q>Rn3N|3F6Fd zsD%tbT~Rvf_-WQY-}2SwR&zgUoD-;poyBPU24nDvOGQ@@)7LwoIciJeP*-wzaKcFV~6*b`l%X9bhzK{j5IPK+7$2B(HSgXXFolygHN8OUas0F2> zCZ38KXr8sNF?XVN=m=_u&RY9-7(spm_4Wku$AZSG;ORQGsc51Ws84BI)C#+z2JC63 zn$uCgbl$`&=tB+ocWb|fIG|5PekaTaRg&6e-9{D9?0 zE&l}Taoibe4;tv57lx&1FNV5Rbx;@9#_Wh%NCIlX!=y_kor)%Q%~wzdypDPsGEx1z zQSI-eZc#RB!Y@$$w^9A~EDs#y9T$RHND0(&Wl_&oG`iZtrc^Z05Y)gf>Q=mtI$;HB zfVGxyHg}>Xe9!#I+D}`49yQ(%mft}=OOH|Gh7D%_wUT0ky@sl0ZH%V95o&_|);`iq zNBto(1$BNF>XW+%wZ)fF6a0=k|Dl;@h;+=P*2Op zqi)U1sPmRtdltr!Z$n-AH>jPvg1YkC=3`7C4|9ile=ZL~O*9iV;3CvRxfFFpYps8; zc@(vv&#(^uj5@F6FmIf4W=&LkGpvkl%v3B%?q*QYN_U`EcnG8LQ`CZPq89Rp_2(My z4Oj%Vu#%R?nDtT5PBYYb@u+dTTK_=QxXGTbGl7b>XeRQ}a^|BJbj7@jnkX>YJFyrB zlb1KEVrBB$*dBXfTU=*eMct~H5#IYBhuXQm{+#Ac6#YA3#+a?FKM_MXZ=jiK?GsQtG!ujM{x77WD_&x*Lv7J6)C7l7C!9uodcQ?| z$?jk@=1cXCuY+1}OUv7$-i`!J#Nn8R2Qdb#jpY7opk7quIMkIdz$DB>O?V%*L!qO* zi8`Bo%w*I;C!iKE4?}Pf^5>|m9Z?TgH*AKZQR8kkeHcLQ@*_X7MwMDLw8R0#CL)$-MVrb(>WM@( z>c12JBI1d$v^B+D#0KiWpzifJ>L24Ef=zO^6M=+EKl(}$D*1>NgxiCL##DOY^8`EY z{6@S>Hz)zwW4iPSxd(| zgvt*@F4c*Ng#IVx9@1`vN)-7{?2J+PfZ%UX|Nm&*McbRy^)1LLldUoxSJ0oGGr#V! zik|YB#5y|l&Z|5}%p_NN)htV04`xoOXYFI~b*nEhdvVZpBAL7sQJdiJQD+L#NY}rS z=wk=Zrmj+!{IOScW?0`|@<{5@c$k=F?JwYZtA9j$TdTi8eXG@}I23N~79FQ9;V<*gqDjkT{{u;lgY5SCVeN4d_#DtvVu#45VU?8!GC`xQ6 zUM8L{Ka#l5^CO0sMQk8)$|w%rN}^vJ<%yF-9FbH0q*8%nrr}Ux8F7esx=f`%iFnZ( z%TwP&_Xfo zROWd&4e4u4tRna~b1Ursc&d|WRQZxPNDLuL&~_95LgbV?R7TT&3iXSq0rg~DMO34{ z9#!fSO~_S>5p#(IC6w1aoRPR%_y2FiK4Kf8veQmnLwx{I&~o+hH^_fW8)`-9drLw6 zIB|*iiBLI0MA*4C@Llpb#IHncLdF05O`>xF$sjwpoWINaQQGRo>AOoklvrYI`*D-i z+v6J?(-7-oOKVS{u2R5M{WMXI{BzB}l*(r`Od$S2EFs^G&58F3l`}*WVj!V%f|x{H zAX07Muc)g`^>F6XKGy1@IeCa3JIw5>_x~212Z(Qo%EZfrN;n5qC-@gvX9WK2Z{>bc zPo;j1c#(QHBBwm05=UNz=uiBcJd>zMJ(zg9^d`^k@}tC4t)EcuPb{ZBFY&DPy@WN1 ze-aak+4SYdqC`jPD(8r$L=|G7wf&vCN(T95f9-#NxSQ?3dAOFo8`l3e`9dPIM*)6VK7+!?VPz1f`~Pm7ghe{1q3eiL!$j zVgrQYc%qeZ-`q;m1AP%yz7O{0skJM>_fhSnKwph|yMldJo8%8z@~G+8zRS%v1^7<2 zC>P*+&@wy7mmPN?u+-qRw2|YT-f0ulqtb^B8k#aDIcm_52`Mk8Oqk+39Dgcg$%(Ed zeEYlJ3HI&lnGxVi?A<-kw=d~%@RDhR?<|=#(G^bhkjbH#Wy>xQSg5N!h<#v delta 10295 zcmZ|U30zfm`p5ADA*djtxLb+}xT3g)JFdB8rlPqf0-~S@DvG;T6Zdl8a?ix2tjMdm z4QjSHGpS?$YO->i$puB78q2h7%=vupd1{=Q*Z;ic;r)Al`}v)7FE|xk^*eIY&vUVC zz#50^T|dWp93w*=XMn%sG*H5|Fv@YVaS_hL2S^uZZnWdn!BELf|YSRHp3;TiBDi8p2sHm0JAW* zuIo0;BoAY^3FyIT_zAXWeWzD_$7xQ*GNiBb0T#n=F&uxuGFYO4;{;+=48odbJuFV% z1RulJ7=rOu-xEua_eY&bqUBi_%KFY!3Yu^s2IF#UjGIsko<(iwGYrA6P>o#7fu-b#w`+epwie zGf)e>iY?HK+Q==dzk^yZfEPgh%A!6+HBlREfqE%DovqNz9Ee&d8B1c8<1q@W2JpcZI~;n>FNd!tTb0P0AS zQ4^0teNlN(8{Uo@zst-=y>y3B^B*@qM4j*%ETzx?mlX6&uA@%i4(d(<`Bu_5UIo;| zv8ei%SRT8hHk63k&{)*?8CV{dq8`N#)W>`;*2c3~4)3BzI}d)+ohS^oKs6kK4J}`c zT3`cefjy`VA4QEnW$mX?cl;IV1iwZt^n=xxjB`&S47IU}ah$&fRHs4{MxpY?sDW{) zqw0zJ6bwRbWRjVSI+<1GCM-*yhZ=tf^?U3LYMvYBJ=8h@EjWK2VM$(L-AN_X9X^4Y zs1a(xj#v%*U<{5!Exg6*ccI38h(PV7qc&F1Bs}UNJG6NlQ9Yxpf>!5c>vYF0DUKdz7s*MbII}|h zQGL_`UD5ZkL)~#QYT`81M#rNj%t38nCTiR~YhQ!9kqxLj-(~p$)Qz0<Ge8 zrr{>~j;f8jKsZKIUj?V%$0 z%|97cKfArh-QfajSb^nf*npZSA2snI)X98^z7xPf-;X-U z_fZR$eu_WEu@-7WiKvZcSU%a8^ZawIVHx_Ky>-aPVhkuiZSXyO20y}HSh=g4r=d3X z4*Fve>SS)A7QT~aA5sdTWWN;e!&m2!K=5RcE1V5vGIhE#iHBTM0BQ_%+`Yg|1N40{As<;*PmLJ7b zJdaV>j`OR9BT#SsLd!Q}P4ah9kM4?jA2m-{ANO}l4Av&^i%oGFX5*WEIDZvt_H|v0 zI{K0cjxz-lF&@t&e=|6hXsn05QAa-=%i$c=GZ^$uiOo`YIorqwS-Ews|wx1lz?%kqPkAH$jY{GX*zo(??+xX&^f zHQ_wehE`hsvbodrTKfsq9i6iJk5L=AXzjPm?@^EFzO|PcsNbSI|Ed(UfmWymdz%AL z8yRMfF{haG%$4R=jHCZvY=~Dd62tkdY2FrQJZj#jq(1)%6tv^v){u!BFx~PO%w^^# ztVH`BtcE91H}Ey;(cD9QnjWI&smf)%Q`=B;35ItHr)he>FBKd5~*PD6fKJz&0 zC{J7dSIcji53Rl|-!|Gvb!>^PQR_{@aGX1Y^Vg2nQK6l0LG5g(=|z3foIv&a18U)O zmS4sQ@*Aje!9(45p%Ut)tBG2$32J{<~7BK*LaL$Tp{$bIgUP36`NA$!64s z@=yyOK+SW~>c23HP$%>g>V!guyW=WgC4K&*DO95&9yQT0a|~*sDX6d5*{C~Nf|_up znP(nC{WAIp>)YduE2G9YN6p*D^6n{|zdH7&LK_%n4I@!^Fc$T)O-3E*64agSM~yp& zdNdbN3lyQ|`G@5{nh#M62aa$JNA-^w!TGD9HWiw%nRV!hdbyr86Hx=QP#emze6hI# zqp4qyn(w&PpEf_iTGW4u8vhIGE4i#E)jjeKs0I3=CKzI-p-y6wss3tTgzCT6+-&Z! z_SaD-b;$Bh(f6oO8@-2>(Nk@t`{#5E)Pw_33nXF;W}r@H1L|mZqfRE@Jb``5&tY|} zHOd{|2?NP{qi$e;Y_EDc%5<4ku8&O+3V*I;9O8#TdYYrkRMv-*HE_iwsjvnf`g z{}9xMb5I-2#Ry!Dy0HVO4W0D0^ZZX!&_WkbJG*T8UGo9zB@0M*2bM-nTp2a4E^6Xf zvn>XZ_dve>oxZ3Iy=4}l*7*p1zyB{%&_p-Q@31!cPuLA>jONcT9Bt;Kp5zqJs@DzGf@h1wpgFmD11kAf;i43>>aV$Z@ zYf{7pT$-g9w|Qn#Pb zo!7>Wgs$rPLHP^G^)#`S{AJ9tK20gNqx=bW!HuYErhc%#?>~#G{I(5HSyxZXKcE~* z@LSbsL+E+FP825|Cv@q1q8I(2vN0olwJbuu(8@oWv6xQ%WWuwaAOECcEVdx#5M``m zIKE1*OTWEhDYqpWP<|a>Py^Ry;;+O?q6TrFc=YN_->Fv7ANvyhsO#p({m&<9LZVB5 zhCN4IBlHsSPc`Q<@ffk5c=S5Kn2uJl*~UNGuJ0ATzkJu9_{lFk-!+213n=M(;*`$+ zTesf#7T2R3Ws|)_IhFEA%R`Y*m(vt4;#K0;YpV5$r=kw!XK|1jEr3o_O9#Yf7@ z|4b}$>wN30&iR-qM;xKycbI_lh`N+J5xVxdgM8&7{Ct^wAhCyXIXs4YiIS9?<8ngZ zlGBM=)O97M5dXed|Au8zsPFzY#6w~Z@qh?tz}vV9TM{o(&LVX6BvPo;RUbdZw=e?3 zh`Yp_#4zg82>rihrN~#}IO4d6A3sqsnt1feAgN58AmZtB2tOs>psee>%WpsVwc#Yv z_LbF?qP&XoB21n{dfOPOCzmIWicX1ZFcQP{bBh7@f>Y|#Ltw2 z@mH8XH03>nm-v#(MnnOz)jF)k6r!Sysc-d@aW|p$egD{$xhm7~IG!fnr92FE)u7ym zc%JeW>`mOEdk~9QGm>Qr?2PLS3Ab_!P06HeD-RoL6xvag<21whp@geCsd~OViMd=tLYN zGO24wyh!|mh@kEdI0XNWy3P_uh_OTuqBw0Y5tRvDlZfw$*U0mUU6i{Lx|-0fqIcIt%8q_L@Wvs*XHA-rlIk7b=z0IpE>k9IWJW|rdghpH z@0rH=fk8dflP4rinChLLOKLK>efz3O&XD&IVvJ4Wny|x`oyVjtvgA!x1jUc68~{9R}Swi z-15$*W?$z^k(7ayuu^Py^#a@`v>n>TQqlf z(W+yGOY^*w2OazEY*xh%zgyz!!F^X39J#h=;i{;l8=;$KUq8O$+J@Y#3)fwLWw*Cf z@*=of+w! IpAi@MFNbI~7ytkO diff --git a/locale/zh_CN/LC_MESSAGES/django.po b/locale/zh_CN/LC_MESSAGES/django.po index 83f128e9..2f547f10 100644 --- a/locale/zh_CN/LC_MESSAGES/django.po +++ b/locale/zh_CN/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.1.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-03-28 22:42+0000\n" +"POT-Creation-Date: 2021-03-31 09:22-0700\n" "PO-Revision-Date: 2021-03-20 00:56+0000\n" "Last-Translator: Kana \n" "Language-Team: Mouse Reeve \n" @@ -49,44 +49,64 @@ msgstr "%(count)d 次使用" msgid "Unlimited" msgstr "ä¸å—é™" -#: bookwyrm/models/fields.py:25 +#: bookwyrm/models/fields.py:24 #, python-format msgid "%(value)s is not a valid remote_id" msgstr "%(value)s ä¸æ˜¯æœ‰æ•ˆçš„ remote_id" -#: bookwyrm/models/fields.py:34 bookwyrm/models/fields.py:47 +#: bookwyrm/models/fields.py:33 bookwyrm/models/fields.py:42 #, python-format msgid "%(value)s is not a valid username" msgstr "%(value)s ä¸æ˜¯æœ‰æ•ˆçš„用户å" -#: bookwyrm/models/fields.py:170 bookwyrm/templates/layout.html:157 +#: bookwyrm/models/fields.py:165 bookwyrm/templates/layout.html:157 msgid "username" msgstr "用户å" -#: bookwyrm/models/fields.py:175 +#: bookwyrm/models/fields.py:170 msgid "A user with that username already exists." msgstr "å·²ç»å­˜åœ¨ä½¿ç”¨è¯¥ç”¨æˆ·å的用户。" -#: bookwyrm/settings.py:148 +#: bookwyrm/settings.py:150 msgid "English" msgstr "English(英语)" -#: bookwyrm/settings.py:149 +#: bookwyrm/settings.py:151 msgid "German" msgstr "Deutsch(德语)" -#: bookwyrm/settings.py:150 +#: bookwyrm/settings.py:152 msgid "Spanish" msgstr "Español(西ç­ç‰™è¯­ï¼‰" -#: bookwyrm/settings.py:151 +#: bookwyrm/settings.py:153 msgid "French" msgstr "Français(法语)" -#: bookwyrm/settings.py:152 +#: bookwyrm/settings.py:154 msgid "Simplified Chinese" msgstr "简体中文" +#: bookwyrm/templates/404.html:4 bookwyrm/templates/404.html:8 +msgid "Not Found" +msgstr "未找到" + +#: bookwyrm/templates/404.html:9 +msgid "The page you requested doesn't seem to exist!" +msgstr "你请求的页é¢ä¼¼ä¹Žå¹¶ä¸å­˜åœ¨ï¼" + +#: bookwyrm/templates/500.html:4 +msgid "Oops!" +msgstr "å“Žå‘€ï¼" + +#: bookwyrm/templates/500.html:8 +msgid "Server Error" +msgstr "æœåŠ¡å™¨é”™è¯¯" + +#: bookwyrm/templates/500.html:9 +msgid "Something went wrong! Sorry about that." +msgstr "æŸäº›ä¸œè¥¿å‡ºé”™äº†ï¼å¯¹ä¸èµ·å•¦ã€‚" + #: bookwyrm/templates/author.html:16 bookwyrm/templates/author.html:17 msgid "Edit Author" msgstr "编辑作者" @@ -110,90 +130,65 @@ msgstr "作者" msgid "Edit Book" msgstr "编辑书目" -#: bookwyrm/templates/book/book.html:45 +#: bookwyrm/templates/book/book.html:49 #: bookwyrm/templates/book/cover_modal.html:5 msgid "Add cover" msgstr "添加å°é¢" -#: bookwyrm/templates/book/book.html:55 +#: bookwyrm/templates/book/book.html:59 msgid "ISBN:" msgstr "ISBN:" -#: bookwyrm/templates/book/book.html:62 +#: bookwyrm/templates/book/book.html:66 #: bookwyrm/templates/book/edit_book.html:211 msgid "OCLC Number:" msgstr "OCLC å·:" -#: bookwyrm/templates/book/book.html:69 +#: bookwyrm/templates/book/book.html:73 #: bookwyrm/templates/book/edit_book.html:215 msgid "ASIN:" msgstr "ASIN:" -#: bookwyrm/templates/book/book.html:79 -#, python-format -msgid "%(format)s, %(pages)s pages" -msgstr "%(format)s, %(pages)s 页" - -#: bookwyrm/templates/book/book.html:81 -#, python-format -msgid "%(pages)s pages" -msgstr "%(pages)s 页" - -#: bookwyrm/templates/book/book.html:86 -#, python-format -msgid "Published %(date)s by %(publisher)s." -msgstr "在 %(date)s ç”± %(publisher)s 出版。" - -#: bookwyrm/templates/book/book.html:88 -#, python-format -msgid "Published %(date)s" -msgstr "于 %(date)s 出版" - -#: bookwyrm/templates/book/book.html:90 -#, python-format -msgid "Published by %(publisher)s." -msgstr "ç”± %(publisher)s 出版。" - -#: bookwyrm/templates/book/book.html:95 +#: bookwyrm/templates/book/book.html:82 msgid "View on OpenLibrary" msgstr "在 OpenLibrary 查看" -#: bookwyrm/templates/book/book.html:104 +#: bookwyrm/templates/book/book.html:91 #, python-format msgid "(%(review_count)s review)" msgid_plural "(%(review_count)s reviews)" msgstr[0] "(%(review_count)s 则书评)" -#: bookwyrm/templates/book/book.html:110 +#: bookwyrm/templates/book/book.html:97 msgid "Add Description" msgstr "添加æè¿°" -#: bookwyrm/templates/book/book.html:117 +#: bookwyrm/templates/book/book.html:104 #: bookwyrm/templates/book/edit_book.html:101 #: bookwyrm/templates/lists/form.html:12 msgid "Description:" msgstr "æè¿°:" -#: bookwyrm/templates/book/book.html:121 +#: bookwyrm/templates/book/book.html:108 #: bookwyrm/templates/book/edit_book.html:225 #: bookwyrm/templates/edit_author.html:78 bookwyrm/templates/lists/form.html:42 -#: bookwyrm/templates/preferences/edit_user.html:68 +#: bookwyrm/templates/preferences/edit_user.html:70 #: bookwyrm/templates/settings/site.html:93 -#: bookwyrm/templates/snippets/readthrough.html:64 +#: bookwyrm/templates/snippets/readthrough.html:65 #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:42 #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:42 #: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:34 msgid "Save" msgstr "ä¿å­˜" -#: bookwyrm/templates/book/book.html:122 bookwyrm/templates/book/book.html:171 +#: bookwyrm/templates/book/book.html:109 bookwyrm/templates/book/book.html:158 #: bookwyrm/templates/book/cover_modal.html:32 #: bookwyrm/templates/book/edit_book.html:226 #: bookwyrm/templates/edit_author.html:79 #: bookwyrm/templates/moderation/report_modal.html:32 #: bookwyrm/templates/snippets/delete_readthrough_modal.html:17 #: bookwyrm/templates/snippets/goal_form.html:32 -#: bookwyrm/templates/snippets/readthrough.html:65 +#: bookwyrm/templates/snippets/readthrough.html:66 #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:43 #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:43 #: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:35 @@ -201,72 +196,72 @@ msgstr "ä¿å­˜" msgid "Cancel" msgstr "å–消" -#: bookwyrm/templates/book/book.html:131 +#: bookwyrm/templates/book/book.html:118 #, python-format msgid "%(count)s editions" msgstr "%(count)s 个版本" -#: bookwyrm/templates/book/book.html:139 +#: bookwyrm/templates/book/book.html:126 #, python-format msgid "This edition is on your %(shelf_name)s shelf." msgstr "此版本在你的 %(shelf_name)s 书架上。" -#: bookwyrm/templates/book/book.html:145 +#: bookwyrm/templates/book/book.html:132 #, python-format msgid "A different edition of this book is on your %(shelf_name)s shelf." msgstr "本书的 å¦ä¸€ä¸ªç‰ˆæœ¬ 在你的 %(shelf_name)s 书架上。" -#: bookwyrm/templates/book/book.html:154 +#: bookwyrm/templates/book/book.html:141 msgid "Your reading activity" msgstr "你的阅读活动" -#: bookwyrm/templates/book/book.html:156 +#: bookwyrm/templates/book/book.html:143 msgid "Add read dates" msgstr "添加阅读日期" -#: bookwyrm/templates/book/book.html:161 +#: bookwyrm/templates/book/book.html:148 msgid "You don't have any reading activity for this book." msgstr "你还没有任何这本书的阅读活动。" -#: bookwyrm/templates/book/book.html:168 +#: bookwyrm/templates/book/book.html:155 msgid "Create" msgstr "创建" -#: bookwyrm/templates/book/book.html:190 +#: bookwyrm/templates/book/book.html:177 msgid "Tags" msgstr "标签" -#: bookwyrm/templates/book/book.html:194 +#: bookwyrm/templates/book/book.html:181 #: bookwyrm/templates/snippets/tag.html:18 msgid "Add tag" msgstr "添加标签" -#: bookwyrm/templates/book/book.html:211 +#: bookwyrm/templates/book/book.html:198 msgid "Subjects" msgstr "主题" -#: bookwyrm/templates/book/book.html:222 +#: bookwyrm/templates/book/book.html:209 msgid "Places" msgstr "地点" -#: bookwyrm/templates/book/book.html:233 bookwyrm/templates/layout.html:64 -#: bookwyrm/templates/lists/lists.html:4 bookwyrm/templates/lists/lists.html:9 +#: bookwyrm/templates/book/book.html:220 bookwyrm/templates/layout.html:64 +#: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12 #: bookwyrm/templates/search_results.html:91 #: bookwyrm/templates/user/user_layout.html:62 msgid "Lists" msgstr "列表" -#: bookwyrm/templates/book/book.html:244 +#: bookwyrm/templates/book/book.html:231 msgid "Add to list" msgstr "添加到列表" -#: bookwyrm/templates/book/book.html:254 +#: bookwyrm/templates/book/book.html:241 #: bookwyrm/templates/book/cover_modal.html:31 #: bookwyrm/templates/lists/list.html:90 msgid "Add" msgstr "添加" -#: bookwyrm/templates/book/book.html:282 +#: bookwyrm/templates/book/book.html:269 msgid "rated it" msgstr "评价了" @@ -402,7 +397,7 @@ msgid "John Doe, Jane Smith" msgstr "张三, æŽå››" #: bookwyrm/templates/book/edit_book.html:155 -#: bookwyrm/templates/snippets/shelf.html:9 +#: bookwyrm/templates/user/shelf.html:72 msgid "Cover" msgstr "å°é¢" @@ -411,6 +406,7 @@ msgid "Physical Properties" msgstr "实体性质" #: bookwyrm/templates/book/edit_book.html:183 +#: bookwyrm/templates/book/format_filter.html:5 msgid "Format:" msgstr "æ ¼å¼:" @@ -435,119 +431,150 @@ msgstr "ISBN 10:" msgid "Openlibrary key:" msgstr "Openlibrary key:" +#: bookwyrm/templates/book/editions.html:5 +#, python-format +msgid "Editions of %(book_title)s" +msgstr "%(book_title)s çš„å„版本" + +#: bookwyrm/templates/book/editions.html:9 +#, python-format +msgid "Editions of \"%(work_title)s\"" +msgstr "\"%(work_title)s\" çš„å„版本" + +#: bookwyrm/templates/book/format_filter.html:8 +#: bookwyrm/templates/book/language_filter.html:8 +msgid "Any" +msgstr "" + +#: bookwyrm/templates/book/language_filter.html:5 +msgid "Language:" +msgstr "" + +#: bookwyrm/templates/book/publisher_info.html:6 +#, python-format +msgid "%(format)s, %(pages)s pages" +msgstr "%(format)s, %(pages)s 页" + +#: bookwyrm/templates/book/publisher_info.html:8 +#, python-format +msgid "%(pages)s pages" +msgstr "%(pages)s 页" + +#: bookwyrm/templates/book/publisher_info.html:13 +#, fuzzy, python-format +#| msgid "%(pages)s pages" +msgid "%(languages)s language" +msgstr "%(pages)s 页" + +#: bookwyrm/templates/book/publisher_info.html:18 +#, python-format +msgid "Published %(date)s by %(publisher)s." +msgstr "在 %(date)s ç”± %(publisher)s 出版。" + +#: bookwyrm/templates/book/publisher_info.html:20 +#, python-format +msgid "Published %(date)s" +msgstr "于 %(date)s 出版" + +#: bookwyrm/templates/book/publisher_info.html:22 +#, python-format +msgid "Published by %(publisher)s." +msgstr "ç”± %(publisher)s 出版。" + #: bookwyrm/templates/components/inline_form.html:8 #: bookwyrm/templates/feed/feed_layout.html:57 msgid "Close" msgstr "关闭" -#: bookwyrm/templates/directory.html:6 bookwyrm/templates/directory.html:11 -#: bookwyrm/templates/layout.html:97 -msgid "Directory" -msgstr "" - -#: bookwyrm/templates/directory.html:19 -msgid "Make your profile discoverable to other BookWyrm users." -msgstr "" - -#: bookwyrm/templates/directory.html:26 -#, fuzzy, python-format -#| msgid "You can set or change your reading goal any time from your profile page" -msgid "You can opt-out at any time in your profile settings." -msgstr "ä½ å¯ä»¥åœ¨ä»»ä½•æ—¶å€™ä»Žä½ çš„ä¸ªäººèµ„æ–™é¡µé¢ ä¸­è®¾ç½®æˆ–æ”¹å˜ä½ çš„阅读目标" - -#: bookwyrm/templates/directory.html:31 -#: bookwyrm/templates/snippets/goal_card.html:22 -msgid "Dismiss message" -msgstr "é£æ•£æ¶ˆæ¯" - -#: bookwyrm/templates/directory.html:44 -#, fuzzy -#| msgid "Show less" -msgid "Show filters" -msgstr "显示更少" - -#: bookwyrm/templates/directory.html:46 -msgid "Hide filters" -msgstr "" - -#: bookwyrm/templates/directory.html:55 -#, fuzzy -#| msgid "User Activity" -msgid "User type" -msgstr "用户活动" - -#: bookwyrm/templates/directory.html:58 -msgid "BookWyrm users" -msgstr "" - -#: bookwyrm/templates/directory.html:62 -msgid "All known users" -msgstr "" - -#: bookwyrm/templates/directory.html:68 +#: bookwyrm/templates/directory/community_filter.html:5 #, fuzzy #| msgid "Comment" msgid "Community" msgstr "评论" -#: bookwyrm/templates/directory.html:71 +#: bookwyrm/templates/directory/community_filter.html:8 #, fuzzy #| msgid "Max uses" msgid "Local users" msgstr "最大使用次数" -#: bookwyrm/templates/directory.html:75 +#: bookwyrm/templates/directory/community_filter.html:12 #, fuzzy #| msgid "Federated" msgid "Federated community" msgstr "跨站" -#: bookwyrm/templates/directory.html:81 -msgid "Order by" +#: bookwyrm/templates/directory/directory.html:6 +#: bookwyrm/templates/directory/directory.html:11 +#: bookwyrm/templates/layout.html:97 +msgid "Directory" msgstr "" -#: bookwyrm/templates/directory.html:84 -#, fuzzy -#| msgid "Suggest" -msgid "Suggested" -msgstr "推è" - -#: bookwyrm/templates/directory.html:85 -msgid "Recently active" +#: bookwyrm/templates/directory/directory.html:19 +msgid "Make your profile discoverable to other BookWyrm users." msgstr "" -#: bookwyrm/templates/directory.html:91 -msgid "Apply filters" -msgstr "" +#: bookwyrm/templates/directory/directory.html:26 +#, fuzzy, python-format +#| msgid "You can set or change your reading goal any time from your profile page" +msgid "You can opt-out at any time in your profile settings." +msgstr "ä½ å¯ä»¥åœ¨ä»»ä½•æ—¶å€™ä»Žä½ çš„ä¸ªäººèµ„æ–™é¡µé¢ ä¸­è®¾ç½®æˆ–æ”¹å˜ä½ çš„阅读目标" -#: bookwyrm/templates/directory.html:94 -#, fuzzy -#| msgid "Clear search" -msgid "Clear filters" -msgstr "清除æœç´¢" +#: bookwyrm/templates/directory/directory.html:31 +#: bookwyrm/templates/snippets/goal_card.html:22 +msgid "Dismiss message" +msgstr "é£æ•£æ¶ˆæ¯" -#: bookwyrm/templates/directory.html:128 +#: bookwyrm/templates/directory/directory.html:71 #, fuzzy #| msgid "followed you" msgid "follower you follow" msgid_plural "followers you follow" msgstr[0] "关注了你" -#: bookwyrm/templates/directory.html:135 +#: bookwyrm/templates/directory/directory.html:78 #, fuzzy #| msgid "Your shelves" msgid "book on your shelves" msgid_plural "books on your shelves" msgstr[0] "你的书架" -#: bookwyrm/templates/directory.html:143 +#: bookwyrm/templates/directory/directory.html:86 msgid "posts" msgstr "" -#: bookwyrm/templates/directory.html:149 +#: bookwyrm/templates/directory/directory.html:92 msgid "last active" msgstr "" +#: bookwyrm/templates/directory/sort_filter.html:5 +msgid "Order by" +msgstr "" + +#: bookwyrm/templates/directory/sort_filter.html:8 +#, fuzzy +#| msgid "Suggest" +msgid "Suggested" +msgstr "推è" + +#: bookwyrm/templates/directory/sort_filter.html:9 +msgid "Recently active" +msgstr "" + +#: bookwyrm/templates/directory/user_type_filter.html:5 +#, fuzzy +#| msgid "User Activity" +msgid "User type" +msgstr "用户活动" + +#: bookwyrm/templates/directory/user_type_filter.html:8 +msgid "BookWyrm users" +msgstr "" + +#: bookwyrm/templates/directory/user_type_filter.html:12 +msgid "All known users" +msgstr "" + #: bookwyrm/templates/discover/about.html:7 #, python-format msgid "About %(site_name)s" @@ -657,16 +684,6 @@ msgstr "Librarything key:" msgid "Goodreads key:" msgstr "Goodreads key:" -#: bookwyrm/templates/editions.html:5 -#, python-format -msgid "Editions of %(book_title)s" -msgstr "%(book_title)s çš„å„版本" - -#: bookwyrm/templates/editions.html:9 -#, python-format -msgid "Editions of \"%(work_title)s\"" -msgstr "\"%(work_title)s\" çš„å„版本" - #: bookwyrm/templates/email/html_layout.html:15 #: bookwyrm/templates/email/text_layout.html:2 msgid "Hi there," @@ -735,18 +752,6 @@ msgstr "" msgid "Reset your %(site_name)s password" msgstr "关于 %(site_name)s" -#: bookwyrm/templates/error.html:4 -msgid "Oops!" -msgstr "å“Žå‘€ï¼" - -#: bookwyrm/templates/error.html:8 -msgid "Server Error" -msgstr "æœåŠ¡å™¨é”™è¯¯" - -#: bookwyrm/templates/error.html:9 -msgid "Something went wrong! Sorry about that." -msgstr "æŸäº›ä¸œè¥¿å‡ºé”™äº†ï¼å¯¹ä¸èµ·å•¦ã€‚" - #: bookwyrm/templates/feed/direct_messages.html:8 #, python-format msgid "Direct Messages with %(username)s" @@ -823,6 +828,8 @@ msgid "Updates" msgstr "æ›´æ–°" #: bookwyrm/templates/feed/feed_layout.html:11 +#: bookwyrm/templates/layout.html:58 +#: bookwyrm/templates/user/books_header.html:3 msgid "Your books" msgstr "你的书目" @@ -831,18 +838,18 @@ msgid "There are no books here right now! Try searching for a book to get starte msgstr "现在这里还没有任何书目ï¼å°è¯•ç€ä»Žæœç´¢æŸæœ¬ä¹¦å¼€å§‹å§" #: bookwyrm/templates/feed/feed_layout.html:23 -#: bookwyrm/templates/user/shelf.html:24 +#: bookwyrm/templates/user/shelf.html:25 msgid "To Read" msgstr "想读" #: bookwyrm/templates/feed/feed_layout.html:24 -#: bookwyrm/templates/user/shelf.html:24 +#: bookwyrm/templates/user/shelf.html:25 msgid "Currently Reading" msgstr "在读" #: bookwyrm/templates/feed/feed_layout.html:25 #: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:11 -#: bookwyrm/templates/user/shelf.html:24 +#: bookwyrm/templates/user/shelf.html:25 msgid "Read" msgstr "读过" @@ -887,27 +894,33 @@ msgstr "%(username)s 在 %(year)s 的书目" msgid "Import Books" msgstr "导入书目" -#: bookwyrm/templates/import.html:14 -msgid "Data source" +#: bookwyrm/templates/import.html:16 +#, fuzzy +#| msgid "Data source" +msgid "Data source:" msgstr "æ•°æ®æ¥æº" -#: bookwyrm/templates/import.html:32 +#: bookwyrm/templates/import.html:29 +msgid "Data file:" +msgstr "" + +#: bookwyrm/templates/import.html:37 msgid "Include reviews" msgstr "纳入书评" -#: bookwyrm/templates/import.html:37 +#: bookwyrm/templates/import.html:42 msgid "Privacy setting for imported reviews:" msgstr "导入书评的éšç§è®¾å®š" -#: bookwyrm/templates/import.html:41 +#: bookwyrm/templates/import.html:48 msgid "Import" msgstr "导入" -#: bookwyrm/templates/import.html:46 +#: bookwyrm/templates/import.html:53 msgid "Recent Imports" msgstr "最近的导入" -#: bookwyrm/templates/import.html:48 +#: bookwyrm/templates/import.html:55 msgid "No recent imports" msgstr "无最近的导入" @@ -964,12 +977,12 @@ msgstr "书目" #: bookwyrm/templates/import_status.html:115 #: bookwyrm/templates/snippets/create_status_form.html:10 -#: bookwyrm/templates/snippets/shelf.html:10 +#: bookwyrm/templates/user/shelf.html:73 msgid "Title" msgstr "标题" #: bookwyrm/templates/import_status.html:118 -#: bookwyrm/templates/snippets/shelf.html:11 +#: bookwyrm/templates/user/shelf.html:74 msgid "Author" msgstr "作者" @@ -1025,10 +1038,6 @@ msgstr "æœç´¢" msgid "Main navigation menu" msgstr "主导航èœå•" -#: bookwyrm/templates/layout.html:58 -msgid "Your shelves" -msgstr "你的书架" - #: bookwyrm/templates/layout.html:61 msgid "Feed" msgstr "动æ€" @@ -1043,7 +1052,7 @@ msgid "Settings" msgstr "设置" #: bookwyrm/templates/layout.html:116 -#: bookwyrm/templates/settings/admin_layout.html:20 +#: bookwyrm/templates/settings/admin_layout.html:24 #: bookwyrm/templates/settings/manage_invite_requests.html:15 #: bookwyrm/templates/settings/manage_invites.html:3 #: bookwyrm/templates/settings/manage_invites.html:15 @@ -1105,7 +1114,7 @@ msgid "BookWyrm is open source software. You can contribute or report issues on msgstr "BookWyrm 是开æºè½¯ä»¶ã€‚ä½ å¯ä»¥åœ¨ GitHub 贡献或报告问题。" #: bookwyrm/templates/lists/create_form.html:5 -#: bookwyrm/templates/lists/lists.html:17 +#: bookwyrm/templates/lists/lists.html:19 msgid "Create List" msgstr "创建列表" @@ -1169,7 +1178,7 @@ msgid "Anyone can suggest books, subject to your approval" msgstr "任何人都å¯ä»¥æŽ¨è书目ã€ä¸»é¢˜è®©ä½ æ‰¹å‡†" #: bookwyrm/templates/lists/form.html:31 -#: bookwyrm/templates/moderation/reports.html:11 +#: bookwyrm/templates/moderation/reports.html:24 msgid "Open" msgstr "开放" @@ -1187,6 +1196,7 @@ msgid "Added by %(username)s" msgstr "ç”± %(username)s 添加" #: bookwyrm/templates/lists/list.html:41 +#: bookwyrm/templates/snippets/shelf_selector.html:28 msgid "Remove" msgstr "移除" @@ -1223,19 +1233,6 @@ msgstr "没有找到书目" msgid "Suggest" msgstr "推è" -#: bookwyrm/templates/lists/lists.html:14 -msgid "Your lists" -msgstr "你的列表" - -#: bookwyrm/templates/lists/lists.html:32 -#, python-format -msgid "See all %(size)s lists" -msgstr "查看所有 %(size)s 个列表" - -#: bookwyrm/templates/lists/lists.html:40 -msgid "Recent Lists" -msgstr "最近的列表" - #: bookwyrm/templates/login.html:4 msgid "Login" msgstr "登录" @@ -1339,23 +1336,33 @@ msgstr "é‡æ–°å¼€å¯" msgid "Resolve" msgstr "已解决" -#: bookwyrm/templates/moderation/reports.html:4 -#: bookwyrm/templates/moderation/reports.html:5 -#: bookwyrm/templates/settings/admin_layout.html:24 +#: bookwyrm/templates/moderation/reports.html:6 +#, fuzzy, python-format +#| msgid "Report @%(username)s" +msgid "Reports: %(server_name)s" +msgstr "报告 %(username)s" + +#: bookwyrm/templates/moderation/reports.html:8 +#: bookwyrm/templates/moderation/reports.html:16 +#: bookwyrm/templates/settings/admin_layout.html:28 msgid "Reports" msgstr "报告" -#: bookwyrm/templates/moderation/reports.html:14 +#: bookwyrm/templates/moderation/reports.html:13 +#, fuzzy, python-format +#| msgid "Report @%(username)s" +msgid "Reports: %(server_name)s" +msgstr "报告 %(username)s" + +#: bookwyrm/templates/moderation/reports.html:27 msgid "Resolved" msgstr "已解决" -#: bookwyrm/templates/notfound.html:4 bookwyrm/templates/notfound.html:8 -msgid "Not Found" -msgstr "未找到" - -#: bookwyrm/templates/notfound.html:9 -msgid "The page you requested doesn't seem to exist!" -msgstr "你请求的页é¢ä¼¼ä¹Žå¹¶ä¸å­˜åœ¨ï¼" +#: bookwyrm/templates/moderation/reports.html:34 +#, fuzzy +#| msgid "No books found" +msgid "No reports found." +msgstr "没有找到书目" #: bookwyrm/templates/notifications.html:14 msgid "Delete notifications" @@ -1587,51 +1594,133 @@ msgstr "管ç†" msgid "Manage Users" msgstr "管ç†ç”¨æˆ·" -#: bookwyrm/templates/settings/admin_layout.html:28 -#: bookwyrm/templates/settings/federation.html:4 +#: bookwyrm/templates/settings/admin_layout.html:19 +#: bookwyrm/templates/settings/user_admin.html:3 +#: bookwyrm/templates/settings/user_admin.html:10 +msgid "Users" +msgstr "" + +#: bookwyrm/templates/settings/admin_layout.html:32 +#: bookwyrm/templates/settings/federation.html:3 +#: bookwyrm/templates/settings/federation.html:5 msgid "Federated Servers" msgstr "互è”çš„æœåŠ¡å™¨" -#: bookwyrm/templates/settings/admin_layout.html:33 +#: bookwyrm/templates/settings/admin_layout.html:37 msgid "Instance Settings" msgstr "实例设置" -#: bookwyrm/templates/settings/admin_layout.html:37 +#: bookwyrm/templates/settings/admin_layout.html:41 #: bookwyrm/templates/settings/site.html:4 #: bookwyrm/templates/settings/site.html:6 msgid "Site Settings" msgstr "站点设置" -#: bookwyrm/templates/settings/admin_layout.html:40 +#: bookwyrm/templates/settings/admin_layout.html:44 #: bookwyrm/templates/settings/site.html:13 msgid "Instance Info" msgstr "实例信æ¯" -#: bookwyrm/templates/settings/admin_layout.html:41 +#: bookwyrm/templates/settings/admin_layout.html:45 #: bookwyrm/templates/settings/site.html:39 msgid "Images" msgstr "图åƒ" -#: bookwyrm/templates/settings/admin_layout.html:42 +#: bookwyrm/templates/settings/admin_layout.html:46 #: bookwyrm/templates/settings/site.html:59 msgid "Footer Content" msgstr "页脚内容" -#: bookwyrm/templates/settings/admin_layout.html:43 +#: bookwyrm/templates/settings/admin_layout.html:47 #: bookwyrm/templates/settings/site.html:77 msgid "Registration" msgstr "注册" -#: bookwyrm/templates/settings/federation.html:10 +#: bookwyrm/templates/settings/federated_server.html:7 +#, fuzzy +#| msgid "Back to reports" +msgid "Back to server list" +msgstr "回到报告" + +#: bookwyrm/templates/settings/federated_server.html:12 +msgid "Details" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:15 +#, fuzzy +#| msgid "Software" +msgid "Software:" +msgstr "软件" + +#: bookwyrm/templates/settings/federated_server.html:19 +#, fuzzy +#| msgid "Description:" +msgid "Version:" +msgstr "æè¿°:" + +#: bookwyrm/templates/settings/federated_server.html:23 +#, fuzzy +#| msgid "Status" +msgid "Status:" +msgstr "状æ€" + +#: bookwyrm/templates/settings/federated_server.html:30 +#: bookwyrm/templates/user/user_layout.html:50 +msgid "Activity" +msgstr "活动" + +#: bookwyrm/templates/settings/federated_server.html:33 +#, fuzzy +#| msgid "Username:" +msgid "Users:" +msgstr "用户å:" + +#: bookwyrm/templates/settings/federated_server.html:36 +#: bookwyrm/templates/settings/federated_server.html:43 +msgid "View all" +msgstr "" + +#: bookwyrm/templates/settings/federated_server.html:40 +#, fuzzy +#| msgid "Reports" +msgid "Reports:" +msgstr "报告" + +#: bookwyrm/templates/settings/federated_server.html:47 +#, fuzzy +#| msgid "followed you" +msgid "Followed by us:" +msgstr "关注了你" + +#: bookwyrm/templates/settings/federated_server.html:53 +#, fuzzy +#| msgid "followed you" +msgid "Followed by them:" +msgstr "关注了你" + +#: bookwyrm/templates/settings/federated_server.html:59 +#, fuzzy +#| msgid "Blocked Users" +msgid "Blocked by us:" +msgstr "å±è”½çš„用户" + +#: bookwyrm/templates/settings/federation.html:13 msgid "Server name" msgstr "æœåŠ¡å™¨å称" -#: bookwyrm/templates/settings/federation.html:11 +#: bookwyrm/templates/settings/federation.html:17 +#, fuzzy +#| msgid "Federated" +msgid "Date federated" +msgstr "跨站" + +#: bookwyrm/templates/settings/federation.html:21 msgid "Software" msgstr "软件" -#: bookwyrm/templates/settings/federation.html:12 +#: bookwyrm/templates/settings/federation.html:24 #: bookwyrm/templates/settings/manage_invite_requests.html:33 +#: bookwyrm/templates/settings/user_admin.html:32 msgid "Status" msgstr "状æ€" @@ -1802,6 +1891,47 @@ msgstr "关注请求" msgid "Registration closed text:" msgstr "注册关闭文字:" +#: bookwyrm/templates/settings/user_admin.html:7 +#, python-format +msgid "Users: %(server_name)s" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:20 +#, fuzzy +#| msgid "Username:" +msgid "Username" +msgstr "用户å:" + +#: bookwyrm/templates/settings/user_admin.html:24 +#, fuzzy +#| msgid "Added:" +msgid "Date Added" +msgstr "添加了:" + +#: bookwyrm/templates/settings/user_admin.html:28 +msgid "Last Active" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:36 +#, fuzzy +#| msgid "Remove" +msgid "Remote server" +msgstr "移除" + +#: bookwyrm/templates/settings/user_admin.html:45 +#, fuzzy +#| msgid "Activity" +msgid "Active" +msgstr "活动" + +#: bookwyrm/templates/settings/user_admin.html:45 +msgid "Inactive" +msgstr "" + +#: bookwyrm/templates/settings/user_admin.html:50 +msgid "Not set" +msgstr "" + #: bookwyrm/templates/snippets/block_button.html:5 msgid "Block" msgstr "å±è”½" @@ -1862,7 +1992,7 @@ msgid "Review:" msgstr "书评" #: bookwyrm/templates/snippets/create_status_form.html:29 -#: bookwyrm/templates/snippets/shelf.html:17 +#: bookwyrm/templates/user/shelf.html:78 msgid "Rating" msgstr "评价" @@ -1936,6 +2066,26 @@ msgstr "喜欢状æ€" msgid "Un-like status" msgstr "å–消喜欢状æ€" +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:7 +#, fuzzy +#| msgid "Show less" +msgid "Show filters" +msgstr "显示更少" + +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:9 +msgid "Hide filters" +msgstr "" + +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:19 +msgid "Apply filters" +msgstr "" + +#: bookwyrm/templates/snippets/filters_panel/filters_panel.html:23 +#, fuzzy +#| msgid "Clear search" +msgid "Clear filters" +msgstr "清除æœç´¢" + #: bookwyrm/templates/snippets/follow_button.html:12 msgid "Follow" msgstr "关注" @@ -2065,32 +2215,32 @@ msgstr "留下评价" msgid "Rate" msgstr "评价" -#: bookwyrm/templates/snippets/readthrough.html:7 +#: bookwyrm/templates/snippets/readthrough.html:8 msgid "Progress Updates:" msgstr "进度更新:" -#: bookwyrm/templates/snippets/readthrough.html:11 +#: bookwyrm/templates/snippets/readthrough.html:12 msgid "finished" msgstr "已完æˆ" -#: bookwyrm/templates/snippets/readthrough.html:14 +#: bookwyrm/templates/snippets/readthrough.html:15 msgid "Show all updates" msgstr "显示所有更新" -#: bookwyrm/templates/snippets/readthrough.html:30 +#: bookwyrm/templates/snippets/readthrough.html:31 msgid "Delete this progress update" msgstr "删除此进度更新" -#: bookwyrm/templates/snippets/readthrough.html:40 +#: bookwyrm/templates/snippets/readthrough.html:41 msgid "started" msgstr "已开始" -#: bookwyrm/templates/snippets/readthrough.html:46 -#: bookwyrm/templates/snippets/readthrough.html:60 +#: bookwyrm/templates/snippets/readthrough.html:47 +#: bookwyrm/templates/snippets/readthrough.html:61 msgid "Edit read dates" msgstr "编辑阅读日期" -#: bookwyrm/templates/snippets/readthrough.html:50 +#: bookwyrm/templates/snippets/readthrough.html:51 msgid "Delete these read dates" msgstr "删除这些阅读日期" @@ -2150,45 +2300,11 @@ msgstr "ç”± %(author)s 所著" msgid "Import book" msgstr "导入书目" -#: bookwyrm/templates/snippets/shelf.html:12 -msgid "Published" -msgstr "已出版" - -#: bookwyrm/templates/snippets/shelf.html:13 -msgid "Shelved" -msgstr "已上架" - -#: bookwyrm/templates/snippets/shelf.html:14 -msgid "Started" -msgstr "已开始" - -#: bookwyrm/templates/snippets/shelf.html:15 -msgid "Finished" -msgstr "已完æˆ" - -#: bookwyrm/templates/snippets/shelf.html:16 -msgid "External links" -msgstr "外部链接" - -#: bookwyrm/templates/snippets/shelf.html:44 -msgid "OpenLibrary" -msgstr "OpenLibrary" - -#: bookwyrm/templates/snippets/shelf.html:61 -msgid "This shelf is empty." -msgstr "此书架是空的。" - -#: bookwyrm/templates/snippets/shelf.html:67 -msgid "Delete shelf" -msgstr "删除书架" - #: bookwyrm/templates/snippets/shelf_selector.html:4 -msgid "Change shelf" -msgstr "æ›´æ¢ä¹¦æž¶" - -#: bookwyrm/templates/snippets/shelf_selector.html:27 -msgid "Unshelve" -msgstr "å–下书架" +#, fuzzy +#| msgid "Your books" +msgid "Move book" +msgstr "你的书目" #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:5 #, python-format @@ -2196,7 +2312,7 @@ msgid "Finish \"%(book_title)s\"" msgstr "å®Œæˆ \"%(book_title)s\"" #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:5 -#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:33 +#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:35 #, fuzzy #| msgid "Updates" msgid "Update progress" @@ -2219,6 +2335,12 @@ msgstr "完æˆé˜…读" msgid "Want to read" msgstr "想è¦é˜…读" +#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:48 +#, fuzzy, python-format +#| msgid "Report @%(username)s" +msgid "Remove from %(name)s" +msgstr "报告 %(username)s" + #: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:5 #, python-format msgid "Start \"%(book_title)s\"" @@ -2287,6 +2409,18 @@ msgstr "更多选项" msgid "Switch to this edition" msgstr "切æ¢åˆ°æ­¤ç‰ˆæœ¬" +#: bookwyrm/templates/snippets/table-sort-header.html:6 +#, fuzzy +#| msgid "Started reading" +msgid "Sorted asccending" +msgstr "已开始阅读" + +#: bookwyrm/templates/snippets/table-sort-header.html:10 +#, fuzzy +#| msgid "Started reading" +msgid "Sorted descending" +msgstr "已开始阅读" + #: bookwyrm/templates/snippets/tag.html:14 msgid "Remove tag" msgstr "移除标签" @@ -2296,6 +2430,12 @@ msgstr "移除标签" msgid "Books tagged \"%(tag.name)s\"" msgstr "标有 \"%(tag.name)s\" 标签的书" +#: bookwyrm/templates/user/books_header.html:5 +#, fuzzy, python-format +#| msgid "%(username)s's %(year)s Books" +msgid "%(username)s's books" +msgstr "%(username)s 在 %(year)s 的书目" + #: bookwyrm/templates/user/create_shelf_form.html:5 #: bookwyrm/templates/user/create_shelf_form.html:22 msgid "Create Shelf" @@ -2341,56 +2481,62 @@ msgstr "列表: %(username)s" msgid "Create list" msgstr "创建列表" -#: bookwyrm/templates/user/shelf.html:9 -msgid "Your Shelves" -msgstr "你的书架" - -#: bookwyrm/templates/user/shelf.html:11 -#, python-format -msgid "%(username)s: Shelves" -msgstr "%(username)s: 书架" - -#: bookwyrm/templates/user/shelf.html:33 +#: bookwyrm/templates/user/shelf.html:34 msgid "Create shelf" msgstr "创建书架" -#: bookwyrm/templates/user/shelf.html:54 +#: bookwyrm/templates/user/shelf.html:55 msgid "Edit shelf" msgstr "编辑书架" +#: bookwyrm/templates/user/shelf.html:75 +msgid "Shelved" +msgstr "已上架" + +#: bookwyrm/templates/user/shelf.html:76 +msgid "Started" +msgstr "已开始" + +#: bookwyrm/templates/user/shelf.html:77 +msgid "Finished" +msgstr "已完æˆ" + +#: bookwyrm/templates/user/shelf.html:121 +msgid "This shelf is empty." +msgstr "此书架是空的。" + +#: bookwyrm/templates/user/shelf.html:127 +msgid "Delete shelf" +msgstr "删除书架" + #: bookwyrm/templates/user/user.html:15 msgid "Edit profile" msgstr "编辑个人资料" -#: bookwyrm/templates/user/user.html:26 -#: bookwyrm/templates/user/user_layout.html:68 -msgid "Shelves" -msgstr "书架" - -#: bookwyrm/templates/user/user.html:31 -#, python-format -msgid "See all %(size)s" +#: bookwyrm/templates/user/user.html:33 +#, fuzzy, python-format +#| msgid "See all %(size)s" +msgid "View all %(size)s" msgstr "查看所有 %(size)s 本" -#: bookwyrm/templates/user/user.html:44 -#, python-format -msgid "See all %(shelf_count)s shelves" -msgstr "查看所有 %(shelf_count)s 个书架" +#: bookwyrm/templates/user/user.html:46 +msgid "View all books" +msgstr "" -#: bookwyrm/templates/user/user.html:56 +#: bookwyrm/templates/user/user.html:58 #, python-format msgid "Set a reading goal for %(year)s" msgstr "设定 %(year)s 的阅读目标" -#: bookwyrm/templates/user/user.html:62 +#: bookwyrm/templates/user/user.html:64 msgid "User Activity" msgstr "用户活动" -#: bookwyrm/templates/user/user.html:65 +#: bookwyrm/templates/user/user.html:67 msgid "RSS feed" msgstr "RSS æµ" -#: bookwyrm/templates/user/user.html:76 +#: bookwyrm/templates/user/user.html:78 msgid "No activities yet!" msgstr "还没有活动ï¼" @@ -2398,14 +2544,16 @@ msgstr "还没有活动ï¼" msgid "Follow Requests" msgstr "关注请求" -#: bookwyrm/templates/user/user_layout.html:50 -msgid "Activity" -msgstr "活动" - #: bookwyrm/templates/user/user_layout.html:56 msgid "Reading Goal" msgstr "阅读目标" +#: bookwyrm/templates/user/user_layout.html:68 +#, fuzzy +#| msgid "Book" +msgid "Books" +msgstr "书目" + #: bookwyrm/templates/user/user_preview.html:13 #, python-format msgid "Joined %(date)s" @@ -2433,6 +2581,48 @@ msgstr "å·²ç»å­˜åœ¨ä½¿ç”¨è¯¥ç”¨æˆ·å的用户。" msgid "A password reset link sent to %s" msgstr "" +#~ msgid "Your shelves" +#~ msgstr "你的书架" + +#~ msgid "Your lists" +#~ msgstr "你的列表" + +#, python-format +#~ msgid "See all %(size)s lists" +#~ msgstr "查看所有 %(size)s 个列表" + +#~ msgid "Recent Lists" +#~ msgstr "最近的列表" + +#~ msgid "Published" +#~ msgstr "已出版" + +#~ msgid "External links" +#~ msgstr "外部链接" + +#~ msgid "OpenLibrary" +#~ msgstr "OpenLibrary" + +#~ msgid "Change shelf" +#~ msgstr "æ›´æ¢ä¹¦æž¶" + +#~ msgid "Unshelve" +#~ msgstr "å–下书架" + +#~ msgid "Your Shelves" +#~ msgstr "你的书架" + +#, python-format +#~ msgid "%(username)s: Shelves" +#~ msgstr "%(username)s: 书架" + +#~ msgid "Shelves" +#~ msgstr "书架" + +#, python-format +#~ msgid "See all %(shelf_count)s shelves" +#~ msgstr "查看所有 %(shelf_count)s 个书架" + #~ msgid "Send follow request" #~ msgstr "å‘é€å…³æ³¨è¯·æ±‚" From 4a490d25a836b48fdc37a784d781e9da77e31a18 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 09:34:16 -0700 Subject: [PATCH 0535/1285] CHanges urls from shelves to books --- bookwyrm/templates/user/user_layout.html | 2 +- bookwyrm/urls.py | 46 ++++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/bookwyrm/templates/user/user_layout.html b/bookwyrm/templates/user/user_layout.html index c40b9e77..90a2e607 100644 --- a/bookwyrm/templates/user/user_layout.html +++ b/bookwyrm/templates/user/user_layout.html @@ -42,7 +42,7 @@ {% endif %}
    {% with user|username as username %} -{% if 'user/'|add:username|add:'/shelf' not in request.path and 'user/'|add:username|add:'/shelves' not in request.path %} +{% if 'user/'|add:username|add:'/books' not in request.path %}
    {% with user|username as username %} -{% if 'user/'|add:username|add:'/books' not in request.path %} +{% if 'user/'|add:username|add:'/books' not in request.path and 'user/'|add:username|add:'/shelf' not in request.path %}
    {% endfor %}
    - {% trans "View all books" %} + {% trans "View all books" %}
    {% endif %} diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index c5f2f542..cd7a4e41 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -161,7 +161,7 @@ urlpatterns = [ re_path( r"^%s/(books|shelf)/(?P[\w-]+)(.json)?/?$" % local_user_path, views.Shelf.as_view(), - name="shelf" + name="shelf", ), re_path(r"^create-shelf/?$", views.create_shelf, name="shelf-create"), re_path(r"^delete-shelf/(?P\d+)?$", views.delete_shelf), From 2fd917a6cc8bbeb359da7abb1ab8067b0f82f90a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 10:24:23 -0700 Subject: [PATCH 0539/1285] Updates test --- bookwyrm/tests/models/test_shelf_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/tests/models/test_shelf_model.py b/bookwyrm/tests/models/test_shelf_model.py index 3bbb9890..ebda0499 100644 --- a/bookwyrm/tests/models/test_shelf_model.py +++ b/bookwyrm/tests/models/test_shelf_model.py @@ -27,7 +27,7 @@ class Shelf(TestCase): shelf = models.Shelf.objects.create( name="Test Shelf", identifier="test-shelf", user=self.local_user ) - expected_id = "https://%s/user/mouse/shelf/test-shelf" % settings.DOMAIN + expected_id = "https://%s/user/mouse/books/test-shelf" % settings.DOMAIN self.assertEqual(shelf.get_remote_id(), expected_id) models.Shelf.broadcast = real_broadcast From a670c8d366edc833cfb0926fe49b947e8550d2d2 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 10:23:20 -0700 Subject: [PATCH 0540/1285] Adds "all books" view" --- bookwyrm/templates/user/shelf.html | 9 +++--- bookwyrm/urls.py | 4 +-- bookwyrm/views/__init__.py | 2 +- bookwyrm/views/shelf.py | 46 ++++++++++++------------------ 4 files changed, 27 insertions(+), 34 deletions(-) diff --git a/bookwyrm/templates/user/shelf.html b/bookwyrm/templates/user/shelf.html index 5173da07..cc4d8d37 100644 --- a/bookwyrm/templates/user/shelf.html +++ b/bookwyrm/templates/user/shelf.html @@ -20,10 +20,13 @@ {% else %}

    {% trans "This shelf is empty." %}

    - {% if shelf.editable %} + {% if shelf.id and shelf.editable %}
    {% csrf_token %} diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index cd7a4e41..27fb678f 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -151,8 +151,8 @@ urlpatterns = [ re_path( r"^list/(?P\d+)/curate/?$", views.Curate.as_view(), name="list-curate" ), - # shelf - re_path(r"%s/books/?$" % user_path, views.user_shelves_page, name="user-shelves"), + # Uyser books + re_path(r"%s/books/?$" % user_path, views.Shelf.as_view(), name="user-shelves"), re_path( r"^%s/(helf|books)/(?P[\w-]+)(.json)?/?$" % user_path, views.Shelf.as_view(), diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 693a7744..fead2a32 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -27,7 +27,7 @@ from .rss_feed import RssFeed from .password import PasswordResetRequest, PasswordReset, ChangePassword from .search import Search from .shelf import Shelf -from .shelf import user_shelves_page, create_shelf, delete_shelf +from .shelf import create_shelf, delete_shelf from .shelf import shelve, unshelve from .site import Site from .status import CreateStatus, DeleteStatus diff --git a/bookwyrm/views/shelf.py b/bookwyrm/views/shelf.py index 75d915cd..ad1128e2 100644 --- a/bookwyrm/views/shelf.py +++ b/bookwyrm/views/shelf.py @@ -1,4 +1,6 @@ """ shelf views""" +from collections import namedtuple + from django.db import IntegrityError from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator @@ -6,6 +8,7 @@ from django.http import HttpResponseBadRequest, HttpResponseNotFound from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse from django.utils.decorators import method_decorator +from django.utils.translation import gettext as _ from django.views import View from django.views.decorators.http import require_POST @@ -13,14 +16,14 @@ from bookwyrm import forms, models from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.settings import PAGE_LENGTH from .helpers import is_api_request, get_edition, get_user_from_username -from .helpers import handle_reading_status +from .helpers import handle_reading_status, privacy_filter, object_visible_to_user # pylint: disable= no-self-use class Shelf(View): """ shelf page """ - def get(self, request, username, shelf_identifier): + def get(self, request, username, shelf_identifier=None): """ display a shelf """ try: user = get_user_from_username(request.user, username) @@ -32,35 +35,28 @@ class Shelf(View): except ValueError: page = 1 + shelves = privacy_filter(request.user, user.shelf_set) + + # get the shelf and make sure the logged in user should be able to see it if shelf_identifier: shelf = user.shelf_set.get(identifier=shelf_identifier) + if not object_visible_to_user(request.user, shelf): + return HttpResponseNotFound() + # this is a constructed "all books" view, with a fake "shelf" obj else: - shelf = user.shelf_set.first() + FakeShelf = namedtuple("Shelf", ("identifier", "name", "user", "books")) + books = models.Edition.objects.filter( + shelfbook__shelf__in=shelves.all() + ).distinct() + shelf = FakeShelf("all", _("All books"), user, books) is_self = request.user == user - shelves = user.shelf_set - if not is_self: - follower = user.followers.filter(id=request.user.id).exists() - # make sure the user has permission to view the shelf - if shelf.privacy == "direct" or ( - shelf.privacy == "followers" and not follower - ): - return HttpResponseNotFound() - - # only show other shelves that should be visible - if follower: - shelves = shelves.filter(privacy__in=["public", "followers"]) - else: - shelves = shelves.filter(privacy="public") - if is_api_request(request): return ActivitypubResponse(shelf.to_activity(**request.GET)) paginated = Paginator( - models.ShelfBook.objects.filter(user=user, shelf=shelf) - .order_by("-updated_date") - .all(), + shelf.books.order_by("-updated_date").all(), PAGE_LENGTH, ) @@ -95,11 +91,6 @@ class Shelf(View): return redirect(shelf.local_path) -def user_shelves_page(request, username): - """ default shelf """ - return Shelf.as_view()(request, username, None) - - @login_required @require_POST def create_shelf(request): @@ -176,7 +167,8 @@ def shelve(request): models.ShelfBook.objects.create( book=book, shelf=desired_shelf, user=request.user ) - # The book is already on this shelf. Might be good to alert, or reject the action? + # The book is already on this shelf. + # Might be good to alert, or reject the action? except IntegrityError: pass return redirect(request.headers.get("Referer", "/")) From f89e94b0c02fe82378e00f354b29b4eed942274b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 10:32:50 -0700 Subject: [PATCH 0541/1285] Adds privacy to fake shelf --- bookwyrm/views/shelf.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bookwyrm/views/shelf.py b/bookwyrm/views/shelf.py index ad1128e2..971eb858 100644 --- a/bookwyrm/views/shelf.py +++ b/bookwyrm/views/shelf.py @@ -44,11 +44,13 @@ class Shelf(View): return HttpResponseNotFound() # this is a constructed "all books" view, with a fake "shelf" obj else: - FakeShelf = namedtuple("Shelf", ("identifier", "name", "user", "books")) + FakeShelf = namedtuple( + "Shelf", ("identifier", "name", "user", "books", "privacy") + ) books = models.Edition.objects.filter( shelfbook__shelf__in=shelves.all() ).distinct() - shelf = FakeShelf("all", _("All books"), user, books) + shelf = FakeShelf("all", _("All books"), user, books, "public") is_self = request.user == user From ad2b938b4c43b6abf2fb9d93d2c3e7ed3d9e33bb Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 10:36:10 -0700 Subject: [PATCH 0542/1285] Removes edit button from all books view --- bookwyrm/templates/user/shelf.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/user/shelf.html b/bookwyrm/templates/user/shelf.html index cc4d8d37..8d23dce4 100644 --- a/bookwyrm/templates/user/shelf.html +++ b/bookwyrm/templates/user/shelf.html @@ -53,7 +53,7 @@
    - {% if is_self %} + {% if is_self and shelf.id %}
    {% trans "Edit shelf" as button_text %} {% include 'snippets/toggle/open_button.html' with text=button_text icon="pencil" controls_text="edit-shelf-form" focus="edit-shelf-form-header" %} From d0a26090d1803847328bb516ab24e709eba2fdb5 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 10:37:09 -0700 Subject: [PATCH 0543/1285] Updates locales --- locale/de_DE/LC_MESSAGES/django.po | 53 +++++++++++++++++------------- locale/en_US/LC_MESSAGES/django.po | 51 +++++++++++++++------------- locale/es/LC_MESSAGES/django.po | 53 +++++++++++++++++------------- locale/fr_FR/LC_MESSAGES/django.po | 53 +++++++++++++++++------------- locale/zh_CN/LC_MESSAGES/django.po | 53 +++++++++++++++++------------- 5 files changed, 148 insertions(+), 115 deletions(-) diff --git a/locale/de_DE/LC_MESSAGES/django.po b/locale/de_DE/LC_MESSAGES/django.po index ac095694..adec1421 100644 --- a/locale/de_DE/LC_MESSAGES/django.po +++ b/locale/de_DE/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-03-31 09:22-0700\n" +"POT-Creation-Date: 2021-03-31 10:36-0700\n" "PO-Revision-Date: 2021-03-02 17:19-0800\n" "Last-Translator: Mouse Reeve \n" "Language-Team: English \n" @@ -418,7 +418,7 @@ msgid "John Doe, Jane Smith" msgstr "" #: bookwyrm/templates/book/edit_book.html:155 -#: bookwyrm/templates/user/shelf.html:72 +#: bookwyrm/templates/user/shelf.html:75 msgid "Cover" msgstr "" @@ -860,14 +860,14 @@ msgid "There are no books here right now! Try searching for a book to get starte msgstr "Hier sind noch keine Bücher! Versuche nach Büchern zu suchen um loszulegen" #: bookwyrm/templates/feed/feed_layout.html:23 -#: bookwyrm/templates/user/shelf.html:25 +#: bookwyrm/templates/user/shelf.html:28 #, fuzzy #| msgid "Read" msgid "To Read" msgstr "Auf der Leseliste" #: bookwyrm/templates/feed/feed_layout.html:24 -#: bookwyrm/templates/user/shelf.html:25 +#: bookwyrm/templates/user/shelf.html:28 #, fuzzy #| msgid "Start reading" msgid "Currently Reading" @@ -875,7 +875,7 @@ msgstr "Gerade lesend" #: bookwyrm/templates/feed/feed_layout.html:25 #: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:11 -#: bookwyrm/templates/user/shelf.html:25 +#: bookwyrm/templates/user/shelf.html:28 msgid "Read" msgstr "Gelesen" @@ -1003,12 +1003,12 @@ msgstr "Buch" #: bookwyrm/templates/import_status.html:115 #: bookwyrm/templates/snippets/create_status_form.html:10 -#: bookwyrm/templates/user/shelf.html:73 +#: bookwyrm/templates/user/shelf.html:76 msgid "Title" msgstr "Titel" #: bookwyrm/templates/import_status.html:118 -#: bookwyrm/templates/user/shelf.html:74 +#: bookwyrm/templates/user/shelf.html:77 msgid "Author" msgstr "Autor*in" @@ -2031,7 +2031,7 @@ msgid "Review:" msgstr "Bewerten" #: bookwyrm/templates/snippets/create_status_form.html:29 -#: bookwyrm/templates/user/shelf.html:78 +#: bookwyrm/templates/user/shelf.html:81 msgid "Rating" msgstr "" @@ -2501,7 +2501,8 @@ msgid "Update shelf" msgstr "Regal aktualisieren" #: bookwyrm/templates/user/followers.html:7 -#: bookwyrm/templates/user/following.html:7 bookwyrm/templates/user/user.html:9 +#: bookwyrm/templates/user/following.html:7 +#: bookwyrm/templates/user/user.html:10 msgid "User Profile" msgstr "Benutzerprofil" @@ -2532,62 +2533,68 @@ msgstr "Listen: %(username)s" msgid "Create list" msgstr "Liste Erstellen" -#: bookwyrm/templates/user/shelf.html:34 +#: bookwyrm/templates/user/shelf.html:24 bookwyrm/views/shelf.py:53 +#, fuzzy +#| msgid "books" +msgid "All books" +msgstr "Bücher" + +#: bookwyrm/templates/user/shelf.html:37 msgid "Create shelf" msgstr "Regal erstellen" -#: bookwyrm/templates/user/shelf.html:55 +#: bookwyrm/templates/user/shelf.html:58 msgid "Edit shelf" msgstr "Regal bearbeiten" -#: bookwyrm/templates/user/shelf.html:75 +#: bookwyrm/templates/user/shelf.html:78 msgid "Shelved" msgstr "Ins Regal gestellt" -#: bookwyrm/templates/user/shelf.html:76 +#: bookwyrm/templates/user/shelf.html:79 msgid "Started" msgstr "Gestartet" -#: bookwyrm/templates/user/shelf.html:77 +#: bookwyrm/templates/user/shelf.html:80 msgid "Finished" msgstr "Abgeschlossen" -#: bookwyrm/templates/user/shelf.html:121 +#: bookwyrm/templates/user/shelf.html:122 msgid "This shelf is empty." msgstr "Dieses Regal ist leer." -#: bookwyrm/templates/user/shelf.html:127 +#: bookwyrm/templates/user/shelf.html:128 msgid "Delete shelf" msgstr "Regal löschen" -#: bookwyrm/templates/user/user.html:15 +#: bookwyrm/templates/user/user.html:16 msgid "Edit profile" msgstr "Profil bearbeiten" -#: bookwyrm/templates/user/user.html:33 +#: bookwyrm/templates/user/user.html:34 #, fuzzy, python-format #| msgid "See all %(size)s" msgid "View all %(size)s" msgstr "Alle %(size)s anzeigen" -#: bookwyrm/templates/user/user.html:46 +#: bookwyrm/templates/user/user.html:47 msgid "View all books" msgstr "" -#: bookwyrm/templates/user/user.html:58 +#: bookwyrm/templates/user/user.html:59 #, python-format msgid "Set a reading goal for %(year)s" msgstr "Leseziel für %(year)s setzen" -#: bookwyrm/templates/user/user.html:64 +#: bookwyrm/templates/user/user.html:65 msgid "User Activity" msgstr "Nutzer*innenaktivität" -#: bookwyrm/templates/user/user.html:67 +#: bookwyrm/templates/user/user.html:68 msgid "RSS feed" msgstr "" -#: bookwyrm/templates/user/user.html:78 +#: bookwyrm/templates/user/user.html:79 msgid "No activities yet!" msgstr "Noch keine Aktivitäten!" diff --git a/locale/en_US/LC_MESSAGES/django.po b/locale/en_US/LC_MESSAGES/django.po index 190a5b1a..ff161897 100644 --- a/locale/en_US/LC_MESSAGES/django.po +++ b/locale/en_US/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-03-31 09:22-0700\n" +"POT-Creation-Date: 2021-03-31 10:36-0700\n" "PO-Revision-Date: 2021-02-28 17:19-0800\n" "Last-Translator: Mouse Reeve \n" "Language-Team: English \n" @@ -396,7 +396,7 @@ msgid "John Doe, Jane Smith" msgstr "" #: bookwyrm/templates/book/edit_book.html:155 -#: bookwyrm/templates/user/shelf.html:72 +#: bookwyrm/templates/user/shelf.html:75 msgid "Cover" msgstr "" @@ -813,18 +813,18 @@ msgid "There are no books here right now! Try searching for a book to get starte msgstr "" #: bookwyrm/templates/feed/feed_layout.html:23 -#: bookwyrm/templates/user/shelf.html:25 +#: bookwyrm/templates/user/shelf.html:28 msgid "To Read" msgstr "" #: bookwyrm/templates/feed/feed_layout.html:24 -#: bookwyrm/templates/user/shelf.html:25 +#: bookwyrm/templates/user/shelf.html:28 msgid "Currently Reading" msgstr "" #: bookwyrm/templates/feed/feed_layout.html:25 #: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:11 -#: bookwyrm/templates/user/shelf.html:25 +#: bookwyrm/templates/user/shelf.html:28 msgid "Read" msgstr "" @@ -950,12 +950,12 @@ msgstr "" #: bookwyrm/templates/import_status.html:115 #: bookwyrm/templates/snippets/create_status_form.html:10 -#: bookwyrm/templates/user/shelf.html:73 +#: bookwyrm/templates/user/shelf.html:76 msgid "Title" msgstr "" #: bookwyrm/templates/import_status.html:118 -#: bookwyrm/templates/user/shelf.html:74 +#: bookwyrm/templates/user/shelf.html:77 msgid "Author" msgstr "" @@ -1915,7 +1915,7 @@ msgid "Review:" msgstr "" #: bookwyrm/templates/snippets/create_status_form.html:29 -#: bookwyrm/templates/user/shelf.html:78 +#: bookwyrm/templates/user/shelf.html:81 msgid "Rating" msgstr "" @@ -2360,7 +2360,8 @@ msgid "Update shelf" msgstr "" #: bookwyrm/templates/user/followers.html:7 -#: bookwyrm/templates/user/following.html:7 bookwyrm/templates/user/user.html:9 +#: bookwyrm/templates/user/following.html:7 +#: bookwyrm/templates/user/user.html:10 msgid "User Profile" msgstr "" @@ -2391,61 +2392,65 @@ msgstr "" msgid "Create list" msgstr "" -#: bookwyrm/templates/user/shelf.html:34 +#: bookwyrm/templates/user/shelf.html:24 bookwyrm/views/shelf.py:53 +msgid "All books" +msgstr "" + +#: bookwyrm/templates/user/shelf.html:37 msgid "Create shelf" msgstr "" -#: bookwyrm/templates/user/shelf.html:55 +#: bookwyrm/templates/user/shelf.html:58 msgid "Edit shelf" msgstr "" -#: bookwyrm/templates/user/shelf.html:75 +#: bookwyrm/templates/user/shelf.html:78 msgid "Shelved" msgstr "" -#: bookwyrm/templates/user/shelf.html:76 +#: bookwyrm/templates/user/shelf.html:79 msgid "Started" msgstr "" -#: bookwyrm/templates/user/shelf.html:77 +#: bookwyrm/templates/user/shelf.html:80 msgid "Finished" msgstr "" -#: bookwyrm/templates/user/shelf.html:121 +#: bookwyrm/templates/user/shelf.html:122 msgid "This shelf is empty." msgstr "" -#: bookwyrm/templates/user/shelf.html:127 +#: bookwyrm/templates/user/shelf.html:128 msgid "Delete shelf" msgstr "" -#: bookwyrm/templates/user/user.html:15 +#: bookwyrm/templates/user/user.html:16 msgid "Edit profile" msgstr "" -#: bookwyrm/templates/user/user.html:33 +#: bookwyrm/templates/user/user.html:34 #, python-format msgid "View all %(size)s" msgstr "" -#: bookwyrm/templates/user/user.html:46 +#: bookwyrm/templates/user/user.html:47 msgid "View all books" msgstr "" -#: bookwyrm/templates/user/user.html:58 +#: bookwyrm/templates/user/user.html:59 #, python-format msgid "Set a reading goal for %(year)s" msgstr "" -#: bookwyrm/templates/user/user.html:64 +#: bookwyrm/templates/user/user.html:65 msgid "User Activity" msgstr "" -#: bookwyrm/templates/user/user.html:67 +#: bookwyrm/templates/user/user.html:68 msgid "RSS feed" msgstr "" -#: bookwyrm/templates/user/user.html:78 +#: bookwyrm/templates/user/user.html:79 msgid "No activities yet!" msgstr "" diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po index 7e53903e..922efe4c 100644 --- a/locale/es/LC_MESSAGES/django.po +++ b/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-03-31 09:22-0700\n" +"POT-Creation-Date: 2021-03-31 10:36-0700\n" "PO-Revision-Date: 2021-03-19 11:49+0800\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -413,7 +413,7 @@ msgid "John Doe, Jane Smith" msgstr "" #: bookwyrm/templates/book/edit_book.html:155 -#: bookwyrm/templates/user/shelf.html:72 +#: bookwyrm/templates/user/shelf.html:75 msgid "Cover" msgstr "Portada:" @@ -857,18 +857,18 @@ msgid "There are no books here right now! Try searching for a book to get starte msgstr "¡No hay ningún libro aqui ahorita! Busca a un libro para empezar" #: bookwyrm/templates/feed/feed_layout.html:23 -#: bookwyrm/templates/user/shelf.html:25 +#: bookwyrm/templates/user/shelf.html:28 msgid "To Read" msgstr "Para leer" #: bookwyrm/templates/feed/feed_layout.html:24 -#: bookwyrm/templates/user/shelf.html:25 +#: bookwyrm/templates/user/shelf.html:28 msgid "Currently Reading" msgstr "Leyendo actualmente" #: bookwyrm/templates/feed/feed_layout.html:25 #: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:11 -#: bookwyrm/templates/user/shelf.html:25 +#: bookwyrm/templates/user/shelf.html:28 msgid "Read" msgstr "Leer" @@ -996,12 +996,12 @@ msgstr "Libro" #: bookwyrm/templates/import_status.html:115 #: bookwyrm/templates/snippets/create_status_form.html:10 -#: bookwyrm/templates/user/shelf.html:73 +#: bookwyrm/templates/user/shelf.html:76 msgid "Title" msgstr "Título" #: bookwyrm/templates/import_status.html:118 -#: bookwyrm/templates/user/shelf.html:74 +#: bookwyrm/templates/user/shelf.html:77 msgid "Author" msgstr "Autor/Autora" @@ -2023,7 +2023,7 @@ msgid "Review:" msgstr "Reseña" #: bookwyrm/templates/snippets/create_status_form.html:29 -#: bookwyrm/templates/user/shelf.html:78 +#: bookwyrm/templates/user/shelf.html:81 msgid "Rating" msgstr "Calificación" @@ -2493,7 +2493,8 @@ msgid "Update shelf" msgstr "Actualizar estante" #: bookwyrm/templates/user/followers.html:7 -#: bookwyrm/templates/user/following.html:7 bookwyrm/templates/user/user.html:9 +#: bookwyrm/templates/user/following.html:7 +#: bookwyrm/templates/user/user.html:10 msgid "User Profile" msgstr "Perfil de usuario" @@ -2524,62 +2525,68 @@ msgstr "Listas: %(username)s" msgid "Create list" msgstr "Crear lista" -#: bookwyrm/templates/user/shelf.html:34 +#: bookwyrm/templates/user/shelf.html:24 bookwyrm/views/shelf.py:53 +#, fuzzy +#| msgid "books" +msgid "All books" +msgstr "libros" + +#: bookwyrm/templates/user/shelf.html:37 msgid "Create shelf" msgstr "Crear estante" -#: bookwyrm/templates/user/shelf.html:55 +#: bookwyrm/templates/user/shelf.html:58 msgid "Edit shelf" msgstr "Editar estante" -#: bookwyrm/templates/user/shelf.html:75 +#: bookwyrm/templates/user/shelf.html:78 msgid "Shelved" msgstr "Archivado" -#: bookwyrm/templates/user/shelf.html:76 +#: bookwyrm/templates/user/shelf.html:79 msgid "Started" msgstr "Empezado" -#: bookwyrm/templates/user/shelf.html:77 +#: bookwyrm/templates/user/shelf.html:80 msgid "Finished" msgstr "Terminado" -#: bookwyrm/templates/user/shelf.html:121 +#: bookwyrm/templates/user/shelf.html:122 msgid "This shelf is empty." msgstr "Este estante está vacio." -#: bookwyrm/templates/user/shelf.html:127 +#: bookwyrm/templates/user/shelf.html:128 msgid "Delete shelf" msgstr "Eliminar estante" -#: bookwyrm/templates/user/user.html:15 +#: bookwyrm/templates/user/user.html:16 msgid "Edit profile" msgstr "Editar perfil" -#: bookwyrm/templates/user/user.html:33 +#: bookwyrm/templates/user/user.html:34 #, fuzzy, python-format #| msgid "See all %(size)s" msgid "View all %(size)s" msgstr "Ver %(size)s" -#: bookwyrm/templates/user/user.html:46 +#: bookwyrm/templates/user/user.html:47 msgid "View all books" msgstr "" -#: bookwyrm/templates/user/user.html:58 +#: bookwyrm/templates/user/user.html:59 #, python-format msgid "Set a reading goal for %(year)s" msgstr "Establecer una meta de lectura para %(year)s" -#: bookwyrm/templates/user/user.html:64 +#: bookwyrm/templates/user/user.html:65 msgid "User Activity" msgstr "Actividad de usuario" -#: bookwyrm/templates/user/user.html:67 +#: bookwyrm/templates/user/user.html:68 msgid "RSS feed" msgstr "Feed RSS" -#: bookwyrm/templates/user/user.html:78 +#: bookwyrm/templates/user/user.html:79 msgid "No activities yet!" msgstr "¡Aún no actividades!" diff --git a/locale/fr_FR/LC_MESSAGES/django.po b/locale/fr_FR/LC_MESSAGES/django.po index 6c6ecb40..7677b0ed 100644 --- a/locale/fr_FR/LC_MESSAGES/django.po +++ b/locale/fr_FR/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.1.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-03-31 09:22-0700\n" +"POT-Creation-Date: 2021-03-31 10:36-0700\n" "PO-Revision-Date: 2021-03-02 12:37+0100\n" "Last-Translator: Fabien Basmaison \n" "Language-Team: Mouse Reeve \n" @@ -422,7 +422,7 @@ msgid "John Doe, Jane Smith" msgstr "" #: bookwyrm/templates/book/edit_book.html:155 -#: bookwyrm/templates/user/shelf.html:72 +#: bookwyrm/templates/user/shelf.html:75 msgid "Cover" msgstr "Couverture" @@ -876,14 +876,14 @@ msgid "There are no books here right now! Try searching for a book to get starte msgstr "Aucun livre ici pour l’instant ! Cherchez un livre pour commencer" #: bookwyrm/templates/feed/feed_layout.html:23 -#: bookwyrm/templates/user/shelf.html:25 +#: bookwyrm/templates/user/shelf.html:28 #, fuzzy #| msgid "Read" msgid "To Read" msgstr "Lu" #: bookwyrm/templates/feed/feed_layout.html:24 -#: bookwyrm/templates/user/shelf.html:25 +#: bookwyrm/templates/user/shelf.html:28 #, fuzzy #| msgid "Started reading" msgid "Currently Reading" @@ -891,7 +891,7 @@ msgstr "Commencer la lecture" #: bookwyrm/templates/feed/feed_layout.html:25 #: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:11 -#: bookwyrm/templates/user/shelf.html:25 +#: bookwyrm/templates/user/shelf.html:28 msgid "Read" msgstr "Lu" @@ -1023,12 +1023,12 @@ msgstr "Livre" #: bookwyrm/templates/import_status.html:115 #: bookwyrm/templates/snippets/create_status_form.html:10 -#: bookwyrm/templates/user/shelf.html:73 +#: bookwyrm/templates/user/shelf.html:76 msgid "Title" msgstr "Titre" #: bookwyrm/templates/import_status.html:118 -#: bookwyrm/templates/user/shelf.html:74 +#: bookwyrm/templates/user/shelf.html:77 msgid "Author" msgstr "Auteur ou autrice" @@ -2055,7 +2055,7 @@ msgid "Review:" msgstr "Critique" #: bookwyrm/templates/snippets/create_status_form.html:29 -#: bookwyrm/templates/user/shelf.html:78 +#: bookwyrm/templates/user/shelf.html:81 msgid "Rating" msgstr "Note" @@ -2531,7 +2531,8 @@ msgid "Update shelf" msgstr "Mettre l’étagère à jour" #: bookwyrm/templates/user/followers.html:7 -#: bookwyrm/templates/user/following.html:7 bookwyrm/templates/user/user.html:9 +#: bookwyrm/templates/user/following.html:7 +#: bookwyrm/templates/user/user.html:10 msgid "User Profile" msgstr "Profil" @@ -2563,62 +2564,68 @@ msgstr "Listes : %(username)s" msgid "Create list" msgstr "Créer une liste" -#: bookwyrm/templates/user/shelf.html:34 +#: bookwyrm/templates/user/shelf.html:24 bookwyrm/views/shelf.py:53 +#, fuzzy +#| msgid "books" +msgid "All books" +msgstr "livres" + +#: bookwyrm/templates/user/shelf.html:37 msgid "Create shelf" msgstr "Créer l’étagère" -#: bookwyrm/templates/user/shelf.html:55 +#: bookwyrm/templates/user/shelf.html:58 msgid "Edit shelf" msgstr "Modifier l’étagère" -#: bookwyrm/templates/user/shelf.html:75 +#: bookwyrm/templates/user/shelf.html:78 msgid "Shelved" msgstr "Ajouté à une étagère" -#: bookwyrm/templates/user/shelf.html:76 +#: bookwyrm/templates/user/shelf.html:79 msgid "Started" msgstr "Commencé" -#: bookwyrm/templates/user/shelf.html:77 +#: bookwyrm/templates/user/shelf.html:80 msgid "Finished" msgstr "Terminé" -#: bookwyrm/templates/user/shelf.html:121 +#: bookwyrm/templates/user/shelf.html:122 msgid "This shelf is empty." msgstr "Cette étagère est vide" -#: bookwyrm/templates/user/shelf.html:127 +#: bookwyrm/templates/user/shelf.html:128 msgid "Delete shelf" msgstr "Supprimer l’étagère" -#: bookwyrm/templates/user/user.html:15 +#: bookwyrm/templates/user/user.html:16 msgid "Edit profile" msgstr "Modifier le profil" -#: bookwyrm/templates/user/user.html:33 +#: bookwyrm/templates/user/user.html:34 #, fuzzy, python-format #| msgid "See all %(size)s" msgid "View all %(size)s" msgstr "Voir les %(size)s" -#: bookwyrm/templates/user/user.html:46 +#: bookwyrm/templates/user/user.html:47 msgid "View all books" msgstr "" -#: bookwyrm/templates/user/user.html:58 +#: bookwyrm/templates/user/user.html:59 #, python-format msgid "Set a reading goal for %(year)s" msgstr "Définir un défi lecture pour %(year)s" -#: bookwyrm/templates/user/user.html:64 +#: bookwyrm/templates/user/user.html:65 msgid "User Activity" msgstr "Activité du compte" -#: bookwyrm/templates/user/user.html:67 +#: bookwyrm/templates/user/user.html:68 msgid "RSS feed" msgstr "Flux RSS" -#: bookwyrm/templates/user/user.html:78 +#: bookwyrm/templates/user/user.html:79 msgid "No activities yet!" msgstr "Aucune activité pour l’instant !" diff --git a/locale/zh_CN/LC_MESSAGES/django.po b/locale/zh_CN/LC_MESSAGES/django.po index 2f547f10..8680ab96 100644 --- a/locale/zh_CN/LC_MESSAGES/django.po +++ b/locale/zh_CN/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.1.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-03-31 09:22-0700\n" +"POT-Creation-Date: 2021-03-31 10:36-0700\n" "PO-Revision-Date: 2021-03-20 00:56+0000\n" "Last-Translator: Kana \n" "Language-Team: Mouse Reeve \n" @@ -397,7 +397,7 @@ msgid "John Doe, Jane Smith" msgstr "张三, æŽå››" #: bookwyrm/templates/book/edit_book.html:155 -#: bookwyrm/templates/user/shelf.html:72 +#: bookwyrm/templates/user/shelf.html:75 msgid "Cover" msgstr "å°é¢" @@ -838,18 +838,18 @@ msgid "There are no books here right now! Try searching for a book to get starte msgstr "现在这里还没有任何书目ï¼å°è¯•ç€ä»Žæœç´¢æŸæœ¬ä¹¦å¼€å§‹å§" #: bookwyrm/templates/feed/feed_layout.html:23 -#: bookwyrm/templates/user/shelf.html:25 +#: bookwyrm/templates/user/shelf.html:28 msgid "To Read" msgstr "想读" #: bookwyrm/templates/feed/feed_layout.html:24 -#: bookwyrm/templates/user/shelf.html:25 +#: bookwyrm/templates/user/shelf.html:28 msgid "Currently Reading" msgstr "在读" #: bookwyrm/templates/feed/feed_layout.html:25 #: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:11 -#: bookwyrm/templates/user/shelf.html:25 +#: bookwyrm/templates/user/shelf.html:28 msgid "Read" msgstr "读过" @@ -977,12 +977,12 @@ msgstr "书目" #: bookwyrm/templates/import_status.html:115 #: bookwyrm/templates/snippets/create_status_form.html:10 -#: bookwyrm/templates/user/shelf.html:73 +#: bookwyrm/templates/user/shelf.html:76 msgid "Title" msgstr "标题" #: bookwyrm/templates/import_status.html:118 -#: bookwyrm/templates/user/shelf.html:74 +#: bookwyrm/templates/user/shelf.html:77 msgid "Author" msgstr "作者" @@ -1992,7 +1992,7 @@ msgid "Review:" msgstr "书评" #: bookwyrm/templates/snippets/create_status_form.html:29 -#: bookwyrm/templates/user/shelf.html:78 +#: bookwyrm/templates/user/shelf.html:81 msgid "Rating" msgstr "评价" @@ -2450,7 +2450,8 @@ msgid "Update shelf" msgstr "更新书架" #: bookwyrm/templates/user/followers.html:7 -#: bookwyrm/templates/user/following.html:7 bookwyrm/templates/user/user.html:9 +#: bookwyrm/templates/user/following.html:7 +#: bookwyrm/templates/user/user.html:10 msgid "User Profile" msgstr "用户个人资料" @@ -2481,62 +2482,68 @@ msgstr "列表: %(username)s" msgid "Create list" msgstr "创建列表" -#: bookwyrm/templates/user/shelf.html:34 +#: bookwyrm/templates/user/shelf.html:24 bookwyrm/views/shelf.py:53 +#, fuzzy +#| msgid "books" +msgid "All books" +msgstr "书目" + +#: bookwyrm/templates/user/shelf.html:37 msgid "Create shelf" msgstr "创建书架" -#: bookwyrm/templates/user/shelf.html:55 +#: bookwyrm/templates/user/shelf.html:58 msgid "Edit shelf" msgstr "编辑书架" -#: bookwyrm/templates/user/shelf.html:75 +#: bookwyrm/templates/user/shelf.html:78 msgid "Shelved" msgstr "已上架" -#: bookwyrm/templates/user/shelf.html:76 +#: bookwyrm/templates/user/shelf.html:79 msgid "Started" msgstr "已开始" -#: bookwyrm/templates/user/shelf.html:77 +#: bookwyrm/templates/user/shelf.html:80 msgid "Finished" msgstr "已完æˆ" -#: bookwyrm/templates/user/shelf.html:121 +#: bookwyrm/templates/user/shelf.html:122 msgid "This shelf is empty." msgstr "此书架是空的。" -#: bookwyrm/templates/user/shelf.html:127 +#: bookwyrm/templates/user/shelf.html:128 msgid "Delete shelf" msgstr "删除书架" -#: bookwyrm/templates/user/user.html:15 +#: bookwyrm/templates/user/user.html:16 msgid "Edit profile" msgstr "编辑个人资料" -#: bookwyrm/templates/user/user.html:33 +#: bookwyrm/templates/user/user.html:34 #, fuzzy, python-format #| msgid "See all %(size)s" msgid "View all %(size)s" msgstr "查看所有 %(size)s 本" -#: bookwyrm/templates/user/user.html:46 +#: bookwyrm/templates/user/user.html:47 msgid "View all books" msgstr "" -#: bookwyrm/templates/user/user.html:58 +#: bookwyrm/templates/user/user.html:59 #, python-format msgid "Set a reading goal for %(year)s" msgstr "设定 %(year)s 的阅读目标" -#: bookwyrm/templates/user/user.html:64 +#: bookwyrm/templates/user/user.html:65 msgid "User Activity" msgstr "用户活动" -#: bookwyrm/templates/user/user.html:67 +#: bookwyrm/templates/user/user.html:68 msgid "RSS feed" msgstr "RSS æµ" -#: bookwyrm/templates/user/user.html:78 +#: bookwyrm/templates/user/user.html:79 msgid "No activities yet!" msgstr "还没有活动ï¼" From 32bd426999ade39129c4b998a6ed581d2c3d5fd8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 11:04:20 -0700 Subject: [PATCH 0544/1285] Fixes shelf remote ids --- bookwyrm/models/shelf.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/bookwyrm/models/shelf.py b/bookwyrm/models/shelf.py index 515bb161..3209da5d 100644 --- a/bookwyrm/models/shelf.py +++ b/bookwyrm/models/shelf.py @@ -37,9 +37,13 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel): """ set the identifier """ super().save(*args, **kwargs) if not self.identifier: - slug = re.sub(r"[^\w]", "", self.name).lower() - self.identifier = "%s-%d" % (slug, self.id) - super().save(*args, **kwargs) + self.identifier = self.get_identifier() + super().save(*args, **kwargs, broadcast=False) + + def get_identifier(self): + """ custom-shelf-123 for the url """ + slug = re.sub(r"[^\w]", "", self.name).lower() + return "{:s}-{:d}".format(slug, self.id) @property def collection_queryset(self): @@ -49,7 +53,8 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel): def get_remote_id(self): """ shelf identifier instead of id """ base_path = self.user.remote_id - return "%s/books/%s" % (base_path, self.identifier) + identifier = self.identifier or self.get_identifier() + return "%s/books/%s" % (base_path, identifier) class Meta: """ user/shelf unqiueness """ From 1a787fd218fc42723dd4501c4e3f1606a469fe37 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 11:11:15 -0700 Subject: [PATCH 0545/1285] Soft removal of tags feature --- bookwyrm/templates/book/book.html | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/bookwyrm/templates/book/book.html b/bookwyrm/templates/book/book.html index 95cf86ad..3931ea5b 100644 --- a/bookwyrm/templates/book/book.html +++ b/bookwyrm/templates/book/book.html @@ -165,32 +165,11 @@ {% include 'snippets/readthrough.html' with readthrough=readthrough %} {% endfor %} - {% endif %} - {% if request.user.is_authenticated %}
    {% include 'snippets/create_status.html' with book=book hide_cover=True %}
    - -
    - - - {% csrf_token %} - - - - -
    {% endif %} - -
    -
    - {% for tag in tags %} - {% include 'snippets/tag.html' with book=book tag=tag user_tags=user_tags %} - {% endfor %} -
    -
    -
    {% if book.subjects %} From e78982b4c7f70ff495c1bd338e972c7403651f70 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 11:42:57 -0700 Subject: [PATCH 0546/1285] Simplifies reading goal progress covers view --- bookwyrm/templates/goal.html | 8 +++----- bookwyrm/templates/snippets/book_cover.html | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/bookwyrm/templates/goal.html b/bookwyrm/templates/goal.html index 5e967f41..fef1438b 100644 --- a/bookwyrm/templates/goal.html +++ b/bookwyrm/templates/goal.html @@ -55,11 +55,9 @@
    {% for book in goal.books %} -
    -
    - - {% include 'discover/small-book.html' with book=book.book rating=goal.ratings %} - + {% endfor %} diff --git a/bookwyrm/templates/snippets/book_cover.html b/bookwyrm/templates/snippets/book_cover.html index 92137853..0dbc3672 100644 --- a/bookwyrm/templates/snippets/book_cover.html +++ b/bookwyrm/templates/snippets/book_cover.html @@ -1,7 +1,7 @@ {% load bookwyrm_tags %}
    {% if book.cover %} -{{ book.alt_text }} +{{ book.alt_text }} {% else %}
    No cover From daf2fd1667b76522d6db0f3534bb83adf8f6a18f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 11:57:39 -0700 Subject: [PATCH 0547/1285] Perform webfinger search on all username syntaxes --- bookwyrm/views/search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/views/search.py b/bookwyrm/views/search.py index 80969d31..18bf1d40 100644 --- a/bookwyrm/views/search.py +++ b/bookwyrm/views/search.py @@ -31,7 +31,7 @@ class Search(View): return JsonResponse([r.json() for r in book_results], safe=False) # use webfinger for mastodon style account@domain.com username - if re.match(r"\B%s" % regex.full_username, query): + if re.match(regex.full_username, query): handle_remote_webfinger(query) # do a user search From 66b7a3d193af9927fe7f2801f61039c54f2019d9 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 12:03:58 -0700 Subject: [PATCH 0548/1285] Avoids error on empty search query --- bookwyrm/connectors/connector_manager.py | 2 ++ bookwyrm/views/search.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bookwyrm/connectors/connector_manager.py b/bookwyrm/connectors/connector_manager.py index 3ed25ceb..853609de 100644 --- a/bookwyrm/connectors/connector_manager.py +++ b/bookwyrm/connectors/connector_manager.py @@ -15,6 +15,8 @@ class ConnectorException(HTTPError): def search(query, min_confidence=0.1): """ find books based on arbitary keywords """ + if not query: + return [] results = [] # Have we got a ISBN ? diff --git a/bookwyrm/views/search.py b/bookwyrm/views/search.py index 18bf1d40..28f393c8 100644 --- a/bookwyrm/views/search.py +++ b/bookwyrm/views/search.py @@ -31,7 +31,7 @@ class Search(View): return JsonResponse([r.json() for r in book_results], safe=False) # use webfinger for mastodon style account@domain.com username - if re.match(regex.full_username, query): + if query and re.match(regex.full_username, query): handle_remote_webfinger(query) # do a user search @@ -73,6 +73,6 @@ class Search(View): "book_results": book_results, "user_results": user_results, "list_results": list_results, - "query": query, + "query": query or "", } return TemplateResponse(request, "search_results.html", data) From 7483cb59192a216ff8951f2d18f361b39d314427 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 13:56:26 -0700 Subject: [PATCH 0549/1285] Adds getting started find books view --- bookwyrm/static/css/format.css | 4 + bookwyrm/templates/get_started/books.html | 98 +++++++++++++++++++++++ bookwyrm/urls.py | 2 + bookwyrm/views/__init__.py | 1 + bookwyrm/views/get_started.py | 35 ++++++++ 5 files changed, 140 insertions(+) create mode 100644 bookwyrm/templates/get_started/books.html create mode 100644 bookwyrm/views/get_started.py diff --git a/bookwyrm/static/css/format.css b/bookwyrm/static/css/format.css index 65fd56ab..e3ae1ce7 100644 --- a/bookwyrm/static/css/format.css +++ b/bookwyrm/static/css/format.css @@ -21,6 +21,10 @@ html { overflow-x: auto; } +.modal-card.is-fullwidth { + min-width: 75% !important; +} + /* --- SHELVING --- */ /** @todo Replace icons with SVG symbols. diff --git a/bookwyrm/templates/get_started/books.html b/bookwyrm/templates/get_started/books.html new file mode 100644 index 00000000..7ead0710 --- /dev/null +++ b/bookwyrm/templates/get_started/books.html @@ -0,0 +1,98 @@ +{% extends 'layout.html' %} +{% load i18n %} +{% load bookwyrm_tags %} +{% load humanize %} + +{% block title %}{% trans "Welcome" %}{% endblock %} + +{% block content %} +{% with site_name=site.name %} + +{% endwith %} +{% endblock %} + diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 27fb678f..6d14beeb 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -109,6 +109,8 @@ urlpatterns = [ re_path(r"^discover/?$", views.Discover.as_view()), re_path(r"^notifications/?$", views.Notifications.as_view()), re_path(r"^directory/?", views.Directory.as_view(), name="directory"), + # Get started + re_path(r"^get-started/?$", views.GetStarted.as_view(), name="get-started"), # feeds re_path(r"^(?Phome|local|federated)/?$", views.Feed.as_view()), re_path( diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index fead2a32..9ea5ebeb 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -9,6 +9,7 @@ from .federation import Federation, FederatedServer from .feed import DirectMessage, Feed, Replies, Status from .follow import follow, unfollow from .follow import accept_follow_request, delete_follow_request +from .get_started import GetStarted from .goal import Goal, hide_goal from .import_data import Import, ImportStatus from .inbox import Inbox diff --git a/bookwyrm/views/get_started.py b/bookwyrm/views/get_started.py new file mode 100644 index 00000000..7db45806 --- /dev/null +++ b/bookwyrm/views/get_started.py @@ -0,0 +1,35 @@ +""" Helping new users figure out the lay of the land """ +from django.contrib.auth.decorators import login_required +from django.db.models import Count +from django.template.response import TemplateResponse +from django.utils.decorators import method_decorator +from django.views import View + +from bookwyrm import models +from bookwyrm.connectors import connector_manager + + +# pylint: disable= no-self-use +@method_decorator(login_required, name="dispatch") +class GetStarted(View): + """ a book! this is the stuff """ + + def get(self, request): + """ info about a book """ + query = request.GET.get('query') + book_results = [] + if query: + book_results = connector_manager.local_search(query, raw=True)[:5] + if len(book_results) < 5: + popular_books = models.Edition.objects.exclude( + parent_work__in=[b.parent_work for b in book_results], + ).annotate( + Count("shelfbook") + ).order_by("-shelfbook__count")[: 5 - len(book_results)] + + + data = { + "book_results": book_results, + "popular_books": popular_books, + } + return TemplateResponse(request, "get_started/books.html", data) From 59c54e05917fbe61490bd4fbab13e38501067658 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 13:59:33 -0700 Subject: [PATCH 0550/1285] Creates get strated layout --- bookwyrm/templates/get_started/books.html | 151 +++++++++------------ bookwyrm/templates/get_started/layout.html | 33 +++++ 2 files changed, 95 insertions(+), 89 deletions(-) create mode 100644 bookwyrm/templates/get_started/layout.html diff --git a/bookwyrm/templates/get_started/books.html b/bookwyrm/templates/get_started/books.html index 7ead0710..cbb208f8 100644 --- a/bookwyrm/templates/get_started/books.html +++ b/bookwyrm/templates/get_started/books.html @@ -1,98 +1,71 @@ -{% extends 'layout.html' %} +{% extends 'get_started/layout.html' %} {% load i18n %} -{% load bookwyrm_tags %} -{% load humanize %} -{% block title %}{% trans "Welcome" %}{% endblock %} +{% block panel %} +
    +

    {% trans "What are you reading?" %}

    +
    +
    + + {% if request.GET.query and not book_results %} +

    {% blocktrans %}Sorry, books were found. You can add books when you start using {{ site_name }}{% endblocktrans %}

    + {% endif %} +
    +
    + +
    +
    +
    -{% block content %} -{% with site_name=site.name %} - + {% endif %} +
    +

    + {% blocktrans %}Popular on {{ site_name }}{% endblocktrans %} +

    +
    + {% for book in popular_books %} +
    + {% include 'snippets/book_cover.html' with book=book %} +
    + +
    +
    + {% endfor %} +
    +
    - -
    -{% endwith %} + + {% endblock %} diff --git a/bookwyrm/templates/get_started/layout.html b/bookwyrm/templates/get_started/layout.html new file mode 100644 index 00000000..eeb01bbe --- /dev/null +++ b/bookwyrm/templates/get_started/layout.html @@ -0,0 +1,33 @@ +{% extends 'layout.html' %} +{% load i18n %} + +{% block title %}{% trans "Welcome" %}{% endblock %} + +{% block content %} +{% with site_name=site.name %} + +{% endwith %} +{% endblock %} + + From 13e153412e1afacfbd44fe8ee3911b048c388c19 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 14:53:00 -0700 Subject: [PATCH 0551/1285] Adds profile and user get started views --- bookwyrm/templates/feed/feed.html | 22 +------ bookwyrm/templates/feed/suggested_users.html | 25 ++++++++ bookwyrm/templates/get_started/books.html | 2 +- bookwyrm/templates/get_started/layout.html | 4 +- bookwyrm/templates/get_started/profile.html | 58 +++++++++++++++++++ bookwyrm/templates/get_started/users.html | 11 ++++ bookwyrm/urls.py | 16 ++++- bookwyrm/views/__init__.py | 2 +- bookwyrm/views/get_started.py | 61 ++++++++++++++++---- 9 files changed, 165 insertions(+), 36 deletions(-) create mode 100644 bookwyrm/templates/feed/suggested_users.html create mode 100644 bookwyrm/templates/get_started/profile.html create mode 100644 bookwyrm/templates/get_started/users.html diff --git a/bookwyrm/templates/feed/feed.html b/bookwyrm/templates/feed/feed.html index 3f9d36f7..e49b4c13 100644 --- a/bookwyrm/templates/feed/feed.html +++ b/bookwyrm/templates/feed/feed.html @@ -54,27 +54,7 @@ {# suggested users on the first page, two statuses down #}

    {% trans "Who to follow" %}

    -
    - {% for user in suggested_users %} -
    -
    - - {% include 'snippets/avatar.html' with user=user large=True %} - {{ user.display_name|truncatechars:10 }} - @{{ user|username|truncatechars:8 }} - - {% include 'snippets/follow_button.html' with user=user minimal=True %} - {% if user.mutuals %} -

    - {% blocktrans with mutuals=user.mutuals|intcomma count counter=user.mutuals %}{{ mutuals }} follower you follow{% plural %}{{ mutuals }} followers you follow{% endblocktrans %} -

    - {% elif user.shared_books %} -

    {% blocktrans with shared_books=user.shared_books|intcomma count counter=user.shared_books %}{{ shared_books }} book on your shelves{% plural %}{{ shared_books }} books on your shelves{% endblocktrans %}

    - {% endif %} -
    -
    - {% endfor %} -
    + {% include 'feed/suggested_users.html' with suggested_users=suggested_users %} View directory
    {% endif %} diff --git a/bookwyrm/templates/feed/suggested_users.html b/bookwyrm/templates/feed/suggested_users.html new file mode 100644 index 00000000..f66662e8 --- /dev/null +++ b/bookwyrm/templates/feed/suggested_users.html @@ -0,0 +1,25 @@ +{% load i18n %} +{% load bookwyrm_tags %} +{% load humanize %} +
    + {% for user in suggested_users %} +
    +
    + + {% include 'snippets/avatar.html' with user=user large=True %} + {{ user.display_name|truncatechars:10 }} + @{{ user|username|truncatechars:8 }} + + {% include 'snippets/follow_button.html' with user=user minimal=True %} + {% if user.mutuals %} +

    + {% blocktrans with mutuals=user.mutuals|intcomma count counter=user.mutuals %}{{ mutuals }} follower you follow{% plural %}{{ mutuals }} followers you follow{% endblocktrans %} +

    + {% elif user.shared_books %} +

    {% blocktrans with shared_books=user.shared_books|intcomma count counter=user.shared_books %}{{ shared_books }} book on your shelves{% plural %}{{ shared_books }} books on your shelves{% endblocktrans %}

    + {% endif %} +
    +
    + {% endfor %} +
    + diff --git a/bookwyrm/templates/get_started/books.html b/bookwyrm/templates/get_started/books.html index cbb208f8..eb10f4ed 100644 --- a/bookwyrm/templates/get_started/books.html +++ b/bookwyrm/templates/get_started/books.html @@ -4,7 +4,7 @@ {% block panel %}

    {% trans "What are you reading?" %}

    -
    +
    {% if request.GET.query and not book_results %} diff --git a/bookwyrm/templates/get_started/layout.html b/bookwyrm/templates/get_started/layout.html index eeb01bbe..b6d373fd 100644 --- a/bookwyrm/templates/get_started/layout.html +++ b/bookwyrm/templates/get_started/layout.html @@ -19,10 +19,12 @@
    diff --git a/bookwyrm/templates/get_started/profile.html b/bookwyrm/templates/get_started/profile.html new file mode 100644 index 00000000..7ee43c08 --- /dev/null +++ b/bookwyrm/templates/get_started/profile.html @@ -0,0 +1,58 @@ +{% extends 'get_started/layout.html' %} +{% load i18n %} + +{% block panel %} +
    +

    {% trans "A little bit about you" %}

    + {% if form.non_field_errors %} +

    {{ form.non_field_errors }}

    + {% endif %} + + {% csrf_token %} +
    +
    +
    + + + {% for error in form.name.errors %} +

    {{ error | escape }}

    + {% endfor %} +
    +
    + + + {% for error in form.summary.errors %} +

    {{ error | escape }}

    + {% endfor %} +
    +
    + +
    +
    + + {{ form.avatar }} + {% for error in form.avatar.errors %} +

    {{ error | escape }}

    + {% endfor %} +
    +
    +
    +
    + +
    +
    + + {% url 'directory' as path %} +

    {% trans "Your account will show up in the directory, and may be recommended to other BookWyrm users." %}

    +
    +
    + +
    +{% endblock %} + diff --git a/bookwyrm/templates/get_started/users.html b/bookwyrm/templates/get_started/users.html new file mode 100644 index 00000000..14c04e63 --- /dev/null +++ b/bookwyrm/templates/get_started/users.html @@ -0,0 +1,11 @@ +{% extends 'get_started/layout.html' %} +{% load i18n %} + +{% block panel %} +
    +

    {% trans "Who to follow" %}

    + + {% include 'feed/suggested_users.html' with suggested_users=suggested_users %} +
    +{% endblock %} + diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 6d14beeb..ef8b3212 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -110,7 +110,21 @@ urlpatterns = [ re_path(r"^notifications/?$", views.Notifications.as_view()), re_path(r"^directory/?", views.Directory.as_view(), name="directory"), # Get started - re_path(r"^get-started/?$", views.GetStarted.as_view(), name="get-started"), + re_path( + r"^get-started/?$", + views.GetStartedProfile.as_view(), + name="get-started-profile", + ), + re_path( + r"^get-started/books/?$", + views.GetStartedBooks.as_view(), + name="get-started-books", + ), + re_path( + r"^get-started/users/?$", + views.GetStartedUsers.as_view(), + name="get-started-users", + ), # feeds re_path(r"^(?Phome|local|federated)/?$", views.Feed.as_view()), re_path( diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 9ea5ebeb..22e6071c 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -9,7 +9,7 @@ from .federation import Federation, FederatedServer from .feed import DirectMessage, Feed, Replies, Status from .follow import follow, unfollow from .follow import accept_follow_request, delete_follow_request -from .get_started import GetStarted +from .get_started import GetStartedBooks, GetStartedProfile, GetStartedUsers from .goal import Goal, hide_goal from .import_data import Import, ImportStatus from .inbox import Inbox diff --git a/bookwyrm/views/get_started.py b/bookwyrm/views/get_started.py index 7db45806..f133e335 100644 --- a/bookwyrm/views/get_started.py +++ b/bookwyrm/views/get_started.py @@ -1,35 +1,74 @@ """ Helping new users figure out the lay of the land """ from django.contrib.auth.decorators import login_required -from django.db.models import Count +from django.db.models import Count, Q from django.template.response import TemplateResponse from django.utils.decorators import method_decorator from django.views import View -from bookwyrm import models +from bookwyrm import forms, models from bookwyrm.connectors import connector_manager +from .helpers import get_suggested_users # pylint: disable= no-self-use @method_decorator(login_required, name="dispatch") -class GetStarted(View): - """ a book! this is the stuff """ +class GetStartedProfile(View): + """ tell us about yourself """ + + def get(self, request): + """ basic profile info """ + data = { + "form": forms.EditUserForm(instance=request.user), + "next": "get-started-books", + } + return TemplateResponse(request, "get_started/profile.html", data) + + +@method_decorator(login_required, name="dispatch") +class GetStartedBooks(View): + """ name a book, any book, we gotta start somewhere """ def get(self, request): """ info about a book """ - query = request.GET.get('query') + query = request.GET.get("query") book_results = [] if query: book_results = connector_manager.local_search(query, raw=True)[:5] if len(book_results) < 5: - popular_books = models.Edition.objects.exclude( - parent_work__in=[b.parent_work for b in book_results], - ).annotate( - Count("shelfbook") - ).order_by("-shelfbook__count")[: 5 - len(book_results)] - + popular_books = ( + models.Edition.objects.exclude( + parent_work__in=[b.parent_work for b in book_results], + ) + .annotate(Count("shelfbook")) + .order_by("-shelfbook__count")[: 5 - len(book_results)] + ) data = { "book_results": book_results, "popular_books": popular_books, + "next": "get-started-users", } return TemplateResponse(request, "get_started/books.html", data) + + +@method_decorator(login_required, name="dispatch") +class GetStartedUsers(View): + """ find friends """ + + def get(self, request): + """ basic profile info """ + suggested_users = ( + get_suggested_users( + request.user, + ~Q(id=request.user.id), + ~Q(followers=request.user), + bookwyrm_user=True, + ) + .order_by("shared_books", "-mutuals", "-last_active_date") + .all()[:5] + ) + data = { + "suggested_users": suggested_users, + "next": "get-started-profile", + } + return TemplateResponse(request, "get_started/users.html", data) From c7863ec8f0ef1375eef8a23907f17a5e279f3f55 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 15:00:49 -0700 Subject: [PATCH 0552/1285] Fixes re-shelving books from all books view --- bookwyrm/templates/snippets/shelf_selector.html | 2 +- bookwyrm/templates/user/shelf.html | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/bookwyrm/templates/snippets/shelf_selector.html b/bookwyrm/templates/snippets/shelf_selector.html index 1bc448ec..edc291e0 100644 --- a/bookwyrm/templates/snippets/shelf_selector.html +++ b/bookwyrm/templates/snippets/shelf_selector.html @@ -12,7 +12,7 @@ diff --git a/bookwyrm/templates/user/shelf.html b/bookwyrm/templates/user/shelf.html index 8d23dce4..d1de6e00 100644 --- a/bookwyrm/templates/user/shelf.html +++ b/bookwyrm/templates/user/shelf.html @@ -111,7 +111,12 @@ {% endif %} {% if shelf.user == request.user %} - {% include 'snippets/shelf_selector.html' with current=shelf %} + {% if not shelf.id %} + {% active_shelf book as current %} + {% include 'snippets/shelf_selector.html' with current=current.shelf class="is-small" %} + {% else %} + {% include 'snippets/shelf_selector.html' with current=shelf class="is-small" %} + {% endif %} {% endif %} From 210cf4a7024823c1d503897df6afed52e3744be1 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 15:03:07 -0700 Subject: [PATCH 0553/1285] Fixes shelf redirects --- bookwyrm/views/shelf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/views/shelf.py b/bookwyrm/views/shelf.py index 971eb858..88982b26 100644 --- a/bookwyrm/views/shelf.py +++ b/bookwyrm/views/shelf.py @@ -102,7 +102,7 @@ def create_shelf(request): return redirect(request.headers.get("Referer", "/")) shelf = form.save() - return redirect("/user/%s/shelf/%s" % (request.user.localname, shelf.identifier)) + return redirect(shelf.local_path) @login_required @@ -114,7 +114,7 @@ def delete_shelf(request, shelf_id): return HttpResponseBadRequest() shelf.delete() - return redirect("/user/%s/shelves" % request.user.localname) + return redirect("user-shelves", request.user.localname) @login_required From f2f700c7b8edeb3ab05acd7e7c487742cf567227 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 31 Mar 2021 15:39:28 -0700 Subject: [PATCH 0554/1285] Adds error messaging for invalid cover urls --- bookwyrm/templates/book/book.html | 3 +++ bookwyrm/views/books.py | 9 +++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bookwyrm/templates/book/book.html b/bookwyrm/templates/book/book.html index 3931ea5b..b91cebba 100644 --- a/bookwyrm/templates/book/book.html +++ b/bookwyrm/templates/book/book.html @@ -49,6 +49,9 @@ {% trans "Add cover" as button_text %} {% include 'snippets/toggle/toggle_button.html' with text=button_text controls_text="add-cover" controls_uid=book.id focus="modal-title-add-cover" class="is-small" %} {% include 'book/cover_modal.html' with book=book controls_text="add-cover" controls_uid=book.id %} + {% if request.GET.cover_error %} +

    {% trans "Failed to load cover" %}

    + {% endif %}
    {% endif %} diff --git a/bookwyrm/views/books.py b/bookwyrm/views/books.py index 632cb2ad..58886cad 100644 --- a/bookwyrm/views/books.py +++ b/bookwyrm/views/books.py @@ -289,18 +289,19 @@ def upload_cover(request, book_id): url = request.POST.get("cover-url") if url: image = set_cover_from_url(url) - book.cover.save(*image) + if image: + book.cover.save(*image) - return redirect("/book/%d" % book.id) + return redirect("{:s}?cover_error=True".format(book.local_path)) form = forms.CoverForm(request.POST, request.FILES, instance=book) if not form.is_valid() or not form.files.get("cover"): - return redirect("/book/%d" % book.id) + return redirect(book.local_path) book.cover = form.files["cover"] book.save() - return redirect("/book/%s" % book.id) + return redirect(book.local_path) def set_cover_from_url(url): From fd6c1973cfdca5733dae2519d5e16d4d92a34a35 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 1 Apr 2021 07:24:56 -0700 Subject: [PATCH 0555/1285] Shelf page 500s where it should 404 --- bookwyrm/views/shelf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bookwyrm/views/shelf.py b/bookwyrm/views/shelf.py index 88982b26..41d1f135 100644 --- a/bookwyrm/views/shelf.py +++ b/bookwyrm/views/shelf.py @@ -39,7 +39,10 @@ class Shelf(View): # get the shelf and make sure the logged in user should be able to see it if shelf_identifier: - shelf = user.shelf_set.get(identifier=shelf_identifier) + try: + shelf = user.shelf_set.get(identifier=shelf_identifier) + except models.Shelf.DoesNotExist: + return HttpResponseNotFound() if not object_visible_to_user(request.user, shelf): return HttpResponseNotFound() # this is a constructed "all books" view, with a fake "shelf" obj From 47204812193eb8e3efd891afb67dac419f03090e Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 1 Apr 2021 08:02:42 -0700 Subject: [PATCH 0556/1285] Fixes accessibility bugs --- bookwyrm/templates/get_started/books.html | 28 ++++------------------ bookwyrm/templates/get_started/layout.html | 4 ++-- 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/bookwyrm/templates/get_started/books.html b/bookwyrm/templates/get_started/books.html index eb10f4ed..110bced2 100644 --- a/bookwyrm/templates/get_started/books.html +++ b/bookwyrm/templates/get_started/books.html @@ -6,7 +6,7 @@

    {% trans "What are you reading?" %}

    - + {% if request.GET.query and not book_results %}

    {% blocktrans %}Sorry, books were found. You can add books when you start using {{ site_name }}{% endblocktrans %}

    {% endif %} @@ -21,7 +21,7 @@
    -
    +

    {% trans "Suggested Books" %}

    {% if book_results %} @@ -29,17 +29,7 @@

    Search results

    {% for book in book_results %} -
    - {% include 'snippets/book_cover.html' with book=book %} -
    - -
    -
    + {% include 'get_started/book_preview.html' %} {% endfor %}
    @@ -50,17 +40,7 @@

    {% for book in popular_books %} -
    - {% include 'snippets/book_cover.html' with book=book %} -
    - -
    -
    + {% include 'get_started/book_preview.html' %} {% endfor %}
    diff --git a/bookwyrm/templates/get_started/layout.html b/bookwyrm/templates/get_started/layout.html index b6d373fd..18da2fb3 100644 --- a/bookwyrm/templates/get_started/layout.html +++ b/bookwyrm/templates/get_started/layout.html @@ -5,11 +5,11 @@ {% block content %} {% with site_name=site.name %} - - + {% endblock %} diff --git a/bookwyrm/templates/get_started/layout.html b/bookwyrm/templates/get_started/layout.html index 18da2fb3..17fc5a9b 100644 --- a/bookwyrm/templates/get_started/layout.html +++ b/bookwyrm/templates/get_started/layout.html @@ -12,7 +12,7 @@

    {% trans "Getting Started" %}

    - +
    + {% csrf_token %}

    {% trans "Suggested Books" %}

    -
    +
    {% if book_results %}

    Search results

    @@ -34,6 +35,7 @@
    {% endif %} + {% if popular_books %}

    {% blocktrans %}Popular on {{ site_name }}{% endblocktrans %} @@ -44,7 +46,11 @@ {% endfor %}

    -
    + {% endif %} + {% if not book_results and not popular_books %} +

    {% trans "No books found" %}

    + {% endif %} + {% endblock %} diff --git a/bookwyrm/tests/views/test_get_started.py b/bookwyrm/tests/views/test_get_started.py index 11896f30..89cc5a26 100644 --- a/bookwyrm/tests/views/test_get_started.py +++ b/bookwyrm/tests/views/test_get_started.py @@ -25,6 +25,9 @@ class GetStartedViews(TestCase): title="Example Edition", remote_id="https://example.com/book/1", ) + models.Connector.objects.create( + identifier="self", connector_file="self_connector", local=True + ) models.SiteSettings.objects.create() def test_profile_view(self): @@ -69,6 +72,36 @@ class GetStartedViews(TestCase): result.render() self.assertEqual(result.status_code, 200) + def test_books_view_with_query(self): + """ there are so many views, this just makes sure it LOADS """ + view = views.GetStartedBooks.as_view() + request = self.factory.get("?query=Example") + request.user = self.local_user + + result = view(request) + + self.assertIsInstance(result, TemplateResponse) + result.render() + self.assertEqual(result.status_code, 200) + + def test_books_view_post(self): + """ shelve some books """ + view = views.GetStartedBooks.as_view() + data = {self.book.id: self.local_user.shelf_set.first().id} + request = self.factory.post("", data) + request.user = self.local_user + + self.assertFalse(self.local_user.shelfbook_set.exists()) + with patch( + "bookwyrm.models.activitypub_mixin.broadcast_task.delay" + ) as delay_mock: + view(request) + self.assertEqual(delay_mock.call_count, 1) + + shelfbook = self.local_user.shelfbook_set.first() + self.assertEqual(shelfbook.book, self.book) + self.assertEqual(shelfbook.user, self.local_user) + def test_users_view(self): """ there are so many views, this just makes sure it LOADS """ view = views.GetStartedUsers.as_view() diff --git a/bookwyrm/views/get_started.py b/bookwyrm/views/get_started.py index 7bf236c8..fba01840 100644 --- a/bookwyrm/views/get_started.py +++ b/bookwyrm/views/get_started.py @@ -1,7 +1,10 @@ """ Helping new users figure out the lay of the land """ +import re + from django.contrib.auth.decorators import login_required from django.db.models import Count, Q -from django.shortcuts import redirect +from django.http import HttpResponseNotFound +from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse from django.utils.decorators import method_decorator from django.views import View @@ -17,11 +20,13 @@ from .user import save_user_form class GetStartedProfile(View): """ tell us about yourself """ + next_view = "get-started-books" + def get(self, request): """ basic profile info """ data = { "form": forms.LimitedEditUserForm(instance=request.user), - "next": "get-started-books", + "next": self.next_view, } return TemplateResponse(request, "get_started/profile.html", data) @@ -34,13 +39,15 @@ class GetStartedProfile(View): data = {"form": form, "next": "get-started-books"} return TemplateResponse(request, "get_started/profile.html", data) save_user_form(form) - return redirect('get-started-books') + return redirect(self.next_view) @method_decorator(login_required, name="dispatch") class GetStartedBooks(View): """ name a book, any book, we gotta start somewhere """ + next_view = "get-started-users" + def get(self, request): """ info about a book """ query = request.GET.get("query") @@ -57,8 +64,9 @@ class GetStartedBooks(View): for b in request.user.shelfbook_set.distinct().all() ] ) - | # - or if it's already in search results - Q(parent_work__in=[b.parent_work for b in book_results]) + | Q( # and exclude if it's already in search results + parent_work__in=[b.parent_work for b in book_results] + ) ) .annotate(Count("shelfbook")) .order_by("-shelfbook__count")[: 5 - len(book_results)] @@ -67,10 +75,26 @@ class GetStartedBooks(View): data = { "book_results": book_results, "popular_books": popular_books, - "next": "get-started-users", + "next": self.next_view, } return TemplateResponse(request, "get_started/books.html", data) + def post(self, request): + """ shelve some books """ + shelve_actions = [ + (k, v) + for k, v in request.POST.items() + if re.match(r"\d+", k) and re.match(r"\d+", v) + ] + for (book_id, shelf_id) in shelve_actions: + book = get_object_or_404(models.Edition, id=book_id) + shelf = get_object_or_404(models.Shelf, id=shelf_id) + if shelf.user != request.user: + # hmmmmm + return HttpResponseNotFound() + models.ShelfBook.objects.create(book=book, shelf=shelf, user=request.user) + return redirect(self.next_view) + @method_decorator(login_required, name="dispatch") class GetStartedUsers(View): diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py index cce921be..fbc41358 100644 --- a/bookwyrm/views/user.py +++ b/bookwyrm/views/user.py @@ -167,6 +167,7 @@ class EditUser(View): return redirect(user.local_path) + def save_user_form(form): """ special handling for the user form """ user = form.save(commit=False) From 5c10bdab7c2f67bbae133aeb695fe696bc09d69c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 1 Apr 2021 09:22:43 -0700 Subject: [PATCH 0562/1285] Adds breadcrumbs --- bookwyrm/templates/get_started/layout.html | 18 +++++++++++++++++- bookwyrm/urls.py | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/bookwyrm/templates/get_started/layout.html b/bookwyrm/templates/get_started/layout.html index 17fc5a9b..4d79dd0e 100644 --- a/bookwyrm/templates/get_started/layout.html +++ b/bookwyrm/templates/get_started/layout.html @@ -18,7 +18,23 @@ {% block panel %}{% endblock %} -
    diff --git a/bookwyrm/templates/get_started/profile.html b/bookwyrm/templates/get_started/profile.html index 7ee43c08..9c31957f 100644 --- a/bookwyrm/templates/get_started/profile.html +++ b/bookwyrm/templates/get_started/profile.html @@ -51,7 +51,7 @@ {% url 'directory' as path %}

    {% trans "Your account will show up in the directory, and may be recommended to other BookWyrm users." %}

    -
    +
    {% endblock %} diff --git a/bookwyrm/templates/get_started/users.html b/bookwyrm/templates/get_started/users.html index 14c04e63..28ab1913 100644 --- a/bookwyrm/templates/get_started/users.html +++ b/bookwyrm/templates/get_started/users.html @@ -5,6 +5,23 @@

    {% trans "Who to follow" %}

    +

    You can follow users on other BookWyrm instances and federated services like Mastodon.

    +
    +
    + + {% if request.GET.query and not user_results %} +

    {% trans "Sorry, no users were found" %}

    + {% endif %} +
    +
    + +
    +
    + {% include 'feed/suggested_users.html' with suggested_users=suggested_users %}
    {% endblock %} diff --git a/bookwyrm/views/get_started.py b/bookwyrm/views/get_started.py index fba01840..184cfc3d 100644 --- a/bookwyrm/views/get_started.py +++ b/bookwyrm/views/get_started.py @@ -114,6 +114,5 @@ class GetStartedUsers(View): ) data = { "suggested_users": suggested_users, - "next": "get-started-profile", } return TemplateResponse(request, "get_started/users.html", data) From fa17d9018f6f5ef90b8eafb328c082e178a7cf79 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 1 Apr 2021 09:39:05 -0700 Subject: [PATCH 0564/1285] Adds user search to get started view --- bookwyrm/views/get_started.py | 37 ++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/bookwyrm/views/get_started.py b/bookwyrm/views/get_started.py index 184cfc3d..f8e0cd03 100644 --- a/bookwyrm/views/get_started.py +++ b/bookwyrm/views/get_started.py @@ -2,6 +2,8 @@ import re from django.contrib.auth.decorators import login_required +from django.contrib.postgres.search import TrigramSimilarity +from django.db.models.functions import Greatest from django.db.models import Count, Q from django.http import HttpResponseNotFound from django.shortcuts import get_object_or_404, redirect @@ -102,17 +104,34 @@ class GetStartedUsers(View): def get(self, request): """ basic profile info """ - suggested_users = ( - get_suggested_users( - request.user, - ~Q(id=request.user.id), - ~Q(followers=request.user), - bookwyrm_user=True, + query = request.GET.get("query") + user_results = ( + models.User.viewer_aware_objects(request.user) + .annotate( + similarity=Greatest( + TrigramSimilarity("username", query), + TrigramSimilarity("localname", query), + ) ) - .order_by("shared_books", "-mutuals", "-last_active_date") - .all()[:5] + .filter( + similarity__gt=0.5, + ) + .order_by("-similarity")[:5] ) + + if user_results.count() < 5: + suggested_users = ( + get_suggested_users( + request.user, + ~Q(id=request.user.id), + ~Q(followers=request.user), + ~Q(id__in=user_results), + bookwyrm_user=True, + ) + .order_by("shared_books", "-mutuals", "-last_active_date") + .all()[:5 - user_results.count()] + ) data = { - "suggested_users": suggested_users, + "suggested_users": list(user_results) + list(suggested_users), } return TemplateResponse(request, "get_started/users.html", data) From 19c2c7f67c64aa5f6c731b74abc90d5eb192ddca Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 1 Apr 2021 09:39:57 -0700 Subject: [PATCH 0565/1285] test get started user view with query --- bookwyrm/tests/views/test_get_started.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bookwyrm/tests/views/test_get_started.py b/bookwyrm/tests/views/test_get_started.py index 89cc5a26..71eb4060 100644 --- a/bookwyrm/tests/views/test_get_started.py +++ b/bookwyrm/tests/views/test_get_started.py @@ -113,3 +113,15 @@ class GetStartedViews(TestCase): self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) + + def test_users_view_with_query(self): + """ there are so many views, this just makes sure it LOADS """ + view = views.GetStartedUsers.as_view() + request = self.factory.get("?query=rat") + request.user = self.local_user + + result = view(request) + + self.assertIsInstance(result, TemplateResponse) + result.render() + self.assertEqual(result.status_code, 200) From bb5aec18f12132618e237443b9d794f9a0dc9325 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 1 Apr 2021 09:46:18 -0700 Subject: [PATCH 0566/1285] Cleans up ui navigation --- bookwyrm/templates/get_started/book_preview.html | 2 +- bookwyrm/templates/get_started/layout.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bookwyrm/templates/get_started/book_preview.html b/bookwyrm/templates/get_started/book_preview.html index b65236be..04d0c424 100644 --- a/bookwyrm/templates/get_started/book_preview.html +++ b/bookwyrm/templates/get_started/book_preview.html @@ -4,7 +4,7 @@