From b4c6587972d96f7d0c1d299a5ac1ed317d1c987e Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Mon, 5 Jul 2021 13:14:35 -0700 Subject: [PATCH 01/21] Fix bw-dev resetdb, some tweaks to bw-dev The main fix here is for resetdb, it previously was failing to drop the db for me, because `web` was up and running and using the database. This commit spins up db by itself first so it can drop and re-create the database successfully, then brings up web to run the migrations. While I was in here, I also updated it so that when running `bw-dev` without any command it will also print the helptext, rather than just exiting silently, got rid of the double-echo of the helptext, and added runweb/rundb commands to run arbitrary commands via bw-dev. --- bw-dev | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/bw-dev b/bw-dev index 958ec933a..541498b44 100755 --- a/bw-dev +++ b/bw-dev @@ -39,7 +39,9 @@ function makeitblack { } CMD=$1 -shift +if [ -n "$CMD" ]; then + shift +fi # show commands as they're executed set -x @@ -56,9 +58,12 @@ case "$CMD" in ;; resetdb) clean - docker-compose up --build -d + # Start just the DB so no one else is using it + docker-compose up --build -d db execdb dropdb -U ${POSTGRES_USER} ${POSTGRES_DB} execdb createdb -U ${POSTGRES_USER} ${POSTGRES_DB} + # Now start up web so we can run the migrations + docker-compose up --build -d web initdb clean ;; @@ -110,7 +115,14 @@ case "$CMD" in generate_preview_images) runweb python manage.py generate_preview_images $@ ;; + runweb) + runweb "$@" + ;; + rundb) + rundb "$@" + ;; *) + set +x # No need to echo echo echo "Unrecognised command. Try: build, clean, up, initdb, resetdb, makemigrations, migrate, bash, shell, dbshell, restart_celery, test, pytest, test_report, black, populate_streams, generate_preview_images" ;; esac From fede777e9bf31d3ae93b2c9c38dc2e914dac5359 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 7 Jul 2021 09:37:21 -0700 Subject: [PATCH 02/21] Hide deleted status counts from book page --- bookwyrm/templates/book/book.html | 8 ++++---- bookwyrm/views/books.py | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/bookwyrm/templates/book/book.html b/bookwyrm/templates/book/book.html index 26bd8322c..d721d4fd5 100644 --- a/bookwyrm/templates/book/book.html +++ b/bookwyrm/templates/book/book.html @@ -209,24 +209,24 @@ diff --git a/bookwyrm/views/books.py b/bookwyrm/views/books.py index a4b9f312a..492d0cac4 100644 --- a/bookwyrm/views/books.py +++ b/bookwyrm/views/books.py @@ -59,7 +59,7 @@ class Book(View): queryset = book.comment_set else: queryset = book.quotation_set - queryset = queryset.filter(user=request.user) + queryset = queryset.filter(user=request.user, deleted=False) else: queryset = reviews.exclude(Q(content__isnull=True) | Q(content="")) queryset = queryset.select_related("user") @@ -102,10 +102,11 @@ class Book(View): book__parent_work=book.parent_work, ).select_related("shelf", "book") + filters = {"user": request.user, "deleted": False} data["user_statuses"] = { - "review_count": book.review_set.filter(user=request.user).count(), - "comment_count": book.comment_set.filter(user=request.user).count(), - "quotation_count": book.quotation_set.filter(user=request.user).count(), + "review_count": book.review_set.filter(**filters).count(), + "comment_count": book.comment_set.filter(**filters).count(), + "quotation_count": book.quotation_set.filter(**filters).count(), } return TemplateResponse(request, "book/book.html", data) From b3cd9483d33d7273bec8bde68d147906b9ab025a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 7 Jul 2021 09:47:07 -0700 Subject: [PATCH 03/21] Adds test for misinterpreted links --- bookwyrm/tests/views/test_status.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bookwyrm/tests/views/test_status.py b/bookwyrm/tests/views/test_status.py index 9253e6e57..68ddcd289 100644 --- a/bookwyrm/tests/views/test_status.py +++ b/bookwyrm/tests/views/test_status.py @@ -8,6 +8,7 @@ from bookwyrm import forms, models, views from bookwyrm.settings import DOMAIN +# pylint: disable=invalid-name @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") class StatusViews(TestCase): """viewing and creating statuses""" @@ -318,6 +319,15 @@ class StatusViews(TestCase): '

hi and fish.com ' "is rad

", ) + def test_to_markdown_detect_url(self, _): + """this is mostly handled in other places, but nonetheless""" + text = "http://fish.com/@hello#okay" + result = views.status.to_markdown(text) + self.assertEqual( + result, + '

fish.com/@hello#okay

', + ) + def test_to_markdown_link(self, _): """this is mostly handled in other places, but nonetheless""" text = "[hi](http://fish.com) is rad" From 23631c3c4fa4edf5f04cb9930828c15e48cfb0f2 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 7 Jul 2021 09:49:22 -0700 Subject: [PATCH 04/21] Fixes failing links --- bookwyrm/views/status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/views/status.py b/bookwyrm/views/status.py index 61b6dc7aa..651021b63 100644 --- a/bookwyrm/views/status.py +++ b/bookwyrm/views/status.py @@ -150,7 +150,7 @@ def find_mentions(content): def format_links(content): """detect and format links""" return re.sub( - r'([^(href=")]|^|\()(https?:\/\/(%s([\w\.\-_\/+&\?=:;,])*))' % regex.DOMAIN, + r'([^(href=")]|^|\()(https?:\/\/(%s([\w\.\-_\/+&\?=:;,@#])*))' % regex.DOMAIN, r'\g<1>\g<3>', content, ) From b41d9440fd68a96c17966f498eca1fb5cf5b2a2b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 7 Jul 2021 10:24:23 -0700 Subject: [PATCH 05/21] Adds tests for ordered collection page generator --- .../tests/models/test_activitypub_mixin.py | 190 ++++++++++++------ 1 file changed, 124 insertions(+), 66 deletions(-) diff --git a/bookwyrm/tests/models/test_activitypub_mixin.py b/bookwyrm/tests/models/test_activitypub_mixin.py index 020d031d2..553ca5469 100644 --- a/bookwyrm/tests/models/test_activitypub_mixin.py +++ b/bookwyrm/tests/models/test_activitypub_mixin.py @@ -9,11 +9,19 @@ from django.test import TestCase from bookwyrm.activitypub.base_activity import ActivityObject from bookwyrm import models from bookwyrm.models import base_model -from bookwyrm.models.activitypub_mixin import ActivitypubMixin -from bookwyrm.models.activitypub_mixin import ActivityMixin, ObjectMixin +from bookwyrm.models.activitypub_mixin import ( + ActivitypubMixin, + ActivityMixin, + ObjectMixin, + OrderedCollectionMixin, + to_ordered_collection_page, +) +from bookwyrm.settings import PAGE_LENGTH +# pylint: disable=invalid-name @patch("bookwyrm.activitystreams.ActivityStream.add_status") +@patch("bookwyrm.preview_images.generate_user_preview_image_task.delay") class ActivitypubMixins(TestCase): """functionality shared across models""" @@ -45,8 +53,7 @@ class ActivitypubMixins(TestCase): "published": "2020-12-04T17:52:22.623807+00:00", } - # ActivitypubMixin - def test_to_activity(self, _): + def test_to_activity(self, *_): """model to ActivityPub json""" @dataclass(init=False) @@ -67,7 +74,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(activity["id"], "https://www.example.com/test") self.assertEqual(activity["type"], "Test") - def test_find_existing_by_remote_id(self, _): + def test_find_existing_by_remote_id(self, *_): """attempt to match a remote id to an object in the db""" # uses a different remote id scheme # this isn't really part of this test directly but it's helpful to state @@ -101,7 +108,7 @@ class ActivitypubMixins(TestCase): # test subclass match result = models.Status.find_existing_by_remote_id("https://comment.net") - def test_find_existing(self, _): + def test_find_existing(self, *_): """match a blob of data to a model""" with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): book = models.Edition.objects.create( @@ -112,7 +119,7 @@ class ActivitypubMixins(TestCase): result = models.Edition.find_existing({"openlibraryKey": "OL1234"}) self.assertEqual(result, book) - def test_get_recipients_public_object(self, _): + def test_get_recipients_public_object(self, *_): """determines the recipients for an object's broadcast""" MockSelf = namedtuple("Self", ("privacy")) mock_self = MockSelf("public") @@ -120,7 +127,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], self.remote_user.inbox) - def test_get_recipients_public_user_object_no_followers(self, _): + def test_get_recipients_public_user_object_no_followers(self, *_): """determines the recipients for a user's object broadcast""" MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) @@ -128,7 +135,7 @@ class ActivitypubMixins(TestCase): recipients = ActivitypubMixin.get_recipients(mock_self) self.assertEqual(len(recipients), 0) - def test_get_recipients_public_user_object(self, _): + def test_get_recipients_public_user_object(self, *_): """determines the recipients for a user's object broadcast""" MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) @@ -138,22 +145,21 @@ class ActivitypubMixins(TestCase): self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], self.remote_user.inbox) - def test_get_recipients_public_user_object_with_mention(self, _): + def test_get_recipients_public_user_object_with_mention(self, *_): """determines the recipients for a user's object broadcast""" MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) self.local_user.followers.add(self.remote_user) - with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): - with patch("bookwyrm.models.user.set_remote_server.delay"): - another_remote_user = models.User.objects.create_user( - "nutria", - "nutria@nutria.com", - "nutriaword", - local=False, - remote_id="https://example.com/users/nutria", - inbox="https://example.com/users/nutria/inbox", - outbox="https://example.com/users/nutria/outbox", - ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + another_remote_user = models.User.objects.create_user( + "nutria", + "nutria@nutria.com", + "nutriaword", + local=False, + remote_id="https://example.com/users/nutria", + inbox="https://example.com/users/nutria/inbox", + outbox="https://example.com/users/nutria/outbox", + ) MockSelf = namedtuple("Self", ("privacy", "user", "recipients")) mock_self = MockSelf("public", self.local_user, [another_remote_user]) @@ -162,22 +168,21 @@ class ActivitypubMixins(TestCase): self.assertTrue(another_remote_user.inbox in recipients) self.assertTrue(self.remote_user.inbox in recipients) - def test_get_recipients_direct(self, _): + def test_get_recipients_direct(self, *_): """determines the recipients for a user's object broadcast""" MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) self.local_user.followers.add(self.remote_user) - with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): - with patch("bookwyrm.models.user.set_remote_server.delay"): - another_remote_user = models.User.objects.create_user( - "nutria", - "nutria@nutria.com", - "nutriaword", - local=False, - remote_id="https://example.com/users/nutria", - inbox="https://example.com/users/nutria/inbox", - outbox="https://example.com/users/nutria/outbox", - ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + another_remote_user = models.User.objects.create_user( + "nutria", + "nutria@nutria.com", + "nutriaword", + local=False, + remote_id="https://example.com/users/nutria", + inbox="https://example.com/users/nutria/inbox", + outbox="https://example.com/users/nutria/outbox", + ) MockSelf = namedtuple("Self", ("privacy", "user", "recipients")) mock_self = MockSelf("direct", self.local_user, [another_remote_user]) @@ -185,22 +190,21 @@ class ActivitypubMixins(TestCase): self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], another_remote_user.inbox) - def test_get_recipients_combine_inboxes(self, _): + def test_get_recipients_combine_inboxes(self, *_): """should combine users with the same shared_inbox""" self.remote_user.shared_inbox = "http://example.com/inbox" self.remote_user.save(broadcast=False) - with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): - with patch("bookwyrm.models.user.set_remote_server.delay"): - another_remote_user = models.User.objects.create_user( - "nutria", - "nutria@nutria.com", - "nutriaword", - local=False, - remote_id="https://example.com/users/nutria", - inbox="https://example.com/users/nutria/inbox", - shared_inbox="http://example.com/inbox", - outbox="https://example.com/users/nutria/outbox", - ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + another_remote_user = models.User.objects.create_user( + "nutria", + "nutria@nutria.com", + "nutriaword", + local=False, + remote_id="https://example.com/users/nutria", + inbox="https://example.com/users/nutria/inbox", + shared_inbox="http://example.com/inbox", + outbox="https://example.com/users/nutria/outbox", + ) MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) self.local_user.followers.add(self.remote_user) @@ -210,20 +214,19 @@ class ActivitypubMixins(TestCase): self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], "http://example.com/inbox") - def test_get_recipients_software(self, _): + def test_get_recipients_software(self, *_): """should differentiate between bookwyrm and other remote users""" - with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): - with patch("bookwyrm.models.user.set_remote_server.delay"): - another_remote_user = models.User.objects.create_user( - "nutria", - "nutria@nutria.com", - "nutriaword", - local=False, - remote_id="https://example.com/users/nutria", - inbox="https://example.com/users/nutria/inbox", - outbox="https://example.com/users/nutria/outbox", - bookwyrm_user=False, - ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + another_remote_user = models.User.objects.create_user( + "nutria", + "nutria@nutria.com", + "nutriaword", + local=False, + remote_id="https://example.com/users/nutria", + inbox="https://example.com/users/nutria/inbox", + outbox="https://example.com/users/nutria/outbox", + bookwyrm_user=False, + ) MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) self.local_user.followers.add(self.remote_user) @@ -241,7 +244,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(recipients[0], another_remote_user.inbox) # ObjectMixin - def test_object_save_create(self, _): + def test_object_save_create(self, *_): """should save uneventufully when broadcast is disabled""" class Success(Exception): @@ -272,7 +275,7 @@ class ActivitypubMixins(TestCase): ObjectModel(user=self.local_user).save(broadcast=False) ObjectModel(user=None).save() - def test_object_save_update(self, _): + def test_object_save_update(self, *_): """should save uneventufully when broadcast is disabled""" class Success(Exception): @@ -298,7 +301,7 @@ class ActivitypubMixins(TestCase): with self.assertRaises(Success): UpdateObjectModel(id=1, last_edited_by=self.local_user).save() - def test_object_save_delete(self, _): + def test_object_save_delete(self, *_): """should create delete activities when objects are deleted by flag""" class ActivitySuccess(Exception): @@ -320,7 +323,7 @@ class ActivitypubMixins(TestCase): with self.assertRaises(ActivitySuccess): DeletableObjectModel(id=1, user=self.local_user, deleted=True).save() - def test_to_delete_activity(self, _): + def test_to_delete_activity(self, *_): """wrapper for Delete activity""" MockSelf = namedtuple("Self", ("remote_id", "to_activity")) mock_self = MockSelf( @@ -335,7 +338,7 @@ class ActivitypubMixins(TestCase): activity["cc"], ["https://www.w3.org/ns/activitystreams#Public"] ) - def test_to_update_activity(self, _): + def test_to_update_activity(self, *_): """ditto above but for Update""" MockSelf = namedtuple("Self", ("remote_id", "to_activity")) mock_self = MockSelf( @@ -352,8 +355,7 @@ class ActivitypubMixins(TestCase): ) self.assertIsInstance(activity["object"], dict) - # Activity mixin - def test_to_undo_activity(self, _): + def test_to_undo_activity(self, *_): """and again, for Undo""" MockSelf = namedtuple("Self", ("remote_id", "to_activity", "user")) mock_self = MockSelf( @@ -366,3 +368,59 @@ class ActivitypubMixins(TestCase): self.assertEqual(activity["actor"], self.local_user.remote_id) self.assertEqual(activity["type"], "Undo") self.assertIsInstance(activity["object"], dict) + + @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") + def test_to_ordered_collection_page(self, *_): + """make sure the paged results of an ordered collection work""" + self.assertEqual(PAGE_LENGTH, 15) + for number in range(0, 2 * PAGE_LENGTH): + models.Status.objects.create( + user=self.local_user, + content="test status {:d}".format(number), + ) + page_1 = to_ordered_collection_page( + models.Status.objects.all(), "http://fish.com/", page=1 + ) + self.assertEqual(page_1.partOf, "http://fish.com/") + self.assertEqual(page_1.id, "http://fish.com/?page=1") + self.assertEqual(page_1.next, "http://fish.com/?page=2") + self.assertEqual(page_1.orderedItems[0]["content"], "test status 29") + self.assertEqual(page_1.orderedItems[1]["content"], "test status 28") + + page_2 = to_ordered_collection_page( + models.Status.objects.all(), "http://fish.com/", page=2 + ) + self.assertEqual(page_2.partOf, "http://fish.com/") + self.assertEqual(page_2.id, "http://fish.com/?page=2") + self.assertEqual(page_2.orderedItems[0]["content"], "test status 14") + self.assertEqual(page_2.orderedItems[-1]["content"], "test status 0") + + @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") + def test_to_ordered_collection(self, *_): + """convert a queryset into an ordered collection object""" + self.assertEqual(PAGE_LENGTH, 15) + + for number in range(0, 2 * PAGE_LENGTH): + models.Status.objects.create( + user=self.local_user, + content="test status {:d}".format(number), + ) + + MockSelf = namedtuple("Self", ("remote_id")) + mock_self = MockSelf("") + + collection = OrderedCollectionMixin.to_ordered_collection( + mock_self, models.Status.objects.all(), remote_id="http://fish.com/" + ) + + self.assertEqual(collection.totalItems, 30) + self.assertEqual(collection.first, "http://fish.com/?page=1") + self.assertEqual(collection.last, "http://fish.com/?page=2") + + page_2 = OrderedCollectionMixin.to_ordered_collection( + mock_self, models.Status.objects.all(), remote_id="http://fish.com/", page=2 + ) + self.assertEqual(page_2.partOf, "http://fish.com/") + self.assertEqual(page_2.id, "http://fish.com/?page=2") + self.assertEqual(page_2.orderedItems[0]["content"], "test status 14") + self.assertEqual(page_2.orderedItems[-1]["content"], "test status 0") From 88c23117ffd725ce2eb48519df885748e9f6902a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 7 Jul 2021 10:56:19 -0700 Subject: [PATCH 06/21] Fixes outbox pagination --- bookwyrm/models/activitypub_mixin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 83b4c0abe..729d9cba0 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -30,6 +30,7 @@ logger = logging.getLogger(__name__) PropertyField = namedtuple("PropertyField", ("set_activity_from_field")) +# pylint: disable=invalid-name def set_activity_from_property_field(activity, obj, field): """assign a model property value to the activity json""" activity[field[1]] = getattr(obj, field[0]) @@ -318,7 +319,9 @@ class OrderedCollectionPageMixin(ObjectMixin): remote_id = remote_id or self.remote_id if page: - return to_ordered_collection_page(queryset, remote_id, **kwargs) + if isinstance(page, list) and len(page) > 0: + page = page[0] + return to_ordered_collection_page(queryset, remote_id, page=page, **kwargs) if collection_only or not hasattr(self, "activity_serializer"): serializer = activitypub.OrderedCollection From 3f15e4410ed2e43684f1578ed8db63428afeca4c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 12 Jul 2021 16:17:20 -0700 Subject: [PATCH 07/21] Fixes link to edit book --- bookwyrm/templates/book/book.html | 2 +- bookwyrm/urls.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/templates/book/book.html b/bookwyrm/templates/book/book.html index d721d4fd5..9a75cbe94 100644 --- a/bookwyrm/templates/book/book.html +++ b/bookwyrm/templates/book/book.html @@ -47,7 +47,7 @@ {% if user_authenticated and can_edit_book %}
- + {% trans "Edit Book" %} diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index ea73f0388..7eccfd651 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -295,7 +295,7 @@ urlpatterns = [ views.Book.as_view(), name="book-user-statuses", ), - re_path(r"%s/edit/?$" % BOOK_PATH, views.EditBook.as_view()), + re_path(r"%s/edit/?$" % BOOK_PATH, views.EditBook.as_view(), name="edit-book"), re_path(r"%s/confirm/?$" % BOOK_PATH, views.ConfirmEditBook.as_view()), re_path(r"^create-book/?$", views.EditBook.as_view(), name="create-book"), re_path(r"^create-book/confirm?$", views.ConfirmEditBook.as_view()), From a16d75976649c7f02f8563165e90c44e7dc36928 Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Tue, 13 Jul 2021 21:02:34 -0700 Subject: [PATCH 08/21] Add shelved_date field and populate it on import --- bookwyrm/importers/importer.py | 7 +++- bookwyrm/migrations/0078_add_shelved_date.py | 34 ++++++++++++++++++++ bookwyrm/models/shelf.py | 4 ++- 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 bookwyrm/migrations/0078_add_shelved_date.py diff --git a/bookwyrm/importers/importer.py b/bookwyrm/importers/importer.py index 203db0343..d5f1449ca 100644 --- a/bookwyrm/importers/importer.py +++ b/bookwyrm/importers/importer.py @@ -2,6 +2,8 @@ import csv import logging +from django.utils import timezone + from bookwyrm import models from bookwyrm.models import ImportJob, ImportItem from bookwyrm.tasks import app @@ -100,7 +102,10 @@ def handle_imported_book(source, user, item, include_reviews, privacy): # shelve the book if it hasn't been shelved already if item.shelf and not existing_shelf: desired_shelf = models.Shelf.objects.get(identifier=item.shelf, user=user) - models.ShelfBook.objects.create(book=item.book, shelf=desired_shelf, user=user) + shelved_date = item.date_added or timezone.now() + models.ShelfBook.objects.create( + book=item.book, shelf=desired_shelf, user=user, shelved_date=shelved_date + ) for read in item.reads: # check for an existing readthrough with the same dates diff --git a/bookwyrm/migrations/0078_add_shelved_date.py b/bookwyrm/migrations/0078_add_shelved_date.py new file mode 100644 index 000000000..b8a95ab17 --- /dev/null +++ b/bookwyrm/migrations/0078_add_shelved_date.py @@ -0,0 +1,34 @@ +# Generated by Django 3.2.4 on 2021-07-03 08:25 + +from django.db import migrations, models +import django.utils.timezone + + +def copy_created_date(app_registry, schema_editor): + db_alias = schema_editor.connection.alias + ShelfBook = app_registry.get_model("bookwyrm", "ShelfBook") + ShelfBook.objects.all().update(shelved_date=models.F("created_date")) + + +def do_nothing(app_registry, schema_editor): + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0077_auto_20210623_2155"), + ] + + operations = [ + migrations.AlterModelOptions( + name="shelfbook", + options={"ordering": ("-shelved_date",)}, + ), + migrations.AddField( + model_name="shelfbook", + name="shelved_date", + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.RunPython(copy_created_date, reverse_code=do_nothing), + ] diff --git a/bookwyrm/models/shelf.py b/bookwyrm/models/shelf.py index 4110ae8dc..c4e907d27 100644 --- a/bookwyrm/models/shelf.py +++ b/bookwyrm/models/shelf.py @@ -1,6 +1,7 @@ """ puttin' books on shelves """ import re from django.db import models +from django.utils import timezone from bookwyrm import activitypub from .activitypub_mixin import CollectionItemMixin, OrderedCollectionMixin @@ -69,6 +70,7 @@ class ShelfBook(CollectionItemMixin, BookWyrmModel): "Edition", on_delete=models.PROTECT, activitypub_field="book" ) shelf = models.ForeignKey("Shelf", on_delete=models.PROTECT) + shelved_date = models.DateTimeField(default=timezone.now) user = fields.ForeignKey( "User", on_delete=models.PROTECT, activitypub_field="actor" ) @@ -86,4 +88,4 @@ class ShelfBook(CollectionItemMixin, BookWyrmModel): you can't put a book on shelf twice""" unique_together = ("book", "shelf") - ordering = ("-created_date",) + ordering = ("-shelved_date", "-created_date", "-updated_date") From eadf5cf4106fbaf6bd18bd7c19521addc60523e0 Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Tue, 13 Jul 2021 21:25:17 -0700 Subject: [PATCH 09/21] Use shelved date for display I'm not sure if there's a better way to access this field, accessing via book.shelfbook__shelved_date in the template didn't seem to work --- bookwyrm/templates/user/shelf/shelf.html | 2 +- bookwyrm/views/shelf.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bookwyrm/templates/user/shelf/shelf.html b/bookwyrm/templates/user/shelf/shelf.html index 2163db8cb..01a7eee9e 100644 --- a/bookwyrm/templates/user/shelf/shelf.html +++ b/bookwyrm/templates/user/shelf/shelf.html @@ -103,7 +103,7 @@ {% include 'snippets/authors.html' %} - {{ book.created_date|naturalday }} + {{ book.shelved_date|naturalday }} {% latest_read_through book user as read_through %} diff --git a/bookwyrm/views/shelf.py b/bookwyrm/views/shelf.py index 540975094..e9ad074d1 100644 --- a/bookwyrm/views/shelf.py +++ b/bookwyrm/views/shelf.py @@ -2,7 +2,7 @@ from collections import namedtuple from django.db import IntegrityError -from django.db.models import OuterRef, Subquery +from django.db.models import OuterRef, Subquery, F from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator from django.http import HttpResponseBadRequest, HttpResponseNotFound @@ -69,7 +69,8 @@ class Shelf(View): reviews = privacy_filter(request.user, reviews) books = books.annotate( - rating=Subquery(reviews.values("rating")[:1]) + rating=Subquery(reviews.values("rating")[:1]), + shelved_date=F("shelfbook__shelved_date"), ).prefetch_related("authors") paginated = Paginator( From f867b1c81d8247fccd30485b7e2583250a077cb4 Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Tue, 13 Jul 2021 22:07:50 -0700 Subject: [PATCH 10/21] Update tests for shelved_date Also make dates while we're at it --- .../tests/importers/test_goodreads_import.py | 57 ++++++++++--------- .../importers/test_librarything_import.py | 37 +++++------- 2 files changed, 44 insertions(+), 50 deletions(-) diff --git a/bookwyrm/tests/importers/test_goodreads_import.py b/bookwyrm/tests/importers/test_goodreads_import.py index 81f47e6f5..499444748 100644 --- a/bookwyrm/tests/importers/test_goodreads_import.py +++ b/bookwyrm/tests/importers/test_goodreads_import.py @@ -3,6 +3,8 @@ from collections import namedtuple import csv import pathlib from unittest.mock import patch +import datetime +import pytz from django.test import TestCase import responses @@ -13,6 +15,10 @@ from bookwyrm.importers.importer import import_data, handle_imported_book from bookwyrm.settings import DOMAIN +def make_date(*args): + return datetime.datetime(*args, tzinfo=pytz.UTC) + + class GoodreadsImport(TestCase): """importing from goodreads csv""" @@ -130,22 +136,25 @@ class GoodreadsImport(TestCase): shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) + self.assertEqual( + shelf.shelfbook_set.first().shelved_date, make_date(2020, 10, 21) + ) readthrough = models.ReadThrough.objects.get(user=self.user) self.assertEqual(readthrough.book, self.book) - # I can't remember how to create dates and I don't want to look it up. - self.assertEqual(readthrough.start_date.year, 2020) - self.assertEqual(readthrough.start_date.month, 10) - self.assertEqual(readthrough.start_date.day, 21) - self.assertEqual(readthrough.finish_date.year, 2020) - self.assertEqual(readthrough.finish_date.month, 10) - self.assertEqual(readthrough.finish_date.day, 25) + self.assertEqual(readthrough.start_date, make_date(2020, 10, 21)) + self.assertEqual(readthrough.finish_date, make_date(2020, 10, 25)) def test_handle_imported_book_already_shelved(self): """goodreads import added a book, this adds related connections""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): shelf = self.user.shelf_set.filter(identifier="to-read").first() - models.ShelfBook.objects.create(shelf=shelf, user=self.user, book=self.book) + models.ShelfBook.objects.create( + shelf=shelf, + user=self.user, + book=self.book, + shelved_date=make_date(2020, 2, 2), + ) import_job = models.ImportJob.objects.create(user=self.user) datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") @@ -164,15 +173,15 @@ class GoodreadsImport(TestCase): shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) + self.assertEqual( + shelf.shelfbook_set.first().shelved_date, make_date(2020, 2, 2) + ) self.assertIsNone(self.user.shelf_set.get(identifier="read").books.first()) + readthrough = models.ReadThrough.objects.get(user=self.user) self.assertEqual(readthrough.book, self.book) - self.assertEqual(readthrough.start_date.year, 2020) - self.assertEqual(readthrough.start_date.month, 10) - self.assertEqual(readthrough.start_date.day, 21) - self.assertEqual(readthrough.finish_date.year, 2020) - self.assertEqual(readthrough.finish_date.month, 10) - self.assertEqual(readthrough.finish_date.day, 25) + self.assertEqual(readthrough.start_date, make_date(2020, 10, 21)) + self.assertEqual(readthrough.finish_date, make_date(2020, 10, 25)) def test_handle_import_twice(self): """re-importing books""" @@ -198,16 +207,14 @@ class GoodreadsImport(TestCase): shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) + self.assertEqual( + shelf.shelfbook_set.first().shelved_date, make_date(2020, 10, 21) + ) readthrough = models.ReadThrough.objects.get(user=self.user) self.assertEqual(readthrough.book, self.book) - # I can't remember how to create dates and I don't want to look it up. - self.assertEqual(readthrough.start_date.year, 2020) - self.assertEqual(readthrough.start_date.month, 10) - self.assertEqual(readthrough.start_date.day, 21) - self.assertEqual(readthrough.finish_date.year, 2020) - self.assertEqual(readthrough.finish_date.month, 10) - self.assertEqual(readthrough.finish_date.day, 25) + self.assertEqual(readthrough.start_date, make_date(2020, 10, 21)) + self.assertEqual(readthrough.finish_date, make_date(2020, 10, 25)) @patch("bookwyrm.activitystreams.ActivityStream.add_status") def test_handle_imported_book_review(self, _): @@ -229,9 +236,7 @@ class GoodreadsImport(TestCase): review = models.Review.objects.get(book=self.book, user=self.user) self.assertEqual(review.content, "mixed feelings") self.assertEqual(review.rating, 2) - self.assertEqual(review.published_date.year, 2019) - self.assertEqual(review.published_date.month, 7) - self.assertEqual(review.published_date.day, 8) + self.assertEqual(review.published_date, make_date(2019, 7, 8)) self.assertEqual(review.privacy, "unlisted") @patch("bookwyrm.activitystreams.ActivityStream.add_status") @@ -256,9 +261,7 @@ class GoodreadsImport(TestCase): review = models.ReviewRating.objects.get(book=self.book, user=self.user) self.assertIsInstance(review, models.ReviewRating) self.assertEqual(review.rating, 2) - self.assertEqual(review.published_date.year, 2019) - self.assertEqual(review.published_date.month, 7) - self.assertEqual(review.published_date.day, 8) + self.assertEqual(review.published_date, make_date(2019, 7, 8)) self.assertEqual(review.privacy, "unlisted") def test_handle_imported_book_reviews_disabled(self): diff --git a/bookwyrm/tests/importers/test_librarything_import.py b/bookwyrm/tests/importers/test_librarything_import.py index 8e299d567..1e4911b40 100644 --- a/bookwyrm/tests/importers/test_librarything_import.py +++ b/bookwyrm/tests/importers/test_librarything_import.py @@ -2,6 +2,8 @@ import csv import pathlib from unittest.mock import patch +import datetime +import pytz from django.test import TestCase import responses @@ -12,6 +14,10 @@ from bookwyrm.importers.importer import import_data, handle_imported_book from bookwyrm.settings import DOMAIN +def make_date(*args): + return datetime.datetime(*args, tzinfo=pytz.UTC) + + class LibrarythingImport(TestCase): """importing from librarything tsv""" @@ -125,13 +131,8 @@ class LibrarythingImport(TestCase): readthrough = models.ReadThrough.objects.get(user=self.user) self.assertEqual(readthrough.book, self.book) - # I can't remember how to create dates and I don't want to look it up. - self.assertEqual(readthrough.start_date.year, 2007) - self.assertEqual(readthrough.start_date.month, 4) - self.assertEqual(readthrough.start_date.day, 16) - self.assertEqual(readthrough.finish_date.year, 2007) - self.assertEqual(readthrough.finish_date.month, 5) - self.assertEqual(readthrough.finish_date.day, 8) + self.assertEqual(readthrough.start_date, make_date(2007, 4, 16)) + self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8)) def test_handle_imported_book_already_shelved(self): """librarything import added a book, this adds related connections""" @@ -160,14 +161,11 @@ class LibrarythingImport(TestCase): shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) self.assertIsNone(self.user.shelf_set.get(identifier="read").books.first()) + readthrough = models.ReadThrough.objects.get(user=self.user) self.assertEqual(readthrough.book, self.book) - self.assertEqual(readthrough.start_date.year, 2007) - self.assertEqual(readthrough.start_date.month, 4) - self.assertEqual(readthrough.start_date.day, 16) - self.assertEqual(readthrough.finish_date.year, 2007) - self.assertEqual(readthrough.finish_date.month, 5) - self.assertEqual(readthrough.finish_date.day, 8) + self.assertEqual(readthrough.start_date, make_date(2007, 4, 16)) + self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8)) def test_handle_import_twice(self): """re-importing books""" @@ -198,13 +196,8 @@ class LibrarythingImport(TestCase): readthrough = models.ReadThrough.objects.get(user=self.user) self.assertEqual(readthrough.book, self.book) - # I can't remember how to create dates and I don't want to look it up. - self.assertEqual(readthrough.start_date.year, 2007) - self.assertEqual(readthrough.start_date.month, 4) - self.assertEqual(readthrough.start_date.day, 16) - self.assertEqual(readthrough.finish_date.year, 2007) - self.assertEqual(readthrough.finish_date.month, 5) - self.assertEqual(readthrough.finish_date.day, 8) + self.assertEqual(readthrough.start_date, make_date(2007, 4, 16)) + self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8)) @patch("bookwyrm.activitystreams.ActivityStream.add_status") def test_handle_imported_book_review(self, _): @@ -226,9 +219,7 @@ class LibrarythingImport(TestCase): review = models.Review.objects.get(book=self.book, user=self.user) self.assertEqual(review.content, "chef d'oeuvre") self.assertEqual(review.rating, 5) - self.assertEqual(review.published_date.year, 2007) - self.assertEqual(review.published_date.month, 5) - self.assertEqual(review.published_date.day, 8) + self.assertEqual(review.published_date, make_date(2007, 5, 8)) self.assertEqual(review.privacy, "unlisted") def test_handle_imported_book_reviews_disabled(self): From 436afb0ebdd98bd87d36aa073405b14320a969d9 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 28 Jul 2021 13:17:56 -0700 Subject: [PATCH 11/21] Fixes heirarchy and classes in feed layout --- bookwyrm/templates/feed/feed_layout.html | 132 ++++++++++++----------- 1 file changed, 67 insertions(+), 65 deletions(-) diff --git a/bookwyrm/templates/feed/feed_layout.html b/bookwyrm/templates/feed/feed_layout.html index 3c6b65a1b..224fac033 100644 --- a/bookwyrm/templates/feed/feed_layout.html +++ b/bookwyrm/templates/feed/feed_layout.html @@ -7,82 +7,84 @@
{% if user.is_authenticated %}
-

{% trans "Your books" %}

- {% if not suggested_books %} -

{% trans "There are no books here right now! Try searching for a book to get started" %}

- {% else %} - {% with active_book=request.GET.book %} -
-
-
    +
    +

    {% trans "Your books" %}

    + {% if not suggested_books %} +

    {% trans "There are no books here right now! Try searching for a book to get started" %}

    + {% else %} + {% with active_book=request.GET.book %} +
    +
    +
      + {% for shelf in suggested_books %} + {% if shelf.books %} + {% with shelf_counter=forloop.counter %} +
    • +

      + {% if shelf.identifier == 'to-read' %}{% trans "To Read" %} + {% elif shelf.identifier == 'reading' %}{% trans "Currently Reading" %} + {% elif shelf.identifier == 'read' %}{% trans "Read" %} + {% else %}{{ shelf.name }}{% endif %} +

      +
      + +
      +
    • + {% endwith %} + {% endif %} + {% endfor %} +
    +
    {% for shelf in suggested_books %} - {% if shelf.books %} {% with shelf_counter=forloop.counter %} -
  • -

    - {% if shelf.identifier == 'to-read' %}{% trans "To Read" %} - {% elif shelf.identifier == 'reading' %}{% trans "Currently Reading" %} - {% elif shelf.identifier == 'read' %}{% trans "Read" %} - {% else %}{{ shelf.name }}{% endif %} -

    -
    - -
    -
  • - {% endwith %} - {% endif %} - {% endfor %} -
-
- {% for shelf in suggested_books %} - {% with shelf_counter=forloop.counter %} - {% for book in shelf.books %} -
+ {% for book in shelf.books %} +
-
-
-
-

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

- {% include 'snippets/shelve_button/shelve_button.html' with book=book %} +
+
+
+

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

+ {% include 'snippets/shelve_button/shelve_button.html' with book=book %} +
+
+
+ {% trans "Close" as button_text %} + {% include 'snippets/toggle/toggle_button.html' with label=button_text controls_text="book" controls_uid=book.id class="delete" nonbutton=True pressed=True %}
-
- {% trans "Close" as button_text %} - {% include 'snippets/toggle/toggle_button.html' with label=button_text controls_text="book" controls_uid=book.id class="delete" nonbutton=True pressed=True %} +
+ {% include 'snippets/create_status.html' with book=book %}
-
- {% include 'snippets/create_status.html' with book=book %} -
+ {% endfor %} + {% endwith %} + {% endfor %}
- {% endfor %} {% endwith %} - {% endfor %} -
- {% endwith %} - {% endif %} + {% endif %} + {% if goal %} -
+

{% blocktrans with yar=goal.year %}{{ year }} Reading Goal{% endblocktrans %}

{% include 'snippets/goal_progress.html' with goal=goal %} From c9602e28ce5b997d6b35a8976bcad27aacf3819b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 28 Jul 2021 13:19:56 -0700 Subject: [PATCH 12/21] Use consistant header size --- bookwyrm/templates/feed/feed_layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/feed/feed_layout.html b/bookwyrm/templates/feed/feed_layout.html index 224fac033..57305df72 100644 --- a/bookwyrm/templates/feed/feed_layout.html +++ b/bookwyrm/templates/feed/feed_layout.html @@ -8,7 +8,7 @@ {% if user.is_authenticated %}
-

{% trans "Your books" %}

+

{% trans "Your books" %}

{% if not suggested_books %}

{% trans "There are no books here right now! Try searching for a book to get started" %}

{% else %} From 7f44693e0c3e4ce0d17b127884839e5cb60b032c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 28 Jul 2021 13:21:42 -0700 Subject: [PATCH 13/21] Renames feed layout file to be more consistent with other templates --- bookwyrm/templates/feed/direct_messages.html | 2 +- bookwyrm/templates/feed/feed.html | 2 +- bookwyrm/templates/feed/{feed_layout.html => layout.html} | 0 bookwyrm/templates/feed/status.html | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename bookwyrm/templates/feed/{feed_layout.html => layout.html} (100%) diff --git a/bookwyrm/templates/feed/direct_messages.html b/bookwyrm/templates/feed/direct_messages.html index 0b7dcd79c..30a50cd0a 100644 --- a/bookwyrm/templates/feed/direct_messages.html +++ b/bookwyrm/templates/feed/direct_messages.html @@ -1,4 +1,4 @@ -{% extends 'feed/feed_layout.html' %} +{% extends 'feed/layout.html' %} {% load i18n %} {% block panel %} diff --git a/bookwyrm/templates/feed/feed.html b/bookwyrm/templates/feed/feed.html index 21e71ae18..44ee5c930 100644 --- a/bookwyrm/templates/feed/feed.html +++ b/bookwyrm/templates/feed/feed.html @@ -1,4 +1,4 @@ -{% extends 'feed/feed_layout.html' %} +{% extends 'feed/layout.html' %} {% load i18n %} {% block panel %} diff --git a/bookwyrm/templates/feed/feed_layout.html b/bookwyrm/templates/feed/layout.html similarity index 100% rename from bookwyrm/templates/feed/feed_layout.html rename to bookwyrm/templates/feed/layout.html diff --git a/bookwyrm/templates/feed/status.html b/bookwyrm/templates/feed/status.html index 9f90f355d..903ca7907 100644 --- a/bookwyrm/templates/feed/status.html +++ b/bookwyrm/templates/feed/status.html @@ -1,4 +1,4 @@ -{% extends 'feed/feed_layout.html' %} +{% extends 'feed/layout.html' %} {% load i18n %} {% block panel %} From 1feb5d9f867cd17b3bfb61af247b983b0a8f784a Mon Sep 17 00:00:00 2001 From: GuDzpoz Date: Fri, 30 Jul 2021 18:14:21 +0800 Subject: [PATCH 14/21] Updated zh_Hans translation; compiled zh_Hant translation. --- locale/zh_Hans/LC_MESSAGES/django.mo | Bin 41231 -> 42356 bytes locale/zh_Hans/LC_MESSAGES/django.po | 537 +++++++++++++++------------ locale/zh_Hant/LC_MESSAGES/django.mo | Bin 0 -> 41166 bytes 3 files changed, 303 insertions(+), 234 deletions(-) create mode 100644 locale/zh_Hant/LC_MESSAGES/django.mo diff --git a/locale/zh_Hans/LC_MESSAGES/django.mo b/locale/zh_Hans/LC_MESSAGES/django.mo index 78797d29ecb255f1ee9b63173a0ddd64afe5b53a..3450c353ac4b48e9636805a6a77931a74b68dd92 100644 GIT binary patch delta 14832 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 Pn*RUw+Pt%-d$Iok&bc(M delta 13789 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 diff --git a/locale/zh_Hans/LC_MESSAGES/django.po b/locale/zh_Hans/LC_MESSAGES/django.po index c40e716c8..a52542b0a 100644 --- a/locale/zh_Hans/LC_MESSAGES/django.po +++ b/locale/zh_Hans/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-06-06 20:52+0000\n" +"POT-Creation-Date: 2021-07-30 09:07+0000\n" "PO-Revision-Date: 2021-03-20 00:56+0000\n" "Last-Translator: Kana \n" "Language-Team: Mouse Reeve \n" @@ -18,99 +18,103 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: bookwyrm/forms.py:224 +#: bookwyrm/forms.py:231 msgid "A user with this email already exists." msgstr "已经存在使用该邮箱的用户。" -#: bookwyrm/forms.py:238 +#: bookwyrm/forms.py:245 msgid "One Day" msgstr "一天" -#: bookwyrm/forms.py:239 +#: bookwyrm/forms.py:246 msgid "One Week" msgstr "一周" -#: bookwyrm/forms.py:240 +#: bookwyrm/forms.py:247 msgid "One Month" msgstr "一个月" -#: bookwyrm/forms.py:241 +#: bookwyrm/forms.py:248 msgid "Does Not Expire" msgstr "永不失效" -#: bookwyrm/forms.py:246 +#: bookwyrm/forms.py:253 #, python-format msgid "%(count)d uses" msgstr "%(count)d 次使用" -#: bookwyrm/forms.py:249 +#: bookwyrm/forms.py:256 msgid "Unlimited" msgstr "不受限" -#: bookwyrm/forms.py:299 +#: bookwyrm/forms.py:306 msgid "List Order" msgstr "列表顺序" -#: bookwyrm/forms.py:300 +#: bookwyrm/forms.py:307 msgid "Book Title" msgstr "书名" -#: bookwyrm/forms.py:301 bookwyrm/templates/snippets/create_status_form.html:34 +#: bookwyrm/forms.py:308 bookwyrm/templates/snippets/create_status_form.html:34 #: bookwyrm/templates/user/shelf/shelf.html:85 #: bookwyrm/templates/user/shelf/shelf.html:116 msgid "Rating" msgstr "评价" -#: bookwyrm/forms.py:303 bookwyrm/templates/lists/list.html:107 +#: bookwyrm/forms.py:310 bookwyrm/templates/lists/list.html:107 msgid "Sort By" msgstr "排序方式" -#: bookwyrm/forms.py:307 +#: bookwyrm/forms.py:314 msgid "Ascending" msgstr "升序" -#: bookwyrm/forms.py:308 +#: bookwyrm/forms.py:315 msgid "Descending" msgstr "降序" -#: bookwyrm/models/fields.py:25 +#: bookwyrm/models/fields.py:26 #, 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:43 +#: bookwyrm/models/fields.py:35 bookwyrm/models/fields.py:44 #, python-format msgid "%(value)s is not a valid username" msgstr "%(value)s 不是有效的用户名" -#: bookwyrm/models/fields.py:166 bookwyrm/templates/layout.html:152 +#: bookwyrm/models/fields.py:167 bookwyrm/templates/layout.html:157 msgid "username" msgstr "用户名" -#: bookwyrm/models/fields.py:171 +#: bookwyrm/models/fields.py:172 msgid "A user with that username already exists." msgstr "已经存在使用该用户名的用户。" -#: bookwyrm/settings.py:156 +#: bookwyrm/settings.py:164 msgid "English" msgstr "English(英语)" -#: bookwyrm/settings.py:157 +#: bookwyrm/settings.py:165 msgid "German" msgstr "Deutsch(德语)" -#: bookwyrm/settings.py:158 +#: bookwyrm/settings.py:166 msgid "Spanish" msgstr "Español(西班牙语)" -#: bookwyrm/settings.py:159 +#: bookwyrm/settings.py:167 msgid "French" msgstr "Français(法语)" -#: bookwyrm/settings.py:160 +#: bookwyrm/settings.py:168 msgid "Simplified Chinese" msgstr "简体中文" +#: bookwyrm/settings.py:169 +msgid "Traditional Chinese" +msgstr "繁體中文(繁体中文)" + #: bookwyrm/templates/404.html:4 bookwyrm/templates/404.html:8 msgid "Not Found" msgstr "未找到" @@ -136,34 +140,42 @@ msgstr "某些东西出错了!对不起啦。" msgid "Edit Author" msgstr "编辑作者" -#: bookwyrm/templates/author/author.html:32 -#: bookwyrm/templates/author/edit_author.html:38 +#: bookwyrm/templates/author/author.html:34 +#: bookwyrm/templates/author/edit_author.html:41 msgid "Aliases:" msgstr "别名:" -#: bookwyrm/templates/author/author.html:38 +#: bookwyrm/templates/author/author.html:45 msgid "Born:" msgstr "出生:" -#: bookwyrm/templates/author/author.html:44 +#: bookwyrm/templates/author/author.html:52 msgid "Died:" msgstr "逝世:" -#: bookwyrm/templates/author/author.html:51 +#: bookwyrm/templates/author/author.html:61 msgid "Wikipedia" msgstr "维基百科" -#: bookwyrm/templates/author/author.html:55 -#: bookwyrm/templates/book/book.html:78 +#: bookwyrm/templates/author/author.html:69 +#: bookwyrm/templates/book/book.html:90 msgid "View on OpenLibrary" msgstr "在 OpenLibrary 查看" -#: bookwyrm/templates/author/author.html:60 -#: bookwyrm/templates/book/book.html:81 +#: bookwyrm/templates/author/author.html:77 +#: bookwyrm/templates/book/book.html:93 msgid "View on Inventaire" msgstr "在 Inventaire 查看" -#: bookwyrm/templates/author/author.html:74 +#: bookwyrm/templates/author/author.html:85 +msgid "View on LibraryThing" +msgstr "在 LibraryThing 查看" + +#: bookwyrm/templates/author/author.html:93 +msgid "View on Goodreads" +msgstr "在 Goodreads 查看" + +#: bookwyrm/templates/author/author.html:108 #, python-format msgid "Books by %(name)s" msgstr "%(name)s 所著的书" @@ -173,212 +185,212 @@ msgid "Edit Author:" msgstr "编辑作者:" #: bookwyrm/templates/author/edit_author.html:13 -#: bookwyrm/templates/book/edit_book.html:18 +#: bookwyrm/templates/book/edit_book.html:19 msgid "Added:" msgstr "添加了:" #: bookwyrm/templates/author/edit_author.html:14 -#: bookwyrm/templates/book/edit_book.html:19 +#: bookwyrm/templates/book/edit_book.html:24 msgid "Updated:" msgstr "更新了:" #: bookwyrm/templates/author/edit_author.html:15 -#: bookwyrm/templates/book/edit_book.html:20 +#: bookwyrm/templates/book/edit_book.html:30 msgid "Last edited by:" msgstr "最后编辑人:" #: bookwyrm/templates/author/edit_author.html:31 -#: bookwyrm/templates/book/edit_book.html:90 +#: bookwyrm/templates/book/edit_book.html:117 msgid "Metadata" msgstr "元数据" -#: bookwyrm/templates/author/edit_author.html:32 +#: bookwyrm/templates/author/edit_author.html:33 #: bookwyrm/templates/lists/form.html:8 #: bookwyrm/templates/user/shelf/create_shelf_form.html:13 #: bookwyrm/templates/user/shelf/edit_shelf_form.html:14 msgid "Name:" msgstr "名称:" -#: bookwyrm/templates/author/edit_author.html:40 -#: bookwyrm/templates/book/edit_book.html:132 -#: bookwyrm/templates/book/edit_book.html:141 -#: bookwyrm/templates/book/edit_book.html:178 +#: bookwyrm/templates/author/edit_author.html:43 +#: bookwyrm/templates/book/edit_book.html:162 +#: bookwyrm/templates/book/edit_book.html:171 +#: bookwyrm/templates/book/edit_book.html:214 msgid "Separate multiple values with commas." msgstr "请用英文逗号(,)分隔多个值。" -#: bookwyrm/templates/author/edit_author.html:46 +#: bookwyrm/templates/author/edit_author.html:50 msgid "Bio:" msgstr "简介:" -#: bookwyrm/templates/author/edit_author.html:51 +#: bookwyrm/templates/author/edit_author.html:57 msgid "Wikipedia link:" msgstr "维基百科链接:" -#: bookwyrm/templates/author/edit_author.html:57 +#: bookwyrm/templates/author/edit_author.html:63 msgid "Birth date:" msgstr "出生日期:" -#: bookwyrm/templates/author/edit_author.html:65 +#: bookwyrm/templates/author/edit_author.html:71 msgid "Death date:" msgstr "死亡日期:" -#: bookwyrm/templates/author/edit_author.html:73 +#: bookwyrm/templates/author/edit_author.html:79 msgid "Author Identifiers" msgstr "作者标识号:" -#: bookwyrm/templates/author/edit_author.html:74 +#: bookwyrm/templates/author/edit_author.html:81 msgid "Openlibrary key:" msgstr "Openlibrary key:" -#: bookwyrm/templates/author/edit_author.html:79 -#: bookwyrm/templates/book/edit_book.html:243 +#: bookwyrm/templates/author/edit_author.html:89 +#: bookwyrm/templates/book/edit_book.html:293 msgid "Inventaire ID:" msgstr "Inventaire ID:" -#: bookwyrm/templates/author/edit_author.html:84 +#: bookwyrm/templates/author/edit_author.html:97 msgid "Librarything key:" msgstr "Librarything key:" -#: bookwyrm/templates/author/edit_author.html:89 +#: bookwyrm/templates/author/edit_author.html:105 msgid "Goodreads key:" msgstr "Goodreads key:" -#: bookwyrm/templates/author/edit_author.html:98 -#: bookwyrm/templates/book/book.html:124 -#: bookwyrm/templates/book/edit_book.html:263 +#: bookwyrm/templates/author/edit_author.html:116 +#: bookwyrm/templates/book/book.html:136 +#: bookwyrm/templates/book/edit_book.html:321 +#: bookwyrm/templates/book/readthrough.html:77 #: bookwyrm/templates/lists/form.html:42 #: bookwyrm/templates/preferences/edit_user.html:70 #: bookwyrm/templates/settings/announcement_form.html:69 #: bookwyrm/templates/settings/edit_server.html:68 #: bookwyrm/templates/settings/federated_server.html:98 -#: bookwyrm/templates/settings/site.html:97 -#: bookwyrm/templates/snippets/readthrough.html:77 +#: bookwyrm/templates/settings/site.html:101 #: 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 -#: bookwyrm/templates/user_admin/user_moderation_actions.html:38 +#: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:36 +#: bookwyrm/templates/user_admin/user_moderation_actions.html:45 msgid "Save" msgstr "保存" -#: bookwyrm/templates/author/edit_author.html:99 -#: bookwyrm/templates/book/book.html:125 bookwyrm/templates/book/book.html:174 +#: bookwyrm/templates/author/edit_author.html:117 +#: bookwyrm/templates/book/book.html:137 bookwyrm/templates/book/book.html:186 #: bookwyrm/templates/book/cover_modal.html:32 -#: bookwyrm/templates/book/edit_book.html:264 +#: bookwyrm/templates/book/edit_book.html:322 +#: bookwyrm/templates/book/readthrough.html:78 #: bookwyrm/templates/moderation/report_modal.html:34 #: bookwyrm/templates/settings/federated_server.html:99 #: bookwyrm/templates/snippets/delete_readthrough_modal.html:17 #: bookwyrm/templates/snippets/goal_form.html:32 -#: bookwyrm/templates/snippets/readthrough.html:78 #: 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 +#: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:37 #: bookwyrm/templates/snippets/shelve_button/want_to_read_modal.html:28 msgid "Cancel" msgstr "取消" -#: bookwyrm/templates/book/book.html:31 +#: bookwyrm/templates/book/book.html:43 #: bookwyrm/templates/discover/large-book.html:25 #: bookwyrm/templates/discover/small-book.html:19 msgid "by" msgstr "作者" -#: bookwyrm/templates/book/book.html:39 bookwyrm/templates/book/book.html:40 +#: bookwyrm/templates/book/book.html:51 bookwyrm/templates/book/book.html:52 msgid "Edit Book" msgstr "编辑书目" -#: bookwyrm/templates/book/book.html:57 +#: bookwyrm/templates/book/book.html:69 #: bookwyrm/templates/book/cover_modal.html:5 msgid "Add cover" msgstr "添加封面" -#: bookwyrm/templates/book/book.html:61 +#: bookwyrm/templates/book/book.html:73 msgid "Failed to load cover" msgstr "加载封面失败" -#: bookwyrm/templates/book/book.html:101 +#: bookwyrm/templates/book/book.html:113 #, python-format msgid "(%(review_count)s review)" msgid_plural "(%(review_count)s reviews)" msgstr[0] "(%(review_count)s 则书评)" -#: bookwyrm/templates/book/book.html:113 +#: bookwyrm/templates/book/book.html:125 msgid "Add Description" msgstr "添加描述" -#: bookwyrm/templates/book/book.html:120 -#: bookwyrm/templates/book/edit_book.html:108 +#: bookwyrm/templates/book/book.html:132 +#: bookwyrm/templates/book/edit_book.html:136 #: bookwyrm/templates/lists/form.html:12 msgid "Description:" msgstr "描述:" -#: bookwyrm/templates/book/book.html:134 +#: bookwyrm/templates/book/book.html:146 #, python-format msgid "%(count)s editions" msgstr "%(count)s 个版本" -#: bookwyrm/templates/book/book.html:142 +#: bookwyrm/templates/book/book.html:154 #, python-format msgid "This edition is on your %(shelf_name)s shelf." msgstr "此版本在你的 %(shelf_name)s 书架上。" -#: bookwyrm/templates/book/book.html:148 +#: bookwyrm/templates/book/book.html:160 #, 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:159 +#: bookwyrm/templates/book/book.html:171 msgid "Your reading activity" msgstr "你的阅读活动" -#: bookwyrm/templates/book/book.html:162 +#: bookwyrm/templates/book/book.html:174 msgid "Add read dates" msgstr "添加阅读日期" -#: bookwyrm/templates/book/book.html:171 +#: bookwyrm/templates/book/book.html:183 msgid "Create" msgstr "创建" -#: bookwyrm/templates/book/book.html:181 +#: bookwyrm/templates/book/book.html:193 msgid "You don't have any reading activity for this book." msgstr "你还没有任何这本书的阅读活动。" -#: bookwyrm/templates/book/book.html:200 +#: bookwyrm/templates/book/book.html:212 msgid "Reviews" msgstr "书评" -#: bookwyrm/templates/book/book.html:205 +#: bookwyrm/templates/book/book.html:217 msgid "Your reviews" msgstr "你的书评" -#: bookwyrm/templates/book/book.html:211 +#: bookwyrm/templates/book/book.html:223 msgid "Your comments" msgstr "你的评论" -#: bookwyrm/templates/book/book.html:217 +#: bookwyrm/templates/book/book.html:229 msgid "Your quotes" msgstr "你的引用" -#: bookwyrm/templates/book/book.html:253 +#: bookwyrm/templates/book/book.html:265 msgid "Subjects" msgstr "主题" -#: bookwyrm/templates/book/book.html:265 +#: bookwyrm/templates/book/book.html:277 msgid "Places" msgstr "地点" -#: bookwyrm/templates/book/book.html:276 bookwyrm/templates/layout.html:61 +#: bookwyrm/templates/book/book.html:288 bookwyrm/templates/layout.html:66 #: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12 #: bookwyrm/templates/search/layout.html:25 #: bookwyrm/templates/search/layout.html:50 -#: bookwyrm/templates/user/layout.html:68 +#: bookwyrm/templates/user/layout.html:73 msgid "Lists" msgstr "列表" -#: bookwyrm/templates/book/book.html:287 +#: bookwyrm/templates/book/book.html:299 msgid "Add to list" msgstr "添加到列表" -#: bookwyrm/templates/book/book.html:297 +#: bookwyrm/templates/book/book.html:309 #: bookwyrm/templates/book/cover_modal.html:31 #: bookwyrm/templates/lists/list.html:179 msgid "Add" @@ -389,22 +401,22 @@ msgid "ISBN:" msgstr "ISBN:" #: bookwyrm/templates/book/book_identifiers.html:14 -#: bookwyrm/templates/book/edit_book.html:248 +#: bookwyrm/templates/book/edit_book.html:301 msgid "OCLC Number:" msgstr "OCLC 号:" #: bookwyrm/templates/book/book_identifiers.html:21 -#: bookwyrm/templates/book/edit_book.html:253 +#: bookwyrm/templates/book/edit_book.html:309 msgid "ASIN:" msgstr "ASIN:" #: bookwyrm/templates/book/cover_modal.html:17 -#: bookwyrm/templates/book/edit_book.html:192 +#: bookwyrm/templates/book/edit_book.html:229 msgid "Upload cover:" msgstr "上传封面:" #: bookwyrm/templates/book/cover_modal.html:23 -#: bookwyrm/templates/book/edit_book.html:198 +#: bookwyrm/templates/book/edit_book.html:235 msgid "Load cover from url:" msgstr "从网址加载封面:" @@ -419,127 +431,132 @@ msgstr "编辑 \"%(book_title)s\"" msgid "Add Book" msgstr "添加书目" -#: bookwyrm/templates/book/edit_book.html:40 +#: bookwyrm/templates/book/edit_book.html:54 msgid "Confirm Book Info" msgstr "确认书目信息" -#: bookwyrm/templates/book/edit_book.html:47 +#: bookwyrm/templates/book/edit_book.html:62 #, python-format msgid "Is \"%(name)s\" an existing author?" msgstr "\"%(name)s\" 是已存在的作者吗?" -#: bookwyrm/templates/book/edit_book.html:52 +#: bookwyrm/templates/book/edit_book.html:71 #, python-format msgid "Author of %(book_title)s" msgstr "%(book_title)s 的作者" -#: bookwyrm/templates/book/edit_book.html:55 +#: bookwyrm/templates/book/edit_book.html:75 msgid "This is a new author" msgstr "这是一位新的作者" -#: bookwyrm/templates/book/edit_book.html:61 +#: bookwyrm/templates/book/edit_book.html:82 #, python-format msgid "Creating a new author: %(name)s" msgstr "正在创建新的作者: %(name)s" -#: bookwyrm/templates/book/edit_book.html:67 +#: bookwyrm/templates/book/edit_book.html:89 msgid "Is this an edition of an existing work?" msgstr "这是已存在的作品的一个版本吗?" -#: bookwyrm/templates/book/edit_book.html:71 +#: bookwyrm/templates/book/edit_book.html:97 msgid "This is a new work" msgstr "这是一个新的作品。" -#: bookwyrm/templates/book/edit_book.html:77 +#: bookwyrm/templates/book/edit_book.html:104 #: bookwyrm/templates/password_reset.html:30 msgid "Confirm" msgstr "确认" -#: bookwyrm/templates/book/edit_book.html:79 +#: bookwyrm/templates/book/edit_book.html:106 #: bookwyrm/templates/feed/status.html:8 msgid "Back" msgstr "返回" -#: bookwyrm/templates/book/edit_book.html:93 +#: bookwyrm/templates/book/edit_book.html:120 msgid "Title:" msgstr "标题:" -#: bookwyrm/templates/book/edit_book.html:101 +#: bookwyrm/templates/book/edit_book.html:128 msgid "Subtitle:" msgstr "副标题:" -#: bookwyrm/templates/book/edit_book.html:114 +#: bookwyrm/templates/book/edit_book.html:144 msgid "Series:" msgstr "系列:" -#: bookwyrm/templates/book/edit_book.html:122 +#: bookwyrm/templates/book/edit_book.html:152 msgid "Series number:" msgstr "系列编号:" -#: bookwyrm/templates/book/edit_book.html:130 +#: bookwyrm/templates/book/edit_book.html:160 msgid "Languages:" msgstr "语言:" -#: bookwyrm/templates/book/edit_book.html:139 +#: bookwyrm/templates/book/edit_book.html:169 msgid "Publisher:" msgstr "出版社:" -#: bookwyrm/templates/book/edit_book.html:148 +#: bookwyrm/templates/book/edit_book.html:178 msgid "First published date:" msgstr "初版时间:" -#: bookwyrm/templates/book/edit_book.html:156 +#: bookwyrm/templates/book/edit_book.html:186 msgid "Published date:" msgstr "出版时间:" -#: bookwyrm/templates/book/edit_book.html:165 +#: bookwyrm/templates/book/edit_book.html:195 msgid "Authors" msgstr "作者" -#: bookwyrm/templates/book/edit_book.html:171 +#: bookwyrm/templates/book/edit_book.html:202 #, python-format -msgid "Remove %(name)s" -msgstr "移除 %(name)s" +msgid "Remove %(name)s" +msgstr "移除 %(name)s" -#: bookwyrm/templates/book/edit_book.html:176 +#: bookwyrm/templates/book/edit_book.html:205 +#, python-format +msgid "Author page for %(name)s" +msgstr "%(name)s 的作者页面" + +#: bookwyrm/templates/book/edit_book.html:212 msgid "Add Authors:" msgstr "添加作者:" -#: bookwyrm/templates/book/edit_book.html:177 +#: bookwyrm/templates/book/edit_book.html:213 msgid "John Doe, Jane Smith" msgstr "张三, 李四" -#: bookwyrm/templates/book/edit_book.html:183 +#: bookwyrm/templates/book/edit_book.html:220 #: bookwyrm/templates/user/shelf/shelf.html:78 msgid "Cover" msgstr "封面" -#: bookwyrm/templates/book/edit_book.html:211 +#: bookwyrm/templates/book/edit_book.html:248 msgid "Physical Properties" msgstr "实体性质" -#: bookwyrm/templates/book/edit_book.html:212 +#: bookwyrm/templates/book/edit_book.html:250 #: bookwyrm/templates/book/format_filter.html:5 msgid "Format:" msgstr "格式:" -#: bookwyrm/templates/book/edit_book.html:220 +#: bookwyrm/templates/book/edit_book.html:258 msgid "Pages:" msgstr "页数:" -#: bookwyrm/templates/book/edit_book.html:227 +#: bookwyrm/templates/book/edit_book.html:267 msgid "Book Identifiers" msgstr "书目标识号" -#: bookwyrm/templates/book/edit_book.html:228 +#: bookwyrm/templates/book/edit_book.html:269 msgid "ISBN 13:" msgstr "ISBN 13:" -#: bookwyrm/templates/book/edit_book.html:233 +#: bookwyrm/templates/book/edit_book.html:277 msgid "ISBN 10:" msgstr "ISBN 10:" -#: bookwyrm/templates/book/edit_book.html:238 +#: bookwyrm/templates/book/edit_book.html:285 msgid "Openlibrary ID:" msgstr "Openlibrary ID:" @@ -585,7 +602,7 @@ msgstr "%(languages)s 语言" #: bookwyrm/templates/book/publisher_info.html:64 #, python-format msgid "Published %(date)s by %(publisher)s." -msgstr "在 %(date)s 由 %(publisher)s 出版。" +msgstr "%(publisher)s 于 %(date)s 出版。" #: bookwyrm/templates/book/publisher_info.html:66 #, python-format @@ -595,15 +612,44 @@ msgstr "于 %(date)s 出版" #: bookwyrm/templates/book/publisher_info.html:68 #, python-format msgid "Published by %(publisher)s." -msgstr "由 %(publisher)s 出版。" +msgstr "%(publisher)s 出版。" #: bookwyrm/templates/book/rating.html:13 msgid "rated it" msgstr "评价了" +#: bookwyrm/templates/book/readthrough.html:8 +msgid "Progress Updates:" +msgstr "进度更新:" + +#: bookwyrm/templates/book/readthrough.html:14 +msgid "finished" +msgstr "已完成" + +#: bookwyrm/templates/book/readthrough.html:25 +msgid "Show all updates" +msgstr "显示所有更新" + +#: bookwyrm/templates/book/readthrough.html:41 +msgid "Delete this progress update" +msgstr "删除此进度更新" + +#: bookwyrm/templates/book/readthrough.html:52 +msgid "started" +msgstr "已开始" + +#: bookwyrm/templates/book/readthrough.html:59 +#: bookwyrm/templates/book/readthrough.html:73 +msgid "Edit read dates" +msgstr "编辑阅读日期" + +#: bookwyrm/templates/book/readthrough.html:63 +msgid "Delete these read dates" +msgstr "删除这些阅读日期" + #: bookwyrm/templates/components/inline_form.html:8 #: bookwyrm/templates/components/modal.html:11 -#: bookwyrm/templates/feed/feed_layout.html:69 +#: bookwyrm/templates/feed/layout.html:70 #: bookwyrm/templates/get_started/layout.html:19 #: bookwyrm/templates/get_started/layout.html:52 #: bookwyrm/templates/search/book.html:32 @@ -629,7 +675,7 @@ msgstr "跨站社区" #: bookwyrm/templates/directory/directory.html:4 #: bookwyrm/templates/directory/directory.html:9 -#: bookwyrm/templates/layout.html:64 +#: bookwyrm/templates/layout.html:69 msgid "Directory" msgstr "目录" @@ -738,7 +784,7 @@ msgstr "本实例不开放。" #: bookwyrm/templates/discover/landing_layout.html:57 msgid "Thank you! Your request has been received." -msgstr "谢谢你!我们已经受到了你的请求。" +msgstr "谢谢你!我们已经收到了你的请求。" #: bookwyrm/templates/discover/landing_layout.html:60 msgid "Request an Invitation" @@ -828,7 +874,7 @@ msgid "Direct Messages with %(username)s" msgstr "与 %(username)s 私信" #: bookwyrm/templates/feed/direct_messages.html:10 -#: bookwyrm/templates/layout.html:92 +#: bookwyrm/templates/layout.html:97 msgid "Direct Messages" msgstr "私信" @@ -872,43 +918,43 @@ msgstr "加载 0 条未读状态" #: bookwyrm/templates/feed/feed.html:47 msgid "There aren't any activities right now! Try following a user to get started" -msgstr "现在还没有任何活动!尝试着从关注一个用户开始吧" +msgstr "现在还没有任何活动!尝试从关注一个用户开始吧" #: bookwyrm/templates/feed/feed.html:55 #: bookwyrm/templates/get_started/users.html:6 msgid "Who to follow" msgstr "可以关注的人" -#: bookwyrm/templates/feed/feed_layout.html:4 +#: bookwyrm/templates/feed/layout.html:4 msgid "Updates" msgstr "更新" -#: bookwyrm/templates/feed/feed_layout.html:10 +#: bookwyrm/templates/feed/layout.html:11 #: bookwyrm/templates/user/shelf/books_header.html:3 msgid "Your books" msgstr "你的书目" -#: bookwyrm/templates/feed/feed_layout.html:12 +#: bookwyrm/templates/feed/layout.html:13 msgid "There are no books here right now! Try searching for a book to get started" msgstr "现在这里还没有任何书目!尝试着从搜索某本书开始吧" -#: bookwyrm/templates/feed/feed_layout.html:23 +#: bookwyrm/templates/feed/layout.html:24 #: bookwyrm/templates/user/shelf/shelf.html:29 msgid "To Read" msgstr "想读" -#: bookwyrm/templates/feed/feed_layout.html:24 +#: bookwyrm/templates/feed/layout.html:25 #: bookwyrm/templates/user/shelf/shelf.html:29 msgid "Currently Reading" msgstr "在读" -#: bookwyrm/templates/feed/feed_layout.html:25 -#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:13 +#: bookwyrm/templates/feed/layout.html:26 +#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:16 #: bookwyrm/templates/user/shelf/shelf.html:29 msgid "Read" msgstr "读过" -#: bookwyrm/templates/feed/feed_layout.html:87 bookwyrm/templates/goal.html:26 +#: bookwyrm/templates/feed/layout.html:89 bookwyrm/templates/goal.html:26 #: bookwyrm/templates/snippets/goal_card.html:6 #, python-format msgid "%(year)s Reading Goal" @@ -955,7 +1001,7 @@ msgstr "你可以在开始使用 %(site_name)s 后添加书目。" #: bookwyrm/templates/get_started/books.html:17 #: bookwyrm/templates/get_started/users.html:18 #: bookwyrm/templates/get_started/users.html:19 -#: bookwyrm/templates/layout.html:37 bookwyrm/templates/layout.html:38 +#: bookwyrm/templates/layout.html:42 bookwyrm/templates/layout.html:43 #: bookwyrm/templates/lists/list.html:139 #: bookwyrm/templates/search/layout.html:4 #: bookwyrm/templates/search/layout.html:9 @@ -1122,65 +1168,78 @@ msgstr "无最近的导入" msgid "Import Status" msgstr "导入状态" -#: bookwyrm/templates/import_status.html:12 +#: bookwyrm/templates/import_status.html:10 +msgid "Back to imports" +msgstr "回到导入" + +#: bookwyrm/templates/import_status.html:14 msgid "Import started:" msgstr "导入开始:" -#: bookwyrm/templates/import_status.html:16 +#: bookwyrm/templates/import_status.html:19 msgid "Import completed:" msgstr "导入完成:" -#: bookwyrm/templates/import_status.html:19 +#: bookwyrm/templates/import_status.html:24 msgid "TASK FAILED" msgstr "任务失败" -#: bookwyrm/templates/import_status.html:25 +#: bookwyrm/templates/import_status.html:31 msgid "Import still in progress." msgstr "还在导入中。" -#: bookwyrm/templates/import_status.html:27 +#: bookwyrm/templates/import_status.html:33 msgid "(Hit reload to update!)" msgstr "(按下重新加载来更新!)" -#: bookwyrm/templates/import_status.html:34 +#: bookwyrm/templates/import_status.html:40 msgid "Failed to load" msgstr "加载失败" -#: bookwyrm/templates/import_status.html:43 +#: bookwyrm/templates/import_status.html:49 #, python-format msgid "Jump to the bottom of the list to select the %(failed_count)s items which failed to import." msgstr "跳转至列表底部来选取 %(failed_count)s 个导入失败的项目。" -#: bookwyrm/templates/import_status.html:78 +#: bookwyrm/templates/import_status.html:61 +#, python-format +msgid "Line %(index)s: %(title)s by %(author)s" +msgstr "第 %(index)s 行: %(author)s 所著的 %(title)s" + +#: bookwyrm/templates/import_status.html:81 msgid "Select all" msgstr "全选" -#: bookwyrm/templates/import_status.html:81 +#: bookwyrm/templates/import_status.html:84 msgid "Retry items" msgstr "重试项目" -#: bookwyrm/templates/import_status.html:107 +#: bookwyrm/templates/import_status.html:111 msgid "Successfully imported" msgstr "成功导入了" -#: bookwyrm/templates/import_status.html:111 +#: bookwyrm/templates/import_status.html:113 +msgid "Import Progress" +msgstr "导入进度" + +#: bookwyrm/templates/import_status.html:118 msgid "Book" msgstr "书目" -#: bookwyrm/templates/import_status.html:114 +#: bookwyrm/templates/import_status.html:121 #: bookwyrm/templates/snippets/create_status_form.html:13 #: bookwyrm/templates/user/shelf/shelf.html:79 #: bookwyrm/templates/user/shelf/shelf.html:99 msgid "Title" msgstr "标题" -#: bookwyrm/templates/import_status.html:117 +#: bookwyrm/templates/import_status.html:124 #: bookwyrm/templates/user/shelf/shelf.html:80 #: bookwyrm/templates/user/shelf/shelf.html:102 msgid "Author" msgstr "作者" -#: bookwyrm/templates/import_status.html:140 +#: bookwyrm/templates/import_status.html:147 msgid "Imported" msgstr "已导入" @@ -1210,27 +1269,27 @@ msgstr "\"%(query)s\" 的搜索结果" msgid "Matching Books" msgstr "匹配的书目" -#: bookwyrm/templates/layout.html:33 +#: bookwyrm/templates/layout.html:38 msgid "Search for a book or user" msgstr "搜索书目或用户" -#: bookwyrm/templates/layout.html:47 bookwyrm/templates/layout.html:48 +#: bookwyrm/templates/layout.html:52 bookwyrm/templates/layout.html:53 msgid "Main navigation menu" msgstr "主导航菜单" -#: bookwyrm/templates/layout.html:58 +#: bookwyrm/templates/layout.html:63 msgid "Feed" msgstr "动态" -#: bookwyrm/templates/layout.html:87 +#: bookwyrm/templates/layout.html:92 msgid "Your Books" msgstr "你的书目" -#: bookwyrm/templates/layout.html:97 +#: bookwyrm/templates/layout.html:102 msgid "Settings" msgstr "设置" -#: bookwyrm/templates/layout.html:106 +#: bookwyrm/templates/layout.html:111 #: bookwyrm/templates/settings/admin_layout.html:31 #: bookwyrm/templates/settings/manage_invite_requests.html:15 #: bookwyrm/templates/settings/manage_invites.html:3 @@ -1238,61 +1297,61 @@ msgstr "设置" msgid "Invites" msgstr "邀请" -#: bookwyrm/templates/layout.html:113 +#: bookwyrm/templates/layout.html:118 msgid "Admin" msgstr "管理员" -#: bookwyrm/templates/layout.html:120 +#: bookwyrm/templates/layout.html:125 msgid "Log out" msgstr "登出" -#: bookwyrm/templates/layout.html:128 bookwyrm/templates/layout.html:129 +#: bookwyrm/templates/layout.html:133 bookwyrm/templates/layout.html:134 #: bookwyrm/templates/notifications.html:6 #: bookwyrm/templates/notifications.html:11 msgid "Notifications" msgstr "通知" -#: bookwyrm/templates/layout.html:151 bookwyrm/templates/layout.html:155 +#: bookwyrm/templates/layout.html:156 bookwyrm/templates/layout.html:160 #: bookwyrm/templates/login.html:17 #: bookwyrm/templates/snippets/register_form.html:4 msgid "Username:" msgstr "用户名:" -#: bookwyrm/templates/layout.html:156 +#: bookwyrm/templates/layout.html:161 msgid "password" msgstr "密码" -#: bookwyrm/templates/layout.html:157 bookwyrm/templates/login.html:36 +#: bookwyrm/templates/layout.html:162 bookwyrm/templates/login.html:36 msgid "Forgot your password?" msgstr "忘记了密码?" -#: bookwyrm/templates/layout.html:160 bookwyrm/templates/login.html:10 +#: bookwyrm/templates/layout.html:165 bookwyrm/templates/login.html:10 #: bookwyrm/templates/login.html:33 msgid "Log in" msgstr "登录" -#: bookwyrm/templates/layout.html:168 +#: bookwyrm/templates/layout.html:173 msgid "Join" msgstr "加入" -#: bookwyrm/templates/layout.html:206 +#: bookwyrm/templates/layout.html:211 msgid "About this instance" msgstr "关于本实例" -#: bookwyrm/templates/layout.html:210 +#: bookwyrm/templates/layout.html:215 msgid "Contact site admin" msgstr "联系站点管理员" -#: bookwyrm/templates/layout.html:214 +#: bookwyrm/templates/layout.html:219 msgid "Documentation" msgstr "文档:" -#: bookwyrm/templates/layout.html:221 +#: bookwyrm/templates/layout.html:226 #, python-format msgid "Support %(site_name)s on %(support_title)s" msgstr "在 %(support_title)s 上支持 %(site_name)s" -#: bookwyrm/templates/layout.html:225 +#: bookwyrm/templates/layout.html:230 msgid "BookWyrm's source code is freely available. You can contribute or report issues on GitHub." msgstr "BookWyrm 是开源软件。你可以在 GitHub 贡献或报告问题。" @@ -1454,7 +1513,7 @@ msgstr "联系管理员以取得邀请" #: bookwyrm/templates/login.html:63 msgid "More about this site" -msgstr "关于本站点的更多" +msgstr "更多关于本站点的信息" #: bookwyrm/templates/moderation/report.html:5 #: bookwyrm/templates/moderation/report.html:6 @@ -1670,6 +1729,7 @@ msgstr "你什么也没错过!" #: bookwyrm/templates/password_reset.html:23 #: bookwyrm/templates/preferences/change_password.html:18 +#: bookwyrm/templates/preferences/delete_user.html:20 msgid "Confirm password:" msgstr "确认密码:" @@ -1683,7 +1743,7 @@ msgstr "重设密码" #: bookwyrm/templates/preferences/blocks.html:4 #: bookwyrm/templates/preferences/blocks.html:7 -#: bookwyrm/templates/preferences/preferences_layout.html:26 +#: bookwyrm/templates/preferences/layout.html:30 msgid "Blocked Users" msgstr "屏蔽的用户" @@ -1694,7 +1754,7 @@ msgstr "当前没有被屏蔽的用户。" #: bookwyrm/templates/preferences/change_password.html:4 #: bookwyrm/templates/preferences/change_password.html:7 #: bookwyrm/templates/preferences/change_password.html:21 -#: bookwyrm/templates/preferences/preferences_layout.html:19 +#: bookwyrm/templates/preferences/layout.html:19 msgid "Change Password" msgstr "更改密码" @@ -1702,6 +1762,21 @@ msgstr "更改密码" msgid "New password:" msgstr "新密码:" +#: bookwyrm/templates/preferences/delete_user.html:4 +#: bookwyrm/templates/preferences/delete_user.html:7 +#: bookwyrm/templates/preferences/delete_user.html:26 +#: bookwyrm/templates/preferences/layout.html:23 +msgid "Delete Account" +msgstr "删除帐号" + +#: bookwyrm/templates/preferences/delete_user.html:12 +msgid "Permanently delete account" +msgstr "永久删除帐号" + +#: bookwyrm/templates/preferences/delete_user.html:14 +msgid "Deleting your account cannot be undone. The username will not be available to register in the future." +msgstr "删除帐号的操作将无法被撤销。对应用户名也无法被再次注册。" + #: bookwyrm/templates/preferences/edit_user.html:4 #: bookwyrm/templates/preferences/edit_user.html:7 msgid "Edit Profile" @@ -1720,18 +1795,33 @@ msgstr "你的帐号会显示在 目录 中,并可能 msgid "Preferred Timezone: " msgstr "偏好的时区:" -#: bookwyrm/templates/preferences/preferences_layout.html:11 +#: bookwyrm/templates/preferences/layout.html:11 msgid "Account" msgstr "帐号" -#: bookwyrm/templates/preferences/preferences_layout.html:15 +#: bookwyrm/templates/preferences/layout.html:15 msgid "Profile" msgstr "个人资料" -#: bookwyrm/templates/preferences/preferences_layout.html:22 +#: bookwyrm/templates/preferences/layout.html:26 msgid "Relationships" msgstr "关系" +#: bookwyrm/templates/reading_progress/finish.html:5 +#, python-format +msgid "Finish \"%(book_title)s\"" +msgstr "完成 \"%(book_title)s\"" + +#: bookwyrm/templates/reading_progress/start.html:5 +#, python-format +msgid "Start \"%(book_title)s\"" +msgstr "开始 \"%(book_title)s\"" + +#: bookwyrm/templates/reading_progress/want.html:5 +#, python-format +msgid "Want to Read \"%(book_title)s\"" +msgstr "想要阅读 \"%(book_title)s\"" + #: bookwyrm/templates/rss/title.html:5 #: bookwyrm/templates/snippets/status/status_header.html:35 msgid "rated" @@ -1774,7 +1864,7 @@ msgstr "搜索类型" #: bookwyrm/templates/search/layout.html:21 #: bookwyrm/templates/search/layout.html:42 -#: bookwyrm/templates/user/layout.html:74 +#: bookwyrm/templates/user/layout.html:79 msgid "Books" msgstr "书目" @@ -1979,7 +2069,7 @@ msgid "Details" msgstr "详细" #: bookwyrm/templates/settings/federated_server.html:39 -#: bookwyrm/templates/user/layout.html:56 +#: bookwyrm/templates/user/layout.html:61 msgid "Activity" msgstr "活动" @@ -2018,7 +2108,7 @@ msgid "Edit" msgstr "编辑" #: bookwyrm/templates/settings/federated_server.html:105 -#: bookwyrm/templates/user_admin/user_moderation_actions.html:3 +#: bookwyrm/templates/user_admin/user_moderation_actions.html:8 msgid "Actions" msgstr "动作" @@ -2224,15 +2314,15 @@ msgstr "管理员邮件:" msgid "Additional info:" msgstr "附加信息:" -#: bookwyrm/templates/settings/site.html:83 -msgid "Allow registration:" -msgstr "允许注册:" - -#: bookwyrm/templates/settings/site.html:87 -msgid "Allow invite requests:" -msgstr "允许请求邀请:" +#: bookwyrm/templates/settings/site.html:85 +msgid "Allow registration" +msgstr "允许注册" #: bookwyrm/templates/settings/site.html:91 +msgid "Allow invite requests" +msgstr "允许请求邀请" + +#: bookwyrm/templates/settings/site.html:95 msgid "Registration closed text:" msgstr "注册关闭文字:" @@ -2442,7 +2532,7 @@ msgstr "目标隐私:" #: bookwyrm/templates/snippets/goal_form.html:26 #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:37 -#: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:29 +#: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:31 #: bookwyrm/templates/snippets/shelve_button/want_to_read_modal.html:20 msgid "Post to feed" msgstr "发布到消息流中" @@ -2518,38 +2608,9 @@ msgstr "留下评价" msgid "Rate" msgstr "评价" -#: bookwyrm/templates/snippets/readthrough.html:8 -msgid "Progress Updates:" -msgstr "进度更新:" - -#: bookwyrm/templates/snippets/readthrough.html:14 -msgid "finished" -msgstr "已完成" - -#: bookwyrm/templates/snippets/readthrough.html:25 -msgid "Show all updates" -msgstr "显示所有更新" - -#: bookwyrm/templates/snippets/readthrough.html:41 -msgid "Delete this progress update" -msgstr "删除此进度更新" - -#: bookwyrm/templates/snippets/readthrough.html:52 -msgid "started" -msgstr "已开始" - -#: bookwyrm/templates/snippets/readthrough.html:59 -#: bookwyrm/templates/snippets/readthrough.html:73 -msgid "Edit read dates" -msgstr "编辑阅读日期" - -#: bookwyrm/templates/snippets/readthrough.html:63 -msgid "Delete these read dates" -msgstr "删除这些阅读日期" - #: bookwyrm/templates/snippets/readthrough_form.html:7 #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:19 -#: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:17 +#: bookwyrm/templates/snippets/shelve_button/start_reading_modal.html:19 msgid "Started reading" msgstr "已开始阅读" @@ -2584,7 +2645,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:36 +#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:45 msgid "Update progress" msgstr "更新进度" @@ -2592,20 +2653,20 @@ msgstr "更新进度" msgid "More shelves" msgstr "更多书架" -#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:10 +#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:11 msgid "Start reading" msgstr "开始阅读" -#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:15 +#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:19 msgid "Finish reading" msgstr "完成阅读" -#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:18 +#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:25 #: bookwyrm/templates/snippets/shelve_button/want_to_read_modal.html:26 msgid "Want to read" msgstr "想要阅读" -#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:47 +#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:57 #, python-format msgid "Remove from %(name)s" msgstr "从 %(name)s 移除" @@ -2669,7 +2730,7 @@ msgstr "删除并重新起草" #: bookwyrm/templates/snippets/status/status_options.html:35 #: bookwyrm/templates/snippets/user_options.html:13 -#: bookwyrm/templates/user_admin/user_moderation_actions.html:6 +#: bookwyrm/templates/user_admin/user_moderation_actions.html:12 msgid "Send direct message" msgstr "发送私信" @@ -2685,15 +2746,15 @@ msgstr "升序排序" msgid "Sorted descending" msgstr "降序排序" -#: bookwyrm/templates/user/layout.html:13 bookwyrm/templates/user/user.html:10 +#: bookwyrm/templates/user/layout.html:18 bookwyrm/templates/user/user.html:10 msgid "User Profile" msgstr "用户个人资料" -#: bookwyrm/templates/user/layout.html:37 +#: bookwyrm/templates/user/layout.html:42 msgid "Follow Requests" msgstr "关注请求" -#: bookwyrm/templates/user/layout.html:62 +#: bookwyrm/templates/user/layout.html:67 msgid "Reading Goal" msgstr "阅读目标" @@ -2876,22 +2937,31 @@ msgstr "实例详情" msgid "View instance" msgstr "查看实例" -#: bookwyrm/templates/user_admin/user_moderation_actions.html:11 +#: bookwyrm/templates/user_admin/user_moderation_actions.html:5 +msgid "Permanently deleted" +msgstr "已永久删除" + +#: bookwyrm/templates/user_admin/user_moderation_actions.html:17 msgid "Suspend user" msgstr "停用用户" -#: bookwyrm/templates/user_admin/user_moderation_actions.html:13 +#: bookwyrm/templates/user_admin/user_moderation_actions.html:19 msgid "Un-suspend user" msgstr "取消停用用户" -#: bookwyrm/templates/user_admin/user_moderation_actions.html:21 +#: bookwyrm/templates/user_admin/user_moderation_actions.html:28 msgid "Access level:" msgstr "访问级别:" -#: bookwyrm/templates/widgets/clearable_file_input_with_warning.html:3 +#: bookwyrm/templates/widgets/clearable_file_input_with_warning.html:22 msgid "File exceeds maximum size: 10MB" msgstr "文件超过了最大大小: 10MB" +#: bookwyrm/templatetags/utilities.py:30 +#, python-format +msgid "%(title)s: %(subtitle)s" +msgstr "%(title)s:%(subtitle)s" + #: bookwyrm/views/import_data.py:67 msgid "Not a valid csv file" msgstr "不是有效的 csv 文件" @@ -2904,4 +2974,3 @@ msgstr "没有找到使用该邮箱的用户。" #, python-format msgid "A password reset link sent to %s" msgstr "密码重置连接已发送给 %s" - diff --git a/locale/zh_Hant/LC_MESSAGES/django.mo b/locale/zh_Hant/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..8c8b5af887a30a2430d7a568e4e987d971a822fb GIT binary patch literal 41166 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 E2Ni* Date: Fri, 30 Jul 2021 23:50:21 -0400 Subject: [PATCH 15/21] Recognize profile summary linebreaks + whitespace --- bookwyrm/sanitize_html.py | 3 +++ bookwyrm/templates/directory/user_card.html | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/bookwyrm/sanitize_html.py b/bookwyrm/sanitize_html.py index 0be64c58c..1a8540e32 100644 --- a/bookwyrm/sanitize_html.py +++ b/bookwyrm/sanitize_html.py @@ -26,6 +26,8 @@ class InputHtmlParser(HTMLParser): # pylint: disable=abstract-method self.output = [] # if the html appears invalid, we just won't allow any at all self.allow_html = True + self.output.append(("data", "")) + self.output.append(("data", "")) def handle_starttag(self, tag, attrs): """check if the tag is valid""" @@ -56,6 +58,7 @@ class InputHtmlParser(HTMLParser): # pylint: disable=abstract-method def get_output(self): """convert the output from a list of tuples to a string""" + self.output.append(("data", "")) if self.tag_stack: self.allow_html = False if not self.allow_html: diff --git a/bookwyrm/templates/directory/user_card.html b/bookwyrm/templates/directory/user_card.html index c52c1f7ae..ca180dbce 100644 --- a/bookwyrm/templates/directory/user_card.html +++ b/bookwyrm/templates/directory/user_card.html @@ -20,7 +20,7 @@
{% if user.summary %} - {{ user.summary|to_markdown|safe|truncatechars_html:40 }} + {{ user.summary|to_markdown|safe|truncatechars_html:81 }} {% else %} {% endif %}
From f996cc7d1084fe321c919332d1daaeb95f7e9b2c Mon Sep 17 00:00:00 2001 From: Kylie <84819232+EvilDrPurple@users.noreply.github.com> Date: Sat, 31 Jul 2021 13:13:43 -0400 Subject: [PATCH 16/21] Revert "Recognize profile summary linebreaks + whitespace" --- bookwyrm/sanitize_html.py | 3 --- bookwyrm/templates/directory/user_card.html | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/bookwyrm/sanitize_html.py b/bookwyrm/sanitize_html.py index 1a8540e32..0be64c58c 100644 --- a/bookwyrm/sanitize_html.py +++ b/bookwyrm/sanitize_html.py @@ -26,8 +26,6 @@ class InputHtmlParser(HTMLParser): # pylint: disable=abstract-method self.output = [] # if the html appears invalid, we just won't allow any at all self.allow_html = True - self.output.append(("data", "")) - self.output.append(("data", "")) def handle_starttag(self, tag, attrs): """check if the tag is valid""" @@ -58,7 +56,6 @@ class InputHtmlParser(HTMLParser): # pylint: disable=abstract-method def get_output(self): """convert the output from a list of tuples to a string""" - self.output.append(("data", "")) if self.tag_stack: self.allow_html = False if not self.allow_html: diff --git a/bookwyrm/templates/directory/user_card.html b/bookwyrm/templates/directory/user_card.html index ca180dbce..c52c1f7ae 100644 --- a/bookwyrm/templates/directory/user_card.html +++ b/bookwyrm/templates/directory/user_card.html @@ -20,7 +20,7 @@
{% if user.summary %} - {{ user.summary|to_markdown|safe|truncatechars_html:81 }} + {{ user.summary|to_markdown|safe|truncatechars_html:40 }} {% else %} {% endif %}
From f2186d6861b1e86ef580278ec699699972ae82e9 Mon Sep 17 00:00:00 2001 From: Kylie Date: Sat, 31 Jul 2021 13:15:49 -0400 Subject: [PATCH 17/21] Add preserve-whitespace CSS tag --- bookwyrm/static/css/bookwyrm.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bookwyrm/static/css/bookwyrm.css b/bookwyrm/static/css/bookwyrm.css index 3db25d1fe..8aa0530c8 100644 --- a/bookwyrm/static/css/bookwyrm.css +++ b/bookwyrm/static/css/bookwyrm.css @@ -72,6 +72,10 @@ body { flex-grow: 1; } +.preserve-whitespace p { + white-space: pre-wrap !important; +} + /** Shelving ******************************************************************************/ From d75f33cfb29347beb5826df882a57e1e29b3b320 Mon Sep 17 00:00:00 2001 From: Kylie Date: Sat, 31 Jul 2021 13:16:47 -0400 Subject: [PATCH 18/21] Apply preserve-whitespace to profile summaries --- bookwyrm/templates/directory/user_card.html | 2 +- bookwyrm/templates/user/layout.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/templates/directory/user_card.html b/bookwyrm/templates/directory/user_card.html index ca180dbce..34059fa6c 100644 --- a/bookwyrm/templates/directory/user_card.html +++ b/bookwyrm/templates/directory/user_card.html @@ -18,7 +18,7 @@
-
+
{% if user.summary %} {{ user.summary|to_markdown|safe|truncatechars_html:81 }} {% else %} {% endif %} diff --git a/bookwyrm/templates/user/layout.html b/bookwyrm/templates/user/layout.html index 69455806e..3897983b7 100644 --- a/bookwyrm/templates/user/layout.html +++ b/bookwyrm/templates/user/layout.html @@ -28,7 +28,7 @@
{% if user.summary %} -
+
{{ user.summary|to_markdown|safe }}
{% endif %} From 1c7ed30d65d6cbbab17070d1c849c09e885557f6 Mon Sep 17 00:00:00 2001 From: Kylie <84819232+EvilDrPurple@users.noreply.github.com> Date: Sat, 31 Jul 2021 19:54:59 -0400 Subject: [PATCH 19/21] Update sanitize_html.py --- bookwyrm/sanitize_html.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bookwyrm/sanitize_html.py b/bookwyrm/sanitize_html.py index 1a8540e32..0be64c58c 100644 --- a/bookwyrm/sanitize_html.py +++ b/bookwyrm/sanitize_html.py @@ -26,8 +26,6 @@ class InputHtmlParser(HTMLParser): # pylint: disable=abstract-method self.output = [] # if the html appears invalid, we just won't allow any at all self.allow_html = True - self.output.append(("data", "")) - self.output.append(("data", "")) def handle_starttag(self, tag, attrs): """check if the tag is valid""" @@ -58,7 +56,6 @@ class InputHtmlParser(HTMLParser): # pylint: disable=abstract-method def get_output(self): """convert the output from a list of tuples to a string""" - self.output.append(("data", "")) if self.tag_stack: self.allow_html = False if not self.allow_html: From 3f01cf87880d74d93de3e0b96fe87c71fdf1bf18 Mon Sep 17 00:00:00 2001 From: Kylie <84819232+EvilDrPurple@users.noreply.github.com> Date: Sat, 31 Jul 2021 19:58:12 -0400 Subject: [PATCH 20/21] Update user_card.html --- bookwyrm/templates/directory/user_card.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/directory/user_card.html b/bookwyrm/templates/directory/user_card.html index 34059fa6c..3f7c10dee 100644 --- a/bookwyrm/templates/directory/user_card.html +++ b/bookwyrm/templates/directory/user_card.html @@ -20,7 +20,7 @@
{% if user.summary %} - {{ user.summary|to_markdown|safe|truncatechars_html:81 }} + {{ user.summary|to_markdown|safe|truncatechars_html:40 }} {% else %} {% endif %}
From a6b8d44627c1e97ffa230dfcd5ef6d9283784365 Mon Sep 17 00:00:00 2001 From: Kylie Date: Sat, 31 Jul 2021 20:30:21 -0400 Subject: [PATCH 21/21] Summary display updates --- bookwyrm/static/css/bookwyrm.css | 4 ++++ bookwyrm/templates/directory/user_card.html | 2 +- bookwyrm/templates/user/layout.html | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bookwyrm/static/css/bookwyrm.css b/bookwyrm/static/css/bookwyrm.css index 8aa0530c8..d10fb9b7e 100644 --- a/bookwyrm/static/css/bookwyrm.css +++ b/bookwyrm/static/css/bookwyrm.css @@ -76,6 +76,10 @@ body { white-space: pre-wrap !important; } +.display-inline p { + display: inline !important; +} + /** Shelving ******************************************************************************/ diff --git a/bookwyrm/templates/directory/user_card.html b/bookwyrm/templates/directory/user_card.html index 3f7c10dee..b7941826b 100644 --- a/bookwyrm/templates/directory/user_card.html +++ b/bookwyrm/templates/directory/user_card.html @@ -18,7 +18,7 @@
-
+
{% if user.summary %} {{ user.summary|to_markdown|safe|truncatechars_html:40 }} {% else %} {% endif %} diff --git a/bookwyrm/templates/user/layout.html b/bookwyrm/templates/user/layout.html index 3897983b7..41d52812f 100644 --- a/bookwyrm/templates/user/layout.html +++ b/bookwyrm/templates/user/layout.html @@ -28,8 +28,10 @@
{% if user.summary %} + {% spaceless %}
{{ user.summary|to_markdown|safe }} + {% endspaceless %}
{% endif %}