diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 084064509f..0000000000 --- a/.coveragerc +++ /dev/null @@ -1,2 +0,0 @@ -[run] -omit = */test*,celerywyrm*,bookwyrm/migrations/* diff --git a/.dockerignore b/.dockerignore index 5edf3de0d9..f84827c689 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,6 @@ __pycache__ +**/tests +locale/**/*.po *.pyc *.pyo *.pyd diff --git a/.env.dev.example b/.env.dev.example new file mode 100644 index 0000000000..3d7311448e --- /dev/null +++ b/.env.dev.example @@ -0,0 +1,4 @@ +DOMAIN=localhost +PORT=1333 +DEBUG=true +NGINX_SETUP=reverse_proxy diff --git a/.env.example b/.env.example index d2ae90f474..9909edfe52 100644 --- a/.env.example +++ b/.env.example @@ -35,6 +35,9 @@ DEFAULT_LANGUAGE="English" STATIC_ROOT=static/ MEDIA_ROOT=images/ +# A comma-separated list of max dimensions for use uploads, each entry denotes a new version +UPLOAD_IMAGE_DIMENSIONS=400,1200,2000 + # Database configuration PGPORT=5432 # POSTGRES_PASSWORD=securedbypassword123 @@ -42,6 +45,9 @@ POSTGRES_USER=bookwyrm POSTGRES_DB=bookwyrm POSTGRES_HOST=db +# Docker container shared memory size, tune accordingly for your db/host memory size if needed +POSTGRES_SHM_SIZE=128mb + # Redis activity stream manager MAX_STREAM_LENGTH=200 REDIS_ACTIVITY_HOST=redis_activity @@ -105,8 +111,7 @@ S3_SIGNED_URL_EXPIRY=900 # AWS_S3_REGION_NAME=None # "fr-par" # AWS_S3_ENDPOINT_URL=None # "https://s3.fr-par.scw.cloud" # AWS_DEFAULT_ACL="" # AWS_DEFAULT_ACL defaults to public-read, however, not all providers, e.g. BackBlaze, support it. - # Uncomment and set this value to an empty string (for Back Blaze) or whatever is required by your provider to override it. - +# Uncomment and set this value to an empty string (for Back Blaze) or whatever is required by your provider to override it. # Commented are example values if you use Azure Blob Storage # USE_AZURE=true diff --git a/.eslintrc.js b/.eslintrc.js index b65fe9885a..abdeffbad5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -86,5 +86,16 @@ module.exports = { }, ], "space-before-blocks": "error", + }, + "globals": { + "gettext": "readonly", + "ngettext": "readonly", + "interpolate": "readonly", + "get_format": "readonly", + "gettext_noop": "readonly", + "pgettext": "readonly", + "npgettext": "readonly", + "pluralidx": "readonly", + "django": "readonly" } }; diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 570174248a..f0acc0ad1e 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,32 +1,14 @@ - ## Description - - - - Related Issue # - Closes # + ## What type of Pull Request is this? - - [ ] Bug Fix - [ ] Enhancement @@ -34,7 +16,6 @@ automatically close the issue. - [ ] Refactor ## Does this PR change settings or dependencies, or break something? - - [ ] This PR changes or adds default settings, configuration, or .env values - [ ] This PR changes or adds dependencies @@ -45,22 +26,18 @@ automatically close the issue. ## Documentation - - - [ ] New or amended documentation will be required if this PR is merged - [ ] I have created a matching pull request in the Documentation repository - [ ] I intend to create a matching pull request in the Documentation repository after this PR is merged ### Tests - - [ ] My changes do not need new tests - [ ] All tests I have added are passing diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml new file mode 100644 index 0000000000..05972bf7ac --- /dev/null +++ b/.github/workflows/container.yml @@ -0,0 +1,81 @@ +name: Container building for release + +on: + pull_request: + branches: ["main"] + paths: + - ".github/workflows/container.yml" + - "Dockerfile" + - "entrypoint.sh" + - "postgres-docker/*" + push: + branches: ["main"] + tags: + - v* + release: + types: + - released + workflow_dispatch: + inputs: + tag: + description: "Docker tag for build results" + type: string + default: manual + required: true + +jobs: + build: + strategy: + matrix: + image: [bookwyrm, postgres-backup] + include: + - image: bookwyrm + context: . + - image: postgres-backup + context: postgres-docker + uses: docker/github-builder/.github/workflows/build.yml@v1 + permissions: + contents: read # for checkout + packages: write # to upload build to GHCR + id-token: write # signing attestations with github oidc token + with: + output: image + push: ${{ github.event_name != 'pull_request' }} + context: ${{ matrix.context }} + platforms: linux/amd64,linux/arm64 + cache: true + cache-mode: max + fail-fast: true + sbom: true + sign: false + cache-scope: ${{ matrix.image }} + meta-images: ghcr.io/${{ github.repository_owner }}/${{ matrix.image }} + meta-tags: | + type=raw,value=${{ inputs.tag }},event=workflow_dispatch + type=semver,pattern=v{{version}} + type=ref,event=branch + meta-flavor: | + latest=${{ github.event_name == 'release' }} + set-meta-labels: true + set-meta-annotations: true + secrets: + registry-auths: | + - registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + cleanup: + needs: build + if: ${{ github.ref_name == 'main' }} + runs-on: ubuntu-latest + strategy: + matrix: + image: [bookwyrm, postgres-backup] + permissions: + packages: write + + steps: + - uses: dataaxiom/ghcr-cleanup-action@v1 + with: + package: ${{ matrix.image }} + delete-untagged: true + older-than: 1 hour diff --git a/.github/workflows/curlylint.yaml b/.github/workflows/curlylint.yaml deleted file mode 100644 index 10ad04ce14..0000000000 --- a/.github/workflows/curlylint.yaml +++ /dev/null @@ -1,28 +0,0 @@ -name: Templates validator - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install curlylint - run: pip install curlylint - - - name: Run linter - run: > - curlylint --rule 'aria_role: true' \ - --rule 'django_forms_rendering: true' \ - --rule 'html_has_lang: true' \ - --rule 'image_alt: true' \ - --rule 'meta_viewport: true' \ - --rule 'no_autofocus: true' \ - --rule 'tabindex_no_positive: true' \ - --exclude '_modal.html|create_status/layout.html|reading_modals/layout.html' \ - bookwyrm/templates diff --git a/.github/workflows/eslint.yaml b/.github/workflows/eslint.yaml new file mode 100644 index 0000000000..99d95a0f0d --- /dev/null +++ b/.github/workflows/eslint.yaml @@ -0,0 +1,25 @@ +name: Lint JavaScript + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + lint: + name: Lint with ESLint. + runs-on: ubuntu-24.04 + + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it. + - uses: actions/checkout@v4 + + - name: Install modules + run: npm install eslint@^8.9.0 + + # See .eslintignore for files that are not linted. + - name: Run ESLint + run: > + npx eslint bookwyrm/static \ + --ext .js,.jsx,.ts,.tsx diff --git a/.github/workflows/lint-frontend.yaml b/.github/workflows/lint-frontend.yaml deleted file mode 100644 index 68142b9467..0000000000 --- a/.github/workflows/lint-frontend.yaml +++ /dev/null @@ -1,38 +0,0 @@ -# @url https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions -name: Lint Frontend (run `./bw-dev stylelint` to fix css errors) - -on: - push: - branches: [ main, ci, frontend ] - paths: - - '.github/workflows/**' - - 'static/**' - - '.eslintrc' - - '.stylelintrc.js' - pull_request: - branches: [ main, ci, frontend ] - -jobs: - lint: - name: Lint with stylelint and ESLint. - runs-on: ubuntu-24.04 - - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it. - - uses: actions/checkout@v4 - - - name: Install modules - # run: npm install stylelint stylelint-config-recommended stylelint-config-standard stylelint-order eslint - run: npm install eslint@^8.9.0 - - # See .stylelintignore for files that are not linted. - # - name: Run stylelint - # run: > - # npx stylelint bookwyrm/static/css/*.scss bookwyrm/static/css/bookwyrm/**/*.scss \ - # --config dev-tools/.stylelintrc.js - - # See .eslintignore for files that are not linted. - - name: Run ESLint - run: > - npx eslint bookwyrm/static \ - --ext .js,.jsx,.ts,.tsx diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index baa4f3a22b..9e73c8f01e 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -1,9 +1,9 @@ name: Python on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] # overrides for .env.example env: @@ -34,66 +34,62 @@ jobs: ports: - 5432:5432 steps: - - uses: actions/checkout@v4 - - name: Set up Python 3.11 - uses: actions/setup-python@v5 - with: - python-version: 3.11 - cache: pip - - name: Install Dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install pytest-github-actions-annotate-failures - - name: Set up .env - run: cp .env.example .env - - name: Check migrations up-to-date - run: python ./manage.py makemigrations --check -v 3 - - name: Run Tests - run: pytest -n 3 + - uses: actions/checkout@v4 + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: 3.11 + cache: pip + - name: Install Dependencies + run: | + python -m pip install --upgrade "pip>=25.1.0" + pip install --group main --group dev + pip install pytest-github-actions-annotate-failures + - name: Set up .env + run: cp .env.example .env + - name: Check migrations up-to-date + run: python ./manage.py makemigrations --check -v 3 + - name: Compile themes and collect statics + run: | + python ./manage.py compile_themes + python ./manage.py collectstatic --no-input + - name: Run Tests + run: pytest -n 3 - pylint: - name: Linting (pylint) + ruff: + name: Linting & Formatting (ruff) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Set up Python 3.11 - uses: actions/setup-python@v5 - with: - python-version: 3.11 - cache: pip - - name: Install Dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - name: Analyse code with pylint - run: pylint bookwyrm/ + - uses: actions/checkout@v4 + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: 3.11 + cache: pip + - name: Install Dependencies + run: | + python -m pip install --upgrade "pip>=25.1.0" + pip install --group main --group dev + - name: Check code formatting with ruff + run: ruff format --check bookwyrm/ celerywyrm/ + - name: Lint code with ruff + run: ruff check bookwyrm/ celerywyrm/ mypy: name: Typing (mypy) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Set up Python 3.11 - uses: actions/setup-python@v5 - with: - python-version: 3.11 - cache: pip - - name: Install Dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - name: Set up .env - run: cp .env.example .env - - name: Analyse code with mypy - run: mypy bookwyrm celerywyrm - - black: - name: Formatting (black; run ./bw-dev black to fix) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - - uses: psf/black@stable - with: - version: "22.*" + - uses: actions/checkout@v4 + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: 3.11 + cache: pip + - name: Install Dependencies + run: | + python -m pip install --upgrade "pip>=25.1.0" + pip install --group main --group dev + - name: Set up .env + run: cp .env.example .env + - name: Analyse code with mypy + run: mypy bookwyrm celerywyrm diff --git a/.github/workflows/stylelint.yaml b/.github/workflows/stylelint.yaml new file mode 100644 index 0000000000..3eaa43c714 --- /dev/null +++ b/.github/workflows/stylelint.yaml @@ -0,0 +1,21 @@ +name: Lint Stylesheets files (run `./bw-dev stylelint` to fix errors) + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install modules + run: npm install stylelint stylelint-config-recommended stylelint-config-standard stylelint-order stylelint-config-standard-scss + + - name: Run Stylelint + run: > + npx stylelint bookwyrm/static/css \ + --config dev-tools/.stylelintrc.js --ignore-path dev-tools/.stylelintignore diff --git a/.gitignore b/.gitignore index e6ba03f7cb..77ddbf1718 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ # BookWyrm .env +.env.dev /images/ /exports/ /static/ diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index e89f7d5363..0000000000 --- a/.pylintrc +++ /dev/null @@ -1,21 +0,0 @@ -[MAIN] -ignore=migrations -load-plugins=pylint.extensions.no_self_use - -[MESSAGES CONTROL] -disable = - cyclic-import, - duplicate-code, - fixme, - no-member, - raise-missing-from, - too-few-public-methods, - too-many-ancestors, - too-many-instance-attributes, - unnecessary-lambda-assignment, - unsubscriptable-object, -enable = - useless-suppression - -[FORMAT] -max-line-length=88 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..24c3fd8857 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +Agents must not make contributions or changes. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 21164970fb..005ff508e3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,53 +1 @@ -# Contributing to BookWyrm - -Our goal is to make BookWyrm a kind and welcoming place where everyone can contribute to the success of the project. Here are some ways you can join the project: - -## Report things that are confusing - -We want BookWyrm to be a fun experience that is intuitive to understand. If you're confused by something, it's probably because it is confusing! We are always keen to improve our [documentation](https://docs.joinbookwyrm.com) and Guided Tour as well as the platform itself. - -You can [create an issue to improve our documentation](https://github.com/bookwyrm-social/documentation/issues) or if you prefer, [ask for help in our Matrix chat room](https://app.element.io/#/room/#bookwyrm:matrix.org). - -## Report bugs - -Sometimes things don't work the way we intended. We would love to have fewer bugs, but we can only fix them if we know about them. - -You can [report bugs](https://github.com/bookwyrm-social/bookwyrm/issues) by clicking "New Issue". The more information you can provide, the easier it will be to understand the problem and squash that bug! - -It's a good idea to search the Issues for key words associated with your bug first because someone else may have already reported it. - -## Request and discuss new features - -Got a great idea for an improvement to BookWyrm? You can [request new features](https://github.com/bookwyrm-social/bookwyrm/issues) by clicking "New Issue". - -It's a good idea to search the Issues for key words associated with your feature suggestion first because someone else may have already requested it. - -## Translate BookWyrm into international languages - -Books are written in many languages, and BookWyrm should be too. If you know more than one language, you might be able to help us to [translate BookWyrm](https://translate.joinbookwyrm.com/). You can find out more about translation [in the documentation](https://docs.joinbookwyrm.com/translation.html). - -## Keep the documentation up to date - -Good documentation is crucial so that people know how to use, contribute to, and administer BookWyrm. No matter how you are involved with BookWyrm, your perspective is valuable and you can contribute to our documentation. - -We managed documentation in [a separate GitHub repository](https://github.com/bookwyrm-social/documentation) where you can [log a documentation issue](https://github.com/bookwyrm-social/documentation/issues) or contribute to the documentation yourself. - -## Test draft versions - -Are you a BookWyrm instance administrator? You can help to test new features when we release them in a draft version of BookWyrm, and report back on your experiences. This is crucial to helping us to release stable versions with fewer bugs. - -## Contribute code - -If you're able to write code, you can contribute that way! Check out the [Guide to the developer environment](https://docs.joinbookwyrm.com/install-dev.html) and our code [style guide](https://docs.joinbookwyrm.com/style_guide.html). - -## Provide expert advice - -Bibliographic metadata wizard? Celery nerd? ActivityPub expert? SQL query obsessive? We need all kinds of expertise! You can contribute to discussions in [the Issues](https://github.com/bookwyrm-social/bookwyrm/issues) or reach out to make suggestions [in our Matrix chat room](https://app.element.io/#/room/#bookwyrm:matrix.org) or via an Issue of your own. - -## More information - -You can find out more about BookWyrm and contributing at [JoinBookWyrm.com](https://joinbookwyrm.com/get-involved/). - -Ensure you are aware of and agree to our [Code of Conduct](https://github.com/bookwyrm-social/bookwyrm/blob/main/CODE_OF_CONDUCT.md). - -Please note that the BookWyrm project is licensed under the [Anti-capitalist Software License](https://github.com/bookwyrm-social/bookwyrm/blob/main/LICENSE.md). \ No newline at end of file +We'd love you to contribute to BookWyrm! Please see [How to Contribute](https://docs.joinbookwyrm.com/contributing.html) in the BookWyrm docs. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 82b0c92c50..910949be8f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,43 @@ -FROM python:3.11 +FROM python:3.11 AS build -ENV PYTHONUNBUFFERED 1 +ENV PYTHONUNBUFFERED=1 +ENV PATH=/venv/bin:$PATH -RUN mkdir /app /app/static /app/images +WORKDIR /app + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked --mount=type=cache,target=/var/lib/apt,sharing=locked apt-get update && apt-get install -y gettext libgettextpo-dev tidy libpq5 libsass-dev + +# libsass doesn't provide arm64 wheel and takes ~25min to compile in github action +# So tell libsass-python to use system installed libsass instead compiling one in build phase +# and build takes as whole ~2min in arm64 github action +ENV SYSTEM_SASS=True + +RUN python -mvenv /venv + +COPY pyproject.toml /app/ +RUN --mount=type=cache,target=/root/.cache/pip pip install "pip>=25.1.0" && pip install --group main --group dev +FROM python:3.11-slim WORKDIR /app -RUN apt-get update && apt-get install -y gettext libgettextpo-dev tidy && apt-get clean +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked --mount=type=cache,target=/var/lib/apt,sharing=locked apt-get update && apt-get install -y gettext tidy libpq5 libsass1 curl +ENV PYTHONUNBUFFERED=1 +ENV PATH=/venv/bin:$PATH +ENV SYSTEM_SASS=True + +COPY --from=build /venv /venv +COPY --from=build /app /app + +COPY entrypoint.sh /entrypoint.sh +COPY README.md LICENSE.md VERSION /app/ +COPY manage.py gunicorn.conf.py /app/ +COPY celerywyrm /app/celerywyrm +COPY locale /app/locale +COPY bookwyrm /app/bookwyrm + +RUN python3 -mcompileall /app/bookwyrm /app/celerywyrm /app/manage.py /app/gunicorn.conf.py + +VOLUME ["/app/exports", "/app/images", "/app/static"] -COPY requirements.txt /app/ -RUN pip install -r requirements.txt --no-cache-dir +# Entrypoint script is used to do database migrations and collectstatic +ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md index 0322308821..ccedf61e85 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # BookWyrm - [![](https://img.shields.io/github/release/bookwyrm-social/bookwyrm.svg?colorB=58839b)](https://github.com/bookwyrm-social/bookwyrm/releases) -[![Run Python Tests](https://github.com/bookwyrm-social/bookwyrm/actions/workflows/django-tests.yml/badge.svg)](https://github.com/bookwyrm-social/bookwyrm/actions/workflows/django-tests.yml) -[![Pylint](https://github.com/bookwyrm-social/bookwyrm/actions/workflows/pylint.yml/badge.svg)](https://github.com/bookwyrm-social/bookwyrm/actions/workflows/pylint.yml) +[![Ruff](https://github.com/bookwyrm-social/bookwyrm/actions/workflows/python.yml/badge.svg?job=ruff)](https://github.com/bookwyrm-social/bookwyrm/actions/workflows/python.yml) + +## Welcome to the codebase! BookWyrm is a social network for tracking your reading, talking about books, writing reviews, and discovering what to read next. Federation allows BookWyrm users to join small, trusted communities that can connect with one another, and with other ActivityPub services like [Mastodon](https://joinmastodon.org/) and [Pleroma](http://pleroma.social/). +👋 Meaningful human interaction is the whole point: please feel welcome to participate regardless of your experience level with programming. We would love to help! By the same token, **please do *not* submit any AI-generated code or content.** ## Links @@ -22,7 +23,7 @@ BookWyrm is a platform for social reading. You can use it to track what you're r ## Federation BookWyrm is built on [ActivityPub](http://activitypub.rocks/). With ActivityPub, it inter-operates with different instances of BookWyrm, and other ActivityPub compliant services, like Mastodon. This means you can run an instance for your book club, and still follow your friend who posts on a server devoted to 20th century Russian speculative fiction. It also means that your friend on mastodon can read and comment on a book review that you post on your BookWyrm instance. -Federation makes it possible to have small, self-determining communities, in contrast to the monolithic service you find on GoodReads or Twitter. An instance can be focused on a particular interest, be just for a group of friends, or anything else that brings people together. Each community can choose which other instances they want to federate with, and moderate and run their community autonomously. Check out https://runyourown.social/ to get a sense of the philosophy and logistics behind small, high-trust social networks. +Federation makes it possible to have small, self-determining communities, in contrast to the monolithic service you find on GoodReads or Twitter. An instance can be focused on a particular interest, be just for a group of friends, or anything else that brings people together. Each community can choose which other instances they want to federate with, and moderate and run their community autonomously. Check out [`runyourown.social`](https://runyourown.social/) to get a sense of the philosophy and logistics behind small, high-trust social networks. Developers of other ActivityPub software can find out more about BookWyrm's implementation at [`FEDERATION.md`](https://github.com/bookwyrm-social/bookwyrm/blob/main/FEDERATION.md). @@ -41,7 +42,7 @@ Federation allows you to interact with users on other instances and services, an Users and administrators can control who can see their posts and what other instances to federate with. ## Tech Stack -Web backend +#### Web backend - [Django](https://www.djangoproject.com/) web server - [PostgreSQL](https://www.postgresql.org/) database - [ActivityPub](https://activitypub.rocks/) federation @@ -49,12 +50,12 @@ Web backend - [Redis](https://redis.io/) task backend - [Redis (again)](https://redis.io/) activity stream manager -Front end +#### Front end - Django templates -- [Bulma.io](https://bulma.io/) css framework +- [`Bulma.io`](https://bulma.io/) CSS framework - Vanilla JavaScript, in moderation -Deployment +#### Deployment - [Docker](https://www.docker.com/) and docker-compose - [Gunicorn](https://gunicorn.org/) web runner - [Flower](https://github.com/mher/flower) celery monitoring @@ -66,4 +67,4 @@ The [documentation website](https://docs.joinbookwyrm.com/) has instruction on h ## Contributing -There are many ways you can contribute to the success and health of the BookWyrm project! You do not have to know how to write code and we are always keen to see more people get involved. Find out how you can join the project at [CONTRIBUTING.md](https://github.com/bookwyrm-social/bookwyrm/blob/main/CONTRIBUTING.md) \ No newline at end of file +There are many ways you can contribute to the success and health of the BookWyrm project! You do not have to know how to write code and we are always keen to see more people get involved. Find out how you can join the project at [`CONTRIBUTING.md`](https://github.com/bookwyrm-social/bookwyrm/blob/main/CONTRIBUTING.md). diff --git a/VERSION b/VERSION index a3df0a6959..2003b639c4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.8.0 +0.9.2 diff --git a/anubis/botPolicy.yaml b/anubis/botPolicy.yaml new file mode 100644 index 0000000000..6c0ce427ca --- /dev/null +++ b/anubis/botPolicy.yaml @@ -0,0 +1,44 @@ +bots: + # import default config, you can check what it contains in https://github.com/TecharoHQ/anubis/blob/main/data/botPolicies.yaml + - import: (data)/meta/default-config.yaml + - name: Other Bookwyrm instance requests + action: ALLOW + expression: + all: + - 'userAgent.startsWith("BookWyrm")' + - '"Accept" in headers' + - 'headers["Accept"].contains("application/activity+json")' + - name: Allow access to manifest.json directly + action: ALLOW + path_regex: "^/manifest.json$" + - name: Allow access to user rss + action: ALLOW + path_regex: "^/user/.*/rss/?$" + - name: Allow access to user reviews/quotes/comments rss + action: ALLOW + path_regex: "^/user/.*/rss-(quotes|reviews|comments)/?$" + # Anubis and Safari don't always see eachother eye-to-eye, most likely happy eyeballs thing + # https://github.com/TecharoHQ/anubis/issues/289 + - name: Allow POST method for book creation etc + action: ALLOW + expression: method == "POST" + # Anubis subauth doesn't seem to handle CHALLENGE nicely, so tune weight + - name: All email confirmations are suspicious by default + action: WEIGH + path_regex: "^/confirm-email/" + weight: + adjust: 10 + # If request has cookie and it contains something like anubis thing, make it less suspicious + - name: Allow email confirmations with something looking like anubis-auth cookie + action: WEIGH + path_regex: "^/confirm-email/" + expression: + all: + - '"Cookies" in headers' + - 'headers["Cookies"].contains("anubis-auth")' + weight: + adjust: -10 + # +status_codes: + CHALLENGE: 200 + DENY: 403 diff --git a/bookwyrm/activitypub/__init__.py b/bookwyrm/activitypub/__init__.py index 41decd68af..3217497a75 100644 --- a/bookwyrm/activitypub/__init__.py +++ b/bookwyrm/activitypub/__init__.py @@ -1,4 +1,5 @@ -""" bring activitypub functions into the namespace """ +"""bring activitypub functions into the namespace""" + import inspect import sys @@ -14,16 +15,18 @@ from .note import Review, Rating from .note import Tombstone from .ordered_collection import OrderedCollection, OrderedCollectionPage -from .ordered_collection import CollectionItem, ListItem, ShelfItem -from .ordered_collection import BookList, Shelf +from .ordered_collection import CollectionItem, ListItem, ShelfItem, SuggestionListItem +from .ordered_collection import BookList, SuggestionList, Shelf from .person import Person, PublicKey from .response import ActivitypubResponse from .book import Edition, Work, Author +from .series import Series, SeriesBook from .verbs import Create, Delete, Undo, Update from .verbs import Follow, Accept, Reject, Block from .verbs import Add, Remove from .verbs import Announce, Like from .verbs import Move +from .verbs import Flag # this creates a list of all the Activity types that we can serialize, # so when an Activity comes in from outside, we can check if it's known diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 2a52d99f03..048cbdfeec 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -1,8 +1,13 @@ -""" basics for an activitypub serializer """ +"""basics for an activitypub serializer""" + from __future__ import annotations +from contextlib import contextmanager from dataclasses import dataclass, fields, MISSING +from hashlib import md5 from json import JSONEncoder import logging +import random +import time from typing import Optional, Union, TypeVar, overload, Any import requests @@ -14,13 +19,20 @@ from bookwyrm import models from bookwyrm.connectors import ConnectorException, get_data from bookwyrm.models import base_model +from bookwyrm.redis_store import r from bookwyrm.signatures import make_signature -from bookwyrm.settings import DOMAIN, INSTANCE_ACTOR_USERNAME +from bookwyrm.settings import ( + DOMAIN, + INSTANCE_ACTOR_USERNAME, + USER_AGENT, + QUERY_TIMEOUT, + SEARCH_TIMEOUT, +) from bookwyrm.tasks import app, MISC logger = logging.getLogger(__name__) -# pylint: disable=invalid-name + TBookWyrmModel = TypeVar("TBookWyrmModel", bound=base_model.BookWyrmModel) @@ -35,8 +47,46 @@ def default(self, o): return o.__dict__ +@contextmanager +def item_lock(object_id): + """Set a mutex lock on an incoming item to prevent race conditions causing duplicates""" + + # hash the remote id so the key is a reasonable length + try: + id_hash = md5(object_id.encode("utf-8")).hexdigest() + except AttributeError: + raise ActivitySerializerError("Incoming object has no identifier") + lock_id = f"lock:{id_hash}" + # expire after enough time to make up to ~59 queries + expire_seconds = time.monotonic() + (QUERY_TIMEOUT * 60) + # try to get the lock until you have it or it has (nearly) expired + while time.monotonic() < expire_seconds - 5: + # 1: arbitrary value for the key + # nx: only set if the key doesn't already exist + # ex: expire after this many seconds + if lock := r.set(lock_id, "1", nx=True, ex=QUERY_TIMEOUT * 60): + break + + # if we don't have the lock, try again in a few seconds + sleep_time = random.randint(1, QUERY_TIMEOUT) + time.sleep(sleep_time) + + if not lock: + raise ActivitySerializerError( + f"Lock expired on {object_id} before being acquired" + ) + + try: + yield lock + finally: + # after we have used it, clear the lock + # only release lock if you have it and it isn't expired + # otherwise we might clear a lock owned by another process + if time.monotonic() < expire_seconds and lock: + r.delete(lock_id) + + @dataclass -# pylint: disable=invalid-name class Signature: """public key block""" @@ -111,7 +161,6 @@ def __init__( value = field.default setattr(self, field.name, value) - # pylint: disable=too-many-locals,too-many-branches,too-many-arguments def to_model( self, model: Optional[type[TBookWyrmModel]] = None, @@ -156,87 +205,94 @@ def to_model( return None instance = instance or model() - # keep track of what we've changed - update_fields = [] - # sets field on the model using the activity value - for field in instance.simple_fields: - try: + identifier = ( + self.id + or instance.id + or getattr(self, "href", None) + or getattr(self, "url", None) + ) + with item_lock(identifier): # lock processing this item to prevent duplicates + # keep track of what we've changed + update_fields = [] + # sets field on the model using the activity value + for field in instance.simple_fields: + try: + changed = field.set_field_from_activity( + instance, + self, + overwrite=overwrite, + allow_external_connections=allow_external_connections, + ) + if changed: + update_fields.append(field.name) + except AttributeError as e: + raise ActivitySerializerError(e) + + # image fields have to be set after other fields because they can save + # too early and jank up users + for field in instance.image_fields: changed = field.set_field_from_activity( instance, self, + save=save, overwrite=overwrite, allow_external_connections=allow_external_connections, ) if changed: update_fields.append(field.name) - except AttributeError as e: - raise ActivitySerializerError(e) - - # image fields have to be set after other fields because they can save - # too early and jank up users - for field in instance.image_fields: - changed = field.set_field_from_activity( - instance, - self, - save=save, - overwrite=overwrite, - allow_external_connections=allow_external_connections, - ) - if changed: - update_fields.append(field.name) - if not save: - return instance + if not save: + return instance - with transaction.atomic(): - # can't force an update on fields unless the object already exists in the db - if not instance.id: - update_fields = None - # we can't set many to many and reverse fields on an unsaved object - try: + with transaction.atomic(): + # can't force an update on fields unless the object already exists in the db + if not instance.id: + update_fields = None + # we can't set many to many and reverse fields on an unsaved object try: - instance.save(broadcast=False, update_fields=update_fields) - except TypeError: - instance.save(update_fields=update_fields) - except IntegrityError as e: - raise ActivitySerializerError(e) - - # add many to many fields, which have to be set post-save - for field in instance.many_to_many_fields: - # mention books/users/hashtags, for example - field.set_field_from_activity( - instance, - self, - allow_external_connections=allow_external_connections, - ) + try: + instance.save(broadcast=False, update_fields=update_fields) + except TypeError: + instance.save(update_fields=update_fields) + except IntegrityError as e: + raise ActivitySerializerError(e) + + # add many to many fields, which have to be set post-save + for field in instance.many_to_many_fields: + # mention books/users/hashtags, for example + field.set_field_from_activity( + instance, + self, + allow_external_connections=allow_external_connections, + ) - # reversed relationships in the models - for ( - model_field_name, - activity_field_name, - ) in instance.deserialize_reverse_fields: - # attachments on Status, for example - values = getattr(self, activity_field_name) - if values is None or values is MISSING: - continue - - model_field = getattr(model, model_field_name) - # creating a Work, model_field is 'editions' - # creating a User, model field is 'key_pair' - related_model = model_field.field.model - related_field_name = model_field.field.name - - for item in values: - if trigger and item == trigger.remote_id: + # reversed relationships in the models + for ( + model_field_name, + activity_field_name, + ) in instance.deserialize_reverse_fields: + # attachments on Status, for example + values = getattr(self, activity_field_name) + if values is None or values is MISSING: continue - set_related_field.delay( - related_model.__name__, - instance.__class__.__name__, - related_field_name, - instance.remote_id, - item, - ) - return instance + + model_field = getattr(model, model_field_name) + # creating a Work, model_field is 'editions' + # creating a User, model field is 'key_pair' + related_model = model_field.field.model + related_field_name = model_field.field.name + + for item in values: + if trigger and item == trigger.remote_id: + continue + set_related_field.delay( + related_model.__name__, + instance.__class__.__name__, + related_field_name, + instance.remote_id, + item, + ) + return instance def serialize(self, **kwargs): """convert to dictionary with context attr""" @@ -317,7 +373,6 @@ def get_model_from_type(activity_type): return model[0] -# pylint: disable=too-many-arguments @overload def resolve_remote_id( remote_id: str, @@ -326,11 +381,9 @@ def resolve_remote_id( save: bool = True, get_activity: bool = False, allow_external_connections: bool = True, -) -> TBookWyrmModel: - ... +) -> TBookWyrmModel: ... -# pylint: disable=too-many-arguments @overload def resolve_remote_id( remote_id: str, @@ -339,11 +392,9 @@ def resolve_remote_id( save: bool = True, get_activity: bool = False, allow_external_connections: bool = True, -) -> base_model.BookWyrmModel: - ... +) -> base_model.BookWyrmModel: ... -# pylint: disable=too-many-arguments def resolve_remote_id( remote_id: str, model: Optional[Union[str, type[base_model.BookWyrmModel]]] = None, @@ -435,12 +486,12 @@ def get_activitypub_data(url): resp = requests.get( url, headers={ - # pylint: disable=line-too-long "Accept": 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', "Date": now, "Signature": make_signature("get", sender, url, now), + "User-Agent": USER_AGENT, }, - timeout=15, + timeout=SEARCH_TIMEOUT, ) except requests.RequestException: raise ConnectorException() diff --git a/bookwyrm/activitypub/book.py b/bookwyrm/activitypub/book.py index 33c327d0fe..2233bed359 100644 --- a/bookwyrm/activitypub/book.py +++ b/bookwyrm/activitypub/book.py @@ -1,4 +1,5 @@ -""" book and author data """ +"""book and author data""" + from dataclasses import dataclass, field from typing import Optional @@ -6,7 +7,6 @@ from .image import Document -# pylint: disable=invalid-name @dataclass(init=False) class BookData(ActivityObject): """shared fields for all book data and authors""" @@ -14,6 +14,7 @@ class BookData(ActivityObject): openlibraryKey: Optional[str] = None inventaireId: Optional[str] = None finnaKey: Optional[str] = None + librisKey: Optional[str] = None librarythingKey: Optional[str] = None goodreadsKey: Optional[str] = None bnfId: Optional[str] = None @@ -25,7 +26,6 @@ class BookData(ActivityObject): lastEditedBy: Optional[str] = None -# pylint: disable=invalid-name @dataclass(init=False) class Book(BookData): """serializes an edition or work, abstract""" @@ -35,8 +35,9 @@ class Book(BookData): subtitle: str = None description: str = "" languages: list[str] = field(default_factory=list) - series: str = "" - seriesNumber: str = "" + series: str = "" # legacy, now deprecated + seriesNumber: str = "" # legacy, now deprecated + seriesBooks: list[str] = field(default_factory=list) subjects: list[str] = field(default_factory=list) subjectPlaces: list[str] = field(default_factory=list) @@ -50,7 +51,6 @@ class Book(BookData): type: str = "Book" -# pylint: disable=invalid-name @dataclass(init=False) class Edition(Book): """Edition instance of a book object""" @@ -77,7 +77,6 @@ class Work(Book): type: str = "Work" -# pylint: disable=invalid-name @dataclass(init=False) class Author(BookData): """author of a book""" diff --git a/bookwyrm/activitypub/image.py b/bookwyrm/activitypub/image.py index 7950faaf89..e92ea5c495 100644 --- a/bookwyrm/activitypub/image.py +++ b/bookwyrm/activitypub/image.py @@ -1,4 +1,5 @@ -""" an image, nothing fancy """ +"""an image, nothing fancy""" + from dataclasses import dataclass from .base_activity import ActivityObject diff --git a/bookwyrm/activitypub/note.py b/bookwyrm/activitypub/note.py index 376560f6e8..ec7ebf4acd 100644 --- a/bookwyrm/activitypub/note.py +++ b/bookwyrm/activitypub/note.py @@ -1,4 +1,5 @@ -""" note serializer and children thereof """ +"""note serializer and children thereof""" + from dataclasses import dataclass, field from typing import Dict, List import re @@ -16,13 +17,12 @@ class Tombstone(ActivityObject): type: str = "Tombstone" - def to_model(self, *args, **kwargs): # pylint: disable=unused-argument + def to_model(self, *args, **kwargs): """this should never really get serialized, just searched for""" model = apps.get_model("bookwyrm.Status") return model.find_existing_by_remote_id(self.id) -# pylint: disable=invalid-name @dataclass(init=False) class Note(ActivityObject): """Note activity""" @@ -41,7 +41,6 @@ class Note(ActivityObject): updated: str = None type: str = "Note" - # pylint: disable=too-many-arguments def to_model( self, model=None, @@ -99,7 +98,6 @@ class GeneratedNote(Note): type: str = "GeneratedNote" -# pylint: disable=invalid-name @dataclass(init=False) class Comment(Note): """like a note but with a book""" diff --git a/bookwyrm/activitypub/ordered_collection.py b/bookwyrm/activitypub/ordered_collection.py index 250490041d..4f81002607 100644 --- a/bookwyrm/activitypub/ordered_collection.py +++ b/bookwyrm/activitypub/ordered_collection.py @@ -1,11 +1,12 @@ -""" defines activitypub collections (lists) """ +"""defines activitypub collections (lists)""" + from dataclasses import dataclass, field from typing import List from .base_activity import ActivityObject +from .book import Work -# pylint: disable=invalid-name @dataclass(init=False) class OrderedCollection(ActivityObject): """structure of an ordered collection activity""" @@ -42,7 +43,15 @@ class BookList(OrderedCollectionPrivate): type: str = "BookList" -# pylint: disable=invalid-name +@dataclass(init=False) +class SuggestionList(OrderedCollectionPrivate): + """structure of an ordered collection activity""" + + summary: str = None + book: Work = None + type: str = "SuggestionList" + + @dataclass(init=False) class OrderedCollectionPage(ActivityObject): """structure of an ordered collection activity""" @@ -73,6 +82,15 @@ class ListItem(CollectionItem): type: str = "ListItem" +@dataclass(init=False) +class SuggestionListItem(CollectionItem): + """a book on a list""" + + book: str + notes: str = None + type: str = "SuggestionListItem" + + @dataclass(init=False) class ShelfItem(CollectionItem): """a book on a list""" diff --git a/bookwyrm/activitypub/person.py b/bookwyrm/activitypub/person.py index dfec92e4cf..e661a009d7 100644 --- a/bookwyrm/activitypub/person.py +++ b/bookwyrm/activitypub/person.py @@ -1,4 +1,5 @@ -""" actor serializer """ +"""actor serializer""" + from dataclasses import dataclass from typing import Dict @@ -6,7 +7,6 @@ from .image import Image -# pylint: disable=invalid-name @dataclass(init=False) class PublicKey(ActivityObject): """public key block""" @@ -21,7 +21,6 @@ def serialize(self, **kwargs): return super().serialize(omit=omit) -# pylint: disable=invalid-name @dataclass(init=False) class Person(ActivityObject): """actor activitypub json""" diff --git a/bookwyrm/activitypub/response.py b/bookwyrm/activitypub/response.py index e480b85dfa..dcc6767f48 100644 --- a/bookwyrm/activitypub/response.py +++ b/bookwyrm/activitypub/response.py @@ -1,4 +1,5 @@ -""" ActivityPub-specific json response wrapper """ +"""ActivityPub-specific json response wrapper""" + from django.http import JsonResponse from .base_activity import ActivityEncoder @@ -18,9 +19,8 @@ def __init__( encoder=ActivityEncoder, safe=False, json_dumps_params=None, - **kwargs + **kwargs, ): - if "content_type" not in kwargs: kwargs["content_type"] = "application/activity+json" diff --git a/bookwyrm/activitypub/series.py b/bookwyrm/activitypub/series.py new file mode 100644 index 0000000000..2037a46afc --- /dev/null +++ b/bookwyrm/activitypub/series.py @@ -0,0 +1,27 @@ +"""book series""" + +from dataclasses import dataclass, field + +from .ordered_collection import OrderedCollection, CollectionItem +from .book import BookData + + +@dataclass(init=False) +class Series(BookData, OrderedCollection): + """serializes a book series""" + + actor: str + name: str + alternativeNames: list[str] = field(default_factory=list) + type: str = "Series" + + +@dataclass(init=False) +class SeriesBook(CollectionItem): + """a book in a series""" + + actor: str + book: str + series: str + seriesNumber: int = None + type: str = "SeriesBook" diff --git a/bookwyrm/activitypub/verbs.py b/bookwyrm/activitypub/verbs.py index 549f14c9c0..6f74bfa957 100644 --- a/bookwyrm/activitypub/verbs.py +++ b/bookwyrm/activitypub/verbs.py @@ -1,9 +1,15 @@ -""" activities that do things """ +"""activities that do things""" + from dataclasses import dataclass, field from typing import List from django.apps import apps -from .base_activity import ActivityObject, Signature, resolve_remote_id +from .base_activity import ( + ActivityObject, + ActivitySerializerError, + Signature, + resolve_remote_id, +) from .ordered_collection import CollectionItem @@ -264,3 +270,47 @@ def action(self, allow_external_connections=True): else: # we might do something with this to move other objects at some point pass + + +@dataclass(init=False) +class Flag(Verb): + """Report a user to their home server""" + + to: str = None + object: List[str] = None + links: List[str] = None + type: str = "Flag" + content: str = None + + def action(self, allow_external_connections=True): + """Create the report and attach reported statuses""" + report = self.to_model() + # go through "objects" and figure out what they are + for obj in self.object: + # what type of obj is it? + try: + item = resolve_remote_id( + remote_id=obj, + save=False, + model="Status", + ) + except ActivitySerializerError: + try: + # Mastodon includes the user in the object + item = resolve_remote_id( + remote_id=obj, + save=False, + model="User", + ) + except ActivitySerializerError: + # ¯\_(ツ)_/¯ + continue + + # fix incoming Mastodon objects + if hasattr(item, "username"): + if not report.reported_user: + report.reported_user = item + continue + + report.statuses.add(item) + report.save() diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py index 08fb757d5a..cc8b8d39bd 100644 --- a/bookwyrm/activitystreams.py +++ b/bookwyrm/activitystreams.py @@ -1,4 +1,5 @@ -""" access the activity streams stored in redis """ +"""access the activity streams stored in redis""" + from datetime import timedelta from django.dispatch import receiver from django.db import transaction @@ -106,7 +107,7 @@ def populate_streams(self, user): self.populate_store(self.stream_id(user.id)) @tracer.start_as_current_span("ActivityStream._get_audience") - def _get_audience(self, status): # pylint: disable=no-self-use + def _get_audience(self, status, exclude_self=False): """given a status, what users should see it, excluding the author""" trace.get_current_span().set_attribute("status_type", status.status_type) trace.get_current_span().set_attribute("status_privacy", status.privacy) @@ -120,12 +121,36 @@ def _get_audience(self, status): # pylint: disable=no-self-use # everybody who could plausibly see this status audience = models.User.objects.filter( - is_active=True, local=True, # we only create feeds for users of this instance + is_active=True, ).exclude( Q(id__in=status.user.blocks.all()) | Q(blocks=status.user) # not blocked ) + if exclude_self: + audience = audience.exclude(id=status.user.id) + + thread_books = models.Status.objects.filter( + # load all the statuses in this thread with an associated book + Q(id=status.id) | Q(thread_id=status.thread_id) + ).values_list( + "comment__book__parent_work", + "review__book__parent_work", + "quotation__book__parent_work", + "mention_books__parent_work", + ) + # flatten the list of sets into a single set of only non-None values + thread_book_ids = set(j for i in thread_books for j in i if j) + if thread_book_ids: + # collect a list of all users that block any of these books + users_blocking = list( + models.User.objects.filter( + blocked_books__in=thread_book_ids + ).values_list("id", flat=True) + ) + + audience = audience.exclude(id__in=users_blocking) + # only visible to the poster and mentioned users if status.privacy == "direct": audience = audience.filter( @@ -149,20 +174,24 @@ def _get_audience(self, status): # pylint: disable=no-self-use return audience.distinct("id") @tracer.start_as_current_span("ActivityStream.get_audience") - def get_audience(self, status): + def get_audience(self, status, exclude_self=False): """given a status, what users should see it""" trace.get_current_span().set_attribute("stream_id", self.key) - audience = self._get_audience(status).values_list("id", flat=True) + audience = self._get_audience(status, exclude_self=exclude_self).values_list( + "id", flat=True + ) status_author = models.User.objects.filter( - is_active=True, local=True, id=status.user.id + local=True, is_active=True, id=status.user.id ).values_list("id", flat=True) + if exclude_self: + return list(set(audience)) return list(set(audience) | set(status_author)) def get_stores_for_users(self, user_ids): """convert a list of user ids into redis store ids""" return [self.stream_id(user_id) for user_id in user_ids] - def get_statuses_for_user(self, user): # pylint: disable=no-self-use + def get_statuses_for_user(self, user): """given a user, what statuses should they see on this stream""" return models.Status.privacy_filter( user, @@ -173,6 +202,68 @@ def get_objects_for_store(self, store): user = models.User.objects.get(id=store.split("-")[0]) return self.get_statuses_for_user(user) + def add_book_statuses(self, user, book): + """add statuses about a book to a user's feed""" + work = book.parent_work + + statuses = models.Status.privacy_filter( + user, + privacy_levels=["public"], + ).exclude(user=user.id) + + book_comments = statuses.filter(Q(comment__book__parent_work=work)) + book_quotations = statuses.filter(Q(quotation__book__parent_work=work)) + book_reviews = statuses.filter(Q(review__book__parent_work=work)) + book_mentions = statuses.filter(Q(mention_books__parent_work=work)) + book_statuses = book_comments.union( + book_quotations, book_reviews, book_mentions + ) + + self.bulk_add_objects_to_store(book_statuses, self.stream_id(user.id)) + + # Evaluate the union once instead of embedding it as a subquery in the + # two lookups below: reusing the union queryset inside ``id__in`` / + # ``thread_id__in`` makes Postgres re-run the whole UNION each time, + # which is what makes the book-status tasks slow. + book_status_rows = list(book_statuses.values_list("id", "thread_id")) + book_status_ids, threads = ( + zip(*book_status_rows) if book_status_rows else ((), ()) + ) + thread_statuses = statuses.exclude(id__in=book_status_ids).filter( + thread_id__in=threads + ) + + self.bulk_add_objects_to_store(thread_statuses, self.stream_id(user.id)) + + def remove_book_statuses(self, user, book): + """remove statuses about a book from a user's feed""" + work = book.parent_work + statuses = models.Status.privacy_filter( + user, + privacy_levels=["public"], + ) + + book_comments = statuses.filter(Q(comment__book__parent_work=work)) + book_quotations = statuses.filter(Q(quotation__book__parent_work=work)) + book_reviews = statuses.filter(Q(review__book__parent_work=work)) + book_mentions = statuses.filter(Q(mention_books__parent_work=work)) + book_statuses = book_comments.union( + book_quotations, book_reviews, book_mentions + ) + + self.bulk_remove_objects_from_store(book_statuses, self.stream_id(user.id)) + + # Evaluate the union once; see add_book_statuses for the rationale. + book_status_rows = list(book_statuses.values_list("id", "thread_id")) + book_status_ids, threads = ( + zip(*book_status_rows) if book_status_rows else ((), ()) + ) + thread_statuses = statuses.exclude(id__in=book_status_ids).filter( + thread_id__in=threads + ) + + self.bulk_remove_objects_from_store(thread_statuses, self.stream_id(user.id)) + class HomeStream(ActivityStream): """users you follow""" @@ -187,7 +278,7 @@ def get_audience(self, status): audience = audience.filter(following=status.user).values_list("id", flat=True) # if the user is the post's author status_author = models.User.objects.filter( - is_active=True, local=True, id=status.user.id + local=True, is_active=True, id=status.user.id ).values_list("id", flat=True) return list(set(audience) | set(status_author)) @@ -203,24 +294,65 @@ def get_statuses_for_user(self, user): ), ) + def add_book_statuses(self, user, book): + """add statuses about a book to a user's feed""" + work = book.parent_work + + statuses = models.Status.privacy_filter( + user, + privacy_levels=["public", "unlisted", "followers"], + ).exclude( + ~Q( # remove everything except + Q(user__followers=user) # user following + | Q(user=user) # is self + | Q(mention_users=user) # mentions user + ), + ) + + book_comments = statuses.filter(Q(comment__book__parent_work=work)) + book_quotations = statuses.filter(Q(quotation__book__parent_work=work)) + book_reviews = statuses.filter(Q(review__book__parent_work=work)) + book_mentions = statuses.filter(Q(mention_books__parent_work=work)) + + book_statuses = book_comments.union( + book_quotations, book_reviews, book_mentions + ) + + self.bulk_add_objects_to_store(book_statuses, self.stream_id(user.id)) + + # Evaluate the union once; see add_book_statuses for the rationale. + book_status_rows = list(book_statuses.values_list("id", "thread_id")) + book_status_ids, threads = ( + zip(*book_status_rows) if book_status_rows else ((), ()) + ) + thread_statuses = statuses.exclude(id__in=book_status_ids).filter( + thread_id__in=threads + ) + + self.bulk_add_objects_to_store(thread_statuses, self.stream_id(user.id)) + class LocalStream(ActivityStream): - """users you follow""" + """Posts from local users""" key = "local" - def get_audience(self, status): + def get_audience(self, status, exclude_self=True): # this stream wants no part in non-public statuses if status.privacy != "public" or not status.user.local: return [] - return super().get_audience(status) + return super().get_audience(status, exclude_self=exclude_self) def get_statuses_for_user(self, user): # all public statuses by a local user - return models.Status.privacy_filter( - user, - privacy_levels=["public"], - ).filter(user__local=True) + return ( + models.Status.privacy_filter( + user, + privacy_levels=["public"], + ) + .filter(user__local=True) + .exclude(user=user.id) + ) class BooksStream(ActivityStream): @@ -228,18 +360,18 @@ class BooksStream(ActivityStream): key = "books" - def _get_audience(self, status): - """anyone with the mentioned book on their shelves""" + def _get_audience(self, status, exclude_self=True): + """anyone with the mentioned book on their shelves except the poster""" work = ( status.book.parent_work if hasattr(status, "book") else status.mention_books.first().parent_work ) - audience = super()._get_audience(status) + audience = super()._get_audience(status, exclude_self=exclude_self) return audience.filter(shelfbook__book__parent_work=work) - def get_audience(self, status): + def get_audience(self, status, exclude_self=True): # only show public statuses on the books feed, # and only statuses that mention books if status.privacy != "public" or not ( @@ -247,7 +379,7 @@ def get_audience(self, status): ): return [] - return super().get_audience(status) + return super().get_audience(status, exclude_self=exclude_self) def get_statuses_for_user(self, user): """any public status that mentions the user's books""" @@ -265,45 +397,10 @@ def get_statuses_for_user(self, user): | Q(review__book__parent_work__id__in=books) | Q(mention_books__parent_work__id__in=books) ) + .exclude(user=user.id) # ignore your own statuses .distinct() ) - def add_book_statuses(self, user, book): - """add statuses about a book to a user's feed""" - work = book.parent_work - statuses = models.Status.privacy_filter( - user, - privacy_levels=["public"], - ) - - book_comments = statuses.filter(Q(comment__book__parent_work=work)) - book_quotations = statuses.filter(Q(quotation__book__parent_work=work)) - book_reviews = statuses.filter(Q(review__book__parent_work=work)) - book_mentions = statuses.filter(Q(mention_books__parent_work=work)) - - self.bulk_add_objects_to_store(book_comments, self.stream_id(user.id)) - self.bulk_add_objects_to_store(book_quotations, self.stream_id(user.id)) - self.bulk_add_objects_to_store(book_reviews, self.stream_id(user.id)) - self.bulk_add_objects_to_store(book_mentions, self.stream_id(user.id)) - - def remove_book_statuses(self, user, book): - """add statuses about a book to a user's feed""" - work = book.parent_work - statuses = models.Status.privacy_filter( - user, - privacy_levels=["public"], - ) - - book_comments = statuses.filter(Q(comment__book__parent_work=work)) - book_quotations = statuses.filter(Q(quotation__book__parent_work=work)) - book_reviews = statuses.filter(Q(review__book__parent_work=work)) - book_mentions = statuses.filter(Q(mention_books__parent_work=work)) - - self.bulk_remove_objects_from_store(book_comments, self.stream_id(user.id)) - self.bulk_remove_objects_from_store(book_quotations, self.stream_id(user.id)) - self.bulk_remove_objects_from_store(book_reviews, self.stream_id(user.id)) - self.bulk_remove_objects_from_store(book_mentions, self.stream_id(user.id)) - # determine which streams are enabled in settings.py streams = { @@ -314,7 +411,6 @@ def remove_book_statuses(self, user, book): @receiver(signals.post_save) -# pylint: disable=unused-argument def add_status_on_create(sender, instance, created, *args, **kwargs): """add newly created statuses to activity feeds""" # we're only interested in new statuses @@ -365,7 +461,6 @@ def add_status_on_create_command(sender, instance, created): @receiver(signals.post_delete, sender=models.Boost) -# pylint: disable=unused-argument def remove_boost_on_delete(sender, instance, *args, **kwargs): """boosts are deleted""" # remove the boost @@ -375,7 +470,6 @@ def remove_boost_on_delete(sender, instance, *args, **kwargs): @receiver(signals.post_save, sender=models.UserFollows) -# pylint: disable=unused-argument def add_statuses_on_follow(sender, instance, created, *args, **kwargs): """add a newly followed user's statuses to feeds""" if not created or not instance.user_subject.local: @@ -386,7 +480,6 @@ def add_statuses_on_follow(sender, instance, created, *args, **kwargs): @receiver(signals.post_delete, sender=models.UserFollows) -# pylint: disable=unused-argument def remove_statuses_on_unfollow(sender, instance, *args, **kwargs): """remove statuses from a feed on unfollow""" if not instance.user_subject.local: @@ -397,7 +490,6 @@ def remove_statuses_on_unfollow(sender, instance, *args, **kwargs): @receiver(signals.post_save, sender=models.UserBlocks) -# pylint: disable=unused-argument def remove_statuses_on_block(sender, instance, *args, **kwargs): """remove statuses from all feeds on block""" # blocks apply ot all feeds @@ -414,7 +506,6 @@ def remove_statuses_on_block(sender, instance, *args, **kwargs): @receiver(signals.post_delete, sender=models.UserBlocks) -# pylint: disable=unused-argument def add_statuses_on_unblock(sender, instance, *args, **kwargs): """add statuses back to all feeds on unblock""" # make sure there isn't a block in the other direction @@ -444,7 +535,6 @@ def add_statuses_on_unblock(sender, instance, *args, **kwargs): @receiver(signals.post_save, sender=models.User) -# pylint: disable=unused-argument def populate_streams_on_account_create(sender, instance, created, *args, **kwargs): """build a user's feeds when they join""" if not created or not instance.local: @@ -461,7 +551,6 @@ def populate_streams_on_account_create_command(instance_id): @receiver(signals.pre_save, sender=models.ShelfBook) -# pylint: disable=unused-argument def add_statuses_on_shelve(sender, instance, *args, **kwargs): """update books stream when user shelves a book""" if not instance.user.local: @@ -477,7 +566,6 @@ def add_statuses_on_shelve(sender, instance, *args, **kwargs): @receiver(signals.post_delete, sender=models.ShelfBook) -# pylint: disable=unused-argument def remove_statuses_on_unshelve(sender, instance, *args, **kwargs): """update books stream when user unshelves a book""" if not instance.user.local: @@ -506,10 +594,30 @@ def add_book_statuses_task(user_id, book_id): @app.task(queue=STREAMS) def remove_book_statuses_task(user_id, book_id): - """remove statuses about a book from a user's books feed""" + """remove statuses about a book from a user's feeds""" + user = models.User.objects.get(id=user_id) + book = models.Edition.objects.get(id=book_id) + BooksStream().remove_book_statuses(user, book) + + +@app.task(queue=STREAMS) +def add_blocked_book_statuses_task(user_id, book_id): + """add statuses related to a formerly blocked book""" + user = models.User.objects.get(id=user_id) + book = models.Edition.objects.get(id=book_id) + BooksStream().add_book_statuses(user, book) + LocalStream().add_book_statuses(user, book) + HomeStream().add_book_statuses(user, book) + + +@app.task(queue=STREAMS) +def remove_blocked_book_statuses_task(user_id, book_id): + """remove statuses about a book from a user's feeds""" user = models.User.objects.get(id=user_id) book = models.Edition.objects.get(id=book_id) BooksStream().remove_book_statuses(user, book) + LocalStream().remove_book_statuses(user, book) + HomeStream().remove_book_statuses(user, book) @app.task(queue=STREAMS) diff --git a/bookwyrm/admin.py b/bookwyrm/admin.py index f028dea083..0611060280 100644 --- a/bookwyrm/admin.py +++ b/bookwyrm/admin.py @@ -1,4 +1,5 @@ -""" models that will show up in django admin for superuser """ +"""models that will show up in django admin for superuser""" + from django.contrib import admin from bookwyrm import models diff --git a/bookwyrm/apps.py b/bookwyrm/apps.py index d5384bb7b5..7667220b51 100644 --- a/bookwyrm/apps.py +++ b/bookwyrm/apps.py @@ -23,7 +23,7 @@ def download_file(url, destination): logger.error("Failed to download file %s: %s", url, err) except OSError as err: logger.error("Couldn't open font file %s for writing: %s", destination, err) - except Exception as err: # pylint:disable=broad-except + except Exception as err: logger.error("Unknown error in file download: %s", err) @@ -36,7 +36,6 @@ class BookwyrmConfig(AppConfig): def ready(self): """set up OTLP and preview image files, if desired""" if settings.OTEL_EXPORTER_OTLP_ENDPOINT or settings.OTEL_EXPORTER_CONSOLE: - # pylint: disable=import-outside-toplevel from bookwyrm.telemetry import open_telemetry open_telemetry.instrumentDjango() diff --git a/bookwyrm/book_search.py b/bookwyrm/book_search.py index 106c68a83a..1f3177591c 100644 --- a/bookwyrm/book_search.py +++ b/bookwyrm/book_search.py @@ -1,4 +1,5 @@ -""" using a bookwyrm instance as a source of book data """ +"""using a bookwyrm instance as a source of book data""" + from __future__ import annotations from dataclasses import asdict, dataclass from functools import reduce @@ -21,8 +22,7 @@ def search( min_confidence: float = 0, filters: Optional[list[Any]] = None, return_first: Literal[False], -) -> QuerySet[models.Edition]: - ... +) -> QuerySet[models.Edition]: ... @overload @@ -32,8 +32,7 @@ def search( min_confidence: float = 0, filters: Optional[list[Any]] = None, return_first: Literal[True], -) -> Optional[models.Edition]: - ... +) -> Optional[models.Edition]: ... def search( @@ -53,7 +52,7 @@ def search( results = None # first, try searching unique identifiers # unique identifiers never have spaces, title/author usually do - if not " " in query: + if " " not in query: results = search_identifiers( query, *filters, return_first=return_first, books=books ) @@ -115,18 +114,30 @@ def search_identifiers( # Oh did you think the 'S' in ISBN stood for 'standard'? normalized_isbn = query.strip().upper().rjust(10, "0") query = normalized_isbn - # pylint: disable=W0212 - or_filters = [ + + edition_deduplication_fields = [ {f.name: query} for f in models.Edition._meta.get_fields() if hasattr(f, "deduplication_field") and f.deduplication_field ] - results = books.filter( - *filters, reduce(operator.or_, (Q(**f) for f in or_filters)) - ).distinct() - if return_first: - return results.first() + results = None + # We assume that identifier hits only one field and we care only first hit + # searching each field seprately makes overall search littlebit slower in case all fields needs to be checked, but each + # query is really small for db load. + # we iterate reverse order so Edition specific fields are first + # like isbn10, isbn13 and oclc number + for f in edition_deduplication_fields[::-1]: + field_results = books.filter(*filters, Q(**f)) + if field_results.exists() is False: + continue + if return_first: + return field_results.first() + if results is None: + results = field_results + else: + results |= field_results + return results @@ -179,7 +190,6 @@ class SearchResult: confidence: float = 1.0 def __repr__(self): - # pylint: disable=consider-using-f-string return "".format( self.key, self.title, self.author, self.confidence ) diff --git a/bookwyrm/connectors/__init__.py b/bookwyrm/connectors/__init__.py index ada2a78261..26f86c14d3 100644 --- a/bookwyrm/connectors/__init__.py +++ b/bookwyrm/connectors/__init__.py @@ -1,6 +1,10 @@ -""" bring connectors into the namespace """ -from .settings import CONNECTORS -from .abstract_connector import ConnectorException -from .abstract_connector import get_data, get_image, maybe_isbn +"""bring connectors into the namespace""" -from .connector_manager import search, first_search_result, create_finna_connector +from .abstract_connector import ConnectorException, get_data, get_image, maybe_isbn +from .connector_manager import ( + create_finna_connector, + create_libris_connector, + first_search_result, + search, +) +from .settings import CONNECTORS diff --git a/bookwyrm/connectors/abstract_connector.py b/bookwyrm/connectors/abstract_connector.py index 178601f2ff..1451c91e0e 100644 --- a/bookwyrm/connectors/abstract_connector.py +++ b/bookwyrm/connectors/abstract_connector.py @@ -1,4 +1,5 @@ -""" functionality outline for a book data connector """ +"""functionality outline for a book data connector""" + from __future__ import annotations from abc import ABC, abstractmethod from typing import Optional, TypedDict, Any, Callable, Union, Iterator @@ -12,11 +13,15 @@ from requests.exceptions import RequestException import aiohttp +from django.contrib.postgres.search import SearchRank, SearchVector from django.core.files.base import ContentFile +from django.utils import timezone from django.db import transaction +from django.db.models import Subquery from bookwyrm import activitypub, models, settings -from bookwyrm.settings import USER_AGENT +from bookwyrm.settings import USER_AGENT, INSTANCE_ACTOR_USERNAME +from bookwyrm.tasks import app, CONNECTORS from .connector_manager import load_more_data, ConnectorException, raise_not_valid_url from .format_mappings import format_mappings from ..book_search import SearchResult @@ -78,7 +83,7 @@ async def get_results( query: str, ) -> Optional[ConnectorResults]: """try this specific connector""" - # pylint: disable=line-too-long + headers = { "Accept": ( 'application/json, application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8' @@ -89,15 +94,24 @@ async def get_results( try: async with session.get(url, headers=headers, params=params) as response: if not response.ok: + update_connector_status.delay( + self.connector.pk, + f"Unable to connect to {url}: {response.reason}", + ) logger.info("Unable to connect to %s: %s", url, response.reason) return None try: raw_data = await response.json() except aiohttp.client_exceptions.ContentTypeError as err: + update_connector_status.delay( + self.connector.pk, f"ContentType Error: {str(err)}" + ) logger.exception(err) + return None + update_connector_status.delay(self.connector.id) return ConnectorResults( connector=self, results=self.process_search_response( @@ -105,11 +119,89 @@ async def get_results( ), ) except asyncio.TimeoutError: + update_connector_status.delay( + self.connector.pk, f"Timed out for url: {url}" + ) logger.info("Connection timed out for url: %s", url) except aiohttp.ClientError as err: + update_connector_status.delay( + self.connector.pk, f"Client Error: {str(err)}" + ) logger.info(err) return None + def get_or_create_seriesbook_from_data( # pylint: disable=no-self-use + self, + work: models.Work, + edition: models.Edition, + ) -> None: + """series may be a a string or an obj""" + user = models.User.objects.get(localname=INSTANCE_ACTOR_USERNAME) + series_to_process = [] + authors = work.authors.all().union(edition.authors.all()) + + # Inventaire series will be a list of activity strings + if hasattr(work, "series") and isinstance(work.series, list): + if len(work.series) > 0: + for data in work.series: + series_data = models.Series(**data) # type: ignore + series_to_process.append(series_data) + + else: + # otherwise it's just a a name + name = work.series or edition.series + if not name or name == "": + return + series_to_process.append(models.Series(name=name)) # type: ignore + work.series_number = work.series_number or edition.series_number + + for series in series_to_process: + instance = None + + vector = SearchVector("name", weight="A") + SearchVector( + "alternative_names", weight="B" + ) + possible_series = ( + models.Series.objects.annotate(search=vector) + .annotate(rank=SearchRank(vector, series.name, normalization=32)) + .filter( + rank__gt=0.19 + ) # short alias names like XY get rank around 0.1956 + .order_by("-rank")[:5] + ) + + if possible_series.exists(): + books = models.Book.objects.filter( + authors__in=Subquery(authors.values("pk")) + ) + + if same_author_sb := models.SeriesBook.objects.filter( + book__in=books + ).filter(series__in=Subquery(possible_series.values("pk"))): + # there is already a series with a seriesbook by a matching author + # let's feel lucky + instance = same_author_sb.first().series # type: ignore + + else: + # leave it for the user to work out + if work.series: + edition.series = series.name + edition.series_number = work.series_number + edition.save() + + continue + + edition.series = None + edition.series_number = None + edition.save() + + activitydata_to_seriesbook( + user=user, + work=work, + new=series, + instance=instance, # type: ignore + ) + @abstractmethod def get_or_create_book(self, remote_id: str) -> Optional[models.Book]: """pull up a book record by whatever means possible""" @@ -185,12 +277,15 @@ def get_or_create_book(self, remote_id: str) -> Optional[models.Book]: work.authors.add(author) edition = self.create_edition_from_data(work, edition_data) + + self.get_or_create_seriesbook_from_data(work, edition) + load_more_data.delay(self.connector.id, work.id) return edition - def get_book_data(self, remote_id: str) -> JsonDict: # pylint: disable=no-self-use + def get_book_data(self, remote_id: str) -> JsonDict: """this allows connectors to override the default behavior""" - return get_data(remote_id) + return get_data(remote_id, is_activitypub=False) def create_edition_from_data( self, @@ -309,8 +404,13 @@ def get_data( url: str, params: Optional[dict[str, str]] = None, timeout: int = settings.QUERY_TIMEOUT, + is_activitypub: bool = True, ) -> JsonDict: """wrapper for request.get""" + # make sure this isn't a forbidden federated request + if is_activitypub: + models.SiteSettings.raise_federation_disabled() + # check if the url is blocked raise_not_valid_url(url) @@ -318,7 +418,7 @@ def get_data( resp = requests.get( url, params=params, - headers={ # pylint: disable=line-too-long + headers={ "Accept": ( 'application/json, application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8' ), @@ -400,7 +500,7 @@ def get_value(self, data: JsonDict) -> Optional[Any]: return None try: return self.formatter(value) - except: # pylint: disable=bare-except + except: return None @@ -437,3 +537,59 @@ def maybe_isbn(query: str) -> bool: 10, 13, ] # ISBN10 or ISBN13, or maybe ISBN10 missing a leading zero + + +def activitydata_to_seriesbook( + user: models.User, + work: models.Work, + new: models.Series, + instance: Optional[models.Series], +) -> None: + """make a series & seriesbook from incoming data""" + + if instance: + for field in [ + "inventaire_id", + "librarything_key", + "goodreads_key", + "wikidata", + "isfdb", + "name", + ]: + if not getattr(instance, field) and getattr(new, field): + setattr(instance, field, getattr(new, field)) + + for name in new.alternative_names: + if name not in instance.alternative_names and name != instance.name: + instance.alternative_names.append(name) + series = instance + series.save() + else: + new.user = user + series = new + series.save() + + # using the work.series_number for every series is safe because + # Inventaire doesn't supply series ordinal when more than one series + models.SeriesBook.objects.get_or_create( + book=work, + series=series, + defaults={"user": user, "series_number": work.series_number}, + ) + + +@app.task(queue=CONNECTORS) +def update_connector_status( + connector_id: int, error_message: str | None = None +) -> None: + try: + connector = models.Connector.objects.get(pk=connector_id) + if error_message: + connector.latest_error = error_message[:255] + connector.most_recent_error = timezone.now() + connector.save(update_fields=["latest_error", "most_recent_error"]) + else: + connector.most_recent_success = timezone.now() + connector.save(update_fields=["most_recent_success"]) + except Exception as err: + logger.exception(err) diff --git a/bookwyrm/connectors/bookwyrm_connector.py b/bookwyrm/connectors/bookwyrm_connector.py index 4064f4b4c5..4df3f12719 100644 --- a/bookwyrm/connectors/bookwyrm_connector.py +++ b/bookwyrm/connectors/bookwyrm_connector.py @@ -1,4 +1,5 @@ -""" using another bookwyrm instance as a source of book data """ +"""using another bookwyrm instance as a source of book data""" + from __future__ import annotations from typing import Any, Iterator @@ -10,8 +11,19 @@ class Connector(AbstractMinimalConnector): """this is basically just for search""" + def __init__(self, identifier: str): + models.SiteSettings.raise_federation_disabled() + super().__init__(identifier) + def get_or_create_book(self, remote_id: str) -> models.Edition: - return activitypub.resolve_remote_id(remote_id, model=models.Edition) + edition = activitypub.resolve_remote_id(remote_id, model=models.Edition) + + if edition.series: + self.get_or_create_seriesbook_from_data( + work=edition.parent_work, edition=edition + ) + + return edition def parse_search_data( self, data: list[dict[str, Any]], min_confidence: float diff --git a/bookwyrm/connectors/connector_manager.py b/bookwyrm/connectors/connector_manager.py index 9965586da2..c7132f211c 100644 --- a/bookwyrm/connectors/connector_manager.py +++ b/bookwyrm/connectors/connector_manager.py @@ -1,4 +1,5 @@ -""" interface with whatever connectors the app has """ +"""interface with whatever connectors the app has""" + from __future__ import annotations import asyncio import importlib @@ -50,15 +51,13 @@ async def async_connector_search( @overload def search( query: str, *, min_confidence: float = 0.1, return_first: Literal[False] -) -> list[abstract_connector.ConnectorResults]: - ... +) -> list[abstract_connector.ConnectorResults]: ... @overload def search( query: str, *, min_confidence: float = 0.1, return_first: Literal[True] -) -> Optional[SearchResult]: - ... +) -> Optional[SearchResult]: ... def search( @@ -111,7 +110,11 @@ def first_search_result( def get_connectors() -> Iterator[abstract_connector.AbstractConnector]: """load all connectors""" - for info in models.Connector.objects.filter(active=True).order_by("priority").all(): + queryset = models.Connector.objects.filter(active=True) + if models.SiteSettings.get().disable_federation: + queryset = queryset.exclude(connector_file="bookwyrm_connector") + + for info in queryset.order_by("priority").all(): yield load_connector(info) @@ -175,7 +178,6 @@ def load_connector( @receiver(signals.post_save, sender="bookwyrm.FederatedServer") -# pylint: disable=unused-argument def create_connector( sender: Any, instance: models.FederatedServer, @@ -191,7 +193,7 @@ def create_connector( def raise_not_valid_url(url: str) -> None: """do some basic reality checks on the url""" parsed = urlparse(url) - if not parsed.scheme in ["http", "https"]: + if parsed.scheme not in ["http", "https"]: raise ConnectorException("Invalid scheme: ", url) if not parsed.hostname: @@ -216,7 +218,7 @@ def create_finna_connector() -> None: name="Finna API", connector_file="finna", base_url="https://www.finna.fi", - books_url="https://api.finna.fi/api/v1/record" "?id=", + books_url="https://api.finna.fi/api/v1/record?id=", covers_url="https://api.finna.fi", search_url="https://api.finna.fi/api/v1/search?limit=20" "&filter[]=format%3a%220%2fBook%2f%22" @@ -229,3 +231,18 @@ def create_finna_connector() -> None: "&field[]=id&field[]=formats&field[]=images" "&lookfor=isbn:", ) + + +def create_libris_connector() -> None: + """create a Libris connector""" + + models.Connector.objects.create( + identifier="libris.kb.se", + name="Libris", + connector_file="libris", + base_url="https://libris.kb.se", + books_url="http://libris.kb.se/xsearch?format=json&format_level=full&n=1&query=", + covers_url="https://libris.kb.se", + search_url="http://libris.kb.se/xsearch?format=json&format_level=full&n=20&query=", + isbn_search_url="http://libris.kb.se/xsearch?format=json&format_level=full&n=5&query=isbn:", + ) diff --git a/bookwyrm/connectors/finna.py b/bookwyrm/connectors/finna.py index 59eedfbdcd..b54f92fd53 100644 --- a/bookwyrm/connectors/finna.py +++ b/bookwyrm/connectors/finna.py @@ -88,7 +88,8 @@ def get_book_data(self, remote_id: str) -> JsonDict: ] } data = get_data( - url=remote_id, params=request_parameters # type:ignore[arg-type] + url=remote_id, + params=request_parameters, # type:ignore[arg-type] ) extracted = data.get("records", []) try: diff --git a/bookwyrm/connectors/format_mappings.py b/bookwyrm/connectors/format_mappings.py index 61f61efaad..f998a23787 100644 --- a/bookwyrm/connectors/format_mappings.py +++ b/bookwyrm/connectors/format_mappings.py @@ -1,4 +1,5 @@ -""" comparing a free text format to the standardized one """ +"""comparing a free text format to the standardized one""" + format_mappings = { "paperback": "Paperback", "soft": "Paperback", diff --git a/bookwyrm/connectors/inventaire.py b/bookwyrm/connectors/inventaire.py index 53aade98d0..71eae41867 100644 --- a/bookwyrm/connectors/inventaire.py +++ b/bookwyrm/connectors/inventaire.py @@ -1,4 +1,5 @@ -""" inventaire data connector """ +"""inventaire data connector""" + import re from typing import Any, Union, Optional, Iterator, Iterable @@ -27,6 +28,8 @@ def __init__(self, identifier: str): Mapping("title", remote_field="wdt:P1476", formatter=get_first), Mapping("title", remote_field="labels", formatter=get_language_code), Mapping("subtitle", remote_field="wdt:P1680", formatter=get_first), + Mapping("series", remote_field="wdt:P179", formatter=self.format_series), + Mapping("seriesNumber", remote_field="wdt:P1545", formatter=get_first), Mapping("inventaireId", remote_field="uri"), Mapping( "description", remote_field="sitelinks", formatter=self.get_description @@ -66,7 +69,7 @@ def get_remote_id(self, value: str) -> str: return f"{self.books_url}?action=by-uris&uris={value}" def get_book_data(self, remote_id: str) -> JsonDict: - data = get_data(remote_id) + data = get_data(remote_id, is_activitypub=False) extracted = list(data.get("entities", {}).values()) try: data = extracted[0] @@ -77,7 +80,7 @@ def get_book_data(self, remote_id: str) -> JsonDict: **data.get("claims", {}), **{ k: data.get(k) - for k in ["uri", "image", "labels", "sitelinks", "type"] + for k in ["uri", "image", "labels", "sitelinks", "type", "originalLang"] if k in data }, } @@ -129,9 +132,9 @@ def is_work_data(self, data: JsonDict) -> bool: def load_edition_data(self, work_uri: str) -> JsonDict: """get a list of editions for a work""" - # pylint: disable=line-too-long + url = f"{self.books_url}?action=reverse-claims&property=wdt:P629&value={work_uri}&sort=true" - return get_data(url) + return get_data(url, is_activitypub=False) def get_edition_from_work_data(self, data: JsonDict) -> JsonDict: work_uri = data.get("uri") @@ -230,7 +233,7 @@ def get_description(self, links: JsonDict) -> str: title = link.get("title") url = f"{self.base_url}/api/data?action=wp-extract&lang=en&title={title}" try: - data = get_data(url) + data = get_data(url, is_activitypub=False) except ConnectorException: return "" return str(data.get("extract", "")) @@ -240,6 +243,42 @@ def get_remote_id_from_model(self, obj: models.BookDataModel) -> str: remote_id_value = obj.inventaire_id return self.get_remote_id(remote_id_value) + def format_series(self, keys: Iterable[str]) -> list[dict[str, str]]: + """resolve series data into activitypub data""" + + series_list = [] + for uri in keys: + try: + series_data = self.get_book_data(self.get_remote_id(uri)) + except ConnectorException: + continue + + alternative_names = set() + series = {} + original_lang = series_data.get("originalLang") + if original_lang: + original_lang = original_lang.split("-")[0] + else: + original_lang = "en" + + for k, v in series_data["labels"].items(): + if k == original_lang: + series["name"] = v + else: + alternative_names.add(v) + + series["alternative_names"] = list(alternative_names) + series["inventaire_id"] = uri + series["wikidata"] = uri.split("wd:")[1] + if series_data.get("wdt:P6947"): + series["goodreads_key"] = series_data["wdt:P6947"][0] + if series_data.get("wdt:P1235"): + series["isfdb"] = series_data["wdt:P1235"][0] + if series_data.get("wdt:P8513"): + series["librarything_key"] = series_data["wdt:P8513"][0] + series_list.append(series) + return series_list + def get_language_code(options: JsonDict, code: str = "en") -> Any: """when there are a bunch of translation but we need a single field""" diff --git a/bookwyrm/connectors/libris.py b/bookwyrm/connectors/libris.py new file mode 100644 index 0000000000..a76f6551fe --- /dev/null +++ b/bookwyrm/connectors/libris.py @@ -0,0 +1,314 @@ +"""Libris (Swedish National Library) data connector""" + +import re +from typing import Iterator + +from bookwyrm import models +from bookwyrm.book_search import SearchResult + +from .abstract_connector import AbstractConnector, JsonDict, Mapping, get_data +from .connector_manager import ConnectorException +from .openlibrary_languages import languages + + +def get_first(value: str | list[str] | None) -> str | None: + """Extract first element if value is a list, otherwise return as-is.""" + if isinstance(value, list): + return value[0] if value else None + return value + + +def join_paragraphs(value: str | list[str] | None) -> str | None: + """join list elements with newlines, or return string as-is.""" + if not value: + return None + return value if isinstance(value, str) else "\n".join(value) + + +def extract_id_from_url(url: str | None) -> str | None: + """extract the Libris ID from a URL like http://libris.kb.se/bib/6rkftbcz44gf3pqm""" + if not url: + return None + match = re.search(r"libris\.kb\.se/bib/([a-zA-Z0-9]+)", url) + if match: + return match.group(1) + return None + + +def resolve_languages(data: str | list[str] | None) -> list[str]: + """Convert ISO language codes to language names using OpenLibrary language map""" + if not data: + return [] + + if isinstance(data, str): + data = [data] + + result = [] + for lang_code in data: + # try to find in OpenLibrary languages + lang_name = languages.get(f"/languages/{lang_code}", lang_code) + result.append(lang_name) + return result + + +def parse_isbn(isbn: str | list[str] | None, length: int) -> str | None: + """extract ISBN of specified length from ISBN field. + + args: + isbn: Raw ISBN value (string or list) + length: Expected ISBN length (10 or 13) + + """ + if not isbn: + return None + + # only allow 'X' for ISBN-10 + pattern = r"[^0-9X]" if length == 10 else r"[^0-9]" + + if isinstance(isbn, list): + for i in isbn: + cleaned = re.sub(pattern, "", str(i).upper()) + if len(cleaned) == length: + return cleaned + return None + + cleaned = re.sub(pattern, "", str(isbn).upper()) + return cleaned if len(cleaned) == length else None + + +def parse_isbn13(isbn: str | list[str] | None) -> str | None: + """extract ISBN-13 from ISBN field""" + return parse_isbn(isbn, 13) + + +def parse_isbn10(isbn: str | list[str] | None) -> str | None: + """extract ISBN-10 from ISBN field""" + return parse_isbn(isbn, 10) + + +def parse_publishers(publisher: str | list[str] | None) -> list[str]: + """parse publisher field, handling 'City : Publisher' format""" + if not publisher: + return [] + + if isinstance(publisher, str): + publisher = [publisher] + + result = [] + for pub in publisher: + if " : " in pub: + result.append(pub.split(" : ", 1)[1].strip()) + else: + result.append(pub.strip()) + return result + + +def parse_author_name(author: str | None) -> str | None: + """ + parse a single author from Libris format. + format is typically: "Lastname, Firstname, year-" or "Lastname, Firstname" + sometimes includes trailing dot: "Lastname, Firstname, year- ." + returns: "Firstname Lastname" or None + """ + if not author: + return None + + # remove year information (e.g., "1976-", "1900-1980", or "1947- .") + author = re.sub(r"(\d\??){1,4}(\W\-?\??){0,3}((\d\?){1,4}$)?", "", author).strip() + + # handle "Lastname, Firstname" format + if ", " in author: + parts = author.split(", ", 1) + if len(parts) == 2: + return f"{parts[1].rstrip(',')} {parts[0].rstrip(',')}" + + return author + + +def parse_authors(creator: str | list[str] | None) -> list[str]: + """parse creator field into list of author names. + + deduplicates authors since the API sometimes returns the same author + multiple times (e.g. with/without trailing dots). + """ + if not creator: + return [] + + if isinstance(creator, str): + creator = [creator] + + result = [] + for author in creator: + name = parse_author_name(author) + if name: + result.append(name) + + # deduplicate while preserving order + return list(dict.fromkeys(result)) + + +def get_first_author_name(data: str | list[str] | None) -> str | None: + """get the first author's name from creator data""" + authors = parse_authors(data) + return authors[0] if authors else None + + +def format_authors_for_display(creator: str | list[str] | None) -> str | None: + """format authors for display in search results (semicolon-separated)""" + authors = parse_authors(creator) + if not authors: + return None + if len(authors) == 1: + return authors[0] + return "; ".join(authors) + + +class Connector(AbstractConnector): + """instantiate a connector for Libris""" + + generated_remote_link_field = "identifier" + + def __init__(self, identifier: str): + super().__init__(identifier) + + self.book_mappings = [ + Mapping("id", remote_field="identifier"), + Mapping( + "librisKey", remote_field="identifier", formatter=extract_id_from_url + ), + Mapping("title", remote_field="title", formatter=get_first), + Mapping("isbn13", remote_field="isbn", formatter=parse_isbn13), + Mapping("isbn10", remote_field="isbn", formatter=parse_isbn10), + Mapping("languages", remote_field="language", formatter=resolve_languages), + Mapping("authors", remote_field="creator", formatter=parse_authors), + Mapping("publishedDate", remote_field="date", formatter=get_first), + Mapping("publishers", remote_field="publisher", formatter=parse_publishers), + Mapping( + "description", remote_field="description", formatter=join_paragraphs + ), + ] + + self.author_mappings = [ + Mapping("id", remote_field="creator", formatter=self.get_remote_author_id), + Mapping("name", remote_field="creator", formatter=get_first_author_name), + ] + + def get_book_data(self, remote_id: str) -> JsonDict: + """fetch book data from Libris using the identifier URL""" + libris_id = extract_id_from_url(remote_id) + if not libris_id: + raise ConnectorException(f"Invalid Libris ID: {remote_id}") + + # query using just the libris bib ID (no prefix needed) + query_url = f"{self.search_url}{libris_id}" + data = get_data(url=query_url) + + xsearch = data.get("xsearch", {}) + records = xsearch.get("list", []) + + if not records: + raise ConnectorException(f"No book found for ID: {libris_id}") + # records is typed as Any because dict.get() on JsonDict returns Any + return records[0] # type: ignore[no-any-return] + + def get_remote_author_id(self, data: JsonDict) -> str | None: + """return search URL for author info""" + creator = data.get("creator") + if creator is None: + return None + author = get_first_author_name(creator) + if author: + return f"{self.search_url}forf:({author})" + return None + + def get_remote_id(self, data: JsonDict) -> str: + """return the identifier URL as the book ID""" + identifier = get_first(data.get("identifier")) + return identifier or "" + + def _parse_records( + self, data: JsonDict, min_confidence: float | None = None + ) -> Iterator[SearchResult]: + """Parse search results from Libris Xsearch API. + + Args: + data: Raw API response data + min_confidence: If provided, stop yielding results below this threshold + """ + xsearch = data.get("xsearch", {}) + records = xsearch.get("list", []) + + for idx, record in enumerate(records): + confidence = 1 / (idx + 1) + if min_confidence is not None and confidence < min_confidence: + break + + # SearchResult requires non-None title and key + title = get_first(record.get("title")) + key = get_first(record.get("identifier")) + if not title or not key: + continue + + yield SearchResult( + title=title, + key=key, + author=format_authors_for_display(record.get("creator")), + cover=None, # Libris doesn't provide cover images in JSON + year=get_first(record.get("date")), + view_link=get_first(record.get("identifier")), + confidence=confidence, + connector=self, + ) + + def parse_search_data( + self, data: JsonDict, min_confidence: float + ) -> Iterator[SearchResult]: + """parse search results from Libris Xsearch API""" + return self._parse_records(data, min_confidence) + + def parse_isbn_search_data(self, data: JsonDict) -> Iterator[SearchResult]: + """parse ISBN search results""" + return self._parse_records(data) + + def get_authors_from_data(self, data: JsonDict) -> Iterator[models.Author]: + """extract author information from book data. + + Libris doesn't have a dedicated author endpoint, so we create authors + directly from the book data rather than fetching from a remote URL. + + WARNING: This matches authors by name only, which will incorrectly link + different people with the same name. + """ + creator = data.get("creator") + authors = parse_authors(creator) + + for author_name in authors: + existing = models.Author.objects.filter(name=author_name).first() + if existing: + yield existing + continue + + author = models.Author.objects.create(name=author_name) + yield author + + def expand_book_data(self, book: models.Book) -> None: + """Libris doesn't have a versions/editions endpoint""" + pass + + def get_remote_id_from_model(self, obj: models.BookDataModel) -> str: + """use libris_key to construct the remote URL""" + if obj.libris_key: + return f"https://libris.kb.se/bib/{obj.libris_key}" + return "" + + def is_work_data(self, data: JsonDict) -> bool: + """Libris doesn't distinguish between works and editions""" + return True + + def get_edition_from_work_data(self, data: JsonDict) -> JsonDict: + """return the same data as the edition""" + return data + + def get_work_from_edition_data(self, data: JsonDict) -> JsonDict: + """return the same data as the work""" + return data diff --git a/bookwyrm/connectors/openlibrary.py b/bookwyrm/connectors/openlibrary.py index b6ecc4cd54..f17880c94a 100644 --- a/bookwyrm/connectors/openlibrary.py +++ b/bookwyrm/connectors/openlibrary.py @@ -1,8 +1,9 @@ -""" openlibrary data connector """ +"""openlibrary data connector""" + import re -from typing import Any, Optional, Union, Iterator, Iterable +from typing import Any, Optional, Union, Iterator, Iterable, cast -from markdown import markdown +import mistune from bookwyrm import models from bookwyrm.book_search import SearchResult @@ -103,10 +104,10 @@ def __init__(self, identifier: str): ] def get_book_data(self, remote_id: str) -> JsonDict: - data = get_data(remote_id) + data = get_data(remote_id, is_activitypub=False) if data.get("type", {}).get("key") == "/type/redirect": remote_id = self.base_url + data.get("location", "") - return get_data(remote_id) + return get_data(remote_id, is_activitypub=False) return data def get_remote_id_from_data(self, data: JsonDict) -> str: @@ -248,9 +249,11 @@ def ignore_edition(edition_data: JsonDict) -> bool: def get_description(description_blob: Union[JsonDict, str]) -> str: """descriptions can be a string or a dict""" if isinstance(description_blob, dict): - description = markdown(description_blob.get("value", "")) + description = cast( + str, mistune.html(description_blob.get("value", "")) + ).rstrip() else: - description = markdown(description_blob) + description = cast(str, mistune.html(description_blob)).rstrip() if ( description.startswith("

") diff --git a/bookwyrm/connectors/openlibrary_languages.py b/bookwyrm/connectors/openlibrary_languages.py index 2520d1ea15..3a6276e74a 100644 --- a/bookwyrm/connectors/openlibrary_languages.py +++ b/bookwyrm/connectors/openlibrary_languages.py @@ -1,4 +1,5 @@ -""" key lookups for openlibrary languages """ +"""key lookups for openlibrary languages""" + languages = { "/languages/eng": "English", "/languages/fre": "French", diff --git a/bookwyrm/connectors/settings.py b/bookwyrm/connectors/settings.py index 4ef149a21d..43f6e06389 100644 --- a/bookwyrm/connectors/settings.py +++ b/bookwyrm/connectors/settings.py @@ -1,8 +1,9 @@ -""" settings book data connectors """ +"""settings book data connectors""" CONNECTORS = [ "openlibrary", "inventaire", "bookwyrm_connector", "finna", + "libris", ] diff --git a/bookwyrm/context_processors.py b/bookwyrm/context_processors.py index bec704a2c7..0622560c74 100644 --- a/bookwyrm/context_processors.py +++ b/bookwyrm/context_processors.py @@ -1,4 +1,5 @@ -""" customize the info available in context for rendering templates """ +"""customize the info available in context for rendering templates""" + from bookwyrm import models, settings @@ -8,7 +9,7 @@ def site_settings(request): if not request.is_secure(): request_protocol = "http://" - site = models.SiteSettings.objects.get() + site = models.SiteSettings.get() theme = "css/themes/bookwyrm-light.scss" if ( hasattr(request, "user") @@ -27,5 +28,4 @@ def site_settings(request): "media_full_url": settings.MEDIA_FULL_URL, "preview_images_enabled": settings.ENABLE_PREVIEW_IMAGES, "request_protocol": request_protocol, - "js_cache": settings.JS_CACHE, } diff --git a/bookwyrm/decorators.py b/bookwyrm/decorators.py new file mode 100644 index 0000000000..ba9526bf9e --- /dev/null +++ b/bookwyrm/decorators.py @@ -0,0 +1,15 @@ +"""Custom view decorators""" + +from functools import wraps +from bookwyrm.models.site import SiteSettings + + +def require_federation(function): + """Ensure that federation is allowed before proceeding with this view""" + + @wraps(function) + def wrap(request, *args, **kwargs): + SiteSettings.raise_federation_disabled() + return function(request, *args, **kwargs) + + return wrap diff --git a/bookwyrm/emailing.py b/bookwyrm/emailing.py index ccc0aea612..2ad053ae67 100644 --- a/bookwyrm/emailing.py +++ b/bookwyrm/emailing.py @@ -1,4 +1,5 @@ -""" send emails """ +"""send emails""" + from django.core.mail import EmailMultiAlternatives from django.template.loader import get_template @@ -9,7 +10,7 @@ def email_data(): """fields every email needs""" - site = models.SiteSettings.objects.get() + site = models.SiteSettings.get() return { "site_name": site.name, "logo": site.logo_small_url, @@ -51,9 +52,11 @@ def password_reset_email(reset_code): def moderation_report_email(report): """a report was created""" data = email_data() - data["reporter"] = report.reporter.localname or report.reporter.username - if report.user: - data["reportee"] = report.user.localname or report.user.username + data["reporter"] = report.user.localname or report.user.username + if report.reported_user: + data["reportee"] = ( + report.reported_user.localname or report.reported_user.username + ) data["report_link"] = report.remote_id data["link_domain"] = report.links.exists() diff --git a/bookwyrm/forms/__init__.py b/bookwyrm/forms/__init__.py index a37d126ac9..00709a7c77 100644 --- a/bookwyrm/forms/__init__.py +++ b/bookwyrm/forms/__init__.py @@ -1,4 +1,5 @@ -""" make forms available to the app """ +"""make forms available to the app""" + # site admin from .admin import * from .author import * diff --git a/bookwyrm/forms/admin.py b/bookwyrm/forms/admin.py index 783c78d50b..bcd871eb8b 100644 --- a/bookwyrm/forms/admin.py +++ b/bookwyrm/forms/admin.py @@ -1,4 +1,5 @@ -""" using django model forms """ +"""using django model forms""" + import datetime from django import forms @@ -12,7 +13,6 @@ from .custom_form import CustomForm, StyledForm -# pylint: disable=missing-class-docstring class ExpiryWidget(widgets.Select): def value_from_datadict(self, data, files, name): """human-readable expiration time buckets""" @@ -177,6 +177,32 @@ class Meta: exclude = ["remote_id"] +class FederationSettings(CustomForm): + class Meta: + model = models.SiteSettings + fields = [ + "disable_federation", + "require_signed_get", + "require_login_nearly_everywhere", + "block_incoming_search", + ] + + widgets = { + "disable_federation": forms.CheckboxInput( + attrs={"aria-describedby": "desc_disable_federation"} + ), + "require_signed_get": forms.CheckboxInput( + attrs={"aria-describedby": "desc_require_signed_get"} + ), + "require_login_nearly_everywhere": forms.CheckboxInput( + attrs={"aria-describedby": "desc_require_login_everywhere"} + ), + "block_incoming_search": forms.CheckboxInput( + attrs={"aria-describedby": "desc_block_incoming_search"} + ), + } + + class AutoModRuleForm(CustomForm): class Meta: model = models.AutoMod @@ -193,7 +219,6 @@ class Meta: "period": forms.Select(attrs={"aria-describedby": "desc_period"}), } - # pylint: disable=arguments-differ def save(self, request, *args, **kwargs): """This is an outside model so the perms check works differently""" if not request.user.has_perm("bookwyrm.moderate_user"): @@ -202,5 +227,4 @@ def save(self, request, *args, **kwargs): class ExportFileExpiryForm(forms.Form): - hours = forms.IntegerField(min_value=1) diff --git a/bookwyrm/forms/author.py b/bookwyrm/forms/author.py index a3a759af7c..5fe6d0ed31 100644 --- a/bookwyrm/forms/author.py +++ b/bookwyrm/forms/author.py @@ -1,11 +1,12 @@ -""" using django model forms """ +"""using django model forms""" + from django import forms from bookwyrm import models from .custom_form import CustomForm +from .widgets import SelectDateWidget -# pylint: disable=missing-class-docstring class AuthorForm(CustomForm): class Meta: model = models.Author @@ -35,8 +36,8 @@ class Meta: ), "wikidata": forms.TextInput(attrs={"aria-describedby": "desc_wikidata"}), "website": forms.TextInput(attrs={"aria-describedby": "desc_website"}), - "born": forms.SelectDateWidget(attrs={"aria-describedby": "desc_born"}), - "died": forms.SelectDateWidget(attrs={"aria-describedby": "desc_died"}), + "born": SelectDateWidget(attrs={"aria-describedby": "desc_born"}), + "died": SelectDateWidget(attrs={"aria-describedby": "desc_died"}), "openlibrary_key": forms.TextInput( attrs={"aria-describedby": "desc_openlibrary_key"} ), diff --git a/bookwyrm/forms/books.py b/bookwyrm/forms/books.py index f9a110efc9..8c92ba730c 100644 --- a/bookwyrm/forms/books.py +++ b/bookwyrm/forms/books.py @@ -1,4 +1,5 @@ -""" using django model forms """ +"""using django model forms""" + from django import forms from file_resubmit.widgets import ResubmitImageWidget @@ -9,7 +10,6 @@ from .widgets import ArrayWidget, SelectDateWidget, Select -# pylint: disable=missing-class-docstring class CoverForm(CustomForm): class Meta: model = models.Book @@ -58,6 +58,7 @@ class Meta: "inventaire_id", "goodreads_key", "finna_key", + "libris_key", "oclc_number", "asin", "aasin", @@ -114,6 +115,9 @@ class Meta: attrs={"aria-describedby": "desc_oclc_number"} ), "finna_key": forms.TextInput(attrs={"aria-describedby": "desc_finna_key"}), + "libris_key": forms.TextInput( + attrs={"aria-describedby": "desc_libris_key"} + ), "ASIN": forms.TextInput(attrs={"aria-describedby": "desc_ASIN"}), "AASIN": forms.TextInput(attrs={"aria-describedby": "desc_AASIN"}), "isfdb": forms.TextInput(attrs={"aria-describedby": "desc_isfdb"}), @@ -143,3 +147,15 @@ class Meta: "cover", "first_published_date", ] + + +class SeriesForm(CustomForm): + class Meta: + model = models.Series + fields = [ + "name", + "alternative_names", + "inventaire_id", + "wikidata", + "isfdb", + ] diff --git a/bookwyrm/forms/custom_form.py b/bookwyrm/forms/custom_form.py index 6b425d216a..fcbe5dd9b6 100644 --- a/bookwyrm/forms/custom_form.py +++ b/bookwyrm/forms/custom_form.py @@ -1,4 +1,5 @@ -""" Overrides django's default form class """ +"""Overrides django's default form class""" + from collections import defaultdict from django.forms import ModelForm from django.forms.widgets import Textarea @@ -29,7 +30,6 @@ def __init__(self, *args, **kwargs): class CustomForm(StyledForm): """Check permissions on save""" - # pylint: disable=arguments-differ def save(self, request, *args, **kwargs): """Save and check perms""" self.instance.raise_not_editable(request.user) diff --git a/bookwyrm/forms/edit_user.py b/bookwyrm/forms/edit_user.py index 13ac285d82..8a6b7224de 100644 --- a/bookwyrm/forms/edit_user.py +++ b/bookwyrm/forms/edit_user.py @@ -1,4 +1,5 @@ -""" using django model forms """ +"""using django model forms""" + from django import forms from django.contrib.auth.password_validation import validate_password from django.core.exceptions import ValidationError @@ -8,8 +9,22 @@ from bookwyrm.models.fields import ClearableFileInputWithWarning from .custom_form import CustomForm -# pylint: disable=missing-class-docstring + class EditUserForm(CustomForm): + readwise_api_key = forms.CharField( + label=_("Readwise access token"), + required=False, + strip=True, + widget=forms.PasswordInput( + attrs={"aria-describedby": "desc_readwise_api_key"}, + render_value=False, + ), + ) + clear_readwise_api_key = forms.BooleanField( + label=_("Remove saved Readwise access token"), + required=False, + ) + class Meta: model = models.User fields = [ @@ -21,6 +36,7 @@ class Meta: "show_ratings", "show_suggested_users", "manually_approves_followers", + "is_profile_private", "default_post_privacy", "discoverable", "hide_follows", @@ -41,6 +57,27 @@ class Meta: ), } + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.instance and self.instance.readwise_api_key: + self.fields["readwise_api_key"].help_text = _( + "A Readwise access token is saved. Leave this blank to keep it." + ) + + def save(self, request, *args, **kwargs): + commit = kwargs.get("commit", True) + user = super().save(request, *args, **kwargs) + changed = False + if self.cleaned_data.get("clear_readwise_api_key"): + user.readwise_api_key = "" + changed = True + elif token := self.cleaned_data.get("readwise_api_key"): + user.readwise_api_key = token + changed = True + if changed and commit: + user.save(update_fields=["readwise_api_key"]) + return user + class LimitedEditUserForm(CustomForm): class Meta: @@ -65,12 +102,6 @@ class Meta: } -class DeleteUserForm(CustomForm): - class Meta: - model = models.User - fields = ["password"] - - class MoveUserForm(CustomForm): target = forms.CharField(widget=forms.TextInput) diff --git a/bookwyrm/forms/forms.py b/bookwyrm/forms/forms.py index 0ecf3e3018..1474ffa2e5 100644 --- a/bookwyrm/forms/forms.py +++ b/bookwyrm/forms/forms.py @@ -1,4 +1,5 @@ -""" using django model forms """ +"""using django model forms""" + import datetime from django import forms from django.forms import widgets @@ -9,7 +10,7 @@ from bookwyrm.models.fields import ClearableFileInputWithWarning from .custom_form import CustomForm -# pylint: disable=missing-class-docstring + class FeedStatusTypesForm(CustomForm): class Meta: model = models.User @@ -45,7 +46,14 @@ class Meta: class ReportForm(CustomForm): class Meta: model = models.Report - fields = ["user", "reporter", "status", "links", "note"] + fields = [ + "reported_user", + "user", + "statuses", + "links", + "note", + "allow_broadcast", + ] class ReadThroughForm(CustomForm): diff --git a/bookwyrm/forms/groups.py b/bookwyrm/forms/groups.py index 90aace3baf..8d3efdc844 100644 --- a/bookwyrm/forms/groups.py +++ b/bookwyrm/forms/groups.py @@ -1,9 +1,9 @@ -""" using django model forms """ +"""using django model forms""" + from bookwyrm import models from .custom_form import CustomForm -# pylint: disable=missing-class-docstring class GroupForm(CustomForm): class Meta: model = models.Group diff --git a/bookwyrm/forms/landing.py b/bookwyrm/forms/landing.py index 4f5b3223f8..b2158ba6e2 100644 --- a/bookwyrm/forms/landing.py +++ b/bookwyrm/forms/landing.py @@ -1,4 +1,5 @@ -""" Forms for the landing pages """ +"""Forms for the landing pages""" + from django import forms from django.contrib.auth.password_validation import validate_password from django.core.exceptions import ValidationError @@ -12,7 +13,6 @@ from .custom_form import CustomForm -# pylint: disable=missing-class-docstring class LoginForm(CustomForm): class Meta: model = models.User @@ -96,6 +96,24 @@ def clean(self): validate_password(new_password) except ValidationError as err: self.add_error("password", err) + return cleaned_data + + +class ForcePasswordResetForm(PasswordResetForm): + current_password = forms.CharField(widget=forms.PasswordInput) + + def clean(self): + """Make sure we aren't re-using the same password""" + cleaned_data = super().clean() + current_password = cleaned_data.get("current_password") + new_password = cleaned_data.get("password") + + if current_password == new_password: + self.add_error( + "password", + _("Password cannot be the same as your current password"), + ) + return cleaned_data class Confirm2FAForm(CustomForm): @@ -113,7 +131,6 @@ def clean_otp(self): totp = pyotp.TOTP(self.instance.otp_secret) if not totp.verify(otp, valid_window=TWO_FACTOR_LOGIN_VALIDITY_WINDOW): - if self.instance.hotp_secret: # maybe it's a backup code? hotp = pyotp.HOTP(self.instance.hotp_secret) diff --git a/bookwyrm/forms/links.py b/bookwyrm/forms/links.py index 06de9a304d..4d7583cd37 100644 --- a/bookwyrm/forms/links.py +++ b/bookwyrm/forms/links.py @@ -1,4 +1,4 @@ -""" using django model forms """ +"""using django model forms""" from urllib.parse import urlparse @@ -8,7 +8,6 @@ from .custom_form import CustomForm -# pylint: disable=missing-class-docstring class LinkDomainForm(CustomForm): class Meta: model = models.LinkDomain @@ -24,7 +23,6 @@ def clean(self): """make sure the domain isn't blocked or pending""" cleaned_data = super().clean() url = cleaned_data.get("url") - filetype = cleaned_data.get("filetype") book = cleaned_data.get("book") domain = urlparse(url).hostname if models.LinkDomain.objects.filter(domain=domain).exists(): @@ -39,16 +37,14 @@ def clean(self): ), ) return - if current_links := models.FileLink.objects.filter( - url=url, book=book, filetype=filetype - ).all(): + if current_links := models.FileLink.objects.filter(url=url, book=book).all(): for link in current_links: if link == self.instance: continue self.add_error( "url", _( - "This link with file type has already been added for this book." + "This link has already been added for this book." " If it is not visible, the domain is still pending." ), ) diff --git a/bookwyrm/forms/lists.py b/bookwyrm/forms/lists.py index f5008baa3c..b6cc3bf56a 100644 --- a/bookwyrm/forms/lists.py +++ b/bookwyrm/forms/lists.py @@ -1,4 +1,5 @@ -""" using django model forms """ +"""using django model forms""" + from django import forms from django.forms import ChoiceField from django.utils.translation import gettext_lazy as _ @@ -7,7 +8,6 @@ from .custom_form import CustomForm -# pylint: disable=missing-class-docstring class ListForm(CustomForm): class Meta: model = models.List @@ -17,7 +17,19 @@ class Meta: class ListItemForm(CustomForm): class Meta: model = models.ListItem - fields = ["user", "book", "book_list", "notes"] + fields = ["user", "edition", "book_list", "notes", "raw_notes"] + + +class SuggestionListForm(CustomForm): + class Meta: + model = models.SuggestionList + fields = ["suggests_for"] + + +class SuggestionListItemForm(CustomForm): + class Meta: + model = models.SuggestionListItem + fields = ["user", "work", "book_list", "notes", "raw_notes"] class SortListForm(forms.Form): diff --git a/bookwyrm/forms/status.py b/bookwyrm/forms/status.py index b562595eeb..cee5c96728 100644 --- a/bookwyrm/forms/status.py +++ b/bookwyrm/forms/status.py @@ -1,9 +1,9 @@ -""" using django model forms """ +"""using django model forms""" + from bookwyrm import models from .custom_form import CustomForm -# pylint: disable=missing-class-docstring class RatingForm(CustomForm): class Meta: model = models.ReviewRating diff --git a/bookwyrm/forms/user_admin.py b/bookwyrm/forms/user_admin.py index a3bf6fa8ec..f46bd41bfa 100644 --- a/bookwyrm/forms/user_admin.py +++ b/bookwyrm/forms/user_admin.py @@ -1,9 +1,9 @@ -""" using django model forms """ +"""using django model forms""" + from bookwyrm import models from .custom_form import CustomForm -# pylint: disable=missing-class-docstring class UserGroupForm(CustomForm): class Meta: model = models.User diff --git a/bookwyrm/forms/widgets.py b/bookwyrm/forms/widgets.py index 001fdbec40..97088cf826 100644 --- a/bookwyrm/forms/widgets.py +++ b/bookwyrm/forms/widgets.py @@ -1,4 +1,5 @@ -""" using django model forms """ +"""using django model forms""" + from django import forms diff --git a/bookwyrm/imagegenerators.py b/bookwyrm/imagegenerators.py index 1d065192e8..dc51683d06 100644 --- a/bookwyrm/imagegenerators.py +++ b/bookwyrm/imagegenerators.py @@ -1,4 +1,5 @@ """Generators for all the different thumbnail sizes""" + from imagekit import ImageSpec, register from imagekit.processors import ResizeToFit diff --git a/bookwyrm/importers/__init__.py b/bookwyrm/importers/__init__.py index 497eebcf69..3c8bab23f0 100644 --- a/bookwyrm/importers/__init__.py +++ b/bookwyrm/importers/__init__.py @@ -1,4 +1,4 @@ -""" import classes """ +"""import classes""" from .importer import Importer from .bookwyrm_import import BookwyrmImporter, BookwyrmBooksImporter diff --git a/bookwyrm/importers/bookwyrm_import.py b/bookwyrm/importers/bookwyrm_import.py index d4fedb4f79..031db4d220 100644 --- a/bookwyrm/importers/bookwyrm_import.py +++ b/bookwyrm/importers/bookwyrm_import.py @@ -1,4 +1,5 @@ """Import data from Bookwyrm export files""" + from django.http import QueryDict from bookwyrm.models import User @@ -11,7 +12,6 @@ class BookwyrmImporter: This is kind of a combination of an importer and a connector. """ - # pylint: disable=no-self-use def process_import( self, user: User, archive_file: bytes, settings: QueryDict ) -> BookwyrmImportJob: diff --git a/bookwyrm/importers/calibre_import.py b/bookwyrm/importers/calibre_import.py index 542175dd7a..1b02698345 100644 --- a/bookwyrm/importers/calibre_import.py +++ b/bookwyrm/importers/calibre_import.py @@ -1,4 +1,5 @@ -""" handle reading a csv from calibre """ +"""handle reading a csv from calibre""" + from typing import Any, Optional from bookwyrm.models import Shelf @@ -21,5 +22,5 @@ def __init__(self, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) def get_shelf(self, normalized_row: dict[str, Optional[str]]) -> Optional[str]: - # Calibre export does not indicate which shelf to use. Use a default one for now - return Shelf.TO_READ + # Calibre export does not indicate which shelf to use. + return super().get_shelf(normalized_row) or Shelf.TO_READ diff --git a/bookwyrm/importers/goodreads_import.py b/bookwyrm/importers/goodreads_import.py index 8b3e94cbc7..e7c5b6aaf4 100644 --- a/bookwyrm/importers/goodreads_import.py +++ b/bookwyrm/importers/goodreads_import.py @@ -1,4 +1,5 @@ -""" handle reading a csv from goodreads """ +"""handle reading a csv from goodreads""" + from typing import Optional from . import Importer diff --git a/bookwyrm/importers/importer.py b/bookwyrm/importers/importer.py index d2a11d7f21..6dd1be43d0 100644 --- a/bookwyrm/importers/importer.py +++ b/bookwyrm/importers/importer.py @@ -1,5 +1,7 @@ -""" handle reading a csv from an external service, defaults are from Goodreads """ +"""handle reading a csv from an external service, defaults are from Goodreads""" + import csv +import itertools from datetime import timedelta from typing import Iterable, Optional @@ -45,7 +47,6 @@ class Importer: "reading": ["currently-reading", "reading", "currently reading"], } - # pylint: disable=too-many-arguments def create_job( self, user: User, @@ -53,11 +54,13 @@ def create_job( include_reviews: bool, privacy: str, create_shelves: bool = True, + shelf_override: str | None = None, ) -> ImportJob: """check over a csv and creates a database entry for the job""" csv_reader = csv.DictReader(csv_file, delimiter=self.delimiter) - rows = list(csv_reader) - if len(rows) < 1: + try: + first_row = next(csv_reader) + except StopIteration: raise ValueError("CSV file is empty") mappings = ( @@ -79,10 +82,10 @@ def create_job( if enforce_limit and allowed_imports <= 0: job.complete_job() return job - for index, entry in enumerate(rows): + for index, entry in enumerate(itertools.chain([first_row], csv_reader)): if enforce_limit and index >= allowed_imports: break - self.create_item(job, index, entry) + self.create_item(job, index, entry, shelf_override) return job def update_legacy_job(self, job: ImportJob) -> None: @@ -106,7 +109,7 @@ def update_legacy_job(self, job: ImportJob) -> None: def create_row_mappings(self, headers: list[str]) -> dict[str, Optional[str]]: """guess what the headers mean""" mappings = {} - for (key, guesses) in self.row_mappings_guesses: + for key, guesses in self.row_mappings_guesses: values = [h for h in headers if h.lower() in guesses] value = values[0] if len(values) else None if value: @@ -114,10 +117,18 @@ def create_row_mappings(self, headers: list[str]) -> dict[str, Optional[str]]: mappings[key] = value return mappings - def create_item(self, job: ImportJob, index: int, data: dict[str, str]) -> None: + def create_item( + self, + job: ImportJob, + index: int, + data: dict[str, str], + shelf_override: str | None = None, + ) -> None: """creates and saves an import item""" normalized = self.normalize_row(data, job.mappings) - normalized["shelf"] = self.get_shelf(normalized) + normalized["shelf"] = ( + shelf_override if shelf_override else self.get_shelf(normalized) + ) ImportItem(job=job, index=index, data=data, normalized_data=normalized).save() def get_shelf(self, normalized_row: dict[str, Optional[str]]) -> Optional[str]: @@ -131,17 +142,15 @@ def get_shelf(self, normalized_row: dict[str, Optional[str]]) -> Optional[str]: ] return shelf[0] if shelf else normalized_row.get("shelf") or None - # pylint: disable=no-self-use def normalize_row( self, entry: dict[str, str], mappings: dict[str, Optional[str]] ) -> dict[str, Optional[str]]: """use the dataclass to create the formatted row of data""" return {k: entry.get(v) if v else None for k, v in mappings.items()} - # pylint: disable=no-self-use def get_import_limit(self, user: User) -> tuple[int, int]: """check if import limit is set and return how many imports are left""" - site_settings = SiteSettings.objects.get() + site_settings = SiteSettings.get() import_size_limit = site_settings.import_size_limit import_limit_reset = site_settings.import_limit_reset enforce_limit = import_size_limit and import_limit_reset @@ -152,7 +161,7 @@ def get_import_limit(self, user: User) -> tuple[int, int]: import_jobs = ImportJob.objects.filter( user=user, created_date__gte=time_range ) - # pylint: disable=consider-using-generator + imported_books = sum([job.successful_item_count for job in import_jobs]) allowed_imports = import_size_limit - imported_books return enforce_limit, allowed_imports diff --git a/bookwyrm/importers/librarything_import.py b/bookwyrm/importers/librarything_import.py index 24a2626bf6..9220689881 100644 --- a/bookwyrm/importers/librarything_import.py +++ b/bookwyrm/importers/librarything_import.py @@ -1,4 +1,5 @@ -""" handle reading a tsv from librarything """ +"""handle reading a tsv from librarything""" + import re from typing import Optional @@ -35,4 +36,5 @@ def get_shelf(self, normalized_row: dict[str, Optional[str]]) -> Optional[str]: return Shelf.READ_FINISHED if normalized_row["date_started"]: return Shelf.READING - return Shelf.TO_READ + # no reading dates: fall back to the "shelf" column + return super().get_shelf(normalized_row) or Shelf.TO_READ diff --git a/bookwyrm/importers/openlibrary_import.py b/bookwyrm/importers/openlibrary_import.py index 6a954ed3c7..d80e8cd2aa 100644 --- a/bookwyrm/importers/openlibrary_import.py +++ b/bookwyrm/importers/openlibrary_import.py @@ -1,5 +1,4 @@ -""" handle reading a csv from openlibrary""" -from typing import Any +"""handle reading a csv from openlibrary""" from . import Importer @@ -9,7 +8,7 @@ class OpenLibraryImporter(Importer): service = "OpenLibrary" - def __init__(self, *args: Any, **kwargs: Any): - self.row_mappings_guesses.append(("openlibrary_key", ["edition id"])) - self.row_mappings_guesses.append(("openlibrary_work_key", ["work id"])) - super().__init__(*args, **kwargs) + row_mappings_guesses = Importer.row_mappings_guesses + [ + ("openlibrary_key", ["edition id"]), + ("openlibrary_work_key", ["work id"]), + ] diff --git a/bookwyrm/importers/openreads_import.py b/bookwyrm/importers/openreads_import.py index e6e12c2ef1..fc0ae9e432 100644 --- a/bookwyrm/importers/openreads_import.py +++ b/bookwyrm/importers/openreads_import.py @@ -1,5 +1,6 @@ -""" handle reading a csv from openreads""" -from typing import Any, Optional +"""handle reading a csv from openreads""" + +from typing import Optional from datetime import datetime from bookwyrm.models import Shelf @@ -18,13 +19,13 @@ class OpenReadsImporter(Importer): service = "OpenReads" - def __init__(self, *args: Any, **kwargs: Any): - self.row_mappings_guesses.append(("openlibrary_key", ["olid"])) - self.row_mappings_guesses.append(("pages", ["pages"])) - self.row_mappings_guesses.append(("description", ["description"])) - self.row_mappings_guesses.append(("physical_format", ["book_format"])) - self.row_mappings_guesses.append(("published_date", ["publication_year"])) - super().__init__(*args, **kwargs) + row_mappings_guesses = Importer.row_mappings_guesses + [ + ("openlibrary_key", ["olid"]), + ("pages", ["pages"]), + ("description", ["description"]), + ("physical_format", ["book_format"]), + ("published_date", ["publication_year"]), + ] def normalize_row( self, entry: dict[str, str], mappings: dict[str, Optional[str]] @@ -57,4 +58,5 @@ def get_shelf(self, normalized_row: dict[str, Optional[str]]) -> Optional[str]: return Shelf.READ_FINISHED if normalized_row["date_started"]: return Shelf.READING - return Shelf.TO_READ + # no reading dates: fall back to the "shelf" column + return super().get_shelf(normalized_row) or Shelf.TO_READ diff --git a/bookwyrm/importers/storygraph_import.py b/bookwyrm/importers/storygraph_import.py index 67cbaa663c..b5954ea345 100644 --- a/bookwyrm/importers/storygraph_import.py +++ b/bookwyrm/importers/storygraph_import.py @@ -1,4 +1,5 @@ -""" handle reading a csv from storygraph""" +"""handle reading a csv from storygraph""" + from . import Importer diff --git a/bookwyrm/isbn/isbn.py b/bookwyrm/isbn/isbn.py index d14dc26196..db67060f58 100644 --- a/bookwyrm/isbn/isbn.py +++ b/bookwyrm/isbn/isbn.py @@ -1,4 +1,5 @@ -""" Use the range message from isbn-international to hyphenate ISBNs """ +"""Use the range message from isbn-international to hyphenate ISBNs""" + import os from typing import Optional from xml.etree import ElementTree diff --git a/bookwyrm/lists_stream.py b/bookwyrm/lists_stream.py index 479eacb193..0b993e9543 100644 --- a/bookwyrm/lists_stream.py +++ b/bookwyrm/lists_stream.py @@ -1,4 +1,5 @@ -""" access the list streams stored in redis """ +"""access the list streams stored in redis""" + from django.dispatch import receiver from django.db import transaction from django.db.models import signals, Count, Q @@ -11,7 +12,7 @@ class ListsStream(RedisStore): """all the lists you can see""" - def stream_id(self, user): # pylint: disable=no-self-use + def stream_id(self, user): """the redis key for this user's instance of this stream""" if isinstance(user, int): # allows the function to take an int or an obj @@ -58,12 +59,12 @@ def populate_lists(self, user): """go from zero to a timeline""" self.populate_store(self.stream_id(user)) - def get_audience(self, book_list): # pylint: disable=no-self-use + def get_audience(self, book_list): """given a list, what users should see it""" # everybody who could plausibly see this list audience = models.User.objects.filter( - is_active=True, local=True, # we only create feeds for users of this instance + is_active=True, ).exclude( # not blocked Q(id__in=book_list.user.blocks.all()) | Q(blocks=book_list.user) ) @@ -100,7 +101,7 @@ def get_stores_for_object(self, obj): """the stores that an object belongs in""" return [self.stream_id(u) for u in self.get_audience(obj)] - def get_lists_for_user(self, user): # pylint: disable=no-self-use + def get_lists_for_user(self, user): """given a user, what lists should they see on this stream""" return models.List.privacy_filter( user, @@ -113,7 +114,6 @@ def get_objects_for_store(self, store): @receiver(signals.post_save, sender=models.List) -# pylint: disable=unused-argument def add_list_on_create(sender, instance, created, *args, update_fields=None, **kwargs): """add newly created lists streams""" if created: @@ -131,7 +131,6 @@ def add_list_on_create(sender, instance, created, *args, update_fields=None, **k @receiver(signals.post_delete, sender=models.List) -# pylint: disable=unused-argument def remove_list_on_delete(sender, instance, *args, **kwargs): """remove deleted lists to streams""" remove_list_task.delay(instance.id) @@ -143,7 +142,6 @@ def add_list_on_create_command(instance_id): @receiver(signals.post_save, sender=models.UserFollows) -# pylint: disable=unused-argument def add_lists_on_follow(sender, instance, created, *args, **kwargs): """add a newly followed user's lists to feeds""" if not created or not instance.user_subject.local: @@ -152,7 +150,6 @@ def add_lists_on_follow(sender, instance, created, *args, **kwargs): @receiver(signals.post_delete, sender=models.UserFollows) -# pylint: disable=unused-argument def remove_lists_on_unfollow(sender, instance, *args, **kwargs): """remove lists from a feed on unfollow""" if not instance.user_subject.local: @@ -164,7 +161,6 @@ def remove_lists_on_unfollow(sender, instance, *args, **kwargs): @receiver(signals.post_save, sender=models.UserBlocks) -# pylint: disable=unused-argument def remove_lists_on_block(sender, instance, *args, **kwargs): """remove lists from all feeds on block""" # blocks apply ot all feeds @@ -177,7 +173,6 @@ def remove_lists_on_block(sender, instance, *args, **kwargs): @receiver(signals.post_delete, sender=models.UserBlocks) -# pylint: disable=unused-argument def add_lists_on_unblock(sender, instance, *args, **kwargs): """add lists back to all feeds on unblock""" # make sure there isn't a block in the other direction @@ -203,7 +198,6 @@ def add_lists_on_unblock(sender, instance, *args, **kwargs): @receiver(signals.post_save, sender=models.User) -# pylint: disable=unused-argument def populate_lists_on_account_create(sender, instance, created, *args, **kwargs): """build a user's feeds when they join""" if not created or not instance.local: diff --git a/bookwyrm/management/commands/add_finna_connector.py b/bookwyrm/management/commands/add_finna_connector.py index 6da28570a3..6f77f05a28 100644 --- a/bookwyrm/management/commands/add_finna_connector.py +++ b/bookwyrm/management/commands/add_finna_connector.py @@ -1,17 +1,17 @@ -""" Add finna connector to connectors """ +"""Add finna connector to connectors""" + from django.core.management.base import BaseCommand from bookwyrm import models def enable_finna_connector(): - models.Connector.objects.create( identifier="api.finna.fi", name="Finna API", connector_file="finna", base_url="https://www.finna.fi", - books_url="https://api.finna.fi/api/v1/record" "?id=", + books_url="https://api.finna.fi/api/v1/record?id=", covers_url="https://api.finna.fi", search_url="https://api.finna.fi/api/v1/search?limit=20" "&filter[]=format%3a%220%2fBook%2f%22" @@ -33,8 +33,6 @@ def remove_finna_connector(): print("Finna connector deactivated") -# pylint: disable=no-self-use -# pylint: disable=unused-argument class Command(BaseCommand): """command-line options""" diff --git a/bookwyrm/management/commands/add_libris_connector.py b/bookwyrm/management/commands/add_libris_connector.py new file mode 100644 index 0000000000..8023f67c37 --- /dev/null +++ b/bookwyrm/management/commands/add_libris_connector.py @@ -0,0 +1,48 @@ +"""Add Libris connector to connectors""" + +from django.core.management.base import BaseCommand + +from bookwyrm import models + + +def enable_libris_connector(): + models.Connector.objects.create( + identifier="libris.kb.se", + name="Libris", + connector_file="libris", + base_url="https://libris.kb.se", + books_url="http://libris.kb.se/xsearch?format=json&format_level=full&n=1&query=", + covers_url="https://libris.kb.se", + search_url="http://libris.kb.se/xsearch?format=json&format_level=full&n=20&query=", + isbn_search_url="http://libris.kb.se/xsearch?format=json&format_level=full&n=5&query=isbn:", + ) + + +def remove_libris_connector(): + models.Connector.objects.filter(identifier="libris.kb.se").update( + active=False, deactivation_reason="Disabled by management command" + ) + print("Libris connector deactivated") + + +class Command(BaseCommand): + """command-line options""" + + help = "Setup Libris API connector" + + def add_arguments(self, parser): + """specify argument to remove connector""" + parser.add_argument( + "--deactivate", + action="store_true", + help="Deactivate the Libris connector from config", + ) + + def handle(self, *args, **options): + """enable or remove connector""" + if options.get("deactivate"): + print("Deactivate Libris connector config if one present") + remove_libris_connector() + else: + print("Adding Libris API connector to configuration") + enable_libris_connector() diff --git a/bookwyrm/management/commands/admin_code.py b/bookwyrm/management/commands/admin_code.py index ee69b78cb9..d153bfe601 100644 --- a/bookwyrm/management/commands/admin_code.py +++ b/bookwyrm/management/commands/admin_code.py @@ -1,4 +1,5 @@ -""" Get your admin code to allow install """ +"""Get your admin code to allow install""" + from django.core.management.base import BaseCommand from bookwyrm import models @@ -6,7 +7,7 @@ def get_admin_code(): """get that code""" - return models.SiteSettings.objects.get().admin_code + return models.SiteSettings.get().admin_code class Command(BaseCommand): @@ -14,7 +15,6 @@ class Command(BaseCommand): help = "Gets admin code for configuring BookWyrm" - # pylint: disable=unused-argument def handle(self, *args, **options): """execute init""" self.stdout.write("*******************************************") diff --git a/bookwyrm/management/commands/compile_themes.py b/bookwyrm/management/commands/compile_themes.py index 95c6699ba5..692e62f609 100644 --- a/bookwyrm/management/commands/compile_themes.py +++ b/bookwyrm/management/commands/compile_themes.py @@ -1,4 +1,5 @@ -""" Our own command to all scss themes """ +"""Our own command to all scss themes""" + import glob import os @@ -18,7 +19,6 @@ class Command(BaseCommand): help = "SCSS compile all BookWyrm themes" - # pylint: disable=unused-argument def handle(self, *args, **options): """compile""" themes_dir = os.path.join( diff --git a/bookwyrm/management/commands/confirm_email.py b/bookwyrm/management/commands/confirm_email.py index 450da7eecf..38f4c34321 100644 --- a/bookwyrm/management/commands/confirm_email.py +++ b/bookwyrm/management/commands/confirm_email.py @@ -1,4 +1,5 @@ -""" manually confirm e-mail of user """ +"""manually confirm e-mail of user""" + from django.core.management.base import BaseCommand from bookwyrm import models diff --git a/bookwyrm/management/commands/deduplicate_book_data.py b/bookwyrm/management/commands/deduplicate_book_data.py index c2d897ce33..8c978b9b71 100644 --- a/bookwyrm/management/commands/deduplicate_book_data.py +++ b/bookwyrm/management/commands/deduplicate_book_data.py @@ -1,5 +1,5 @@ -""" PROCEED WITH CAUTION: uses deduplication fields to permanently -merge book data objects """ +"""PROCEED WITH CAUTION: uses deduplication fields to permanently +merge book data objects""" from django.core.management.base import BaseCommand from django.db.models import Count @@ -50,7 +50,6 @@ def add_arguments(self, parser): help="don't actually merge, only print what would happen", ) - # pylint: disable=no-self-use,unused-argument def handle(self, *args, **options): """run deduplications""" dedupe_model(models.Edition, dry_run=options["dry_run"]) diff --git a/bookwyrm/management/commands/erase_deleted_user_data.py b/bookwyrm/management/commands/erase_deleted_user_data.py index 40c3f042b3..33b018c7c7 100644 --- a/bookwyrm/management/commands/erase_deleted_user_data.py +++ b/bookwyrm/management/commands/erase_deleted_user_data.py @@ -1,24 +1,24 @@ -""" Erase any data stored about deleted users """ +"""Erase any data stored about deleted users""" + import sys from django.core.management.base import BaseCommand, CommandError from bookwyrm import models from bookwyrm.models.user import erase_user_data -# pylint: disable=missing-function-docstring + class Command(BaseCommand): """command-line options""" help = "Remove Two Factor Authorisation from user" - def add_arguments(self, parser): # pylint: disable=no-self-use + def add_arguments(self, parser): parser.add_argument( "--dryrun", action="store_true", help="Preview users to be cleared without altering the database", ) - def handle(self, *args, **options): # pylint: disable=unused-argument - + def handle(self, *args, **options): # Check for anything fishy bad_state = models.User.objects.filter(is_deleted=True, is_active=True) if bad_state.exists(): diff --git a/bookwyrm/management/commands/erase_streams.py b/bookwyrm/management/commands/erase_streams.py index ecd36006cc..f2f7dd468f 100644 --- a/bookwyrm/management/commands/erase_streams.py +++ b/bookwyrm/management/commands/erase_streams.py @@ -1,4 +1,5 @@ -""" Delete user streams """ +"""Delete user streams""" + from django.core.management.base import BaseCommand import redis @@ -16,7 +17,7 @@ class Command(BaseCommand): """delete activity streams for all users""" help = "Delete all the user streams" - # pylint: disable=no-self-use,unused-argument + def handle(self, *args, **options): """flush all, baby""" erase_streams() diff --git a/bookwyrm/management/commands/fix_isbn10_entries.py b/bookwyrm/management/commands/fix_isbn10_entries.py new file mode 100644 index 0000000000..4b2b25fa7a --- /dev/null +++ b/bookwyrm/management/commands/fix_isbn10_entries.py @@ -0,0 +1,28 @@ +from django.core.management.base import BaseCommand +from django.db.models.functions import Length +from bookwyrm import models + + +def find_long_isbn10_editions(): + """Fix isbn-10 entries that have incorrect checksum""" + entries_to_fix = ( + models.Edition.objects.annotate(isbn10_length=Length("isbn_10")) + .filter(isbn10_length__gt=10) + .filter(isbn_10__endswith="11") + ) + + print(f"{entries_to_fix.count()} editions to find") + + for edition_to_fix in entries_to_fix: + edition_to_fix.isbn_10 = edition_to_fix.isbn_10[:9] + "0" + edition_to_fix.save(broadcast=True, update_fields=["isbn_10"]) + + +class Command(BaseCommand): + """Fix isbn-10 entries that have incorrect checksum""" + + help = "Find and fix isbn-10 entries that have incorrect checksum" + + def handle(self, *args, **options): + """run fix""" + find_long_isbn10_editions() diff --git a/bookwyrm/management/commands/generate_preview_images.py b/bookwyrm/management/commands/generate_preview_images.py index 9ff16c26ae..2f1ce60cf2 100644 --- a/bookwyrm/management/commands/generate_preview_images.py +++ b/bookwyrm/management/commands/generate_preview_images.py @@ -1,16 +1,15 @@ -""" Generate preview images """ +"""Generate preview images""" + from django.core.management.base import BaseCommand from bookwyrm import models, preview_images -# pylint: disable=line-too-long class Command(BaseCommand): """Creates previews for existing objects""" help = "Generate preview images" - # pylint: disable=no-self-use def add_arguments(self, parser): """options for how the command is run""" parser.add_argument( @@ -20,7 +19,6 @@ def add_arguments(self, parser): help="Generates images for ALL types: site, users and books. Can use a lot of computing power.", ) - # pylint: disable=no-self-use,unused-argument def handle(self, *args, **options): """generate preview images""" self.stdout.write( @@ -40,7 +38,6 @@ def handle(self, *args, **options): preview_images.generate_site_preview_image_task.delay() self.stdout.write(" OK 🖼") - # pylint: disable=consider-using-f-string if options["all"]: # Users users = models.User.objects.filter( diff --git a/bookwyrm/management/commands/initdb.py b/bookwyrm/management/commands/initdb.py index 88941a653e..45ed8fd03e 100644 --- a/bookwyrm/management/commands/initdb.py +++ b/bookwyrm/management/commands/initdb.py @@ -1,4 +1,5 @@ -""" What you need in the database to make it work """ +"""What you need in the database to make it work""" + from django.core.management.base import BaseCommand from django.contrib.auth.models import Group, Permission from django.contrib.contenttypes.models import ContentType @@ -79,39 +80,44 @@ def init_connectors(): """access book data sources""" models.Connector.objects.get_or_create( identifier="bookwyrm.social", - name="Bookwyrm.social", - connector_file="bookwyrm_connector", - base_url="https://bookwyrm.social", - books_url="https://bookwyrm.social/book", - covers_url="https://bookwyrm.social/images/", - search_url="https://bookwyrm.social/search?q=", - isbn_search_url="https://bookwyrm.social/isbn/", - priority=2, + defaults={ + "name": "Bookwyrm.social", + "connector_file": "bookwyrm_connector", + "base_url": "https://bookwyrm.social", + "books_url": "https://bookwyrm.social/book", + "covers_url": "https://bookwyrm.social/images/", + "search_url": "https://bookwyrm.social/search?q=", + "isbn_search_url": "https://bookwyrm.social/isbn/", + "priority": 2, + }, ) - # pylint: disable=line-too-long models.Connector.objects.get_or_create( identifier="inventaire.io", - name="Inventaire", - connector_file="inventaire", - base_url="https://inventaire.io", - books_url="https://inventaire.io/api/entities", - covers_url="https://inventaire.io", - search_url="https://inventaire.io/api/search?types=works&types=works&search=", - isbn_search_url="https://inventaire.io/api/entities?action=by-uris&uris=isbn%3A", - priority=3, + defaults={ + "name": "Inventaire", + "connector_file": "inventaire", + "base_url": "https://inventaire.io", + "books_url": "https://inventaire.io/api/entities", + "covers_url": "https://inventaire.io", + "search_url": "https://inventaire.io/api/search?types=works&types=works&search=", + "isbn_search_url": "https://inventaire.io/api/entities?action=by-uris&uris=isbn%3A", + "priority": 3, + }, ) models.Connector.objects.get_or_create( identifier="openlibrary.org", - name="OpenLibrary", - connector_file="openlibrary", - base_url="https://openlibrary.org", - books_url="https://openlibrary.org", - covers_url="https://covers.openlibrary.org", - search_url="https://openlibrary.org/search?q=", - isbn_search_url="https://openlibrary.org/api/books?jscmd=data&format=json&bibkeys=ISBN:", - priority=3, + defaults={ + "name": "OpenLibrary", + "connector_file": "openlibrary", + "base_url": "https://openlibrary.org", + "books_url": "https://openlibrary.org", + "covers_url": "https://covers.openlibrary.org", + "search_url": "https://openlibrary.org/search?q=", + "isbn_search_url": "https://openlibrary.org/api/books?jscmd=data&format=json&bibkeys=ISBN:", + "priority": 3, + }, ) @@ -139,13 +145,13 @@ def init_link_domains(): for domain, name in domains: models.LinkDomain.objects.get_or_create( domain=domain, - name=name, - status="approved", + defaults={ + "name": name, + "status": "approved", + }, ) -# pylint: disable=no-self-use -# pylint: disable=unused-argument class Command(BaseCommand): """command-line options""" diff --git a/bookwyrm/management/commands/merge_authors.py b/bookwyrm/management/commands/merge_authors.py index 7465df1479..a05029e9eb 100644 --- a/bookwyrm/management/commands/merge_authors.py +++ b/bookwyrm/management/commands/merge_authors.py @@ -1,5 +1,6 @@ -""" PROCEED WITH CAUTION: uses deduplication fields to permanently -merge author data objects """ +"""PROCEED WITH CAUTION: uses deduplication fields to permanently +merge author data objects""" + from bookwyrm import models from bookwyrm.management.merge_command import MergeCommand diff --git a/bookwyrm/management/commands/merge_editions.py b/bookwyrm/management/commands/merge_editions.py index 9ed6962019..4b7f367809 100644 --- a/bookwyrm/management/commands/merge_editions.py +++ b/bookwyrm/management/commands/merge_editions.py @@ -1,5 +1,6 @@ -""" PROCEED WITH CAUTION: uses deduplication fields to permanently -merge edition data objects """ +"""PROCEED WITH CAUTION: uses deduplication fields to permanently +merge edition data objects""" + from bookwyrm import models from bookwyrm.management.merge_command import MergeCommand diff --git a/bookwyrm/management/commands/merge_works.py b/bookwyrm/management/commands/merge_works.py index 619d0509ac..e8c5e08319 100644 --- a/bookwyrm/management/commands/merge_works.py +++ b/bookwyrm/management/commands/merge_works.py @@ -1,5 +1,6 @@ -""" PROCEED WITH CAUTION: uses deduplication fields to permanently -merge work data objects """ +"""PROCEED WITH CAUTION: uses deduplication fields to permanently +merge work data objects""" + from bookwyrm import models from bookwyrm.management.merge_command import MergeCommand diff --git a/bookwyrm/management/commands/populate_lists_streams.py b/bookwyrm/management/commands/populate_lists_streams.py index 0a057401cc..ec4b9c687e 100644 --- a/bookwyrm/management/commands/populate_lists_streams.py +++ b/bookwyrm/management/commands/populate_lists_streams.py @@ -1,4 +1,5 @@ -""" Re-create list streams """ +"""Re-create list streams""" + from django.core.management.base import BaseCommand from bookwyrm import lists_stream, models @@ -22,7 +23,6 @@ class Command(BaseCommand): help = "Populate list streams for all users" - # pylint: disable=no-self-use,unused-argument def handle(self, *args, **options): """run feed builder""" populate_lists_streams() diff --git a/bookwyrm/management/commands/populate_streams.py b/bookwyrm/management/commands/populate_streams.py index 5f83670c20..8ff2ef36e4 100644 --- a/bookwyrm/management/commands/populate_streams.py +++ b/bookwyrm/management/commands/populate_streams.py @@ -1,4 +1,5 @@ -""" Re-create user streams """ +"""Re-create user streams""" + from django.core.management.base import BaseCommand from bookwyrm import activitystreams, lists_stream, models @@ -32,7 +33,6 @@ def add_arguments(self, parser): help="Specifies which time of stream to populate", ) - # pylint: disable=no-self-use,unused-argument def handle(self, *args, **options): """run feed builder""" stream = options.get("stream") diff --git a/bookwyrm/management/commands/populate_suggestions.py b/bookwyrm/management/commands/populate_suggestions.py index 32495497e4..ac35df5bc9 100644 --- a/bookwyrm/management/commands/populate_suggestions.py +++ b/bookwyrm/management/commands/populate_suggestions.py @@ -1,4 +1,5 @@ -""" Populate suggested users """ +"""Populate suggested users""" + from django.core.management.base import BaseCommand from bookwyrm import models @@ -19,7 +20,7 @@ class Command(BaseCommand): """start all over with user suggestions""" help = "Populate suggested users for all users" - # pylint: disable=no-self-use,unused-argument + def handle(self, *args, **options): """run builder""" populate_suggestions() diff --git a/bookwyrm/management/commands/purge_remote_deleted_users.py b/bookwyrm/management/commands/purge_remote_deleted_users.py new file mode 100644 index 0000000000..d1e6d832f9 --- /dev/null +++ b/bookwyrm/management/commands/purge_remote_deleted_users.py @@ -0,0 +1,96 @@ +"""Remove remove delted users from the database""" + +from django.core.management.base import BaseCommand +from django.db.models import Count, F +from bookwyrm import models + + +class Command(BaseCommand): + """command-line options""" + + help = "Remove users that were only added because they were deleted" + + def add_arguments(self, parser): + parser.add_argument( + "--confirm", + action="store_true", + help="Delete identified users from database", + ) + + def handle(self, *args, **options): + """Find and report on user accounts that can be purged""" + + targets = models.User.objects.filter( + local=False, + is_deleted=True, + is_active=False, + bookwyrm_user=False, + status=None, + favorites=None, + ).filter(date_joined__date=F("last_active_date__date")) + + target_ids = targets.values_list("id", flat=True) + + statuses = models.Status.objects.filter(user__in=target_ids) + followers = models.User.objects.filter(local=True, followers__in=target_ids) + followed = models.User.objects.filter(local=True, following__in=target_ids) + favorites = models.Favorite.objects.filter(user__in=target_ids) + + servers = ( + models.FederatedServer.objects.filter(user__in=target_ids) + .annotate(total_users=Count("user")) + .order_by("-total_users") + ) + + if options["confirm"]: + """This time for real""" + + try: + self.stdout.write( + f"\nPurging {targets.count()} users from {servers.count()} instances:\n\n" + ) + server_list = [(s.server_name, s.total_users) for s in servers.all()] + for name, total in server_list: + self.stdout.write(f"{name}: {total}") + self.stdout.write("\nDeleting...") + + # this is a bulk operation so it bypasses the overridden method and actually deletes + # see https://docs.djangoproject.com/en/5.2/topics/db/queries/#topics-db-queries-delete + targets.delete() + + self.stdout.write("User deletion completed.") + self.stdout.write( + "Thankyou for being a BookWyrm administrator, happy reading!\n\n" + ) + + except Exception as e: + self.stdout.write(f"User deletion failed with error:\n\n{e}") + + else: + self.stdout.write( + f"\nYour database has {targets.count()} candidates for deletion from {servers.count()} instances.\n" + ) + self.stdout.write("Values associated with these users:\n\n") + self.stdout.write("------------------------------------------------") + self.stdout.write(f"Statuses: {statuses.count()}") + self.stdout.write(f"Favorites: {favorites.count()}") + self.stdout.write(f"Local followers: {followers.count()}") + self.stdout.write(f"Locals following: {followed.count()}") + self.stdout.write("------------------------------------------------\n\n") + associated_models_count = ( + statuses.count() + + followers.count() + + followed.count() + + favorites.count() + ) + + if associated_models_count == 0: + self.stdout.write("It is safe to delete these users.") + self.stdout.write( + "To do so, run the command again with the --confirm flag\n\n" + ) + else: + self.stdout.write("It is NOT SAFE to delete all these users.") + self.stdout.write( + "Please log an issue at https://github.com/bookwyrm-social/bookwyrm/issues with this output\n\n" + ) diff --git a/bookwyrm/management/commands/remove_2fa.py b/bookwyrm/management/commands/remove_2fa.py index 1c9d5f71a4..09a10e09cb 100644 --- a/bookwyrm/management/commands/remove_2fa.py +++ b/bookwyrm/management/commands/remove_2fa.py @@ -1,6 +1,6 @@ """deactivate two factor auth""" -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import BaseCommand from bookwyrm import models diff --git a/bookwyrm/management/commands/remove_editions.py b/bookwyrm/management/commands/remove_editions.py index 5cb430a93b..a67161f7a8 100644 --- a/bookwyrm/management/commands/remove_editions.py +++ b/bookwyrm/management/commands/remove_editions.py @@ -1,4 +1,5 @@ -""" PROCEED WITH CAUTION: this permanently deletes book data """ +"""PROCEED WITH CAUTION: this permanently deletes book data""" + from django.core.management.base import BaseCommand from django.db.models import Count, Q from bookwyrm import models @@ -20,7 +21,7 @@ def remove_editions(): models.Edition.objects.filter( Q(languages=[]) | Q(languages__contains=["English"]), **filters, - **null_fields + **null_fields, ) .annotate(Count("parent_work__editions")) .filter( @@ -36,7 +37,7 @@ class Command(BaseCommand): """deduplicate allllll the book data models""" help = "merges duplicate book data" - # pylint: disable=no-self-use,unused-argument + def handle(self, *args, **options): """run deduplications""" remove_editions() diff --git a/bookwyrm/management/commands/remove_remote_user_preview_images.py b/bookwyrm/management/commands/remove_remote_user_preview_images.py index d4dc131d82..95e57f7127 100644 --- a/bookwyrm/management/commands/remove_remote_user_preview_images.py +++ b/bookwyrm/management/commands/remove_remote_user_preview_images.py @@ -1,17 +1,16 @@ -""" Remove preview images for remote users """ +"""Remove preview images for remote users""" + from django.core.management.base import BaseCommand from django.db.models import Q from bookwyrm import models, preview_images -# pylint: disable=line-too-long class Command(BaseCommand): """Remove preview images for remote users""" help = "Remove preview images for remote users" - # pylint: disable=no-self-use,unused-argument def handle(self, *args, **options): """generate preview images""" self.stdout.write( @@ -35,6 +34,6 @@ def handle(self, *args, **options): self.stdout.write(".", ending="") self.stdout.write(" OK 🖼") else: - self.stdout.write(f" | There was no remote users with preview images.") + self.stdout.write(" | There was no remote users with preview images.") self.stdout.write("🧑‍🚒 ⎨ I’m all done! ✧ Enjoy ✧") diff --git a/bookwyrm/management/commands/repair_editions.py b/bookwyrm/management/commands/repair_editions.py index 304cd5e51f..c8d9c10594 100644 --- a/bookwyrm/management/commands/repair_editions.py +++ b/bookwyrm/management/commands/repair_editions.py @@ -1,4 +1,5 @@ -""" Repair editions with missing works """ +"""Repair editions with missing works""" + from django.core.management.base import BaseCommand from bookwyrm import models @@ -8,7 +9,6 @@ class Command(BaseCommand): help = "Repairs an edition that is in a broken state" - # pylint: disable=unused-argument def handle(self, *args, **options): """Find and repair broken editions""" # Find broken editions diff --git a/bookwyrm/management/commands/revoke_preview_image_tasks.py b/bookwyrm/management/commands/revoke_preview_image_tasks.py index 7b0947b12c..d2a596f9c6 100644 --- a/bookwyrm/management/commands/revoke_preview_image_tasks.py +++ b/bookwyrm/management/commands/revoke_preview_image_tasks.py @@ -1,4 +1,5 @@ -""" Actually let's not generate those preview images """ +"""Actually let's not generate those preview images""" + import json from django.core.management.base import BaseCommand from bookwyrm.tasks import app @@ -7,7 +8,6 @@ class Command(BaseCommand): """Find and revoke image tasks""" - # pylint: disable=unused-argument def handle(self, *args, **options): """revoke nonessential low priority tasks""" types = [ diff --git a/bookwyrm/management/commands/show_duplicate_authors.py b/bookwyrm/management/commands/show_duplicate_authors.py index 34e5da2b0e..95f7d632b2 100644 --- a/bookwyrm/management/commands/show_duplicate_authors.py +++ b/bookwyrm/management/commands/show_duplicate_authors.py @@ -41,7 +41,6 @@ class Command(BaseCommand): help = "show authors with same name but different id" - # pylint: disable=no-self-use,unused-argument def handle(self, *args, **options): """run deduplications""" find_duplicate_author_names() diff --git a/bookwyrm/management/commands/upgrade_series.py b/bookwyrm/management/commands/upgrade_series.py new file mode 100644 index 0000000000..64e88d337c --- /dev/null +++ b/bookwyrm/management/commands/upgrade_series.py @@ -0,0 +1,86 @@ +"""fix legacy series""" + +from django.core.management.base import BaseCommand +from django.contrib.postgres.search import SearchRank, SearchVector +from django.db.models import Subquery, Q +from bookwyrm import activitypub +from bookwyrm.models import Edition, Series, SeriesBook + + +def upgrade_series_data(): + """turn strings into things""" + + series_count = Series.objects.count() + seriesbook_count = SeriesBook.objects.count() + + for book in ( + Edition.objects.exclude(parent_work=None) + .exclude(series=None) + .exclude(series="") + ): + user = activitypub.get_representative() + vector = SearchVector("name", weight="A") + SearchVector( + "alternative_names", weight="B" + ) + possible_series = ( + Series.objects.annotate(search=vector) + .annotate(rank=SearchRank(vector, book.series, normalization=32)) + .filter(rank__gt=0.19) + .order_by("-rank") + ) + + if possible_series.exists(): + books = Edition.objects.filter( + authors__in=Subquery(book.authors.values("pk")) + ).values( + "parent_work__pk" + ) # the parent work is the book attached to the series + + same_author_sb = SeriesBook.objects.filter(book__in=books).filter( + series__in=Subquery(possible_series.values("pk")) + ) # there is a possible series with a seriesbook by a matching author + + match = same_author_sb.filter( + Q(series__name__iexact=book.series) + | Q(series__alternative_names__icontains=book.series) + ) # it's the same series + + if match: + series = match.first().series + + else: + # there might be a matching series but we don't know + # leave it for a user to work out manually + continue + else: + series = Series.objects.create(user=user, name=book.series) + + # Use get_or_create so we don't duplicate. + SeriesBook.objects.get_or_create( + book=book.parent_work, + series=series, + defaults={"user": user, "series_number": book.series_number}, + ) + + book.series = None + book.series_number = None + book.save(broadcast=False) + + # print how many things we created + new_series_count = Series.objects.count() + new_seriesbook_count = SeriesBook.objects.count() + net_series = new_series_count - series_count + net_books = new_seriesbook_count - seriesbook_count + + print("-------") + print(f"Created {net_series} new Series and {net_books} new SeriesBooks") + + +class Command(BaseCommand): + """Turn legacy series data into Series and SeriesBook objects""" + + help = "Turn legacy series data into Series and SeriesBook objects" + + def handle(self, *args, **options): + """run data migration""" + upgrade_series_data() diff --git a/bookwyrm/management/merge_command.py b/bookwyrm/management/merge_command.py index 66e60814ae..cd170c8056 100644 --- a/bookwyrm/management/merge_command.py +++ b/bookwyrm/management/merge_command.py @@ -14,7 +14,6 @@ def add_arguments(self, parser): help="don't actually merge, only print what would happen", ) - # pylint: disable=no-self-use,unused-argument def handle(self, *args, **options): """merge the two objects""" model = self.MODEL diff --git a/bookwyrm/middleware/__init__.py b/bookwyrm/middleware/__init__.py index 85c3a56fe8..6d29d0fe17 100644 --- a/bookwyrm/middleware/__init__.py +++ b/bookwyrm/middleware/__init__.py @@ -1,4 +1,8 @@ -""" look at all this nice middleware! """ +"""look at all this nice middleware!""" + from .timezone_middleware import TimezoneMiddleware from .ip_middleware import IPBlocklistMiddleware from .file_too_big import FileTooBig +from .force_logout import ForceLogoutMiddleware +from .require_signed_get import RequireSignedGet +from .require_login import RequireLoginNearlyEverywhere diff --git a/bookwyrm/middleware/file_too_big.py b/bookwyrm/middleware/file_too_big.py index de1349d96a..468c498684 100644 --- a/bookwyrm/middleware/file_too_big.py +++ b/bookwyrm/middleware/file_too_big.py @@ -18,13 +18,10 @@ def __call__(self, request): """If RequestDataTooBig is thrown, render the 413 error page""" try: - body = request.body # pylint: disable=unused-variable + response = self.get_response(request) except RequestDataTooBig: - rendered = render(request, "413.html") response = HttpResponse(rendered) - return response - response = self.get_response(request) return response diff --git a/bookwyrm/middleware/force_logout.py b/bookwyrm/middleware/force_logout.py new file mode 100644 index 0000000000..d901e1a2e8 --- /dev/null +++ b/bookwyrm/middleware/force_logout.py @@ -0,0 +1,15 @@ +"""Check if user needs to reset their password and log them out""" + +from django.contrib.auth import logout + + +class ForceLogoutMiddleware: + """Log out any users who are flagged as needing a password reset""" + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + if request.user.is_authenticated and request.user.force_password_reset: + logout(request) + return self.get_response(request) diff --git a/bookwyrm/middleware/ip_middleware.py b/bookwyrm/middleware/ip_middleware.py index 8063dd1f60..4d9f2bbf63 100644 --- a/bookwyrm/middleware/ip_middleware.py +++ b/bookwyrm/middleware/ip_middleware.py @@ -1,4 +1,5 @@ -""" Block IP addresses """ +"""Block IP addresses""" + from django.http import Http404 from bookwyrm import models diff --git a/bookwyrm/middleware/require_login.py b/bookwyrm/middleware/require_login.py new file mode 100644 index 0000000000..29e8e68b37 --- /dev/null +++ b/bookwyrm/middleware/require_login.py @@ -0,0 +1,56 @@ +"""require signed headers and user log in depending on site settings""" + +import re + +from django.contrib.auth.decorators import login_required + +from bookwyrm.models import SiteSettings +from bookwyrm.views.helpers import is_api_request + +allowed_views = [ + # basic web utility views + r"/robots.txt", + r"/manifest.json", + r"^/opensearch.xml$", + # site basics + r"^/?$", + r"^/about/?$", + r"^/privacy/?$", + r"^/conduct/?$", + # setup + r"^/setup/?$", + r"^/setup/admin/?$", + # login and register + r"^/login/?$", + r"^/login/(?Pconfirmed)/?$", + r"^/register/?$", + r"^/password-reset/?$", + r"/confirm-email/?$", + r"/confirm-email/(?P[A-Za-z0-9]+)/?$", + r"^/resend-link/?$", + r"^/invite/(?P[A-Za-z0-9]+)/?$", + r"^/2fa-check/?$", +] + + +class RequireLoginNearlyEverywhere: + """lock down access depending on site settings""" + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + """run before next middleware or view""" + + if SiteSettings.get().require_login_nearly_everywhere and not is_api_request( + request + ): + # require login unless endpoint is on the allow list + if not request.user.is_authenticated: + for path in allowed_views: + if re.search(path, request.path): + return self.get_response(request) + return login_required(self.get_response)(request) + + # we're good, continue + return self.get_response(request) diff --git a/bookwyrm/middleware/require_signed_get.py b/bookwyrm/middleware/require_signed_get.py new file mode 100644 index 0000000000..1a744eea8d --- /dev/null +++ b/bookwyrm/middleware/require_signed_get.py @@ -0,0 +1,111 @@ +"""Require all GET API requests to use signed HTTP headers""" + +import re +from requests.exceptions import HTTPError + +from django.http import HttpResponseForbidden + +from bookwyrm.activitypub import resolve_remote_id +from bookwyrm.connectors import get_data +from bookwyrm.models import SiteSettings, User +from bookwyrm.signatures import Signature +from bookwyrm.views.helpers import is_api_request +from bookwyrm.views.inbox import raise_is_blocked_user_agent + + +class RequireSignedGet: + """lock down incoming GET API requests""" + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + """run before next middleware or view""" + + if is_api_request(request): + # Always check if this server is on our shitlist + raise_is_blocked_user_agent(request) + + # if we disabled federation, disallow all API requests + # this will include nodeinfo and webfinger etc + SiteSettings.raise_federation_disabled() + + if SiteSettings.get().require_signed_get and request.method == "GET": + allowed_api_views = [ + r"^/\.well-known/webfinger/?$", + r"^/\.well-known/nodeinfo/?$", + r"^/\.well-known/host-meta/?$", + r"^/nodeinfo/2\.0/?$", + ] + + # ignore well-known and nodeinfo paths + # otherwise nobody can follow our users + for path in allowed_api_views: + if re.search(path, request.path): + return self.get_response(request) + + # require signed headers for everything else + if not has_valid_get_signature(request): + return HttpResponseForbidden("Invalid signature") + + # we're good, continue + return self.get_response(request) + + +def has_valid_get_signature(request): + """verify incoming signature""" + try: + signature = Signature.parse(request) + # have we seen this actor before? It's probably an instance actor + known_actor = User.objects.filter(key_pair__remote_id=signature.key_id).first() + if known_actor: + remote_actor = known_actor + else: + # Key id is probably the same as the user endpoint + remote_actor = resolve_remote_id(signature.key_id, model=User, save=False) + + if not remote_actor: + # ...but it might not be + # see https://swicg.github.io/activitypub-http-signature/#how-to-obtain-a-signature-s-public-key + data = get_data(signature.key_id) + + try: + actor_id = data.get("owner") + except KeyError: + actor_id = data.get("controller") + + remote_actor = resolve_remote_id(actor_id, model=User, save=False) + + # well we tried everything I guess + if not remote_actor: + return False + + if signature.key_id != remote_actor.key_pair.remote_id: + if ( + signature.key_id != f"{remote_actor.remote_id}#main-key" + ): # legacy Bookwyrm + raise ValueError("Wrong actor created signature.") + + try: + signature.verify( + remote_actor.key_pair.public_key, request, request_type="get" + ) + except ValueError: + old_key = remote_actor.key_pair.public_key + remote_actor = resolve_remote_id( + remote_actor.remote_id, model=User, refresh=True, save=False + ) + if not remote_actor: + return False + if remote_actor.key_pair.public_key == old_key: + raise # Key unchanged. + signature.verify( + remote_actor.key_pair.public_key, request, request_type="get" + ) + except (KeyError, ValueError, HTTPError): + return False + + # save previously unknown actors so we can check our DB next time + if not remote_actor.id: + remote_actor.save(broadcast=False) + return True diff --git a/bookwyrm/middleware/timezone_middleware.py b/bookwyrm/middleware/timezone_middleware.py index 3cf084154c..4337d04193 100644 --- a/bookwyrm/middleware/timezone_middleware.py +++ b/bookwyrm/middleware/timezone_middleware.py @@ -1,4 +1,5 @@ -""" Makes the app aware of the users timezone """ +"""Makes the app aware of the users timezone""" + import zoneinfo from django.utils import timezone diff --git a/bookwyrm/migrations/0192_auto_20240102_2156.py b/bookwyrm/migrations/0192_auto_20240102_2156.py new file mode 100644 index 0000000000..7537bbbaae --- /dev/null +++ b/bookwyrm/migrations/0192_auto_20240102_2156.py @@ -0,0 +1,57 @@ +# Generated by Django 3.2.23 on 2024-01-02 21:56 + +import bookwyrm.models.fields +from django.conf import settings +from django.db import migrations +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0191_merge_20240102_0326"), + ] + + operations = [ + migrations.AlterField( + model_name="report", + name="links", + field=bookwyrm.models.fields.ManyToManyField( + blank=True, to="bookwyrm.Link" + ), + ), + migrations.AlterField( + model_name="report", + name="note", + field=bookwyrm.models.fields.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name="report", + name="reporter", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="reporter", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AlterField( + model_name="report", + name="status", + field=bookwyrm.models.fields.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.status", + ), + ), + migrations.AlterField( + model_name="report", + name="user", + field=bookwyrm.models.fields.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/bookwyrm/migrations/0193_rename_user_report_reported_user.py b/bookwyrm/migrations/0193_rename_user_report_reported_user.py new file mode 100644 index 0000000000..0633e69fa1 --- /dev/null +++ b/bookwyrm/migrations/0193_rename_user_report_reported_user.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.23 on 2024-01-02 22:16 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0192_auto_20240102_2156"), + ] + + operations = [ + migrations.RenameField( + model_name="report", + old_name="user", + new_name="reported_user", + ), + ] diff --git a/bookwyrm/migrations/0194_rename_reporter_report_user.py b/bookwyrm/migrations/0194_rename_reporter_report_user.py new file mode 100644 index 0000000000..3c2c0f1802 --- /dev/null +++ b/bookwyrm/migrations/0194_rename_reporter_report_user.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.23 on 2024-01-02 22:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0193_rename_user_report_reported_user"), + ] + + operations = [ + migrations.RenameField( + model_name="report", + old_name="reporter", + new_name="user", + ), + ] diff --git a/bookwyrm/migrations/0195_auto_20240102_2310.py b/bookwyrm/migrations/0195_auto_20240102_2310.py new file mode 100644 index 0000000000..c6af5ae780 --- /dev/null +++ b/bookwyrm/migrations/0195_auto_20240102_2310.py @@ -0,0 +1,39 @@ +# Generated by Django 3.2.23 on 2024-01-02 23:10 + +import bookwyrm.models.fields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0194_rename_reporter_report_user"), + ] + + operations = [ + migrations.AddField( + model_name="report", + name="allow_broadcast", + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name="report", + name="reported_user", + field=bookwyrm.models.fields.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="reported_user", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AlterField( + model_name="report", + name="user", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL + ), + ), + ] diff --git a/bookwyrm/migrations/0200_merge_20240403_1728.py b/bookwyrm/migrations/0200_merge_20240403_1728.py new file mode 100644 index 0000000000..e39991ea22 --- /dev/null +++ b/bookwyrm/migrations/0200_merge_20240403_1728.py @@ -0,0 +1,13 @@ +# Generated by Django 3.2.25 on 2024-04-03 17:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0195_auto_20240102_2310"), + ("bookwyrm", "0199_status_bookwyrm_st_remote__06aeba_idx"), + ] + + operations = [] diff --git a/bookwyrm/migrations/0209_merge_20240824_2211.py b/bookwyrm/migrations/0209_merge_20240824_2211.py new file mode 100644 index 0000000000..68b8b41257 --- /dev/null +++ b/bookwyrm/migrations/0209_merge_20240824_2211.py @@ -0,0 +1,13 @@ +# Generated by Django 4.2.15 on 2024-08-24 22:11 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0200_merge_20240403_1728"), + ("bookwyrm", "0208_merge_0207_merge_20240629_0626_0207_sqlparse_update"), + ] + + operations = [] diff --git a/bookwyrm/migrations/0210_report_statuses_alter_report_status.py b/bookwyrm/migrations/0210_report_statuses_alter_report_status.py new file mode 100644 index 0000000000..194547f006 --- /dev/null +++ b/bookwyrm/migrations/0210_report_statuses_alter_report_status.py @@ -0,0 +1,52 @@ +# Generated by Django 4.2.15 on 2024-08-24 23:28 + +from django.db import migrations, models +import django.db.models.deletion + + +def copy_status_field(apps, schema_editor): + """Move the foreign key status to the new many to many field""" + reports = apps.get_model("bookwyrm", "Report") + db_alias = schema_editor.connection.alias + for report in reports.objects.using(db_alias).filter(status__isnull=False): + report.statuses.add(report.status) + + +def reverse_copy_status_field(apps, schema_editor): + """Move the first status in the many to many to the old FK""" + reports = apps.get_model("bookwyrm", "Report") + db_alias = schema_editor.connection.alias + for report in reports.objects.using(db_alias).filter(statuses__isnull=False): + report.status = report.statuses.first() + report.save(broadcast=False, updated_fields=["status"]) + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0209_merge_20240824_2211"), + ] + + operations = [ + migrations.AddField( + model_name="report", + name="statuses", + field=models.ManyToManyField( + blank=True, + null=True, + related_name="status_intermediary_name", + to="bookwyrm.status", + ), + ), + migrations.AlterField( + model_name="report", + name="status", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.status", + ), + ), + migrations.RunPython(copy_status_field, reverse_code=reverse_copy_status_field), + ] diff --git a/bookwyrm/migrations/0211_remove_report_status_alter_report_statuses.py b/bookwyrm/migrations/0211_remove_report_status_alter_report_statuses.py new file mode 100644 index 0000000000..18d2c687be --- /dev/null +++ b/bookwyrm/migrations/0211_remove_report_status_alter_report_statuses.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.15 on 2024-08-24 23:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0210_report_statuses_alter_report_status"), + ] + + operations = [ + migrations.RemoveField( + model_name="report", + name="status", + ), + migrations.AlterField( + model_name="report", + name="statuses", + field=models.ManyToManyField(blank=True, null=True, to="bookwyrm.status"), + ), + ] diff --git a/bookwyrm/migrations/0219_datamigration_fix_isbn10_20251017_1810.py b/bookwyrm/migrations/0219_datamigration_fix_isbn10_20251017_1810.py new file mode 100644 index 0000000000..514ba9b3d8 --- /dev/null +++ b/bookwyrm/migrations/0219_datamigration_fix_isbn10_20251017_1810.py @@ -0,0 +1,27 @@ +# Generated by Django 5.2.3 on 2025-10-19 12:31 + +from django.db import migrations +from django.db.models.functions import Length + + +def fix_isbn10_entries(apps, schema_editor): + Edition = apps.get_model("bookwyrm", "Edition") + db_alias = schema_editor.connection.alias + editions_to_fix = ( + Edition.objects.using(db_alias) + .annotate(isbn_10_length=Length("isbn_10")) + .filter(isbn_10_length__gt=10) + .filter(isbn_10__endswith="11") + ) + + for edition_to_fix in editions_to_fix: + edition_to_fix.isbn_10 = edition_to_fix.isbn_10[:9] + "0" + edition_to_fix.save(update_fields=["isbn_10"]) + + +class Migration(migrations.Migration): + dependencies = [ + ("bookwyrm", "0218_merge_0217_merge_20250816_0749_0217_usersession"), + ] + + operations = [migrations.RunPython(fix_isbn10_entries, reverse_code=migrations.RunPython.noop)] diff --git a/bookwyrm/migrations/0219_findmissingcoversjob.py b/bookwyrm/migrations/0219_findmissingcoversjob.py new file mode 100644 index 0000000000..8b89b0509f --- /dev/null +++ b/bookwyrm/migrations/0219_findmissingcoversjob.py @@ -0,0 +1,54 @@ +# Generated by Django 5.2.3 on 2025-09-09 10:48 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0218_merge_0217_merge_20250816_0749_0217_usersession"), + ] + + operations = [ + migrations.CreateModel( + name="FindMissingCoversJob", + fields=[ + ( + "parentjob_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="bookwyrm.parentjob", + ), + ), + ( + "job_type", + models.CharField( + choices=[("missing", "Missing"), ("wrong_path", "Wrong Path")], + default="missing", + max_length=10, + null=True, + ), + ), + ("completed_tasks", models.IntegerField(default=0)), + ( + "editions", + models.ManyToManyField( + related_name="missing_covers_job", to="bookwyrm.edition" + ), + ), + ( + "found_covers", + models.ManyToManyField(related_name="+", to="bookwyrm.edition"), + ), + ], + options={ + "abstract": False, + }, + bases=("bookwyrm.parentjob",), + ), + ] diff --git a/bookwyrm/migrations/0220_auto_20251115_2033.py b/bookwyrm/migrations/0220_auto_20251115_2033.py new file mode 100644 index 0000000000..881b7f01ec --- /dev/null +++ b/bookwyrm/migrations/0220_auto_20251115_2033.py @@ -0,0 +1,27 @@ +# Generated by Django 5.2.3 on 2025-11-15 20:33 + +from django.db import migrations +from django.contrib.auth.hashers import make_password + + +def set_unusable_passwords(apps, schema_editor): + user_model = apps.get_model("bookwyrm", "User") + deleted_users = user_model.objects.filter( + is_deleted=True, allow_reactivation=False, local=True + ) + for user in deleted_users: + user.password = make_password(None) + user.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0219_datamigration_fix_isbn10_20251017_1810"), + ] + + operations = [ + migrations.RunPython( + set_unusable_passwords, reverse_code=migrations.RunPython.noop + ) + ] diff --git a/bookwyrm/migrations/0221_user_force_password_reset.py b/bookwyrm/migrations/0221_user_force_password_reset.py new file mode 100644 index 0000000000..a809eed99a --- /dev/null +++ b/bookwyrm/migrations/0221_user_force_password_reset.py @@ -0,0 +1,27 @@ +# Generated by Django 5.2.3 on 2025-11-15 20:52 + +from django.db import migrations, models + + +def force_admin_reset(apps, schema_editor): + user_model = apps.get_model("bookwyrm", "User") + user_model.objects.filter( + models.Q(groups__name__in=["moderator", "admin"]) | models.Q(is_superuser=True), + is_active=True, + ).distinct().update(force_password_reset=True) + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0220_auto_20251115_2033"), + ] + + operations = [ + migrations.AddField( + model_name="user", + name="force_password_reset", + field=models.BooleanField(default=False), + ), + migrations.RunPython(force_admin_reset, reverse_code=migrations.RunPython.noop), + ] diff --git a/bookwyrm/migrations/0222_merge_20251116_1831.py b/bookwyrm/migrations/0222_merge_20251116_1831.py new file mode 100644 index 0000000000..c5f28dd872 --- /dev/null +++ b/bookwyrm/migrations/0222_merge_20251116_1831.py @@ -0,0 +1,13 @@ +# Generated by Django 5.2.3 on 2025-11-16 18:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0219_findmissingcoversjob"), + ("bookwyrm", "0221_user_force_password_reset"), + ] + + operations = [] diff --git a/bookwyrm/migrations/0223_sitesettings_disable_federation.py b/bookwyrm/migrations/0223_sitesettings_disable_federation.py new file mode 100644 index 0000000000..118bdf0cb1 --- /dev/null +++ b/bookwyrm/migrations/0223_sitesettings_disable_federation.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.3 on 2025-11-16 18:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0222_merge_20251116_1831"), + ] + + operations = [ + migrations.AddField( + model_name="sitesettings", + name="disable_federation", + field=models.BooleanField(default=False), + ), + ] diff --git a/bookwyrm/migrations/0224_book_bookwyrm_bo_remote__43009f_bloom_and_more.py b/bookwyrm/migrations/0224_book_bookwyrm_bo_remote__43009f_bloom_and_more.py new file mode 100644 index 0000000000..a2999d67f7 --- /dev/null +++ b/bookwyrm/migrations/0224_book_bookwyrm_bo_remote__43009f_bloom_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 5.2.9 on 2025-12-29 10:44 + +import django.contrib.postgres.indexes +from django.contrib.postgres.operations import BloomExtension +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0223_sitesettings_disable_federation'), + ] + + operations = [ + BloomExtension(), + migrations.AddIndex( + model_name='book', + index=django.contrib.postgres.indexes.BloomIndex(fields=['remote_id', 'openlibrary_key', 'finna_key', 'inventaire_id', 'librarything_key', 'goodreads_key', 'bnf_id', 'viaf', 'wikidata', 'asin', 'aasin', 'isfdb'], name='bookwyrm_bo_remote__43009f_bloom'), + ), + migrations.AddIndex( + model_name='edition', + index=django.contrib.postgres.indexes.BloomIndex(fields=['isbn_10', 'isbn_13', 'oclc_number'], name='bookwyrm_ed_isbn_10_435a21_bloom'), + ), + ] diff --git a/bookwyrm/migrations/0225_remove_book_bookwyrm_bo_remote__43009f_bloom_and_more.py b/bookwyrm/migrations/0225_remove_book_bookwyrm_bo_remote__43009f_bloom_and_more.py new file mode 100644 index 0000000000..9f58ef2ac5 --- /dev/null +++ b/bookwyrm/migrations/0225_remove_book_bookwyrm_bo_remote__43009f_bloom_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 5.2.9 on 2026-01-22 18:49 + +import django.contrib.postgres.indexes +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0224_book_bookwyrm_bo_remote__43009f_bloom_and_more'), + ] + + operations = [ + migrations.RemoveIndex( + model_name='book', + name='bookwyrm_bo_remote__43009f_bloom', + ), + migrations.AddIndex( + model_name='book', + index=django.contrib.postgres.indexes.BloomIndex(fields=['origin_id', 'remote_id', 'openlibrary_key', 'finna_key', 'inventaire_id', 'librarything_key', 'goodreads_key', 'bnf_id', 'viaf', 'wikidata', 'asin', 'aasin', 'isfdb'], name='bookwyrm_bo_origin__a05fb1_bloom'), + ), + ] diff --git a/bookwyrm/migrations/0226_remove_book_bookwyrm_bo_origin__a05fb1_bloom_and_more.py b/bookwyrm/migrations/0226_remove_book_bookwyrm_bo_origin__a05fb1_bloom_and_more.py new file mode 100644 index 0000000000..bc53f2f5d8 --- /dev/null +++ b/bookwyrm/migrations/0226_remove_book_bookwyrm_bo_origin__a05fb1_bloom_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 5.2.9 on 2026-01-24 17:29 + +import bookwyrm.models.fields +import django.contrib.postgres.indexes +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0225_remove_book_bookwyrm_bo_remote__43009f_bloom_and_more'), + ] + + operations = [ + migrations.RemoveIndex( + model_name='book', + name='bookwyrm_bo_origin__a05fb1_bloom', + ), + migrations.AddField( + model_name='author', + name='libris_key', + field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='book', + name='libris_key', + field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name='connector', + name='connector_file', + field=models.CharField(choices=[('openlibrary', 'Openlibrary'), ('inventaire', 'Inventaire'), ('bookwyrm_connector', 'Bookwyrm Connector'), ('finna', 'Finna'), ('libris', 'Libris')], max_length=255), + ), + migrations.AddIndex( + model_name='book', + index=django.contrib.postgres.indexes.BloomIndex(fields=['origin_id', 'remote_id', 'openlibrary_key', 'finna_key', 'libris_key', 'inventaire_id', 'librarything_key', 'goodreads_key', 'bnf_id', 'viaf', 'wikidata', 'asin', 'aasin', 'isfdb'], name='bookwyrm_bo_origin__d045df_bloom'), + ), + ] diff --git a/bookwyrm/migrations/0227_edition_bookwyrm_ed_parent__c4f87c_idx_and_more.py b/bookwyrm/migrations/0227_edition_bookwyrm_ed_parent__c4f87c_idx_and_more.py new file mode 100644 index 0000000000..0c8376cd4c --- /dev/null +++ b/bookwyrm/migrations/0227_edition_bookwyrm_ed_parent__c4f87c_idx_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 5.2.9 on 2026-01-27 15:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0226_remove_book_bookwyrm_bo_origin__a05fb1_bloom_and_more'), + ] + + operations = [ + migrations.AddIndex( + model_name='edition', + index=models.Index(fields=['parent_work', '-edition_rank'], name='bookwyrm_ed_parent__c4f87c_idx'), + ), + migrations.AddIndex( + model_name='list', + index=models.Index(fields=['privacy', '-updated_date'], name='bookwyrm_li_privacy_a38257_idx'), + ), + ] diff --git a/bookwyrm/migrations/0228_merge_20260203_2251.py b/bookwyrm/migrations/0228_merge_20260203_2251.py new file mode 100644 index 0000000000..666e0a5b62 --- /dev/null +++ b/bookwyrm/migrations/0228_merge_20260203_2251.py @@ -0,0 +1,14 @@ +# Generated by Django 5.2.9 on 2026-02-03 22:51 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0211_remove_report_status_alter_report_statuses'), + ('bookwyrm', '0227_edition_bookwyrm_ed_parent__c4f87c_idx_and_more'), + ] + + operations = [ + ] diff --git a/bookwyrm/migrations/0228_remove_user_bookwyrm_us_is_acti_972dc4_idx_and_more.py b/bookwyrm/migrations/0228_remove_user_bookwyrm_us_is_acti_972dc4_idx_and_more.py new file mode 100644 index 0000000000..19b3e18598 --- /dev/null +++ b/bookwyrm/migrations/0228_remove_user_bookwyrm_us_is_acti_972dc4_idx_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 5.2.9 on 2026-02-28 08:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('bookwyrm', '0227_edition_bookwyrm_ed_parent__c4f87c_idx_and_more'), + ] + + operations = [ + migrations.RemoveIndex( + model_name='user', + name='bookwyrm_us_is_acti_972dc4_idx', + ), + migrations.AddIndex( + model_name='user', + index=models.Index(fields=['local', 'is_active', 'last_active_date'], name='bookwyrm_us_local_40fe61_idx'), + ), + ] diff --git a/bookwyrm/migrations/0229_alter_report_statuses.py b/bookwyrm/migrations/0229_alter_report_statuses.py new file mode 100644 index 0000000000..0983f42535 --- /dev/null +++ b/bookwyrm/migrations/0229_alter_report_statuses.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.9 on 2026-02-03 22:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0228_merge_20260203_2251'), + ] + + operations = [ + migrations.AlterField( + model_name='report', + name='statuses', + field=models.ManyToManyField(blank=True, to='bookwyrm.status'), + ), + ] diff --git a/bookwyrm/migrations/0229_alter_review_name.py b/bookwyrm/migrations/0229_alter_review_name.py new file mode 100644 index 0000000000..ff04c118c9 --- /dev/null +++ b/bookwyrm/migrations/0229_alter_review_name.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.9 on 2026-04-25 07:17 + +import bookwyrm.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0228_remove_user_bookwyrm_us_is_acti_972dc4_idx_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='review', + name='name', + field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/bookwyrm/migrations/0229_series_mergedseries_seriesbook.py b/bookwyrm/migrations/0229_series_mergedseries_seriesbook.py new file mode 100644 index 0000000000..ac995eeb0f --- /dev/null +++ b/bookwyrm/migrations/0229_series_mergedseries_seriesbook.py @@ -0,0 +1,77 @@ +# Generated by Django 5.2.9 on 2026-05-16 10:45 + +import bookwyrm.models.activitypub_mixin +import bookwyrm.models.fields +import django.contrib.postgres.search +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0228_remove_user_bookwyrm_us_is_acti_972dc4_idx_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='Series', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_date', models.DateTimeField(auto_now_add=True)), + ('updated_date', models.DateTimeField(auto_now=True)), + ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), + ('origin_id', models.CharField(blank=True, max_length=255, null=True)), + ('openlibrary_key', bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True)), + ('finna_key', bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True)), + ('libris_key', bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True)), + ('inventaire_id', bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True)), + ('librarything_key', bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True)), + ('goodreads_key', bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True)), + ('bnf_id', bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True)), + ('viaf', bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True)), + ('wikidata', bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True)), + ('asin', bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True)), + ('aasin', bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True)), + ('isfdb', bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True)), + ('search_vector', django.contrib.postgres.search.SearchVectorField(null=True)), + ('name', bookwyrm.models.fields.TextField(max_length=255)), + ('alternative_names', bookwyrm.models.fields.ArrayField(base_field=bookwyrm.models.fields.CharField(max_length=255), blank=True, default=list, size=None)), + ('last_edited_by', bookwyrm.models.fields.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ('user', bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + bases=(bookwyrm.models.activitypub_mixin.OrderedCollectionMixin, bookwyrm.models.activitypub_mixin.ObjectMixin, models.Model), + ), + migrations.CreateModel( + name='MergedSeries', + fields=[ + ('deleted_id', models.IntegerField(primary_key=True, serialize=False)), + ('merged_into', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='absorbed', to='bookwyrm.series')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SeriesBook', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_date', models.DateTimeField(auto_now_add=True)), + ('updated_date', models.DateTimeField(auto_now=True)), + ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), + ('series_number', bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True)), + ('book', bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='seriesbooks', to='bookwyrm.book')), + ('series', bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='seriesbooks', to='bookwyrm.series')), + ('user', bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['series_number'], + 'unique_together': {('book', 'series'), ('series', 'series_number')}, + }, + bases=(bookwyrm.models.activitypub_mixin.CollectionItemMixin, models.Model), + ), + ] diff --git a/bookwyrm/migrations/0230_merge_20260522_2105.py b/bookwyrm/migrations/0230_merge_20260522_2105.py new file mode 100644 index 0000000000..37c10e382c --- /dev/null +++ b/bookwyrm/migrations/0230_merge_20260522_2105.py @@ -0,0 +1,14 @@ +# Generated by Django 5.2.14 on 2026-05-22 21:05 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0229_alter_review_name'), + ('bookwyrm', '0229_series_mergedseries_seriesbook'), + ] + + operations = [ + ] diff --git a/bookwyrm/migrations/0231_sitesettings_block_incoming_search_and_more.py b/bookwyrm/migrations/0231_sitesettings_block_incoming_search_and_more.py new file mode 100644 index 0000000000..93c29d5a8a --- /dev/null +++ b/bookwyrm/migrations/0231_sitesettings_block_incoming_search_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 5.2.14 on 2026-05-23 01:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0230_merge_20260522_2105'), + ] + + operations = [ + migrations.AddField( + model_name='sitesettings', + name='block_incoming_search', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='sitesettings', + name='require_login_nearly_everywhere', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='sitesettings', + name='require_signed_get', + field=models.BooleanField(default=False), + ), + ] diff --git a/bookwyrm/migrations/0232_merge_20260606_0829.py b/bookwyrm/migrations/0232_merge_20260606_0829.py new file mode 100644 index 0000000000..e1f99b3e0a --- /dev/null +++ b/bookwyrm/migrations/0232_merge_20260606_0829.py @@ -0,0 +1,14 @@ +# Generated by Django 5.2.14 on 2026-06-06 08:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0229_alter_report_statuses'), + ('bookwyrm', '0231_sitesettings_block_incoming_search_and_more'), + ] + + operations = [ + ] diff --git a/bookwyrm/migrations/0232_user_readwise_api_key.py b/bookwyrm/migrations/0232_user_readwise_api_key.py new file mode 100644 index 0000000000..3c52603364 --- /dev/null +++ b/bookwyrm/migrations/0232_user_readwise_api_key.py @@ -0,0 +1,17 @@ +"""add Readwise integration token""" + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("bookwyrm", "0231_sitesettings_block_incoming_search_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="user", + name="readwise_api_key", + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/bookwyrm/migrations/0233_author_born_precision_author_died_precision_and_more.py b/bookwyrm/migrations/0233_author_born_precision_author_died_precision_and_more.py new file mode 100644 index 0000000000..b03016338d --- /dev/null +++ b/bookwyrm/migrations/0233_author_born_precision_author_died_precision_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 5.2.14 on 2026-06-09 16:38 + +import bookwyrm.models.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0232_user_readwise_api_key'), + ] + + operations = [ + migrations.AddField( + model_name='author', + name='born_precision', + field=models.CharField(blank=True, choices=[('DAY', 'Day prec.'), ('MONTH', 'Month prec.'), ('YEAR', 'Year prec.')], editable=False, max_length=10, null=True), + ), + migrations.AddField( + model_name='author', + name='died_precision', + field=models.CharField(blank=True, choices=[('DAY', 'Day prec.'), ('MONTH', 'Month prec.'), ('YEAR', 'Year prec.')], editable=False, max_length=10, null=True), + ), + migrations.AlterField( + model_name='author', + name='born', + field=bookwyrm.models.fields.PartialDateField(blank=True, null=True), + ), + migrations.AlterField( + model_name='author', + name='died', + field=bookwyrm.models.fields.PartialDateField(blank=True, null=True), + ), + ] diff --git a/bookwyrm/migrations/0234_listitem_raw_notes.py b/bookwyrm/migrations/0234_listitem_raw_notes.py new file mode 100644 index 0000000000..d878beeb20 --- /dev/null +++ b/bookwyrm/migrations/0234_listitem_raw_notes.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.14 on 2026-06-09 16:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0233_author_born_precision_author_died_precision_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='listitem', + name='raw_notes', + field=models.TextField(blank=True, max_length=300, null=True), + ), + ] diff --git a/bookwyrm/migrations/0235_merge_20260615_1637.py b/bookwyrm/migrations/0235_merge_20260615_1637.py new file mode 100644 index 0000000000..6a9cc21484 --- /dev/null +++ b/bookwyrm/migrations/0235_merge_20260615_1637.py @@ -0,0 +1,14 @@ +# Generated by Django 5.2.14 on 2026-06-15 16:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0232_merge_20260606_0829'), + ('bookwyrm', '0234_listitem_raw_notes'), + ] + + operations = [ + ] diff --git a/bookwyrm/migrations/0236_user_blocked_books.py b/bookwyrm/migrations/0236_user_blocked_books.py new file mode 100644 index 0000000000..7262d47be4 --- /dev/null +++ b/bookwyrm/migrations/0236_user_blocked_books.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.14 on 2026-06-15 18:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0235_merge_20260615_1637'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='blocked_books', + field=models.ManyToManyField(related_name='blocked_by', to='bookwyrm.work'), + ), + ] diff --git a/bookwyrm/migrations/0237_alter_seriesbook_unique_together.py b/bookwyrm/migrations/0237_alter_seriesbook_unique_together.py new file mode 100644 index 0000000000..59e3e9febc --- /dev/null +++ b/bookwyrm/migrations/0237_alter_seriesbook_unique_together.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.14 on 2026-06-16 16:53 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0236_user_blocked_books'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='seriesbook', + unique_together={('book', 'series')}, + ), + ] diff --git a/bookwyrm/migrations/0238_user_is_profile_private.py b/bookwyrm/migrations/0238_user_is_profile_private.py new file mode 100644 index 0000000000..739629ce79 --- /dev/null +++ b/bookwyrm/migrations/0238_user_is_profile_private.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.14 on 2026-06-17 13:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0237_alter_seriesbook_unique_together'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='is_profile_private', + field=models.BooleanField(default=False), + ), + ] diff --git a/bookwyrm/migrations/0239_alter_list_options_alter_listitem_options_and_more.py b/bookwyrm/migrations/0239_alter_list_options_alter_listitem_options_and_more.py new file mode 100644 index 0000000000..86ea22135c --- /dev/null +++ b/bookwyrm/migrations/0239_alter_list_options_alter_listitem_options_and_more.py @@ -0,0 +1,85 @@ +# Generated by Django 5.2.14 on 2026-06-17 21:23 + +import bookwyrm.models.activitypub_mixin +import bookwyrm.models.fields +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0238_user_is_profile_private'), + ] + + operations = [ + migrations.AlterModelOptions( + name='list', + options={}, + ), + migrations.AlterModelOptions( + name='listitem', + options={}, + ), + migrations.RenameField( + model_name='listitem', + old_name='book', + new_name='edition', + ), + migrations.AlterUniqueTogether( + name='listitem', + unique_together={('edition', 'book_list'), ('order', 'book_list')}, + ), + migrations.RemoveField( + model_name='list', + name='books', + ), + migrations.AddField( + model_name='list', + name='editions', + field=models.ManyToManyField(through='bookwyrm.ListItem', through_fields=('book_list', 'edition'), to='bookwyrm.edition'), + ), + migrations.CreateModel( + name='SuggestionList', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_date', models.DateTimeField(auto_now_add=True)), + ('updated_date', models.DateTimeField(auto_now=True)), + ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), + ('embed_key', models.UUIDField(editable=False, null=True, unique=True)), + ('privacy', bookwyrm.models.fields.PrivacyField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('followers', 'Followers'), ('direct', 'Private')], default='public', max_length=255)), + ('suggests_for', bookwyrm.models.fields.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='suggestion_list', to='bookwyrm.work')), + ('user', bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ('-updated_date',), + 'abstract': False, + }, + bases=(bookwyrm.models.activitypub_mixin.OrderedCollectionMixin, models.Model), + ), + migrations.CreateModel( + name='SuggestionListItem', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_date', models.DateTimeField(auto_now_add=True)), + ('updated_date', models.DateTimeField(auto_now=True)), + ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), + ('notes', bookwyrm.models.fields.HtmlField(blank=True, max_length=300, null=True)), + ('raw_notes', models.TextField(blank=True, max_length=300, null=True)), + ('book_list', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookwyrm.suggestionlist')), + ('endorsement', models.ManyToManyField(related_name='suggestion_endorsers', to=settings.AUTH_USER_MODEL)), + ('user', bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ('work', bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.work')), + ], + options={ + 'unique_together': {('work', 'book_list')}, + }, + bases=(bookwyrm.models.activitypub_mixin.CollectionItemMixin, models.Model), + ), + migrations.AddField( + model_name='suggestionlist', + name='works', + field=models.ManyToManyField(through='bookwyrm.SuggestionListItem', through_fields=('book_list', 'work'), to='bookwyrm.work'), + ), + ] diff --git a/bookwyrm/migrations/0240_userupload_useruploadversion.py b/bookwyrm/migrations/0240_userupload_useruploadversion.py new file mode 100644 index 0000000000..260beafa2f --- /dev/null +++ b/bookwyrm/migrations/0240_userupload_useruploadversion.py @@ -0,0 +1,36 @@ +# Generated by Django 5.2.14 on 2026-06-24 23:45 + +import bookwyrm.models.user_upload +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0239_alter_list_options_alter_listitem_options_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='UserUpload', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('original_name', models.TextField()), + ('original_content_type', models.TextField()), + ('original_file', models.ImageField(upload_to=bookwyrm.models.user_upload.user_upload_directory_path)), + ('status', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user_image_uploads', to='bookwyrm.status')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_uploads', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='UserUploadVersion', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('max_dimension', models.TextField()), + ('file', models.ImageField(upload_to=bookwyrm.models.user_upload.user_upload_version_directory_path)), + ('user_upload', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='versions', to='bookwyrm.userupload')), + ], + ), + ] diff --git a/bookwyrm/migrations/0241_connector_latest_error_connector_most_recent_error_and_more.py b/bookwyrm/migrations/0241_connector_latest_error_connector_most_recent_error_and_more.py new file mode 100644 index 0000000000..d84fe0d82f --- /dev/null +++ b/bookwyrm/migrations/0241_connector_latest_error_connector_most_recent_error_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 5.2.14 on 2026-06-26 14:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0240_userupload_useruploadversion'), + ] + + operations = [ + migrations.AddField( + model_name='connector', + name='latest_error', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='connector', + name='most_recent_error', + field=models.DateTimeField(null=True), + ), + migrations.AddField( + model_name='connector', + name='most_recent_success', + field=models.DateTimeField(null=True), + ), + ] diff --git a/bookwyrm/models/__init__.py b/bookwyrm/models/__init__.py index 9c93d3b1ac..222f565ef3 100644 --- a/bookwyrm/models/__init__.py +++ b/bookwyrm/models/__init__.py @@ -1,14 +1,16 @@ -""" bring all the models into the app namespace """ +"""bring all the models into the app namespace""" + import inspect import sys -from .book import Book, Work, Edition, BookDataModel +from .book import Book, Work, Edition, BookDataModel, Series, SeriesBook from .author import Author from .link import Link, FileLink, LinkDomain from .connector import Connector from .shelf import Shelf, ShelfBook from .list import List, ListItem +from .list import SuggestionList, SuggestionListItem from .status import Status, GeneratedNote, Comment, Quotation from .status import Review, ReviewRating @@ -22,6 +24,7 @@ from .relationship import UserFollows, UserFollowRequest, UserBlocks from .report import Report, ReportAction from .federated_server import FederatedServer +from .user_upload import UserUpload, UserUploadVersion from .group import Group, GroupMember, GroupMemberInvitation diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 71f7b0ed90..dd559dc97c 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -1,4 +1,5 @@ -""" activitypub model functionality """ +"""activitypub model functionality""" + import asyncio from base64 import b64encode from collections import namedtuple @@ -15,6 +16,7 @@ from Crypto.Signature import pkcs1_15 from Crypto.Hash import SHA256 from django.apps import apps +from django.core.exceptions import PermissionDenied from django.core.paginator import Paginator from django.db.models import Q from django.utils.http import http_date @@ -66,7 +68,6 @@ def __init__(self, *args, **kwargs): ) if hasattr(self, "property_fields"): self.activity_fields += [ - # pylint: disable=cell-var-from-loop PropertyField(lambda a, o: set_activity_from_property_field(a, o, f)) for f in self.property_fields ] @@ -129,24 +130,21 @@ def find_existing(cls, data): def broadcast(self, activity, sender, software=None, queue=BROADCAST): """send out an activity""" + site_model = apps.get_model("bookwyrm.SiteSettings", require_ready=True) + try: + site_model.raise_federation_disabled() + except PermissionDenied: + return + + recipients = self.get_recipients(software=software) + if len(recipients) == 0: + return - # if we're posting about ShelfBooks, set a delay to give the base activity - # time to add the book on remote servers first to avoid race conditions - countdown = ( - 10 - if ( - isinstance(activity, object) - and not isinstance(activity["object"], str) - and activity["object"].get("type", None) in ["GeneratedNote", "Comment"] - ) - else 0 - ) broadcast_task.apply_async( - countdown=countdown, args=( sender.id, json.dumps(activity, cls=activitypub.ActivityEncoder), - self.get_recipients(software=software), + recipients, ), queue=queue, ) @@ -205,7 +203,7 @@ def to_activity_dataclass(self): activity = generate_activity(self) return self.activity_serializer(**activity) - def to_activity(self, **kwargs): # pylint: disable=unused-argument + def to_activity(self, **kwargs): """convert from a model to a json activity""" return self.to_activity_dataclass().serialize() @@ -404,11 +402,9 @@ def broadcast(self, activity, sender, software="bookwyrm", queue=BROADCAST): @property def privacy(self): - """inherit the privacy of the list, or direct if pending""" + """inherit the privacy of the list""" collection_field = getattr(self, self.collection_field) - if self.approved: - return collection_field.privacy - return "direct" + return collection_field.privacy @property def recipients(self): @@ -528,6 +524,11 @@ def unfurl_related_field(related_field, sort_field=None): @app.task(queue=BROADCAST) def broadcast_task(sender_id: int, activity: str, recipients: list[str]): """the celery task for broadcast""" + # checking this here ought to be redundant unless there are already-spawned tasks + # when federation is turned off. In that case this should prevent them from running. + site_model = apps.get_model("bookwyrm.SiteSettings", require_ready=True) + site_model.raise_federation_disabled() + user_model = apps.get_model("bookwyrm.User", require_ready=True) sender = user_model.objects.select_related("key_pair").get(id=sender_id) asyncio.run(async_broadcast(recipients, sender, activity)) @@ -596,7 +597,6 @@ async def sign_and_send( logger.exception(err) -# pylint: disable=unused-argument def to_ordered_collection_page( queryset, remote_id, id_only=False, page=1, pure=False, **kwargs ): diff --git a/bookwyrm/models/announcement.py b/bookwyrm/models/announcement.py index 4581dbdae2..a1a25fb418 100644 --- a/bookwyrm/models/announcement.py +++ b/bookwyrm/models/announcement.py @@ -1,4 +1,5 @@ -""" admin announcements """ +"""admin announcements""" + from django.db import models from django.db.models import Q from django.utils import timezone diff --git a/bookwyrm/models/annual_goal.py b/bookwyrm/models/annual_goal.py index d36b822df2..7cce07115f 100644 --- a/bookwyrm/models/annual_goal.py +++ b/bookwyrm/models/annual_goal.py @@ -1,11 +1,12 @@ -""" How many books do you want to read this year """ +"""How many books do you want to read this year""" + from django.core.validators import MinValueValidator from django.db import models from django.utils import timezone from bookwyrm.models.status import Review from .base_model import BookWyrmModel -from . import fields, Review +from . import fields def get_current_year(): diff --git a/bookwyrm/models/antispam.py b/bookwyrm/models/antispam.py index 1067cbf1d7..f6bae08326 100644 --- a/bookwyrm/models/antispam.py +++ b/bookwyrm/models/antispam.py @@ -1,4 +1,5 @@ -""" Lets try NOT to sell viagra """ +"""Lets try NOT to sell viagra""" + from functools import reduce import operator @@ -101,7 +102,7 @@ def automod_users(reporter): reduce(operator.or_, (Q(**f) for f in filters)), is_active=True, local=True, - report__isnull=True, # don't flag users that already have reports + reported_user__isnull=True, # don't flag users that already have reports ).distinct() report_model = apps.get_model("bookwyrm", "Report", require_ready=True) @@ -109,9 +110,9 @@ def automod_users(reporter): return report_model.objects.bulk_create( [ report_model( - reporter=reporter, + user=reporter, note=_("Automatically generated report"), - user=u, + reported_user=u, ) for u in users ] @@ -140,14 +141,14 @@ def automod_statuses(reporter): ).distinct() report_model = apps.get_model("bookwyrm", "Report", require_ready=True) - return report_model.objects.bulk_create( - [ - report_model( - reporter=reporter, + reports = [] + for status in statuses: + with transaction.atomic(): + report = report_model.objects.create( + user=reporter, note=_("Automatically generated report"), - user=s.user, - status=s, + reported_user=status.user, ) - for s in statuses - ] - ) + report.statuses.add(status) + reports.append(report) + return reports diff --git a/bookwyrm/models/attachment.py b/bookwyrm/models/attachment.py index c8b2e51c2d..ae3fad950a 100644 --- a/bookwyrm/models/attachment.py +++ b/bookwyrm/models/attachment.py @@ -1,4 +1,5 @@ -""" media that is posted in the app """ +"""media that is posted in the app""" + from django.db import models from bookwyrm import activitypub diff --git a/bookwyrm/models/author.py b/bookwyrm/models/author.py index 20c4e9e005..3a8d9ebd8b 100644 --- a/bookwyrm/models/author.py +++ b/bookwyrm/models/author.py @@ -1,4 +1,4 @@ -""" database schema for info about authors """ +"""database schema for info about authors""" import re from typing import Any @@ -37,8 +37,8 @@ class Author(BookDataModel): max_length=255, blank=True, null=True, deduplication_field=True ) # idk probably other keys would be useful here? - born = fields.DateTimeField(blank=True, null=True) - died = fields.DateTimeField(blank=True, null=True) + born = fields.PartialDateField(blank=True, null=True) + died = fields.PartialDateField(blank=True, null=True) name = fields.CharField(max_length=255) aliases = fields.ArrayField( models.CharField(max_length=255), blank=True, default=list @@ -75,8 +75,6 @@ def get_remote_id(self): class Meta: """sets up indexes and triggers""" - # pylint: disable=line-too-long - indexes = (GinIndex(fields=["search_vector"]),) triggers = [ pgtrigger.Trigger( diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py index 814e59a733..67e91acc1f 100644 --- a/bookwyrm/models/base_model.py +++ b/bookwyrm/models/base_model.py @@ -1,4 +1,5 @@ -""" base model with default fields """ +"""base model with default fields""" + import base64 from Crypto import Random @@ -93,7 +94,6 @@ def raise_visible_to_user(self, viewer): self.privacy in ["direct", "followers"] and self.mention_users.filter(id=viewer.id).first() ): - return # you can see groups of which you are a member @@ -149,7 +149,7 @@ def privacy_filter(cls, viewer, privacy_levels=None): # you can't see followers only or direct messages if you're not logged in if viewer.is_anonymous: privacy_levels = [ - p for p in privacy_levels if not p in ["followers", "direct"] + p for p in privacy_levels if p not in ["followers", "direct"] ] else: # exclude blocks from both directions @@ -186,7 +186,6 @@ def direct_filter(cls, queryset, viewer): @receiver(models.signals.post_save) -# pylint: disable=unused-argument def set_remote_id(sender, instance, created, *args, **kwargs): """set the remote_id after save (when the id is available)""" if not created or not hasattr(instance, "get_remote_id"): diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 164f8e9322..0df4fb3444 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -1,16 +1,17 @@ -""" database schema for books and shelves """ +"""database schema for books and shelves""" from itertools import chain +from functools import reduce import re +import operator from typing import Any, Dict, Optional, Iterable from typing_extensions import Self - from django.contrib.postgres.search import SearchVectorField -from django.contrib.postgres.indexes import GinIndex +from django.contrib.postgres.indexes import GinIndex, BloomIndex, Index from django.core.cache import cache -from django.core.exceptions import ValidationError +from django.core.exceptions import ValidationError, PermissionDenied from django.db import models, transaction -from django.db.models import Prefetch, ManyToManyField +from django.db.models import Prefetch, ManyToManyField, Q from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ from model_utils import FieldTracker @@ -30,7 +31,12 @@ ) from bookwyrm.utils.db import format_trigger, add_update_fields -from .activitypub_mixin import OrderedCollectionPageMixin, ObjectMixin +from .activitypub_mixin import ( + OrderedCollectionMixin, + CollectionItemMixin, + OrderedCollectionPageMixin, + ObjectMixin, +) from .base_model import BookWyrmModel from . import fields @@ -45,6 +51,9 @@ class BookDataModel(ObjectMixin, BookWyrmModel): finna_key = fields.CharField( max_length=255, blank=True, null=True, deduplication_field=True ) + libris_key = fields.CharField( + max_length=255, blank=True, null=True, deduplication_field=True + ) inventaire_id = fields.CharField( max_length=255, blank=True, null=True, deduplication_field=True ) @@ -100,6 +109,16 @@ def finna_link(self): """generate the url from the finna key""" return f"http://finna.fi/Record/{self.finna_key}" + @property + def wikidata_link(self): + """generate the url from the isfdb id""" + return f"https://www.wikidata.org/wiki/{self.wikidata}" + + @property + def libris_link(self): + """generate the url from the libris key""" + return f"https://libris.kb.se/bib/{self.libris_key}" + class Meta: """can't initialize this model, that wouldn't make sense""" @@ -119,7 +138,6 @@ def save( super().save(*args, update_fields=update_fields, **kwargs) - # pylint: disable=arguments-differ def broadcast(self, activity, sender, software="bookwyrm", **kwargs): """only send book data updates to other bookwyrm instances""" super().broadcast(activity, sender, software=software, **kwargs) @@ -148,7 +166,7 @@ def merge_into(self, canonical: Self, dry_run=False) -> Dict[str, Any]: # the linking table anyway. If we update it through that model # instead then we won’t lose the extra fields in the linking # table. - # pylint: disable=protected-access + related_field_obj = related_model._meta.get_field(related_field) if isinstance(related_field_obj, ManyToManyField): through = related_field_obj.remote_field.through @@ -227,6 +245,14 @@ class MergedAuthor(MergedBookDataModel): ) +class MergedSeries(MergedBookDataModel): + """an Series that has been merged into another one""" + + merged_into = models.ForeignKey( + "Series", on_delete=models.PROTECT, related_name="absorbed" + ) + + class Book(BookDataModel): """a generic book, which can mean either an edition or a work""" @@ -242,8 +268,11 @@ class Book(BookDataModel): languages = fields.ArrayField( models.CharField(max_length=255), blank=True, default=list ) + + # these legacy fields are still used for editing and as a fallback: series = fields.TextField(max_length=255, blank=True, null=True) series_number = fields.CharField(max_length=255, blank=True, null=True) + subjects = fields.ArrayField( models.CharField(max_length=255), blank=True, null=True, default=list ) @@ -371,10 +400,16 @@ def guess_sort_title(self, user=None): *(LANGUAGE_ARTICLES[language].get("articles") for language in lang_codes) ) - return re.sub(f'^{" |^".join(articles)} ', "", str(self.title).lower()) + return re.sub(f"^{' |^'.join(articles)} ", "", str(self.title).lower()) + + def book_series(self): + """get the series this book is in""" + series = set() + for sb in self.seriesbooks.all(): + series.add(sb.series) + return list(series) def __repr__(self): - # pylint: disable=consider-using-f-string return "<{} key={!r} title={!r}>".format( self.__class__, self.openlibrary_key, @@ -384,9 +419,28 @@ def __repr__(self): class Meta: """set up indexes and triggers""" - # pylint: disable=line-too-long - - indexes = (GinIndex(fields=["search_vector"]),) + indexes = [ + GinIndex(fields=["search_vector"]), + # Add bloom index for all deduplication_fields + BloomIndex( + fields=[ + "origin_id", + "remote_id", + "openlibrary_key", + "finna_key", + "libris_key", + "inventaire_id", + "librarything_key", + "goodreads_key", + "bnf_id", + "viaf", + "wikidata", + "asin", + "aasin", + "isfdb", + ] + ), + ] triggers = [ pgtrigger.Trigger( name="update_search_vector_on_book_edit", @@ -464,8 +518,13 @@ def to_edition_list(self, **kwargs): serialize_reverse_fields = [ ("editions", "editions", "-edition_rank"), ("file_links", "fileLinks", "-created_date"), + ("seriesbooks", "seriesBooks", "-created_date"), + ] + deserialize_reverse_fields = [ + ("editions", "editions"), + ("file_links", "fileLinks"), + ("seriesbooks", "seriesBooks"), ] - deserialize_reverse_fields = [("editions", "editions"), ("file_links", "fileLinks")] # https://schema.org/BookFormatType @@ -538,18 +597,18 @@ def validate_isbn13(maybe_isbn: str) -> None: _("%(value)s doesn't look like an ISBN"), params={"value": maybe_isbn} ) - if (isbn10_version := isbn_13_to_10(normalized_isbn)) is None: - raise ValidationError( - _("%(value)s doesn't look like an ISBN"), params={"value": maybe_isbn} - ) - - if (checksum_version := isbn_10_to_13(isbn10_version)) is None: - raise ValidationError( - _("%(value)s doesn't look like an ISBN"), params={"value": maybe_isbn} - ) + # calculate checksum version + checksum_version = normalized_isbn[:-1] + checksum = sum(int(i) for i in checksum_version[::2]) + sum( + int(i) * 3 for i in checksum_version[1::2] + ) + checkdigit = checksum % 10 + if checkdigit != 0: + checkdigit = 10 - checkdigit + checksum_version += str(checkdigit) - # We might have 978 or 979 prefix, so ignore that on comparing - if checksum_version[3:] != normalized_isbn[3:]: + # Check if we got same checksum + if checksum_version != normalized_isbn: raise ValidationError( _( "%(value)s doesn't have correct ISBN checksum, " @@ -608,8 +667,97 @@ class Edition(Book): activity_serializer = activitypub.Edition name_field = "title" - serialize_reverse_fields = [("file_links", "fileLinks", "-created_date")] - deserialize_reverse_fields = [("file_links", "fileLinks")] + serialize_reverse_fields = [ + ("file_links", "fileLinks", "-created_date"), + ("seriesbooks", "seriesBooks", "-created_date"), + ] + deserialize_reverse_fields = [ + ("file_links", "fileLinks"), + ("seriesbooks", "seriesBooks"), + ] + + class Meta: + indexes = [ + BloomIndex( + fields=[ + "isbn_10", + "isbn_13", + "oclc_number", + ] + ), + Index(fields=["parent_work", "-edition_rank"]), + ] + + @classmethod + def find_existing(cls, data): + """compare data to fields that can be used for deduplication. + This always includes remote_id, but can also be unique identifiers + like an isbn for an edition""" + filters = [] + # grabs all the data from the model to create django queryset filters + for field in cls._meta.get_fields(): + if ( + not hasattr(field, "deduplication_field") + or not field.deduplication_field + ): + continue + + value = data.get(field.get_activitypub_field()) + if not value: + continue + filters.append({field.name: value}) + + if "id" in data: + # kinda janky, but this handles special case for books + filters.append({"origin_id": data["id"]}) + + if not filters: + # if there are no deduplication fields, it will match the first + # item no matter what. this shouldn't happen but just in case. + return None + + # For books, we want to first check with isbn10/13/oclc fields if possible + # as it hits Edition table bloom index + book_filters = [] + for filter_item in filters: + filter_fields = { + field_name: field_value + for field_name, field_value in filter_item.items() + if field_name in ["isbn_10", "isbn_13", "oclc_number"] + } + if filter_fields: + book_filters.append(filter_fields) + objects = cls.objects + if hasattr(objects, "select_subclasses"): + objects = objects.select_subclasses() + + if book_filters: + possible_book = objects.filter( + reduce(operator.or_, (Q(**f) for f in book_filters)) + ) + if (book := possible_book.first()) is not None: + return book + + # if no match, drop isbn10/13/oclc_number from filters so bloom index hits from Book-table + book_filters = [] + for filter_item in filters: + filter_fields = { + field_name: field_value + for field_name, field_value in filter_item.items() + if field_name not in ["isbn_10", "isbn_13", "oclc_number"] + } + if filter_fields: + book_filters.append(filter_fields) + + filters = book_filters + + if not filters: + return None + + # an OR operation on all the match fields, sorry for the dense syntax + match = objects.filter(reduce(operator.or_, (Q(**f) for f in filters))) + # there OUGHT to be only one match + return match.first() @property def hyphenated_isbn13(self): @@ -639,6 +787,15 @@ def get_rank(self): # max rank is 9 return rank + def clean(self): + """Don't try to add a series the book is already part of""" + if self.pk and self.series: + if self.parent_work.seriesbooks.filter( + Q(series__name__iexact=self.series) + | Q(series__alternative_names__icontains=self.series) + ): + raise ValidationError({"series": _("Book is already in this series")}) + def save( self, *args: Any, update_fields: Optional[Iterable[str]] = None, **kwargs: Any ) -> None: @@ -697,11 +854,16 @@ def repair(self): @classmethod def viewer_aware_objects(cls, viewer): - """annotate a book query with metadata related to the user""" + """filter blocked books and annotate a book query with metadata related to the user""" queryset = cls.objects + if not viewer or not viewer.is_authenticated: return queryset + queryset = queryset.exclude( + parent_work__in=viewer.blocked_books.values_list("id", flat=True) + ) + queryset = queryset.prefetch_related( Prefetch( "shelfbook_set", @@ -741,7 +903,7 @@ def isbn_10_to_13(isbn_10): def isbn_13_to_10(isbn_13): """convert isbn 13 to 10, if possible""" - if isbn_13[:3] not in ["978", "979"]: + if isbn_13[:3] not in ["978"]: return None isbn_13 = re.sub(r"[^0-9X]", "", isbn_13) @@ -755,7 +917,9 @@ def isbn_13_to_10(isbn_13): except ValueError: return None checkdigit = checksum % 11 - checkdigit = 11 - checkdigit + checkdigit = ( + 11 - checkdigit + ) % 11 # we calculate checkdigit to make the whole sum % 11 == 0 if checkdigit == 10: checkdigit = "X" return converted + str(checkdigit) @@ -766,7 +930,6 @@ def normalize_isbn(isbn): return re.sub(r"[^0-9X]", "", isbn) -# pylint: disable=unused-argument @receiver(models.signals.post_save, sender=Edition) def preview_image(instance, *args, **kwargs): """create preview image on book create""" @@ -780,3 +943,79 @@ def preview_image(instance, *args, **kwargs): transaction.on_commit( lambda: generate_edition_preview_image_task.delay(instance.id) ) + + +class Series(OrderedCollectionMixin, BookDataModel): + """a series of books""" + + user = fields.ForeignKey( + "User", on_delete=models.PROTECT, activitypub_field="actor", related_name="+" + ) # for broadcast, should always be instance user but we can't set that here + name = fields.TextField(max_length=255) + alternative_names = fields.ArrayField( + fields.CharField(max_length=255), blank=True, default=list + ) # like aliases on an author + + activity_serializer = activitypub.Series + + def get_remote_id(self): + """series need a remote id""" + return f"{BASE_URL}/series/{self.id}" + + @property + def collection_queryset(self): + """list of books for this series, overrides OrderedCollectionMixin""" + seriesbooks = self.seriesbooks.all().values("book__pk") + works = Work.objects.filter(id__in=seriesbooks) + books = Edition.objects.filter(parent_work__in=works).order_by("-updated_date") + return books + + def raise_not_editable(self, viewer): + if not viewer.has_perm("bookwyrm.edit_book"): + raise PermissionDenied() + + @property + def isfdb_link(self): + """generate the url from the isfdb id""" + return f"https://www.isfdb.org/cgi-bin/pe.cgi?{self.isfdb}" + + +class SeriesBook(CollectionItemMixin, BookWyrmModel): + """connect a book to a series with a series number""" + + user = fields.ForeignKey( + "User", on_delete=models.PROTECT, activitypub_field="actor", related_name="+" + ) # for broadcast, should always be instance user but we can't set that here + series = fields.ForeignKey( + "Series", on_delete=models.CASCADE, related_name="seriesbooks" + ) + book = fields.ForeignKey( + "Book", on_delete=models.CASCADE, related_name="seriesbooks" + ) + series_number = fields.CharField(max_length=255, blank=True, null=True) + + collection_field = "series" + activity_serializer = activitypub.SeriesBook + + class Meta: + ordering = ["series_number"] + unique_together = [("book", "series")] + + def get_remote_id(self): + """need a remote id to provide the URI for series""" + return f"{BASE_URL}/seriesbook/{self.id}" + + def raise_not_editable(self, viewer): + if not viewer.has_perm("bookwyrm.edit_book"): + raise PermissionDenied() + + @property + def natural_sort_key(self): + """numeric-aware key for series_number, so '2' sorts before '10'""" + value = self.series_number + if value is None or len(value) == 0: + return float("inf"), "" + match = re.match(r"\d+(?:\.\d+)?", value) + numeric_prefix = float(match.group()) if match else float("inf") + rest = value[match.end() :] if match else value + return numeric_prefix, rest diff --git a/bookwyrm/models/bookwyrm_export_job.py b/bookwyrm/models/bookwyrm_export_job.py index 8fbb659ad0..19a2332098 100644 --- a/bookwyrm/models/bookwyrm_export_job.py +++ b/bookwyrm/models/bookwyrm_export_job.py @@ -6,6 +6,7 @@ from boto3.session import Session as BotoSession from s3_tar import S3Tar +from django.db import transaction from django.db.models import FileField, JSONField from django.core.serializers.json import DjangoJSONEncoder from django.core.files.base import ContentFile @@ -28,7 +29,7 @@ class BookwyrmAwsSession(BotoSession): """a boto session that always uses settings.AWS_S3_ENDPOINT_URL""" def client(self, *args, **kwargs): - kwargs["endpoint_url"] = settings.AWS_S3_ENDPOINT_URL + kwargs["endpoint_url"] = storages["exports"].endpoint_url return super().client("s3", *args, **kwargs) @@ -59,26 +60,27 @@ def create_export_json_task(**kwargs): if job.status == "stopped": return - try: - # generate JSON - data = export_user(job.user) - data["settings"] = export_settings(job.user) - data["goals"] = export_goals(job.user) - data["books"] = export_books(job.user) - data["saved_lists"] = export_saved_lists(job.user) - data["follows"] = export_follows(job.user) - data["blocks"] = export_blocks(job.user) - job.export_json = data - job.save(update_fields=["export_json"]) - - # trigger task to create tar file - create_archive_task.delay(job_id=job.id) - - except Exception as err: # pylint: disable=broad-except - logger.exception( - "create_export_json_task for job %s failed with error: %s", job.id, err - ) - job.set_status("failed") + with transaction.atomic(): + try: + # generate JSON + data = export_user(job.user) + data["settings"] = export_settings(job.user) + data["goals"] = export_goals(job.user) + data["books"] = export_books(job.user) + data["saved_lists"] = export_saved_lists(job.user) + data["follows"] = export_follows(job.user) + data["blocks"] = export_blocks(job.user) + job.export_json = data + job.save(update_fields=["export_json"]) + + # trigger task to create tar file + create_archive_task.delay(job_id=job.id) + + except Exception as err: + logger.exception( + "create_export_json_task for job %s failed with error: %s", job.id, err + ) + job.set_status("failed") def archive_file_location(file, directory="") -> str: @@ -86,17 +88,6 @@ def archive_file_location(file, directory="") -> str: return os.path.join(directory, file.name) -def add_file_to_s3_tar(s3_tar: S3Tar, storage, file, directory=""): - """ - add file to S3Tar inside directory, keeping any directories under its - storage location - """ - s3_tar.add_file( - os.path.join(storage.location, file.name), - folder=os.path.dirname(archive_file_location(file, directory=directory)), - ) - - @app.task(queue=IMPORTS, base=ParentTask) def create_archive_task(**kwargs): """create the archive containing the JSON file and additional files""" @@ -112,12 +103,10 @@ def create_archive_task(**kwargs): archive_filename = f"{export_task_id}.tar.gz" export_json_bytes = DjangoJSONEncoder().encode(job.export_json).encode("utf-8") user = job.user + exports_storage = storages["exports"] - if settings.USE_S3: - # Storage for writing temporary files - exports_storage = storages["exports"] - - # Handle for creating the final archive + if settings.USE_S3_FOR_EXPORTS: + # Handle creating the final archive s3_tar = S3Tar( exports_storage.bucket_name, os.path.join(exports_storage.location, archive_filename), @@ -134,11 +123,12 @@ def create_archive_task(**kwargs): os.path.join(exports_storage.location, export_json_tmp_file) ) - # Add avatar to TAR - images_storage = storages["default"] - if user.avatar: - add_file_to_s3_tar(s3_tar, images_storage, user.avatar) + exports_storage.save(user.avatar.name, user.avatar) + s3_tar.add_file( + os.path.join(exports_storage.location, user.avatar.name), + folder="avatars", + ) # Create archive and store file name s3_tar.tar() @@ -147,8 +137,12 @@ def create_archive_task(**kwargs): # Delete temporary files exports_storage.delete(export_json_tmp_file) + exports_storage.delete(user.avatar.name) else: + # exports saved to local storage + # this is the default even when using S3 for other files + # Use the scheduled task to periodically delete these job.export_data = archive_filename with job.export_data.open("wb") as tar_file: with BookwyrmTarFile.open(mode="w:gz", fileobj=tar_file) as tar: @@ -163,7 +157,7 @@ def create_archive_task(**kwargs): job.complete_job() - except Exception as err: # pylint: disable=broad-except + except Exception as err: logger.exception( "create_archive_task for job %s failed with error: %s", job.id, err ) @@ -193,7 +187,7 @@ def export_settings(user: User): def export_saved_lists(user: User): """add user saved lists to export JSON""" - return [l.remote_id for l in user.saved_lists.all()] + return [saved_list.remote_id for saved_list in user.saved_lists.all()] def export_follows(user: User): @@ -251,14 +245,14 @@ def export_book(user: User, edition: Edition): # Lists and ListItems # ListItems include "notes" and "approved" so we need them # even though we know it's this book - list_items = ListItem.objects.filter(book=edition, user=user).distinct() + list_items = ListItem.objects.filter(edition=edition, user=user).distinct() data["lists"] = [] for item in list_items: list_info = item.book_list.to_activity() - list_info[ - "privacy" - ] = item.book_list.privacy # this isn't serialized so we add it + list_info["privacy"] = ( + item.book_list.privacy + ) # this isn't serialized so we add it list_info["list_item"] = item.to_activity() data["lists"].append(list_info) @@ -298,24 +292,37 @@ def export_book(user: User, edition: Edition): def get_books_for_user(user): """ Get all the books and editions related to a user. - We use union() instead of Q objects because it creates + We use selecting book_id instead of Q objects because it creates multiple simple queries instead of a complex DB query that can time out. """ - shelf_eds = Edition.objects.select_related("parent_work").filter(shelves__user=user) - rt_eds = Edition.objects.select_related("parent_work").filter( - readthrough__user=user + shelf_ids = ShelfBook.objects.filter(user=user).values_list("book_id", flat=True) + readthrough = ReadThrough.objects.filter(user=user).values_list( + "book_id", flat=True ) - review_eds = Edition.objects.select_related("parent_work").filter(review__user=user) - list_eds = Edition.objects.select_related("parent_work").filter(list__user=user) - comment_eds = Edition.objects.select_related("parent_work").filter( - comment__user=user, comment__deleted=False + reviews = Review.objects.filter(user=user).values_list("book_id", flat=True) + lists = ListItem.objects.filter(user=user).values_list("edition_id", flat=True) + comments = Comment.objects.filter(user=user, deleted=False).values_list( + "book_id", flat=True ) - quote_eds = Edition.objects.select_related("parent_work").filter( - quotation__user=user, quotation__deleted=False + quotes = Quotation.objects.filter(user=user, deleted=False).values_list( + "book_id", flat=True ) - editions = shelf_eds.union(rt_eds, review_eds, list_eds, comment_eds, quote_eds) + editions = ( + Edition.objects.select_related("parent_work") + .filter( + id__in=( + set(shelf_ids) + | set(readthrough) + | set(reviews) + | set(lists) + | set(comments) + | set(quotes) + ) + ) + .distinct() + ) return editions diff --git a/bookwyrm/models/bookwyrm_import_job.py b/bookwyrm/models/bookwyrm_import_job.py index d679be2928..ebc31c726e 100644 --- a/bookwyrm/models/bookwyrm_import_job.py +++ b/bookwyrm/models/bookwyrm_import_job.py @@ -217,7 +217,7 @@ class UserImportSubTask(SubTask): def before_start(self, task_id, args, kwargs): """Handler called before the task starts.""" - model = apps.get_model(f'bookwyrm.{kwargs["job_type"]}', require_ready=True) + model = apps.get_model(f"bookwyrm.{kwargs['job_type']}", require_ready=True) child_job = model.objects.get(id=kwargs["child_id"]) child_job.task_id = task_id child_job.save(update_fields=["task_id"]) @@ -227,12 +227,11 @@ def on_success(self, retval, task_id, args, kwargs): """Run by the worker if the task executes successfully""" # we want to complete our own UserImportBook job, not ChildJob - model = apps.get_model(f'bookwyrm.{kwargs["job_type"]}', require_ready=True) + model = apps.get_model(f"bookwyrm.{kwargs['job_type']}", require_ready=True) subtask = model.objects.get(id=kwargs["child_id"]) subtask.complete_job() -# pylint: disable=too-many-branches @app.task(queue=IMPORTS, base=ImportUserTask) def start_import_task(**kwargs): """trigger the child import tasks for each user data @@ -297,7 +296,6 @@ def start_import_task(**kwargs): requests.exceptions.ConnectionError, ConnectionRefusedError, ): - origin_is_ok = False for data in job.import_data.get("books"): @@ -306,7 +304,7 @@ def start_import_task(**kwargs): archive_file.close() - except Exception as err: # pylint: disable=broad-except + except Exception as err: logger.error( "User Import Job %s Failed with error: %s", job.id, err, exc_info=True ) @@ -349,7 +347,7 @@ def create_book_from_json(book_data): @app.task(queue=IMPORTS, base=UserImportSubTask) -def import_book_task(**kwargs): # pylint: disable=too-many-branches +def import_book_task(**kwargs): """Take work and edition data, find or create the edition and work in the database""" @@ -364,7 +362,6 @@ def import_book_task(**kwargs): # pylint: disable=too-many-branches edition = book_data.get("edition") book = models.Edition.find_existing(edition) if not book: - if kwargs["origin_is_ok"]: # try importing from the instance the user is coming from remote_id = edition.get("id") @@ -391,7 +388,7 @@ def import_book_task(**kwargs): # pylint: disable=too-many-branches if "include_lists" in required: upsert_lists(task.parent_job.user, book.id, book_data.get("lists")) - except Exception as err: # pylint: disable=broad-except + except Exception as err: logger.error( "Book Import Task %s for Job %s Failed with error: %s", task.id, job.id, err ) @@ -461,15 +458,12 @@ def upsert_status_task(**kwargs): status["cc"] = update_followers_address(user, status["cc"]) status[ "replies" - ] = ( - {} - ) # this parses incorrectly but we can't set it without knowing the new id + ] = {} # this parses incorrectly but we can't set it without knowing the new id status["inReplyToBook"] = task.book.remote_id parsed = activitypub.parse(status) if not status_already_exists( user, parsed ): # don't duplicate posts on multiple import - instance = parsed.to_model( model=status_class, save=True, overwrite=True ) @@ -497,7 +491,7 @@ def upsert_status_task(**kwargs): task.save(update_fields=["fail_reason"]) task.set_status("failed") - except Exception as err: # pylint: disable=broad-except + except Exception as err: logger.error("User Status Import Task %s Failed with error: %s", task.id, err) task.fail_reason = _("Unknown error importing book status") task.save(update_fields=["fail_reason"]) @@ -509,7 +503,6 @@ def upsert_readthroughs(user, book_id, data): find or create the instances in the database""" for read_through in data: - obj = {} keys = [ "progress_mode", @@ -547,7 +540,6 @@ def upsert_lists( for blist in lists: booklist = models.List.objects.filter(name=blist["name"], user=user).first() if not booklist: - blist["owner"] = user.remote_id parsed = activitypub.parse(blist) booklist = parsed.to_model(model=models.List, save=True, overwrite=True) @@ -555,13 +547,13 @@ def upsert_lists( booklist.privacy = blist["privacy"] booklist.save() - item = models.ListItem.objects.filter(book=book, book_list=booklist).exists() + item = models.ListItem.objects.filter(edition=book, book_list=booklist).exists() if not item: - count = booklist.books.count() + count = booklist.editions.count() notes = blist["list_item"].get("notes", "") approved = blist["list_item"].get("approved", False) models.ListItem.objects.create( - book=book, + edition=book, book_list=booklist, user=user, notes=notes, @@ -575,7 +567,6 @@ def upsert_shelves(user, book, shelves): DB entries if they don't already exist""" for shelf in shelves: - book_shelf = models.Shelf.objects.filter(name=shelf["name"], user=user).first() if not book_shelf: @@ -617,7 +608,7 @@ def update_user_settings(user, data): ("discoverable", "discoverable"), ] - for (ap_field, bw_field) in ap_fields: + for ap_field, bw_field in ap_fields: setattr(user, bw_field, data[ap_field]) bw_fields = [ @@ -669,7 +660,6 @@ def import_user_relationship_task(**kwargs): try: if task.relationship == "follow": - followee = activitypub.resolve_remote_id(task.remote_id, models.User) if followee: ( @@ -698,7 +688,6 @@ def import_user_relationship_task(**kwargs): task.set_status("failed") elif task.relationship == "block": - user_object = activitypub.resolve_remote_id(task.remote_id, models.User) if user_object: exists = models.UserBlocks.objects.filter( @@ -733,7 +722,7 @@ def import_user_relationship_task(**kwargs): task.save(update_fields=["fail_reason"]) task.set_status("failed") - except Exception as err: # pylint: disable=broad-except + except Exception as err: logger.error( "User Import Relationship Task %s Failed with error: %s", task.id, err ) diff --git a/bookwyrm/models/connector.py b/bookwyrm/models/connector.py index d62f97f02e..5819b205f1 100644 --- a/bookwyrm/models/connector.py +++ b/bookwyrm/models/connector.py @@ -1,4 +1,5 @@ -""" manages interfaces with external sources of book data """ +"""manages interfaces with external sources of book data""" + from typing import Optional from django.db import models @@ -22,6 +23,9 @@ class Connector(BookWyrmModel): deactivation_reason = models.CharField( max_length=255, choices=DeactivationReason, null=True, blank=True ) + most_recent_success = models.DateTimeField(null=True) + most_recent_error = models.DateTimeField(null=True) + latest_error = models.CharField(max_length=255, null=True, blank=True) base_url = models.CharField(max_length=255) books_url = models.CharField(max_length=255) diff --git a/bookwyrm/models/favorite.py b/bookwyrm/models/favorite.py index 98fbce550d..10a766201f 100644 --- a/bookwyrm/models/favorite.py +++ b/bookwyrm/models/favorite.py @@ -1,4 +1,5 @@ -""" like/fav/star a status """ +"""like/fav/star a status""" + from django.db import models from bookwyrm import activitypub @@ -20,7 +21,6 @@ class Favorite(ActivityMixin, BookWyrmModel): activity_serializer = activitypub.Like - # pylint: disable=unused-argument @classmethod def ignore_activity(cls, activity, allow_external_connections=True): """don't bother with incoming favs of unknown statuses""" diff --git a/bookwyrm/models/federated_server.py b/bookwyrm/models/federated_server.py index 5e08fc11d6..599701efc4 100644 --- a/bookwyrm/models/federated_server.py +++ b/bookwyrm/models/federated_server.py @@ -1,4 +1,5 @@ -""" connections to external ActivityPub servers """ +"""connections to external ActivityPub servers""" + from urllib.parse import urlparse from django.apps import apps diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index 3cf455603e..f31c1d54b2 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -1,4 +1,5 @@ -""" activitypub-aware django model fields """ +"""activitypub-aware django model fields""" + from dataclasses import MISSING from datetime import datetime import re @@ -15,7 +16,7 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django.utils.encoding import filepath_to_uri -from markdown import markdown +import mistune from bookwyrm import activitypub from bookwyrm.connectors import get_image @@ -129,7 +130,6 @@ def field_to_activity(self, value): return {self.activitypub_wrapper: value} return value - # pylint: disable=unused-argument def field_from_activity(self, value, allow_external_connections=True, trigger=None): """formatter to convert activitypub into a model value""" if value and hasattr(self, "activitypub_wrapper"): @@ -293,7 +293,7 @@ def set_activity_from_field(self, activity, instance): activity["cc"] = [] -class ForeignKey( # pylint: disable=abstract-method +class ForeignKey( ActivitypubRelatedFieldMixin, models.ForeignKey, ): @@ -305,9 +305,7 @@ def field_to_activity(self, value): return value.remote_id -class OneToOneField( # pylint: disable=abstract-method - ActivitypubRelatedFieldMixin, models.OneToOneField -): +class OneToOneField(ActivitypubRelatedFieldMixin, models.OneToOneField): """activitypub-aware foreign key field""" def field_to_activity(self, value): @@ -316,9 +314,7 @@ def field_to_activity(self, value): return value.to_activity() -class ManyToManyField( # pylint: disable=abstract-method - ActivitypubFieldMixin, models.ManyToManyField -): +class ManyToManyField(ActivitypubFieldMixin, models.ManyToManyField): """activitypub-aware many to many field""" def __init__(self, *args, link_only=False, **kwargs): @@ -359,17 +355,17 @@ def field_from_activity(self, value, allow_external_connections=True, trigger=No validate_remote_id(remote_id) except ValidationError: continue - items.append( - activitypub.resolve_remote_id( - remote_id, - model=self.related_model, - allow_external_connections=allow_external_connections, - ) + item = activitypub.resolve_remote_id( + remote_id, + model=self.related_model, + allow_external_connections=allow_external_connections, ) + if item is not None: + items.append(item) return items -class TagField(ManyToManyField): # pylint: disable=abstract-method +class TagField(ManyToManyField): """special case of many to many that uses Tags""" def __init__(self, *args, **kwargs): @@ -423,13 +419,13 @@ def field_from_activity(self, value, allow_external_connections=True, trigger=No items.append(hashtag) else: # for other tag types we fetch them remotely - items.append( - activitypub.resolve_remote_id( - link.href, - model=self.related_model, - allow_external_connections=allow_external_connections, - ) + item = activitypub.resolve_remote_id( + link.href, + model=self.related_model, + allow_external_connections=allow_external_connections, ) + if item is not None: + items.append(item) return items @@ -462,7 +458,6 @@ def __init__(self, *args, alt_field=None, **kwargs): self.alt_field = alt_field super().__init__(*args, **kwargs) - # pylint: disable=arguments-renamed,too-many-arguments def set_field_from_activity( self, instance, data, save=True, overwrite=True, allow_external_connections=True ): @@ -578,7 +573,9 @@ def field_to_activity(self, value) -> str: return value.partial_isoformat() if value else None def field_from_activity(self, value, allow_external_connections=True, trigger=None): - # pylint: disable=no-else-return + if not value: + return None + try: return from_partial_isoformat(value) except ValueError: @@ -611,7 +608,7 @@ def field_from_activity(self, value, allow_external_connections=True, trigger=No return clean(value) def field_to_activity(self, value): - return markdown(value) if value else value + return mistune.html(value).rstrip() if value else value class ArrayField(ActivitypubFieldMixin, DjangoArrayField): @@ -642,11 +639,11 @@ class BooleanField(ActivitypubFieldMixin, models.BooleanField): class IntegerField(ActivitypubFieldMixin, models.IntegerField): - """activitypub-aware boolean field""" + """activitypub-aware integer field""" class DecimalField(ActivitypubFieldMixin, models.DecimalField): - """activitypub-aware boolean field""" + """activitypub-aware decimal field""" def field_to_activity(self, value): if not value: diff --git a/bookwyrm/models/group.py b/bookwyrm/models/group.py index 40a32b5dcf..f0ccc42808 100644 --- a/bookwyrm/models/group.py +++ b/bookwyrm/models/group.py @@ -1,4 +1,5 @@ -""" do book related things with other users """ +"""do book related things with other users""" + from django.db import models, IntegrityError, transaction from django.db.models import Q from bookwyrm.settings import BASE_URL @@ -142,7 +143,7 @@ def save(self, *args, **kwargs): @transaction.atomic def accept(self): """turn this request into the real deal""" - # pylint: disable-next=import-outside-toplevel + from .notification import Notification, NotificationType # circular dependency GroupMember.from_request(self) diff --git a/bookwyrm/models/hashtag.py b/bookwyrm/models/hashtag.py index 5126f012db..1a955e98a0 100644 --- a/bookwyrm/models/hashtag.py +++ b/bookwyrm/models/hashtag.py @@ -1,4 +1,5 @@ -""" model for tags """ +"""model for tags""" + from bookwyrm import activitypub from .activitypub_mixin import ActivitypubMixin from .base_model import BookWyrmModel diff --git a/bookwyrm/models/housekeeping.py b/bookwyrm/models/housekeeping.py index e89887785e..dc06ac947c 100644 --- a/bookwyrm/models/housekeeping.py +++ b/bookwyrm/models/housekeeping.py @@ -1,12 +1,24 @@ -""" cleanup tasks """ +"""cleanup tasks""" + import math from datetime import datetime, timedelta, timezone +from itertools import chain -from django.db.models import DateTimeField, IntegerField +from django.db.models import ( + CharField, + DateTimeField, + IntegerField, + ManyToManyField, + TextChoices, +) +from django.utils.translation import gettext_lazy as _ -from bookwyrm.tasks import app, MISC from bookwyrm import models from bookwyrm.models.job import ParentJob, ParentTask +from bookwyrm.connectors.abstract_connector import get_data +from bookwyrm.connectors.connector_manager import search +from bookwyrm.tasks import app, MISC +from bookwyrm.utils.images import set_cover_from_url class CleanUpUserExportFilesJob(ParentJob): @@ -56,7 +68,6 @@ def start_job(self): class CleanUpExportsTask(ParentTask): """Task to delete expired user export files""" - # pylint: disable=too-many-arguments, unused-argument, no-self-use def after_return(self, status, retval, task_id, args, kwargs, einfo): """Handler called after the task returns""" @@ -95,3 +106,145 @@ def start_export_deletions(**kwargs): job = CleanUpUserExportFilesJob.objects.create(user=user, expiry_date=expiry_date) job.start_job() + + +class FindMissingCoversJob(ParentJob): + """Job to search the fedi for covers we don't have""" + + class JobType(TextChoices): + """Possible status types.""" + + MISSING = "missing", _("Missing") + WRONG_PATH = "wrong_path", _("Wrong Path") + + job_type = CharField( + max_length=10, choices=JobType.choices, default=JobType.MISSING, null=True + ) + editions = ManyToManyField( + models.Edition, + related_name="missing_covers_job", + ) + found_covers = ManyToManyField( + models.Edition, + related_name="+", + ) + + completed_tasks = IntegerField(default=0) + + def start_job(self): + """Report that the job has started and trigger subtasks""" + + self.set_status("active") + + for edition in self.editions.all(): + get_missing_cover_task.delay(job_id=self.id, edition_id=edition.id) + + if self.editions.count() == 0: + self.complete_job() + + +class MissingCoverTask(ParentTask): + """Task to find a cover for an individual edition""" + + def on_success(self, retval, task_id, args, kwargs): + """Override from ParentTask. Save edition to found_covers if we found one""" + + if retval: + job = FindMissingCoversJob.objects.get(id=kwargs["job_id"]) + edition = models.Edition.objects.get(id=kwargs["edition_id"]) + + job.found_covers.add(edition) + + def after_return(self, status, retval, task_id, args, kwargs, einfo): + """Handler called after the task returns""" + + job = FindMissingCoversJob.objects.get(id=kwargs["job_id"]) + job.completed_tasks += 1 + job.save(update_fields=["completed_tasks"]) + + if job.completed_tasks == job.editions.count(): + job.complete_job() + + +@app.task(queue=MISC, base=MissingCoverTask) +def get_missing_cover_task(**kwargs): + """trigger the child tasks for each edition""" + + job = FindMissingCoversJob.objects.get(id=kwargs["job_id"]) + edition = models.Edition.objects.get(id=kwargs["edition_id"]) + + if job.status == "stopped": + return None + + return get_cover_from_identifiers(edition=edition) + + +def get_cover_from_identifiers(edition): + """for a given edition, can we find a book cover from the fedi?""" + + # idk there is probably a more pythonic way of doing this + + # Try first searching by isbn if possible + isbn_fields = ["isbn_10", "isbn_13"] + fields = [ + f.name + for f in models.Edition._meta.get_fields() + if hasattr(f, "deduplication_field") + and f.deduplication_field + and f.name not in isbn_fields + ] + + for field in chain.from_iterable([isbn_fields, fields]): + if not getattr(edition, field): + continue + query_result = ( + search(query=getattr(edition, field), min_confidence=0.999) or None + ) + if not query_result: + continue + + for search_result in query_result: + for result in search_result["results"]: + data = get_data(getattr(result, "key")) + + if data and data.get("cover"): + if data.get("cover").get("url") in ["", None]: + continue + + url = data["cover"]["url"] + image = set_cover_from_url(url) + if image: + edition.cover.save(*image) + return edition.id + + return None + + +def get_covers_with_incorrect_filepaths(): + """return a queryset of all books where the name path doesn't resolve""" + + has_cover = models.Edition.objects.exclude(cover="") + wrong_path_ids = [ + x.id for x in has_cover if not x.cover.storage.exists(x.cover.name) + ] + + return models.Edition.objects.filter(id__in=wrong_path_ids) + + +@app.task(queue=MISC) +def run_missing_covers_job(**kwargs): + """create and start a FindMissingCoversJob""" + + job_type = kwargs.get("type", "missing") + + if job_type == "wrong_path": + editions = get_covers_with_incorrect_filepaths() + else: + editions = models.Edition.objects.filter(cover="") + + job = FindMissingCoversJob.objects.create( + user_id=kwargs["user_id"], job_type=job_type + ) + job.editions.set(editions) + + job.start_job() diff --git a/bookwyrm/models/import_job.py b/bookwyrm/models/import_job.py index 5a6ba3f512..6bfcb5653e 100644 --- a/bookwyrm/models/import_job.py +++ b/bookwyrm/models/import_job.py @@ -1,4 +1,5 @@ -""" track progress of goodreads imports """ +"""track progress of goodreads imports""" + from datetime import datetime import math import re @@ -302,6 +303,11 @@ def date_read(self): """the date a book was completed""" return self._parse_datefield("date_finished") + @property + def date_reviewed(self): + """the date a book was reviewed""" + return self._parse_datefield("review_published") + @property def reads(self): """formats a read through dataset for the book in this line""" @@ -330,11 +336,9 @@ def reads(self): return [] def __repr__(self): - # pylint: disable=consider-using-f-string return "<{!r} Item {!r}>".format(self.index, self.normalized_data.get("title")) def __str__(self): - # pylint: disable=consider-using-f-string return "{} by {}".format( self.normalized_data.get("title"), self.normalized_data.get("authors") ) @@ -385,7 +389,7 @@ def import_item_task(item_id): item.update_job() -def handle_imported_book(item): # pylint: disable=too-many-branches +def handle_imported_book(item): """process a csv and then post about it""" job = item.job if job.complete: @@ -406,7 +410,7 @@ def handle_imported_book(item): # pylint: disable=too-many-branches # shelve the book if it hasn't been shelved already shelved_date = item.date_added or timezone.now() - shelfname = getattr(item, "shelf_name", item.shelf) + shelfname = getattr(item, "shelf", item.shelf) try: shelf = Shelf.objects.get(name=shelfname, user=user) @@ -414,7 +418,6 @@ def handle_imported_book(item): # pylint: disable=too-many-branches try: shelf = Shelf.objects.get(identifier=item.shelf, user=user) except ObjectDoesNotExist: - shelf = Shelf.objects.create( user=user, identifier=item.shelf, @@ -447,11 +450,9 @@ def handle_imported_book(item): # pylint: disable=too-many-branches # but "now" is a bad guess unless we have no choice published_date_guess = ( - item.review_published or item.date_read or item.date_added or timezone.now() + item.date_reviewed or item.date_read or item.date_added or timezone.now() ) if item.review: - - # pylint: disable=consider-using-f-string review_title = "Review of {!r} on {!r}".format( item.book.title, job.source, diff --git a/bookwyrm/models/job.py b/bookwyrm/models/job.py index ff4895dc60..9147473068 100644 --- a/bookwyrm/models/job.py +++ b/bookwyrm/models/job.py @@ -122,7 +122,7 @@ def notify_child_job_complete(self): if not self.complete and self.has_completed: self.complete_job() - def __terminate_job(self): # pylint: disable=unused-private-member + def __terminate_job(self): """Tell workers to ignore and not execute this task & pending child tasks. Extend. """ @@ -186,9 +186,7 @@ class ParentTask(app.Task): Usage e.g. @app.task(base=ParentTask) """ - def before_start( - self, task_id, args, kwargs - ): # pylint: disable=no-self-use, unused-argument + def before_start(self, task_id, args, kwargs): """Handler called before the task starts. Override. Prepare ParentJob before the task starts. @@ -213,9 +211,7 @@ def before_start( if kwargs.get("no_children"): job.set_status(ChildJob.Status.ACTIVE) - def on_success( - self, retval, task_id, args, kwargs - ): # pylint: disable=no-self-use, unused-argument + def on_success(self, retval, task_id, args, kwargs): """Run by the worker if the task executes successfully. Override. Update ParentJob on Task complete. @@ -248,9 +244,7 @@ class SubTask(app.Task): Usage e.g. @app.task(base=SubTask) """ - def before_start( - self, task_id, args, kwargs - ): # pylint: disable=no-self-use, unused-argument + def before_start(self, task_id, args, kwargs): """Handler called before the task starts. Override. Prepare ChildJob before the task starts. @@ -272,9 +266,7 @@ def before_start( child_job.save(update_fields=["task_id"]) child_job.set_status(ChildJob.Status.ACTIVE) - def on_success( - self, retval, task_id, args, kwargs - ): # pylint: disable=no-self-use, unused-argument + def on_success(self, retval, task_id, args, kwargs): """Run by the worker if the task executes successfully. Override. Notify ChildJob of task completion. diff --git a/bookwyrm/models/link.py b/bookwyrm/models/link.py index 4519f0c81e..7d58423b8f 100644 --- a/bookwyrm/models/link.py +++ b/bookwyrm/models/link.py @@ -1,4 +1,5 @@ -""" outlink data """ +"""outlink data""" + from typing import Optional, Iterable from urllib.parse import urlparse diff --git a/bookwyrm/models/list.py b/bookwyrm/models/list.py index df7e8162c2..3c06b90450 100644 --- a/bookwyrm/models/list.py +++ b/bookwyrm/models/list.py @@ -1,11 +1,15 @@ -""" make a list of books!! """ +"""make a list of books!!""" + from typing import Optional, Iterable import uuid +from django.apps import apps +from django.contrib.postgres.indexes import Index from django.core.exceptions import PermissionDenied from django.db import models from django.db.models import Q from django.utils import timezone +from django.utils.translation import gettext_lazy as _ from bookwyrm import activitypub from bookwyrm.settings import BASE_URL @@ -22,15 +26,107 @@ ) -class List(OrderedCollectionMixin, BookWyrmModel): - """a list of books""" +class AbstractList(OrderedCollectionMixin, BookWyrmModel): + """Abstract model for regular lists and suggestion lists""" - name = fields.CharField(max_length=100) + embed_key = models.UUIDField(unique=True, null=True, editable=False) + activity_serializer = activitypub.BookList + privacy = fields.PrivacyField() user = fields.ForeignKey( "User", on_delete=models.PROTECT, activitypub_field="owner" ) + + def save(self, *args, update_fields: Optional[Iterable[str]] = None, **kwargs): + """on save, update embed_key and avoid clash with existing code""" + if not self.embed_key: + self.embed_key = uuid.uuid4() + update_fields = add_update_fields(update_fields, "embed_key") + + super().save(*args, update_fields=update_fields, **kwargs) + + @property + def collection_queryset(self): + raise NotImplementedError + + class Meta: + """default sorting""" + + ordering = ("-updated_date",) + abstract = True + + +class SuggestionList(AbstractList): + """a list of user-provided suggested things to read next""" + + works = models.ManyToManyField( + "Work", + symmetrical=False, + through="SuggestionListItem", + through_fields=("book_list", "work"), + ) + + suggests_for = fields.OneToOneField( + "Work", + on_delete=models.PROTECT, + activitypub_field="book", + related_name="suggestion_list", + unique=True, + ) + activity_serializer = activitypub.SuggestionList + + @property + def collection_queryset(self): + """list of books for this shelf, overrides OrderedCollectionMixin""" + return self.works.order_by("suggestionlistitem") + + @property + def editions(self): + default_editions = [w.default_edition.id for w in self.works.all()] + edition_model = apps.get_model("bookwyrm", "Edition") + return edition_model.objects.filter(id__in=default_editions) + + def save(self, *args, **kwargs): + """on save, update embed_key and avoid clash with existing code""" + self.user = activitypub.get_representative() + self.privacy = "public" + + super().save(*args, **kwargs) + + def raise_not_editable(self, viewer): + """anyone can create a suggestion list, no one can edit""" + return + + def get_remote_id(self): + """don't want the user to be in there in this case""" + return f"{BASE_URL}/book/{self.suggests_for.id}/suggestions" + + @property + def name(self): + """The name comes from the book title if it's a suggestion list""" + return _("Suggestions for %(title)s") % {"title": self.suggests_for.title} + + @property + def description(self): + """The description comes from the book title if it's a suggestion list""" + return _( + "This is the list of suggestions for %(title)s" + ) % { + "title": self.suggests_for.title, + "url": self.suggests_for.local_path, + } + + +class List(AbstractList): + """a list of books""" + + editions = models.ManyToManyField( + "Edition", + symmetrical=False, + through="ListItem", + through_fields=("book_list", "edition"), + ) + name = fields.CharField(max_length=100) description = fields.TextField(blank=True, null=True, activitypub_field="summary") - privacy = fields.PrivacyField() curation = fields.CharField( max_length=255, default="closed", choices=CurationType.choices ) @@ -41,28 +137,26 @@ class List(OrderedCollectionMixin, BookWyrmModel): blank=True, null=True, ) - books = models.ManyToManyField( - "Edition", - symmetrical=False, - through="ListItem", - through_fields=("book_list", "book"), - ) - embed_key = models.UUIDField(unique=True, null=True, editable=False) - activity_serializer = activitypub.BookList - def get_remote_id(self): - """don't want the user to be in there in this case""" - return f"{BASE_URL}/list/{self.id}" + class Meta: + """default sorting""" + + indexes = [Index(fields=["privacy", "-updated_date"])] + + @property + def works(self): + work_model = apps.get_model("bookwyrm", "Work") + works = self.editions.all().values_list("parent_work__id", flat=True).distinct() + return work_model.objects.filter(id__in=works) @property def collection_queryset(self): """list of books for this shelf, overrides OrderedCollectionMixin""" - return self.books.filter(listitem__approved=True).order_by("listitem") - - class Meta: - """default sorting""" + return self.editions.filter(listitem__approved=True).order_by("listitem") - ordering = ("-updated_date",) + def get_remote_id(self): + """don't want the user to be in there in this case""" + return f"{BASE_URL}/list/{self.id}" def raise_not_editable(self, viewer): """the associated user OR the list owner can edit""" @@ -126,44 +220,79 @@ def remove_from_group(cls, owner, user): group=None, curation="closed" ) - def save(self, *args, update_fields: Optional[Iterable[str]] = None, **kwargs): - """on save, update embed_key and avoid clash with existing code""" - if not self.embed_key: - self.embed_key = uuid.uuid4() - update_fields = add_update_fields(update_fields, "embed_key") - - super().save(*args, update_fields=update_fields, **kwargs) +class AbstractListItem(CollectionItemMixin, BookWyrmModel): + """Abstract class for list items for all types of lists""" -class ListItem(CollectionItemMixin, BookWyrmModel): - """ok""" - - book = fields.ForeignKey( - "Edition", on_delete=models.PROTECT, activitypub_field="book" - ) - book_list = models.ForeignKey("List", on_delete=models.CASCADE) user = fields.ForeignKey( "User", on_delete=models.PROTECT, activitypub_field="actor" ) notes = fields.HtmlField(blank=True, null=True, max_length=300) - approved = models.BooleanField(default=True) - order = fields.IntegerField() + raw_notes = models.TextField(blank=True, null=True, max_length=300) + endorsement = models.ManyToManyField("User", related_name="endorsers") activity_serializer = activitypub.ListItem collection_field = "book_list" - def save(self, *args, **kwargs): - """Update the list's date""" - super().save(*args, **kwargs) - # tick the updated date on the parent list - self.book_list.updated_date = timezone.now() - self.book_list.save(broadcast=False, update_fields=["updated_date"]) + def endorse(self, user): + """another user supports this suggestion""" + # you can't endorse your own contribution, silly + if user == self.user: + return + self.endorsement.add(user) + + def unendorse(self, user): + """the user rescinds support this suggestion""" + if user == self.user: + return + self.endorsement.remove(user) def raise_not_deletable(self, viewer): """the associated user OR the list owner can delete""" if self.book_list.user == viewer: return + super().raise_not_deletable(viewer) + + class Meta: + """A book may only be placed into a list once, + and each order in the list may be used only once""" + + ordering = ("-created_date",) + abstract = True + + +class ListItem(AbstractListItem): + """ok""" + + edition = fields.ForeignKey( + "Edition", on_delete=models.PROTECT, activitypub_field="book" + ) + + book_list = models.ForeignKey("List", on_delete=models.CASCADE) + approved = models.BooleanField(default=True) + order = fields.IntegerField() + + @property + def work(self): + """match the suggestion list model by producing a work""" + return self.edition.parent_work + + @property + def edit_path_name(self): + """the form submit link to edit this item""" + return "list-item" + + @property + def privacy(self): + """inherit the privacy of the list, or direct if pending""" + collection_field = getattr(self, self.collection_field) + if self.approved: + return collection_field.privacy + return "direct" + + def raise_not_deletable(self, viewer): + """the associated user OR the list owner can delete""" # group members can delete items in group lists is_group_member = GroupMember.objects.filter( group=self.book_list.group, user=viewer @@ -172,9 +301,40 @@ def raise_not_deletable(self, viewer): return super().raise_not_deletable(viewer) + def save(self, *args, **kwargs): + """Update the list's date""" + super().save(*args, **kwargs) + # tick the updated date on the parent list + self.book_list.updated_date = timezone.now() + self.book_list.save(broadcast=False, update_fields=["updated_date"]) + class Meta: """A book may only be placed into a list once, and each order in the list may be used only once""" - unique_together = (("book", "book_list"), ("order", "book_list")) - ordering = ("-created_date",) + unique_together = (("edition", "book_list"), ("order", "book_list")) + + +class SuggestionListItem(AbstractListItem): + """items on a suggestion list""" + + work = fields.ForeignKey("Work", on_delete=models.PROTECT, activitypub_field="book") + book_list = models.ForeignKey("SuggestionList", on_delete=models.CASCADE) + endorsement = models.ManyToManyField("User", related_name="suggestion_endorsers") + activity_serializer = activitypub.SuggestionListItem + + @property + def edition(self): + """match the list model by producing an edition""" + return self.work.default_edition + + @property + def edit_path_name(self): + """the form submit link to edit this item""" + return "suggestion-list-item" + + class Meta: + """A book may only be placed into a list once, + and each order in the list may be used only once""" + + unique_together = ("work", "book_list") diff --git a/bookwyrm/models/move.py b/bookwyrm/models/move.py index 5038058b78..f7c891cef9 100644 --- a/bookwyrm/models/move.py +++ b/bookwyrm/models/move.py @@ -1,4 +1,5 @@ -""" move an object including migrating a user account """ +"""move an object including migrating a user account""" + from django.core.exceptions import PermissionDenied from django.db import models diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index ca1e2aeb05..aa9d81efc9 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -1,4 +1,5 @@ -""" alert a user to activity """ +"""alert a user to activity""" + from django.db import models, transaction from django.dispatch import receiver from bookwyrm.models.bookwyrm_export_job import BookwyrmExportJob @@ -130,7 +131,6 @@ def unnotify(cls, user, related_user, **kwargs): @receiver(models.signals.post_save, sender=Favorite) -# pylint: disable=unused-argument def notify_on_fav(sender, instance, *args, **kwargs): """someone liked your content, you ARE loved""" Notification.notify( @@ -142,7 +142,6 @@ def notify_on_fav(sender, instance, *args, **kwargs): @receiver(models.signals.post_delete, sender=Favorite) -# pylint: disable=unused-argument def notify_on_unfav(sender, instance, *args, **kwargs): """oops, didn't like that after all""" if not instance.status.user.local: @@ -157,7 +156,6 @@ def notify_on_unfav(sender, instance, *args, **kwargs): @receiver(models.signals.post_save) @transaction.atomic -# pylint: disable=unused-argument def notify_user_on_mention(sender, instance, *args, **kwargs): """creating and deleting statuses with @ mentions and replies""" if not issubclass(sender, Status): @@ -194,7 +192,6 @@ def notify_user_on_mention(sender, instance, *args, **kwargs): @receiver(models.signals.post_save, sender=Boost) -# pylint: disable=unused-argument def notify_user_on_boost(sender, instance, *args, **kwargs): """boosting a status""" if ( @@ -212,7 +209,6 @@ def notify_user_on_boost(sender, instance, *args, **kwargs): @receiver(models.signals.post_delete, sender=Boost) -# pylint: disable=unused-argument def notify_user_on_unboost(sender, instance, *args, **kwargs): """unboosting a status""" Notification.unnotify( @@ -224,7 +220,6 @@ def notify_user_on_unboost(sender, instance, *args, **kwargs): @receiver(models.signals.post_save, sender=ImportJob) -# pylint: disable=unused-argument def notify_user_on_import_complete( sender, instance, *args, update_fields=None, **kwargs ): @@ -240,7 +235,6 @@ def notify_user_on_import_complete( @receiver(models.signals.post_save, sender=BookwyrmImportJob) -# pylint: disable=unused-argument def notify_user_on_user_import_complete( sender, instance, *args, update_fields=None, **kwargs ): @@ -254,7 +248,6 @@ def notify_user_on_user_import_complete( @receiver(models.signals.post_save, sender=BookwyrmExportJob) -# pylint: disable=unused-argument def notify_user_on_user_export_complete( sender, instance, *args, update_fields=None, **kwargs ): @@ -271,7 +264,6 @@ def notify_user_on_user_export_complete( @receiver(models.signals.post_save, sender=Report) @transaction.atomic -# pylint: disable=unused-argument def notify_admins_on_report(sender, instance, created, *args, **kwargs): """something is up, make sure the admins know""" if not created: @@ -290,7 +282,6 @@ def notify_admins_on_report(sender, instance, created, *args, **kwargs): @receiver(models.signals.post_save, sender=LinkDomain) @transaction.atomic -# pylint: disable=unused-argument def notify_admins_on_link_domain(sender, instance, created, *args, **kwargs): """a new link domain needs to be verified""" if not created: @@ -309,7 +300,6 @@ def notify_admins_on_link_domain(sender, instance, created, *args, **kwargs): @receiver(models.signals.post_save, sender=InviteRequest) @transaction.atomic -# pylint: disable=unused-argument def notify_admins_on_invite_request(sender, instance, created, *args, **kwargs): """need to handle a new invite request""" if not created: @@ -326,7 +316,6 @@ def notify_admins_on_invite_request(sender, instance, created, *args, **kwargs): @receiver(models.signals.post_save, sender=GroupMemberInvitation) -# pylint: disable=unused-argument def notify_user_on_group_invite(sender, instance, *args, **kwargs): """Cool kids club here we come""" Notification.notify( @@ -339,7 +328,6 @@ def notify_user_on_group_invite(sender, instance, *args, **kwargs): @receiver(models.signals.post_save, sender=ListItem) @transaction.atomic -# pylint: disable=unused-argument def notify_user_on_list_item_add(sender, instance, created, *args, **kwargs): """Someone added to your list""" if not created: @@ -359,7 +347,6 @@ def notify_user_on_list_item_add(sender, instance, created, *args, **kwargs): @receiver(models.signals.post_save, sender=UserFollowRequest) @transaction.atomic -# pylint: disable=unused-argument def notify_user_on_follow(sender, instance, created, *args, **kwargs): """Someone added to your list""" if not created or not instance.user_object.local: diff --git a/bookwyrm/models/readthrough.py b/bookwyrm/models/readthrough.py index 670b35821f..46feaafaa9 100644 --- a/bookwyrm/models/readthrough.py +++ b/bookwyrm/models/readthrough.py @@ -1,4 +1,5 @@ -""" progress in a book """ +"""progress in a book""" + from typing import Optional, Iterable from django.core import validators diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index c4344d812c..d8bbe3b049 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -1,4 +1,5 @@ -""" defines relationships between users """ +"""defines relationships between users""" + from django.core.cache import cache from django.db import models, transaction, IntegrityError from django.db.models import Q @@ -80,7 +81,7 @@ class UserFollows(ActivityMixin, UserRelationship): status = "follows" - def to_activity(self): # pylint: disable=arguments-differ + def to_activity(self): """overrides default to manually set serializer""" return activitypub.Follow(**generate_activity(self)) diff --git a/bookwyrm/models/report.py b/bookwyrm/models/report.py index 64ade3a406..825eeb219b 100644 --- a/bookwyrm/models/report.py +++ b/bookwyrm/models/report.py @@ -1,10 +1,13 @@ -""" flagged for moderation """ -from django.core.exceptions import PermissionDenied +"""flagged for moderation""" + from django.db import models from django.utils.translation import gettext_lazy as _ +from bookwyrm import activitypub from bookwyrm.settings import BASE_URL +from .activitypub_mixin import ActivityMixin from .base_model import BookWyrmModel +from . import fields # Report action enums @@ -22,28 +25,62 @@ DELETE_ITEM = "delete_item" -class Report(BookWyrmModel): +class Report(ActivityMixin, BookWyrmModel): """reported status or user""" - reporter = models.ForeignKey( - "User", related_name="reporter", on_delete=models.PROTECT + activity_serializer = activitypub.Flag + + user = fields.ForeignKey( + "User", + on_delete=models.PROTECT, + activitypub_field="actor", ) - note = models.TextField(null=True, blank=True) - user = models.ForeignKey("User", on_delete=models.PROTECT, null=True, blank=True) - status = models.ForeignKey( - "Status", + note = fields.TextField(null=True, blank=True, activitypub_field="content") + reported_user = fields.ForeignKey( + "User", + related_name="reported_user", + on_delete=models.PROTECT, null=True, blank=True, - on_delete=models.PROTECT, + activitypub_field="to", ) - links = models.ManyToManyField("Link", blank=True) + statuses = models.ManyToManyField( + "Status", + blank=True, + ) + links = fields.ManyToManyField("Link", blank=True) resolved = models.BooleanField(default=False) - - def raise_not_editable(self, viewer): - """instead of user being the owner field, it's reporter""" - if self.reporter == viewer or viewer.has_perm("bookwyrm.moderate_user"): + allow_broadcast = models.BooleanField(default=False) + + def to_activity_dataclass(self, **kwargs): + """generate the objects field""" + activity = super().to_activity_dataclass(**kwargs) + activity.object = self.object() + return activity + + def object(self): + """Generate a list of reported objects in a format Mastodon will like""" + items = [] + if self.reported_user and hasattr(self.reported_user, "remote_id"): + items.append(self.reported_user.remote_id) + if self.statuses: + items += self.statuses.values_list("remote_id", flat=True) + + return items + + def broadcast(self, activity, sender, *args, **kwargs): + """only need to send an activity for remote offenders""" + # don't try to broadcast if the reporter doesn't want you to, + # or if the reported user is local + if self.reported_user.local or not self.allow_broadcast: return - raise PermissionDenied() + super().broadcast(activity, sender, *args, **kwargs) + + def get_recipients(self, software=None): + """Send this to the public inbox of the offending instance""" + if self.reported_user.local: + return [] + return [self.reported_user.shared_inbox or self.reported_user.inbox] def get_remote_id(self): return f"{BASE_URL}/settings/reports/{self.id}" diff --git a/bookwyrm/models/session.py b/bookwyrm/models/session.py index 3de199d299..d99d03483a 100644 --- a/bookwyrm/models/session.py +++ b/bookwyrm/models/session.py @@ -1,4 +1,5 @@ -""" functions for managing user sessions """ +"""functions for managing user sessions""" + from importlib import import_module import ua_parser diff --git a/bookwyrm/models/shelf.py b/bookwyrm/models/shelf.py index 0b9ef2b09e..8e935b0c6b 100644 --- a/bookwyrm/models/shelf.py +++ b/bookwyrm/models/shelf.py @@ -1,4 +1,5 @@ -""" puttin' books on shelves """ +"""puttin' books on shelves""" + import re from typing import Optional, Iterable from django.core.cache import cache @@ -7,7 +8,6 @@ from django.utils import timezone from bookwyrm import activitypub -from bookwyrm.settings import BASE_URL from bookwyrm.tasks import BROADCAST from bookwyrm.utils.db import add_update_fields from .activitypub_mixin import CollectionItemMixin, OrderedCollectionMixin @@ -74,7 +74,8 @@ def get_remote_id(self): @property def local_path(self): """No slugs""" - return self.get_remote_id().replace(BASE_URL, "") + identifier = self.identifier or self.get_identifier() + return f"{self.user.local_path}/books/{identifier}" def raise_not_deletable(self, viewer): """don't let anyone delete a default shelf""" diff --git a/bookwyrm/models/site.py b/bookwyrm/models/site.py index 31e77e7e2b..5d584b56a3 100644 --- a/bookwyrm/models/site.py +++ b/bookwyrm/models/site.py @@ -1,6 +1,8 @@ -""" the particulars for this instance of BookWyrm """ +"""the particulars for this instance of BookWyrm""" + +from __future__ import annotations import datetime -from typing import Optional, Iterable +from typing import Any, Optional, Iterable from urllib.parse import urljoin import uuid @@ -30,8 +32,7 @@ class Meta: abstract = True - # pylint: disable=no-self-use - def raise_not_editable(self, viewer): + def raise_not_editable(self, viewer: User) -> None: """Check if the user has the right permissions""" if viewer.has_perm("bookwyrm.edit_instance_settings"): return @@ -56,6 +57,11 @@ class SiteSettings(SiteModel): install_mode = models.BooleanField(default=False) admin_code = models.CharField(max_length=50, default=uuid.uuid4) + # security + require_signed_get = models.BooleanField(default=False) + require_login_nearly_everywhere = models.BooleanField(default=False) + block_incoming_search = models.BooleanField(default=False) + # about page registration_closed_text = models.TextField( default="We aren't taking new users at this time. You can find an open " @@ -83,6 +89,7 @@ class SiteSettings(SiteModel): invite_question_text = models.CharField( max_length=255, blank=True, default="What is your favourite book?" ) + # images logo = models.ImageField(upload_to="logos/", null=True, blank=True) logo_small = models.ImageField(upload_to="logos/", null=True, blank=True) @@ -103,12 +110,13 @@ class SiteSettings(SiteModel): import_limit_reset = models.IntegerField(default=0) user_exports_enabled = models.BooleanField(default=False) user_import_time_limit = models.IntegerField(default=48) + disable_federation = models.BooleanField(default=False) export_files_lifetime_hours = models.IntegerField(default=72) field_tracker = FieldTracker(fields=["name", "instance_tagline", "logo"]) @classmethod - def get(cls): + def get(cls) -> SiteSettings: """gets the site settings db entry or defaults""" try: return cls.objects.get(id=1) @@ -117,29 +125,37 @@ def get(cls): default_settings.save() return default_settings + @classmethod + def raise_federation_disabled(cls) -> None: + """Don't connect to the outside world""" + if cls.get().disable_federation: + raise PermissionDenied("Federation is disabled") + @property - def logo_url(self): + def logo_url(self) -> Any: """helper to build the logo url""" return self.get_url("logo", "images/logo.png") @property - def logo_small_url(self): + def logo_small_url(self) -> Any: """helper to build the logo url""" return self.get_url("logo_small", "images/logo-small.png") @property - def favicon_url(self): + def favicon_url(self) -> Any: """helper to build the logo url""" return self.get_url("favicon", "images/favicon.png") - def get_url(self, field, default_path): + def get_url(self, field: str, default_path: str) -> Any: """get a media url or a default static path""" uploaded = getattr(self, field, None) if uploaded: return get_absolute_url(uploaded) return urljoin(STATIC_FULL_URL, default_path) - def save(self, *args, update_fields: Optional[Iterable[str]] = None, **kwargs): + def save( + self, *args, update_fields: Optional[Iterable[str]] = None, **kwargs + ) -> None: """if require_confirm_email is disabled, make sure no users are pending, if enabled, make sure invite_question_text is not empty""" if not self.invite_question_text: @@ -162,8 +178,7 @@ class Theme(SiteModel): path = models.CharField(max_length=50, unique=True) loads = models.BooleanField(null=True, blank=True) - def __str__(self): - # pylint: disable=invalid-str-returned + def __str__(self) -> str: return self.name @@ -178,21 +193,20 @@ class SiteInvite(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) invitees = models.ManyToManyField(User, related_name="invitees") - # pylint: disable=no-self-use - def raise_not_editable(self, viewer): + def raise_not_editable(self, viewer: User) -> None: """Admins only""" if viewer.has_perm("bookwyrm.create_invites"): return raise PermissionDenied() - def valid(self): + def valid(self) -> bool: """make sure it hasn't expired or been used""" return (self.expiry is None or self.expiry > timezone.now()) and ( self.use_limit is None or self.times_used < self.use_limit ) @property - def link(self): + def link(self) -> str: """formats the invite link""" return f"{BASE_URL}/invite/{self.code}" @@ -208,20 +222,20 @@ class InviteRequest(BookWyrmModel): invite_sent = models.BooleanField(default=False) ignored = models.BooleanField(default=False) - def raise_not_editable(self, viewer): + def raise_not_editable(self, viewer: User) -> None: """Only check perms on edit, not create""" if not self.id or viewer.has_perm("bookwyrm.create_invites"): return raise PermissionDenied() - def save(self, *args, **kwargs): + def save(self, *args, **kwargs) -> None: """don't create a request for a registered email""" if not self.id and User.objects.filter(email=self.email).exists(): raise IntegrityError() super().save(*args, **kwargs) -def get_password_reset_expiry(): +def get_password_reset_expiry() -> datetime.datetime: """give people a limited time to use the link""" now = timezone.now() return now + datetime.timedelta(days=1) @@ -234,19 +248,18 @@ class PasswordReset(models.Model): expiry = models.DateTimeField(default=get_password_reset_expiry) user = models.OneToOneField(User, on_delete=models.CASCADE) - def valid(self): + def valid(self) -> bool: """make sure it hasn't expired or been used""" return self.expiry > timezone.now() @property - def link(self): + def link(self) -> str: """formats the invite link""" return f"{BASE_URL}/password-reset/{self.code}" -# pylint: disable=unused-argument @receiver(models.signals.post_save, sender=SiteSettings) -def preview_image(instance, *args, **kwargs): +def preview_image(instance: SiteSettings, *args, **kwargs) -> None: """Update image preview for the default site image""" if not ENABLE_PREVIEW_IMAGES: return @@ -257,10 +270,10 @@ def preview_image(instance, *args, **kwargs): @app.task(queue=MISC) -def check_for_updates_task(): +def check_for_updates_task() -> None: """See if git remote knows about a new version""" - site = SiteSettings.objects.get() - release = get_data(RELEASE_API, timeout=3) + site = SiteSettings.get() + release = get_data(RELEASE_API, timeout=3, is_activitypub=False) available_version = release.get("tag_name", None) if available_version: site.available_version = available_version diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 2b357ebd22..2de5b75e09 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -1,4 +1,5 @@ -""" models for storing different kinds of Activities """ +"""models for storing different kinds of Activities""" + from dataclasses import MISSING from typing import Optional, Iterable import re @@ -108,7 +109,7 @@ def delete(self, *args, **kwargs): # clear user content self.content = None if hasattr(self, "quotation"): - self.quotation = None # pylint: disable=attribute-defined-outside-init + self.quotation = None self.deleted_date = timezone.now() self.save(*args, **kwargs) @@ -125,15 +126,14 @@ def recipients(self): return list(mentions) @classmethod - def ignore_activity( - cls, activity, allow_external_connections=True - ): # pylint: disable=too-many-return-statements + def ignore_activity(cls, activity, allow_external_connections=True): """keep notes if they are replies to existing statuses""" if activity.type == "Announce": boosted = activitypub.resolve_remote_id( activity.object, get_activity=True, allow_external_connections=allow_external_connections, + model=apps.get_model("bookwyrm.Status", require_ready=True), ) if not boosted: # if we can't load the status, definitely ignore it @@ -253,7 +253,7 @@ def to_activity_dataclass(self, pure=False): activity.attachment = covers return activity - def to_activity(self, pure=False): # pylint: disable=arguments-differ + def to_activity(self, pure=False): """json serialized activitypub class""" return self.to_activity_dataclass(pure=pure).serialize() @@ -277,6 +277,36 @@ def direct_filter(cls, queryset, viewer): ~Q(Q(user=viewer) | Q(mention_users=viewer)), privacy="direct" ) + @classmethod + def blocked_book_filter(cls, viewer, privacy_levels=None): + """filter out all statuses related to a book this user has blocked""" + + queryset = super().privacy_filter(viewer, privacy_levels=privacy_levels) + + if not viewer or not viewer.is_authenticated: + return queryset + + blocked = viewer.blocked_books.values_list("id", flat=True) + + book_comments = queryset.filter(comment__book__parent_work__in=blocked) + book_quotations = queryset.filter(quotation__book__parent_work__in=blocked) + book_reviews = queryset.filter(review__book__parent_work__in=blocked) + book_mentions = queryset.filter(mention_books__parent_work__in=blocked) + book_statuses = book_comments.union( + book_quotations, book_reviews, book_mentions + ) + + threads = book_statuses.values_list("thread_id", flat=True) + thread_statuses = queryset.exclude( + id__in=book_statuses.values_list("id", flat=True) + ).filter(thread_id__in=threads) + + exclude = book_statuses.union(thread_statuses).values_list("id", flat=True) + + return queryset.exclude(id__in=exclude).filter( + deleted=False, user__is_active=True + ) + @classmethod def followers_filter(cls, queryset, viewer): """Override-able filter for "followers" privacy level""" @@ -353,8 +383,7 @@ def pure_content(self): """indicate the book in question for mastodon (or w/e) users""" progress = self.progress or 0 citation = ( - f'comment on ' - f"{self.book.title}" + f'comment on {self.book.title}' ) if self.progress_mode == "PG" and progress > 0: citation += f", p. {progress}" @@ -424,7 +453,7 @@ def page_title(self): class Review(BookStatus): """a book review""" - name = fields.CharField(max_length=255, null=True) + name = fields.CharField(max_length=255, null=True, blank=True) rating = fields.DecimalField( default=None, null=True, @@ -447,7 +476,11 @@ def pure_name(self): @property def pure_content(self): """indicate the book in question for mastodon (or w/e) users""" - return self.content + if self.content: + return self.content + else: + template = get_template("snippets/generated_status/rating_pure_name.html") + return template.render({"book": self.book, "rating": self.rating}).strip() @property def page_title(self): @@ -533,7 +566,6 @@ def __init__(self, *args, **kwargs): self.deserialize_reverse_fields = [] -# pylint: disable=unused-argument @receiver(models.signals.post_save) def preview_image(instance, sender, *args, **kwargs): """Updates book previews if the rating has changed""" diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 2f411fb5ad..a85f5c6251 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -1,4 +1,5 @@ -""" database schema for user data """ +"""database schema for user data""" + import datetime from importlib import import_module import re @@ -48,13 +49,13 @@ def get_feed_filter_choices(): return [f[0] for f in FeedFilterChoices] -# pylint: disable=too-many-public-methods class User(OrderedCollectionPageMixin, AbstractUser): """a user who wants to read books""" username = fields.UsernameField() email = models.EmailField(unique=True, null=True) is_deleted = models.BooleanField(default=False) + force_password_reset = models.BooleanField(default=False) key_pair = fields.OneToOneField( "KeyPair", @@ -122,6 +123,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): through_fields=("user_subject", "user_object"), related_name="blocked_by", ) + blocked_books = models.ManyToManyField("Work", related_name="blocked_by") saved_lists = models.ManyToManyField( "List", symmetrical=False, related_name="saved_lists", blank=True ) @@ -142,6 +144,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): manually_approves_followers = fields.BooleanField(default=False) theme = models.ForeignKey("Theme", null=True, blank=True, on_delete=models.SET_NULL) hide_follows = fields.BooleanField(default=False) + is_profile_private = models.BooleanField(default=False) # migration fields moved_to = fields.RemoteIdField( @@ -161,6 +164,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): discoverable = fields.BooleanField(default=False) show_guided_tour = models.BooleanField(default=True) show_ratings = models.BooleanField(default=True) + readwise_api_key = models.CharField(max_length=255, null=True, blank=True) # feed options feed_status_types = DjangoArrayField( @@ -204,7 +208,7 @@ class Meta(AbstractUser.Meta): indexes = [ models.Index(fields=["username"]), - models.Index(fields=["is_active", "local"]), + models.Index(fields=["local", "is_active", "last_active_date"]), ] @property @@ -225,8 +229,7 @@ def following_link(self): @property def alt_text(self): """alt text with username""" - # pylint: disable=consider-using-f-string - return "avatar for {:s}".format(self.localname or self.username) + return _("avatar for {name}").format(name=(self.localname or self.username)) @property def display_name(self): @@ -253,6 +256,16 @@ def has_unread_mentions(self): notification_type__in=["REPLY", "MENTION", "TAG", "REPORT"], ).exists() + @property + def filters_applied(self): + return set(self.feed_status_types) != set(get_feed_filter_choices()) + + def get_followers(self): + return self.followers.filter(is_active=True).order_by("-updated_date") + + def get_following(self): + return self.following.filter(is_active=True).order_by("-updated_date") + activity_serializer = activitypub.Person @classmethod @@ -306,7 +319,7 @@ def to_following_activity(self, **kwargs): """activitypub following list""" remote_id = f"{self.remote_id}/following" return self.to_ordered_collection( - self.following.order_by("-updated_date").all(), + self.get_following().all(), remote_id=remote_id, collection_only=True, id_only=True, @@ -317,7 +330,7 @@ def to_followers_activity(self, **kwargs): """activitypub followers list""" remote_id = self.followers_url return self.to_ordered_collection( - self.followers.order_by("-updated_date").all(), + self.get_followers().all(), remote_id=remote_id, collection_only=True, id_only=True, @@ -417,6 +430,7 @@ def delete(self, *args, **kwargs): self.is_active = False self.allow_reactivation = False self.is_deleted = True + self.set_unusable_password() self.erase_user_data() self.erase_user_statuses() @@ -476,7 +490,7 @@ def reactivate(self): @property def local_path(self): """this model doesn't inherit bookwyrm model, so here we are""" - # pylint: disable=consider-using-f-string + return "/user/{:s}".format(self.localname or self.username) def create_shelves(self): @@ -523,6 +537,15 @@ def refresh_user_sessions(self): if not cache_session.exists(session_key=sess.session_key): sess.delete() + def is_profile_visible_to(self, viewer_id): + if not self.is_profile_private: + return True + if viewer_id is None: + return False + if self.id == viewer_id: + return True + return self.followers.filter(id=viewer_id).exists() + class KeyPair(ActivitypubMixin, BookWyrmModel): """public and private keys for a user""" @@ -647,7 +670,6 @@ def get_remote_reviews(outbox): activitypub.Review(**activity).to_model() -# pylint: disable=unused-argument @receiver(models.signals.post_save, sender=User) def preview_image(instance, *args, **kwargs): """create preview images when user is updated""" diff --git a/bookwyrm/models/user_upload.py b/bookwyrm/models/user_upload.py new file mode 100644 index 0000000000..49b0987384 --- /dev/null +++ b/bookwyrm/models/user_upload.py @@ -0,0 +1,50 @@ +"""database schema for images uploaded by users""" + +from django.db import models +import time +import os + + +def user_upload_directory_path(instance, filename): + return "uploads/user_{0}/{1}_{2}".format(instance.user.id, time.time_ns(), filename) + + +class UserUpload(models.Model): + original_name = models.TextField() + original_content_type = models.TextField() + original_file = models.ImageField(upload_to=user_upload_directory_path) + user = models.ForeignKey( + "User", + on_delete=models.CASCADE, + null=False, + blank=False, + related_name="user_uploads", + ) + status = models.ForeignKey( + "Status", + on_delete=models.CASCADE, + null=True, + blank=True, + related_name="user_image_uploads", + ) + + +def user_upload_version_directory_path(instance, filename): + return "uploads/user_{0}/{1}/{2}{3}".format( + instance.user_upload.user.id, + instance.user_upload.id, + instance.max_dimension, + os.path.splitext(filename)[1], + ) + + +class UserUploadVersion(models.Model): + max_dimension = models.TextField() + file = models.ImageField(upload_to=user_upload_version_directory_path) + user_upload = models.ForeignKey( + "UserUpload", + on_delete=models.CASCADE, + null=False, + blank=False, + related_name="versions", + ) diff --git a/bookwyrm/preview_images.py b/bookwyrm/preview_images.py index 66d2e2d4b9..e94afdbcdf 100644 --- a/bookwyrm/preview_images.py +++ b/bookwyrm/preview_images.py @@ -1,4 +1,4 @@ -""" Generate social media preview images for twitter/mastodon/etc """ +"""Generate social media preview images for twitter/mastodon/etc""" import math import os @@ -6,7 +6,11 @@ from io import BytesIO from uuid import uuid4 import logging +import unicodedata as ud +from collections import Counter +import arabic_reshaper +from bidi.algorithm import get_display import colorsys from colorthief import ColorThief from PIL import Image, ImageDraw, ImageFont, ImageOps, ImageColor @@ -51,17 +55,28 @@ def get_imagefont(name, size): def get_font(weight, size=28): """Gets a custom font with the given weight and size""" - font = get_imagefont(DEFAULT_FONT, size) - try: - if weight == "light": - font.set_variation_by_name("Light") - if weight == "bold": - font.set_variation_by_name("Bold") - if weight == "regular": - font.set_variation_by_name("Regular") - except OSError: - pass + fonts = DEFAULT_FONT.split(",") + for file in fonts: + file = file.strip() + font = get_imagefont(file, size) + if weight in file.lower(): + break + + try: + variations = font.get_variation_names() + variations_lower = [ + v.decode("UTF-8").lower() for v in font.get_variation_names() + ] + idx = ( + variations_lower.index(weight.lower()) + if weight.lower() in variations_lower + else 0 + ) + font.set_variation_by_name(variations[idx]) + + except: + pass return font @@ -69,6 +84,8 @@ def get_font(weight, size=28): def get_wrapped_text(text, font, content_width): """text wrap length depends on the max width of the content""" + text = process_arabic_script(text) + low = 0 high = len(text) @@ -97,6 +114,26 @@ def get_wrapped_text(text, font, content_width): return wrapped_text, height +def process_arabic_script(text): + """First we need to re-shape the text, then apply the bidirectional text algo""" + reshaped_text = arabic_reshaper.reshape(text) + bidi_text = get_display(reshaped_text) + return bidi_text + + +def get_text_direction(text): + """Get the dominant strong direction + We we will not be using direction="rtl" | "ltr" property in multiline_text() + because the bidirectionality has already been treated with process_arabic_script + instead, we use this property to set the align property + """ + # from https://stackoverflow.com/a/75739782 + count = Counter([ud.bidirectional(c) for c in list(text)]) + rtl_count = count["R"] + count["AL"] + count["RLE"] + count["RLI"] + ltr_count = count["L"] + count["LRE"] + count["LRI"] + return "right" if rtl_count > ltr_count else "left" + + def generate_texts_layer(texts, content_width): """Adds text for images""" font_text_zero = get_font("bold", size=20) @@ -107,7 +144,15 @@ def generate_texts_layer(texts, content_width): text_layer = Image.new("RGBA", (content_width, IMG_HEIGHT), color=TRANSPARENT_COLOR) text_layer_draw = ImageDraw.Draw(text_layer) + text_x = 0 + text_anchor = None text_y = 0 + main_title = texts["text_one"] if "text_one" in texts else "" + text_align = get_text_direction(main_title) + + if text_align == "right": + text_x = content_width + text_anchor = "ra" if "text_zero" in texts and texts["text_zero"]: # Text zero (Site preview domain name) @@ -116,7 +161,12 @@ def generate_texts_layer(texts, content_width): ) text_layer_draw.multiline_text( - (0, text_y), text_zero, font=font_text_zero, fill=TEXT_COLOR + (text_x, text_y), + text_zero, + font=font_text_zero, + fill=TEXT_COLOR, + align=text_align, + anchor=text_anchor, ) try: @@ -131,7 +181,12 @@ def generate_texts_layer(texts, content_width): ) text_layer_draw.multiline_text( - (0, text_y), text_one, font=font_text_one, fill=TEXT_COLOR + (text_x, text_y), + text_one, + font=font_text_one, + fill=TEXT_COLOR, + align=text_align, + anchor=text_anchor, ) try: @@ -146,7 +201,12 @@ def generate_texts_layer(texts, content_width): ) text_layer_draw.multiline_text( - (0, text_y), text_two, font=font_text_two, fill=TEXT_COLOR + (text_x, text_y), + text_two, + font=font_text_two, + fill=TEXT_COLOR, + align=text_align, + anchor=text_anchor, ) try: @@ -161,18 +221,26 @@ def generate_texts_layer(texts, content_width): ) text_layer_draw.multiline_text( - (0, text_y), text_three, font=font_text_three, fill=TEXT_COLOR + (text_x, text_y), + text_three, + font=font_text_three, + fill=TEXT_COLOR, + align=text_align, + anchor=text_anchor, ) text_layer_box = text_layer.getbbox() - return text_layer.crop(text_layer_box) + return text_layer.crop(text_layer_box), text_align def generate_instance_layer(content_width): """Places components for instance preview""" font_instance = get_font("light", size=28) - site = models.SiteSettings.objects.get() + site = models.SiteSettings.get() + site_name = process_arabic_script(site.name) + site_name_width = round(font_instance.getlength(site_name)) + site_name_direction = get_text_direction(site_name) if site.logo_small: with Image.open(site.logo_small) as logo_img: @@ -185,33 +253,30 @@ def generate_instance_layer(content_width): except FileNotFoundError: logo_img = None - instance_layer = Image.new("RGBA", (content_width, 62), color=TRANSPARENT_COLOR) - - instance_text_x = 0 + logo_gutter_width = 60 if logo_img else 0 + layer_width = logo_gutter_width + site_name_width + instance_layer = Image.new("RGBA", (layer_width, 62), color=TRANSPARENT_COLOR) if logo_img: logo_img.thumbnail((50, 50), Image.Resampling.LANCZOS) - - instance_layer.paste(logo_img, (0, 0)) - - instance_text_x = instance_text_x + 60 + logo_img_x = 0 if site_name_direction == "left" else layer_width - 50 + instance_layer.paste(logo_img, (logo_img_x, 0)) instance_layer_draw = ImageDraw.Draw(instance_layer) + text_x = logo_gutter_width if site_name_direction == "left" else 0 instance_layer_draw.text( - (instance_text_x, 10), site.name, font=font_instance, fill=TEXT_COLOR + (text_x, 10), site_name, font=font_instance, fill=TEXT_COLOR ) - line_width = 50 + 10 + round(font_instance.getlength(site.name)) - line_layer = Image.new( - "RGBA", (line_width, 2), color=(*(ImageColor.getrgb(TEXT_COLOR)), 50) + "RGBA", (layer_width, 2), color=(*(ImageColor.getrgb(TEXT_COLOR)), 50) ) instance_layer.alpha_composite(line_layer, (0, 60)) return instance_layer -def generate_rating_layer(rating, content_width): +def generate_rating_layer(rating): """Places components for rating preview""" path_star_full = os.path.join(settings.STATIC_ROOT, "images/icons/star-full.png") path_star_empty = os.path.join(settings.STATIC_ROOT, "images/icons/star-empty.png") @@ -220,12 +285,14 @@ def generate_rating_layer(rating, content_width): icon_size = 64 icon_margin = 10 + layer_width = (5 * icon_size) + (4 * icon_margin) + rating_layer_base = Image.new( - "RGBA", (content_width, icon_size), color=TRANSPARENT_COLOR + "RGBA", (layer_width, icon_size), color=TRANSPARENT_COLOR ) - rating_layer_color = Image.new("RGBA", (content_width, icon_size), color=TEXT_COLOR) + rating_layer_color = Image.new("RGBA", (layer_width, icon_size), color=TEXT_COLOR) rating_layer_mask = Image.new( - "RGBA", (content_width, icon_size), color=TRANSPARENT_COLOR + "RGBA", (layer_width, icon_size), color=TRANSPARENT_COLOR ) position_x = 0 @@ -280,8 +347,6 @@ def generate_default_inner_img(): return default_cover -# pylint: disable=too-many-locals -# pylint: disable=too-many-statements def generate_preview_image( texts=None, picture=None, rating=None, show_instance_layer=True ): @@ -296,7 +361,7 @@ def generate_preview_image( ) color_thief = ColorThief(picture) dominant_color = color_thief.get_color(quality=1) - except: # pylint: disable=bare-except + except: inner_img_layer = generate_default_inner_img() dominant_color = ImageColor.getrgb(DEFAULT_COVER_COLOR) @@ -329,10 +394,16 @@ def generate_preview_image( img = Image.new("RGBA", (IMG_WIDTH, IMG_HEIGHT), color=image_bg_color) # Contents - inner_img_x = margin + inner_img_width - inner_img_layer.width inner_img_y = math.floor((IMG_HEIGHT - inner_img_layer.height) / 2) - content_x = margin + inner_img_width + gutter - content_width = IMG_WIDTH - content_x - margin + content_width = IMG_WIDTH - margin - gutter - inner_img_width - margin + + texts_layer, text_align = generate_texts_layer(texts, content_width) + if text_align == "left": + inner_img_x = margin + inner_img_width - inner_img_layer.width + content_x = margin + inner_img_width + gutter + elif text_align == "right": + inner_img_x = IMG_WIDTH - margin - inner_img_width + content_x = margin contents_layer = Image.new( "RGBA", (content_width, IMG_HEIGHT), color=TRANSPARENT_COLOR @@ -341,20 +412,31 @@ def generate_preview_image( if show_instance_layer: instance_layer = generate_instance_layer(content_width) - contents_layer.alpha_composite(instance_layer, (0, contents_composite_y)) + instance_layer_x = ( + 0 if text_align == "left" else content_width - instance_layer.width + ) + contents_layer.alpha_composite( + instance_layer, (instance_layer_x, contents_composite_y) + ) contents_composite_y = contents_composite_y + instance_layer.height + gutter - texts_layer = generate_texts_layer(texts, content_width) - contents_layer.alpha_composite(texts_layer, (0, contents_composite_y)) + texts_layer_x = 0 if text_align == "left" else content_width - texts_layer.width + contents_layer.alpha_composite(texts_layer, (texts_layer_x, contents_composite_y)) contents_composite_y = contents_composite_y + texts_layer.height + gutter if rating: # Add some more margin contents_composite_y = contents_composite_y + gutter - rating_layer = generate_rating_layer(rating, content_width) - + rating_layer = generate_rating_layer(rating) if rating_layer: - contents_layer.alpha_composite(rating_layer, (0, contents_composite_y)) + rating_layer_x = ( + 0 if text_align == "left" else content_width - rating_layer.width + ) + if text_align == "right": + rating_layer = ImageOps.mirror(rating_layer) + contents_layer.alpha_composite( + rating_layer, (rating_layer_x, contents_composite_y) + ) contents_composite_y = contents_composite_y + rating_layer.height + gutter contents_layer_box = contents_layer.getbbox() @@ -426,7 +508,7 @@ def generate_site_preview_image_task(): if not settings.ENABLE_PREVIEW_IMAGES: return - site = models.SiteSettings.objects.get() + site = models.SiteSettings.get() if site.logo: logo = site.logo diff --git a/bookwyrm/readwise.py b/bookwyrm/readwise.py new file mode 100644 index 0000000000..73c07af1ed --- /dev/null +++ b/bookwyrm/readwise.py @@ -0,0 +1,99 @@ +"""Readwise integration helpers.""" + +import logging +import re +from html import unescape + +import requests +from django.utils.html import strip_tags + +from bookwyrm import models, settings +from bookwyrm.models.readthrough import ProgressMode +from bookwyrm.tasks import app, MISC + +logger = logging.getLogger(__name__) + +READWISE_HIGHLIGHTS_URL = "https://readwise.io/api/v2/highlights/" +READWISE_SOURCE_TYPE = "bookwyrm" + + +def _clean_text(value): + value = unescape(strip_tags(value or "")) + return re.sub(r"\s+", " ", value).strip() + + +def _truncate(value, length): + if not value: + return value + return value[:length] + + +def _page_location(quotation): + if quotation.position_mode != ProgressMode.PAGE or not quotation.position: + return None + try: + return int(quotation.position) + except (TypeError, ValueError): + return None + + +def build_readwise_highlight(quotation): + """Convert a BookWyrm quotation to a Readwise highlight payload item.""" + highlight = { + "text": _truncate(_clean_text(quotation.raw_quote or quotation.quote), 8191), + "title": _truncate(quotation.book.title, 511), + "source_type": READWISE_SOURCE_TYPE, + "category": "books", + "source_url": _truncate(quotation.book.remote_id, 2047), + "highlight_url": _truncate( + quotation.remote_id or quotation.get_remote_id(), 4095 + ), + "highlighted_at": quotation.published_date.isoformat(), + } + + if author := _clean_text(quotation.book.author_text): + highlight["author"] = _truncate(author, 1024) + + if note := _clean_text(quotation.raw_content or quotation.content): + highlight["note"] = _truncate(note, 8191) + + if location := _page_location(quotation): + highlight["location_type"] = "page" + highlight["location"] = location + + return highlight + + +@app.task(queue=MISC) +def sync_readwise_quotation(quotation_id): + """Send a quotation to Readwise for users who configured a token.""" + quotation = ( + models.Quotation.objects.select_related("user", "book") + .prefetch_related("book__authors") + .get(id=quotation_id) + ) + + token = quotation.user.readwise_api_key + if not token or quotation.deleted: + return None + + highlight = build_readwise_highlight(quotation) + if not highlight["text"]: + return None + + try: + response = requests.post( + READWISE_HIGHLIGHTS_URL, + headers={ + "Authorization": f"Token {token}", + "User-Agent": settings.USER_AGENT, + }, + json={"highlights": [highlight]}, + timeout=settings.QUERY_TIMEOUT, + ) + response.raise_for_status() + except requests.RequestException as err: + logger.warning("Unable to sync quotation %s to Readwise: %s", quotation_id, err) + return None + + return response.json() diff --git a/bookwyrm/redis_store.py b/bookwyrm/redis_store.py index e188487aaa..963b94ac62 100644 --- a/bookwyrm/redis_store.py +++ b/bookwyrm/redis_store.py @@ -1,6 +1,10 @@ -""" access the activity stores stored in redis """ +"""access the activity stores stored in redis""" + from abc import ABC, abstractmethod +from typing import Any + import redis +from django.db.models.query import QuerySet from bookwyrm import settings @@ -32,7 +36,6 @@ def add_object_to_stores(self, obj, stores, execute=True): # and go! return pipeline.execute() - # pylint: disable=no-self-use def remove_object_from_stores(self, obj, stores): """remove an object from all stores""" # if the stores are provided, the object can just be an id @@ -45,27 +48,28 @@ def remove_object_from_stores(self, obj, stores): pipeline.zrem(store, -1, obj_id) pipeline.execute() - def bulk_add_objects_to_store(self, objs, store): + def bulk_add_objects_to_store(self, objs: QuerySet[Any], store: str) -> None: """add a list of objects to a given store""" pipeline = r.pipeline() - for obj in objs[: self.max_length]: + max_length_objs = objs[: self.max_length] + for obj in max_length_objs: pipeline.zadd(store, self.get_value(obj)) - if objs and self.max_length: + if max_length_objs and self.max_length: pipeline.zremrangebyrank(store, 0, -1 * self.max_length) pipeline.execute() - def bulk_remove_objects_from_store(self, objs, store): + def bulk_remove_objects_from_store(self, objs: QuerySet[Any], store: str) -> None: """remove a list of objects from a given store""" pipeline = r.pipeline() for obj in objs[: self.max_length]: pipeline.zrem(store, -1, obj.id) pipeline.execute() - def get_store(self, store, **kwargs): # pylint: disable=no-self-use + def get_store(self, store: str, **kwargs) -> list[int]: """load the values in a store""" return r.zrevrange(store, 0, -1, **kwargs) - def populate_store(self, store): + def populate_store(self, store: str) -> None: """go from zero to a store""" pipeline = r.pipeline() queryset = self.get_objects_for_store(store) diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index b877c9f878..56a0504317 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -3,7 +3,7 @@ import os from typing import AnyStr -from environs import Env +from environs import FileAwareEnv import requests @@ -11,9 +11,7 @@ from django.core.exceptions import ImproperlyConfigured -# pylint: disable=line-too-long - -env = Env() +env = FileAwareEnv() env.read_env() DOMAIN = env("DOMAIN") @@ -32,8 +30,6 @@ DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English") SESSION_COOKIE_AGE = env.int("SESSION_COOKIE_AGE", 3600 * 24 * 365) # One year ...ish -JS_CACHE = "8a89cad7" - # email EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend") EMAIL_HOST = env("EMAIL_HOST") @@ -65,15 +61,31 @@ PREVIEW_IMG_WIDTH = env.int("PREVIEW_IMG_WIDTH", 1200) PREVIEW_IMG_HEIGHT = env.int("PREVIEW_IMG_HEIGHT", 630) PREVIEW_DEFAULT_COVER_COLOR = env.str("PREVIEW_DEFAULT_COVER_COLOR", "#002549") -PREVIEW_DEFAULT_FONT = env.str("PREVIEW_DEFAULT_FONT", "Source Han Sans") +PREVIEW_DEFAULT_FONT = env.str( + "PREVIEW_DEFAULT_FONT", + "NotoSansMultilanguage-Bold, NotoSansMultilanguage-Regular, NotoSansMultilanguage-Light", +) -FONTS = { - "Source Han Sans": { - "directory": "source_han_sans", - "filename": "SourceHanSans-VF.ttf.ttc", - "url": "https://github.com/adobe-fonts/source-han-sans/raw/release/Variable/OTC/SourceHanSans-VF.ttf.ttc", - } -} +FONTS = env.json( + "FONTS", + { + "NotoSansMultilanguage-Bold": { + "directory": "NotoMultilanguageFonts", + "filename": "NotoSansMultilanguage-Bold.ttf", + "url": "https://github.com/sefadogann/noto-multilanguage/raw/refs/heads/main/NotoMultilanguageFonts/NotoSansMultilanguage-Bold.ttf", + }, + "NotoSansMultilanguage-Regular": { + "directory": "NotoMultilanguageFonts", + "filename": "NotoSansMultilanguage-Regular.ttf", + "url": "https://github.com/sefadogann/noto-multilanguage/raw/refs/heads/main/NotoMultilanguageFonts/NotoSansMultilanguage-Regular.ttf", + }, + "NotoSansMultilanguage-Light": { + "directory": "NotoMultilanguageFonts", + "filename": "NotoSansMultilanguage-Light.ttf", + "url": "https://github.com/sefadogann/noto-multilanguage/raw/refs/heads/main/NotoMultilanguageFonts/NotoSansMultilanguage-Light.ttf", + }, + }, +) FONT_DIR = os.path.join(STATIC_ROOT, "fonts") # Quick-start development settings - unsuitable for production @@ -121,20 +133,28 @@ "django.middleware.csrf.CsrfViewMiddleware", "csp.middleware.CSPMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", + "bookwyrm.middleware.RequireSignedGet", + "bookwyrm.middleware.RequireLoginNearlyEverywhere", "bookwyrm.middleware.TimezoneMiddleware", "bookwyrm.middleware.IPBlocklistMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "bookwyrm.middleware.FileTooBig", + "bookwyrm.middleware.ForceLogoutMiddleware", ] ROOT_URLCONF = "bookwyrm.urls" + +default_loaders = [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", +] +cached_loaders = [("django.template.loaders.cached.Loader", default_loaders)] TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": ["templates"], - "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.debug", @@ -143,6 +163,7 @@ "django.contrib.messages.context_processors.messages", "bookwyrm.context_processors.site_settings", ], + "loaders": default_loaders if DEBUG else cached_loaders, }, }, ] @@ -244,6 +265,7 @@ # timeout for a query to an individual connector QUERY_TIMEOUT = env.int("INTERACTIVE_QUERY_TIMEOUT", env.int("QUERY_TIMEOUT", 5)) +CACHE_KEY_PREFIX = "django_cache" # Redis cache backend if env.bool("USE_DUMMY_CACHE", False): CACHES = { @@ -260,6 +282,7 @@ "default": { "BACKEND": "django.core.cache.backends.redis.RedisCache", "LOCATION": REDIS_ACTIVITY_URL, + "KEY_PREFIX": CACHE_KEY_PREFIX, }, "file_resubmit": { "BACKEND": "django.core.cache.backends.filebased.FileBasedCache", @@ -489,7 +512,7 @@ "BACKEND": "django.core.files.storage.FileSystemStorage", }, "staticfiles": { - "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", + "BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage", }, } # Static settings @@ -525,6 +548,7 @@ "endpoint_url": env("EXPORTS_S3_ENDPOINT_URL", env("AWS_S3_ENDPOINT_URL")), "custom_domain": env("EXPORTS_S3_CUSTOM_DOMAIN", None), "bucket_name": env("EXPORTS_STORAGE_BUCKET_NAME"), + "querystring_auth": True, }, } else: @@ -554,3 +578,6 @@ # We only allow specifying DATA_UPLOAD_MAX_MEMORY_SIZE in MiB from .env # (note the difference in variable names). DATA_UPLOAD_MAX_MEMORY_SIZE = env.int("DATA_UPLOAD_MAX_MEMORY_MiB", 100) << 20 + + +UPLOAD_IMAGE_DIMENSIONS = env.list("UPLOAD_IMAGE_DIMENSIONS", [400, 1200], subcast=int) diff --git a/bookwyrm/signatures.py b/bookwyrm/signatures.py index f59367b51e..29550446ec 100644 --- a/bookwyrm/signatures.py +++ b/bookwyrm/signatures.py @@ -1,4 +1,5 @@ -""" signs activitypub activities """ +"""signs activitypub activities""" + import hashlib from urllib.parse import urlparse import datetime @@ -102,7 +103,7 @@ def parse(cls, request): return cls(key_id, headers, signature) - def verify(self, public_key, request): + def verify(self, public_key, request, request_type="post"): """verify rsa signature""" if http_date_age(request.headers["date"]) > MAX_SIGNATURE_AGE: raise ValueError(f"Request too old: {request.headers['date']}") @@ -111,7 +112,9 @@ def verify(self, public_key, request): comparison_string = [] for signed_header_name in self.headers.split(" "): if signed_header_name == "(request-target)": - comparison_string.append(f"(request-target): post {request.path}") + comparison_string.append( + f"(request-target): {request_type} {request.path}" + ) else: if signed_header_name == "digest": verify_digest(request) diff --git a/bookwyrm/static/css/bookwyrm/_all.scss b/bookwyrm/static/css/bookwyrm/_all.scss index 7e50fe3e17..3de604ceed 100644 --- a/bookwyrm/static/css/bookwyrm/_all.scss +++ b/bookwyrm/static/css/bookwyrm/_all.scss @@ -9,8 +9,10 @@ @import "components/breadcrumbs"; @import "components/copy"; @import "components/details"; +@import "components/embed"; @import "components/file_input"; @import "components/live_message"; +@import "components/modal"; @import "components/shelving"; @import "components/stars"; @import "components/status"; diff --git a/bookwyrm/static/css/bookwyrm/components/_book_preview_table.scss b/bookwyrm/static/css/bookwyrm/components/_book_preview_table.scss index fdbb29f0e0..1343b971a9 100644 --- a/bookwyrm/static/css/bookwyrm/components/_book_preview_table.scss +++ b/bookwyrm/static/css/bookwyrm/components/_book_preview_table.scss @@ -5,6 +5,10 @@ vertical-align: middle; } +.book-preview .nowrap-date-whitespace { + white-space: nowrap; +} + @media only screen and (max-width: 768px) { table.is-mobile, table.is-mobile tbody { diff --git a/bookwyrm/static/css/bookwyrm/components/_details.scss b/bookwyrm/static/css/bookwyrm/components/_details.scss index 4145554ebe..73bae4311d 100644 --- a/bookwyrm/static/css/bookwyrm/components/_details.scss +++ b/bookwyrm/static/css/bookwyrm/components/_details.scss @@ -3,6 +3,7 @@ details summary { cursor: pointer; + list-style-type: none; } summary::-webkit-details-marker { @@ -79,8 +80,6 @@ details.dropdown .dropdown-menu a:focus-visible { ******************************************************************************/ details.details-panel { - box-shadow: 0 0 0 1px $border; - transition: box-shadow 0.2s ease; padding: 0; > * { @@ -96,11 +95,6 @@ details.details-panel { } } -details[open].details-panel, -details.details-panel:hover { - box-shadow: 0 0 0 1px $border; -} - details summary .details-close { position: absolute; right: 0; @@ -136,3 +130,17 @@ details[open] summary .details-close { #navbar-dropdown .navbar-item:hover { background-color: $background-secondary; } + +/** Special tyoe of details panel that shows some content + ******************************************************************************/ +.preview-details { + background-color: $notification-background-color; + border-radius: $notification-radius; + position: relative; + padding: 0.75rem; +} + +.preview-details .details-close { + top: 0.75rem; + right: 0.75rem; +} diff --git a/bookwyrm/static/css/bookwyrm/components/_embed.scss b/bookwyrm/static/css/bookwyrm/components/_embed.scss new file mode 100644 index 0000000000..d35112bd57 --- /dev/null +++ b/bookwyrm/static/css/bookwyrm/components/_embed.scss @@ -0,0 +1,6 @@ +/** CSS for Embedded layout + ******************************************************************************/ + + .logo-image-height { + height: 2rem; + } \ No newline at end of file diff --git a/bookwyrm/static/css/bookwyrm/components/_modal.scss b/bookwyrm/static/css/bookwyrm/components/_modal.scss new file mode 100644 index 0000000000..eeb828edcc --- /dev/null +++ b/bookwyrm/static/css/bookwyrm/components/_modal.scss @@ -0,0 +1,6 @@ +/* Modal dialogues + ******************************************************************************/ + + .box-sized-input { + box-sizing: border-box + } \ No newline at end of file diff --git a/bookwyrm/static/css/bookwyrm/components/_stars.scss b/bookwyrm/static/css/bookwyrm/components/_stars.scss index db2772dc05..0175826538 100644 --- a/bookwyrm/static/css/bookwyrm/components/_stars.scss +++ b/bookwyrm/static/css/bookwyrm/components/_stars.scss @@ -13,44 +13,80 @@ * * Specificity makes hovering taking over checked inputs. * - * \e9d9: filled star * \e9d7: empty star; + * \e9d8: half-filled star; + * \e9d9: filled star * -------------------------------------------------------------------------- */ .form-rate-stars { width: max-content; } -/* All stars are visually filled by default. */ +/* All stars are visually empty by default. */ .form-rate-stars .icon::before { + content: "\e9d7"; /* icon-star-empty */ +} + +// if a star container is checked, fill all stars +.form-rate-stars:has(input:checked) .icon::before { content: "\e9d9"; /* icon-star-full */ } -/* Icons directly following half star inputs are marked as half */ -.form-rate-stars input.half:checked ~ .icon::before { +// if a star container has a half-input checked, its icon is a half-star +.form-rate-stars .star-container:has(input.half:checked) .icon::before { content: "\e9d8"; /* icon-star-half */ } -/* stylelint-disable no-descending-specificity */ -.form-rate-stars input.half:checked + input + .icon:hover::before { - content: "\e9d8" !important; /* icon-star-half */ +// if a star container has a full-input checked, its icon is a full-star +.form-rate-stars .star-container:has(input.full:checked) .icon::before { + content: "\e9d9"; /* icon-star-full */ } -/* Icons directly following half check inputs that follow the checked input are emptied. */ -.form-rate-stars input.half:checked + input + .icon ~ .icon::before { +// stars following stars with checked inputs have icons that are empty +.form-rate-stars .star-container:has(input:checked) ~ .star-container .icon::before { content: "\e9d7"; /* icon-star-empty */ } -/* Icons directly following inputs that follow the checked input are emptied. */ -.form-rate-stars input:checked ~ input + .icon::before { - content: "\e9d7"; /* icon-star-empty */ +// When a star is hovered, pre-fill all icons as full +.form-rate-stars:hover .icon::before +{ + content: "\e9d9" !important; /* icon-star-full */ +} + +// when an star is hovered, following icons are emptied +.form-rate-stars .star-container:hover ~ .star-container .icon::before +{ + content: "\e9d7" !important; /* icon-star-empty */ } -/* When a label is hovered, repeat the fill-all-then-empty-following pattern. */ -.form-rate-stars:hover .icon.icon::before { +// if you hover over a star's half-input make its icon a half-star +.inputs-container:has(input.half:hover) + .icon::before +{ + content: "\e9d8" !important; /* icon-star-half */ +} + +// if you hover over a star's full-input make its icon a full-star +.inputs-container:has(input.full:hover) + .icon::before +{ content: "\e9d9" !important; /* icon-star-full */ } -.form-rate-stars .icon:hover ~ .icon::before { - content: "\e9d7" !important; /* icon-star-empty */ +.form-rate-stars input.half { + width: 0.75rem !important; + position:relative; + +} + +.form-rate-stars input.full { + width: 0.75rem !important; + position:relative; +} + +.form-rate-stars .star-container { + position: relative; +} + +.inputs-container { + position: absolute; + opacity: 0; } diff --git a/bookwyrm/static/css/bookwyrm/components/_status.scss b/bookwyrm/static/css/bookwyrm/components/_status.scss index 3ec679cd9e..8f61f929e4 100644 --- a/bookwyrm/static/css/bookwyrm/components/_status.scss +++ b/bookwyrm/static/css/bookwyrm/components/_status.scss @@ -42,8 +42,12 @@ /* Threads ******************************************************************************/ -.thread .is-main .card { - box-shadow: 0 0.5em 1em -0.125em rgba($link, 0.35), 0 0 0 1px rgba($link, 0.02); +.is-thread-secondary .card { + background-color: $background-secondary; +} + +.is-thread-main .card { + box-shadow: 0.2em 0.2em $border; } .thread::after { diff --git a/bookwyrm/static/css/bookwyrm/overrides/_bulma_overrides.scss b/bookwyrm/static/css/bookwyrm/overrides/_bulma_overrides.scss index 9ab44f89da..907dfd4a15 100644 --- a/bookwyrm/static/css/bookwyrm/overrides/_bulma_overrides.scss +++ b/bookwyrm/static/css/bookwyrm/overrides/_bulma_overrides.scss @@ -58,10 +58,37 @@ .card { overflow: visible; + box-shadow: none; + border: 1px solid $border; } -.card.has-border { - border: 1px solid $border; +.card, .card-header { + box-shadow: none; +} + +.card-content { + padding: 1rem; +} + +.card-footer { + border-top: none; +} + +.card-footer-item { + border: none !important; +} + +.card-footer-item { + padding: 0.25rem; +} + +.box { + box-shadow: none; + border: 1px solid $border; +} + +.popout { + box-shadow: 0.2em 0.2em $border; } .scroll-x { @@ -115,3 +142,7 @@ font-size: $size-6; } } + +.is-break-word { + overflow-wrap: anywhere; +} diff --git a/bookwyrm/static/css/bookwyrm/utilities/_a11y.scss b/bookwyrm/static/css/bookwyrm/utilities/_a11y.scss index cf4ef6ab0e..cefd8f8d8b 100644 --- a/bookwyrm/static/css/bookwyrm/utilities/_a11y.scss +++ b/bookwyrm/static/css/bookwyrm/utilities/_a11y.scss @@ -1,7 +1,7 @@ @media only screen and (max-width: 768px) { .is-sr-only-mobile { border: none !important; - clip: rect(0, 0, 0, 0) !important; + clip: rect(0, 0, 0, 0) !important; /* stylelint-disable property-no-deprecated */ height: 0.01em !important; overflow: hidden !important; padding: 0 !important; diff --git a/bookwyrm/static/css/fonts/icomoon.eot b/bookwyrm/static/css/fonts/icomoon.eot index 33dc07eecb..ec0fa22cd5 100644 Binary files a/bookwyrm/static/css/fonts/icomoon.eot and b/bookwyrm/static/css/fonts/icomoon.eot differ diff --git a/bookwyrm/static/css/fonts/icomoon.svg b/bookwyrm/static/css/fonts/icomoon.svg index 058c192262..df36bd5126 100644 --- a/bookwyrm/static/css/fonts/icomoon.svg +++ b/bookwyrm/static/css/fonts/icomoon.svg @@ -7,28 +7,29 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + @@ -51,4 +52,6 @@ + + \ No newline at end of file diff --git a/bookwyrm/static/css/fonts/icomoon.ttf b/bookwyrm/static/css/fonts/icomoon.ttf index 89d3be8fa0..eb8b919f29 100644 Binary files a/bookwyrm/static/css/fonts/icomoon.ttf and b/bookwyrm/static/css/fonts/icomoon.ttf differ diff --git a/bookwyrm/static/css/fonts/icomoon.woff b/bookwyrm/static/css/fonts/icomoon.woff index 95325ab4aa..e5610c0614 100644 Binary files a/bookwyrm/static/css/fonts/icomoon.woff and b/bookwyrm/static/css/fonts/icomoon.woff differ diff --git a/bookwyrm/static/css/vendor/icons.css b/bookwyrm/static/css/vendor/icons.css index 6af5c28134..35e737e848 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?nr4nq7'); - src: url('../fonts/icomoon.eot?nr4nq7#iefix') format('embedded-opentype'), - url('../fonts/icomoon.ttf?nr4nq7') format('truetype'), - url('../fonts/icomoon.woff?nr4nq7') format('woff'), - url('../fonts/icomoon.svg?nr4nq7#icomoon') format('svg'); + src: url('../fonts/icomoon.eot?weljc9'); + src: url('../fonts/icomoon.eot?weljc9#iefix') format('embedded-opentype'), + url('../fonts/icomoon.ttf?weljc9') format('truetype'), + url('../fonts/icomoon.woff?weljc9') format('woff'), + url('../fonts/icomoon.svg?weljc9#icomoon') format('svg'); font-weight: normal; font-style: normal; font-display: block; @@ -25,73 +25,75 @@ -moz-osx-font-smoothing: grayscale; } -.icon.is-small { - font-size: small; +.icon-external-link:before { + content: "\e900"; } - -.icon-book:before { +.icon-question-circle:before { content: "\e901"; } -.icon-envelope:before { +.icon-book:before { content: "\e902"; } -.icon-arrow-right:before { +.icon-envelope:before { content: "\e903"; } -.icon-bell:before { +.icon-arrow-right:before { content: "\e904"; } -.icon-x:before { +.icon-bell:before { content: "\e905"; } -.icon-quote-close:before { +.icon-x:before { content: "\e906"; } -.icon-quote-open:before { +.icon-quote-close:before { content: "\e907"; } -.icon-image:before { +.icon-quote-open:before { content: "\e908"; } -.icon-pencil:before { +.icon-image:before { content: "\e909"; } -.icon-list:before { +.icon-pencil:before { content: "\e90a"; } -.icon-unlock:before { +.icon-list:before { content: "\e90b"; } -.icon-globe:before { +.icon-unlock:before { content: "\e90c"; } -.icon-lock:before { +.icon-globe:before { content: "\e90d"; } -.icon-chain-broken:before { +.icon-lock:before { content: "\e90e"; } -.icon-chain:before { +.icon-chain-broken:before { content: "\e90f"; } -.icon-comments:before { +.icon-chain:before { content: "\e910"; } -.icon-comment:before { +.icon-comments:before { content: "\e911"; } -.icon-boost:before { +.icon-comment:before { content: "\e912"; } -.icon-arrow-left:before { +.icon-boost:before { content: "\e913"; } -.icon-arrow-up:before { +.icon-arrow-left:before { content: "\e914"; } -.icon-arrow-down:before { +.icon-arrow-up:before { content: "\e915"; } +.icon-arrow-down:before { + content: "\e916"; +} .icon-local:before { content: "\e917"; } @@ -125,9 +127,21 @@ .icon-copy:before { content: "\e92c"; } +.icon-barcode:before { + content: "\e937"; +} +.icon-spinner:before { + content: "\e97a"; +} .icon-search:before { content: "\e986"; } +.icon-eye:before { + content: "\e9ce"; +} +.icon-eye-blocked:before { + content: "\e9d1"; +} .icon-star-empty:before { content: "\e9d7"; } @@ -143,21 +157,12 @@ .icon-plus:before { content: "\ea0a"; } -.icon-question-circle:before { - content: "\e900"; -} -.icon-spinner:before { - content: "\e97a"; -} .icon-download:before { content: "\ea36"; } -.icon-barcode:before { - content: "\e937"; -} -.icon-eye:before { - content: "\e9ce"; +.icon-share:before { + content: "\ea7d"; } -.icon-eye-blocked:before { - content: "\e9d1"; +.icon-new-tab:before { + content: "\ea7e"; } diff --git a/bookwyrm/static/js/autocomplete.js b/bookwyrm/static/js/autocomplete.js index 6836d356d8..84286ac619 100644 --- a/bookwyrm/static/js/autocomplete.js +++ b/bookwyrm/static/js/autocomplete.js @@ -1,6 +1,27 @@ (function () { "use strict"; + const mimeTypes = [ + "AAC", + "AZW", + "Daisy", + "EPUB", + "FB2", + "FB3", + "FLAC", + "HTML", + "M4A", + "M4B", + "MOBI", + "MP3", + "OGG", + "PDF", + "Plaintext", + "Print book", + ]; + + const regexEscape = (string) => string.replace(/[$^*()-+.?[]{}|\\\/]/g, "\\$&"); + /** * Suggest a completion as a user types * @@ -18,15 +39,16 @@ function autocomplete(event) { const input = event.target; - // Get suggestions - let trie = tries[input.getAttribute("data-autocomplete")]; + const cleanInput = regexEscape(input.value); - let suggestions = getSuggestions(input.value, trie); + const suggestions = mimeTypes.filter((mimeType) => + RegExp("^" + cleanInput, "i").test(mimeType) + ); const boxId = input.getAttribute("list"); // Create suggestion box, if needed - let suggestionsBox = document.getElementById(boxId); + const suggestionsBox = document.getElementById(boxId); // Clear existing suggestions suggestionsBox.innerHTML = ""; @@ -40,149 +62,7 @@ }); } - function getSuggestions(input, trie) { - // Follow the trie through the provided input - input = input.toLowerCase(); - - input.split("").forEach((letter) => { - if (!trie) { - return; - } - - trie = trie[letter]; - }); - - if (!trie) { - return []; - } - - return searchTrie(trie); - } - - function searchTrie(trie) { - const options = Object.values(trie); - - if (typeof trie == "string") { - return [trie]; - } - - return options - .map((option) => { - const newTrie = option; - - if (typeof newTrie == "string") { - return [newTrie]; - } - - return searchTrie(newTrie); - }) - .reduce((prev, next) => prev.concat(next)); - } - document.querySelectorAll("[data-autocomplete]").forEach((input) => { input.addEventListener("input", autocomplete); }); })(); - -const tries = { - mimetype: { - a: { - a: { - c: "AAC", - }, - z: { - w: "AZW", - }, - }, - d: { - a: { - i: { - s: { - y: "Daisy", - }, - }, - }, - }, - e: { - p: { - u: { - b: "EPUB", - }, - }, - }, - f: { - b: { - 2: "FB2", - 3: "FB3", - }, - l: { - a: { - c: "FLAC", - }, - }, - }, - h: { - t: { - m: { - l: "HTML", - }, - }, - }, - m: { - 4: { - a: "M4A", - b: "M4B", - }, - o: { - b: { - i: "MOBI", - }, - }, - p: { - 3: "MP3", - }, - }, - o: { - g: { - g: "OGG", - }, - }, - p: { - d: { - f: "PDF", - }, - l: { - a: { - i: { - n: { - t: { - e: { - x: { - t: "Plaintext", - }, - }, - }, - }, - }, - }, - }, - r: { - i: { - n: { - t: { - " ": { - b: { - o: { - o: { - k: "Print book", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, -}; diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index 3088165d6f..b2b1991aa5 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -60,6 +60,12 @@ let BookWyrm = new (class { .forEach((form) => form.addEventListener("submit", (e) => this.setPreferredTimezone(e, form)) ); + + document + .querySelectorAll("button[name='button-book-list']") + .forEach((button) => + button.addEventListener("click", this.checkListSelection.bind(this)) + ); } /** @@ -139,7 +145,7 @@ let BookWyrm = new (class { * @return {undefined} */ updateCountElement(counter, data) { - let count = data.count; + const count = data.count; if (count === undefined) { return; @@ -149,9 +155,21 @@ let BookWyrm = new (class { const hasMentions = data.has_mentions; if (count != currentCount) { - this.addRemoveClass(counter.closest("[data-poll-wrapper]"), "is-hidden", count < 1); + const wrapper = counter.closest("[data-poll-wrapper]"); + + if (count < 1) { + this.classHide(wrapper); + } else { + this.classShow(wrapper); + } + counter.innerText = count; - this.addRemoveClass(counter.closest("[data-poll-wrapper]"), "is-danger", hasMentions); + + if (hasMentions) { + this.classList.add("is-danger"); + } else { + this.classList.remove("is-danger"); + } } } @@ -162,11 +180,11 @@ let BookWyrm = new (class { * @return {undefined} */ revealForm(event) { - let trigger = event.currentTarget; - let hidden = trigger.closest(".hidden-form").querySelectorAll(".is-hidden")[0]; + const trigger = event.currentTarget; + const hidden = trigger.closest(".hidden-form").querySelectorAll(".is-hidden")[0]; if (hidden) { - this.addRemoveClass(hidden, "is-hidden", !hidden); + this.classShow(hidden); } } @@ -177,11 +195,11 @@ let BookWyrm = new (class { * @return {undefined} */ hideForm(event) { - let trigger = event.currentTarget; - let targetId = trigger.dataset.hides; - let visible = document.getElementById(targetId); + const trigger = event.currentTarget; + const targetId = trigger.dataset.hides; + const visible = document.getElementById(targetId); - this.addRemoveClass(visible, "is-hidden", true); + this.classHide(visible); } /** @@ -191,9 +209,9 @@ let BookWyrm = new (class { * @return {undefined} */ hideSelf(event) { - let trigger = event.currentTarget; + const trigger = event.currentTarget; - this.addRemoveClass(trigger, "is-hidden", true); + this.classHide(trigger); } /** @@ -203,13 +221,13 @@ let BookWyrm = new (class { * @return {undefined} */ toggleAction(event) { - let trigger = event.currentTarget; + const trigger = event.currentTarget; if (!trigger.dataset.allowDefault || event.currentTarget == event.target) { event.preventDefault(); } - let pressed = trigger.getAttribute("aria-pressed") === "false"; - let targetId = trigger.dataset.controls; + const pressed = trigger.getAttribute("aria-pressed") === "false"; + const targetId = trigger.dataset.controls; // Toggle pressed status on all triggers controlling the same target. document @@ -223,10 +241,15 @@ let BookWyrm = new (class { // @todo Find a better way to handle the exception. if (targetId && !trigger.classList.contains("pulldown-menu")) { - let target = document.getElementById(targetId); + const target = document.getElementById(targetId); - this.addRemoveClass(target, "is-hidden", !pressed); - this.addRemoveClass(target, "is-active", pressed); + if (pressed) { + this.classShow(target); + this.classActivate(target); + } else { + this.classHide(target); + this.classDeactivate(target); + } } // Show/hide pulldown-menus. @@ -235,28 +258,28 @@ let BookWyrm = new (class { } // Show/hide container. - let container = document.getElementById("hide_" + targetId); + const container = document.getElementById("hide_" + targetId); if (container) { this.toggleContainer(container, pressed); } // Check checkbox, if appropriate. - let checkbox = trigger.dataset.controlsCheckbox; + const checkbox = trigger.dataset.controlsCheckbox; if (checkbox) { this.toggleCheckbox(checkbox, pressed); } // Toggle form disabled, if appropriate - let disable = trigger.dataset.disables; + const disable = trigger.dataset.disables; if (disable) { this.toggleDisabled(disable, !pressed); } // Set focus, if appropriate. - let focus = trigger.dataset.focusTarget; + const focus = trigger.dataset.focusTarget; if (focus) { this.toggleFocus(focus); @@ -272,14 +295,18 @@ let BookWyrm = new (class { * @return {undefined} */ toggleMenu(trigger, targetId) { - let expanded = trigger.getAttribute("aria-expanded") == "false"; + const expanded = trigger.getAttribute("aria-expanded") == "false"; trigger.setAttribute("aria-expanded", expanded); if (targetId) { - let target = document.getElementById(targetId); + const target = document.getElementById(targetId); - this.addRemoveClass(target, "is-active", expanded); + if (expanded) { + this.classActivate(target); + } else { + this.classDeactivate(target); + } } } @@ -291,7 +318,11 @@ let BookWyrm = new (class { * @return {undefined} */ toggleContainer(container, pressed) { - this.addRemoveClass(container, "is-hidden", pressed); + if (pressed) { + this.classHide(container); + } else { + this.classShow(container); + } } /** @@ -324,7 +355,7 @@ let BookWyrm = new (class { * @return {undefined} */ toggleFocus(nodeId) { - let node = document.getElementById(nodeId); + const node = document.getElementById(nodeId); node.focus(); @@ -348,13 +379,15 @@ let BookWyrm = new (class { const relatedforms = document.querySelectorAll(`.${form.dataset.id}`); // Toggle class on all related forms. - relatedforms.forEach((relatedForm) => - bookwyrm.addRemoveClass( - relatedForm, - "is-hidden", - relatedForm.className.indexOf("is-hidden") == -1 - ) - ); + relatedforms.forEach((relatedForm) => { + const isHidden = relatedForm.className.indexOf("is-hidden") != -1; + + if (isHidden) { + bookwyrm.classShow(relatedForm); + } else { + bookwyrm.classHide(relatedForm); + } + }); this.ajaxPost(form).catch((error) => { // @todo Display a notification in the UI instead. @@ -378,24 +411,23 @@ let BookWyrm = new (class { }); } - /** - * 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 { - node.classList.remove(classname); - } + classHide(node) { + node.classList.add("is-hidden"); + } + + classShow(node) { + node.classList.remove("is-hidden"); + } + + classActivate(node) { + node.classList.add("is-active"); + } + + classDeactivate(node) { + node.classList.remove("is-active"); } disableIfTooLarge(eventOrElement) { - const { addRemoveClass } = this; const element = eventOrElement.currentTarget || eventOrElement; const limit = element.dataset.maxUpload; @@ -406,10 +438,10 @@ let BookWyrm = new (class { if (isTooBig) { submits.forEach((submitter) => (submitter.disabled = true)); - warns.forEach((sib) => addRemoveClass(sib, "is-hidden", false)); + warns.forEach((sib) => this.classShow(sib)); } else { submits.forEach((submitter) => (submitter.disabled = false)); - warns.forEach((sib) => addRemoveClass(sib, "is-hidden", true)); + warns.forEach((sib) => this.classHide(sib)); } } @@ -507,7 +539,15 @@ let BookWyrm = new (class { function handleModalClose(modalElement) { modalElement.removeEventListener("keydown", handleFocusTrap); - history.back(); + let redirect_path = modalElement + .querySelector("[data-modal-close]") + .getAttribute("data-modal-close"); + + if (redirect_path) { + window.location.replace(redirect_path); + } else { + history.back(); + } } } @@ -852,14 +892,45 @@ let BookWyrm = new (class { if (passwordInputElement.type === "password") { passwordInputElement.type = "text"; - this.addRemoveClass(iconElement, "icon-eye-blocked"); - this.addRemoveClass(iconElement, "icon-eye", true); + iconElement.classList.remove("icon-eye-blocked"); + iconElement.classList.add("icon-eye"); } else { passwordInputElement.type = "password"; - this.addRemoveClass(iconElement, "icon-eye"); - this.addRemoveClass(iconElement, "icon-eye-blocked", true); + iconElement.classList.add("icon-eye-blocked"); + iconElement.classList.remove("icon-eye"); } this.toggleFocus(passwordElementId); } + + /** + * When user want to add book to list, it checks if the list needs + * to be created and if so open a modal to do it. Otherwise it submit + * the form to add the book to the selected list. + * + * @param {Event} event - The click event from the button + * @returns {undefined} + */ + checkListSelection(event) { + const selectElement = document.getElementById("id_list"); + const formElement = document.querySelector("form[name='list-add']"); + const modalElement = document.getElementById("modal-create-list-with-book"); + + if (!selectElement || !formElement) { + console.error("List management elements not found. Check your HTML IDs."); + + return; + } + + if (selectElement.value === "NEW_LIST_CREATION") { + if (modalElement) { + event.currentTarget.dataset.modalOpen = "modal-create-list-with-book"; + this.handleModalButton(event); + } else { + console.error("Modal element not found with ID: modal-create-list-with-book"); + } + } else { + formElement.submit(); + } + } })(); diff --git a/bookwyrm/static/js/localstorage.js b/bookwyrm/static/js/localstorage.js index 7d0dc9d819..9088d555e2 100644 --- a/bookwyrm/static/js/localstorage.js +++ b/bookwyrm/static/js/localstorage.js @@ -18,8 +18,8 @@ let LocalStorageTools = new (class { */ updateDisplay(event) { // Used in set reading goal - let key = event.target.dataset.id; - let value = event.target.dataset.value; + const key = event.target.dataset.id; + const value = event.target.dataset.value; window.localStorage.setItem(key, value); @@ -36,9 +36,13 @@ let LocalStorageTools = new (class { */ setDisplay(node) { // Used in set reading goal - let key = node.dataset.hide; - let value = window.localStorage.getItem(key); + const key = node.dataset.hide; + const value = window.localStorage.getItem(key); - BookWyrm.addRemoveClass(node, "is-hidden", value); + if (value) { + BookWyrm.classHide(node); + } else { + BookWyrm.classShow(node); + } } })(); diff --git a/bookwyrm/static/js/status_cache.js b/bookwyrm/static/js/status_cache.js index 0a9f3abc5f..7abde1a10e 100644 --- a/bookwyrm/static/js/status_cache.js +++ b/bookwyrm/static/js/status_cache.js @@ -12,10 +12,6 @@ let StatusCache = new (class { document .querySelectorAll(".submit-status") .forEach((button) => button.addEventListener("submit", this.submitStatus.bind(this))); - - document - .querySelectorAll(".form-rate-stars label.icon") - .forEach((button) => button.addEventListener("click", this.toggleStar.bind(this))); } /** @@ -26,8 +22,8 @@ let StatusCache = new (class { */ updateDraft(event) { // Used in set reading goal - let key = event.target.dataset.cacheDraft; - let value = event.target.value; + const key = event.target.dataset.cacheDraft; + const value = event.target.value; if (!value) { window.localStorage.removeItem(key); @@ -46,8 +42,8 @@ let StatusCache = new (class { */ populateDraft(node) { // Used in set reading goal - let key = node.dataset.cacheDraft; - let value = window.localStorage.getItem(key); + const key = node.dataset.cacheDraft; + const value = window.localStorage.getItem(key); if (!value) { return; @@ -79,14 +75,14 @@ let StatusCache = new (class { event.preventDefault(); - BookWyrm.addRemoveClass(form, "is-processing", true); + form.classList.add("is-processing"); trigger.setAttribute("disabled", null); BookWyrm.ajaxPost(form) .finally(() => { // Change icon to remove ongoing activity on the current UI. // Enable back the element used to submit the form. - BookWyrm.addRemoveClass(form, "is-processing", false); + form.classList.remove("is-processing"); trigger.removeAttribute("disabled"); }) .then((response) => { @@ -107,14 +103,14 @@ let StatusCache = new (class { * @param {String} the id of the message dom element * @return {undefined} */ - announceMessage(message_id) { - const element = document.getElementById(message_id); + announceMessage(messageId) { + const element = document.getElementById(messageId); let copy = element.cloneNode(true); copy.id = null; element.insertAdjacentElement("beforebegin", copy); - BookWyrm.addRemoveClass(copy, "is-hidden", false); + BookWyrm.classShow(copy); setTimeout( function () { copy.remove(); @@ -140,7 +136,7 @@ let StatusCache = new (class { ); // Close modals - let modal = form.closest(".modal.is-active"); + const modal = form.closest(".modal.is-active"); if (modal) { modal.getElementsByClassName("modal-close")[0].click(); @@ -158,7 +154,7 @@ let StatusCache = new (class { } // Close reply panel - let reply = form.closest(".reply-panel"); + const reply = form.closest(".reply-panel"); if (reply) { document.querySelector("[data-controls=" + reply.id + "]").click(); @@ -176,25 +172,25 @@ let StatusCache = new (class { */ cycleShelveButtons(button, identifier) { // Pressed button - let shelf = button.querySelector("[data-shelf-identifier='" + identifier + "']"); - let next_identifier = shelf.dataset.shelfNext; + const shelf = button.querySelector("[data-shelf-identifier='" + identifier + "']"); + let nextIdentifier = shelf.dataset.shelfNext; // Set all buttons to hidden button .querySelectorAll("[data-shelf-identifier]") - .forEach((item) => BookWyrm.addRemoveClass(item, "is-hidden", true)); + .forEach((item) => BookWyrm.classHide(item)); // Button that should be visible now - let next = button.querySelector("[data-shelf-identifier=" + next_identifier + "]"); + const next = button.querySelector("[data-shelf-identifier=" + nextIdentifier + "]"); // Show the desired button - BookWyrm.addRemoveClass(next, "is-hidden", false); + BookWyrm.classShow(next); // ------ update the dropdown buttons // Remove existing hidden class button .querySelectorAll("[data-shelf-dropdown-identifier]") - .forEach((item) => BookWyrm.addRemoveClass(item, "is-hidden", false)); + .forEach((item) => BookWyrm.classShow(item)); // Remove existing disabled states @@ -202,51 +198,30 @@ let StatusCache = new (class { .querySelectorAll("[data-shelf-dropdown-identifier] button") .forEach((item) => (item.disabled = false)); - next_identifier = next_identifier == "complete" ? "read" : next_identifier; - next_identifier = - next_identifier == "stopped-reading-complete" ? "stopped-reading" : next_identifier; + nextIdentifier = nextIdentifier == "complete" ? "read" : nextIdentifier; + nextIdentifier = + nextIdentifier == "stopped-reading-complete" ? "stopped-reading" : nextIdentifier; // Disable the current state button.querySelector( "[data-shelf-dropdown-identifier=" + identifier + "] button" ).disabled = true; - let main_button = button.querySelector( - "[data-shelf-dropdown-identifier=" + next_identifier + "]" + const mainButton = button.querySelector( + "[data-shelf-dropdown-identifier=" + nextIdentifier + "]" ); // Hide the option that's shown as the main button - BookWyrm.addRemoveClass(main_button, "is-hidden", true); + BookWyrm.classHide(mainButton); // Just hide the other two menu options, idk what to do with them - button - .querySelectorAll("[data-extra-options]") - .forEach((item) => BookWyrm.addRemoveClass(item, "is-hidden", true)); + button.querySelectorAll("[data-extra-options]").forEach((item) => BookWyrm.classHide(item)); // Close menu - let menu = button.querySelector("details[open]"); + const menu = button.querySelector("details[open]"); if (menu) { menu.removeAttribute("open"); } } - - /** - * Reveal half-stars - * - * @param {Event} event - * @return {undefined} - */ - toggleStar(event) { - const label = event.currentTarget; - let wholeStar = document.getElementById(label.getAttribute("for")); - - if (wholeStar.checked) { - event.preventDefault(); - let halfStar = document.getElementById(label.dataset.forHalf); - - wholeStar.checked = null; - halfStar.checked = "checked"; - } - } })(); diff --git a/bookwyrm/static/js/vendor/shepherd.min.js.map b/bookwyrm/static/js/vendor/shepherd.min.js.map new file mode 100644 index 0000000000..4586751717 --- /dev/null +++ b/bookwyrm/static/js/vendor/shepherd.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"shepherd.min.js","sources":["../../node_modules/deepmerge/dist/cjs.js","../../src/js/utils/type-check.js","../../src/js/utils/auto-bind.js","../../src/js/utils/bind.js","../../node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindow.js","../../node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","../../node_modules/@popperjs/core/lib/utils/getBasePlacement.js","../../node_modules/@popperjs/core/lib/dom-utils/getBoundingClientRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getLayoutRect.js","../../node_modules/@popperjs/core/lib/dom-utils/contains.js","../../node_modules/@popperjs/core/lib/dom-utils/getComputedStyle.js","../../node_modules/@popperjs/core/lib/dom-utils/getDocumentElement.js","../../node_modules/@popperjs/core/lib/dom-utils/getParentNode.js","../../node_modules/@popperjs/core/lib/dom-utils/getOffsetParent.js","../../node_modules/@popperjs/core/lib/dom-utils/isTableElement.js","../../node_modules/@popperjs/core/lib/utils/getMainAxisFromPlacement.js","../../node_modules/@popperjs/core/lib/utils/mergePaddingObject.js","../../node_modules/@popperjs/core/lib/utils/getFreshSideObject.js","../../node_modules/@popperjs/core/lib/utils/expandToHashMap.js","../../node_modules/@popperjs/core/lib/utils/getVariation.js","../../node_modules/@popperjs/core/lib/modifiers/computeStyles.js","../../node_modules/@popperjs/core/lib/enums.js","../../node_modules/@popperjs/core/lib/utils/getOppositePlacement.js","../../node_modules/@popperjs/core/lib/utils/getOppositeVariationPlacement.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindowScroll.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindowScrollBarX.js","../../node_modules/@popperjs/core/lib/dom-utils/isScrollParent.js","../../node_modules/@popperjs/core/lib/dom-utils/getScrollParent.js","../../node_modules/@popperjs/core/lib/dom-utils/listScrollParents.js","../../node_modules/@popperjs/core/lib/utils/rectToClientRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getClippingRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getViewportRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getDocumentRect.js","../../node_modules/@popperjs/core/lib/utils/computeOffsets.js","../../node_modules/@popperjs/core/lib/utils/detectOverflow.js","../../node_modules/@popperjs/core/lib/utils/computeAutoPlacement.js","../../node_modules/@popperjs/core/lib/modifiers/flip.js","../../node_modules/@popperjs/core/lib/modifiers/hide.js","../../node_modules/@popperjs/core/lib/dom-utils/getCompositeRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getNodeScroll.js","../../node_modules/@popperjs/core/lib/dom-utils/getHTMLElementScroll.js","../../node_modules/@popperjs/core/lib/utils/orderModifiers.js","../../node_modules/@popperjs/core/lib/utils/debounce.js","../../node_modules/@popperjs/core/lib/utils/mergeByName.js","../../node_modules/@popperjs/core/lib/createPopper.js","../../src/js/utils/popper-options.js","../../src/js/utils/general.js","../../node_modules/svelte/internal/index.mjs","../../src/js/components/shepherd-button.svelte","../../src/js/components/shepherd-footer.svelte","../../src/js/components/shepherd-cancel-icon.svelte","../../src/js/components/shepherd-title.svelte","../../src/js/components/shepherd-header.svelte","../../src/js/components/shepherd-text.svelte","../../src/js/components/shepherd-content.svelte","../../src/js/components/shepherd-element.svelte","../../src/js/utils/cleanup.js","../../src/js/components/shepherd-modal.svelte","../../src/js/utils/overlay-path.js","../../src/js/evented.js","../../node_modules/@popperjs/core/lib/utils/math.js","../../node_modules/@popperjs/core/lib/modifiers/eventListeners.js","../../node_modules/@popperjs/core/lib/popper.js","../../node_modules/@popperjs/core/lib/modifiers/popperOffsets.js","../../node_modules/@popperjs/core/lib/modifiers/applyStyles.js","../../node_modules/@popperjs/core/lib/modifiers/offset.js","../../node_modules/@popperjs/core/lib/modifiers/preventOverflow.js","../../node_modules/@popperjs/core/lib/utils/getAltAxis.js","../../node_modules/@popperjs/core/lib/utils/within.js","../../node_modules/@popperjs/core/lib/modifiers/arrow.js","../../node_modules/smoothscroll-polyfill/dist/smoothscroll.js","../../src/js/step.js","../../src/js/tour.js","../../src/js/shepherd.js"],"sourcesContent":["'use strict';\n\nvar isMergeableObject = function isMergeableObject(value) {\n\treturn isNonNullObject(value)\n\t\t&& !isSpecial(value)\n};\n\nfunction isNonNullObject(value) {\n\treturn !!value && typeof value === 'object'\n}\n\nfunction isSpecial(value) {\n\tvar stringValue = Object.prototype.toString.call(value);\n\n\treturn stringValue === '[object RegExp]'\n\t\t|| stringValue === '[object Date]'\n\t\t|| isReactElement(value)\n}\n\n// see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25\nvar canUseSymbol = typeof Symbol === 'function' && Symbol.for;\nvar REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for('react.element') : 0xeac7;\n\nfunction isReactElement(value) {\n\treturn value.$$typeof === REACT_ELEMENT_TYPE\n}\n\nfunction emptyTarget(val) {\n\treturn Array.isArray(val) ? [] : {}\n}\n\nfunction cloneUnlessOtherwiseSpecified(value, options) {\n\treturn (options.clone !== false && options.isMergeableObject(value))\n\t\t? deepmerge(emptyTarget(value), value, options)\n\t\t: value\n}\n\nfunction defaultArrayMerge(target, source, options) {\n\treturn target.concat(source).map(function(element) {\n\t\treturn cloneUnlessOtherwiseSpecified(element, options)\n\t})\n}\n\nfunction getMergeFunction(key, options) {\n\tif (!options.customMerge) {\n\t\treturn deepmerge\n\t}\n\tvar customMerge = options.customMerge(key);\n\treturn typeof customMerge === 'function' ? customMerge : deepmerge\n}\n\nfunction getEnumerableOwnPropertySymbols(target) {\n\treturn Object.getOwnPropertySymbols\n\t\t? Object.getOwnPropertySymbols(target).filter(function(symbol) {\n\t\t\treturn target.propertyIsEnumerable(symbol)\n\t\t})\n\t\t: []\n}\n\nfunction getKeys(target) {\n\treturn Object.keys(target).concat(getEnumerableOwnPropertySymbols(target))\n}\n\nfunction propertyIsOnObject(object, property) {\n\ttry {\n\t\treturn property in object\n\t} catch(_) {\n\t\treturn false\n\t}\n}\n\n// Protects from prototype poisoning and unexpected merging up the prototype chain.\nfunction propertyIsUnsafe(target, key) {\n\treturn propertyIsOnObject(target, key) // Properties are safe to merge if they don't exist in the target yet,\n\t\t&& !(Object.hasOwnProperty.call(target, key) // unsafe if they exist up the prototype chain,\n\t\t\t&& Object.propertyIsEnumerable.call(target, key)) // and also unsafe if they're nonenumerable.\n}\n\nfunction mergeObject(target, source, options) {\n\tvar destination = {};\n\tif (options.isMergeableObject(target)) {\n\t\tgetKeys(target).forEach(function(key) {\n\t\t\tdestination[key] = cloneUnlessOtherwiseSpecified(target[key], options);\n\t\t});\n\t}\n\tgetKeys(source).forEach(function(key) {\n\t\tif (propertyIsUnsafe(target, key)) {\n\t\t\treturn\n\t\t}\n\n\t\tif (propertyIsOnObject(target, key) && options.isMergeableObject(source[key])) {\n\t\t\tdestination[key] = getMergeFunction(key, options)(target[key], source[key], options);\n\t\t} else {\n\t\t\tdestination[key] = cloneUnlessOtherwiseSpecified(source[key], options);\n\t\t}\n\t});\n\treturn destination\n}\n\nfunction deepmerge(target, source, options) {\n\toptions = options || {};\n\toptions.arrayMerge = options.arrayMerge || defaultArrayMerge;\n\toptions.isMergeableObject = options.isMergeableObject || isMergeableObject;\n\t// cloneUnlessOtherwiseSpecified is added to `options` so that custom arrayMerge()\n\t// implementations can use it. The caller may not replace it.\n\toptions.cloneUnlessOtherwiseSpecified = cloneUnlessOtherwiseSpecified;\n\n\tvar sourceIsArray = Array.isArray(source);\n\tvar targetIsArray = Array.isArray(target);\n\tvar sourceAndTargetTypesMatch = sourceIsArray === targetIsArray;\n\n\tif (!sourceAndTargetTypesMatch) {\n\t\treturn cloneUnlessOtherwiseSpecified(source, options)\n\t} else if (sourceIsArray) {\n\t\treturn options.arrayMerge(target, source, options)\n\t} else {\n\t\treturn mergeObject(target, source, options)\n\t}\n}\n\ndeepmerge.all = function deepmergeAll(array, options) {\n\tif (!Array.isArray(array)) {\n\t\tthrow new Error('first argument should be an array')\n\t}\n\n\treturn array.reduce(function(prev, next) {\n\t\treturn deepmerge(prev, next, options)\n\t}, {})\n};\n\nvar deepmerge_1 = deepmerge;\n\nmodule.exports = deepmerge_1;\n","/**\n * Checks if `value` is classified as an `Element`.\n * @param {*} value The param to check if it is an Element\n */\nexport function isElement(value) {\n return value instanceof Element;\n}\n\n/**\n * Checks if `value` is classified as an `HTMLElement`.\n * @param {*} value The param to check if it is an HTMLElement\n */\nexport function isHTMLElement(value) {\n return value instanceof HTMLElement;\n}\n\n/**\n * Checks if `value` is classified as a `Function` object.\n * @param {*} value The param to check if it is a function\n */\nexport function isFunction(value) {\n return typeof value === 'function';\n}\n\n/**\n * Checks if `value` is classified as a `String` object.\n * @param {*} value The param to check if it is a string\n */\nexport function isString(value) {\n return typeof value === 'string';\n}\n\n/**\n * Checks if `value` is undefined.\n * @param {*} value The param to check if it is undefined\n */\nexport function isUndefined(value) {\n return value === undefined;\n}\n","/**\n * Binds all the methods on a JS Class to the `this` context of the class.\n * Adapted from https://github.com/sindresorhus/auto-bind\n * @param {object} self The `this` context of the class\n * @return {object} The `this` context of the class\n */\nexport default function autoBind(self) {\n const keys = Object.getOwnPropertyNames(self.constructor.prototype);\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i];\n const val = self[key];\n if (key !== 'constructor' && typeof val === 'function') {\n self[key] = val.bind(self);\n }\n }\n\n return self;\n}\n","import { isUndefined } from './type-check';\n\n/**\n * Sets up the handler to determine if we should advance the tour\n * @param {string} selector\n * @param {Step} step The step instance\n * @return {Function}\n * @private\n */\nfunction _setupAdvanceOnHandler(selector, step) {\n return (event) => {\n if (step.isOpen()) {\n const targetIsEl = step.el && event.currentTarget === step.el;\n const targetIsSelector =\n !isUndefined(selector) && event.currentTarget.matches(selector);\n\n if (targetIsSelector || targetIsEl) {\n step.tour.next();\n }\n }\n };\n}\n\n/**\n * Bind the event handler for advanceOn\n * @param {Step} step The step instance\n */\nexport function bindAdvance(step) {\n // An empty selector matches the step element\n const { event, selector } = step.options.advanceOn || {};\n if (event) {\n const handler = _setupAdvanceOnHandler(selector, step);\n\n // TODO: this should also bind/unbind on show/hide\n let el;\n try {\n el = document.querySelector(selector);\n } catch (e) {\n // TODO\n }\n if (!isUndefined(selector) && !el) {\n return console.error(\n `No element was found for the selector supplied to advanceOn: ${selector}`\n );\n } else if (el) {\n el.addEventListener(event, handler);\n step.on('destroy', () => {\n return el.removeEventListener(event, handler);\n });\n } else {\n document.body.addEventListener(event, handler, true);\n step.on('destroy', () => {\n return document.body.removeEventListener(event, handler, true);\n });\n }\n } else {\n return console.error(\n 'advanceOn was defined, but no event name was passed.'\n );\n }\n}\n","export default function getNodeName(element) {\n return element ? (element.nodeName || '').toLowerCase() : null;\n}","export default function getWindow(node) {\n if (node == null) {\n return window;\n }\n\n if (node.toString() !== '[object Window]') {\n var ownerDocument = node.ownerDocument;\n return ownerDocument ? ownerDocument.defaultView || window : window;\n }\n\n return node;\n}","import getWindow from \"./getWindow.js\";\n\nfunction isElement(node) {\n var OwnElement = getWindow(node).Element;\n return node instanceof OwnElement || node instanceof Element;\n}\n\nfunction isHTMLElement(node) {\n var OwnElement = getWindow(node).HTMLElement;\n return node instanceof OwnElement || node instanceof HTMLElement;\n}\n\nfunction isShadowRoot(node) {\n // IE 11 has no ShadowRoot\n if (typeof ShadowRoot === 'undefined') {\n return false;\n }\n\n var OwnElement = getWindow(node).ShadowRoot;\n return node instanceof OwnElement || node instanceof ShadowRoot;\n}\n\nexport { isElement, isHTMLElement, isShadowRoot };","import { auto } from \"../enums.js\";\nexport default function getBasePlacement(placement) {\n return placement.split('-')[0];\n}","import { isHTMLElement } from \"./instanceOf.js\";\nimport { round } from \"../utils/math.js\";\nexport default function getBoundingClientRect(element, includeScale) {\n if (includeScale === void 0) {\n includeScale = false;\n }\n\n var rect = element.getBoundingClientRect();\n var scaleX = 1;\n var scaleY = 1;\n\n if (isHTMLElement(element) && includeScale) {\n var offsetHeight = element.offsetHeight;\n var offsetWidth = element.offsetWidth; // Do not attempt to divide by 0, otherwise we get `Infinity` as scale\n // Fallback to 1 in case both values are `0`\n\n if (offsetWidth > 0) {\n scaleX = round(rect.width) / offsetWidth || 1;\n }\n\n if (offsetHeight > 0) {\n scaleY = round(rect.height) / offsetHeight || 1;\n }\n }\n\n return {\n width: rect.width / scaleX,\n height: rect.height / scaleY,\n top: rect.top / scaleY,\n right: rect.right / scaleX,\n bottom: rect.bottom / scaleY,\n left: rect.left / scaleX,\n x: rect.left / scaleX,\n y: rect.top / scaleY\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\"; // Returns the layout rect of an element relative to its offsetParent. Layout\n// means it doesn't take into account transforms.\n\nexport default function getLayoutRect(element) {\n var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed.\n // Fixes https://github.com/popperjs/popper-core/issues/1223\n\n var width = element.offsetWidth;\n var height = element.offsetHeight;\n\n if (Math.abs(clientRect.width - width) <= 1) {\n width = clientRect.width;\n }\n\n if (Math.abs(clientRect.height - height) <= 1) {\n height = clientRect.height;\n }\n\n return {\n x: element.offsetLeft,\n y: element.offsetTop,\n width: width,\n height: height\n };\n}","import { isShadowRoot } from \"./instanceOf.js\";\nexport default function contains(parent, child) {\n var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method\n\n if (parent.contains(child)) {\n return true;\n } // then fallback to custom implementation with Shadow DOM support\n else if (rootNode && isShadowRoot(rootNode)) {\n var next = child;\n\n do {\n if (next && parent.isSameNode(next)) {\n return true;\n } // $FlowFixMe[prop-missing]: need a better way to handle this...\n\n\n next = next.parentNode || next.host;\n } while (next);\n } // Give up, the result is false\n\n\n return false;\n}","import getWindow from \"./getWindow.js\";\nexport default function getComputedStyle(element) {\n return getWindow(element).getComputedStyle(element);\n}","import { isElement } from \"./instanceOf.js\";\nexport default function getDocumentElement(element) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing]\n element.document) || window.document).documentElement;\n}","import getNodeName from \"./getNodeName.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport { isShadowRoot } from \"./instanceOf.js\";\nexport default function getParentNode(element) {\n if (getNodeName(element) === 'html') {\n return element;\n }\n\n return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle\n // $FlowFixMe[incompatible-return]\n // $FlowFixMe[prop-missing]\n element.assignedSlot || // step into the shadow DOM of the parent of a slotted node\n element.parentNode || ( // DOM Element detected\n isShadowRoot(element) ? element.host : null) || // ShadowRoot detected\n // $FlowFixMe[incompatible-call]: HTMLElement is a Node\n getDocumentElement(element) // fallback\n\n );\n}","import getWindow from \"./getWindow.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isHTMLElement, isShadowRoot } from \"./instanceOf.js\";\nimport isTableElement from \"./isTableElement.js\";\nimport getParentNode from \"./getParentNode.js\";\n\nfunction getTrueOffsetParent(element) {\n if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837\n getComputedStyle(element).position === 'fixed') {\n return null;\n }\n\n return element.offsetParent;\n} // `.offsetParent` reports `null` for fixed elements, while absolute elements\n// return the containing block\n\n\nfunction getContainingBlock(element) {\n var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') !== -1;\n var isIE = navigator.userAgent.indexOf('Trident') !== -1;\n\n if (isIE && isHTMLElement(element)) {\n // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport\n var elementCss = getComputedStyle(element);\n\n if (elementCss.position === 'fixed') {\n return null;\n }\n }\n\n var currentNode = getParentNode(element);\n\n if (isShadowRoot(currentNode)) {\n currentNode = currentNode.host;\n }\n\n while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {\n var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that\n // create a containing block.\n // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n\n if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') {\n return currentNode;\n } else {\n currentNode = currentNode.parentNode;\n }\n }\n\n return null;\n} // Gets the closest ancestor positioned element. Handles some edge cases,\n// such as table ancestors and cross browser bugs.\n\n\nexport default function getOffsetParent(element) {\n var window = getWindow(element);\n var offsetParent = getTrueOffsetParent(element);\n\n while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {\n offsetParent = getTrueOffsetParent(offsetParent);\n }\n\n if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static')) {\n return window;\n }\n\n return offsetParent || getContainingBlock(element) || window;\n}","import getNodeName from \"./getNodeName.js\";\nexport default function isTableElement(element) {\n return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0;\n}","export default function getMainAxisFromPlacement(placement) {\n return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y';\n}","import getFreshSideObject from \"./getFreshSideObject.js\";\nexport default function mergePaddingObject(paddingObject) {\n return Object.assign({}, getFreshSideObject(), paddingObject);\n}","export default function getFreshSideObject() {\n return {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0\n };\n}","export default function expandToHashMap(value, keys) {\n return keys.reduce(function (hashMap, key) {\n hashMap[key] = value;\n return hashMap;\n }, {});\n}","export default function getVariation(placement) {\n return placement.split('-')[1];\n}","import { top, left, right, bottom, end } from \"../enums.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getWindow from \"../dom-utils/getWindow.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getComputedStyle from \"../dom-utils/getComputedStyle.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport { round } from \"../utils/math.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar unsetSides = {\n top: 'auto',\n right: 'auto',\n bottom: 'auto',\n left: 'auto'\n}; // Round the offsets to the nearest suitable subpixel based on the DPR.\n// Zooming can change the DPR, but it seems to report a value that will\n// cleanly divide the values into the appropriate subpixels.\n\nfunction roundOffsetsByDPR(_ref) {\n var x = _ref.x,\n y = _ref.y;\n var win = window;\n var dpr = win.devicePixelRatio || 1;\n return {\n x: round(x * dpr) / dpr || 0,\n y: round(y * dpr) / dpr || 0\n };\n}\n\nexport function mapToStyles(_ref2) {\n var _Object$assign2;\n\n var popper = _ref2.popper,\n popperRect = _ref2.popperRect,\n placement = _ref2.placement,\n variation = _ref2.variation,\n offsets = _ref2.offsets,\n position = _ref2.position,\n gpuAcceleration = _ref2.gpuAcceleration,\n adaptive = _ref2.adaptive,\n roundOffsets = _ref2.roundOffsets,\n isFixed = _ref2.isFixed;\n var _offsets$x = offsets.x,\n x = _offsets$x === void 0 ? 0 : _offsets$x,\n _offsets$y = offsets.y,\n y = _offsets$y === void 0 ? 0 : _offsets$y;\n\n var _ref3 = typeof roundOffsets === 'function' ? roundOffsets({\n x: x,\n y: y\n }) : {\n x: x,\n y: y\n };\n\n x = _ref3.x;\n y = _ref3.y;\n var hasX = offsets.hasOwnProperty('x');\n var hasY = offsets.hasOwnProperty('y');\n var sideX = left;\n var sideY = top;\n var win = window;\n\n if (adaptive) {\n var offsetParent = getOffsetParent(popper);\n var heightProp = 'clientHeight';\n var widthProp = 'clientWidth';\n\n if (offsetParent === getWindow(popper)) {\n offsetParent = getDocumentElement(popper);\n\n if (getComputedStyle(offsetParent).position !== 'static' && position === 'absolute') {\n heightProp = 'scrollHeight';\n widthProp = 'scrollWidth';\n }\n } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it\n\n\n offsetParent = offsetParent;\n\n if (placement === top || (placement === left || placement === right) && variation === end) {\n sideY = bottom;\n var offsetY = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.height : // $FlowFixMe[prop-missing]\n offsetParent[heightProp];\n y -= offsetY - popperRect.height;\n y *= gpuAcceleration ? 1 : -1;\n }\n\n if (placement === left || (placement === top || placement === bottom) && variation === end) {\n sideX = right;\n var offsetX = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.width : // $FlowFixMe[prop-missing]\n offsetParent[widthProp];\n x -= offsetX - popperRect.width;\n x *= gpuAcceleration ? 1 : -1;\n }\n }\n\n var commonStyles = Object.assign({\n position: position\n }, adaptive && unsetSides);\n\n var _ref4 = roundOffsets === true ? roundOffsetsByDPR({\n x: x,\n y: y\n }) : {\n x: x,\n y: y\n };\n\n x = _ref4.x;\n y = _ref4.y;\n\n if (gpuAcceleration) {\n var _Object$assign;\n\n return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) <= 1 ? \"translate(\" + x + \"px, \" + y + \"px)\" : \"translate3d(\" + x + \"px, \" + y + \"px, 0)\", _Object$assign));\n }\n\n return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + \"px\" : '', _Object$assign2[sideX] = hasX ? x + \"px\" : '', _Object$assign2.transform = '', _Object$assign2));\n}\n\nfunction computeStyles(_ref5) {\n var state = _ref5.state,\n options = _ref5.options;\n var _options$gpuAccelerat = options.gpuAcceleration,\n gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat,\n _options$adaptive = options.adaptive,\n adaptive = _options$adaptive === void 0 ? true : _options$adaptive,\n _options$roundOffsets = options.roundOffsets,\n roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;\n\n if (process.env.NODE_ENV !== \"production\") {\n var transitionProperty = getComputedStyle(state.elements.popper).transitionProperty || '';\n\n if (adaptive && ['transform', 'top', 'right', 'bottom', 'left'].some(function (property) {\n return transitionProperty.indexOf(property) >= 0;\n })) {\n console.warn(['Popper: Detected CSS transitions on at least one of the following', 'CSS properties: \"transform\", \"top\", \"right\", \"bottom\", \"left\".', '\\n\\n', 'Disable the \"computeStyles\" modifier\\'s `adaptive` option to allow', 'for smooth transitions, or remove these properties from the CSS', 'transition declaration on the popper element if only transitioning', 'opacity or background-color for example.', '\\n\\n', 'We recommend using the popper element as a wrapper around an inner', 'element that can have any CSS property transitioned for animations.'].join(' '));\n }\n }\n\n var commonStyles = {\n placement: getBasePlacement(state.placement),\n variation: getVariation(state.placement),\n popper: state.elements.popper,\n popperRect: state.rects.popper,\n gpuAcceleration: gpuAcceleration,\n isFixed: state.options.strategy === 'fixed'\n };\n\n if (state.modifiersData.popperOffsets != null) {\n state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.popperOffsets,\n position: state.options.strategy,\n adaptive: adaptive,\n roundOffsets: roundOffsets\n })));\n }\n\n if (state.modifiersData.arrow != null) {\n state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.arrow,\n position: 'absolute',\n adaptive: false,\n roundOffsets: roundOffsets\n })));\n }\n\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-placement': state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'computeStyles',\n enabled: true,\n phase: 'beforeWrite',\n fn: computeStyles,\n data: {}\n};","export var top = 'top';\nexport var bottom = 'bottom';\nexport var right = 'right';\nexport var left = 'left';\nexport var auto = 'auto';\nexport var basePlacements = [top, bottom, right, left];\nexport var start = 'start';\nexport var end = 'end';\nexport var clippingParents = 'clippingParents';\nexport var viewport = 'viewport';\nexport var popper = 'popper';\nexport var reference = 'reference';\nexport var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) {\n return acc.concat([placement + \"-\" + start, placement + \"-\" + end]);\n}, []);\nexport var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) {\n return acc.concat([placement, placement + \"-\" + start, placement + \"-\" + end]);\n}, []); // modifiers that need to read the DOM\n\nexport var beforeRead = 'beforeRead';\nexport var read = 'read';\nexport var afterRead = 'afterRead'; // pure-logic modifiers\n\nexport var beforeMain = 'beforeMain';\nexport var main = 'main';\nexport var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state)\n\nexport var beforeWrite = 'beforeWrite';\nexport var write = 'write';\nexport var afterWrite = 'afterWrite';\nexport var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite];","var hash = {\n left: 'right',\n right: 'left',\n bottom: 'top',\n top: 'bottom'\n};\nexport default function getOppositePlacement(placement) {\n return placement.replace(/left|right|bottom|top/g, function (matched) {\n return hash[matched];\n });\n}","var hash = {\n start: 'end',\n end: 'start'\n};\nexport default function getOppositeVariationPlacement(placement) {\n return placement.replace(/start|end/g, function (matched) {\n return hash[matched];\n });\n}","import getWindow from \"./getWindow.js\";\nexport default function getWindowScroll(node) {\n var win = getWindow(node);\n var scrollLeft = win.pageXOffset;\n var scrollTop = win.pageYOffset;\n return {\n scrollLeft: scrollLeft,\n scrollTop: scrollTop\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nexport default function getWindowScrollBarX(element) {\n // If has a CSS width greater than the viewport, then this will be\n // incorrect for RTL.\n // Popper 1 is broken in this case and never had a bug report so let's assume\n // it's not an issue. I don't think anyone ever specifies width on \n // anyway.\n // Browsers where the left scrollbar doesn't cause an issue report `0` for\n // this (e.g. Edge 2019, IE11, Safari)\n return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft;\n}","import getComputedStyle from \"./getComputedStyle.js\";\nexport default function isScrollParent(element) {\n // Firefox wants us to check `-x` and `-y` variations as well\n var _getComputedStyle = getComputedStyle(element),\n overflow = _getComputedStyle.overflow,\n overflowX = _getComputedStyle.overflowX,\n overflowY = _getComputedStyle.overflowY;\n\n return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);\n}","import getParentNode from \"./getParentNode.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nexport default function getScrollParent(node) {\n if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return node.ownerDocument.body;\n }\n\n if (isHTMLElement(node) && isScrollParent(node)) {\n return node;\n }\n\n return getScrollParent(getParentNode(node));\n}","import getScrollParent from \"./getScrollParent.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getWindow from \"./getWindow.js\";\nimport isScrollParent from \"./isScrollParent.js\";\n/*\ngiven a DOM element, return the list of all scroll parents, up the list of ancesors\nuntil we get to the top window object. This list is what we attach scroll listeners\nto, because if any of these parent elements scroll, we'll need to re-calculate the\nreference element's position.\n*/\n\nexport default function listScrollParents(element, list) {\n var _element$ownerDocumen;\n\n if (list === void 0) {\n list = [];\n }\n\n var scrollParent = getScrollParent(element);\n var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body);\n var win = getWindow(scrollParent);\n var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent;\n var updatedList = list.concat(target);\n return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here\n updatedList.concat(listScrollParents(getParentNode(target)));\n}","export default function rectToClientRect(rect) {\n return Object.assign({}, rect, {\n left: rect.x,\n top: rect.y,\n right: rect.x + rect.width,\n bottom: rect.y + rect.height\n });\n}","import { viewport } from \"../enums.js\";\nimport getViewportRect from \"./getViewportRect.js\";\nimport getDocumentRect from \"./getDocumentRect.js\";\nimport listScrollParents from \"./listScrollParents.js\";\nimport getOffsetParent from \"./getOffsetParent.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport contains from \"./contains.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport rectToClientRect from \"../utils/rectToClientRect.js\";\nimport { max, min } from \"../utils/math.js\";\n\nfunction getInnerBoundingClientRect(element) {\n var rect = getBoundingClientRect(element);\n rect.top = rect.top + element.clientTop;\n rect.left = rect.left + element.clientLeft;\n rect.bottom = rect.top + element.clientHeight;\n rect.right = rect.left + element.clientWidth;\n rect.width = element.clientWidth;\n rect.height = element.clientHeight;\n rect.x = rect.left;\n rect.y = rect.top;\n return rect;\n}\n\nfunction getClientRectFromMixedType(element, clippingParent) {\n return clippingParent === viewport ? rectToClientRect(getViewportRect(element)) : isElement(clippingParent) ? getInnerBoundingClientRect(clippingParent) : rectToClientRect(getDocumentRect(getDocumentElement(element)));\n} // A \"clipping parent\" is an overflowable container with the characteristic of\n// clipping (or hiding) overflowing elements with a position different from\n// `initial`\n\n\nfunction getClippingParents(element) {\n var clippingParents = listScrollParents(getParentNode(element));\n var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;\n var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;\n\n if (!isElement(clipperElement)) {\n return [];\n } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414\n\n\n return clippingParents.filter(function (clippingParent) {\n return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body';\n });\n} // Gets the maximum area that the element is visible in due to any number of\n// clipping parents\n\n\nexport default function getClippingRect(element, boundary, rootBoundary) {\n var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary);\n var clippingParents = [].concat(mainClippingParents, [rootBoundary]);\n var firstClippingParent = clippingParents[0];\n var clippingRect = clippingParents.reduce(function (accRect, clippingParent) {\n var rect = getClientRectFromMixedType(element, clippingParent);\n accRect.top = max(rect.top, accRect.top);\n accRect.right = min(rect.right, accRect.right);\n accRect.bottom = min(rect.bottom, accRect.bottom);\n accRect.left = max(rect.left, accRect.left);\n return accRect;\n }, getClientRectFromMixedType(element, firstClippingParent));\n clippingRect.width = clippingRect.right - clippingRect.left;\n clippingRect.height = clippingRect.bottom - clippingRect.top;\n clippingRect.x = clippingRect.left;\n clippingRect.y = clippingRect.top;\n return clippingRect;\n}","import getWindow from \"./getWindow.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nexport default function getViewportRect(element) {\n var win = getWindow(element);\n var html = getDocumentElement(element);\n var visualViewport = win.visualViewport;\n var width = html.clientWidth;\n var height = html.clientHeight;\n var x = 0;\n var y = 0; // NB: This isn't supported on iOS <= 12. If the keyboard is open, the popper\n // can be obscured underneath it.\n // Also, `html.clientHeight` adds the bottom bar height in Safari iOS, even\n // if it isn't open, so if this isn't available, the popper will be detected\n // to overflow the bottom of the screen too early.\n\n if (visualViewport) {\n width = visualViewport.width;\n height = visualViewport.height; // Uses Layout Viewport (like Chrome; Safari does not currently)\n // In Chrome, it returns a value very close to 0 (+/-) but contains rounding\n // errors due to floating point numbers, so we need to check precision.\n // Safari returns a number <= 0, usually < -1 when pinch-zoomed\n // Feature detection fails in mobile emulation mode in Chrome.\n // Math.abs(win.innerWidth / visualViewport.scale - visualViewport.width) <\n // 0.001\n // Fallback here: \"Not Safari\" userAgent\n\n if (!/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {\n x = visualViewport.offsetLeft;\n y = visualViewport.offsetTop;\n }\n }\n\n return {\n width: width,\n height: height,\n x: x + getWindowScrollBarX(element),\n y: y\n };\n}","import getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nimport { max } from \"../utils/math.js\"; // Gets the entire size of the scrollable document area, even extending outside\n// of the `` and `` rect bounds if horizontally scrollable\n\nexport default function getDocumentRect(element) {\n var _element$ownerDocumen;\n\n var html = getDocumentElement(element);\n var winScroll = getWindowScroll(element);\n var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;\n var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);\n var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);\n var x = -winScroll.scrollLeft + getWindowScrollBarX(element);\n var y = -winScroll.scrollTop;\n\n if (getComputedStyle(body || html).direction === 'rtl') {\n x += max(html.clientWidth, body ? body.clientWidth : 0) - width;\n }\n\n return {\n width: width,\n height: height,\n x: x,\n y: y\n };\n}","import getBasePlacement from \"./getBasePlacement.js\";\nimport getVariation from \"./getVariation.js\";\nimport getMainAxisFromPlacement from \"./getMainAxisFromPlacement.js\";\nimport { top, right, bottom, left, start, end } from \"../enums.js\";\nexport default function computeOffsets(_ref) {\n var reference = _ref.reference,\n element = _ref.element,\n placement = _ref.placement;\n var basePlacement = placement ? getBasePlacement(placement) : null;\n var variation = placement ? getVariation(placement) : null;\n var commonX = reference.x + reference.width / 2 - element.width / 2;\n var commonY = reference.y + reference.height / 2 - element.height / 2;\n var offsets;\n\n switch (basePlacement) {\n case top:\n offsets = {\n x: commonX,\n y: reference.y - element.height\n };\n break;\n\n case bottom:\n offsets = {\n x: commonX,\n y: reference.y + reference.height\n };\n break;\n\n case right:\n offsets = {\n x: reference.x + reference.width,\n y: commonY\n };\n break;\n\n case left:\n offsets = {\n x: reference.x - element.width,\n y: commonY\n };\n break;\n\n default:\n offsets = {\n x: reference.x,\n y: reference.y\n };\n }\n\n var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null;\n\n if (mainAxis != null) {\n var len = mainAxis === 'y' ? 'height' : 'width';\n\n switch (variation) {\n case start:\n offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2);\n break;\n\n case end:\n offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2);\n break;\n\n default:\n }\n }\n\n return offsets;\n}","import getClippingRect from \"../dom-utils/getClippingRect.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getBoundingClientRect from \"../dom-utils/getBoundingClientRect.js\";\nimport computeOffsets from \"./computeOffsets.js\";\nimport rectToClientRect from \"./rectToClientRect.js\";\nimport { clippingParents, reference, popper, bottom, top, right, basePlacements, viewport } from \"../enums.js\";\nimport { isElement } from \"../dom-utils/instanceOf.js\";\nimport mergePaddingObject from \"./mergePaddingObject.js\";\nimport expandToHashMap from \"./expandToHashMap.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport default function detectOverflow(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n _options$placement = _options.placement,\n placement = _options$placement === void 0 ? state.placement : _options$placement,\n _options$boundary = _options.boundary,\n boundary = _options$boundary === void 0 ? clippingParents : _options$boundary,\n _options$rootBoundary = _options.rootBoundary,\n rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary,\n _options$elementConte = _options.elementContext,\n elementContext = _options$elementConte === void 0 ? popper : _options$elementConte,\n _options$altBoundary = _options.altBoundary,\n altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary,\n _options$padding = _options.padding,\n padding = _options$padding === void 0 ? 0 : _options$padding;\n var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n var altContext = elementContext === popper ? reference : popper;\n var popperRect = state.rects.popper;\n var element = state.elements[altBoundary ? altContext : elementContext];\n var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary);\n var referenceClientRect = getBoundingClientRect(state.elements.reference);\n var popperOffsets = computeOffsets({\n reference: referenceClientRect,\n element: popperRect,\n strategy: 'absolute',\n placement: placement\n });\n var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets));\n var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect\n // 0 or negative = within the clipping rect\n\n var overflowOffsets = {\n top: clippingClientRect.top - elementClientRect.top + paddingObject.top,\n bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom,\n left: clippingClientRect.left - elementClientRect.left + paddingObject.left,\n right: elementClientRect.right - clippingClientRect.right + paddingObject.right\n };\n var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element\n\n if (elementContext === popper && offsetData) {\n var offset = offsetData[placement];\n Object.keys(overflowOffsets).forEach(function (key) {\n var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;\n var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';\n overflowOffsets[key] += offset[axis] * multiply;\n });\n }\n\n return overflowOffsets;\n}","import getVariation from \"./getVariation.js\";\nimport { variationPlacements, basePlacements, placements as allPlacements } from \"../enums.js\";\nimport detectOverflow from \"./detectOverflow.js\";\nimport getBasePlacement from \"./getBasePlacement.js\";\nexport default function computeAutoPlacement(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n placement = _options.placement,\n boundary = _options.boundary,\n rootBoundary = _options.rootBoundary,\n padding = _options.padding,\n flipVariations = _options.flipVariations,\n _options$allowedAutoP = _options.allowedAutoPlacements,\n allowedAutoPlacements = _options$allowedAutoP === void 0 ? allPlacements : _options$allowedAutoP;\n var variation = getVariation(placement);\n var placements = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) {\n return getVariation(placement) === variation;\n }) : basePlacements;\n var allowedPlacements = placements.filter(function (placement) {\n return allowedAutoPlacements.indexOf(placement) >= 0;\n });\n\n if (allowedPlacements.length === 0) {\n allowedPlacements = placements;\n\n if (process.env.NODE_ENV !== \"production\") {\n console.error(['Popper: The `allowedAutoPlacements` option did not allow any', 'placements. Ensure the `placement` option matches the variation', 'of the allowed placements.', 'For example, \"auto\" cannot be used to allow \"bottom-start\".', 'Use \"auto-start\" instead.'].join(' '));\n }\n } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...\n\n\n var overflows = allowedPlacements.reduce(function (acc, placement) {\n acc[placement] = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding\n })[getBasePlacement(placement)];\n return acc;\n }, {});\n return Object.keys(overflows).sort(function (a, b) {\n return overflows[a] - overflows[b];\n });\n}","import getOppositePlacement from \"../utils/getOppositePlacement.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getOppositeVariationPlacement from \"../utils/getOppositeVariationPlacement.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport computeAutoPlacement from \"../utils/computeAutoPlacement.js\";\nimport { bottom, top, start, right, left, auto } from \"../enums.js\";\nimport getVariation from \"../utils/getVariation.js\"; // eslint-disable-next-line import/no-unused-modules\n\nfunction getExpandedFallbackPlacements(placement) {\n if (getBasePlacement(placement) === auto) {\n return [];\n }\n\n var oppositePlacement = getOppositePlacement(placement);\n return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)];\n}\n\nfunction flip(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n\n if (state.modifiersData[name]._skip) {\n return;\n }\n\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis,\n specifiedFallbackPlacements = options.fallbackPlacements,\n padding = options.padding,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n _options$flipVariatio = options.flipVariations,\n flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio,\n allowedAutoPlacements = options.allowedAutoPlacements;\n var preferredPlacement = state.options.placement;\n var basePlacement = getBasePlacement(preferredPlacement);\n var isBasePlacement = basePlacement === preferredPlacement;\n var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement));\n var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) {\n return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n flipVariations: flipVariations,\n allowedAutoPlacements: allowedAutoPlacements\n }) : placement);\n }, []);\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var checksMap = new Map();\n var makeFallbackChecks = true;\n var firstFittingPlacement = placements[0];\n\n for (var i = 0; i < placements.length; i++) {\n var placement = placements[i];\n\n var _basePlacement = getBasePlacement(placement);\n\n var isStartVariation = getVariation(placement) === start;\n var isVertical = [top, bottom].indexOf(_basePlacement) >= 0;\n var len = isVertical ? 'width' : 'height';\n var overflow = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n altBoundary: altBoundary,\n padding: padding\n });\n var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top;\n\n if (referenceRect[len] > popperRect[len]) {\n mainVariationSide = getOppositePlacement(mainVariationSide);\n }\n\n var altVariationSide = getOppositePlacement(mainVariationSide);\n var checks = [];\n\n if (checkMainAxis) {\n checks.push(overflow[_basePlacement] <= 0);\n }\n\n if (checkAltAxis) {\n checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0);\n }\n\n if (checks.every(function (check) {\n return check;\n })) {\n firstFittingPlacement = placement;\n makeFallbackChecks = false;\n break;\n }\n\n checksMap.set(placement, checks);\n }\n\n if (makeFallbackChecks) {\n // `2` may be desired in some cases – research later\n var numberOfChecks = flipVariations ? 3 : 1;\n\n var _loop = function _loop(_i) {\n var fittingPlacement = placements.find(function (placement) {\n var checks = checksMap.get(placement);\n\n if (checks) {\n return checks.slice(0, _i).every(function (check) {\n return check;\n });\n }\n });\n\n if (fittingPlacement) {\n firstFittingPlacement = fittingPlacement;\n return \"break\";\n }\n };\n\n for (var _i = numberOfChecks; _i > 0; _i--) {\n var _ret = _loop(_i);\n\n if (_ret === \"break\") break;\n }\n }\n\n if (state.placement !== firstFittingPlacement) {\n state.modifiersData[name]._skip = true;\n state.placement = firstFittingPlacement;\n state.reset = true;\n }\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'flip',\n enabled: true,\n phase: 'main',\n fn: flip,\n requiresIfExists: ['offset'],\n data: {\n _skip: false\n }\n};","import { top, bottom, left, right } from \"../enums.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\n\nfunction getSideOffsets(overflow, rect, preventedOffsets) {\n if (preventedOffsets === void 0) {\n preventedOffsets = {\n x: 0,\n y: 0\n };\n }\n\n return {\n top: overflow.top - rect.height - preventedOffsets.y,\n right: overflow.right - rect.width + preventedOffsets.x,\n bottom: overflow.bottom - rect.height + preventedOffsets.y,\n left: overflow.left - rect.width - preventedOffsets.x\n };\n}\n\nfunction isAnySideFullyClipped(overflow) {\n return [top, right, bottom, left].some(function (side) {\n return overflow[side] >= 0;\n });\n}\n\nfunction hide(_ref) {\n var state = _ref.state,\n name = _ref.name;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var preventedOffsets = state.modifiersData.preventOverflow;\n var referenceOverflow = detectOverflow(state, {\n elementContext: 'reference'\n });\n var popperAltOverflow = detectOverflow(state, {\n altBoundary: true\n });\n var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect);\n var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets);\n var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets);\n var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets);\n state.modifiersData[name] = {\n referenceClippingOffsets: referenceClippingOffsets,\n popperEscapeOffsets: popperEscapeOffsets,\n isReferenceHidden: isReferenceHidden,\n hasPopperEscaped: hasPopperEscaped\n };\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-reference-hidden': isReferenceHidden,\n 'data-popper-escaped': hasPopperEscaped\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'hide',\n enabled: true,\n phase: 'main',\n requiresIfExists: ['preventOverflow'],\n fn: hide\n};","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getNodeScroll from \"./getNodeScroll.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport { round } from \"../utils/math.js\";\n\nfunction isElementScaled(element) {\n var rect = element.getBoundingClientRect();\n var scaleX = round(rect.width) / element.offsetWidth || 1;\n var scaleY = round(rect.height) / element.offsetHeight || 1;\n return scaleX !== 1 || scaleY !== 1;\n} // Returns the composite rect of an element relative to its offsetParent.\n// Composite means it takes into account transforms as well as layout.\n\n\nexport default function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) {\n if (isFixed === void 0) {\n isFixed = false;\n }\n\n var isOffsetParentAnElement = isHTMLElement(offsetParent);\n var offsetParentIsScaled = isHTMLElement(offsetParent) && isElementScaled(offsetParent);\n var documentElement = getDocumentElement(offsetParent);\n var rect = getBoundingClientRect(elementOrVirtualElement, offsetParentIsScaled);\n var scroll = {\n scrollLeft: 0,\n scrollTop: 0\n };\n var offsets = {\n x: 0,\n y: 0\n };\n\n if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {\n if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078\n isScrollParent(documentElement)) {\n scroll = getNodeScroll(offsetParent);\n }\n\n if (isHTMLElement(offsetParent)) {\n offsets = getBoundingClientRect(offsetParent, true);\n offsets.x += offsetParent.clientLeft;\n offsets.y += offsetParent.clientTop;\n } else if (documentElement) {\n offsets.x = getWindowScrollBarX(documentElement);\n }\n }\n\n return {\n x: rect.left + scroll.scrollLeft - offsets.x,\n y: rect.top + scroll.scrollTop - offsets.y,\n width: rect.width,\n height: rect.height\n };\n}","import getWindowScroll from \"./getWindowScroll.js\";\nimport getWindow from \"./getWindow.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getHTMLElementScroll from \"./getHTMLElementScroll.js\";\nexport default function getNodeScroll(node) {\n if (node === getWindow(node) || !isHTMLElement(node)) {\n return getWindowScroll(node);\n } else {\n return getHTMLElementScroll(node);\n }\n}","export default function getHTMLElementScroll(element) {\n return {\n scrollLeft: element.scrollLeft,\n scrollTop: element.scrollTop\n };\n}","import { modifierPhases } from \"../enums.js\"; // source: https://stackoverflow.com/questions/49875255\n\nfunction order(modifiers) {\n var map = new Map();\n var visited = new Set();\n var result = [];\n modifiers.forEach(function (modifier) {\n map.set(modifier.name, modifier);\n }); // On visiting object, check for its dependencies and visit them recursively\n\n function sort(modifier) {\n visited.add(modifier.name);\n var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []);\n requires.forEach(function (dep) {\n if (!visited.has(dep)) {\n var depModifier = map.get(dep);\n\n if (depModifier) {\n sort(depModifier);\n }\n }\n });\n result.push(modifier);\n }\n\n modifiers.forEach(function (modifier) {\n if (!visited.has(modifier.name)) {\n // check for visited object\n sort(modifier);\n }\n });\n return result;\n}\n\nexport default function orderModifiers(modifiers) {\n // order based on dependencies\n var orderedModifiers = order(modifiers); // order based on phase\n\n return modifierPhases.reduce(function (acc, phase) {\n return acc.concat(orderedModifiers.filter(function (modifier) {\n return modifier.phase === phase;\n }));\n }, []);\n}","export default function debounce(fn) {\n var pending;\n return function () {\n if (!pending) {\n pending = new Promise(function (resolve) {\n Promise.resolve().then(function () {\n pending = undefined;\n resolve(fn());\n });\n });\n }\n\n return pending;\n };\n}","export default function mergeByName(modifiers) {\n var merged = modifiers.reduce(function (merged, current) {\n var existing = merged[current.name];\n merged[current.name] = existing ? Object.assign({}, existing, current, {\n options: Object.assign({}, existing.options, current.options),\n data: Object.assign({}, existing.data, current.data)\n }) : current;\n return merged;\n }, {}); // IE11 does not support Object.values\n\n return Object.keys(merged).map(function (key) {\n return merged[key];\n });\n}","import getCompositeRect from \"./dom-utils/getCompositeRect.js\";\nimport getLayoutRect from \"./dom-utils/getLayoutRect.js\";\nimport listScrollParents from \"./dom-utils/listScrollParents.js\";\nimport getOffsetParent from \"./dom-utils/getOffsetParent.js\";\nimport getComputedStyle from \"./dom-utils/getComputedStyle.js\";\nimport orderModifiers from \"./utils/orderModifiers.js\";\nimport debounce from \"./utils/debounce.js\";\nimport validateModifiers from \"./utils/validateModifiers.js\";\nimport uniqueBy from \"./utils/uniqueBy.js\";\nimport getBasePlacement from \"./utils/getBasePlacement.js\";\nimport mergeByName from \"./utils/mergeByName.js\";\nimport detectOverflow from \"./utils/detectOverflow.js\";\nimport { isElement } from \"./dom-utils/instanceOf.js\";\nimport { auto } from \"./enums.js\";\nvar INVALID_ELEMENT_ERROR = 'Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.';\nvar INFINITE_LOOP_ERROR = 'Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.';\nvar DEFAULT_OPTIONS = {\n placement: 'bottom',\n modifiers: [],\n strategy: 'absolute'\n};\n\nfunction areValidElements() {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n return !args.some(function (element) {\n return !(element && typeof element.getBoundingClientRect === 'function');\n });\n}\n\nexport function popperGenerator(generatorOptions) {\n if (generatorOptions === void 0) {\n generatorOptions = {};\n }\n\n var _generatorOptions = generatorOptions,\n _generatorOptions$def = _generatorOptions.defaultModifiers,\n defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def,\n _generatorOptions$def2 = _generatorOptions.defaultOptions,\n defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2;\n return function createPopper(reference, popper, options) {\n if (options === void 0) {\n options = defaultOptions;\n }\n\n var state = {\n placement: 'bottom',\n orderedModifiers: [],\n options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions),\n modifiersData: {},\n elements: {\n reference: reference,\n popper: popper\n },\n attributes: {},\n styles: {}\n };\n var effectCleanupFns = [];\n var isDestroyed = false;\n var instance = {\n state: state,\n setOptions: function setOptions(setOptionsAction) {\n var options = typeof setOptionsAction === 'function' ? setOptionsAction(state.options) : setOptionsAction;\n cleanupModifierEffects();\n state.options = Object.assign({}, defaultOptions, state.options, options);\n state.scrollParents = {\n reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [],\n popper: listScrollParents(popper)\n }; // Orders the modifiers based on their dependencies and `phase`\n // properties\n\n var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers\n\n state.orderedModifiers = orderedModifiers.filter(function (m) {\n return m.enabled;\n }); // Validate the provided modifiers so that the consumer will get warned\n // if one of the modifiers is invalid for any reason\n\n if (process.env.NODE_ENV !== \"production\") {\n var modifiers = uniqueBy([].concat(orderedModifiers, state.options.modifiers), function (_ref) {\n var name = _ref.name;\n return name;\n });\n validateModifiers(modifiers);\n\n if (getBasePlacement(state.options.placement) === auto) {\n var flipModifier = state.orderedModifiers.find(function (_ref2) {\n var name = _ref2.name;\n return name === 'flip';\n });\n\n if (!flipModifier) {\n console.error(['Popper: \"auto\" placements require the \"flip\" modifier be', 'present and enabled to work.'].join(' '));\n }\n }\n\n var _getComputedStyle = getComputedStyle(popper),\n marginTop = _getComputedStyle.marginTop,\n marginRight = _getComputedStyle.marginRight,\n marginBottom = _getComputedStyle.marginBottom,\n marginLeft = _getComputedStyle.marginLeft; // We no longer take into account `margins` on the popper, and it can\n // cause bugs with positioning, so we'll warn the consumer\n\n\n if ([marginTop, marginRight, marginBottom, marginLeft].some(function (margin) {\n return parseFloat(margin);\n })) {\n console.warn(['Popper: CSS \"margin\" styles cannot be used to apply padding', 'between the popper and its reference element or boundary.', 'To replicate margin, use the `offset` modifier, as well as', 'the `padding` option in the `preventOverflow` and `flip`', 'modifiers.'].join(' '));\n }\n }\n\n runModifierEffects();\n return instance.update();\n },\n // Sync update – it will always be executed, even if not necessary. This\n // is useful for low frequency updates where sync behavior simplifies the\n // logic.\n // For high frequency updates (e.g. `resize` and `scroll` events), always\n // prefer the async Popper#update method\n forceUpdate: function forceUpdate() {\n if (isDestroyed) {\n return;\n }\n\n var _state$elements = state.elements,\n reference = _state$elements.reference,\n popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements\n // anymore\n\n if (!areValidElements(reference, popper)) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(INVALID_ELEMENT_ERROR);\n }\n\n return;\n } // Store the reference and popper rects to be read by modifiers\n\n\n state.rects = {\n reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'),\n popper: getLayoutRect(popper)\n }; // Modifiers have the ability to reset the current update cycle. The\n // most common use case for this is the `flip` modifier changing the\n // placement, which then needs to re-run all the modifiers, because the\n // logic was previously ran for the previous placement and is therefore\n // stale/incorrect\n\n state.reset = false;\n state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier\n // is filled with the initial data specified by the modifier. This means\n // it doesn't persist and is fresh on each update.\n // To ensure persistent data, use `${name}#persistent`\n\n state.orderedModifiers.forEach(function (modifier) {\n return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);\n });\n var __debug_loops__ = 0;\n\n for (var index = 0; index < state.orderedModifiers.length; index++) {\n if (process.env.NODE_ENV !== \"production\") {\n __debug_loops__ += 1;\n\n if (__debug_loops__ > 100) {\n console.error(INFINITE_LOOP_ERROR);\n break;\n }\n }\n\n if (state.reset === true) {\n state.reset = false;\n index = -1;\n continue;\n }\n\n var _state$orderedModifie = state.orderedModifiers[index],\n fn = _state$orderedModifie.fn,\n _state$orderedModifie2 = _state$orderedModifie.options,\n _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2,\n name = _state$orderedModifie.name;\n\n if (typeof fn === 'function') {\n state = fn({\n state: state,\n options: _options,\n name: name,\n instance: instance\n }) || state;\n }\n }\n },\n // Async and optimistically optimized update – it will not be executed if\n // not necessary (debounced to run at most once-per-tick)\n update: debounce(function () {\n return new Promise(function (resolve) {\n instance.forceUpdate();\n resolve(state);\n });\n }),\n destroy: function destroy() {\n cleanupModifierEffects();\n isDestroyed = true;\n }\n };\n\n if (!areValidElements(reference, popper)) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(INVALID_ELEMENT_ERROR);\n }\n\n return instance;\n }\n\n instance.setOptions(options).then(function (state) {\n if (!isDestroyed && options.onFirstUpdate) {\n options.onFirstUpdate(state);\n }\n }); // Modifiers have the ability to execute arbitrary code before the first\n // update cycle runs. They will be executed in the same order as the update\n // cycle. This is useful when a modifier adds some persistent data that\n // other modifiers need to use, but the modifier is run after the dependent\n // one.\n\n function runModifierEffects() {\n state.orderedModifiers.forEach(function (_ref3) {\n var name = _ref3.name,\n _ref3$options = _ref3.options,\n options = _ref3$options === void 0 ? {} : _ref3$options,\n effect = _ref3.effect;\n\n if (typeof effect === 'function') {\n var cleanupFn = effect({\n state: state,\n name: name,\n instance: instance,\n options: options\n });\n\n var noopFn = function noopFn() {};\n\n effectCleanupFns.push(cleanupFn || noopFn);\n }\n });\n }\n\n function cleanupModifierEffects() {\n effectCleanupFns.forEach(function (fn) {\n return fn();\n });\n effectCleanupFns = [];\n }\n\n return instance;\n };\n}\nexport var createPopper = /*#__PURE__*/popperGenerator(); // eslint-disable-next-line import/no-unused-modules\n\nexport { detectOverflow };","function _getCenteredStylePopperModifier() {\n return [\n {\n name: 'applyStyles',\n fn({ state }) {\n Object.keys(state.elements).forEach((name) => {\n if (name !== 'popper') {\n return;\n }\n const style = {\n position: 'fixed',\n left: '50%',\n top: '50%',\n transform: 'translate(-50%, -50%)'\n };\n\n const attributes = state.attributes[name] || {};\n const element = state.elements[name];\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach((name) => {\n const value = attributes[name];\n if (value === false) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value === true ? '' : value);\n }\n });\n });\n }\n },\n {\n name: 'computeStyles',\n options: {\n adaptive: false\n }\n }\n ];\n}\n\n/**\n * Generates the array of options for a tooltip that doesn't have a\n * target element in the DOM -- and thus is positioned in the center\n * of the view\n *\n * @param {Step} step The step instance\n * @return {Object} The final Popper options object\n */\nexport function makeCenteredPopper(step) {\n const centeredStylePopperModifier = _getCenteredStylePopperModifier();\n\n let popperOptions = {\n placement: 'top',\n strategy: 'fixed',\n modifiers: [\n {\n name: 'focusAfterRender',\n enabled: true,\n phase: 'afterWrite',\n fn() {\n setTimeout(() => {\n if (step.el) {\n step.el.focus();\n }\n }, 300);\n }\n }\n ]\n };\n\n popperOptions = {\n ...popperOptions,\n modifiers: Array.from(\n new Set([...popperOptions.modifiers, ...centeredStylePopperModifier])\n )\n };\n\n return popperOptions;\n}\n","import { createPopper } from '@popperjs/core';\nimport { isFunction, isString } from './type-check';\nimport { makeCenteredPopper } from './popper-options';\n\n/**\n * Ensure class prefix ends in `-`\n * @param {string} prefix The prefix to prepend to the class names generated by nano-css\n * @return {string} The prefix ending in `-`\n */\nexport function normalizePrefix(prefix) {\n if (!isString(prefix) || prefix === '') {\n return '';\n }\n\n return prefix.charAt(prefix.length - 1) !== '-' ? `${prefix}-` : prefix;\n}\n\n/**\n * Resolves attachTo options, converting element option value to a qualified HTMLElement.\n * @param {Step} step The step instance\n * @returns {{}|{element, on}}\n * `element` is a qualified HTML Element\n * `on` is a string position value\n */\nexport function parseAttachTo(step) {\n const options = step.options.attachTo || {};\n const returnOpts = Object.assign({}, options);\n\n if (isFunction(returnOpts.element)) {\n // Bind the callback to step so that it has access to the object, to enable running additional logic\n returnOpts.element = returnOpts.element.call(step);\n }\n\n if (isString(returnOpts.element)) {\n // Can't override the element in user opts reference because we can't\n // guarantee that the element will exist in the future.\n try {\n returnOpts.element = document.querySelector(returnOpts.element);\n } catch (e) {\n // TODO\n }\n if (!returnOpts.element) {\n console.error(\n `The element for this Shepherd step was not found ${options.element}`\n );\n }\n }\n\n return returnOpts;\n}\n\n/**\n * Checks if the step should be centered or not. Does not trigger attachTo.element evaluation, making it a pure\n * alternative for the deprecated step.isCentered() method.\n * @param resolvedAttachToOptions\n * @returns {boolean}\n */\nexport function shouldCenterStep(resolvedAttachToOptions) {\n if (resolvedAttachToOptions === undefined || resolvedAttachToOptions === null) {\n return true\n }\n \n return !resolvedAttachToOptions.element || !resolvedAttachToOptions.on;\n}\n\n/**\n * Determines options for the tooltip and initializes\n * `step.tooltip` as a Popper instance.\n * @param {Step} step The step instance\n */\nexport function setupTooltip(step) {\n if (step.tooltip) {\n step.tooltip.destroy();\n }\n\n const attachToOptions = step._getResolvedAttachToOptions();\n\n let target = attachToOptions.element;\n const popperOptions = getPopperOptions(attachToOptions, step);\n\n if (shouldCenterStep(attachToOptions)) {\n target = document.body;\n const content = step.shepherdElementComponent.getElement();\n content.classList.add('shepherd-centered');\n }\n\n step.tooltip = createPopper(target, step.el, popperOptions);\n step.target = attachToOptions.element;\n\n return popperOptions;\n}\n\n/**\n * Create a unique id for steps, tours, modals, etc\n * @return {string}\n */\nexport function uuid() {\n let d = Date.now();\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (d + Math.random() * 16) % 16 | 0;\n d = Math.floor(d / 16);\n return (c == 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n\n/**\n * Gets the `Popper` options from a set of base `attachTo` options\n * @param attachToOptions\n * @param {Step} step The step instance\n * @return {Object}\n * @private\n */\nexport function getPopperOptions(attachToOptions, step) {\n let popperOptions = {\n modifiers: [\n {\n name: 'preventOverflow',\n options: {\n altAxis: true,\n tether: false\n }\n },\n {\n name: 'focusAfterRender',\n enabled: true,\n phase: 'afterWrite',\n fn() {\n setTimeout(() => {\n if (step.el) {\n step.el.focus();\n }\n }, 300);\n }\n }\n ],\n strategy: 'absolute'\n };\n\n if (shouldCenterStep(attachToOptions)) {\n popperOptions = makeCenteredPopper(step);\n } else {\n popperOptions.placement = attachToOptions.on;\n }\n\n const defaultStepOptions =\n step.tour && step.tour.options && step.tour.options.defaultStepOptions;\n\n if (defaultStepOptions) {\n popperOptions = _mergeModifiers(defaultStepOptions, popperOptions);\n }\n\n popperOptions = _mergeModifiers(step.options, popperOptions);\n\n return popperOptions;\n}\n\nfunction _mergeModifiers(stepOptions, popperOptions) {\n if (stepOptions.popperOptions) {\n let mergedPopperOptions = Object.assign(\n {},\n popperOptions,\n stepOptions.popperOptions\n );\n\n if (\n stepOptions.popperOptions.modifiers &&\n stepOptions.popperOptions.modifiers.length > 0\n ) {\n const names = stepOptions.popperOptions.modifiers.map((mod) => mod.name);\n const filteredModifiers = popperOptions.modifiers.filter(\n (mod) => !names.includes(mod.name)\n );\n\n mergedPopperOptions.modifiers = Array.from(\n new Set([...filteredModifiers, ...stepOptions.popperOptions.modifiers])\n );\n }\n\n return mergedPopperOptions;\n }\n\n return popperOptions;\n}\n","function noop() { }\nconst identity = x => x;\nfunction assign(tar, src) {\n // @ts-ignore\n for (const k in src)\n tar[k] = src[k];\n return tar;\n}\nfunction is_promise(value) {\n return value && typeof value === 'object' && typeof value.then === 'function';\n}\nfunction add_location(element, file, line, column, char) {\n element.__svelte_meta = {\n loc: { file, line, column, char }\n };\n}\nfunction run(fn) {\n return fn();\n}\nfunction blank_object() {\n return Object.create(null);\n}\nfunction run_all(fns) {\n fns.forEach(run);\n}\nfunction is_function(thing) {\n return typeof thing === 'function';\n}\nfunction safe_not_equal(a, b) {\n return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');\n}\nlet src_url_equal_anchor;\nfunction src_url_equal(element_src, url) {\n if (!src_url_equal_anchor) {\n src_url_equal_anchor = document.createElement('a');\n }\n src_url_equal_anchor.href = url;\n return element_src === src_url_equal_anchor.href;\n}\nfunction not_equal(a, b) {\n return a != a ? b == b : a !== b;\n}\nfunction is_empty(obj) {\n return Object.keys(obj).length === 0;\n}\nfunction validate_store(store, name) {\n if (store != null && typeof store.subscribe !== 'function') {\n throw new Error(`'${name}' is not a store with a 'subscribe' method`);\n }\n}\nfunction subscribe(store, ...callbacks) {\n if (store == null) {\n return noop;\n }\n const unsub = store.subscribe(...callbacks);\n return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;\n}\nfunction get_store_value(store) {\n let value;\n subscribe(store, _ => value = _)();\n return value;\n}\nfunction component_subscribe(component, store, callback) {\n component.$$.on_destroy.push(subscribe(store, callback));\n}\nfunction create_slot(definition, ctx, $$scope, fn) {\n if (definition) {\n const slot_ctx = get_slot_context(definition, ctx, $$scope, fn);\n return definition[0](slot_ctx);\n }\n}\nfunction get_slot_context(definition, ctx, $$scope, fn) {\n return definition[1] && fn\n ? assign($$scope.ctx.slice(), definition[1](fn(ctx)))\n : $$scope.ctx;\n}\nfunction get_slot_changes(definition, $$scope, dirty, fn) {\n if (definition[2] && fn) {\n const lets = definition[2](fn(dirty));\n if ($$scope.dirty === undefined) {\n return lets;\n }\n if (typeof lets === 'object') {\n const merged = [];\n const len = Math.max($$scope.dirty.length, lets.length);\n for (let i = 0; i < len; i += 1) {\n merged[i] = $$scope.dirty[i] | lets[i];\n }\n return merged;\n }\n return $$scope.dirty | lets;\n }\n return $$scope.dirty;\n}\nfunction update_slot_base(slot, slot_definition, ctx, $$scope, slot_changes, get_slot_context_fn) {\n if (slot_changes) {\n const slot_context = get_slot_context(slot_definition, ctx, $$scope, get_slot_context_fn);\n slot.p(slot_context, slot_changes);\n }\n}\nfunction update_slot(slot, slot_definition, ctx, $$scope, dirty, get_slot_changes_fn, get_slot_context_fn) {\n const slot_changes = get_slot_changes(slot_definition, $$scope, dirty, get_slot_changes_fn);\n update_slot_base(slot, slot_definition, ctx, $$scope, slot_changes, get_slot_context_fn);\n}\nfunction get_all_dirty_from_scope($$scope) {\n if ($$scope.ctx.length > 32) {\n const dirty = [];\n const length = $$scope.ctx.length / 32;\n for (let i = 0; i < length; i++) {\n dirty[i] = -1;\n }\n return dirty;\n }\n return -1;\n}\nfunction exclude_internal_props(props) {\n const result = {};\n for (const k in props)\n if (k[0] !== '$')\n result[k] = props[k];\n return result;\n}\nfunction compute_rest_props(props, keys) {\n const rest = {};\n keys = new Set(keys);\n for (const k in props)\n if (!keys.has(k) && k[0] !== '$')\n rest[k] = props[k];\n return rest;\n}\nfunction compute_slots(slots) {\n const result = {};\n for (const key in slots) {\n result[key] = true;\n }\n return result;\n}\nfunction once(fn) {\n let ran = false;\n return function (...args) {\n if (ran)\n return;\n ran = true;\n fn.call(this, ...args);\n };\n}\nfunction null_to_empty(value) {\n return value == null ? '' : value;\n}\nfunction set_store_value(store, ret, value) {\n store.set(value);\n return ret;\n}\nconst has_prop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);\nfunction action_destroyer(action_result) {\n return action_result && is_function(action_result.destroy) ? action_result.destroy : noop;\n}\n\nconst is_client = typeof window !== 'undefined';\nlet now = is_client\n ? () => window.performance.now()\n : () => Date.now();\nlet raf = is_client ? cb => requestAnimationFrame(cb) : noop;\n// used internally for testing\nfunction set_now(fn) {\n now = fn;\n}\nfunction set_raf(fn) {\n raf = fn;\n}\n\nconst tasks = new Set();\nfunction run_tasks(now) {\n tasks.forEach(task => {\n if (!task.c(now)) {\n tasks.delete(task);\n task.f();\n }\n });\n if (tasks.size !== 0)\n raf(run_tasks);\n}\n/**\n * For testing purposes only!\n */\nfunction clear_loops() {\n tasks.clear();\n}\n/**\n * Creates a new task that runs on each raf frame\n * until it returns a falsy value or is aborted\n */\nfunction loop(callback) {\n let task;\n if (tasks.size === 0)\n raf(run_tasks);\n return {\n promise: new Promise(fulfill => {\n tasks.add(task = { c: callback, f: fulfill });\n }),\n abort() {\n tasks.delete(task);\n }\n };\n}\n\n// Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM\n// at the end of hydration without touching the remaining nodes.\nlet is_hydrating = false;\nfunction start_hydrating() {\n is_hydrating = true;\n}\nfunction end_hydrating() {\n is_hydrating = false;\n}\nfunction upper_bound(low, high, key, value) {\n // Return first index of value larger than input value in the range [low, high)\n while (low < high) {\n const mid = low + ((high - low) >> 1);\n if (key(mid) <= value) {\n low = mid + 1;\n }\n else {\n high = mid;\n }\n }\n return low;\n}\nfunction init_hydrate(target) {\n if (target.hydrate_init)\n return;\n target.hydrate_init = true;\n // We know that all children have claim_order values since the unclaimed have been detached if target is not \n let children = target.childNodes;\n // If target is , there may be children without claim_order\n if (target.nodeName === 'HEAD') {\n const myChildren = [];\n for (let i = 0; i < children.length; i++) {\n const node = children[i];\n if (node.claim_order !== undefined) {\n myChildren.push(node);\n }\n }\n children = myChildren;\n }\n /*\n * Reorder claimed children optimally.\n * We can reorder claimed children optimally by finding the longest subsequence of\n * nodes that are already claimed in order and only moving the rest. The longest\n * subsequence subsequence of nodes that are claimed in order can be found by\n * computing the longest increasing subsequence of .claim_order values.\n *\n * This algorithm is optimal in generating the least amount of reorder operations\n * possible.\n *\n * Proof:\n * We know that, given a set of reordering operations, the nodes that do not move\n * always form an increasing subsequence, since they do not move among each other\n * meaning that they must be already ordered among each other. Thus, the maximal\n * set of nodes that do not move form a longest increasing subsequence.\n */\n // Compute longest increasing subsequence\n // m: subsequence length j => index k of smallest value that ends an increasing subsequence of length j\n const m = new Int32Array(children.length + 1);\n // Predecessor indices + 1\n const p = new Int32Array(children.length);\n m[0] = -1;\n let longest = 0;\n for (let i = 0; i < children.length; i++) {\n const current = children[i].claim_order;\n // Find the largest subsequence length such that it ends in a value less than our current value\n // upper_bound returns first greater value, so we subtract one\n // with fast path for when we are on the current longest subsequence\n const seqLen = ((longest > 0 && children[m[longest]].claim_order <= current) ? longest + 1 : upper_bound(1, longest, idx => children[m[idx]].claim_order, current)) - 1;\n p[i] = m[seqLen] + 1;\n const newLen = seqLen + 1;\n // We can guarantee that current is the smallest value. Otherwise, we would have generated a longer sequence.\n m[newLen] = i;\n longest = Math.max(newLen, longest);\n }\n // The longest increasing subsequence of nodes (initially reversed)\n const lis = [];\n // The rest of the nodes, nodes that will be moved\n const toMove = [];\n let last = children.length - 1;\n for (let cur = m[longest] + 1; cur != 0; cur = p[cur - 1]) {\n lis.push(children[cur - 1]);\n for (; last >= cur; last--) {\n toMove.push(children[last]);\n }\n last--;\n }\n for (; last >= 0; last--) {\n toMove.push(children[last]);\n }\n lis.reverse();\n // We sort the nodes being moved to guarantee that their insertion order matches the claim order\n toMove.sort((a, b) => a.claim_order - b.claim_order);\n // Finally, we move the nodes\n for (let i = 0, j = 0; i < toMove.length; i++) {\n while (j < lis.length && toMove[i].claim_order >= lis[j].claim_order) {\n j++;\n }\n const anchor = j < lis.length ? lis[j] : null;\n target.insertBefore(toMove[i], anchor);\n }\n}\nfunction append(target, node) {\n target.appendChild(node);\n}\nfunction append_styles(target, style_sheet_id, styles) {\n const append_styles_to = get_root_for_style(target);\n if (!append_styles_to.getElementById(style_sheet_id)) {\n const style = element('style');\n style.id = style_sheet_id;\n style.textContent = styles;\n append_stylesheet(append_styles_to, style);\n }\n}\nfunction get_root_for_style(node) {\n if (!node)\n return document;\n const root = node.getRootNode ? node.getRootNode() : node.ownerDocument;\n if (root && root.host) {\n return root;\n }\n return node.ownerDocument;\n}\nfunction append_empty_stylesheet(node) {\n const style_element = element('style');\n append_stylesheet(get_root_for_style(node), style_element);\n return style_element.sheet;\n}\nfunction append_stylesheet(node, style) {\n append(node.head || node, style);\n}\nfunction append_hydration(target, node) {\n if (is_hydrating) {\n init_hydrate(target);\n if ((target.actual_end_child === undefined) || ((target.actual_end_child !== null) && (target.actual_end_child.parentElement !== target))) {\n target.actual_end_child = target.firstChild;\n }\n // Skip nodes of undefined ordering\n while ((target.actual_end_child !== null) && (target.actual_end_child.claim_order === undefined)) {\n target.actual_end_child = target.actual_end_child.nextSibling;\n }\n if (node !== target.actual_end_child) {\n // We only insert if the ordering of this node should be modified or the parent node is not target\n if (node.claim_order !== undefined || node.parentNode !== target) {\n target.insertBefore(node, target.actual_end_child);\n }\n }\n else {\n target.actual_end_child = node.nextSibling;\n }\n }\n else if (node.parentNode !== target || node.nextSibling !== null) {\n target.appendChild(node);\n }\n}\nfunction insert(target, node, anchor) {\n target.insertBefore(node, anchor || null);\n}\nfunction insert_hydration(target, node, anchor) {\n if (is_hydrating && !anchor) {\n append_hydration(target, node);\n }\n else if (node.parentNode !== target || node.nextSibling != anchor) {\n target.insertBefore(node, anchor || null);\n }\n}\nfunction detach(node) {\n node.parentNode.removeChild(node);\n}\nfunction destroy_each(iterations, detaching) {\n for (let i = 0; i < iterations.length; i += 1) {\n if (iterations[i])\n iterations[i].d(detaching);\n }\n}\nfunction element(name) {\n return document.createElement(name);\n}\nfunction element_is(name, is) {\n return document.createElement(name, { is });\n}\nfunction object_without_properties(obj, exclude) {\n const target = {};\n for (const k in obj) {\n if (has_prop(obj, k)\n // @ts-ignore\n && exclude.indexOf(k) === -1) {\n // @ts-ignore\n target[k] = obj[k];\n }\n }\n return target;\n}\nfunction svg_element(name) {\n return document.createElementNS('http://www.w3.org/2000/svg', name);\n}\nfunction text(data) {\n return document.createTextNode(data);\n}\nfunction space() {\n return text(' ');\n}\nfunction empty() {\n return text('');\n}\nfunction listen(node, event, handler, options) {\n node.addEventListener(event, handler, options);\n return () => node.removeEventListener(event, handler, options);\n}\nfunction prevent_default(fn) {\n return function (event) {\n event.preventDefault();\n // @ts-ignore\n return fn.call(this, event);\n };\n}\nfunction stop_propagation(fn) {\n return function (event) {\n event.stopPropagation();\n // @ts-ignore\n return fn.call(this, event);\n };\n}\nfunction self(fn) {\n return function (event) {\n // @ts-ignore\n if (event.target === this)\n fn.call(this, event);\n };\n}\nfunction trusted(fn) {\n return function (event) {\n // @ts-ignore\n if (event.isTrusted)\n fn.call(this, event);\n };\n}\nfunction attr(node, attribute, value) {\n if (value == null)\n node.removeAttribute(attribute);\n else if (node.getAttribute(attribute) !== value)\n node.setAttribute(attribute, value);\n}\nfunction set_attributes(node, attributes) {\n // @ts-ignore\n const descriptors = Object.getOwnPropertyDescriptors(node.__proto__);\n for (const key in attributes) {\n if (attributes[key] == null) {\n node.removeAttribute(key);\n }\n else if (key === 'style') {\n node.style.cssText = attributes[key];\n }\n else if (key === '__value') {\n node.value = node[key] = attributes[key];\n }\n else if (descriptors[key] && descriptors[key].set) {\n node[key] = attributes[key];\n }\n else {\n attr(node, key, attributes[key]);\n }\n }\n}\nfunction set_svg_attributes(node, attributes) {\n for (const key in attributes) {\n attr(node, key, attributes[key]);\n }\n}\nfunction set_custom_element_data(node, prop, value) {\n if (prop in node) {\n node[prop] = typeof node[prop] === 'boolean' && value === '' ? true : value;\n }\n else {\n attr(node, prop, value);\n }\n}\nfunction xlink_attr(node, attribute, value) {\n node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value);\n}\nfunction get_binding_group_value(group, __value, checked) {\n const value = new Set();\n for (let i = 0; i < group.length; i += 1) {\n if (group[i].checked)\n value.add(group[i].__value);\n }\n if (!checked) {\n value.delete(__value);\n }\n return Array.from(value);\n}\nfunction to_number(value) {\n return value === '' ? null : +value;\n}\nfunction time_ranges_to_array(ranges) {\n const array = [];\n for (let i = 0; i < ranges.length; i += 1) {\n array.push({ start: ranges.start(i), end: ranges.end(i) });\n }\n return array;\n}\nfunction children(element) {\n return Array.from(element.childNodes);\n}\nfunction init_claim_info(nodes) {\n if (nodes.claim_info === undefined) {\n nodes.claim_info = { last_index: 0, total_claimed: 0 };\n }\n}\nfunction claim_node(nodes, predicate, processNode, createNode, dontUpdateLastIndex = false) {\n // Try to find nodes in an order such that we lengthen the longest increasing subsequence\n init_claim_info(nodes);\n const resultNode = (() => {\n // We first try to find an element after the previous one\n for (let i = nodes.claim_info.last_index; i < nodes.length; i++) {\n const node = nodes[i];\n if (predicate(node)) {\n const replacement = processNode(node);\n if (replacement === undefined) {\n nodes.splice(i, 1);\n }\n else {\n nodes[i] = replacement;\n }\n if (!dontUpdateLastIndex) {\n nodes.claim_info.last_index = i;\n }\n return node;\n }\n }\n // Otherwise, we try to find one before\n // We iterate in reverse so that we don't go too far back\n for (let i = nodes.claim_info.last_index - 1; i >= 0; i--) {\n const node = nodes[i];\n if (predicate(node)) {\n const replacement = processNode(node);\n if (replacement === undefined) {\n nodes.splice(i, 1);\n }\n else {\n nodes[i] = replacement;\n }\n if (!dontUpdateLastIndex) {\n nodes.claim_info.last_index = i;\n }\n else if (replacement === undefined) {\n // Since we spliced before the last_index, we decrease it\n nodes.claim_info.last_index--;\n }\n return node;\n }\n }\n // If we can't find any matching node, we create a new one\n return createNode();\n })();\n resultNode.claim_order = nodes.claim_info.total_claimed;\n nodes.claim_info.total_claimed += 1;\n return resultNode;\n}\nfunction claim_element_base(nodes, name, attributes, create_element) {\n return claim_node(nodes, (node) => node.nodeName === name, (node) => {\n const remove = [];\n for (let j = 0; j < node.attributes.length; j++) {\n const attribute = node.attributes[j];\n if (!attributes[attribute.name]) {\n remove.push(attribute.name);\n }\n }\n remove.forEach(v => node.removeAttribute(v));\n return undefined;\n }, () => create_element(name));\n}\nfunction claim_element(nodes, name, attributes) {\n return claim_element_base(nodes, name, attributes, element);\n}\nfunction claim_svg_element(nodes, name, attributes) {\n return claim_element_base(nodes, name, attributes, svg_element);\n}\nfunction claim_text(nodes, data) {\n return claim_node(nodes, (node) => node.nodeType === 3, (node) => {\n const dataStr = '' + data;\n if (node.data.startsWith(dataStr)) {\n if (node.data.length !== dataStr.length) {\n return node.splitText(dataStr.length);\n }\n }\n else {\n node.data = dataStr;\n }\n }, () => text(data), true // Text nodes should not update last index since it is likely not worth it to eliminate an increasing subsequence of actual elements\n );\n}\nfunction claim_space(nodes) {\n return claim_text(nodes, ' ');\n}\nfunction find_comment(nodes, text, start) {\n for (let i = start; i < nodes.length; i += 1) {\n const node = nodes[i];\n if (node.nodeType === 8 /* comment node */ && node.textContent.trim() === text) {\n return i;\n }\n }\n return nodes.length;\n}\nfunction claim_html_tag(nodes, is_svg) {\n // find html opening tag\n const start_index = find_comment(nodes, 'HTML_TAG_START', 0);\n const end_index = find_comment(nodes, 'HTML_TAG_END', start_index);\n if (start_index === end_index) {\n return new HtmlTagHydration(undefined, is_svg);\n }\n init_claim_info(nodes);\n const html_tag_nodes = nodes.splice(start_index, end_index - start_index + 1);\n detach(html_tag_nodes[0]);\n detach(html_tag_nodes[html_tag_nodes.length - 1]);\n const claimed_nodes = html_tag_nodes.slice(1, html_tag_nodes.length - 1);\n for (const n of claimed_nodes) {\n n.claim_order = nodes.claim_info.total_claimed;\n nodes.claim_info.total_claimed += 1;\n }\n return new HtmlTagHydration(claimed_nodes, is_svg);\n}\nfunction set_data(text, data) {\n data = '' + data;\n if (text.wholeText !== data)\n text.data = data;\n}\nfunction set_input_value(input, value) {\n input.value = value == null ? '' : value;\n}\nfunction set_input_type(input, type) {\n try {\n input.type = type;\n }\n catch (e) {\n // do nothing\n }\n}\nfunction set_style(node, key, value, important) {\n if (value === null) {\n node.style.removeProperty(key);\n }\n else {\n node.style.setProperty(key, value, important ? 'important' : '');\n }\n}\nfunction select_option(select, value) {\n for (let i = 0; i < select.options.length; i += 1) {\n const option = select.options[i];\n if (option.__value === value) {\n option.selected = true;\n return;\n }\n }\n select.selectedIndex = -1; // no option should be selected\n}\nfunction select_options(select, value) {\n for (let i = 0; i < select.options.length; i += 1) {\n const option = select.options[i];\n option.selected = ~value.indexOf(option.__value);\n }\n}\nfunction select_value(select) {\n const selected_option = select.querySelector(':checked') || select.options[0];\n return selected_option && selected_option.__value;\n}\nfunction select_multiple_value(select) {\n return [].map.call(select.querySelectorAll(':checked'), option => option.__value);\n}\n// unfortunately this can't be a constant as that wouldn't be tree-shakeable\n// so we cache the result instead\nlet crossorigin;\nfunction is_crossorigin() {\n if (crossorigin === undefined) {\n crossorigin = false;\n try {\n if (typeof window !== 'undefined' && window.parent) {\n void window.parent.document;\n }\n }\n catch (error) {\n crossorigin = true;\n }\n }\n return crossorigin;\n}\nfunction add_resize_listener(node, fn) {\n const computed_style = getComputedStyle(node);\n if (computed_style.position === 'static') {\n node.style.position = 'relative';\n }\n const iframe = element('iframe');\n iframe.setAttribute('style', 'display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; ' +\n 'overflow: hidden; border: 0; opacity: 0; pointer-events: none; z-index: -1;');\n iframe.setAttribute('aria-hidden', 'true');\n iframe.tabIndex = -1;\n const crossorigin = is_crossorigin();\n let unsubscribe;\n if (crossorigin) {\n iframe.src = \"data:text/html,\";\n unsubscribe = listen(window, 'message', (event) => {\n if (event.source === iframe.contentWindow)\n fn();\n });\n }\n else {\n iframe.src = 'about:blank';\n iframe.onload = () => {\n unsubscribe = listen(iframe.contentWindow, 'resize', fn);\n };\n }\n append(node, iframe);\n return () => {\n if (crossorigin) {\n unsubscribe();\n }\n else if (unsubscribe && iframe.contentWindow) {\n unsubscribe();\n }\n detach(iframe);\n };\n}\nfunction toggle_class(element, name, toggle) {\n element.classList[toggle ? 'add' : 'remove'](name);\n}\nfunction custom_event(type, detail, { bubbles = false, cancelable = false } = {}) {\n const e = document.createEvent('CustomEvent');\n e.initCustomEvent(type, bubbles, cancelable, detail);\n return e;\n}\nfunction query_selector_all(selector, parent = document.body) {\n return Array.from(parent.querySelectorAll(selector));\n}\nclass HtmlTag {\n constructor(is_svg = false) {\n this.is_svg = false;\n this.is_svg = is_svg;\n this.e = this.n = null;\n }\n c(html) {\n this.h(html);\n }\n m(html, target, anchor = null) {\n if (!this.e) {\n if (this.is_svg)\n this.e = svg_element(target.nodeName);\n else\n this.e = element(target.nodeName);\n this.t = target;\n this.c(html);\n }\n this.i(anchor);\n }\n h(html) {\n this.e.innerHTML = html;\n this.n = Array.from(this.e.childNodes);\n }\n i(anchor) {\n for (let i = 0; i < this.n.length; i += 1) {\n insert(this.t, this.n[i], anchor);\n }\n }\n p(html) {\n this.d();\n this.h(html);\n this.i(this.a);\n }\n d() {\n this.n.forEach(detach);\n }\n}\nclass HtmlTagHydration extends HtmlTag {\n constructor(claimed_nodes, is_svg = false) {\n super(is_svg);\n this.e = this.n = null;\n this.l = claimed_nodes;\n }\n c(html) {\n if (this.l) {\n this.n = this.l;\n }\n else {\n super.c(html);\n }\n }\n i(anchor) {\n for (let i = 0; i < this.n.length; i += 1) {\n insert_hydration(this.t, this.n[i], anchor);\n }\n }\n}\nfunction attribute_to_object(attributes) {\n const result = {};\n for (const attribute of attributes) {\n result[attribute.name] = attribute.value;\n }\n return result;\n}\nfunction get_custom_elements_slots(element) {\n const result = {};\n element.childNodes.forEach((node) => {\n result[node.slot || 'default'] = true;\n });\n return result;\n}\n\n// we need to store the information for multiple documents because a Svelte application could also contain iframes\n// https://github.com/sveltejs/svelte/issues/3624\nconst managed_styles = new Map();\nlet active = 0;\n// https://github.com/darkskyapp/string-hash/blob/master/index.js\nfunction hash(str) {\n let hash = 5381;\n let i = str.length;\n while (i--)\n hash = ((hash << 5) - hash) ^ str.charCodeAt(i);\n return hash >>> 0;\n}\nfunction create_style_information(doc, node) {\n const info = { stylesheet: append_empty_stylesheet(node), rules: {} };\n managed_styles.set(doc, info);\n return info;\n}\nfunction create_rule(node, a, b, duration, delay, ease, fn, uid = 0) {\n const step = 16.666 / duration;\n let keyframes = '{\\n';\n for (let p = 0; p <= 1; p += step) {\n const t = a + (b - a) * ease(p);\n keyframes += p * 100 + `%{${fn(t, 1 - t)}}\\n`;\n }\n const rule = keyframes + `100% {${fn(b, 1 - b)}}\\n}`;\n const name = `__svelte_${hash(rule)}_${uid}`;\n const doc = get_root_for_style(node);\n const { stylesheet, rules } = managed_styles.get(doc) || create_style_information(doc, node);\n if (!rules[name]) {\n rules[name] = true;\n stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length);\n }\n const animation = node.style.animation || '';\n node.style.animation = `${animation ? `${animation}, ` : ''}${name} ${duration}ms linear ${delay}ms 1 both`;\n active += 1;\n return name;\n}\nfunction delete_rule(node, name) {\n const previous = (node.style.animation || '').split(', ');\n const next = previous.filter(name\n ? anim => anim.indexOf(name) < 0 // remove specific animation\n : anim => anim.indexOf('__svelte') === -1 // remove all Svelte animations\n );\n const deleted = previous.length - next.length;\n if (deleted) {\n node.style.animation = next.join(', ');\n active -= deleted;\n if (!active)\n clear_rules();\n }\n}\nfunction clear_rules() {\n raf(() => {\n if (active)\n return;\n managed_styles.forEach(info => {\n const { stylesheet } = info;\n let i = stylesheet.cssRules.length;\n while (i--)\n stylesheet.deleteRule(i);\n info.rules = {};\n });\n managed_styles.clear();\n });\n}\n\nfunction create_animation(node, from, fn, params) {\n if (!from)\n return noop;\n const to = node.getBoundingClientRect();\n if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom)\n return noop;\n const { delay = 0, duration = 300, easing = identity, \n // @ts-ignore todo: should this be separated from destructuring? Or start/end added to public api and documentation?\n start: start_time = now() + delay, \n // @ts-ignore todo:\n end = start_time + duration, tick = noop, css } = fn(node, { from, to }, params);\n let running = true;\n let started = false;\n let name;\n function start() {\n if (css) {\n name = create_rule(node, 0, 1, duration, delay, easing, css);\n }\n if (!delay) {\n started = true;\n }\n }\n function stop() {\n if (css)\n delete_rule(node, name);\n running = false;\n }\n loop(now => {\n if (!started && now >= start_time) {\n started = true;\n }\n if (started && now >= end) {\n tick(1, 0);\n stop();\n }\n if (!running) {\n return false;\n }\n if (started) {\n const p = now - start_time;\n const t = 0 + 1 * easing(p / duration);\n tick(t, 1 - t);\n }\n return true;\n });\n start();\n tick(0, 1);\n return stop;\n}\nfunction fix_position(node) {\n const style = getComputedStyle(node);\n if (style.position !== 'absolute' && style.position !== 'fixed') {\n const { width, height } = style;\n const a = node.getBoundingClientRect();\n node.style.position = 'absolute';\n node.style.width = width;\n node.style.height = height;\n add_transform(node, a);\n }\n}\nfunction add_transform(node, a) {\n const b = node.getBoundingClientRect();\n if (a.left !== b.left || a.top !== b.top) {\n const style = getComputedStyle(node);\n const transform = style.transform === 'none' ? '' : style.transform;\n node.style.transform = `${transform} translate(${a.left - b.left}px, ${a.top - b.top}px)`;\n }\n}\n\nlet current_component;\nfunction set_current_component(component) {\n current_component = component;\n}\nfunction get_current_component() {\n if (!current_component)\n throw new Error('Function called outside component initialization');\n return current_component;\n}\nfunction beforeUpdate(fn) {\n get_current_component().$$.before_update.push(fn);\n}\nfunction onMount(fn) {\n get_current_component().$$.on_mount.push(fn);\n}\nfunction afterUpdate(fn) {\n get_current_component().$$.after_update.push(fn);\n}\nfunction onDestroy(fn) {\n get_current_component().$$.on_destroy.push(fn);\n}\nfunction createEventDispatcher() {\n const component = get_current_component();\n return (type, detail, { cancelable = false } = {}) => {\n const callbacks = component.$$.callbacks[type];\n if (callbacks) {\n // TODO are there situations where events could be dispatched\n // in a server (non-DOM) environment?\n const event = custom_event(type, detail, { cancelable });\n callbacks.slice().forEach(fn => {\n fn.call(component, event);\n });\n return !event.defaultPrevented;\n }\n return true;\n };\n}\nfunction setContext(key, context) {\n get_current_component().$$.context.set(key, context);\n return context;\n}\nfunction getContext(key) {\n return get_current_component().$$.context.get(key);\n}\nfunction getAllContexts() {\n return get_current_component().$$.context;\n}\nfunction hasContext(key) {\n return get_current_component().$$.context.has(key);\n}\n// TODO figure out if we still want to support\n// shorthand events, or if we want to implement\n// a real bubbling mechanism\nfunction bubble(component, event) {\n const callbacks = component.$$.callbacks[event.type];\n if (callbacks) {\n // @ts-ignore\n callbacks.slice().forEach(fn => fn.call(this, event));\n }\n}\n\nconst dirty_components = [];\nconst intros = { enabled: false };\nconst binding_callbacks = [];\nconst render_callbacks = [];\nconst flush_callbacks = [];\nconst resolved_promise = Promise.resolve();\nlet update_scheduled = false;\nfunction schedule_update() {\n if (!update_scheduled) {\n update_scheduled = true;\n resolved_promise.then(flush);\n }\n}\nfunction tick() {\n schedule_update();\n return resolved_promise;\n}\nfunction add_render_callback(fn) {\n render_callbacks.push(fn);\n}\nfunction add_flush_callback(fn) {\n flush_callbacks.push(fn);\n}\n// flush() calls callbacks in this order:\n// 1. All beforeUpdate callbacks, in order: parents before children\n// 2. All bind:this callbacks, in reverse order: children before parents.\n// 3. All afterUpdate callbacks, in order: parents before children. EXCEPT\n// for afterUpdates called during the initial onMount, which are called in\n// reverse order: children before parents.\n// Since callbacks might update component values, which could trigger another\n// call to flush(), the following steps guard against this:\n// 1. During beforeUpdate, any updated components will be added to the\n// dirty_components array and will cause a reentrant call to flush(). Because\n// the flush index is kept outside the function, the reentrant call will pick\n// up where the earlier call left off and go through all dirty components. The\n// current_component value is saved and restored so that the reentrant call will\n// not interfere with the \"parent\" flush() call.\n// 2. bind:this callbacks cannot trigger new flush() calls.\n// 3. During afterUpdate, any updated components will NOT have their afterUpdate\n// callback called a second time; the seen_callbacks set, outside the flush()\n// function, guarantees this behavior.\nconst seen_callbacks = new Set();\nlet flushidx = 0; // Do *not* move this inside the flush() function\nfunction flush() {\n const saved_component = current_component;\n do {\n // first, call beforeUpdate functions\n // and update components\n while (flushidx < dirty_components.length) {\n const component = dirty_components[flushidx];\n flushidx++;\n set_current_component(component);\n update(component.$$);\n }\n set_current_component(null);\n dirty_components.length = 0;\n flushidx = 0;\n while (binding_callbacks.length)\n binding_callbacks.pop()();\n // then, once components are updated, call\n // afterUpdate functions. This may cause\n // subsequent updates...\n for (let i = 0; i < render_callbacks.length; i += 1) {\n const callback = render_callbacks[i];\n if (!seen_callbacks.has(callback)) {\n // ...so guard against infinite loops\n seen_callbacks.add(callback);\n callback();\n }\n }\n render_callbacks.length = 0;\n } while (dirty_components.length);\n while (flush_callbacks.length) {\n flush_callbacks.pop()();\n }\n update_scheduled = false;\n seen_callbacks.clear();\n set_current_component(saved_component);\n}\nfunction update($$) {\n if ($$.fragment !== null) {\n $$.update();\n run_all($$.before_update);\n const dirty = $$.dirty;\n $$.dirty = [-1];\n $$.fragment && $$.fragment.p($$.ctx, dirty);\n $$.after_update.forEach(add_render_callback);\n }\n}\n\nlet promise;\nfunction wait() {\n if (!promise) {\n promise = Promise.resolve();\n promise.then(() => {\n promise = null;\n });\n }\n return promise;\n}\nfunction dispatch(node, direction, kind) {\n node.dispatchEvent(custom_event(`${direction ? 'intro' : 'outro'}${kind}`));\n}\nconst outroing = new Set();\nlet outros;\nfunction group_outros() {\n outros = {\n r: 0,\n c: [],\n p: outros // parent group\n };\n}\nfunction check_outros() {\n if (!outros.r) {\n run_all(outros.c);\n }\n outros = outros.p;\n}\nfunction transition_in(block, local) {\n if (block && block.i) {\n outroing.delete(block);\n block.i(local);\n }\n}\nfunction transition_out(block, local, detach, callback) {\n if (block && block.o) {\n if (outroing.has(block))\n return;\n outroing.add(block);\n outros.c.push(() => {\n outroing.delete(block);\n if (callback) {\n if (detach)\n block.d(1);\n callback();\n }\n });\n block.o(local);\n }\n}\nconst null_transition = { duration: 0 };\nfunction create_in_transition(node, fn, params) {\n let config = fn(node, params);\n let running = false;\n let animation_name;\n let task;\n let uid = 0;\n function cleanup() {\n if (animation_name)\n delete_rule(node, animation_name);\n }\n function go() {\n const { delay = 0, duration = 300, easing = identity, tick = noop, css } = config || null_transition;\n if (css)\n animation_name = create_rule(node, 0, 1, duration, delay, easing, css, uid++);\n tick(0, 1);\n const start_time = now() + delay;\n const end_time = start_time + duration;\n if (task)\n task.abort();\n running = true;\n add_render_callback(() => dispatch(node, true, 'start'));\n task = loop(now => {\n if (running) {\n if (now >= end_time) {\n tick(1, 0);\n dispatch(node, true, 'end');\n cleanup();\n return running = false;\n }\n if (now >= start_time) {\n const t = easing((now - start_time) / duration);\n tick(t, 1 - t);\n }\n }\n return running;\n });\n }\n let started = false;\n return {\n start() {\n if (started)\n return;\n started = true;\n delete_rule(node);\n if (is_function(config)) {\n config = config();\n wait().then(go);\n }\n else {\n go();\n }\n },\n invalidate() {\n started = false;\n },\n end() {\n if (running) {\n cleanup();\n running = false;\n }\n }\n };\n}\nfunction create_out_transition(node, fn, params) {\n let config = fn(node, params);\n let running = true;\n let animation_name;\n const group = outros;\n group.r += 1;\n function go() {\n const { delay = 0, duration = 300, easing = identity, tick = noop, css } = config || null_transition;\n if (css)\n animation_name = create_rule(node, 1, 0, duration, delay, easing, css);\n const start_time = now() + delay;\n const end_time = start_time + duration;\n add_render_callback(() => dispatch(node, false, 'start'));\n loop(now => {\n if (running) {\n if (now >= end_time) {\n tick(0, 1);\n dispatch(node, false, 'end');\n if (!--group.r) {\n // this will result in `end()` being called,\n // so we don't need to clean up here\n run_all(group.c);\n }\n return false;\n }\n if (now >= start_time) {\n const t = easing((now - start_time) / duration);\n tick(1 - t, t);\n }\n }\n return running;\n });\n }\n if (is_function(config)) {\n wait().then(() => {\n // @ts-ignore\n config = config();\n go();\n });\n }\n else {\n go();\n }\n return {\n end(reset) {\n if (reset && config.tick) {\n config.tick(1, 0);\n }\n if (running) {\n if (animation_name)\n delete_rule(node, animation_name);\n running = false;\n }\n }\n };\n}\nfunction create_bidirectional_transition(node, fn, params, intro) {\n let config = fn(node, params);\n let t = intro ? 0 : 1;\n let running_program = null;\n let pending_program = null;\n let animation_name = null;\n function clear_animation() {\n if (animation_name)\n delete_rule(node, animation_name);\n }\n function init(program, duration) {\n const d = (program.b - t);\n duration *= Math.abs(d);\n return {\n a: t,\n b: program.b,\n d,\n duration,\n start: program.start,\n end: program.start + duration,\n group: program.group\n };\n }\n function go(b) {\n const { delay = 0, duration = 300, easing = identity, tick = noop, css } = config || null_transition;\n const program = {\n start: now() + delay,\n b\n };\n if (!b) {\n // @ts-ignore todo: improve typings\n program.group = outros;\n outros.r += 1;\n }\n if (running_program || pending_program) {\n pending_program = program;\n }\n else {\n // if this is an intro, and there's a delay, we need to do\n // an initial tick and/or apply CSS animation immediately\n if (css) {\n clear_animation();\n animation_name = create_rule(node, t, b, duration, delay, easing, css);\n }\n if (b)\n tick(0, 1);\n running_program = init(program, duration);\n add_render_callback(() => dispatch(node, b, 'start'));\n loop(now => {\n if (pending_program && now > pending_program.start) {\n running_program = init(pending_program, duration);\n pending_program = null;\n dispatch(node, running_program.b, 'start');\n if (css) {\n clear_animation();\n animation_name = create_rule(node, t, running_program.b, running_program.duration, 0, easing, config.css);\n }\n }\n if (running_program) {\n if (now >= running_program.end) {\n tick(t = running_program.b, 1 - t);\n dispatch(node, running_program.b, 'end');\n if (!pending_program) {\n // we're done\n if (running_program.b) {\n // intro — we can tidy up immediately\n clear_animation();\n }\n else {\n // outro — needs to be coordinated\n if (!--running_program.group.r)\n run_all(running_program.group.c);\n }\n }\n running_program = null;\n }\n else if (now >= running_program.start) {\n const p = now - running_program.start;\n t = running_program.a + running_program.d * easing(p / running_program.duration);\n tick(t, 1 - t);\n }\n }\n return !!(running_program || pending_program);\n });\n }\n }\n return {\n run(b) {\n if (is_function(config)) {\n wait().then(() => {\n // @ts-ignore\n config = config();\n go(b);\n });\n }\n else {\n go(b);\n }\n },\n end() {\n clear_animation();\n running_program = pending_program = null;\n }\n };\n}\n\nfunction handle_promise(promise, info) {\n const token = info.token = {};\n function update(type, index, key, value) {\n if (info.token !== token)\n return;\n info.resolved = value;\n let child_ctx = info.ctx;\n if (key !== undefined) {\n child_ctx = child_ctx.slice();\n child_ctx[key] = value;\n }\n const block = type && (info.current = type)(child_ctx);\n let needs_flush = false;\n if (info.block) {\n if (info.blocks) {\n info.blocks.forEach((block, i) => {\n if (i !== index && block) {\n group_outros();\n transition_out(block, 1, 1, () => {\n if (info.blocks[i] === block) {\n info.blocks[i] = null;\n }\n });\n check_outros();\n }\n });\n }\n else {\n info.block.d(1);\n }\n block.c();\n transition_in(block, 1);\n block.m(info.mount(), info.anchor);\n needs_flush = true;\n }\n info.block = block;\n if (info.blocks)\n info.blocks[index] = block;\n if (needs_flush) {\n flush();\n }\n }\n if (is_promise(promise)) {\n const current_component = get_current_component();\n promise.then(value => {\n set_current_component(current_component);\n update(info.then, 1, info.value, value);\n set_current_component(null);\n }, error => {\n set_current_component(current_component);\n update(info.catch, 2, info.error, error);\n set_current_component(null);\n if (!info.hasCatch) {\n throw error;\n }\n });\n // if we previously had a then/catch block, destroy it\n if (info.current !== info.pending) {\n update(info.pending, 0);\n return true;\n }\n }\n else {\n if (info.current !== info.then) {\n update(info.then, 1, info.value, promise);\n return true;\n }\n info.resolved = promise;\n }\n}\nfunction update_await_block_branch(info, ctx, dirty) {\n const child_ctx = ctx.slice();\n const { resolved } = info;\n if (info.current === info.then) {\n child_ctx[info.value] = resolved;\n }\n if (info.current === info.catch) {\n child_ctx[info.error] = resolved;\n }\n info.block.p(child_ctx, dirty);\n}\n\nconst globals = (typeof window !== 'undefined'\n ? window\n : typeof globalThis !== 'undefined'\n ? globalThis\n : global);\n\nfunction destroy_block(block, lookup) {\n block.d(1);\n lookup.delete(block.key);\n}\nfunction outro_and_destroy_block(block, lookup) {\n transition_out(block, 1, 1, () => {\n lookup.delete(block.key);\n });\n}\nfunction fix_and_destroy_block(block, lookup) {\n block.f();\n destroy_block(block, lookup);\n}\nfunction fix_and_outro_and_destroy_block(block, lookup) {\n block.f();\n outro_and_destroy_block(block, lookup);\n}\nfunction update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list, lookup, node, destroy, create_each_block, next, get_context) {\n let o = old_blocks.length;\n let n = list.length;\n let i = o;\n const old_indexes = {};\n while (i--)\n old_indexes[old_blocks[i].key] = i;\n const new_blocks = [];\n const new_lookup = new Map();\n const deltas = new Map();\n i = n;\n while (i--) {\n const child_ctx = get_context(ctx, list, i);\n const key = get_key(child_ctx);\n let block = lookup.get(key);\n if (!block) {\n block = create_each_block(key, child_ctx);\n block.c();\n }\n else if (dynamic) {\n block.p(child_ctx, dirty);\n }\n new_lookup.set(key, new_blocks[i] = block);\n if (key in old_indexes)\n deltas.set(key, Math.abs(i - old_indexes[key]));\n }\n const will_move = new Set();\n const did_move = new Set();\n function insert(block) {\n transition_in(block, 1);\n block.m(node, next);\n lookup.set(block.key, block);\n next = block.first;\n n--;\n }\n while (o && n) {\n const new_block = new_blocks[n - 1];\n const old_block = old_blocks[o - 1];\n const new_key = new_block.key;\n const old_key = old_block.key;\n if (new_block === old_block) {\n // do nothing\n next = new_block.first;\n o--;\n n--;\n }\n else if (!new_lookup.has(old_key)) {\n // remove old block\n destroy(old_block, lookup);\n o--;\n }\n else if (!lookup.has(new_key) || will_move.has(new_key)) {\n insert(new_block);\n }\n else if (did_move.has(old_key)) {\n o--;\n }\n else if (deltas.get(new_key) > deltas.get(old_key)) {\n did_move.add(new_key);\n insert(new_block);\n }\n else {\n will_move.add(old_key);\n o--;\n }\n }\n while (o--) {\n const old_block = old_blocks[o];\n if (!new_lookup.has(old_block.key))\n destroy(old_block, lookup);\n }\n while (n)\n insert(new_blocks[n - 1]);\n return new_blocks;\n}\nfunction validate_each_keys(ctx, list, get_context, get_key) {\n const keys = new Set();\n for (let i = 0; i < list.length; i++) {\n const key = get_key(get_context(ctx, list, i));\n if (keys.has(key)) {\n throw new Error('Cannot have duplicate keys in a keyed each');\n }\n keys.add(key);\n }\n}\n\nfunction get_spread_update(levels, updates) {\n const update = {};\n const to_null_out = {};\n const accounted_for = { $$scope: 1 };\n let i = levels.length;\n while (i--) {\n const o = levels[i];\n const n = updates[i];\n if (n) {\n for (const key in o) {\n if (!(key in n))\n to_null_out[key] = 1;\n }\n for (const key in n) {\n if (!accounted_for[key]) {\n update[key] = n[key];\n accounted_for[key] = 1;\n }\n }\n levels[i] = n;\n }\n else {\n for (const key in o) {\n accounted_for[key] = 1;\n }\n }\n }\n for (const key in to_null_out) {\n if (!(key in update))\n update[key] = undefined;\n }\n return update;\n}\nfunction get_spread_object(spread_props) {\n return typeof spread_props === 'object' && spread_props !== null ? spread_props : {};\n}\n\n// source: https://html.spec.whatwg.org/multipage/indices.html\nconst boolean_attributes = new Set([\n 'allowfullscreen',\n 'allowpaymentrequest',\n 'async',\n 'autofocus',\n 'autoplay',\n 'checked',\n 'controls',\n 'default',\n 'defer',\n 'disabled',\n 'formnovalidate',\n 'hidden',\n 'ismap',\n 'loop',\n 'multiple',\n 'muted',\n 'nomodule',\n 'novalidate',\n 'open',\n 'playsinline',\n 'readonly',\n 'required',\n 'reversed',\n 'selected'\n]);\n\nconst void_element_names = /^(?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/;\nfunction is_void(name) {\n return void_element_names.test(name) || name.toLowerCase() === '!doctype';\n}\n\nconst invalid_attribute_name_character = /[\\s'\">/=\\u{FDD0}-\\u{FDEF}\\u{FFFE}\\u{FFFF}\\u{1FFFE}\\u{1FFFF}\\u{2FFFE}\\u{2FFFF}\\u{3FFFE}\\u{3FFFF}\\u{4FFFE}\\u{4FFFF}\\u{5FFFE}\\u{5FFFF}\\u{6FFFE}\\u{6FFFF}\\u{7FFFE}\\u{7FFFF}\\u{8FFFE}\\u{8FFFF}\\u{9FFFE}\\u{9FFFF}\\u{AFFFE}\\u{AFFFF}\\u{BFFFE}\\u{BFFFF}\\u{CFFFE}\\u{CFFFF}\\u{DFFFE}\\u{DFFFF}\\u{EFFFE}\\u{EFFFF}\\u{FFFFE}\\u{FFFFF}\\u{10FFFE}\\u{10FFFF}]/u;\n// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2\n// https://infra.spec.whatwg.org/#noncharacter\nfunction spread(args, attrs_to_add) {\n const attributes = Object.assign({}, ...args);\n if (attrs_to_add) {\n const classes_to_add = attrs_to_add.classes;\n const styles_to_add = attrs_to_add.styles;\n if (classes_to_add) {\n if (attributes.class == null) {\n attributes.class = classes_to_add;\n }\n else {\n attributes.class += ' ' + classes_to_add;\n }\n }\n if (styles_to_add) {\n if (attributes.style == null) {\n attributes.style = style_object_to_string(styles_to_add);\n }\n else {\n attributes.style = style_object_to_string(merge_ssr_styles(attributes.style, styles_to_add));\n }\n }\n }\n let str = '';\n Object.keys(attributes).forEach(name => {\n if (invalid_attribute_name_character.test(name))\n return;\n const value = attributes[name];\n if (value === true)\n str += ' ' + name;\n else if (boolean_attributes.has(name.toLowerCase())) {\n if (value)\n str += ' ' + name;\n }\n else if (value != null) {\n str += ` ${name}=\"${value}\"`;\n }\n });\n return str;\n}\nfunction merge_ssr_styles(style_attribute, style_directive) {\n const style_object = {};\n for (const individual_style of style_attribute.split(';')) {\n const colon_index = individual_style.indexOf(':');\n const name = individual_style.slice(0, colon_index).trim();\n const value = individual_style.slice(colon_index + 1).trim();\n if (!name)\n continue;\n style_object[name] = value;\n }\n for (const name in style_directive) {\n const value = style_directive[name];\n if (value) {\n style_object[name] = value;\n }\n else {\n delete style_object[name];\n }\n }\n return style_object;\n}\nconst escaped = {\n '\"': '"',\n \"'\": ''',\n '&': '&',\n '<': '<',\n '>': '>'\n};\nfunction escape(html) {\n return String(html).replace(/[\"'&<>]/g, match => escaped[match]);\n}\nfunction escape_attribute_value(value) {\n return typeof value === 'string' ? escape(value) : value;\n}\nfunction escape_object(obj) {\n const result = {};\n for (const key in obj) {\n result[key] = escape_attribute_value(obj[key]);\n }\n return result;\n}\nfunction each(items, fn) {\n let str = '';\n for (let i = 0; i < items.length; i += 1) {\n str += fn(items[i], i);\n }\n return str;\n}\nconst missing_component = {\n $$render: () => ''\n};\nfunction validate_component(component, name) {\n if (!component || !component.$$render) {\n if (name === 'svelte:component')\n name += ' this={...}';\n throw new Error(`<${name}> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules`);\n }\n return component;\n}\nfunction debug(file, line, column, values) {\n console.log(`{@debug} ${file ? file + ' ' : ''}(${line}:${column})`); // eslint-disable-line no-console\n console.log(values); // eslint-disable-line no-console\n return '';\n}\nlet on_destroy;\nfunction create_ssr_component(fn) {\n function $$render(result, props, bindings, slots, context) {\n const parent_component = current_component;\n const $$ = {\n on_destroy,\n context: new Map(context || (parent_component ? parent_component.$$.context : [])),\n // these will be immediately discarded\n on_mount: [],\n before_update: [],\n after_update: [],\n callbacks: blank_object()\n };\n set_current_component({ $$ });\n const html = fn(result, props, bindings, slots);\n set_current_component(parent_component);\n return html;\n }\n return {\n render: (props = {}, { $$slots = {}, context = new Map() } = {}) => {\n on_destroy = [];\n const result = { title: '', head: '', css: new Set() };\n const html = $$render(result, props, {}, $$slots, context);\n run_all(on_destroy);\n return {\n html,\n css: {\n code: Array.from(result.css).map(css => css.code).join('\\n'),\n map: null // TODO\n },\n head: result.title + result.head\n };\n },\n $$render\n };\n}\nfunction add_attribute(name, value, boolean) {\n if (value == null || (boolean && !value))\n return '';\n const assignment = (boolean && value === true) ? '' : `=\"${escape_attribute_value(value.toString())}\"`;\n return ` ${name}${assignment}`;\n}\nfunction add_classes(classes) {\n return classes ? ` class=\"${classes}\"` : '';\n}\nfunction style_object_to_string(style_object) {\n return Object.keys(style_object)\n .filter(key => style_object[key])\n .map(key => `${key}: ${style_object[key]};`)\n .join(' ');\n}\nfunction add_styles(style_object) {\n const styles = style_object_to_string(style_object);\n return styles ? ` style=\"${styles}\"` : '';\n}\n\nfunction bind(component, name, callback) {\n const index = component.$$.props[name];\n if (index !== undefined) {\n component.$$.bound[index] = callback;\n callback(component.$$.ctx[index]);\n }\n}\nfunction create_component(block) {\n block && block.c();\n}\nfunction claim_component(block, parent_nodes) {\n block && block.l(parent_nodes);\n}\nfunction mount_component(component, target, anchor, customElement) {\n const { fragment, on_mount, on_destroy, after_update } = component.$$;\n fragment && fragment.m(target, anchor);\n if (!customElement) {\n // onMount happens before the initial afterUpdate\n add_render_callback(() => {\n const new_on_destroy = on_mount.map(run).filter(is_function);\n if (on_destroy) {\n on_destroy.push(...new_on_destroy);\n }\n else {\n // Edge case - component was destroyed immediately,\n // most likely as a result of a binding initialising\n run_all(new_on_destroy);\n }\n component.$$.on_mount = [];\n });\n }\n after_update.forEach(add_render_callback);\n}\nfunction destroy_component(component, detaching) {\n const $$ = component.$$;\n if ($$.fragment !== null) {\n run_all($$.on_destroy);\n $$.fragment && $$.fragment.d(detaching);\n // TODO null out other refs, including component.$$ (but need to\n // preserve final state?)\n $$.on_destroy = $$.fragment = null;\n $$.ctx = [];\n }\n}\nfunction make_dirty(component, i) {\n if (component.$$.dirty[0] === -1) {\n dirty_components.push(component);\n schedule_update();\n component.$$.dirty.fill(0);\n }\n component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));\n}\nfunction init(component, options, instance, create_fragment, not_equal, props, append_styles, dirty = [-1]) {\n const parent_component = current_component;\n set_current_component(component);\n const $$ = component.$$ = {\n fragment: null,\n ctx: null,\n // state\n props,\n update: noop,\n not_equal,\n bound: blank_object(),\n // lifecycle\n on_mount: [],\n on_destroy: [],\n on_disconnect: [],\n before_update: [],\n after_update: [],\n context: new Map(options.context || (parent_component ? parent_component.$$.context : [])),\n // everything else\n callbacks: blank_object(),\n dirty,\n skip_bound: false,\n root: options.target || parent_component.$$.root\n };\n append_styles && append_styles($$.root);\n let ready = false;\n $$.ctx = instance\n ? instance(component, options.props || {}, (i, ret, ...rest) => {\n const value = rest.length ? rest[0] : ret;\n if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) {\n if (!$$.skip_bound && $$.bound[i])\n $$.bound[i](value);\n if (ready)\n make_dirty(component, i);\n }\n return ret;\n })\n : [];\n $$.update();\n ready = true;\n run_all($$.before_update);\n // `false` as a special case of no DOM component\n $$.fragment = create_fragment ? create_fragment($$.ctx) : false;\n if (options.target) {\n if (options.hydrate) {\n start_hydrating();\n const nodes = children(options.target);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n $$.fragment && $$.fragment.l(nodes);\n nodes.forEach(detach);\n }\n else {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n $$.fragment && $$.fragment.c();\n }\n if (options.intro)\n transition_in(component.$$.fragment);\n mount_component(component, options.target, options.anchor, options.customElement);\n end_hydrating();\n flush();\n }\n set_current_component(parent_component);\n}\nlet SvelteElement;\nif (typeof HTMLElement === 'function') {\n SvelteElement = class extends HTMLElement {\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n connectedCallback() {\n const { on_mount } = this.$$;\n this.$$.on_disconnect = on_mount.map(run).filter(is_function);\n // @ts-ignore todo: improve typings\n for (const key in this.$$.slotted) {\n // @ts-ignore todo: improve typings\n this.appendChild(this.$$.slotted[key]);\n }\n }\n attributeChangedCallback(attr, _oldValue, newValue) {\n this[attr] = newValue;\n }\n disconnectedCallback() {\n run_all(this.$$.on_disconnect);\n }\n $destroy() {\n destroy_component(this, 1);\n this.$destroy = noop;\n }\n $on(type, callback) {\n // TODO should this delegate to addEventListener?\n const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));\n callbacks.push(callback);\n return () => {\n const index = callbacks.indexOf(callback);\n if (index !== -1)\n callbacks.splice(index, 1);\n };\n }\n $set($$props) {\n if (this.$$set && !is_empty($$props)) {\n this.$$.skip_bound = true;\n this.$$set($$props);\n this.$$.skip_bound = false;\n }\n }\n };\n}\n/**\n * Base class for Svelte components. Used when dev=false.\n */\nclass SvelteComponent {\n $destroy() {\n destroy_component(this, 1);\n this.$destroy = noop;\n }\n $on(type, callback) {\n const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));\n callbacks.push(callback);\n return () => {\n const index = callbacks.indexOf(callback);\n if (index !== -1)\n callbacks.splice(index, 1);\n };\n }\n $set($$props) {\n if (this.$$set && !is_empty($$props)) {\n this.$$.skip_bound = true;\n this.$$set($$props);\n this.$$.skip_bound = false;\n }\n }\n}\n\nfunction dispatch_dev(type, detail) {\n document.dispatchEvent(custom_event(type, Object.assign({ version: '3.48.0' }, detail), { bubbles: true }));\n}\nfunction append_dev(target, node) {\n dispatch_dev('SvelteDOMInsert', { target, node });\n append(target, node);\n}\nfunction append_hydration_dev(target, node) {\n dispatch_dev('SvelteDOMInsert', { target, node });\n append_hydration(target, node);\n}\nfunction insert_dev(target, node, anchor) {\n dispatch_dev('SvelteDOMInsert', { target, node, anchor });\n insert(target, node, anchor);\n}\nfunction insert_hydration_dev(target, node, anchor) {\n dispatch_dev('SvelteDOMInsert', { target, node, anchor });\n insert_hydration(target, node, anchor);\n}\nfunction detach_dev(node) {\n dispatch_dev('SvelteDOMRemove', { node });\n detach(node);\n}\nfunction detach_between_dev(before, after) {\n while (before.nextSibling && before.nextSibling !== after) {\n detach_dev(before.nextSibling);\n }\n}\nfunction detach_before_dev(after) {\n while (after.previousSibling) {\n detach_dev(after.previousSibling);\n }\n}\nfunction detach_after_dev(before) {\n while (before.nextSibling) {\n detach_dev(before.nextSibling);\n }\n}\nfunction listen_dev(node, event, handler, options, has_prevent_default, has_stop_propagation) {\n const modifiers = options === true ? ['capture'] : options ? Array.from(Object.keys(options)) : [];\n if (has_prevent_default)\n modifiers.push('preventDefault');\n if (has_stop_propagation)\n modifiers.push('stopPropagation');\n dispatch_dev('SvelteDOMAddEventListener', { node, event, handler, modifiers });\n const dispose = listen(node, event, handler, options);\n return () => {\n dispatch_dev('SvelteDOMRemoveEventListener', { node, event, handler, modifiers });\n dispose();\n };\n}\nfunction attr_dev(node, attribute, value) {\n attr(node, attribute, value);\n if (value == null)\n dispatch_dev('SvelteDOMRemoveAttribute', { node, attribute });\n else\n dispatch_dev('SvelteDOMSetAttribute', { node, attribute, value });\n}\nfunction prop_dev(node, property, value) {\n node[property] = value;\n dispatch_dev('SvelteDOMSetProperty', { node, property, value });\n}\nfunction dataset_dev(node, property, value) {\n node.dataset[property] = value;\n dispatch_dev('SvelteDOMSetDataset', { node, property, value });\n}\nfunction set_data_dev(text, data) {\n data = '' + data;\n if (text.wholeText === data)\n return;\n dispatch_dev('SvelteDOMSetData', { node: text, data });\n text.data = data;\n}\nfunction validate_each_argument(arg) {\n if (typeof arg !== 'string' && !(arg && typeof arg === 'object' && 'length' in arg)) {\n let msg = '{#each} only iterates over array-like objects.';\n if (typeof Symbol === 'function' && arg && Symbol.iterator in arg) {\n msg += ' You can use a spread to convert this iterable into an array.';\n }\n throw new Error(msg);\n }\n}\nfunction validate_slots(name, slot, keys) {\n for (const slot_key of Object.keys(slot)) {\n if (!~keys.indexOf(slot_key)) {\n console.warn(`<${name}> received an unexpected slot \"${slot_key}\".`);\n }\n }\n}\nfunction validate_dynamic_element(tag) {\n const is_string = typeof tag === 'string';\n if (tag && !is_string) {\n throw new Error(' expects \"this\" attribute to be a string.');\n }\n}\nfunction validate_void_dynamic_element(tag) {\n if (tag && is_void(tag)) {\n throw new Error(` is self-closing and cannot have content.`);\n }\n}\n/**\n * Base class for Svelte components with some minor dev-enhancements. Used when dev=true.\n */\nclass SvelteComponentDev extends SvelteComponent {\n constructor(options) {\n if (!options || (!options.target && !options.$$inline)) {\n throw new Error(\"'target' is a required option\");\n }\n super();\n }\n $destroy() {\n super.$destroy();\n this.$destroy = () => {\n console.warn('Component was already destroyed'); // eslint-disable-line no-console\n };\n }\n $capture_state() { }\n $inject_state() { }\n}\n/**\n * Base class to create strongly typed Svelte components.\n * This only exists for typing purposes and should be used in `.d.ts` files.\n *\n * ### Example:\n *\n * You have component library on npm called `component-library`, from which\n * you export a component called `MyComponent`. For Svelte+TypeScript users,\n * you want to provide typings. Therefore you create a `index.d.ts`:\n * ```ts\n * import { SvelteComponentTyped } from \"svelte\";\n * export class MyComponent extends SvelteComponentTyped<{foo: string}> {}\n * ```\n * Typing this makes it possible for IDEs like VS Code with the Svelte extension\n * to provide intellisense and to use the component like this in a Svelte file\n * with TypeScript:\n * ```svelte\n * \n * \n * ```\n *\n * #### Why not make this part of `SvelteComponent(Dev)`?\n * Because\n * ```ts\n * class ASubclassOfSvelteComponent extends SvelteComponent<{foo: string}> {}\n * const component: typeof SvelteComponent = ASubclassOfSvelteComponent;\n * ```\n * will throw a type error, so we need to separate the more strictly typed class.\n */\nclass SvelteComponentTyped extends SvelteComponentDev {\n constructor(options) {\n super(options);\n }\n}\nfunction loop_guard(timeout) {\n const start = Date.now();\n return () => {\n if (Date.now() - start > timeout) {\n throw new Error('Infinite loop detected');\n }\n };\n}\n\nexport { HtmlTag, HtmlTagHydration, SvelteComponent, SvelteComponentDev, SvelteComponentTyped, SvelteElement, action_destroyer, add_attribute, add_classes, add_flush_callback, add_location, add_render_callback, add_resize_listener, add_styles, add_transform, afterUpdate, append, append_dev, append_empty_stylesheet, append_hydration, append_hydration_dev, append_styles, assign, attr, attr_dev, attribute_to_object, beforeUpdate, bind, binding_callbacks, blank_object, bubble, check_outros, children, claim_component, claim_element, claim_html_tag, claim_space, claim_svg_element, claim_text, clear_loops, component_subscribe, compute_rest_props, compute_slots, createEventDispatcher, create_animation, create_bidirectional_transition, create_component, create_in_transition, create_out_transition, create_slot, create_ssr_component, current_component, custom_event, dataset_dev, debug, destroy_block, destroy_component, destroy_each, detach, detach_after_dev, detach_before_dev, detach_between_dev, detach_dev, dirty_components, dispatch_dev, each, element, element_is, empty, end_hydrating, escape, escape_attribute_value, escape_object, escaped, exclude_internal_props, fix_and_destroy_block, fix_and_outro_and_destroy_block, fix_position, flush, getAllContexts, getContext, get_all_dirty_from_scope, get_binding_group_value, get_current_component, get_custom_elements_slots, get_root_for_style, get_slot_changes, get_spread_object, get_spread_update, get_store_value, globals, group_outros, handle_promise, hasContext, has_prop, identity, init, insert, insert_dev, insert_hydration, insert_hydration_dev, intros, invalid_attribute_name_character, is_client, is_crossorigin, is_empty, is_function, is_promise, is_void, listen, listen_dev, loop, loop_guard, merge_ssr_styles, missing_component, mount_component, noop, not_equal, now, null_to_empty, object_without_properties, onDestroy, onMount, once, outro_and_destroy_block, prevent_default, prop_dev, query_selector_all, raf, run, run_all, safe_not_equal, schedule_update, select_multiple_value, select_option, select_options, select_value, self, setContext, set_attributes, set_current_component, set_custom_element_data, set_data, set_data_dev, set_input_type, set_input_value, set_now, set_raf, set_store_value, set_style, set_svg_attributes, space, spread, src_url_equal, start_hydrating, stop_propagation, subscribe, svg_element, text, tick, time_ranges_to_array, to_number, toggle_class, transition_in, transition_out, trusted, update_await_block_branch, update_keyed_each, update_slot, update_slot_base, validate_component, validate_dynamic_element, validate_each_argument, validate_each_keys, validate_slots, validate_store, validate_void_dynamic_element, xlink_attr };\n","\n\n\n\n\n {@html text}\n\n","\n\n\n\n

\n","\n\n\n\n\n ×\n\n","\n\n\n\n\n\n","\n\n\n\n
\n {#if title}\n \n {/if}\n\n {#if cancelIcon && cancelIcon.enabled}\n \n {/if}\n
\n","\n\n\n\n\n\n\n","\n\n\n\n\n {#if !isUndefined(step.options.title) || (step.options.cancelIcon && step.options.cancelIcon.enabled)}\n \n {/if}\n\n {#if !isUndefined(step.options.text)}\n \n {/if}\n\n {#if Array.isArray(step.options.buttons) && step.options.buttons.length}\n \n {/if}\n\n","\n\n\n\n\n {#if step.options.arrow && step.options.attachTo && step.options.attachTo.element && step.options.attachTo.on}\n
\n {/if}\n \n\n","/**\n * Cleanup the steps and set pointerEvents back to 'auto'\n * @param tour The tour object\n */\nexport function cleanupSteps(tour) {\n if (tour) {\n const { steps } = tour;\n\n steps.forEach((step) => {\n if (\n step.options &&\n step.options.canClickTarget === false &&\n step.options.attachTo\n ) {\n if (step.target instanceof HTMLElement) {\n step.target.classList.remove('shepherd-target-click-disabled');\n }\n }\n });\n }\n}\n","\n\n\n \n\n\n\n","/**\n * Generates the svg path data for a rounded rectangle overlay\n * @param {Object} dimension - Dimensions of rectangle.\n * @param {number} width - Width.\n * @param {number} height - Height.\n * @param {number} [x=0] - Offset from top left corner in x axis. default 0.\n * @param {number} [y=0] - Offset from top left corner in y axis. default 0.\n * @param {number} [r=0] - Corner Radius. Keep this smaller than half of width or height.\n * @returns {string} - Rounded rectangle overlay path data.\n */\nexport function makeOverlayPath({ width, height, x = 0, y = 0, r = 0 }) {\n const { innerWidth: w, innerHeight: h } = window;\n\n return `M${w},${h}\\\nH0\\\nV0\\\nH${w}\\\nV${h}\\\nZ\\\nM${x + r},${y}\\\na${r},${r},0,0,0-${r},${r}\\\nV${height + y - r}\\\na${r},${r},0,0,0,${r},${r}\\\nH${width + x - r}\\\na${r},${r},0,0,0,${r}-${r}\\\nV${y + r}\\\na${r},${r},0,0,0-${r}-${r}\\\nZ`;\n}\n","import { isUndefined } from './utils/type-check';\n\nexport class Evented {\n on(event, handler, ctx, once = false) {\n if (isUndefined(this.bindings)) {\n this.bindings = {};\n }\n if (isUndefined(this.bindings[event])) {\n this.bindings[event] = [];\n }\n this.bindings[event].push({ handler, ctx, once });\n\n return this;\n }\n\n once(event, handler, ctx) {\n return this.on(event, handler, ctx, true);\n }\n\n off(event, handler) {\n if (isUndefined(this.bindings) || isUndefined(this.bindings[event])) {\n return this;\n }\n\n if (isUndefined(handler)) {\n delete this.bindings[event];\n } else {\n this.bindings[event].forEach((binding, index) => {\n if (binding.handler === handler) {\n this.bindings[event].splice(index, 1);\n }\n });\n }\n\n return this;\n }\n\n trigger(event, ...args) {\n if (!isUndefined(this.bindings) && this.bindings[event]) {\n this.bindings[event].forEach((binding, index) => {\n const { ctx, handler, once } = binding;\n\n const context = ctx || this;\n\n handler.apply(context, args);\n\n if (once) {\n this.bindings[event].splice(index, 1);\n }\n });\n }\n\n return this;\n }\n}\n","export var max = Math.max;\nexport var min = Math.min;\nexport var round = Math.round;","import getWindow from \"../dom-utils/getWindow.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar passive = {\n passive: true\n};\n\nfunction effect(_ref) {\n var state = _ref.state,\n instance = _ref.instance,\n options = _ref.options;\n var _options$scroll = options.scroll,\n scroll = _options$scroll === void 0 ? true : _options$scroll,\n _options$resize = options.resize,\n resize = _options$resize === void 0 ? true : _options$resize;\n var window = getWindow(state.elements.popper);\n var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper);\n\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.addEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.addEventListener('resize', instance.update, passive);\n }\n\n return function () {\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.removeEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.removeEventListener('resize', instance.update, passive);\n }\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'eventListeners',\n enabled: true,\n phase: 'write',\n fn: function fn() {},\n effect: effect,\n data: {}\n};","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nimport offset from \"./modifiers/offset.js\";\nimport flip from \"./modifiers/flip.js\";\nimport preventOverflow from \"./modifiers/preventOverflow.js\";\nimport arrow from \"./modifiers/arrow.js\";\nimport hide from \"./modifiers/hide.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles, offset, flip, preventOverflow, arrow, hide];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow }; // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper as createPopperLite } from \"./popper-lite.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport * from \"./modifiers/index.js\";","import computeOffsets from \"../utils/computeOffsets.js\";\n\nfunction popperOffsets(_ref) {\n var state = _ref.state,\n name = _ref.name;\n // Offsets are the actual position the popper needs to have to be\n // properly positioned near its reference element\n // This is the most basic placement, and will be adjusted by\n // the modifiers in the next step\n state.modifiersData[name] = computeOffsets({\n reference: state.rects.reference,\n element: state.rects.popper,\n strategy: 'absolute',\n placement: state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'popperOffsets',\n enabled: true,\n phase: 'read',\n fn: popperOffsets,\n data: {}\n};","import getNodeName from \"../dom-utils/getNodeName.js\";\nimport { isHTMLElement } from \"../dom-utils/instanceOf.js\"; // This modifier takes the styles prepared by the `computeStyles` modifier\n// and applies them to the HTMLElements such as popper and arrow\n\nfunction applyStyles(_ref) {\n var state = _ref.state;\n Object.keys(state.elements).forEach(function (name) {\n var style = state.styles[name] || {};\n var attributes = state.attributes[name] || {};\n var element = state.elements[name]; // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n } // Flow doesn't support to extend this property, but it's the most\n // effective way to apply styles to an HTMLElement\n // $FlowFixMe[cannot-write]\n\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (name) {\n var value = attributes[name];\n\n if (value === false) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value === true ? '' : value);\n }\n });\n });\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state;\n var initialStyles = {\n popper: {\n position: state.options.strategy,\n left: '0',\n top: '0',\n margin: '0'\n },\n arrow: {\n position: 'absolute'\n },\n reference: {}\n };\n Object.assign(state.elements.popper.style, initialStyles.popper);\n state.styles = initialStyles;\n\n if (state.elements.arrow) {\n Object.assign(state.elements.arrow.style, initialStyles.arrow);\n }\n\n return function () {\n Object.keys(state.elements).forEach(function (name) {\n var element = state.elements[name];\n var attributes = state.attributes[name] || {};\n var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them\n\n var style = styleProperties.reduce(function (style, property) {\n style[property] = '';\n return style;\n }, {}); // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n }\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (attribute) {\n element.removeAttribute(attribute);\n });\n });\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'applyStyles',\n enabled: true,\n phase: 'write',\n fn: applyStyles,\n effect: effect,\n requires: ['computeStyles']\n};","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport { top, left, right, placements } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport function distanceAndSkiddingToXY(placement, rects, offset) {\n var basePlacement = getBasePlacement(placement);\n var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1;\n\n var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, {\n placement: placement\n })) : offset,\n skidding = _ref[0],\n distance = _ref[1];\n\n skidding = skidding || 0;\n distance = (distance || 0) * invertDistance;\n return [left, right].indexOf(basePlacement) >= 0 ? {\n x: distance,\n y: skidding\n } : {\n x: skidding,\n y: distance\n };\n}\n\nfunction offset(_ref2) {\n var state = _ref2.state,\n options = _ref2.options,\n name = _ref2.name;\n var _options$offset = options.offset,\n offset = _options$offset === void 0 ? [0, 0] : _options$offset;\n var data = placements.reduce(function (acc, placement) {\n acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset);\n return acc;\n }, {});\n var _data$state$placement = data[state.placement],\n x = _data$state$placement.x,\n y = _data$state$placement.y;\n\n if (state.modifiersData.popperOffsets != null) {\n state.modifiersData.popperOffsets.x += x;\n state.modifiersData.popperOffsets.y += y;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'offset',\n enabled: true,\n phase: 'main',\n requires: ['popperOffsets'],\n fn: offset\n};","import { top, left, right, bottom, start } from \"../enums.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport getAltAxis from \"../utils/getAltAxis.js\";\nimport { within, withinMaxClamp } from \"../utils/within.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport getFreshSideObject from \"../utils/getFreshSideObject.js\";\nimport { min as mathMin, max as mathMax } from \"../utils/math.js\";\n\nfunction preventOverflow(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n padding = options.padding,\n _options$tether = options.tether,\n tether = _options$tether === void 0 ? true : _options$tether,\n _options$tetherOffset = options.tetherOffset,\n tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset;\n var overflow = detectOverflow(state, {\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n altBoundary: altBoundary\n });\n var basePlacement = getBasePlacement(state.placement);\n var variation = getVariation(state.placement);\n var isBasePlacement = !variation;\n var mainAxis = getMainAxisFromPlacement(basePlacement);\n var altAxis = getAltAxis(mainAxis);\n var popperOffsets = state.modifiersData.popperOffsets;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, {\n placement: state.placement\n })) : tetherOffset;\n var normalizedTetherOffsetValue = typeof tetherOffsetValue === 'number' ? {\n mainAxis: tetherOffsetValue,\n altAxis: tetherOffsetValue\n } : Object.assign({\n mainAxis: 0,\n altAxis: 0\n }, tetherOffsetValue);\n var offsetModifierState = state.modifiersData.offset ? state.modifiersData.offset[state.placement] : null;\n var data = {\n x: 0,\n y: 0\n };\n\n if (!popperOffsets) {\n return;\n }\n\n if (checkMainAxis) {\n var _offsetModifierState$;\n\n var mainSide = mainAxis === 'y' ? top : left;\n var altSide = mainAxis === 'y' ? bottom : right;\n var len = mainAxis === 'y' ? 'height' : 'width';\n var offset = popperOffsets[mainAxis];\n var min = offset + overflow[mainSide];\n var max = offset - overflow[altSide];\n var additive = tether ? -popperRect[len] / 2 : 0;\n var minLen = variation === start ? referenceRect[len] : popperRect[len];\n var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go\n // outside the reference bounds\n\n var arrowElement = state.elements.arrow;\n var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : {\n width: 0,\n height: 0\n };\n var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject();\n var arrowPaddingMin = arrowPaddingObject[mainSide];\n var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want\n // to include its full size in the calculation. If the reference is small\n // and near the edge of a boundary, the popper can overflow even if the\n // reference is not overflowing as well (e.g. virtual elements with no\n // width or height)\n\n var arrowLen = within(0, referenceRect[len], arrowRect[len]);\n var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis : minLen - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis;\n var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis : maxLen + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis;\n var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow);\n var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;\n var offsetModifierValue = (_offsetModifierState$ = offsetModifierState == null ? void 0 : offsetModifierState[mainAxis]) != null ? _offsetModifierState$ : 0;\n var tetherMin = offset + minOffset - offsetModifierValue - clientOffset;\n var tetherMax = offset + maxOffset - offsetModifierValue;\n var preventedOffset = within(tether ? mathMin(min, tetherMin) : min, offset, tether ? mathMax(max, tetherMax) : max);\n popperOffsets[mainAxis] = preventedOffset;\n data[mainAxis] = preventedOffset - offset;\n }\n\n if (checkAltAxis) {\n var _offsetModifierState$2;\n\n var _mainSide = mainAxis === 'x' ? top : left;\n\n var _altSide = mainAxis === 'x' ? bottom : right;\n\n var _offset = popperOffsets[altAxis];\n\n var _len = altAxis === 'y' ? 'height' : 'width';\n\n var _min = _offset + overflow[_mainSide];\n\n var _max = _offset - overflow[_altSide];\n\n var isOriginSide = [top, left].indexOf(basePlacement) !== -1;\n\n var _offsetModifierValue = (_offsetModifierState$2 = offsetModifierState == null ? void 0 : offsetModifierState[altAxis]) != null ? _offsetModifierState$2 : 0;\n\n var _tetherMin = isOriginSide ? _min : _offset - referenceRect[_len] - popperRect[_len] - _offsetModifierValue + normalizedTetherOffsetValue.altAxis;\n\n var _tetherMax = isOriginSide ? _offset + referenceRect[_len] + popperRect[_len] - _offsetModifierValue - normalizedTetherOffsetValue.altAxis : _max;\n\n var _preventedOffset = tether && isOriginSide ? withinMaxClamp(_tetherMin, _offset, _tetherMax) : within(tether ? _tetherMin : _min, _offset, tether ? _tetherMax : _max);\n\n popperOffsets[altAxis] = _preventedOffset;\n data[altAxis] = _preventedOffset - _offset;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'preventOverflow',\n enabled: true,\n phase: 'main',\n fn: preventOverflow,\n requiresIfExists: ['offset']\n};","export default function getAltAxis(axis) {\n return axis === 'x' ? 'y' : 'x';\n}","import { max as mathMax, min as mathMin } from \"./math.js\";\nexport function within(min, value, max) {\n return mathMax(min, mathMin(value, max));\n}\nexport function withinMaxClamp(min, value, max) {\n var v = within(min, value, max);\n return v > max ? max : v;\n}","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport contains from \"../dom-utils/contains.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport { within } from \"../utils/within.js\";\nimport mergePaddingObject from \"../utils/mergePaddingObject.js\";\nimport expandToHashMap from \"../utils/expandToHashMap.js\";\nimport { left, right, basePlacements, top, bottom } from \"../enums.js\";\nimport { isHTMLElement } from \"../dom-utils/instanceOf.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar toPaddingObject = function toPaddingObject(padding, state) {\n padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, {\n placement: state.placement\n })) : padding;\n return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n};\n\nfunction arrow(_ref) {\n var _state$modifiersData$;\n\n var state = _ref.state,\n name = _ref.name,\n options = _ref.options;\n var arrowElement = state.elements.arrow;\n var popperOffsets = state.modifiersData.popperOffsets;\n var basePlacement = getBasePlacement(state.placement);\n var axis = getMainAxisFromPlacement(basePlacement);\n var isVertical = [left, right].indexOf(basePlacement) >= 0;\n var len = isVertical ? 'height' : 'width';\n\n if (!arrowElement || !popperOffsets) {\n return;\n }\n\n var paddingObject = toPaddingObject(options.padding, state);\n var arrowRect = getLayoutRect(arrowElement);\n var minProp = axis === 'y' ? top : left;\n var maxProp = axis === 'y' ? bottom : right;\n var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len];\n var startDiff = popperOffsets[axis] - state.rects.reference[axis];\n var arrowOffsetParent = getOffsetParent(arrowElement);\n var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0;\n var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is\n // outside of the popper bounds\n\n var min = paddingObject[minProp];\n var max = clientSize - arrowRect[len] - paddingObject[maxProp];\n var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;\n var offset = within(min, center, max); // Prevents breaking syntax highlighting...\n\n var axisProp = axis;\n state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$);\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state,\n options = _ref2.options;\n var _options$element = options.element,\n arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element;\n\n if (arrowElement == null) {\n return;\n } // CSS selector\n\n\n if (typeof arrowElement === 'string') {\n arrowElement = state.elements.popper.querySelector(arrowElement);\n\n if (!arrowElement) {\n return;\n }\n }\n\n if (process.env.NODE_ENV !== \"production\") {\n if (!isHTMLElement(arrowElement)) {\n console.error(['Popper: \"arrow\" element must be an HTMLElement (not an SVGElement).', 'To use an SVG arrow, wrap it in an HTMLElement that will be used as', 'the arrow.'].join(' '));\n }\n }\n\n if (!contains(state.elements.popper, arrowElement)) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(['Popper: \"arrow\" modifier\\'s `element` must be a child of the popper', 'element.'].join(' '));\n }\n\n return;\n }\n\n state.elements.arrow = arrowElement;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'arrow',\n enabled: true,\n phase: 'main',\n fn: arrow,\n effect: effect,\n requires: ['popperOffsets'],\n requiresIfExists: ['preventOverflow']\n};","/* smoothscroll v0.4.4 - 2019 - Dustan Kasten, Jeremias Menichelli - MIT License */\n(function () {\n 'use strict';\n\n // polyfill\n function polyfill() {\n // aliases\n var w = window;\n var d = document;\n\n // return if scroll behavior is supported and polyfill is not forced\n if (\n 'scrollBehavior' in d.documentElement.style &&\n w.__forceSmoothScrollPolyfill__ !== true\n ) {\n return;\n }\n\n // globals\n var Element = w.HTMLElement || w.Element;\n var SCROLL_TIME = 468;\n\n // object gathering original scroll methods\n var original = {\n scroll: w.scroll || w.scrollTo,\n scrollBy: w.scrollBy,\n elementScroll: Element.prototype.scroll || scrollElement,\n scrollIntoView: Element.prototype.scrollIntoView\n };\n\n // define timing method\n var now =\n w.performance && w.performance.now\n ? w.performance.now.bind(w.performance)\n : Date.now;\n\n /**\n * indicates if a the current browser is made by Microsoft\n * @method isMicrosoftBrowser\n * @param {String} userAgent\n * @returns {Boolean}\n */\n function isMicrosoftBrowser(userAgent) {\n var userAgentPatterns = ['MSIE ', 'Trident/', 'Edge/'];\n\n return new RegExp(userAgentPatterns.join('|')).test(userAgent);\n }\n\n /*\n * IE has rounding bug rounding down clientHeight and clientWidth and\n * rounding up scrollHeight and scrollWidth causing false positives\n * on hasScrollableSpace\n */\n var ROUNDING_TOLERANCE = isMicrosoftBrowser(w.navigator.userAgent) ? 1 : 0;\n\n /**\n * changes scroll position inside an element\n * @method scrollElement\n * @param {Number} x\n * @param {Number} y\n * @returns {undefined}\n */\n function scrollElement(x, y) {\n this.scrollLeft = x;\n this.scrollTop = y;\n }\n\n /**\n * returns result of applying ease math function to a number\n * @method ease\n * @param {Number} k\n * @returns {Number}\n */\n function ease(k) {\n return 0.5 * (1 - Math.cos(Math.PI * k));\n }\n\n /**\n * indicates if a smooth behavior should be applied\n * @method shouldBailOut\n * @param {Number|Object} firstArg\n * @returns {Boolean}\n */\n function shouldBailOut(firstArg) {\n if (\n firstArg === null ||\n typeof firstArg !== 'object' ||\n firstArg.behavior === undefined ||\n firstArg.behavior === 'auto' ||\n firstArg.behavior === 'instant'\n ) {\n // first argument is not an object/null\n // or behavior is auto, instant or undefined\n return true;\n }\n\n if (typeof firstArg === 'object' && firstArg.behavior === 'smooth') {\n // first argument is an object and behavior is smooth\n return false;\n }\n\n // throw error when behavior is not supported\n throw new TypeError(\n 'behavior member of ScrollOptions ' +\n firstArg.behavior +\n ' is not a valid value for enumeration ScrollBehavior.'\n );\n }\n\n /**\n * indicates if an element has scrollable space in the provided axis\n * @method hasScrollableSpace\n * @param {Node} el\n * @param {String} axis\n * @returns {Boolean}\n */\n function hasScrollableSpace(el, axis) {\n if (axis === 'Y') {\n return el.clientHeight + ROUNDING_TOLERANCE < el.scrollHeight;\n }\n\n if (axis === 'X') {\n return el.clientWidth + ROUNDING_TOLERANCE < el.scrollWidth;\n }\n }\n\n /**\n * indicates if an element has a scrollable overflow property in the axis\n * @method canOverflow\n * @param {Node} el\n * @param {String} axis\n * @returns {Boolean}\n */\n function canOverflow(el, axis) {\n var overflowValue = w.getComputedStyle(el, null)['overflow' + axis];\n\n return overflowValue === 'auto' || overflowValue === 'scroll';\n }\n\n /**\n * indicates if an element can be scrolled in either axis\n * @method isScrollable\n * @param {Node} el\n * @param {String} axis\n * @returns {Boolean}\n */\n function isScrollable(el) {\n var isScrollableY = hasScrollableSpace(el, 'Y') && canOverflow(el, 'Y');\n var isScrollableX = hasScrollableSpace(el, 'X') && canOverflow(el, 'X');\n\n return isScrollableY || isScrollableX;\n }\n\n /**\n * finds scrollable parent of an element\n * @method findScrollableParent\n * @param {Node} el\n * @returns {Node} el\n */\n function findScrollableParent(el) {\n while (el !== d.body && isScrollable(el) === false) {\n el = el.parentNode || el.host;\n }\n\n return el;\n }\n\n /**\n * self invoked function that, given a context, steps through scrolling\n * @method step\n * @param {Object} context\n * @returns {undefined}\n */\n function step(context) {\n var time = now();\n var value;\n var currentX;\n var currentY;\n var elapsed = (time - context.startTime) / SCROLL_TIME;\n\n // avoid elapsed times higher than one\n elapsed = elapsed > 1 ? 1 : elapsed;\n\n // apply easing to elapsed time\n value = ease(elapsed);\n\n currentX = context.startX + (context.x - context.startX) * value;\n currentY = context.startY + (context.y - context.startY) * value;\n\n context.method.call(context.scrollable, currentX, currentY);\n\n // scroll more if we have not reached our destination\n if (currentX !== context.x || currentY !== context.y) {\n w.requestAnimationFrame(step.bind(w, context));\n }\n }\n\n /**\n * scrolls window or element with a smooth behavior\n * @method smoothScroll\n * @param {Object|Node} el\n * @param {Number} x\n * @param {Number} y\n * @returns {undefined}\n */\n function smoothScroll(el, x, y) {\n var scrollable;\n var startX;\n var startY;\n var method;\n var startTime = now();\n\n // define scroll context\n if (el === d.body) {\n scrollable = w;\n startX = w.scrollX || w.pageXOffset;\n startY = w.scrollY || w.pageYOffset;\n method = original.scroll;\n } else {\n scrollable = el;\n startX = el.scrollLeft;\n startY = el.scrollTop;\n method = scrollElement;\n }\n\n // scroll looping over a frame\n step({\n scrollable: scrollable,\n method: method,\n startTime: startTime,\n startX: startX,\n startY: startY,\n x: x,\n y: y\n });\n }\n\n // ORIGINAL METHODS OVERRIDES\n // w.scroll and w.scrollTo\n w.scroll = w.scrollTo = function() {\n // avoid action when no arguments are passed\n if (arguments[0] === undefined) {\n return;\n }\n\n // avoid smooth behavior if not required\n if (shouldBailOut(arguments[0]) === true) {\n original.scroll.call(\n w,\n arguments[0].left !== undefined\n ? arguments[0].left\n : typeof arguments[0] !== 'object'\n ? arguments[0]\n : w.scrollX || w.pageXOffset,\n // use top prop, second argument if present or fallback to scrollY\n arguments[0].top !== undefined\n ? arguments[0].top\n : arguments[1] !== undefined\n ? arguments[1]\n : w.scrollY || w.pageYOffset\n );\n\n return;\n }\n\n // LET THE SMOOTHNESS BEGIN!\n smoothScroll.call(\n w,\n d.body,\n arguments[0].left !== undefined\n ? ~~arguments[0].left\n : w.scrollX || w.pageXOffset,\n arguments[0].top !== undefined\n ? ~~arguments[0].top\n : w.scrollY || w.pageYOffset\n );\n };\n\n // w.scrollBy\n w.scrollBy = function() {\n // avoid action when no arguments are passed\n if (arguments[0] === undefined) {\n return;\n }\n\n // avoid smooth behavior if not required\n if (shouldBailOut(arguments[0])) {\n original.scrollBy.call(\n w,\n arguments[0].left !== undefined\n ? arguments[0].left\n : typeof arguments[0] !== 'object' ? arguments[0] : 0,\n arguments[0].top !== undefined\n ? arguments[0].top\n : arguments[1] !== undefined ? arguments[1] : 0\n );\n\n return;\n }\n\n // LET THE SMOOTHNESS BEGIN!\n smoothScroll.call(\n w,\n d.body,\n ~~arguments[0].left + (w.scrollX || w.pageXOffset),\n ~~arguments[0].top + (w.scrollY || w.pageYOffset)\n );\n };\n\n // Element.prototype.scroll and Element.prototype.scrollTo\n Element.prototype.scroll = Element.prototype.scrollTo = function() {\n // avoid action when no arguments are passed\n if (arguments[0] === undefined) {\n return;\n }\n\n // avoid smooth behavior if not required\n if (shouldBailOut(arguments[0]) === true) {\n // if one number is passed, throw error to match Firefox implementation\n if (typeof arguments[0] === 'number' && arguments[1] === undefined) {\n throw new SyntaxError('Value could not be converted');\n }\n\n original.elementScroll.call(\n this,\n // use left prop, first number argument or fallback to scrollLeft\n arguments[0].left !== undefined\n ? ~~arguments[0].left\n : typeof arguments[0] !== 'object' ? ~~arguments[0] : this.scrollLeft,\n // use top prop, second argument or fallback to scrollTop\n arguments[0].top !== undefined\n ? ~~arguments[0].top\n : arguments[1] !== undefined ? ~~arguments[1] : this.scrollTop\n );\n\n return;\n }\n\n var left = arguments[0].left;\n var top = arguments[0].top;\n\n // LET THE SMOOTHNESS BEGIN!\n smoothScroll.call(\n this,\n this,\n typeof left === 'undefined' ? this.scrollLeft : ~~left,\n typeof top === 'undefined' ? this.scrollTop : ~~top\n );\n };\n\n // Element.prototype.scrollBy\n Element.prototype.scrollBy = function() {\n // avoid action when no arguments are passed\n if (arguments[0] === undefined) {\n return;\n }\n\n // avoid smooth behavior if not required\n if (shouldBailOut(arguments[0]) === true) {\n original.elementScroll.call(\n this,\n arguments[0].left !== undefined\n ? ~~arguments[0].left + this.scrollLeft\n : ~~arguments[0] + this.scrollLeft,\n arguments[0].top !== undefined\n ? ~~arguments[0].top + this.scrollTop\n : ~~arguments[1] + this.scrollTop\n );\n\n return;\n }\n\n this.scroll({\n left: ~~arguments[0].left + this.scrollLeft,\n top: ~~arguments[0].top + this.scrollTop,\n behavior: arguments[0].behavior\n });\n };\n\n // Element.prototype.scrollIntoView\n Element.prototype.scrollIntoView = function() {\n // avoid smooth behavior if not required\n if (shouldBailOut(arguments[0]) === true) {\n original.scrollIntoView.call(\n this,\n arguments[0] === undefined ? true : arguments[0]\n );\n\n return;\n }\n\n // LET THE SMOOTHNESS BEGIN!\n var scrollableParent = findScrollableParent(this);\n var parentRects = scrollableParent.getBoundingClientRect();\n var clientRects = this.getBoundingClientRect();\n\n if (scrollableParent !== d.body) {\n // reveal element inside parent\n smoothScroll.call(\n this,\n scrollableParent,\n scrollableParent.scrollLeft + clientRects.left - parentRects.left,\n scrollableParent.scrollTop + clientRects.top - parentRects.top\n );\n\n // reveal parent in viewport unless is fixed\n if (w.getComputedStyle(scrollableParent).position !== 'fixed') {\n w.scrollBy({\n left: parentRects.left,\n top: parentRects.top,\n behavior: 'smooth'\n });\n }\n } else {\n // reveal element in viewport\n w.scrollBy({\n left: clientRects.left,\n top: clientRects.top,\n behavior: 'smooth'\n });\n }\n };\n }\n\n if (typeof exports === 'object' && typeof module !== 'undefined') {\n // commonjs\n module.exports = { polyfill: polyfill };\n } else {\n // global\n polyfill();\n }\n\n}());\n","import merge from 'deepmerge';\nimport { Evented } from './evented.js';\nimport autoBind from './utils/auto-bind.js';\nimport {\n isElement,\n isHTMLElement,\n isFunction,\n isUndefined\n} from './utils/type-check.js';\nimport { bindAdvance } from './utils/bind.js';\nimport {\n setupTooltip,\n parseAttachTo,\n normalizePrefix,\n uuid\n} from './utils/general.js';\nimport ShepherdElement from './components/shepherd-element.svelte';\n\n// Polyfills\nimport smoothscroll from 'smoothscroll-polyfill';\nsmoothscroll.polyfill();\n\n/**\n * A class representing steps to be added to a tour.\n * @extends {Evented}\n */\nexport class Step extends Evented {\n /**\n * Create a step\n * @param {Tour} tour The tour for the step\n * @param {object} options The options for the step\n * @param {boolean} options.arrow Whether to display the arrow for the tooltip or not. Defaults to `true`.\n * @param {object} options.attachTo The element the step should be attached to on the page.\n * An object with properties `element` and `on`.\n *\n * ```js\n * const step = new Step(tour, {\n * attachTo: { element: '.some .selector-path', on: 'left' },\n * ...moreOptions\n * });\n * ```\n *\n * If you don’t specify an `attachTo` the element will appear in the middle of the screen. The same will happen if your `attachTo.element` callback returns `null`, `undefined`, or a selector that does not exist in the DOM.\n * If you omit the `on` portion of `attachTo`, the element will still be highlighted, but the tooltip will appear\n * in the middle of the screen, without an arrow pointing to the target.\n * If the element to highlight does not yet exist while instantiating tour steps, you may use lazy evaluation by supplying a function to `attachTo.element`. The function will be called in the `before-show` phase.\n * @param {string|HTMLElement|function} options.attachTo.element An element selector string, DOM element, or a function (returning a selector, a DOM element, `null` or `undefined`).\n * @param {string} options.attachTo.on The optional direction to place the Popper tooltip relative to the element.\n * - Possible string values: 'auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end'\n * @param {Object} options.advanceOn An action on the page which should advance shepherd to the next step.\n * It should be an object with a string `selector` and an `event` name\n * ```js\n * const step = new Step(tour, {\n * advanceOn: { selector: '.some .selector-path', event: 'click' },\n * ...moreOptions\n * });\n * ```\n * `event` doesn’t have to be an event inside the tour, it can be any event fired on any element on the page.\n * You can also always manually advance the Tour by calling `myTour.next()`.\n * @param {function} options.beforeShowPromise A function that returns a promise.\n * When the promise resolves, the rest of the `show` code for the step will execute.\n * @param {Object[]} options.buttons An array of buttons to add to the step. These will be rendered in a\n * footer below the main body text.\n * @param {function} options.buttons.button.action A function executed when the button is clicked on.\n * It is automatically bound to the `tour` the step is associated with, so things like `this.next` will\n * work inside the action.\n * You can use action to skip steps or navigate to specific steps, with something like:\n * ```js\n * action() {\n * return this.show('some_step_name');\n * }\n * ```\n * @param {string} options.buttons.button.classes Extra classes to apply to the ``\n * @param {boolean} options.buttons.button.disabled Should the button be disabled?\n * @param {string} options.buttons.button.label The aria-label text of the button\n * @param {boolean} options.buttons.button.secondary If true, a shepherd-button-secondary class is applied to the button\n * @param {string} options.buttons.button.text The HTML text of the button\n * @param {boolean} options.canClickTarget A boolean, that when set to false, will set `pointer-events: none` on the target\n * @param {object} options.cancelIcon Options for the cancel icon\n * @param {boolean} options.cancelIcon.enabled Should a cancel “✕” be shown in the header of the step?\n * @param {string} options.cancelIcon.label The label to add for `aria-label`\n * @param {string} options.classes A string of extra classes to add to the step's content element.\n * @param {string} options.highlightClass An extra class to apply to the `attachTo` element when it is\n * highlighted (that is, when its step is active). You can then target that selector in your CSS.\n * @param {string} options.id The string to use as the `id` for the step.\n * @param {number} options.modalOverlayOpeningPadding An amount of padding to add around the modal overlay opening\n * @param {number} options.modalOverlayOpeningRadius An amount of border radius to add around the modal overlay opening\n * @param {object} options.popperOptions Extra options to pass to Popper\n * @param {boolean|Object} options.scrollTo Should the element be scrolled to when this step is shown? If true, uses the default `scrollIntoView`,\n * if an object, passes that object as the params to `scrollIntoView` i.e. `{behavior: 'smooth', block: 'center'}`\n * @param {function} options.scrollToHandler A function that lets you override the default scrollTo behavior and\n * define a custom action to do the scrolling, and possibly other logic.\n * @param {function} options.showOn A function that, when it returns `true`, will show the step.\n * If it returns false, the step will be skipped.\n * @param {string} options.text The text in the body of the step. It can be one of three types:\n * ```\n * - HTML string\n * - `HTMLElement` object\n * - `Function` to be executed when the step is built. It must return one the two options above.\n * ```\n * @param {string} options.title The step's title. It becomes an `h3` at the top of the step. It can be one of two types:\n * ```\n * - HTML string\n * - `Function` to be executed when the step is built. It must return HTML string.\n * ```\n * @param {object} options.when You can define `show`, `hide`, etc events inside `when`. For example:\n * ```js\n * when: {\n * show: function() {\n * window.scrollTo(0, 0);\n * }\n * }\n * ```\n * @return {Step} The newly created Step instance\n */\n constructor(tour, options = {}) {\n super(tour, options);\n this.tour = tour;\n this.classPrefix = this.tour.options\n ? normalizePrefix(this.tour.options.classPrefix)\n : '';\n this.styles = tour.styles;\n\n /**\n * Resolved attachTo options. Due to lazy evaluation, we only resolve the options during `before-show` phase.\n * Do not use this directly, use the _getResolvedAttachToOptions method instead.\n * @type {null|{}|{element, to}}\n * @private\n */\n this._resolvedAttachTo = null;\n\n autoBind(this);\n\n this._setOptions(options);\n\n return this;\n }\n\n /**\n * Cancel the tour\n * Triggers the `cancel` event\n */\n cancel() {\n this.tour.cancel();\n this.trigger('cancel');\n }\n\n /**\n * Complete the tour\n * Triggers the `complete` event\n */\n complete() {\n this.tour.complete();\n this.trigger('complete');\n }\n\n /**\n * Remove the step, delete the step's element, and destroy the Popper instance for the step.\n * Triggers `destroy` event\n */\n destroy() {\n if (this.tooltip) {\n this.tooltip.destroy();\n this.tooltip = null;\n }\n\n if (isHTMLElement(this.el) && this.el.parentNode) {\n this.el.parentNode.removeChild(this.el);\n this.el = null;\n }\n\n this._updateStepTargetOnHide();\n\n this.trigger('destroy');\n }\n\n /**\n * Returns the tour for the step\n * @return {Tour} The tour instance\n */\n getTour() {\n return this.tour;\n }\n\n /**\n * Hide the step\n */\n hide() {\n this.tour.modal.hide();\n\n this.trigger('before-hide');\n\n if (this.el) {\n this.el.hidden = true;\n }\n\n this._updateStepTargetOnHide();\n\n this.trigger('hide');\n }\n\n /**\n * Resolves attachTo options.\n * @returns {{}|{element, on}}\n * @private\n */\n _resolveAttachToOptions() {\n this._resolvedAttachTo = parseAttachTo(this);\n return this._resolvedAttachTo;\n }\n\n /**\n * A selector for resolved attachTo options.\n * @returns {{}|{element, on}}\n * @private\n */\n _getResolvedAttachToOptions() {\n if (this._resolvedAttachTo === null) {\n return this._resolveAttachToOptions();\n }\n\n return this._resolvedAttachTo;\n }\n\n /**\n * Check if the step is open and visible\n * @return {boolean} True if the step is open and visible\n */\n isOpen() {\n return Boolean(this.el && !this.el.hidden);\n }\n\n /**\n * Wraps `_show` and ensures `beforeShowPromise` resolves before calling show\n * @return {*|Promise}\n */\n show() {\n if (isFunction(this.options.beforeShowPromise)) {\n const beforeShowPromise = this.options.beforeShowPromise();\n if (!isUndefined(beforeShowPromise)) {\n return beforeShowPromise.then(() => this._show());\n }\n }\n this._show();\n }\n\n /**\n * Updates the options of the step.\n *\n * @param {Object} options The options for the step\n */\n updateStepOptions(options) {\n Object.assign(this.options, options);\n\n if (this.shepherdElementComponent) {\n this.shepherdElementComponent.$set({ step: this });\n }\n }\n\n /**\n * Returns the element for the step\n * @return {HTMLElement|null|undefined} The element instance. undefined if it has never been shown, null if it has been destroyed\n */\n getElement() {\n return this.el;\n }\n\n /**\n * Returns the target for the step\n * @return {HTMLElement|null|undefined} The element instance. undefined if it has never been shown, null if query string has not been found\n */\n getTarget() {\n return this.target;\n }\n\n /**\n * Creates Shepherd element for step based on options\n *\n * @return {Element} The DOM element for the step tooltip\n * @private\n */\n _createTooltipContent() {\n const descriptionId = `${this.id}-description`;\n const labelId = `${this.id}-label`;\n\n this.shepherdElementComponent = new ShepherdElement({\n target: this.tour.options.stepsContainer || document.body,\n props: {\n classPrefix: this.classPrefix,\n descriptionId,\n labelId,\n step: this,\n styles: this.styles\n }\n });\n\n return this.shepherdElementComponent.getElement();\n }\n\n /**\n * If a custom scrollToHandler is defined, call that, otherwise do the generic\n * scrollIntoView call.\n *\n * @param {boolean|Object} scrollToOptions If true, uses the default `scrollIntoView`,\n * if an object, passes that object as the params to `scrollIntoView` i.e. `{ behavior: 'smooth', block: 'center' }`\n * @private\n */\n _scrollTo(scrollToOptions) {\n const { element } = this._getResolvedAttachToOptions();\n\n if (isFunction(this.options.scrollToHandler)) {\n this.options.scrollToHandler(element);\n } else if (\n isElement(element) &&\n typeof element.scrollIntoView === 'function'\n ) {\n element.scrollIntoView(scrollToOptions);\n }\n }\n\n /**\n * _getClassOptions gets all possible classes for the step\n * @param {Object} stepOptions The step specific options\n * @returns {String} unique string from array of classes\n * @private\n */\n _getClassOptions(stepOptions) {\n const defaultStepOptions =\n this.tour && this.tour.options && this.tour.options.defaultStepOptions;\n const stepClasses = stepOptions.classes ? stepOptions.classes : '';\n const defaultStepOptionsClasses =\n defaultStepOptions && defaultStepOptions.classes\n ? defaultStepOptions.classes\n : '';\n const allClasses = [\n ...stepClasses.split(' '),\n ...defaultStepOptionsClasses.split(' ')\n ];\n const uniqClasses = new Set(allClasses);\n\n return Array.from(uniqClasses).join(' ').trim();\n }\n\n /**\n * Sets the options for the step, maps `when` to events, sets up buttons\n * @param {Object} options The options for the step\n * @private\n */\n _setOptions(options = {}) {\n let tourOptions =\n this.tour && this.tour.options && this.tour.options.defaultStepOptions;\n\n tourOptions = merge({}, tourOptions || {});\n\n this.options = Object.assign(\n {\n arrow: true\n },\n tourOptions,\n options\n );\n\n const { when } = this.options;\n\n this.options.classes = this._getClassOptions(options);\n\n this.destroy();\n this.id = this.options.id || `step-${uuid()}`;\n\n if (when) {\n Object.keys(when).forEach((event) => {\n this.on(event, when[event], this);\n });\n }\n }\n\n /**\n * Create the element and set up the Popper instance\n * @private\n */\n _setupElements() {\n if (!isUndefined(this.el)) {\n this.destroy();\n }\n\n this.el = this._createTooltipContent();\n\n if (this.options.advanceOn) {\n bindAdvance(this);\n }\n setupTooltip(this);\n }\n\n /**\n * Triggers `before-show`, generates the tooltip DOM content,\n * sets up a Popper instance for the tooltip, then triggers `show`.\n * @private\n */\n _show() {\n this.trigger('before-show');\n\n // Force resolve to make sure the options are updated on subsequent shows.\n this._resolveAttachToOptions();\n this._setupElements();\n\n if (!this.tour.modal) {\n this.tour._setupModal();\n }\n\n this.tour.modal.setupForStep(this);\n this._styleTargetElementForStep(this);\n this.el.hidden = false;\n\n // start scrolling to target before showing the step\n if (this.options.scrollTo) {\n setTimeout(() => {\n this._scrollTo(this.options.scrollTo);\n });\n }\n\n this.el.hidden = false;\n\n const content = this.shepherdElementComponent.getElement();\n const target = this.target || document.body;\n target.classList.add(`${this.classPrefix}shepherd-enabled`);\n target.classList.add(`${this.classPrefix}shepherd-target`);\n content.classList.add('shepherd-enabled');\n\n this.trigger('show');\n }\n\n /**\n * Modulates the styles of the passed step's target element, based on the step's options and\n * the tour's `modal` option, to visually emphasize the element\n *\n * @param step The step object that attaches to the element\n * @private\n */\n _styleTargetElementForStep(step) {\n const targetElement = step.target;\n\n if (!targetElement) {\n return;\n }\n\n if (step.options.highlightClass) {\n targetElement.classList.add(step.options.highlightClass);\n }\n\n targetElement.classList.remove('shepherd-target-click-disabled');\n\n if (step.options.canClickTarget === false) {\n targetElement.classList.add('shepherd-target-click-disabled');\n }\n }\n\n /**\n * When a step is hidden, remove the highlightClass and 'shepherd-enabled'\n * and 'shepherd-target' classes\n * @private\n */\n _updateStepTargetOnHide() {\n const target = this.target || document.body;\n\n if (this.options.highlightClass) {\n target.classList.remove(this.options.highlightClass);\n }\n\n target.classList.remove(\n 'shepherd-target-click-disabled',\n `${this.classPrefix}shepherd-enabled`,\n `${this.classPrefix}shepherd-target`\n );\n }\n}\n","import { Evented } from './evented.js';\nimport { Step } from './step.js';\nimport autoBind from './utils/auto-bind.js';\nimport {\n isHTMLElement,\n isFunction,\n isString,\n isUndefined\n} from './utils/type-check.js';\nimport { cleanupSteps } from './utils/cleanup.js';\nimport { normalizePrefix, uuid } from './utils/general.js';\nimport ShepherdModal from './components/shepherd-modal.svelte';\n\nconst Shepherd = new Evented();\n\n/**\n * Class representing the site tour\n * @extends {Evented}\n */\nexport class Tour extends Evented {\n /**\n * @param {Object} options The options for the tour\n * @param {boolean} options.confirmCancel If true, will issue a `window.confirm` before cancelling\n * @param {string} options.confirmCancelMessage The message to display in the confirm dialog\n * @param {string} options.classPrefix The prefix to add to the `shepherd-enabled` and `shepherd-target` class names as well as the `data-shepherd-step-id`.\n * @param {Object} options.defaultStepOptions Default options for Steps ({@link Step#constructor}), created through `addStep`\n * @param {boolean} options.exitOnEsc Exiting the tour with the escape key will be enabled unless this is explicitly\n * set to false.\n * @param {boolean} options.keyboardNavigation Navigating the tour via left and right arrow keys will be enabled\n * unless this is explicitly set to false.\n * @param {HTMLElement} options.stepsContainer An optional container element for the steps.\n * If not set, the steps will be appended to `document.body`.\n * @param {HTMLElement} options.modalContainer An optional container element for the modal.\n * If not set, the modal will be appended to `document.body`.\n * @param {object[] | Step[]} options.steps An array of step options objects or Step instances to initialize the tour with\n * @param {string} options.tourName An optional \"name\" for the tour. This will be appended to the the tour's\n * dynamically generated `id` property -- which is also set on the `body` element as the `data-shepherd-active-tour` attribute\n * whenever the tour becomes active.\n * @param {boolean} options.useModalOverlay Whether or not steps should be placed above a darkened\n * modal overlay. If true, the overlay will create an opening around the target element so that it\n * can remain interactive\n * @returns {Tour}\n */\n constructor(options = {}) {\n super(options);\n\n autoBind(this);\n\n const defaultTourOptions = {\n exitOnEsc: true,\n keyboardNavigation: true\n };\n\n this.options = Object.assign({}, defaultTourOptions, options);\n this.classPrefix = normalizePrefix(this.options.classPrefix);\n this.steps = [];\n this.addSteps(this.options.steps);\n\n // Pass these events onto the global Shepherd object\n const events = [\n 'active',\n 'cancel',\n 'complete',\n 'inactive',\n 'show',\n 'start'\n ];\n events.map((event) => {\n ((e) => {\n this.on(e, (opts) => {\n opts = opts || {};\n opts.tour = this;\n Shepherd.trigger(e, opts);\n });\n })(event);\n });\n\n this._setTourID();\n\n return this;\n }\n\n /**\n * Adds a new step to the tour\n * @param {Object|Step} options An object containing step options or a Step instance\n * @param {number} index The optional index to insert the step at. If undefined, the step\n * is added to the end of the array.\n * @return {Step} The newly added step\n */\n addStep(options, index) {\n let step = options;\n\n if (!(step instanceof Step)) {\n step = new Step(this, step);\n } else {\n step.tour = this;\n }\n\n if (!isUndefined(index)) {\n this.steps.splice(index, 0, step);\n } else {\n this.steps.push(step);\n }\n\n return step;\n }\n\n /**\n * Add multiple steps to the tour\n * @param {Array | Array} steps The steps to add to the tour\n */\n addSteps(steps) {\n if (Array.isArray(steps)) {\n steps.forEach((step) => {\n this.addStep(step);\n });\n }\n\n return this;\n }\n\n /**\n * Go to the previous step in the tour\n */\n back() {\n const index = this.steps.indexOf(this.currentStep);\n this.show(index - 1, false);\n }\n\n /**\n * Calls _done() triggering the 'cancel' event\n * If `confirmCancel` is true, will show a window.confirm before cancelling\n */\n cancel() {\n if (this.options.confirmCancel) {\n const cancelMessage =\n this.options.confirmCancelMessage ||\n 'Are you sure you want to stop the tour?';\n const stopTour = window.confirm(cancelMessage);\n if (stopTour) {\n this._done('cancel');\n }\n } else {\n this._done('cancel');\n }\n }\n\n /**\n * Calls _done() triggering the `complete` event\n */\n complete() {\n this._done('complete');\n }\n\n /**\n * Gets the step from a given id\n * @param {Number|String} id The id of the step to retrieve\n * @return {Step} The step corresponding to the `id`\n */\n getById(id) {\n return this.steps.find((step) => {\n return step.id === id;\n });\n }\n\n /**\n * Gets the current step\n * @returns {Step|null}\n */\n getCurrentStep() {\n return this.currentStep;\n }\n\n /**\n * Hide the current step\n */\n hide() {\n const currentStep = this.getCurrentStep();\n\n if (currentStep) {\n return currentStep.hide();\n }\n }\n\n /**\n * Check if the tour is active\n * @return {boolean}\n */\n isActive() {\n return Shepherd.activeTour === this;\n }\n\n /**\n * Go to the next step in the tour\n * If we are at the end, call `complete`\n */\n next() {\n const index = this.steps.indexOf(this.currentStep);\n\n if (index === this.steps.length - 1) {\n this.complete();\n } else {\n this.show(index + 1, true);\n }\n }\n\n /**\n * Removes the step from the tour\n * @param {String} name The id for the step to remove\n */\n removeStep(name) {\n const current = this.getCurrentStep();\n\n // Find the step, destroy it and remove it from this.steps\n this.steps.some((step, i) => {\n if (step.id === name) {\n if (step.isOpen()) {\n step.hide();\n }\n\n step.destroy();\n this.steps.splice(i, 1);\n\n return true;\n }\n });\n\n if (current && current.id === name) {\n this.currentStep = undefined;\n\n // If we have steps left, show the first one, otherwise just cancel the tour\n this.steps.length ? this.show(0) : this.cancel();\n }\n }\n\n /**\n * Show a specific step in the tour\n * @param {Number|String} key The key to look up the step by\n * @param {Boolean} forward True if we are going forward, false if backward\n */\n show(key = 0, forward = true) {\n const step = isString(key) ? this.getById(key) : this.steps[key];\n\n if (step) {\n this._updateStateBeforeShow();\n\n const shouldSkipStep =\n isFunction(step.options.showOn) && !step.options.showOn();\n\n // If `showOn` returns false, we want to skip the step, otherwise, show the step like normal\n if (shouldSkipStep) {\n this._skipStep(step, forward);\n } else {\n this.trigger('show', {\n step,\n previous: this.currentStep\n });\n\n this.currentStep = step;\n step.show();\n }\n }\n }\n\n /**\n * Start the tour\n */\n start() {\n this.trigger('start');\n\n // Save the focused element before the tour opens\n this.focusedElBeforeOpen = document.activeElement;\n\n this.currentStep = null;\n\n this._setupModal();\n\n this._setupActiveTour();\n this.next();\n }\n\n /**\n * Called whenever the tour is cancelled or completed, basically anytime we exit the tour\n * @param {String} event The event name to trigger\n * @private\n */\n _done(event) {\n const index = this.steps.indexOf(this.currentStep);\n if (Array.isArray(this.steps)) {\n this.steps.forEach((step) => step.destroy());\n }\n\n cleanupSteps(this);\n\n this.trigger(event, { index });\n\n Shepherd.activeTour = null;\n this.trigger('inactive', { tour: this });\n\n if (this.modal) {\n this.modal.hide();\n }\n\n if (event === 'cancel' || event === 'complete') {\n if (this.modal) {\n const modalContainer = document.querySelector(\n '.shepherd-modal-overlay-container'\n );\n\n if (modalContainer) {\n modalContainer.remove();\n }\n }\n }\n\n // Focus the element that was focused before the tour started\n if (isHTMLElement(this.focusedElBeforeOpen)) {\n this.focusedElBeforeOpen.focus();\n }\n }\n\n /**\n * Make this tour \"active\"\n * @private\n */\n _setupActiveTour() {\n this.trigger('active', { tour: this });\n\n Shepherd.activeTour = this;\n }\n\n /**\n * _setupModal create the modal container and instance\n * @private\n */\n _setupModal() {\n this.modal = new ShepherdModal({\n target: this.options.modalContainer || document.body,\n props: {\n classPrefix: this.classPrefix,\n styles: this.styles\n }\n });\n }\n\n /**\n * Called when `showOn` evaluates to false, to skip the step or complete the tour if it's the last step\n * @param {Step} step The step to skip\n * @param {Boolean} forward True if we are going forward, false if backward\n * @private\n */\n _skipStep(step, forward) {\n const index = this.steps.indexOf(step);\n\n if (index === this.steps.length - 1) {\n this.complete();\n } else {\n const nextIndex = forward ? index + 1 : index - 1;\n this.show(nextIndex, forward);\n }\n }\n\n /**\n * Before showing, hide the current step and if the tour is not\n * already active, call `this._setupActiveTour`.\n * @private\n */\n _updateStateBeforeShow() {\n if (this.currentStep) {\n this.currentStep.hide();\n }\n\n if (!this.isActive()) {\n this._setupActiveTour();\n }\n }\n\n /**\n * Sets this.id to `${tourName}--${uuid}`\n * @private\n */\n _setTourID() {\n const tourName = this.options.tourName || 'tour';\n\n this.id = `${tourName}--${uuid()}`;\n }\n}\n\nexport { Shepherd };\n","import { Step } from './step.js';\nimport { Shepherd, Tour } from './tour.js';\n\nObject.assign(Shepherd, { Tour, Step });\n\nexport default Shepherd;\n"],"names":["cloneUnlessOtherwiseSpecified","value","options","clone","isMergeableObject","deepmerge","Array","isArray","defaultArrayMerge","target","source","concat","map","element","getEnumerableOwnPropertySymbols","Object","getOwnPropertySymbols","filter","symbol","propertyIsEnumerable","getKeys","keys","propertyIsOnObject","object","property","_","mergeObject","destination","forEach","key","hasOwnProperty","call","customMerge","getMergeFunction","arrayMerge","sourceIsArray","targetIsArray","isFunction","isString","autoBind","self","getOwnPropertyNames","constructor","i","length","val","bind","_setupAdvanceOnHandler","selector","step","event","isOpen","targetIsEl","currentTarget","el","undefined","isUndefined","matches","tour","next","bindAdvance","advanceOn","handler","document","querySelector","e","addEventListener","on","removeEventListener","body","console","error","getNodeName","toLowerCase","nodeName","getWindow","node","window","toString","ownerDocument","defaultView","isElement","OwnElement","Element","isHTMLElement","HTMLElement","isShadowRoot","ShadowRoot","getBasePlacement","placement","split","getBoundingClientRect","includeScale","rect","scaleX","scaleY","offsetHeight","offsetWidth","round","width","height","top","right","bottom","left","x","y","getLayoutRect","clientRect","Math","abs","offsetLeft","offsetTop","contains","parent","child","rootNode","getRootNode","isSameNode","parentNode","host","getComputedStyle","getDocumentElement","documentElement","getParentNode","assignedSlot","getTrueOffsetParent","position","offsetParent","getOffsetParent","indexOf","isFirefox","navigator","userAgent","getContainingBlock","currentNode","css","transform","perspective","contain","willChange","getMainAxisFromPlacement","mergePaddingObject","paddingObject","assign","expandToHashMap","reduce","hashMap","getVariation","mapToStyles","_ref2","_Object$assign2","popper","popperRect","variation","offsets","gpuAcceleration","adaptive","roundOffsets","isFixed","_offsets$x","_offsets$y","_ref3","hasX","hasY","sideX","sideY","win","heightProp","widthProp","end","visualViewport","commonStyles","unsetSides","roundOffsetsByDPR","dpr","devicePixelRatio","_ref4","_Object$assign","getOppositePlacement","replace","matched","hash","getOppositeVariationPlacement","getWindowScroll","scrollLeft","pageXOffset","scrollTop","pageYOffset","getWindowScrollBarX","isScrollParent","_getComputedStyle","test","overflow","overflowY","overflowX","getScrollParent","listScrollParents","list","_element$ownerDocumen","scrollParent","isBody","updatedList","rectToClientRect","getClientRectFromMixedType","clippingParent","viewport","html","clientWidth","clientHeight","clientTop","clientLeft","winScroll","max","scrollWidth","scrollHeight","direction","getClippingParents","clippingParents","clipperElement","canEscapeClipping","getClippingRect","boundary","rootBoundary","mainClippingParents","clippingRect","accRect","min","computeOffsets","_ref","reference","basePlacement","commonX","commonY","mainAxis","len","start","detectOverflow","state","_options","_options$placement","_options$boundary","_options$rootBoundary","_options$elementConte","elementContext","_options$altBoundary","altBoundary","_options$padding","padding","basePlacements","rects","elements","clippingClientRect","contextElement","referenceClientRect","popperOffsets","strategy","popperClientRect","elementClientRect","overflowOffsets","offsetData","modifiersData","offset","multiply","axis","computeAutoPlacement","flipVariations","_options$allowedAutoP","allowedAutoPlacements","allPlacements","placements","variationPlacements","allowedPlacements","overflows","acc","sort","a","b","getExpandedFallbackPlacements","auto","oppositePlacement","getSideOffsets","preventedOffsets","isAnySideFullyClipped","some","side","getCompositeRect","elementOrVirtualElement","isOffsetParentAnElement","isElementScaled","offsetParentIsScaled","scroll","order","modifiers","modifier","visited","add","name","requires","requiresIfExists","dep","has","depModifier","get","result","push","Map","Set","set","orderModifiers","orderedModifiers","modifierPhases","phase","debounce","fn","pending","Promise","resolve","then","mergeByName","merged","current","existing","data","areValidElements","_len","arguments","args","_key","_getCenteredStylePopperModifier","attributes","style","removeAttribute","setAttribute","makeCenteredPopper","centeredStylePopperModifier","popperOptions","enabled","setTimeout","focus","from","normalizePrefix","prefix","charAt","uuid","d","Date","now","c","random","floor","r","getPopperOptions","attachToOptions","altAxis","tether","defaultStepOptions","_mergeModifiers","stepOptions","mergedPopperOptions","mod","filteredModifiers","names","noop","tar","src","k","run","is_function","thing","safe_not_equal","detach","removeChild","svg_element","createElementNS","listen","attr","attribute","getAttribute","set_attributes","descriptors","getOwnPropertyDescriptors","__proto__","cssText","toggle_class","toggle","classList","get_current_component","current_component","Error","add_render_callback","render_callbacks","flush","saved_component","flushidx","dirty_components","component","$$","fragment","update","before_update","fns","dirty","p","ctx","after_update","binding_callbacks","pop","seen_callbacks","callback","flush_callbacks","update_scheduled","clear","group_outros","outros","check_outros","transition_in","block","local","outroing","delete","transition_out","o","create_component","mount_component","anchor","customElement","on_mount","on_destroy","m","new_on_destroy","destroy_component","detaching","init","instance","create_fragment","not_equal","props","append_styles","parent_component","bound","create","on_disconnect","context","callbacks","skip_bound","root","ready","ret","resolved_promise","fill","hydrate","nodes","children","childNodes","l","intro","createElement","button","button_class_value","insertBefore","apply","getConfigOption","option","config","$$props","action","classes","disabled","label","secondary","text","$$invalidate","createTextNode","each_blocks","iterations","create_if_block","footer","buttons","button_aria_label_value","appendChild","span","cancelIcon","preventDefault","cancel","h3","labelId","title","innerHTML","$$value","create_if_block_1","header","div","descriptionId","show_if_2","show_if_1","show_if","arrow","attachTo","div_aria_describedby_value","to_null_out","accounted_for","$$scope","updates","n","levels","getClassesArray","className","classPrefix","firstFocusableElement","focusableElements","lastFocusableElement","dataStepId","hasCancelIcon","hasTitle","id","querySelectorAll","oldClasses","remove","newClasses","keyCode","KEY_TAB","shiftKey","activeElement","KEY_ESC","exitOnEsc","LEFT_ARROW","keyboardNavigation","back","RIGHT_ARROW","cleanupSteps","steps","canClickTarget","svg","path","_getScrollParent","parentElement","closeModalOpening","openingProperties","hide","modalIsVisible","_cleanupStepEventListeners","positionModal","modalOverlayOpeningPadding","modalOverlayOpeningRadius","targetElement","elementRect","scrollRect","scrollBottom","show","rafId","cancelAnimationFrame","_preventModalBodyTouch","passive","_styleForStep","rafLoop","requestAnimationFrame","pathDefinition","innerWidth","w","innerHeight","h","stopPropagation","setupForStep","useModalOverlay","isNonNullObject","stringValue","prototype","$$typeof","REACT_ELEMENT_TYPE","canUseSymbol","Symbol","for","all","deepmerge.all","array","prev","cjs","Evented","once","bindings","off","binding","index","splice","trigger","DEFAULT_OPTIONS","createPopper","popperGenerator","generatorOptions","_generatorOptions$def","_generatorOptions","defaultModifiers","_generatorOptions$def2","defaultOptions","runModifierEffects","_ref3$options","effect","cleanupFn","effectCleanupFns","noopFn","cleanupModifierEffects","styles","isDestroyed","setOptions","setOptionsAction","scrollParents","forceUpdate","_state$elements","reset","_state$orderedModifie","_state$orderedModifie2","destroy","onFirstUpdate","eventListeners","_options$scroll","_options$resize","resize","popperOffsets$1","computeStyles$1","computeStyles","_ref5","_options$gpuAccelerat","_options$adaptive","_options$roundOffsets","applyStyles$1","applyStyles","effect$2","initialStyles","margin","styleProperties","offset$1","_options$offset","distanceAndSkiddingToXY","invertDistance","skidding","distance","_data$state$placement","flip$1","flip","_skip","_options$mainAxis","checkMainAxis","_options$altAxis","checkAltAxis","specifiedFallbackPlacements","fallbackPlacements","_options$flipVariatio","preferredPlacement","isBasePlacement","referenceRect","checksMap","makeFallbackChecks","firstFittingPlacement","_basePlacement","isStartVariation","isVertical","mainVariationSide","altVariationSide","checks","every","check","_loop","_i","fittingPlacement","find","slice","preventOverflow$1","preventOverflow","_options$tether","_options$tetherOffset","tetherOffset","tetherOffsetValue","normalizedTetherOffsetValue","offsetModifierState","_offsetModifierState$","mainSide","altSide","additive","minLen","maxLen","arrowElement","arrowRect","arrowPaddingObject","arrowPaddingMin","arrowPaddingMax","arrowLen","mathMax","min$1","mathMin","minOffset","maxOffset","clientOffset","arrowOffsetParent","offsetModifierValue","tetherMax","preventedOffset","_offsetModifierState$2","_offset","_min","_max","isOriginSide","_offsetModifierValue","_tetherMin","_tetherMax","v","withinMaxClamp","within","_preventedOffset","arrow$1","_state$modifiersData$","minProp","maxProp","endDiff","startDiff","clientSize","center","centerOffset","effect$1","_options$element","hide$1","referenceOverflow","popperAltOverflow","referenceClippingOffsets","popperEscapeOffsets","isReferenceHidden","hasPopperEscaped","SvelteComponent","$destroy","$on","type","$set","$$set","module","polyfill","scrollElement","shouldBailOut","firstArg","behavior","TypeError","hasScrollableSpace","ROUNDING_TOLERANCE","canOverflow","overflowValue","isScrollable","isScrollableY","isScrollableX","elapsed","startTime","SCROLL_TIME","cos","PI","currentX","startX","currentY","startY","method","scrollable","smoothScroll","scrollX","scrollY","original","__forceSmoothScrollPolyfill__","scrollTo","scrollBy","elementScroll","scrollIntoView","performance","isMicrosoftBrowser","w.scrollTo","w.scrollBy","Element.prototype.scrollTo","SyntaxError","Element.prototype.scrollBy","Element.prototype.scrollIntoView","parentRects","scrollableParent","clientRects","smoothscroll","Step","_resolvedAttachTo","_setOptions","complete","tooltip","_updateStepTargetOnHide","getTour","modal","hidden","_resolveAttachToOptions","returnOpts","_getResolvedAttachToOptions","Boolean","beforeShowPromise","_show","updateStepOptions","shepherdElementComponent","getElement","getTarget","_createTooltipContent","ShepherdElement","stepsContainer","_scrollTo","scrollToOptions","scrollToHandler","_getClassOptions","defaultStepOptionsClasses","allClasses","uniqClasses","join","trim","tourOptions","merge","when","_setupElements","content","_setupModal","_styleTargetElementForStep","highlightClass","Shepherd","Tour","defaultTourOptions","addSteps","events","opts","_setTourID","addStep","currentStep","confirmCancel","_done","getById","getCurrentStep","isActive","activeTour","removeStep","forward","_updateStateBeforeShow","showOn","_skipStep","previous","focusedElBeforeOpen","_setupActiveTour","modalContainer","ShepherdModal"],"mappings":"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"} \ No newline at end of file diff --git a/bookwyrm/static/js/xhr_files.js b/bookwyrm/static/js/xhr_files.js new file mode 100644 index 0000000000..3ed78392de --- /dev/null +++ b/bookwyrm/static/js/xhr_files.js @@ -0,0 +1,93 @@ +/* exported XhrFiles */ + +let XhrFiles = new (class { + constructor() { + this.initEventListeners(); + } + + initEventListeners() { + window.addEventListener("dragover", (e) => { + e.preventDefault(); + }); + + window.addEventListener("drop", (e) => { + e.preventDefault(); + }); + + document + .querySelectorAll("[data-droppable-textfield]") + .forEach((t) => t.addEventListener("drop", this.dropFile.bind(this))); + } + + /** + * Upload file when dropped in element + * + * @param {Event} event + * @return {undefined} + */ + dropFile(event) { + event.preventDefault(); + + // Use DataTransferItemList interface to access the file(s) + [...event.dataTransfer.items].forEach((item) => { + // If dropped items aren't files, reject them + if (item.kind === "file") { + const file = item.getAsFile(); + + if (file.size > event.currentTarget.dataset.maxUpload) { + const errStr = interpolate(gettext("File exceeds maximum size: %s"), [ + event.currentTarget.dataset.maxUploadHuman, + ]); + + alert(errStr); + + return; + } + + this.uploadFile(file, event.target); + } + }); + } + + uploadFile(file, target) { + const self = this; + const xhr = new XMLHttpRequest(); + + xhr.addEventListener("load", function (e) { + if (this.status != 201) { + console.error(this.response); + + if (this.status == 422) { + alert(gettext("The provided file isn't a valid image")); + } + + return; + } + const responseJSON = JSON.parse(e.target.responseText); + + self.insertImageMarkdown(target, file, responseJSON); + }); + xhr.open("post", "/upload", true); + const fd = new FormData(); + + fd.append("filename", file.name); + fd.append("file", file); + fd.append("csrfmiddlewaretoken", this.csrfToken()); + + xhr.send(fd); + } + + csrfToken() { + return document.querySelector("[name=csrfmiddlewaretoken]").value; + } + + insertImageMarkdown(target, file, metadata) { + const imageMarkdown = `!image(${metadata.name})`; + const content = target.value; + const caret = target.selectionEnd; + const preCaret = content.slice(0, caret); + const postCaret = content.slice(caret); + + target.value = preCaret.concat(imageMarkdown, postCaret); + } +})(); diff --git a/bookwyrm/status.py b/bookwyrm/status.py index de7682ee72..31141b525a 100644 --- a/bookwyrm/status.py +++ b/bookwyrm/status.py @@ -1,4 +1,5 @@ -""" Handle user activity """ +"""Handle user activity""" + from django.db import transaction from bookwyrm import models diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index 3e1cf17bde..d643ec9570 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -1,4 +1,5 @@ -""" store recommended follows in redis """ +"""store recommended follows in redis""" + import math import logging from django.dispatch import receiver @@ -26,13 +27,13 @@ def get_rank(self, obj): """get computed rank""" return obj.mutuals # + (1.0 - (1.0 / (obj.shared_books + 1))) - def store_id(self, user): # pylint: disable=no-self-use + def store_id(self, user): """the key used to store this user's recs""" if isinstance(user, int): return f"{user}-suggestions" return f"{user.id}-suggestions" - def get_counts_from_rank(self, rank): # pylint: disable=no-self-use + def get_counts_from_rank(self, rank): """calculate mutuals count and shared books count from rank""" return { "mutuals": math.floor(rank), @@ -55,7 +56,7 @@ def get_stores_for_object(self, obj): """the stores that an object belongs in""" return [self.store_id(u) for u in self.get_users_for_object(obj)] - def get_users_for_object(self, obj): # pylint: disable=no-self-use + def get_users_for_object(self, obj): """given a user, who might want to follow them""" return models.User.objects.filter(local=True, is_active=True).exclude( Q(id=obj.id) | Q(followers=obj) | Q(id__in=obj.blocks.all()) | Q(blocks=obj) @@ -92,11 +93,16 @@ def remove_suggestion(self, user, suggested_user): def get_suggestions(self, user, local=False): """get suggestions""" + local = local or models.SiteSettings.get().disable_federation + values = self.get_store(self.store_id(user), withscores=True) annotations = [ When(pk=int(pk), then=self.get_counts_from_rank(score)["mutuals"]) for (pk, score) in values ] + invalid_suggestion = ( + Q(id=user.id) | Q(followers=user) | Q(follower_requests=user) + ) # annotate users with mutuals and shared book counts users = ( models.User.objects.filter( @@ -106,6 +112,7 @@ def get_suggestions(self, user, local=False): mutuals=Case(*annotations, output_field=IntegerField(), default=0) ) .exclude(localname=INSTANCE_ACTOR_USERNAME) + .exclude(invalid_suggestion) ) if local: users = users.filter(local=True) @@ -114,30 +121,36 @@ def get_suggestions(self, user, local=False): def get_annotated_users(viewer, *args, **kwargs): """Users, annotated with things they have in common""" - return ( - models.User.objects.filter(discoverable=True, is_active=True, *args, **kwargs) - .exclude(Q(id__in=viewer.blocks.all()) | Q(blocks=viewer)) - .annotate( - mutuals=Count( - "followers", - filter=Q( - ~Q(id=viewer.id), - ~Q(id__in=viewer.following.all()), - followers__in=viewer.following.all(), - ), - distinct=True, + following = kwargs.pop("following", None) + query = models.User.objects.filter( + discoverable=True, is_active=True, *args, **kwargs + ).exclude(Q(id__in=viewer.blocks.all()) | Q(blocks=viewer) | Q(id=viewer.id)) + + if following is True: + query = query.filter(id__in=viewer.following.all()) + elif following is False: + query = query.exclude(id__in=viewer.following.all()) + + return query.annotate( + mutuals=Count( + "followers", + filter=Q( + ~Q(id=viewer.id), + ~Q(id__in=viewer.following.all()), + followers__in=viewer.following.all(), ), - # shared_books=Count( - # "shelfbook", - # filter=Q( - # ~Q(id=viewer.id), - # shelfbook__book__parent_work__in=[ - # s.book.parent_work for s in viewer.shelfbook_set.all() - # ], - # ), - # distinct=True, - # ), - ) + distinct=True, + ), + # shared_books=Count( + # "shelfbook", + # filter=Q( + # ~Q(id=viewer.id), + # shelfbook__book__parent_work__in=[ + # s.book.parent_work for s in viewer.shelfbook_set.all() + # ], + # ), + # distinct=True, + # ), ) @@ -145,7 +158,6 @@ def get_annotated_users(viewer, *args, **kwargs): @receiver(signals.post_save, sender=models.UserFollows) -# pylint: disable=unused-argument def update_suggestions_on_follow(sender, instance, created, *args, **kwargs): """remove a follow from the recs and update the ranks""" if not created or not instance.user_object.discoverable: @@ -157,7 +169,6 @@ def update_suggestions_on_follow(sender, instance, created, *args, **kwargs): @receiver(signals.post_save, sender=models.UserFollowRequest) -# pylint: disable=unused-argument def update_suggestions_on_follow_request(sender, instance, created, *args, **kwargs): """remove a follow from the recs and update the ranks""" if not created or not instance.user_object.discoverable: @@ -168,7 +179,6 @@ def update_suggestions_on_follow_request(sender, instance, created, *args, **kwa @receiver(signals.post_save, sender=models.UserBlocks) -# pylint: disable=unused-argument def update_suggestions_on_block(sender, instance, *args, **kwargs): """remove blocked users from recs""" if instance.user_subject.local and instance.user_object.discoverable: @@ -178,7 +188,6 @@ def update_suggestions_on_block(sender, instance, *args, **kwargs): @receiver(signals.post_delete, sender=models.UserFollows) -# pylint: disable=unused-argument def update_suggestions_on_unfollow(sender, instance, **kwargs): """update rankings, but don't re-suggest because it was probably intentional""" if instance.user_object.discoverable: @@ -187,7 +196,7 @@ def update_suggestions_on_unfollow(sender, instance, **kwargs): # @receiver(signals.post_save, sender=models.ShelfBook) # @receiver(signals.post_delete, sender=models.ShelfBook) -# # pylint: disable=unused-argument +# # def update_rank_on_shelving(sender, instance, *args, **kwargs): # """when a user shelves or unshelves a book, re-compute their rank""" # # if it's a local user, re-calculate who is rec'ed to them @@ -200,7 +209,6 @@ def update_suggestions_on_unfollow(sender, instance, **kwargs): @receiver(signals.post_save, sender=models.User) -# pylint: disable=unused-argument def update_user(sender, instance, created, update_fields=None, **kwargs): """an updated user, neat""" # a new user is found, create suggestions for them @@ -209,7 +217,7 @@ def update_user(sender, instance, created, update_fields=None, **kwargs): # we know what fields were updated and discoverability didn't change if not instance.bookwyrm_user or ( - update_fields and not "discoverable" in update_fields + update_fields and "discoverable" not in update_fields ): return diff --git a/bookwyrm/tasks.py b/bookwyrm/tasks.py index 79e1b63408..3c65dfde90 100644 --- a/bookwyrm/tasks.py +++ b/bookwyrm/tasks.py @@ -1,4 +1,5 @@ -""" background tasks """ +"""background tasks""" + import os from celery import Celery diff --git a/bookwyrm/templates/about/about.html b/bookwyrm/templates/about/about.html index ef3f340371..95b7c20c74 100644 --- a/bookwyrm/templates/about/about.html +++ b/bookwyrm/templates/about/about.html @@ -11,6 +11,7 @@ {% block about_content %} {% get_current_language as LANGUAGE_CODE %} + {# seven day cache #} {% cache 604800 about_page_superlatives LANGUAGE_CODE %} @@ -31,6 +32,7 @@

+

{% blocktrans trimmed %} Track your reading, talk about books, write reviews, and discover what to read next. Always ad-free, anti-corporate, and community-oriented, BookWyrm is human-scale software, designed to stay small and personal. diff --git a/bookwyrm/templates/about/layout.html b/bookwyrm/templates/about/layout.html index 22237508c0..cdbe9afc0d 100644 --- a/bookwyrm/templates/about/layout.html +++ b/bookwyrm/templates/about/layout.html @@ -3,7 +3,7 @@ {% load i18n %} {% block about_panel %} -

+
{% include "snippets/about.html" with size="m" %} {% if active_users %}
    diff --git a/bookwyrm/templates/author/author.html b/bookwyrm/templates/author/author.html index 043f3fa54d..9d014b3325 100644 --- a/bookwyrm/templates/author/author.html +++ b/bookwyrm/templates/author/author.html @@ -4,6 +4,7 @@ {% load humanize %} {% load utilities %} {% load book_display_tags %} +{% load date_ext %} {% block title %}{{ author.name }}{% endblock %} @@ -30,7 +31,7 @@

    {{ author.name }}

    {% firstof author.aliases author.born author.died as details %} {% firstof author.wikipedia_link author.website author.openlibrary_key author.inventaire_id author.isni author.isfdb author.wikidata as links %} {% if details or links %} -
    +
    {% if details %}

    {% trans "Author details" %}

    @@ -44,19 +45,35 @@

    {% trans "Author details" %}

    {% endif %} + {% with date=author.born|naturalday_partial %} {% if author.born %}
    {% trans "Born:" %}
    -
    {{ author.born|naturalday }}
    +
    {{ date }}
    {% endif %} + {% endwith %} + {% with date=author.died|naturalday_partial %} {% if author.died %}
    {% trans "Died:" %}
    -
    {{ author.died|naturalday }}
    +
    {{ date }}
    {% endif %} + {% endwith %} + + {% if series %} +
    +
    {% trans "Series:" %}
    +
    + {% for s in series %} + {{ s.name }}{% if not forloop.last%},{% endif %} + {% endfor %} +
    +
    + + {% endif %} {% endif %} @@ -79,7 +96,7 @@

    {% trans "External links" %}

    {% trans "View on Wikidata" %}
    - {% endif %} + {% endif %} {% if author.website %}
    @@ -158,7 +175,7 @@

    {% trans "External links" %}

    {% endif %} -
    +
    {% if author.bio %} {% include "snippets/trimmed_text.html" with full=author.bio trim_length=200 %} {% endif %} @@ -169,7 +186,7 @@

    {% blocktrans with name=author.name %}Books by {{ name }} {% with book=book|author_edition:author %}
    - {% include 'landing/small-book.html' with book=book %} + {% include 'snippets/small-book.html' with book=book %}
    {% include 'snippets/shelve_button/shelve_button.html' with book=book %}
    diff --git a/bookwyrm/templates/author/edit_author.html b/bookwyrm/templates/author/edit_author.html index f3e908c9b6..82595d3247 100644 --- a/bookwyrm/templates/author/edit_author.html +++ b/bookwyrm/templates/author/edit_author.html @@ -65,14 +65,14 @@

    {% trans "Metadata" %}

    - + {{ form.born }} {% include 'snippets/form_errors.html' with errors_list=form.born.errors id="desc_born" %}
    - + {{ form.died }} {% include 'snippets/form_errors.html' with errors_list=form.died.errors id="desc_died" %}
    diff --git a/bookwyrm/templates/book/book.html b/bookwyrm/templates/book/book.html index 001a5f5bb0..e7039e8b6f 100644 --- a/bookwyrm/templates/book/book.html +++ b/bookwyrm/templates/book/book.html @@ -1,10 +1,10 @@ {% extends 'layout.html' %} {% load i18n %} -{% load book_display_tags %} -{% load humanize %} +{% load shelf_tags %} {% load utilities %} +{% load book_display_tags %} {% load static %} -{% load shelf_tags %} +{% load humanize %} {% block title %}{{ book|book_title }}{% endblock %} @@ -16,6 +16,7 @@ {% block head_links %} {% include 'snippets/dublincore.html' with book=book %} {% endblock %} + {% block content %} {% if update_error %}
    @@ -29,234 +30,29 @@ {% with user_authenticated=request.user.is_authenticated can_edit_book=perms.bookwyrm.edit_book %}
    -
    -

    - {{ book.title }} -

    - - {% if book.subtitle or book.series %} -

    - {% if book.subtitle %} - - - - {{ book.subtitle }} - - {% endif %} - - {% if book.series %} - {% spaceless %} - - {% if book.authors.exists %} - - {% endif %} - - {% if book.series_number %} - , # - {{ book.series_number }} - {% endif %} - {% endspaceless %} - {% endif %} -

    - {% endif %} - - {% if book.authors.exists %} -
    - {% trans "by" %} {% include 'snippets/authors.html' with book=book %} -
    - {% endif %} -
    - - {% if user_authenticated and can_edit_book %} - - {% endif %} + {% include "book/sections/title.html" %}
    -
    - {% if not book.cover %} - {% if user_authenticated %} - - {% join "add_cover" book.id as modal_id %} - {% include 'book/cover_add_modal.html' with id=modal_id %} - {% if request.GET.cover_error %} -

    {% trans "Failed to load cover" %}

    - {% endif %} - {% else %} - {% include 'snippets/book_cover.html' with book=book cover_class='is-h-m-mobile' %} - {% endif %} - {% endif %} - - {% if book.cover %} - - {% include 'book/cover_show_modal.html' with book=book id="cover_show_modal" %} - {% endif %} - - {% include 'snippets/rate_action.html' with user=request.user book=book %} - +
    + {% include "book/sections/cover.html" %}
    {% include 'snippets/shelve_button/shelve_button.html' %}
    -
    - {% with book=book %} -
    - {% include 'book/publisher_info.html' %} -
    - -
    - {% include 'book/book_identifiers.html' %} -
    - {% endwith %} - - {% trans "Load data" as button_text %} - {% if book.openlibrary_key %} -

    - - {% trans "View on OpenLibrary" %} - - {% if request.user.is_authenticated and perms.bookwyrm.edit_book %} - - {% include "book/sync_modal.html" with source="openlibrary.org" source_name="OpenLibrary" id="openlibrary_sync" %} - {% endif %} -

    - {% endif %} - {% if book.inventaire_id %} -

    - - {% trans "View on Inventaire" %} - - - {% if request.user.is_authenticated and perms.bookwyrm.edit_book %} - - {% include "book/sync_modal.html" with source="inventaire.io" source_name="Inventaire" id="inventaire_sync" %} - {% endif %} -

    - {% endif %} - {% if book.isfdb %} -

    - - {% trans "View on ISFDB" %} - -

    - {% endif %} - {% if book.finna_key %} -

    - - {% trans "View on Finna" %} - - - {% if request.user.is_authenticated and perms.bookwyrm.edit_book %} - - {% include "book/sync_modal.html" with source="api.finna.fi" source_name="Finna" id="finna_sync" %} - {% endif %} -

    - {% endif %} + {% include "book/sections/lists.html" %} +
    -
    +
    +
    + {% include "book/sections/description.html" %} +
    - - - {% with full=book|book_description itemprop='abstract' %} - {% include 'snippets/trimmed_text.html' %} - {% endwith %} - - {% if user_authenticated and can_edit_book and not book|book_description %} - {% trans 'Add Description' as button_text %} - {% include 'snippets/toggle/open_button.html' with class="mb-2" text=button_text controls_text="add_description" controls_uid=book.id focus="id_description" hide_active=True id="hide_description" %} - - - {% endif %} - - {% with work=book.parent_work editions_count=book.parent_work.editions.count %} -

    +

    {% endwith %} + {% include "book/sections/metadata.html" with content_count=2 %}
    - - {# user's relationship to the book #}
    - {% if user_shelfbooks.count > 0 %} -

    - {% trans "You have shelved this edition in:" %} -

    -
      - {% for shelf in user_shelfbooks %} -
    • - {{ shelf.shelf|translate_shelf_name }} -
      - {% include 'snippets/shelf_selector.html' with shelf=shelf.shelf class="is-small" readthrough=readthrough %} -
      -
    • - {% endfor %} -
    - {% endif %} - {% for shelf in other_edition_shelves %} -

    - {% blocktrans with book_path=shelf.book.local_path shelf_path=shelf.shelf.local_path shelf_name=shelf.shelf|translate_shelf_name %}A different edition of this book is on your {{ shelf_name }} shelf.{% endblocktrans %} - {% include 'snippets/switch_edition_button.html' with edition=book %} -

    - {% endfor %} + {% include "book/sections/subjects.html" %}
    +
    + {% if user_authenticated %} + {% if readthroughs.exists %} + {% include "book/sections/reading.html" with content_count=readthroughs.count %} + {% endif %} - {% if user_authenticated %} - -
    -
    -
    -

    {% trans "Your reading activity" %}

    -
    -
    +
    -
    - {% include "readthrough/readthrough_modal.html" with id="add-readthrough" %} - - {% if not readthroughs.exists %} -

    {% trans "You don't have any reading activity for this book." %}

    - {% endif %} - {% for readthrough in readthroughs %} - {% include 'readthrough/readthrough_list.html' with readthrough=readthrough %} - {% endfor %} -
    - - -
    - {% with 0|uuid as controls_uid %} - {% include 'snippets/create_status.html' with book=book hide_cover=True %} - {% endwith %} -
    - {% endif %} -
    - {% if request.user.is_authenticated %} - {% if user_statuses.review_count or user_statuses.comment_count or user_statuses.quotation_count %} - - {% endif %} + {% include "readthrough/readthrough_modal.html" with id="add-readthrough" %} {% endif %} - {% for status in statuses %} -
    - {% include 'snippets/status/status.html' with status=status hide_book=True depth=1 %} -
    - {% endfor %} - - {% if ratings %} -
    - {% for rating in ratings %} - {% include 'book/rating.html' with user=rating.user rating=rating %} + {% if other_edition_shelves %} +
    + {% for shelf in other_edition_shelves %} +

    + {% blocktrans trimmed with book_path=shelf.book.local_path shelf_path=shelf.shelf.local_path shelf_name=shelf.shelf|translate_shelf_name %} + A different edition of this book is on your {{ shelf_name }} shelf. + {% endblocktrans %} + {% include 'snippets/switch_edition_button.html' with edition=book %} +

    {% endfor %}
    {% endif %} -
    - {% include 'snippets/pagination.html' with page=statuses path=request.path anchor="#reviews" %} -
    -
    -
    - {% if book.subjects %} -
    -

    {% trans "Subjects" %}

    - -
      - {% for subject in book.subjects %} -
    • {{ subject }}
    • - {% endfor %} -
    -
    - {% endif %} - - {% if book.subject_places %} -
    -

    {% trans "Places" %}

    -
      - {% for place in book.subject_places %} -
    • {{ place }}
    • - {% endfor %} -
    -
    - {% endif %} - - {% if lists.exists or request.user.list_set.exists %} -
    -

    {% trans "Lists" %}

    - - {% if list_options.exists %} -
    - {% csrf_token %} - - - -
    -
    - -
    -
    - -
    -
    -
    - {% endif %} + +
    + {% include "book/sections/reviews.html" %}
    - {% endif %} +
    -
    -
    + + {% endwith %} {% endblock %} {% block scripts %} - - + + {% if request.user.show_guided_tour %} {% include 'guided_tour/book.html' %} {% endif %} diff --git a/bookwyrm/templates/book/book_identifiers.html b/bookwyrm/templates/book/book_identifiers.html index a288bae788..b27fdc92ac 100644 --- a/bookwyrm/templates/book/book_identifiers.html +++ b/bookwyrm/templates/book/book_identifiers.html @@ -4,18 +4,20 @@ {% if book.isbn_13 or book.oclc_number or book.asin or book.aasin or book.isfdb %}
    {% if book.isbn_13 %} -
    -
    {% trans "ISBN:" %}
    -
    {{ book.hyphenated_isbn13 }}
    +
    +
    {% trans "ISBN:" %}
    +
    + {{ book.hyphenated_isbn13 }}
    - {% trans "Copied ISBN!" %}
    -
    + +
    {% endif %} {% if book.oclc_number %} @@ -59,6 +61,13 @@
    {{ book.finna_key}}
    {% endif %} + + {% if book.libris_key %} +
    +
    {% trans "Libris ID:" %}
    +
    {{ book.libris_key }}
    +
    + {% endif %} {% endif %} {% endspaceless %} diff --git a/bookwyrm/templates/book/edit/edit_book.html b/bookwyrm/templates/book/edit/edit_book.html index 05f40523ff..b2e9f7b0b6 100644 --- a/bookwyrm/templates/book/edit/edit_book.html +++ b/bookwyrm/templates/book/edit/edit_book.html @@ -68,10 +68,10 @@

    {% if confirm_mode %}

    {% trans "Confirm Book Info" %}

    -
    +
    {% if author_matches %} -
    +
    {% for author in author_matches %}
    @@ -112,12 +112,13 @@

    {% trans "Confirm Book Info" %}

    {% endfor %}
    {% elif add_author %} -

    {% blocktrans with name=add_author %}Creating a new author: {{ name }}{% endblocktrans %}

    +

    {% blocktrans with name=add_author %}Creating a new author: {{ name }}{% endblocktrans %}

    {% endif %} - {% if not book.parent_work %} -
    + {% if not book.parent_work.exists %} +
    + {% if book_matches%} {% trans "Is this an edition of an existing work?" %} @@ -129,9 +130,44 @@

    {% trans "Confirm Book Info" %}

    + {% endif %}
    {% endif %} + {% if form.series.value %} +
    + {% if series_matches %} + {% if confirm_series_mode %} +
    + {% trans "Are you sure this is a new series? The following series have similar names and a matching author." %} +
    + + {% endif %} +
    + + {% trans "Is this book part of one of these series?" %} + + {% for series in series_matches %} + + {% endfor %} + +
    + {% else %} + + {% trans "Creating a new series" %}: + +

    {{ form.series.value }}

    + {% endif %} +
    + {% endif %}
    diff --git a/bookwyrm/templates/book/edit/edit_book_form.html b/bookwyrm/templates/book/edit/edit_book_form.html index 31a7f25d3e..5686e3fa96 100644 --- a/bookwyrm/templates/book/edit/edit_book_form.html +++ b/bookwyrm/templates/book/edit/edit_book_form.html @@ -59,29 +59,6 @@

    {% include 'snippets/form_errors.html' with errors_list=form.description.errors id="desc_description" %}

    -
    -
    -
    - - - - {% include 'snippets/form_errors.html' with errors_list=form.series.errors id="desc_series" %} -
    -
    -
    -
    - - {{ form.series_number }} - - {% include 'snippets/form_errors.html' with errors_list=form.series_number.errors id="desc_series_number" %} -
    -
    -
    -
    + +
    + + {{ form.libris_key }} + + {% include 'snippets/form_errors.html' with errors_list=form.libris_key.errors id="desc_libris_key" %} +
    diff --git a/bookwyrm/templates/book/edit/edit_series.html b/bookwyrm/templates/book/edit/edit_series.html new file mode 100644 index 0000000000..2eb961535a --- /dev/null +++ b/bookwyrm/templates/book/edit/edit_series.html @@ -0,0 +1,93 @@ +{% extends 'layout.html' %} +{% load humanize %} +{% load i18n %} +{% load static %} +{% load utilities %} + + +{% block title %} + {% blocktrans with title=series.name %}Edit "{{ title }}"{% endblocktrans %} +{% endblock %} + +{% block content %} + +{% if form.non_field_errors %} +
    +

    {{ form.non_field_errors }}

    +
    +{% endif %} + +

    {% blocktrans with name=series.name %}Edit "{{ name }}"{% endblocktrans %}

    + +{% for match in matches.all %} + {{match.name}} +{% endfor %} + +
    + {% csrf_token %} +
    +
    + + + +
    +
    + + {% for alt in series.alternative_names %} + + + {% endfor %} + + +
    + + + +
    +
    +
    + + + + + + + + +
    +
    + +
    + {% for seriesbook in series.seriesbooks.all %} +
    +
    + + + + {% include 'snippets/small-book.html' with book=seriesbook.book.work.default_edition %} +
    +
    + + +
    +
    + {% endfor %} +
    +
    + {% include 'snippets/form_errors.html' with errors_list=seriesbook_errors id="seriesbook_errors" %} +
    + + + {% trans "Back" %} + +
    +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/bookwyrm/templates/book/file_links/edit_links.html b/bookwyrm/templates/book/file_links/edit_links.html index e6819f6bb4..8bd8a3c964 100644 --- a/bookwyrm/templates/book/file_links/edit_links.html +++ b/bookwyrm/templates/book/file_links/edit_links.html @@ -34,7 +34,7 @@

    {% trans "Filetype" %} {% trans "Domain" %} {% trans "Status" %} - {% trans "Actions" %} + {% trans "Availability" %} {% for link in links %} diff --git a/bookwyrm/templates/book/file_links/file_link_page.html b/bookwyrm/templates/book/file_links/file_link_page.html index 00efe5089c..038d63a18b 100644 --- a/bookwyrm/templates/book/file_links/file_link_page.html +++ b/bookwyrm/templates/book/file_links/file_link_page.html @@ -11,5 +11,5 @@ {% endblock %} {% block scripts %} - + {% endblock %} diff --git a/bookwyrm/templates/book/file_links/links.html b/bookwyrm/templates/book/file_links/links.html index febc39e56c..f1d53952e3 100644 --- a/bookwyrm/templates/book/file_links/links.html +++ b/bookwyrm/templates/book/file_links/links.html @@ -6,7 +6,7 @@ {% if links.exists or request.user.is_authenticated %}
    -

    {% trans "Get a copy" %}

    +

    {% trans "Get a copy" %}

    {% if can_edit_book %}
    diff --git a/bookwyrm/templates/book/file_links/verification_modal.html b/bookwyrm/templates/book/file_links/verification_modal.html index 4f2f96a4a9..121572dea0 100644 --- a/bookwyrm/templates/book/file_links/verification_modal.html +++ b/bookwyrm/templates/book/file_links/verification_modal.html @@ -24,7 +24,7 @@ {% endif %} -{% trans "Continue" %} +{% trans "Continue" %} {% endblock %} diff --git a/bookwyrm/templates/book/sections/cover.html b/bookwyrm/templates/book/sections/cover.html new file mode 100644 index 0000000000..e65076ab33 --- /dev/null +++ b/bookwyrm/templates/book/sections/cover.html @@ -0,0 +1,49 @@ +{% load i18n %} +{% load static %} +{% load utilities %} + +{% if not book.cover %} + {% if user_authenticated %} + + {% join "add_cover" book.id as modal_id %} + {% include 'book/cover_add_modal.html' with id=modal_id %} + {% if request.GET.cover_error %} +

    {% trans "Failed to load cover" %}

    + {% endif %} + {% else %} + {% include 'snippets/book_cover.html' with book=book cover_class='is-h-m-mobile' %} + {% endif %} +{% endif %} + +{% if book.cover %} + + {% include 'book/cover_show_modal.html' with book=book id="cover_show_modal" %} +{% endif %} + +{% include 'snippets/rate_action.html' with user=request.user book=book %} + diff --git a/bookwyrm/templates/book/sections/description.html b/bookwyrm/templates/book/sections/description.html new file mode 100644 index 0000000000..1941727dc4 --- /dev/null +++ b/bookwyrm/templates/book/sections/description.html @@ -0,0 +1,52 @@ +{% load i18n %} +{% load book_display_tags %} + + + +{% with full=book|book_description itemprop='abstract' %} + {% include 'snippets/trimmed_text.html' with trim_length=90 %} +{% endwith %} + +{% if user_authenticated and can_edit_book and not book|book_description %} +{% trans 'Add Description' as button_text %} +{% include 'snippets/toggle/open_button.html' with class="mb-2 is-small" text=button_text controls_text="add_description" controls_uid=book.id focus="id_description" hide_active=True id="hide_description" %} + + +{% endif %} diff --git a/bookwyrm/templates/book/sections/lists.html b/bookwyrm/templates/book/sections/lists.html new file mode 100644 index 0000000000..b2de27a763 --- /dev/null +++ b/bookwyrm/templates/book/sections/lists.html @@ -0,0 +1,38 @@ +{% load i18n %} + +{% if lists.exists or list_options.exists %} +
    + {% if lists.exists %} +

    {% trans "Lists" %}

    + + {% endif %} + + {% if list_options.exists %} +
    + {% csrf_token %} + + + +
    +
    + +
    +
    + +
    +
    +
    + {% include "lists/create_list_modal.html" with id="modal-create-list-with-book" %} + {% endif %} +
    +{% endif %} + diff --git a/bookwyrm/templates/book/sections/metadata.html b/bookwyrm/templates/book/sections/metadata.html new file mode 100644 index 0000000000..482894c4bf --- /dev/null +++ b/bookwyrm/templates/book/sections/metadata.html @@ -0,0 +1,80 @@ +{% extends "components/details.html" %} + +{% load i18n %} + +{% block always-visible %} +
    + {% include 'book/publisher_info.html' %} +
    +{% endblock %} + +{% block sometimes-visible %} + +
    + {% include 'book/book_identifiers.html' %} +
    + + {% trans "Load data" as button_text %} + {% if book.openlibrary_key %} +

    + + {% trans "View on OpenLibrary" %} + + {% if request.user.is_authenticated and perms.bookwyrm.edit_book %} + + {% endif %} +

    + {% include "book/sync_modal.html" with source="openlibrary.org" source_name="OpenLibrary" id="openlibrary_sync" %} + {% endif %} + + {% if book.inventaire_id %} +

    + + {% trans "View on Inventaire" %} + + + {% if request.user.is_authenticated and perms.bookwyrm.edit_book %} + + {% endif %} +

    + {% include "book/sync_modal.html" with source="inventaire.io" source_name="Inventaire" id="inventaire_sync" %} + {% endif %} + + {% if book.isfdb %} +

    + + {% trans "View on ISFDB" %} + +

    + {% endif %} + + {% if book.finna_key %} +

    + + {% trans "View on Finna" %} + + + {% if request.user.is_authenticated and perms.bookwyrm.edit_book %} + + {% endif %} +

    + {% include "book/sync_modal.html" with source="api.finna.fi" source_name="Finna" id="finna_sync" %} + {% endif %} + + {% if book.libris_key %} +

    + + {% trans "View on Libris" %} + +

    + {% endif %} +{% endblock %} diff --git a/bookwyrm/templates/book/sections/reading.html b/bookwyrm/templates/book/sections/reading.html new file mode 100644 index 0000000000..7ce923e370 --- /dev/null +++ b/bookwyrm/templates/book/sections/reading.html @@ -0,0 +1,13 @@ +{% extends "components/details.html" %} + +{% block always-visible %} + {% include 'readthrough/readthrough_list.html' with readthrough=readthroughs.first %} +{% endblock %} + +{% block sometimes-visible %} + {% for readthrough in readthroughs %} + {% if not forloop.first %} + {% include 'readthrough/readthrough_list.html' with readthrough=readthrough %} + {% endif %} + {% endfor %} +{% endblock %} diff --git a/bookwyrm/templates/book/sections/reviews.html b/bookwyrm/templates/book/sections/reviews.html new file mode 100644 index 0000000000..41250aa720 --- /dev/null +++ b/bookwyrm/templates/book/sections/reviews.html @@ -0,0 +1,73 @@ +{% load i18n %} +{% load utilities %} + +

    {% trans "Reviews" %}

    +{% if user_authenticated %} +
    + + {% trans "Add your thoughts" %} + + +
    + {% with 0|uuid as controls_uid %} + {% include 'snippets/create_status.html' with book=book hide_cover=True %} + {% endwith %} +
    +
    +{% endif %} +
    + {% if request.user.is_authenticated %} + {% if user_statuses.review_count or user_statuses.comment_count or user_statuses.quotation_count %} + + {% endif %} + {% endif %} + + {% for status in statuses %} +
    + {% include 'snippets/status/status.html' with status=status hide_book=True depth=1 %} +
    + {% endfor %} + + {% if ratings %} +
    + {% for rating in ratings %} + {% include 'book/rating.html' with user=rating.user rating=rating %} + {% endfor %} +
    + {% endif %} +
    + {% include 'snippets/pagination.html' with page=statuses path=request.path anchor="#reviews" %} +
    +
    diff --git a/bookwyrm/templates/book/sections/subjects.html b/bookwyrm/templates/book/sections/subjects.html new file mode 100644 index 0000000000..93fd6316f4 --- /dev/null +++ b/bookwyrm/templates/book/sections/subjects.html @@ -0,0 +1,28 @@ +{% load i18n %} +{% if book.subjects or book.subject_places %} +
    +
    + + {% trans "Subjects" %} + + + + {% if book.subjects %} +
      + {% for subject in book.subjects %} +
    • {{ subject }}
    • + {% endfor %} +
    + {% endif %} + + {% if book.subject_places %} +

    {% trans "Places" %}

    +
      + {% for place in book.subject_places %} +
    • {{ place }}
    • + {% endfor %} +
    + {% endif %} +
    +
    +{% endif %} diff --git a/bookwyrm/templates/book/sections/title.html b/bookwyrm/templates/book/sections/title.html new file mode 100644 index 0000000000..391a7ee470 --- /dev/null +++ b/bookwyrm/templates/book/sections/title.html @@ -0,0 +1,75 @@ +{% load i18n %} + +
    +

    + {{ book.title }} +

    + + {% if book.subtitle %} +

    + + + {{ book.subtitle }} + +

    + {% endif %} + + {% if book.authors.exists %} +
    + {% include 'snippets/authors.html' with book=book %} +
    + {% endif %} + + {% if book.series or book.parent_work.seriesbooks %} + {% if book.series %} + {% spaceless %} +
    +
    + {% blocktrans trimmed with series=book.series %} + This book might be part of the {{series}} series. + {% endblocktrans %} + {% if user_authenticated and can_edit_book %} + {% trans "Edit it to confirm." %} + {% endif %} +
    +
    + {% endspaceless %} + {% endif %} + + {% if book.parent_work.seriesbooks %} + {% for sbook in book.parent_work.seriesbooks.all %} +
    + {% spaceless %} + {% if sbook.series_number %} + + {% blocktrans trimmed with title=sbook.series.name path=sbook.series.local_path number=sbook.series_number %} + Book {{ number }} in {{ title }} + {% endblocktrans %} + + {% else %} + + {% blocktrans trimmed with title=sbook.series.name path=sbook.series.local_path %} + Part of {{ title }} + {% endblocktrans %} + + {% endif %} + {% endspaceless %} +
    + {% endfor%} + {% endif %} + + {% endif %} +
    + +{% if user_authenticated and can_edit_book %} + +{% endif %} + diff --git a/bookwyrm/templates/book/series.html b/bookwyrm/templates/book/series.html index 8b945452f0..0490768205 100644 --- a/bookwyrm/templates/book/series.html +++ b/bookwyrm/templates/book/series.html @@ -2,35 +2,82 @@ {% load i18n %} {% load book_display_tags %} -{% block title %}{{ series_name }}{% endblock %} - +{% block title %}{{ series.name }}{% endblock %} {% block content %} -
    -

    {{ series_name }}

    -
    - {% trans "Series by" %} {{ author.name }} +
    + {% with user_authenticated=request.user.is_authenticated can_edit_book=perms.bookwyrm.edit_book %} + {% if user_authenticated and can_edit_book %} + + {% endif %} +
    +

    {{ series.name }}

    + {% if series_authors %} +

    + {% trans "by" %} + {% include 'snippets/authors.html' with book=series_authors limit=5 %} +

    + {% endif %} +
    +
    + {% if series.alternative_names%} +
    +

    {% trans "Alternative names" %}

    +
    + {% for title in series.alternative_names %} + {{title}} + {% endfor %} +
    +
    + {% endif %} +
    +
    + {% if series.inventaire_id or series.wikidata or series.isfdb %} +
    +

    {% trans "External links" %}

    -
    - {% for book in books %} - {% with book=book %} - {# @todo Set `hasPart` property in some meaningful way #} -
    -
    - {% if book.series_number %}{% blocktrans with series_number=book.series_number %}Book {{ series_number }}{% endblocktrans %}{% else %}{% trans 'Unsorted Book' %}{% endif %} - {% include 'landing/small-book.html' with book=book %} + {% if series.inventaire_id%} + + {% endif %} + + {% if series.isfdb %} +
    + ISFDB +
    + {% endif %} + + {% if series.wikidata %} +
    + Wikidata +
    + {% endif %}
    + {% endif %}
    - {% endwith %} +
    +
    + +
    + {% for seriesbook in series_books %} +
    +
    + + + {% if seriesbook.series_number %}{% blocktrans with series_number=seriesbook.series_number %}Book {{ series_number }}{% endblocktrans %}{% endif %} + {% include 'snippets/small-book.html' with book=seriesbook.book.work.default_edition %} +
    +
    {% endfor %}
    + {% endwith %} +
    + {% include 'snippets/pagination.html' with page=series_books path=request.path %} +
    {% endblock %} diff --git a/bookwyrm/templates/book/suggestion_list/book_card.html b/bookwyrm/templates/book/suggestion_list/book_card.html new file mode 100644 index 0000000000..3b34203cd1 --- /dev/null +++ b/bookwyrm/templates/book/suggestion_list/book_card.html @@ -0,0 +1,55 @@ +{% load i18n %} +{% load book_display_tags %} +{% load utilities %} + +
    +
    +
    + +
    + {% url 'user-feed' item.user|username as user_path %} + + {% if item.notes %} + {% blocktrans trimmed with user_link=item.user|user_link %} + {{ user_link }} says: + {% endblocktrans %} + {% else %} + {% blocktrans trimmed with user_link=item.user|user_link %} + {{ user_link }} recommends: + {% endblocktrans %} + {% endif %} +
    +
    + + {% with item_book=item.work.default_edition %} +
    + + {% if item.notes %} +
    + {% include 'snippets/trimmed_text.html' with full=item.notes trim_length=15 %} +
    + {% endif %} +
    + {% include 'snippets/book_titleby.html' with book=item_book %} +
    +
    + {% endwith %} +
    + +
    + diff --git a/bookwyrm/templates/book/suggestion_list/endorsement_button.html b/bookwyrm/templates/book/suggestion_list/endorsement_button.html new file mode 100644 index 0000000000..6d5bdb82f7 --- /dev/null +++ b/bookwyrm/templates/book/suggestion_list/endorsement_button.html @@ -0,0 +1,10 @@ +{% load i18n %} +
    + {% csrf_token %} + +
    diff --git a/bookwyrm/templates/book/suggestion_list/list.html b/bookwyrm/templates/book/suggestion_list/list.html new file mode 100644 index 0000000000..dca6c47be0 --- /dev/null +++ b/bookwyrm/templates/book/suggestion_list/list.html @@ -0,0 +1,65 @@ +{% load i18n %} +{% load humanize %} + +{% if request.user.is_authenticated or suggestion_list %} +

    + {% trans "Readers also recommend:" %} + {% if suggestion_list and items|length > 0 %} + + {% trans "View all suggestions" %} ({{ item_count }}) + + {% endif %} +

    + +{% if suggestion_list %} +
    + + {% if items|length == 0 %} +
    +
    +

    {% trans "Have a recommendation for someone who liked this book?" %}

    +
    + {% url 'book-add-suggestion' book.parent_work.id as fallback_url %} +
    + +
    +
    +
    +
    + {% else %} +
      + {% for item in items %} +
    1. + {% include "book/suggestion_list/book_card.html" with list=suggestion_list %} +
    2. + {% endfor %} +
    + +
    +
    + +
    +
    + {% endif %} + {% with search_url=request.path %} + {% include "book/suggestion_list/search_modal.html" with list=suggestion_list is_suggestion=True id="suggestion_search" active=suggestion_query|default:show_suggestions close_path=request.path %} + {% endwith %} +
    +{% else %} +
    +
    + {% csrf_token %} + + +

    {% trans "Have a recommendation for someone who liked this book?" %}

    + +
    +
    +{% endif %} +{% endif %} diff --git a/bookwyrm/templates/book/suggestion_list/search.html b/bookwyrm/templates/book/suggestion_list/search.html new file mode 100644 index 0000000000..eb92820581 --- /dev/null +++ b/bookwyrm/templates/book/suggestion_list/search.html @@ -0,0 +1,11 @@ +{% extends 'layout.html' %} +{% load i18n %} + +{% block title %} +{% trans "Add suggestions" %} +{% endblock %} + +{% block content %} +{% url 'book-add-suggestion' book.parent_work.id|default:work_id as search_url %} +{% include "book/suggestion_list/search_modal.html" with active=True %} +{% endblock %} diff --git a/bookwyrm/templates/book/suggestion_list/search_modal.html b/bookwyrm/templates/book/suggestion_list/search_modal.html new file mode 100644 index 0000000000..c7400bc1aa --- /dev/null +++ b/bookwyrm/templates/book/suggestion_list/search_modal.html @@ -0,0 +1,15 @@ +{% extends 'components/modal.html' %} +{% load i18n %} +{% load utilities %} + +{% block modal-title %} + {% trans "Add suggestions" %} +{% endblock %} + +{% block modal-body %} + +{% if request.user.is_authenticated %} + {% include "lists/suggestion_search.html" with is_suggestion=True query_param="suggestion_query" columns=True search_url=search_url %} +{% endif %} + +{% endblock %} diff --git a/bookwyrm/templates/components/card.html b/bookwyrm/templates/components/card.html index 7e38bd405d..b6666a57e0 100644 --- a/bookwyrm/templates/components/card.html +++ b/bookwyrm/templates/components/card.html @@ -1,4 +1,4 @@ -
    +
    {% block card-header %} {% endblock %} @@ -11,7 +11,7 @@ {% endif %} -

{% endblock %} @@ -104,7 +105,7 @@

{% trans "Display" %}

- +
{{ form.theme }}
@@ -117,6 +118,12 @@

{% trans "Display" %}

{% trans "Privacy" %}

+
+ +
+ + + +
+

{% trans "Integrations" %}

+
+
+ + {{ form.readwise_api_key }} + + {% include 'snippets/form_errors.html' with errors_list=form.readwise_api_key.errors id="desc_readwise_api_key" %} +

+ {% blocktrans %}Send new quote posts to Readwise. You can create a token at readwise.io/access_token.{% endblocktrans %} + {% if form.readwise_api_key.help_text %}{{ form.readwise_api_key.help_text }}{% endif %} +

+
+ {% if request.user.readwise_api_key %} +
+ +
+ {% endif %} +
+
{% endblock %} diff --git a/bookwyrm/templates/preferences/export-user.html b/bookwyrm/templates/preferences/export-user.html index 65f9cfa5f2..f2fbf956c1 100644 --- a/bookwyrm/templates/preferences/export-user.html +++ b/bookwyrm/templates/preferences/export-user.html @@ -160,7 +160,7 @@

{% trans "Recent Exports" %}

{% trans "Download your export" %} - {% else %} + {% elif export.job.complete %} {% trans "Archive is no longer available" %} {% endif %} diff --git a/bookwyrm/templates/preferences/export.html b/bookwyrm/templates/preferences/export.html index e301eb5cce..3864fe5908 100644 --- a/bookwyrm/templates/preferences/export.html +++ b/bookwyrm/templates/preferences/export.html @@ -12,14 +12,12 @@

{% trans "Your CSV export file will include all the books on your shelves, books you have reviewed, and books with reading activity.
Use this to import into a service like Goodreads." %}

-

-

- {% csrf_token %} - -
-

+
+ {% csrf_token %} + +
{% endblock %} diff --git a/bookwyrm/templates/preferences/layout.html b/bookwyrm/templates/preferences/layout.html index 2eba92f1b0..dd3c578dc2 100644 --- a/bookwyrm/templates/preferences/layout.html +++ b/bookwyrm/templates/preferences/layout.html @@ -55,12 +55,16 @@

{% trans "Export BookWyrm Account" %} - +
diff --git a/bookwyrm/templates/preferences/move_user.html b/bookwyrm/templates/preferences/move_user.html index 47b370e82d..f6975d212d 100644 --- a/bookwyrm/templates/preferences/move_user.html +++ b/bookwyrm/templates/preferences/move_user.html @@ -16,7 +16,7 @@

{% trans "Migrate account to another server" %}

{% trans "Moving your account will notify all your followers and direct them to follow the new account." %}

- {% blocktrans %} + {% blocktrans trimmed %} {{ user }} will be marked as moved and will not be discoverable or usable unless you undo the move. {% endblocktrans %}

diff --git a/bookwyrm/templates/readthrough/readthrough.html b/bookwyrm/templates/readthrough/readthrough.html index 0b42017e61..7450c27238 100644 --- a/bookwyrm/templates/readthrough/readthrough.html +++ b/bookwyrm/templates/readthrough/readthrough.html @@ -4,7 +4,7 @@ {% block title %} {% blocktrans trimmed with title=book|book_title %} -Update read dates for "{{ title }}" +Update read dates for "{{ title }}" {% endblocktrans %} {% endblock %} diff --git a/bookwyrm/templates/readthrough/readthrough_list.html b/bookwyrm/templates/readthrough/readthrough_list.html index 05d0df765e..89e474b870 100644 --- a/bookwyrm/templates/readthrough/readthrough_list.html +++ b/bookwyrm/templates/readthrough/readthrough_list.html @@ -2,82 +2,77 @@ {% load humanize %} {% load tz %} {% load utilities %} -
-
-
-
- {% trans "Progress Updates:" %} -
    - {% if readthrough.finish_date or readthrough.stopped_date or readthrough.progress %} -
  • - {% if readthrough.finish_date %} - {{ readthrough.finish_date | localtime | naturalday }}: {% trans "finished" %} - {% elif readthrough.stopped_date %} - {{ readthrough.stopped_date | localtime | naturalday }}: {% trans "stopped" %} - {% else %} - - {% if readthrough.progress_mode == 'PG' %} - {% include 'snippets/page_text.html' with page=readthrough.progress total_pages=book.pages %} - {% else %} - {{ readthrough.progress }}% - {% endif %} - {% endif %} +
    +
      + {% if readthrough.start_date %} +
    • + {% trans "Started:" %} {{ readthrough.start_date | localtime | naturalday }} - {% if readthrough.progress %} - {% trans "Show all updates" as button_text %} - {% include 'snippets/toggle/toggle_button.html' with text=button_text controls_text="updates" controls_uid=readthrough.id class="is-small" %} - + {% if readthrough.progress %} + {% trans "Show all updates" as button_text %} + {% include 'snippets/toggle/toggle_button.html' with text=button_text controls_text="updates" controls_uid=readthrough.id class="is-small" %} + -
    -
    -
    -
    - {% trans "Edit read dates" as button_text %} - -
    -
    - {% trans "Delete these read dates" as button_text %} - -
    + +
  • + {% endfor %} +
+ {% endif %} + + {% endif %} + {% if readthrough.finish_date or readthrough.stopped_date or readthrough.progress %} +
  • + {% if readthrough.finish_date %} + {% trans "Finished:" %} {{ readthrough.finish_date | localtime | naturalday }} + {% elif readthrough.stopped_date %} + {% trans "Stopped:" %} {{ readthrough.stopped_date | localtime | naturalday }} + {% else %} + + {% if readthrough.progress_mode == 'PG' %} + {% include 'snippets/page_text.html' with page=readthrough.progress total_pages=book.pages %} + {% else %} + {{ readthrough.progress }}% + {% endif %} + {% endif %} +
  • + {% endif %} +
  • +
    +
    + {% trans "Edit read dates" as button_text %} + +
    +
    + {% trans "Delete these read dates" as button_text %} +
    -
  • -
    + + +
    {% join "edit_readthrough" readthrough.id as edit_modal_id %} diff --git a/bookwyrm/templates/report.html b/bookwyrm/templates/report.html index be7ed68f7a..b1ea826925 100644 --- a/bookwyrm/templates/report.html +++ b/bookwyrm/templates/report.html @@ -6,5 +6,5 @@ {% endblock %} {% block content %} -{% include "snippets/report_modal.html" with user=user active=True static=True id="report-modal" %} +{% include "snippets/report_modal.html" with reported_user=reported_user active=True static=True id="report-modal" %} {% endblock %} diff --git a/bookwyrm/templates/search/barcode_modal.html b/bookwyrm/templates/search/barcode_modal.html index 9a1f3b9611..829af1163d 100644 --- a/bookwyrm/templates/search/barcode_modal.html +++ b/bookwyrm/templates/search/barcode_modal.html @@ -2,9 +2,7 @@ {% load i18n %} {% block modal-title %} - {% blocktrans %} - Scan Barcode - {% endblocktrans %} + {% trans "Scan Barcode" %} {% endblock %} {% block modal-body %} diff --git a/bookwyrm/templates/search/book.html b/bookwyrm/templates/search/book.html index b93c967545..9174e4655c 100644 --- a/bookwyrm/templates/search/book.html +++ b/bookwyrm/templates/search/book.html @@ -1,10 +1,20 @@ {% extends 'search/layout.html' %} {% load i18n %} {% load humanize %} +{% load utilities %} {% load book_display_tags %} {% block panel %} +{% if blocked_books_excluded %} +

    + {% url "prefs-block-books" as bb_path %} + {% blocktrans trimmed %} + Some blocked books have been excluded. + {% endblocktrans %} +

    +{% endif %} + {% if results or remote_results %}
      {% for result in results %} diff --git a/bookwyrm/templates/search/layout.html b/bookwyrm/templates/search/layout.html index 59ea0304e4..889510f7ee 100644 --- a/bookwyrm/templates/search/layout.html +++ b/bookwyrm/templates/search/layout.html @@ -17,8 +17,8 @@

    -
    - {% if request.user.is_authenticated %} diff --git a/bookwyrm/templates/settings/connectors/available.html b/bookwyrm/templates/settings/connectors/available.html index 536d3d3176..8bffc5c628 100644 --- a/bookwyrm/templates/settings/connectors/available.html +++ b/bookwyrm/templates/settings/connectors/available.html @@ -6,6 +6,8 @@

    {% if connector == "finna" %} Finna + {% elif connector == "libris" %} + Libris {% endif %}

    @@ -13,6 +15,8 @@

    {% if connector == "finna" %} {% trans "Finna.fi is a search service that collects material from hundreds of Finnish organisations under one roof." %} + {% elif connector == "libris" %} + {% trans "Libris is the Swedish national library catalog, providing access to records from Swedish libraries." %} {% endif %}

    diff --git a/bookwyrm/templates/settings/connectors/connector.html b/bookwyrm/templates/settings/connectors/connector.html index 70eec0c00c..1b27e717be 100644 --- a/bookwyrm/templates/settings/connectors/connector.html +++ b/bookwyrm/templates/settings/connectors/connector.html @@ -1,4 +1,5 @@ {% load i18n %} +{% load humanize %}
  • @@ -19,7 +20,7 @@ {% csrf_token %}
    - +
    @@ -37,13 +38,28 @@
    {% endif %} + + {% if connector.most_recent_error %} + + {% endif %} {% if connector.active %}
  • - \ No newline at end of file + diff --git a/bookwyrm/templates/settings/connectors/connectors.html b/bookwyrm/templates/settings/connectors/connectors.html index ff45f97c3c..c95a5f2cd4 100644 --- a/bookwyrm/templates/settings/connectors/connectors.html +++ b/bookwyrm/templates/settings/connectors/connectors.html @@ -6,33 +6,31 @@ {% block header %}{% trans "Connector Settings" %}{% endblock %} {% block panel %} -
    -
    -

    {% trans "Connectors are sources of data about books and authors." %}

    -

    {% trans "The priority determines the order in which search results appear. The highest priority is 1. The default priority is 2." %}

    -

    - {% trans "Connector settings only determine whether a connector will be used to deliver search results. To manage more interactions with other federated servers, including domain blocks, see" %} {% trans "Federated Instances" %}. -

    -

    External Connectors

    -
      - {% for connector in update_connectors %} - {% include "settings/connectors/update.html" %} +
      +

      {% trans "Connectors are sources of data about books and authors." %}

      +

      {% trans "The priority determines the order in which search results appear. The highest priority is 1. The default priority is 2." %}

      +

      + {% trans "Connector settings only determine whether a connector will be used to deliver search results. To manage more interactions with other federated servers, including domain blocks, see" %} {% trans "Federated Instances" %}. +

      +

      External Connectors

      +
        + {% for connector in update_connectors %} + {% include "settings/connectors/update.html" %} + {% endfor %} + {% for connector in other_connectors %} + {% include "settings/connectors/connector.html" %} + {% endfor %} + {% if available_connectors %} + {% for connector in available_connectors %} + {% include "settings/connectors/available.html" %} {% endfor %} - {% for connector in other_connectors %} + {% endif%} +
      +

      BookWyrm Connectors

      +
        + {% for connector in bookwyrm_connectors %} {% include "settings/connectors/connector.html" %} {% endfor %} - {% if available_connectors %} - {% for connector in available_connectors %} - {% include "settings/connectors/available.html" %} - {% endfor %} - {% endif%} -
      -

      BookWyrm Connectors

      -
        - {% for connector in bookwyrm_connectors %} - {% include "settings/connectors/connector.html" %} - {% endfor %} -
      -
      -
    + + {% endblock %} diff --git a/bookwyrm/templates/settings/dashboard/dashboard.html b/bookwyrm/templates/settings/dashboard/dashboard.html index d43b3bade0..9ecd35bea7 100644 --- a/bookwyrm/templates/settings/dashboard/dashboard.html +++ b/bookwyrm/templates/settings/dashboard/dashboard.html @@ -140,6 +140,7 @@

    {% trans "Works created" %}

    {% block scripts %} + {% include 'settings/dashboard/user_chart.html' %} {% include 'settings/dashboard/status_chart.html' %} {% include 'settings/dashboard/registration_chart.html' %} diff --git a/bookwyrm/templates/settings/federation/settings.html b/bookwyrm/templates/settings/federation/settings.html new file mode 100644 index 0000000000..450fcbe138 --- /dev/null +++ b/bookwyrm/templates/settings/federation/settings.html @@ -0,0 +1,84 @@ +{% extends 'settings/layout.html' %} +{% load i18n %} +{% block title %}{% trans "Federation Settings" %}{% endblock %} + +{% block header %}{% trans "Federated Settings" %}{% endblock %} + +{% block panel %} +{% if success %} +
    + + + {% trans "Settings saved" %} + +
    +{% endif %} + +{% if form.errors %} +
    + + + {% trans "Unable to save settings" %} + +
    +{% endif %} + + + {% csrf_token %} +
    +
    + + + + {% trans "Prevents anonymous requests from federated instances. Roughly equivalent to Mastodon's 'secure mode' or 'AUTHORIZED_FETCH'" %} + +
    +
    + + + {% trans "Prevents anonymous users from seeing most pages. To block JSON requests as well, enable 'Require signed GET requests'" %} + +
    +
    + + + {% trans "Prevents your instance from interacting with other federated services. Existing data from other instances will still be present." %} + +
    +
    + + + {% trans "Prevents other servers from searching your instance. It is strongly recommended you do NOT enable this setting." %} + + + +
    +
    +
    + +
    + + + +{% endblock %} + diff --git a/bookwyrm/templates/settings/files.html b/bookwyrm/templates/settings/files.html index 39b3d26ca5..98b425f1be 100644 --- a/bookwyrm/templates/settings/files.html +++ b/bookwyrm/templates/settings/files.html @@ -56,7 +56,7 @@

    {% trans "Schedule file deletion" %}

    -
    + {% csrf_token %}
    @@ -202,6 +202,167 @@

    Recent jobs

    {% endif %}
    +
    +

    {% trans "Find book covers from connectors" %}

    +
    +

    + {% trans "These jobs find cover images where they are missing or have incorrect filepaths." %} +

    +
    +

    {% trans "Find missing covers" %}

    +
    +

    + {% trans "Schedule a regular scan to find cover images for editions without them. This can be resource intensive so the recommended schedule is no less than every seven days." %} +

    +
    + {% if find_covers_task %} +
    +
    + {% trans "Schedule:" %} +
    +
    + {{ find_covers_task.schedule }} +
    + +
    + {% trans "Last run:" %} +
    +
    + {{ find_covers_task.last_run_at|naturaltime }} +
    + +
    + {% trans "Total run count:" %} +
    +
    + {{ find_covers_task.total_run_count }} +
    + +
    + {% trans "Enabled:" %} +
    +
    + + {{ find_covers_task.enabled|yesno }} + +
    +
    + +
    +
    + {% csrf_token %} + +
    +
    + {% csrf_token %} + +

    {% trans "Last run date will not be updated" %}

    +
    +
    + {% else %} +
    +
    + {% csrf_token %} +
    + +
    +
    + {{ covers_form.every }} +
    +
    +

    + {{ covers_form.every.help_text }} +

    +
    +
    + +
    + {{ covers_form.period }} +
    +

    + {{ covers_form.period.help_text }} +

    +
    + +
    +
    + {% endif %} + +
    +

    {% trans "Fix broken book cover filepaths" %}

    +
    +

    + {% trans "If you have lost your cover image files (e.g. due to server migration failure) the scheduled job above will not replace them. Run this job instead to attempt to find covers for books where the current cover filepath does not resolve to a file." %} +

    +
    +
    +
    + {% csrf_token %} + +

    {% trans "This job cannot be scheduled to run regularly" %}

    +
    +
    +
    + + + {% if covers_jobs %} +
    +

    Recent jobs

    +
    + + + + + + + + + {% for job in covers_jobs %} + + + + + + + + {% endfor %} +
    {% trans "Date" %}{% trans "Status" %}{% trans "Editions checked" %}{% trans "Covers fixed" %}
    {{ job.updated_date }} + + {% if job.status %} + {{ job.get_status_display }} + {% elif job.complete %} + {% trans "Complete" %} + {% else %} + {% trans "Active" %} + {% endif %} + + {{ job.editions.count }}{{ job.found_covers.count }} + {% if not job.complete %} +
    + {% csrf_token %} + +
    + {% endif %} +
    +
    +
    + {% endif %} + +
    {% if success %}
    diff --git a/bookwyrm/templates/settings/invites/manage_invite_requests.html b/bookwyrm/templates/settings/invites/manage_invite_requests.html index 59eca25d58..ad58fbec6d 100644 --- a/bookwyrm/templates/settings/invites/manage_invite_requests.html +++ b/bookwyrm/templates/settings/invites/manage_invite_requests.html @@ -73,20 +73,29 @@

    {# no invite OR invite not yet used #} {% if not req.invite.times_used %} -
    - {% csrf_token %} - - {% if not req.invite %} - - {% else %} - - {% endif %} -
    +
    +
    + {% csrf_token %} + + {% if not req.invite %} + + {% else %} + + {% endif %} +
    +
    {% endif %} {# invite created but not used #} {% if req.invite and not req.invite.times_used %} - {# #} +
    +
    + {% csrf_token %} + + + +
    +
    {% elif req.invite %} {# accepted #} {% if req.invite.invitees.exists %} @@ -95,15 +104,17 @@

      {% endif %} {% else %} -
    - {% csrf_token %} - - {% if not req.ignored %} - - {% else %} - - {% endif %} -
    +
    +
    + {% csrf_token %} + + {% if not req.ignored %} + + {% else %} + + {% endif %} +
    +
    {% endif %}

    diff --git a/bookwyrm/templates/settings/invites/manage_invites.html b/bookwyrm/templates/settings/invites/manage_invites.html index 95b8bdf006..4c492b35a3 100644 --- a/bookwyrm/templates/settings/invites/manage_invites.html +++ b/bookwyrm/templates/settings/invites/manage_invites.html @@ -48,9 +48,10 @@

    {% trans "Generate New Invite" %}

    {% trans "Expires" %} {% trans "Max uses" %} {% trans "Times used" %} + {% trans "Action" %} {% if not invites %} - {% trans "No active invites" %} + {% trans "No active invites" %} {% endif %} {% for invite in invites %} @@ -58,6 +59,14 @@

    {% trans "Generate New Invite" %}

    {{ invite.expiry|naturaltime }} {{ invite.use_limit }} {{ invite.times_used }} + +
    + {% csrf_token %} + + + +
    + {% endfor %} diff --git a/bookwyrm/templates/settings/layout.html b/bookwyrm/templates/settings/layout.html index 489e77125c..b06e7b259f 100644 --- a/bookwyrm/templates/settings/layout.html +++ b/bookwyrm/templates/settings/layout.html @@ -41,7 +41,22 @@ {% url 'settings-invites' as alt_url %} {% trans "Invites" %} + {% if perms.bookwyrm.moderate_user %} +
  • + {% url 'settings-force-password-reset' as url %} + {% trans "Force Password Reset" %} +
  • + {% endif %} + + {% endif %} + {% if perms.bookwyrm.control_federation %} + +
    -{% if report.status %} +{% if report.statuses.exists %}
    -

    {% trans "Reported status" %}

    - {% if report.status.deleted %} - {% trans "Status has been deleted" %} - {% else %} - {% include 'snippets/status/status.html' with status=report.status|load_subclass moderation_mode=True %} - {% endif %} +

    {% trans "Reported statuses" %}

    + {% for status in report.statuses.all %} +
    + {% if status.deleted %} + {% trans "Status has been deleted" %} + {% else %} + {% include 'snippets/status/status.html' with status=status|load_subclass moderation_mode=True %} + {% endif %} +
    + {% endfor %}
    {% endif %} @@ -56,10 +60,10 @@

    {% trans "Reported links" %}

    {% endif %} -{% if report.user %} -{% include 'settings/users/user_info.html' with user=report.user %} +{% if report.reported_user %} +{% include 'settings/users/user_info.html' with user=report.reported_user %} -{% include 'settings/users/user_moderation_actions.html' with user=report.user %} +{% include 'settings/users/user_moderation_actions.html' with user=report.reported_user %} {% endif %}
    @@ -70,8 +74,8 @@

    {% trans "Moderation Activity" %}

  • - {% blocktrans trimmed with user=report.reporter|username user_link=report.reporter.local_path %} - {{ user}} opened this report + {% blocktrans trimmed with user=report.user|username user_link=report.user.local_path %} + {{ user }} opened this report {% endblocktrans %}

    {{ report.created_date }} diff --git a/bookwyrm/templates/settings/reports/report_header.html b/bookwyrm/templates/settings/reports/report_header.html index b77c6c6ae6..57bd110f4b 100644 --- a/bookwyrm/templates/settings/reports/report_header.html +++ b/bookwyrm/templates/settings/reports/report_header.html @@ -1,16 +1,22 @@ {% load i18n %} {% load utilities %} -{% if report.status %} +{% if report.statuses.exists %} -{% blocktrans trimmed with report_id=report.id username=report.user|username %} +{% if report.reported_user %} +{% blocktrans trimmed with report_id=report.id username=report.reported_user|username %} Report #{{ report_id }}: Status posted by @{{ username }} {% endblocktrans %} +{% else %} +{% blocktrans trimmed with report_id=report.id %} +Report #{{ report_id }} +{% endblocktrans %} +{% endif %} {% elif report.links.exists %} - {% if report.user %} - {% blocktrans trimmed with report_id=report.id username=report.user|username %} + {% if report.reported_user %} + {% blocktrans trimmed with report_id=report.id username=report.reported_user|username %} Report #{{ report_id }}: Link added by @{{ username }} {% endblocktrans %} {% else %} @@ -21,7 +27,7 @@ {% else %} -{% blocktrans trimmed with report_id=report.id username=report.user|username %} +{% blocktrans trimmed with report_id=report.id username=report.reported_user|username %} Report #{{ report_id }}: User @{{ username }} {% endblocktrans %} diff --git a/bookwyrm/templates/settings/reports/report_preview.html b/bookwyrm/templates/settings/reports/report_preview.html index bd0009c519..85eb2a1004 100644 --- a/bookwyrm/templates/settings/reports/report_preview.html +++ b/bookwyrm/templates/settings/reports/report_preview.html @@ -21,8 +21,17 @@

    {% block card-footer %} +{% if not report.reported_user.local %} + +{% endif %} diff --git a/bookwyrm/templates/settings/users/delete_user_form.html b/bookwyrm/templates/settings/users/delete_user_form.html index 267e26ae93..ab25e8c54c 100644 --- a/bookwyrm/templates/settings/users/delete_user_form.html +++ b/bookwyrm/templates/settings/users/delete_user_form.html @@ -10,14 +10,14 @@ {% csrf_token %}

    {% blocktrans trimmed with username=user.localname %} - Are you sure you want to delete {{username}}'s account? This action cannot be undone. To proceed, please enter your password to confirm deletion. + Are you sure you want to delete {{username}}'s account? This action cannot be undone. {% endblocktrans %}

    - - - - {% include 'snippets/form_errors.html' with errors_list=form.password.errors id="desc_password" %} +
    diff --git a/bookwyrm/templates/settings/users/force_password_reset.html b/bookwyrm/templates/settings/users/force_password_reset.html new file mode 100644 index 0000000000..8aa9e8302c --- /dev/null +++ b/bookwyrm/templates/settings/users/force_password_reset.html @@ -0,0 +1,61 @@ +{% extends 'settings/layout.html' %} +{% load i18n %} +{% load utilities %} +{% load humanize %} + +{% block title %} +{% trans "Force Password Reset" %} +{% endblock %} + +{% block header %} +{% trans "Force Password Reset" %} +{% endblock %} + +{% block panel %} + +

    + {% trans "All users in the selected category will be logged out and required to set a new password to log back in." %} + {% trans "If your account is in the group, you will be logged out out after submitting." %} +

    +{% if submitted %} +

    + {% trans "Users given password resets:" %} {{ force_count}} +

    +{% endif %} + +
    + {% if count != 0 and not count %} +
    +
    + +
    + +
    +
    + +
    + + {% else %} + +

    {% trans "Number of users that will be effected:" %} {{ count|intcomma }}

    +
    + {% csrf_token %} + +

    + {% trans "Are you sure you want to force password reset for these users:" %} +

    + +
    + + {% endif %} +
    + +{% endblock %} diff --git a/bookwyrm/templates/settings/users/user_info.html b/bookwyrm/templates/settings/users/user_info.html index e07a7e4396..934281d836 100644 --- a/bookwyrm/templates/settings/users/user_info.html +++ b/bookwyrm/templates/settings/users/user_info.html @@ -56,7 +56,7 @@

    {% trans "User details" %}

    {{ user.email }}
    {% endif %} - {% with report_count=user.report_set.count %} + {% with report_count=user.reported_user.count %}
    {% trans "Reports:" %}
    {{ report_count|intcomma }} diff --git a/bookwyrm/templates/shelf/shelf.html b/bookwyrm/templates/shelf/shelf.html index f80009e662..3519c1b80d 100644 --- a/bookwyrm/templates/shelf/shelf.html +++ b/bookwyrm/templates/shelf/shelf.html @@ -183,13 +183,13 @@

    {% if request.user.is_authenticated %} {% if is_self %} - + {{ book.shelved_date|naturalday }} - + {{ book.start_date|naturalday|default_if_none:""}} - + {{ book.finish_date|naturalday|default_if_none:""}} {% endif %} diff --git a/bookwyrm/templates/snippets/about.html b/bookwyrm/templates/snippets/about.html index b127662388..0b118519f5 100644 --- a/bookwyrm/templates/snippets/about.html +++ b/bookwyrm/templates/snippets/about.html @@ -6,7 +6,7 @@ BookWyrm logo

    -
    +

    {{ site.instance_description | safe }}

    diff --git a/bookwyrm/templates/snippets/block_book_button.html b/bookwyrm/templates/snippets/block_book_button.html new file mode 100644 index 0000000000..0387e7e29d --- /dev/null +++ b/bookwyrm/templates/snippets/block_book_button.html @@ -0,0 +1,12 @@ +{% load i18n %} +{% if not blocks %} +
    + {% csrf_token %} + +
    +{% else %} +
    + {% csrf_token %} + +
    +{% endif %} diff --git a/bookwyrm/templates/snippets/create_status/content_field.html b/bookwyrm/templates/snippets/create_status/content_field.html index cc4205b2ce..8f6339bece 100644 --- a/bookwyrm/templates/snippets/create_status/content_field.html +++ b/bookwyrm/templates/snippets/create_status/content_field.html @@ -12,6 +12,9 @@