From 55ad637da4e4fee8f5120318a8a964c7764f0894 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Thu, 4 Mar 2021 18:40:57 +0100 Subject: [PATCH 001/288] Add editorconfig and matching Github Workflow. --- .editorconfig | 29 +++++++++++++++++++++++++++ .github/workflows/linters-global.yaml | 21 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/workflows/linters-global.yaml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..1a4da9972 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,29 @@ +# @see https://editorconfig.org/ + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 100 + +[*.{md,markdown}] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 +max_line_length = off + +[{bw-dev,fr-dev,LICENSE}] +max_line_length = off + +[*.{csv,json,html,md,po,py,svg,tsv}] +max_line_length = off + +[fonts/**] +insert_final_newline = unset diff --git a/.github/workflows/linters-global.yaml b/.github/workflows/linters-global.yaml new file mode 100644 index 000000000..b815ba628 --- /dev/null +++ b/.github/workflows/linters-global.yaml @@ -0,0 +1,21 @@ +# @url https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions +name: Global Linters + +on: + push: + branches: [ main, ci ] + pull_request: + branches: [ main, ci ] + +jobs: + linters: + name: linters + runs-on: ubuntu-20.04 + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: EditorConfig lint + uses: greut/eclint-action@v0 From 99d2b34a1e391d299f6a4460cb3dd1edd7f39b5d Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Thu, 4 Mar 2021 18:44:36 +0100 Subject: [PATCH 002/288] [lint] Fix missing new lines. --- .coveragerc | 2 +- .dockerignore | 2 +- .gitignore | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.coveragerc b/.coveragerc index 35bf78f5f..084064509 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,2 @@ [run] -omit = */test*,celerywyrm*,bookwyrm/migrations/* \ No newline at end of file +omit = */test*,celerywyrm*,bookwyrm/migrations/* diff --git a/.dockerignore b/.dockerignore index 3bf9f2c5b..a5130c8bd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,4 +4,4 @@ __pycache__ *.pyd .git .github -.pytest* \ No newline at end of file +.pytest* diff --git a/.gitignore b/.gitignore index 1384056f2..a81f0db1b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,4 @@ /images/ # Testing -.coverage \ No newline at end of file +.coverage From 4a8d9bee735b1587a099853e9d7f0145f9790318 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Thu, 4 Mar 2021 19:05:01 +0100 Subject: [PATCH 003/288] [lint] Fix indentation in .md files. --- .github/ISSUE_TEMPLATE/bug_report.md | 14 ++-- CODE_OF_CONDUCT.md | 6 +- README.md | 108 +++++++++++++-------------- 3 files changed, 64 insertions(+), 64 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea782..56af524e4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -24,15 +24,15 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] **Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] **Additional context** Add any other context about the problem here. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index a60597ae8..3ab87444d 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -23,13 +23,13 @@ include: Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or - advances +advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic - address, without explicit permission +address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a - professional setting +professional setting ## Our Responsibilities diff --git a/README.md b/README.md index 2564d7af0..f74556090 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,12 @@ Social reading and reviewing, decentralized with ActivityPub - [Joining BookWyrm](#joining-bookwyrm) - [Contributing](#contributing) - [About BookWyrm](#about-bookwyrm) - - [What it is and isn't](#what-it-is-and-isnt) - - [The role of federation](#the-role-of-federation) - - [Features](#features) - - [Setting up the developer environment](#setting-up-the-developer-environment) - - [Installing in Production](#installing-in-production) - - [Book data](#book-data) + - [What it is and isn't](#what-it-is-and-isnt) + - [The role of federation](#the-role-of-federation) + - [Features](#features) +- [Setting up the developer environment](#setting-up-the-developer-environment) +- [Installing in Production](#installing-in-production) +- [Book data](#book-data) ## 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://github.com/mouse-reeve/bookwyrm/blob/main/instances.md) list. @@ -47,49 +47,49 @@ Federation makes it possible to have small, self-determining communities, in con ### Features Since the project is still in its early stages, the features are growing every day, and there is plenty of room for suggestions and ideas. Open an [issue](https://github.com/mouse-reeve/bookwyrm/issues) to get the conversation going! - - Posting about books +- Posting about books - Compose reviews, with or without ratings, which are aggregated in the book page - Compose other kinds of statuses about books, such as: - - Comments on a book - - Quotes or excerpts + - Comments on a book + - Quotes or excerpts - Reply to statuses - View aggregate reviews of a book across connected BookWyrm instances - Differentiate local and federated reviews and rating in your activity feed - - Track reading activity +- Track reading activity - Shelve books on default "to-read," "currently reading," and "read" shelves - Create custom shelves - Store started reading/finished reading dates, as well as progress updates along the way - Update followers about reading activity (optionally, and with granular privacy controls) - Create lists of books which can be open to submissions from anyone, curated, or only edited by the creator - - Federation with ActivityPub +- Federation with ActivityPub - Broadcast and receive user statuses and activity - Share book data between instances to create a networked database of metadata - Identify shared books across instances and aggregate related content - Follow and interact with users across BookWyrm instances - Inter-operate with non-BookWyrm ActivityPub services (currently, Mastodon is supported) - - Granular privacy controls +- Granular privacy controls - Private, followers-only, and public privacy levels for posting, shelves, and lists - Option for users to manually approve followers - Allow blocking and flagging for moderation ### The Tech Stack Web backend - - [Django](https://www.djangoproject.com/) web server - - [PostgreSQL](https://www.postgresql.org/) database - - [ActivityPub](http://activitypub.rocks/) federation - - [Celery](http://celeryproject.org/) task queuing - - [Redis](https://redis.io/) task backend +- [Django](https://www.djangoproject.com/) web server +- [PostgreSQL](https://www.postgresql.org/) database +- [ActivityPub](http://activitypub.rocks/) federation +- [Celery](http://celeryproject.org/) task queuing +- [Redis](https://redis.io/) task backend Front end - - Django templates - - [Bulma.io](https://bulma.io/) css framework - - Vanilla JavaScript, in moderation +- Django templates +- [Bulma.io](https://bulma.io/) css framework +- Vanilla JavaScript, in moderation Deployment - - [Docker](https://www.docker.com/) and docker-compose - - [Gunicorn](https://gunicorn.org/) web runner - - [Flower](https://github.com/mher/flower) celery monitoring - - [Nginx](https://nginx.org/en/) HTTP server +- [Docker](https://www.docker.com/) and docker-compose +- [Gunicorn](https://gunicorn.org/) web runner +- [Flower](https://github.com/mher/flower) celery monitoring +- [Nginx](https://nginx.org/en/) HTTP server ## Setting up the developer environment @@ -144,10 +144,10 @@ You can add the `-l ` to only compile one language. When you refr This project is still young and isn't, at the moment, very stable, so please proceed with caution when running in production. ### Server setup - - Get a domain name and set up DNS for your server - - Set your server up with appropriate firewalls for running a web application (this instruction set is tested against Ubuntu 20.04) - - Set up an email service (such as mailgun) and the appropriate SMTP/DNS settings - - Install Docker and docker-compose +- Get a domain name and set up DNS for your server +- Set your server up with appropriate firewalls for running a web application (this instruction set is tested against Ubuntu 20.04) +- Set up an email service (such as mailgun) and the appropriate SMTP/DNS settings +- Install Docker and docker-compose ### Install and configure BookWyrm @@ -155,33 +155,33 @@ The `production` branch of BookWyrm contains a number of tools not on the `main` Instructions for running BookWyrm in production: - - Get the application code: - `git clone git@github.com:mouse-reeve/bookwyrm.git` - - Switch to the `production` branch - `git checkout production` - - Create your environment variables file - `cp .env.example .env` - - Add your domain, email address, SMTP credentials - - Set a secure redis password and secret key - - Set a secure database password for postgres - - Update your nginx configuration in `nginx/default.conf` - - Replace `your-domain.com` with your domain name - - Run the application (this should also set up a Certbot ssl cert for your domain) with - `docker-compose up --build`, and make sure all the images build successfully - - When docker has built successfully, stop the process with `CTRL-C` - - Comment out the `command: certonly...` line in `docker-compose.yml` - - Run docker-compose in the background with: `docker-compose up -d` - - Initialize the database with: `./bw-dev initdb` - - Set up schedule backups with cron that runs that `docker-compose exec db pg_dump -U ` and saves the backup to a safe location +- Get the application code: + `git clone git@github.com:mouse-reeve/bookwyrm.git` +- Switch to the `production` branch + `git checkout production` +- Create your environment variables file + `cp .env.example .env` + - Add your domain, email address, SMTP credentials + - Set a secure redis password and secret key + - Set a secure database password for postgres +- Update your nginx configuration in `nginx/default.conf` + - Replace `your-domain.com` with your domain name +- Run the application (this should also set up a Certbot ssl cert for your domain) with + `docker-compose up --build`, and make sure all the images build successfully +- When docker has built successfully, stop the process with `CTRL-C` +- Comment out the `command: certonly...` line in `docker-compose.yml` +- Run docker-compose in the background with: `docker-compose up -d` +- Initialize the database with: `./bw-dev initdb` +- Set up schedule backups with cron that runs that `docker-compose exec db pg_dump -U ` and saves the backup to a safe location Congrats! You did it, go to your domain and enjoy the fruits of your labors. ### Configure your instance - - Register a user account in the application UI - - Make your account a superuser (warning: do *not* use django's `createsuperuser` command) - - On your server, open the django shell +- Register a user account in the application UI +- Make your account a superuser (warning: do *not* use django's `createsuperuser` command) + - On your server, open the django shell `./bw-dev shell` - - Load your user and make it a superuser + - Load your user and make it a superuser ```python from bookwyrm import models user = models.User.objects.get(id=1) @@ -189,16 +189,16 @@ Congrats! You did it, go to your domain and enjoy the fruits of your labors. user.is_superuser = True user.save() ``` - - Go to the site settings (`/settings/site-settings` on your domain) and configure your instance name, description, code of conduct, and toggle whether registration is open on your instance + - Go to the site settings (`/settings/site-settings` on your domain) and configure your instance name, description, code of conduct, and toggle whether registration is open on your instance ## 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. There are three concepts in the book data model: - - `Book`, an abstract, high-level concept that could mean either a `Work` or an `Edition`. No data is saved as a `Book`, it serves as shared model for `Work` and `Edition` - - `Work`, the theoretical umbrella concept of a book that encompasses every edition of the book, and - - `Edition`, a concrete, actually published version of a book +- `Book`, an abstract, high-level concept that could mean either a `Work` or an `Edition`. No data is saved as a `Book`, it serves as shared model for `Work` and `Edition` +- `Work`, the theoretical umbrella concept of a book that encompasses every edition of the book, and +- `Edition`, a concrete, actually published version of a book Whenever a user interacts with a book, they are interacting with a specific edition. Every work has a default edition, but the user can select other editions. Reviews aggregated for all editions of a work when you view an edition's page. From 1266a740e559ad3b646260554c91b34ca6f0c559 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Thu, 4 Mar 2021 19:06:12 +0100 Subject: [PATCH 004/288] [lint] Fix indentation in .py files. --- bookwyrm/activitypub/response.py | 2 +- bookwyrm/models/book.py | 2 +- bookwyrm/models/fields.py | 8 +++++--- bookwyrm/models/status.py | 6 ++++-- bookwyrm/tests/views/test_status.py | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/bookwyrm/activitypub/response.py b/bookwyrm/activitypub/response.py index 8f3c050bb..2f4edaaf2 100644 --- a/bookwyrm/activitypub/response.py +++ b/bookwyrm/activitypub/response.py @@ -10,7 +10,7 @@ class ActivitypubResponse(JsonResponse): JsonResponse. """ def __init__(self, data, encoder=ActivityEncoder, safe=False, - json_dumps_params=None, **kwargs): + json_dumps_params=None, **kwargs): if 'content_type' not in kwargs: kwargs['content_type'] = 'application/activity+json' diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index f1f208303..ca2c09227 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -224,7 +224,7 @@ def isbn_10_to_13(isbn_10): # multiply the odd digits by 1 and the even digits by 3 and sum them try: checksum = sum(int(i) for i in converted[::2]) + \ - sum(int(i) * 3 for i in converted[1::2]) + sum(int(i) * 3 for i in converted[1::2]) except ValueError: return None # add the checksum mod 10 to the end diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index 4ea527eba..a00718bd3 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -46,9 +46,11 @@ def validate_username(value): class ActivitypubFieldMixin: ''' make a database field serializable ''' - def __init__(self, *args, \ - activitypub_field=None, activitypub_wrapper=None, - deduplication_field=False, **kwargs): + def __init__( + self, *args, \ + activitypub_field=None, activitypub_wrapper=None, + deduplication_field=False, **kwargs + ): self.deduplication_field = deduplication_field if activitypub_wrapper: self.activitypub_wrapper = activitypub_field diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index ba9727f58..30e879704 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -74,8 +74,10 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): for mention_user in self.mention_users.all(): # avoid double-notifying about this status if not mention_user.local or \ - (self.reply_parent and \ - mention_user == self.reply_parent.user): + ( + self.reply_parent and \ + mention_user == self.reply_parent.user + ): continue notification_model.objects.create( user=mention_user, diff --git a/bookwyrm/tests/views/test_status.py b/bookwyrm/tests/views/test_status.py index 4594a2031..98e3d399d 100644 --- a/bookwyrm/tests/views/test_status.py +++ b/bookwyrm/tests/views/test_status.py @@ -201,7 +201,7 @@ class StatusViews(TestCase): 'archive.org/details/dli.granth.72113/page/n25/mode/2up' \ % url) url = 'https://openlibrary.org/search' \ - '?q=arkady+strugatsky&mode=everything' + '?q=arkady+strugatsky&mode=everything' self.assertEqual( views.status.format_links(url), 'openlibrary.org/search' \ From fc16211125c6e8b17da302916d469d33fa48624f Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Thu, 4 Mar 2021 19:24:11 +0100 Subject: [PATCH 005/288] [lint] Ignore newline in svg files within fonts/. --- .editorconfig | 15 ++++++--------- bookwyrm/static/css/fonts/.editorconfig | 4 ++++ 2 files changed, 10 insertions(+), 9 deletions(-) create mode 100644 bookwyrm/static/css/fonts/.editorconfig diff --git a/.editorconfig b/.editorconfig index 1a4da9972..112b22d65 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,18 +12,15 @@ indent_style = space insert_final_newline = true max_line_length = 100 -[*.{md,markdown}] -trim_trailing_whitespace = false - -[*.{yml,yaml}] -indent_size = 2 -max_line_length = off - [{bw-dev,fr-dev,LICENSE}] max_line_length = off [*.{csv,json,html,md,po,py,svg,tsv}] max_line_length = off -[fonts/**] -insert_final_newline = unset +[*.{md,markdown}] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 +max_line_length = off diff --git a/bookwyrm/static/css/fonts/.editorconfig b/bookwyrm/static/css/fonts/.editorconfig new file mode 100644 index 000000000..2e5ec87a5 --- /dev/null +++ b/bookwyrm/static/css/fonts/.editorconfig @@ -0,0 +1,4 @@ +# @see https://editorconfig.org/ + +[*.svg] +insert_final_newline = unset From 420a33d79f5584c5ef7e0e80df5369f462ee5aae Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Thu, 4 Mar 2021 19:27:03 +0100 Subject: [PATCH 006/288] [lint] fix indentation in CSS files. --- bookwyrm/static/css/format.css | 4 +- bookwyrm/static/css/icons.css | 124 ++++++++++++++++----------------- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/bookwyrm/static/css/format.css b/bookwyrm/static/css/format.css index 9d4b3105f..5534e6a7e 100644 --- a/bookwyrm/static/css/format.css +++ b/bookwyrm/static/css/format.css @@ -65,8 +65,8 @@ content: '\e9d9'; } .form-rate-stars label.icon:hover:before { - content: '\e9d9'; - } + content: '\e9d9'; +} .form-rate-stars label.icon:hover ~ label.icon:before{ content: '\e9d7'; } diff --git a/bookwyrm/static/css/icons.css b/bookwyrm/static/css/icons.css index c84446afa..9915ecd18 100644 --- a/bookwyrm/static/css/icons.css +++ b/bookwyrm/static/css/icons.css @@ -1,153 +1,153 @@ @font-face { - font-family: 'icomoon'; - src: url('fonts/icomoon.eot?n5x55'); - src: url('fonts/icomoon.eot?n5x55#iefix') format('embedded-opentype'), - url('fonts/icomoon.ttf?n5x55') format('truetype'), - url('fonts/icomoon.woff?n5x55') format('woff'), - url('fonts/icomoon.svg?n5x55#icomoon') format('svg'); - font-weight: normal; - font-style: normal; - font-display: block; + font-family: 'icomoon'; + src: url('fonts/icomoon.eot?n5x55'); + src: url('fonts/icomoon.eot?n5x55#iefix') format('embedded-opentype'), + url('fonts/icomoon.ttf?n5x55') format('truetype'), + url('fonts/icomoon.woff?n5x55') format('woff'), + url('fonts/icomoon.svg?n5x55#icomoon') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; } [class^="icon-"], [class*=" icon-"] { - /* use !important to prevent issues with browser extensions that change fonts */ - font-family: 'icomoon' !important; - speak: never; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'icomoon' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; - /* Better Font Rendering =========== */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } .icon-graphic-heart:before { - content: "\e91e"; + content: "\e91e"; } .icon-graphic-paperplane:before { - content: "\e91f"; + content: "\e91f"; } .icon-graphic-banknote:before { - content: "\e920"; + content: "\e920"; } .icon-stars:before { - content: "\e91a"; + content: "\e91a"; } .icon-warning:before { - content: "\e91b"; + content: "\e91b"; } .icon-book:before { - content: "\e900"; + content: "\e900"; } .icon-bookmark:before { - content: "\e91c"; + content: "\e91c"; } .icon-rss:before { - content: "\e91d"; + content: "\e91d"; } .icon-envelope:before { - content: "\e901"; + content: "\e901"; } .icon-arrow-right:before { - content: "\e902"; + content: "\e902"; } .icon-bell:before { - content: "\e903"; + content: "\e903"; } .icon-x:before { - content: "\e904"; + content: "\e904"; } .icon-quote-close:before { - content: "\e905"; + content: "\e905"; } .icon-quote-open:before { - content: "\e906"; + content: "\e906"; } .icon-image:before { - content: "\e907"; + content: "\e907"; } .icon-pencil:before { - content: "\e908"; + content: "\e908"; } .icon-list:before { - content: "\e909"; + content: "\e909"; } .icon-unlock:before { - content: "\e90a"; + content: "\e90a"; } .icon-unlisted:before { - content: "\e90a"; + content: "\e90a"; } .icon-globe:before { - content: "\e90b"; + content: "\e90b"; } .icon-public:before { - content: "\e90b"; + content: "\e90b"; } .icon-lock:before { - content: "\e90c"; + content: "\e90c"; } .icon-followers:before { - content: "\e90c"; + content: "\e90c"; } .icon-chain-broken:before { - content: "\e90d"; + content: "\e90d"; } .icon-chain:before { - content: "\e90e"; + content: "\e90e"; } .icon-comments:before { - content: "\e90f"; + content: "\e90f"; } .icon-comment:before { - content: "\e910"; + content: "\e910"; } .icon-boost:before { - content: "\e911"; + content: "\e911"; } .icon-arrow-left:before { - content: "\e912"; + content: "\e912"; } .icon-arrow-up:before { - content: "\e913"; + content: "\e913"; } .icon-arrow-down:before { - content: "\e914"; + content: "\e914"; } .icon-home:before { - content: "\e915"; + content: "\e915"; } .icon-local:before { - content: "\e916"; + content: "\e916"; } .icon-dots-three:before { - content: "\e917"; + content: "\e917"; } .icon-check:before { - content: "\e918"; + content: "\e918"; } .icon-dots-three-vertical:before { - content: "\e919"; + content: "\e919"; } .icon-search:before { - content: "\e986"; + content: "\e986"; } .icon-star-empty:before { - content: "\e9d7"; + content: "\e9d7"; } .icon-star-half:before { - content: "\e9d8"; + content: "\e9d8"; } .icon-star-full:before { - content: "\e9d9"; + content: "\e9d9"; } .icon-heart:before { - content: "\e9da"; + content: "\e9da"; } .icon-plus:before { - content: "\ea0a"; + content: "\ea0a"; } From 1cb84b0f620ed092a8678484e981dca279fd0b4f Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Thu, 4 Mar 2021 19:36:17 +0100 Subject: [PATCH 007/288] [lint] Fix indentation in JS files. --- bookwyrm/static/js/shared.js | 402 ++++++++++++++++++----------------- 1 file changed, 202 insertions(+), 200 deletions(-) diff --git a/bookwyrm/static/js/shared.js b/bookwyrm/static/js/shared.js index 7d65ee039..f391d460d 100644 --- a/bookwyrm/static/js/shared.js +++ b/bookwyrm/static/js/shared.js @@ -195,249 +195,251 @@ function removeClass(el, className) { */ class TabGroup { constructor(container) { - this.container = container; - - this.tablist = this.container.querySelector('[role="tablist"]'); - this.buttons = 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) { - return; - } - - this.keys = this.keys(); - this.direction = this.direction(); - this.initButtons(); - this.initPanels(); + this.container = container; + + this.tablist = this.container.querySelector('[role="tablist"]'); + this.buttons = 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) { + return; + } + + this.keys = this.keys(); + this.direction = this.direction(); + this.initButtons(); + this.initPanels(); } - + keys() { - return { - end: 35, - home: 36, - left: 37, - up: 38, - right: 39, - down: 40 - }; + return { + end: 35, + home: 36, + left: 37, + up: 38, + right: 39, + down: 40 + }; } - + // Add or substract depending on key pressed direction() { - return { - 37: -1, - 38: -1, - 39: 1, - 40: 1 - }; + return { + 37: -1, + 38: -1, + 39: 1, + 40: 1 + }; } - - initButtons() { - let count = 0; - for(let button of this.buttons) { - let isSelected = button.getAttribute("aria-selected") === "true"; - button.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)); - - button.index = count++; - } - } - - initPanels() { - let selectedPanelId = this.tablist.querySelector('[role="tab"][aria-selected="true"]').getAttribute("aria-controls"); - for(let panel of this.panels) { - if(panel.getAttribute("id") !== selectedPanelId) { - panel.setAttribute("hidden", ""); - } - panel.setAttribute("tabindex", "0"); - } - } - - clickEventListener(event) { - let button = event.target.closest('a'); - event.preventDefault(); - - this.activateTab(button, false); + initButtons() { + let count = 0; + for(let button of this.buttons) { + let isSelected = button.getAttribute("aria-selected") === "true"; + button.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)); + + button.index = count++; + } } - + + initPanels() { + let selectedPanelId = this.tablist + .querySelector('[role="tab"][aria-selected="true"]') + .getAttribute("aria-controls"); + for(let panel of this.panels) { + if(panel.getAttribute("id") !== selectedPanelId) { + panel.setAttribute("hidden", ""); + } + panel.setAttribute("tabindex", "0"); + } + } + + clickEventListener(event) { + let button = event.target.closest('a'); + + event.preventDefault(); + + this.activateTab(button, false); + } + // Handle keydown on tabs keydownEventListener(event) { - var key = event.keyCode; - - switch (key) { - case this.keys.end: - event.preventDefault(); - // Activate last tab - this.activateTab(this.buttons[this.buttons.length - 1]); - break; - case this.keys.home: - event.preventDefault(); - // Activate first tab - this.activateTab(this.buttons[0]); - break; - - // Up and down are in keydown - // because we need to prevent page scroll >:) - case this.keys.up: - case this.keys.down: - this.determineOrientation(event); - break; - }; + var key = event.keyCode; + + switch (key) { + case this.keys.end: + event.preventDefault(); + // Activate last tab + this.activateTab(this.buttons[this.buttons.length - 1]); + break; + case this.keys.home: + event.preventDefault(); + // Activate first tab + this.activateTab(this.buttons[0]); + break; + + // Up and down are in keydown + // because we need to prevent page scroll >:) + case this.keys.up: + case this.keys.down: + this.determineOrientation(event); + break; + }; } - + // Handle keyup on tabs keyupEventListener(event) { - var key = event.keyCode; - - switch (key) { - case this.keys.left: - case this.keys.right: - this.determineOrientation(event); - break; - }; + var key = event.keyCode; + + switch (key) { + case this.keys.left: + case this.keys.right: + this.determineOrientation(event); + break; + }; } - + // When a tablist’s aria-orientation is set to vertical, // only up and down arrow should function. // In all other cases only left and right arrow function. determineOrientation(event) { - var key = event.keyCode; - var vertical = this.tablist.getAttribute('aria-orientation') == 'vertical'; - var proceed = false; - - if (vertical) { - if (key === this.keys.up || key === this.keys.down) { - event.preventDefault(); - proceed = true; + var key = event.keyCode; + var vertical = this.tablist.getAttribute('aria-orientation') == 'vertical'; + var proceed = false; + + if (vertical) { + if (key === this.keys.up || key === this.keys.down) { + event.preventDefault(); + proceed = true; + }; + } + else { + if (key === this.keys.left || key === this.keys.right) { + proceed = true; + }; }; - } - else { - if (key === this.keys.left || key === this.keys.right) { - proceed = true; + + if (proceed) { + this.switchTabOnArrowPress(event); }; - }; - - if (proceed) { - this.switchTabOnArrowPress(event); - }; } - + // Either focus the next, previous, first, or last tab // depending on key pressed switchTabOnArrowPress(event) { - var pressed = event.keyCode; - - for (let button of this.buttons) { - button.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(); - } - else if (pressed === this.keys.left || pressed === this.keys.up) { - this.focusLastTab(); - } - else if (pressed === this.keys.right || pressed == this.keys.down) { - this.focusFirstTab(); - } + var pressed = event.keyCode; + + for (let button of this.buttons) { + button.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(); + } + else if (pressed === this.keys.left || pressed === this.keys.up) { + this.focusLastTab(); + } + else if (pressed === this.keys.right || pressed == this.keys.down) { + this.focusFirstTab(); + } + } } - } } - + // Activates any given tab panel activateTab (tab, setFocus) { - if(tab.getAttribute("role") !== "tab") { - tab = tab.closest('[role="tab"]'); - } - - setFocus = setFocus || true; - - // Deactivate all other tabs - this.deactivateTabs(); - - // Remove tabindex attribute - tab.removeAttribute('tabindex'); - - // Set the tab as selected - tab.setAttribute('aria-selected', 'true'); - - // Give the tab parent an is-active class - tab.parentNode.classList.add('is-active'); - - // Get the value of aria-controls (which is an ID) - var controls = tab.getAttribute('aria-controls'); - - // Remove hidden attribute from tab panel to make it visible - document.getElementById(controls).removeAttribute('hidden'); - - // Set focus when required - if (setFocus) { - tab.focus(); - } + if(tab.getAttribute("role") !== "tab") { + tab = tab.closest('[role="tab"]'); + } + + setFocus = setFocus || true; + + // Deactivate all other tabs + this.deactivateTabs(); + + // Remove tabindex attribute + tab.removeAttribute('tabindex'); + + // Set the tab as selected + tab.setAttribute('aria-selected', 'true'); + + // Give the tab parent an is-active class + tab.parentNode.classList.add('is-active'); + + // Get the value of aria-controls (which is an ID) + var controls = tab.getAttribute('aria-controls'); + + // Remove hidden attribute from tab panel to make it visible + document.getElementById(controls).removeAttribute('hidden'); + + // Set focus when required + if (setFocus) { + tab.focus(); + } } - + // 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 panel of this.panels) { - panel.setAttribute('hidden', 'hidden'); - } + 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 panel of this.panels) { + panel.setAttribute('hidden', 'hidden'); + } } - + focusFirstTab() { - this.buttons[0].focus(); + this.buttons[0].focus(); } - + focusLastTab() { - this.buttons[this.buttons.length - 1].focus(); + this.buttons[this.buttons.length - 1].focus(); } - + // Determine whether there should be a delay // when user navigates with the arrow keys determineDelay() { - var hasDelay = this.tablist.hasAttribute('data-delay'); - var delay = 0; - - if (hasDelay) { - var delayValue = this.tablist.getAttribute('data-delay'); - if (delayValue) { - delay = delayValue; - } - else { - // If no value is specified, default to 300ms - delay = 300; + var hasDelay = this.tablist.hasAttribute('data-delay'); + var delay = 0; + + if (hasDelay) { + var delayValue = this.tablist.getAttribute('data-delay'); + if (delayValue) { + delay = delayValue; + } + else { + // If no value is specified, default to 300ms + delay = 300; + }; }; - }; - - return delay; + + return delay; } - + focusEventHandler(event) { - var target = event.target; - - setTimeout(this.checkTabFocus.bind(this), this.delay, target); + var target = event.target; + + setTimeout(this.checkTabFocus.bind(this), this.delay, target); }; - + // Only activate tab on focus if it still has focus after the delay checkTabFocus(target) { - let focused = document.activeElement; - - if (target === focused) { - this.activateTab(target, false); - } + let focused = document.activeElement; + + if (target === focused) { + this.activateTab(target, false); + } } - } +} From c370cad7f96e919cd580e33840fed7a7ce2cd013 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Thu, 4 Mar 2021 19:42:23 +0100 Subject: [PATCH 008/288] [lint] Fix white spaces in HTML. --- bookwyrm/templates/import.html | 18 +++++++++--------- bookwyrm/templates/layout.html | 2 +- .../templates/snippets/progress_update.html | 11 ++++++++--- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/bookwyrm/templates/import.html b/bookwyrm/templates/import.html index 6b6febb19..99ff5c424 100644 --- a/bookwyrm/templates/import.html +++ b/bookwyrm/templates/import.html @@ -11,16 +11,16 @@ {% csrf_token %} diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index bc2e7e090..038707f8d 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -67,7 +67,7 @@