From 6971c9b133af3d4ebc6f32c62535f6e330457d34 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Mon, 5 Apr 2021 16:16:05 +0200 Subject: [PATCH 001/112] [assets] Move toggleAllCheckboxes code to its own file. --- bookwyrm/static/js/check_all.js | 29 ++++++++++++++++++----------- bookwyrm/static/js/shared.js | 9 +-------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bookwyrm/static/js/check_all.js b/bookwyrm/static/js/check_all.js index 07d30a68..72c83e3e 100644 --- a/bookwyrm/static/js/check_all.js +++ b/bookwyrm/static/js/check_all.js @@ -1,17 +1,24 @@ -/* exported toggleAllCheckboxes */ - /** * Toggle all descendant checkboxes of a target. * - * Use `data-target="ID_OF_TARGET"` on the node being listened to. - * - * @param {Event} event - change Event - * @return {undefined} + * Use `data-target="ID_OF_TARGET"` on the node on which the event is listened + * to (checkbox, button, link…), where_ID_OF_TARGET_ should be the ID of an + * ancestor for the checkboxes. */ -function toggleAllCheckboxes(event) { - const mainCheckbox = event.target; +(function() { + 'use strict'; + + function toggleAllCheckboxes(event) { + const mainCheckbox = event.target; + + document + .querySelectorAll(`#${mainCheckbox.dataset.target} [type="checkbox"]`) + .forEach(checkbox => {checkbox.checked = mainCheckbox.checked;}); + } document - .querySelectorAll(`#${mainCheckbox.dataset.target} [type="checkbox"]`) - .forEach(checkbox => {checkbox.checked = mainCheckbox.checked;}); -} + .querySelectorAll('[data-action="toggle-all"]') + .forEach(input => { + input.addEventListener('change', toggleAllCheckboxes); + }); +})(); diff --git a/bookwyrm/static/js/shared.js b/bookwyrm/static/js/shared.js index 7a198619..5ca3d7d7 100644 --- a/bookwyrm/static/js/shared.js +++ b/bookwyrm/static/js/shared.js @@ -1,4 +1,4 @@ -/* globals setDisplay TabGroup toggleAllCheckboxes updateDisplay */ +/* globals setDisplay TabGroup updateDisplay */ // set up javascript listeners window.onload = function() { @@ -36,13 +36,6 @@ window.onload = function() { // update localstorage Array.from(document.getElementsByClassName('set-display')) .forEach(t => t.onclick = updateDisplay); - - // Toggle all checkboxes. - document - .querySelectorAll('[data-action="toggle-all"]') - .forEach(input => { - input.addEventListener('change', toggleAllCheckboxes); - }); }; function back(e) { From 5aea7343b40f53aad7a512d6da80b7300d3498a0 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Mon, 5 Apr 2021 16:16:48 +0200 Subject: [PATCH 002/112] [assets] Rename some files for consistency. --- bookwyrm/static/css/{format.css => bookwyrm.css} | 0 bookwyrm/static/js/{shared.js => bookwyrm.js} | 0 bookwyrm/templates/layout.html | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename bookwyrm/static/css/{format.css => bookwyrm.css} (100%) rename bookwyrm/static/js/{shared.js => bookwyrm.js} (100%) diff --git a/bookwyrm/static/css/format.css b/bookwyrm/static/css/bookwyrm.css similarity index 100% rename from bookwyrm/static/css/format.css rename to bookwyrm/static/css/bookwyrm.css diff --git a/bookwyrm/static/js/shared.js b/bookwyrm/static/js/bookwyrm.js similarity index 100% rename from bookwyrm/static/js/shared.js rename to bookwyrm/static/js/bookwyrm.js diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index 80eb386a..ccc52a50 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -6,8 +6,8 @@ {% block title %}BookWyrm{% endblock %} | {{ site.name }} - + @@ -212,7 +212,7 @@ - + {% block scripts %}{% endblock %} From cbed5e331b02b95f09ee6aab7d703ce2b896d558 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Mon, 5 Apr 2021 16:17:11 +0200 Subject: [PATCH 003/112] [assets] Move some frontend assets to _vendor_ directories: This simplifies linting of files we have no grasp on, and clarifies responsibilities. - Add .eslintignore. - Restrict linting to bookwyrm/static. --- .eslintignore | 1 + .github/workflows/lint-frontend.yaml | 6 ++++-- .stylelintignore | 3 +-- bookwyrm/static/css/{ => vendor}/bulma.css.map | 0 bookwyrm/static/css/{ => vendor}/bulma.min.css | 0 bookwyrm/static/css/{ => vendor}/icons.css | 0 bookwyrm/static/js/{ => vendor}/tabs.js | 0 bookwyrm/templates/book/book.html | 2 +- bookwyrm/templates/feed/feed_layout.html | 2 +- bookwyrm/templates/layout.html | 6 +++--- 10 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 .eslintignore rename bookwyrm/static/css/{ => vendor}/bulma.css.map (100%) rename bookwyrm/static/css/{ => vendor}/bulma.min.css (100%) rename bookwyrm/static/css/{ => vendor}/icons.css (100%) rename bookwyrm/static/js/{ => vendor}/tabs.js (100%) diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..b2cd33f8 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +**/vendor/** diff --git a/.github/workflows/lint-frontend.yaml b/.github/workflows/lint-frontend.yaml index 978bbbbe..4c5e8823 100644 --- a/.github/workflows/lint-frontend.yaml +++ b/.github/workflows/lint-frontend.yaml @@ -22,8 +22,10 @@ jobs: - name: Install modules run: yarn + # See .stylelintignore for files that are not linted. - name: Run stylelint - run: yarn stylelint **/static/**/*.css --report-needless-disables --report-invalid-scope-disables + run: yarn stylelint bookwyrm/static/**/*.css --report-needless-disables --report-invalid-scope-disables + # See .eslintignore for files that are not linted. - name: Run ESLint - run: yarn eslint . --ext .js,.jsx,.ts,.tsx + run: yarn eslint bookwyrm/static --ext .js,.jsx,.ts,.tsx diff --git a/.stylelintignore b/.stylelintignore index f456cb22..b2cd33f8 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -1,2 +1 @@ -bookwyrm/static/css/bulma.*.css* -bookwyrm/static/css/icons.css +**/vendor/** diff --git a/bookwyrm/static/css/bulma.css.map b/bookwyrm/static/css/vendor/bulma.css.map similarity index 100% rename from bookwyrm/static/css/bulma.css.map rename to bookwyrm/static/css/vendor/bulma.css.map diff --git a/bookwyrm/static/css/bulma.min.css b/bookwyrm/static/css/vendor/bulma.min.css similarity index 100% rename from bookwyrm/static/css/bulma.min.css rename to bookwyrm/static/css/vendor/bulma.min.css diff --git a/bookwyrm/static/css/icons.css b/bookwyrm/static/css/vendor/icons.css similarity index 100% rename from bookwyrm/static/css/icons.css rename to bookwyrm/static/css/vendor/icons.css diff --git a/bookwyrm/static/js/tabs.js b/bookwyrm/static/js/vendor/tabs.js similarity index 100% rename from bookwyrm/static/js/tabs.js rename to bookwyrm/static/js/vendor/tabs.js diff --git a/bookwyrm/templates/book/book.html b/bookwyrm/templates/book/book.html index b91cebba..a7887c61 100644 --- a/bookwyrm/templates/book/book.html +++ b/bookwyrm/templates/book/book.html @@ -267,5 +267,5 @@ {% endblock %} {% block scripts %} - + {% endblock %} diff --git a/bookwyrm/templates/feed/feed_layout.html b/bookwyrm/templates/feed/feed_layout.html index 9afd5e60..edbab016 100644 --- a/bookwyrm/templates/feed/feed_layout.html +++ b/bookwyrm/templates/feed/feed_layout.html @@ -104,5 +104,5 @@ {% endblock %} {% block scripts %} - + {% endblock %} diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index ccc52a50..c1ff1276 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -5,9 +5,9 @@ {% block title %}BookWyrm{% endblock %} | {{ site.name }} - - - + + + From 27e47b0a35999972de72abff340a6778f2269649 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Mon, 5 Apr 2021 16:35:09 +0200 Subject: [PATCH 004/112] [lint] Update context for linting frontend files: - Lint files when pushing on the _frontend_ branch. - Lint files when eslint or stylelint config files are updated. - Use _strict_ parsing of JS files by default. This should make the JS linting test to fail. --- .eslintrc.js | 6 +++++- .github/workflows/lint-frontend.yaml | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index d39859f1..b5f3c311 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,5 +6,9 @@ module.exports = { "es6": true }, - "extends": "eslint:recommended" + "extends": "eslint:recommended", + + "rules": { + "strict": "error" + } }; diff --git a/.github/workflows/lint-frontend.yaml b/.github/workflows/lint-frontend.yaml index 4c5e8823..f370d83b 100644 --- a/.github/workflows/lint-frontend.yaml +++ b/.github/workflows/lint-frontend.yaml @@ -3,12 +3,14 @@ name: Lint Frontend on: push: - branches: [ main, ci ] + branches: [ main, ci, frontend ] paths: - '.github/workflows/**' - 'static/**' + - '.eslintrc' + - '.stylelintrc' pull_request: - branches: [ main, ci ] + branches: [ main, ci, frontend ] jobs: lint: From 964b47ea972e6484542603fb255049e04a45bbfe Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Mon, 5 Apr 2021 16:46:30 +0200 Subject: [PATCH 005/112] [assets] Fix path for icomoon font. --- bookwyrm/static/css/vendor/icons.css | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bookwyrm/static/css/vendor/icons.css b/bookwyrm/static/css/vendor/icons.css index 9915ecd1..c6dbad1d 100644 --- a/bookwyrm/static/css/vendor/icons.css +++ b/bookwyrm/static/css/vendor/icons.css @@ -1,10 +1,10 @@ @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'); + 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; From 1901f7e6cb2f1111ba1a8754adcde0c8636c5c36 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 15:16:21 -0700 Subject: [PATCH 006/112] Check if incoming domains are blocked --- bookwyrm/models/federated_server.py | 19 ++++++++++--- bookwyrm/views/inbox.py | 44 +++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/bookwyrm/models/federated_server.py b/bookwyrm/models/federated_server.py index 8f7d903e..d7dc5d96 100644 --- a/bookwyrm/models/federated_server.py +++ b/bookwyrm/models/federated_server.py @@ -2,16 +2,27 @@ from django.db import models from .base_model import BookWyrmModel +FederationStatus = models.TextChoices( + "Status", + [ + "federated", + "blocked", + ], +) + class FederatedServer(BookWyrmModel): """ store which servers we federate with """ server_name = models.CharField(max_length=255, unique=True) - # federated, blocked, whatever else - status = models.CharField(max_length=255, default="federated") + status = models.CharField( + max_length=255, default="federated", choices=FederationStatus.choices + ) # is it mastodon, bookwyrm, etc application_type = models.CharField(max_length=255, null=True) application_version = models.CharField(max_length=255, null=True) - -# TODO: blocked servers + def block(self): + """ block a server """ + self.status = "blocked" + self.save() diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index 34bd2e1c..a4ae1e99 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -1,9 +1,10 @@ """ incoming activities """ import json -from urllib.parse import urldefrag +import re +from urllib.parse import urldefrag, urlparse -from django.http import HttpResponse -from django.http import HttpResponseBadRequest, HttpResponseNotFound +from django.http import HttpResponse, HttpResponseNotFound +from django.http import HttpResponseBadRequest, HttpResponseForbidden from django.utils.decorators import method_decorator from django.views import View from django.views.decorators.csrf import csrf_exempt @@ -12,6 +13,7 @@ import requests from bookwyrm import activitypub, models from bookwyrm.tasks import app from bookwyrm.signatures import Signature +from bookwyrm.utils import regex @method_decorator(csrf_exempt, name="dispatch") @@ -21,6 +23,10 @@ class Inbox(View): def post(self, request, username=None): """ only works as POST request """ + # first check if this server is on our shitlist + if is_blocked_user_agent(request): + return HttpResponseForbidden() + # make sure the user's inbox even exists if username: try: @@ -34,6 +40,10 @@ class Inbox(View): except json.decoder.JSONDecodeError: return HttpResponseBadRequest() + # let's be extra sure we didn't block this domain + if is_blocked_activity(activity_json): + return HttpResponseForbidden() + if ( not "object" in activity_json or not "type" in activity_json @@ -54,6 +64,34 @@ class Inbox(View): return HttpResponse() +def is_blocked_user_agent(request): + """ check if a request is from a blocked server based on user agent """ + # check user agent + user_agent = request.headers.get("User-Agent") + domain = re.match(regex.domain, user_agent) + if not domain: + # idk, we'll try again later with the actor + return False + return is_blocked(domain) + + +def is_blocked_activity(activity_json): + """ get the sender out of activity json and check if it's blocked """ + actor = activity_json.get("actor") + if not actor: + # well I guess it's not even a valid activity so who knows + return False + url = urlparse(actor) + return is_blocked(url.netloc) + + +def is_blocked(domain): + """ is this domain blocked? """ + return models.FederatedServer.object.filter( + server_name=domain, status="blocked" + ).exists() + + @app.task def activity_task(activity_json): """ do something with this json we think is legit """ From e4fe47b53865fde8c404a78674fb8f2499b602d4 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 15:26:53 -0700 Subject: [PATCH 007/112] Default blocks in initdb --- bookwyrm/management/commands/initdb.py | 13 ++++++++++++- bookwyrm/templates/settings/federated_server.html | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/bookwyrm/management/commands/initdb.py b/bookwyrm/management/commands/initdb.py index d6101c87..a86a1652 100644 --- a/bookwyrm/management/commands/initdb.py +++ b/bookwyrm/management/commands/initdb.py @@ -2,7 +2,7 @@ from django.core.management.base import BaseCommand, CommandError from django.contrib.auth.models import Group, Permission from django.contrib.contenttypes.models import ContentType -from bookwyrm.models import Connector, SiteSettings, User +from bookwyrm.models import Connector, FederatedServer, SiteSettings, User from bookwyrm.settings import DOMAIN @@ -107,6 +107,16 @@ def init_connectors(): ) +def init_federated_servers(): + """ big no to nazis """ + built_in_blocks = ["gab.ai", "gab.com"] + for server in built_in_blocks: + FederatedServer.objects.create( + server_name=server, + status="blocked", + ) + + def init_settings(): SiteSettings.objects.create() @@ -118,4 +128,5 @@ class Command(BaseCommand): init_groups() init_permissions() init_connectors() + init_federated_servers() init_settings() diff --git a/bookwyrm/templates/settings/federated_server.html b/bookwyrm/templates/settings/federated_server.html index 13715bfb..edab3139 100644 --- a/bookwyrm/templates/settings/federated_server.html +++ b/bookwyrm/templates/settings/federated_server.html @@ -21,7 +21,7 @@
{% trans "Status:" %}
-
Federated
+
{{ server.status }}
From ad543f46c1728a4ec573462eeab4646b88f7a34d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 15:38:32 -0700 Subject: [PATCH 008/112] Adds block and unblock functionality --- .../templates/settings/federated_server.html | 16 ++++++++++++++++ bookwyrm/views/federation.py | 9 ++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/bookwyrm/templates/settings/federated_server.html b/bookwyrm/templates/settings/federated_server.html index edab3139..147d6514 100644 --- a/bookwyrm/templates/settings/federated_server.html +++ b/bookwyrm/templates/settings/federated_server.html @@ -4,6 +4,10 @@ {% block header %} {{ server.server_name }} + +{% if server.status == "blocked" %}{% trans "Blocked" %} +{% endif %} + {% trans "Back to server list" %} {% endblock %} @@ -64,5 +68,17 @@ +
+

{% trans "Actions" %}

+
+ {% csrf_token %} + {% if server.status != 'blocked' %} + + {% else %} + + {% endif %} +
+
+ {% endblock %} diff --git a/bookwyrm/views/federation.py b/bookwyrm/views/federation.py index 464a207c..0405ebf5 100644 --- a/bookwyrm/views/federation.py +++ b/bookwyrm/views/federation.py @@ -1,7 +1,7 @@ """ manage federated servers """ from django.contrib.auth.decorators import login_required, permission_required from django.core.paginator import Paginator -from django.shortcuts import get_object_or_404 +from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse from django.utils.decorators import method_decorator from django.views import View @@ -61,3 +61,10 @@ class FederatedServer(View): ), } return TemplateResponse(request, "settings/federated_server.html", data) + + def post(self, request, server): # pylint: disable=unused-argument + """ (un)block a server """ + server = get_object_or_404(models.FederatedServer, id=server) + server.status = "blocked" if server.status == "federated" else "federated" + server.save() + return redirect("settings-federated-server", server.id) From 194fcb8055b229843b2449f8dd8cc649e7ec45d7 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 15:54:20 -0700 Subject: [PATCH 009/112] Adds migration for federated server table statuses --- .../migrations/0062_auto_20210405_2249.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 bookwyrm/migrations/0062_auto_20210405_2249.py diff --git a/bookwyrm/migrations/0062_auto_20210405_2249.py b/bookwyrm/migrations/0062_auto_20210405_2249.py new file mode 100644 index 00000000..8229bf7c --- /dev/null +++ b/bookwyrm/migrations/0062_auto_20210405_2249.py @@ -0,0 +1,22 @@ +# Generated by Django 3.1.6 on 2021-04-05 22:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0061_auto_20210402_1435"), + ] + + operations = [ + migrations.AlterField( + model_name="federatedserver", + name="status", + field=models.CharField( + choices=[("federated", "Federated"), ("blocked", "Blocked")], + default="federated", + max_length=255, + ), + ), + ] From 34b790a0864cf153100a8f6388cc5d9598bde4f2 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 15:54:33 -0700 Subject: [PATCH 010/112] Adds tests for blocked server checks --- bookwyrm/tests/views/test_inbox.py | 25 +++++++++++++++++++++++++ bookwyrm/views/inbox.py | 2 ++ 2 files changed, 27 insertions(+) diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 37cf00dd..889fdc32 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -6,6 +6,7 @@ from unittest.mock import patch from django.http import HttpResponseNotAllowed, HttpResponseNotFound from django.test import TestCase, Client +from django.test.client import RequestFactory import responses from bookwyrm import models, views @@ -18,6 +19,7 @@ class Inbox(TestCase): def setUp(self): """ basic user and book data """ self.client = Client() + self.factory = RequestFactory() self.local_user = models.User.objects.create_user( "mouse@example.com", "mouse@mouse.com", @@ -936,3 +938,26 @@ class Inbox(TestCase): views.inbox.activity_task(activity) self.assertTrue(redis_mock.called) self.assertFalse(models.UserBlocks.objects.exists()) + + def test_is_blocked_user_agent(self): + """ check for blocked servers """ + request = self.factory.post( + "", + HTTP_USER_AGENT="http.rb/4.4.1 (Mastodon/3.3.0; +https://mastodon.social/)", + ) + self.assertFalse(views.inbox.is_blocked_user_agent(request)) + + models.FederatedServer.objects.create( + server_name="mastodon.social", status="blocked" + ) + self.assertTrue(views.inbox.is_blocked_user_agent(request)) + + def test_is_blocked_activity(self): + """ check for blocked servers """ + activity = {"actor": "https://mastodon.social/user/whaatever/else"} + self.assertFalse(views.inbox.is_blocked_user_agent(activity)) + + models.FederatedServer.objects.create( + server_name="mastodon.social", status="blocked" + ) + self.assertTrue(views.inbox.is_blocked_user_agent(activity)) diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index a4ae1e99..9e5aa019 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -68,6 +68,8 @@ def is_blocked_user_agent(request): """ check if a request is from a blocked server based on user agent """ # check user agent user_agent = request.headers.get("User-Agent") + if not user_agent: + return False domain = re.match(regex.domain, user_agent) if not domain: # idk, we'll try again later with the actor From a4b892dfadfe915c0dc2185682fe652e1a7661f2 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 16:47:48 -0700 Subject: [PATCH 011/112] Fixes domain block tests --- bookwyrm/tests/views/test_federation.py | 16 ++++++++++++++++ bookwyrm/tests/views/test_inbox.py | 4 ++-- bookwyrm/views/inbox.py | 5 +++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index a60ea432..874e277c 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -44,3 +44,19 @@ class FederationViews(TestCase): self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) + + def test_server_page_post(self): + """ block and unblock a server """ + server = models.FederatedServer.objects.create(server_name="hi.there.com") + self.assertEqual(server.status, "federated") + + view = views.FederatedServer.as_view() + request = self.factory.post("") + request.user = self.local_user + request.user.is_superuser = True + + view(request, server.id) + self.assertEqual(server.status, "blocked") + + view(request, server.id) + self.assertEqual(server.status, "federated") diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 889fdc32..f15b99f1 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -955,9 +955,9 @@ class Inbox(TestCase): def test_is_blocked_activity(self): """ check for blocked servers """ activity = {"actor": "https://mastodon.social/user/whaatever/else"} - self.assertFalse(views.inbox.is_blocked_user_agent(activity)) + self.assertFalse(views.inbox.is_blocked_activity(activity)) models.FederatedServer.objects.create( server_name="mastodon.social", status="blocked" ) - self.assertTrue(views.inbox.is_blocked_user_agent(activity)) + self.assertTrue(views.inbox.is_blocked_activity(activity)) diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index 9e5aa019..387efe9f 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -70,7 +70,8 @@ def is_blocked_user_agent(request): user_agent = request.headers.get("User-Agent") if not user_agent: return False - domain = re.match(regex.domain, user_agent) + url = re.search(r"+https?://{:s}/?".format(regex.domain), user_agent) + domain = urlparse(url).netloc if not domain: # idk, we'll try again later with the actor return False @@ -89,7 +90,7 @@ def is_blocked_activity(activity_json): def is_blocked(domain): """ is this domain blocked? """ - return models.FederatedServer.object.filter( + return models.FederatedServer.objects.filter( server_name=domain, status="blocked" ).exists() From fb72db7507459829956b3ade89144912246980ec Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 16:50:36 -0700 Subject: [PATCH 012/112] Fixes federation view tests --- bookwyrm/tests/views/test_federation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index 874e277c..3783417c 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -56,7 +56,9 @@ class FederationViews(TestCase): request.user.is_superuser = True view(request, server.id) + server.refresh_from_db() self.assertEqual(server.status, "blocked") view(request, server.id) + server.refresh_from_db() self.assertEqual(server.status, "federated") From 6b0a3ce4b1833d82b6529563567be6be7a6d18cd Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Tue, 6 Apr 2021 09:06:12 +0200 Subject: [PATCH 013/112] [assets] Move localStorage chunks of code to their own file: This should prevent a sync issue with updateDisplay not always being loaded on time. --- bookwyrm/static/js/bookwyrm.js | 10 +--------- bookwyrm/static/js/localstorage.js | 8 ++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index 5ca3d7d7..a4149c6f 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -1,4 +1,4 @@ -/* globals setDisplay TabGroup updateDisplay */ +/* globals TabGroup */ // set up javascript listeners window.onload = function() { @@ -28,14 +28,6 @@ window.onload = function() { Array.from(document.getElementsByClassName('tab-group')) .forEach(t => new TabGroup(t)); - - // display based on localstorage vars - document.querySelectorAll('[data-hide]') - .forEach(t => setDisplay(t)); - - // update localstorage - Array.from(document.getElementsByClassName('set-display')) - .forEach(t => t.onclick = updateDisplay); }; function back(e) { diff --git a/bookwyrm/static/js/localstorage.js b/bookwyrm/static/js/localstorage.js index aa79ee30..5104b44a 100644 --- a/bookwyrm/static/js/localstorage.js +++ b/bookwyrm/static/js/localstorage.js @@ -18,3 +18,11 @@ function setDisplay(el) { var value = window.localStorage.getItem(key); addRemoveClass(el, 'hidden', value); } + +// display based on localstorage vars +document.querySelectorAll('[data-hide]') + .forEach(t => setDisplay(t)); + +// update localstorage +Array.from(document.getElementsByClassName('set-display')) + .forEach(t => t.onclick = updateDisplay); From f6c3e581b95c2f1b3acbf62b9595fc9b9def1618 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Tue, 6 Apr 2021 09:11:27 +0200 Subject: [PATCH 014/112] =?UTF-8?q?[assets]=20Replace=20`Array.from(docume?= =?UTF-8?q?nt.getElementsByClassName(=E2=80=A6))`=20by=20`querySelectorAll?= =?UTF-8?q?(=E2=80=A6)`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bookwyrm/static/js/bookwyrm.js | 10 ++++++---- bookwyrm/static/js/localstorage.js | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index a4149c6f..7d542747 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -7,11 +7,11 @@ window.onload = function() { .forEach(t => t.onclick = toggleAction); // javascript interactions (boost/fav) - Array.from(document.getElementsByClassName('interaction')) + document.querySelectorAll('.interaction') .forEach(t => t.onsubmit = interact); // handle aria settings on menus - Array.from(document.getElementsByClassName('pulldown-menu')) + document.querySelectorAll('.pulldown-menu') .forEach(t => t.onclick = toggleMenu); // hidden submit button in a form @@ -26,7 +26,7 @@ window.onload = function() { document.querySelectorAll('[data-back]') .forEach(t => t.onclick = back); - Array.from(document.getElementsByClassName('tab-group')) + document.querySelectorAll('.tab-group') .forEach(t => new TabGroup(t)); }; @@ -103,7 +103,9 @@ function interact(e) { e.preventDefault(); ajaxPost(e.target); var identifier = e.target.getAttribute('data-id'); - Array.from(document.getElementsByClassName(identifier)) + // @todo This should be `querySelector`, unless there are duplicated IDs, + // which is a problem in itself. + document.querySelectorAll(`#${identifier}`) .forEach(t => addRemoveClass(t, 'hidden', t.className.indexOf('hidden') == -1)); } diff --git a/bookwyrm/static/js/localstorage.js b/bookwyrm/static/js/localstorage.js index 5104b44a..514d8748 100644 --- a/bookwyrm/static/js/localstorage.js +++ b/bookwyrm/static/js/localstorage.js @@ -24,5 +24,5 @@ document.querySelectorAll('[data-hide]') .forEach(t => setDisplay(t)); // update localstorage -Array.from(document.getElementsByClassName('set-display')) +document.querySelectorAll('.set-display') .forEach(t => t.onclick = updateDisplay); From f430363be0c55a8275d3e91e098c75b3f2cb94d9 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Tue, 6 Apr 2021 09:11:56 +0200 Subject: [PATCH 015/112] [assets] Simplify a function expression. --- bookwyrm/static/js/check_all.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/static/js/check_all.js b/bookwyrm/static/js/check_all.js index 72c83e3e..de067f78 100644 --- a/bookwyrm/static/js/check_all.js +++ b/bookwyrm/static/js/check_all.js @@ -13,7 +13,7 @@ document .querySelectorAll(`#${mainCheckbox.dataset.target} [type="checkbox"]`) - .forEach(checkbox => {checkbox.checked = mainCheckbox.checked;}); + .forEach(checkbox => checkbox.checked = mainCheckbox.checked); } document From 2f2f7db0865972401ea6a092bb82c3032007d19f Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Tue, 6 Apr 2021 09:44:59 +0200 Subject: [PATCH 016/112] [assets] Use classes for JS files: - Classes strictly parse code implicitly. - Fix previously updated code. --- bookwyrm/static/js/bookwyrm.js | 304 +++++++++++++++-------------- bookwyrm/static/js/localstorage.js | 54 ++--- 2 files changed, 187 insertions(+), 171 deletions(-) diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index 7d542747..0ac33e0f 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -1,156 +1,168 @@ +/* exported BookWyrm */ /* globals TabGroup */ -// set up javascript listeners -window.onload = function() { - // buttons that display or hide content - document.querySelectorAll('[data-controls]') - .forEach(t => t.onclick = toggleAction); - - // javascript interactions (boost/fav) - document.querySelectorAll('.interaction') - .forEach(t => t.onsubmit = interact); - - // handle aria settings on menus - document.querySelectorAll('.pulldown-menu') - .forEach(t => t.onclick = toggleMenu); - - // hidden submit button in a form - document.querySelectorAll('.hidden-form input') - .forEach(t => t.onchange = revealForm); - - // polling - document.querySelectorAll('[data-poll]') - .forEach(el => polling(el)); - - // browser back behavior - document.querySelectorAll('[data-back]') - .forEach(t => t.onclick = back); - - document.querySelectorAll('.tab-group') - .forEach(t => new TabGroup(t)); -}; - -function back(e) { - e.preventDefault(); - history.back(); -} - -function polling(el, delay) { - delay = delay || 10000; - delay += (Math.random() * 1000); - setTimeout(function() { - fetch('/api/updates/' + el.getAttribute('data-poll')) - .then(response => response.json()) - .then(data => updateCountElement(el, data)); - polling(el, delay * 1.25); - }, delay, el); -} - -function updateCountElement(el, data) { - const currentCount = el.innerText; - const count = data.count; - if (count != currentCount) { - addRemoveClass(el.closest('[data-poll-wrapper]'), 'hidden', count < 1); - el.innerText = count; - } -} - - -function revealForm(e) { - var hidden = e.currentTarget.closest('.hidden-form').getElementsByClassName('hidden')[0]; - if (hidden) { - removeClass(hidden, 'hidden'); - } -} - - -function toggleAction(e) { - var el = e.currentTarget; - var pressed = el.getAttribute('aria-pressed') == 'false'; - - var targetId = el.getAttribute('data-controls'); - document.querySelectorAll('[data-controls="' + targetId + '"]') - .forEach(t => t.setAttribute('aria-pressed', (t.getAttribute('aria-pressed') == 'false'))); - - if (targetId) { - var target = document.getElementById(targetId); - addRemoveClass(target, 'hidden', !pressed); - addRemoveClass(target, 'is-active', pressed); +let BookWyrm = new class { + constructor() { + this.initOnLoad(); + this.initEventListeners(); } - // show/hide container - var container = document.getElementById('hide-' + targetId); - if (container) { - addRemoveClass(container, 'hidden', pressed); + initEventListeners() { + // buttons that display or hide content + document.querySelectorAll('[data-controls]') + .forEach(t => t.onclick = this.toggleAction.bind(this)); + + // javascript interactions (boost/fav) + document.querySelectorAll('.interaction') + .forEach(t => t.onsubmit = this.interact.bind(this)); + + // handle aria settings on menus + document.querySelectorAll('.pulldown-menu') + .forEach(t => t.onclick = this.toggleMenu.bind(this)); + + // hidden submit button in a form + document.querySelectorAll('.hidden-form input') + .forEach(t => t.onchange = this.revealForm.bind(this)); + + // polling + document.querySelectorAll('[data-poll]') + .forEach(el => this.polling(el)); + + // browser back behavior + document.querySelectorAll('[data-back]') + .forEach(t => t.onclick = this.back); } - // set checkbox, if appropriate - var checkbox = el.getAttribute('data-controls-checkbox'); - if (checkbox) { - document.getElementById(checkbox).checked = !!pressed; + initOnLoad(){ + // set up javascript listeners + window.onload = function() { + document.querySelectorAll('.tab-group') + .forEach(t => new TabGroup(t)); + }; } - // set focus, if appropriate - var focus = el.getAttribute('data-focus-target'); - if (focus) { - var focusEl = document.getElementById(focus); - focusEl.focus(); - setTimeout(function(){ focusEl.selectionStart = focusEl.selectionEnd = 10000; }, 0); + back(e) { + e.preventDefault(); + history.back(); + } + + polling(el, delay) { + let poller = this; + delay = delay || 10000; + delay += (Math.random() * 1000); + setTimeout(function() { + fetch('/api/updates/' + el.getAttribute('data-poll')) + .then(response => response.json()) + .then(data => poller.updateCountElement(el, data)); + poller.polling(el, delay * 1.25); + }, delay, el); + } + + updateCountElement(el, data) { + const currentCount = el.innerText; + const count = data.count; + if (count != currentCount) { + this.addRemoveClass(el.closest('[data-poll-wrapper]'), 'hidden', count < 1); + el.innerText = count; + } + } + + revealForm(e) { + var hidden = e.currentTarget.closest('.hidden-form').getElementsByClassName('hidden')[0]; + if (hidden) { + this.removeClass(hidden, 'hidden'); + } + } + + toggleAction(e) { + var el = e.currentTarget; + var pressed = el.getAttribute('aria-pressed') == 'false'; + + var targetId = el.getAttribute('data-controls'); + document.querySelectorAll('[data-controls="' + targetId + '"]') + .forEach(t => t.setAttribute('aria-pressed', (t.getAttribute('aria-pressed') == 'false'))); + + if (targetId) { + var target = document.getElementById(targetId); + this.addRemoveClass(target, 'hidden', !pressed); + this.addRemoveClass(target, 'is-active', pressed); + } + + // show/hide container + var container = document.getElementById('hide-' + targetId); + if (container) { + this.addRemoveClass(container, 'hidden', pressed); + } + + // set checkbox, if appropriate + var checkbox = el.getAttribute('data-controls-checkbox'); + if (checkbox) { + document.getElementById(checkbox).checked = !!pressed; + } + + // set focus, if appropriate + var focus = el.getAttribute('data-focus-target'); + if (focus) { + var focusEl = document.getElementById(focus); + focusEl.focus(); + setTimeout(function(){ focusEl.selectionStart = focusEl.selectionEnd = 10000; }, 0); + } + } + + // @todo Only update status if the promise is successful. + interact(e) { + e.preventDefault(); + this.ajaxPost(e.target); + var identifier = e.target.getAttribute('data-id'); + + // @todo This probably should be done with IDs. + document.querySelectorAll(`.${identifier}`) + .forEach(t => this.addRemoveClass(t, 'hidden', t.className.indexOf('hidden') == -1)); + } + + toggleMenu(e) { + var el = e.currentTarget; + var expanded = el.getAttribute('aria-expanded') == 'false'; + el.setAttribute('aria-expanded', expanded); + var targetId = el.getAttribute('data-controls'); + if (targetId) { + var target = document.getElementById(targetId); + this.addRemoveClass(target, 'is-active', expanded); + } + } + + ajaxPost(form) { + fetch(form.action, { + method : "POST", + body: new FormData(form) + }); + } + + addRemoveClass(el, classname, bool) { + if (bool) { + this.addClass(el, classname); + } else { + this.removeClass(el, classname); + } + } + + addClass(el, classname) { + var classes = el.className.split(' '); + if (classes.indexOf(classname) > -1) { + return; + } + el.className = classes.concat(classname).join(' '); + } + + removeClass(el, className) { + var classes = []; + if (el.className) { + classes = el.className.split(' '); + } + const idx = classes.indexOf(className); + if (idx > -1) { + classes.splice(idx, 1); + } + el.className = classes.join(' '); } } - -function interact(e) { - e.preventDefault(); - ajaxPost(e.target); - var identifier = e.target.getAttribute('data-id'); - // @todo This should be `querySelector`, unless there are duplicated IDs, - // which is a problem in itself. - document.querySelectorAll(`#${identifier}`) - .forEach(t => addRemoveClass(t, 'hidden', t.className.indexOf('hidden') == -1)); -} - -function toggleMenu(e) { - var el = e.currentTarget; - var expanded = el.getAttribute('aria-expanded') == 'false'; - el.setAttribute('aria-expanded', expanded); - var targetId = el.getAttribute('data-controls'); - if (targetId) { - var target = document.getElementById(targetId); - addRemoveClass(target, 'is-active', expanded); - } -} - -function ajaxPost(form) { - fetch(form.action, { - method : "POST", - body: new FormData(form) - }); -} - -function addRemoveClass(el, classname, bool) { - if (bool) { - addClass(el, classname); - } else { - removeClass(el, classname); - } -} - -function addClass(el, classname) { - var classes = el.className.split(' '); - if (classes.indexOf(classname) > -1) { - return; - } - el.className = classes.concat(classname).join(' '); -} - -function removeClass(el, className) { - var classes = []; - if (el.className) { - classes = el.className.split(' '); - } - const idx = classes.indexOf(className); - if (idx > -1) { - classes.splice(idx, 1); - } - el.className = classes.join(' '); -} diff --git a/bookwyrm/static/js/localstorage.js b/bookwyrm/static/js/localstorage.js index 514d8748..b8ef02cd 100644 --- a/bookwyrm/static/js/localstorage.js +++ b/bookwyrm/static/js/localstorage.js @@ -1,28 +1,32 @@ -/* exported updateDisplay */ -/* globals addRemoveClass */ +/* exported LocalStorageTools */ +/* globals BookWyrm */ -// set javascript listeners -function updateDisplay(e) { - // used in set reading goal - var key = e.target.getAttribute('data-id'); - var value = e.target.getAttribute('data-value'); - window.localStorage.setItem(key, value); +let LocalStorageTools = new class { + constructor() { + // display based on localstorage vars + document.querySelectorAll('[data-hide]') + .forEach(t => this.setDisplay(t)); - document.querySelectorAll('[data-hide="' + key + '"]') - .forEach(t => setDisplay(t)); + // update localstorage + document.querySelectorAll('.set-display') + .forEach(t => t.onclick = this.updateDisplay.bind(this)); + } + + // set javascript listeners + updateDisplay(e) { + // used in set reading goal + var key = e.target.getAttribute('data-id'); + var value = e.target.getAttribute('data-value'); + window.localStorage.setItem(key, value); + + document.querySelectorAll('[data-hide="' + key + '"]') + .forEach(t => this.setDisplay(t)); + } + + setDisplay(el) { + // used in set reading goal + var key = el.getAttribute('data-hide'); + var value = window.localStorage.getItem(key); + BookWyrm.addRemoveClass(el, 'hidden', value); + } } - -function setDisplay(el) { - // used in set reading goal - var key = el.getAttribute('data-hide'); - var value = window.localStorage.getItem(key); - addRemoveClass(el, 'hidden', value); -} - -// display based on localstorage vars -document.querySelectorAll('[data-hide]') - .forEach(t => setDisplay(t)); - -// update localstorage -document.querySelectorAll('.set-display') - .forEach(t => t.onclick = updateDisplay); From 991d897ac75ddbd0122d61d175ba0f34380cf53a Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Tue, 6 Apr 2021 09:57:52 +0200 Subject: [PATCH 017/112] [assets] Listen to events as soon as possible. --- bookwyrm/static/js/bookwyrm.js | 38 ++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index 0ac33e0f..ceab5176 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -3,42 +3,50 @@ let BookWyrm = new class { constructor() { - this.initOnLoad(); + this.initOnDOMLoaded(); + this.initReccuringTasks(); this.initEventListeners(); } initEventListeners() { // buttons that display or hide content document.querySelectorAll('[data-controls]') - .forEach(t => t.onclick = this.toggleAction.bind(this)); + .forEach(button => button.onclick = this.toggleAction.bind(this)); // javascript interactions (boost/fav) document.querySelectorAll('.interaction') - .forEach(t => t.onsubmit = this.interact.bind(this)); + .forEach(button => button.onsubmit = this.interact.bind(this)); // handle aria settings on menus document.querySelectorAll('.pulldown-menu') - .forEach(t => t.onclick = this.toggleMenu.bind(this)); + .forEach(button => button.onclick = this.toggleMenu.bind(this)); // hidden submit button in a form document.querySelectorAll('.hidden-form input') - .forEach(t => t.onchange = this.revealForm.bind(this)); - - // polling - document.querySelectorAll('[data-poll]') - .forEach(el => this.polling(el)); + .forEach(button => button.onchange = this.revealForm.bind(this)); // browser back behavior document.querySelectorAll('[data-back]') - .forEach(t => t.onclick = this.back); + .forEach(button => button.onclick = this.back); } - initOnLoad(){ - // set up javascript listeners - window.onload = function() { + /** + * Execute code once the DOM is loaded. + */ + initOnDOMLoaded() { + window.addEventListener('DOMContentLoaded', function() { document.querySelectorAll('.tab-group') - .forEach(t => new TabGroup(t)); - }; + .forEach(tabs => new TabGroup(tabs)); + }); + } + + /** + * Execute recurring tasks. + */ + initReccuringTasks() { + // Polling + document.querySelectorAll('[data-poll]') + .forEach(liveArea => this.polling(liveArea)); } back(e) { From 70c652d5657dffbb971c3c13cfe301eeab7303a8 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Tue, 6 Apr 2021 10:42:52 +0200 Subject: [PATCH 018/112] [assets] Add rules to ESLint: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix long line. - Enforce a few stylistic habits: - Avoid some potential dangerous constructs. - `arrow-spacing`: Use at least one space around arrows. - `keyword-spacing`: Use at least one space around keywords (if, else, for…). - `no-multiple-empty-lines`: Only use one empty line between code. - `no-var`: Use `let` or `const` instead of `var`: - `padded-blocks`: Do not pad blocks. - `padding-line-between-statements`: Use empty lines between some statements. - `space-before-blocks`: Use at least one space before the opening brace of a block. --- .eslintrc.js | 60 +++++++++++++++++++++++++++++- bookwyrm/static/js/bookwyrm.js | 58 ++++++++++++++++++++--------- bookwyrm/static/js/localstorage.js | 10 +++-- 3 files changed, 105 insertions(+), 23 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index b5f3c311..5a247a43 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -9,6 +9,64 @@ module.exports = { "extends": "eslint:recommended", "rules": { - "strict": "error" + // Possible Errors + "no-async-promise-executor": "error", + "no-await-in-loop": "error", + "no-class-assign": "error", + "no-confusing-arrow": "error", + "no-const-assign": "error", + "no-dupe-class-members": "error", + "no-duplicate-imports": "error", + "no-template-curly-in-string": "error", + "no-useless-computed-key": "error", + "no-useless-constructor": "error", + "no-useless-rename": "error", + "require-atomic-updates": "error", + + // Best practices + "strict": "error", + "no-var": "error", + + // Stylistic Issues + "arrow-spacing": "error", + "keyword-spacing": "error", + "no-multiple-empty-lines": [ + "error", + { + "max": 1, + }, + ], + "padded-blocks": [ + "error", + "never", + ], + "padding-line-between-statements": [ + "error", + { + // always before return + "blankLine": "always", + "prev": "*", + "next": "return", + }, + { + // always before block-like expressions + "blankLine": "always", + "prev": "*", + "next": "block-like", + }, + { + // always after variable declaration + "blankLine": "always", + "prev": [ "const", "let", "var" ], + "next": "*", + }, + { + // not necessary between variable declaration + "blankLine": "any", + "prev": [ "const", "let", "var" ], + "next": [ "const", "let", "var" ], + }, + ], + "space-before-blocks": "error", } }; diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index ceab5176..3df49ae5 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -56,8 +56,10 @@ let BookWyrm = new class { polling(el, delay) { let poller = this; + delay = delay || 10000; delay += (Math.random() * 1000); + setTimeout(function() { fetch('/api/updates/' + el.getAttribute('data-poll')) .then(response => response.json()) @@ -69,6 +71,7 @@ let BookWyrm = new class { updateCountElement(el, data) { const currentCount = el.innerText; const count = data.count; + if (count != currentCount) { this.addRemoveClass(el.closest('[data-poll-wrapper]'), 'hidden', count < 1); el.innerText = count; @@ -76,52 +79,62 @@ let BookWyrm = new class { } revealForm(e) { - var hidden = e.currentTarget.closest('.hidden-form').getElementsByClassName('hidden')[0]; + let hidden = e.currentTarget.closest('.hidden-form').getElementsByClassName('hidden')[0]; + if (hidden) { this.removeClass(hidden, 'hidden'); } } toggleAction(e) { - var el = e.currentTarget; - var pressed = el.getAttribute('aria-pressed') == 'false'; + let el = e.currentTarget; + let pressed = el.getAttribute('aria-pressed') == 'false'; + let targetId = el.getAttribute('data-controls'); - var targetId = el.getAttribute('data-controls'); document.querySelectorAll('[data-controls="' + targetId + '"]') - .forEach(t => t.setAttribute('aria-pressed', (t.getAttribute('aria-pressed') == 'false'))); + .forEach(t => { + t.setAttribute('aria-pressed', (t.getAttribute('aria-pressed') == 'false')) + }); if (targetId) { - var target = document.getElementById(targetId); + let target = document.getElementById(targetId); + this.addRemoveClass(target, 'hidden', !pressed); this.addRemoveClass(target, 'is-active', pressed); } // show/hide container - var container = document.getElementById('hide-' + targetId); + let container = document.getElementById('hide-' + targetId); + if (container) { this.addRemoveClass(container, 'hidden', pressed); } // set checkbox, if appropriate - var checkbox = el.getAttribute('data-controls-checkbox'); + let checkbox = el.getAttribute('data-controls-checkbox'); + if (checkbox) { document.getElementById(checkbox).checked = !!pressed; } // set focus, if appropriate - var focus = el.getAttribute('data-focus-target'); + let focus = el.getAttribute('data-focus-target'); + if (focus) { - var focusEl = document.getElementById(focus); + let focusEl = document.getElementById(focus); + focusEl.focus(); - setTimeout(function(){ focusEl.selectionStart = focusEl.selectionEnd = 10000; }, 0); + setTimeout(function() { focusEl.selectionStart = focusEl.selectionEnd = 10000; }, 0); } } // @todo Only update status if the promise is successful. interact(e) { e.preventDefault(); + + let identifier = e.target.getAttribute('data-id'); + this.ajaxPost(e.target); - var identifier = e.target.getAttribute('data-id'); // @todo This probably should be done with IDs. document.querySelectorAll(`.${identifier}`) @@ -129,12 +142,15 @@ let BookWyrm = new class { } toggleMenu(e) { - var el = e.currentTarget; - var expanded = el.getAttribute('aria-expanded') == 'false'; + let el = e.currentTarget; + let expanded = el.getAttribute('aria-expanded') == 'false'; + let targetId = el.getAttribute('data-controls'); + el.setAttribute('aria-expanded', expanded); - var targetId = el.getAttribute('data-controls'); + if (targetId) { - var target = document.getElementById(targetId); + let target = document.getElementById(targetId); + this.addRemoveClass(target, 'is-active', expanded); } } @@ -155,22 +171,28 @@ let BookWyrm = new class { } addClass(el, classname) { - var classes = el.className.split(' '); + let classes = el.className.split(' '); + if (classes.indexOf(classname) > -1) { return; } + el.className = classes.concat(classname).join(' '); } removeClass(el, className) { - var classes = []; + let classes = []; + if (el.className) { classes = el.className.split(' '); } + const idx = classes.indexOf(className); + if (idx > -1) { classes.splice(idx, 1); } + el.className = classes.join(' '); } } diff --git a/bookwyrm/static/js/localstorage.js b/bookwyrm/static/js/localstorage.js index b8ef02cd..08b83162 100644 --- a/bookwyrm/static/js/localstorage.js +++ b/bookwyrm/static/js/localstorage.js @@ -15,8 +15,9 @@ let LocalStorageTools = new class { // set javascript listeners updateDisplay(e) { // used in set reading goal - var key = e.target.getAttribute('data-id'); - var value = e.target.getAttribute('data-value'); + let key = e.target.getAttribute('data-id'); + let value = e.target.getAttribute('data-value'); + window.localStorage.setItem(key, value); document.querySelectorAll('[data-hide="' + key + '"]') @@ -25,8 +26,9 @@ let LocalStorageTools = new class { setDisplay(el) { // used in set reading goal - var key = el.getAttribute('data-hide'); - var value = window.localStorage.getItem(key); + let key = el.getAttribute('data-hide'); + let value = window.localStorage.getItem(key); + BookWyrm.addRemoveClass(el, 'hidden', value); } } From 7e49b3cb26bf55d2114567cfd043c84410782fbe Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Tue, 6 Apr 2021 11:19:44 +0200 Subject: [PATCH 019/112] [assets] Simplify addRemoveClass function. --- bookwyrm/static/js/bookwyrm.js | 46 ++++++++++------------------------ 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index 3df49ae5..0ba4b7c2 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -81,9 +81,7 @@ let BookWyrm = new class { revealForm(e) { let hidden = e.currentTarget.closest('.hidden-form').getElementsByClassName('hidden')[0]; - if (hidden) { - this.removeClass(hidden, 'hidden'); - } + this.addRemoveClass(hidden, 'hidden', !hidden); } toggleAction(e) { @@ -162,37 +160,19 @@ let BookWyrm = new class { }); } - addRemoveClass(el, classname, bool) { - if (bool) { - this.addClass(el, classname); + /** + * Add or remove a class based on a boolean condition. + * + * @param {object} node - DOM node to change class on + * @param {string} classname - Name of the class + * @param {boolean} add - Add? + * @return {undefined} + */ + addRemoveClass(node, classname, add) { + if (add) { + node.classList.add(classname); } else { - this.removeClass(el, classname); + node.classList.remove(classname); } } - - addClass(el, classname) { - let classes = el.className.split(' '); - - if (classes.indexOf(classname) > -1) { - return; - } - - el.className = classes.concat(classname).join(' '); - } - - removeClass(el, className) { - let classes = []; - - if (el.className) { - classes = el.className.split(' '); - } - - const idx = classes.indexOf(className); - - if (idx > -1) { - classes.splice(idx, 1); - } - - el.className = classes.join(' '); - } } From 62fe2ef600e340747e5bbec65cabcc24bc20596d Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Tue, 6 Apr 2021 11:37:23 +0200 Subject: [PATCH 020/112] [assets] Replace inline events with `addEventListener`; this breaks sliding menu. --- bookwyrm/static/js/bookwyrm.js | 10 +++++----- bookwyrm/static/js/localstorage.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index 0ba4b7c2..6be1d4fb 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -11,23 +11,23 @@ let BookWyrm = new class { initEventListeners() { // buttons that display or hide content document.querySelectorAll('[data-controls]') - .forEach(button => button.onclick = this.toggleAction.bind(this)); + .forEach(button => button.addEventListener('click', this.toggleAction.bind(this))); // javascript interactions (boost/fav) document.querySelectorAll('.interaction') - .forEach(button => button.onsubmit = this.interact.bind(this)); + .forEach(button => button.addEventListener('submit', this.interact.bind(this))); // handle aria settings on menus document.querySelectorAll('.pulldown-menu') - .forEach(button => button.onclick = this.toggleMenu.bind(this)); + .forEach(button => button.addEventListener('click', this.toggleMenu.bind(this))); // hidden submit button in a form document.querySelectorAll('.hidden-form input') - .forEach(button => button.onchange = this.revealForm.bind(this)); + .forEach(button => button.addEventListener('change', this.revealForm.bind(this))); // browser back behavior document.querySelectorAll('[data-back]') - .forEach(button => button.onclick = this.back); + .forEach(button => button.addEventListener('click', this.back)); } /** diff --git a/bookwyrm/static/js/localstorage.js b/bookwyrm/static/js/localstorage.js index 08b83162..17adf2bf 100644 --- a/bookwyrm/static/js/localstorage.js +++ b/bookwyrm/static/js/localstorage.js @@ -9,7 +9,7 @@ let LocalStorageTools = new class { // update localstorage document.querySelectorAll('.set-display') - .forEach(t => t.onclick = this.updateDisplay.bind(this)); + .forEach(t => t.addEventListener('click', this.updateDisplay.bind(this))); } // set javascript listeners From d6ee136c109dc3ed013c6e736673553428cf097a Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Tue, 6 Apr 2021 11:54:45 +0200 Subject: [PATCH 021/112] [assets] Allow to run `./bw-dev collectstatic` every time a change occurs in bookwyrm/static. --- README.md | 2 ++ package.json | 6 +++++- yarn.lock | 22 +++++++++++++++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e798fedf..91225a56 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,8 @@ If you edit the CSS or JavaScript, you will need to run Django's `collectstatic` ./bw-dev collectstatic ``` +If you have [installed yarn](https://yarnpkg.com/getting-started/install), you can run `yarn watch:static` to automatically run the previous script every time a change occurs in _bookwyrm/static_ directory. + ### Working with translations and locale files Text in the html files are wrapped in translation tags (`{% trans %}` and `{% blocktrans %}`), and Django generates locale files for all the strings in which you can add translations for the text. You can find existing translations in the `locale/` directory. diff --git a/package.json b/package.json index 8059255d..b7abe434 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,12 @@ { + "scripts": { + "watch:static": "yarn watch \"./bw-dev collectstatic\" bookwyrm/static/**" + }, "devDependencies": { "eslint": "^7.23.0", "stylelint": "^13.12.0", "stylelint-config-standard": "^21.0.0", - "stylelint-order": "^4.1.0" + "stylelint-order": "^4.1.0", + "watch": "^1.0.2" } } diff --git a/yarn.lock b/yarn.lock index de4e0107..c1a1c181 100644 --- a/yarn.lock +++ b/yarn.lock @@ -768,6 +768,13 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +exec-sh@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.2.tgz#2a5e7ffcbd7d0ba2755bdecb16e5a427dfbdec36" + integrity sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw== + dependencies: + merge "^1.2.0" + execall@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/execall/-/execall-2.0.0.tgz#16a06b5fe5099df7d00be5d9c06eecded1663b45" @@ -1368,6 +1375,11 @@ merge2@^1.3.0: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +merge@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" + integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== + micromark@~2.11.0: version "2.11.4" resolved "https://registry.yarnpkg.com/micromark/-/micromark-2.11.4.tgz#d13436138eea826383e822449c9a5c50ee44665a" @@ -1405,7 +1417,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.5: +minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -2183,6 +2195,14 @@ vfile@^4.0.0: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" +watch@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/watch/-/watch-1.0.2.tgz#340a717bde765726fa0aa07d721e0147a551df0c" + integrity sha1-NApxe952Vyb6CqB9ch4BR6VR3ww= + dependencies: + exec-sh "^0.2.0" + minimist "^1.2.0" + which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" From 5d3d00f6946dd6c88c8e0f086fcdd1125b98abfe Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Tue, 6 Apr 2021 15:36:34 +0200 Subject: [PATCH 022/112] [assets] Use `dataset` + use expressive names for some variables. --- bookwyrm/static/js/bookwyrm.js | 34 ++++++++++++++++-------------- bookwyrm/static/js/localstorage.js | 6 +++--- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index 6be1d4fb..a434d36a 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -54,18 +54,18 @@ let BookWyrm = new class { history.back(); } - polling(el, delay) { + polling(counter, delay) { let poller = this; delay = delay || 10000; delay += (Math.random() * 1000); setTimeout(function() { - fetch('/api/updates/' + el.getAttribute('data-poll')) + fetch('/api/updates/' + counter.dataset.poll) .then(response => response.json()) - .then(data => poller.updateCountElement(el, data)); - poller.polling(el, delay * 1.25); - }, delay, el); + .then(data => poller.updateCountElement(counter, data)); + poller.polling(counter, delay * 1.25); + }, delay, counter); } updateCountElement(el, data) { @@ -84,15 +84,17 @@ let BookWyrm = new class { this.addRemoveClass(hidden, 'hidden', !hidden); } - toggleAction(e) { - let el = e.currentTarget; - let pressed = el.getAttribute('aria-pressed') == 'false'; - let targetId = el.getAttribute('data-controls'); + toggleAction(event) { + let trigger = event.currentTarget; + let pressed = trigger.getAttribute('aria-pressed') == 'false'; + let targetId = trigger.dataset.controls; + // Un‑press all triggers controlling the same target. document.querySelectorAll('[data-controls="' + targetId + '"]') - .forEach(t => { - t.setAttribute('aria-pressed', (t.getAttribute('aria-pressed') == 'false')) - }); + .forEach(triggers => triggers.setAttribute( + 'aria-pressed', + (triggers.getAttribute('aria-pressed') == 'false')) + ); if (targetId) { let target = document.getElementById(targetId); @@ -109,14 +111,14 @@ let BookWyrm = new class { } // set checkbox, if appropriate - let checkbox = el.getAttribute('data-controls-checkbox'); + let checkbox = trigger.dataset['controls-checkbox']; if (checkbox) { document.getElementById(checkbox).checked = !!pressed; } // set focus, if appropriate - let focus = el.getAttribute('data-focus-target'); + let focus = trigger.dataset['focus-target']; if (focus) { let focusEl = document.getElementById(focus); @@ -130,7 +132,7 @@ let BookWyrm = new class { interact(e) { e.preventDefault(); - let identifier = e.target.getAttribute('data-id'); + let identifier = e.target.dataset.id; this.ajaxPost(e.target); @@ -142,7 +144,7 @@ let BookWyrm = new class { toggleMenu(e) { let el = e.currentTarget; let expanded = el.getAttribute('aria-expanded') == 'false'; - let targetId = el.getAttribute('data-controls'); + let targetId = el.dataset.controls; el.setAttribute('aria-expanded', expanded); diff --git a/bookwyrm/static/js/localstorage.js b/bookwyrm/static/js/localstorage.js index 17adf2bf..5d978c2f 100644 --- a/bookwyrm/static/js/localstorage.js +++ b/bookwyrm/static/js/localstorage.js @@ -15,8 +15,8 @@ let LocalStorageTools = new class { // set javascript listeners updateDisplay(e) { // used in set reading goal - let key = e.target.getAttribute('data-id'); - let value = e.target.getAttribute('data-value'); + let key = e.target.dataset.id; + let value = e.target.dataset.value; window.localStorage.setItem(key, value); @@ -26,7 +26,7 @@ let LocalStorageTools = new class { setDisplay(el) { // used in set reading goal - let key = el.getAttribute('data-hide'); + let key = el.dataset.hide; let value = window.localStorage.getItem(key); BookWyrm.addRemoveClass(el, 'hidden', value); From 52d2f0e3318c7fb4071e99e270e40982333b1a8d Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Tue, 6 Apr 2021 16:17:20 +0200 Subject: [PATCH 023/112] [assets] Document functions and variables: - Use expressive names for variables. - Add docblocks for each function. - Add ESLint rules for comments. --- .eslintrc.js | 18 +++++ bookwyrm/static/js/bookwyrm.js | 120 ++++++++++++++++++++++------- bookwyrm/static/js/localstorage.js | 31 +++++--- 3 files changed, 132 insertions(+), 37 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 5a247a43..b65fe988 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -29,7 +29,25 @@ module.exports = { // Stylistic Issues "arrow-spacing": "error", + "capitalized-comments": [ + "warn", + "always", + { + "ignoreConsecutiveComments": true + }, + ], "keyword-spacing": "error", + "lines-around-comment": [ + "error", + { + "beforeBlockComment": true, + "beforeLineComment": true, + "allowBlockStart": true, + "allowClassStart": true, + "allowObjectStart": true, + "allowArrayStart": true, + }, + ], "no-multiple-empty-lines": [ "error", { diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index a434d36a..63947aaa 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -49,13 +49,28 @@ let BookWyrm = new class { .forEach(liveArea => this.polling(liveArea)); } - back(e) { - e.preventDefault(); + /** + * Go back in browser history. + * + * @param {Event} event + * + * @return {undefined} + */ + back(event) { + event.preventDefault(); history.back(); } + /** + * Update a counter with recurring requests to the API + * + * @param {Object} counter - DOM node + * @param {int} delay - frequency for polling in ms + * + * @return {undefined} + */ polling(counter, delay) { - let poller = this; + const bookwyrm = this; delay = delay || 10000; delay += (Math.random() * 1000); @@ -63,27 +78,51 @@ let BookWyrm = new class { setTimeout(function() { fetch('/api/updates/' + counter.dataset.poll) .then(response => response.json()) - .then(data => poller.updateCountElement(counter, data)); - poller.polling(counter, delay * 1.25); + .then(data => bookwyrm.updateCountElement(counter, data)); + + bookwyrm.polling(counter, delay * 1.25); }, delay, counter); } - updateCountElement(el, data) { - const currentCount = el.innerText; + /** + * Update a counter. + * + * @param {object} counter - DOM node + * @param {object} data - json formatted response from a fetch + * + * @return {undefined} + */ + updateCountElement(counter, data) { + const currentCount = counter.innerText; const count = data.count; if (count != currentCount) { - this.addRemoveClass(el.closest('[data-poll-wrapper]'), 'hidden', count < 1); - el.innerText = count; + this.addRemoveClass(counter.closest('[data-poll-wrapper]'), 'hidden', count < 1); + counter.innerText = count; } } - revealForm(e) { - let hidden = e.currentTarget.closest('.hidden-form').getElementsByClassName('hidden')[0]; + /** + * Toggle form. + * + * @param {Event} event + * + * @return {undefined} + */ + revealForm(event) { + let trigger = event.currentTarget; + let hidden = trigger.closest('.hidden-form').querySelectorAll('.hidden')[0]; this.addRemoveClass(hidden, 'hidden', !hidden); } + /** + * Execute actions on targets based on triggers. + * + * @param {Event} event + * + * @return {undefined} + */ toggleAction(event) { let trigger = event.currentTarget; let pressed = trigger.getAttribute('aria-pressed') == 'false'; @@ -103,21 +142,21 @@ let BookWyrm = new class { this.addRemoveClass(target, 'is-active', pressed); } - // show/hide container + // Show/hide container. let container = document.getElementById('hide-' + targetId); if (container) { this.addRemoveClass(container, 'hidden', pressed); } - // set checkbox, if appropriate + // Check checkbox, if appropriate. let checkbox = trigger.dataset['controls-checkbox']; if (checkbox) { document.getElementById(checkbox).checked = !!pressed; } - // set focus, if appropriate + // Set focus, if appropriate. let focus = trigger.dataset['focus-target']; if (focus) { @@ -128,25 +167,45 @@ let BookWyrm = new class { } } - // @todo Only update status if the promise is successful. - interact(e) { - e.preventDefault(); + /** + * Make a request and update the UI accordingly. + * This function is used for boosts and favourites. + * + * @todo Only update status if the promise is successful. + * + * @param {Event} event + * + * @return {undefined} + */ + interact(event) { + event.preventDefault(); - let identifier = e.target.dataset.id; - - this.ajaxPost(e.target); + this.ajaxPost(event.target); // @todo This probably should be done with IDs. - document.querySelectorAll(`.${identifier}`) - .forEach(t => this.addRemoveClass(t, 'hidden', t.className.indexOf('hidden') == -1)); + document.querySelectorAll(`.${event.target.dataset.id}`) + .forEach(node => this.addRemoveClass( + node, + 'hidden', + node.className.indexOf('hidden') == -1 + )); } - toggleMenu(e) { - let el = e.currentTarget; - let expanded = el.getAttribute('aria-expanded') == 'false'; - let targetId = el.dataset.controls; + /** + * Handle ARIA states on toggled menus. + * + * @note This function seems to be redundant and conflicts with toggleAction. + * + * @param {Event} event + * + * @return {undefined} + */ + toggleMenu(event) { + let trigger = event.currentTarget; + let expanded = trigger.getAttribute('aria-expanded') == 'false'; + let targetId = trigger.dataset.controls; - el.setAttribute('aria-expanded', expanded); + trigger.setAttribute('aria-expanded', expanded); if (targetId) { let target = document.getElementById(targetId); @@ -155,6 +214,13 @@ let BookWyrm = new class { } } + /** + * Submit a form using POST. + * + * @param {object} form - Form to be submitted + * + * @return {undefined} + */ ajaxPost(form) { fetch(form.action, { method : "POST", diff --git a/bookwyrm/static/js/localstorage.js b/bookwyrm/static/js/localstorage.js index 5d978c2f..eccac347 100644 --- a/bookwyrm/static/js/localstorage.js +++ b/bookwyrm/static/js/localstorage.js @@ -3,32 +3,43 @@ let LocalStorageTools = new class { constructor() { - // display based on localstorage vars document.querySelectorAll('[data-hide]') .forEach(t => this.setDisplay(t)); - // update localstorage document.querySelectorAll('.set-display') .forEach(t => t.addEventListener('click', this.updateDisplay.bind(this))); } - // set javascript listeners - updateDisplay(e) { + /** + * Update localStorage, then display content based on keys in localStorage. + * + * @param {Event} event + * + * @return {undefined} + */ + updateDisplay(event) { // used in set reading goal - let key = e.target.dataset.id; - let value = e.target.dataset.value; + let key = event.target.dataset.id; + let value = event.target.dataset.value; window.localStorage.setItem(key, value); document.querySelectorAll('[data-hide="' + key + '"]') - .forEach(t => this.setDisplay(t)); + .forEach(node => this.setDisplay(node)); } - setDisplay(el) { + /** + * Toggle display of a DOM node based on its value in the localStorage. + * + * @param {object} node - DOM node to toggle. + * + * @return {undefined} + */ + setDisplay(node) { // used in set reading goal - let key = el.dataset.hide; + let key = node.dataset.hide; let value = window.localStorage.getItem(key); - BookWyrm.addRemoveClass(el, 'hidden', value); + BookWyrm.addRemoveClass(node, 'hidden', value); } } From a21f954fb1e90a99a915273de994ce7a5936f79b Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Tue, 6 Apr 2021 16:29:58 +0200 Subject: [PATCH 024/112] Remove redundant `class` attribute and format template. --- bookwyrm/templates/components/dropdown.html | 27 ++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/bookwyrm/templates/components/dropdown.html b/bookwyrm/templates/components/dropdown.html index 72582ddc..734d6276 100644 --- a/bookwyrm/templates/components/dropdown.html +++ b/bookwyrm/templates/components/dropdown.html @@ -1,13 +1,34 @@ +{% spaceless %} {% load bookwyrm_tags %} + {% with 0|uuid as uuid %} - {% endwith %} +{% endspaceless %} From 9d95f54aa23ebc684255ba301cc8391333b61860 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Tue, 6 Apr 2021 17:48:56 +0200 Subject: [PATCH 025/112] [assets] Refactor `toggleAction` and `toggleMenu` a bit. --- bookwyrm/static/js/bookwyrm.js | 102 ++++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 28 deletions(-) diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index 63947aaa..0ec14336 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -9,25 +9,29 @@ let BookWyrm = new class { } initEventListeners() { - // buttons that display or hide content document.querySelectorAll('[data-controls]') - .forEach(button => button.addEventListener('click', this.toggleAction.bind(this))); + .forEach(button => button.addEventListener( + 'click', + this.toggleAction.bind(this)) + ); - // javascript interactions (boost/fav) document.querySelectorAll('.interaction') - .forEach(button => button.addEventListener('submit', this.interact.bind(this))); + .forEach(button => button.addEventListener( + 'submit', + this.interact.bind(this)) + ); - // handle aria settings on menus - document.querySelectorAll('.pulldown-menu') - .forEach(button => button.addEventListener('click', this.toggleMenu.bind(this))); - - // hidden submit button in a form document.querySelectorAll('.hidden-form input') - .forEach(button => button.addEventListener('change', this.revealForm.bind(this))); + .forEach(button => button.addEventListener( + 'change', + this.revealForm.bind(this)) + ); - // browser back behavior document.querySelectorAll('[data-back]') - .forEach(button => button.addEventListener('click', this.back)); + .forEach(button => button.addEventListener( + 'click', + this.back) + ); } /** @@ -125,45 +129,48 @@ let BookWyrm = new class { */ toggleAction(event) { let trigger = event.currentTarget; - let pressed = trigger.getAttribute('aria-pressed') == 'false'; + let pressed = trigger.getAttribute('aria-pressed') === 'false'; let targetId = trigger.dataset.controls; - // Un‑press all triggers controlling the same target. + // Toggle pressed status on all triggers controlling the same target. document.querySelectorAll('[data-controls="' + targetId + '"]') - .forEach(triggers => triggers.setAttribute( + .forEach(otherTrigger => otherTrigger.setAttribute( 'aria-pressed', - (triggers.getAttribute('aria-pressed') == 'false')) - ); + otherTrigger.getAttribute('aria-pressed') === 'false' + )); - if (targetId) { + // @todo Find a better way to handle the exception. + if (targetId && ! trigger.classList.contains('pulldown-menu')) { let target = document.getElementById(targetId); this.addRemoveClass(target, 'hidden', !pressed); this.addRemoveClass(target, 'is-active', pressed); } + // Show/hide pulldown-menus. + if (trigger.classList.contains('pulldown-menu')) { + this.toggleMenu(trigger, targetId); + } + // Show/hide container. let container = document.getElementById('hide-' + targetId); if (container) { - this.addRemoveClass(container, 'hidden', pressed); + this.toggleContainer(container, pressed); } // Check checkbox, if appropriate. let checkbox = trigger.dataset['controls-checkbox']; if (checkbox) { - document.getElementById(checkbox).checked = !!pressed; + this.toggleCheckbox(checkbox, pressed); } // Set focus, if appropriate. let focus = trigger.dataset['focus-target']; if (focus) { - let focusEl = document.getElementById(focus); - - focusEl.focus(); - setTimeout(function() { focusEl.selectionStart = focusEl.selectionEnd = 10000; }, 0); + this.toggleFocus(focus); } } @@ -192,7 +199,7 @@ let BookWyrm = new class { } /** - * Handle ARIA states on toggled menus. + * Show or hide menus. * * @note This function seems to be redundant and conflicts with toggleAction. * @@ -200,10 +207,8 @@ let BookWyrm = new class { * * @return {undefined} */ - toggleMenu(event) { - let trigger = event.currentTarget; + toggleMenu(trigger, targetId) { let expanded = trigger.getAttribute('aria-expanded') == 'false'; - let targetId = trigger.dataset.controls; trigger.setAttribute('aria-expanded', expanded); @@ -214,6 +219,47 @@ let BookWyrm = new class { } } + /** + * Show or hide generic containers. + * + * @param {object} container - DOM node + * @param {boolean} pressed - Is the trigger pressed? + * + * @return {undefined} + */ + toggleContainer(container, pressed) { + this.addRemoveClass(container, 'hidden', pressed); + } + + /** + * Check or uncheck a checbox. + * + * @param {object} checkbox - DOM node + * @param {boolean} pressed - Is the trigger pressed? + * + * @return {undefined} + */ + toggleCheckbox(checkbox, pressed) { + document.getElementById(checkbox).checked = !!pressed; + } + + /** + * Give the focus to an element. + * + * @param {string} nodeId - ID of the DOM node to focus (button, link…) + * + * @return {undefined} + */ + toggleFocus(nodeId) { + let node = document.getElementById(nodeId); + + node.focus(); + + setTimeout(function() { + node.selectionStart = node.selectionEnd = 10000; + }, 0); + } + /** * Submit a form using POST. * From 44040201f9d28ea5ea78dfd847d2c72dafcef038 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Tue, 6 Apr 2021 17:57:39 +0200 Subject: [PATCH 026/112] [assets] Move interact function. --- bookwyrm/static/js/bookwyrm.js | 48 +++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index 0ec14336..b3959e30 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -174,30 +174,6 @@ let BookWyrm = new class { } } - /** - * Make a request and update the UI accordingly. - * This function is used for boosts and favourites. - * - * @todo Only update status if the promise is successful. - * - * @param {Event} event - * - * @return {undefined} - */ - interact(event) { - event.preventDefault(); - - this.ajaxPost(event.target); - - // @todo This probably should be done with IDs. - document.querySelectorAll(`.${event.target.dataset.id}`) - .forEach(node => this.addRemoveClass( - node, - 'hidden', - node.className.indexOf('hidden') == -1 - )); - } - /** * Show or hide menus. * @@ -260,6 +236,30 @@ let BookWyrm = new class { }, 0); } + /** + * Make a request and update the UI accordingly. + * This function is used for boosts and favourites. + * + * @todo Only update status if the promise is successful. + * + * @param {Event} event + * + * @return {undefined} + */ + interact(event) { + event.preventDefault(); + + this.ajaxPost(event.target); + + // @todo This probably should be done with IDs. + document.querySelectorAll(`.${event.target.dataset.id}`) + .forEach(node => this.addRemoveClass( + node, + 'hidden', + node.className.indexOf('hidden') == -1 + )); + } + /** * Submit a form using POST. * From 1c05107f2bf322505b1962fc245de1071da15d0f Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Wed, 7 Apr 2021 07:53:30 +0200 Subject: [PATCH 027/112] [assets] Tweak comments. --- bookwyrm/static/js/bookwyrm.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index b3959e30..f36df109 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -177,8 +177,6 @@ let BookWyrm = new class { /** * Show or hide menus. * - * @note This function seems to be redundant and conflicts with toggleAction. - * * @param {Event} event * * @return {undefined} @@ -222,6 +220,9 @@ let BookWyrm = new class { /** * Give the focus to an element. * + * This is useful with modals: the focus needs to go back to the element that + * was focused before opening the modal. + * * @param {string} nodeId - ID of the DOM node to focus (button, link…) * * @return {undefined} From 5d569e89266d4d6b97134e6abc9ecb71986d9b3d Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Wed, 7 Apr 2021 09:24:34 +0200 Subject: [PATCH 028/112] [assets] Only update status if the promise is successful: - Use promises in `ajaxPost` and `interact`. - Add some animations in CSS. --- bookwyrm/static/css/bookwyrm.css | 28 ++++++++++++++++++ bookwyrm/static/js/bookwyrm.js | 51 ++++++++++++++++++++++++-------- 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/bookwyrm/static/css/bookwyrm.css b/bookwyrm/static/css/bookwyrm.css index a01aff82..980b4c4a 100644 --- a/bookwyrm/static/css/bookwyrm.css +++ b/bookwyrm/static/css/bookwyrm.css @@ -224,3 +224,31 @@ html { content: "\e905"; right: 0; } + +/** Animations and transitions + ******************************************************************************/ + +@keyframes beating { + 0% { font-size: 100%; } + 25% { font-size: 125%; } + 100% { font-size: 75%; } +} + +@keyframes turning { + from { transform: rotateZ(0deg); } + to { transform: rotateZ(360deg); } +} + +.is-processing .icon-heart::before { + animation: beating 0.5s infinite alternate ease-in-out; +} + +.is-processing .icon-boost::before { + animation: turning 0.5s infinite ease-in-out; +} + +@media (prefers-reduced-motion: reduce) { + .is-processing .icon::before { + animation-duration: 1.5s; + } +} diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index f36df109..0531b4d7 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -241,8 +241,6 @@ let BookWyrm = new class { * Make a request and update the UI accordingly. * This function is used for boosts and favourites. * - * @todo Only update status if the promise is successful. - * * @param {Event} event * * @return {undefined} @@ -250,15 +248,44 @@ let BookWyrm = new class { interact(event) { event.preventDefault(); - this.ajaxPost(event.target); + const bookwyrm = this; - // @todo This probably should be done with IDs. - document.querySelectorAll(`.${event.target.dataset.id}`) - .forEach(node => this.addRemoveClass( - node, - 'hidden', - node.className.indexOf('hidden') == -1 - )); + let allTriggers = document.querySelectorAll(`.${event.target.dataset.id}`); + + // Change icon to show ongoing activity on the current UI. + allTriggers.forEach(node => bookwyrm.addRemoveClass( + node, + 'is-processing', + true + )); + + this.ajaxPost(event.target) + .finally(() => { + // Change icon to remove ongoing activity on the current UI. + allTriggers.forEach(node => bookwyrm.addRemoveClass( + node, + 'is-processing', + false + )); + }) + .then(function() { + allTriggers.forEach(node => bookwyrm.addRemoveClass( + node, + 'hidden', + node.className.indexOf('hidden') == -1 + )); + }) + .catch(error => { + // @todo Display a notification in the UI instead. + // For now, the absence of change will be enough. + console.warn('Request failed:', error); + + allTriggers.forEach(node => bookwyrm.addRemoveClass( + node, + 'has-error', + node.className.indexOf('hidden') == -1 + )); + }); } /** @@ -266,10 +293,10 @@ let BookWyrm = new class { * * @param {object} form - Form to be submitted * - * @return {undefined} + * @return {Promise} */ ajaxPost(form) { - fetch(form.action, { + return fetch(form.action, { method : "POST", body: new FormData(form) }); From 54805afb51a50bef8c1293652512fac41d000fc8 Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Wed, 7 Apr 2021 10:37:11 +0200 Subject: [PATCH 029/112] [assets] Tweak JSDoc. --- bookwyrm/static/js/bookwyrm.js | 16 ++-------------- bookwyrm/static/js/check_all.js | 24 +++++++++++++++++------- bookwyrm/static/js/localstorage.js | 2 -- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index 0531b4d7..dacb8091 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -57,7 +57,6 @@ let BookWyrm = new class { * Go back in browser history. * * @param {Event} event - * * @return {undefined} */ back(event) { @@ -67,10 +66,10 @@ let BookWyrm = new class { /** * Update a counter with recurring requests to the API + * The delay is slightly randomized and increased on each cycle. * * @param {Object} counter - DOM node * @param {int} delay - frequency for polling in ms - * * @return {undefined} */ polling(counter, delay) { @@ -93,7 +92,6 @@ let BookWyrm = new class { * * @param {object} counter - DOM node * @param {object} data - json formatted response from a fetch - * * @return {undefined} */ updateCountElement(counter, data) { @@ -110,7 +108,6 @@ let BookWyrm = new class { * Toggle form. * * @param {Event} event - * * @return {undefined} */ revealForm(event) { @@ -124,7 +121,6 @@ let BookWyrm = new class { * Execute actions on targets based on triggers. * * @param {Event} event - * * @return {undefined} */ toggleAction(event) { @@ -178,7 +174,6 @@ let BookWyrm = new class { * Show or hide menus. * * @param {Event} event - * * @return {undefined} */ toggleMenu(trigger, targetId) { @@ -198,7 +193,6 @@ let BookWyrm = new class { * * @param {object} container - DOM node * @param {boolean} pressed - Is the trigger pressed? - * * @return {undefined} */ toggleContainer(container, pressed) { @@ -210,7 +204,6 @@ let BookWyrm = new class { * * @param {object} checkbox - DOM node * @param {boolean} pressed - Is the trigger pressed? - * * @return {undefined} */ toggleCheckbox(checkbox, pressed) { @@ -219,12 +212,9 @@ let BookWyrm = new class { /** * Give the focus to an element. - * - * This is useful with modals: the focus needs to go back to the element that - * was focused before opening the modal. + * Only move the focus based on user interactions. * * @param {string} nodeId - ID of the DOM node to focus (button, link…) - * * @return {undefined} */ toggleFocus(nodeId) { @@ -242,7 +232,6 @@ let BookWyrm = new class { * This function is used for boosts and favourites. * * @param {Event} event - * * @return {undefined} */ interact(event) { @@ -292,7 +281,6 @@ let BookWyrm = new class { * Submit a form using POST. * * @param {object} form - Form to be submitted - * * @return {Promise} */ ajaxPost(form) { diff --git a/bookwyrm/static/js/check_all.js b/bookwyrm/static/js/check_all.js index de067f78..fd29f2cd 100644 --- a/bookwyrm/static/js/check_all.js +++ b/bookwyrm/static/js/check_all.js @@ -1,13 +1,23 @@ -/** - * Toggle all descendant checkboxes of a target. - * - * Use `data-target="ID_OF_TARGET"` on the node on which the event is listened - * to (checkbox, button, link…), where_ID_OF_TARGET_ should be the ID of an - * ancestor for the checkboxes. - */ + (function() { 'use strict'; + /** + * Toggle all descendant checkboxes of a target. + * + * Use `data-target="ID_OF_TARGET"` on the node on which the event is listened + * to (checkbox, button, link…), where_ID_OF_TARGET_ should be the ID of an + * ancestor for the checkboxes. + * + * @example + * + * @param {Event} event + * @return {undefined} + */ function toggleAllCheckboxes(event) { const mainCheckbox = event.target; diff --git a/bookwyrm/static/js/localstorage.js b/bookwyrm/static/js/localstorage.js index eccac347..e724cb01 100644 --- a/bookwyrm/static/js/localstorage.js +++ b/bookwyrm/static/js/localstorage.js @@ -14,7 +14,6 @@ let LocalStorageTools = new class { * Update localStorage, then display content based on keys in localStorage. * * @param {Event} event - * * @return {undefined} */ updateDisplay(event) { @@ -32,7 +31,6 @@ let LocalStorageTools = new class { * Toggle display of a DOM node based on its value in the localStorage. * * @param {object} node - DOM node to toggle. - * * @return {undefined} */ setDisplay(node) { From f2a2b410a116a82be0803515d35839fc8ff36adf Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Wed, 7 Apr 2021 10:49:52 +0200 Subject: [PATCH 030/112] [assets] Add comment to mention replacing font icons with SVG. --- bookwyrm/static/css/vendor/icons.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bookwyrm/static/css/vendor/icons.css b/bookwyrm/static/css/vendor/icons.css index c6dbad1d..c78af145 100644 --- a/bookwyrm/static/css/vendor/icons.css +++ b/bookwyrm/static/css/vendor/icons.css @@ -1,3 +1,6 @@ + +/** @todo Replace icons with SVG symbols. + @see https://www.youtube.com/watch?v=9xXBYcWgCHA */ @font-face { font-family: 'icomoon'; src: url('../fonts/icomoon.eot?n5x55'); From f77088b9646de170efd6ace8ba3d7d97bb702a9c Mon Sep 17 00:00:00 2001 From: Fabien Basmaison Date: Wed, 7 Apr 2021 17:31:00 +0200 Subject: [PATCH 031/112] [assets] Remove redundant class already provided by bulma: Replace all classes `hidden` with `is-hidden` in templates, CSS and JS. --- bookwyrm/static/css/bookwyrm.css | 28 +++++++++---------- bookwyrm/static/js/bookwyrm.js | 16 +++++------ bookwyrm/static/js/localstorage.js | 2 +- bookwyrm/templates/book/book.html | 4 +-- .../templates/components/inline_form.html | 2 +- bookwyrm/templates/components/modal.html | 2 +- bookwyrm/templates/feed/feed.html | 2 +- bookwyrm/templates/goal.html | 2 +- bookwyrm/templates/layout.html | 2 +- bookwyrm/templates/search_results.html | 2 +- bookwyrm/templates/snippets/boost_button.html | 4 +-- .../snippets/content_warning_field.html | 2 +- .../snippets/create_status_form.html | 2 +- bookwyrm/templates/snippets/fav_button.html | 4 +-- .../snippets/filters_panel/filters_panel.html | 2 +- .../templates/snippets/follow_button.html | 4 +-- bookwyrm/templates/snippets/rate_action.html | 2 +- bookwyrm/templates/snippets/readthrough.html | 4 +-- .../shelve_button/shelve_button_options.html | 2 +- .../snippets/status/status_body.html | 2 +- .../snippets/status/status_content.html | 2 +- bookwyrm/templates/snippets/trimmed_text.html | 2 +- bookwyrm/templates/user/lists.html | 2 +- 23 files changed, 48 insertions(+), 48 deletions(-) diff --git a/bookwyrm/static/css/bookwyrm.css b/bookwyrm/static/css/bookwyrm.css index 980b4c4a..59961cac 100644 --- a/bookwyrm/static/css/bookwyrm.css +++ b/bookwyrm/static/css/bookwyrm.css @@ -25,16 +25,6 @@ html { min-width: 75% !important; } -/* --- "disabled" for non-buttons --- */ -.is-disabled { - background-color: #dbdbdb; - border-color: #dbdbdb; - box-shadow: none; - color: #7a7a7a; - opacity: 0.5; - cursor: not-allowed; -} - /* --- SHELVING --- */ /** @todo Replace icons with SVG symbols. @@ -57,10 +47,6 @@ html { display: none; } -.hidden { - display: none !important; -} - .hidden.transition-y, .hidden.transition-x { display: block !important; @@ -252,3 +238,17 @@ html { animation-duration: 1.5s; } } + +/* States + ******************************************************************************/ + +/* "disabled" for non-buttons */ + +.is-disabled { + background-color: #dbdbdb; + border-color: #dbdbdb; + box-shadow: none; + color: #7a7a7a; + opacity: 0.5; + cursor: not-allowed; +} diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index dacb8091..0393b4cc 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -99,7 +99,7 @@ let BookWyrm = new class { const count = data.count; if (count != currentCount) { - this.addRemoveClass(counter.closest('[data-poll-wrapper]'), 'hidden', count < 1); + this.addRemoveClass(counter.closest('[data-poll-wrapper]'), 'is-hidden', count < 1); counter.innerText = count; } } @@ -112,9 +112,9 @@ let BookWyrm = new class { */ revealForm(event) { let trigger = event.currentTarget; - let hidden = trigger.closest('.hidden-form').querySelectorAll('.hidden')[0]; + let hidden = trigger.closest('.hidden-form').querySelectorAll('.is-hidden')[0]; - this.addRemoveClass(hidden, 'hidden', !hidden); + this.addRemoveClass(hidden, 'is-hidden', !hidden); } /** @@ -139,7 +139,7 @@ let BookWyrm = new class { if (targetId && ! trigger.classList.contains('pulldown-menu')) { let target = document.getElementById(targetId); - this.addRemoveClass(target, 'hidden', !pressed); + this.addRemoveClass(target, 'is-hidden', !pressed); this.addRemoveClass(target, 'is-active', pressed); } @@ -196,7 +196,7 @@ let BookWyrm = new class { * @return {undefined} */ toggleContainer(container, pressed) { - this.addRemoveClass(container, 'hidden', pressed); + this.addRemoveClass(container, 'is-hidden', pressed); } /** @@ -260,8 +260,8 @@ let BookWyrm = new class { .then(function() { allTriggers.forEach(node => bookwyrm.addRemoveClass( node, - 'hidden', - node.className.indexOf('hidden') == -1 + 'is-hidden', + node.className.indexOf('is-hidden') == -1 )); }) .catch(error => { @@ -272,7 +272,7 @@ let BookWyrm = new class { allTriggers.forEach(node => bookwyrm.addRemoveClass( node, 'has-error', - node.className.indexOf('hidden') == -1 + node.className.indexOf('is-hidden') == -1 )); }); } diff --git a/bookwyrm/static/js/localstorage.js b/bookwyrm/static/js/localstorage.js index e724cb01..05955779 100644 --- a/bookwyrm/static/js/localstorage.js +++ b/bookwyrm/static/js/localstorage.js @@ -38,6 +38,6 @@ let LocalStorageTools = new class { let key = node.dataset.hide; let value = window.localStorage.getItem(key); - BookWyrm.addRemoveClass(node, 'hidden', value); + BookWyrm.addRemoveClass(node, 'is-hidden', value); } } diff --git a/bookwyrm/templates/book/book.html b/bookwyrm/templates/book/book.html index a7887c61..9f9e53e7 100644 --- a/bookwyrm/templates/book/book.html +++ b/bookwyrm/templates/book/book.html @@ -100,7 +100,7 @@ {% trans 'Add Description' as button_text %} {% 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" %} -