Merge branch 'stable' into develop

environments/review-front-wvff-cfe5gn/deployments/13838
Marcos Peña 2022-09-12 11:37:18 +02:00
commit 875a6ba2df
13 zmienionych plików z 238 dodań i 57 usunięć

Wyświetl plik

@ -10,6 +10,65 @@ This changelog is viewable on the web at https://docs.funkwhale.audio/changelog.
.. towncrier .. towncrier
1.2.8 (2022-09-12)
------------------
Upgrade instructions are available at
https://docs.funkwhale.audio/admin/upgrading.html
Features:
- Add Sentry SDK to collect errors at the backend
Bugfixes:
- Fix exponentially growing database when using in-place-imports on a regular base #1676
- Fix navigating to registration request not showing anything (#1836)
- Fix player cover image overlapping queue list
- Fixed metadata handling for Various Artists albums (#1201)
- Fixed search behaviour in radio builder's filters (#733)
- Fixed unpredictable subsonic search3 results (#1782)
Committers:
- Ciarán Ainsworth
- Georg Krause
- Marcos Peña
- Mathias Koehler
- wvffle
Contributors to our Issues:
- AMoonRabbit
- Agate
- Ciarán Ainsworth
- Georg Krause
- JuniorJPDJ
- Kasper Seweryn
- Kelvin Hammond
- Marcos Peña
- Meliurwen
- Micha Gläß-Stöcker
- Miv2nir
- Sam Birch
- Tolriq
- Tony Wasserka
- f1reflyyyylmao
- heyarne
- petitminion
- troll
Contributors to our Merge Requests:
- Ciarán Ainsworth
- Georg Krause
- JuniorJPDJ
- Kasper Seweryn
- Marcos Peña
- interru
1.2.7 (2022-07-14) 1.2.7 (2022-07-14)
------------------ ------------------

Wyświetl plik

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
__version__ = "1.2.7" __version__ = "1.2.8"
__version_info__ = tuple( __version_info__ = tuple(
[ [
int(num) if num.isdigit() else num int(num) if num.isdigit() else num

Wyświetl plik

@ -187,7 +187,7 @@ def order_for_search(qs, field):
When searching, it's often more useful to have short results first, When searching, it's often more useful to have short results first,
this function will order the given qs based on the length of the given field this function will order the given qs based on the length of the given field
""" """
return qs.annotate(__size=models.functions.Length(field)).order_by("__size") return qs.annotate(__size=models.functions.Length(field)).order_by("__size", "pk")
def recursive_getattr(obj, key, permissive=False): def recursive_getattr(obj, key, permissive=False):

Wyświetl plik

@ -0,0 +1,35 @@
from django.core.management.base import BaseCommand
from django.db import transaction
from funkwhale_api.music import models
class Command(BaseCommand):
help = """
This command makes it easy to prune all skipped Uploads from the database.
Due to a bug they might caused the database to grow exponentially,
especially when using in-place-imports on a regular basis. This command
helps to clean up the database again.
"""
def add_arguments(self, parser):
parser.add_argument(
"--force",
default=False,
help="Disable dry run mode and apply pruning for real on the database",
)
@transaction.atomic
def handle(self, *args, **options):
skipped = models.Uploads.objects.filter(import_status="skipped")
count = len(skipped)
if options["force"]:
skipped.delete()
print(f"Deleted {count} entries from the database.")
return
print(
f"Would delete {count} entries from the database.\
Run with --force to actually apply changes to the database"
)

Wyświetl plik

@ -480,8 +480,8 @@ class ArtistField(serializers.Field):
def get_value(self, data): def get_value(self, data):
if self.for_album: if self.for_album:
keys = [ keys = [
("artists", "artists"), ("artists", "album_artist"),
("names", "album_artist"), ("names", "artists"),
("mbids", "musicbrainz_albumartistid"), ("mbids", "musicbrainz_albumartistid"),
] ]
else: else:
@ -525,7 +525,14 @@ class ArtistField(serializers.Field):
if separator in data["artists"]: if separator in data["artists"]:
names = [n.strip() for n in data["artists"].split(separator)] names = [n.strip() for n in data["artists"].split(separator)]
break break
if not names: # corner case: 'album artist' field with only one artist but multiple names in 'artits' field
if (
not names
and data.get("names", None)
and any(separator in data["names"] for separator in separators)
):
names = [n.strip() for n in data["names"].split(separators[0])]
elif not names:
names = [data["artists"]] names = [data["artists"]]
elif used_separator and mbids: elif used_separator and mbids:
names = [n.strip() for n in data["names"].split(used_separator)] names = [n.strip() for n in data["names"].split(used_separator)]

Wyświetl plik

@ -284,7 +284,9 @@ def process_upload(upload, update_denormalization=True):
upload.import_status = "skipped" upload.import_status = "skipped"
upload.import_details = { upload.import_details = {
"code": "already_imported_in_owned_libraries", "code": "already_imported_in_owned_libraries",
"duplicates": list(owned_duplicates), # In order to avoid exponential growth of the database, we only
# reference the first known upload which gets duplicated
"duplicates": owned_duplicates[0],
} }
upload.import_date = timezone.now() upload.import_date = timezone.now()
upload.save( upload.save(
@ -436,6 +438,7 @@ def get_owned_duplicates(upload, track):
) )
.exclude(pk=upload.pk) .exclude(pk=upload.pk)
.values_list("uuid", flat=True) .values_list("uuid", flat=True)
.order_by("creation_date")
) )

Wyświetl plik

@ -2,7 +2,7 @@ import collections
import persisting_theory import persisting_theory
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db.models import Q from django.db.models import Q, functions
from django.urls import reverse_lazy from django.urls import reverse_lazy
from funkwhale_api.music import models from funkwhale_api.music import models
@ -132,9 +132,13 @@ class ArtistFilter(RadioFilter):
"name": "ids", "name": "ids",
"type": "list", "type": "list",
"subtype": "number", "subtype": "number",
"autocomplete": reverse_lazy("api:v1:artists-list"), "autocomplete": reverse_lazy("api:v1:search"),
"autocomplete_qs": "q={query}", "autocomplete_qs": "q={query}",
"autocomplete_fields": {"name": "name", "value": "id"}, "autocomplete_fields": {
"remoteValues": "artists",
"name": "name",
"value": "id",
},
"label": "Artist", "label": "Artist",
"placeholder": "Select artists", "placeholder": "Select artists",
} }
@ -145,7 +149,8 @@ class ArtistFilter(RadioFilter):
filter_config["ids"] = sorted(filter_config["ids"]) filter_config["ids"] = sorted(filter_config["ids"])
names = ( names = (
models.Artist.objects.filter(pk__in=filter_config["ids"]) models.Artist.objects.filter(pk__in=filter_config["ids"])
.order_by("id") .annotate(__size=functions.Length("name"))
.order_by("__size", "id")
.values_list("name", flat=True) .values_list("name", flat=True)
) )
filter_config["names"] = list(names) filter_config["names"] = list(names)
@ -176,13 +181,13 @@ class TagFilter(RadioFilter):
"name": "names", "name": "names",
"type": "list", "type": "list",
"subtype": "string", "subtype": "string",
"autocomplete": reverse_lazy("api:v1:tags-list"), "autocomplete": reverse_lazy("api:v1:search"),
"autocomplete_fields": { "autocomplete_fields": {
"remoteValues": "results", "remoteValues": "tags",
"name": "name", "name": "name",
"value": "name", "value": "name",
}, },
"autocomplete_qs": "q={query}&ordering=length", "autocomplete_qs": "q={query}",
"label": "Tags", "label": "Tags",
"placeholder": "Select tags", "placeholder": "Select tags",
} }
@ -196,3 +201,28 @@ class TagFilter(RadioFilter):
| Q(artist__tagged_items__tag__name__in=names) | Q(artist__tagged_items__tag__name__in=names)
| Q(album__tagged_items__tag__name__in=names) | Q(album__tagged_items__tag__name__in=names)
) )
def clean_config(self, filter_config):
filter_config = super().clean_config(filter_config)
filter_config["names"] = sorted(filter_config["names"])
names = (
models.tags_models.Tag.objects.filter(name__in=filter_config["names"])
.annotate(__size=functions.Length("name"))
.order_by("__size", "pk")
.values_list("name", flat=True)
)
filter_config["names"] = list(names)
return filter_config
def validate(self, config):
super().validate(config)
try:
names = models.tags_models.Tag.objects.filter(
name__in=config["names"]
).values_list("name", flat=True)
diff = set(config["names"]) - set(names)
assert len(diff) == 0
except KeyError:
raise ValidationError("You must provide a name")
except AssertionError:
raise ValidationError('No tag matching names "{}"'.format(diff))

Wyświetl plik

@ -566,7 +566,7 @@ class SubsonicViewSet(viewsets.GenericViewSet):
except (TypeError, KeyError, ValueError): except (TypeError, KeyError, ValueError):
size = 20 size = 20
size = min(size, 100) size = min(size, 500)
queryset = c["queryset"] queryset = c["queryset"]
if query: if query:
queryset = c["queryset"].filter( queryset = c["queryset"].filter(

Wyświetl plik

@ -452,7 +452,7 @@ def test_upload_import_skip_existing_track_in_own_library(factories, temp_signal
assert duplicate.import_status == "skipped" assert duplicate.import_status == "skipped"
assert duplicate.import_details == { assert duplicate.import_details == {
"code": "already_imported_in_owned_libraries", "code": "already_imported_in_owned_libraries",
"duplicates": [str(existing.uuid)], "duplicates": str(existing.uuid),
} }
handler.assert_called_once_with( handler.assert_called_once_with(

Wyświetl plik

@ -0,0 +1,37 @@
from funkwhale_api.radios import filters
def test_clean_config_artist_name_sorting(factories):
artist3 = factories["music.Artist"](name="The Green Eyes")
artist2 = factories["music.Artist"](name="The Green Eyed Machine")
artist1 = factories["music.Artist"](name="The Green Seed")
factories["music.Artist"]()
filter_config = {"type": "artist", "ids": [artist3.pk, artist1.pk, artist2.pk]}
artist_filter = filters.ArtistFilter()
config = artist_filter.clean_config(filter_config)
# list of names whose artists have been sorted by name then by id
sorted_names = [
a.name
for a in list(
sorted([artist2, artist1, artist3], key=lambda x: (len(x.name), x.id))
)
]
assert config["names"] == sorted_names
def test_clean_config_tag_name_sorting(factories):
tag3 = factories["tags.Tag"](name="Rock")
tag2 = factories["tags.Tag"](name="Classic")
tag1 = factories["tags.Tag"](name="Punk")
factories["tags.Tag"]()
filter_config = {"type": "tag", "names": [tag3.name, tag1.name, tag2.name]}
tag_filter = filters.TagFilter()
config = tag_filter.clean_config(filter_config)
# list of names whose tags have been sorted by name then by id
sorted_names = [
a.name
for a in list(sorted([tag2, tag1, tag3], key=lambda x: (len(x.name), x.id)))
]
assert config["names"] == sorted_names

77
docs/poetry.lock wygenerowano
Wyświetl plik

@ -15,10 +15,10 @@ optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
[package.extras] [package.extras]
tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
[[package]] [[package]]
name = "babel" name = "Babel"
version = "2.10.3" version = "2.10.3"
description = "Internationalization utilities" description = "Internationalization utilities"
category = "main" category = "main"
@ -30,7 +30,7 @@ pytz = ">=2015.7"
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2022.6.15" version = "2022.6.15.1"
description = "Python package for providing Mozilla's CA Bundle." description = "Python package for providing Mozilla's CA Bundle."
category = "main" category = "main"
optional = false optional = false
@ -38,7 +38,7 @@ python-versions = ">=3.6"
[[package]] [[package]]
name = "charset-normalizer" name = "charset-normalizer"
version = "2.1.0" version = "2.1.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main" category = "main"
optional = false optional = false
@ -67,8 +67,8 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]] [[package]]
name = "django" name = "Django"
version = "4.0.5" version = "4.0.6"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
category = "main" category = "main"
optional = false optional = false
@ -121,7 +121,7 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]] [[package]]
name = "jinja2" name = "Jinja2"
version = "3.1.2" version = "3.1.2"
description = "A very fast and expressive template engine." description = "A very fast and expressive template engine."
category = "main" category = "main"
@ -156,7 +156,7 @@ code_style = ["pre-commit (==2.6)"]
benchmarking = ["pytest-benchmark (>=3.2,<4.0)", "pytest", "psutil"] benchmarking = ["pytest-benchmark (>=3.2,<4.0)", "pytest", "psutil"]
[[package]] [[package]]
name = "markupsafe" name = "MarkupSafe"
version = "2.1.1" version = "2.1.1"
description = "Safely add untrusted strings to HTML/XML markup." description = "Safely add untrusted strings to HTML/XML markup."
category = "main" category = "main"
@ -181,7 +181,7 @@ code_style = ["pre-commit (==2.6)"]
[[package]] [[package]]
name = "mdurl" name = "mdurl"
version = "0.1.1" version = "0.1.2"
description = "Markdown URL utilities" description = "Markdown URL utilities"
category = "main" category = "main"
optional = false optional = false
@ -222,13 +222,16 @@ python-versions = ">=3.6"
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]] [[package]]
name = "pygments" name = "Pygments"
version = "2.12.0" version = "2.13.0"
description = "Pygments is a syntax highlighting package written in Python." description = "Pygments is a syntax highlighting package written in Python."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
[package.extras]
plugins = ["importlib-metadata"]
[[package]] [[package]]
name = "pyparsing" name = "pyparsing"
version = "3.0.9" version = "3.0.9"
@ -238,18 +241,18 @@ optional = false
python-versions = ">=3.6.8" python-versions = ">=3.6.8"
[package.extras] [package.extras]
diagrams = ["railroad-diagrams", "jinja2"] diagrams = ["jinja2", "railroad-diagrams"]
[[package]] [[package]]
name = "pytz" name = "pytz"
version = "2022.1" version = "2022.2.1"
description = "World timezone definitions, modern and historical" description = "World timezone definitions, modern and historical"
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
[[package]] [[package]]
name = "pyyaml" name = "PyYAML"
version = "6.0" version = "6.0"
description = "YAML parser and emitter for Python" description = "YAML parser and emitter for Python"
category = "main" category = "main"
@ -444,7 +447,7 @@ optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
[package.extras] [package.extras]
lint = ["flake8", "mypy", "docutils-stubs"] lint = ["docutils-stubs", "flake8", "mypy"]
test = ["pytest"] test = ["pytest"]
[[package]] [[package]]
@ -465,7 +468,7 @@ python-versions = ">=3.7"
[[package]] [[package]]
name = "tzdata" name = "tzdata"
version = "2022.1" version = "2022.2"
description = "Provider of IANA time zone data" description = "Provider of IANA time zone data"
category = "main" category = "main"
optional = false optional = false
@ -477,11 +480,11 @@ version = "1.26.11"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.5.*, <4"
[package.extras] [package.extras]
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[metadata] [metadata]
@ -498,13 +501,13 @@ asgiref = [
{file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"},
{file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"},
] ]
babel = [ Babel = [
{file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"},
{file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"},
] ]
certifi = [ certifi = [
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, {file = "certifi-2022.6.15.1-py3-none-any.whl", hash = "sha256:43dadad18a7f168740e66944e4fa82c6611848ff9056ad910f8f7a3e46ab89e0"},
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, {file = "certifi-2022.6.15.1.tar.gz", hash = "sha256:cffdcd380919da6137f76633531a5817e3a9f268575c128249fb637e4f9e73fb"},
] ]
charset-normalizer = [ charset-normalizer = [
{file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"}, {file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"},
@ -518,9 +521,9 @@ colorama = [
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
] ]
django = [ Django = [
{file = "Django-4.0.5-py3-none-any.whl", hash = "sha256:502ae42b6ab1b612c933fb50d5ff850facf858a4c212f76946ecd8ea5b3bf2d9"}, {file = "Django-4.0.6-py3-none-any.whl", hash = "sha256:ca54ebedfcbc60d191391efbf02ba68fb52165b8bf6ccd6fe71f098cac1fe59e"},
{file = "Django-4.0.5.tar.gz", hash = "sha256:f7431a5de7277966f3785557c3928433347d998c1e6459324501378a291e5aab"}, {file = "Django-4.0.6.tar.gz", hash = "sha256:a67a793ff6827fd373555537dca0da293a63a316fe34cb7f367f898ccca3c3ae"},
] ]
django-environ = [ django-environ = [
{file = "django-environ-0.9.0.tar.gz", hash = "sha256:bff5381533056328c9ac02f71790bd5bf1cea81b1beeb648f28b81c9e83e0a21"}, {file = "django-environ-0.9.0.tar.gz", hash = "sha256:bff5381533056328c9ac02f71790bd5bf1cea81b1beeb648f28b81c9e83e0a21"},
@ -538,7 +541,7 @@ imagesize = [
{file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"},
{file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"},
] ]
jinja2 = [ Jinja2 = [
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
] ]
@ -546,7 +549,7 @@ markdown-it-py = [
{file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"}, {file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"},
{file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"}, {file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"},
] ]
markupsafe = [ MarkupSafe = [
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
@ -593,8 +596,8 @@ mdit-py-plugins = [
{file = "mdit_py_plugins-0.3.0-py3-none-any.whl", hash = "sha256:b1279701cee2dbf50e188d3da5f51fee8d78d038cdf99be57c6b9d1aa93b4073"}, {file = "mdit_py_plugins-0.3.0-py3-none-any.whl", hash = "sha256:b1279701cee2dbf50e188d3da5f51fee8d78d038cdf99be57c6b9d1aa93b4073"},
] ]
mdurl = [ mdurl = [
{file = "mdurl-0.1.1-py3-none-any.whl", hash = "sha256:6a8f6804087b7128040b2fb2ebe242bdc2affaeaa034d5fc9feeed30b443651b"}, {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
{file = "mdurl-0.1.1.tar.gz", hash = "sha256:f79c9709944df218a4cdb0fcc0b0c7ead2f44594e3e84dc566606f04ad749c20"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
] ]
myst-parser = [ myst-parser = [
{file = "myst-parser-0.18.0.tar.gz", hash = "sha256:739a4d96773a8e55a2cacd3941ce46a446ee23dcd6b37e06f73f551ad7821d86"}, {file = "myst-parser-0.18.0.tar.gz", hash = "sha256:739a4d96773a8e55a2cacd3941ce46a446ee23dcd6b37e06f73f551ad7821d86"},
@ -604,19 +607,19 @@ packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
] ]
pygments = [ Pygments = [
{file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"},
{file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"},
] ]
pyparsing = [ pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
] ]
pytz = [ pytz = [
{file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, {file = "pytz-2022.2.1-py2.py3-none-any.whl", hash = "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197"},
{file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, {file = "pytz-2022.2.1.tar.gz", hash = "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"},
] ]
pyyaml = [ PyYAML = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
@ -712,8 +715,8 @@ typing-extensions = [
{file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
] ]
tzdata = [ tzdata = [
{file = "tzdata-2022.1-py2.py3-none-any.whl", hash = "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9"}, {file = "tzdata-2022.2-py2.py3-none-any.whl", hash = "sha256:c3119520447d68ef3eb8187a55a4f44fa455f30eb1b4238fa5691ba094f2b05b"},
{file = "tzdata-2022.1.tar.gz", hash = "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"}, {file = "tzdata-2022.2.tar.gz", hash = "sha256:21f4f0d7241572efa7f7a4fdabb052e61b55dc48274e6842697ccdf5253e5451"},
] ]
urllib3 = [ urllib3 = [
{file = "urllib3-1.26.11-py2.py3-none-any.whl", hash = "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc"}, {file = "urllib3-1.26.11-py2.py3-none-any.whl", hash = "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc"},

Wyświetl plik

@ -123,8 +123,10 @@
top: 0; top: 0;
width: 32%; width: 32%;
> img { > img {
height: 50vh; width: 100%;
width: 50vh; height: auto;
max-height: 50vh;
max-width: 50vh;
} }
@include media("<desktop") { @include media("<desktop") {
padding: 0.5em; padding: 0.5em;

Wyświetl plik

@ -233,6 +233,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56"
integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==
"@babel/helper-string-parser@^7.18.10":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56"
integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==
"@babel/helper-validator-identifier@^7.18.6": "@babel/helper-validator-identifier@^7.18.6":
version "7.18.6" version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
@ -938,6 +943,7 @@
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600"
integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==
dependencies: dependencies:
"@babel/helper-string-parser" "^7.18.10"
"@babel/helper-string-parser" "^7.18.10" "@babel/helper-string-parser" "^7.18.10"
"@babel/helper-validator-identifier" "^7.18.6" "@babel/helper-validator-identifier" "^7.18.6"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
@ -3499,7 +3505,7 @@ get-func-name@^2.0.0:
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==
get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.2: get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.2, get-intrinsic@^1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598"
integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==
@ -3556,9 +3562,8 @@ glob-parent@^6.0.1:
fs.realpath "^1.0.0" fs.realpath "^1.0.0"
inflight "^1.0.4" inflight "^1.0.4"
inherits "2" inherits "2"
minimatch "^3.1.1" minimatch "^5.0.1"
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.0"
globals@^11.1.0: globals@^11.1.0:
version "11.12.0" version "11.12.0"