diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 1b14149f2..08661e9c2 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -24,5 +24,5 @@ jobs: pip install pylint - name: Analysing the code with pylint run: | - pylint bookwyrm/ --ignore=migrations,tests --disable=E1101,E1135,E1136,R0903,R0901,R0902,W0707,W0511,W0406,R0401,R0801 + pylint bookwyrm/ --ignore=migrations --disable=E1101,E1135,E1136,R0903,R0901,R0902,W0707,W0511,W0406,R0401,R0801 diff --git a/README.md b/README.md index bd7344df9..cf40d284d 100644 --- a/README.md +++ b/README.md @@ -9,21 +9,18 @@ Social reading and reviewing, decentralized with ActivityPub - [What it is and isn't](#what-it-is-and-isnt) - [The role of federation](#the-role-of-federation) - [Features](#features) -- [Book data](#book-data) -- [Set up Bookwyrm](#set-up-bookwyrm) +- [Set up BookWyrm](#set-up-bookwyrm) ## Joining BookWyrm -BookWyrm is still a young piece of software, and isn't at the level of stability and feature-richness that you'd find in a production-ready application. But it does what it says on the box! If you'd like to join an instance, you can check out the [instances](https://joinbookwyrm.com/instances/) list. - -You can request an invite by entering your email address at https://bookwyrm.social. +If you'd like to join an instance, you can check out the [instances](https://joinbookwyrm.com/instances/) list. ## Contributing -See [contributing](https://docs.joinbookwyrm.com/how-to-contribute.html) for code, translation or monetary contributions. +See [contributing](https://docs.joinbookwyrm.com/contributing.html) for code, translation or monetary contributions. ## About BookWyrm ### What it is and isn't -BookWyrm is a platform for social reading! You can use it to track what you're reading, review books, and follow your friends. It isn't primarily meant for cataloguing or as a data-source for books, but it does do both of those things to some degree. +BookWyrm is a platform for social reading. You can use it to track what you're reading, review books, and follow your friends. It isn't primarily meant for cataloguing or as a data-source for books, but it does do both of those things to some degree. ### The role of federation BookWyrm is built on [ActivityPub](http://activitypub.rocks/). With ActivityPub, it inter-operates with different instances of BookWyrm, and other ActivityPub compliant services, like Mastodon. This means you can run an instance for your book club, and still follow your friend who posts on a server devoted to 20th century Russian speculative fiction. It also means that your friend on mastodon can read and comment on a book review that you post on your BookWyrm instance. @@ -78,8 +75,5 @@ Deployment - [Nginx](https://nginx.org/en/) HTTP server -## Book data -The application is set up to share book and author data between instances, and get book data from arbitrary outside sources. Right now, the only connector is to OpenLibrary, but other connectors could be written. - -## Set up Bookwyrm -The [documentation website](https://docs.joinbookwyrm.com/) has instruction on how to set up Bookwyrm in a [developer environment](https://docs.joinbookwyrm.com/developer-environment.html) or [production](https://docs.joinbookwyrm.com/installing-in-production.html). +## Set up BookWyrm +The [documentation website](https://docs.joinbookwyrm.com/) has instruction on how to set up BookWyrm in a [developer environment](https://docs.joinbookwyrm.com/install-dev.html) or [production](https://docs.joinbookwyrm.com/install-prod.html). diff --git a/bookwyrm/management/commands/initdb.py b/bookwyrm/management/commands/initdb.py index 4e23a5306..160502ca0 100644 --- a/bookwyrm/management/commands/initdb.py +++ b/bookwyrm/management/commands/initdb.py @@ -105,16 +105,6 @@ def init_connectors(): ) -def init_federated_servers(): - """big no to nazis""" - built_in_blocks = ["gab.ai", "gab.com"] - for server in built_in_blocks: - models.FederatedServer.objects.create( - server_name=server, - status="blocked", - ) - - def init_settings(): """info about the instance""" models.SiteSettings.objects.create( @@ -163,7 +153,6 @@ class Command(BaseCommand): "group", "permission", "connector", - "federatedserver", "settings", "linkdomain", ] @@ -176,8 +165,6 @@ class Command(BaseCommand): 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": diff --git a/bookwyrm/migrations/0147_alter_user_preferred_language.py b/bookwyrm/migrations/0147_alter_user_preferred_language.py new file mode 100644 index 000000000..0c9609b0b --- /dev/null +++ b/bookwyrm/migrations/0147_alter_user_preferred_language.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.12 on 2022-03-26 16:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0146_auto_20220316_2352"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="preferred_language", + field=models.CharField( + blank=True, + choices=[ + ("en-us", "English"), + ("de-de", "Deutsch (German)"), + ("es-es", "Español (Spanish)"), + ("gl-es", "Galego (Galician)"), + ("it-it", "Italiano (Italian)"), + ("fr-fr", "Français (French)"), + ("lt-lt", "Lietuvių (Lithuanian)"), + ("no-no", "Norsk (Norwegian)"), + ("pt-br", "Português do Brasil (Brazilian Portuguese)"), + ("pt-pt", "Português Europeu (European Portuguese)"), + ("ro-ro", "Română (Romanian)"), + ("sv-se", "Svenska (Swedish)"), + ("zh-hans", "简体中文 (Simplified Chinese)"), + ("zh-hant", "繁體中文 (Traditional Chinese)"), + ], + max_length=255, + null=True, + ), + ), + ] diff --git a/bookwyrm/migrations/0148_alter_user_preferred_language.py b/bookwyrm/migrations/0148_alter_user_preferred_language.py new file mode 100644 index 000000000..05784f280 --- /dev/null +++ b/bookwyrm/migrations/0148_alter_user_preferred_language.py @@ -0,0 +1,39 @@ +# Generated by Django 3.2.12 on 2022-03-31 14:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0147_alter_user_preferred_language"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="preferred_language", + field=models.CharField( + blank=True, + choices=[ + ("en-us", "English"), + ("de-de", "Deutsch (German)"), + ("es-es", "Español (Spanish)"), + ("gl-es", "Galego (Galician)"), + ("it-it", "Italiano (Italian)"), + ("fi-fi", "Suomi (Finnish)"), + ("fr-fr", "Français (French)"), + ("lt-lt", "Lietuvių (Lithuanian)"), + ("no-no", "Norsk (Norwegian)"), + ("pt-br", "Português do Brasil (Brazilian Portuguese)"), + ("pt-pt", "Português Europeu (European Portuguese)"), + ("ro-ro", "Română (Romanian)"), + ("sv-se", "Svenska (Swedish)"), + ("zh-hans", "简体中文 (Simplified Chinese)"), + ("zh-hant", "繁體中文 (Traditional Chinese)"), + ], + max_length=255, + null=True, + ), + ), + ] diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py index f8d3b7818..eeb2e940d 100644 --- a/bookwyrm/models/base_model.py +++ b/bookwyrm/models/base_model.py @@ -8,6 +8,7 @@ from django.db.models import Q from django.dispatch import receiver from django.http import Http404 from django.utils.translation import gettext_lazy as _ +from django.utils.text import slugify from bookwyrm.settings import DOMAIN from .fields import RemoteIdField @@ -35,10 +36,11 @@ class BookWyrmModel(models.Model): remote_id = RemoteIdField(null=True, activitypub_field="id") def get_remote_id(self): - """generate a url that resolves to the local object""" + """generate the url that resolves to the local object, without a slug""" base_path = f"https://{DOMAIN}" if hasattr(self, "user"): base_path = f"{base_path}{self.user.local_path}" + model_name = type(self).__name__.lower() return f"{base_path}/{model_name}/{self.id}" @@ -49,8 +51,20 @@ class BookWyrmModel(models.Model): @property def local_path(self): - """how to link to this object in the local app""" - return self.get_remote_id().replace(f"https://{DOMAIN}", "") + """how to link to this object in the local app, with a slug""" + local = self.get_remote_id().replace(f"https://{DOMAIN}", "") + + name = None + if hasattr(self, "name_field"): + name = getattr(self, self.name_field) + elif hasattr(self, "name"): + name = self.name + + if name: + slug = slugify(name) + local = f"{local}/s/{slug}" + + return local def raise_visible_to_user(self, viewer): """is a user authorized to view an object?""" diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index b506c11ca..62c61cc40 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -125,7 +125,7 @@ class ActivitypubFieldMixin: """model_field_name to activitypubFieldName""" if self.activitypub_field: return self.activitypub_field - name = self.name.split(".")[-1] + name = self.name.rsplit(".", maxsplit=1)[-1] components = name.split("_") return components[0] + "".join(x.title() for x in components[1:]) @@ -389,7 +389,7 @@ class ImageField(ActivitypubFieldMixin, models.ImageField): self.alt_field = alt_field super().__init__(*args, **kwargs) - # pylint: disable=arguments-differ + # pylint: disable=arguments-differ,arguments-renamed def set_field_from_activity(self, instance, data, save=True, overwrite=True): """helper function for assinging a value to the field""" value = getattr(data, self.get_activitypub_field()) diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index e95c38fa5..171f45840 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -39,15 +39,14 @@ class UserRelationship(BookWyrmModel): def save(self, *args, **kwargs): """clear the template cache""" - # invalidate the template cache - cache.delete_many( - [ - f"relationship-{self.user_subject.id}-{self.user_object.id}", - f"relationship-{self.user_object.id}-{self.user_subject.id}", - ] - ) + clear_cache(self.user_subject, self.user_object) super().save(*args, **kwargs) + def delete(self, *args, **kwargs): + """clear the template cache""" + clear_cache(self.user_subject, self.user_object) + super().delete(*args, **kwargs) + class Meta: """relationships should be unique""" @@ -90,7 +89,9 @@ class UserFollows(ActivityMixin, UserRelationship): user_object=self.user_subject, ) ).exists(): - raise IntegrityError() + raise IntegrityError( + "Attempting to follow blocked user", self.user_subject, self.user_object + ) # don't broadcast this type of relationship -- accepts and requests # are handled by the UserFollowRequest model super().save(*args, broadcast=False, **kwargs) @@ -98,11 +99,12 @@ class UserFollows(ActivityMixin, UserRelationship): @classmethod def from_request(cls, follow_request): """converts a follow request into a follow relationship""" - return cls.objects.create( + obj, _ = cls.objects.get_or_create( user_subject=follow_request.user_subject, user_object=follow_request.user_object, remote_id=follow_request.remote_id, ) + return obj class UserFollowRequest(ActivitypubMixin, UserRelationship): @@ -133,7 +135,9 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship): user_object=self.user_subject, ) ).exists(): - raise IntegrityError() + raise IntegrityError( + "Attempting to follow blocked user", self.user_subject, self.user_object + ) super().save(*args, **kwargs) if broadcast and self.user_subject.local and not self.user_object.local: @@ -174,7 +178,8 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship): with transaction.atomic(): UserFollows.from_request(self) - self.delete() + if self.id: + self.delete() def reject(self): """generate a Reject for this follow request""" @@ -207,3 +212,13 @@ class UserBlocks(ActivityMixin, UserRelationship): Q(user_subject=self.user_subject, user_object=self.user_object) | Q(user_subject=self.user_object, user_object=self.user_subject) ).delete() + + +def clear_cache(user_subject, user_object): + """clear relationship cache""" + cache.delete_many( + [ + f"relationship-{user_subject.id}-{user_object.id}", + f"relationship-{user_object.id}-{user_subject.id}", + ] + ) diff --git a/bookwyrm/models/shelf.py b/bookwyrm/models/shelf.py index 320d495d2..8ea274ea1 100644 --- a/bookwyrm/models/shelf.py +++ b/bookwyrm/models/shelf.py @@ -6,6 +6,7 @@ from django.db import models from django.utils import timezone from bookwyrm import activitypub +from bookwyrm.settings import DOMAIN from .activitypub_mixin import CollectionItemMixin, OrderedCollectionMixin from .base_model import BookWyrmModel from . import fields @@ -65,6 +66,11 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel): identifier = self.identifier or self.get_identifier() return f"{base_path}/books/{identifier}" + @property + def local_path(self): + """No slugs""" + return self.get_remote_id().replace(f"https://{DOMAIN}", "") + def raise_not_deletable(self, viewer): """don't let anyone delete a default shelf""" super().raise_not_deletable(viewer) diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index f8d4c397f..416610e49 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -284,11 +284,13 @@ LANGUAGES = [ ("es-es", _("Español (Spanish)")), ("gl-es", _("Galego (Galician)")), ("it-it", _("Italiano (Italian)")), + ("fi-fi", _("Suomi (Finnish)")), ("fr-fr", _("Français (French)")), ("lt-lt", _("Lietuvių (Lithuanian)")), ("no-no", _("Norsk (Norwegian)")), ("pt-br", _("Português do Brasil (Brazilian Portuguese)")), ("pt-pt", _("Português Europeu (European Portuguese)")), + ("ro-ro", _("Română (Romanian)")), ("sv-se", _("Svenska (Swedish)")), ("zh-hans", _("简体中文 (Simplified Chinese)")), ("zh-hant", _("繁體中文 (Traditional Chinese)")), diff --git a/bookwyrm/static/css/bookwyrm/_all.scss b/bookwyrm/static/css/bookwyrm/_all.scss index 79e5cf52c..31e732ebe 100644 --- a/bookwyrm/static/css/bookwyrm/_all.scss +++ b/bookwyrm/static/css/bookwyrm/_all.scss @@ -36,6 +36,18 @@ body { flex-direction: column; } +::-webkit-scrollbar { + width: 12px; + height: 12px; +} +::-webkit-scrollbar-thumb { + background: $scrollbar-thumb; + border-radius: 0.5em; +} +::-webkit-scrollbar-track { + background: $scrollbar-track; +} + button { border: none; margin: 0; diff --git a/bookwyrm/static/css/bookwyrm/components/_details.scss b/bookwyrm/static/css/bookwyrm/components/_details.scss index 5708c9461..c9a0b33b8 100644 --- a/bookwyrm/static/css/bookwyrm/components/_details.scss +++ b/bookwyrm/static/css/bookwyrm/components/_details.scss @@ -114,3 +114,17 @@ details[open] summary .details-close { padding-bottom: 0.25rem; } } + +/** Navbar details + ******************************************************************************/ + +#navbar-dropdown .navbar-item { + color: $text; + font-size: 0.875rem; + padding: 0.375rem 3rem 0.375rem 1rem; + white-space: nowrap; +} + +#navbar-dropdown .navbar-item:hover { + background-color: $background-secondary; +} diff --git a/bookwyrm/static/css/bookwyrm/utilities/_colors.scss b/bookwyrm/static/css/bookwyrm/utilities/_colors.scss index e44efee95..f38d2a40b 100644 --- a/bookwyrm/static/css/bookwyrm/utilities/_colors.scss +++ b/bookwyrm/static/css/bookwyrm/utilities/_colors.scss @@ -23,3 +23,8 @@ .has-background-tertiary { background-color: $background-tertiary !important; } + +/* Workaround for dark theme as .has-text-black doesn't give desired effect. */ +.has-text-default { + color: $text !important; +} diff --git a/bookwyrm/static/css/themes/bookwyrm-dark.scss b/bookwyrm/static/css/themes/bookwyrm-dark.scss index 0a4f6f23e..88ee865bb 100644 --- a/bookwyrm/static/css/themes/bookwyrm-dark.scss +++ b/bookwyrm/static/css/themes/bookwyrm-dark.scss @@ -28,6 +28,8 @@ $background-body: rgb(24, 27, 28); $background-secondary: rgb(28, 30, 32); $background-tertiary: rgb(32, 34, 36); $modal-background-background-color: rgba($black, 0.8); +$scrollbar-track: $background-secondary; +$scrollbar-thumb: $light; /* highlight colors */ $primary-highlight: $primary; @@ -51,6 +53,7 @@ $link-hover: $white-bis; $link-hover-border: #51595d; $link-focus: $white-bis; $link-active: $white-bis; +$link-light: #0d1c26; /* bulma overrides */ $background: $background-secondary; @@ -81,6 +84,13 @@ $progress-value-background-color: $border-light; $family-primary: $family-sans-serif; $family-secondary: $family-sans-serif; +.has-text-muted { + color: $grey-lighter !important; +} + +.has-text-more-muted { + color: $grey-light !important; +} @import "../bookwyrm.scss"; @import "../vendor/icons.css"; diff --git a/bookwyrm/static/css/themes/bookwyrm-light.scss b/bookwyrm/static/css/themes/bookwyrm-light.scss index c74d2ee20..75f05164b 100644 --- a/bookwyrm/static/css/themes/bookwyrm-light.scss +++ b/bookwyrm/static/css/themes/bookwyrm-light.scss @@ -19,6 +19,8 @@ $scheme-main: $white-bis; $background-body: $white; $background-secondary: $white-ter; $background-tertiary: $white-bis; +$scrollbar-track: $background-secondary; +$scrollbar-thumb: $grey-lighter; /* highlight colors */ $primary-highlight: $primary-light; @@ -55,5 +57,13 @@ $invisible-overlay-background-color: rgba($scheme-invert, 0.66); $family-primary: $family-sans-serif; $family-secondary: $family-sans-serif; +.has-text-muted { + color: $grey-dark !important; +} + +.has-text-more-muted { + color: $grey !important; +} + @import "../bookwyrm.scss"; @import "../vendor/icons.css"; diff --git a/bookwyrm/templates/about/about.html b/bookwyrm/templates/about/about.html index f1ddd2f38..b04e21b17 100644 --- a/bookwyrm/templates/about/about.html +++ b/bookwyrm/templates/about/about.html @@ -99,7 +99,7 @@

{% url "conduct" as coc_path %} {% blocktrans trimmed with site_name=site.name %} - {{ site_name }}'s moderators and administrators keep the site up and running, enforce the code of conduct, and respond when users report spam and bad behavior. + {{ site_name }}'s moderators and administrators keep the site up and running, enforce the code of conduct, and respond when users report spam and bad behavior. {% endblocktrans %}

diff --git a/bookwyrm/templates/book/book.html b/bookwyrm/templates/book/book.html index 453480f99..e7d10f4f3 100644 --- a/bookwyrm/templates/book/book.html +++ b/bookwyrm/templates/book/book.html @@ -284,7 +284,7 @@ {% if user_statuses.review_count or user_statuses.comment_count or user_statuses.quotation_count %}