mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-12-24 17:10:31 +00:00
Merge branch 'main' into readthrough-dates
This commit is contained in:
commit
01d82cfa3a
123 changed files with 3139 additions and 1768 deletions
|
@ -32,7 +32,7 @@ indent_size = 2
|
||||||
max_line_length = off
|
max_line_length = off
|
||||||
|
|
||||||
# Computer generated files
|
# Computer generated files
|
||||||
[{package.json,*.lock,*.mo}]
|
[{icons.css,package.json,*.lock,*.mo}]
|
||||||
indent_size = unset
|
indent_size = unset
|
||||||
indent_style = unset
|
indent_style = unset
|
||||||
max_line_length = unset
|
max_line_length = unset
|
||||||
|
|
|
@ -43,6 +43,9 @@ EMAIL_HOST_PASSWORD=emailpassword123
|
||||||
EMAIL_USE_TLS=true
|
EMAIL_USE_TLS=true
|
||||||
EMAIL_USE_SSL=false
|
EMAIL_USE_SSL=false
|
||||||
|
|
||||||
|
# Thumbnails Generation
|
||||||
|
ENABLE_THUMBNAIL_GENERATION=false
|
||||||
|
|
||||||
# S3 configuration
|
# S3 configuration
|
||||||
USE_S3=false
|
USE_S3=false
|
||||||
AWS_ACCESS_KEY_ID=
|
AWS_ACCESS_KEY_ID=
|
||||||
|
@ -58,6 +61,7 @@ AWS_SECRET_ACCESS_KEY=
|
||||||
# AWS_S3_REGION_NAME=None # "fr-par"
|
# AWS_S3_REGION_NAME=None # "fr-par"
|
||||||
# AWS_S3_ENDPOINT_URL=None # "https://s3.fr-par.scw.cloud"
|
# AWS_S3_ENDPOINT_URL=None # "https://s3.fr-par.scw.cloud"
|
||||||
|
|
||||||
|
|
||||||
# Preview image generation can be computing and storage intensive
|
# Preview image generation can be computing and storage intensive
|
||||||
# ENABLE_PREVIEW_IMAGES=True
|
# ENABLE_PREVIEW_IMAGES=True
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,9 @@ EMAIL_HOST_PASSWORD=emailpassword123
|
||||||
EMAIL_USE_TLS=true
|
EMAIL_USE_TLS=true
|
||||||
EMAIL_USE_SSL=false
|
EMAIL_USE_SSL=false
|
||||||
|
|
||||||
|
# Thumbnails Generation
|
||||||
|
ENABLE_THUMBNAIL_GENERATION=false
|
||||||
|
|
||||||
# S3 configuration
|
# S3 configuration
|
||||||
USE_S3=false
|
USE_S3=false
|
||||||
AWS_ACCESS_KEY_ID=
|
AWS_ACCESS_KEY_ID=
|
||||||
|
@ -58,6 +61,7 @@ AWS_SECRET_ACCESS_KEY=
|
||||||
# AWS_S3_REGION_NAME=None # "fr-par"
|
# AWS_S3_REGION_NAME=None # "fr-par"
|
||||||
# AWS_S3_ENDPOINT_URL=None # "https://s3.fr-par.scw.cloud"
|
# AWS_S3_ENDPOINT_URL=None # "https://s3.fr-par.scw.cloud"
|
||||||
|
|
||||||
|
|
||||||
# Preview image generation can be computing and storage intensive
|
# Preview image generation can be computing and storage intensive
|
||||||
# ENABLE_PREVIEW_IMAGES=True
|
# ENABLE_PREVIEW_IMAGES=True
|
||||||
|
|
||||||
|
|
28
.github/workflows/curlylint.yaml
vendored
Normal file
28
.github/workflows/curlylint.yaml
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
name: Templates validator
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install curlylint
|
||||||
|
run: pip install curlylint
|
||||||
|
|
||||||
|
- name: Run linter
|
||||||
|
run: >
|
||||||
|
curlylint --rule 'aria_role: true' \
|
||||||
|
--rule 'django_forms_rendering: true' \
|
||||||
|
--rule 'html_has_lang: true' \
|
||||||
|
--rule 'image_alt: true' \
|
||||||
|
--rule 'meta_viewport: true' \
|
||||||
|
--rule 'no_autofocus: true' \
|
||||||
|
--rule 'tabindex_no_positive: true' \
|
||||||
|
--exclude '_modal.html|create_status/layout.html' \
|
||||||
|
bookwyrm/templates
|
|
@ -106,8 +106,10 @@ class ActivityObject:
|
||||||
value = field.default
|
value = field.default
|
||||||
setattr(self, field.name, value)
|
setattr(self, field.name, value)
|
||||||
|
|
||||||
# pylint: disable=too-many-locals,too-many-branches
|
# pylint: disable=too-many-locals,too-many-branches,too-many-arguments
|
||||||
def to_model(self, model=None, instance=None, allow_create=True, save=True):
|
def to_model(
|
||||||
|
self, model=None, instance=None, allow_create=True, save=True, overwrite=True
|
||||||
|
):
|
||||||
"""convert from an activity to a model instance"""
|
"""convert from an activity to a model instance"""
|
||||||
model = model or get_model_from_type(self.type)
|
model = model or get_model_from_type(self.type)
|
||||||
|
|
||||||
|
@ -129,9 +131,12 @@ class ActivityObject:
|
||||||
|
|
||||||
# keep track of what we've changed
|
# keep track of what we've changed
|
||||||
update_fields = []
|
update_fields = []
|
||||||
|
# sets field on the model using the activity value
|
||||||
for field in instance.simple_fields:
|
for field in instance.simple_fields:
|
||||||
try:
|
try:
|
||||||
changed = field.set_field_from_activity(instance, self)
|
changed = field.set_field_from_activity(
|
||||||
|
instance, self, overwrite=overwrite
|
||||||
|
)
|
||||||
if changed:
|
if changed:
|
||||||
update_fields.append(field.name)
|
update_fields.append(field.name)
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
|
@ -140,7 +145,9 @@ class ActivityObject:
|
||||||
# image fields have to be set after other fields because they can save
|
# image fields have to be set after other fields because they can save
|
||||||
# too early and jank up users
|
# too early and jank up users
|
||||||
for field in instance.image_fields:
|
for field in instance.image_fields:
|
||||||
changed = field.set_field_from_activity(instance, self, save=save)
|
changed = field.set_field_from_activity(
|
||||||
|
instance, self, save=save, overwrite=overwrite
|
||||||
|
)
|
||||||
if changed:
|
if changed:
|
||||||
update_fields.append(field.name)
|
update_fields.append(field.name)
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,9 @@ class Comment(Note):
|
||||||
"""like a note but with a book"""
|
"""like a note but with a book"""
|
||||||
|
|
||||||
inReplyToBook: str
|
inReplyToBook: str
|
||||||
|
readingStatus: str = None
|
||||||
|
progress: int = None
|
||||||
|
progressMode: str = None
|
||||||
type: str = "Comment"
|
type: str = "Comment"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,14 +23,15 @@ class ActivityStream(RedisStore):
|
||||||
"""statuses are sorted by date published"""
|
"""statuses are sorted by date published"""
|
||||||
return obj.published_date.timestamp()
|
return obj.published_date.timestamp()
|
||||||
|
|
||||||
def add_status(self, status):
|
def add_status(self, status, increment_unread=False):
|
||||||
"""add a status to users' feeds"""
|
"""add a status to users' feeds"""
|
||||||
# the pipeline contains all the add-to-stream activities
|
# the pipeline contains all the add-to-stream activities
|
||||||
pipeline = self.add_object_to_related_stores(status, execute=False)
|
pipeline = self.add_object_to_related_stores(status, execute=False)
|
||||||
|
|
||||||
for user in self.get_audience(status):
|
if increment_unread:
|
||||||
# add to the unread status count
|
for user in self.get_audience(status):
|
||||||
pipeline.incr(self.unread_id(user))
|
# add to the unread status count
|
||||||
|
pipeline.incr(self.unread_id(user))
|
||||||
|
|
||||||
# and go!
|
# and go!
|
||||||
pipeline.execute()
|
pipeline.execute()
|
||||||
|
@ -262,7 +263,7 @@ def add_status_on_create(sender, instance, created, *args, **kwargs):
|
||||||
return
|
return
|
||||||
|
|
||||||
for stream in streams.values():
|
for stream in streams.values():
|
||||||
stream.add_status(instance)
|
stream.add_status(instance, increment_unread=created)
|
||||||
|
|
||||||
if sender != models.Boost:
|
if sender != models.Boost:
|
||||||
return
|
return
|
||||||
|
@ -273,9 +274,10 @@ def add_status_on_create(sender, instance, created, *args, **kwargs):
|
||||||
created_date__lt=instance.created_date,
|
created_date__lt=instance.created_date,
|
||||||
)
|
)
|
||||||
for stream in streams.values():
|
for stream in streams.values():
|
||||||
stream.remove_object_from_related_stores(boosted)
|
audience = stream.get_stores_for_object(instance)
|
||||||
|
stream.remove_object_from_related_stores(boosted, stores=audience)
|
||||||
for status in old_versions:
|
for status in old_versions:
|
||||||
stream.remove_object_from_related_stores(status)
|
stream.remove_object_from_related_stores(status, stores=audience)
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.post_delete, sender=models.Boost)
|
@receiver(signals.post_delete, sender=models.Boost)
|
||||||
|
|
|
@ -139,7 +139,7 @@ class AbstractConnector(AbstractMinimalConnector):
|
||||||
**dict_from_mappings(work_data, self.book_mappings)
|
**dict_from_mappings(work_data, self.book_mappings)
|
||||||
)
|
)
|
||||||
# this will dedupe automatically
|
# this will dedupe automatically
|
||||||
work = work_activity.to_model(model=models.Work)
|
work = work_activity.to_model(model=models.Work, overwrite=False)
|
||||||
for author in self.get_authors_from_data(work_data):
|
for author in self.get_authors_from_data(work_data):
|
||||||
work.authors.add(author)
|
work.authors.add(author)
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ class AbstractConnector(AbstractMinimalConnector):
|
||||||
mapped_data = dict_from_mappings(edition_data, self.book_mappings)
|
mapped_data = dict_from_mappings(edition_data, self.book_mappings)
|
||||||
mapped_data["work"] = work.remote_id
|
mapped_data["work"] = work.remote_id
|
||||||
edition_activity = activitypub.Edition(**mapped_data)
|
edition_activity = activitypub.Edition(**mapped_data)
|
||||||
edition = edition_activity.to_model(model=models.Edition)
|
edition = edition_activity.to_model(model=models.Edition, overwrite=False)
|
||||||
edition.connector = self.connector
|
edition.connector = self.connector
|
||||||
edition.save()
|
edition.save()
|
||||||
|
|
||||||
|
@ -182,7 +182,7 @@ class AbstractConnector(AbstractMinimalConnector):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# this will dedupe
|
# this will dedupe
|
||||||
return activity.to_model(model=models.Author)
|
return activity.to_model(model=models.Author, overwrite=False)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def is_work_data(self, data):
|
def is_work_data(self, data):
|
||||||
|
|
|
@ -145,8 +145,8 @@ class Connector(AbstractConnector):
|
||||||
def get_edition_from_work_data(self, data):
|
def get_edition_from_work_data(self, data):
|
||||||
data = self.load_edition_data(data.get("uri"))
|
data = self.load_edition_data(data.get("uri"))
|
||||||
try:
|
try:
|
||||||
uri = data["uris"][0]
|
uri = data.get("uris", [])[0]
|
||||||
except KeyError:
|
except IndexError:
|
||||||
raise ConnectorException("Invalid book data")
|
raise ConnectorException("Invalid book data")
|
||||||
return self.get_book_data(self.get_remote_id(uri))
|
return self.get_book_data(self.get_remote_id(uri))
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ def site_settings(request): # pylint: disable=unused-argument
|
||||||
return {
|
return {
|
||||||
"site": models.SiteSettings.objects.get(),
|
"site": models.SiteSettings.objects.get(),
|
||||||
"active_announcements": models.Announcement.active_announcements(),
|
"active_announcements": models.Announcement.active_announcements(),
|
||||||
|
"thumbnail_generation_enabled": settings.ENABLE_THUMBNAIL_GENERATION,
|
||||||
"media_full_url": settings.MEDIA_FULL_URL,
|
"media_full_url": settings.MEDIA_FULL_URL,
|
||||||
"preview_images_enabled": settings.ENABLE_PREVIEW_IMAGES,
|
"preview_images_enabled": settings.ENABLE_PREVIEW_IMAGES,
|
||||||
"request_protocol": request_protocol,
|
"request_protocol": request_protocol,
|
||||||
|
|
|
@ -86,6 +86,7 @@ class CommentForm(CustomForm):
|
||||||
"privacy",
|
"privacy",
|
||||||
"progress",
|
"progress",
|
||||||
"progress_mode",
|
"progress_mode",
|
||||||
|
"reading_status",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
113
bookwyrm/imagegenerators.py
Normal file
113
bookwyrm/imagegenerators.py
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
"""Generators for all the different thumbnail sizes"""
|
||||||
|
from imagekit import ImageSpec, register
|
||||||
|
from imagekit.processors import ResizeToFit
|
||||||
|
|
||||||
|
|
||||||
|
class BookXSmallWebp(ImageSpec):
|
||||||
|
"""Handles XSmall size in Webp format"""
|
||||||
|
|
||||||
|
processors = [ResizeToFit(80, 80)]
|
||||||
|
format = "WEBP"
|
||||||
|
options = {"quality": 95}
|
||||||
|
|
||||||
|
|
||||||
|
class BookXSmallJpg(ImageSpec):
|
||||||
|
"""Handles XSmall size in Jpeg format"""
|
||||||
|
|
||||||
|
processors = [ResizeToFit(80, 80)]
|
||||||
|
format = "JPEG"
|
||||||
|
options = {"quality": 95}
|
||||||
|
|
||||||
|
|
||||||
|
class BookSmallWebp(ImageSpec):
|
||||||
|
"""Handles Small size in Webp format"""
|
||||||
|
|
||||||
|
processors = [ResizeToFit(100, 100)]
|
||||||
|
format = "WEBP"
|
||||||
|
options = {"quality": 95}
|
||||||
|
|
||||||
|
|
||||||
|
class BookSmallJpg(ImageSpec):
|
||||||
|
"""Handles Small size in Jpeg format"""
|
||||||
|
|
||||||
|
processors = [ResizeToFit(100, 100)]
|
||||||
|
format = "JPEG"
|
||||||
|
options = {"quality": 95}
|
||||||
|
|
||||||
|
|
||||||
|
class BookMediumWebp(ImageSpec):
|
||||||
|
"""Handles Medium size in Webp format"""
|
||||||
|
|
||||||
|
processors = [ResizeToFit(150, 150)]
|
||||||
|
format = "WEBP"
|
||||||
|
options = {"quality": 95}
|
||||||
|
|
||||||
|
|
||||||
|
class BookMediumJpg(ImageSpec):
|
||||||
|
"""Handles Medium size in Jpeg format"""
|
||||||
|
|
||||||
|
processors = [ResizeToFit(150, 150)]
|
||||||
|
format = "JPEG"
|
||||||
|
options = {"quality": 95}
|
||||||
|
|
||||||
|
|
||||||
|
class BookLargeWebp(ImageSpec):
|
||||||
|
"""Handles Large size in Webp format"""
|
||||||
|
|
||||||
|
processors = [ResizeToFit(200, 200)]
|
||||||
|
format = "WEBP"
|
||||||
|
options = {"quality": 95}
|
||||||
|
|
||||||
|
|
||||||
|
class BookLargeJpg(ImageSpec):
|
||||||
|
"""Handles Large size in Jpeg format"""
|
||||||
|
|
||||||
|
processors = [ResizeToFit(200, 200)]
|
||||||
|
format = "JPEG"
|
||||||
|
options = {"quality": 95}
|
||||||
|
|
||||||
|
|
||||||
|
class BookXLargeWebp(ImageSpec):
|
||||||
|
"""Handles XLarge size in Webp format"""
|
||||||
|
|
||||||
|
processors = [ResizeToFit(250, 250)]
|
||||||
|
format = "WEBP"
|
||||||
|
options = {"quality": 95}
|
||||||
|
|
||||||
|
|
||||||
|
class BookXLargeJpg(ImageSpec):
|
||||||
|
"""Handles XLarge size in Jpeg format"""
|
||||||
|
|
||||||
|
processors = [ResizeToFit(250, 250)]
|
||||||
|
format = "JPEG"
|
||||||
|
options = {"quality": 95}
|
||||||
|
|
||||||
|
|
||||||
|
class BookXxLargeWebp(ImageSpec):
|
||||||
|
"""Handles XxLarge size in Webp format"""
|
||||||
|
|
||||||
|
processors = [ResizeToFit(500, 500)]
|
||||||
|
format = "WEBP"
|
||||||
|
options = {"quality": 95}
|
||||||
|
|
||||||
|
|
||||||
|
class BookXxLargeJpg(ImageSpec):
|
||||||
|
"""Handles XxLarge size in Jpeg format"""
|
||||||
|
|
||||||
|
processors = [ResizeToFit(500, 500)]
|
||||||
|
format = "JPEG"
|
||||||
|
options = {"quality": 95}
|
||||||
|
|
||||||
|
|
||||||
|
register.generator("bw:book:xsmall:webp", BookXSmallWebp)
|
||||||
|
register.generator("bw:book:xsmall:jpg", BookXSmallJpg)
|
||||||
|
register.generator("bw:book:small:webp", BookSmallWebp)
|
||||||
|
register.generator("bw:book:small:jpg", BookSmallJpg)
|
||||||
|
register.generator("bw:book:medium:webp", BookMediumWebp)
|
||||||
|
register.generator("bw:book:medium:jpg", BookMediumJpg)
|
||||||
|
register.generator("bw:book:large:webp", BookLargeWebp)
|
||||||
|
register.generator("bw:book:large:jpg", BookLargeJpg)
|
||||||
|
register.generator("bw:book:xlarge:webp", BookXLargeWebp)
|
||||||
|
register.generator("bw:book:xlarge:jpg", BookXLargeJpg)
|
||||||
|
register.generator("bw:book:xxlarge:webp", BookXxLargeWebp)
|
||||||
|
register.generator("bw:book:xxlarge:jpg", BookXxLargeJpg)
|
56
bookwyrm/migrations/0083_auto_20210816_2022.py
Normal file
56
bookwyrm/migrations/0083_auto_20210816_2022.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# Generated by Django 3.2.4 on 2021-08-16 20:22
|
||||||
|
|
||||||
|
import bookwyrm.models.fields
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0082_auto_20210806_2324"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="comment",
|
||||||
|
name="reading_status",
|
||||||
|
field=bookwyrm.models.fields.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
("to-read", "Toread"),
|
||||||
|
("reading", "Reading"),
|
||||||
|
("read", "Read"),
|
||||||
|
],
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="quotation",
|
||||||
|
name="reading_status",
|
||||||
|
field=bookwyrm.models.fields.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
("to-read", "Toread"),
|
||||||
|
("reading", "Reading"),
|
||||||
|
("read", "Read"),
|
||||||
|
],
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="review",
|
||||||
|
name="reading_status",
|
||||||
|
field=bookwyrm.models.fields.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
("to-read", "Toread"),
|
||||||
|
("reading", "Reading"),
|
||||||
|
("read", "Read"),
|
||||||
|
],
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
56
bookwyrm/migrations/0084_auto_20210817_1916.py
Normal file
56
bookwyrm/migrations/0084_auto_20210817_1916.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# Generated by Django 3.2.4 on 2021-08-17 19:16
|
||||||
|
|
||||||
|
import bookwyrm.models.fields
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0083_auto_20210816_2022"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="comment",
|
||||||
|
name="reading_status",
|
||||||
|
field=bookwyrm.models.fields.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
("to-read", "To-Read"),
|
||||||
|
("reading", "Reading"),
|
||||||
|
("read", "Read"),
|
||||||
|
],
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="quotation",
|
||||||
|
name="reading_status",
|
||||||
|
field=bookwyrm.models.fields.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
("to-read", "To-Read"),
|
||||||
|
("reading", "Reading"),
|
||||||
|
("read", "Read"),
|
||||||
|
],
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="review",
|
||||||
|
name="reading_status",
|
||||||
|
field=bookwyrm.models.fields.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
("to-read", "To-Read"),
|
||||||
|
("reading", "Reading"),
|
||||||
|
("read", "Read"),
|
||||||
|
],
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
20
bookwyrm/migrations/0085_user_saved_lists.py
Normal file
20
bookwyrm/migrations/0085_user_saved_lists.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 3.2.4 on 2021-08-23 18:05
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0084_auto_20210817_1916"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="user",
|
||||||
|
name="saved_lists",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
related_name="saved_lists", to="bookwyrm.List"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -7,10 +7,16 @@ from django.db import models
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from model_utils import FieldTracker
|
from model_utils import FieldTracker
|
||||||
from model_utils.managers import InheritanceManager
|
from model_utils.managers import InheritanceManager
|
||||||
|
from imagekit.models import ImageSpecField
|
||||||
|
|
||||||
from bookwyrm import activitypub
|
from bookwyrm import activitypub
|
||||||
from bookwyrm.preview_images import generate_edition_preview_image_task
|
from bookwyrm.preview_images import generate_edition_preview_image_task
|
||||||
from bookwyrm.settings import DOMAIN, DEFAULT_LANGUAGE, ENABLE_PREVIEW_IMAGES
|
from bookwyrm.settings import (
|
||||||
|
DOMAIN,
|
||||||
|
DEFAULT_LANGUAGE,
|
||||||
|
ENABLE_PREVIEW_IMAGES,
|
||||||
|
ENABLE_THUMBNAIL_GENERATION,
|
||||||
|
)
|
||||||
|
|
||||||
from .activitypub_mixin import OrderedCollectionPageMixin, ObjectMixin
|
from .activitypub_mixin import OrderedCollectionPageMixin, ObjectMixin
|
||||||
from .base_model import BookWyrmModel
|
from .base_model import BookWyrmModel
|
||||||
|
@ -97,6 +103,40 @@ class Book(BookDataModel):
|
||||||
objects = InheritanceManager()
|
objects = InheritanceManager()
|
||||||
field_tracker = FieldTracker(fields=["authors", "title", "subtitle", "cover"])
|
field_tracker = FieldTracker(fields=["authors", "title", "subtitle", "cover"])
|
||||||
|
|
||||||
|
if ENABLE_THUMBNAIL_GENERATION:
|
||||||
|
cover_bw_book_xsmall_webp = ImageSpecField(
|
||||||
|
source="cover", id="bw:book:xsmall:webp"
|
||||||
|
)
|
||||||
|
cover_bw_book_xsmall_jpg = ImageSpecField(
|
||||||
|
source="cover", id="bw:book:xsmall:jpg"
|
||||||
|
)
|
||||||
|
cover_bw_book_small_webp = ImageSpecField(
|
||||||
|
source="cover", id="bw:book:small:webp"
|
||||||
|
)
|
||||||
|
cover_bw_book_small_jpg = ImageSpecField(source="cover", id="bw:book:small:jpg")
|
||||||
|
cover_bw_book_medium_webp = ImageSpecField(
|
||||||
|
source="cover", id="bw:book:medium:webp"
|
||||||
|
)
|
||||||
|
cover_bw_book_medium_jpg = ImageSpecField(
|
||||||
|
source="cover", id="bw:book:medium:jpg"
|
||||||
|
)
|
||||||
|
cover_bw_book_large_webp = ImageSpecField(
|
||||||
|
source="cover", id="bw:book:large:webp"
|
||||||
|
)
|
||||||
|
cover_bw_book_large_jpg = ImageSpecField(source="cover", id="bw:book:large:jpg")
|
||||||
|
cover_bw_book_xlarge_webp = ImageSpecField(
|
||||||
|
source="cover", id="bw:book:xlarge:webp"
|
||||||
|
)
|
||||||
|
cover_bw_book_xlarge_jpg = ImageSpecField(
|
||||||
|
source="cover", id="bw:book:xlarge:jpg"
|
||||||
|
)
|
||||||
|
cover_bw_book_xxlarge_webp = ImageSpecField(
|
||||||
|
source="cover", id="bw:book:xxlarge:webp"
|
||||||
|
)
|
||||||
|
cover_bw_book_xxlarge_jpg = ImageSpecField(
|
||||||
|
source="cover", id="bw:book:xxlarge:jpg"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def author_text(self):
|
def author_text(self):
|
||||||
"""format a list of authors"""
|
"""format a list of authors"""
|
||||||
|
|
|
@ -66,7 +66,7 @@ class ActivitypubFieldMixin:
|
||||||
self.activitypub_field = activitypub_field
|
self.activitypub_field = activitypub_field
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def set_field_from_activity(self, instance, data):
|
def set_field_from_activity(self, instance, data, overwrite=True):
|
||||||
"""helper function for assinging a value to the field. Returns if changed"""
|
"""helper function for assinging a value to the field. Returns if changed"""
|
||||||
try:
|
try:
|
||||||
value = getattr(data, self.get_activitypub_field())
|
value = getattr(data, self.get_activitypub_field())
|
||||||
|
@ -79,8 +79,15 @@ class ActivitypubFieldMixin:
|
||||||
if formatted is None or formatted is MISSING or formatted == {}:
|
if formatted is None or formatted is MISSING or formatted == {}:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
current_value = (
|
||||||
|
getattr(instance, self.name) if hasattr(instance, self.name) else None
|
||||||
|
)
|
||||||
|
# if we're not in overwrite mode, only continue updating the field if its unset
|
||||||
|
if current_value and not overwrite:
|
||||||
|
return False
|
||||||
|
|
||||||
# the field is unchanged
|
# the field is unchanged
|
||||||
if hasattr(instance, self.name) and getattr(instance, self.name) == formatted:
|
if current_value == formatted:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
setattr(instance, self.name, formatted)
|
setattr(instance, self.name, formatted)
|
||||||
|
@ -210,7 +217,10 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField):
|
||||||
)
|
)
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
def set_field_from_activity(self, instance, data):
|
def set_field_from_activity(self, instance, data, overwrite=True):
|
||||||
|
if not overwrite:
|
||||||
|
return False
|
||||||
|
|
||||||
original = getattr(instance, self.name)
|
original = getattr(instance, self.name)
|
||||||
to = data.to
|
to = data.to
|
||||||
cc = data.cc
|
cc = data.cc
|
||||||
|
@ -273,8 +283,11 @@ class ManyToManyField(ActivitypubFieldMixin, models.ManyToManyField):
|
||||||
self.link_only = link_only
|
self.link_only = link_only
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def set_field_from_activity(self, instance, data):
|
def set_field_from_activity(self, instance, data, overwrite=True):
|
||||||
"""helper function for assinging a value to the field"""
|
"""helper function for assinging a value to the field"""
|
||||||
|
if not overwrite and getattr(instance, self.name).exists():
|
||||||
|
return False
|
||||||
|
|
||||||
value = getattr(data, self.get_activitypub_field())
|
value = getattr(data, self.get_activitypub_field())
|
||||||
formatted = self.field_from_activity(value)
|
formatted = self.field_from_activity(value)
|
||||||
if formatted is None or formatted is MISSING:
|
if formatted is None or formatted is MISSING:
|
||||||
|
@ -377,13 +390,16 @@ class ImageField(ActivitypubFieldMixin, models.ImageField):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
# pylint: disable=arguments-differ
|
# pylint: disable=arguments-differ
|
||||||
def set_field_from_activity(self, instance, data, save=True):
|
def set_field_from_activity(self, instance, data, save=True, overwrite=True):
|
||||||
"""helper function for assinging a value to the field"""
|
"""helper function for assinging a value to the field"""
|
||||||
value = getattr(data, self.get_activitypub_field())
|
value = getattr(data, self.get_activitypub_field())
|
||||||
formatted = self.field_from_activity(value)
|
formatted = self.field_from_activity(value)
|
||||||
if formatted is None or formatted is MISSING:
|
if formatted is None or formatted is MISSING:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if not overwrite and hasattr(instance, self.name):
|
||||||
|
return False
|
||||||
|
|
||||||
getattr(instance, self.name).save(*formatted, save=save)
|
getattr(instance, self.name).save(*formatted, save=save)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -235,12 +235,31 @@ class GeneratedNote(Status):
|
||||||
pure_type = "Note"
|
pure_type = "Note"
|
||||||
|
|
||||||
|
|
||||||
class Comment(Status):
|
ReadingStatusChoices = models.TextChoices(
|
||||||
"""like a review but without a rating and transient"""
|
"ReadingStatusChoices", ["to-read", "reading", "read"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BookStatus(Status):
|
||||||
|
"""Shared fields for comments, quotes, reviews"""
|
||||||
|
|
||||||
book = fields.ForeignKey(
|
book = fields.ForeignKey(
|
||||||
"Edition", on_delete=models.PROTECT, activitypub_field="inReplyToBook"
|
"Edition", on_delete=models.PROTECT, activitypub_field="inReplyToBook"
|
||||||
)
|
)
|
||||||
|
pure_type = "Note"
|
||||||
|
|
||||||
|
reading_status = fields.CharField(
|
||||||
|
max_length=255, choices=ReadingStatusChoices.choices, null=True, blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""not a real model, sorry"""
|
||||||
|
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class Comment(BookStatus):
|
||||||
|
"""like a review but without a rating and transient"""
|
||||||
|
|
||||||
# this is it's own field instead of a foreign key to the progress update
|
# this is it's own field instead of a foreign key to the progress update
|
||||||
# so that the update can be deleted without impacting the status
|
# so that the update can be deleted without impacting the status
|
||||||
|
@ -265,16 +284,12 @@ class Comment(Status):
|
||||||
)
|
)
|
||||||
|
|
||||||
activity_serializer = activitypub.Comment
|
activity_serializer = activitypub.Comment
|
||||||
pure_type = "Note"
|
|
||||||
|
|
||||||
|
|
||||||
class Quotation(Status):
|
class Quotation(BookStatus):
|
||||||
"""like a review but without a rating and transient"""
|
"""like a review but without a rating and transient"""
|
||||||
|
|
||||||
quote = fields.HtmlField()
|
quote = fields.HtmlField()
|
||||||
book = fields.ForeignKey(
|
|
||||||
"Edition", on_delete=models.PROTECT, activitypub_field="inReplyToBook"
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pure_content(self):
|
def pure_content(self):
|
||||||
|
@ -289,16 +304,12 @@ class Quotation(Status):
|
||||||
)
|
)
|
||||||
|
|
||||||
activity_serializer = activitypub.Quotation
|
activity_serializer = activitypub.Quotation
|
||||||
pure_type = "Note"
|
|
||||||
|
|
||||||
|
|
||||||
class Review(Status):
|
class Review(BookStatus):
|
||||||
"""a book review"""
|
"""a book review"""
|
||||||
|
|
||||||
name = fields.CharField(max_length=255, null=True)
|
name = fields.CharField(max_length=255, null=True)
|
||||||
book = fields.ForeignKey(
|
|
||||||
"Edition", on_delete=models.PROTECT, activitypub_field="inReplyToBook"
|
|
||||||
)
|
|
||||||
rating = fields.DecimalField(
|
rating = fields.DecimalField(
|
||||||
default=None,
|
default=None,
|
||||||
null=True,
|
null=True,
|
||||||
|
|
|
@ -104,6 +104,9 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||||
through_fields=("user_subject", "user_object"),
|
through_fields=("user_subject", "user_object"),
|
||||||
related_name="blocked_by",
|
related_name="blocked_by",
|
||||||
)
|
)
|
||||||
|
saved_lists = models.ManyToManyField(
|
||||||
|
"List", symmetrical=False, related_name="saved_lists"
|
||||||
|
)
|
||||||
favorites = models.ManyToManyField(
|
favorites = models.ManyToManyField(
|
||||||
"Status",
|
"Status",
|
||||||
symmetrical=False,
|
symmetrical=False,
|
||||||
|
|
|
@ -33,10 +33,11 @@ class RedisStore(ABC):
|
||||||
# and go!
|
# and go!
|
||||||
return pipeline.execute()
|
return pipeline.execute()
|
||||||
|
|
||||||
def remove_object_from_related_stores(self, obj):
|
def remove_object_from_related_stores(self, obj, stores=None):
|
||||||
"""remove an object from all stores"""
|
"""remove an object from all stores"""
|
||||||
|
stores = stores or self.get_stores_for_object(obj)
|
||||||
pipeline = r.pipeline()
|
pipeline = r.pipeline()
|
||||||
for store in self.get_stores_for_object(obj):
|
for store in stores:
|
||||||
pipeline.zrem(store, -1, obj.id)
|
pipeline.zrem(store, -1, obj.id)
|
||||||
pipeline.execute()
|
pipeline.execute()
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,7 @@ INSTALLED_APPS = [
|
||||||
"django_rename_app",
|
"django_rename_app",
|
||||||
"bookwyrm",
|
"bookwyrm",
|
||||||
"celery",
|
"celery",
|
||||||
|
"imagekit",
|
||||||
"storages",
|
"storages",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -191,6 +192,9 @@ USER_AGENT = "%s (BookWyrm/%s; +https://%s/)" % (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Imagekit generated thumbnails
|
||||||
|
ENABLE_THUMBNAIL_GENERATION = env.bool("ENABLE_THUMBNAIL_GENERATION", False)
|
||||||
|
IMAGEKIT_CACHEFILE_DIR = "thumbnails"
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||||
|
|
|
@ -29,6 +29,10 @@ body {
|
||||||
min-width: 75% !important;
|
min-width: 75% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-card-body {
|
||||||
|
max-height: 70vh;
|
||||||
|
}
|
||||||
|
|
||||||
.clip-text {
|
.clip-text {
|
||||||
max-height: 35em;
|
max-height: 35em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -232,16 +236,21 @@ body {
|
||||||
/* Cover caption
|
/* Cover caption
|
||||||
* -------------------------------------------------------------------------- */
|
* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
.no-cover .cover_caption {
|
.no-cover .cover-caption {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
padding: 0.25em;
|
padding: 0.5em;
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
color: white;
|
color: white;
|
||||||
background-color: #002549;
|
background-color: #002549;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
white-space: initial;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Avatars
|
/** Avatars
|
||||||
|
|
Binary file not shown.
|
@ -33,13 +33,12 @@
|
||||||
<glyph unicode="" glyph-name="dots-three" d="M512.051 573.44c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64c62.208 0 112.589 50.432 112.589 112.64s-50.381 112.64-112.589 112.64zM153.651 573.44c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64c62.208 0 112.589 50.483 112.589 112.64s-50.381 112.64-112.589 112.64zM870.451 573.44c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64c62.208 0 112.589 50.432 112.589 112.64s-50.381 112.64-112.589 112.64z" />
|
<glyph unicode="" glyph-name="dots-three" d="M512.051 573.44c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64c62.208 0 112.589 50.432 112.589 112.64s-50.381 112.64-112.589 112.64zM153.651 573.44c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64c62.208 0 112.589 50.483 112.589 112.64s-50.381 112.64-112.589 112.64zM870.451 573.44c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64c62.208 0 112.589 50.432 112.589 112.64s-50.381 112.64-112.589 112.64z" />
|
||||||
<glyph unicode="" glyph-name="check" d="M424.653 102.502c-22.272 0-43.366 10.394-56.883 28.314l-182.938 241.715c-23.808 31.386-17.613 76.083 13.824 99.891 31.488 23.91 76.186 17.613 99.994-13.824l120.371-158.925 302.643 485.99c20.838 33.382 64.87 43.622 98.355 22.784 33.434-20.787 43.725-64.819 22.835-98.304l-357.581-573.952c-12.39-20.019-33.843-32.512-57.344-33.587-1.126-0.102-2.15-0.102-3.277-0.102z" />
|
<glyph unicode="" glyph-name="check" d="M424.653 102.502c-22.272 0-43.366 10.394-56.883 28.314l-182.938 241.715c-23.808 31.386-17.613 76.083 13.824 99.891 31.488 23.91 76.186 17.613 99.994-13.824l120.371-158.925 302.643 485.99c20.838 33.382 64.87 43.622 98.355 22.784 33.434-20.787 43.725-64.819 22.835-98.304l-357.581-573.952c-12.39-20.019-33.843-32.512-57.344-33.587-1.126-0.102-2.15-0.102-3.277-0.102z" />
|
||||||
<glyph unicode="" glyph-name="dots-three-vertical" d="M512.051 573.44c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64c62.208 0 112.589 50.432 112.589 112.64s-50.381 112.64-112.589 112.64zM512.051 706.56c62.208 0 112.589 50.483 112.589 112.64s-50.381 112.64-112.589 112.64c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64zM512.051 215.040c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64c62.208 0 112.589 50.432 112.589 112.64s-50.381 112.64-112.589 112.64z" />
|
<glyph unicode="" glyph-name="dots-three-vertical" d="M512.051 573.44c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64c62.208 0 112.589 50.432 112.589 112.64s-50.381 112.64-112.589 112.64zM512.051 706.56c62.208 0 112.589 50.483 112.589 112.64s-50.381 112.64-112.589 112.64c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64zM512.051 215.040c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64c62.208 0 112.589 50.432 112.589 112.64s-50.381 112.64-112.589 112.64z" />
|
||||||
<glyph unicode="" glyph-name="stars" d="M726.857 664.381l-38.857 103.619-38.857-103.619-73.143-24.381 73.143-24.381 38.857-103.619 38.857 103.619 73.143 24.381-73.143 24.381zM662.857 280.381l-38.857 103.619-38.857-103.619-73.143-24.381 73.143-24.381 38.857-103.619 38.857 103.619 73.143 24.381-73.143 24.381zM430.703 528.432l-62.703 175.568-62.703-175.568-145.297-48.432 145.297-48.432 62.703-175.568 62.703 175.568 145.297 48.432-145.297 48.432z" />
|
<glyph unicode="" glyph-name="bookmark" horiz-adv-x="731" d="M665.143 877.714c8.571 0 17.143-1.714 25.143-5.143 25.143-9.714 41.143-33.143 41.143-58.857v-736.571c0-25.714-16-49.143-41.143-58.857-8-3.429-16.571-4.571-25.143-4.571-17.714 0-34.286 6.286-47.429 18.286l-252 242.286-252-242.286c-13.143-12-29.714-18.857-47.429-18.857-8.571 0-17.143 1.714-25.143 5.143-25.143 9.714-41.143 33.143-41.143 58.857v736.571c0 25.714 16 49.143 41.143 58.857 8 3.429 16.571 5.143 25.143 5.143h598.857z" />
|
||||||
<glyph unicode="" glyph-name="warning" d="M907.5 204.9l-345.1 558.4c-27.8 44.9-73 45-100.8 0v0l-345.1-558.4c-37.3-60.3-10.2-108.9 60.4-108.9h670.2c70.5 0 97.6 48.7 60.4 108.9zM512 192c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zM544 351.9c0-17.4-14.3-31.9-32-31.9-17.8 0-32 14.3-32 31.9v192.2c0 17.4 14.3 31.9 32 31.9v0c17.8 0 32-14.3 32-31.9v-192.2z" />
|
<glyph unicode="" glyph-name="warning" d="M907.5 204.9l-345.1 558.4c-27.8 44.9-73 45-100.8 0v0l-345.1-558.4c-37.3-60.3-10.2-108.9 60.4-108.9h670.2c70.5 0 97.6 48.7 60.4 108.9zM512 192c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zM544 351.9c0-17.4-14.3-31.9-32-31.9-17.8 0-32 14.3-32 31.9v192.2c0 17.4 14.3 31.9 32 31.9v0c17.8 0 32-14.3 32-31.9v-192.2z" />
|
||||||
<glyph unicode="" glyph-name="bookmark" d="M800 32h-512v704h224v-292l113.312 86.016 110.688-86.016v292h128v-640c0-35.328-28.672-64-64-64zM625.312 572l-81.312-64v260h160v-260l-78.688 64zM192 800v-32c0-17.664 14.336-32 32-32h32v-704h-32c-35.328 0-64 28.672-64 64v704c0 35.328 28.672 64 64 64h576c23.616 0 44.032-12.928 55.136-32h-631.136c-17.664 0-32-14.304-32-32z" />
|
|
||||||
<glyph unicode="" glyph-name="rss" horiz-adv-x="805" d="M219.429 182.857c0-60.571-49.143-109.714-109.714-109.714s-109.714 49.143-109.714 109.714 49.143 109.714 109.714 109.714 109.714-49.143 109.714-109.714zM512 112.571c0.571-10.286-2.857-20-9.714-27.429-6.857-8-16.571-12-26.857-12h-77.143c-18.857 0-34.286 14.286-36 33.143-16.571 174.286-154.857 312.571-329.143 329.143-18.857 1.714-33.143 17.143-33.143 36v77.143c0 10.286 4 20 12 26.857 6.286 6.286 15.429 9.714 24.571 9.714h2.857c121.714-9.714 236.571-62.857 322.857-149.714 86.857-86.286 140-201.143 149.714-322.857zM804.571 111.428c0.571-9.714-2.857-19.429-10.286-26.857-6.857-7.429-16-11.429-26.286-11.429h-81.714c-19.429 0-35.429 14.857-36.571 34.286-18.857 332-283.429 596.571-615.429 616-19.429 1.143-34.286 17.143-34.286 36v81.714c0 10.286 4 19.429 11.429 26.286 6.857 6.857 16 10.286 25.143 10.286h1.714c200-10.286 388-94.286 529.714-236.571 142.286-141.714 226.286-329.714 236.571-529.714z" />
|
<glyph unicode="" glyph-name="rss" horiz-adv-x="805" d="M219.429 182.857c0-60.571-49.143-109.714-109.714-109.714s-109.714 49.143-109.714 109.714 49.143 109.714 109.714 109.714 109.714-49.143 109.714-109.714zM512 112.571c0.571-10.286-2.857-20-9.714-27.429-6.857-8-16.571-12-26.857-12h-77.143c-18.857 0-34.286 14.286-36 33.143-16.571 174.286-154.857 312.571-329.143 329.143-18.857 1.714-33.143 17.143-33.143 36v77.143c0 10.286 4 20 12 26.857 6.286 6.286 15.429 9.714 24.571 9.714h2.857c121.714-9.714 236.571-62.857 322.857-149.714 86.857-86.286 140-201.143 149.714-322.857zM804.571 111.428c0.571-9.714-2.857-19.429-10.286-26.857-6.857-7.429-16-11.429-26.286-11.429h-81.714c-19.429 0-35.429 14.857-36.571 34.286-18.857 332-283.429 596.571-615.429 616-19.429 1.143-34.286 17.143-34.286 36v81.714c0 10.286 4 19.429 11.429 26.286 6.857 6.857 16 10.286 25.143 10.286h1.714c200-10.286 388-94.286 529.714-236.571 142.286-141.714 226.286-329.714 236.571-529.714z" />
|
||||||
<glyph unicode="" glyph-name="heart1" d="M934.176 791.52c-116.128 115.072-301.824 117.472-422.112 9.216-120.32 108.256-305.952 105.856-422.144-9.216-119.712-118.528-119.712-310.688 0-429.28 34.208-33.888 353.696-350.112 353.696-350.112 37.856-37.504 99.072-37.504 136.896 0 0 0 349.824 346.304 353.696 350.112 119.744 118.592 119.744 310.752-0.032 429.28zM888.576 407.424l-353.696-350.112c-12.576-12.512-33.088-12.512-45.6 0l-353.696 350.112c-94.4 93.44-94.4 245.472 0 338.912 91.008 90.080 237.312 93.248 333.088 7.104l43.392-39.040 43.36 39.040c95.808 86.144 242.112 83.008 333.12-7.104 94.4-93.408 94.4-245.44 0.032-338.912zM296.096 719.968c8.864 0 16-7.168 16-16s-7.168-16-16-16h-0.032c-57.408 0-103.968-46.56-103.968-103.968v-0.032c0-8.832-7.168-16-16-16s-16 7.168-16 16v0c0 75.072 60.832 135.904 135.872 135.968 0.064 0 0.064 0.032 0.128 0.032z" />
|
<glyph unicode="" glyph-name="graphic-heart" d="M934.176 791.52c-116.128 115.072-301.824 117.472-422.112 9.216-120.32 108.256-305.952 105.856-422.144-9.216-119.712-118.528-119.712-310.688 0-429.28 34.208-33.888 353.696-350.112 353.696-350.112 37.856-37.504 99.072-37.504 136.896 0 0 0 349.824 346.304 353.696 350.112 119.744 118.592 119.744 310.752-0.032 429.28zM888.576 407.424l-353.696-350.112c-12.576-12.512-33.088-12.512-45.6 0l-353.696 350.112c-94.4 93.44-94.4 245.472 0 338.912 91.008 90.080 237.312 93.248 333.088 7.104l43.392-39.040 43.36 39.040c95.808 86.144 242.112 83.008 333.12-7.104 94.4-93.408 94.4-245.44 0.032-338.912zM296.096 719.968c8.864 0 16-7.168 16-16s-7.168-16-16-16h-0.032c-57.408 0-103.968-46.56-103.968-103.968v-0.032c0-8.832-7.168-16-16-16s-16 7.168-16 16v0c0 75.072 60.832 135.904 135.872 135.968 0.064 0 0.064 0.032 0.128 0.032z" />
|
||||||
<glyph unicode="" glyph-name="paperplane" d="M1009.376 954.88c-5.312 3.424-11.36 5.12-17.376 5.12-6.176 0-12.384-1.76-17.76-5.376l-960-640c-9.888-6.56-15.328-18.112-14.048-29.952 1.216-11.808 8.896-22.016 19.936-26.368l250.368-100.192 117.728-206.016c5.632-9.888 16.096-16 27.424-16.128 0.128 0 0.224 0 0.352 0 11.232 0 21.664 5.952 27.424 15.552l66.464 110.816 310.24-124.064c3.808-1.536 7.808-2.272 11.872-2.272 5.44 0 10.816 1.376 15.68 4.128 8.448 4.736 14.24 13.056 15.872 22.624l160 960c2.080 12.576-3.488 25.184-14.176 32.128zM100.352 295.136l741.6 494.432-539.2-577.184c-2.848 1.696-5.376 3.936-8.512 5.184l-193.888 77.568zM326.048 189.888c-0.064 0.128-0.16 0.192-0.224 0.32l606.176 648.8-516.768-805.184-89.184 156.064zM806.944 12.512l-273.312 109.312c-6.496 2.56-13.248 3.424-19.936 3.808l420.864 652.416-127.616-765.536z" />
|
<glyph unicode="" glyph-name="graphic-paperplane" d="M1009.376 954.88c-5.312 3.424-11.36 5.12-17.376 5.12-6.176 0-12.384-1.76-17.76-5.376l-960-640c-9.888-6.56-15.328-18.112-14.048-29.952 1.216-11.808 8.896-22.016 19.936-26.368l250.368-100.192 117.728-206.016c5.632-9.888 16.096-16 27.424-16.128 0.128 0 0.224 0 0.352 0 11.232 0 21.664 5.952 27.424 15.552l66.464 110.816 310.24-124.064c3.808-1.536 7.808-2.272 11.872-2.272 5.44 0 10.816 1.376 15.68 4.128 8.448 4.736 14.24 13.056 15.872 22.624l160 960c2.080 12.576-3.488 25.184-14.176 32.128zM100.352 295.136l741.6 494.432-539.2-577.184c-2.848 1.696-5.376 3.936-8.512 5.184l-193.888 77.568zM326.048 189.888c-0.064 0.128-0.16 0.192-0.224 0.32l606.176 648.8-516.768-805.184-89.184 156.064zM806.944 12.512l-273.312 109.312c-6.496 2.56-13.248 3.424-19.936 3.808l420.864 652.416-127.616-765.536z" />
|
||||||
<glyph unicode="" glyph-name="banknote" d="M1005.28 621.248l-320 320c-15.872 15.872-38.88 22.24-60.672 16.864-11.488-2.816-21.76-8.736-29.888-16.864-7.264-7.264-12.736-16.256-15.872-26.304-14.496-47.008-39.552-87.872-76.64-124.928-49.536-49.504-114.048-87.008-182.304-126.656-72.448-41.984-147.296-85.504-208.64-146.816-52.128-52.192-87.616-110.24-108.416-177.632-7.008-22.752-0.896-47.36 15.872-64.192l320-320c15.872-15.872 38.88-22.24 60.672-16.864 11.488 2.88 21.76 8.736 29.888 16.864 7.264 7.264 12.736 16.256 15.872 26.368 14.528 47.008 39.584 87.872 76.704 124.928 49.504 49.504 113.984 86.944 182.304 126.56 72.384 42.048 147.264 85.568 208.576 146.88 52.128 52.128 87.616 110.24 108.448 177.632 6.976 22.72 0.832 47.424-15.904 64.16zM384 0c-105.984 105.984-214.016 214.048-320 320 90.944 294.432 485.12 281.568 576 576 105.984-105.952 214.048-214.016 320.064-320-90.976-294.368-485.152-281.568-576.064-576zM625.984 483.2c-10.432 8.736-20.928 14.688-31.488 17.632-10.496 2.944-20.992 4.128-31.616 3.36-10.496-0.8-21.248-3.2-32-7.328-10.752-4.192-21.568-8.736-32.448-14.016-17.184 19.744-34.368 39.264-51.552 57.376 7.744 7.008 15.264 10.56 22.496 10.816 7.264 0.32 14.24-0.448 20.864-2.112 6.752-1.696 12.928-3.136 18.624-4.256 5.76-1.12 10.752 0.128 15.136 3.808 4.64 4 7.2 9.184 7.552 15.424 0.32 6.304-2.048 12.448-7.328 18.432-6.752 7.744-14.88 12.448-24.64 14.176-9.632 1.696-19.488 1.568-29.76-0.672-10.112-2.304-19.744-6.112-28.864-11.488s-16.448-10.88-21.888-16.256c-2.080 1.984-4.16 3.936-6.24 5.888-2.304 2.112-5.184 3.264-8.64 3.2-3.488 0-6.368-1.504-8.736-4.256-2.304-2.688-3.36-5.824-2.944-9.12 0.32-3.424 1.696-6.048 4.064-8.064 2.080-1.76 4.16-3.488 6.24-5.312-8.192-9.888-14.944-20.8-20.256-32.32-5.376-11.552-8.576-23.008-9.76-34.112-1.248-11.2-0.064-21.44 3.36-30.944 3.424-9.568 9.76-17.696 19.008-25.376 15.072-12.512 32.8-17.824 53.376-16.64 20.512 1.248 42.624 7.36 66.4 20.128 18.88-21.824 37.824-43.488 56.736-63.616-8-6.752-15.008-10.624-21.184-11.872-6.176-1.312-11.68-1.184-16.672 0.32-4.992 1.568-9.632 3.808-13.888 6.688-4.256 2.944-8.448 5.44-12.64 7.488-4.128 2.048-8.384 3.2-12.736 3.264s-8.992-2.048-14.112-6.432c-5.248-4.576-7.872-9.888-7.872-15.872 0-5.952 2.752-12 8.128-18.112 5.44-6.112 12.512-11.264 21.056-15.328s18.208-6.624 28.832-7.328c10.624-0.736 21.824 0.864 33.632 5.248 11.872 4.32 23.616 12.128 35.2 23.744 5.568-5.44 11.2-10.624 16.8-15.616 2.368-2.048 5.248-3.072 8.736-2.816 3.36 0.128 6.304 1.696 8.64 4.512 2.368 2.88 3.36 6.048 3.008 9.376-0.32 3.36-1.696 5.952-4 7.808-5.632 4.512-11.264 9.248-16.864 14.24 9.568 11.744 17.248 24.128 22.944 36.384 5.696 12.32 9.056 24.192 10.176 35.2 1.12 11.072-0.192 21.056-3.808 30.112-3.584 9.184-9.952 17.056-19.072 24.64zM447.072 461.504c-9.056-0.384-16.96 2.624-23.872 9.312-2.944 2.816-4.992 6.24-6.24 10.304-1.312 4.064-1.76 8.512-1.248 13.376 0.448 4.8 1.888 9.824 4.384 14.88 2.368 5.056 5.888 10.112 10.368 15.008 16.224-16.128 32.416-33.824 48.64-52.128-12.288-6.752-22.976-10.368-32.032-10.752zM598.016 397.44c-2.88-5.312-6.176-10.048-10.048-14.176-17.952 18.112-35.872 38.016-53.76 58.432 4.576 2.048 9.376 4.192 14.56 6.368s10.368 3.616 15.552 4.512c5.312 0.8 10.56 0.576 15.808-0.672 5.184-1.312 10.112-4.128 14.688-8.576 4.512-4.512 7.36-9.184 8.512-14.24 1.248-5.12 1.312-10.304 0.448-15.616-0.928-5.344-2.816-10.656-5.76-16.032zM470.944 250.24c6.304 5.088 15.584 4.832 21.376-1.056 6.272-6.24 6.272-16.448 0-22.688-0.512-0.512-1.056-0.864-1.632-1.312l0.064-0.064c-20.256-15.392-36.896-29.248-54.848-47.2-16.224-16.192-30.88-33.248-43.552-50.56l-20.448-28c-0.64-1.152-1.408-2.208-2.368-3.2-6.272-6.24-16.48-6.24-22.72 0-5.44 5.44-6.112 13.824-2.112 20.064l-0.064 0.064 21.888 29.888c13.664 18.688 29.376 36.992 46.752 54.368 18.080 18.144 37.6 34.336 57.6 49.696h0.064zM588.096 713.12c16.192 16.192 30.816 33.184 43.52 50.592l21.248 29.12c0.768 1.376 1.632 2.752 2.816 3.936 6.304 6.304 16.512 6.304 22.816 0 5.984-6.016 6.24-15.52 0.8-21.888l0.064-0.064-21.888-30.016c-13.696-18.688-29.376-36.928-46.752-54.304-18.080-18.080-37.568-34.336-57.568-49.696l-0.128 0.064c-6.368-5.856-16.256-5.728-22.368 0.448-6.304 6.304-6.304 16.576 0 22.88 1.12 1.184 2.432 2.016 3.744 2.752 18.816 14.368 36.96 29.44 53.696 46.176z" />
|
<glyph unicode="" glyph-name="graphic-banknote" d="M1005.28 621.248l-320 320c-15.872 15.872-38.88 22.24-60.672 16.864-11.488-2.816-21.76-8.736-29.888-16.864-7.264-7.264-12.736-16.256-15.872-26.304-14.496-47.008-39.552-87.872-76.64-124.928-49.536-49.504-114.048-87.008-182.304-126.656-72.448-41.984-147.296-85.504-208.64-146.816-52.128-52.192-87.616-110.24-108.416-177.632-7.008-22.752-0.896-47.36 15.872-64.192l320-320c15.872-15.872 38.88-22.24 60.672-16.864 11.488 2.88 21.76 8.736 29.888 16.864 7.264 7.264 12.736 16.256 15.872 26.368 14.528 47.008 39.584 87.872 76.704 124.928 49.504 49.504 113.984 86.944 182.304 126.56 72.384 42.048 147.264 85.568 208.576 146.88 52.128 52.128 87.616 110.24 108.448 177.632 6.976 22.72 0.832 47.424-15.904 64.16zM384 0c-105.984 105.984-214.016 214.048-320 320 90.944 294.432 485.12 281.568 576 576 105.984-105.952 214.048-214.016 320.064-320-90.976-294.368-485.152-281.568-576.064-576zM625.984 483.2c-10.432 8.736-20.928 14.688-31.488 17.632-10.496 2.944-20.992 4.128-31.616 3.36-10.496-0.8-21.248-3.2-32-7.328-10.752-4.192-21.568-8.736-32.448-14.016-17.184 19.744-34.368 39.264-51.552 57.376 7.744 7.008 15.264 10.56 22.496 10.816 7.264 0.32 14.24-0.448 20.864-2.112 6.752-1.696 12.928-3.136 18.624-4.256 5.76-1.12 10.752 0.128 15.136 3.808 4.64 4 7.2 9.184 7.552 15.424 0.32 6.304-2.048 12.448-7.328 18.432-6.752 7.744-14.88 12.448-24.64 14.176-9.632 1.696-19.488 1.568-29.76-0.672-10.112-2.304-19.744-6.112-28.864-11.488s-16.448-10.88-21.888-16.256c-2.080 1.984-4.16 3.936-6.24 5.888-2.304 2.112-5.184 3.264-8.64 3.2-3.488 0-6.368-1.504-8.736-4.256-2.304-2.688-3.36-5.824-2.944-9.12 0.32-3.424 1.696-6.048 4.064-8.064 2.080-1.76 4.16-3.488 6.24-5.312-8.192-9.888-14.944-20.8-20.256-32.32-5.376-11.552-8.576-23.008-9.76-34.112-1.248-11.2-0.064-21.44 3.36-30.944 3.424-9.568 9.76-17.696 19.008-25.376 15.072-12.512 32.8-17.824 53.376-16.64 20.512 1.248 42.624 7.36 66.4 20.128 18.88-21.824 37.824-43.488 56.736-63.616-8-6.752-15.008-10.624-21.184-11.872-6.176-1.312-11.68-1.184-16.672 0.32-4.992 1.568-9.632 3.808-13.888 6.688-4.256 2.944-8.448 5.44-12.64 7.488-4.128 2.048-8.384 3.2-12.736 3.264s-8.992-2.048-14.112-6.432c-5.248-4.576-7.872-9.888-7.872-15.872 0-5.952 2.752-12 8.128-18.112 5.44-6.112 12.512-11.264 21.056-15.328s18.208-6.624 28.832-7.328c10.624-0.736 21.824 0.864 33.632 5.248 11.872 4.32 23.616 12.128 35.2 23.744 5.568-5.44 11.2-10.624 16.8-15.616 2.368-2.048 5.248-3.072 8.736-2.816 3.36 0.128 6.304 1.696 8.64 4.512 2.368 2.88 3.36 6.048 3.008 9.376-0.32 3.36-1.696 5.952-4 7.808-5.632 4.512-11.264 9.248-16.864 14.24 9.568 11.744 17.248 24.128 22.944 36.384 5.696 12.32 9.056 24.192 10.176 35.2 1.12 11.072-0.192 21.056-3.808 30.112-3.584 9.184-9.952 17.056-19.072 24.64zM447.072 461.504c-9.056-0.384-16.96 2.624-23.872 9.312-2.944 2.816-4.992 6.24-6.24 10.304-1.312 4.064-1.76 8.512-1.248 13.376 0.448 4.8 1.888 9.824 4.384 14.88 2.368 5.056 5.888 10.112 10.368 15.008 16.224-16.128 32.416-33.824 48.64-52.128-12.288-6.752-22.976-10.368-32.032-10.752zM598.016 397.44c-2.88-5.312-6.176-10.048-10.048-14.176-17.952 18.112-35.872 38.016-53.76 58.432 4.576 2.048 9.376 4.192 14.56 6.368s10.368 3.616 15.552 4.512c5.312 0.8 10.56 0.576 15.808-0.672 5.184-1.312 10.112-4.128 14.688-8.576 4.512-4.512 7.36-9.184 8.512-14.24 1.248-5.12 1.312-10.304 0.448-15.616-0.928-5.344-2.816-10.656-5.76-16.032zM470.944 250.24c6.304 5.088 15.584 4.832 21.376-1.056 6.272-6.24 6.272-16.448 0-22.688-0.512-0.512-1.056-0.864-1.632-1.312l0.064-0.064c-20.256-15.392-36.896-29.248-54.848-47.2-16.224-16.192-30.88-33.248-43.552-50.56l-20.448-28c-0.64-1.152-1.408-2.208-2.368-3.2-6.272-6.24-16.48-6.24-22.72 0-5.44 5.44-6.112 13.824-2.112 20.064l-0.064 0.064 21.888 29.888c13.664 18.688 29.376 36.992 46.752 54.368 18.080 18.144 37.6 34.336 57.6 49.696h0.064zM588.096 713.12c16.192 16.192 30.816 33.184 43.52 50.592l21.248 29.12c0.768 1.376 1.632 2.752 2.816 3.936 6.304 6.304 16.512 6.304 22.816 0 5.984-6.016 6.24-15.52 0.8-21.888l0.064-0.064-21.888-30.016c-13.696-18.688-29.376-36.928-46.752-54.304-18.080-18.080-37.568-34.336-57.568-49.696l-0.128 0.064c-6.368-5.856-16.256-5.728-22.368 0.448-6.304 6.304-6.304 16.576 0 22.88 1.12 1.184 2.432 2.016 3.744 2.752 18.816 14.368 36.96 29.44 53.696 46.176z" />
|
||||||
<glyph unicode="" glyph-name="search" d="M992.262 88.604l-242.552 206.294c-25.074 22.566-51.89 32.926-73.552 31.926 57.256 67.068 91.842 154.078 91.842 249.176 0 212.078-171.922 384-384 384-212.076 0-384-171.922-384-384s171.922-384 384-384c95.098 0 182.108 34.586 249.176 91.844-1-21.662 9.36-48.478 31.926-73.552l206.294-242.552c35.322-39.246 93.022-42.554 128.22-7.356s31.892 92.898-7.354 128.22zM384 320c-141.384 0-256 114.616-256 256s114.616 256 256 256 256-114.616 256-256-114.614-256-256-256z" />
|
<glyph unicode="" glyph-name="search" d="M992.262 88.604l-242.552 206.294c-25.074 22.566-51.89 32.926-73.552 31.926 57.256 67.068 91.842 154.078 91.842 249.176 0 212.078-171.922 384-384 384-212.076 0-384-171.922-384-384s171.922-384 384-384c95.098 0 182.108 34.586 249.176 91.844-1-21.662 9.36-48.478 31.926-73.552l206.294-242.552c35.322-39.246 93.022-42.554 128.22-7.356s31.892 92.898-7.354 128.22zM384 320c-141.384 0-256 114.616-256 256s114.616 256 256 256 256-114.616 256-256-114.614-256-256-256z" />
|
||||||
<glyph unicode="" glyph-name="star-empty" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-223.462-117.48 42.676 248.83-180.786 176.222 249.84 36.304 111.732 226.396 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />
|
<glyph unicode="" glyph-name="star-empty" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-223.462-117.48 42.676 248.83-180.786 176.222 249.84 36.304 111.732 226.396 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />
|
||||||
<glyph unicode="" glyph-name="star-half" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-0.942-0.496 0.942 570.768 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />
|
<glyph unicode="" glyph-name="star-half" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-0.942-0.496 0.942 570.768 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />
|
||||||
|
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 32 KiB |
Binary file not shown.
Binary file not shown.
128
bookwyrm/static/css/vendor/icons.css
vendored
128
bookwyrm/static/css/vendor/icons.css
vendored
|
@ -1,156 +1,150 @@
|
||||||
|
|
||||||
/** @todo Replace icons with SVG symbols.
|
|
||||||
@see https://www.youtube.com/watch?v=9xXBYcWgCHA */
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'icomoon';
|
font-family: 'icomoon';
|
||||||
src: url('../fonts/icomoon.eot?n5x55');
|
src: url('../fonts/icomoon.eot?19nagi');
|
||||||
src: url('../fonts/icomoon.eot?n5x55#iefix') format('embedded-opentype'),
|
src: url('../fonts/icomoon.eot?19nagi#iefix') format('embedded-opentype'),
|
||||||
url('../fonts/icomoon.ttf?n5x55') format('truetype'),
|
url('../fonts/icomoon.ttf?19nagi') format('truetype'),
|
||||||
url('../fonts/icomoon.woff?n5x55') format('woff'),
|
url('../fonts/icomoon.woff?19nagi') format('woff'),
|
||||||
url('../fonts/icomoon.svg?n5x55#icomoon') format('svg');
|
url('../fonts/icomoon.svg?19nagi#icomoon') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class^="icon-"], [class*=" icon-"] {
|
[class^="icon-"], [class*=" icon-"] {
|
||||||
/* use !important to prevent issues with browser extensions that change fonts */
|
/* use !important to prevent issues with browser extensions that change fonts */
|
||||||
font-family: 'icomoon' !important;
|
font-family: 'icomoon' !important;
|
||||||
speak: never;
|
speak: never;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-variant: normal;
|
font-variant: normal;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
|
||||||
/* Better Font Rendering =========== */
|
/* Better Font Rendering =========== */
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-graphic-heart:before {
|
.icon-graphic-heart:before {
|
||||||
content: "\e91e";
|
content: "\e91e";
|
||||||
}
|
}
|
||||||
.icon-graphic-paperplane:before {
|
.icon-graphic-paperplane:before {
|
||||||
content: "\e91f";
|
content: "\e91f";
|
||||||
}
|
}
|
||||||
.icon-graphic-banknote:before {
|
.icon-graphic-banknote:before {
|
||||||
content: "\e920";
|
content: "\e920";
|
||||||
}
|
|
||||||
.icon-stars:before {
|
|
||||||
content: "\e91a";
|
|
||||||
}
|
}
|
||||||
.icon-warning:before {
|
.icon-warning:before {
|
||||||
content: "\e91b";
|
content: "\e91b";
|
||||||
}
|
}
|
||||||
.icon-book:before {
|
.icon-book:before {
|
||||||
content: "\e900";
|
content: "\e900";
|
||||||
}
|
}
|
||||||
.icon-bookmark:before {
|
.icon-bookmark:before {
|
||||||
content: "\e91c";
|
content: "\e91a";
|
||||||
}
|
}
|
||||||
.icon-rss:before {
|
.icon-rss:before {
|
||||||
content: "\e91d";
|
content: "\e91d";
|
||||||
}
|
}
|
||||||
.icon-envelope:before {
|
.icon-envelope:before {
|
||||||
content: "\e901";
|
content: "\e901";
|
||||||
}
|
}
|
||||||
.icon-arrow-right:before {
|
.icon-arrow-right:before {
|
||||||
content: "\e902";
|
content: "\e902";
|
||||||
}
|
}
|
||||||
.icon-bell:before {
|
.icon-bell:before {
|
||||||
content: "\e903";
|
content: "\e903";
|
||||||
}
|
}
|
||||||
.icon-x:before {
|
.icon-x:before {
|
||||||
content: "\e904";
|
content: "\e904";
|
||||||
}
|
}
|
||||||
.icon-quote-close:before {
|
.icon-quote-close:before {
|
||||||
content: "\e905";
|
content: "\e905";
|
||||||
}
|
}
|
||||||
.icon-quote-open:before {
|
.icon-quote-open:before {
|
||||||
content: "\e906";
|
content: "\e906";
|
||||||
}
|
}
|
||||||
.icon-image:before {
|
.icon-image:before {
|
||||||
content: "\e907";
|
content: "\e907";
|
||||||
}
|
}
|
||||||
.icon-pencil:before {
|
.icon-pencil:before {
|
||||||
content: "\e908";
|
content: "\e908";
|
||||||
}
|
}
|
||||||
.icon-list:before {
|
.icon-list:before {
|
||||||
content: "\e909";
|
content: "\e909";
|
||||||
}
|
}
|
||||||
.icon-unlock:before {
|
.icon-unlock:before {
|
||||||
content: "\e90a";
|
content: "\e90a";
|
||||||
}
|
}
|
||||||
.icon-unlisted:before {
|
.icon-unlisted:before {
|
||||||
content: "\e90a";
|
content: "\e90a";
|
||||||
}
|
}
|
||||||
.icon-globe:before {
|
.icon-globe:before {
|
||||||
content: "\e90b";
|
content: "\e90b";
|
||||||
}
|
}
|
||||||
.icon-public:before {
|
.icon-public:before {
|
||||||
content: "\e90b";
|
content: "\e90b";
|
||||||
}
|
}
|
||||||
.icon-lock:before {
|
.icon-lock:before {
|
||||||
content: "\e90c";
|
content: "\e90c";
|
||||||
}
|
}
|
||||||
.icon-followers:before {
|
.icon-followers:before {
|
||||||
content: "\e90c";
|
content: "\e90c";
|
||||||
}
|
}
|
||||||
.icon-chain-broken:before {
|
.icon-chain-broken:before {
|
||||||
content: "\e90d";
|
content: "\e90d";
|
||||||
}
|
}
|
||||||
.icon-chain:before {
|
.icon-chain:before {
|
||||||
content: "\e90e";
|
content: "\e90e";
|
||||||
}
|
}
|
||||||
.icon-comments:before {
|
.icon-comments:before {
|
||||||
content: "\e90f";
|
content: "\e90f";
|
||||||
}
|
}
|
||||||
.icon-comment:before {
|
.icon-comment:before {
|
||||||
content: "\e910";
|
content: "\e910";
|
||||||
}
|
}
|
||||||
.icon-boost:before {
|
.icon-boost:before {
|
||||||
content: "\e911";
|
content: "\e911";
|
||||||
}
|
}
|
||||||
.icon-arrow-left:before {
|
.icon-arrow-left:before {
|
||||||
content: "\e912";
|
content: "\e912";
|
||||||
}
|
}
|
||||||
.icon-arrow-up:before {
|
.icon-arrow-up:before {
|
||||||
content: "\e913";
|
content: "\e913";
|
||||||
}
|
}
|
||||||
.icon-arrow-down:before {
|
.icon-arrow-down:before {
|
||||||
content: "\e914";
|
content: "\e914";
|
||||||
}
|
}
|
||||||
.icon-home:before {
|
.icon-home:before {
|
||||||
content: "\e915";
|
content: "\e915";
|
||||||
}
|
}
|
||||||
.icon-local:before {
|
.icon-local:before {
|
||||||
content: "\e916";
|
content: "\e916";
|
||||||
}
|
}
|
||||||
.icon-dots-three:before {
|
.icon-dots-three:before {
|
||||||
content: "\e917";
|
content: "\e917";
|
||||||
}
|
}
|
||||||
.icon-check:before {
|
.icon-check:before {
|
||||||
content: "\e918";
|
content: "\e918";
|
||||||
}
|
}
|
||||||
.icon-dots-three-vertical:before {
|
.icon-dots-three-vertical:before {
|
||||||
content: "\e919";
|
content: "\e919";
|
||||||
}
|
}
|
||||||
.icon-search:before {
|
.icon-search:before {
|
||||||
content: "\e986";
|
content: "\e986";
|
||||||
}
|
}
|
||||||
.icon-star-empty:before {
|
.icon-star-empty:before {
|
||||||
content: "\e9d7";
|
content: "\e9d7";
|
||||||
}
|
}
|
||||||
.icon-star-half:before {
|
.icon-star-half:before {
|
||||||
content: "\e9d8";
|
content: "\e9d8";
|
||||||
}
|
}
|
||||||
.icon-star-full:before {
|
.icon-star-full:before {
|
||||||
content: "\e9d9";
|
content: "\e9d9";
|
||||||
}
|
}
|
||||||
.icon-heart:before {
|
.icon-heart:before {
|
||||||
content: "\e9da";
|
content: "\e9da";
|
||||||
}
|
}
|
||||||
.icon-plus:before {
|
.icon-plus:before {
|
||||||
content: "\ea0a";
|
content: "\ea0a";
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,8 +138,11 @@ let BookWyrm = new class {
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
toggleAction(event) {
|
toggleAction(event) {
|
||||||
event.preventDefault();
|
|
||||||
let trigger = event.currentTarget;
|
let trigger = event.currentTarget;
|
||||||
|
|
||||||
|
if (!trigger.dataset.allowDefault || event.currentTarget == event.target) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
let pressed = trigger.getAttribute('aria-pressed') === 'false';
|
let pressed = trigger.getAttribute('aria-pressed') === 'false';
|
||||||
let targetId = trigger.dataset.controls;
|
let targetId = trigger.dataset.controls;
|
||||||
|
|
||||||
|
@ -177,6 +180,13 @@ let BookWyrm = new class {
|
||||||
this.toggleCheckbox(checkbox, pressed);
|
this.toggleCheckbox(checkbox, pressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Toggle form disabled, if appropriate
|
||||||
|
let disable = trigger.dataset.disables;
|
||||||
|
|
||||||
|
if (disable) {
|
||||||
|
this.toggleDisabled(disable, !pressed);
|
||||||
|
}
|
||||||
|
|
||||||
// Set focus, if appropriate.
|
// Set focus, if appropriate.
|
||||||
let focus = trigger.dataset.focusTarget;
|
let focus = trigger.dataset.focusTarget;
|
||||||
|
|
||||||
|
@ -227,6 +237,17 @@ let BookWyrm = new class {
|
||||||
document.getElementById(checkbox).checked = !!pressed;
|
document.getElementById(checkbox).checked = !!pressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable or disable a form element or fieldset
|
||||||
|
*
|
||||||
|
* @param {string} form_element - id of the element
|
||||||
|
* @param {boolean} pressed - Is the trigger pressed?
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
toggleDisabled(form_element, pressed) {
|
||||||
|
document.getElementById(form_element).disabled = !!pressed;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Give the focus to an element.
|
* Give the focus to an element.
|
||||||
* Only move the focus based on user interactions.
|
* Only move the focus based on user interactions.
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
"""Handles backends for storages"""
|
"""Handles backends for storages"""
|
||||||
|
import os
|
||||||
|
from tempfile import SpooledTemporaryFile
|
||||||
from storages.backends.s3boto3 import S3Boto3Storage
|
from storages.backends.s3boto3 import S3Boto3Storage
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,3 +17,33 @@ class ImagesStorage(S3Boto3Storage): # pylint: disable=abstract-method
|
||||||
location = "images"
|
location = "images"
|
||||||
default_acl = "public-read"
|
default_acl = "public-read"
|
||||||
file_overwrite = False
|
file_overwrite = False
|
||||||
|
|
||||||
|
"""
|
||||||
|
This is our custom version of S3Boto3Storage that fixes a bug in
|
||||||
|
boto3 where the passed in file is closed upon upload.
|
||||||
|
From:
|
||||||
|
https://github.com/matthewwithanm/django-imagekit/issues/391#issuecomment-275367006
|
||||||
|
https://github.com/boto/boto3/issues/929
|
||||||
|
https://github.com/matthewwithanm/django-imagekit/issues/391
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _save(self, name, content):
|
||||||
|
"""
|
||||||
|
We create a clone of the content file as when this is passed to
|
||||||
|
boto3 it wrongly closes the file upon upload where as the storage
|
||||||
|
backend expects it to still be open
|
||||||
|
"""
|
||||||
|
# Seek our content back to the start
|
||||||
|
content.seek(0, os.SEEK_SET)
|
||||||
|
|
||||||
|
# Create a temporary file that will write to disk after a specified
|
||||||
|
# size. This file will be automatically deleted when closed by
|
||||||
|
# boto3 or after exiting the `with` statement if the boto3 is fixed
|
||||||
|
with SpooledTemporaryFile() as content_autoclose:
|
||||||
|
|
||||||
|
# Write our original content into our copy that will be closed by boto3
|
||||||
|
content_autoclose.write(content.read())
|
||||||
|
|
||||||
|
# Upload the object which will auto close the
|
||||||
|
# content_autoclose instance
|
||||||
|
return super()._save(name, content_autoclose)
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
|
|
||||||
{% if author.wikipedia_link %}
|
{% if author.wikipedia_link %}
|
||||||
<p class="my-1">
|
<p class="my-1">
|
||||||
<a itemprop="sameAs" href="{{ author.wikipedia_link }}" rel=”noopener” target="_blank">
|
<a itemprop="sameAs" href="{{ author.wikipedia_link }}" rel="noopener" target="_blank">
|
||||||
{% trans "Wikipedia" %}
|
{% trans "Wikipedia" %}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -70,7 +70,7 @@
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if author.inventaire_id %}
|
{% if author.inventaire_id %}
|
||||||
<p class="my-1">
|
<p class="my-1">
|
||||||
<a itemprop="sameAs" href="https://inventaire.io/entity/{{ author.inventaire_id }}" target="_blank" rel="noopener">
|
<a itemprop="sameAs" href="https://inventaire.io/entity/{{ author.inventaire_id }}" target="_blank" rel="noopener">
|
||||||
|
@ -86,7 +86,7 @@
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if author.goodreads_key %}
|
{% if author.goodreads_key %}
|
||||||
<p class="my-1">
|
<p class="my-1">
|
||||||
<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">
|
||||||
|
@ -109,7 +109,7 @@
|
||||||
<div class="columns is-multiline is-mobile">
|
<div class="columns is-multiline is-mobile">
|
||||||
{% for book in books %}
|
{% for book in books %}
|
||||||
<div class="column is-one-fifth">
|
<div class="column is-one-fifth">
|
||||||
{% include 'discover/small-book.html' with book=book %}
|
{% include 'landing/small-book.html' with book=book %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
|
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-one-fifth">
|
<div class="column is-one-fifth">
|
||||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-m-mobile' %}
|
{% include 'snippets/book_cover.html' with size='xxlarge' size_mobile='medium' book=book cover_class='is-h-m-mobile' %}
|
||||||
{% include 'snippets/rate_action.html' with user=request.user book=book %}
|
{% include 'snippets/rate_action.html' with user=request.user book=book %}
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
@ -134,7 +134,7 @@
|
||||||
<form name="add-description" method="POST" action="/add-description/{{ book.id }}">
|
<form name="add-description" method="POST" action="/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>
|
||||||
<textarea name="description" cols="None" rows="None" class="textarea" id="id_description_{{ book.id }}"></textarea>
|
<textarea name="description" cols="None" rows="None" class="textarea" id="id_description_{{ book.id }}"></textarea>
|
||||||
</p>
|
</p>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
|
|
@ -42,11 +42,18 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if book %}
|
<form
|
||||||
<form class="block" name="edit-book" action="{{ book.local_path }}/{% if confirm_mode %}confirm{% else %}edit{% endif %}" method="post" enctype="multipart/form-data">
|
class="block"
|
||||||
{% else %}
|
{% if book %}
|
||||||
<form class="block" name="create-book" action="/create-book{% if confirm_mode %}/confirm{% endif %}" method="post" enctype="multipart/form-data">
|
name="edit-book"
|
||||||
{% endif %}
|
action="{{ book.local_path }}/{% if confirm_mode %}confirm{% else %}edit{% endif %}"
|
||||||
|
{% else %}
|
||||||
|
name="create-book"
|
||||||
|
action="/create-book{% if confirm_mode %}/confirm{% endif %}"
|
||||||
|
{% endif %}
|
||||||
|
method="post"
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
>
|
||||||
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if confirm_mode %}
|
{% if confirm_mode %}
|
||||||
|
@ -220,7 +227,7 @@
|
||||||
<h2 class="title is-4">{% trans "Cover" %}</h2>
|
<h2 class="title is-4">{% trans "Cover" %}</h2>
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-3 is-cover">
|
<div class="column is-3 is-cover">
|
||||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-xl-mobile is-w-auto-tablet' %}
|
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-xl-mobile is-w-auto-tablet' size_mobile='xlarge' size='large' %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column">
|
<div class="column">
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{% extends 'snippets/filters_panel/filters_panel.html' %}
|
{% extends 'snippets/filters_panel/filters_panel.html' %}
|
||||||
|
|
||||||
{% block filter_fields %}
|
{% block filter_fields %}
|
||||||
|
{% include 'book/search_filter.html' %}
|
||||||
{% include 'book/language_filter.html' %}
|
{% include 'book/language_filter.html' %}
|
||||||
{% include 'book/format_filter.html' %}
|
{% include 'book/format_filter.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<div class="columns is-gapless mb-6">
|
<div class="columns is-gapless mb-6">
|
||||||
<div class="column is-cover">
|
<div class="column is-cover">
|
||||||
<a href="{{ book.local_path }}">
|
<a href="{{ book.local_path }}">
|
||||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-w-m is-h-m align to-l-mobile' %}
|
{% include 'snippets/book_cover.html' with size='medium' book=book cover_class='is-w-m is-h-m align to-l-mobile' %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
{% trans "Progress Updates:" %}
|
{% trans "Progress Updates:" %}
|
||||||
</dl>
|
|
||||||
<ul>
|
<ul>
|
||||||
{% if readthrough.finish_date or readthrough.progress %}
|
{% if readthrough.finish_date or readthrough.progress %}
|
||||||
<li>
|
<li>
|
||||||
|
|
8
bookwyrm/templates/book/search_filter.html
Normal file
8
bookwyrm/templates/book/search_filter.html
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{% extends 'snippets/filters_panel/filter_field.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block filter %}
|
||||||
|
<label class="label" for="id_search">{% trans "Search editions" %}</label>
|
||||||
|
<input type="text" class="input" name="q" value="{{ request.GET.q|default:'' }}" id="id_search">
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<a
|
<a
|
||||||
class="align to-b to-l"
|
class="align to-b to-l"
|
||||||
href="{{ book.local_path }}"
|
href="{{ book.local_path }}"
|
||||||
>{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto-tablet' %}</a>
|
>{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto-tablet' size='xxlarge' %}</a>
|
||||||
|
|
||||||
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
||||||
<h3 class="title is-6">
|
<h3 class="title is-6">
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
{% if status.book or status.mention_books.exists %}
|
{% if status.book or status.mention_books.exists %}
|
||||||
{% load_book status as book %}
|
{% load_book status as book %}
|
||||||
<a href="{{ book.local_path }}">
|
<a href="{{ book.local_path }}">
|
||||||
{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto align to-b to-l' %}
|
{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto align to-b to-l' size='xxlarge' %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="block mt-2">
|
<div class="block mt-2">
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<html>
|
<html lang="{% get_lang %}">
|
||||||
<body>
|
<body>
|
||||||
<div>
|
<div>
|
||||||
<strong>Subject:</strong> {% include subject_path %}
|
<strong>Subject:</strong> {% include subject_path %}
|
||||||
|
|
|
@ -40,11 +40,10 @@
|
||||||
{% if suggested_users %}
|
{% if suggested_users %}
|
||||||
{# suggested users for when things are very lonely #}
|
{# suggested users for when things are very lonely #}
|
||||||
{% include 'feed/suggested_users.html' with suggested_users=suggested_users %}
|
{% include 'feed/suggested_users.html' with suggested_users=suggested_users %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% for activity in activities %}
|
{% for activity in activities %}
|
||||||
|
|
||||||
{% if not activities.number > 1 and forloop.counter0 == 2 and suggested_users %}
|
{% if not activities.number > 1 and forloop.counter0 == 2 and suggested_users %}
|
||||||
|
|
|
@ -2,5 +2,5 @@
|
||||||
<section class="block">
|
<section class="block">
|
||||||
<h2 class="title is-5">{% trans "Who to follow" %}</h2>
|
<h2 class="title is-5">{% trans "Who to follow" %}</h2>
|
||||||
{% include 'snippets/suggested_users.html' with suggested_users=suggested_users %}
|
{% include 'snippets/suggested_users.html' with suggested_users=suggested_users %}
|
||||||
<a class="help" href="{% url 'directory' %}">View directory <span class="icon icon-arrow-right"></a>
|
<a class="help" href="{% url 'directory' %}">{% trans "View directory" %} <span class="icon icon-arrow-right"></span></a>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="column is-cover">
|
<div class="column is-cover">
|
||||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-l is-h-m-mobile' %}
|
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-l is-h-m-mobile' size_mobile='medium' size='large' %}
|
||||||
|
|
||||||
<div class="select is-small mt-1 mb-3">
|
<div class="select is-small mt-1 mb-3">
|
||||||
<select name="{{ book.id }}" aria-label="{% blocktrans with book_title=book.title %}Have you read {{ book_title }}?{% endblocktrans %}">
|
<select name="{{ book.id }}" aria-label="{% blocktrans with book_title=book.title %}Have you read {{ book_title }}?{% endblocktrans %}">
|
||||||
|
|
|
@ -20,10 +20,10 @@
|
||||||
<dt class="has-text-weight-medium">{% trans "Import completed:" %}</dt>
|
<dt class="has-text-weight-medium">{% trans "Import completed:" %}</dt>
|
||||||
<dd class="ml-2">{{ task.date_done | naturaltime }}</dd>
|
<dd class="ml-2">{{ task.date_done | naturaltime }}</dd>
|
||||||
</div>
|
</div>
|
||||||
|
{% elif task.failed %}
|
||||||
|
<div class="notification is-danger">{% trans "TASK FAILED" %}</div>
|
||||||
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
{% elif task.failed %}
|
|
||||||
<div class="notification is-danger">{% trans "TASK FAILED" %}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
|
@ -84,26 +84,26 @@
|
||||||
|
|
||||||
<button class="button is-block mt-3" type="submit">{% trans "Retry items" %}</button>
|
<button class="button is-block mt-3" type="submit">{% trans "Retry items" %}</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
<ul>
|
|
||||||
{% for item in failed_items %}
|
|
||||||
<li class="pb-1">
|
|
||||||
<p>
|
|
||||||
Line {{ item.index }}:
|
|
||||||
<strong>{{ item.data.Title }}</strong> by
|
|
||||||
{{ item.data.Author }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{ item.fail_reason }}.
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<ul>
|
||||||
|
{% for item in failed_items %}
|
||||||
|
<li class="pb-1">
|
||||||
|
<p>
|
||||||
|
Line {{ item.index }}:
|
||||||
|
<strong>{{ item.data.Title }}</strong> by
|
||||||
|
{{ item.data.Author }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ item.fail_reason }}.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@
|
||||||
<td>
|
<td>
|
||||||
{% if item.book %}
|
{% if item.book %}
|
||||||
<a href="{{ item.book.local_path }}">
|
<a href="{{ item.book.local_path }}">
|
||||||
{% include 'snippets/book_cover.html' with book=item.book cover_class='is-h-s' %}
|
{% include 'snippets/book_cover.html' with book=item.book cover_class='is-h-s' size='small' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
@ -153,7 +153,6 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endspaceless %}{% endblock %}
|
{% endspaceless %}{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
{% extends 'layout.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{% trans "Search Results" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% with book_results|first as local_results %}
|
|
||||||
<div class="block">
|
|
||||||
<h1 class="title">{% blocktrans %}Search Results for "{{ query }}"{% endblocktrans %}</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="block columns">
|
|
||||||
<div class="column">
|
|
||||||
<h2 class="title">{% trans "Matching Books" %}</h2>
|
|
||||||
<section class="block">
|
|
||||||
{% if not results %}
|
|
||||||
<p>{% blocktrans %}No books found for "{{ query }}"{% endblocktrans %}</p>
|
|
||||||
{% else %}
|
|
||||||
<ul>
|
|
||||||
{% for result in results %}
|
|
||||||
<li class="pd-4">
|
|
||||||
<a href="{{ result.key }}">{% include 'snippets/search_result_text.html' with result=result link=True %}</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="column">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock %}
|
|
|
@ -9,7 +9,7 @@
|
||||||
<a
|
<a
|
||||||
class="align to-b to-l"
|
class="align to-b to-l"
|
||||||
href="{{ book.local_path }}"
|
href="{{ book.local_path }}"
|
||||||
>{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto-tablet' %}</a>
|
>{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto-tablet' size='xxlarge' %}</a>
|
||||||
|
|
||||||
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
{% if book %}
|
{% if book %}
|
||||||
{% with book=book %}
|
{% with book=book %}
|
||||||
<a href="{{ book.local_path }}">
|
<a href="{{ book.local_path }}">
|
||||||
{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto align to-b to-l' %}
|
{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto align to-b to-l' size='xxlarge' %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div role="button" tabindex="0" class="navbar-burger pulldown-menu" data-controls="main-nav" aria-expanded="false">
|
<div role="button" tabindex="0" class="navbar-burger pulldown-menu" data-controls="main_nav" aria-expanded="false">
|
||||||
<div class="navbar-item mt-3">
|
<div class="navbar-item mt-3">
|
||||||
<div class="icon icon-dots-three-vertical" title="{% trans 'Main navigation menu' %}">
|
<div class="icon icon-dots-three-vertical" title="{% trans 'Main navigation menu' %}">
|
||||||
<span class="is-sr-only">{% trans "Main navigation menu" %}</span>
|
<span class="is-sr-only">{% trans "Main navigation menu" %}</span>
|
||||||
|
|
39
bookwyrm/templates/lists/bookmark_button.html
Normal file
39
bookwyrm/templates/lists/bookmark_button.html
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
{% load i18n %}
|
||||||
|
{% load interaction %}
|
||||||
|
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
|
||||||
|
{% with request.user|saved:list as saved %}
|
||||||
|
<form
|
||||||
|
name="save-{{ list.id }}"
|
||||||
|
action="{% url 'list-save' list.id %}"
|
||||||
|
method="POST"
|
||||||
|
class="interaction save_{{ list.id }} {% if saved %}is-hidden{% endif %}"
|
||||||
|
data-id="save_{{ list.id }}"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
{% trans "Save" as text %}
|
||||||
|
<button class="button" type="submit">
|
||||||
|
<span class="icon icon-bookmark m-0-mobile" title="{{ text }}"></span>
|
||||||
|
<span class="is-sr-only-mobile">{{ text }}</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form
|
||||||
|
name="unsave-{{ list.id }}"
|
||||||
|
action="{% url 'list-unsave' list.id %}"
|
||||||
|
method="POST"
|
||||||
|
class="interaction save_{{ list.id }} active {% if not saved %}is-hidden{% endif %}"
|
||||||
|
data-id="save_{{ list.id }}"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
{% trans "Un-save" as text %}
|
||||||
|
<button class="button" type="submit">
|
||||||
|
<span class="icon icon-bookmark m-0-mobile has-text-primary" title="{{ text }}"></span>
|
||||||
|
<span class="is-sr-only-mobile">{{ text }}</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
href="{{ book.local_path }}"
|
href="{{ book.local_path }}"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
{% include 'snippets/book_cover.html' with cover_class='is-w-xs-mobile is-w-s is-h-xs-mobile is-h-s' %}
|
{% include 'snippets/book_cover.html' with cover_class='is-w-xs-mobile is-w-s is-h-xs-mobile is-h-s' size_mobile='xsmall' size='small' %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="column ml-3">
|
<div class="column ml-3">
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
>
|
>
|
||||||
<div class="column is-3-mobile is-2-tablet is-cover align to-t">
|
<div class="column is-3-mobile is-2-tablet is-cover align to-t">
|
||||||
<a href="{{ item.book.local_path }}" aria-hidden="true">
|
<a href="{{ item.book.local_path }}" aria-hidden="true">
|
||||||
{% include 'snippets/book_cover.html' with cover_class='is-w-auto is-h-m-tablet is-align-items-flex-start' %}
|
{% include 'snippets/book_cover.html' with cover_class='is-w-auto is-h-m-tablet is-align-items-flex-start' size='medium' %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@
|
||||||
href="{{ book.local_path }}"
|
href="{{ book.local_path }}"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-w-auto is-h-s-mobile align to-t' %}
|
{% include 'snippets/book_cover.html' with book=book cover_class='is-w-auto is-h-s-mobile align to-t' size='small' %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="column ml-3">
|
<div class="column ml-3">
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
{% load i18n %}
|
||||||
{% load markdown %}
|
{% load markdown %}
|
||||||
|
{% load interaction %}
|
||||||
|
|
||||||
<div class="columns is-multiline">
|
<div class="columns is-multiline">
|
||||||
{% for list in lists %}
|
{% for list in lists %}
|
||||||
<div class="column is-one-quarter">
|
<div class="column is-one-quarter">
|
||||||
|
@ -7,6 +10,14 @@
|
||||||
<h4 class="card-header-title">
|
<h4 class="card-header-title">
|
||||||
<a href="{{ list.local_path }}">{{ list.name }}</a> <span class="subtitle">{% include 'snippets/privacy-icons.html' with item=list %}</span>
|
<a href="{{ list.local_path }}">{{ list.name }}</a> <span class="subtitle">{% include 'snippets/privacy-icons.html' with item=list %}</span>
|
||||||
</h4>
|
</h4>
|
||||||
|
{% if request.user.is_authenticated and request.user|saved:list %}
|
||||||
|
<div class="card-header-icon">
|
||||||
|
{% trans "Saved" as text %}
|
||||||
|
<span class="icon icon-bookmark has-text-grey" title="{{ text }}">
|
||||||
|
<span class="is-sr-only">{{ text }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{% with list_books=list.listitem_set.all|slice:5 %}
|
{% with list_books=list.listitem_set.all|slice:5 %}
|
||||||
|
@ -14,7 +25,7 @@
|
||||||
<div class="card-image columns is-mobile is-gapless is-clipped">
|
<div class="card-image columns is-mobile is-gapless is-clipped">
|
||||||
{% for book in list_books %}
|
{% for book in list_books %}
|
||||||
<a class="column is-cover" href="{{ book.book.local_path }}">
|
<a class="column is-cover" href="{{ book.book.local_path }}">
|
||||||
{% include 'snippets/book_cover.html' with book=book.book cover_class='is-h-s' aria='show' %}
|
{% include 'snippets/book_cover.html' with book=book.book cover_class='is-h-s' size='small' aria='show' %}
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,12 +11,13 @@
|
||||||
{% include 'lists/created_text.html' with list=list %}
|
{% include 'lists/created_text.html' with list=list %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% if request.user == list.user %}
|
<div class="column is-narrow is-flex">
|
||||||
<div class="column is-narrow">
|
{% if request.user == list.user %}
|
||||||
{% trans "Edit List" as button_text %}
|
{% trans "Edit List" as button_text %}
|
||||||
{% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="pencil" controls_text="edit_list" focus="edit_list_header" %}
|
{% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="pencil" controls_text="edit_list" focus="edit_list_header" %}
|
||||||
|
{% endif %}
|
||||||
|
{% include "lists/bookmark_button.html" with list=list %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="block content">
|
<div class="block content">
|
||||||
|
|
|
@ -27,6 +27,21 @@
|
||||||
{% include 'lists/create_form.html' with controls_text="create_list" %}
|
{% include 'lists/create_form.html' with controls_text="create_list" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
<nav class="tabs">
|
||||||
|
<ul>
|
||||||
|
{% url 'lists' as url %}
|
||||||
|
<li{% if request.path in url %} class="is-active"{% endif %}>
|
||||||
|
<a href="{{ url }}">{% trans "All Lists" %}</a>
|
||||||
|
</li>
|
||||||
|
{% url 'saved-lists' as url %}
|
||||||
|
<li{% if url in request.path %} class="is-active"{% endif %}>
|
||||||
|
<a href="{{ url }}">{% trans "Saved Lists" %}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if lists %}
|
{% if lists %}
|
||||||
<section class="block">
|
<section class="block">
|
||||||
{% include 'lists/list_items.html' with lists=lists %}
|
{% include 'lists/list_items.html' with lists=lists %}
|
||||||
|
|
|
@ -9,6 +9,6 @@ Finish "{{ book_title }}"
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% include "snippets/shelve_button/finish_reading_modal.html" with book=book active=True %}
|
{% include "snippets/reading_modals/finish_reading_modal.html" with book=book active=True %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -9,6 +9,6 @@ Start "{{ book_title }}"
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% include "snippets/shelve_button/start_reading_modal.html" with book=book active=True %}
|
{% include "snippets/reading_modals/start_reading_modal.html" with book=book active=True %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -9,6 +9,6 @@ Want to Read "{{ book_title }}"
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% include "snippets/shelve_button/want_to_read_modal.html" with book=book active=True %}
|
{% include "snippets/reading_modals/want_to_read_modal.html" with book=book active=True %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
{% block edit-button %}
|
{% block edit-button %}
|
||||||
{% trans "Create Announcement" as button_text %}
|
{% trans "Create Announcement" as button_text %}
|
||||||
{% include 'snippets/toggle/open_button.html' with controls_text="create_announcement" icon_with_text="plus" text=button_text focus="create_announcement_header" %}
|
{% include 'snippets/toggle/open_button.html' with controls_text="create_announcement" icon_with_text="plus" text=button_text focus="create_announcement_header" %}
|
||||||
</a>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block panel %}
|
{% block panel %}
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for invite in invites %}
|
{% for invite in invites %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{ invite.link }}">{{ invite.link }}</td>
|
<td><a href="{{ invite.link }}">{{ invite.link }}</a></td>
|
||||||
<td>{{ invite.expiry|naturaltime }}</td>
|
<td>{{ invite.expiry|naturaltime }}</td>
|
||||||
<td>{{ invite.use_limit }}</td>
|
<td>{{ invite.use_limit }}</td>
|
||||||
<td>{{ invite.times_used }}</td>
|
<td>{{ invite.times_used }}</td>
|
||||||
|
|
|
@ -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">FediBlock</a>, with a list of entries that have <code>instance</code> and <code>url</code> fields. For example:
|
||||||
<pre>
|
<pre>
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,41 +2,67 @@
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load imagekit %}
|
||||||
|
{% load utilities %}
|
||||||
|
|
||||||
<figure
|
{% if book.cover %}
|
||||||
class="
|
<picture class="cover-container {{ cover_class }}">
|
||||||
cover-container
|
{% if external_path %}
|
||||||
{{ cover_class }}
|
<img
|
||||||
|
class="book-cover"
|
||||||
{% if not book.cover %}
|
src="{{ book.cover }}"
|
||||||
no-cover
|
itemprop="thumbnailUrl"
|
||||||
{% endif %}
|
alt="{{ book.alt_text|default:'' }}"
|
||||||
"
|
>
|
||||||
|
|
||||||
{% if book.alt_text %}
|
|
||||||
title="{{ book.alt_text }}"
|
|
||||||
{% endif %}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
class="book-cover"
|
|
||||||
|
|
||||||
{% if book.cover %}
|
|
||||||
src="{% if img_path is None %}{% get_media_prefix %}{% else %}{{ img_path }}{% endif %}{{ book.cover }}"
|
|
||||||
itemprop="thumbnailUrl"
|
|
||||||
|
|
||||||
{% if book.alt_text %}
|
|
||||||
alt="{{ book.alt_text }}"
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
|
{% if thumbnail_generation_enabled %}
|
||||||
|
|
||||||
|
{% if size_mobile %}
|
||||||
|
<source
|
||||||
|
media="(max-width: 768px)"
|
||||||
|
type="image/webp"
|
||||||
|
srcset="{% get_book_cover_thumbnail book=book size=size_mobile ext='webp' %}"
|
||||||
|
/>
|
||||||
|
<source
|
||||||
|
media="(max-width: 768px)"
|
||||||
|
type="image/jpg"
|
||||||
|
srcset="{% get_book_cover_thumbnail book=book size=size_mobile ext='jpg' %}"
|
||||||
|
/>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<source
|
||||||
|
type="image/webp"
|
||||||
|
srcset="{% get_book_cover_thumbnail book=book size=size ext='webp' %}"
|
||||||
|
/>
|
||||||
|
<source
|
||||||
|
type="image/jpg"
|
||||||
|
srcset="{% get_book_cover_thumbnail book=book size=size ext='jpg' %}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<img
|
||||||
|
alt="{{ book.alt_text|default:'' }}"
|
||||||
|
class="book-cover"
|
||||||
|
itemprop="thumbnailUrl"
|
||||||
|
src="{% if img_path is None %}{% get_media_prefix %}{% else %}{{ img_path }}{% endif %}{{ book.cover }}"
|
||||||
|
>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
</picture>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not book.cover and book.alt_text %}
|
||||||
|
<figure class="cover-container no-cover {{ cover_class }}">
|
||||||
|
<img
|
||||||
|
class="book-cover"
|
||||||
src="{% static "images/no_cover.jpg" %}"
|
src="{% static "images/no_cover.jpg" %}"
|
||||||
alt="{% trans "No cover" %}"
|
alt="{% trans "No cover" %}"
|
||||||
{% endif %}
|
>
|
||||||
>
|
<figcaption class="cover-caption">
|
||||||
|
|
||||||
{% if not book.cover and book.alt_text %}
|
|
||||||
<figcaption class="cover_caption">
|
|
||||||
<p>{{ book.alt_text }}</p>
|
<p>{{ book.alt_text }}</p>
|
||||||
</figcaption>
|
</figcaption>
|
||||||
{% endif %}
|
</figure>
|
||||||
</figure>
|
{% endif %}
|
||||||
{% endspaceless %}
|
{% endspaceless %}
|
||||||
|
|
|
@ -14,6 +14,6 @@ draft: an existing Status object that is providing default values for input fiel
|
||||||
id="id_content_{{ type }}_{{ book.id }}{{ reply_parent.id }}"
|
id="id_content_{{ type }}_{{ book.id }}{{ reply_parent.id }}"
|
||||||
placeholder="{{ placeholder }}"
|
placeholder="{{ placeholder }}"
|
||||||
aria-label="{% if reply_parent %}{% trans 'Reply' %}{% else %}{% trans 'Content' %}{% endif %}"
|
aria-label="{% if reply_parent %}{% trans 'Reply' %}{% else %}{% trans 'Content' %}{% endif %}"
|
||||||
{% if type != "quotation" %}required{% endif %}
|
{% if not optional and type != "quotation" %}required{% endif %}
|
||||||
>{% if reply_parent %}{{ reply_parent|mentions:request.user }}{% endif %}{% if mention %}@{{ mention|username }} {% endif %}{{ draft.content|default:'' }}</textarea>
|
>{% if reply_parent %}{{ reply_parent|mentions:request.user }}{% endif %}{% if mention %}@{{ mention|username }} {% endif %}{{ draft.content|default:'' }}</textarea>
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,27 @@
|
||||||
|
|
||||||
{% with status.id|uuid as uuid %}
|
{% with status.id|uuid as uuid %}
|
||||||
{% with request.user|liked:status as liked %}
|
{% with request.user|liked:status as liked %}
|
||||||
<form name="favorite" action="/favorite/{{ status.id }}" method="POST" class="interaction fav_{{ status.id }}_{{ uuid }} {% if liked %}is-hidden{% endif %}" data-id="fav_{{ status.id }}_{{ uuid }}">
|
<form
|
||||||
|
name="favorite"
|
||||||
|
action="{% url 'fav' status.id %}"
|
||||||
|
method="POST"
|
||||||
|
class="interaction fav_{{ status.id }}_{{ uuid }} {% if liked %}is-hidden{% endif %}"
|
||||||
|
data-id="fav_{{ status.id }}_{{ uuid }}"
|
||||||
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="button is-small is-light is-transparent" type="submit">
|
<button class="button is-small is-light is-transparent" type="submit">
|
||||||
<span class="icon icon-heart m-0-mobile" title="{% trans 'Like' %}">
|
<span class="icon icon-heart m-0-mobile" title="{% trans 'Like' %}"></span>
|
||||||
</span>
|
|
||||||
<span class="is-sr-only-mobile">{% trans "Like" %}</span>
|
<span class="is-sr-only-mobile">{% trans "Like" %}</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<form name="unfavorite" action="/unfavorite/{{ status.id }}" method="POST" class="interaction fav_{{ status.id }}_{{ uuid }} active {% if not liked %}is-hidden{% endif %}" data-id="fav_{{ status.id }}_{{ uuid }}">
|
|
||||||
|
<form
|
||||||
|
name="unfavorite"
|
||||||
|
action="{% url 'unfav' status.id %}"
|
||||||
|
method="POST"
|
||||||
|
class="interaction fav_{{ status.id }}_{{ uuid }} active {% if not liked %}is-hidden{% endif %}"
|
||||||
|
data-id="fav_{{ status.id }}_{{ uuid }}"
|
||||||
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="button is-light is-transparent is-small" type="submit">
|
<button class="button is-light is-transparent is-small" type="submit">
|
||||||
<span class="icon icon-heart has-text-primary m-0-mobile" title="{% trans 'Un-like' %}"></span>
|
<span class="icon icon-heart has-text-primary m-0-mobile" title="{% trans 'Un-like' %}"></span>
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
{% extends 'snippets/reading_modals/layout.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load utilities %}
|
||||||
|
|
||||||
|
{% block modal-title %}
|
||||||
|
{% blocktrans trimmed with book_title=book|book_title %}
|
||||||
|
Finish "<em>{{ book_title }}</em>"
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-form-open %}
|
||||||
|
<form name="finish-reading" action="{% url 'reading-status' 'finish' book.id %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="reading_status" value="read">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block reading-dates %}
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-half">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="finish_id_start_date_{{ uuid }}">
|
||||||
|
{% trans "Started reading" %}
|
||||||
|
</label>
|
||||||
|
<input type="date" name="start_date" class="input" id="finish_id_start_date_{{ uuid }}" value="{{ readthrough.start_date | date:"Y-m-d" }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-half">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="id_finish_date_{{ uuid }}">
|
||||||
|
{% trans "Finished reading" %}
|
||||||
|
</label>
|
||||||
|
<input type="date" name="finish_date" class="input" id="id_finish_date_{{ uuid }}" value="{% now "Y-m-d" %}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
15
bookwyrm/templates/snippets/reading_modals/form.html
Normal file
15
bookwyrm/templates/snippets/reading_modals/form.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{% extends "snippets/create_status/layout.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block form_open %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content_label %}
|
||||||
|
{% trans "Comment:" %}
|
||||||
|
<span class="help mt-0 has-text-weight-normal">{% trans "(Optional)" %}</span>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block initial_fields %}
|
||||||
|
<input type="hidden" name="user" value="{{ request.user.id }}">
|
||||||
|
<input type="hidden" name="mention_books" value="{{ book.id }}">
|
||||||
|
<input type="hidden" name="book" value="{{ book.id }}">
|
||||||
|
{% endblock %}
|
28
bookwyrm/templates/snippets/reading_modals/layout.html
Normal file
28
bookwyrm/templates/snippets/reading_modals/layout.html
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{% extends 'components/modal.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load utilities %}
|
||||||
|
|
||||||
|
{% block modal-body %}
|
||||||
|
|
||||||
|
{% block reading-dates %}{% endblock %}
|
||||||
|
|
||||||
|
{% with 0|uuid as local_uuid %}
|
||||||
|
<div class="is-flex is-justify-content-space-between">
|
||||||
|
<label for="post_status_{{ local_uuid }}_{{ uuid }}" data-controls="reading_content_{{ local_uuid }}_{{ uuid }}" data-controls-checkbox="post_status_{{ local_uuid }}_{{ uuid }}" data-disables="reading_content_fieldset_{{ local_uuid }}_{{ uuid }}" aria-pressed="true" data-allow-default="true">
|
||||||
|
<input type="checkbox" name="post-status" class="checkbox" id="post_status_{{ local_uuid }}_{{ uuid }}" checked>
|
||||||
|
{% trans "Post to feed" %}
|
||||||
|
</label>
|
||||||
|
<div class="is-hidden" id="hide_reading_content_{{ local_uuid }}_{{ uuid }}">
|
||||||
|
<button class="button is-link" type="submit">{% trans "Save" %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="reading_content_{{ local_uuid }}_{{ uuid }}">
|
||||||
|
<hr aria-hidden="true">
|
||||||
|
<fieldset id="reading_content_fieldset_{{ local_uuid }}_{{ uuid }}">
|
||||||
|
{% include "snippets/reading_modals/form.html" with optional=True %}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,24 @@
|
||||||
|
{% extends 'snippets/reading_modals/layout.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load utilities %}
|
||||||
|
|
||||||
|
{% block modal-title %}
|
||||||
|
{% blocktrans trimmed with book_title=book|book_title %}
|
||||||
|
Start "<em>{{ book_title }}</em>"
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-form-open %}
|
||||||
|
<form name="start-reading" action="{% url 'reading-status' 'start' book.id %}" method="post">
|
||||||
|
<input type="hidden" name="reading_status" value="reading">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block reading-dates %}
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="start_id_start_date_{{ uuid }}">
|
||||||
|
{% trans "Started reading" %}
|
||||||
|
</label>
|
||||||
|
<input type="date" name="start_date" class="input" id="start_id_start_date_{{ uuid }}" value="{% now "Y-m-d" %}">
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,15 @@
|
||||||
|
{% extends 'snippets/reading_modals/layout.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load utilities %}
|
||||||
|
|
||||||
|
{% block modal-title %}
|
||||||
|
{% blocktrans trimmed with book_title=book|book_title %}
|
||||||
|
Want to Read "<em>{{ book_title }}</em>"
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-form-open %}
|
||||||
|
<form name="shelve" action="{% url 'reading-status' 'want' book.id %}" method="post">
|
||||||
|
<input type="hidden" name="reading_status" value="to-read">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% endblock %}
|
|
@ -1,7 +1,7 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="columns is-mobile is-gapless">
|
<div class="columns is-mobile is-gapless">
|
||||||
<div class="column is-cover">
|
<div class="column is-cover">
|
||||||
{% include 'snippets/book_cover.html' with book=result cover_class='is-w-xs is-h-xs' img_path=false %}
|
{% include 'snippets/book_cover.html' with book=result cover_class='is-w-xs is-h-xs' external_path=True %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column is-10 ml-3">
|
<div class="column is-10 ml-3">
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
<a
|
<a
|
||||||
href="{{ result.view_link|default:result.key }}"
|
href="{{ result.view_link|default:result.key }}"
|
||||||
{% if remote_result %}
|
{% if remote_result %}
|
||||||
rel=”noopener”
|
rel="noopener"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
>{{ result.title }}</a>
|
>{{ result.title }}</a>
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
{% extends 'components/modal.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block modal-title %}
|
|
||||||
{% blocktrans with book_title=book.title %}Finish "<em>{{ book_title }}</em>"{% endblocktrans %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block modal-form-open %}
|
|
||||||
<form name="finish-reading" action="{% url 'reading-status' 'finish' book.id %}" method="post">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-body %}
|
|
||||||
<section class="modal-card-body">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="id" value="{{ readthrough.id }}">
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="finish_id_start_date-{{ uuid }}">
|
|
||||||
{% trans "Started reading" %}
|
|
||||||
</label>
|
|
||||||
<input type="date" name="start_date" class="input" id="finish_id_start_date-{{ uuid }}" value="{{ readthrough.start_date | date:"Y-m-d" }}">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="id_finish_date-{{ uuid }}">
|
|
||||||
{% trans "Finished reading" %}
|
|
||||||
</label>
|
|
||||||
<input type="date" name="finish_date" class="input" id="id_finish_date-{{ uuid }}" value="{% now "Y-m-d" %}">
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-footer %}
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column field">
|
|
||||||
<label for="post_status-{{ uuid }}">
|
|
||||||
<input type="checkbox" name="post-status" class="checkbox" id="post_status-{{ uuid }}" checked>
|
|
||||||
{% trans "Post to feed" %}
|
|
||||||
</label>
|
|
||||||
{% include 'snippets/privacy_select.html' %}
|
|
||||||
</div>
|
|
||||||
<div class="column has-text-right">
|
|
||||||
<button type="submit" class="button is-success">{% trans "Save" %}</button>
|
|
||||||
{% trans "Cancel" as button_text %}
|
|
||||||
{% include 'snippets/toggle/close_button.html' with text=button_text controls_text="finish-reading" controls_uid=uuid %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
{% block modal-form-close %}</form>{% endblock %}
|
|
|
@ -19,13 +19,13 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% include 'snippets/shelve_button/want_to_read_modal.html' with book=active_shelf.book controls_text="want_to_read" controls_uid=uuid %}
|
{% include 'snippets/reading_modals/want_to_read_modal.html' with book=active_shelf.book controls_text="want_to_read" controls_uid=uuid %}
|
||||||
|
|
||||||
{% include 'snippets/shelve_button/start_reading_modal.html' with book=active_shelf.book controls_text="start_reading" controls_uid=uuid %}
|
{% include 'snippets/reading_modals/start_reading_modal.html' with book=active_shelf.book controls_text="start_reading" controls_uid=uuid %}
|
||||||
|
|
||||||
{% include 'snippets/shelve_button/finish_reading_modal.html' with book=active_shelf.book controls_text="finish_reading" controls_uid=uuid readthrough=readthrough %}
|
{% include 'snippets/reading_modals/finish_reading_modal.html' with book=active_shelf.book controls_text="finish_reading" controls_uid=uuid readthrough=readthrough %}
|
||||||
|
|
||||||
{% include 'snippets/shelve_button/progress_update_modal.html' with book=active_shelf_book.book controls_text="progress_update" controls_uid=uuid readthrough=readthrough %}
|
{% include 'snippets/reading_modals/progress_update_modal.html' with book=active_shelf_book.book controls_text="progress_update" controls_uid=uuid readthrough=readthrough %}
|
||||||
|
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -13,7 +13,9 @@
|
||||||
{% include 'snippets/toggle/toggle_button.html' with class=class text=button_text controls_text="start_reading" controls_uid=button_uuid focus="modal_title_start_reading" disabled=is_current fallback_url=fallback_url %}
|
{% include 'snippets/toggle/toggle_button.html' with class=class text=button_text controls_text="start_reading" controls_uid=button_uuid focus="modal_title_start_reading" disabled=is_current fallback_url=fallback_url %}
|
||||||
|
|
||||||
{% endif %}{% elif shelf.identifier == 'read' and active_shelf.shelf.identifier == 'read' %}{% if not dropdown or active_shelf.shelf.identifier|next_shelf != shelf.identifier %}
|
{% endif %}{% elif shelf.identifier == 'read' and active_shelf.shelf.identifier == 'read' %}{% if not dropdown or active_shelf.shelf.identifier|next_shelf != shelf.identifier %}
|
||||||
<button type="button" class="button {{ class }}" disabled><span>{% trans "Read" %}</span>
|
<button type="button" class="button {{ class }}" disabled>
|
||||||
|
<span>{% trans "Read" %}</span>
|
||||||
|
</button>
|
||||||
{% endif %}{% elif shelf.identifier == 'read' %}{% if not dropdown or active_shelf.shelf.identifier|next_shelf != shelf.identifier %}
|
{% endif %}{% elif shelf.identifier == 'read' %}{% if not dropdown or active_shelf.shelf.identifier|next_shelf != shelf.identifier %}
|
||||||
|
|
||||||
{% trans "Finish reading" as button_text %}
|
{% trans "Finish reading" as button_text %}
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
{% extends 'components/modal.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block modal-title %}
|
|
||||||
{% blocktrans trimmed with book_title=book.title %}
|
|
||||||
Start "<em>{{ book_title }}</em>"
|
|
||||||
{% endblocktrans %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-form-open %}
|
|
||||||
<form name="start-reading" action="{% url 'reading-status' 'start' book.id %}" method="post">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-body %}
|
|
||||||
<section class="modal-card-body">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="start_id_start_date-{{ uuid }}">
|
|
||||||
{% trans "Started reading" %}
|
|
||||||
</label>
|
|
||||||
<input type="date" name="start_date" class="input" id="start_id_start_date-{{ uuid }}" value="{% now "Y-m-d" %}">
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-footer %}
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column field">
|
|
||||||
<label for="post_status_start-{{ uuid }}">
|
|
||||||
<input type="checkbox" name="post-status" class="checkbox" id="post_status_start-{{ uuid }}" checked>
|
|
||||||
{% trans "Post to feed" %}
|
|
||||||
</label>
|
|
||||||
{% include 'snippets/privacy_select.html' %}
|
|
||||||
</div>
|
|
||||||
<div class="column has-text-right">
|
|
||||||
<button class="button is-success" type="submit">{% trans "Save" %}</button>
|
|
||||||
{% trans "Cancel" as button_text %}
|
|
||||||
{% include 'snippets/toggle/toggle_button.html' with text=button_text controls_text="start-reading" controls_uid=uuid %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
{% block modal-form-close %}</form>{% endblock %}
|
|
|
@ -1,33 +0,0 @@
|
||||||
{% extends 'components/modal.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block modal-title %}
|
|
||||||
{% blocktrans with book_title=book.title %}Want to Read "<em>{{ book_title }}</em>"{% endblocktrans %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-form-open %}
|
|
||||||
<form name="shelve" action="{% url 'reading-status' 'want' book.id %}" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="book" value="{{ active_shelf.book.id }}">
|
|
||||||
<input type="hidden" name="shelf" value="to-read">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-footer %}
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column field">
|
|
||||||
<label for="post_status_want-{{ uuid }}">
|
|
||||||
<input type="checkbox" name="post-status" class="checkbox" id="post_status_want-{{ uuid }}" checked>
|
|
||||||
{% trans "Post to feed" %}
|
|
||||||
</label>
|
|
||||||
{% include 'snippets/privacy_select.html' %}
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<button class="button is-success" type="submit">
|
|
||||||
<span>{% trans "Want to read" %}</span>
|
|
||||||
</button>
|
|
||||||
{% trans "Cancel" as button_text %}
|
|
||||||
{% include 'snippets/toggle/toggle_button.html' with text=button_text controls_text="want-to-read" controls_uid=uuid %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
{% block modal-form-close %}</form>{% endblock %}
|
|
|
@ -3,7 +3,7 @@
|
||||||
{% block card-content %}
|
{% block card-content %}
|
||||||
{% with status_type=status.status_type %}
|
{% with status_type=status.status_type %}
|
||||||
|
|
||||||
{% if status_type == 'GeneratedNote' or status_type == 'Rating' or not status.content %}
|
{% if status_type == 'GeneratedNote' or status_type == 'Rating' %}
|
||||||
{% include 'snippets/status/generated_status.html' with status=status %}
|
{% include 'snippets/status/generated_status.html' with status=status %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% include 'snippets/status/content_status.html' with status=status %}
|
{% include 'snippets/status/content_status.html' with status=status %}
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<div class="column is-cover">
|
<div class="column is-cover">
|
||||||
<div class="columns is-mobile is-gapless">
|
<div class="columns is-mobile is-gapless">
|
||||||
<div class="column is-cover">
|
<div class="column is-cover">
|
||||||
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book cover_class='is-w-s-mobile is-h-l-tablet' %}</a>
|
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book cover_class='is-w-s-mobile is-h-l-tablet' size_mobile='medium' size='large' %}</a>
|
||||||
|
|
||||||
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
{% with book=status.book|default:status.mention_books.first %}
|
{% with book=status.book|default:status.mention_books.first %}
|
||||||
<div class="columns is-mobile is-gapless">
|
<div class="columns is-mobile is-gapless">
|
||||||
<a class="column is-cover is-narrow" href="{{ book.local_path }}">
|
<a class="column is-cover is-narrow" href="{{ book.local_path }}">
|
||||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-xs is-h-s-tablet' %}
|
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-xs is-h-s-tablet' size='small' size_mobile='xsmall' %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="column ml-3">
|
<div class="column ml-3">
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{% load i18n %}{% load utilities %}
|
{% load i18n %}{% load utilities %}
|
||||||
{% blocktrans with book_path=book.local_path book=status.book|book_title %}commented on <a href="{{ book_path }}">{{ book }}</a>{% endblocktrans %}
|
{% blocktrans with book_path=status.book.local_path book=status.book|book_title %}commented on <a href="{{ book_path }}">{{ book }}</a>{% endblocktrans %}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
{% with parent_status=status|parent %}
|
{% with parent_status=status|parent %}
|
||||||
{% if parent_status %}
|
{% if parent_status %}
|
||||||
{% blocktrans trimmed with username=parent_status.user.display_name user_path=parent_status.user.local_path status_path=parent_status.local_path %}
|
{% blocktrans trimmed with username=parent_status.user.display_name user_path=parent_status.user.local_path status_path=parent_status.local_path %}
|
||||||
replied to <a href="{{ user_path }}">{{ username}}'s</a> <a href="{{ status_path }}">status</a>
|
replied to <a href="{{ user_path }}">{{ username}}</a>'s <a href="{{ status_path }}">status</a>
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
{% spaceless %}
|
{% spaceless %}
|
||||||
{% load i18n %}{% load utilities %}
|
{% load i18n %}
|
||||||
|
{% load utilities %}
|
||||||
|
{% load status_display %}
|
||||||
|
|
||||||
{% with book=status.mention_books.first %}
|
{% load_book status as book %}
|
||||||
{% blocktrans with book_path=book.remote_id book=book|book_title %}finished reading <a href="{{ book_path }}">{{ book }}</a>{% endblocktrans %}
|
{% blocktrans with book_path=book.remote_id book=book|book_title %}finished reading <a href="{{ book_path }}">{{ book }}</a>{% endblocktrans %}
|
||||||
{% endwith %}
|
|
||||||
{% endspaceless %}
|
{% endspaceless %}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
{% spaceless %}
|
{% spaceless %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
{% load status_display %}
|
||||||
|
|
||||||
{% with book=status.mention_books.first %}
|
{% load_book status as book %}
|
||||||
{% blocktrans with book_path=book.remote_id book=book|book_title %}started reading <a href="{{ book_path }}">{{ book }}</a>{% endblocktrans %}
|
{% blocktrans with book_path=book.remote_id book=book|book_title %}started reading <a href="{{ book_path }}">{{ book }}</a>{% endblocktrans %}
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
{% endspaceless %}
|
{% endspaceless %}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{% spaceless %}
|
{% spaceless %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
{% load status_display %}
|
||||||
|
|
||||||
{% with book=status.mention_books.first %}
|
{% load_book status as book %}
|
||||||
{% blocktrans with book_path=book.remote_id book=book|book_title %}<a href="{{ user_path }}">{{ username }}</a> wants to read <a href="{{ book_path }}">{{ book }}</a>{% endblocktrans %}
|
{% blocktrans with book_path=book.remote_id book=book|book_title %}<a href="{{ user_path }}">{{ username }}</a> wants to read <a href="{{ book_path }}">{{ book }}</a>{% endblocktrans %}
|
||||||
{% endwith %}
|
|
||||||
{% endspaceless %}
|
{% endspaceless %}
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div
|
<div
|
||||||
dir="auto"
|
dir="auto"
|
||||||
{% if itemprop %}itemprop="{{ itemprop }}{% endif %}"
|
{% if itemprop %}itemprop="{{ itemprop }}"{% endif %}
|
||||||
>
|
>
|
||||||
{{ full }}
|
{{ full }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div
|
<div
|
||||||
dir="auto"
|
dir="auto"
|
||||||
{% if itemprop %}itemprop="{{ itemprop }}{% endif %}"
|
{% if itemprop %}itemprop="{{ itemprop }}"{% endif %}
|
||||||
>
|
>
|
||||||
{{ full }}
|
{{ full }}
|
||||||
</div>
|
</div>
|
||||||
|
|
2
bookwyrm/templates/user/layout.html
Normal file → Executable file
2
bookwyrm/templates/user/layout.html
Normal file → Executable file
|
@ -31,8 +31,8 @@
|
||||||
{% spaceless %}
|
{% spaceless %}
|
||||||
<div class="column box has-background-white-bis content preserve-whitespace">
|
<div class="column box has-background-white-bis content preserve-whitespace">
|
||||||
{{ user.summary|to_markdown|safe }}
|
{{ user.summary|to_markdown|safe }}
|
||||||
{% endspaceless %}
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endspaceless %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if not is_self and request.user.is_authenticated %}
|
{% if not is_self and request.user.is_authenticated %}
|
||||||
|
|
0
bookwyrm/templates/user/lists.html
Normal file → Executable file
0
bookwyrm/templates/user/lists.html
Normal file → Executable file
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'user/layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
{% load bookwyrm_tags %}
|
{% load bookwyrm_tags %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
|
@ -8,15 +8,17 @@
|
||||||
{% include 'user/shelf/books_header.html' %}
|
{% include 'user/shelf/books_header.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block opengraph_images %}
|
||||||
<header class="columns">
|
{% include 'snippets/opengraph_images.html' with image=user.preview_image %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<header class="block">
|
||||||
<h1 class="title">
|
<h1 class="title">
|
||||||
{% include 'user/shelf/books_header.html' %}
|
{% include 'user/shelf/books_header.html' %}
|
||||||
</h1>
|
</h1>
|
||||||
</header>
|
</header>
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block tabs %}
|
|
||||||
<div class="block columns">
|
<div class="block columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
|
@ -41,9 +43,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block panel %}
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{% include 'user/shelf/create_shelf_form.html' with controls_text='create_shelf_form' %}
|
{% include 'user/shelf/create_shelf_form.html' with controls_text='create_shelf_form' %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
{% spaceless %}
|
{% spaceless %}
|
||||||
<tr class="book-preview">
|
<tr class="book-preview">
|
||||||
<td class="book-preview-top-row">
|
<td class="book-preview-top-row">
|
||||||
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book cover_class='is-w-s-tablet is-h-s' %}</a>
|
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book cover_class='is-w-s-tablet is-h-s' size='small' %}</a>
|
||||||
</td>
|
</td>
|
||||||
<td data-title="{% trans "Title" %}">
|
<td data-title="{% trans "Title" %}">
|
||||||
<a href="{{ book.local_path }}">{{ book.title }}</a>
|
<a href="{{ book.local_path }}">{{ book.title }}</a>
|
||||||
|
|
4
bookwyrm/templates/user/user.html
Normal file → Executable file
4
bookwyrm/templates/user/user.html
Normal file → Executable file
|
@ -35,7 +35,7 @@
|
||||||
{% for book in shelf.books %}
|
{% for book in shelf.books %}
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<a href="{{ book.local_path }}">
|
<a href="{{ book.local_path }}">
|
||||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-m is-h-s-mobile' %}
|
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-m is-h-s-mobile' size_mobile='small' size='medium' %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if not activities %}
|
{% if not activities %}
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<p>{% trans "No activities yet!" %}</a>
|
<p>{% trans "No activities yet!" %}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
0
bookwyrm/templates/user/user_preview.html
Normal file → Executable file
0
bookwyrm/templates/user/user_preview.html
Normal file → Executable file
|
@ -3,4 +3,5 @@
|
||||||
{% block filter_fields %}
|
{% block filter_fields %}
|
||||||
{% include 'user_admin/server_filter.html' %}
|
{% include 'user_admin/server_filter.html' %}
|
||||||
{% include 'user_admin/username_filter.html' %}
|
{% include 'user_admin/username_filter.html' %}
|
||||||
|
{% include 'directory/community_filter.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -2,23 +2,30 @@
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
|
||||||
{% if widget.is_initial %}
|
{% if widget.is_initial %}
|
||||||
<p class="mb-1">
|
<p class="mb-1">
|
||||||
{{ widget.initial_text }}:
|
{{ widget.initial_text }}:
|
||||||
<a href="{{ widget.value.url }}">{{ widget.value|truncatepath:10 }}</a>
|
<a href="{{ widget.value.url }}">{{ widget.value|truncatepath:10 }}</a>
|
||||||
</p>
|
</p>
|
||||||
{% if not widget.required %}
|
|
||||||
<p class="mb-1">
|
{% if not widget.required %}
|
||||||
<label class="has-text-weight-normal">
|
<p class="mb-1">
|
||||||
|
<label class="has-text-weight-normal">
|
||||||
<input type="checkbox" name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}"{% if widget.attrs.disabled %} disabled{% endif %}>
|
<input type="checkbox" name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}"{% if widget.attrs.disabled %} disabled{% endif %}>
|
||||||
{{ widget.clear_checkbox_label }}
|
{{ widget.clear_checkbox_label }}
|
||||||
</label>{% endif %}
|
</label>
|
||||||
</p>
|
</p>
|
||||||
<p class="mb-1">
|
{% endif %}
|
||||||
{{ widget.input_text }}:
|
|
||||||
{% else %}
|
|
||||||
<p class="mb-1">
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<input type="{{ widget.type }}" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
|
|
||||||
<span class="help file-cta is-hidden file-too-big">{% trans "File exceeds maximum size: 10MB" %}</span>
|
<p class="mb-1">
|
||||||
|
{% if widget.is_initial %}
|
||||||
|
{{ widget.input_text }}:
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<input type="{{ widget.type }}" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
|
||||||
|
<span class="help file-cta is-hidden file-too-big">
|
||||||
|
{% trans "File exceeds maximum size: 10MB" %}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
@ -16,3 +16,9 @@ def get_user_liked(user, status):
|
||||||
def get_user_boosted(user, status):
|
def get_user_boosted(user, status):
|
||||||
"""did the given user fav a status?"""
|
"""did the given user fav a status?"""
|
||||||
return status.boosters.filter(user=user).exists()
|
return status.boosters.filter(user=user).exists()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="saved")
|
||||||
|
def get_user_saved_lists(user, book_list):
|
||||||
|
"""did the user save a list"""
|
||||||
|
return user.saved_lists.filter(id=book_list.id).exists()
|
||||||
|
|
|
@ -70,7 +70,13 @@ def get_header_template(status):
|
||||||
"""get the path for the status template"""
|
"""get the path for the status template"""
|
||||||
if isinstance(status, models.Boost):
|
if isinstance(status, models.Boost):
|
||||||
status = status.boosted_status
|
status = status.boosted_status
|
||||||
filename = "snippets/status/headers/{:s}.html".format(status.status_type.lower())
|
try:
|
||||||
|
header_type = status.reading_status.replace("-", "_")
|
||||||
|
if not header_type:
|
||||||
|
raise AttributeError()
|
||||||
|
except AttributeError:
|
||||||
|
header_type = status.status_type.lower()
|
||||||
|
filename = f"snippets/status/headers/{header_type}.html"
|
||||||
header_template = select_template([filename, "snippets/status/headers/note.html"])
|
header_template = select_template([filename, "snippets/status/headers/note.html"])
|
||||||
return header_template.render({"status": status})
|
return header_template.render({"status": status})
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import os
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from django import template
|
from django import template
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.templatetags.static import static
|
||||||
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
@ -50,3 +51,15 @@ def truncatepath(value, arg):
|
||||||
except ValueError: # invalid literal for int()
|
except ValueError: # invalid literal for int()
|
||||||
return path_list[-1] # Fail silently.
|
return path_list[-1] # Fail silently.
|
||||||
return "%s/…%s" % (path_list[0], path_list[-1][-length:])
|
return "%s/…%s" % (path_list[0], path_list[-1][-length:])
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag(takes_context=False)
|
||||||
|
def get_book_cover_thumbnail(book, size="medium", ext="jpg"):
|
||||||
|
"""Returns a book thumbnail at the specified size and extension, with fallback if needed"""
|
||||||
|
if size == "":
|
||||||
|
size = "medium"
|
||||||
|
try:
|
||||||
|
cover_thumbnail = getattr(book, "cover_bw_book_%s_%s" % (size, ext))
|
||||||
|
return cover_thumbnail.url
|
||||||
|
except OSError:
|
||||||
|
return static("images/no_cover.jpg")
|
||||||
|
|
|
@ -8,6 +8,7 @@ from bookwyrm import activitystreams, models
|
||||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||||
@patch("bookwyrm.activitystreams.BooksStream.add_book_statuses")
|
@patch("bookwyrm.activitystreams.BooksStream.add_book_statuses")
|
||||||
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
||||||
|
# pylint: disable=too-many-public-methods
|
||||||
class Activitystreams(TestCase):
|
class Activitystreams(TestCase):
|
||||||
"""using redis to build activity streams"""
|
"""using redis to build activity streams"""
|
||||||
|
|
||||||
|
@ -286,3 +287,76 @@ class Activitystreams(TestCase):
|
||||||
# yes book, yes audience
|
# yes book, yes audience
|
||||||
result = activitystreams.BooksStream().get_statuses_for_user(self.local_user)
|
result = activitystreams.BooksStream().get_statuses_for_user(self.local_user)
|
||||||
self.assertEqual(list(result), [status])
|
self.assertEqual(list(result), [status])
|
||||||
|
|
||||||
|
@patch("bookwyrm.activitystreams.LocalStream.remove_object_from_related_stores")
|
||||||
|
@patch("bookwyrm.activitystreams.BooksStream.remove_object_from_related_stores")
|
||||||
|
def test_boost_to_another_timeline(self, *_):
|
||||||
|
"""add a boost and deduplicate the boosted status on the timeline"""
|
||||||
|
status = models.Status.objects.create(user=self.local_user, content="hi")
|
||||||
|
with patch(
|
||||||
|
"bookwyrm.activitystreams.HomeStream.remove_object_from_related_stores"
|
||||||
|
):
|
||||||
|
boost = models.Boost.objects.create(
|
||||||
|
boosted_status=status,
|
||||||
|
user=self.another_user,
|
||||||
|
)
|
||||||
|
with patch(
|
||||||
|
"bookwyrm.activitystreams.HomeStream.remove_object_from_related_stores"
|
||||||
|
) as mock:
|
||||||
|
activitystreams.add_status_on_create(models.Boost, boost, True)
|
||||||
|
self.assertTrue(mock.called)
|
||||||
|
call_args = mock.call_args
|
||||||
|
self.assertEqual(call_args[0][0], status)
|
||||||
|
self.assertEqual(
|
||||||
|
call_args[1]["stores"], ["{:d}-home".format(self.another_user.id)]
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch("bookwyrm.activitystreams.LocalStream.remove_object_from_related_stores")
|
||||||
|
@patch("bookwyrm.activitystreams.BooksStream.remove_object_from_related_stores")
|
||||||
|
def test_boost_to_following_timeline(self, *_):
|
||||||
|
"""add a boost and deduplicate the boosted status on the timeline"""
|
||||||
|
self.local_user.following.add(self.another_user)
|
||||||
|
status = models.Status.objects.create(user=self.local_user, content="hi")
|
||||||
|
with patch(
|
||||||
|
"bookwyrm.activitystreams.HomeStream.remove_object_from_related_stores"
|
||||||
|
):
|
||||||
|
boost = models.Boost.objects.create(
|
||||||
|
boosted_status=status,
|
||||||
|
user=self.another_user,
|
||||||
|
)
|
||||||
|
with patch(
|
||||||
|
"bookwyrm.activitystreams.HomeStream.remove_object_from_related_stores"
|
||||||
|
) as mock:
|
||||||
|
activitystreams.add_status_on_create(models.Boost, boost, True)
|
||||||
|
self.assertTrue(mock.called)
|
||||||
|
call_args = mock.call_args
|
||||||
|
self.assertEqual(call_args[0][0], status)
|
||||||
|
self.assertTrue(
|
||||||
|
"{:d}-home".format(self.another_user.id) in call_args[1]["stores"]
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
"{:d}-home".format(self.local_user.id) in call_args[1]["stores"]
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch("bookwyrm.activitystreams.LocalStream.remove_object_from_related_stores")
|
||||||
|
@patch("bookwyrm.activitystreams.BooksStream.remove_object_from_related_stores")
|
||||||
|
def test_boost_to_same_timeline(self, *_):
|
||||||
|
"""add a boost and deduplicate the boosted status on the timeline"""
|
||||||
|
status = models.Status.objects.create(user=self.local_user, content="hi")
|
||||||
|
with patch(
|
||||||
|
"bookwyrm.activitystreams.HomeStream.remove_object_from_related_stores"
|
||||||
|
):
|
||||||
|
boost = models.Boost.objects.create(
|
||||||
|
boosted_status=status,
|
||||||
|
user=self.local_user,
|
||||||
|
)
|
||||||
|
with patch(
|
||||||
|
"bookwyrm.activitystreams.HomeStream.remove_object_from_related_stores"
|
||||||
|
) as mock:
|
||||||
|
activitystreams.add_status_on_create(models.Boost, boost, True)
|
||||||
|
self.assertTrue(mock.called)
|
||||||
|
call_args = mock.call_args
|
||||||
|
self.assertEqual(call_args[0][0], status)
|
||||||
|
self.assertEqual(
|
||||||
|
call_args[1]["stores"], ["{:d}-home".format(self.local_user.id)]
|
||||||
|
)
|
||||||
|
|
|
@ -77,6 +77,33 @@ class InboxCreate(TestCase):
|
||||||
views.inbox.activity_task(activity)
|
views.inbox.activity_task(activity)
|
||||||
self.assertEqual(models.Status.objects.count(), 1)
|
self.assertEqual(models.Status.objects.count(), 1)
|
||||||
|
|
||||||
|
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||||
|
def test_create_comment_with_reading_status(self, *_):
|
||||||
|
"""the "it justs works" mode"""
|
||||||
|
datafile = pathlib.Path(__file__).parent.joinpath("../../data/ap_comment.json")
|
||||||
|
status_data = json.loads(datafile.read_bytes())
|
||||||
|
status_data["readingStatus"] = "to-read"
|
||||||
|
|
||||||
|
models.Edition.objects.create(
|
||||||
|
title="Test Book", remote_id="https://example.com/book/1"
|
||||||
|
)
|
||||||
|
activity = self.create_json
|
||||||
|
activity["object"] = status_data
|
||||||
|
|
||||||
|
with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock:
|
||||||
|
views.inbox.activity_task(activity)
|
||||||
|
self.assertTrue(redis_mock.called)
|
||||||
|
|
||||||
|
status = models.Comment.objects.get()
|
||||||
|
self.assertEqual(status.remote_id, "https://example.com/user/mouse/comment/6")
|
||||||
|
self.assertEqual(status.content, "commentary")
|
||||||
|
self.assertEqual(status.reading_status, "to-read")
|
||||||
|
self.assertEqual(status.user, self.local_user)
|
||||||
|
|
||||||
|
# while we're here, lets ensure we avoid dupes
|
||||||
|
views.inbox.activity_task(activity)
|
||||||
|
self.assertEqual(models.Status.objects.count(), 1)
|
||||||
|
|
||||||
def test_create_status_remote_note_with_mention(self, _):
|
def test_create_status_remote_note_with_mention(self, _):
|
||||||
"""should only create it under the right circumstances"""
|
"""should only create it under the right circumstances"""
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
|
|
|
@ -280,49 +280,6 @@ class BookViews(TestCase):
|
||||||
self.assertEqual(book.authors.first().name, "Sappho")
|
self.assertEqual(book.authors.first().name, "Sappho")
|
||||||
self.assertEqual(book.authors.first(), book.parent_work.authors.first())
|
self.assertEqual(book.authors.first(), book.parent_work.authors.first())
|
||||||
|
|
||||||
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
|
||||||
def test_switch_edition(self, _):
|
|
||||||
"""updates user's relationships to a book"""
|
|
||||||
work = models.Work.objects.create(title="test work")
|
|
||||||
edition1 = models.Edition.objects.create(title="first ed", parent_work=work)
|
|
||||||
edition2 = models.Edition.objects.create(title="second ed", parent_work=work)
|
|
||||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
|
||||||
shelf = models.Shelf.objects.create(name="Test Shelf", user=self.local_user)
|
|
||||||
models.ShelfBook.objects.create(
|
|
||||||
book=edition1,
|
|
||||||
user=self.local_user,
|
|
||||||
shelf=shelf,
|
|
||||||
)
|
|
||||||
models.ReadThrough.objects.create(user=self.local_user, book=edition1)
|
|
||||||
|
|
||||||
self.assertEqual(models.ShelfBook.objects.get().book, edition1)
|
|
||||||
self.assertEqual(models.ReadThrough.objects.get().book, edition1)
|
|
||||||
request = self.factory.post("", {"edition": edition2.id})
|
|
||||||
request.user = self.local_user
|
|
||||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
|
||||||
views.switch_edition(request)
|
|
||||||
|
|
||||||
self.assertEqual(models.ShelfBook.objects.get().book, edition2)
|
|
||||||
self.assertEqual(models.ReadThrough.objects.get().book, edition2)
|
|
||||||
|
|
||||||
def test_editions_page(self):
|
|
||||||
"""there are so many views, this just makes sure it LOADS"""
|
|
||||||
view = views.Editions.as_view()
|
|
||||||
request = self.factory.get("")
|
|
||||||
with patch("bookwyrm.views.books.is_api_request") as is_api:
|
|
||||||
is_api.return_value = False
|
|
||||||
result = view(request, self.work.id)
|
|
||||||
self.assertIsInstance(result, TemplateResponse)
|
|
||||||
result.render()
|
|
||||||
self.assertEqual(result.status_code, 200)
|
|
||||||
|
|
||||||
request = self.factory.get("")
|
|
||||||
with patch("bookwyrm.views.books.is_api_request") as is_api:
|
|
||||||
is_api.return_value = True
|
|
||||||
result = view(request, self.work.id)
|
|
||||||
self.assertIsInstance(result, ActivitypubResponse)
|
|
||||||
self.assertEqual(result.status_code, 200)
|
|
||||||
|
|
||||||
def test_upload_cover_file(self):
|
def test_upload_cover_file(self):
|
||||||
"""add a cover via file upload"""
|
"""add a cover via file upload"""
|
||||||
self.assertFalse(self.book.cover)
|
self.assertFalse(self.book.cover)
|
||||||
|
|
126
bookwyrm/tests/views/test_editions.py
Normal file
126
bookwyrm/tests/views/test_editions.py
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
""" test for app action functionality """
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from django.template.response import TemplateResponse
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.test.client import RequestFactory
|
||||||
|
|
||||||
|
from bookwyrm import models, views
|
||||||
|
from bookwyrm.activitypub import ActivitypubResponse
|
||||||
|
|
||||||
|
|
||||||
|
class BookViews(TestCase):
|
||||||
|
"""books books books"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""we need basic test data and mocks"""
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
|
||||||
|
self.local_user = models.User.objects.create_user(
|
||||||
|
"mouse@local.com",
|
||||||
|
"mouse@mouse.com",
|
||||||
|
"mouseword",
|
||||||
|
local=True,
|
||||||
|
localname="mouse",
|
||||||
|
remote_id="https://example.com/users/mouse",
|
||||||
|
)
|
||||||
|
self.work = models.Work.objects.create(title="Test Work")
|
||||||
|
self.book = models.Edition.objects.create(
|
||||||
|
title="Example Edition",
|
||||||
|
remote_id="https://example.com/book/1",
|
||||||
|
parent_work=self.work,
|
||||||
|
physical_format="paperback",
|
||||||
|
)
|
||||||
|
|
||||||
|
models.SiteSettings.objects.create()
|
||||||
|
|
||||||
|
def test_editions_page(self):
|
||||||
|
"""there are so many views, this just makes sure it LOADS"""
|
||||||
|
view = views.Editions.as_view()
|
||||||
|
request = self.factory.get("")
|
||||||
|
with patch("bookwyrm.views.editions.is_api_request") as is_api:
|
||||||
|
is_api.return_value = False
|
||||||
|
result = view(request, self.work.id)
|
||||||
|
self.assertIsInstance(result, TemplateResponse)
|
||||||
|
result.render()
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
self.assertTrue("paperback" in result.context_data["formats"])
|
||||||
|
|
||||||
|
def test_editions_page_filtered(self):
|
||||||
|
"""editions view with filters"""
|
||||||
|
models.Edition.objects.create(
|
||||||
|
title="Fish",
|
||||||
|
physical_format="okay",
|
||||||
|
parent_work=self.work,
|
||||||
|
)
|
||||||
|
view = views.Editions.as_view()
|
||||||
|
request = self.factory.get("")
|
||||||
|
with patch("bookwyrm.views.editions.is_api_request") as is_api:
|
||||||
|
is_api.return_value = False
|
||||||
|
result = view(request, self.work.id)
|
||||||
|
self.assertIsInstance(result, TemplateResponse)
|
||||||
|
result.render()
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
self.assertEqual(len(result.context_data["editions"].object_list), 2)
|
||||||
|
self.assertEqual(len(result.context_data["formats"]), 2)
|
||||||
|
self.assertTrue("paperback" in result.context_data["formats"])
|
||||||
|
self.assertTrue("okay" in result.context_data["formats"])
|
||||||
|
|
||||||
|
request = self.factory.get("", {"q": "fish"})
|
||||||
|
with patch("bookwyrm.views.editions.is_api_request") as is_api:
|
||||||
|
is_api.return_value = False
|
||||||
|
result = view(request, self.work.id)
|
||||||
|
result.render()
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
self.assertEqual(len(result.context_data["editions"].object_list), 1)
|
||||||
|
|
||||||
|
request = self.factory.get("", {"q": "okay"})
|
||||||
|
with patch("bookwyrm.views.editions.is_api_request") as is_api:
|
||||||
|
is_api.return_value = False
|
||||||
|
result = view(request, self.work.id)
|
||||||
|
result.render()
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
self.assertEqual(len(result.context_data["editions"].object_list), 1)
|
||||||
|
|
||||||
|
request = self.factory.get("", {"format": "okay"})
|
||||||
|
with patch("bookwyrm.views.editions.is_api_request") as is_api:
|
||||||
|
is_api.return_value = False
|
||||||
|
result = view(request, self.work.id)
|
||||||
|
result.render()
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
self.assertEqual(len(result.context_data["editions"].object_list), 1)
|
||||||
|
|
||||||
|
def test_editions_page_api(self):
|
||||||
|
"""there are so many views, this just makes sure it LOADS"""
|
||||||
|
view = views.Editions.as_view()
|
||||||
|
request = self.factory.get("")
|
||||||
|
with patch("bookwyrm.views.editions.is_api_request") as is_api:
|
||||||
|
is_api.return_value = True
|
||||||
|
result = view(request, self.work.id)
|
||||||
|
self.assertIsInstance(result, ActivitypubResponse)
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
|
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
||||||
|
def test_switch_edition(self, _):
|
||||||
|
"""updates user's relationships to a book"""
|
||||||
|
work = models.Work.objects.create(title="test work")
|
||||||
|
edition1 = models.Edition.objects.create(title="first ed", parent_work=work)
|
||||||
|
edition2 = models.Edition.objects.create(title="second ed", parent_work=work)
|
||||||
|
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||||
|
shelf = models.Shelf.objects.create(name="Test Shelf", user=self.local_user)
|
||||||
|
models.ShelfBook.objects.create(
|
||||||
|
book=edition1,
|
||||||
|
user=self.local_user,
|
||||||
|
shelf=shelf,
|
||||||
|
)
|
||||||
|
models.ReadThrough.objects.create(user=self.local_user, book=edition1)
|
||||||
|
|
||||||
|
self.assertEqual(models.ShelfBook.objects.get().book, edition1)
|
||||||
|
self.assertEqual(models.ReadThrough.objects.get().book, edition1)
|
||||||
|
request = self.factory.post("", {"edition": edition2.id})
|
||||||
|
request.user = self.local_user
|
||||||
|
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||||
|
views.switch_edition(request)
|
||||||
|
|
||||||
|
self.assertEqual(models.ShelfBook.objects.get().book, edition2)
|
||||||
|
self.assertEqual(models.ReadThrough.objects.get().book, edition2)
|
|
@ -91,6 +91,52 @@ class ListViews(TestCase):
|
||||||
result.render()
|
result.render()
|
||||||
self.assertEqual(result.status_code, 200)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
|
def test_saved_lists_page(self):
|
||||||
|
"""there are so many views, this just makes sure it LOADS"""
|
||||||
|
view = views.SavedLists.as_view()
|
||||||
|
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||||
|
booklist = models.List.objects.create(
|
||||||
|
name="Public list", user=self.local_user
|
||||||
|
)
|
||||||
|
models.List.objects.create(
|
||||||
|
name="Private list", privacy="direct", user=self.local_user
|
||||||
|
)
|
||||||
|
self.local_user.saved_lists.add(booklist)
|
||||||
|
request = self.factory.get("")
|
||||||
|
request.user = self.local_user
|
||||||
|
|
||||||
|
result = view(request)
|
||||||
|
self.assertIsInstance(result, TemplateResponse)
|
||||||
|
result.render()
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
self.assertEqual(result.context_data["lists"].object_list, [booklist])
|
||||||
|
|
||||||
|
def test_saved_lists_page_empty(self):
|
||||||
|
"""there are so many views, this just makes sure it LOADS"""
|
||||||
|
view = views.SavedLists.as_view()
|
||||||
|
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||||
|
models.List.objects.create(name="Public list", user=self.local_user)
|
||||||
|
models.List.objects.create(
|
||||||
|
name="Private list", privacy="direct", user=self.local_user
|
||||||
|
)
|
||||||
|
request = self.factory.get("")
|
||||||
|
request.user = self.local_user
|
||||||
|
|
||||||
|
result = view(request)
|
||||||
|
self.assertIsInstance(result, TemplateResponse)
|
||||||
|
result.render()
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
self.assertEqual(len(result.context_data["lists"].object_list), 0)
|
||||||
|
|
||||||
|
def test_saved_lists_page_logged_out(self):
|
||||||
|
"""logged out saved lists"""
|
||||||
|
view = views.SavedLists.as_view()
|
||||||
|
request = self.factory.get("")
|
||||||
|
request.user = self.anonymous_user
|
||||||
|
|
||||||
|
result = view(request)
|
||||||
|
self.assertEqual(result.status_code, 302)
|
||||||
|
|
||||||
def test_lists_create(self):
|
def test_lists_create(self):
|
||||||
"""create list view"""
|
"""create list view"""
|
||||||
view = views.Lists.as_view()
|
view = views.Lists.as_view()
|
||||||
|
@ -328,15 +374,8 @@ class ListViews(TestCase):
|
||||||
def test_user_lists_page_logged_out(self):
|
def test_user_lists_page_logged_out(self):
|
||||||
"""there are so many views, this just makes sure it LOADS"""
|
"""there are so many views, this just makes sure it LOADS"""
|
||||||
view = views.UserLists.as_view()
|
view = views.UserLists.as_view()
|
||||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
|
||||||
models.List.objects.create(name="Public list", user=self.local_user)
|
|
||||||
models.List.objects.create(
|
|
||||||
name="Private list", privacy="direct", user=self.local_user
|
|
||||||
)
|
|
||||||
request = self.factory.get("")
|
request = self.factory.get("")
|
||||||
request.user = self.anonymous_user
|
request.user = self.anonymous_user
|
||||||
|
|
||||||
result = view(request, self.local_user.username)
|
result = view(request, self.local_user.username)
|
||||||
self.assertIsInstance(result, TemplateResponse)
|
self.assertEqual(result.status_code, 302)
|
||||||
result.render()
|
|
||||||
self.assertEqual(result.status_code, 200)
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
""" testing import """
|
""" testing import """
|
||||||
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
from django.test import RequestFactory, TestCase
|
from django.test import RequestFactory, TestCase
|
||||||
|
|
||||||
|
@ -7,59 +6,77 @@ from bookwyrm import models
|
||||||
from bookwyrm.views import rss_feed
|
from bookwyrm.views import rss_feed
|
||||||
|
|
||||||
|
|
||||||
|
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||||
|
@patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream")
|
||||||
|
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||||
class RssFeedView(TestCase):
|
class RssFeedView(TestCase):
|
||||||
"""rss feed behaves as expected"""
|
"""rss feed behaves as expected"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""test data"""
|
|
||||||
self.site = models.SiteSettings.objects.create()
|
|
||||||
|
|
||||||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
|
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
|
||||||
self.user = models.User.objects.create_user(
|
self.local_user = models.User.objects.create_user(
|
||||||
"rss_user", "rss@test.rss", "password", local=True
|
"rss_user", "rss@test.rss", "password", local=True
|
||||||
)
|
)
|
||||||
|
|
||||||
work = models.Work.objects.create(title="Test Work")
|
work = models.Work.objects.create(title="Test Work")
|
||||||
self.book = models.Edition.objects.create(
|
self.book = models.Edition.objects.create(
|
||||||
title="Example Edition",
|
title="Example Edition",
|
||||||
remote_id="https://example.com/book/1",
|
remote_id="https://example.com/book/1",
|
||||||
parent_work=work,
|
parent_work=work,
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
|
||||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
|
||||||
self.review = models.Review.objects.create(
|
|
||||||
name="Review name",
|
|
||||||
content="test content",
|
|
||||||
rating=3,
|
|
||||||
user=self.user,
|
|
||||||
book=self.book,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.quote = models.Quotation.objects.create(
|
|
||||||
quote="a sickening sense",
|
|
||||||
content="test content",
|
|
||||||
user=self.user,
|
|
||||||
book=self.book,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.generatednote = models.GeneratedNote.objects.create(
|
|
||||||
content="test content", user=self.user
|
|
||||||
)
|
|
||||||
|
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
|
|
||||||
@patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream")
|
models.SiteSettings.objects.create()
|
||||||
def test_rss_feed(self, _):
|
|
||||||
|
def test_rss_empty(self, *_):
|
||||||
"""load an rss feed"""
|
"""load an rss feed"""
|
||||||
view = rss_feed.RssFeed()
|
view = rss_feed.RssFeed()
|
||||||
request = self.factory.get("/user/rss_user/rss")
|
request = self.factory.get("/user/rss_user/rss")
|
||||||
request.user = self.user
|
request.user = self.local_user
|
||||||
with patch("bookwyrm.models.SiteSettings.objects.get") as site:
|
result = view(request, username=self.local_user.username)
|
||||||
site.return_value = self.site
|
self.assertEqual(result.status_code, 200)
|
||||||
result = view(request, username=self.user.username)
|
self.assertIn(b"Status updates from rss_user", result.content)
|
||||||
|
|
||||||
|
def test_rss_comment(self, *_):
|
||||||
|
"""load an rss feed"""
|
||||||
|
models.Comment.objects.create(
|
||||||
|
content="comment test content",
|
||||||
|
user=self.local_user,
|
||||||
|
book=self.book,
|
||||||
|
)
|
||||||
|
view = rss_feed.RssFeed()
|
||||||
|
request = self.factory.get("/user/rss_user/rss")
|
||||||
|
request.user = self.local_user
|
||||||
|
result = view(request, username=self.local_user.username)
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
self.assertIn(b"Example Edition", result.content)
|
||||||
|
|
||||||
|
def test_rss_review(self, *_):
|
||||||
|
"""load an rss feed"""
|
||||||
|
models.Review.objects.create(
|
||||||
|
name="Review name",
|
||||||
|
content="test content",
|
||||||
|
rating=3,
|
||||||
|
user=self.local_user,
|
||||||
|
book=self.book,
|
||||||
|
)
|
||||||
|
view = rss_feed.RssFeed()
|
||||||
|
request = self.factory.get("/user/rss_user/rss")
|
||||||
|
request.user = self.local_user
|
||||||
|
result = view(request, username=self.local_user.username)
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
|
def test_rss_quotation(self, *_):
|
||||||
|
"""load an rss feed"""
|
||||||
|
models.Quotation.objects.create(
|
||||||
|
quote="a sickening sense",
|
||||||
|
content="test content",
|
||||||
|
user=self.local_user,
|
||||||
|
book=self.book,
|
||||||
|
)
|
||||||
|
view = rss_feed.RssFeed()
|
||||||
|
request = self.factory.get("/user/rss_user/rss")
|
||||||
|
request.user = self.local_user
|
||||||
|
result = view(request, username=self.local_user.username)
|
||||||
self.assertEqual(result.status_code, 200)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
self.assertIn(b"Status updates from rss_user", result.content)
|
|
||||||
self.assertIn(b"a sickening sense", result.content)
|
self.assertIn(b"a sickening sense", result.content)
|
||||||
self.assertIn(b"Example Edition", result.content)
|
|
||||||
|
|
|
@ -218,6 +218,7 @@ urlpatterns = [
|
||||||
# lists
|
# lists
|
||||||
re_path(r"%s/lists/?$" % USER_PATH, views.UserLists.as_view(), name="user-lists"),
|
re_path(r"%s/lists/?$" % USER_PATH, views.UserLists.as_view(), name="user-lists"),
|
||||||
re_path(r"^list/?$", views.Lists.as_view(), name="lists"),
|
re_path(r"^list/?$", views.Lists.as_view(), name="lists"),
|
||||||
|
re_path(r"^list/saved/?$", views.SavedLists.as_view(), name="saved-lists"),
|
||||||
re_path(r"^list/(?P<list_id>\d+)(.json)?/?$", views.List.as_view(), name="list"),
|
re_path(r"^list/(?P<list_id>\d+)(.json)?/?$", views.List.as_view(), name="list"),
|
||||||
re_path(r"^list/add-book/?$", views.list.add_book, name="list-add-book"),
|
re_path(r"^list/add-book/?$", views.list.add_book, name="list-add-book"),
|
||||||
re_path(
|
re_path(
|
||||||
|
@ -233,6 +234,8 @@ urlpatterns = [
|
||||||
re_path(
|
re_path(
|
||||||
r"^list/(?P<list_id>\d+)/curate/?$", views.Curate.as_view(), name="list-curate"
|
r"^list/(?P<list_id>\d+)/curate/?$", views.Curate.as_view(), name="list-curate"
|
||||||
),
|
),
|
||||||
|
re_path(r"^save-list/(?P<list_id>\d+)/?$", views.save_list, name="list-save"),
|
||||||
|
re_path(r"^unsave-list/(?P<list_id>\d+)/?$", views.unsave_list, name="list-unsave"),
|
||||||
# User books
|
# User books
|
||||||
re_path(r"%s/books/?$" % USER_PATH, views.Shelf.as_view(), name="user-shelves"),
|
re_path(r"%s/books/?$" % USER_PATH, views.Shelf.as_view(), name="user-shelves"),
|
||||||
re_path(
|
re_path(
|
||||||
|
@ -294,8 +297,10 @@ urlpatterns = [
|
||||||
name="redraft",
|
name="redraft",
|
||||||
),
|
),
|
||||||
# interact
|
# interact
|
||||||
re_path(r"^favorite/(?P<status_id>\d+)/?$", views.Favorite.as_view()),
|
re_path(r"^favorite/(?P<status_id>\d+)/?$", views.Favorite.as_view(), name="fav"),
|
||||||
re_path(r"^unfavorite/(?P<status_id>\d+)/?$", views.Unfavorite.as_view()),
|
re_path(
|
||||||
|
r"^unfavorite/(?P<status_id>\d+)/?$", views.Unfavorite.as_view(), name="unfav"
|
||||||
|
),
|
||||||
re_path(r"^boost/(?P<status_id>\d+)/?$", views.Boost.as_view()),
|
re_path(r"^boost/(?P<status_id>\d+)/?$", views.Boost.as_view()),
|
||||||
re_path(r"^unboost/(?P<status_id>\d+)/?$", views.Unboost.as_view()),
|
re_path(r"^unboost/(?P<status_id>\d+)/?$", views.Unboost.as_view()),
|
||||||
# books
|
# books
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue