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