Merge branch 'main' into run-not-exec
This commit is contained in:
		| @@ -28,10 +28,14 @@ MAX_STREAM_LENGTH=200 | |||||||
| REDIS_ACTIVITY_HOST=redis_activity | REDIS_ACTIVITY_HOST=redis_activity | ||||||
| REDIS_ACTIVITY_PORT=6379 | REDIS_ACTIVITY_PORT=6379 | ||||||
| REDIS_ACTIVITY_PASSWORD=redispassword345 | REDIS_ACTIVITY_PASSWORD=redispassword345 | ||||||
|  | # Optional, use a different redis database (defaults to 0) | ||||||
|  | # REDIS_ACTIVITY_DB_INDEX=0 | ||||||
|  |  | ||||||
| # Redis as celery broker | # Redis as celery broker | ||||||
| REDIS_BROKER_PORT=6379 | REDIS_BROKER_PORT=6379 | ||||||
| REDIS_BROKER_PASSWORD=redispassword123 | REDIS_BROKER_PASSWORD=redispassword123 | ||||||
|  | # Optional, use a different redis database (defaults to 0) | ||||||
|  | # REDIS_BROKER_DB_INDEX=0 | ||||||
|  |  | ||||||
| # Monitoring for celery | # Monitoring for celery | ||||||
| FLOWER_PORT=8888 | FLOWER_PORT=8888 | ||||||
|   | |||||||
| @@ -20,22 +20,6 @@ class ActivityEncoder(JSONEncoder): | |||||||
|         return o.__dict__ |         return o.__dict__ | ||||||
|  |  | ||||||
|  |  | ||||||
| @dataclass |  | ||||||
| class Link: |  | ||||||
|     """for tagging a book in a status""" |  | ||||||
|  |  | ||||||
|     href: str |  | ||||||
|     name: str |  | ||||||
|     type: str = "Link" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @dataclass |  | ||||||
| class Mention(Link): |  | ||||||
|     """a subtype of Link for mentioning an actor""" |  | ||||||
|  |  | ||||||
|     type: str = "Mention" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @dataclass | @dataclass | ||||||
| # pylint: disable=invalid-name | # pylint: disable=invalid-name | ||||||
| class Signature: | class Signature: | ||||||
| @@ -198,8 +182,9 @@ class ActivityObject: | |||||||
|                 ) |                 ) | ||||||
|         return instance |         return instance | ||||||
|  |  | ||||||
|     def serialize(self): |     def serialize(self, **kwargs): | ||||||
|         """convert to dictionary with context attr""" |         """convert to dictionary with context attr""" | ||||||
|  |         omit = kwargs.get("omit", ()) | ||||||
|         data = self.__dict__.copy() |         data = self.__dict__.copy() | ||||||
|         # recursively serialize |         # recursively serialize | ||||||
|         for (k, v) in data.items(): |         for (k, v) in data.items(): | ||||||
| @@ -208,8 +193,9 @@ class ActivityObject: | |||||||
|                     data[k] = v.serialize() |                     data[k] = v.serialize() | ||||||
|             except TypeError: |             except TypeError: | ||||||
|                 pass |                 pass | ||||||
|         data = {k: v for (k, v) in data.items() if v is not None} |         data = {k: v for (k, v) in data.items() if v is not None and k not in omit} | ||||||
|         data["@context"] = "https://www.w3.org/ns/activitystreams" |         if "@context" not in omit: | ||||||
|  |             data["@context"] = "https://www.w3.org/ns/activitystreams" | ||||||
|         return data |         return data | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -222,35 +208,32 @@ def set_related_field( | |||||||
|     model = apps.get_model(f"bookwyrm.{model_name}", require_ready=True) |     model = apps.get_model(f"bookwyrm.{model_name}", require_ready=True) | ||||||
|     origin_model = apps.get_model(f"bookwyrm.{origin_model_name}", require_ready=True) |     origin_model = apps.get_model(f"bookwyrm.{origin_model_name}", require_ready=True) | ||||||
|  |  | ||||||
|     with transaction.atomic(): |     if isinstance(data, str): | ||||||
|         if isinstance(data, str): |         existing = model.find_existing_by_remote_id(data) | ||||||
|             existing = model.find_existing_by_remote_id(data) |         if existing: | ||||||
|             if existing: |             data = existing.to_activity() | ||||||
|                 data = existing.to_activity() |         else: | ||||||
|             else: |             data = get_data(data) | ||||||
|                 data = get_data(data) |     activity = model.activity_serializer(**data) | ||||||
|         activity = model.activity_serializer(**data) |  | ||||||
|  |  | ||||||
|         # this must exist because it's the object that triggered this function |     # this must exist because it's the object that triggered this function | ||||||
|         instance = origin_model.find_existing_by_remote_id(related_remote_id) |     instance = origin_model.find_existing_by_remote_id(related_remote_id) | ||||||
|         if not instance: |     if not instance: | ||||||
|             raise ValueError(f"Invalid related remote id: {related_remote_id}") |         raise ValueError(f"Invalid related remote id: {related_remote_id}") | ||||||
|  |  | ||||||
|         # set the origin's remote id on the activity so it will be there when |     # set the origin's remote id on the activity so it will be there when | ||||||
|         # the model instance is created |     # the model instance is created | ||||||
|         # edition.parentWork = instance, for example |     # edition.parentWork = instance, for example | ||||||
|         model_field = getattr(model, related_field_name) |     model_field = getattr(model, related_field_name) | ||||||
|         if hasattr(model_field, "activitypub_field"): |     if hasattr(model_field, "activitypub_field"): | ||||||
|             setattr( |         setattr(activity, getattr(model_field, "activitypub_field"), instance.remote_id) | ||||||
|                 activity, getattr(model_field, "activitypub_field"), instance.remote_id |     item = activity.to_model() | ||||||
|             ) |  | ||||||
|         item = activity.to_model() |  | ||||||
|  |  | ||||||
|         # if the related field isn't serialized (attachments on Status), then |     # if the related field isn't serialized (attachments on Status), then | ||||||
|         # we have to set it post-creation |     # we have to set it post-creation | ||||||
|         if not hasattr(model_field, "activitypub_field"): |     if not hasattr(model_field, "activitypub_field"): | ||||||
|             setattr(item, related_field_name, instance) |         setattr(item, related_field_name, instance) | ||||||
|             item.save() |         item.save() | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_model_from_type(activity_type): | def get_model_from_type(activity_type): | ||||||
| @@ -304,3 +287,27 @@ def resolve_remote_id( | |||||||
|  |  | ||||||
|     # if we're refreshing, "result" will be set and we'll update it |     # if we're refreshing, "result" will be set and we'll update it | ||||||
|     return item.to_model(model=model, instance=result, save=save) |     return item.to_model(model=model, instance=result, save=save) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass(init=False) | ||||||
|  | class Link(ActivityObject): | ||||||
|  |     """for tagging a book in a status""" | ||||||
|  |  | ||||||
|  |     href: str | ||||||
|  |     name: str = None | ||||||
|  |     mediaType: str = None | ||||||
|  |     id: str = None | ||||||
|  |     attributedTo: str = None | ||||||
|  |     type: str = "Link" | ||||||
|  |  | ||||||
|  |     def serialize(self, **kwargs): | ||||||
|  |         """remove fields""" | ||||||
|  |         omit = ("id", "type", "@context") | ||||||
|  |         return super().serialize(omit=omit) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass(init=False) | ||||||
|  | class Mention(Link): | ||||||
|  |     """a subtype of Link for mentioning an actor""" | ||||||
|  |  | ||||||
|  |     type: str = "Mention" | ||||||
|   | |||||||
| @@ -17,6 +17,8 @@ class BookData(ActivityObject): | |||||||
|     goodreadsKey: str = None |     goodreadsKey: str = None | ||||||
|     bnfId: str = None |     bnfId: str = None | ||||||
|     lastEditedBy: str = None |     lastEditedBy: str = None | ||||||
|  |     links: List[str] = field(default_factory=lambda: []) | ||||||
|  |     fileLinks: List[str] = field(default_factory=lambda: []) | ||||||
|  |  | ||||||
|  |  | ||||||
| # pylint: disable=invalid-name | # pylint: disable=invalid-name | ||||||
|   | |||||||
| @@ -15,6 +15,11 @@ class PublicKey(ActivityObject): | |||||||
|     publicKeyPem: str |     publicKeyPem: str | ||||||
|     type: str = "PublicKey" |     type: str = "PublicKey" | ||||||
|  |  | ||||||
|  |     def serialize(self, **kwargs): | ||||||
|  |         """remove fields""" | ||||||
|  |         omit = ("type", "@context") | ||||||
|  |         return super().serialize(omit=omit) | ||||||
|  |  | ||||||
|  |  | ||||||
| # pylint: disable=invalid-name | # pylint: disable=invalid-name | ||||||
| @dataclass(init=False) | @dataclass(init=False) | ||||||
|   | |||||||
| @@ -216,6 +216,18 @@ class CoverForm(CustomForm): | |||||||
|         help_texts = {f: None for f in fields} |         help_texts = {f: None for f in fields} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LinkDomainForm(CustomForm): | ||||||
|  |     class Meta: | ||||||
|  |         model = models.LinkDomain | ||||||
|  |         fields = ["name"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FileLinkForm(CustomForm): | ||||||
|  |     class Meta: | ||||||
|  |         model = models.FileLink | ||||||
|  |         fields = ["url", "filetype", "availability", "book", "added_by"] | ||||||
|  |  | ||||||
|  |  | ||||||
| class EditionForm(CustomForm): | class EditionForm(CustomForm): | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = models.Edition |         model = models.Edition | ||||||
| @@ -230,6 +242,8 @@ class EditionForm(CustomForm): | |||||||
|             "shelves", |             "shelves", | ||||||
|             "connector", |             "connector", | ||||||
|             "search_vector", |             "search_vector", | ||||||
|  |             "links", | ||||||
|  |             "file_links", | ||||||
|         ] |         ] | ||||||
|         widgets = { |         widgets = { | ||||||
|             "title": forms.TextInput(attrs={"aria-describedby": "desc_title"}), |             "title": forms.TextInput(attrs={"aria-describedby": "desc_title"}), | ||||||
| @@ -439,7 +453,7 @@ class GroupForm(CustomForm): | |||||||
| class ReportForm(CustomForm): | class ReportForm(CustomForm): | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = models.Report |         model = models.Report | ||||||
|         fields = ["user", "reporter", "statuses", "note"] |         fields = ["user", "reporter", "statuses", "links", "note"] | ||||||
|  |  | ||||||
|  |  | ||||||
| class EmailBlocklistForm(CustomForm): | class EmailBlocklistForm(CustomForm): | ||||||
| @@ -478,3 +492,19 @@ class SortListForm(forms.Form): | |||||||
|             ("descending", _("Descending")), |             ("descending", _("Descending")), | ||||||
|         ), |         ), | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ReadThroughForm(CustomForm): | ||||||
|  |     def clean(self): | ||||||
|  |         """make sure the email isn't in use by a registered user""" | ||||||
|  |         cleaned_data = super().clean() | ||||||
|  |         start_date = cleaned_data.get("start_date") | ||||||
|  |         finish_date = cleaned_data.get("finish_date") | ||||||
|  |         if start_date and finish_date and start_date > finish_date: | ||||||
|  |             self.add_error( | ||||||
|  |                 "finish_date", _("Reading finish date cannot be before start date.") | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |     class Meta: | ||||||
|  |         model = models.ReadThrough | ||||||
|  |         fields = ["user", "book", "start_date", "finish_date"] | ||||||
|   | |||||||
| @@ -5,7 +5,9 @@ import redis | |||||||
| from bookwyrm import settings | from bookwyrm import settings | ||||||
|  |  | ||||||
| r = redis.Redis( | r = redis.Redis( | ||||||
|     host=settings.REDIS_ACTIVITY_HOST, port=settings.REDIS_ACTIVITY_PORT, db=0 |     host=settings.REDIS_ACTIVITY_HOST, | ||||||
|  |     port=settings.REDIS_ACTIVITY_PORT, | ||||||
|  |     db=settings.REDIS_ACTIVITY_DB_INDEX, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ from django.core.management.base import BaseCommand | |||||||
| from django.contrib.auth.models import Group, Permission | from django.contrib.auth.models import Group, Permission | ||||||
| from django.contrib.contenttypes.models import ContentType | from django.contrib.contenttypes.models import ContentType | ||||||
|  |  | ||||||
| from bookwyrm.models import Connector, FederatedServer, SiteSettings, User | from bookwyrm import models | ||||||
|  |  | ||||||
|  |  | ||||||
| def init_groups(): | def init_groups(): | ||||||
| @@ -55,7 +55,7 @@ def init_permissions(): | |||||||
|         }, |         }, | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|     content_type = ContentType.objects.get_for_model(User) |     content_type = models.ContentType.objects.get_for_model(User) | ||||||
|     for permission in permissions: |     for permission in permissions: | ||||||
|         permission_obj = Permission.objects.create( |         permission_obj = Permission.objects.create( | ||||||
|             codename=permission["codename"], |             codename=permission["codename"], | ||||||
| @@ -72,7 +72,7 @@ def init_permissions(): | |||||||
|  |  | ||||||
| def init_connectors(): | def init_connectors(): | ||||||
|     """access book data sources""" |     """access book data sources""" | ||||||
|     Connector.objects.create( |     models.Connector.objects.create( | ||||||
|         identifier="bookwyrm.social", |         identifier="bookwyrm.social", | ||||||
|         name="BookWyrm dot Social", |         name="BookWyrm dot Social", | ||||||
|         connector_file="bookwyrm_connector", |         connector_file="bookwyrm_connector", | ||||||
| @@ -84,7 +84,7 @@ def init_connectors(): | |||||||
|         priority=2, |         priority=2, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     Connector.objects.create( |     models.Connector.objects.create( | ||||||
|         identifier="inventaire.io", |         identifier="inventaire.io", | ||||||
|         name="Inventaire", |         name="Inventaire", | ||||||
|         connector_file="inventaire", |         connector_file="inventaire", | ||||||
| @@ -96,7 +96,7 @@ def init_connectors(): | |||||||
|         priority=3, |         priority=3, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     Connector.objects.create( |     models.Connector.objects.create( | ||||||
|         identifier="openlibrary.org", |         identifier="openlibrary.org", | ||||||
|         name="OpenLibrary", |         name="OpenLibrary", | ||||||
|         connector_file="openlibrary", |         connector_file="openlibrary", | ||||||
| @@ -113,7 +113,7 @@ def init_federated_servers(): | |||||||
|     """big no to nazis""" |     """big no to nazis""" | ||||||
|     built_in_blocks = ["gab.ai", "gab.com"] |     built_in_blocks = ["gab.ai", "gab.com"] | ||||||
|     for server in built_in_blocks: |     for server in built_in_blocks: | ||||||
|         FederatedServer.objects.create( |         models.FederatedServer.objects.create( | ||||||
|             server_name=server, |             server_name=server, | ||||||
|             status="blocked", |             status="blocked", | ||||||
|         ) |         ) | ||||||
| @@ -121,18 +121,61 @@ def init_federated_servers(): | |||||||
|  |  | ||||||
| def init_settings(): | def init_settings(): | ||||||
|     """info about the instance""" |     """info about the instance""" | ||||||
|     SiteSettings.objects.create( |     models.SiteSettings.objects.create( | ||||||
|         support_link="https://www.patreon.com/bookwyrm", |         support_link="https://www.patreon.com/bookwyrm", | ||||||
|         support_title="Patreon", |         support_title="Patreon", | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def init_link_domains(*_): | ||||||
|  |     """safe book links""" | ||||||
|  |     domains = [ | ||||||
|  |         ("standardebooks.org", "Standard EBooks"), | ||||||
|  |         ("www.gutenberg.org", "Project Gutenberg"), | ||||||
|  |         ("archive.org", "Internet Archive"), | ||||||
|  |         ("openlibrary.org", "Open Library"), | ||||||
|  |         ("theanarchistlibrary.org", "The Anarchist Library"), | ||||||
|  |     ] | ||||||
|  |     for domain, name in domains: | ||||||
|  |         models.LinkDomain.objects.create( | ||||||
|  |             domain=domain, | ||||||
|  |             name=name, | ||||||
|  |             status="approved", | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Command(BaseCommand): | class Command(BaseCommand): | ||||||
|     help = "Initializes the database with starter data" |     help = "Initializes the database with starter data" | ||||||
|  |  | ||||||
|  |     def add_arguments(self, parser): | ||||||
|  |         parser.add_argument( | ||||||
|  |             "--limit", | ||||||
|  |             default=None, | ||||||
|  |             help="Limit init to specific table", | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def handle(self, *args, **options): |     def handle(self, *args, **options): | ||||||
|         init_groups() |         limit = options.get("limit") | ||||||
|         init_permissions() |         tables = [ | ||||||
|         init_connectors() |             "group", | ||||||
|         init_federated_servers() |             "permission", | ||||||
|         init_settings() |             "connector", | ||||||
|  |             "federatedserver", | ||||||
|  |             "settings", | ||||||
|  |             "linkdomain", | ||||||
|  |         ] | ||||||
|  |         if limit and limit not in tables: | ||||||
|  |             raise Exception("Invalid table limit:", limit) | ||||||
|  |  | ||||||
|  |         if not limit or limit == "group": | ||||||
|  |             init_groups() | ||||||
|  |         if not limit or limit == "permission": | ||||||
|  |             init_permissions() | ||||||
|  |         if not limit or limit == "connector": | ||||||
|  |             init_connectors() | ||||||
|  |         if not limit or limit == "federatedserver": | ||||||
|  |             init_federated_servers() | ||||||
|  |         if not limit or limit == "settings": | ||||||
|  |             init_settings() | ||||||
|  |         if not limit or limit == "linkdomain": | ||||||
|  |             init_link_domains() | ||||||
|   | |||||||
| @@ -22,13 +22,6 @@ class Command(BaseCommand): | |||||||
|  |  | ||||||
|     help = "Populate list streams for all users" |     help = "Populate list streams for all users" | ||||||
|  |  | ||||||
|     def add_arguments(self, parser): |  | ||||||
|         parser.add_argument( |  | ||||||
|             "--stream", |  | ||||||
|             default=None, |  | ||||||
|             help="Specifies which time of stream to populate", |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     # pylint: disable=no-self-use,unused-argument |     # pylint: disable=no-self-use,unused-argument | ||||||
|     def handle(self, *args, **options): |     def handle(self, *args, **options): | ||||||
|         """run feed builder""" |         """run feed builder""" | ||||||
|   | |||||||
							
								
								
									
										55
									
								
								bookwyrm/migrations/0126_auto_20220112_2315.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								bookwyrm/migrations/0126_auto_20220112_2315.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | # Generated by Django 3.2.10 on 2022-01-12 23:15 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ("bookwyrm", "0125_alter_user_preferred_language"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name="annualgoal", | ||||||
|  |             name="privacy", | ||||||
|  |             field=models.CharField( | ||||||
|  |                 choices=[ | ||||||
|  |                     ("public", "Public"), | ||||||
|  |                     ("unlisted", "Unlisted"), | ||||||
|  |                     ("followers", "Followers"), | ||||||
|  |                     ("direct", "Private"), | ||||||
|  |                 ], | ||||||
|  |                 default="public", | ||||||
|  |                 max_length=255, | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name="importjob", | ||||||
|  |             name="privacy", | ||||||
|  |             field=models.CharField( | ||||||
|  |                 choices=[ | ||||||
|  |                     ("public", "Public"), | ||||||
|  |                     ("unlisted", "Unlisted"), | ||||||
|  |                     ("followers", "Followers"), | ||||||
|  |                     ("direct", "Private"), | ||||||
|  |                 ], | ||||||
|  |                 default="public", | ||||||
|  |                 max_length=255, | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name="user", | ||||||
|  |             name="default_post_privacy", | ||||||
|  |             field=models.CharField( | ||||||
|  |                 choices=[ | ||||||
|  |                     ("public", "Public"), | ||||||
|  |                     ("unlisted", "Unlisted"), | ||||||
|  |                     ("followers", "Followers"), | ||||||
|  |                     ("direct", "Private"), | ||||||
|  |                 ], | ||||||
|  |                 default="public", | ||||||
|  |                 max_length=255, | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										144
									
								
								bookwyrm/migrations/0126_filelink_link_linkdomain.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								bookwyrm/migrations/0126_filelink_link_linkdomain.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | |||||||
|  | # Generated by Django 3.2.10 on 2022-01-10 21:20 | ||||||
|  |  | ||||||
|  | import bookwyrm.models.activitypub_mixin | ||||||
|  | import bookwyrm.models.fields | ||||||
|  | from django.conf import settings | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.db.models.deletion | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ("bookwyrm", "0125_alter_user_preferred_language"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name="LinkDomain", | ||||||
|  |             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], | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ("domain", models.CharField(max_length=255, unique=True)), | ||||||
|  |                 ( | ||||||
|  |                     "status", | ||||||
|  |                     models.CharField( | ||||||
|  |                         choices=[ | ||||||
|  |                             ("approved", "Approved"), | ||||||
|  |                             ("blocked", "Blocked"), | ||||||
|  |                             ("pending", "Pending"), | ||||||
|  |                         ], | ||||||
|  |                         default="pending", | ||||||
|  |                         max_length=50, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ("name", models.CharField(max_length=100)), | ||||||
|  |                 ( | ||||||
|  |                     "reported_by", | ||||||
|  |                     models.ForeignKey( | ||||||
|  |                         blank=True, | ||||||
|  |                         null=True, | ||||||
|  |                         on_delete=django.db.models.deletion.SET_NULL, | ||||||
|  |                         to=settings.AUTH_USER_MODEL, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 "abstract": False, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name="Link", | ||||||
|  |             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], | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ("url", bookwyrm.models.fields.URLField(max_length=255)), | ||||||
|  |                 ( | ||||||
|  |                     "added_by", | ||||||
|  |                     bookwyrm.models.fields.ForeignKey( | ||||||
|  |                         null=True, | ||||||
|  |                         on_delete=django.db.models.deletion.SET_NULL, | ||||||
|  |                         to=settings.AUTH_USER_MODEL, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ( | ||||||
|  |                     "domain", | ||||||
|  |                     models.ForeignKey( | ||||||
|  |                         blank=True, | ||||||
|  |                         null=True, | ||||||
|  |                         on_delete=django.db.models.deletion.CASCADE, | ||||||
|  |                         related_name="links", | ||||||
|  |                         to="bookwyrm.linkdomain", | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 "abstract": False, | ||||||
|  |             }, | ||||||
|  |             bases=(bookwyrm.models.activitypub_mixin.ActivitypubMixin, models.Model), | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name="FileLink", | ||||||
|  |             fields=[ | ||||||
|  |                 ( | ||||||
|  |                     "link_ptr", | ||||||
|  |                     models.OneToOneField( | ||||||
|  |                         auto_created=True, | ||||||
|  |                         on_delete=django.db.models.deletion.CASCADE, | ||||||
|  |                         parent_link=True, | ||||||
|  |                         primary_key=True, | ||||||
|  |                         serialize=False, | ||||||
|  |                         to="bookwyrm.link", | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 ("filetype", bookwyrm.models.fields.CharField(max_length=5)), | ||||||
|  |                 ( | ||||||
|  |                     "book", | ||||||
|  |                     models.ForeignKey( | ||||||
|  |                         null=True, | ||||||
|  |                         on_delete=django.db.models.deletion.CASCADE, | ||||||
|  |                         related_name="file_links", | ||||||
|  |                         to="bookwyrm.book", | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 "abstract": False, | ||||||
|  |             }, | ||||||
|  |             bases=("bookwyrm.link",), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										22
									
								
								bookwyrm/migrations/0127_auto_20220110_2211.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								bookwyrm/migrations/0127_auto_20220110_2211.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | # Generated by Django 3.2.10 on 2022-01-10 22:11 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ("bookwyrm", "0126_filelink_link_linkdomain"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.RemoveConstraint( | ||||||
|  |             model_name="report", | ||||||
|  |             name="self_report", | ||||||
|  |         ), | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="report", | ||||||
|  |             name="links", | ||||||
|  |             field=models.ManyToManyField(blank=True, to="bookwyrm.Link"), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -0,0 +1,13 @@ | |||||||
|  | # Generated by Django 3.2.10 on 2022-01-13 01:14 | ||||||
|  |  | ||||||
|  | from django.db import migrations | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ("bookwyrm", "0126_auto_20220112_2315"), | ||||||
|  |         ("bookwyrm", "0127_auto_20220110_2211"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [] | ||||||
							
								
								
									
										32
									
								
								bookwyrm/migrations/0129_auto_20220117_1716.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								bookwyrm/migrations/0129_auto_20220117_1716.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | # Generated by Django 3.2.10 on 2022-01-17 17:16 | ||||||
|  |  | ||||||
|  | import bookwyrm.models.fields | ||||||
|  | from django.db import migrations | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ("bookwyrm", "0128_merge_0126_auto_20220112_2315_0127_auto_20220110_2211"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="filelink", | ||||||
|  |             name="availability", | ||||||
|  |             field=bookwyrm.models.fields.CharField( | ||||||
|  |                 choices=[ | ||||||
|  |                     ("free", "Free"), | ||||||
|  |                     ("purchase", "Purchasable"), | ||||||
|  |                     ("loan", "Available for loan"), | ||||||
|  |                 ], | ||||||
|  |                 default="free", | ||||||
|  |                 max_length=100, | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name="filelink", | ||||||
|  |             name="filetype", | ||||||
|  |             field=bookwyrm.models.fields.CharField(max_length=50), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -4,6 +4,7 @@ import sys | |||||||
|  |  | ||||||
| from .book import Book, Work, Edition, BookDataModel | from .book import Book, Work, Edition, BookDataModel | ||||||
| from .author import Author | from .author import Author | ||||||
|  | from .link import Link, FileLink, LinkDomain | ||||||
| from .connector import Connector | from .connector import Connector | ||||||
|  |  | ||||||
| from .shelf import Shelf, ShelfBook | from .shelf import Shelf, ShelfBook | ||||||
|   | |||||||
| @@ -241,8 +241,11 @@ class Work(OrderedCollectionPageMixin, Book): | |||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     activity_serializer = activitypub.Work |     activity_serializer = activitypub.Work | ||||||
|     serialize_reverse_fields = [("editions", "editions", "-edition_rank")] |     serialize_reverse_fields = [ | ||||||
|     deserialize_reverse_fields = [("editions", "editions")] |         ("editions", "editions", "-edition_rank"), | ||||||
|  |         ("file_links", "fileLinks", "-created_date"), | ||||||
|  |     ] | ||||||
|  |     deserialize_reverse_fields = [("editions", "editions"), ("file_links", "fileLinks")] | ||||||
|  |  | ||||||
|  |  | ||||||
| # https://schema.org/BookFormatType | # https://schema.org/BookFormatType | ||||||
| @@ -296,6 +299,8 @@ class Edition(Book): | |||||||
|  |  | ||||||
|     activity_serializer = activitypub.Edition |     activity_serializer = activitypub.Edition | ||||||
|     name_field = "title" |     name_field = "title" | ||||||
|  |     serialize_reverse_fields = [("file_links", "fileLinks", "-created_date")] | ||||||
|  |     deserialize_reverse_fields = [("file_links", "fileLinks")] | ||||||
|  |  | ||||||
|     def get_rank(self): |     def get_rank(self): | ||||||
|         """calculate how complete the data is on this edition""" |         """calculate how complete the data is on this edition""" | ||||||
| @@ -337,6 +342,11 @@ class Edition(Book): | |||||||
|         # set rank |         # set rank | ||||||
|         self.edition_rank = self.get_rank() |         self.edition_rank = self.get_rank() | ||||||
|  |  | ||||||
|  |         # clear author cache | ||||||
|  |         if self.id: | ||||||
|  |             for author_id in self.authors.values_list("id", flat=True): | ||||||
|  |                 cache.delete(f"author-books-{author_id}") | ||||||
|  |  | ||||||
|         return super().save(*args, **kwargs) |         return super().save(*args, **kwargs) | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|   | |||||||
| @@ -203,9 +203,12 @@ class UsernameField(ActivitypubFieldMixin, models.CharField): | |||||||
|         return value.split("@")[0] |         return value.split("@")[0] | ||||||
|  |  | ||||||
|  |  | ||||||
| PrivacyLevels = models.TextChoices( | PrivacyLevels = [ | ||||||
|     "Privacy", ["public", "unlisted", "followers", "direct"] |     ("public", _("Public")), | ||||||
| ) |     ("unlisted", _("Unlisted")), | ||||||
|  |     ("followers", _("Followers")), | ||||||
|  |     ("direct", _("Private")), | ||||||
|  | ] | ||||||
|  |  | ||||||
|  |  | ||||||
| class PrivacyField(ActivitypubFieldMixin, models.CharField): | class PrivacyField(ActivitypubFieldMixin, models.CharField): | ||||||
| @@ -214,9 +217,7 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField): | |||||||
|     public = "https://www.w3.org/ns/activitystreams#Public" |     public = "https://www.w3.org/ns/activitystreams#Public" | ||||||
|  |  | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         super().__init__( |         super().__init__(*args, max_length=255, choices=PrivacyLevels, default="public") | ||||||
|             *args, max_length=255, choices=PrivacyLevels.choices, default="public" |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     # pylint: disable=invalid-name |     # pylint: disable=invalid-name | ||||||
|     def set_field_from_activity(self, instance, data, overwrite=True): |     def set_field_from_activity(self, instance, data, overwrite=True): | ||||||
| @@ -516,6 +517,10 @@ class CharField(ActivitypubFieldMixin, models.CharField): | |||||||
|     """activitypub-aware char field""" |     """activitypub-aware char field""" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class URLField(ActivitypubFieldMixin, models.URLField): | ||||||
|  |     """activitypub-aware url field""" | ||||||
|  |  | ||||||
|  |  | ||||||
| class TextField(ActivitypubFieldMixin, models.TextField): | class TextField(ActivitypubFieldMixin, models.TextField): | ||||||
|     """activitypub-aware text field""" |     """activitypub-aware text field""" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -40,9 +40,7 @@ class ImportJob(models.Model): | |||||||
|     mappings = models.JSONField() |     mappings = models.JSONField() | ||||||
|     complete = models.BooleanField(default=False) |     complete = models.BooleanField(default=False) | ||||||
|     source = models.CharField(max_length=100) |     source = models.CharField(max_length=100) | ||||||
|     privacy = models.CharField( |     privacy = models.CharField(max_length=255, default="public", choices=PrivacyLevels) | ||||||
|         max_length=255, default="public", choices=PrivacyLevels.choices |  | ||||||
|     ) |  | ||||||
|     retry = models.BooleanField(default=False) |     retry = models.BooleanField(default=False) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|   | |||||||
							
								
								
									
										95
									
								
								bookwyrm/models/link.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								bookwyrm/models/link.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | |||||||
|  | """ outlink data """ | ||||||
|  | from urllib.parse import urlparse | ||||||
|  |  | ||||||
|  | from django.core.exceptions import PermissionDenied | ||||||
|  | from django.db import models | ||||||
|  | from django.utils.translation import gettext_lazy as _ | ||||||
|  |  | ||||||
|  | from bookwyrm import activitypub | ||||||
|  | from .activitypub_mixin import ActivitypubMixin | ||||||
|  | from .base_model import BookWyrmModel | ||||||
|  | from . import fields | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Link(ActivitypubMixin, BookWyrmModel): | ||||||
|  |     """a link to a website""" | ||||||
|  |  | ||||||
|  |     url = fields.URLField(max_length=255, activitypub_field="href") | ||||||
|  |     added_by = fields.ForeignKey( | ||||||
|  |         "User", on_delete=models.SET_NULL, null=True, activitypub_field="attributedTo" | ||||||
|  |     ) | ||||||
|  |     domain = models.ForeignKey( | ||||||
|  |         "LinkDomain", | ||||||
|  |         on_delete=models.CASCADE, | ||||||
|  |         null=True, | ||||||
|  |         blank=True, | ||||||
|  |         related_name="links", | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     activity_serializer = activitypub.Link | ||||||
|  |     reverse_unfurl = True | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def name(self): | ||||||
|  |         """link name via the assocaited domain""" | ||||||
|  |         return self.domain.name | ||||||
|  |  | ||||||
|  |     def save(self, *args, **kwargs): | ||||||
|  |         """create a link""" | ||||||
|  |         # get or create the associated domain | ||||||
|  |         if not self.domain: | ||||||
|  |             domain = urlparse(self.url).netloc | ||||||
|  |             self.domain, _ = LinkDomain.objects.get_or_create(domain=domain) | ||||||
|  |  | ||||||
|  |         # this is never broadcast, the owning model broadcasts an update | ||||||
|  |         if "broadcast" in kwargs: | ||||||
|  |             del kwargs["broadcast"] | ||||||
|  |         return super().save(*args, **kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | AvailabilityChoices = [ | ||||||
|  |     ("free", _("Free")), | ||||||
|  |     ("purchase", _("Purchasable")), | ||||||
|  |     ("loan", _("Available for loan")), | ||||||
|  | ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FileLink(Link): | ||||||
|  |     """a link to a file""" | ||||||
|  |  | ||||||
|  |     book = models.ForeignKey( | ||||||
|  |         "Book", on_delete=models.CASCADE, related_name="file_links", null=True | ||||||
|  |     ) | ||||||
|  |     filetype = fields.CharField(max_length=50, activitypub_field="mediaType") | ||||||
|  |     availability = fields.CharField( | ||||||
|  |         max_length=100, choices=AvailabilityChoices, default="free" | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | StatusChoices = [ | ||||||
|  |     ("approved", _("Approved")), | ||||||
|  |     ("blocked", _("Blocked")), | ||||||
|  |     ("pending", _("Pending")), | ||||||
|  | ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LinkDomain(BookWyrmModel): | ||||||
|  |     """List of domains used in links""" | ||||||
|  |  | ||||||
|  |     domain = models.CharField(max_length=255, unique=True) | ||||||
|  |     status = models.CharField(max_length=50, choices=StatusChoices, default="pending") | ||||||
|  |     name = models.CharField(max_length=100) | ||||||
|  |     reported_by = models.ForeignKey( | ||||||
|  |         "User", blank=True, null=True, on_delete=models.SET_NULL | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     def raise_not_editable(self, viewer): | ||||||
|  |         if viewer.has_perm("moderate_post"): | ||||||
|  |             return | ||||||
|  |         raise PermissionDenied() | ||||||
|  |  | ||||||
|  |     def save(self, *args, **kwargs): | ||||||
|  |         """set a default name""" | ||||||
|  |         if not self.name: | ||||||
|  |             self.name = self.domain | ||||||
|  |         super().save(*args, **kwargs) | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| """ progress in a book """ | """ progress in a book """ | ||||||
| from django.core import validators | from django.core import validators | ||||||
|  | from django.core.cache import cache | ||||||
| from django.db import models | from django.db import models | ||||||
| from django.db.models import F, Q | from django.db.models import F, Q | ||||||
|  |  | ||||||
| @@ -30,6 +31,7 @@ class ReadThrough(BookWyrmModel): | |||||||
|  |  | ||||||
|     def save(self, *args, **kwargs): |     def save(self, *args, **kwargs): | ||||||
|         """update user active time""" |         """update user active time""" | ||||||
|  |         cache.delete(f"latest_read_through-{self.user.id}-{self.book.id}") | ||||||
|         self.user.update_active_date() |         self.user.update_active_date() | ||||||
|         # an active readthrough must have an unset finish date |         # an active readthrough must have an unset finish date | ||||||
|         if self.finish_date: |         if self.finish_date: | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| """ defines relationships between users """ | """ defines relationships between users """ | ||||||
| from django.apps import apps | from django.apps import apps | ||||||
| from django.core.cache import cache | from django.core.cache import cache | ||||||
| from django.core.cache.utils import make_template_fragment_key |  | ||||||
| from django.db import models, transaction, IntegrityError | from django.db import models, transaction, IntegrityError | ||||||
| from django.db.models import Q | from django.db.models import Q | ||||||
|  |  | ||||||
| @@ -41,15 +40,12 @@ class UserRelationship(BookWyrmModel): | |||||||
|     def save(self, *args, **kwargs): |     def save(self, *args, **kwargs): | ||||||
|         """clear the template cache""" |         """clear the template cache""" | ||||||
|         # invalidate the template cache |         # invalidate the template cache | ||||||
|         cache_keys = [ |         cache.delete_many( | ||||||
|             make_template_fragment_key( |             [ | ||||||
|                 "follow_button", [self.user_subject.id, self.user_object.id] |                 f"relationship-{self.user_subject.id}-{self.user_object.id}", | ||||||
|             ), |                 f"relationship-{self.user_object.id}-{self.user_subject.id}", | ||||||
|             make_template_fragment_key( |             ] | ||||||
|                 "follow_button", [self.user_object.id, self.user_subject.id] |         ) | ||||||
|             ), |  | ||||||
|         ] |  | ||||||
|         cache.delete_many(cache_keys) |  | ||||||
|         super().save(*args, **kwargs) |         super().save(*args, **kwargs) | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| """ flagged for moderation """ | """ flagged for moderation """ | ||||||
| from django.db import models | from django.db import models | ||||||
| from django.db.models import F, Q |  | ||||||
| from .base_model import BookWyrmModel | from .base_model import BookWyrmModel | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -13,14 +12,12 @@ class Report(BookWyrmModel): | |||||||
|     note = models.TextField(null=True, blank=True) |     note = models.TextField(null=True, blank=True) | ||||||
|     user = models.ForeignKey("User", on_delete=models.PROTECT) |     user = models.ForeignKey("User", on_delete=models.PROTECT) | ||||||
|     statuses = models.ManyToManyField("Status", blank=True) |     statuses = models.ManyToManyField("Status", blank=True) | ||||||
|  |     links = models.ManyToManyField("Link", blank=True) | ||||||
|     resolved = models.BooleanField(default=False) |     resolved = models.BooleanField(default=False) | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         """don't let users report themselves""" |         """set order by default""" | ||||||
|  |  | ||||||
|         constraints = [ |  | ||||||
|             models.CheckConstraint(check=~Q(reporter=F("user")), name="self_report") |  | ||||||
|         ] |  | ||||||
|         ordering = ("-created_date",) |         ordering = ("-created_date",) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| """ puttin' books on shelves """ | """ puttin' books on shelves """ | ||||||
| import re | import re | ||||||
|  | from django.core.cache import cache | ||||||
| from django.core.exceptions import PermissionDenied | from django.core.exceptions import PermissionDenied | ||||||
| from django.db import models | from django.db import models | ||||||
| from django.utils import timezone | from django.utils import timezone | ||||||
| @@ -94,8 +95,15 @@ class ShelfBook(CollectionItemMixin, BookWyrmModel): | |||||||
|     def save(self, *args, **kwargs): |     def save(self, *args, **kwargs): | ||||||
|         if not self.user: |         if not self.user: | ||||||
|             self.user = self.shelf.user |             self.user = self.shelf.user | ||||||
|  |         if self.id and self.user.local: | ||||||
|  |             cache.delete(f"book-on-shelf-{self.book.id}-{self.shelf.id}") | ||||||
|         super().save(*args, **kwargs) |         super().save(*args, **kwargs) | ||||||
|  |  | ||||||
|  |     def delete(self, *args, **kwargs): | ||||||
|  |         if self.id and self.user.local: | ||||||
|  |             cache.delete(f"book-on-shelf-{self.book.id}-{self.shelf.id}") | ||||||
|  |         super().delete(*args, **kwargs) | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         """an opinionated constraint! |         """an opinionated constraint! | ||||||
|         you can't put a book on shelf twice""" |         you can't put a book on shelf twice""" | ||||||
|   | |||||||
| @@ -90,6 +90,14 @@ class SiteSettings(models.Model): | |||||||
|             return get_absolute_url(uploaded) |             return get_absolute_url(uploaded) | ||||||
|         return urljoin(STATIC_FULL_URL, default_path) |         return urljoin(STATIC_FULL_URL, default_path) | ||||||
|  |  | ||||||
|  |     def save(self, *args, **kwargs): | ||||||
|  |         """if require_confirm_email is disabled, make sure no users are pending""" | ||||||
|  |         if not self.require_confirm_email: | ||||||
|  |             User.objects.filter(is_active=False, deactivation_reason="pending").update( | ||||||
|  |                 is_active=True, deactivation_reason=None | ||||||
|  |             ) | ||||||
|  |         super().save(*args, **kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
| class SiteInvite(models.Model): | class SiteInvite(models.Model): | ||||||
|     """gives someone access to create an account on the instance""" |     """gives someone access to create an account on the instance""" | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ from dataclasses import MISSING | |||||||
| import re | import re | ||||||
|  |  | ||||||
| from django.apps import apps | from django.apps import apps | ||||||
|  | from django.core.cache import cache | ||||||
| from django.core.exceptions import PermissionDenied | from django.core.exceptions import PermissionDenied | ||||||
| from django.core.validators import MaxValueValidator, MinValueValidator | from django.core.validators import MaxValueValidator, MinValueValidator | ||||||
| from django.db import models | from django.db import models | ||||||
| @@ -373,6 +374,12 @@ class Review(BookStatus): | |||||||
|     activity_serializer = activitypub.Review |     activity_serializer = activitypub.Review | ||||||
|     pure_type = "Article" |     pure_type = "Article" | ||||||
|  |  | ||||||
|  |     def save(self, *args, **kwargs): | ||||||
|  |         """clear rating caches""" | ||||||
|  |         if self.book.parent_work: | ||||||
|  |             cache.delete(f"book-rating-{self.book.parent_work.id}-*") | ||||||
|  |         super().save(*args, **kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
| class ReviewRating(Review): | class ReviewRating(Review): | ||||||
|     """a subtype of review that only contains a rating""" |     """a subtype of review that only contains a rating""" | ||||||
|   | |||||||
| @@ -129,7 +129,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): | |||||||
|         related_name="favorite_statuses", |         related_name="favorite_statuses", | ||||||
|     ) |     ) | ||||||
|     default_post_privacy = models.CharField( |     default_post_privacy = models.CharField( | ||||||
|         max_length=255, default="public", choices=fields.PrivacyLevels.choices |         max_length=255, default="public", choices=fields.PrivacyLevels | ||||||
|     ) |     ) | ||||||
|     remote_id = fields.RemoteIdField(null=True, unique=True, activitypub_field="id") |     remote_id = fields.RemoteIdField(null=True, unique=True, activitypub_field="id") | ||||||
|     created_date = models.DateTimeField(auto_now_add=True) |     created_date = models.DateTimeField(auto_now_add=True) | ||||||
| @@ -346,6 +346,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): | |||||||
|  |  | ||||||
|     def delete(self, *args, **kwargs): |     def delete(self, *args, **kwargs): | ||||||
|         """deactivate rather than delete a user""" |         """deactivate rather than delete a user""" | ||||||
|  |         # pylint: disable=attribute-defined-outside-init | ||||||
|         self.is_active = False |         self.is_active = False | ||||||
|         # skip the logic in this class's save() |         # skip the logic in this class's save() | ||||||
|         super().save(*args, **kwargs) |         super().save(*args, **kwargs) | ||||||
| @@ -406,14 +407,6 @@ class KeyPair(ActivitypubMixin, BookWyrmModel): | |||||||
|             self.private_key, self.public_key = create_key_pair() |             self.private_key, self.public_key = create_key_pair() | ||||||
|         return super().save(*args, **kwargs) |         return super().save(*args, **kwargs) | ||||||
|  |  | ||||||
|     def to_activity(self, **kwargs): |  | ||||||
|         """override default AP serializer to add context object |  | ||||||
|         idk if this is the best way to go about this""" |  | ||||||
|         activity_object = super().to_activity(**kwargs) |  | ||||||
|         del activity_object["@context"] |  | ||||||
|         del activity_object["type"] |  | ||||||
|         return activity_object |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_current_year(): | def get_current_year(): | ||||||
|     """sets default year for annual goal to this year""" |     """sets default year for annual goal to this year""" | ||||||
| @@ -427,7 +420,7 @@ class AnnualGoal(BookWyrmModel): | |||||||
|     goal = models.IntegerField(validators=[MinValueValidator(1)]) |     goal = models.IntegerField(validators=[MinValueValidator(1)]) | ||||||
|     year = models.IntegerField(default=get_current_year) |     year = models.IntegerField(default=get_current_year) | ||||||
|     privacy = models.CharField( |     privacy = models.CharField( | ||||||
|         max_length=255, default="public", choices=fields.PrivacyLevels.choices |         max_length=255, default="public", choices=fields.PrivacyLevels | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ r = redis.Redis( | |||||||
|     host=settings.REDIS_ACTIVITY_HOST, |     host=settings.REDIS_ACTIVITY_HOST, | ||||||
|     port=settings.REDIS_ACTIVITY_PORT, |     port=settings.REDIS_ACTIVITY_PORT, | ||||||
|     password=settings.REDIS_ACTIVITY_PASSWORD, |     password=settings.REDIS_ACTIVITY_PASSWORD, | ||||||
|     db=0, |     db=settings.REDIS_ACTIVITY_DB_INDEX, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,12 +9,12 @@ from django.utils.translation import gettext_lazy as _ | |||||||
| env = Env() | env = Env() | ||||||
| env.read_env() | env.read_env() | ||||||
| DOMAIN = env("DOMAIN") | DOMAIN = env("DOMAIN") | ||||||
| VERSION = "0.1.1" | VERSION = "0.2.0" | ||||||
|  |  | ||||||
| PAGE_LENGTH = env("PAGE_LENGTH", 15) | PAGE_LENGTH = env("PAGE_LENGTH", 15) | ||||||
| DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English") | DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English") | ||||||
|  |  | ||||||
| JS_CACHE = "2d3181e1" | JS_CACHE = "76c5ff1f" | ||||||
|  |  | ||||||
| # email | # email | ||||||
| EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend") | EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend") | ||||||
| @@ -25,7 +25,7 @@ EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD") | |||||||
| EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS", True) | EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS", True) | ||||||
| EMAIL_USE_SSL = env.bool("EMAIL_USE_SSL", False) | EMAIL_USE_SSL = env.bool("EMAIL_USE_SSL", False) | ||||||
| EMAIL_SENDER_NAME = env("EMAIL_SENDER_NAME", "admin") | EMAIL_SENDER_NAME = env("EMAIL_SENDER_NAME", "admin") | ||||||
| EMAIL_SENDER_DOMAIN = env("EMAIL_SENDER_NAME", DOMAIN) | EMAIL_SENDER_DOMAIN = env("EMAIL_SENDER_DOMAIN", DOMAIN) | ||||||
| EMAIL_SENDER = f"{EMAIL_SENDER_NAME}@{EMAIL_SENDER_DOMAIN}" | EMAIL_SENDER = f"{EMAIL_SENDER_NAME}@{EMAIL_SENDER_DOMAIN}" | ||||||
|  |  | ||||||
| # Build paths inside the project like this: os.path.join(BASE_DIR, ...) | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) | ||||||
| @@ -106,6 +106,58 @@ TEMPLATES = [ | |||||||
|     }, |     }, | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | LOG_LEVEL = env("LOG_LEVEL", "INFO").upper() | ||||||
|  | # Override aspects of the default handler to our taste | ||||||
|  | # See https://docs.djangoproject.com/en/3.2/topics/logging/#default-logging-configuration | ||||||
|  | # for a reference to the defaults we're overriding | ||||||
|  | # | ||||||
|  | # It seems that in order to override anything you have to include its | ||||||
|  | # entire dependency tree (handlers and filters) which makes this a | ||||||
|  | # bit verbose | ||||||
|  | LOGGING = { | ||||||
|  |     "version": 1, | ||||||
|  |     "disable_existing_loggers": False, | ||||||
|  |     "filters": { | ||||||
|  |         # These are copied from the default configuration, required for | ||||||
|  |         # implementing mail_admins below | ||||||
|  |         "require_debug_false": { | ||||||
|  |             "()": "django.utils.log.RequireDebugFalse", | ||||||
|  |         }, | ||||||
|  |         "require_debug_true": { | ||||||
|  |             "()": "django.utils.log.RequireDebugTrue", | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|  |     "handlers": { | ||||||
|  |         # Overrides the default handler to make it log to console | ||||||
|  |         # regardless of the DEBUG setting (default is to not log to | ||||||
|  |         # console if DEBUG=False) | ||||||
|  |         "console": { | ||||||
|  |             "level": LOG_LEVEL, | ||||||
|  |             "class": "logging.StreamHandler", | ||||||
|  |         }, | ||||||
|  |         # This is copied as-is from the default logger, and is | ||||||
|  |         # required for the django section below | ||||||
|  |         "mail_admins": { | ||||||
|  |             "level": "ERROR", | ||||||
|  |             "filters": ["require_debug_false"], | ||||||
|  |             "class": "django.utils.log.AdminEmailHandler", | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|  |     "loggers": { | ||||||
|  |         # Install our new console handler for Django's logger, and | ||||||
|  |         # override the log level while we're at it | ||||||
|  |         "django": { | ||||||
|  |             "handlers": ["console", "mail_admins"], | ||||||
|  |             "level": LOG_LEVEL, | ||||||
|  |         }, | ||||||
|  |         # Add a bookwyrm-specific logger | ||||||
|  |         "bookwyrm": { | ||||||
|  |             "handlers": ["console"], | ||||||
|  |             "level": LOG_LEVEL, | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| WSGI_APPLICATION = "bookwyrm.wsgi.application" | WSGI_APPLICATION = "bookwyrm.wsgi.application" | ||||||
|  |  | ||||||
| @@ -113,6 +165,7 @@ WSGI_APPLICATION = "bookwyrm.wsgi.application" | |||||||
| REDIS_ACTIVITY_HOST = env("REDIS_ACTIVITY_HOST", "localhost") | REDIS_ACTIVITY_HOST = env("REDIS_ACTIVITY_HOST", "localhost") | ||||||
| REDIS_ACTIVITY_PORT = env("REDIS_ACTIVITY_PORT", 6379) | REDIS_ACTIVITY_PORT = env("REDIS_ACTIVITY_PORT", 6379) | ||||||
| REDIS_ACTIVITY_PASSWORD = env("REDIS_ACTIVITY_PASSWORD", None) | REDIS_ACTIVITY_PASSWORD = env("REDIS_ACTIVITY_PASSWORD", None) | ||||||
|  | REDIS_ACTIVITY_DB_INDEX = env("REDIS_ACTIVITY_DB_INDEX", 0) | ||||||
|  |  | ||||||
| MAX_STREAM_LENGTH = int(env("MAX_STREAM_LENGTH", 200)) | MAX_STREAM_LENGTH = int(env("MAX_STREAM_LENGTH", 200)) | ||||||
|  |  | ||||||
| @@ -139,7 +192,7 @@ else: | |||||||
|     CACHES = { |     CACHES = { | ||||||
|         "default": { |         "default": { | ||||||
|             "BACKEND": "django_redis.cache.RedisCache", |             "BACKEND": "django_redis.cache.RedisCache", | ||||||
|             "LOCATION": f"redis://:{REDIS_ACTIVITY_PASSWORD}@{REDIS_ACTIVITY_HOST}:{REDIS_ACTIVITY_PORT}/0", |             "LOCATION": f"redis://:{REDIS_ACTIVITY_PASSWORD}@{REDIS_ACTIVITY_HOST}:{REDIS_ACTIVITY_PORT}/{REDIS_ACTIVITY_DB_INDEX}", | ||||||
|             "OPTIONS": { |             "OPTIONS": { | ||||||
|                 "CLIENT_CLASS": "django_redis.client.DefaultClient", |                 "CLIENT_CLASS": "django_redis.client.DefaultClient", | ||||||
|             }, |             }, | ||||||
|   | |||||||
| @@ -720,6 +720,11 @@ ol.ordered-list li::before { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .overflow-wrap-anywhere { | ||||||
|  |     overflow-wrap: anywhere; | ||||||
|  |     min-width: 10em; | ||||||
|  | } | ||||||
|  |  | ||||||
| /* Threads | /* Threads | ||||||
|  ******************************************************************************/ |  ******************************************************************************/ | ||||||
|  |  | ||||||
| @@ -751,6 +756,13 @@ ol.ordered-list li::before { | |||||||
|     padding: 0 0.75em; |     padding: 0 0.75em; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* Notifications page | ||||||
|  |  ******************************************************************************/ | ||||||
|  |  | ||||||
|  | .notification a.icon { | ||||||
|  |     text-decoration: none !important; | ||||||
|  | } | ||||||
|  |  | ||||||
| /* Breadcrumbs | /* Breadcrumbs | ||||||
|  ******************************************************************************/ |  ******************************************************************************/ | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										184
									
								
								bookwyrm/static/js/autocomplete.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								bookwyrm/static/js/autocomplete.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,184 @@ | |||||||
|  | (function () { | ||||||
|  |     "use strict"; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Suggest a completion as a user types | ||||||
|  |      * | ||||||
|  |      * Use `data-autocomplete="<completions set identifier>"`on the input field. | ||||||
|  |      * specifying the trie to be used for autocomplete | ||||||
|  |      * | ||||||
|  |      * @example | ||||||
|  |      * <input | ||||||
|  |      *     type="input" | ||||||
|  |      *     data-autocomplete="mimetype" | ||||||
|  |      * > | ||||||
|  |      * @param  {Event} event | ||||||
|  |      * @return {undefined} | ||||||
|  |      */ | ||||||
|  |     function autocomplete(event) { | ||||||
|  |         const input = event.target; | ||||||
|  |  | ||||||
|  |         // Get suggestions | ||||||
|  |         let trie = tries[input.getAttribute("data-autocomplete")]; | ||||||
|  |  | ||||||
|  |         let suggestions = getSuggestions(input.value, trie); | ||||||
|  |  | ||||||
|  |         const boxId = input.getAttribute("list"); | ||||||
|  |  | ||||||
|  |         // Create suggestion box, if needed | ||||||
|  |         let suggestionsBox = document.getElementById(boxId); | ||||||
|  |  | ||||||
|  |         // Clear existing suggestions | ||||||
|  |         suggestionsBox.innerHTML = ""; | ||||||
|  |  | ||||||
|  |         // Populate suggestions box | ||||||
|  |         suggestions.forEach((suggestion) => { | ||||||
|  |             const suggestionItem = document.createElement("option"); | ||||||
|  |  | ||||||
|  |             suggestionItem.textContent = suggestion; | ||||||
|  |             suggestionsBox.appendChild(suggestionItem); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function getSuggestions(input, trie) { | ||||||
|  |         // Follow the trie through the provided input | ||||||
|  |         input = input.toLowerCase(); | ||||||
|  |  | ||||||
|  |         input.split("").forEach((letter) => { | ||||||
|  |             if (!trie) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             trie = trie[letter]; | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         if (!trie) { | ||||||
|  |             return []; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return searchTrie(trie); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function searchTrie(trie) { | ||||||
|  |         const options = Object.values(trie); | ||||||
|  |  | ||||||
|  |         if (typeof trie == "string") { | ||||||
|  |             return [trie]; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return options | ||||||
|  |             .map((option) => { | ||||||
|  |                 const newTrie = option; | ||||||
|  |  | ||||||
|  |                 if (typeof newTrie == "string") { | ||||||
|  |                     return [newTrie]; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return searchTrie(newTrie); | ||||||
|  |             }) | ||||||
|  |             .reduce((prev, next) => prev.concat(next)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     document.querySelectorAll("[data-autocomplete]").forEach((input) => { | ||||||
|  |         input.addEventListener("input", autocomplete); | ||||||
|  |     }); | ||||||
|  | })(); | ||||||
|  |  | ||||||
|  | const tries = { | ||||||
|  |     mimetype: { | ||||||
|  |         a: { | ||||||
|  |             a: { | ||||||
|  |                 c: "AAC", | ||||||
|  |             }, | ||||||
|  |             z: { | ||||||
|  |                 w: "AZW", | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         d: { | ||||||
|  |             a: { | ||||||
|  |                 i: { | ||||||
|  |                     s: { | ||||||
|  |                         y: "Daisy", | ||||||
|  |                     }, | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         e: { | ||||||
|  |             p: { | ||||||
|  |                 u: { | ||||||
|  |                     b: "ePub", | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         f: { | ||||||
|  |             l: { | ||||||
|  |                 a: { | ||||||
|  |                     c: "FLAC", | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         h: { | ||||||
|  |             t: { | ||||||
|  |                 m: { | ||||||
|  |                     l: "HTML", | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         m: { | ||||||
|  |             4: { | ||||||
|  |                 a: "M4A", | ||||||
|  |                 b: "M4B", | ||||||
|  |             }, | ||||||
|  |             o: { | ||||||
|  |                 b: { | ||||||
|  |                     i: "MOBI", | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |             p: { | ||||||
|  |                 3: "MP3", | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         o: { | ||||||
|  |             g: { | ||||||
|  |                 g: "OGG", | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         p: { | ||||||
|  |             d: { | ||||||
|  |                 f: "PDF", | ||||||
|  |             }, | ||||||
|  |             l: { | ||||||
|  |                 a: { | ||||||
|  |                     i: { | ||||||
|  |                         n: { | ||||||
|  |                             t: { | ||||||
|  |                                 e: { | ||||||
|  |                                     x: { | ||||||
|  |                                         t: "Plaintext", | ||||||
|  |                                     }, | ||||||
|  |                                 }, | ||||||
|  |                             }, | ||||||
|  |                         }, | ||||||
|  |                     }, | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |             r: { | ||||||
|  |                 i: { | ||||||
|  |                     n: { | ||||||
|  |                         t: { | ||||||
|  |                             " ": { | ||||||
|  |                                 b: { | ||||||
|  |                                     o: { | ||||||
|  |                                         o: { | ||||||
|  |                                             k: "Print book", | ||||||
|  |                                         }, | ||||||
|  |                                     }, | ||||||
|  |                                 }, | ||||||
|  |                             }, | ||||||
|  |                         }, | ||||||
|  |                     }, | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|  | }; | ||||||
| @@ -35,7 +35,7 @@ let BookWyrm = new (class { | |||||||
|             .forEach((node) => node.addEventListener("change", this.disableIfTooLarge.bind(this))); |             .forEach((node) => node.addEventListener("change", this.disableIfTooLarge.bind(this))); | ||||||
|  |  | ||||||
|         document |         document | ||||||
|             .querySelectorAll("button[data-modal-open]") |             .querySelectorAll("[data-modal-open]") | ||||||
|             .forEach((node) => node.addEventListener("click", this.handleModalButton.bind(this))); |             .forEach((node) => node.addEventListener("click", this.handleModalButton.bind(this))); | ||||||
|  |  | ||||||
|         document |         document | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| {% load humanize %} | {% load humanize %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
| {% load bookwyrm_tags %} | {% load landing_page_tags %} | ||||||
| {% load cache %} | {% load cache %} | ||||||
|  |  | ||||||
| {% block title %} | {% block title %} | ||||||
| @@ -12,6 +12,7 @@ | |||||||
| {% block about_content %} | {% block about_content %} | ||||||
| {# seven day cache #} | {# seven day cache #} | ||||||
| {% cache 604800 about_page %} | {% cache 604800 about_page %} | ||||||
|  |  | ||||||
| {% get_book_superlatives as superlatives %} | {% get_book_superlatives as superlatives %} | ||||||
| <section class="content pb-4"> | <section class="content pb-4"> | ||||||
|     <h2> |     <h2> | ||||||
| @@ -26,7 +27,7 @@ | |||||||
|     </p> |     </p> | ||||||
|  |  | ||||||
|     <div class="columns"> |     <div class="columns"> | ||||||
|         {% if top_rated %} |         {% if superlatives.top_rated %} | ||||||
|         {% with book=superlatives.top_rated.default_edition rating=top_rated.rating %} |         {% with book=superlatives.top_rated.default_edition rating=top_rated.rating %} | ||||||
|         <div class="column is-one-third is-flex"> |         <div class="column is-one-third is-flex"> | ||||||
|             <div class="media notification"> |             <div class="media notification"> | ||||||
| @@ -45,7 +46,7 @@ | |||||||
|         {% endwith %} |         {% endwith %} | ||||||
|         {% endif %} |         {% endif %} | ||||||
|  |  | ||||||
|         {% if wanted %} |         {% if superlatives.wanted %} | ||||||
|         {% with book=superlatives.wanted.default_edition %} |         {% with book=superlatives.wanted.default_edition %} | ||||||
|         <div class="column is-one-third is-flex"> |         <div class="column is-one-third is-flex"> | ||||||
|             <div class="media notification"> |             <div class="media notification"> | ||||||
| @@ -64,7 +65,7 @@ | |||||||
|         {% endwith %} |         {% endwith %} | ||||||
|         {% endif %} |         {% endif %} | ||||||
|  |  | ||||||
|         {% if controversial %} |         {% if superlatives.controversial %} | ||||||
|         {% with book=superlatives.controversial.default_edition %} |         {% with book=superlatives.controversial.default_edition %} | ||||||
|         <div class="column is-one-third is-flex"> |         <div class="column is-one-third is-flex"> | ||||||
|             <div class="media notification"> |             <div class="media notification"> | ||||||
| @@ -95,7 +96,7 @@ | |||||||
|         <h2 class="title is-3">{% trans "Meet your admins" %}</h2> |         <h2 class="title is-3">{% trans "Meet your admins" %}</h2> | ||||||
|         <p> |         <p> | ||||||
|         {% url "conduct" as coc_path %} |         {% url "conduct" as coc_path %} | ||||||
|         {% blocktrans with site_name=site.name %} |         {% blocktrans trimmed with site_name=site.name %} | ||||||
|             {{ site_name }}'s moderators and administrators keep the site up and running, enforce the <a href="coc_path">code of conduct</a>, and respond when users report spam and bad behavior. |             {{ site_name }}'s moderators and administrators keep the site up and running, enforce the <a href="coc_path">code of conduct</a>, and respond when users report spam and bad behavior. | ||||||
|         {% endblocktrans %} |         {% endblocktrans %} | ||||||
|         </p> |         </p> | ||||||
|   | |||||||
| @@ -23,18 +23,13 @@ | |||||||
|     </div> |     </div> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| <div class="block columns is-flex-direction-row-reverse" itemscope itemtype="https://schema.org/Person"> | <div class="block columns" itemscope itemtype="https://schema.org/Person"> | ||||||
|     <meta itemprop="name" content="{{ author.name }}"> |     <meta itemprop="name" content="{{ author.name }}"> | ||||||
|     {% if author.bio %} |  | ||||||
|     <div class="column"> |  | ||||||
|         {% include "snippets/trimmed_text.html" with full=author.bio trim_length=200 %} |  | ||||||
|     </div> |  | ||||||
|     {% endif %} |  | ||||||
|  |  | ||||||
|     {% firstof author.aliases author.born author.died as details %} |     {% firstof author.aliases author.born author.died as details %} | ||||||
|     {% firstof author.wikipedia_link author.openlibrary_key author.inventaire_id author.isni as links %} |     {% firstof author.wikipedia_link author.openlibrary_key author.inventaire_id author.isni as links %} | ||||||
|     {% if details or links %} |     {% if details or links %} | ||||||
|     <div class="column is-two-fifths"> |     <div class="column is-3"> | ||||||
|         {% if details %} |         {% if details %} | ||||||
|         <section class="block content"> |         <section class="block content"> | ||||||
|             <h2 class="title is-4">{% trans "Author details" %}</h2> |             <h2 class="title is-4">{% trans "Author details" %}</h2> | ||||||
| @@ -71,7 +66,7 @@ | |||||||
|             <div class="box"> |             <div class="box"> | ||||||
|                 {% if author.wikipedia_link %} |                 {% if author.wikipedia_link %} | ||||||
|                 <div> |                 <div> | ||||||
|                     <a itemprop="sameAs" href="{{ author.wikipedia_link }}" rel="noopener" target="_blank"> |                     <a itemprop="sameAs" href="{{ author.wikipedia_link }}" rel="noopener noreferrer" target="_blank"> | ||||||
|                         {% trans "Wikipedia" %} |                         {% trans "Wikipedia" %} | ||||||
|                     </a> |                     </a> | ||||||
|                 </div> |                 </div> | ||||||
| @@ -79,7 +74,7 @@ | |||||||
|  |  | ||||||
|                 {% if author.isni %} |                 {% if author.isni %} | ||||||
|                 <div class="mt-1"> |                 <div class="mt-1"> | ||||||
|                     <a itemprop="sameAs" href="{{ author.isni_link }}" rel="noopener" target="_blank"> |                     <a itemprop="sameAs" href="{{ author.isni_link }}" rel="noopener noreferrer" target="_blank"> | ||||||
|                         {% trans "View ISNI record" %} |                         {% trans "View ISNI record" %} | ||||||
|                     </a> |                     </a> | ||||||
|                 </div> |                 </div> | ||||||
| @@ -88,7 +83,7 @@ | |||||||
|                 {% trans "Load data" as button_text %} |                 {% trans "Load data" as button_text %} | ||||||
|                 {% if author.openlibrary_key %} |                 {% if author.openlibrary_key %} | ||||||
|                 <div class="mt-1 is-flex"> |                 <div class="mt-1 is-flex"> | ||||||
|                     <a class="mr-3" itemprop="sameAs" href="{{ author.openlibrary_link }}" target="_blank" rel="noopener"> |                     <a class="mr-3" itemprop="sameAs" href="{{ author.openlibrary_link }}" target="_blank" rel="noopener noreferrer"> | ||||||
|                         {% trans "View on OpenLibrary" %} |                         {% trans "View on OpenLibrary" %} | ||||||
|                     </a> |                     </a> | ||||||
|                     {% if request.user.is_authenticated and perms.bookwyrm.edit_book %} |                     {% if request.user.is_authenticated and perms.bookwyrm.edit_book %} | ||||||
| @@ -103,7 +98,7 @@ | |||||||
|  |  | ||||||
|                 {% if author.inventaire_id %} |                 {% if author.inventaire_id %} | ||||||
|                 <div class="mt-1 is-flex"> |                 <div class="mt-1 is-flex"> | ||||||
|                     <a class="mr-3" itemprop="sameAs" href="{{ author.inventaire_link }}" target="_blank" rel="noopener"> |                     <a class="mr-3" itemprop="sameAs" href="{{ author.inventaire_link }}" target="_blank" rel="noopener noreferrer"> | ||||||
|                         {% trans "View on Inventaire" %} |                         {% trans "View on Inventaire" %} | ||||||
|                     </a> |                     </a> | ||||||
|  |  | ||||||
| @@ -119,7 +114,7 @@ | |||||||
|  |  | ||||||
|                 {% if author.librarything_key %} |                 {% if author.librarything_key %} | ||||||
|                 <div class="mt-1"> |                 <div class="mt-1"> | ||||||
|                     <a itemprop="sameAs" href="https://www.librarything.com/author/{{ author.librarything_key }}" target="_blank" rel="noopener"> |                     <a itemprop="sameAs" href="https://www.librarything.com/author/{{ author.librarything_key }}" target="_blank" rel="noopener noreferrer"> | ||||||
|                         {% trans "View on LibraryThing" %} |                         {% trans "View on LibraryThing" %} | ||||||
|                     </a> |                     </a> | ||||||
|                 </div> |                 </div> | ||||||
| @@ -127,7 +122,7 @@ | |||||||
|  |  | ||||||
|                 {% if author.goodreads_key %} |                 {% if author.goodreads_key %} | ||||||
|                 <div> |                 <div> | ||||||
|                     <a itemprop="sameAs" href="https://www.goodreads.com/author/show/{{ author.goodreads_key }}" target="_blank" rel="noopener"> |                     <a itemprop="sameAs" href="https://www.goodreads.com/author/show/{{ author.goodreads_key }}" target="_blank" rel="noopener noreferrer"> | ||||||
|                         {% trans "View on Goodreads" %} |                         {% trans "View on Goodreads" %} | ||||||
|                     </a> |                     </a> | ||||||
|                 </div> |                 </div> | ||||||
| @@ -137,26 +132,30 @@ | |||||||
|         {% endif %} |         {% endif %} | ||||||
|     </div> |     </div> | ||||||
|     {% endif %} |     {% endif %} | ||||||
| </div> |  | ||||||
|  |  | ||||||
| <hr aria-hidden="true"> |     <div class="column"> | ||||||
|  |         {% if author.bio %} | ||||||
|  |         {% include "snippets/trimmed_text.html" with full=author.bio trim_length=200 %} | ||||||
|  |         {% endif %} | ||||||
|  |  | ||||||
| <div class="block"> |         <h2 class="title is-4">{% blocktrans with name=author.name %}Books by {{ name }}{% endblocktrans %}</h2> | ||||||
|     <h2 class="title is-4">{% blocktrans with name=author.name %}Books by {{ name }}{% endblocktrans %}</h2> |         <div class="columns is-multiline is-mobile"> | ||||||
|     <div class="columns is-multiline is-mobile"> |         {% for book in books %} | ||||||
|     {% for book in books %} |         {% with book=book.default_edition %} | ||||||
|         <div class="column is-one-fifth-tablet is-half-mobile is-flex is-flex-direction-column"> |             <div class="column is-one-fifth-tablet is-half-mobile is-flex is-flex-direction-column"> | ||||||
|             <div class="is-flex-grow-1"> |                 <div class="is-flex-grow-1"> | ||||||
|                 {% include 'landing/small-book.html' with book=book %} |                     {% include 'landing/small-book.html' with book=book %} | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'snippets/shelve_button/shelve_button.html' with book=book %} | ||||||
|             </div> |             </div> | ||||||
|             {% include 'snippets/shelve_button/shelve_button.html' with book=book %} |         {% endwith %} | ||||||
|  |         {% endfor %} | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <div> | ||||||
|  |             {% include 'snippets/pagination.html' with page=books %} | ||||||
|         </div> |         </div> | ||||||
|     {% endfor %} |  | ||||||
|     </div> |     </div> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| <div> |  | ||||||
|     {% include 'snippets/pagination.html' with page=books %} |  | ||||||
| </div> |  | ||||||
|  |  | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| {% extends 'layout.html' %} | {% extends 'layout.html' %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load bookwyrm_tags %} | {% load book_display_tags %} | ||||||
| {% load humanize %} | {% load humanize %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
| {% load static %} | {% load static %} | ||||||
| @@ -122,7 +122,7 @@ | |||||||
|                 {% trans "Load data" as button_text %} |                 {% trans "Load data" as button_text %} | ||||||
|                 {% if book.openlibrary_key %} |                 {% if book.openlibrary_key %} | ||||||
|                 <p> |                 <p> | ||||||
|                     <a href="{{ book.openlibrary_link }}" target="_blank" rel="noopener"> |                     <a href="{{ book.openlibrary_link }}" target="_blank" rel="noopener noreferrer"> | ||||||
|                         {% trans "View on OpenLibrary" %} |                         {% trans "View on OpenLibrary" %} | ||||||
|                     </a> |                     </a> | ||||||
|                     {% if request.user.is_authenticated and perms.bookwyrm.edit_book %} |                     {% if request.user.is_authenticated and perms.bookwyrm.edit_book %} | ||||||
| @@ -136,7 +136,7 @@ | |||||||
|                 {% endif %} |                 {% endif %} | ||||||
|                 {% if book.inventaire_id %} |                 {% if book.inventaire_id %} | ||||||
|                 <p> |                 <p> | ||||||
|                     <a href="{{ book.inventaire_link }}" target="_blank" rel="noopener"> |                     <a href="{{ book.inventaire_link }}" target="_blank" rel="noopener noreferrer"> | ||||||
|                         {% trans "View on Inventaire" %} |                         {% trans "View on Inventaire" %} | ||||||
|                     </a> |                     </a> | ||||||
|  |  | ||||||
| @@ -183,7 +183,7 @@ | |||||||
|                 {% include 'snippets/toggle/open_button.html' with text=button_text controls_text="add_description" controls_uid=book.id focus="id_description" hide_active=True id="hide_description" %} |                 {% include 'snippets/toggle/open_button.html' with text=button_text controls_text="add_description" controls_uid=book.id focus="id_description" hide_active=True id="hide_description" %} | ||||||
|  |  | ||||||
|                 <div class="box is-hidden" id="add_description_{{ book.id }}"> |                 <div class="box is-hidden" id="add_description_{{ book.id }}"> | ||||||
|                     <form name="add-description" method="POST" action="/add-description/{{ book.id }}"> |                     <form name="add-description" method="POST" action="{% url "add-description" book.id %}"> | ||||||
|                         {% csrf_token %} |                         {% csrf_token %} | ||||||
|                         <p class="fields is-grouped"> |                         <p class="fields is-grouped"> | ||||||
|                             <label class="label" for="id_description_{{ book.id }}">{% trans "Description:" %}</label> |                             <label class="label" for="id_description_{{ book.id }}">{% trans "Description:" %}</label> | ||||||
| @@ -237,29 +237,21 @@ | |||||||
|                     <h2 class="title is-5">{% trans "Your reading activity" %}</h2> |                     <h2 class="title is-5">{% trans "Your reading activity" %}</h2> | ||||||
|                     </div> |                     </div> | ||||||
|                     <div class="column is-narrow"> |                     <div class="column is-narrow"> | ||||||
|                         {% trans "Add read dates" as button_text %} |                         <button class="button is-small" data-modal-open="add-readthrough"> | ||||||
|                         {% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="plus" class="is-small" controls_text="add_readthrough" focus="add_readthrough_focus_" %} |                             <span class="icon icon-plus m-mobile-0" aria-hidden="true"></span> | ||||||
|  |                             <span class="is-sr-only-mobile"> | ||||||
|  |                                 {% trans "Add read dates" %} | ||||||
|  |                             </span> | ||||||
|  |                         </button> | ||||||
|                     </div> |                     </div> | ||||||
|                 </header> |                 </header> | ||||||
|                 <section class="is-hidden box" id="add_readthrough"> |                 {% include "readthrough/readthrough_modal.html" with id="add-readthrough" %} | ||||||
|                     <form name="add-readthrough" action="/create-readthrough" method="post"> |  | ||||||
|                         {% include 'snippets/readthrough_form.html' with readthrough=None %} |  | ||||||
|                         <div class="field is-grouped"> |  | ||||||
|                             <div class="control"> |  | ||||||
|                                 <button class="button is-primary" type="submit">{% trans "Create" %}</button> |  | ||||||
|                             </div> |  | ||||||
|                             <div class="control"> |  | ||||||
|                                 {% trans "Cancel" as button_text %} |  | ||||||
|                                 {% include 'snippets/toggle/close_button.html' with text=button_text controls_text="add_readthrough" %} |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                     </form> |  | ||||||
|                 </section> |  | ||||||
|                 {% if not readthroughs.exists %} |                 {% if not readthroughs.exists %} | ||||||
|                 <p>{% trans "You don't have any reading activity for this book." %}</p> |                 <p>{% trans "You don't have any reading activity for this book." %}</p> | ||||||
|                 {% endif %} |                 {% endif %} | ||||||
|                 {% for readthrough in readthroughs %} |                 {% for readthrough in readthroughs %} | ||||||
|                 {% include 'book/readthrough.html' with readthrough=readthrough %} |                 {% include 'readthrough/readthrough_list.html' with readthrough=readthrough %} | ||||||
|                 {% endfor %} |                 {% endfor %} | ||||||
|             </section> |             </section> | ||||||
|             <hr aria-hidden="true"> |             <hr aria-hidden="true"> | ||||||
| @@ -327,7 +319,7 @@ | |||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|         <div class="column is-one-fifth"> |         <div class="column is-one-fifth is-clipped"> | ||||||
|             {% if book.subjects %} |             {% if book.subjects %} | ||||||
|                 <section class="content block"> |                 <section class="content block"> | ||||||
|                     <h2 class="title is-5">{% trans "Subjects" %}</h2> |                     <h2 class="title is-5">{% trans "Subjects" %}</h2> | ||||||
| @@ -352,11 +344,11 @@ | |||||||
|             {% endif %} |             {% endif %} | ||||||
|  |  | ||||||
|             {% if lists.exists or request.user.list_set.exists %} |             {% if lists.exists or request.user.list_set.exists %} | ||||||
|             <section class="content block"> |             <section class="content block is-clipped"> | ||||||
|                 <h2 class="title is-5">{% trans "Lists" %}</h2> |                 <h2 class="title is-5">{% trans "Lists" %}</h2> | ||||||
|                 <ul> |                 <ul> | ||||||
|                 {% for list in lists %} |                 {% for list in lists %} | ||||||
|                 <li><a href="{{ list.local_path }}">{{ list.name }}</a></li> |                     <li><a href="{{ list.local_path }}">{{ list.name }}</a></li> | ||||||
|                 {% endfor %} |                 {% endfor %} | ||||||
|                 </ul> |                 </ul> | ||||||
|  |  | ||||||
| @@ -366,7 +358,7 @@ | |||||||
|                     <input type="hidden" name="book" value="{{ book.id }}"> |                     <input type="hidden" name="book" value="{{ book.id }}"> | ||||||
|                     <label class="label" for="id_list">{% trans "Add to list" %}</label> |                     <label class="label" for="id_list">{% trans "Add to list" %}</label> | ||||||
|                     <div class="field has-addons"> |                     <div class="field has-addons"> | ||||||
|                         <div class="select control"> |                         <div class="select control is-clipped"> | ||||||
|                             <select name="list" id="id_list"> |                             <select name="list" id="id_list"> | ||||||
|                                 {% for list in user.list_set.all %} |                                 {% for list in user.list_set.all %} | ||||||
|                                 <option value="{{ list.id }}">{{ list.name }}</option> |                                 <option value="{{ list.id }}">{{ list.name }}</option> | ||||||
| @@ -381,6 +373,10 @@ | |||||||
|                 {% endif %} |                 {% endif %} | ||||||
|             </section> |             </section> | ||||||
|             {% endif %} |             {% endif %} | ||||||
|  |  | ||||||
|  |             <section class="content block"> | ||||||
|  |                 {% include "book/file_links/links.html" %} | ||||||
|  |             </section> | ||||||
|         </div> |         </div> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
| @@ -390,4 +386,5 @@ | |||||||
|  |  | ||||||
| {% block scripts %} | {% block scripts %} | ||||||
| <script src="{% static "js/vendor/tabs.js" %}?v={{ js_cache }}"></script> | <script src="{% static "js/vendor/tabs.js" %}?v={{ js_cache }}"></script> | ||||||
|  | <script src="{% static "js/autocomplete.js" %}?v={{ js_cache }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
							
								
								
									
										64
									
								
								bookwyrm/templates/book/file_links/add_link_modal.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								bookwyrm/templates/book/file_links/add_link_modal.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | {% extends 'components/modal.html' %} | ||||||
|  | {% load i18n %} | ||||||
|  | {% load static %} | ||||||
|  |  | ||||||
|  | {% block modal-title %} | ||||||
|  | {% trans "Add file link" %} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block modal-form-open %} | ||||||
|  | <form name="add-link" method="POST" action="{% url 'file-link-add' book.id %}"> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block modal-body %} | ||||||
|  | {% csrf_token %} | ||||||
|  | <input type="hidden" name="book" value="{{ book.id }}"> | ||||||
|  | <input type="hidden" name="added_by" value="{{ request.user.id }}"> | ||||||
|  |  | ||||||
|  | <p class="notification"> | ||||||
|  |     {% trans "Links from unknown domains will need to be approved by a moderator before they are added." %} | ||||||
|  | </p> | ||||||
|  |  | ||||||
|  | <div class="columns"> | ||||||
|  |     <div class="column is-four-fifths"> | ||||||
|  |         <label class="label" for="id_url">{% trans "URL:" %}</label> | ||||||
|  |         <input type="url" name="url" maxlength="255" class="input" required="" id="id_url" value="{% firstof file_link_form.url.value "" %}" placeholder="https://..." aria-describedby="desc_name"> | ||||||
|  |         {% include 'snippets/form_errors.html' with errors_list=file_link_form.url.errors id="desc_url" %} | ||||||
|  |     </div> | ||||||
|  |     <div class="column is-one-fifth"> | ||||||
|  |         <label class="label" for="id_filetype">{% trans "File type:" %}</label> | ||||||
|  |         <input | ||||||
|  |             type="text" | ||||||
|  |             name="filetype" | ||||||
|  |             maxlength="50" | ||||||
|  |             class="input" | ||||||
|  |             required="" | ||||||
|  |             id="id_filetype" | ||||||
|  |             value="{% firstof file_link_form.filetype.value '' %}" | ||||||
|  |             placeholder="ePub" | ||||||
|  |             list="mimetypes-list" | ||||||
|  |             data-autocomplete="mimetype" | ||||||
|  |         > | ||||||
|  |         <datalist id="mimetypes-list"></datalist> | ||||||
|  |         {% include 'snippets/form_errors.html' with errors_list=file_link_form.filetype.errors id="desc_filetype" %} | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | <div> | ||||||
|  |     <label class="label" for="id_availability"> | ||||||
|  |         {% trans "Availability:" %} | ||||||
|  |     </label> | ||||||
|  |     <div class="select"> | ||||||
|  |         {{ file_link_form.availability }} | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block modal-footer %} | ||||||
|  | <button class="button is-primary" type="submit">{% trans "Save" %}</button> | ||||||
|  | {% if not static %} | ||||||
|  |     <button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button> | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
|  | {% endblock %} | ||||||
|  | {% block modal-form-close %}</form>{% endblock %} | ||||||
							
								
								
									
										114
									
								
								bookwyrm/templates/book/file_links/edit_links.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								bookwyrm/templates/book/file_links/edit_links.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | |||||||
|  | {% extends 'layout.html' %} | ||||||
|  | {% load i18n %} | ||||||
|  | {% load utilities %} | ||||||
|  |  | ||||||
|  | {% block title %}{% trans "Edit links" %}{% endblock %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  |  | ||||||
|  | <header class="block content"> | ||||||
|  |     <h1 class="title"> | ||||||
|  |         {% blocktrans with title=book|book_title %} | ||||||
|  |         Links for "<em>{{ title }}</em>" | ||||||
|  |         {% endblocktrans %} | ||||||
|  |     </h1> | ||||||
|  | </header> | ||||||
|  |  | ||||||
|  | <nav class="breadcrumb subtitle" aria-label="breadcrumbs"> | ||||||
|  |     <ul> | ||||||
|  |         <li><a href="{% url 'book' book.id %}">{{ book|book_title }}</a></li> | ||||||
|  |         <li class="is-active"> | ||||||
|  |             <a href="#" aria-current="page"> | ||||||
|  |                 {% trans "Edit links" %} | ||||||
|  |             </a> | ||||||
|  |         </li> | ||||||
|  |     </ul> | ||||||
|  | </nav> | ||||||
|  |  | ||||||
|  | <section class="block content"> | ||||||
|  |     <div class="table-container"> | ||||||
|  |         <table class="is-striped is-fullwidth"> | ||||||
|  |             <tr> | ||||||
|  |                 <th>{% trans "URL" %}</th> | ||||||
|  |                 <th>{% trans "Added by" %}</th> | ||||||
|  |                 <th>{% trans "Filetype" %}</th> | ||||||
|  |                 <th>{% trans "Domain" %}</th> | ||||||
|  |                 <th>{% trans "Status" %}</th> | ||||||
|  |                 <th colspan="2">{% trans "Actions" %}</th> | ||||||
|  |             </tr> | ||||||
|  |             {% for link in links %} | ||||||
|  |             <tr> | ||||||
|  |                 <td class="overflow-wrap-anywhere"> | ||||||
|  |                     <a href="{{ link.url }}" target="_blank" rel="noopener noreferrer">{{ link.url }}</a> | ||||||
|  |                 </td> | ||||||
|  |                 <td> | ||||||
|  |                     <a href="{% url 'user-feed' link.added_by.id %}">{{ link.added_by.display_name }}</a> | ||||||
|  |                 </td> | ||||||
|  |                 <td> | ||||||
|  |                     {{ link.filelink.filetype }} | ||||||
|  |                 </td> | ||||||
|  |                 <td> | ||||||
|  |                     {{ link.domain.name }} | ||||||
|  |                     <p> | ||||||
|  |                         <a href="{% url 'report-link' link.added_by.id link.id %}">{% trans "Report spam" %}</a> | ||||||
|  |                     </p> | ||||||
|  |                 </td> | ||||||
|  |                 <td> | ||||||
|  |                     {% with status=link.domain.status %} | ||||||
|  |                     <span class="tag {% if status == 'blocked' %}has-background-danger{% elif status == 'approved' %}has-background-primary{% endif %}"> | ||||||
|  |                         <span class="icon" aria-hidden="true"> | ||||||
|  |                             <span class="icon-{% if status == 'blocked' %}x{% elif status == 'approved' %}check{% else %}lock{% endif %}"></span> | ||||||
|  |                         </span> | ||||||
|  |                         <span> | ||||||
|  |                             {{ link.domain.get_status_display }} | ||||||
|  |                         </span> | ||||||
|  |                     </span> | ||||||
|  |                     {% endwith %} | ||||||
|  |                 </td> | ||||||
|  |                 <td> | ||||||
|  |                     <form name="edit-link" class="control" method="post" action="{% url 'file-link' book.id link.id %}"> | ||||||
|  |                         {% csrf_token %} | ||||||
|  |                         <input type="hidden" name="url" value="{{ link.form.url.value }}"> | ||||||
|  |                         <input type="hidden" name="filetype" value="{{ link.form.filetype.value }}"> | ||||||
|  |                         <input type="hidden" name="added_by" value="{{ link.form.added_by.value }}"> | ||||||
|  |                         <input type="hidden" name="book" value="{{ link.form.book.value }}"> | ||||||
|  |                         <div class="field has-addons"> | ||||||
|  |                             <div class="control"> | ||||||
|  |                                 <div class="select"> | ||||||
|  |                                     {{ link.form.availability }} | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|  |                             <div class="control"> | ||||||
|  |                                 <button class="button is-primary" type="submit">{% trans "Save" %}</button> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                     </form> | ||||||
|  |                 </td> | ||||||
|  |                 <td> | ||||||
|  |                     <form name="delete-link-{{ link.id }}" class="control" method="post" action="{% url 'file-link-delete' book.id link.id %}"> | ||||||
|  |                         {% csrf_token %} | ||||||
|  |                         <button class="button is-danger is-light" type="submit">Delete link</button> | ||||||
|  |                     </form> | ||||||
|  |                 </td> | ||||||
|  |             </tr> | ||||||
|  |             {% endfor %} | ||||||
|  |             {% if not book.file_links.exists %} | ||||||
|  |             <tr> | ||||||
|  |                 <td colspan="5"><em>{% trans "No links available for this book." %}</em></td> | ||||||
|  |             </tr> | ||||||
|  |             {% endif %} | ||||||
|  |         </table> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     {% url 'file-link-add' book.id as fallback_url %} | ||||||
|  |     <form name="add-link" method="get" action="{{ fallback_url }}"> | ||||||
|  |         <button class="button" type="submit" data-modal-open="add-links"> | ||||||
|  |             <span class="icon icon-plus m-0-mobile" aria-hidden="true"></span> | ||||||
|  |             <span class="is-sr-only-mobile"> | ||||||
|  |                 {% trans "Add link to file" %} | ||||||
|  |             </span> | ||||||
|  |         </button> | ||||||
|  |     </form> | ||||||
|  | </section> | ||||||
|  |  | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										15
									
								
								bookwyrm/templates/book/file_links/file_link_page.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								bookwyrm/templates/book/file_links/file_link_page.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | {% extends 'layout.html' %} | ||||||
|  | {% load i18n %} | ||||||
|  | {% load static %} | ||||||
|  |  | ||||||
|  | {% block title %} | ||||||
|  | {% trans "File Links" %} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | {% include "book/file_links/add_link_modal.html" with book=book active=True static=True id="file-link" %} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block scripts %} | ||||||
|  | <script src="{% static "js/autocomplete.js" %}?v={{ js_cache }}"></script> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										58
									
								
								bookwyrm/templates/book/file_links/links.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								bookwyrm/templates/book/file_links/links.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | {% load i18n %} | ||||||
|  | {% load book_display_tags %} | ||||||
|  | {% load utilities %} | ||||||
|  |  | ||||||
|  | {% get_book_file_links book as links %} | ||||||
|  | {% if links.exists or request.user.is_authenticated %} | ||||||
|  | <header class="columns is-mobile mb-0"> | ||||||
|  |     <div class="column"> | ||||||
|  |         <h2 class="title is-5">{% trans "Get a copy" %}</h2> | ||||||
|  |     </div> | ||||||
|  |     {% if can_edit_book %} | ||||||
|  |     <div class="column is-narrow"> | ||||||
|  |         {% url 'file-link-add' book.id as fallback_url %} | ||||||
|  |         <form name="add-link" method="get" action="{{ fallback_url }}"> | ||||||
|  |             <button class="button is-small" type="submit" data-modal-open="add-links"> | ||||||
|  |                 <span class="icon icon-plus"> | ||||||
|  |                     <span class="is-sr-only"> | ||||||
|  |                         {% trans "Add link to file" %} | ||||||
|  |                     </span> | ||||||
|  |                 </span> | ||||||
|  |             </button> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  |     {% endif %} | ||||||
|  | </header> | ||||||
|  | {% if links %} | ||||||
|  | <ul class="mt-0"> | ||||||
|  |     {% for link in links.all %} | ||||||
|  |     {% join "verify" link.id as verify_modal %} | ||||||
|  |     <li> | ||||||
|  |         <a href="{{ link.url }}" rel="noopener noreferrer" target="_blank" title="{{ link.url }}" data-modal-open="{{ verify_modal }}">{{ link.name }}</a> | ||||||
|  |         ({{ link.filetype }}) | ||||||
|  |  | ||||||
|  |         {% if link.availability != "free" %} | ||||||
|  |         <p class="help"> | ||||||
|  |             {{ link.get_availability_display }} | ||||||
|  |         </p> | ||||||
|  |         {% endif %} | ||||||
|  |     </li> | ||||||
|  |     {% endfor %} | ||||||
|  | </ul> | ||||||
|  | {% for link in links.all %} | ||||||
|  | {% join "verify" link.id as verify_modal %} | ||||||
|  | {% include "book/file_links/verification_modal.html" with id=verify_modal %} | ||||||
|  | {% endfor %} | ||||||
|  | {% else %} | ||||||
|  | <em>{% trans "No links available" %}</em> | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
|  | {% if can_edit_book and links.exists %} | ||||||
|  | <a href="{% url 'file-link' book.id %}" class="is-pulled-right"> | ||||||
|  |     <span class="icon icon-pencil" aria-hidden="true"></span> | ||||||
|  |     <span>{% trans "Edit links" %}</span> | ||||||
|  | </a> | ||||||
|  | {% endif %} | ||||||
|  | {% include 'book/file_links/add_link_modal.html' with book=book id="add-links" %} | ||||||
|  |  | ||||||
|  | {% endif %} | ||||||
							
								
								
									
										29
									
								
								bookwyrm/templates/book/file_links/verification_modal.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								bookwyrm/templates/book/file_links/verification_modal.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | {% extends 'components/modal.html' %} | ||||||
|  | {% load i18n %} | ||||||
|  |  | ||||||
|  | {% block modal-title %} | ||||||
|  | {% trans "Leaving BookWyrm" %} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | {% block modal-body %} | ||||||
|  |  | ||||||
|  | {% blocktrans trimmed with link_url=link.url %} | ||||||
|  | This link is taking you to: <code>{{ link_url }}</code>.<br> | ||||||
|  | Is that where you'd like to go? | ||||||
|  | {% endblocktrans %} | ||||||
|  |  | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | {% block modal-footer %} | ||||||
|  | <a href="{{ link.url }}" target="_blank" rel="noopener noreferrer" class="button is-primary">{% trans "Continue" %}</a> | ||||||
|  | <button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button> | ||||||
|  |  | ||||||
|  | {% if request.user.is_authenticated %} | ||||||
|  | <div class="has-text-right is-flex-grow-1"> | ||||||
|  |     <a href="{% url 'report-link' link.added_by.id link.id %}">{% trans "Report spam" %}</a> | ||||||
|  | </div> | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
|  | {% endblock %} | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% load bookwyrm_tags %} | {% load rating_tags %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
| {% load status_display %} | {% load status_display %} | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% load bookwyrm_tags %} | {% load landing_page_tags %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load status_display %} | {% load status_display %} | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| {% extends 'feed/layout.html' %} | {% extends 'feed/layout.html' %} | ||||||
|  | {% load feed_page_tags %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load bookwyrm_tags %} |  | ||||||
|  |  | ||||||
| {% block opengraph_images %} | {% block opengraph_images %} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load bookwyrm_tags %} | {% load feed_page_tags %} | ||||||
|  |  | ||||||
| {% suggested_books as suggested_books %} | {% suggested_books as suggested_books %} | ||||||
| <section class="block"> | <section class="block"> | ||||||
| @@ -16,10 +16,7 @@ | |||||||
|             {% with shelf_counter=forloop.counter %} |             {% with shelf_counter=forloop.counter %} | ||||||
|                 <li> |                 <li> | ||||||
|                     <p> |                     <p> | ||||||
|                         {% if shelf.identifier == 'to-read' %}{% trans "To Read" %} |                         {% include "snippets/translated_shelf_name.html" with shelf=shelf %} | ||||||
|                         {% elif shelf.identifier == 'reading' %}{% trans "Currently Reading" %} |  | ||||||
|                         {% elif shelf.identifier == 'read' %}{% trans "Read" %} |  | ||||||
|                         {% else %}{{ shelf.name }}{% endif %} |  | ||||||
|                     </p> |                     </p> | ||||||
|                     <div class="tabs is-small is-toggle"> |                     <div class="tabs is-small is-toggle"> | ||||||
|                         <ul> |                         <ul> | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| {% extends 'groups/layout.html' %} | {% extends 'groups/layout.html' %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load bookwyrm_tags %} | {% load group_tags %} | ||||||
| {% load bookwyrm_group_tags %} |  | ||||||
| {% load markdown %} | {% load markdown %} | ||||||
|  |  | ||||||
| {% block panel %} | {% block panel %} | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| {% extends 'layout.html' %} | {% extends 'layout.html' %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load bookwyrm_group_tags %} | {% load group_tags %} | ||||||
|  |  | ||||||
| {% block title %}{{ group.name }}{% endblock %} | {% block title %}{{ group.name }}{% endblock %} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,8 +1,7 @@ | |||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
| {% load humanize %} | {% load humanize %} | ||||||
| {% load bookwyrm_tags %} | {% load group_tags %} | ||||||
| {% load bookwyrm_group_tags %} |  | ||||||
|  |  | ||||||
| <h2 class="title is-5">Group Members</h2> | <h2 class="title is-5">Group Members</h2> | ||||||
| {% if group.user == request.user %} | {% if group.user == request.user %} | ||||||
|   | |||||||
| @@ -3,6 +3,6 @@ | |||||||
|  |  | ||||||
| {% block tooltip_content %} | {% block tooltip_content %} | ||||||
|  |  | ||||||
| {% trans 'You can download your Goodreads data from the <a href="https://www.goodreads.com/review/import" target="_blank" rel="noopener">Import/Export page</a> of your Goodreads account.' %} | {% trans 'You can download your Goodreads data from the <a href="https://www.goodreads.com/review/import" target="_blank" rel="noopener noreferrer">Import/Export page</a> of your Goodreads account.' %} | ||||||
|  |  | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -1,13 +1,17 @@ | |||||||
| {% extends 'landing/layout.html' %} | {% extends 'landing/layout.html' %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load cache %} | {% load cache %} | ||||||
|  | {% load landing_page_tags %} | ||||||
|  |  | ||||||
| {% block panel %} | {% block panel %} | ||||||
|  |  | ||||||
| <div class="block is-hidden-tablet"> | <div class="block is-hidden-tablet"> | ||||||
|     <h2 class="title has-text-centered">{% trans "Recent Books" %}</h2> |     <h2 class="title has-text-centered">{% trans "Recent Books" %}</h2> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| {% cache 60 * 60 %} | {% get_current_language as LANGUAGE_CODE %} | ||||||
|  | {% cache 60 * 60 LANGUAGE_CODE %} | ||||||
|  | {% get_landing_books as books %} | ||||||
| <section class="tile is-ancestor"> | <section class="tile is-ancestor"> | ||||||
|     <div class="tile is-vertical is-6"> |     <div class="tile is-vertical is-6"> | ||||||
|         <div class="tile is-parent"> |         <div class="tile is-parent"> | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| {% load bookwyrm_tags %} | {% load book_display_tags %} | ||||||
|  | {% load rating_tags %} | ||||||
| {% load markdown %} | {% load markdown %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% load bookwyrm_tags %} | {% load rating_tags %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
|  |  | ||||||
| {% if book %} | {% if book %} | ||||||
|   | |||||||
| @@ -1,7 +1,8 @@ | |||||||
| {% extends 'embed-layout.html' %} | {% extends 'embed-layout.html' %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load bookwyrm_tags %} | {% load book_display_tags %} | ||||||
| {% load bookwyrm_group_tags %} | {% load rating_tags %} | ||||||
|  | {% load group_tags %} | ||||||
| {% load markdown %} | {% load markdown %} | ||||||
|  |  | ||||||
| {% block title %}{% blocktrans with list_name=list.name owner=list.user.display_name %}{{ list_name }}, a list by {{owner}}{% endblocktrans %}{% endblock title %} | {% block title %}{% blocktrans with list_name=list.name owner=list.user.display_name %}{{ list_name }}, a list by {{owner}}{% endblocktrans %}{% endblock title %} | ||||||
|   | |||||||
| @@ -1,7 +1,8 @@ | |||||||
| {% extends 'lists/layout.html' %} | {% extends 'lists/layout.html' %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load bookwyrm_tags %} | {% load rating_tags %} | ||||||
| {% load bookwyrm_group_tags %} | {% load book_display_tags %} | ||||||
|  | {% load group_tags %} | ||||||
| {% load markdown %} | {% load markdown %} | ||||||
|  |  | ||||||
| {% block breadcrumbs %} | {% block breadcrumbs %} | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% extends 'notifications/items/item_layout.html' %} | {% extends 'notifications/items/layout.html' %} | ||||||
|  |  | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% extends 'notifications/items/item_layout.html' %} | {% extends 'notifications/items/layout.html' %} | ||||||
|  |  | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% extends 'notifications/items/item_layout.html' %} | {% extends 'notifications/items/layout.html' %} | ||||||
|  |  | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% extends 'notifications/items/item_layout.html' %} | {% extends 'notifications/items/layout.html' %} | ||||||
|  |  | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% extends 'notifications/items/item_layout.html' %} | {% extends 'notifications/items/layout.html' %} | ||||||
|  |  | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% extends 'notifications/items/item_layout.html' %} | {% extends 'notifications/items/layout.html' %} | ||||||
|  |  | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% extends 'notifications/items/item_layout.html' %} | {% extends 'notifications/items/layout.html' %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
|  |  | ||||||
| {% block primary_link %}{% spaceless %} | {% block primary_link %}{% spaceless %} | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% extends 'notifications/items/item_layout.html' %} | {% extends 'notifications/items/layout.html' %} | ||||||
|  |  | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% extends 'notifications/items/item_layout.html' %} | {% extends 'notifications/items/layout.html' %} | ||||||
|  |  | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| {% load bookwyrm_tags %} | {% load notification_page_tags %} | ||||||
| {% related_status notification as related_status %} | {% related_status notification as related_status %} | ||||||
| <div class="box is-shadowless has-background-white-ter {% if notification.id in unread %} is-primary{% endif %}"> | <div class="notification {% if notification.id in unread %}has-background-primary{% endif %}"> | ||||||
|     <div class="columns is-mobile"> |     <div class="columns is-mobile {% if notification.id in unread %}has-text-white{% else %}has-text-grey{% endif %}"> | ||||||
|         <div class="column is-narrow is-size-3 {% if notification.id in unread%}has-text-white{% else %}has-text-grey{% endif %}"> |         <div class="column is-narrow is-size-3"> | ||||||
|             <a class="has-text-dark" href="{% block primary_link %}{% endblock %}"> |             <a class="icon" href="{% block primary_link %}{% endblock %}"> | ||||||
|                 {% block icon %}{% endblock %} |                 {% block icon %}{% endblock %} | ||||||
|             </a> |             </a> | ||||||
|         </div> |         </div> | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% extends 'notifications/items/item_layout.html' %} | {% extends 'notifications/items/layout.html' %} | ||||||
|  |  | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% extends 'notifications/items/item_layout.html' %} | {% extends 'notifications/items/layout.html' %} | ||||||
|  |  | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% extends 'notifications/items/item_layout.html' %} | {% extends 'notifications/items/layout.html' %} | ||||||
|  |  | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% extends 'notifications/items/item_layout.html' %} | {% extends 'notifications/items/layout.html' %} | ||||||
|  |  | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% extends 'notifications/items/item_layout.html' %} | {% extends 'notifications/items/layout.html' %} | ||||||
|  |  | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {% extends 'notifications/items/item_layout.html' %} | {% extends 'notifications/items/layout.html' %} | ||||||
|  |  | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
|     xmlns="http://a9.com/-/spec/opensearch/1.1/" |     xmlns="http://a9.com/-/spec/opensearch/1.1/" | ||||||
|     xmlns:moz="http://www.mozilla.org/2006/browser/search/" |     xmlns:moz="http://www.mozilla.org/2006/browser/search/" | ||||||
| > | > | ||||||
|     <ShortName>BW</ShortName> |     <ShortName>{{ site_name }}</ShortName> | ||||||
|     <Description>{% blocktrans trimmed with site_name=site.name %} |     <Description>{% blocktrans trimmed with site_name=site.name %} | ||||||
|         {{ site_name }} search |         {{ site_name }} search | ||||||
|     {% endblocktrans %}</Description> |     {% endblocktrans %}</Description> | ||||||
|   | |||||||
| @@ -34,26 +34,26 @@ | |||||||
|                 <div class="column"> |                 <div class="column"> | ||||||
|                     {{ form.avatar }} |                     {{ form.avatar }} | ||||||
|  |  | ||||||
|                     {% include 'snippets/form_errors.html' with errors_list=form.avatar.errors id="desc_avatar" %}     |                     {% include 'snippets/form_errors.html' with errors_list=form.avatar.errors id="desc_avatar" %} | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|             <div class="field"> |             <div class="field"> | ||||||
|                 <label class="label" for="id_name">{% trans "Display name:" %}</label> |                 <label class="label" for="id_name">{% trans "Display name:" %}</label> | ||||||
|                 {{ form.name }} |                 {{ form.name }} | ||||||
|  |  | ||||||
|                 {% include 'snippets/form_errors.html' with errors_list=form.name.errors id="desc_name" %}     |                 {% include 'snippets/form_errors.html' with errors_list=form.name.errors id="desc_name" %} | ||||||
|             </div> |             </div> | ||||||
|             <div class="field"> |             <div class="field"> | ||||||
|                 <label class="label" for="id_summary">{% trans "Summary:" %}</label> |                 <label class="label" for="id_summary">{% trans "Summary:" %}</label> | ||||||
|                 {{ form.summary }} |                 {{ form.summary }} | ||||||
|  |  | ||||||
|                 {% include 'snippets/form_errors.html' with errors_list=form.summary.errors id="desc_summary" %}     |                 {% include 'snippets/form_errors.html' with errors_list=form.summary.errors id="desc_summary" %} | ||||||
|             </div> |             </div> | ||||||
|             <div class="field"> |             <div class="field"> | ||||||
|                 <label class="label" for="id_email">{% trans "Email address:" %}</label> |                 <label class="label" for="id_email">{% trans "Email address:" %}</label> | ||||||
|                 {{ form.email }} |                 {{ form.email }} | ||||||
|  |  | ||||||
|                 {% include 'snippets/form_errors.html' with errors_list=form.email.errors id="desc_email" %}     |                 {% include 'snippets/form_errors.html' with errors_list=form.email.errors id="desc_email" %} | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|     </section> |     </section> | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								bookwyrm/templates/readthrough/readthrough.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								bookwyrm/templates/readthrough/readthrough.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | {% extends 'layout.html' %} | ||||||
|  | {% load i18n %} | ||||||
|  | {% load utilities %} | ||||||
|  |  | ||||||
|  | {% block title %} | ||||||
|  | {% blocktrans trimmed with title=book|book_title %} | ||||||
|  | Update read dates for "<em>{{ title }}</em>" | ||||||
|  | {% endblocktrans %} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  |  | ||||||
|  | {% include "readthrough/readthrough_modal.html" with book=book active=True static=True %} | ||||||
|  |  | ||||||
|  | {% endblock %} | ||||||
| @@ -4,6 +4,7 @@ | |||||||
| {% csrf_token %} | {% csrf_token %} | ||||||
| <input type="hidden" name="id" value="{{ readthrough.id }}"> | <input type="hidden" name="id" value="{{ readthrough.id }}"> | ||||||
| <input type="hidden" name="book" value="{{ book.id }}"> | <input type="hidden" name="book" value="{{ book.id }}"> | ||||||
|  | <input type="hidden" name="user" value="{{ request.user.id }}"> | ||||||
| <div class="field"> | <div class="field"> | ||||||
|     <label class="label" tabindex="0" id="add_readthrough_focus_{{ readthrough.id }}" for="id_start_date_{{ readthrough.id }}"> |     <label class="label" tabindex="0" id="add_readthrough_focus_{{ readthrough.id }}" for="id_start_date_{{ readthrough.id }}"> | ||||||
|         {% trans "Started reading" %} |         {% trans "Started reading" %} | ||||||
| @@ -3,7 +3,7 @@ | |||||||
| {% load tz %} | {% load tz %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
| <div class="content"> | <div class="content"> | ||||||
|     <div id="hide_edit_readthrough_{{ readthrough.id }}" class="box is-shadowless has-background-white-bis"> |     <div class="box is-shadowless has-background-white-bis"> | ||||||
|         <div class="columns"> |         <div class="columns"> | ||||||
|             <div class="column"> |             <div class="column"> | ||||||
|                 {% trans "Progress Updates:" %} |                 {% trans "Progress Updates:" %} | ||||||
| @@ -58,7 +58,11 @@ | |||||||
|                 <div class="field has-addons"> |                 <div class="field has-addons"> | ||||||
|                     <div class="control"> |                     <div class="control"> | ||||||
|                         {% trans "Edit read dates" as button_text %} |                         {% trans "Edit read dates" as button_text %} | ||||||
|                         {% include 'snippets/toggle/toggle_button.html' with class="is-small" text=button_text icon="pencil" controls_text="edit_readthrough" controls_uid=readthrough.id focus="edit_readthrough" %} |                         <button class="button is-small" type="button" data-modal-open="edit_readthrough_{{ readthrough.id }}"> | ||||||
|  |                             <span class="icon icon-pencil" title="{{ button_text }}"> | ||||||
|  |                                 <span class="is-sr-only">{{ button_text }}</span> | ||||||
|  |                             </span> | ||||||
|  |                         </button> | ||||||
|                     </div> |                     </div> | ||||||
|                     <div class="control"> |                     <div class="control"> | ||||||
|                         {% trans "Delete these read dates" as button_text %} |                         {% trans "Delete these read dates" as button_text %} | ||||||
| @@ -74,16 +78,7 @@ | |||||||
|     </div> |     </div> | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| <div class="box is-hidden" id="edit_readthrough_{{ readthrough.id }}" tabindex="0"> | {% join "edit_readthrough" readthrough.id as edit_modal_id %} | ||||||
|     <h3 class="title is-5">{% trans "Edit read dates" %}</h3> | {% include "readthrough/readthrough_modal.html" with readthrough=readthrough id=edit_modal_id %} | ||||||
|     <form name="edit-readthrough" action="/edit-readthrough" method="post"> | {% join "delete_readthrough" readthrough.id as delete_modal_id %} | ||||||
|         {% include 'snippets/readthrough_form.html' with readthrough=readthrough %} | {% include 'readthrough/delete_readthrough_modal.html' with id=delete_modal_id %} | ||||||
|         <div class="field is-grouped"> |  | ||||||
|             <button class="button is-primary" type="submit">{% trans "Save" %}</button> |  | ||||||
|             {% trans "Cancel" as button_text %} |  | ||||||
|             {% include 'snippets/toggle/close_button.html' with text=button_text controls_text="edit_readthrough" controls_uid=readthrough.id %} |  | ||||||
|         </div> |  | ||||||
|     </form> |  | ||||||
| </div> |  | ||||||
| {% join "delete_readthrough" readthrough.id as modal_id %} |  | ||||||
| {% include 'book/delete_readthrough_modal.html' with id=modal_id %} |  | ||||||
							
								
								
									
										80
									
								
								bookwyrm/templates/readthrough/readthrough_modal.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								bookwyrm/templates/readthrough/readthrough_modal.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | {% extends "components/modal.html" %} | ||||||
|  | {% load i18n %} | ||||||
|  | {% load utilities %} | ||||||
|  |  | ||||||
|  | {% block modal-title %} | ||||||
|  |  | ||||||
|  | {% if readthrough %} | ||||||
|  | {% blocktrans trimmed with title=book|book_title %} | ||||||
|  |     Update read dates for "<em>{{ title }}</em>" | ||||||
|  | {% endblocktrans %} | ||||||
|  | {% else %} | ||||||
|  | {% blocktrans trimmed with title=book|book_title %} | ||||||
|  |     Add read dates for "<em>{{ title }}</em>" | ||||||
|  | {% endblocktrans %} | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block modal-form-open %} | ||||||
|  | <form name="add-readthrough-{{ readthrough.id }}" action="/create-readthrough" method="post"> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block modal-body %} | ||||||
|  | {% csrf_token %} | ||||||
|  |  | ||||||
|  | <input type="hidden" name="id" value="{{ readthrough.id }}"> | ||||||
|  | <input type="hidden" name="book" value="{{ book.id }}"> | ||||||
|  | <input type="hidden" name="user" value="{{ request.user.id }}"> | ||||||
|  | <div class="field"> | ||||||
|  |     <label class="label" tabindex="0" id="add_readthrough_focus_{{ readthrough.id }}" for="id_start_date_{{ readthrough.id }}"> | ||||||
|  |         {% trans "Started reading" %} | ||||||
|  |     </label> | ||||||
|  |     {% firstof form.start_date.value readthrough.start_date|date:"Y-m-d" as value %} | ||||||
|  |     <input | ||||||
|  |         type="date" | ||||||
|  |         name="start_date" | ||||||
|  |         class="input" | ||||||
|  |         id="id_start_date_{{ readthrough.id }}" | ||||||
|  |         value="{{ value }}" | ||||||
|  |         aria-describedby="desc_start_date" | ||||||
|  |     > | ||||||
|  |     {% include 'snippets/form_errors.html' with errors_list=form.start_date.errors id="desc_start_date" %} | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | {# Only show progress for editing existing readthroughs #} | ||||||
|  | {% if readthrough.id and not readthrough.finish_date %} | ||||||
|  | {% join "id_progress" readthrough.id as field_id %} | ||||||
|  | <label class="label" for="{{ field_id }}"> | ||||||
|  |     {% trans "Progress" %} | ||||||
|  | </label> | ||||||
|  | {% include "snippets/progress_field.html" with id=field_id %} | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
|  | <div class="field"> | ||||||
|  |     <label class="label" for="id_finish_date_{{ readthrough.id }}"> | ||||||
|  |         {% trans "Finished reading" %} | ||||||
|  |     </label> | ||||||
|  |     {% firstof form.finish_date.value readthrough.finish_date|date:"Y-m-d" as value %} | ||||||
|  |     <input | ||||||
|  |         type="date" | ||||||
|  |         name="finish_date" | ||||||
|  |         class="input" | ||||||
|  |         id="id_finish_date_{{ readthrough.id }}" | ||||||
|  |         value="{{ value }}" | ||||||
|  |         aria-describedby="desc_finish_date" | ||||||
|  |     > | ||||||
|  |     {% include 'snippets/form_errors.html' with errors_list=form.finish_date.errors id="desc_finish_date" %} | ||||||
|  | </div> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block modal-footer %} | ||||||
|  | <button class="button is-primary" type="submit">{% trans "Save" %}</button> | ||||||
|  | {% if not static %} | ||||||
|  | <button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button> | ||||||
|  | {% endif %} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block modal-form-close %} | ||||||
|  | </form> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										10
									
								
								bookwyrm/templates/report.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								bookwyrm/templates/report.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | {% extends "layout.html" %} | ||||||
|  | {% load i18n %} | ||||||
|  |  | ||||||
|  | {% block title %} | ||||||
|  | {% trans "Report" %} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | {% include "snippets/report_modal.html" with user=user active=True static=True %} | ||||||
|  | {% endblock %} | ||||||
| @@ -63,7 +63,7 @@ | |||||||
|                                         <strong> |                                         <strong> | ||||||
|                                             <a |                                             <a | ||||||
|                                                 href="{{ result.view_link|default:result.key }}" |                                                 href="{{ result.view_link|default:result.key }}" | ||||||
|                                                 rel="noopener" |                                                 rel="noopener noreferrer" | ||||||
|                                                 target="_blank" |                                                 target="_blank" | ||||||
|                                             >{{ result.title }}</a> |                                             >{{ result.title }}</a> | ||||||
|                                         </strong> |                                         </strong> | ||||||
| @@ -75,9 +75,11 @@ | |||||||
|                                     <form class="mt-1" action="/resolve-book" method="post"> |                                     <form class="mt-1" action="/resolve-book" method="post"> | ||||||
|                                         {% csrf_token %} |                                         {% csrf_token %} | ||||||
|                                         <input type="hidden" name="remote_id" value="{{ result.key }}"> |                                         <input type="hidden" name="remote_id" value="{{ result.key }}"> | ||||||
|                                         <button type="submit" class="button is-small is-link"> |                                         <div class="control"> | ||||||
|                                             {% trans "Import book" %} |                                             <button type="submit" class="button is-small is-link"> | ||||||
|                                         </button> |                                                 {% trans "Import book" %} | ||||||
|  |                                             </button> | ||||||
|  |                                         </div> | ||||||
|                                     </form> |                                     </form> | ||||||
|                                 </div> |                                 </div> | ||||||
|                             </div> |                             </div> | ||||||
|   | |||||||
| @@ -48,6 +48,17 @@ | |||||||
|         </a> |         </a> | ||||||
|     </div> |     </div> | ||||||
|     {% endif %} |     {% endif %} | ||||||
|  |     {% if pending_domains %} | ||||||
|  |     <div class="column"> | ||||||
|  |         <a href="{% url 'settings-link-domain' %}" class="notification is-primary is-block"> | ||||||
|  |             {% blocktrans trimmed count counter=pending_domains with display_count=pending_domains|intcomma %} | ||||||
|  |             {{ display_count }} domain needs review | ||||||
|  |             {% plural %} | ||||||
|  |             {{ display_count }} domains need review | ||||||
|  |             {% endblocktrans %} | ||||||
|  |         </a> | ||||||
|  |     </div> | ||||||
|  |     {% endif %} | ||||||
|     {% if not site.allow_registration and site.allow_invite_requests and invite_requests %} |     {% if not site.allow_registration and site.allow_invite_requests and invite_requests %} | ||||||
|     <div class="column"> |     <div class="column"> | ||||||
|         <a href="{% url 'settings-invite-requests' %}" class="notification is-block is-success is-light"> |         <a href="{% url 'settings-invite-requests' %}" class="notification is-block is-success is-light"> | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ | |||||||
|     <div class="field"> |     <div class="field"> | ||||||
|         <label class="label" for="id_file">JSON data:</label> |         <label class="label" for="id_file">JSON data:</label> | ||||||
|         <aside class="help"> |         <aside class="help"> | ||||||
|             Expects a json file in the format provided by <a href="https://fediblock.org/" target="_blank" rel="noopener">FediBlock</a>, with a list of entries that have <code>instance</code> and <code>url</code> fields. For example: |             Expects a json file in the format provided by <a href="https://fediblock.org/" target="_blank" rel="noopener noreferrer">FediBlock</a>, with a list of entries that have <code>instance</code> and <code>url</code> fields. For example: | ||||||
|             <pre> |             <pre> | ||||||
| [ | [ | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -62,6 +62,10 @@ | |||||||
|                 {% url 'settings-ip-blocks' as url %} |                 {% url 'settings-ip-blocks' as url %} | ||||||
|                 <a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "IP Address Blocklist" %}</a> |                 <a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "IP Address Blocklist" %}</a> | ||||||
|             </li> |             </li> | ||||||
|  |             <li> | ||||||
|  |                 {% url 'settings-link-domain' as url %} | ||||||
|  |                 <a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Link Domains" %}</a> | ||||||
|  |             </li> | ||||||
|         </ul> |         </ul> | ||||||
|         {% endif %} |         {% endif %} | ||||||
|         {% if perms.bookwyrm.edit_instance_settings %} |         {% if perms.bookwyrm.edit_instance_settings %} | ||||||
|   | |||||||
| @@ -0,0 +1,25 @@ | |||||||
|  | {% extends 'components/modal.html' %} | ||||||
|  | {% load i18n %} | ||||||
|  |  | ||||||
|  | {% block modal-title %} | ||||||
|  | {% blocktrans with url=domain.domain %}Set display name for {{ url }}{% endblocktrans %} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block modal-form-open %} | ||||||
|  | <form name="edit-domain-{{ domain.id }}" method="post" action="{% url 'settings-link-domain' status=status domain_id=domain.id %}"> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block modal-body %} | ||||||
|  | {% csrf_token %} | ||||||
|  | <label class="label" for="id_name">{% trans "Name:" %}</label> | ||||||
|  | <div class="control"> | ||||||
|  |     <input type="text" id="id_name" class="input" name="name" value="{{ domain.name }}" required> | ||||||
|  | </div> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block modal-footer %} | ||||||
|  | <button type="submit" class="button is-primary">{% trans "Set" %}</button> | ||||||
|  | <button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block modal-form-close %}</form>{% endblock %} | ||||||
							
								
								
									
										106
									
								
								bookwyrm/templates/settings/link_domains/link_domains.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								bookwyrm/templates/settings/link_domains/link_domains.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | |||||||
|  | {% extends 'settings/layout.html' %} | ||||||
|  | {% load i18n %} | ||||||
|  | {% load utilities %} | ||||||
|  |  | ||||||
|  | {% block title %}{% trans "Link Domains" %}{% endblock %} | ||||||
|  |  | ||||||
|  | {% block header %}{% trans "Link Domains" %}{% endblock %} | ||||||
|  |  | ||||||
|  | {% block panel %} | ||||||
|  | <p class="notification block"> | ||||||
|  |     {% trans "Link domains must be approved before they are shown on book pages. Please make sure that the domains are not hosting spam, malicious code, or deceptive links before approving." %} | ||||||
|  | </p> | ||||||
|  |  | ||||||
|  | <div class="block"> | ||||||
|  |     <div class="tabs"> | ||||||
|  |         <ul> | ||||||
|  |             {% url 'settings-link-domain' status='pending' as url %} | ||||||
|  |             <li {% if request.path in url %}class="is-active" aria-current="page"{% endif %}> | ||||||
|  |                 <a href="{{ url }}">{% trans "Pending" %} ({{ counts.pending }})</a> | ||||||
|  |             </li> | ||||||
|  |             {% url 'settings-link-domain' status='approved' as url %} | ||||||
|  |             <li {% if url in request.path %}class="is-active" aria-current="page"{% endif %}> | ||||||
|  |                 <a href="{{ url }}">{% trans "Approved" %} ({{ counts.approved }})</a> | ||||||
|  |             </li> | ||||||
|  |             {% url 'settings-link-domain' status='blocked' as url %} | ||||||
|  |             <li {% if url in request.path %}class="is-active" aria-current="page"{% endif %}> | ||||||
|  |                 <a href="{{ url }}">{% trans "Blocked" %} ({{ counts.blocked }})</a> | ||||||
|  |             </li> | ||||||
|  |         </ul> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     {% for domain in domains %} | ||||||
|  |     {% join "domain" domain.id as domain_modal %} | ||||||
|  |     <div class="box content" id="{{ domain.id }}"> | ||||||
|  |         <div class="columns is-mobile"> | ||||||
|  |             <header class="column"> | ||||||
|  |                 <h2 class="title is-5"> | ||||||
|  |                     {{ domain.name }} | ||||||
|  |                     (<a href="http://{{ domain.domain }}" target="_blank" rel="noopener noreferrer">{{ domain.domain }}</a>) | ||||||
|  |                 </h2> | ||||||
|  |             </header> | ||||||
|  |             <div class="column is-narrow"> | ||||||
|  |                 <button type="button" class="button" data-modal-open="{{ domain_modal }}"> | ||||||
|  |                     <span class="icon icon-pencil m-0-mobile" aria-hidden="treu"></span> | ||||||
|  |                     <span class="is-sr-only-mobile">{% trans "Set display name" %}</span> | ||||||
|  |                 </button> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="block"> | ||||||
|  |             <details class="details-panel"> | ||||||
|  |                 <summary> | ||||||
|  |                     <span role="heading" aria-level="3" class="title is-6 mb-0"> | ||||||
|  |                         {% trans "View links" %} | ||||||
|  |                         ({{ domain.links.count }}) | ||||||
|  |                     </span> | ||||||
|  |                     <span class="details-close icon icon-x" aria-hidden></span> | ||||||
|  |                 </summary> | ||||||
|  |  | ||||||
|  |                 <div class="table-container mt-4"> | ||||||
|  |                     {% include "settings/link_domains/link_table.html" with links=domain.links.all|slice:10 %} | ||||||
|  |                 </div> | ||||||
|  |             </details> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         {% include "settings/link_domains/edit_domain_modal.html" with domain=domain id=domain_modal %} | ||||||
|  |  | ||||||
|  |         <div class="field has-addons"> | ||||||
|  |             {% if status != "approved" %} | ||||||
|  |             <form | ||||||
|  |                 name="domain-{{ domains.id }}-approve" | ||||||
|  |                 class="control" | ||||||
|  |                 method="post" | ||||||
|  |                 action="{% url 'settings-link-domain-status' domain.id 'approved' %}" | ||||||
|  |             > | ||||||
|  |                 {% csrf_token %} | ||||||
|  |                 <button type="submit" class="button is-success is-light">{% trans "Approve" %}</button> | ||||||
|  |             </form> | ||||||
|  |             {% endif %} | ||||||
|  |             {% if status != "blocked" %} | ||||||
|  |             <form | ||||||
|  |                 name="domain-{{ domains.id }}-block" | ||||||
|  |                 class="control" | ||||||
|  |                 method="post" | ||||||
|  |                 action="{% url 'settings-link-domain-status' domain.id 'blocked' %}" | ||||||
|  |             > | ||||||
|  |                 {% csrf_token %} | ||||||
|  |                 <button type="submit" class="button is-danger is-light">{% trans "Block" %}</button> | ||||||
|  |             </form> | ||||||
|  |             {% endif %} | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |     {% endfor %} | ||||||
|  |  | ||||||
|  |     {% if not domains.exists %} | ||||||
|  |         {% if status == "approved" %} | ||||||
|  |         <em>{% trans "No domains currently approved" %}</em> | ||||||
|  |         {% elif status == "pending" %} | ||||||
|  |         <em>{% trans "No domains currently pending" %}</em> | ||||||
|  |         {% else %} | ||||||
|  |         <em>{% trans "No domains currently blocked" %}</em> | ||||||
|  |         {% endif %} | ||||||
|  |     {% endif %} | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
							
								
								
									
										42
									
								
								bookwyrm/templates/settings/link_domains/link_table.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								bookwyrm/templates/settings/link_domains/link_table.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | {% load i18n %} | ||||||
|  | {% load utilities %} | ||||||
|  |  | ||||||
|  | <table class="is-striped is-fullwidth"> | ||||||
|  |     <tr> | ||||||
|  |         <th>{% trans "URL" %}</th> | ||||||
|  |         <th>{% trans "Added by" %}</th> | ||||||
|  |         <th>{% trans "Filetype" %}</th> | ||||||
|  |         <th>{% trans "Book" %}</th> | ||||||
|  |         {% block additional_headers %}{% endblock %} | ||||||
|  |     </tr> | ||||||
|  |     {% for link in links %} | ||||||
|  |     <tr> | ||||||
|  |         <td class="overflow-wrap-anywhere"> | ||||||
|  |             <a href="{{ link.url }}" target="_blank" rel="noopener noreferrer">{{ link.url }}</a> | ||||||
|  |         </td> | ||||||
|  |         <td> | ||||||
|  |             <a href="{% url 'settings-user' link.added_by.id %}">@{{ link.added_by|username }}</a> | ||||||
|  |         </td> | ||||||
|  |         <td> | ||||||
|  |             {% if link.filelink.filetype %} | ||||||
|  |             {{ link.filelink.filetype }} | ||||||
|  |             {% endif %} | ||||||
|  |         </td> | ||||||
|  |  | ||||||
|  |         <td> | ||||||
|  |         {% if link.filelink.book %} | ||||||
|  |             {% with book=link.filelink.book %} | ||||||
|  |             {% include "snippets/book_titleby.html" with book=book %} | ||||||
|  |             {% endwith %} | ||||||
|  |         {% endif %} | ||||||
|  |         </td> | ||||||
|  |  | ||||||
|  |         {% block additional_data %}{% endblock %} | ||||||
|  |     </tr> | ||||||
|  |     {% endfor %} | ||||||
|  |     {% if not links %} | ||||||
|  |     <tr> | ||||||
|  |         <td colspan="7"><em>{% trans "No links available for this domain." %}</em></td> | ||||||
|  |     </tr> | ||||||
|  |     {% endif %} | ||||||
|  | </table> | ||||||
| @@ -2,10 +2,12 @@ | |||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load humanize %} | {% load humanize %} | ||||||
|  |  | ||||||
| {% block title %}{% blocktrans with report_id=report.id username=report.user.username %}Report #{{ report_id }}: {{ username }}{% endblocktrans %}{% endblock %} | {% block title %} | ||||||
|  | {% include "settings/reports/report_header.html" with report=report %} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
| {% block header %} | {% block header %} | ||||||
| {% blocktrans with report_id=report.id username=report.user.username %}Report #{{ report_id }}: {{ username }}{% endblocktrans %} | {% include "settings/reports/report_header.html" with report=report %} | ||||||
| <a href="{% url 'settings-reports' %}" class="has-text-weight-normal help">{% trans "Back to reports" %}</a> | <a href="{% url 'settings-reports' %}" class="has-text-weight-normal help">{% trans "Back to reports" %}</a> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| @@ -15,6 +17,36 @@ | |||||||
|     {% include 'settings/reports/report_preview.html' with report=report %} |     {% include 'settings/reports/report_preview.html' with report=report %} | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|  | {% if report.statuses.exists %} | ||||||
|  | <div class="block"> | ||||||
|  |     <h3 class="title is-4">{% trans "Reported statuses" %}</h3> | ||||||
|  |     <ul> | ||||||
|  |         {% for status in report.statuses.select_subclasses.all %} | ||||||
|  |         <li> | ||||||
|  |             {% if status.deleted %} | ||||||
|  |             <em>{% trans "Status has been deleted" %}</em> | ||||||
|  |             {% else %} | ||||||
|  |             {% include 'snippets/status/status.html' with status=status moderation_mode=True %} | ||||||
|  |             {% endif %} | ||||||
|  |         </li> | ||||||
|  |         {% endfor %} | ||||||
|  |     </ul> | ||||||
|  | </div> | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
|  | {% if report.links.exists %} | ||||||
|  | <div class="block"> | ||||||
|  |     <h3 class="title is-4">{% trans "Reported links" %}</h3> | ||||||
|  |     <div class="card block"> | ||||||
|  |         <div class="card-content content"> | ||||||
|  |             <div class="table-container"> | ||||||
|  |                 {% include "settings/reports/report_links_table.html" with links=report.links.all %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
| {% include 'settings/users/user_info.html' with user=report.user %} | {% include 'settings/users/user_info.html' with user=report.user %} | ||||||
|  |  | ||||||
| {% include 'settings/users/user_moderation_actions.html' with user=report.user %} | {% include 'settings/users/user_moderation_actions.html' with user=report.user %} | ||||||
| @@ -41,23 +73,4 @@ | |||||||
|         <button class="button">{% trans "Comment" %}</button> |         <button class="button">{% trans "Comment" %}</button> | ||||||
|     </form> |     </form> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| <div class="block"> |  | ||||||
|     <h3 class="title is-4">{% trans "Reported statuses" %}</h3> |  | ||||||
|     {% if not report.statuses.exists %} |  | ||||||
|     <em>{% trans "No statuses reported" %}</em> |  | ||||||
|     {% else %} |  | ||||||
|     <ul> |  | ||||||
|         {% for status in report.statuses.select_subclasses.all %} |  | ||||||
|         <li> |  | ||||||
|             {% if status.deleted %} |  | ||||||
|             <em>{% trans "Status has been deleted" %}</em> |  | ||||||
|             {% else %} |  | ||||||
|             {% include 'snippets/status/status.html' with status=status moderation_mode=True %} |  | ||||||
|             {% endif %} |  | ||||||
|         </li> |  | ||||||
|         {% endfor %} |  | ||||||
|     </ul> |  | ||||||
|     {% endif %} |  | ||||||
| </div> |  | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								bookwyrm/templates/settings/reports/report_header.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								bookwyrm/templates/settings/reports/report_header.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | {% load i18n %} | ||||||
|  | {% load utilities %} | ||||||
|  |  | ||||||
|  | {% if report.statuses.exists %} | ||||||
|  |  | ||||||
|  | {% blocktrans trimmed with report_id=report.id username=report.user|username %} | ||||||
|  | Report #{{ report_id }}: Status posted by @{{ username }} | ||||||
|  | {% endblocktrans %} | ||||||
|  |  | ||||||
|  | {% elif report.links.exists %} | ||||||
|  |  | ||||||
|  | {% blocktrans trimmed with report_id=report.id username=report.user|username %} | ||||||
|  | Report #{{ report_id }}: Link added by @{{ username }} | ||||||
|  | {% endblocktrans %} | ||||||
|  |  | ||||||
|  | {% else %} | ||||||
|  |  | ||||||
|  | {% blocktrans trimmed with report_id=report.id username=report.user|username %} | ||||||
|  | Report #{{ report_id }}: User @{{ username }} | ||||||
|  | {% endblocktrans %} | ||||||
|  |  | ||||||
|  | {% endif %} | ||||||
							
								
								
									
										20
									
								
								bookwyrm/templates/settings/reports/report_links_table.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								bookwyrm/templates/settings/reports/report_links_table.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | {% extends "settings/link_domains/link_table.html" %} | ||||||
|  | {% load i18n %} | ||||||
|  |  | ||||||
|  | {% block additional_headers %} | ||||||
|  | <th>{% trans "Domain" %}</th> | ||||||
|  | <th>{% trans "Actions" %}</th> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block additional_data %} | ||||||
|  | <td> | ||||||
|  |     <a href="{% url 'settings-link-domain' 'pending' %}#{{ link.domain.id }}"> | ||||||
|  |         {{ link.domain.domain }} | ||||||
|  |     </a> | ||||||
|  | </td> | ||||||
|  | <td> | ||||||
|  |     <form> | ||||||
|  |         <button type="submit" class="button is-danger is-light">{% trans "Block domain" %}</button> | ||||||
|  |     </form> | ||||||
|  | </td> | ||||||
|  | {% endblock %} | ||||||
| @@ -1,9 +1,13 @@ | |||||||
| {% extends 'components/card.html' %} | {% extends 'components/card.html' %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load humanize %} | {% load humanize %} | ||||||
|  | {% load utilities %} | ||||||
|  |  | ||||||
| {% block card-header %} | {% block card-header %} | ||||||
| <h2 class="card-header-title has-background-white-ter is-block"> | <h2 class="card-header-title has-background-white-ter is-block"> | ||||||
|     <a href="{% url 'settings-report' report.id %}">{% blocktrans with report_id=report.id username=report.user.username %}Report #{{ report_id }}: {{ username }}{% endblocktrans %}</a> |     <a href="{% url 'settings-report' report.id %}"> | ||||||
|  |         {% include "settings/reports/report_header.html" with report=report %} | ||||||
|  |     </a> | ||||||
| </h2> | </h2> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| @@ -17,7 +21,7 @@ | |||||||
|  |  | ||||||
| {% block card-footer %} | {% block card-footer %} | ||||||
| <div class="card-footer-item"> | <div class="card-footer-item"> | ||||||
|     <p>{% blocktrans with username=report.reporter.display_name path=report.reporter.local_path %}Reported by <a href="{{ path }}">{{ username }}</a>{% endblocktrans %}</p> |     <p>{% blocktrans with username=report.reporter|username path=report.reporter.local_path %}Reported by <a href="{{ path }}">@{{ username }}</a>{% endblocktrans %}</p> | ||||||
| </div> | </div> | ||||||
| <div class="card-footer-item"> | <div class="card-footer-item"> | ||||||
|     {{ report.created_date | naturaltime }} |     {{ report.created_date | naturaltime }} | ||||||
|   | |||||||
| @@ -5,71 +5,73 @@ | |||||||
|         {% trans "Permanently deleted" %} |         {% trans "Permanently deleted" %} | ||||||
|     </div> |     </div> | ||||||
|     {% else %} |     {% else %} | ||||||
|     <h3>{% trans "Actions" %}</h3> |     <h3>{% trans "User Actions" %}</h3> | ||||||
|  |  | ||||||
|     <div class="is-flex"> |     <div class="box"> | ||||||
|         {% if user.is_active %} |         <div class="is-flex"> | ||||||
|         <p class="mr-1"> |             {% if user.is_active %} | ||||||
|             <a class="button" href="{% url 'direct-messages-user' user.username %}">{% trans "Send direct message" %}</a> |             <p class="mr-1"> | ||||||
|         </p> |                 <a class="button" href="{% url 'direct-messages-user' user.username %}">{% trans "Send direct message" %}</a> | ||||||
|         {% endif %} |             </p> | ||||||
|  |             {% endif %} | ||||||
|  |  | ||||||
|         {% if user.is_active or user.deactivation_reason == "pending" %} |             {% if user.is_active or user.deactivation_reason == "pending" %} | ||||||
|         <form name="suspend" method="post" action="{% url 'settings-report-suspend' user.id  %}" class="mr-1"> |             <form name="suspend" method="post" action="{% url 'settings-report-suspend' user.id  %}" class="mr-1"> | ||||||
|             {% csrf_token %} |                 {% csrf_token %} | ||||||
|             <button type="submit" class="button is-danger is-light">{% trans "Suspend user" %}</button> |                 <button type="submit" class="button is-danger is-light">{% trans "Suspend user" %}</button> | ||||||
|         </form> |             </form> | ||||||
|         {% else %} |             {% else %} | ||||||
|         <form name="unsuspend" method="post" action="{% url 'settings-report-unsuspend' user.id  %}" class="mr-1"> |             <form name="unsuspend" method="post" action="{% url 'settings-report-unsuspend' user.id  %}" class="mr-1"> | ||||||
|             {% csrf_token %} |                 {% csrf_token %} | ||||||
|             <button class="button">{% trans "Un-suspend user" %}</button> |                 <button class="button">{% trans "Un-suspend user" %}</button> | ||||||
|         </form> |             </form> | ||||||
|  |             {% endif %} | ||||||
|  |  | ||||||
|  |             {% if user.local %} | ||||||
|  |             <div> | ||||||
|  |                 {% trans "Permanently delete user" as button_text %} | ||||||
|  |                 {% include "snippets/toggle/open_button.html" with controls_text="delete_user" text=button_text class="is-danger is-light" %} | ||||||
|  |             </div> | ||||||
|  |             {% endif %} | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         {% if user.local %} | ||||||
|  |         <div> | ||||||
|  |             {% include "settings/users/delete_user_form.html" with controls_text="delete_user" class="mt-2 mb-2" %} | ||||||
|  |         </div> | ||||||
|         {% endif %} |         {% endif %} | ||||||
|  |  | ||||||
|         {% if user.local %} |         {% if user.local %} | ||||||
|         <div> |         <div> | ||||||
|             {% trans "Permanently delete user" as button_text %} |             <form name="permission" method="post" action="{% url 'settings-user' user.id %}"> | ||||||
|             {% include "snippets/toggle/open_button.html" with controls_text="delete_user" text=button_text class="is-danger is-light" %} |                 {% csrf_token %} | ||||||
|  |                 <label class="label" for="id_user_group">{% trans "Access level:" %}</label> | ||||||
|  |                 {% if group_form.non_field_errors %} | ||||||
|  |                 {{ group_form.non_field_errors }} | ||||||
|  |                 {% endif %} | ||||||
|  |                 {% with group=user.groups.first %} | ||||||
|  |                 <div class="select"> | ||||||
|  |                     <select name="groups" id="id_user_group" aria-describedby="desc_user_group"> | ||||||
|  |                         {% for value, name in group_form.fields.groups.choices %} | ||||||
|  |                         <option value="{{ value }}" {% if name == group.name %}selected{% endif %}> | ||||||
|  |                             {{ name|title }} | ||||||
|  |                         </option> | ||||||
|  |                         {% endfor %} | ||||||
|  |                         <option value="" {% if not group %}selected{% endif %}> | ||||||
|  |                             User | ||||||
|  |                         </option> | ||||||
|  |                     </select> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 {% include 'snippets/form_errors.html' with errors_list=group_form.groups.errors id="desc_user_group" %} | ||||||
|  |                 {% endwith %} | ||||||
|  |                 <button class="button"> | ||||||
|  |                     {% trans "Save" %} | ||||||
|  |                 </button> | ||||||
|  |             </form> | ||||||
|         </div> |         </div> | ||||||
|         {% endif %} |         {% endif %} | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     {% if user.local %} |  | ||||||
|     <div> |  | ||||||
|         {% include "settings/users/delete_user_form.html" with controls_text="delete_user" class="mt-2 mb-2" %} |  | ||||||
|     </div> |  | ||||||
|     {% endif %} |  | ||||||
|  |  | ||||||
|     {% if user.local %} |  | ||||||
|     <div> |  | ||||||
|         <form name="permission" method="post" action="{% url 'settings-user' user.id %}"> |  | ||||||
|             {% csrf_token %} |  | ||||||
|             <label class="label" for="id_user_group">{% trans "Access level:" %}</label> |  | ||||||
|             {% if group_form.non_field_errors %} |  | ||||||
|             {{ group_form.non_field_errors }} |  | ||||||
|             {% endif %} |  | ||||||
|             {% with group=user.groups.first %} |  | ||||||
|             <div class="select"> |  | ||||||
|                 <select name="groups" id="id_user_group" aria-describedby="desc_user_group"> |  | ||||||
|                     {% for value, name in group_form.fields.groups.choices %} |  | ||||||
|                     <option value="{{ value }}" {% if name == group.name %}selected{% endif %}> |  | ||||||
|                         {{ name|title }} |  | ||||||
|                     </option> |  | ||||||
|                     {% endfor %} |  | ||||||
|                     <option value="" {% if not group %}selected{% endif %}> |  | ||||||
|                         User |  | ||||||
|                     </option> |  | ||||||
|                 </select> |  | ||||||
|             </div> |  | ||||||
|  |  | ||||||
|             {% include 'snippets/form_errors.html' with errors_list=group_form.groups.errors id="desc_user_group" %} |  | ||||||
|             {% endwith %} |  | ||||||
|             <button class="button"> |  | ||||||
|                 {% trans "Save" %} |  | ||||||
|             </button> |  | ||||||
|         </form> |  | ||||||
|     </div> |  | ||||||
|     {% endif %} |  | ||||||
|  |  | ||||||
|     {% endif %} |     {% endif %} | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| {% extends 'layout.html' %} | {% extends 'layout.html' %} | ||||||
| {% load bookwyrm_tags %} | {% load shelf_tags %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
| {% load humanize %} | {% load humanize %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| @@ -19,6 +19,17 @@ | |||||||
|     </h1> |     </h1> | ||||||
| </header> | </header> | ||||||
|  |  | ||||||
|  | <nav class="breadcrumb subtitle" aria-label="breadcrumbs"> | ||||||
|  |     <ul> | ||||||
|  |         <li><a href="{% url 'user-feed' user|username %}">{% trans "User profile" %}</a></li> | ||||||
|  |         <li class="is-active"> | ||||||
|  |             <a href="#" aria-current="page"> | ||||||
|  |                 {% include "snippets/translated_shelf_name.html" with shelf=shelf %} | ||||||
|  |             </a> | ||||||
|  |         </li> | ||||||
|  |     </ul> | ||||||
|  | </nav> | ||||||
|  |  | ||||||
| <nav class="block columns is-mobile scroll-x"> | <nav class="block columns is-mobile scroll-x"> | ||||||
|     <div class="column pr-0"> |     <div class="column pr-0"> | ||||||
|         <div class="tabs"> |         <div class="tabs"> | ||||||
| @@ -34,15 +45,7 @@ | |||||||
|                         href="{{ shelf_tab.local_path }}" |                         href="{{ shelf_tab.local_path }}" | ||||||
|                         {% if shelf_tab.identifier == shelf.identifier %} aria-current="page"{% endif %} |                         {% if shelf_tab.identifier == shelf.identifier %} aria-current="page"{% endif %} | ||||||
|                     > |                     > | ||||||
|                         {% if shelf_tab.identifier == 'to-read' %} |                         {% include 'user/books_header.html' with shelf=shelf_tab %} | ||||||
|                             {% trans "To Read" %} |  | ||||||
|                         {% elif shelf_tab.identifier == 'reading' %} |  | ||||||
|                             {% trans "Currently Reading" %} |  | ||||||
|                         {% elif shelf_tab.identifier == 'read' %} |  | ||||||
|                             {% trans "Read" %} |  | ||||||
|                         {% else %} |  | ||||||
|                             {{ shelf_tab.name }} |  | ||||||
|                         {% endif %} |  | ||||||
|                     </a> |                     </a> | ||||||
|                 </li> |                 </li> | ||||||
|                 {% endfor %} |                 {% endfor %} | ||||||
| @@ -89,7 +92,7 @@ | |||||||
|                 </span> |                 </span> | ||||||
|                 {% with count=books.paginator.count %} |                 {% with count=books.paginator.count %} | ||||||
|                 {% if count %} |                 {% if count %} | ||||||
|                 <p class="help"> |                 <span class="help"> | ||||||
|                     {% blocktrans trimmed count counter=count with formatted_count=count|intcomma %} |                     {% blocktrans trimmed count counter=count with formatted_count=count|intcomma %} | ||||||
|                         {{ formatted_count }} book |                         {{ formatted_count }} book | ||||||
|                     {% plural %} |                     {% plural %} | ||||||
| @@ -101,7 +104,7 @@ | |||||||
|                         (showing {{ start }}-{{ end }}) |                         (showing {{ start }}-{{ end }}) | ||||||
|                         {% endblocktrans %} |                         {% endblocktrans %} | ||||||
|                     {% endif %} |                     {% endif %} | ||||||
|                 </p> |                 </span> | ||||||
|                 {% endif %} |                 {% endif %} | ||||||
|                 {% endwith %} |                 {% endwith %} | ||||||
|             </h2> |             </h2> | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load bookwyrm_group_tags %} | {% load group_tags %} | ||||||
|  |  | ||||||
| {% if request.user == user or not request.user == group.user or not request.user.is_authenticated %} | {% if request.user == user or not request.user == group.user or not request.user.is_authenticated %} | ||||||
| {% elif user in request.user.blocks.all %} | {% elif user in request.user.blocks.all %} | ||||||
| {% include 'snippets/block_button.html' with blocks=True %} | {% include 'snippets/block_button.html' with blocks=True %} | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| {% extends "snippets/create_status/layout.html" %} | {% extends "snippets/create_status/layout.html" %} | ||||||
| {% load bookwyrm_tags %} | {% load shelf_tags %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
| {% load status_display %} | {% load status_display %} | ||||||
|   | |||||||
| @@ -1,4 +1,3 @@ | |||||||
| {% load bookwyrm_tags %} |  | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
| {% load status_display %} | {% load status_display %} | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| {% extends "snippets/create_status/layout.html" %} | {% extends "snippets/create_status/layout.html" %} | ||||||
| {% load bookwyrm_tags %} |  | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
| {% load status_display %} | {% load status_display %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| {% extends "snippets/create_status/layout.html" %} | {% extends "snippets/create_status/layout.html" %} | ||||||
| {% load bookwyrm_tags %} |  | ||||||
| {% load utilities %} | {% load utilities %} | ||||||
| {% load status_display %} | {% load status_display %} | ||||||
| {% load i18n %} | {% load i18n %} | ||||||
|   | |||||||
| @@ -3,16 +3,15 @@ | |||||||
|     <summary class="is-flex is-align-items-center is-flex-wrap-wrap is-gap-2"> |     <summary class="is-flex is-align-items-center is-flex-wrap-wrap is-gap-2"> | ||||||
|         <span class="mb-0 title {% if size == 'small' %}is-6{% else %}is-5{% endif %} is-flex-shrink-0"> |         <span class="mb-0 title {% if size == 'small' %}is-6{% else %}is-5{% endif %} is-flex-shrink-0"> | ||||||
|             {% trans "Filters" %} |             {% trans "Filters" %} | ||||||
|  |  | ||||||
|         </span> |         </span> | ||||||
|  |  | ||||||
|         {% if filters_applied %} |         {% if filters_applied %} | ||||||
|         <span class="tag is-success is-light ml-2 mb-0 is-{{ size|default:'normal' }}"> |         <span class="tag is-success is-light ml-2 mb-0 is-{{ size|default:'normal' }}"> | ||||||
|             {{ _("Filters are applied") }} |             {% trans "Filters are applied" %} | ||||||
|         </span> |         </span> | ||||||
|         {% endif %} |         {% endif %} | ||||||
|  |  | ||||||
|         {% if request.GET %} |         {% if method != "post" and request.GET %} | ||||||
|         <span class="mb-0 tags has-addons"> |         <span class="mb-0 tags has-addons"> | ||||||
|             <span class="mb-0 tag is-success is-light is-{{ size|default:'normal' }}"> |             <span class="mb-0 tag is-success is-light is-{{ size|default:'normal' }}"> | ||||||
|                 {% trans "Filters are applied" %} |                 {% trans "Filters are applied" %} | ||||||
|   | |||||||
| @@ -1,13 +1,18 @@ | |||||||
| {% load i18n %} | {% load i18n %} | ||||||
|  | {% load interaction %} | ||||||
|  |  | ||||||
| {% if request.user == user or not request.user.is_authenticated %} | {% if request.user == user or not request.user.is_authenticated %} | ||||||
| {% elif user in request.user.blocks.all %} | {# nothing to see here -- either it's yourself or your logged out #} | ||||||
|  | {% else %} | ||||||
|  |  | ||||||
|  | {% get_relationship user as relationship %} | ||||||
|  | {% if relationship.is_blocked %} | ||||||
| {% include 'snippets/block_button.html' with blocks=True %} | {% include 'snippets/block_button.html' with blocks=True %} | ||||||
| {% else %} | {% else %} | ||||||
|  |  | ||||||
| <div class="field{% if not minimal %} has-addons{% else %} mb-0{% endif %}"> | <div class="field{% if not minimal %} has-addons{% else %} mb-0{% endif %}"> | ||||||
|     <div class="control"> |     <div class="control"> | ||||||
|         <form action="{% url 'follow' %}" method="POST" class="interaction follow_{{ user.id }} {% if request.user in user.followers.all or request.user in user.follower_requests.all %}is-hidden{%endif %}" data-id="follow_{{ user.id }}"> |         <form action="{% url 'follow' %}" method="POST" class="interaction follow_{{ user.id }} {% if relationship.is_following or relationship.is_follow_pending %}is-hidden{%endif %}" data-id="follow_{{ user.id }}"> | ||||||
|             {% csrf_token %} |             {% csrf_token %} | ||||||
|             <input type="hidden" name="user" value="{{ user.username }}"> |             <input type="hidden" name="user" value="{{ user.username }}"> | ||||||
|             <button class="button is-small{% if not minimal %} is-link{% endif %}" type="submit"> |             <button class="button is-small{% if not minimal %} is-link{% endif %}" type="submit"> | ||||||
| @@ -18,10 +23,10 @@ | |||||||
|                 {% endif %} |                 {% endif %} | ||||||
|             </button> |             </button> | ||||||
|         </form> |         </form> | ||||||
|         <form action="{% url 'unfollow' %}" method="POST" class="interaction follow_{{ user.id }} {% if not request.user in user.followers.all and not request.user in user.follower_requests.all %}is-hidden{%endif %}" data-id="follow_{{ user.id }}"> |         <form action="{% url 'unfollow' %}" method="POST" class="interaction follow_{{ user.id }} {% if not relationship.is_following and not relationship.is_follow_pending %}is-hidden{%endif %}" data-id="follow_{{ user.id }}"> | ||||||
|             {% csrf_token %} |             {% csrf_token %} | ||||||
|             <input type="hidden" name="user" value="{{ user.username }}"> |             <input type="hidden" name="user" value="{{ user.username }}"> | ||||||
|             {% if user.manually_approves_followers and request.user not in user.followers.all %} |             {% if user.manually_approves_followers and not relationship.is_following %} | ||||||
|             <button class="button is-small is-danger is-light" type="submit"> |             <button class="button is-small is-danger is-light" type="submit"> | ||||||
|                 {% trans "Undo follow request" %} |                 {% trans "Undo follow request" %} | ||||||
|             </button> |             </button> | ||||||
| @@ -42,4 +47,7 @@ | |||||||
|     </div> |     </div> | ||||||
|     {% endif %} |     {% endif %} | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
| {% endif %} | {% endif %} | ||||||
|   | |||||||
| @@ -1,10 +1,16 @@ | |||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% if rating %} | {% if rating %} | ||||||
|  |  | ||||||
| {% blocktrans with book_title=book.title|safe display_rating=rating|floatformat:"-1" review_title=name|safe count counter=rating %}Review of "{{ book_title }}" ({{ display_rating }} star): {{ review_title }}{% plural %}Review of "{{ book_title }}" ({{ display_rating }} stars): {{ review_title }}{% endblocktrans %} | {% blocktrans trimmed with book_title=book.title|safe book_path=book.local_path display_rating=rating|floatformat:"-1" review_title=name|safe count counter=rating %} | ||||||
|  | Review of "{{ book_title }}" ({{ display_rating }} star): {{ review_title }} | ||||||
|  | {% plural %} | ||||||
|  | Review of "{{ book_title }}" ({{ display_rating }} stars): {{ review_title }} | ||||||
|  | {% endblocktrans %} | ||||||
|  |  | ||||||
| {% else %} | {% else %} | ||||||
|  |  | ||||||
| {% blocktrans with book_title=book.title|safe review_title=name|safe %}Review of "{{ book_title }}": {{ review_title }}{% endblocktrans %} | {% blocktrans trimmed with book_title=book.title|safe book_path=book.local_path review_title=name|safe %} | ||||||
|  | Review of "{{ book_title }}": {{ review_title }} | ||||||
|  | {% endblocktrans %} | ||||||
|  |  | ||||||
| {% endif %} | {% endif %} | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load bookwyrm_group_tags %} | {% load group_tags %} | ||||||
|  |  | ||||||
| {% if group|is_invited:request.user %} | {% if group|is_invited:request.user %} | ||||||
| <div class="field is-grouped"> | <div class="field is-grouped"> | ||||||
|     <form action="/accept-group-invitation/" method="POST"> |     <form action="/accept-group-invitation/" method="POST"> | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load bookwyrm_tags %} | {% load rating_tags %} | ||||||
|  |  | ||||||
| {% if request.user.is_authenticated %} | {% if request.user.is_authenticated %} | ||||||
| <span class="is-sr-only">{% trans "Leave a rating" %}</span> | <span class="is-sr-only">{% trans "Leave a rating" %}</span> | ||||||
| <div class="block"> | <div class="block"> | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| {% load i18n %} | {% load i18n %} | ||||||
| {% load bookwyrm_group_tags %} | {% load group_tags %} | ||||||
|  |  | ||||||
| {% if request.user == user or not request.user == group.user or not request.user.is_authenticated %} | {% if request.user == user or not request.user == group.user or not request.user.is_authenticated %} | ||||||
| {% else %} | {% else %} | ||||||
| {% if user in request.user.blocks.all %} | {% if user in request.user.blocks.all %} | ||||||
|   | |||||||
| @@ -12,6 +12,6 @@ | |||||||
| > | > | ||||||
|     {% trans "Report" %} |     {% trans "Report" %} | ||||||
| </button> | </button> | ||||||
| {% include 'snippets/report_modal.html' with user=user reporter=request.user id=modal_id %} | {% include 'snippets/report_modal.html' with user=user id=modal_id status=status.id %} | ||||||
|  |  | ||||||
| {% endwith %} | {% endwith %} | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user