From: Radek Czajka Date: Fri, 6 Nov 2026 23:47:06 +0000 (+0100) Subject: Docker and testing changes X-Git-Url: https://git.mdrn.pl/wolnelektury.git/commitdiff_plain/HEAD?hp=515610278a618c78e49ddd5410cb6a8952841685 Docker and testing changes --- diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..6d8372f6d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +**/__pycache__ +**/*~ +**/#*# +**/.#* +src/wolnelektury/localsettings.py +src/wolnelektury/dev.db + diff --git a/.gitignore b/.gitignore index 3d746f904..6c612361e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ localsettings.py +.env dev.sqlite *.db *.db-journal diff --git a/Dockerfile b/Dockerfile index b23530b9c..7d4c87540 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,32 @@ -FROM python:3.9-trixie AS base +# Wolne Lektury images. +# +# Stages marked with ##### are build targets: +# deps dev image (docker-compose.yml, code mounted) +# test docker-compose.test.yml +# prod production app image (default, last stage) +# nginx production nginx with static files +# postgres Postgres with Polish dictionary +# Stages marked with ===== are intermediate. -ARG UID=1000 -ARG GID=1000 + +# Global build arguments. Stages using UID/GID must redeclare them +# (bare `ARG UID`) to bring them into scope; the defaults are set here. + +ARG PYTHON_IMAGE=python:3.9-trixie +ARG UID=8100 +ARG GID=8100 + + +########## +# deps ############################################################# +# +# Base system and requirements. +# Used directly as the dev image, with code mounted. +# +FROM ${PYTHON_IMAGE} AS deps + +ARG UID +ARG GID RUN apt-get update && apt-get install -y \ git \ @@ -38,36 +63,111 @@ WORKDIR /app/src RUN mkdir /app/.ipython +#=======# +# app #============================================================= +# +# Code and production requirements. Not production yet: needs +# staticfiles.json manifest from static-builder. +# +FROM deps AS app +USER root -FROM base AS dev +RUN pip install --no-cache-dir gunicorn -#RUN pip install --no-cache-dir coverage +USER app +COPY src /app/src +#==================# +# static-builder #================================================== +# +# Static files are collected here. Statics are then copied into +# the nginx image, and staticfiles.json manifest is copied back +# into the test and prod images. +# +FROM app AS static-builder -FROM base AS prod +RUN python manage.py collectstatic --noinput -USER root -RUN pip install --no-cache-dir gunicorn +########### +# nginx ############################################################ +# +# Static files for production nginx. Config is mounted. +# +FROM nginx:alpine AS nginx -USER app +ARG UID +ARG GID -COPY src /app/src +# COPY nginx.conf conf.d/... +RUN addgroup -g $GID app && addgroup nginx app +COPY --from=static-builder /app/var/static /app/var/static +#===============# +# polish-dict #===================================================== +# +# Polish hunspell dictionary without diacritics, to allow +# full text search without diacritics. +# +FROM ${PYTHON_IMAGE} AS polish-dict -FROM prod AS static-builder +RUN apt-get update \ + && apt-get install -y --no-install-recommends hunspell-pl \ + && rm -rf /var/lib/apt/lists/* -RUN python manage.py collectstatic --noinput +COPY scripts/fold_hunspell.py /usr/local/bin/fold_hunspell.py +RUN set -eu; \ + mkdir /out; cd /tmp; \ + enc="$(sed -n 's/^SET[[:space:]]*//p' /usr/share/hunspell/pl_PL.aff | tr -d '\r')"; \ + iconv -f "${enc:-UTF-8}" -t UTF-8 /usr/share/hunspell/pl_PL.dic > pl.dict; \ + iconv -f "${enc:-UTF-8}" -t UTF-8 /usr/share/hunspell/pl_PL.aff > pl.affix; \ + python3 /usr/local/bin/fold_hunspell.py pl.dict pl.affix \ + /out/polish.dict /out/polish.affix; \ + dpkg-query -W -f='hunspell-pl ${Version}\n' hunspell-pl > /out/polish.version -FROM nginx:alpine AS nginx +############## +# postgres ######################################################### +# +# Postgres with the dictionary from polish-dict. +# +FROM postgres:18 AS postgres -# COPY nginx.conf conf.d/... +COPY --from=polish-dict /out/ /tmp/polish-dict/ +RUN mv /tmp/polish-dict/* "/usr/share/postgresql/$PG_MAJOR/tsearch_data/" \ + && rmdir /tmp/polish-dict -COPY --from=static-builder /app/var/static /app/var/static +COPY scripts/postgres-polish.sql /docker-entrypoint-initdb.d/10-postgres-polish.sql + + +########## +# test ############################################################# +# +# Production image plus test requirements. Defined before `prod`, +# so that `prod` stays the default (last) stage. +# +FROM app AS test + +USER root +COPY requirements/requirements-test.txt /tmp/ +RUN pip install --no-cache-dir -r /tmp/requirements-test.txt +USER app +RUN mkdir -p /app/var/counters +COPY --from=static-builder /app/var/static/staticfiles.json /app/var/static/staticfiles.json +CMD ["./manage.py", "test"] + + +########## +# prod ############################################################# +# +# Production image: app + staticfiles.json manifest. +# +FROM app AS prod + +COPY --from=static-builder /app/var/static/staticfiles.json /app/var/static/staticfiles.json diff --git a/Makefile b/Makefile index 59e222af6..3f1b3c4c9 100644 --- a/Makefile +++ b/Makefile @@ -12,15 +12,14 @@ deploy: src/wolnelektury/localsettings.py src/manage.py collectstatic --noinput -.ONESHELL: +TEST_COMPOSE := docker compose -f docker-compose.test.yml + test: - cd src - python -Wall -m coverage run --branch --source='.' ./manage.py test; true - coverage html -d ../htmlcov.new - rm -rf ../htmlcov - mv ../htmlcov.new ../htmlcov - coverage report - rm .coverage + mkdir -p htmlcov + UID=$(UID) GID=$(GID) $(TEST_COMPOSE) run --rm --build test sh -c '\ + python -Wall -m coverage run --branch --source=. ./manage.py test $(ARGS); s=$$?; \ + coverage report --skip-covered; coverage html -d /app/htmlcov; exit $$s'; \ + status=$$?; $(TEST_COMPOSE) down; exit $$status up: diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 000000000..11c669785 --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,31 @@ +# make test +# make test ARGS=catalogue + +name: wolnelektury-test + +x-env: &env + POSTGRES_USER: wl + POSTGRES_PASSWORD: wl + +services: + test: + build: + context: . + target: test + args: + UID: ${UID:-1000} + GID: ${GID:-1000} + environment: + <<: *env + SECRET_KEY: test + ALLOWED_HOSTS: "*" + COVERAGE_FILE: /tmp/.coverage + volumes: + - ./htmlcov:/app/htmlcov + depends_on: [db] + + db: + build: {context: ., target: postgres} + environment: *env + tmpfs: [/var/lib/postgresql] + diff --git a/docker-compose.yml b/docker-compose.yml index bda15bb40..55a09d874 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ x-app-base: &app-base build: context: . - target: dev + target: deps args: UID: ${UID:-1000} GID: ${GID:-1000} @@ -26,16 +26,19 @@ x-app-base: &app-base services: web: <<: *app-base - ports: - - "8000:8000" command: ./manage.py runserver 0.0.0.0:8000 + ports: + # Dev: use http://localhost:8000/ directly + - "127.0.0.1:8000:8000" celery: <<: *app-base command: celery -A wolnelektury worker -l info db: - image: postgres:18 + build: + context: . + target: postgres env_file: - .env volumes: @@ -47,6 +50,24 @@ services: memcached: image: memcached:1.6-alpine + nginx: + # Just for testing production-like environment. + # docker compose --profile nginx up + profiles: ["nginx"] + build: + context: . + target: nginx + args: + UID: ${UID:-1000} + GID: ${GID:-1000} + ports: + - "127.0.0.1:8101:80" + volumes: + - ./var/media:/app/var/media:ro + - ./scripts/nginx.conf:/etc/nginx/conf.d/default.conf:ro + depends_on: + - web + volumes: - ipython: \ No newline at end of file + ipython: diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 600ec63f3..853c411bc 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -21,7 +21,7 @@ djangorestframework-xml==2.0.0 django-admin-ordering==0.18 django-countries==7.6.1 --e git+https://github.com/rczajka/django-forms-builder@270cc22f80ec4681735b4e01689562fbab79ceda#egg=django-forms-builder +-e git+https://github.com/rczajka/django-forms-builder@6533f9442f004d44ed362202b6bdab82b73dd5e9#egg=django-forms-builder oauthlib>=3.0.1,<3.1 diff --git a/scripts/fold_hunspell.py b/scripts/fold_hunspell.py new file mode 100644 index 000000000..bc63e7873 --- /dev/null +++ b/scripts/fold_hunspell.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +""" +Tworzy wersję słownika hunspell bez polskich znaków dla PostgreSQL (szablon ispell). + +Użycie: + fold_hunspell.py polish.dict polish.affix polish_ascii.dict polish_ascii.affix + +Pliki wejściowe muszą być w UTF-8 (po iconv z ISO-8859-2). + +Samo usunięcie znaków psuje warunki reguł z klasami negowanymi: np. +`SFX H ć ło [^ąeśź]ć` (wszystko poza ą, e, ś, ź przed ć) po zamianie +stałoby się `[^aesz]c` i przestało pasować do czasowników na -ać. +Dlatego litera zostaje w klasie negowanej tylko wtedy, gdy oryginał +wykluczał wszystkie litery, które się na nią składają; w przeciwnym +razie jest usuwana, a pusta klasa zamienia się w `.`. Reguły stają się +przez to nieco zbyt liberalne (nadmiarowe formy), ale żadna forma nie ginie. +""" +import re +import sys +import unicodedata + + +def fold(text): + text = text.replace('ł', 'l').replace('Ł', 'L') + return ''.join( + c for c in unicodedata.normalize('NFKD', text) + if not unicodedata.combining(c) + ) + + +LETTERS = 'aąbcćdeęfghijklłmnńoóprsśtuvwxyzźż' +GROUPS = {} +for letter in LETTERS: + GROUPS.setdefault(fold(letter), set()).add(letter) + + +def fold_negated_class(match): + excluded = set(match.group(1)) + keep = [] + for c in match.group(1): + folded = fold(c) + if GROUPS.get(folded, {c}) <= excluded and folded not in keep: + keep.append(folded) + return '[^' + ''.join(keep) + ']' if keep else '.' + + +def fold_affix_line(line): + parts = line.split() + if len(parts) >= 5 and parts[0] in ('SFX', 'PFX') and '[^' in parts[4]: + condition = re.sub(r'\[\^([^\]]*)\]', fold_negated_class, parts[4]) + line = line.replace(parts[4], condition, 1) + if line.startswith('SET '): + return 'SET UTF-8\n' + return fold(line) + + +def main(dict_in, affix_in, dict_out, affix_out): + with open(dict_in, encoding='utf-8') as src, open(dict_out, 'w', encoding='utf-8') as dst: + dst.write(fold(src.read())) + with open(affix_in, encoding='utf-8') as src, open(affix_out, 'w', encoding='utf-8') as dst: + for line in src: + dst.write(fold_affix_line(line)) + + +if __name__ == '__main__': + main(*sys.argv[1:5]) diff --git a/scripts/nginx.conf b/scripts/nginx.conf new file mode 100644 index 000000000..561a5d4e4 --- /dev/null +++ b/scripts/nginx.conf @@ -0,0 +1,60 @@ +server { + listen 80; + + access_log off; + error_log /dev/stderr warn; + + client_max_body_size 4G; + send_timeout 300s; + proxy_buffers 16 16k; + proxy_buffer_size 32k; + + location /static/ { + root /app/var; + expires 30d; + } + + location /media/ { + root /app/var; + expires 30d; + } + location ~ ^/media/.*\.(mp3|ogg|daisy\.zip)$ { + add_header Content-Disposition "attachment"; + } + location ~ ^/media/.*\.txt$ { + add_header Content-Type "text/plain; charset=utf-8"; + } + location ~ ^/media/book/(pdf|epub|mobi|fb2|cover|xml)/ { + # kill cache + add_header Last-Modified $date_gmt; + add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; + if_modified_since off; + expires off; + etag off; + } + + location /api/ { + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE' always; + add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always; + add_header 'Access-Control-Expose-Headers' 'Link' always; + + if ($request_method = OPTIONS) { + return 204; + } + + proxy_set_header Accept-Language ""; + proxy_set_header Host $host; + proxy_pass http://web:8000; + } + location / { + root /app/var/static/root; + try_files $uri @backend; + } + + location @backend { + proxy_set_header Accept-Language ""; + proxy_set_header Host $host; + proxy_pass http://web:8000; + } +} diff --git a/scripts/postgres-polish.sql b/scripts/postgres-polish.sql new file mode 100644 index 000000000..100764abd --- /dev/null +++ b/scripts/postgres-polish.sql @@ -0,0 +1,27 @@ +CREATE EXTENSION IF NOT EXISTS unaccent; + +DO $$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_ts_dict + WHERE dictname = 'polish_hunspell' + AND dictnamespace = 'public'::regnamespace) THEN + CREATE TEXT SEARCH DICTIONARY public.polish_hunspell ( + TEMPLATE = ispell, DictFile = polish, AffFile = polish + ); + END IF; + IF NOT EXISTS (SELECT 1 FROM pg_ts_config + WHERE cfgname = 'polish' + AND cfgnamespace = 'public'::regnamespace) THEN + CREATE TEXT SEARCH CONFIGURATION public.polish (COPY = pg_catalog.simple); + END IF; +END $$; + +ALTER TEXT SEARCH DICTIONARY public.polish_hunspell ( + DictFile = polish, AffFile = polish, StopWords +); + +ALTER TEXT SEARCH CONFIGURATION public.polish + ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, word, hword, hword_part + WITH unaccent, polish_hunspell, simple; + + diff --git a/src/catalogue/models/snippet.py b/src/catalogue/models/snippet.py index 4c25b8c97..24b2d8512 100644 --- a/src/catalogue/models/snippet.py +++ b/src/catalogue/models/snippet.py @@ -26,4 +26,4 @@ class Snippet(models.Model): @classmethod def update_all(cls): - cls.objects.all().update(search_vector = build_search_vector('text')) + cls.objects.all().update(search_vector=UnaccentSearchVector('text')) diff --git a/src/catalogue/templates/catalogue/book_detail.html b/src/catalogue/templates/catalogue/book_detail.html index 4852c9a32..f87041a80 100644 --- a/src/catalogue/templates/catalogue/book_detail.html +++ b/src/catalogue/templates/catalogue/book_detail.html @@ -8,6 +8,12 @@ {% load annoy %} +{% block settings %} +{% load title %} +{% title book.pretty_title %} +{% endblock %} + + {% block global-content %}
{% annoy_banner 'book-page' %} @@ -168,11 +174,11 @@
{% if book.has_audio %} - + {% endif %}
- +
{% if first_text %} @@ -194,10 +200,9 @@ {% endif %} -
-
+
-

{% trans "Pobieranie e-booka" %}

+

{% trans "Wybierz wersję dla siebie" %}:

@@ -261,13 +266,11 @@ -
-
+ {% if book.has_audio %} -
-
+
-

{% trans "Pobieranie audiobooka" %}

+

{% trans "Wybierz wersję dla siebie" %}:

@@ -319,8 +322,7 @@ -
-
+ {% endif %}
{% else %} diff --git a/src/catalogue/templates/catalogue/book_text.html b/src/catalogue/templates/catalogue/book_text.html index 45805c4b9..ad0089a40 100644 --- a/src/catalogue/templates/catalogue/book_text.html +++ b/src/catalogue/templates/catalogue/book_text.html @@ -9,7 +9,10 @@ {% load annoy %} -{% block title %}{{ book.pretty_title }}{% endblock %} +{% block settings %} +{% load title %} +{% title book.pretty_title %} +{% endblock %} {% block extrahead %} diff --git a/src/catalogue/templatetags/catalogue_tags.py b/src/catalogue/templatetags/catalogue_tags.py index e50ab5817..29019755d 100644 --- a/src/catalogue/templatetags/catalogue_tags.py +++ b/src/catalogue/templatetags/catalogue_tags.py @@ -164,7 +164,7 @@ def book_tree_texml(book_list, books_by_parent, depth=1): %(children)s """ % { "depth": depth, - "title": book.title, + "title": book.title.replace('&', '&').replace('<', '<'), "audiences": ", ".join(book.audiences_pl()), "audiobook": "audiobook" if book.has_media('mp3') else "", "children": book_tree_texml(books_by_parent.get(book.id, ()), books_by_parent, depth + 1) diff --git a/src/catalogue/test_utils.py b/src/catalogue/test_utils.py index a14552287..6ad67c749 100644 --- a/src/catalogue/test_utils.py +++ b/src/catalogue/test_utils.py @@ -38,7 +38,7 @@ class PersonStub: class BookInfoStub: - _empty_fields = ['cover_url', 'variant_of'] + _empty_fields = ['cover_url', 'variant_of', 'license'] # allow single definition for multiple-value fields _salias = { 'authors': 'author', diff --git a/src/search/utils.py b/src/search/utils.py index 77ff1ae11..69263e707 100644 --- a/src/search/utils.py +++ b/src/search/utils.py @@ -1,31 +1,10 @@ from django.conf import settings -from django.db.models import Func -from django.contrib.postgres.search import SearchQuery, SearchVectorField +from django.contrib.postgres.search import SearchQuery, SearchVector -class UnaccentSearchQuery(SearchQuery): - ''' - The idea is to run unaccent *after* the query is already passed through the language dictionary. - ''' - def as_sql(self, *args, **kwargs): - sql, params = super().as_sql(*args, **kwargs) - if settings.SEARCH_USE_UNACCENT: - sql = f'unaccent({sql}::text)::tsquery' - return sql, params +# Deprecated +UnaccentSearchQuery = SearchQuery -class UnaccentSearchVector(Func): - ''' - We do the indexing twice, to account for non-diacritic versions. - For example: user enters 'róże' -> stem to 'róża' -> unaccent to 'roza'. - But user enters 'roze' -> stem leaves it as is, so we need original form in the vector. - ''' - function='to_tsvector' - if settings.SEARCH_USE_UNACCENT: - template = f'''unaccent( - %(function)s('{settings.SEARCH_CONFIG}', %(expressions)s)::text)::tsvector || - to_tsvector( - '{settings.SEARCH_CONFIG_SIMPLE}', - unaccent(%(expressions)s) - )''' - output_field = SearchVectorField() +def UnaccentSearchVector(expression): + return SearchVector(expression, config=settings.SEARCH_CONFIG) diff --git a/src/search/views.py b/src/search/views.py index 6066cd97a..6a9e1ea84 100644 --- a/src/search/views.py +++ b/src/search/views.py @@ -29,7 +29,7 @@ def get_hints(prefix, user=None, limit=10): data = [] if len(data) < limit: authors = catalogue.models.Tag.objects.filter( - category='author', name_pl__iregex='\m' + prefix).only('name', 'id', 'slug', 'category') + category='author', name_pl__iregex=r'\m' + prefix).only('name', 'id', 'slug', 'category') data.extend([ { 'type': 'author', @@ -44,7 +44,7 @@ def get_hints(prefix, user=None, limit=10): if user is not None and user.is_authenticated and len(data) < limit: tags = social.models.UserList.objects.filter( - user=user, name__iregex='\m' + prefix).only('name', 'id', 'slug') + user=user, name__iregex=r'\m' + prefix).only('name', 'id', 'slug') data.extend([ { 'type': 'userlist', @@ -56,7 +56,7 @@ def get_hints(prefix, user=None, limit=10): ]) if len(data) < limit: tags = catalogue.models.Tag.objects.filter( - category__in=('theme', 'genre', 'epoch', 'kind'), name_pl__iregex='\m' + prefix).only('name', 'id', 'slug', 'category') + category__in=('theme', 'genre', 'epoch', 'kind'), name_pl__iregex=r'\m' + prefix).only('name', 'id', 'slug', 'category') data.extend([ { 'type': tag.category, @@ -69,7 +69,7 @@ def get_hints(prefix, user=None, limit=10): ]) if len(data) < limit: collections = catalogue.models.Collection.objects.filter( - title_pl__iregex='\m' + prefix).only('title', 'slug') + title_pl__iregex=r'\m' + prefix).only('title', 'slug') data.extend([ { 'type': 'collection', @@ -80,7 +80,7 @@ def get_hints(prefix, user=None, limit=10): for collection in collections[:limit - len(data)] ]) if len(data) < limit: - for b in catalogue.models.Book.objects.filter(findable=True, title__iregex='\m' + prefix)[:limit-len(data)]: + for b in catalogue.models.Book.objects.filter(findable=True, title__iregex=r'\m' + prefix)[:limit-len(data)]: author_str = b.author_unicode() translator = b.translator() if translator: @@ -99,7 +99,7 @@ def get_hints(prefix, user=None, limit=10): infos = infopages.models.InfoPage.objects.filter( published=True, findable=True, - title_pl__iregex='\m' + prefix).only('title', 'id', 'slug') + title_pl__iregex=r'\m' + prefix).only('title', 'id', 'slug') data.extend([ { 'type': 'info', diff --git a/src/social/api/serializers.py b/src/social/api/serializers.py index 155c0e621..3b581a0dd 100644 --- a/src/social/api/serializers.py +++ b/src/social/api/serializers.py @@ -154,6 +154,8 @@ class UserListItemReadSerializerV3(UserListItemSerializer): class UserListItemReadSerializer(UserListItemSerializer): book = catalogue.api.serializers.BookSerializer2() bookmark = bookmarks.api.views.BookmarkSerializer() + quote = bookmarks.api.views.QuoteSerializer() + class Meta: model = models.UserListItem fields = [ diff --git a/src/stats/models.py b/src/stats/models.py index ca0b55dab..5dcd55139 100644 --- a/src/stats/models.py +++ b/src/stats/models.py @@ -23,7 +23,7 @@ class VisitsBase(models.Model): data = urlopen(url).read().decode('utf-16') lines = data.split('\n')[1:] for line in lines: - m = re.match('^/katalog/lektura/([^,]+)\.html,', line) + m = re.match(r'^/katalog/lektura/([^,]+)\.html,', line) if m is not None: slug = m.group(1) _url, uviews, views, _rest = line.split(',', 3) diff --git a/src/wolnelektury/settings/basic.py b/src/wolnelektury/settings/basic.py index 84503ed5f..50e0edd75 100644 --- a/src/wolnelektury/settings/basic.py +++ b/src/wolnelektury/settings/basic.py @@ -4,6 +4,7 @@ import os DEBUG = os.environ.get('DEBUG', '') +SECRET_KEY = os.environ.get('SECRET_KEY', None) ADMINS = [ # ('Your Name', 'your_email@domain.com'), diff --git a/src/wolnelektury/settings/test.py b/src/wolnelektury/settings/test.py index f5d70b26e..42a453e19 100644 --- a/src/wolnelektury/settings/test.py +++ b/src/wolnelektury/settings/test.py @@ -10,3 +10,11 @@ MEDIA_URL = '/media/' SEARCH_CONFIG = 'english' SEARCH_CONFIG_SIMPLE = 'simple' SEARCH_USE_UNACCENT = False + +# Infrastructure: tests don't need Redis or Memcached. +CELERY_BROKER_URL = 'memory://' +CELERY_TASK_ALWAYS_EAGER = True +CACHES = { + 'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}, + 'template_fragments': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}, +} diff --git a/src/wolnelektury/static/2022/styles/components/_media.scss b/src/wolnelektury/static/2022/styles/components/_media.scss index 6b15f8044..714638e25 100644 --- a/src/wolnelektury/static/2022/styles/components/_media.scss +++ b/src/wolnelektury/static/2022/styles/components/_media.scss @@ -121,38 +121,19 @@ } } -.c-media__popup { - position: fixed; - z-index: 10; - top: 0; - left: 0; - height: 100%; - align-items: flex-start; - justify-content: center; - background-color: rgba($color-black, 0.35); - display: none; - overflow-y: scroll; - padding: 10vh 16px 10px 10px; - width: 100%; - - @include rwd($break-flow) { - padding: 10vh 40px 40px 40px; - } - - &.is-open { - display: flex; - } -} - .c-media__popup__box { - background-color: #fff; - position: relative; - border-radius: 10px; - overflow: hidden; + padding: 0; + border: 0; + background: none; + border-radius: 10px; - @include rwd($break-flow) { - min-width: 640px; - } + @include rwd($break-flow) { + min-width: 640px; + } + + &::backdrop { + background-color: rgba($color-black, 0.35); + } } .c-media__popup__box__lead { @@ -183,6 +164,7 @@ } .c-media__popup__box__items { + background: white; padding: 16px; @include rwd($break-flow) { padding: 40px; @@ -266,6 +248,7 @@ } .c-media__popup__close { + --focus-ring: #fff; position: absolute; padding: 0; border: 0; diff --git a/src/wolnelektury/static/2022/styles/layout/_books.scss b/src/wolnelektury/static/2022/styles/layout/_books.scss index e14adbfac..74106ffec 100644 --- a/src/wolnelektury/static/2022/styles/layout/_books.scss +++ b/src/wolnelektury/static/2022/styles/layout/_books.scss @@ -28,7 +28,6 @@ padding: 21px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0); transition: all $ease-dynamic 350ms; - outline: 0; margin: 5px; &.l-books__item--placeholder { @@ -36,10 +35,6 @@ visibility: hidden; } - & * { - outline: 0; - } - &:hover { box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25); } @@ -55,6 +50,7 @@ line-height: 140%; letter-spacing: -0.01em; color: #FFFFFF; + --focus-ring: #fff; display: flex; flex-direction: column; @@ -106,8 +102,11 @@ font-size: 0; margin: 0 0 11px; - img { - width: 100%; + a { + display: block; + img { + width: 100%; + } } } @@ -181,7 +180,6 @@ border-radius: 52px; border: 1px solid #D9D9D9; width: 100%; - outline: 0; transition: border $ease-out 350ms; @input rwd($break-flow) { @@ -229,7 +227,6 @@ padding: 0; margin: 0 0 0 30px; background-color: transparent; - outline: 0; font-weight: $regular; @include font-size(18px); line-height: 150%; diff --git a/src/wolnelektury/static/2022/styles/layout/_footer.scss b/src/wolnelektury/static/2022/styles/layout/_footer.scss index fd4a53ad9..eddcf33f1 100644 --- a/src/wolnelektury/static/2022/styles/layout/_footer.scss +++ b/src/wolnelektury/static/2022/styles/layout/_footer.scss @@ -1,4 +1,5 @@ .l-footer { + --focus-ring: #fff; color: #FFFFFF; background: #083F4D; margin: 0 auto; diff --git a/src/wolnelektury/static/2022/styles/layout/_navigation.scss b/src/wolnelektury/static/2022/styles/layout/_navigation.scss index 9e3aafc54..ece84d5b2 100644 --- a/src/wolnelektury/static/2022/styles/layout/_navigation.scss +++ b/src/wolnelektury/static/2022/styles/layout/_navigation.scss @@ -29,24 +29,6 @@ body { align-items: center; justify-content: space-between; } - - &.is-open { - position: fixed; - top: 0; left: 0; - width: 100%; - z-index: $master-layer; - - .l-navigation__logo { - width: unset; - overflow: unset; - } - - .l-navigation__menu { - opacity: 1; - visibility: visible; - pointer-events: auto; - } - } } .l-navigation__logo { @@ -342,12 +324,8 @@ body:has(dialog[open]) { gap: 3%; } - ul { - margin: 0; - padding: 0; - list-style: none; - //max-width: 212px; - width: 45%; + .l-navigation__menu__group { + width: 45%; @include rwd($break-menu) { width: 100%; @@ -355,6 +333,19 @@ body:has(dialog[open]) { margin-bottom: 20px; } + h2 { + margin: 16px 0 0 0; + color: #92BD39; + font-weight: $semibold; + @include font-size(21px); + line-height: 140%; + letter-spacing: -0.01em; + } + + ul { + margin: 0; + padding: 0; + list-style: none; li { font-weight: $regular; font-size: 16px; @@ -366,14 +357,6 @@ body:has(dialog[open]) { line-height: 115%; } - strong { - color: #92BD39; - font-weight: $semibold; - @include font-size(21px); - line-height: 140%; - letter-spacing: -0.01em; - } - a { color: $color-white; &:hover { @@ -395,6 +378,7 @@ body:has(dialog[open]) { border: 0; } } + } } } diff --git a/src/wolnelektury/static/2022/styles/layout/_quotes.scss b/src/wolnelektury/static/2022/styles/layout/_quotes.scss index fe38f2ac7..e98c6915d 100644 --- a/src/wolnelektury/static/2022/styles/layout/_quotes.scss +++ b/src/wolnelektury/static/2022/styles/layout/_quotes.scss @@ -19,7 +19,6 @@ } .l-quotes__item { - outline: 0; background: #E1F1F2; border-radius: 10px; width: 558px; diff --git a/src/wolnelektury/static/2022/styles/layout/_your-books.scss b/src/wolnelektury/static/2022/styles/layout/_your-books.scss index 98570d4c3..bab0d0a6b 100644 --- a/src/wolnelektury/static/2022/styles/layout/_your-books.scss +++ b/src/wolnelektury/static/2022/styles/layout/_your-books.scss @@ -30,7 +30,6 @@ } button { - outline: 0; width: 40px; height: 40px; background: transparent; diff --git a/src/wolnelektury/static/js/main.js b/src/wolnelektury/static/js/main.js index b646bf450..5b7f897d7 100644 --- a/src/wolnelektury/static/js/main.js +++ b/src/wolnelektury/static/js/main.js @@ -34,34 +34,26 @@ // Ebook/Audiobook Btns (function() { - let button = $('.c-media__btn button:not(.l-button--media--full)'); - let popupLayer = $('.c-media__popup'); - let closeButton = $('.c-media__popup__close'); + let dialogs = $(".l-button--dialog"); + dialogs.on('click', function () { + let target = $(this).attr('id'); + $('[data-popup=' + target)[0].showModal(); + }); + + $('.c-media__popup__close').on('click', function() { + $(this).closest('dialog')[0].close(); + }); +})(); + + +// Player +(function() { let playButton = $('.c-player__btn--md'); let chaptersButton = $('.c-player__chapters span'); let select = $('.c-select'); let selectItem = $('.c-select li'); let volumeButton = $('.icon-volume'); - button.on('click', function () { - let target = $(this).attr('id'); - $('[data-popup=' + target).addClass('is-open'); - $('body').addClass('popup-open'); - }); - - closeButton.on('click', function() { - $(this).closest('.c-media__popup').removeClass('is-open'); - $('body').removeClass('popup-open'); - }); - - popupLayer.on('click', function(e) { - let _this = $(this); - if($(e.target).is(popupLayer)) { - _this.removeClass('is-open'); - $('body').removeClass('popup-open'); - } - }); - chaptersButton.on('click', function() { $(this).parent().toggleClass('is-active'); }); @@ -74,12 +66,6 @@ selectItem.removeClass('is-active'); $(this).addClass('is-active'); }); - - $(document).keyup(function(e) { - if (e.keyCode === 27) { - $('.c-media__popup').removeClass('is-open'); - } - }); })(); // Homepage books sliders @@ -383,7 +369,7 @@ updateLiked($btn); }, error: function() { - window.location.href = $('#login-link').attr('href'); + window.location.href = $('.login-link').attr('href'); } }) } else { @@ -396,7 +382,7 @@ updateLiked($btn); }, error: function() { - window.location.href = $('#login-link').attr('href') + window.location.href = $('.login-link').attr('href') } }); } diff --git a/src/wolnelektury/templates/header.html b/src/wolnelektury/templates/header.html index 7b257b354..9bb3b1521 100644 --- a/src/wolnelektury/templates/header.html +++ b/src/wolnelektury/templates/header.html @@ -17,8 +17,8 @@ WolneLektury.pl