diff --git a/.env.example b/.env.example index 7769a67b..31e46447 100644 --- a/.env.example +++ b/.env.example @@ -41,7 +41,7 @@ REDIS_BROKER_PASSWORD=redispassword123 # Monitoring for celery FLOWER_PORT=8888 -FLOWER_USER=mouse +FLOWER_USER=admin FLOWER_PASSWORD=changeme # Email config diff --git a/.github/workflows/lint-frontend.yaml b/.github/workflows/lint-frontend.yaml index 041afa13..7041c48b 100644 --- a/.github/workflows/lint-frontend.yaml +++ b/.github/workflows/lint-frontend.yaml @@ -8,7 +8,7 @@ on: - '.github/workflows/**' - 'static/**' - '.eslintrc' - - '.stylelintrc' + - '.stylelintrc.js' pull_request: branches: [ main, ci, frontend ] @@ -22,17 +22,16 @@ jobs: - uses: actions/checkout@v2 - name: Install modules - run: yarn + run: npm install stylelint stylelint-config-recommended stylelint-config-standard stylelint-order eslint # See .stylelintignore for files that are not linted. - name: Run stylelint run: > - yarn stylelint bookwyrm/static/**/*.css \ - --report-needless-disables \ - --report-invalid-scope-disables + npx stylelint bookwyrm/static/css/*.css \ + --config dev-tools/.stylelintrc.js # See .eslintignore for files that are not linted. - name: Run ESLint run: > - yarn eslint bookwyrm/static \ + npx eslint bookwyrm/static \ --ext .js,.jsx,.ts,.tsx diff --git a/.github/workflows/prettier.yaml b/.github/workflows/prettier.yaml index 80696060..c4a031db 100644 --- a/.github/workflows/prettier.yaml +++ b/.github/workflows/prettier.yaml @@ -17,8 +17,7 @@ jobs: - uses: actions/checkout@v2 - name: Install modules - run: npm install . + run: npm install prettier - # See .stylelintignore for files that are not linted. - name: Run Prettier run: npx prettier --check bookwyrm/static/js/*.js diff --git a/.gitignore b/.gitignore index e5582694..e11bbfbe 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,9 @@ .idea #Node tools -/node_modules/ +node_modules/ +package-lock.json +yarn.lock #nginx nginx/default.conf diff --git a/bookwyrm/activitypub/book.py b/bookwyrm/activitypub/book.py index 2238e3a8..e6a01b35 100644 --- a/bookwyrm/activitypub/book.py +++ b/bookwyrm/activitypub/book.py @@ -16,6 +16,9 @@ class BookData(ActivityObject): librarythingKey: str = None goodreadsKey: str = None bnfId: str = None + viaf: str = None + wikidata: str = None + asin: str = None lastEditedBy: str = None links: List[str] = field(default_factory=lambda: []) fileLinks: List[str] = field(default_factory=lambda: []) @@ -27,8 +30,8 @@ class Book(BookData): """serializes an edition or work, abstract""" title: str - sortTitle: str = "" - subtitle: str = "" + sortTitle: str = None + subtitle: str = None description: str = "" languages: List[str] = field(default_factory=lambda: []) series: str = "" @@ -53,7 +56,6 @@ class Edition(Book): isbn10: str = "" isbn13: str = "" oclcNumber: str = "" - asin: str = "" pages: int = None physicalFormat: str = "" physicalFormatDetail: str = "" diff --git a/bookwyrm/apps.py b/bookwyrm/apps.py index af3b064e..d494877d 100644 --- a/bookwyrm/apps.py +++ b/bookwyrm/apps.py @@ -32,8 +32,11 @@ class BookwyrmConfig(AppConfig): name = "bookwyrm" verbose_name = "BookWyrm" + # pylint: disable=no-self-use def ready(self): + """set up OTLP and preview image files, if desired""" if settings.OTEL_EXPORTER_OTLP_ENDPOINT: + # pylint: disable=import-outside-toplevel from bookwyrm.telemetry import open_telemetry open_telemetry.instrumentDjango() diff --git a/bookwyrm/connectors/openlibrary.py b/bookwyrm/connectors/openlibrary.py index c15277f8..118222a1 100644 --- a/bookwyrm/connectors/openlibrary.py +++ b/bookwyrm/connectors/openlibrary.py @@ -68,7 +68,30 @@ class Connector(AbstractConnector): Mapping("born", remote_field="birth_date"), Mapping("died", remote_field="death_date"), Mapping("bio", formatter=get_description), - Mapping("isni", remote_field="remote_ids", formatter=get_isni), + Mapping( + "isni", + remote_field="remote_ids", + formatter=lambda b: get_dict_field(b, "isni"), + ), + Mapping( + "asin", + remote_field="remote_ids", + formatter=lambda b: get_dict_field(b, "amazon"), + ), + Mapping( + "viaf", + remote_field="remote_ids", + formatter=lambda b: get_dict_field(b, "viaf"), + ), + Mapping( + "wikidata", + remote_field="remote_ids", + formatter=lambda b: get_dict_field(b, "wikidata"), + ), + Mapping( + "wikipedia_link", remote_field="links", formatter=get_wikipedia_link + ), + Mapping("inventaire_id", remote_field="links", formatter=get_inventaire_id), ] def get_book_data(self, remote_id): @@ -227,11 +250,38 @@ def get_languages(language_blob): return langs -def get_isni(remote_ids_blob): +def get_dict_field(blob, field_name): """extract the isni from the remote id data for the author""" - if not remote_ids_blob or not isinstance(remote_ids_blob, dict): + if not blob or not isinstance(blob, dict): return None - return remote_ids_blob.get("isni") + return blob.get(field_name) + + +def get_wikipedia_link(links): + """extract wikipedia links""" + if not isinstance(links, list): + return None + + for link in links: + if not isinstance(link, dict): + continue + if link.get("title") == "wikipedia": + return link.get("url") + return None + + +def get_inventaire_id(links): + """extract and format inventaire ids""" + if not isinstance(links, list): + return None + + for link in links: + if not isinstance(link, dict): + continue + if link.get("title") == "inventaire.io": + iv_link = link.get("url") + return iv_link.split("/")[-1] + return None def pick_default_edition(options): diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index 564ea91b..865bc02f 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -54,6 +54,13 @@ class RegisterForm(CustomForm): help_texts = {f: None for f in fields} widgets = {"password": PasswordInput()} + def clean(self): + """Check if the username is taken""" + cleaned_data = super().clean() + localname = cleaned_data.get("localname").strip() + if models.User.objects.filter(localname=localname).first(): + self.add_error("localname", _("User with this username already exists")) + class RatingForm(CustomForm): class Meta: @@ -433,7 +440,7 @@ class GoalForm(CustomForm): class SiteForm(CustomForm): class Meta: model = models.SiteSettings - exclude = [] + exclude = ["admin_code", "install_mode"] widgets = { "instance_short_description": forms.TextInput( attrs={"aria-describedby": "desc_instance_short_description"} diff --git a/bookwyrm/management/commands/admin_code.py b/bookwyrm/management/commands/admin_code.py new file mode 100644 index 00000000..ee69b78c --- /dev/null +++ b/bookwyrm/management/commands/admin_code.py @@ -0,0 +1,23 @@ +""" Get your admin code to allow install """ +from django.core.management.base import BaseCommand + +from bookwyrm import models + + +def get_admin_code(): + """get that code""" + return models.SiteSettings.objects.get().admin_code + + +class Command(BaseCommand): + """command-line options""" + + help = "Gets admin code for configuring BookWyrm" + + # pylint: disable=unused-argument + def handle(self, *args, **options): + """execute init""" + self.stdout.write("*******************************************") + self.stdout.write("Use this code to create your admin account:") + self.stdout.write(get_admin_code()) + self.stdout.write("*******************************************") diff --git a/bookwyrm/management/commands/generate_preview_images.py b/bookwyrm/management/commands/generate_preview_images.py index df218623..0454e5e5 100644 --- a/bookwyrm/management/commands/generate_preview_images.py +++ b/bookwyrm/management/commands/generate_preview_images.py @@ -10,7 +10,9 @@ class Command(BaseCommand): help = "Generate preview images" + # pylint: disable=no-self-use def add_arguments(self, parser): + """options for how the command is run""" parser.add_argument( "--all", "-a", @@ -38,6 +40,7 @@ class Command(BaseCommand): preview_images.generate_site_preview_image_task.delay() self.stdout.write(" OK 🖼") + # pylint: disable=consider-using-f-string if options["all"]: # Users users = models.User.objects.filter( diff --git a/bookwyrm/management/commands/initdb.py b/bookwyrm/management/commands/initdb.py index 09d86462..4e23a530 100644 --- a/bookwyrm/management/commands/initdb.py +++ b/bookwyrm/management/commands/initdb.py @@ -120,6 +120,7 @@ def init_settings(): models.SiteSettings.objects.create( support_link="https://www.patreon.com/bookwyrm", support_title="Patreon", + install_mode=True, ) diff --git a/bookwyrm/migrations/0134_announcement_display_type.py b/bookwyrm/migrations/0134_announcement_display_type.py new file mode 100644 index 00000000..ba845012 --- /dev/null +++ b/bookwyrm/migrations/0134_announcement_display_type.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.11 on 2022-02-11 18:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0133_alter_listitem_notes"), + ] + + operations = [ + migrations.AddField( + model_name="announcement", + name="display_type", + field=models.CharField( + choices=[ + ("white-ter", "None"), + ("primary-light", "Primary"), + ("success-light", "Success"), + ("link-light", "Link"), + ("warning-light", "Warning"), + ("danger-light", "Danger"), + ], + default="white-ter", + max_length=20, + ), + ), + ] diff --git a/bookwyrm/migrations/0135_auto_20220217_1624.py b/bookwyrm/migrations/0135_auto_20220217_1624.py new file mode 100644 index 00000000..b557fb03 --- /dev/null +++ b/bookwyrm/migrations/0135_auto_20220217_1624.py @@ -0,0 +1,58 @@ +# Generated by Django 3.2.12 on 2022-02-17 16:24 + +import bookwyrm.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0134_announcement_display_type"), + ] + + operations = [ + migrations.RenameField( + model_name="author", + old_name="viaf_id", + new_name="viaf", + ), + migrations.RemoveField( + model_name="edition", + name="asin", + ), + migrations.AddField( + model_name="author", + name="asin", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), + ), + migrations.AddField( + model_name="author", + name="wikidata", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), + ), + migrations.AddField( + model_name="book", + name="asin", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), + ), + migrations.AddField( + model_name="book", + name="viaf", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), + ), + migrations.AddField( + model_name="book", + name="wikidata", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), + ), + ] diff --git a/bookwyrm/migrations/0136_auto_20220217_1708.py b/bookwyrm/migrations/0136_auto_20220217_1708.py new file mode 100644 index 00000000..916fb124 --- /dev/null +++ b/bookwyrm/migrations/0136_auto_20220217_1708.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.12 on 2022-02-17 17:08 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0135_auto_20220217_1624"), + ] + + operations = [ + migrations.AddField( + model_name="sitesettings", + name="admin_code", + field=models.CharField(default=uuid.uuid4, max_length=50), + ), + migrations.AddField( + model_name="sitesettings", + name="install_mode", + field=models.BooleanField(default=False), + ), + ] diff --git a/bookwyrm/migrations/0137_alter_sitesettings_allow_registration.py b/bookwyrm/migrations/0137_alter_sitesettings_allow_registration.py new file mode 100644 index 00000000..ba5000ac --- /dev/null +++ b/bookwyrm/migrations/0137_alter_sitesettings_allow_registration.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2022-02-17 19:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0136_auto_20220217_1708"), + ] + + operations = [ + migrations.AlterField( + model_name="sitesettings", + name="allow_registration", + field=models.BooleanField(default=False), + ), + ] diff --git a/bookwyrm/models/announcement.py b/bookwyrm/models/announcement.py index 498d5041..cbed38ae 100644 --- a/bookwyrm/models/announcement.py +++ b/bookwyrm/models/announcement.py @@ -2,10 +2,21 @@ from django.db import models from django.db.models import Q from django.utils import timezone +from django.utils.translation import gettext_lazy as _ from .base_model import BookWyrmModel +DisplayTypes = [ + ("white-ter", _("None")), + ("primary-light", _("Primary")), + ("success-light", _("Success")), + ("link-light", _("Link")), + ("warning-light", _("Warning")), + ("danger-light", _("Danger")), +] + + class Announcement(BookWyrmModel): """The admin has something to say""" @@ -16,6 +27,13 @@ class Announcement(BookWyrmModel): start_date = models.DateTimeField(blank=True, null=True) end_date = models.DateTimeField(blank=True, null=True) active = models.BooleanField(default=True) + display_type = models.CharField( + max_length=20, + blank=False, + null=False, + choices=DisplayTypes, + default="white-ter", + ) @classmethod def active_announcements(cls): diff --git a/bookwyrm/models/author.py b/bookwyrm/models/author.py index 5edac57d..78d153a2 100644 --- a/bookwyrm/models/author.py +++ b/bookwyrm/models/author.py @@ -21,9 +21,6 @@ class Author(BookDataModel): isni = fields.CharField( max_length=255, blank=True, null=True, deduplication_field=True ) - viaf_id = fields.CharField( - max_length=255, blank=True, null=True, deduplication_field=True - ) gutenberg_id = fields.CharField( max_length=255, blank=True, null=True, deduplication_field=True ) diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index ffc03d3e..3ea8e1a8 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -46,6 +46,15 @@ class BookDataModel(ObjectMixin, BookWyrmModel): bnf_id = fields.CharField( # Bibliothèque nationale de France max_length=255, blank=True, null=True, deduplication_field=True ) + viaf = fields.CharField( + max_length=255, blank=True, null=True, deduplication_field=True + ) + wikidata = fields.CharField( + max_length=255, blank=True, null=True, deduplication_field=True + ) + asin = fields.CharField( + max_length=255, blank=True, null=True, deduplication_field=True + ) search_vector = SearchVectorField(null=True) last_edited_by = fields.ForeignKey( @@ -271,9 +280,6 @@ class Edition(Book): oclc_number = fields.CharField( max_length=255, blank=True, null=True, deduplication_field=True ) - asin = fields.CharField( - max_length=255, blank=True, null=True, deduplication_field=True - ) pages = fields.IntegerField(blank=True, null=True) physical_format = fields.CharField( max_length=255, choices=FormatChoices, null=True, blank=True diff --git a/bookwyrm/models/site.py b/bookwyrm/models/site.py index b2119e23..a40d295b 100644 --- a/bookwyrm/models/site.py +++ b/bookwyrm/models/site.py @@ -1,6 +1,7 @@ """ the particulars for this instance of BookWyrm """ import datetime from urllib.parse import urljoin +import uuid from django.db import models, IntegrityError from django.dispatch import receiver @@ -24,6 +25,10 @@ class SiteSettings(models.Model): instance_description = models.TextField(default="This instance has no description.") instance_short_description = models.CharField(max_length=255, blank=True, null=True) + # admin setup options + install_mode = models.BooleanField(default=False) + admin_code = models.CharField(max_length=50, default=uuid.uuid4) + # about page registration_closed_text = models.TextField( default="We aren't taking new users at this time. You can find an open " @@ -38,7 +43,7 @@ class SiteSettings(models.Model): privacy_policy = models.TextField(default="Add a privacy policy here.") # registration - allow_registration = models.BooleanField(default=True) + allow_registration = models.BooleanField(default=False) allow_invite_requests = models.BooleanField(default=True) require_confirm_email = models.BooleanField(default=True) diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 41e15922..7bbdef66 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -11,6 +11,11 @@ env.read_env() DOMAIN = env("DOMAIN") VERSION = "0.3.0" +RELEASE_API = env( + "RELEASE_API", + "https://api.github.com/repos/bookwyrm-social/bookwyrm/releases/latest", +) + PAGE_LENGTH = env("PAGE_LENGTH", 15) DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English") @@ -67,7 +72,7 @@ SECRET_KEY = env("SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! DEBUG = env.bool("DEBUG", True) -USE_HTTPS = env.bool("USE_HTTPS", False) +USE_HTTPS = env.bool("USE_HTTPS", not DEBUG) ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", ["*"]) diff --git a/bookwyrm/static/css/bookwyrm.css b/bookwyrm/static/css/bookwyrm.css index 5084d50d..303bc031 100644 --- a/bookwyrm/static/css/bookwyrm.css +++ b/bookwyrm/static/css/bookwyrm.css @@ -301,6 +301,183 @@ details.dropdown .dropdown-menu a:focus-visible { } } +/** Bookwyrm Tabs + ******************************************************************************/ + +.bw-tabs { + -webkit-overflow-scrolling: touch; + -webkit-touch-callout: none; + position: relative; + align-items: center; + display: flex; + font-size: 1rem; + justify-content: flex-start; + overflow-x: auto; + overflow-y: hidden; + user-select: none; + white-space: nowrap; +} + +.bw-tabs::before { + border-bottom-color: #dbdbdb; + border-bottom-style: solid; + border-bottom-width: 1px; + bottom: 0; + content: ""; + position: absolute; + width: 100%; +} + +.bw-tabs:not(:last-child) { + margin-bottom: 1.5rem; +} + +.bw-tabs a { + align-items: center; + border-bottom-color: #dbdbdb; + border-bottom-style: solid; + border-bottom-width: 1px; + color: #4a4a4a; + display: flex; + justify-content: center; + margin-bottom: -1px; + padding: 0.5em 1em; + position: relative; +} + +.bw-tabs a:hover { + border-bottom-color: transparent; + color: #363636; +} + +.bw-tabs a.is-active { + border-bottom-color: transparent; + color: #3273dc; +} + +.bw-tabs.is-left { + padding-right: 0.75em; +} + +.bw-tabs.is-center { + flex: none; + justify-content: center; + padding-left: 0.75em; + padding-right: 0.75em; +} + +.bw-tabs.is-right { + justify-content: flex-end; + padding-left: 0.75em; +} + +.bw-tabs .icon:first-child { + margin-right: 0.5em; +} + +.bw-tabs .icon:last-child { + margin-left: 0.5em; +} + +.bw-tabs.is-centered { + justify-content: center; +} + +.bw-tabs.is-boxed a { + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} + +.bw-tabs.is-boxed a:hover { + background-color: #f5f5f5; + border-bottom-color: #dbdbdb; +} + +.bw-tabs.is-boxed a.is-active { + background-color: #fff; + border-color: #dbdbdb; + border-bottom-color: #fff !important; +} + +.bw-tabs.is-fullwidth a { + flex-grow: 1; + flex-shrink: 0; +} + +.bw-tabs.is-toggle a { + border-color: #dbdbdb; + border-style: solid; + border-width: 1px; + margin-bottom: 0; + position: relative; +} + +.bw-tabs.is-toggle a:hover { + background-color: #f5f5f5; + border-color: #b5b5b5; + z-index: 2; +} + +.bw-tabs.is-toggle a + a { + margin-left: -1px; +} + +.bw-tabs.is-toggle a:first-child { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} + +.bw-tabs.is-toggle a:last-child { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.bw-tabs.is-toggle a.is-active { + background-color: #3273dc; + border-color: #3273dc; + color: #fff; + z-index: 1; +} + +.bw-tabs.is-toggle { + border-bottom: none; +} + +.bw-tabs.is-toggle.is-toggle-rounded a:first-child { + border-bottom-left-radius: 290486px; + border-top-left-radius: 290486px; + padding-left: 1.25em; +} + +.bw-tabs.is-toggle.is-toggle-rounded a:last-child { + border-bottom-right-radius: 290486px; + border-top-right-radius: 290486px; + padding-right: 1.25em; +} + +.bw-tabs.is-small { + font-size: 0.75rem; +} + +.bw-tabs.is-medium { + font-size: 1.25rem; +} + +.bw-tabs.is-large { + font-size: 1.5rem; +} + +.bw-tabs.has-aside-text a { + margin-top: 1.5rem; +} + +.bw-tabs a .aside-text { + position: absolute; + top: calc(-0.75rem - 0.75rem); + left: 0; + color: #4a4a4a; +} + /** Details panel ******************************************************************************/ @@ -319,7 +496,7 @@ details.details-panel summary { position: relative; } -details.details-panel summary .details-close { +details summary .details-close { position: absolute; right: 0; top: 0; @@ -327,7 +504,7 @@ details.details-panel summary .details-close { transition: transform 0.2s ease; } -details[open].details-panel summary .details-close { +details[open] summary .details-close { transform: rotate(0deg); } diff --git a/bookwyrm/static/js/vendor/tabs.js b/bookwyrm/static/js/vendor/tabs.js index f9568b29..0535cc86 100644 --- a/bookwyrm/static/js/vendor/tabs.js +++ b/bookwyrm/static/js/vendor/tabs.js @@ -11,17 +11,17 @@ class TabGroup { this.container = container; this.tablist = this.container.querySelector('[role="tablist"]'); - this.buttons = this.tablist.querySelectorAll('[role="tab"]'); + this.tabs = this.tablist.querySelectorAll('[role="tab"]'); this.panels = this.container.querySelectorAll(':scope > [role="tabpanel"]'); this.delay = this.determineDelay(); - if(!this.tablist || !this.buttons.length || !this.panels.length) { + if(!this.tablist || !this.tabs.length || !this.panels.length) { return; } this.keys = this.keys(); this.direction = this.direction(); - this.initButtons(); + this.initTabs(); this.initPanels(); } @@ -46,17 +46,21 @@ class TabGroup { }; } - initButtons() { + initTabs() { let count = 0; - for(let button of this.buttons) { - let isSelected = button.getAttribute("aria-selected") === "true"; - button.setAttribute("tabindex", isSelected ? "0" : "-1"); + for(let tab of this.tabs) { + let isSelected = tab.getAttribute("aria-selected") === "true"; + tab.setAttribute("tabindex", isSelected ? "0" : "-1"); - button.addEventListener('click', this.clickEventListener.bind(this)); - button.addEventListener('keydown', this.keydownEventListener.bind(this)); - button.addEventListener('keyup', this.keyupEventListener.bind(this)); + tab.addEventListener('click', this.clickEventListener.bind(this)); + tab.addEventListener('keydown', this.keydownEventListener.bind(this)); + tab.addEventListener('keyup', this.keyupEventListener.bind(this)); - button.index = count++; + if (isSelected) { + tab.scrollIntoView(); + } + + tab.index = count++; } } @@ -73,11 +77,11 @@ class TabGroup { } clickEventListener(event) { - let button = event.target.closest('a'); + let tab = event.target.closest('[role="tab"]'); event.preventDefault(); - this.activateTab(button, false); + this.activateTab(tab, false); } // Handle keydown on tabs @@ -88,12 +92,12 @@ class TabGroup { case this.keys.end: event.preventDefault(); // Activate last tab - this.activateTab(this.buttons[this.buttons.length - 1]); + this.activateTab(this.tabs[this.tabs.length - 1]); break; case this.keys.home: event.preventDefault(); // Activate first tab - this.activateTab(this.buttons[0]); + this.activateTab(this.tabs[0]); break; // Up and down are in keydown @@ -147,15 +151,15 @@ class TabGroup { switchTabOnArrowPress(event) { var pressed = event.keyCode; - for (let button of this.buttons) { - button.addEventListener('focus', this.focusEventHandler.bind(this)); + for (let tab of this.tabs) { + tab.addEventListener('focus', this.focusEventHandler.bind(this)); } if (this.direction[pressed]) { var target = event.target; if (target.index !== undefined) { - if (this.buttons[target.index + this.direction[pressed]]) { - this.buttons[target.index + this.direction[pressed]].focus(); + if (this.tabs[target.index + this.direction[pressed]]) { + this.tabs[target.index + this.direction[pressed]].focus(); } else if (pressed === this.keys.left || pressed === this.keys.up) { this.focusLastTab(); @@ -184,8 +188,8 @@ class TabGroup { // Set the tab as selected tab.setAttribute('aria-selected', 'true'); - // Give the tab parent an is-active class - tab.parentNode.classList.add('is-active'); + // Give the tab is-active class + tab.classList.add('is-active'); // Get the value of aria-controls (which is an ID) var controls = tab.getAttribute('aria-controls'); @@ -201,11 +205,11 @@ class TabGroup { // Deactivate all tabs and tab panels deactivateTabs() { - for (let button of this.buttons) { - button.parentNode.classList.remove('is-active'); - button.setAttribute('tabindex', '-1'); - button.setAttribute('aria-selected', 'false'); - button.removeEventListener('focus', this.focusEventHandler.bind(this)); + for (let tab of this.tabs) { + tab.classList.remove('is-active'); + tab.setAttribute('tabindex', '-1'); + tab.setAttribute('aria-selected', 'false'); + tab.removeEventListener('focus', this.focusEventHandler.bind(this)); } for (let panel of this.panels) { @@ -214,11 +218,11 @@ class TabGroup { } focusFirstTab() { - this.buttons[0].focus(); + this.tabs[0].focus(); } focusLastTab() { - this.buttons[this.buttons.length - 1].focus(); + this.tabs[this.tabs.length - 1].focus(); } // Determine whether there should be a delay diff --git a/bookwyrm/templates/book/file_links/edit_links.html b/bookwyrm/templates/book/file_links/edit_links.html index 39d3b998..9048ce7a 100644 --- a/bookwyrm/templates/book/file_links/edit_links.html +++ b/bookwyrm/templates/book/file_links/edit_links.html @@ -6,24 +6,24 @@ {% block content %} -
+

{% blocktrans with title=book|book_title %} Links for "{{ title }}" {% endblocktrans %}

-
- + +
diff --git a/bookwyrm/templates/discover/large-book.html b/bookwyrm/templates/discover/large-book.html index a6ff0aca..d227502e 100644 --- a/bookwyrm/templates/discover/large-book.html +++ b/bookwyrm/templates/discover/large-book.html @@ -30,7 +30,7 @@
diff --git a/bookwyrm/templates/discover/small-book.html b/bookwyrm/templates/discover/small-book.html index 2da93d52..320064fc 100644 --- a/bookwyrm/templates/discover/small-book.html +++ b/bookwyrm/templates/discover/small-book.html @@ -15,7 +15,7 @@
diff --git a/bookwyrm/templates/feed/suggested_books.html b/bookwyrm/templates/feed/suggested_books.html index 435d4f51..2582dcf0 100644 --- a/bookwyrm/templates/feed/suggested_books.html +++ b/bookwyrm/templates/feed/suggested_books.html @@ -9,37 +9,30 @@ {% else %} {% with active_book=request.GET.book %}
-
- + {% endwith %} + {% endif %} + {% endfor %}
{% for shelf in suggested_books %} {% with shelf_counter=forloop.counter %} diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index 43e8eb22..a7ea3923 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -32,6 +32,7 @@ {% block head_links %}{% endblock %} +{% block body %}