# ide config
.idea/
+.vscode/
+
+target
-language: rust
-rust:
- - stable
-matrix:
- allow_failures:
- - rust: nightly
- fast_finish: true
-cache: cargo
-before_cache:
- - rm -rfv target/debug/incremental/lemmy_server-*
- - rm -rfv target/debug/.fingerprint/lemmy_server-*
- - rm -rfv target/debug/build/lemmy_server-*
- - rm -rfv target/debug/deps/lemmy_server-*
- - rm -rfv target/debug/lemmy_server.d
-before_script:
- - psql -c "create user lemmy with password 'password' superuser;" -U postgres
- - psql -c 'create database lemmy with owner lemmy;' -U postgres
- - rustup component add clippy --toolchain stable-x86_64-unknown-linux-gnu
-before_install:
- - cd server
-script:
- # Default checks, but fail if anything is detected
- - cargo build
- - cargo clippy -- -D clippy::style -D clippy::correctness -D clippy::complexity -D clippy::perf
- - cargo install diesel_cli --no-default-features --features postgres --force
- - diesel migration run
- - cargo test --workspace
+sudo: required
+language: node_js
+node_js:
+- 14
+services:
+- docker
env:
+ matrix:
+ - DOCKER_COMPOSE_VERSION=1.25.5
global:
- - DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy
- - LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy
- - RUST_TEST_THREADS=1
-
-addons:
- postgresql: "9.4"
+ - secure: nzmFoTxPn7OT+qcTULezSCT6B44j/q8RxERBQSr1FVXaCcDrBr6q9ewhGy7BHWP74r4qbif4m9r3sNELZCoFYFP3JwLnrZfX/xUwU8p61eFD2PMOJAdOywDxb94SvooOSnjBmxNvRsuqf6Zmnw378mbsSVCi9Xbx9jpoV4Jq8zKgO0M8WIl/lj2dijD95WIMrHcorbzKS3+2zW3LkPiC2bnfDAUmUDfaCj1gh9FCvzZMtrSxu7kxAeFCkR16TJUciIcGgag8rLHfxwG0h2uEJJ+3/62qCWUdgnj171oTE4ZRi0hdvt2HOY5wjHfS2y1ZxWYgo31uws3pyoTNeQZi0o7Q9Xe/4JXYZXvDfuscSZ9RiuhAstCVswtXPJJVVJQ9cdl5eX1TI0bz8eVRvRy4p40OIBjKiobkmRjl8sXjFbpYAIvFr+TgSa/K/bxm3POfI0B8bIHI85zFxUMrWt5i2IJ0dWvDNHrz+CWWKn1vVFYbBNPgDDHtE0P3LWLEioWFf+ULycjW8DefWc+b63Lf9SSaEE7FnX2mc+BaHCgubCDkJy9Au4xP8zQlJjgZwOdTedw5jvmwz3fqMZBpHypVUXzZs7cRhMWtQ7TAoGb8TOqXNgPEVW+BARNXl0wAamTgjt9v20x0wkp+/SLJwMNY+zvwmzxzd5R9TPgDOqyIRTU=
+ - secure: ALZqC4OYV315P7EZyk+c/PLJdneeU7jMC30TTzMcX3hospIu7naWekZ+HUnziFDQKZxIHWKZsq1R52DWhsERLrPF3SVa+QiXu8vTTPrETBWnu9VgyFzgdEbUKRas1X3qerEAHcNBms1EAl2FOiQM1k5EDygrClv4KWgyzntEtKJbN2UCFKxtoBSdMZA6fcGtCwffcj8uIAIP2NhZixbU+smVgVbpMpe6QEuuEoVlVrfH8iXxb8Gi+qkd0YIYAHkjtTqQ/nHuAUhcuEE0mORTNGPv7CmTwpuQiGCCdtySZc7Qq8z1x2y7RLy0+RVxM0PR8UV6iy4ipyTgZ6wTF30ksLDxOI3GlRaKF3F6kLErOiEiEUOqa+zLgUM0OLGTn+KLATQDx74in5NcKjKUAnkuxdZyuDbifvQb5tqfrGdXd22pzVZbielRJRW59ig0Nr5cxEpRtoRkoFKNk7o3XlD6JmIBjKn1UHkZ4H/oLUKIXT2qOP2fIEzgLjfpSuGwhvJRz1KRP49HYVl7Gkd45/RdZ519W0gnMkIrEaod90iXSFNTgmJTGeH0Mv0jHameN47PIT3c49MOy5Hj0XCHUPfc6qqrdGnliS5hTnrFThCfn5ZuSZxVdgGLJUQvV+D+5KDqjFdGyNGVGoEg0YdrDtGXmpojbyQDJAT7ToL3yIBF7co=
+before_install:
+# Install docker-compose
+- sudo rm /usr/local/bin/docker-compose
+- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname
+ -s`-`uname -m` > docker-compose
+- chmod +x docker-compose
+- sudo mv docker-compose /usr/local/bin
+# Change dir
+- cd docker/travis
+script:
+- "./run-tests.sh"
+deploy:
+ provider: script
+ script: bash docker_push.sh
+ on:
+ tags: true
<div align="center">
![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/LemmyNet/lemmy.svg)
-[![Build Status](https://travis-ci.org/LemmyNet/lemmy.svg?branch=master)](https://travis-ci.org/LemmyNet/lemmy)
+[![Build Status](https://travis-ci.org/LemmyNet/lemmy.svg?branch=main)](https://travis-ci.org/LemmyNet/lemmy)
[![GitHub issues](https://img.shields.io/github/issues-raw/LemmyNet/lemmy.svg)](https://github.com/LemmyNet/lemmy/issues)
[![Docker Pulls](https://img.shields.io/docker/pulls/dessalines/lemmy.svg)](https://cloud.docker.com/repository/docker/dessalines/lemmy/)
[![Translation status](http://weblate.yerbamate.dev/widgets/lemmy/-/lemmy/svg-badge.svg)](http://weblate.yerbamate.dev/engage/lemmy/)
·
<a href="https://github.com/LemmyNet/lemmy/issues">Request Feature</a>
·
- <a href="https://github.com/LemmyNet/lemmy/blob/master/RELEASES.md">Releases</a>
+ <a href="https://github.com/LemmyNet/lemmy/blob/main/RELEASES.md">Releases</a>
</p>
</p>
Front Page|Post
---|---
-![main screen](https://raw.githubusercontent.com/LemmyNet/lemmy/master/docs/img/main_screen.png)|![chat screen](https://raw.githubusercontent.com/LemmyNet/lemmy/master/docs/img/chat_screen.png)
+![main screen](https://raw.githubusercontent.com/LemmyNet/lemmy/main/docs/img/main_screen.png)|![chat screen](https://raw.githubusercontent.com/LemmyNet/lemmy/main/docs/img/chat_screen.png)
[Lemmy](https://github.com/LemmyNet/lemmy) is similar to sites like [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), [Raddle](https://raddle.me), or [Hacker News](https://news.ycombinator.com/): you subscribe to forums you're interested in, post links and discussions, then vote, and comment on them. Behind the scenes, it is very different; anyone can easily run a server, and all these servers are federated (think email), and connected to the same universe, called the [Fediverse](https://en.wikipedia.org/wiki/Fediverse).
- [Ansible](https://dev.lemmy.ml/docs/administration_install_ansible.html)
- [Kubernetes](https://dev.lemmy.ml/docs/administration_install_kubernetes.html)
+## Lemmy Projects
+
+### Apps
+
+- [Lemmy-mobile (Android / IOS) - React native ( under development )](https://github.com/koredefashokun/lemmy-mobile)
+
+### Libraries
+
+- [lemmy-js-client](https://github.com/LemmyNet/lemmy-js-client)
+- [Kotlin API ( under development )](https://github.com/eiknat/lemmy-client)
+
## Support / Donate
Lemmy is free, open-source software, meaning no advertising, monetizing, or venture capital, ever. Your donations directly support full-time development of the project.
+# Lemmy v0.7.40 Pre-Release (2020-08-05)
+
+We've [added a lot](https://github.com/LemmyNet/lemmy/compare/v0.7.40...v0.7.0) in this pre-release:
+
+- New post sorts `Active` (previously called hot), and `Hot`. Active shows posts with recent comments, hot shows highly ranked posts.
+- Customizeable site icon and banner, user icon and banner, and community icon and banner.
+- Added user preferred names / display names, bios, and cakedays.
+- User settings are now shared across browsers (a page refresh will pick up changes).
+- Visual / Audio captchas through the lemmy API.
+- Lots of UI prettiness.
+- Lots of bug fixes.
+- Lots of additional translations.
+- Lots of federation prepping / additions / refactors.
+
+This release removes the need for you to have a pictrs nginx route (the requests are now routed through lemmy directly). Follow the upgrade instructions below to replace your nginx with the new one.
+
+## Upgrading
+
+**With Ansible:**
+
+```
+# run these commands locally
+git pull
+cd ansible
+ansible-playbook lemmy.yml
+```
+
+**With manual Docker installation:**
+```
+# run these commands on your server
+cd /lemmy
+wget https://raw.githubusercontent.com/LemmyNet/lemmy/master/ansible/templates/nginx.conf
+# Replace the {{ vars }}
+sudo mv nginx.conf /etc/nginx/sites-enabled/lemmy.conf
+sudo nginx -s reload
+wget https://raw.githubusercontent.com/LemmyNet/lemmy/master/docker/prod/docker-compose.yml
+sudo docker-compose up -d
+```
+
+
# Lemmy v0.7.0 Release (2020-06-23)
This release replaces [pictshare](https://github.com/HaschekSolutions/pictshare)
-proxy_cache_path /var/cache/lemmy_frontend levels=1:2 keys_zone=lemmy_frontend_cache:10m max_size=100m use_temp_path=off;
limit_req_zone $binary_remote_addr zone=lemmy_ratelimit:10m rate=1r/s;
server {
# Upload limit for pictrs
client_max_body_size 20M;
- # Rate limit
- limit_req zone=lemmy_ratelimit burst=30 nodelay;
-
location / {
proxy_pass http://0.0.0.0:8536;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ # Cuts off the trailing slash on URLs to make them valid
+ rewrite ^(.+)/+$ $1 permanent;
+
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
- # Proxy Cache
- proxy_cache lemmy_frontend_cache;
- proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
- proxy_cache_revalidate on;
- proxy_cache_lock on;
- proxy_cache_min_uses 5;
+ # Rate limit
+ limit_req zone=lemmy_ratelimit burst=30 nodelay;
}
# Redirect pictshare images to pictrs
return 301 /pictrs/image/$1;
}
- # pict-rs images
+ # Separate location block to disable rate limiting for images
location /pictrs {
- location /pictrs/image {
- proxy_pass http://0.0.0.0:8537/image;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header Host $host;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- }
- # Block the import
- return 403;
+ proxy_pass http://0.0.0.0:8536/pictrs;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /iframely/ {
# Install libpq for postgres
RUN apk add libpq
+# Install Espeak for captchas
+RUN apk add espeak
+
# Copy resources
COPY server/config/defaults.hjson /config/defaults.hjson
COPY --from=rust /app/server/target/x86_64-unknown-linux-musl/debug/lemmy_server /app/lemmy
postgres:
image: postgres:12-alpine
ports:
- - "127.0.0.1:5432:5432"
+ # use a different port so it doesnt conflict with postgres running on the host
+ - "127.0.0.1:5433:5432"
environment:
- POSTGRES_USER=lemmy
- POSTGRES_PASSWORD=password
yarn
popd
-mkdir -p volumes/pictrs_{alpha,beta,gamma}
-sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma}
+mkdir -p volumes/pictrs_{alpha,beta,gamma,delta,epsilon}
+sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma,delta,epsilon}
sudo docker build ../../ --file ../federation/Dockerfile --tag lemmy-federation:latest
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8560/api/v1/site')" != "200" ]]; do sleep 1; done
+while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8570/api/v1/site')" != "200" ]]; do sleep 1; done
+while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8580/api/v1/site')" != "200" ]]; do sleep 1; done
yarn api-test || true
popd
#!/bin/bash
set -e
+sudo docker-compose --file ../federation/docker-compose.yml --project-directory . down
sudo rm -rf volumes
pushd ../../server/
yarn
popd
-mkdir -p volumes/pictrs_{alpha,beta,gamma}
-sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma}
+mkdir -p volumes/pictrs_{alpha,beta,gamma,delta,epsilon}
+sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma,delta,epsilon}
sudo docker build ../../ --file ../federation/Dockerfile --tag lemmy-federation:latest
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8560/api/v1/site')" != "200" ]]; do sleep 1; done
+while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8570/api/v1/site')" != "200" ]]; do sleep 1; done
+while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8580/api/v1/site')" != "200" ]]; do sleep 1; done
yarn api-test || true
popd
- "8540:8540"
- "8550:8550"
- "8560:8560"
+ - "8570:8570"
+ - "8580:8580"
volumes:
# Hack to make this work from both docker/federation/ and docker/federation-test/
- ../federation/nginx.conf:/etc/nginx/nginx.conf
restart: on-failure
depends_on:
- - lemmy-alpha
- pictrs
+ - iframely
+ - lemmy-alpha
- lemmy-beta
- lemmy-gamma
- - iframely
+ - lemmy-delta
+ - lemmy-epsilon
pictrs:
restart: always
- LEMMY_FRONT_END_DIR=/app/dist
- LEMMY_FEDERATION__ENABLED=true
- LEMMY_FEDERATION__TLS_ENABLED=false
- - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma
+ - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma,lemmy-delta,lemmy-epsilon
- LEMMY_PORT=8540
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-alpha
+ - LEMMY_RATE_LIMIT__POST=99999
+ - LEMMY_RATE_LIMIT__REGISTER=99999
+ - LEMMY_CAPTCHA__ENABLED=false
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
- LEMMY_FRONT_END_DIR=/app/dist
- LEMMY_FEDERATION__ENABLED=true
- LEMMY_FEDERATION__TLS_ENABLED=false
- - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma
+ - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma,lemmy-delta,lemmy-epsilon
- LEMMY_PORT=8550
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-beta
+ - LEMMY_RATE_LIMIT__POST=99999
+ - LEMMY_RATE_LIMIT__REGISTER=99999
+ - LEMMY_CAPTCHA__ENABLED=false
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
- LEMMY_FRONT_END_DIR=/app/dist
- LEMMY_FEDERATION__ENABLED=true
- LEMMY_FEDERATION__TLS_ENABLED=false
- - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta
+ - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta,lemmy-delta,lemmy-epsilon
- LEMMY_PORT=8560
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-gamma
+ - LEMMY_RATE_LIMIT__POST=99999
+ - LEMMY_RATE_LIMIT__REGISTER=99999
+ - LEMMY_CAPTCHA__ENABLED=false
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
volumes:
- ./volumes/postgres_gamma:/var/lib/postgresql/data
+ # An instance with only an allowlist for beta
+ lemmy-delta:
+ image: lemmy-federation:latest
+ environment:
+ - LEMMY_HOSTNAME=lemmy-delta:8570
+ - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_delta:5432/lemmy
+ - LEMMY_JWT_SECRET=changeme
+ - LEMMY_FRONT_END_DIR=/app/dist
+ - LEMMY_FEDERATION__ENABLED=true
+ - LEMMY_FEDERATION__TLS_ENABLED=false
+ - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta
+ - LEMMY_PORT=8570
+ - LEMMY_SETUP__ADMIN_USERNAME=lemmy_delta
+ - LEMMY_SETUP__ADMIN_PASSWORD=lemmy
+ - LEMMY_SETUP__SITE_NAME=lemmy-delta
+ - LEMMY_RATE_LIMIT__POST=99999
+ - LEMMY_RATE_LIMIT__REGISTER=99999
+ - LEMMY_CAPTCHA__ENABLED=false
+ - RUST_BACKTRACE=1
+ - RUST_LOG=debug
+ depends_on:
+ - postgres_delta
+ postgres_delta:
+ image: postgres:12-alpine
+ environment:
+ - POSTGRES_USER=lemmy
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=lemmy
+ volumes:
+ - ./volumes/postgres_delta:/var/lib/postgresql/data
+
+ # An instance who has a blocklist, with lemmy-alpha blocked
+ lemmy-epsilon:
+ image: lemmy-federation:latest
+ environment:
+ - LEMMY_HOSTNAME=lemmy-epsilon:8580
+ - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_epsilon:5432/lemmy
+ - LEMMY_JWT_SECRET=changeme
+ - LEMMY_FRONT_END_DIR=/app/dist
+ - LEMMY_FEDERATION__ENABLED=true
+ - LEMMY_FEDERATION__TLS_ENABLED=false
+ - LEMMY_FEDERATION__BLOCKED_INSTANCES=lemmy-alpha
+ - LEMMY_PORT=8580
+ - LEMMY_SETUP__ADMIN_USERNAME=lemmy_epsilon
+ - LEMMY_SETUP__ADMIN_PASSWORD=lemmy
+ - LEMMY_SETUP__SITE_NAME=lemmy-epsilon
+ - LEMMY_RATE_LIMIT__POST=99999
+ - LEMMY_RATE_LIMIT__REGISTER=99999
+ - LEMMY_CAPTCHA__ENABLED=false
+ - RUST_BACKTRACE=1
+ - RUST_LOG=debug
+ depends_on:
+ - postgres_epsilon
+ postgres_epsilon:
+ image: postgres:12-alpine
+ environment:
+ - POSTGRES_USER=lemmy
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=lemmy
+ volumes:
+ - ./volumes/postgres_epsilon:/var/lib/postgresql/data
+
iframely:
image: dogbin/iframely:latest
volumes:
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ # Cuts off the trailing slash on URLs to make them valid
+ rewrite ^(.+)/+$ $1 permanent;
+
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
- # pict-rs images
- location /pictrs {
- location /pictrs/image {
- proxy_pass http://pictrs:8080/image;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header Host $host;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- }
- # Block the import
- return 403;
- }
-
location /iframely/ {
proxy_pass http://iframely:80/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ # Cuts off the trailing slash on URLs to make them valid
+ rewrite ^(.+)/+$ $1 permanent;
+
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
- # pict-rs images
- location /pictrs {
- location /pictrs/image {
- proxy_pass http://pictrs:8080/image;
+ location /iframely/ {
+ proxy_pass http://iframely:80/;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ }
+ }
+
+ server {
+ listen 8560;
+ server_name 127.0.0.1;
+ access_log off;
+
+ # Upload limit for pictshare
+ client_max_body_size 50M;
+
+ location / {
+ proxy_pass http://lemmy-gamma:8560;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- }
- # Block the import
- return 403;
+
+ # Cuts off the trailing slash on URLs to make them valid
+ rewrite ^(.+)/+$ $1 permanent;
+
+ # WebSocket support
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
}
location /iframely/ {
}
server {
- listen 8560;
+ listen 8570;
server_name 127.0.0.1;
access_log off;
client_max_body_size 50M;
location / {
- proxy_pass http://lemmy-gamma:8560;
+ proxy_pass http://lemmy-delta:8570;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ # Cuts off the trailing slash on URLs to make them valid
+ rewrite ^(.+)/+$ $1 permanent;
+
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
- # pict-rs images
- location /pictrs {
- location /pictrs/image {
- proxy_pass http://pictrs:8080/image;
+ location /iframely/ {
+ proxy_pass http://iframely:80/;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ }
+ }
+
+ server {
+ listen 8580;
+ server_name 127.0.0.1;
+ access_log off;
+
+ # Upload limit for pictshare
+ client_max_body_size 50M;
+
+ location / {
+ proxy_pass http://lemmy-epsilon:8580;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- }
- # Block the import
- return 403;
+
+ # Cuts off the trailing slash on URLs to make them valid
+ rewrite ^(.+)/+$ $1 permanent;
+
+ # WebSocket support
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
}
location /iframely/ {
cargo build &
popd || exit
-if [ "$1" = "-yarn" ]; then
+if [ "$1" != "--no-yarn-build" ]; then
pushd ../../ui/ || exit
yarn
yarn build
sudo docker build ../../ --file Dockerfile -t lemmy-federation:latest
-for Item in alpha beta gamma ; do
+for Item in alpha beta gamma delta epsilon ; do
sudo mkdir -p volumes/pictrs_$Item
sudo chown -R 991:991 volumes/pictrs_$Item
done
# for more info about the config, check out the documentation
# https://dev.lemmy.ml/docs/administration_configuration.html
+ setup: {
+ # username for the admin user
+ admin_username: "lemmy"
+ # password for the admin user
+ admin_password: "lemmy"
+ # name of the site (can be changed later)
+ site_name: "lemmy-test"
+ }
+
# the domain name of your instance (eg "dev.lemmy.ml")
hostname: "my_domain"
# address where lemmy should listen for incoming requests
# Install libpq for postgres
RUN apk add libpq
+
+# Install Espeak for captchas
+RUN apk add espeak
+
RUN addgroup -g 1000 lemmy
RUN adduser -D -s /bin/sh -u 1000 -G lemmy lemmy
#!/bin/sh
set -e
-git checkout master
+git checkout main
# Import translations
git fetch weblate
-git merge weblate/master
+git merge weblate/main
# Creating the new tag
new_tag="$1"
# Setting the version on the front end
cd ../../
-echo "export const version: string = '$new_tag';" > "ui/src/version.ts"
-git add "ui/src/version.ts"
# Setting the version on the backend
echo "pub const VERSION: &str = \"$new_tag\";" > "server/src/version.rs"
git add "server/src/version.rs"
# Changing the docker-compose prod
sed -i "s/dessalines\/lemmy:.*/dessalines\/lemmy:$new_tag/" ../prod/docker-compose.yml
sed -i "s/dessalines\/lemmy:.*/dessalines\/lemmy:$new_tag/" ../../ansible/templates/docker-compose.yml
+sed -i "s/dessalines\/lemmy:v.*/dessalines\/lemmy:$new_tag/" ../travis/docker_push.sh
git add ../prod/docker-compose.yml
git add ../../ansible/templates/docker-compose.yml
+git add ../travis/docker_push.sh
# The commit
git commit -m"Version $new_tag"
git tag $new_tag
-export COMPOSE_DOCKER_CLI_BUILD=1
-export DOCKER_BUILDKIT=1
-
-# Rebuilding docker
-if [ $third_semver -eq 0 ]; then
- # TODO get linux/arm/v7 build working
- # Build for Raspberry Pi / other archs too
- docker buildx build --platform linux/amd64,linux/arm64 ../../ \
- --file Dockerfile \
- --tag dessalines/lemmy:$new_tag \
- --push
-else
- docker buildx build --platform linux/amd64 ../../ \
- --file Dockerfile \
- --tag dessalines/lemmy:$new_tag \
- --push
-fi
+# Now doing the building on travis, but leave this in for when you need to do an arm build
+
+# export COMPOSE_DOCKER_CLI_BUILD=1
+# export DOCKER_BUILDKIT=1
+
+# # Rebuilding docker
+# if [ $third_semver -eq 0 ]; then
+# # TODO get linux/arm/v7 build working
+# # Build for Raspberry Pi / other archs too
+# docker buildx build --platform linux/amd64,linux/arm64 ../../ \
+# --file Dockerfile \
+# --tag dessalines/lemmy:$new_tag \
+# --push
+# else
+# docker buildx build --platform linux/amd64 ../../ \
+# --file Dockerfile \
+# --tag dessalines/lemmy:$new_tag \
+# --push
+# fi
# Push
git push origin $new_tag
git push
# Pushing to any ansible deploys
-cd ../../../lemmy-ansible || exit
-ansible-playbook -i prod playbooks/site.yml --vault-password-file vault_pass
+# cd ../../../lemmy-ansible || exit
+# ansible-playbook -i prod playbooks/site.yml --vault-password-file vault_pass
restart: always
lemmy:
- image: dessalines/lemmy:v0.7.21
+ image: dessalines/lemmy:v0.7.55
ports:
- "127.0.0.1:8536:8536"
restart: always
--- /dev/null
+version: '3.3'
+
+services:
+ nginx:
+ image: nginx:1.17-alpine
+ ports:
+ - "8540:8540"
+ - "8550:8550"
+ - "8560:8560"
+ - "8570:8570"
+ - "8580:8580"
+ volumes:
+ # Hack to make this work from both docker/federation/ and docker/federation-test/
+ - ../federation/nginx.conf:/etc/nginx/nginx.conf
+ restart: on-failure
+ depends_on:
+ - pictrs
+ - iframely
+ - lemmy-alpha
+ - lemmy-beta
+ - lemmy-gamma
+ - lemmy-delta
+ - lemmy-epsilon
+
+ pictrs:
+ restart: always
+ image: asonix/pictrs:v0.1.13-r0
+ user: 991:991
+ volumes:
+ - ./volumes/pictrs_alpha:/mnt
+
+ lemmy-alpha:
+ image: dessalines/lemmy:travis
+ environment:
+ - LEMMY_HOSTNAME=lemmy-alpha:8540
+ - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_alpha:5432/lemmy
+ - LEMMY_JWT_SECRET=changeme
+ - LEMMY_FRONT_END_DIR=/app/dist
+ - LEMMY_FEDERATION__ENABLED=true
+ - LEMMY_FEDERATION__TLS_ENABLED=false
+ - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma,lemmy-delta,lemmy-epsilon
+ - LEMMY_PORT=8540
+ - LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha
+ - LEMMY_SETUP__ADMIN_PASSWORD=lemmy
+ - LEMMY_SETUP__SITE_NAME=lemmy-alpha
+ - LEMMY_RATE_LIMIT__POST=99999
+ - LEMMY_RATE_LIMIT__REGISTER=99999
+ - LEMMY_CAPTCHA__ENABLED=false
+ - RUST_BACKTRACE=1
+ - RUST_LOG=debug
+ depends_on:
+ - postgres_alpha
+ postgres_alpha:
+ image: postgres:12-alpine
+ environment:
+ - POSTGRES_USER=lemmy
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=lemmy
+ volumes:
+ - ./volumes/postgres_alpha:/var/lib/postgresql/data
+
+ lemmy-beta:
+ image: dessalines/lemmy:travis
+ environment:
+ - LEMMY_HOSTNAME=lemmy-beta:8550
+ - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_beta:5432/lemmy
+ - LEMMY_JWT_SECRET=changeme
+ - LEMMY_FRONT_END_DIR=/app/dist
+ - LEMMY_FEDERATION__ENABLED=true
+ - LEMMY_FEDERATION__TLS_ENABLED=false
+ - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma,lemmy-delta,lemmy-epsilon
+ - LEMMY_PORT=8550
+ - LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta
+ - LEMMY_SETUP__ADMIN_PASSWORD=lemmy
+ - LEMMY_SETUP__SITE_NAME=lemmy-beta
+ - LEMMY_RATE_LIMIT__POST=99999
+ - LEMMY_RATE_LIMIT__REGISTER=99999
+ - LEMMY_CAPTCHA__ENABLED=false
+ - RUST_BACKTRACE=1
+ - RUST_LOG=debug
+ depends_on:
+ - postgres_beta
+ postgres_beta:
+ image: postgres:12-alpine
+ environment:
+ - POSTGRES_USER=lemmy
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=lemmy
+ volumes:
+ - ./volumes/postgres_beta:/var/lib/postgresql/data
+
+ lemmy-gamma:
+ image: dessalines/lemmy:travis
+ environment:
+ - LEMMY_HOSTNAME=lemmy-gamma:8560
+ - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_gamma:5432/lemmy
+ - LEMMY_JWT_SECRET=changeme
+ - LEMMY_FRONT_END_DIR=/app/dist
+ - LEMMY_FEDERATION__ENABLED=true
+ - LEMMY_FEDERATION__TLS_ENABLED=false
+ - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta,lemmy-delta,lemmy-epsilon
+ - LEMMY_PORT=8560
+ - LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma
+ - LEMMY_SETUP__ADMIN_PASSWORD=lemmy
+ - LEMMY_SETUP__SITE_NAME=lemmy-gamma
+ - LEMMY_RATE_LIMIT__POST=99999
+ - LEMMY_RATE_LIMIT__REGISTER=99999
+ - LEMMY_CAPTCHA__ENABLED=false
+ - RUST_BACKTRACE=1
+ - RUST_LOG=debug
+ depends_on:
+ - postgres_gamma
+ postgres_gamma:
+ image: postgres:12-alpine
+ environment:
+ - POSTGRES_USER=lemmy
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=lemmy
+ volumes:
+ - ./volumes/postgres_gamma:/var/lib/postgresql/data
+
+ # An instance with only an allowlist for beta
+ lemmy-delta:
+ image: dessalines/lemmy:travis
+ environment:
+ - LEMMY_HOSTNAME=lemmy-delta:8570
+ - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_delta:5432/lemmy
+ - LEMMY_JWT_SECRET=changeme
+ - LEMMY_FRONT_END_DIR=/app/dist
+ - LEMMY_FEDERATION__ENABLED=true
+ - LEMMY_FEDERATION__TLS_ENABLED=false
+ - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta
+ - LEMMY_PORT=8570
+ - LEMMY_SETUP__ADMIN_USERNAME=lemmy_delta
+ - LEMMY_SETUP__ADMIN_PASSWORD=lemmy
+ - LEMMY_SETUP__SITE_NAME=lemmy-delta
+ - LEMMY_RATE_LIMIT__POST=99999
+ - LEMMY_RATE_LIMIT__REGISTER=99999
+ - LEMMY_CAPTCHA__ENABLED=false
+ - RUST_BACKTRACE=1
+ - RUST_LOG=debug
+ depends_on:
+ - postgres_delta
+ postgres_delta:
+ image: postgres:12-alpine
+ environment:
+ - POSTGRES_USER=lemmy
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=lemmy
+ volumes:
+ - ./volumes/postgres_delta:/var/lib/postgresql/data
+
+ # An instance who has a blocklist, with lemmy-alpha blocked
+ lemmy-epsilon:
+ image: dessalines/lemmy:travis
+ environment:
+ - LEMMY_HOSTNAME=lemmy-epsilon:8580
+ - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_epsilon:5432/lemmy
+ - LEMMY_JWT_SECRET=changeme
+ - LEMMY_FRONT_END_DIR=/app/dist
+ - LEMMY_FEDERATION__ENABLED=true
+ - LEMMY_FEDERATION__TLS_ENABLED=false
+ - LEMMY_FEDERATION__BLOCKED_INSTANCES=lemmy-alpha
+ - LEMMY_PORT=8580
+ - LEMMY_SETUP__ADMIN_USERNAME=lemmy_epsilon
+ - LEMMY_SETUP__ADMIN_PASSWORD=lemmy
+ - LEMMY_SETUP__SITE_NAME=lemmy-epsilon
+ - LEMMY_RATE_LIMIT__POST=99999
+ - LEMMY_RATE_LIMIT__REGISTER=99999
+ - LEMMY_CAPTCHA__ENABLED=false
+ - RUST_BACKTRACE=1
+ - RUST_LOG=debug
+ depends_on:
+ - postgres_epsilon
+ postgres_epsilon:
+ image: postgres:12-alpine
+ environment:
+ - POSTGRES_USER=lemmy
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=lemmy
+ volumes:
+ - ./volumes/postgres_epsilon:/var/lib/postgresql/data
+
+ iframely:
+ image: dogbin/iframely:latest
+ volumes:
+ - ../iframely.config.local.js:/iframely/config.local.js:ro
+ restart: always
--- /dev/null
+#!/bin/sh
+echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
+docker tag dessalines/lemmy:travis \
+ dessalines/lemmy:v0.7.55
+docker push dessalines/lemmy:v0.7.55
--- /dev/null
+#!/bin/bash
+set -e
+
+# make sure there are no old containers or old data around
+sudo docker-compose down
+sudo rm -rf volumes
+
+mkdir -p volumes/pictrs_{alpha,beta,gamma,delta,epsilon}
+sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma,delta,epsilon}
+
+sudo docker build ../../ --file ../prod/Dockerfile --tag dessalines/lemmy:travis
+
+sudo docker-compose up -d
+
+pushd ../../ui
+echo "Waiting for Lemmy to start..."
+while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done
+while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done
+while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8560/api/v1/site')" != "200" ]]; do sleep 1; done
+while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8570/api/v1/site')" != "200" ]]; do sleep 1; done
+while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8580/api/v1/site')" != "200" ]]; do sleep 1; done
+yarn
+yarn api-test
+popd
+
+sudo docker-compose down
+
+sudo rm -r volumes/
Front Page|Post
---|---
-![main screen](https://raw.githubusercontent.com/LemmyNet/lemmy/master/docs/img/main_screen.png)|![chat screen](https://raw.githubusercontent.com/LemmyNet/lemmy/master/docs/img/chat_screen.png)
+![main screen](https://raw.githubusercontent.com/LemmyNet/lemmy/main/docs/img/main_screen.png)|![chat screen](https://raw.githubusercontent.com/LemmyNet/lemmy/main/docs/img/chat_screen.png)
[Lemmy](https://github.com/LemmyNet/lemmy) is similar to sites like [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), [Raddle](https://raddle.me), or [Hacker News](https://news.ycombinator.com/): you subscribe to forums you're interested in, post links and discussions, then vote, and comment on them. Behind the scenes, it is very different; anyone can easily run a server, and all these servers are federated (think email), and connected to the same universe, called the [Fediverse](https://en.wikipedia.org/wiki/Fediverse).
\`Inline code\` with backticks | |`Inline code` with backticks
\`\`\`<br>\# code block <br>print '3 backticks or'<br>print 'indent 4 spaces' <br>\`\`\` | ····\# code block<br>····print '3 backticks or'<br>····print 'indent 4 spaces' | \# code block <br>print '3 backticks or'<br>print 'indent 4 spaces'
::: spoiler hidden or nsfw stuff<br>*a bunch of spoilers here*<br>::: | | <details><summary> hidden or nsfw stuff </summary><p><em>a bunch of spoilers here</em></p></details>
+Some ~subscript~ text | | Some <sub>subscript</sub> text
+Some ^superscript^ text | | Some <sup>superscript</sup> text
[CommonMark Tutorial](https://commonmark.org/help/tutorial/)
Time = time since submission (in hours)
Gravity = Decay gravity, 1.8 is default
```
-- For posts, in order to bring up active posts, it uses the latest comment time (limited to a max creation age of a month ago)
+- Lemmy uses the same `Rank` algorithm above, in two sorts: `Active`, and `Hot`.
+ - `Active` uses the post votes, and latest comment time (limited to two days).
+ - `Hot` uses the post votes, and the post published time.
- Use Max(1, score) to make sure all comments are affected by time decay.
- Add 3 to the score, so that everything that has less than 3 downvotes will seem new. Otherwise all new comments would stay at zero, near the bottom.
- The sign and abs of the score are necessary for dealing with the log of negative scores.
A plot of rank over 24 hours, of scores of 1, 5, 10, 100, 1000, with a scale factor of 10k.
-![](https://raw.githubusercontent.com/LemmyNet/lemmy/master/docs/img/rank_algorithm.png)
+![](https://raw.githubusercontent.com/LemmyNet/lemmy/main/docs/img/rank_algorithm.png)
To incrementally backup the DB to an `.sql` file, you can run:
```bash
-docker exec -t FOLDERNAME_postgres_1 pg_dumpall -c -U lemmy > lemmy_dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql
+docker-compose exec postgres pg_dumpall -c -U lemmy > lemmy_dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql
```
### A Sample backup script
```bash
#!/bin/sh
# DB Backup
-ssh MY_USER@MY_IP "docker exec -t FOLDERNAME_postgres_1 pg_dumpall -c -U lemmy" > ~/BACKUP_LOCATION/INSTANCE_NAME_dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql
+ssh MY_USER@MY_IP "docker-compose exec postgres pg_dumpall -c -U lemmy" > ~/BACKUP_LOCATION/INSTANCE_NAME_dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql
# Volumes folder Backup
rsync -avP -zz --rsync-path="sudo rsync" MY_USER@MY_IP:/LEMMY_LOCATION/volumes ~/BACKUP_LOCATION/FOLDERNAME
docker exec -i FOLDERNAME_postgres_1 psql -U lemmy -c "alter user lemmy with password 'bleh'"
```
+### Changing your domain name
+
+If you haven't federated yet, you can change your domain name in the DB. **Warning: do not do this after you've federated, or it will break federation.**
+
+Get into `psql` for your docker:
+
+`docker-compose exec postgres psql -U lemmy`
+
+```
+-- Post
+update post set ap_id = replace (ap_id, 'old_domain', 'new_domain');
+update post set url = replace (url, 'old_domain', 'new_domain');
+update post set body = replace (body, 'old_domain', 'new_domain');
+update post set thumbnail_url = replace (thumbnail_url, 'old_domain', 'new_domain');
+
+delete from post_aggregates_fast;
+insert into post_aggregates_fast select * from post_aggregates_view;
+
+-- Comments
+update comment set ap_id = replace (ap_id, 'old_domain', 'new_domain');
+update comment set content = replace (content, 'old_domain', 'new_domain');
+
+delete from comment_aggregates_fast;
+insert into comment_aggregates_fast select * from comment_aggregates_view;
+
+-- User
+update user_ set actor_id = replace (actor_id, 'old_domain', 'new_domain');
+update user_ set avatar = replace (avatar, 'old_domain', 'new_domain');
+
+delete from user_fast;
+insert into user_fast select * from user_view;
+
+-- Community
+update community set actor_id = replace (actor_id, 'old_domain', 'new_domain');
+
+delete from community_aggregates_fast;
+insert into community_aggregates_fast select * from community_aggregates_view;
+```
+
## More resources
- https://stackoverflow.com/questions/24718706/backup-restore-a-dockerized-postgresql-database
# Configuration
The configuration is based on the file
-[defaults.hjson](https://yerbamate.dev/LemmyNet/lemmy/src/branch/master/server/config/defaults.hjson).
+[defaults.hjson](https://yerbamate.dev/LemmyNet/lemmy/src/branch/main/server/config/defaults.hjson).
This file also contains documentation for all the available options. To override the defaults, you
can copy the options you want to change into your local `config.hjson` file.
-To use a different `config.hjson` location than the current directory, set the environment variable `LEMMY_CONFIG_LOCATION`.
+To use a different `config.hjson` location than the current directory, set the environment variable `LEMMY_CONFIG_LOCATION`. Make sure you copy the `defaults.hjson` if you do this, otherwise you will be missing settings.
Additionally, you can override any config files with environment variables. These have the same
name as the config options, and are prefixed with `LEMMY_`. For example, you can override the
To update to a new version, just run the following in your local Lemmy repo:
```bash
-git pull origin master
+git pull origin main
cd ansible
ansible-playbook lemmy.yml --become
```
cd /lemmy
# download default config files
-wget https://raw.githubusercontent.com/LemmyNet/lemmy/master/docker/prod/docker-compose.yml
-wget https://raw.githubusercontent.com/LemmyNet/lemmy/master/docker/lemmy.hjson
-wget https://raw.githubusercontent.com/LemmyNet/lemmy/master/docker/iframely.config.local.js
+wget https://raw.githubusercontent.com/LemmyNet/lemmy/main/docker/prod/docker-compose.yml
+wget https://raw.githubusercontent.com/LemmyNet/lemmy/main/docker/lemmy.hjson
+wget https://raw.githubusercontent.com/LemmyNet/lemmy/main/docker/iframely.config.local.js
# Set correct permissions for pictrs folder
mkdir -p volumes/pictrs
`docker-compose up -d`
-To make Lemmy available outside the server, you need to setup a reverse proxy, like Nginx. [A sample nginx config](https://raw.githubusercontent.com/LemmyNet/lemmy/master/ansible/templates/nginx.conf), could be setup with:
+To make Lemmy available outside the server, you need to setup a reverse proxy, like Nginx. [A sample nginx config](https://raw.githubusercontent.com/LemmyNet/lemmy/main/ansible/templates/nginx.conf), could be setup with:
```bash
-wget https://raw.githubusercontent.com/LemmyNet/lemmy/master/ansible/templates/nginx.conf
+wget https://raw.githubusercontent.com/LemmyNet/lemmy/main/ansible/templates/nginx.conf
# Replace the {{ vars }}
sudo mv nginx.conf /etc/nginx/sites-enabled/lemmy.conf
```
To update to the newest version, you can manually change the version in `docker-compose.yml`. Alternatively, fetch the latest version from our git repo:
```bash
-wget https://raw.githubusercontent.com/LemmyNet/lemmy/master/docker/prod/docker-compose.yml
+wget https://raw.githubusercontent.com/LemmyNet/lemmy/main/docker/prod/docker-compose.yml
docker-compose up -d
```
sudo docker-compose pull
sudo docker-compose up -d
```
+
+## Security Model
+
+- HTTP signature verify: This ensures that activity really comes from the activity that it claims
+- check_is_apub_valid : Makes sure its in our allowed instances list
+- Lower level checks: To make sure that the user that creates/updates/removes a post is actually on the same instance as that post
+
+For the last point, note that we are *not* checking whether the actor that sends the create activity for a post is
+actually identical to the post's creator, or that the user that removes a post is a mod/admin. These things are checked
+by the API code, and its the responsibility of each instance to check user permissions. This does not leave any attack
+vector, as a normal instance user cant do actions that violate the API rules. The only one who could do that is the
+admin (and the software deployed by the admin). But the admin can do anything on the instance, including send activities
+from other user accounts. So we wouldnt actually gain any security by checking mod permissions or similar.
\ No newline at end of file
-### Ubuntu
-
-
-#### Build requirements:
+### Install build requirements
+#### Ubuntu
```
-sudo apt install git cargo libssl-dev pkg-config libpq-dev yarn curl gnupg2 git
+sudo apt install git cargo libssl-dev pkg-config libpq-dev yarn curl gnupg2 espeak
# install yarn
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update && sudo apt install yarn
```
-#### Get the source code
+#### macOS
+
+Install Rust using [the recommended option on rust-lang.org](https://www.rust-lang.org/tools/install) (rustup).
+
+Then, install [Homebrew](https://brew.sh/) if you don't already have it installed.
+
+Finally, install Node and Yarn.
+
+```
+brew install node yarn
+```
+
+### Get the source code
```
git clone https://github.com/LemmyNet/lemmy.git
# or alternatively from gitea
All the following commands need to be run either in `lemmy/server` or `lemmy/ui`, as indicated
by the `cd` command.
-#### Build the backend (Rust)
+### Build the backend (Rust)
```
cd server
cargo build
# for development, use `cargo check` instead)
```
-#### Build the frontend (Typescript)
+### Build the frontend (Typescript)
```
cd ui
yarn
yarn build
```
-#### Setup postgresql
+### Setup postgresql
+#### Ubuntu
```
sudo apt install postgresql
sudo systemctl start postgresql
-# initialize postgres database
+
+# Either execute server/db-init.sh, or manually initialize the postgres database:
sudo -u postgres psql -c "create user lemmy with password 'password' superuser;" -U postgres
sudo -u postgres psql -c 'create database lemmy with owner lemmy;' -U postgres
export LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy
-# or execute server/db-init.sh
```
-#### Run a local development instance
+#### macOS
+```
+brew install postgresql
+brew services start postgresql
+/usr/local/opt/postgres/bin/createuser -s postgres
+
+# Either execute server/db-init.sh, or manually initialize the postgres database:
+psql -c "create user lemmy with password 'password' superuser;" -U postgres
+psql -c 'create database lemmy with owner lemmy;' -U postgres
+export LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy
+```
+
+### Run a local development instance
```
# run each of these in a seperate terminal
cd server && cargo run
-ui & yarn start
+cd ui && yarn start
```
Then open [localhost:4444](http://localhost:4444) in your browser. It will auto-refresh if you edit
- [Errors](#errors)
- [API documentation](#api-documentation)
* [Sort Types](#sort-types)
+ * [Undoing actions](#undoing-actions)
* [Websocket vs HTTP](#websocket-vs-http)
* [User / Authentication / Admin actions](#user--authentication--admin-actions)
+ [Login](#login)
- [Request](#request-5)
- [Response](#response-5)
- [HTTP](#http-6)
- + [Edit User Mention](#edit-user-mention)
+ + [Mark User Mention as read](#mark-user-mention-as-read)
- [Request](#request-6)
- [Response](#response-6)
- [HTTP](#http-7)
- + [Mark All As Read](#mark-all-as-read)
+ + [Get Private Messages](#get-private-messages)
- [Request](#request-7)
- [Response](#response-7)
- [HTTP](#http-8)
- + [Delete Account](#delete-account)
+ + [Create Private Message](#create-private-message)
- [Request](#request-8)
- [Response](#response-8)
- [HTTP](#http-9)
- + [Add admin](#add-admin)
+ + [Edit Private Message](#edit-private-message)
- [Request](#request-9)
- [Response](#response-9)
- [HTTP](#http-10)
- + [Ban user](#ban-user)
+ + [Delete Private Message](#delete-private-message)
- [Request](#request-10)
- [Response](#response-10)
- [HTTP](#http-11)
- * [Site](#site)
- + [List Categories](#list-categories)
+ + [Mark Private Message as Read](#mark-private-message-as-read)
- [Request](#request-11)
- [Response](#response-11)
- [HTTP](#http-12)
- + [Search](#search)
+ + [Mark All As Read](#mark-all-as-read)
- [Request](#request-12)
- [Response](#response-12)
- [HTTP](#http-13)
- + [Get Modlog](#get-modlog)
+ + [Delete Account](#delete-account)
- [Request](#request-13)
- [Response](#response-13)
- [HTTP](#http-14)
- + [Create Site](#create-site)
+ + [Add admin](#add-admin)
- [Request](#request-14)
- [Response](#response-14)
- [HTTP](#http-15)
- + [Edit Site](#edit-site)
+ + [Ban user](#ban-user)
- [Request](#request-15)
- [Response](#response-15)
- [HTTP](#http-16)
- + [Get Site](#get-site)
+ * [Site](#site)
+ + [List Categories](#list-categories)
- [Request](#request-16)
- [Response](#response-16)
- [HTTP](#http-17)
- + [Transfer Site](#transfer-site)
+ + [Search](#search)
- [Request](#request-17)
- [Response](#response-17)
- [HTTP](#http-18)
- + [Get Site Config](#get-site-config)
+ + [Get Modlog](#get-modlog)
- [Request](#request-18)
- [Response](#response-18)
- [HTTP](#http-19)
- + [Save Site Config](#save-site-config)
+ + [Create Site](#create-site)
- [Request](#request-19)
- [Response](#response-19)
- [HTTP](#http-20)
- * [Community](#community)
- + [Get Community](#get-community)
+ + [Edit Site](#edit-site)
- [Request](#request-20)
- [Response](#response-20)
- [HTTP](#http-21)
- + [Create Community](#create-community)
+ + [Get Site](#get-site)
- [Request](#request-21)
- [Response](#response-21)
- [HTTP](#http-22)
- + [List Communities](#list-communities)
+ + [Transfer Site](#transfer-site)
- [Request](#request-22)
- [Response](#response-22)
- [HTTP](#http-23)
- + [Ban from Community](#ban-from-community)
+ + [Get Site Config](#get-site-config)
- [Request](#request-23)
- [Response](#response-23)
- [HTTP](#http-24)
- + [Add Mod to Community](#add-mod-to-community)
+ + [Save Site Config](#save-site-config)
- [Request](#request-24)
- [Response](#response-24)
- [HTTP](#http-25)
- + [Edit Community](#edit-community)
+ * [Community](#community)
+ + [Get Community](#get-community)
- [Request](#request-25)
- [Response](#response-25)
- [HTTP](#http-26)
- + [Follow Community](#follow-community)
+ + [Create Community](#create-community)
- [Request](#request-26)
- [Response](#response-26)
- [HTTP](#http-27)
- + [Get Followed Communities](#get-followed-communities)
+ + [List Communities](#list-communities)
- [Request](#request-27)
- [Response](#response-27)
- [HTTP](#http-28)
- + [Transfer Community](#transfer-community)
+ + [Ban from Community](#ban-from-community)
- [Request](#request-28)
- [Response](#response-28)
- [HTTP](#http-29)
- * [Post](#post)
- + [Create Post](#create-post)
+ + [Add Mod to Community](#add-mod-to-community)
- [Request](#request-29)
- [Response](#response-29)
- [HTTP](#http-30)
- + [Get Post](#get-post)
+ + [Edit Community](#edit-community)
- [Request](#request-30)
- [Response](#response-30)
- [HTTP](#http-31)
- + [Get Posts](#get-posts)
+ + [Delete Community](#delete-community)
- [Request](#request-31)
- [Response](#response-31)
- [HTTP](#http-32)
- + [Create Post Like](#create-post-like)
+ + [Remove Community](#remove-community)
- [Request](#request-32)
- [Response](#response-32)
- [HTTP](#http-33)
- + [Edit Post](#edit-post)
+ + [Follow Community](#follow-community)
- [Request](#request-33)
- [Response](#response-33)
- [HTTP](#http-34)
- + [Save Post](#save-post)
+ + [Get Followed Communities](#get-followed-communities)
- [Request](#request-34)
- [Response](#response-34)
- [HTTP](#http-35)
- * [Comment](#comment)
- + [Create Comment](#create-comment)
+ + [Transfer Community](#transfer-community)
- [Request](#request-35)
- [Response](#response-35)
- [HTTP](#http-36)
- + [Edit Comment](#edit-comment)
+ * [Post](#post)
+ + [Create Post](#create-post)
- [Request](#request-36)
- [Response](#response-36)
- [HTTP](#http-37)
- + [Save Comment](#save-comment)
+ + [Get Post](#get-post)
- [Request](#request-37)
- [Response](#response-37)
- [HTTP](#http-38)
- + [Create Comment Like](#create-comment-like)
+ + [Get Posts](#get-posts)
- [Request](#request-38)
- [Response](#response-38)
- [HTTP](#http-39)
+ + [Create Post Like](#create-post-like)
+ - [Request](#request-39)
+ - [Response](#response-39)
+ - [HTTP](#http-40)
+ + [Edit Post](#edit-post)
+ - [Request](#request-40)
+ - [Response](#response-40)
+ - [HTTP](#http-41)
+ + [Delete Post](#delete-post)
+ - [Request](#request-41)
+ - [Response](#response-41)
+ - [HTTP](#http-42)
+ + [Remove Post](#remove-post)
+ - [Request](#request-42)
+ - [Response](#response-42)
+ - [HTTP](#http-43)
+ + [Lock Post](#lock-post)
+ - [Request](#request-43)
+ - [Response](#response-43)
+ - [HTTP](#http-44)
+ + [Sticky Post](#sticky-post)
+ - [Request](#request-44)
+ - [Response](#response-44)
+ - [HTTP](#http-45)
+ + [Save Post](#save-post)
+ - [Request](#request-45)
+ - [Response](#response-45)
+ - [HTTP](#http-46)
+ * [Comment](#comment)
+ + [Create Comment](#create-comment)
+ - [Request](#request-46)
+ - [Response](#response-46)
+ - [HTTP](#http-47)
+ + [Edit Comment](#edit-comment)
+ - [Request](#request-47)
+ - [Response](#response-47)
+ - [HTTP](#http-48)
+ + [Delete Comment](#delete-comment)
+ - [Request](#request-48)
+ - [Response](#response-48)
+ - [HTTP](#http-49)
+ + [Remove Comment](#remove-comment)
+ - [Request](#request-49)
+ - [Response](#response-49)
+ - [HTTP](#http-50)
+ + [Mark Comment as Read](#mark-comment-as-read)
+ - [Request](#request-50)
+ - [Response](#response-50)
+ - [HTTP](#http-51)
+ + [Save Comment](#save-comment)
+ - [Request](#request-51)
+ - [Response](#response-51)
+ - [HTTP](#http-52)
+ + [Create Comment Like](#create-comment-like)
+ - [Request](#request-52)
+ - [Response](#response-52)
+ - [HTTP](#http-53)
* [RSS / Atom feeds](#rss--atom-feeds)
+ [All](#all)
+ [Community](#community-1)
These go wherever there is a `sort` field. The available sort types are:
-- `Hot` - the hottest posts/communities, depending on votes, views, comments and publish date
+- `Active` - the hottest posts/communities, depending on votes, and newest comment publish date.
+- `Hot` - the hottest posts/communities, depending on votes and publish date.
- `New` - the newest posts/communities
- `TopDay` - the most upvoted posts/communities of the current day.
- `TopWeek` - the most upvoted posts/communities of the current week.
- `TopYear` - the most upvoted posts/communities of the current year.
- `TopAll` - the most upvoted posts/communities on the current instance.
+### Undoing actions
+
+Whenever you see a `deleted: bool`, `removed: bool`, `read: bool`, `locked: bool`, etc, you can undo this action by sending `false`.
+
### Websocket vs HTTP
- Below are the websocket JSON requests / responses. For HTTP, ignore all fields except those inside `data`.
email: Option<String>,
password: String,
password_verify: String,
- admin: bool
+ admin: bool,
+ captcha_uuid: Option<String>, // Only checked if these are enabled in the server
+ captcha_answer: Option<String>,
}
}
```
`POST /user/register`
+#### Get Captcha
+
+These expire after 10 minutes.
+
+##### Request
+```rust
+{
+ op: "GetCaptcha",
+}
+```
+##### Response
+```rust
+{
+ op: "GetCaptcha",
+ data: {
+ ok?: { // Will be undefined if captchas are disabled
+ png: String, // A Base64 encoded png
+ wav: Option<String>, // A Base64 encoded wav audio file
+ uuid: String,
+ }
+ }
+}
+```
+
+##### HTTP
+
+`GET /user/get_captcha`
+
#### Get User Details
##### Request
```rust
theme: String, // Default 'darkly'
default_sort_type: i16, // The Sort types from above, zero indexed as a number
default_listing_type: i16, // Post listing types are `All, Subscribed, Community`
- auth: String
+ lang: String,
+ avatar: Option<String>,
+ banner: Option<String>,
+ preferred_username: Option<String>,
+ email: Option<String>,
+ bio: Option<String>,
+ matrix_user_id: Option<String>,
+ new_password: Option<String>,
+ new_password_verify: Option<String>,
+ old_password: Option<String>,
+ show_avatars: bool,
+ send_notifications_to_email: bool,
+ auth: String,
}
}
```
`GET /user/mentions`
-#### Edit User Mention
+#### Mark User Mention as read
+
+Only the recipient can do this.
+
##### Request
```rust
{
- op: "EditUserMention",
+ op: "MarkUserMentionAsRead",
data: {
user_mention_id: i32,
- read: Option<bool>,
+ read: bool,
auth: String,
}
}
##### Response
```rust
{
- op: "EditUserMention",
+ op: "MarkUserMentionAsRead",
data: {
mention: UserMentionView,
}
```
##### HTTP
-`PUT /user/mention`
+`POST /user/mention/mark_as_read`
+
+#### Get Private Messages
+##### Request
+```rust
+{
+ op: "GetPrivateMessages",
+ data: {
+ unread_only: bool,
+ page: Option<i64>,
+ limit: Option<i64>,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "GetPrivateMessages",
+ data: {
+ messages: Vec<PrivateMessageView>,
+ }
+}
+```
+
+##### HTTP
+
+`GET /private_message/list`
+
+#### Create Private Message
+##### Request
+```rust
+{
+ op: "CreatePrivateMessage",
+ data: {
+ content: String,
+ recipient_id: i32,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "CreatePrivateMessage",
+ data: {
+ message: PrivateMessageView,
+ }
+}
+```
+
+##### HTTP
+
+`POST /private_message`
+
+#### Edit Private Message
+##### Request
+```rust
+{
+ op: "EditPrivateMessage",
+ data: {
+ edit_id: i32,
+ content: String,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "EditPrivateMessage",
+ data: {
+ message: PrivateMessageView,
+ }
+}
+```
+
+##### HTTP
+
+`PUT /private_message`
+
+#### Delete Private Message
+##### Request
+```rust
+{
+ op: "DeletePrivateMessage",
+ data: {
+ edit_id: i32,
+ deleted: bool,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "DeletePrivateMessage",
+ data: {
+ message: PrivateMessageView,
+ }
+}
+```
+
+##### HTTP
+
+`POST /private_message/delete`
+
+#### Mark Private Message as Read
+
+Only the recipient can do this.
+
+##### Request
+```rust
+{
+ op: "MarkPrivateMessageAsRead",
+ data: {
+ edit_id: i32,
+ read: bool,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "MarkPrivateMessageAsRead",
+ data: {
+ message: PrivateMessageView,
+ }
+}
+```
+
+##### HTTP
+
+`POST /private_message/mark_as_read`
#### Mark All As Read
data: {
user_id: i32,
ban: bool,
+ remove_data: Option<bool>, // Removes/Restores their comments, posts, and communities
reason: Option<String>,
expires: Option<i64>,
auth: String
data: {
name: String,
description: Option<String>,
+ icon: Option<String>,
+ banner: Option<String>,
auth: String
}
}
data: {
name: String,
description: Option<String>,
+ icon: Option<String>,
+ banner: Option<String>,
auth: String
}
}
```rust
{
op: "GetSite"
+ data: {
+ auth: Option<String>,
+ }
+
}
```
##### Response
site: Option<SiteView>,
admins: Vec<UserView>,
banned: Vec<UserView>,
+ online: usize, // This is currently broken
+ version: String,
+ my_user: Option<User_>, // Gives back your user and settings if logged in
}
}
```
data: {
community: CommunityView,
moderators: Vec<CommunityModeratorView>,
- admins: Vec<UserView>,
}
}
```
name: String,
title: String,
description: Option<String>,
+ icon: Option<String>,
+ banner: Option<String>,
category_id: i32 ,
auth: String
}
community_id: i32,
user_id: i32,
ban: bool,
+ remove_data: Option<bool>, // Removes/Restores their comments and posts for that community
reason: Option<String>,
expires: Option<i64>,
auth: String
`POST /community/mod`
#### Edit Community
-Mods and admins can remove and lock a community, creators can delete it.
+Only mods can edit a community.
##### Request
```rust
op: "EditCommunity",
data: {
edit_id: i32,
- name: String,
title: String,
description: Option<String>,
+ icon: Option<String>,
+ banner: Option<String>,
category_id: i32,
- removed: Option<bool>,
- deleted: Option<bool>,
- reason: Option<String>,
- expires: Option<i64>,
auth: String
}
}
`PUT /community`
+#### Delete Community
+Only a creator can delete a community
+
+##### Request
+```rust
+{
+ op: "DeleteCommunity",
+ data: {
+ edit_id: i32,
+ deleted: bool,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "DeleteCommunity",
+ data: {
+ community: CommunityView
+ }
+}
+```
+##### HTTP
+
+`POST /community/delete`
+
+#### Remove Community
+Only admins can remove a community.
+
+##### Request
+```rust
+{
+ op: "RemoveCommunity",
+ data: {
+ edit_id: i32,
+ removed: bool,
+ reason: Option<String>,
+ expires: Option<i64>,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "RemoveCommunity",
+ data: {
+ community: CommunityView
+ }
+}
+```
+##### HTTP
+
+`POST /community/remove`
+
#### Follow Community
##### Request
```rust
name: String,
url: Option<String>,
body: Option<String>,
+ nsfw: bool,
community_id: i32,
- auth: String
+ auth: String,
}
}
```
comments: Vec<CommentView>,
community: CommunityView,
moderators: Vec<CommunityModeratorView>,
- admins: Vec<UserView>,
}
}
```
`POST /post/like`
#### Edit Post
-
-Mods and admins can remove and lock a post, creators can delete it.
-
##### Request
```rust
{
op: "EditPost",
data: {
edit_id: i32,
- creator_id: i32,
- community_id: i32,
name: String,
url: Option<String>,
body: Option<String>,
- removed: Option<bool>,
- deleted: Option<bool>,
- locked: Option<bool>,
- reason: Option<String>,
- auth: String
+ nsfw: bool,
+ auth: String,
}
}
```
`PUT /post`
+#### Delete Post
+##### Request
+```rust
+{
+ op: "DeletePost",
+ data: {
+ edit_id: i32,
+ deleted: bool,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "DeletePost",
+ data: {
+ post: PostView
+ }
+}
+```
+
+##### HTTP
+
+`POST /post/delete`
+
+#### Remove Post
+
+Only admins and mods can remove a post.
+
+##### Request
+```rust
+{
+ op: "RemovePost",
+ data: {
+ edit_id: i32,
+ removed: bool,
+ reason: Option<String>,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "RemovePost",
+ data: {
+ post: PostView
+ }
+}
+```
+
+##### HTTP
+
+`POST /post/remove`
+
+#### Lock Post
+
+Only admins and mods can lock a post.
+
+##### Request
+```rust
+{
+ op: "LockPost",
+ data: {
+ edit_id: i32,
+ locked: bool,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "LockPost",
+ data: {
+ post: PostView
+ }
+}
+```
+
+##### HTTP
+
+`POST /post/lock`
+
+#### Sticky Post
+
+Only admins and mods can sticky a post.
+
+##### Request
+```rust
+{
+ op: "StickyPost",
+ data: {
+ edit_id: i32,
+ stickied: bool,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "StickyPost",
+ data: {
+ post: PostView
+ }
+}
+```
+
+##### HTTP
+
+`POST /post/sticky`
+
#### Save Post
##### Request
```rust
data: {
content: String,
parent_id: Option<i32>,
- edit_id: Option<i32>,
post_id: i32,
+ form_id: Option<String>, // An optional form id, so you know which message came back
auth: String
}
}
#### Edit Comment
-Mods and admins can remove a comment, creators can delete it.
+Only the creator can edit the comment.
##### Request
```rust
op: "EditComment",
data: {
content: String,
- parent_id: Option<i32>,
edit_id: i32,
- creator_id: i32,
- post_id: i32,
- removed: Option<bool>,
- deleted: Option<bool>,
- reason: Option<String>,
- read: Option<bool>,
- auth: String
+ form_id: Option<String>,
+ auth: String,
}
}
```
`PUT /comment`
+#### Delete Comment
+
+Only the creator can delete the comment.
+
+##### Request
+```rust
+{
+ op: "DeleteComment",
+ data: {
+ edit_id: i32,
+ deleted: bool,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "DeleteComment",
+ data: {
+ comment: CommentView
+ }
+}
+```
+##### HTTP
+
+`POST /comment/delete`
+
+
+#### Remove Comment
+
+Only a mod or admin can remove the comment.
+
+##### Request
+```rust
+{
+ op: "RemoveComment",
+ data: {
+ edit_id: i32,
+ removed: bool,
+ reason: Option<String>,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "RemoveComment",
+ data: {
+ comment: CommentView
+ }
+}
+```
+##### HTTP
+
+`POST /comment/remove`
+
+#### Mark Comment as Read
+
+Only the recipient can do this.
+
+##### Request
+```rust
+{
+ op: "MarkCommentAsRead",
+ data: {
+ edit_id: i32,
+ read: bool,
+ auth: String,
+ }
+}
+```
+##### Response
+```rust
+{
+ op: "MarkCommentAsRead",
+ data: {
+ comment: CommentView
+ }
+}
+```
+##### HTTP
+
+`POST /comment/mark_as_read`
+
#### Save Comment
##### Request
```rust
op: "CreateCommentLike",
data: {
comment_id: i32,
- post_id: i32,
score: i16,
auth: String
}
# It is not intended for manual editing.
[[package]]
name = "activitystreams"
-version = "0.6.2"
+version = "0.7.0-alpha.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "464cb473bfb402b857cc15b1153974c203a43f1485da4dda15cd17a738548958"
+checksum = "e3490e8e9d7744aada19fb2fb4e2564f8c22fd080a3561093ac91ed7d10bfe78"
dependencies = [
- "activitystreams-derive",
"chrono",
"mime",
"serde 1.0.114",
"url",
]
-[[package]]
-name = "activitystreams-derive"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c39ba5929399e9f921055bac76dd8f47419fa5b6b6da1ac4c1e82b94ed0ac7b4"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "activitystreams-ext"
-version = "0.1.0"
-source = "git+https://git.asonix.dog/asonix/activitystreams-ext#e5c97f4ea9f60e49bc7ff27fb0fb515d3190fd25"
-dependencies = [
- "activitystreams-new",
- "serde 1.0.114",
- "serde_json",
-]
-
-[[package]]
-name = "activitystreams-new"
-version = "0.1.0"
-source = "git+https://git.asonix.dog/asonix/activitystreams-sketch#99c7e9aa5596eda846a1ebd5978ca72d11d4c08a"
+version = "0.1.0-alpha.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb8e19a0810cc25df3535061a08b7d8f8a734d309ea4411c57a9767e4a2ffa0e"
dependencies = [
"activitystreams",
"serde 1.0.114",
"serde_json",
- "typed-builder",
]
[[package]]
dependencies = [
"actix-rt",
"actix_derive",
- "bitflags",
+ "bitflags 1.2.1",
"bytes",
"crossbeam-channel",
"derive_more",
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09e55f0a5c2ca15795035d90c46bd0e73a5123b72f68f12596d6ba5282051380"
dependencies = [
- "bitflags",
+ "bitflags 1.2.1",
"bytes",
"futures-core",
"futures-sink",
[[package]]
name = "actix-files"
-version = "0.3.0-alpha.1"
+version = "0.3.0-beta.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23b32e0fdd5998c2712549cbc39dff46c8754d55e3dd9f4d017d9e28de30cac6"
+checksum = "627f597ad98061816766201db8afc7444752992f2919b2e60f53a7fa27f01aed"
dependencies = [
"actix-http",
"actix-service",
"actix-web",
- "bitflags",
+ "bitflags 1.2.1",
"bytes",
"derive_more",
"futures-core",
[[package]]
name = "actix-http"
-version = "2.0.0-alpha.4"
+version = "2.0.0-beta.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd7ea0568480d199952a51de70271946da57c33cc0e8b83f54383e70958dff21"
+checksum = "44529cd6813ebf4a2f2a6ea36ffe88a0e4b0bc08b26ad0b8f7f4581d4d4f3247"
dependencies = [
"actix-codec",
"actix-connect",
"actix-tls",
"actix-utils",
"base64 0.12.3",
- "bitflags",
+ "bitflags 1.2.1",
"brotli2",
"bytes",
+ "cookie",
"copyless",
"derive_more",
"either",
"http",
"httparse",
"indexmap",
+ "itoa",
"language-tags",
"lazy_static",
"log",
"serde 1.0.114",
"serde_json",
"serde_urlencoded",
- "sha-1",
+ "sha-1 0.9.1",
"slab",
"time 0.2.16",
]
[[package]]
name = "actix-threadpool"
-version = "0.3.2"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91164716d956745c79dcea5e66d2aa04506549958accefcede5368c70f2fd4ff"
+checksum = "d209f04d002854b9afd3743032a27b066158817965bf5d036824d19ac2cc0e30"
dependencies = [
"derive_more",
"futures-channel",
"lazy_static",
"log",
"num_cpus",
- "parking_lot 0.10.2",
+ "parking_lot 0.11.0",
"threadpool",
]
"actix-codec",
"actix-rt",
"actix-service",
- "bitflags",
+ "bitflags 1.2.1",
"bytes",
"either",
"futures",
[[package]]
name = "actix-web"
-version = "3.0.0-alpha.3"
+version = "3.0.0-beta.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bd6df56ec5f9a1a0d8335f156f36e1e8f76dbd736fa0cc0f6bc3a69be1e6124"
+checksum = "9125c29b7d9911bfdb4d0d4d8f1cf4fee4f21515cf2a405a423c30c245364297"
dependencies = [
"actix-codec",
"actix-http",
[[package]]
name = "actix-web-actors"
-version = "3.0.0-alpha.1"
+version = "3.0.0-beta.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b5efeb3907582f9c724ce27be093ab8aafabd97be828bc6750c0d467f5e1aa3"
+checksum = "55ef22b33c49a28dda61866d5573c5b8ceb080a099cd59e7371b78b48bbf1bc0"
dependencies = [
"actix",
"actix-codec",
"actix-http",
"actix-web",
"bytes",
- "futures",
+ "futures-channel",
+ "futures-core",
"pin-project",
]
[[package]]
name = "actix-web-codegen"
-version = "0.2.2"
+version = "0.3.0-beta.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a71bf475cbe07281d0b3696abb48212db118e7e23219f13596ce865235ff5766"
+checksum = "df9679f5b1f4c819de08b63b0a61a131b2fdc30b367c2c208984fda8eaa07fa0"
dependencies = [
"proc-macro2",
"quote",
[[package]]
name = "addr2line"
-version = "0.12.2"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "602d785912f476e480434627e8732e6766b760c045bbf897d9dfaa9f4fbd399c"
+checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
-version = "0.2.2"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccc9a9dd069569f212bc4330af9f17c4afb5e8ce185e83dbb14f1349dda18b10"
+checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
[[package]]
name = "adler32"
-version = "1.1.0"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d"
+checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "aho-corasick"
"winapi 0.3.9",
]
+[[package]]
+name = "anyhow"
+version = "1.0.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
+
[[package]]
name = "arc-swap"
version = "0.4.7"
[[package]]
name = "awc"
-version = "2.0.0-alpha.2"
+version = "2.0.0-beta.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7038a9747cd5159b9f0550895eaf865c0143baa7e4eee834e9294d0a7e0e4be"
+checksum = "eafb5c150b1dc89bf6aa5907ed6900534320c41920b5d6f13ff3ddb40f14dfac"
dependencies = [
"actix-codec",
"actix-http",
[[package]]
name = "backtrace"
-version = "0.3.49"
+version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05100821de9e028f12ae3d189176b41ee198341eb8f369956407fea2f5cc666c"
+checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293"
dependencies = [
"addr2line",
"cfg-if",
"libc",
- "miniz_oxide 0.3.7",
+ "miniz_oxide",
"object",
"rustc-demangle",
]
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1"
+[[package]]
+name = "base64"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557"
+dependencies = [
+ "byteorder",
+]
+
[[package]]
name = "base64"
version = "0.9.3"
"getrandom",
]
+[[package]]
+name = "bitflags"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
+
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
- "generic-array 0.14.2",
+ "generic-array 0.14.3",
]
[[package]]
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa136449e765dc7faa244561ccae839c394048667929af599b5d931ebe7b7f10"
dependencies = [
- "generic-array 0.14.2",
+ "generic-array 0.14.3",
]
[[package]]
"libc",
]
+[[package]]
+name = "buf-min"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6ae7069aad07c7cdefe6a22a671f00650728bd2331a4cc62e1e5d0becdf9ca4"
+dependencies = [
+ "bytes",
+]
+
[[package]]
name = "bufstream"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+[[package]]
+name = "bytemuck"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db7a1029718df60331e557c9e83a55523c955e5dd2a7bfeffad6bbd50b538ae9"
+
[[package]]
name = "byteorder"
version = "1.3.4"
[[package]]
name = "bytes"
-version = "0.5.5"
+version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "118cf036fbb97d0816e3c34b2d7a1e8cfc60f68fcf63d550ddbe9bd5f59c213b"
-dependencies = [
- "loom",
-]
+checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
[[package]]
name = "bytestring"
"bytes",
]
+[[package]]
+name = "c_vec"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8a318911dce53b5f1ca6539c44f5342c632269f0fa7ea3e35f32458c27a7c30"
+
+[[package]]
+name = "captcha"
+version = "0.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d060a3be43adb2fe89d3448e9a193149806139b1ce99281865fcab7aeaf04ed"
+dependencies = [
+ "base64 0.5.2",
+ "image",
+ "lodepng",
+ "rand 0.3.23",
+ "serde_json",
+ "time 0.1.43",
+]
+
[[package]]
name = "cc"
-version = "1.0.57"
+version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fde55d2a2bfaa4c9668bbc63f531fbdeee3ffe188f4662511ce2c22b3eedebe"
+checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518"
[[package]]
name = "cfg-if"
[[package]]
name = "chrono"
-version = "0.4.12"
+version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0fee792e164f78f5fe0c296cc2eb3688a2ca2b70cdff33040922d298203f0c4"
+checksum = "c74d84029116787153e02106bf53e66828452a4b325cc8652b788b5967c0a0b6"
dependencies = [
"num-integer",
"num-traits 0.2.12",
dependencies = [
"ansi_term",
"atty",
- "bitflags",
+ "bitflags 1.2.1",
"strsim 0.8.0",
"textwrap",
"unicode-width",
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
- "bitflags",
+ "bitflags 1.2.1",
]
[[package]]
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
dependencies = [
- "bitflags",
+ "bitflags 1.2.1",
]
+[[package]]
+name = "color_quant"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd"
+
[[package]]
name = "comrak"
version = "0.7.0"
"serde-hjson",
]
+[[package]]
+name = "cookie"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1373a16a4937bc34efec7b391f9c1500c30b8478a701a4f44c9165cc0475a6e0"
+dependencies = [
+ "percent-encoding",
+ "time 0.2.16",
+ "version_check 0.9.2",
+]
+
[[package]]
name = "copyless"
version = "0.1.5"
[[package]]
name = "cpuid-bool"
-version = "0.1.0"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d375c433320f6c5057ae04a04376eef4d04ce2801448cf8863a78da99107be4"
+checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
[[package]]
name = "crc32fast"
[[package]]
name = "crossbeam-channel"
-version = "0.4.2"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061"
+checksum = "09ee0cc8804d5393478d743b035099520087a5186f3b93fa58cec08fa62407b6"
dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+ "maybe-uninit",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
+dependencies = [
+ "autocfg 1.0.0",
+ "cfg-if",
+ "crossbeam-utils",
+ "lazy_static",
+ "maybe-uninit",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"
+dependencies = [
+ "cfg-if",
"crossbeam-utils",
"maybe-uninit",
]
"syn",
]
+[[package]]
+name = "deflate"
+version = "0.7.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4"
+dependencies = [
+ "adler32",
+ "byteorder",
+]
+
[[package]]
name = "derive_builder"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2de9deab977a153492a1468d1b1c0662c1cf39e5ea87d0c060ecd59ef18d8c"
dependencies = [
- "bitflags",
+ "bitflags 1.2.1",
"byteorder",
"chrono",
"diesel_derives",
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
- "generic-array 0.14.2",
+ "generic-array 0.14.3",
]
[[package]]
"syn",
]
+[[package]]
+name = "enum_primitive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180"
+dependencies = [
+ "num-traits 0.1.43",
+]
+
[[package]]
name = "env_logger"
version = "0.7.1"
"termcolor",
]
-[[package]]
-name = "failure"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
-dependencies = [
- "backtrace",
- "failure_derive",
-]
-
-[[package]]
-name = "failure_derive"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "synstructure",
-]
-
[[package]]
name = "fake-simd"
version = "0.1.2"
"cfg-if",
"crc32fast",
"libc",
- "miniz_oxide 0.4.0",
+ "miniz_oxide",
]
[[package]]
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
dependencies = [
- "bitflags",
+ "bitflags 1.2.1",
"fuchsia-zircon-sys",
]
"byteorder",
]
-[[package]]
-name = "generator"
-version = "0.6.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "add72f17bb81521258fcc8a7a3245b1e184e916bfbe34f0ea89558f440df5c68"
-dependencies = [
- "cc",
- "libc",
- "log",
- "rustc_version",
- "winapi 0.3.9",
-]
-
[[package]]
name = "generic-array"
version = "0.12.3"
[[package]]
name = "generic-array"
-version = "0.14.2"
+version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac746a5f3bbfdadd6106868134545e684693d54d9d44f6e9588a7d54af0bf980"
+checksum = "60fb4bb6bba52f78a471264d9a3b7d026cc0af47b22cd2cffbc0b787ca003e63"
dependencies = [
"typenum",
"version_check 0.9.2",
"wasi",
]
+[[package]]
+name = "gif"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2e41945ba23db3bf51b24756d73d81acb4f28d85c3dccc32c6fae904438c25f"
+dependencies = [
+ "color_quant",
+ "lzw",
+]
+
[[package]]
name = "gimli"
-version = "0.21.0"
+version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c"
+checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
[[package]]
name = "h2"
-version = "0.2.5"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff"
+checksum = "993f9e0baeed60001cf565546b0d3dbe6a6ad23f2bd31644a133c641eccf6d53"
dependencies = [
"bytes",
"fnv",
"futures-util",
"http",
"indexmap",
- "log",
"slab",
"tokio",
"tokio-util 0.3.1",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34f595585f103464d8d2f6e9864682d74c1601fed5e07d62b1c9058dba8246fb"
+dependencies = [
+ "autocfg 1.0.0",
]
[[package]]
[[package]]
name = "hermit-abi"
-version = "0.1.14"
+version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909"
+checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9"
dependencies = [
"libc",
]
[[package]]
name = "http-signature-normalization"
-version = "0.5.1"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "648233553603e7bb55bc1ea08a514661e212c09c10f6434507894273d8b5e773"
+checksum = "ee917294413cec0db93a8af6ecfa63730c1d2bb604bd1da69ba75b342fb23f21"
dependencies = [
"chrono",
"thiserror",
"unicode-normalization",
]
+[[package]]
+name = "image"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c3f4f5ea213ed9899eca760a8a14091d4b82d33e27cf8ced336ff730e9f6da8"
+dependencies = [
+ "byteorder",
+ "enum_primitive",
+ "gif",
+ "jpeg-decoder",
+ "num-iter",
+ "num-rational",
+ "num-traits 0.1.43",
+ "png",
+ "scoped_threadpool",
+]
+
[[package]]
name = "indexmap"
-version = "1.4.0"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe"
+checksum = "5b88cd59ee5f71fea89a62248fc8f387d44400cefe05ef548466d61ced9029a7"
dependencies = [
"autocfg 1.0.0",
+ "hashbrown",
]
+[[package]]
+name = "inflate"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1238524675af3938a7c74980899535854b88ba07907bb1c944abe5b8fc437e5"
+
[[package]]
name = "instant"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69da7ce1490173c2bf4d26bc8be429aaeeaf4cce6c4b970b7949651fa17655fe"
+checksum = "5b141fdc7836c525d4d594027d318c84161ca17aaf8113ab1f81ab93ae897485"
[[package]]
name = "iovec"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
+[[package]]
+name = "jpeg-decoder"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc797adac5f083b8ff0ca6f6294a999393d76e197c36488e2ef732c4715f6fa3"
+dependencies = [
+ "byteorder",
+ "rayon",
+]
+
[[package]]
name = "js-sys"
-version = "0.3.41"
+version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4b9172132a62451e56142bff9afc91c8e4a4500aa5b847da36815b63bfda916"
+checksum = "85a7e2c92a4804dd459b86c339278d0fe87cf93757fae222c3fa3ae75458bc73"
dependencies = [
"wasm-bindgen",
]
"bcrypt",
"chrono",
"diesel",
+ "lazy_static",
"log",
+ "regex",
"serde 1.0.114",
"serde_json",
"sha2",
"strum",
"strum_macros",
+ "url",
]
[[package]]
dependencies = [
"activitystreams",
"activitystreams-ext",
- "activitystreams-new",
"actix",
"actix-files",
"actix-rt",
"actix-web",
"actix-web-actors",
+ "anyhow",
"async-trait",
"awc",
"base64 0.12.3",
"bcrypt",
+ "captcha",
"chrono",
"diesel",
"diesel_migrations",
"dotenv",
"env_logger",
- "failure",
"futures",
"http",
"http-signature-normalization-actix",
"sha2",
"strum",
"strum_macros",
+ "thiserror",
"tokio",
"url",
"uuid 0.8.1",
checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616"
dependencies = [
"arrayvec",
- "bitflags",
+ "bitflags 1.2.1",
"cfg-if",
"ryu",
"static_assertions",
[[package]]
name = "libc"
-version = "0.2.71"
+version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
+checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10"
[[package]]
name = "linked-hash-map"
[[package]]
name = "lock_api"
-version = "0.4.0"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de302ce1fe7482db13738fbaf2e21cfb06a986b89c0bf38d88abf16681aada4e"
+checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c"
dependencies = [
"scopeguard",
]
[[package]]
-name = "log"
-version = "0.4.8"
+name = "lodepng"
+version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
+checksum = "8ac1dfdf85b7d5dea61a620e12c051a72078189366a0b3c0ab331e30847def2f"
dependencies = [
- "cfg-if",
+ "c_vec",
+ "cc",
+ "libc",
+ "rgb",
]
[[package]]
-name = "loom"
-version = "0.3.4"
+name = "log"
+version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ecc775857611e1df29abba5c41355cdf540e7e9d4acfdf0f355eefee82330b7"
+checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
dependencies = [
"cfg-if",
- "generator",
- "scoped-tls",
]
[[package]]
"linked-hash-map 0.5.3",
]
+[[package]]
+name = "lzw"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
+
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
+[[package]]
+name = "memoffset"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f"
+dependencies = [
+ "autocfg 1.0.0",
+]
+
[[package]]
name = "migrations_internals"
version = "1.4.1"
"unicase",
]
-[[package]]
-name = "miniz_oxide"
-version = "0.3.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
-dependencies = [
- "adler32",
-]
-
[[package]]
name = "miniz_oxide"
version = "0.4.0"
"num-traits 0.2.12",
]
+[[package]]
+name = "num-iter"
+version = "0.1.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f"
+dependencies = [
+ "autocfg 1.0.0",
+ "num-integer",
+ "num-traits 0.2.12",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.1.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e"
+dependencies = [
+ "num-integer",
+ "num-traits 0.2.12",
+]
+
[[package]]
name = "num-traits"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4"
dependencies = [
- "bitflags",
+ "bitflags 1.2.1",
"cfg-if",
"foreign-types",
"lazy_static",
checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733"
dependencies = [
"instant",
- "lock_api 0.4.0",
+ "lock_api 0.4.1",
"parking_lot_core 0.8.0",
]
dependencies = [
"maplit",
"pest",
- "sha-1",
+ "sha-1 0.8.2",
]
[[package]]
name = "pin-project"
-version = "0.4.22"
+version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12e3a6cdbfe94a5e4572812a0201f8c0ed98c1c452c7b8563ce2276988ef9c17"
+checksum = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
-version = "0.4.22"
+version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a0ffd45cf79d88737d7cc85bfd5d2894bee1139b356e616fe85dc389c61aaf7"
+checksum = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f"
dependencies = [
"proc-macro2",
"quote",
[[package]]
name = "pkg-config"
-version = "0.3.17"
+version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
+checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33"
+
+[[package]]
+name = "png"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48f397b84083c2753ba53c7b56ad023edb94512b2885ffe227c66ff7edb61868"
+dependencies = [
+ "bitflags 0.7.0",
+ "deflate",
+ "inflate",
+ "num-iter",
+]
[[package]]
name = "ppv-lite86"
[[package]]
name = "proc-macro-hack"
-version = "0.5.16"
+version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
+checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598"
[[package]]
name = "proc-macro-nested"
[[package]]
name = "proc-macro2"
-version = "1.0.18"
+version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
+checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12"
dependencies = [
"unicode-xid",
]
"scheduled-thread-pool",
]
+[[package]]
+name = "rand"
+version = "0.3.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c"
+dependencies = [
+ "libc",
+ "rand 0.4.6",
+]
+
[[package]]
name = "rand"
version = "0.4.6"
"rand_core 0.3.1",
]
+[[package]]
+name = "rayon"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080"
+dependencies = [
+ "autocfg 1.0.0",
+ "crossbeam-deque",
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-queue",
+ "crossbeam-utils",
+ "lazy_static",
+ "num_cpus",
+]
+
[[package]]
name = "rdrand"
version = "0.4.0"
[[package]]
name = "redox_syscall"
-version = "0.1.56"
+version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "regex"
"quick-error",
]
+[[package]]
+name = "rgb"
+version = "0.8.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ef54b45ae131327a88597e2463fee4098ad6c88ba7b6af4b3987db8aad4098"
+dependencies = [
+ "bytemuck",
+]
+
[[package]]
name = "ring"
version = "0.16.15"
]
[[package]]
-name = "scoped-tls"
-version = "0.1.2"
+name = "scoped_threadpool"
+version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28"
+checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
[[package]]
name = "scopeguard"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535"
dependencies = [
- "bitflags",
+ "bitflags 1.2.1",
"core-foundation",
"core-foundation-sys",
"libc",
[[package]]
name = "serde_json"
-version = "1.0.56"
+version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3"
+checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
dependencies = [
"indexmap",
"itoa",
"opaque-debug 0.2.3",
]
+[[package]]
+name = "sha-1"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "170a36ea86c864a3f16dd2687712dd6646f7019f301e57537c7f4dc9f5916770"
+dependencies = [
+ "block-buffer 0.9.0",
+ "cfg-if",
+ "cpuid-bool",
+ "digest 0.9.0",
+ "opaque-debug 0.3.0",
+]
+
[[package]]
name = "sha1"
version = "0.6.0"
[[package]]
name = "simple_asn1"
-version = "0.4.0"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b25ecba7165254f0c97d6c22a64b1122a03634b18d20a34daf21e18f892e618"
+checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b"
dependencies = [
"chrono",
"num-bigint",
[[package]]
name = "smallvec"
-version = "1.4.0"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
+checksum = "3757cb9d89161a2f24e1cf78efa0c1fcff485d18e3f55e0aa3480824ddaa0f3f"
[[package]]
name = "socket2"
[[package]]
name = "syn"
-version = "1.0.33"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-xid",
-]
-
-[[package]]
-name = "synstructure"
-version = "0.12.4"
+version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
+checksum = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250"
dependencies = [
"proc-macro2",
"quote",
- "syn",
"unicode-xid",
]
[[package]]
name = "tokio"
-version = "0.2.21"
+version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58"
+checksum = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd"
dependencies = [
"bytes",
"fnv",
"tokio",
]
+[[package]]
+name = "tracing"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0aae59226cf195d8e74d4b34beae1859257efb4e5fed3f147d2dc2c7d372178"
+dependencies = [
+ "cfg-if",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2734b5a028fa697686f16c6d18c2c6a3c7e41513f9a213abb6754c4acb3c8d7"
+dependencies = [
+ "lazy_static",
+]
+
[[package]]
name = "trust-dns-proto"
version = "0.19.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d"
-[[package]]
-name = "typed-builder"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85fc4459191c621a53ef6c6ca5642e6e0e5ccc61f3e5b8ad6b6ab5317f0200fb"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "typenum"
version = "1.12.0"
[[package]]
name = "v_escape"
-version = "0.7.4"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "660b101c07b5d0863deb9e7fb3138777e858d6d2a79f9e6049a27d1cc77c6da6"
+checksum = "7b2d5ca56f0412d5ad5e642202e5c8fb61b61ad39435a53ed501fbd45380e8d3"
dependencies = [
+ "buf-min",
"v_escape_derive",
]
[[package]]
name = "v_escape_derive"
-version = "0.5.6"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2ca2a14bc3fc5b64d188b087a7d3a927df87b152e941ccfbc66672e20c467ae"
+checksum = "cae7cffca0b1f9af9b20610f6fdeee9ffcce61417b5ad186a5d482dc904e24cd"
dependencies = [
"nom 4.2.3",
"proc-macro2",
[[package]]
name = "v_htmlescape"
-version = "0.4.5"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e33e939c0d8cf047514fb6ba7d5aac78bc56677a6938b2ee67000b91f2e97e41"
+checksum = "f5fd25529cb2f78527b5ee507bcfb357b26d057b5e480853c26d49a4ead5c629"
dependencies = [
"cfg-if",
"v_escape",
[[package]]
name = "wasm-bindgen"
-version = "0.2.64"
+version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a634620115e4a229108b71bde263bb4220c483b3f07f5ba514ee8d15064c4c2"
+checksum = "f0563a9a4b071746dd5aedbc3a28c6fe9be4586fb3fbadb67c400d4f53c6b16c"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.64"
+version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e53963b583d18a5aa3aaae4b4c1cb535218246131ba22a71f05b518098571df"
+checksum = "bc71e4c5efa60fb9e74160e89b93353bc24059999c0ae0fb03affc39770310b0"
dependencies = [
"bumpalo",
"lazy_static",
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.64"
+version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fcfd5ef6eec85623b4c6e844293d4516470d8f19cd72d0d12246017eb9060b8"
+checksum = "97c57cefa5fa80e2ba15641578b44d36e7a64279bc5ed43c6dbaf329457a2ed2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.64"
+version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9adff9ee0e94b926ca81b57f57f86d5545cdcb1d259e21ec9bdd95b901754c75"
+checksum = "841a6d1c35c6f596ccea1f82504a192a60378f64b3bb0261904ad8f2f5657556"
dependencies = [
"proc-macro2",
"quote",
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.64"
+version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f7b90ea6c632dd06fd765d44542e234d5e63d9bb917ecd64d79778a13bd79ae"
+checksum = "93b162580e34310e5931c4b792560108b10fd14d64915d7fff8ff00180e70092"
[[package]]
name = "web-sys"
-version = "0.3.41"
+version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "863539788676619aac1a23e2df3655e96b32b0e05eb72ca34ba045ad573c625d"
+checksum = "dda38f4e5ca63eda02c059d243aa25b5f35ab98451e518c51612cd0f1bd19a47"
dependencies = [
"js-sys",
"wasm-bindgen",
diesel = "1.4.4"
diesel_migrations = "1.4.0"
dotenv = "0.15.0"
-activitystreams = "0.6.2"
-activitystreams-new = { git = "https://git.asonix.dog/asonix/activitystreams-sketch" }
-activitystreams-ext = { git = "https://git.asonix.dog/asonix/activitystreams-ext" }
+activitystreams = "0.7.0-alpha.3"
+activitystreams-ext = "0.1.0-alpha.2"
bcrypt = "0.8.0"
chrono = { version = "0.4.7", features = ["serde"] }
serde_json = { version = "1.0.52", features = ["preserve_order"]}
-failure = "0.1.8"
serde = { version = "1.0.105", features = ["derive"] }
actix = "0.10.0-alpha.2"
actix-web = { version = "3.0.0-alpha.3", features = ["rustls"] }
uuid = { version = "0.8", features = ["serde", "v4"] }
sha2 = "0.9"
async-trait = "0.1.36"
+captcha = "0.0.7"
+anyhow = "1.0.32"
+thiserror = "1.0.20"
jwt_secret: "changeme"
# The location of the frontend
front_end_dir: "../ui/dist"
+ # address where pictrs is available
+ pictrs_url: "http://pictrs:8080"
# rate limits for various user actions, by user ip
rate_limit: {
# maximum number of messages created in interval
register: 3
# interval length for registration limit
register_per_second: 3600
+ # maximum number of image uploads in interval
+ image: 6
+ # interval length for image uploads
+ image_per_second: 3600
}
# settings related to activitypub federation
federation: {
enabled: false
# whether tls is required for activitypub. only disable this for debugging, never for producion.
tls_enabled: true
- # comma seperated list of instances with which federation is allowed
+ # comma separated list of instances with which federation is allowed
allowed_instances: ""
+ # comma separated list of instances which are blocked from federating
+ blocked_instances: ""
+ }
+ captcha: {
+ enabled: true
+ difficulty: medium # Can be easy, medium, or hard
}
# # email sending configuration
# email: {
strum_macros = "0.18.0"
log = "0.4.0"
sha2 = "0.9"
-bcrypt = "0.8.0"
\ No newline at end of file
+bcrypt = "0.8.0"
+url = { version = "2.1.1", features = ["serde"] }
+lazy_static = "1.3.0"
+regex = "1.3.5"
activity.find(activity_id).first::<Self>(conn)
}
- fn delete(conn: &PgConnection, activity_id: i32) -> Result<usize, Error> {
- use crate::schema::activity::dsl::*;
- diesel::delete(activity.find(activity_id)).execute(conn)
- }
-
fn create(conn: &PgConnection, new_activity: &ActivityForm) -> Result<Self, Error> {
use crate::schema::activity::dsl::*;
insert_into(activity)
email: None,
matrix_user_id: None,
avatar: None,
+ banner: None,
admin: false,
banned: false,
updated: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_862362".into(),
bio: None,
local: true,
private_key: None,
};
let read_activity = Activity::read(&conn, inserted_activity.id).unwrap();
- let num_deleted = Activity::delete(&conn, inserted_activity.id).unwrap();
User_::delete(&conn, inserted_creator.id).unwrap();
assert_eq!(expected_activity, read_activity);
assert_eq!(expected_activity, inserted_activity);
- assert_eq!(1, num_deleted);
}
}
category.find(category_id).first::<Self>(conn)
}
- fn delete(conn: &PgConnection, category_id: i32) -> Result<usize, Error> {
- diesel::delete(category.find(category_id)).execute(conn)
- }
-
fn create(conn: &PgConnection, new_category: &CategoryForm) -> Result<Self, Error> {
insert_into(category)
.values(new_category)
use super::{post::Post, *};
use crate::schema::{comment, comment_like, comment_saved};
+use url::{ParseError, Url};
// WITH RECURSIVE MyTree AS (
// SELECT * FROM comment WHERE parent_id IS NULL
pub local: bool,
}
+impl CommentForm {
+ pub fn get_ap_id(&self) -> Result<Url, ParseError> {
+ Url::parse(&self.ap_id)
+ }
+}
+
impl Crud<CommentForm> for Comment {
fn read(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
use crate::schema::comment::dsl::*;
comment.filter(ap_id.eq(object_id)).first::<Self>(conn)
}
- pub fn mark_as_read(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
+ pub fn permadelete_for_creator(
+ conn: &PgConnection,
+ for_creator_id: i32,
+ ) -> Result<Vec<Self>, Error> {
+ use crate::schema::comment::dsl::*;
+ diesel::update(comment.filter(creator_id.eq(for_creator_id)))
+ .set((
+ content.eq("*Permananently Deleted*"),
+ deleted.eq(true),
+ updated.eq(naive_now()),
+ ))
+ .get_results::<Self>(conn)
+ }
+
+ pub fn update_deleted(
+ conn: &PgConnection,
+ comment_id: i32,
+ new_deleted: bool,
+ ) -> Result<Self, Error> {
use crate::schema::comment::dsl::*;
+ diesel::update(comment.find(comment_id))
+ .set((deleted.eq(new_deleted), updated.eq(naive_now())))
+ .get_result::<Self>(conn)
+ }
+ pub fn update_removed(
+ conn: &PgConnection,
+ comment_id: i32,
+ new_removed: bool,
+ ) -> Result<Self, Error> {
+ use crate::schema::comment::dsl::*;
diesel::update(comment.find(comment_id))
- .set(read.eq(true))
+ .set((removed.eq(new_removed), updated.eq(naive_now())))
.get_result::<Self>(conn)
}
- pub fn permadelete(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
+ pub fn update_removed_for_creator(
+ conn: &PgConnection,
+ for_creator_id: i32,
+ new_removed: bool,
+ ) -> Result<Vec<Self>, Error> {
+ use crate::schema::comment::dsl::*;
+ diesel::update(comment.filter(creator_id.eq(for_creator_id)))
+ .set((removed.eq(new_removed), updated.eq(naive_now())))
+ .get_results::<Self>(conn)
+ }
+
+ pub fn update_read(conn: &PgConnection, comment_id: i32, new_read: bool) -> Result<Self, Error> {
use crate::schema::comment::dsl::*;
+ diesel::update(comment.find(comment_id))
+ .set(read.eq(new_read))
+ .get_result::<Self>(conn)
+ }
+ pub fn update_content(
+ conn: &PgConnection,
+ comment_id: i32,
+ new_content: &str,
+ ) -> Result<Self, Error> {
+ use crate::schema::comment::dsl::*;
diesel::update(comment.find(comment_id))
- .set((
- content.eq("*Permananently Deleted*"),
- deleted.eq(true),
- updated.eq(naive_now()),
- ))
+ .set((content.eq(new_content), updated.eq(naive_now())))
.get_result::<Self>(conn)
}
+
+ pub fn upsert(conn: &PgConnection, comment_form: &CommentForm) -> Result<Self, Error> {
+ let existing = Self::read_from_apub_id(conn, &comment_form.ap_id);
+ match existing {
+ Err(NotFound {}) => Ok(Self::create(conn, &comment_form)?),
+ Ok(p) => Ok(Self::update(conn, p.id, &comment_form)?),
+ Err(e) => Err(e),
+ }
+ }
}
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Clone)]
}
impl Likeable<CommentLikeForm> for CommentLike {
- fn read(conn: &PgConnection, comment_id_from: i32) -> Result<Vec<Self>, Error> {
- use crate::schema::comment_like::dsl::*;
- comment_like
- .filter(comment_id.eq(comment_id_from))
- .load::<Self>(conn)
- }
-
fn like(conn: &PgConnection, comment_like_form: &CommentLikeForm) -> Result<Self, Error> {
use crate::schema::comment_like::dsl::*;
insert_into(comment_like)
.values(comment_like_form)
.get_result::<Self>(conn)
}
- fn remove(conn: &PgConnection, comment_like_form: &CommentLikeForm) -> Result<usize, Error> {
- use crate::schema::comment_like::dsl::*;
+ fn remove(conn: &PgConnection, user_id: i32, comment_id: i32) -> Result<usize, Error> {
+ use crate::schema::comment_like::dsl;
diesel::delete(
- comment_like
- .filter(comment_id.eq(comment_like_form.comment_id))
- .filter(user_id.eq(comment_like_form.user_id)),
+ dsl::comment_like
+ .filter(dsl::comment_id.eq(comment_id))
+ .filter(dsl::user_id.eq(user_id)),
)
.execute(conn)
}
}
-impl CommentLike {
- pub fn from_post(conn: &PgConnection, post_id_from: i32) -> Result<Vec<Self>, Error> {
- use crate::schema::comment_like::dsl::*;
- comment_like
- .filter(post_id.eq(post_id_from))
- .load::<Self>(conn)
- }
-}
-
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
#[belongs_to(Comment)]
#[table_name = "comment_saved"]
email: None,
matrix_user_id: None,
avatar: None,
+ banner: None,
admin: false,
banned: false,
updated: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_283687".into(),
bio: None,
local: true,
private_key: None,
deleted: None,
updated: None,
nsfw: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_928738972".into(),
local: true,
private_key: None,
public_key: None,
last_refreshed_at: None,
published: None,
+ banner: None,
+ icon: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
let read_comment = Comment::read(&conn, inserted_comment.id).unwrap();
let updated_comment = Comment::update(&conn, inserted_comment.id, &comment_form).unwrap();
- let like_removed = CommentLike::remove(&conn, &comment_like_form).unwrap();
+ let like_removed = CommentLike::remove(&conn, inserted_user.id, inserted_comment.id).unwrap();
let saved_removed = CommentSaved::unsave(&conn, &comment_saved_form).unwrap();
let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap();
Comment::delete(&conn, inserted_child_comment.id).unwrap();
community_actor_id -> Text,
community_local -> Bool,
community_name -> Varchar,
+ community_icon -> Nullable<Text>,
banned -> Bool,
banned_from_community -> Bool,
creator_actor_id -> Text,
creator_local -> Bool,
creator_name -> Varchar,
+ creator_preferred_username -> Nullable<Varchar>,
creator_published -> Timestamp,
creator_avatar -> Nullable<Text>,
score -> BigInt,
upvotes -> BigInt,
downvotes -> BigInt,
hot_rank -> Int4,
+ hot_rank_active -> Int4,
user_id -> Nullable<Int4>,
my_vote -> Nullable<Int4>,
subscribed -> Nullable<Bool>,
community_actor_id -> Text,
community_local -> Bool,
community_name -> Varchar,
+ community_icon -> Nullable<Text>,
banned -> Bool,
banned_from_community -> Bool,
creator_actor_id -> Text,
creator_local -> Bool,
creator_name -> Varchar,
+ creator_preferred_username -> Nullable<Varchar>,
creator_published -> Timestamp,
creator_avatar -> Nullable<Text>,
score -> BigInt,
upvotes -> BigInt,
downvotes -> BigInt,
hot_rank -> Int4,
+ hot_rank_active -> Int4,
user_id -> Nullable<Int4>,
my_vote -> Nullable<Int4>,
subscribed -> Nullable<Bool>,
pub community_actor_id: String,
pub community_local: bool,
pub community_name: String,
+ pub community_icon: Option<String>,
pub banned: bool,
pub banned_from_community: bool,
pub creator_actor_id: String,
pub creator_local: bool,
pub creator_name: String,
+ pub creator_preferred_username: Option<String>,
pub creator_published: chrono::NaiveDateTime,
pub creator_avatar: Option<String>,
pub score: i64,
pub upvotes: i64,
pub downvotes: i64,
pub hot_rank: i32,
+ pub hot_rank_active: i32,
pub user_id: Option<i32>,
pub my_vote: Option<i32>,
pub subscribed: Option<bool>,
SortType::Hot => query
.order_by(hot_rank.desc())
.then_order_by(published.desc()),
+ SortType::Active => query
+ .order_by(hot_rank_active.desc())
+ .then_order_by(published.desc()),
SortType::New => query.order_by(published.desc()),
SortType::TopAll => query.order_by(score.desc()),
SortType::TopYear => query
community_actor_id -> Text,
community_local -> Bool,
community_name -> Varchar,
+ community_icon -> Nullable<Varchar>,
banned -> Bool,
banned_from_community -> Bool,
creator_actor_id -> Text,
creator_local -> Bool,
creator_name -> Varchar,
+ creator_preferred_username -> Nullable<Varchar>,
creator_avatar -> Nullable<Text>,
creator_published -> Timestamp,
score -> BigInt,
upvotes -> BigInt,
downvotes -> BigInt,
hot_rank -> Int4,
+ hot_rank_active -> Int4,
user_id -> Nullable<Int4>,
my_vote -> Nullable<Int4>,
subscribed -> Nullable<Bool>,
pub community_actor_id: String,
pub community_local: bool,
pub community_name: String,
+ pub community_icon: Option<String>,
pub banned: bool,
pub banned_from_community: bool,
pub creator_actor_id: String,
pub creator_local: bool,
pub creator_name: String,
+ pub creator_preferred_username: Option<String>,
pub creator_avatar: Option<String>,
pub creator_published: chrono::NaiveDateTime,
pub score: i64,
pub upvotes: i64,
pub downvotes: i64,
pub hot_rank: i32,
+ pub hot_rank_active: i32,
pub user_id: Option<i32>,
pub my_vote: Option<i32>,
pub subscribed: Option<bool>,
}
query = match self.sort {
- // SortType::Hot => query.order_by(hot_rank.desc()),
+ // SortType::Hot => query.order_by(hot_rank.desc()), // TODO why is this commented
SortType::New => query.order_by(published.desc()),
SortType::TopAll => query.order_by(score.desc()),
SortType::TopYear => query
email: None,
matrix_user_id: None,
avatar: None,
+ banner: None,
admin: false,
banned: false,
updated: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_92873982".into(),
bio: None,
local: true,
private_key: None,
deleted: None,
updated: None,
nsfw: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_7625376".into(),
local: true,
private_key: None,
public_key: None,
last_refreshed_at: None,
published: None,
+ icon: None,
+ banner: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
post_name: inserted_post.name.to_owned(),
community_id: inserted_community.id,
community_name: inserted_community.name.to_owned(),
+ community_icon: None,
parent_id: None,
removed: false,
deleted: false,
published: inserted_comment.published,
updated: None,
creator_name: inserted_user.name.to_owned(),
+ creator_preferred_username: None,
creator_published: inserted_user.published,
creator_avatar: None,
score: 1,
downvotes: 0,
hot_rank: 0,
+ hot_rank_active: 0,
upvotes: 1,
user_id: None,
my_vote: None,
post_name: inserted_post.name.to_owned(),
community_id: inserted_community.id,
community_name: inserted_community.name.to_owned(),
+ community_icon: None,
parent_id: None,
removed: false,
deleted: false,
published: inserted_comment.published,
updated: None,
creator_name: inserted_user.name.to_owned(),
+ creator_preferred_username: None,
creator_published: inserted_user.published,
creator_avatar: None,
score: 1,
downvotes: 0,
hot_rank: 0,
+ hot_rank_active: 0,
upvotes: 1,
user_id: Some(inserted_user.id),
my_vote: Some(1),
.list()
.unwrap();
read_comment_views_no_user[0].hot_rank = 0;
+ read_comment_views_no_user[0].hot_rank_active = 0;
let mut read_comment_views_with_user = CommentQueryBuilder::create(&conn)
.for_post_id(inserted_post.id)
.list()
.unwrap();
read_comment_views_with_user[0].hot_rank = 0;
+ read_comment_views_with_user[0].hot_rank_active = 0;
- let like_removed = CommentLike::remove(&conn, &comment_like_form).unwrap();
+ let like_removed = CommentLike::remove(&conn, inserted_user.id, inserted_comment.id).unwrap();
let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap();
Post::delete(&conn, inserted_post.id).unwrap();
Community::delete(&conn, inserted_community.id).unwrap();
use crate::{
+ naive_now,
schema::{community, community_follower, community_moderator, community_user_ban},
Bannable,
Crud,
pub private_key: Option<String>,
pub public_key: Option<String>,
pub last_refreshed_at: chrono::NaiveDateTime,
+ pub icon: Option<String>,
+ pub banner: Option<String>,
}
-// TODO add better delete, remove, lock actions here.
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize, Debug)]
#[table_name = "community"]
pub struct CommunityForm {
pub private_key: Option<String>,
pub public_key: Option<String>,
pub last_refreshed_at: Option<chrono::NaiveDateTime>,
+ pub icon: Option<Option<String>>,
+ pub banner: Option<Option<String>>,
}
impl Crud<CommunityForm> for Community {
.first::<Self>(conn)
}
- pub fn read_from_actor_id(conn: &PgConnection, community_id: &str) -> Result<Self, Error> {
+ pub fn read_from_actor_id(conn: &PgConnection, for_actor_id: &str) -> Result<Self, Error> {
use crate::schema::community::dsl::*;
community
- .filter(actor_id.eq(community_id))
+ .filter(actor_id.eq(for_actor_id))
.first::<Self>(conn)
}
- pub fn list_local(conn: &PgConnection) -> Result<Vec<Self>, Error> {
+ pub fn update_deleted(
+ conn: &PgConnection,
+ community_id: i32,
+ new_deleted: bool,
+ ) -> Result<Self, Error> {
use crate::schema::community::dsl::*;
- community.filter(local.eq(true)).load::<Community>(conn)
+ diesel::update(community.find(community_id))
+ .set((deleted.eq(new_deleted), updated.eq(naive_now())))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn update_removed(
+ conn: &PgConnection,
+ community_id: i32,
+ new_removed: bool,
+ ) -> Result<Self, Error> {
+ use crate::schema::community::dsl::*;
+ diesel::update(community.find(community_id))
+ .set((removed.eq(new_removed), updated.eq(naive_now())))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn update_removed_for_creator(
+ conn: &PgConnection,
+ for_creator_id: i32,
+ new_removed: bool,
+ ) -> Result<Vec<Self>, Error> {
+ use crate::schema::community::dsl::*;
+ diesel::update(community.filter(creator_id.eq(for_creator_id)))
+ .set((removed.eq(new_removed), updated.eq(naive_now())))
+ .get_results::<Self>(conn)
+ }
+
+ pub fn update_creator(
+ conn: &PgConnection,
+ community_id: i32,
+ new_creator_id: i32,
+ ) -> Result<Self, Error> {
+ use crate::schema::community::dsl::*;
+ diesel::update(community.find(community_id))
+ .set((creator_id.eq(new_creator_id), updated.eq(naive_now())))
+ .get_result::<Self>(conn)
+ }
+
+ fn community_mods_and_admins(conn: &PgConnection, community_id: i32) -> Result<Vec<i32>, Error> {
+ use crate::{community_view::CommunityModeratorView, user_view::UserView};
+ let mut mods_and_admins: Vec<i32> = Vec::new();
+ mods_and_admins.append(
+ &mut CommunityModeratorView::for_community(conn, community_id)
+ .map(|v| v.into_iter().map(|m| m.user_id).collect())?,
+ );
+ mods_and_admins
+ .append(&mut UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect())?);
+ Ok(mods_and_admins)
+ }
+
+ pub fn is_mod_or_admin(conn: &PgConnection, user_id: i32, community_id: i32) -> bool {
+ Self::community_mods_and_admins(conn, community_id)
+ .unwrap_or_default()
+ .contains(&user_id)
}
}
email: None,
matrix_user_id: None,
avatar: None,
+ banner: None,
admin: false,
banned: false,
updated: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_8266238".into(),
bio: None,
local: true,
private_key: None,
removed: None,
deleted: None,
updated: None,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_7625376".into(),
local: true,
private_key: None,
public_key: None,
last_refreshed_at: None,
published: None,
+ icon: None,
+ banner: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
deleted: false,
published: inserted_community.published,
updated: None,
- actor_id: "http://fake.com".into(),
+ actor_id: inserted_community.actor_id.to_owned(),
local: true,
private_key: None,
public_key: None,
last_refreshed_at: inserted_community.published,
+ icon: None,
+ banner: None,
};
let community_follower_form = CommunityFollowerForm {
id -> Int4,
name -> Varchar,
title -> Varchar,
+ icon -> Nullable<Text>,
+ banner -> Nullable<Text>,
description -> Nullable<Text>,
category_id -> Int4,
creator_id -> Int4,
creator_actor_id -> Text,
creator_local -> Bool,
creator_name -> Varchar,
+ creator_preferred_username -> Nullable<Varchar>,
creator_avatar -> Nullable<Text>,
category_name -> Varchar,
number_of_subscribers -> BigInt,
id -> Int4,
name -> Varchar,
title -> Varchar,
+ icon -> Nullable<Text>,
+ banner -> Nullable<Text>,
description -> Nullable<Text>,
category_id -> Int4,
creator_id -> Int4,
creator_actor_id -> Text,
creator_local -> Bool,
creator_name -> Varchar,
+ creator_preferred_username -> Nullable<Varchar>,
creator_avatar -> Nullable<Text>,
category_name -> Varchar,
number_of_subscribers -> BigInt,
user_actor_id -> Text,
user_local -> Bool,
user_name -> Varchar,
+ user_preferred_username -> Nullable<Varchar>,
avatar -> Nullable<Text>,
community_actor_id -> Text,
community_local -> Bool,
community_name -> Varchar,
+ community_icon -> Nullable<Text>,
}
}
user_actor_id -> Text,
user_local -> Bool,
user_name -> Varchar,
+ user_preferred_username -> Nullable<Varchar>,
avatar -> Nullable<Text>,
community_actor_id -> Text,
community_local -> Bool,
community_name -> Varchar,
+ community_icon -> Nullable<Text>,
}
}
user_actor_id -> Text,
user_local -> Bool,
user_name -> Varchar,
+ user_preferred_username -> Nullable<Varchar>,
avatar -> Nullable<Text>,
community_actor_id -> Text,
community_local -> Bool,
community_name -> Varchar,
+ community_icon -> Nullable<Text>,
}
}
pub id: i32,
pub name: String,
pub title: String,
+ pub icon: Option<String>,
+ pub banner: Option<String>,
pub description: Option<String>,
pub category_id: i32,
pub creator_id: i32,
pub creator_actor_id: String,
pub creator_local: bool,
pub creator_name: String,
+ pub creator_preferred_username: Option<String>,
pub creator_avatar: Option<String>,
pub category_name: String,
pub number_of_subscribers: i64,
// The view lets you pass a null user_id, if you're not logged in
match self.sort {
- SortType::Hot => {
- query = query
- .order_by(hot_rank.desc())
- .then_order_by(number_of_subscribers.desc())
- .filter(user_id.is_null())
- }
SortType::New => query = query.order_by(published.desc()).filter(user_id.is_null()),
SortType::TopAll => match self.from_user_id {
Some(from_user_id) => {
.filter(user_id.is_null())
}
},
- _ => (),
+ // Covers all other sorts, including hot
+ _ => {
+ query = query
+ .order_by(hot_rank.desc())
+ .then_order_by(number_of_subscribers.desc())
+ .filter(user_id.is_null())
+ }
};
if !self.show_nsfw {
pub user_actor_id: String,
pub user_local: bool,
pub user_name: String,
+ pub user_preferred_username: Option<String>,
pub avatar: Option<String>,
pub community_actor_id: String,
pub community_local: bool,
pub community_name: String,
+ pub community_icon: Option<String>,
}
impl CommunityModeratorView {
- pub fn for_community(conn: &PgConnection, from_community_id: i32) -> Result<Vec<Self>, Error> {
+ pub fn for_community(conn: &PgConnection, for_community_id: i32) -> Result<Vec<Self>, Error> {
use super::community_view::community_moderator_view::dsl::*;
community_moderator_view
- .filter(community_id.eq(from_community_id))
+ .filter(community_id.eq(for_community_id))
.order_by(published)
.load::<Self>(conn)
}
- pub fn for_user(conn: &PgConnection, from_user_id: i32) -> Result<Vec<Self>, Error> {
+ pub fn for_user(conn: &PgConnection, for_user_id: i32) -> Result<Vec<Self>, Error> {
use super::community_view::community_moderator_view::dsl::*;
community_moderator_view
- .filter(user_id.eq(from_user_id))
+ .filter(user_id.eq(for_user_id))
.order_by(published)
.load::<Self>(conn)
}
pub user_actor_id: String,
pub user_local: bool,
pub user_name: String,
+ pub user_preferred_username: Option<String>,
pub avatar: Option<String>,
pub community_actor_id: String,
pub community_local: bool,
pub community_name: String,
+ pub community_icon: Option<String>,
}
impl CommunityFollowerView {
pub user_actor_id: String,
pub user_local: bool,
pub user_name: String,
+ pub user_preferred_username: Option<String>,
pub avatar: Option<String>,
pub community_actor_id: String,
pub community_local: bool,
pub community_name: String,
+ pub community_icon: Option<String>,
}
impl CommunityUserBanView {
- pub fn for_community(conn: &PgConnection, from_community_id: i32) -> Result<Vec<Self>, Error> {
- use super::community_view::community_user_ban_view::dsl::*;
- community_user_ban_view
- .filter(community_id.eq(from_community_id))
- .load::<Self>(conn)
- }
-
- pub fn for_user(conn: &PgConnection, from_user_id: i32) -> Result<Vec<Self>, Error> {
- use super::community_view::community_user_ban_view::dsl::*;
- community_user_ban_view
- .filter(user_id.eq(from_user_id))
- .load::<Self>(conn)
- }
-
pub fn get(
conn: &PgConnection,
from_user_id: i32,
pub extern crate diesel;
#[macro_use]
pub extern crate strum_macros;
+#[macro_use]
+pub extern crate lazy_static;
pub extern crate bcrypt;
pub extern crate chrono;
pub extern crate log;
+pub extern crate regex;
pub extern crate serde;
pub extern crate serde_json;
pub extern crate sha2;
use chrono::NaiveDateTime;
use diesel::{dsl::*, result::Error, *};
+use regex::Regex;
use serde::{Deserialize, Serialize};
use std::{env, env::VarError};
fn update(conn: &PgConnection, id: i32, form: &T) -> Result<Self, Error>
where
Self: Sized;
- fn delete(conn: &PgConnection, id: i32) -> Result<usize, Error>
+ fn delete(_conn: &PgConnection, _id: i32) -> Result<usize, Error>
where
- Self: Sized;
+ Self: Sized,
+ {
+ unimplemented!()
+ }
}
pub trait Followable<T> {
}
pub trait Likeable<T> {
- fn read(conn: &PgConnection, id: i32) -> Result<Vec<Self>, Error>
- where
- Self: Sized;
fn like(conn: &PgConnection, form: &T) -> Result<Self, Error>
where
Self: Sized;
- fn remove(conn: &PgConnection, form: &T) -> Result<usize, Error>
+ fn remove(conn: &PgConnection, user_id: i32, item_id: i32) -> Result<usize, Error>
where
Self: Sized;
}
#[derive(EnumString, ToString, Debug, Serialize, Deserialize)]
pub enum SortType {
+ Active,
Hot,
New,
TopDay,
chrono::prelude::Utc::now().naive_utc()
}
+pub fn is_email_regex(test: &str) -> bool {
+ EMAIL_REGEX.is_match(test)
+}
+
+pub fn diesel_option_overwrite(opt: &Option<String>) -> Option<Option<String>> {
+ match opt {
+ // An empty string is an erase
+ Some(unwrapped) => {
+ if !unwrapped.eq("") {
+ Some(Some(unwrapped.to_owned()))
+ } else {
+ Some(None)
+ }
+ }
+ None => None,
+ }
+}
+
+lazy_static! {
+ static ref EMAIL_REGEX: Regex =
+ Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
+}
+
#[cfg(test)]
mod tests {
use super::fuzzy_search;
- use crate::get_database_url_from_env;
+ use crate::{get_database_url_from_env, is_email_regex};
use diesel::{Connection, PgConnection};
pub fn establish_unpooled_connection() -> PgConnection {
let test = "This is a fuzzy search";
assert_eq!(fuzzy_search(test), "%This%is%a%fuzzy%search%".to_string());
}
+
+ #[test]
+ fn test_email() {
+ assert!(is_email_regex("gush@gmail.com"));
+ assert!(!is_email_regex("nada_neutho"));
+ }
}
mod_remove_post.find(from_id).first::<Self>(conn)
}
- fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
- use crate::schema::mod_remove_post::dsl::*;
- diesel::delete(mod_remove_post.find(from_id)).execute(conn)
- }
-
fn create(conn: &PgConnection, form: &ModRemovePostForm) -> Result<Self, Error> {
use crate::schema::mod_remove_post::dsl::*;
insert_into(mod_remove_post)
mod_lock_post.find(from_id).first::<Self>(conn)
}
- fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
- use crate::schema::mod_lock_post::dsl::*;
- diesel::delete(mod_lock_post.find(from_id)).execute(conn)
- }
-
fn create(conn: &PgConnection, form: &ModLockPostForm) -> Result<Self, Error> {
use crate::schema::mod_lock_post::dsl::*;
insert_into(mod_lock_post)
mod_sticky_post.find(from_id).first::<Self>(conn)
}
- fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
- use crate::schema::mod_sticky_post::dsl::*;
- diesel::delete(mod_sticky_post.find(from_id)).execute(conn)
- }
-
fn create(conn: &PgConnection, form: &ModStickyPostForm) -> Result<Self, Error> {
use crate::schema::mod_sticky_post::dsl::*;
insert_into(mod_sticky_post)
mod_remove_comment.find(from_id).first::<Self>(conn)
}
- fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
- use crate::schema::mod_remove_comment::dsl::*;
- diesel::delete(mod_remove_comment.find(from_id)).execute(conn)
- }
-
fn create(conn: &PgConnection, form: &ModRemoveCommentForm) -> Result<Self, Error> {
use crate::schema::mod_remove_comment::dsl::*;
insert_into(mod_remove_comment)
mod_remove_community.find(from_id).first::<Self>(conn)
}
- fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
- use crate::schema::mod_remove_community::dsl::*;
- diesel::delete(mod_remove_community.find(from_id)).execute(conn)
- }
-
fn create(conn: &PgConnection, form: &ModRemoveCommunityForm) -> Result<Self, Error> {
use crate::schema::mod_remove_community::dsl::*;
insert_into(mod_remove_community)
mod_ban_from_community.find(from_id).first::<Self>(conn)
}
- fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
- use crate::schema::mod_ban_from_community::dsl::*;
- diesel::delete(mod_ban_from_community.find(from_id)).execute(conn)
- }
-
fn create(conn: &PgConnection, form: &ModBanFromCommunityForm) -> Result<Self, Error> {
use crate::schema::mod_ban_from_community::dsl::*;
insert_into(mod_ban_from_community)
mod_ban.find(from_id).first::<Self>(conn)
}
- fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
- use crate::schema::mod_ban::dsl::*;
- diesel::delete(mod_ban.find(from_id)).execute(conn)
- }
-
fn create(conn: &PgConnection, form: &ModBanForm) -> Result<Self, Error> {
use crate::schema::mod_ban::dsl::*;
insert_into(mod_ban).values(form).get_result::<Self>(conn)
mod_add_community.find(from_id).first::<Self>(conn)
}
- fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
- use crate::schema::mod_add_community::dsl::*;
- diesel::delete(mod_add_community.find(from_id)).execute(conn)
- }
-
fn create(conn: &PgConnection, form: &ModAddCommunityForm) -> Result<Self, Error> {
use crate::schema::mod_add_community::dsl::*;
insert_into(mod_add_community)
mod_add.find(from_id).first::<Self>(conn)
}
- fn delete(conn: &PgConnection, from_id: i32) -> Result<usize, Error> {
- use crate::schema::mod_add::dsl::*;
- diesel::delete(mod_add.find(from_id)).execute(conn)
- }
-
fn create(conn: &PgConnection, form: &ModAddForm) -> Result<Self, Error> {
use crate::schema::mod_add::dsl::*;
insert_into(mod_add).values(form).get_result::<Self>(conn)
email: None,
matrix_user_id: None,
avatar: None,
+ banner: None,
admin: false,
banned: false,
updated: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_829398".into(),
bio: None,
local: true,
private_key: None,
email: None,
matrix_user_id: None,
avatar: None,
+ banner: None,
admin: false,
banned: false,
updated: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_82982738".into(),
bio: None,
local: true,
private_key: None,
deleted: None,
updated: None,
nsfw: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_283687".into(),
local: true,
private_key: None,
public_key: None,
last_refreshed_at: None,
published: None,
+ icon: None,
+ banner: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
when_: inserted_mod_add.when_,
};
- ModRemovePost::delete(&conn, inserted_mod_remove_post.id).unwrap();
- ModLockPost::delete(&conn, inserted_mod_lock_post.id).unwrap();
- ModStickyPost::delete(&conn, inserted_mod_sticky_post.id).unwrap();
- ModRemoveComment::delete(&conn, inserted_mod_remove_comment.id).unwrap();
- ModRemoveCommunity::delete(&conn, inserted_mod_remove_community.id).unwrap();
- ModBanFromCommunity::delete(&conn, inserted_mod_ban_from_community.id).unwrap();
- ModBan::delete(&conn, inserted_mod_ban.id).unwrap();
- ModAddCommunity::delete(&conn, inserted_mod_add_community.id).unwrap();
- ModAdd::delete(&conn, inserted_mod_add.id).unwrap();
-
Comment::delete(&conn, inserted_comment.id).unwrap();
Post::delete(&conn, inserted_post.id).unwrap();
Community::delete(&conn, inserted_community.id).unwrap();
.find(password_reset_request_id)
.first::<Self>(conn)
}
- fn delete(conn: &PgConnection, password_reset_request_id: i32) -> Result<usize, Error> {
- diesel::delete(password_reset_request.find(password_reset_request_id)).execute(conn)
- }
fn create(conn: &PgConnection, form: &PasswordResetRequestForm) -> Result<Self, Error> {
insert_into(password_reset_request)
.values(form)
email: None,
matrix_user_id: None,
avatar: None,
+ banner: None,
admin: false,
banned: false,
updated: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_8292378".into(),
bio: None,
local: true,
private_key: None,
};
use diesel::{dsl::*, result::Error, *};
use serde::{Deserialize, Serialize};
+use url::{ParseError, Url};
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name = "post"]
pub local: bool,
}
+impl PostForm {
+ pub fn get_ap_id(&self) -> Result<Url, ParseError> {
+ Url::parse(&self.ap_id)
+ }
+}
+
impl Post {
pub fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
use crate::schema::post::dsl::*;
use crate::schema::post::dsl::*;
post
.filter(community_id.eq(the_community_id))
+ .then_order_by(published.desc())
+ .then_order_by(stickied.desc())
+ .limit(20)
.load::<Self>(conn)
}
.get_result::<Self>(conn)
}
- pub fn permadelete(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
+ pub fn permadelete_for_creator(
+ conn: &PgConnection,
+ for_creator_id: i32,
+ ) -> Result<Vec<Self>, Error> {
use crate::schema::post::dsl::*;
let perma_deleted = "*Permananently Deleted*";
let perma_deleted_url = "https://deleted.com";
- diesel::update(post.find(post_id))
+ diesel::update(post.filter(creator_id.eq(for_creator_id)))
.set((
name.eq(perma_deleted),
url.eq(perma_deleted_url),
deleted.eq(true),
updated.eq(naive_now()),
))
+ .get_results::<Self>(conn)
+ }
+
+ pub fn update_deleted(
+ conn: &PgConnection,
+ post_id: i32,
+ new_deleted: bool,
+ ) -> Result<Self, Error> {
+ use crate::schema::post::dsl::*;
+ diesel::update(post.find(post_id))
+ .set((deleted.eq(new_deleted), updated.eq(naive_now())))
.get_result::<Self>(conn)
}
+
+ pub fn update_removed(
+ conn: &PgConnection,
+ post_id: i32,
+ new_removed: bool,
+ ) -> Result<Self, Error> {
+ use crate::schema::post::dsl::*;
+ diesel::update(post.find(post_id))
+ .set((removed.eq(new_removed), updated.eq(naive_now())))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn update_removed_for_creator(
+ conn: &PgConnection,
+ for_creator_id: i32,
+ for_community_id: Option<i32>,
+ new_removed: bool,
+ ) -> Result<Vec<Self>, Error> {
+ use crate::schema::post::dsl::*;
+
+ let mut update = diesel::update(post).into_boxed();
+ update = update.filter(creator_id.eq(for_creator_id));
+
+ if let Some(for_community_id) = for_community_id {
+ update = update.filter(community_id.eq(for_community_id));
+ }
+
+ update
+ .set((removed.eq(new_removed), updated.eq(naive_now())))
+ .get_results::<Self>(conn)
+ }
+
+ pub fn update_locked(conn: &PgConnection, post_id: i32, new_locked: bool) -> Result<Self, Error> {
+ use crate::schema::post::dsl::*;
+ diesel::update(post.find(post_id))
+ .set(locked.eq(new_locked))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn update_stickied(
+ conn: &PgConnection,
+ post_id: i32,
+ new_stickied: bool,
+ ) -> Result<Self, Error> {
+ use crate::schema::post::dsl::*;
+ diesel::update(post.find(post_id))
+ .set(stickied.eq(new_stickied))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn is_post_creator(user_id: i32, post_creator_id: i32) -> bool {
+ user_id == post_creator_id
+ }
+
+ pub fn upsert(conn: &PgConnection, post_form: &PostForm) -> Result<Post, Error> {
+ let existing = Self::read_from_apub_id(conn, &post_form.ap_id);
+ match existing {
+ Err(NotFound {}) => Ok(Self::create(conn, &post_form)?),
+ Ok(p) => Ok(Self::update(conn, p.id, &post_form)?),
+ Err(e) => Err(e),
+ }
+ }
}
impl Crud<PostForm> for Post {
}
impl Likeable<PostLikeForm> for PostLike {
- fn read(conn: &PgConnection, post_id_from: i32) -> Result<Vec<Self>, Error> {
- use crate::schema::post_like::dsl::*;
- post_like
- .filter(post_id.eq(post_id_from))
- .load::<Self>(conn)
- }
fn like(conn: &PgConnection, post_like_form: &PostLikeForm) -> Result<Self, Error> {
use crate::schema::post_like::dsl::*;
insert_into(post_like)
.values(post_like_form)
.get_result::<Self>(conn)
}
- fn remove(conn: &PgConnection, post_like_form: &PostLikeForm) -> Result<usize, Error> {
- use crate::schema::post_like::dsl::*;
+ fn remove(conn: &PgConnection, user_id: i32, post_id: i32) -> Result<usize, Error> {
+ use crate::schema::post_like::dsl;
diesel::delete(
- post_like
- .filter(post_id.eq(post_like_form.post_id))
- .filter(user_id.eq(post_like_form.user_id)),
+ dsl::post_like
+ .filter(dsl::post_id.eq(post_id))
+ .filter(dsl::user_id.eq(user_id)),
)
.execute(conn)
}
#[table_name = "post_read"]
pub struct PostRead {
pub id: i32,
+
pub post_id: i32,
+
pub user_id: i32,
+
pub published: chrono::NaiveDateTime,
}
#[table_name = "post_read"]
pub struct PostReadForm {
pub post_id: i32,
+
pub user_id: i32,
}
.values(post_read_form)
.get_result::<Self>(conn)
}
+
fn mark_as_unread(conn: &PgConnection, post_read_form: &PostReadForm) -> Result<usize, Error> {
use crate::schema::post_read::dsl::*;
diesel::delete(
email: None,
matrix_user_id: None,
avatar: None,
+ banner: None,
admin: false,
banned: false,
updated: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_8292683678".into(),
bio: None,
local: true,
private_key: None,
deleted: None,
updated: None,
nsfw: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_8223262378".into(),
local: true,
private_key: None,
public_key: None,
last_refreshed_at: None,
published: None,
+ icon: None,
+ banner: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
let read_post = Post::read(&conn, inserted_post.id).unwrap();
let updated_post = Post::update(&conn, inserted_post.id, &new_post).unwrap();
- let like_removed = PostLike::remove(&conn, &post_like_form).unwrap();
+ let like_removed = PostLike::remove(&conn, inserted_user.id, inserted_post.id).unwrap();
let saved_removed = PostSaved::unsave(&conn, &post_saved_form).unwrap();
let read_removed = PostRead::mark_as_unread(&conn, &post_read_form).unwrap();
let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
creator_actor_id -> Text,
creator_local -> Bool,
creator_name -> Varchar,
+ creator_preferred_username -> Nullable<Varchar>,
creator_published -> Timestamp,
creator_avatar -> Nullable<Text>,
banned -> Bool,
community_actor_id -> Text,
community_local -> Bool,
community_name -> Varchar,
+ community_icon -> Nullable<Text>,
community_removed -> Bool,
community_deleted -> Bool,
community_nsfw -> Bool,
upvotes -> BigInt,
downvotes -> BigInt,
hot_rank -> Int4,
+ hot_rank_active -> Int4,
newest_activity_time -> Timestamp,
user_id -> Nullable<Int4>,
my_vote -> Nullable<Int4>,
creator_actor_id -> Text,
creator_local -> Bool,
creator_name -> Varchar,
+ creator_preferred_username -> Nullable<Varchar>,
creator_published -> Timestamp,
creator_avatar -> Nullable<Text>,
banned -> Bool,
community_actor_id -> Text,
community_local -> Bool,
community_name -> Varchar,
+ community_icon -> Nullable<Text>,
community_removed -> Bool,
community_deleted -> Bool,
community_nsfw -> Bool,
upvotes -> BigInt,
downvotes -> BigInt,
hot_rank -> Int4,
+ hot_rank_active -> Int4,
newest_activity_time -> Timestamp,
user_id -> Nullable<Int4>,
my_vote -> Nullable<Int4>,
pub creator_actor_id: String,
pub creator_local: bool,
pub creator_name: String,
+ pub creator_preferred_username: Option<String>,
pub creator_published: chrono::NaiveDateTime,
pub creator_avatar: Option<String>,
pub banned: bool,
pub community_actor_id: String,
pub community_local: bool,
pub community_name: String,
+ pub community_icon: Option<String>,
pub community_removed: bool,
pub community_deleted: bool,
pub community_nsfw: bool,
pub upvotes: i64,
pub downvotes: i64,
pub hot_rank: i32,
+ pub hot_rank_active: i32,
pub newest_activity_time: chrono::NaiveDateTime,
pub user_id: Option<i32>,
pub my_vote: Option<i32>,
self
}
- pub fn unread_only(mut self, unread_only: bool) -> Self {
- self.unread_only = unread_only;
- self
- }
-
pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
self.page = page.get_optional();
self
}
query = match self.sort {
+ SortType::Active => query
+ .then_order_by(hot_rank_active.desc())
+ .then_order_by(published.desc()),
SortType::Hot => query
.then_order_by(hot_rank.desc())
.then_order_by(published.desc()),
email: None,
matrix_user_id: None,
avatar: None,
+ banner: None,
updated: None,
admin: false,
banned: false,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_8282738268".into(),
bio: None,
local: true,
private_key: None,
deleted: None,
updated: None,
nsfw: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_2763".into(),
local: true,
private_key: None,
public_key: None,
last_refreshed_at: None,
published: None,
+ icon: None,
+ banner: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
score: 1,
};
- let post_like_form = PostLikeForm {
- post_id: inserted_post.id,
- user_id: inserted_user.id,
- score: 1,
- };
-
let read_post_listings_with_user = PostQueryBuilder::create(&conn)
.listing_type(ListingType::Community)
.sort(&SortType::New)
body: None,
creator_id: inserted_user.id,
creator_name: user_name.to_owned(),
+ creator_preferred_username: None,
creator_published: inserted_user.published,
creator_avatar: None,
banned: false,
locked: false,
stickied: false,
community_name: community_name.to_owned(),
+ community_icon: None,
community_removed: false,
community_deleted: false,
community_nsfw: false,
upvotes: 1,
downvotes: 0,
hot_rank: read_post_listing_no_user.hot_rank,
+ hot_rank_active: read_post_listing_no_user.hot_rank_active,
published: inserted_post.published,
newest_activity_time: inserted_post.published,
updated: None,
stickied: false,
creator_id: inserted_user.id,
creator_name: user_name,
+ creator_preferred_username: None,
creator_published: inserted_user.published,
creator_avatar: None,
banned: false,
banned_from_community: false,
community_id: inserted_community.id,
community_name,
+ community_icon: None,
community_removed: false,
community_deleted: false,
community_nsfw: false,
upvotes: 1,
downvotes: 0,
hot_rank: read_post_listing_with_user.hot_rank,
+ hot_rank_active: read_post_listing_with_user.hot_rank_active,
published: inserted_post.published,
newest_activity_time: inserted_post.published,
updated: None,
community_local: true,
};
- let like_removed = PostLike::remove(&conn, &post_like_form).unwrap();
+ let like_removed = PostLike::remove(&conn, inserted_user.id, inserted_post.id).unwrap();
let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
Community::delete(&conn, inserted_community.id).unwrap();
User_::delete(&conn, inserted_user.id).unwrap();
-use crate::{schema::private_message, Crud};
+use crate::{naive_now, schema::private_message, Crud};
use diesel::{dsl::*, result::Error, *};
use serde::{Deserialize, Serialize};
private_message.find(private_message_id).first::<Self>(conn)
}
- fn delete(conn: &PgConnection, private_message_id: i32) -> Result<usize, Error> {
- use crate::schema::private_message::dsl::*;
- diesel::delete(private_message.find(private_message_id)).execute(conn)
- }
-
fn create(conn: &PgConnection, private_message_form: &PrivateMessageForm) -> Result<Self, Error> {
use crate::schema::private_message::dsl::*;
insert_into(private_message)
.filter(ap_id.eq(object_id))
.first::<Self>(conn)
}
+
+ pub fn update_content(
+ conn: &PgConnection,
+ private_message_id: i32,
+ new_content: &str,
+ ) -> Result<Self, Error> {
+ use crate::schema::private_message::dsl::*;
+ diesel::update(private_message.find(private_message_id))
+ .set((content.eq(new_content), updated.eq(naive_now())))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn update_deleted(
+ conn: &PgConnection,
+ private_message_id: i32,
+ new_deleted: bool,
+ ) -> Result<Self, Error> {
+ use crate::schema::private_message::dsl::*;
+ diesel::update(private_message.find(private_message_id))
+ .set(deleted.eq(new_deleted))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn update_read(
+ conn: &PgConnection,
+ private_message_id: i32,
+ new_read: bool,
+ ) -> Result<Self, Error> {
+ use crate::schema::private_message::dsl::*;
+ diesel::update(private_message.find(private_message_id))
+ .set(read.eq(new_read))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn mark_all_as_read(conn: &PgConnection, for_recipient_id: i32) -> Result<Vec<Self>, Error> {
+ use crate::schema::private_message::dsl::*;
+ diesel::update(
+ private_message
+ .filter(recipient_id.eq(for_recipient_id))
+ .filter(read.eq(false)),
+ )
+ .set(read.eq(true))
+ .get_results::<Self>(conn)
+ }
}
#[cfg(test)]
email: None,
matrix_user_id: None,
avatar: None,
+ banner: None,
admin: false,
banned: false,
updated: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_6723878".into(),
bio: None,
local: true,
private_key: None,
email: None,
matrix_user_id: None,
avatar: None,
+ banner: None,
admin: false,
banned: false,
updated: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_287263876".into(),
bio: None,
local: true,
private_key: None,
let read_private_message = PrivateMessage::read(&conn, inserted_private_message.id).unwrap();
let updated_private_message =
PrivateMessage::update(&conn, inserted_private_message.id, &private_message_form).unwrap();
- let num_deleted = PrivateMessage::delete(&conn, inserted_private_message.id).unwrap();
+ let deleted_private_message =
+ PrivateMessage::update_deleted(&conn, inserted_private_message.id, true).unwrap();
+ let marked_read_private_message =
+ PrivateMessage::update_read(&conn, inserted_private_message.id, true).unwrap();
User_::delete(&conn, inserted_creator.id).unwrap();
User_::delete(&conn, inserted_recipient.id).unwrap();
assert_eq!(expected_private_message, read_private_message);
assert_eq!(expected_private_message, updated_private_message);
assert_eq!(expected_private_message, inserted_private_message);
- assert_eq!(1, num_deleted);
+ assert!(deleted_private_message.deleted);
+ assert!(marked_read_private_message.read);
}
}
ap_id -> Text,
local -> Bool,
creator_name -> Varchar,
+ creator_preferred_username -> Nullable<Varchar>,
creator_avatar -> Nullable<Text>,
creator_actor_id -> Text,
creator_local -> Bool,
recipient_name -> Varchar,
+ recipient_preferred_username -> Nullable<Varchar>,
recipient_avatar -> Nullable<Text>,
recipient_actor_id -> Text,
recipient_local -> Bool,
pub ap_id: String,
pub local: bool,
pub creator_name: String,
+ pub creator_preferred_username: Option<String>,
pub creator_avatar: Option<String>,
pub creator_actor_id: String,
pub creator_local: bool,
pub recipient_name: String,
+ pub recipient_preferred_username: Option<String>,
pub recipient_avatar: Option<String>,
pub recipient_actor_id: String,
pub recipient_local: bool,
community_actor_id -> Nullable<Varchar>,
community_local -> Nullable<Bool>,
community_name -> Nullable<Varchar>,
+ community_icon -> Nullable<Text>,
banned -> Nullable<Bool>,
banned_from_community -> Nullable<Bool>,
creator_actor_id -> Nullable<Varchar>,
creator_local -> Nullable<Bool>,
creator_name -> Nullable<Varchar>,
+ creator_preferred_username -> Nullable<Varchar>,
creator_published -> Nullable<Timestamp>,
creator_avatar -> Nullable<Text>,
score -> Nullable<Int8>,
upvotes -> Nullable<Int8>,
downvotes -> Nullable<Int8>,
hot_rank -> Nullable<Int4>,
+ hot_rank_active -> Nullable<Int4>,
}
}
private_key -> Nullable<Text>,
public_key -> Nullable<Text>,
last_refreshed_at -> Timestamp,
+ icon -> Nullable<Text>,
+ banner -> Nullable<Text>,
}
}
id -> Int4,
name -> Nullable<Varchar>,
title -> Nullable<Varchar>,
+ icon -> Nullable<Text>,
+ banner -> Nullable<Text>,
description -> Nullable<Text>,
category_id -> Nullable<Int4>,
creator_id -> Nullable<Int4>,
creator_actor_id -> Nullable<Varchar>,
creator_local -> Nullable<Bool>,
creator_name -> Nullable<Varchar>,
+ creator_preferred_username -> Nullable<Varchar>,
creator_avatar -> Nullable<Text>,
category_name -> Nullable<Varchar>,
number_of_subscribers -> Nullable<Int8>,
creator_actor_id -> Nullable<Varchar>,
creator_local -> Nullable<Bool>,
creator_name -> Nullable<Varchar>,
+ creator_preferred_username -> Nullable<Varchar>,
creator_published -> Nullable<Timestamp>,
creator_avatar -> Nullable<Text>,
banned -> Nullable<Bool>,
community_actor_id -> Nullable<Varchar>,
community_local -> Nullable<Bool>,
community_name -> Nullable<Varchar>,
+ community_icon -> Nullable<Text>,
community_removed -> Nullable<Bool>,
community_deleted -> Nullable<Bool>,
community_nsfw -> Nullable<Bool>,
upvotes -> Nullable<Int8>,
downvotes -> Nullable<Int8>,
hot_rank -> Nullable<Int4>,
+ hot_rank_active -> Nullable<Int4>,
newest_activity_time -> Nullable<Timestamp>,
}
}
enable_downvotes -> Bool,
open_registration -> Bool,
enable_nsfw -> Bool,
+ icon -> Nullable<Text>,
+ banner -> Nullable<Text>,
}
}
private_key -> Nullable<Text>,
public_key -> Nullable<Text>,
last_refreshed_at -> Timestamp,
+ banner -> Nullable<Text>,
}
}
id -> Int4,
actor_id -> Nullable<Varchar>,
name -> Nullable<Varchar>,
+ preferred_username -> Nullable<Varchar>,
avatar -> Nullable<Text>,
+ banner -> Nullable<Text>,
email -> Nullable<Text>,
matrix_user_id -> Nullable<Text>,
bio -> Nullable<Text>,
-use crate::{schema::site, Crud};
+use crate::{naive_now, schema::site, Crud};
use diesel::{dsl::*, result::Error, *};
use serde::{Deserialize, Serialize};
pub enable_downvotes: bool,
pub open_registration: bool,
pub enable_nsfw: bool,
+ pub icon: Option<String>,
+ pub banner: Option<String>,
}
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
pub enable_downvotes: bool,
pub open_registration: bool,
pub enable_nsfw: bool,
+ // when you want to null out a column, you have to send Some(None)), since sending None means you just don't want to update that column.
+ pub icon: Option<Option<String>>,
+ pub banner: Option<Option<String>>,
}
impl Crud<SiteForm> for Site {
site.first::<Self>(conn)
}
- fn delete(conn: &PgConnection, site_id: i32) -> Result<usize, Error> {
- use crate::schema::site::dsl::*;
- diesel::delete(site.find(site_id)).execute(conn)
- }
-
fn create(conn: &PgConnection, new_site: &SiteForm) -> Result<Self, Error> {
use crate::schema::site::dsl::*;
insert_into(site).values(new_site).get_result::<Self>(conn)
.get_result::<Self>(conn)
}
}
+
+impl Site {
+ pub fn transfer(conn: &PgConnection, new_creator_id: i32) -> Result<Self, Error> {
+ use crate::schema::site::dsl::*;
+ diesel::update(site.find(1))
+ .set((creator_id.eq(new_creator_id), updated.eq(naive_now())))
+ .get_result::<Self>(conn)
+ }
+}
enable_downvotes -> Bool,
open_registration -> Bool,
enable_nsfw -> Bool,
+ icon -> Nullable<Text>,
+ banner -> Nullable<Text>,
creator_name -> Varchar,
+ creator_preferred_username -> Nullable<Varchar>,
creator_avatar -> Nullable<Text>,
number_of_users -> BigInt,
number_of_posts -> BigInt,
pub enable_downvotes: bool,
pub open_registration: bool,
pub enable_nsfw: bool,
+ pub icon: Option<String>,
+ pub banner: Option<String>,
pub creator_name: String,
+ pub creator_preferred_username: Option<String>,
pub creator_avatar: Option<String>,
pub number_of_users: i64,
pub number_of_posts: i64,
use crate::{
+ is_email_regex,
naive_now,
schema::{user_, user_::dsl::*},
Crud,
};
use bcrypt::{hash, DEFAULT_COST};
use diesel::{dsl::*, result::Error, *};
+use serde::{Deserialize, Serialize};
-#[derive(Clone, Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name = "user_"]
pub struct User_ {
pub id: i32,
pub private_key: Option<String>,
pub public_key: Option<String>,
pub last_refreshed_at: chrono::NaiveDateTime,
+ pub banner: Option<String>,
}
#[derive(Insertable, AsChangeset, Clone, Debug)]
pub password_encrypted: String,
pub admin: bool,
pub banned: bool,
- pub email: Option<String>,
- pub avatar: Option<String>,
+ pub email: Option<Option<String>>,
+ pub avatar: Option<Option<String>>,
pub updated: Option<chrono::NaiveDateTime>,
pub show_nsfw: bool,
pub theme: String,
pub private_key: Option<String>,
pub public_key: Option<String>,
pub last_refreshed_at: Option<chrono::NaiveDateTime>,
+ pub banner: Option<Option<String>>,
}
impl Crud<UserForm> for User_ {
use crate::schema::user_::dsl::*;
user_.filter(actor_id.eq(object_id)).first::<Self>(conn)
}
-}
-impl User_ {
+ pub fn find_by_email_or_username(
+ conn: &PgConnection,
+ username_or_email: &str,
+ ) -> Result<Self, Error> {
+ if is_email_regex(username_or_email) {
+ Self::find_by_email(conn, username_or_email)
+ } else {
+ Self::find_by_username(conn, username_or_email)
+ }
+ }
+
pub fn find_by_username(conn: &PgConnection, username: &str) -> Result<User_, Error> {
- user_.filter(name.eq(username)).first::<User_>(conn)
+ user_.filter(name.ilike(username)).first::<User_>(conn)
}
pub fn find_by_email(conn: &PgConnection, from_email: &str) -> Result<User_, Error> {
email: None,
matrix_user_id: None,
avatar: None,
+ banner: None,
admin: false,
banned: false,
updated: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_9826382637".into(),
bio: None,
local: true,
private_key: None,
email: None,
matrix_user_id: None,
avatar: None,
+ banner: None,
admin: false,
banned: false,
published: inserted_user.published,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
- actor_id: "http://fake.com".into(),
+ actor_id: inserted_user.actor_id.to_owned(),
bio: None,
local: true,
private_key: None,
user_mention.find(user_mention_id).first::<Self>(conn)
}
- fn delete(conn: &PgConnection, user_mention_id: i32) -> Result<usize, Error> {
- use crate::schema::user_mention::dsl::*;
- diesel::delete(user_mention.find(user_mention_id)).execute(conn)
- }
-
fn create(conn: &PgConnection, user_mention_form: &UserMentionForm) -> Result<Self, Error> {
use crate::schema::user_mention::dsl::*;
insert_into(user_mention)
}
}
+impl UserMention {
+ pub fn update_read(
+ conn: &PgConnection,
+ user_mention_id: i32,
+ new_read: bool,
+ ) -> Result<Self, Error> {
+ use crate::schema::user_mention::dsl::*;
+ diesel::update(user_mention.find(user_mention_id))
+ .set(read.eq(new_read))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn mark_all_as_read(conn: &PgConnection, for_recipient_id: i32) -> Result<Vec<Self>, Error> {
+ use crate::schema::user_mention::dsl::*;
+ diesel::update(
+ user_mention
+ .filter(recipient_id.eq(for_recipient_id))
+ .filter(read.eq(false)),
+ )
+ .set(read.eq(true))
+ .get_results::<Self>(conn)
+ }
+}
+
#[cfg(test)]
mod tests {
use crate::{
email: None,
matrix_user_id: None,
avatar: None,
+ banner: None,
admin: false,
banned: false,
updated: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_628763".into(),
bio: None,
local: true,
private_key: None,
email: None,
matrix_user_id: None,
avatar: None,
+ banner: None,
admin: false,
banned: false,
updated: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_927389278".into(),
bio: None,
local: true,
private_key: None,
deleted: None,
updated: None,
nsfw: false,
- actor_id: "http://fake.com".into(),
+ actor_id: "changeme_876238".into(),
local: true,
private_key: None,
public_key: None,
last_refreshed_at: None,
published: None,
+ icon: None,
+ banner: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
let read_mention = UserMention::read(&conn, inserted_mention.id).unwrap();
let updated_mention =
UserMention::update(&conn, inserted_mention.id, &user_mention_form).unwrap();
- let num_deleted = UserMention::delete(&conn, inserted_mention.id).unwrap();
Comment::delete(&conn, inserted_comment.id).unwrap();
Post::delete(&conn, inserted_post.id).unwrap();
Community::delete(&conn, inserted_community.id).unwrap();
assert_eq!(expected_mention, read_mention);
assert_eq!(expected_mention, inserted_mention);
assert_eq!(expected_mention, updated_mention);
- assert_eq!(1, num_deleted);
}
}
community_actor_id -> Text,
community_local -> Bool,
community_name -> Varchar,
+ community_icon -> Nullable<Text>,
banned -> Bool,
banned_from_community -> Bool,
creator_name -> Varchar,
+ creator_preferred_username -> Nullable<Varchar>,
creator_avatar -> Nullable<Text>,
score -> BigInt,
upvotes -> BigInt,
downvotes -> BigInt,
hot_rank -> Int4,
+ hot_rank_active -> Int4,
user_id -> Nullable<Int4>,
my_vote -> Nullable<Int4>,
saved -> Nullable<Bool>,
community_actor_id -> Text,
community_local -> Bool,
community_name -> Varchar,
+ community_icon -> Nullable<Text>,
banned -> Bool,
banned_from_community -> Bool,
creator_name -> Varchar,
+ creator_preferred_username -> Nullable<Varchar>,
creator_avatar -> Nullable<Text>,
score -> BigInt,
upvotes -> BigInt,
downvotes -> BigInt,
hot_rank -> Int4,
+ hot_rank_active -> Int4,
user_id -> Nullable<Int4>,
my_vote -> Nullable<Int4>,
saved -> Nullable<Bool>,
pub community_actor_id: String,
pub community_local: bool,
pub community_name: String,
+ pub community_icon: Option<String>,
pub banned: bool,
pub banned_from_community: bool,
pub creator_name: String,
+ pub creator_preferred_username: Option<String>,
pub creator_avatar: Option<String>,
pub score: i64,
pub upvotes: i64,
pub downvotes: i64,
pub hot_rank: i32,
+ pub hot_rank_active: i32,
pub user_id: Option<i32>,
pub my_vote: Option<i32>,
pub saved: Option<bool>,
SortType::Hot => query
.order_by(hot_rank.desc())
.then_order_by(published.desc()),
+ SortType::Active => query
+ .order_by(hot_rank_active.desc())
+ .then_order_by(published.desc()),
SortType::New => query.order_by(published.desc()),
SortType::TopAll => query.order_by(score.desc()),
SortType::TopYear => query
id -> Int4,
actor_id -> Text,
name -> Varchar,
+ preferred_username -> Nullable<Varchar>,
avatar -> Nullable<Text>,
+ banner -> Nullable<Text>,
email -> Nullable<Text>,
matrix_user_id -> Nullable<Text>,
bio -> Nullable<Text>,
id -> Int4,
actor_id -> Text,
name -> Varchar,
+ preferred_username -> Nullable<Varchar>,
avatar -> Nullable<Text>,
+ banner -> Nullable<Text>,
email -> Nullable<Text>,
matrix_user_id -> Nullable<Text>,
bio -> Nullable<Text>,
pub id: i32,
pub actor_id: String,
pub name: String,
+ pub preferred_username: Option<String>,
pub avatar: Option<String>,
- pub email: Option<String>,
+ pub banner: Option<String>,
+ pub email: Option<String>, // TODO this shouldn't be in this view
pub matrix_user_id: Option<String>,
pub bio: Option<String>,
pub local: bool,
pub admin: bool,
pub banned: bool,
- pub show_avatars: bool,
- pub send_notifications_to_email: bool,
+ pub show_avatars: bool, // TODO this is a setting, probably doesn't need to be here
+ pub send_notifications_to_email: bool, // TODO also never used
pub published: chrono::NaiveDateTime,
pub number_of_posts: i64,
pub post_score: i64,
SortType::Hot => query
.order_by(comment_score.desc())
.then_order_by(published.desc()),
+ SortType::Active => query
+ .order_by(comment_score.desc())
+ .then_order_by(published.desc()),
SortType::New => query.order_by(published.desc()),
SortType::TopAll => query.order_by(comment_score.desc()),
SortType::TopYear => query
}
impl UserView {
- pub fn read(conn: &PgConnection, from_user_id: i32) -> Result<Self, Error> {
- use super::user_view::user_fast::dsl::*;
- user_fast.find(from_user_id).first::<Self>(conn)
- }
-
pub fn admins(conn: &PgConnection) -> Result<Vec<Self>, Error> {
use super::user_view::user_fast::dsl::*;
+ use diesel::sql_types::{Nullable, Text};
user_fast
+ // The select is necessary here to not get back emails
+ .select((
+ id,
+ actor_id,
+ name,
+ preferred_username,
+ avatar,
+ banner,
+ "".into_sql::<Nullable<Text>>(),
+ matrix_user_id,
+ bio,
+ local,
+ admin,
+ banned,
+ show_avatars,
+ send_notifications_to_email,
+ published,
+ number_of_posts,
+ post_score,
+ number_of_comments,
+ comment_score,
+ ))
.filter(admin.eq(true))
.order_by(published)
.load::<Self>(conn)
pub fn banned(conn: &PgConnection) -> Result<Vec<Self>, Error> {
use super::user_view::user_fast::dsl::*;
- user_fast.filter(banned.eq(true)).load::<Self>(conn)
+ use diesel::sql_types::{Nullable, Text};
+ user_fast
+ .select((
+ id,
+ actor_id,
+ name,
+ preferred_username,
+ avatar,
+ banner,
+ "".into_sql::<Nullable<Text>>(),
+ matrix_user_id,
+ bio,
+ local,
+ admin,
+ banned,
+ show_avatars,
+ send_notifications_to_email,
+ published,
+ number_of_posts,
+ post_score,
+ number_of_comments,
+ comment_score,
+ ))
+ .filter(banned.eq(true))
+ .load::<Self>(conn)
+ }
+
+ pub fn get_user_secure(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
+ use super::user_view::user_fast::dsl::*;
+ use diesel::sql_types::{Nullable, Text};
+ user_fast
+ .select((
+ id,
+ actor_id,
+ name,
+ preferred_username,
+ avatar,
+ banner,
+ "".into_sql::<Nullable<Text>>(),
+ matrix_user_id,
+ bio,
+ local,
+ admin,
+ banned,
+ show_avatars,
+ send_notifications_to_email,
+ published,
+ number_of_posts,
+ post_score,
+ number_of_comments,
+ comment_score,
+ ))
+ .find(user_id)
+ .first::<Self>(conn)
}
}
comrak = "0.7"
lazy_static = "1.3.0"
openssl = "0.10"
-url = { version = "2.1.1", features = ["serde"] }
\ No newline at end of file
+url = { version = "2.1.1", features = ["serde"] }
pub mod settings;
use crate::settings::Settings;
-use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, Utc};
+use chrono::{DateTime, FixedOffset, Local, NaiveDateTime};
use itertools::Itertools;
use lettre::{
smtp::{
use std::io::{Error, ErrorKind};
use url::Url;
-pub fn to_datetime_utc(ndt: NaiveDateTime) -> DateTime<Utc> {
- DateTime::<Utc>::from_utc(ndt, Utc)
+#[macro_export]
+macro_rules! location_info {
+ () => {
+ format!(
+ "None value at {}:{}, column {}",
+ file!(),
+ line!(),
+ column!()
+ )
+ };
}
pub fn naive_from_unix(time: i64) -> NaiveDateTime {
DateTime::<FixedOffset>::from_utc(datetime, *now.offset())
}
-pub fn is_email_regex(test: &str) -> bool {
- EMAIL_REGEX.is_match(test)
-}
-
pub fn remove_slurs(test: &str) -> String {
SLUR_REGEX.replace_all(test, "*removed*").to_string()
}
VALID_USERNAME_REGEX.is_match(name)
}
+// Can't do a regex here, reverse lookarounds not supported
+pub fn is_valid_preferred_username(preferred_username: &str) -> bool {
+ !preferred_username.starts_with('@')
+ && preferred_username.len() >= 3
+ && preferred_username.len() <= 20
+}
+
pub fn is_valid_community_name(name: &str) -> bool {
VALID_COMMUNITY_NAME_REGEX.is_match(name)
}
#[cfg(test)]
mod tests {
use crate::{
- is_email_regex,
is_valid_community_name,
is_valid_post_title,
+ is_valid_preferred_username,
is_valid_username,
remove_slurs,
scrape_text_for_mentions,
assert_eq!(mentions[1].domain, "lemmy-alpha:8540".to_string());
}
- #[test]
- fn test_email() {
- assert!(is_email_regex("gush@gmail.com"));
- assert!(!is_email_regex("nada_neutho"));
- }
-
#[test]
fn test_valid_register_username() {
assert!(is_valid_username("Hello_98"));
assert!(!is_valid_username(""));
}
+ #[test]
+ fn test_valid_preferred_username() {
+ assert!(is_valid_preferred_username("hello @there"));
+ assert!(!is_valid_preferred_username("@hello there"));
+ }
+
#[test]
fn test_valid_community_name() {
assert!(is_valid_community_name("example"));
#[derive(Debug, Deserialize, Clone)]
pub struct Settings {
pub setup: Option<Setup>,
- pub database: Database,
+ pub database: DatabaseConfig,
pub hostname: String,
pub bind: IpAddr,
pub port: u16,
pub jwt_secret: String,
pub front_end_dir: String,
+ pub pictrs_url: String,
pub rate_limit: RateLimitConfig,
pub email: Option<EmailConfig>,
- pub federation: Federation,
+ pub federation: FederationConfig,
+ pub captcha: CaptchaConfig,
}
#[derive(Debug, Deserialize, Clone)]
pub post_per_second: i32,
pub register: i32,
pub register_per_second: i32,
+ pub image: i32,
+ pub image_per_second: i32,
}
#[derive(Debug, Deserialize, Clone)]
}
#[derive(Debug, Deserialize, Clone)]
-pub struct Database {
+pub struct CaptchaConfig {
+ pub enabled: bool,
+ pub difficulty: String, // easy, medium, or hard
+}
+
+#[derive(Debug, Deserialize, Clone)]
+pub struct DatabaseConfig {
pub user: String,
pub password: String,
pub host: String,
}
#[derive(Debug, Deserialize, Clone)]
-pub struct Federation {
+pub struct FederationConfig {
pub enabled: bool,
pub tls_enabled: bool,
pub allowed_instances: String,
+ pub blocked_instances: String,
}
lazy_static! {
fn init() -> Result<Self, ConfigError> {
let mut s = Config::new();
- s.merge(File::with_name(CONFIG_FILE_DEFAULTS))?;
+ s.merge(File::with_name(&Self::get_config_defaults_location()))?;
- s.merge(File::with_name(&Self::get_config_location()).required(false))?;
+ s.merge(File::with_name(CONFIG_FILE).required(false))?;
// Add in settings from the environment (with a prefix of LEMMY)
// Eg.. `LEMMY_DEBUG=1 ./target/app` would set the `debug` key
)
}
- pub fn api_endpoint(&self) -> String {
- format!("{}/api/v1", self.hostname)
+ pub fn get_config_defaults_location() -> String {
+ env::var("LEMMY_CONFIG_LOCATION").unwrap_or_else(|_| CONFIG_FILE_DEFAULTS.to_string())
}
- pub fn get_config_location() -> String {
- env::var("LEMMY_CONFIG_LOCATION").unwrap_or_else(|_| CONFIG_FILE.to_string())
+ pub fn read_config_file() -> Result<String, Error> {
+ fs::read_to_string(CONFIG_FILE)
}
- pub fn read_config_file() -> Result<String, Error> {
- fs::read_to_string(Self::get_config_location())
+ pub fn get_allowed_instances(&self) -> Vec<String> {
+ let mut allowed_instances: Vec<String> = self
+ .federation
+ .allowed_instances
+ .split(',')
+ .map(|d| d.to_string())
+ .collect();
+
+ // The defaults.hjson config always returns a [""]
+ allowed_instances.retain(|d| !d.eq(""));
+
+ allowed_instances
+ }
+
+ pub fn get_blocked_instances(&self) -> Vec<String> {
+ let mut blocked_instances: Vec<String> = self
+ .federation
+ .blocked_instances
+ .split(',')
+ .map(|d| d.to_string())
+ .collect();
+
+ // The defaults.hjson config always returns a [""]
+ blocked_instances.retain(|d| !d.eq(""));
+
+ blocked_instances
}
pub fn save_config_file(data: &str) -> Result<String, Error> {
- fs::write(Self::get_config_location(), data)?;
+ fs::write(CONFIG_FILE, data)?;
// Reload the new settings
// From https://stackoverflow.com/questions/29654927/how-do-i-assign-a-string-to-a-mutable-static-variable/47181804#47181804
--- /dev/null
+
+alter table community alter column actor_id set not null;
+alter table community alter column actor_id set default 'http://fake.com';
+alter table user_ alter column actor_id set not null;
+alter table user_ alter column actor_id set default 'http://fake.com';
+
+drop function generate_unique_changeme;
+
+update community
+set actor_id = 'http://fake.com'
+where actor_id like 'changeme_%';
+
+update user_
+set actor_id = 'http://fake.com'
+where actor_id like 'changeme_%';
+
+drop index idx_user_lower_actor_id;
+create unique index idx_user_name_lower_actor_id on user_ (lower(name), lower(actor_id));
+
+drop index idx_community_lower_actor_id;
--- /dev/null
+-- Following this issue : https://github.com/LemmyNet/lemmy/issues/957
+
+-- Creating a unique changeme actor_id
+create or replace function generate_unique_changeme()
+returns text language sql
+as $$
+ select 'changeme_' || string_agg (substr('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789', ceil (random() * 62)::integer, 1), '')
+ from generate_series(1, 20)
+$$;
+
+-- Need to delete the possible community and user dupes for ones that don't start with the fake one
+-- A few test inserts, to make sure this removes later dupes
+-- insert into community (name, title, category_id, creator_id) values ('testcom', 'another testcom', 1, 2);
+delete from community a using (
+ select min(id) as id, actor_id
+ from community
+ group by actor_id having count(*) > 1
+) b
+where a.actor_id = b.actor_id
+and a.id <> b.id;
+
+delete from user_ a using (
+ select min(id) as id, actor_id
+ from user_
+ group by actor_id having count(*) > 1
+) b
+where a.actor_id = b.actor_id
+and a.id <> b.id;
+
+-- Replacing the current default on the columns, to the unique one
+update community
+set actor_id = generate_unique_changeme()
+where actor_id = 'http://fake.com';
+
+update user_
+set actor_id = generate_unique_changeme()
+where actor_id = 'http://fake.com';
+
+-- Add the unique indexes
+alter table community alter column actor_id set not null;
+alter table community alter column actor_id set default generate_unique_changeme();
+
+alter table user_ alter column actor_id set not null;
+alter table user_ alter column actor_id set default generate_unique_changeme();
+
+-- Add lowercase uniqueness too
+drop index idx_user_name_lower_actor_id;
+create unique index idx_user_lower_actor_id on user_ (lower(actor_id));
+
+create unique index idx_community_lower_actor_id on community (lower(actor_id));
--- /dev/null
+-- Drops first
+drop view site_view;
+drop table user_fast;
+drop view user_view;
+drop view post_fast_view;
+drop table post_aggregates_fast;
+drop view post_view;
+drop view post_aggregates_view;
+drop view community_moderator_view;
+drop view community_follower_view;
+drop view community_user_ban_view;
+drop view community_view;
+drop view community_aggregates_view;
+drop view community_fast_view;
+drop table community_aggregates_fast;
+drop view private_message_view;
+drop view user_mention_view;
+drop view reply_fast_view;
+drop view comment_fast_view;
+drop view comment_view;
+drop view user_mention_fast_view;
+drop table comment_aggregates_fast;
+drop view comment_aggregates_view;
+
+alter table site
+ drop column icon,
+ drop column banner;
+
+alter table community
+ drop column icon,
+ drop column banner;
+
+alter table user_ drop column banner;
+
+-- Site
+create view site_view as
+select *,
+(select name from user_ u where s.creator_id = u.id) as creator_name,
+(select avatar from user_ u where s.creator_id = u.id) as creator_avatar,
+(select count(*) from user_) as number_of_users,
+(select count(*) from post) as number_of_posts,
+(select count(*) from comment) as number_of_comments,
+(select count(*) from community) as number_of_communities
+from site s;
+
+-- User
+create view user_view as
+select
+ u.id,
+ u.actor_id,
+ u.name,
+ u.avatar,
+ u.email,
+ u.matrix_user_id,
+ u.bio,
+ u.local,
+ u.admin,
+ u.banned,
+ u.show_avatars,
+ u.send_notifications_to_email,
+ u.published,
+ coalesce(pd.posts, 0) as number_of_posts,
+ coalesce(pd.score, 0) as post_score,
+ coalesce(cd.comments, 0) as number_of_comments,
+ coalesce(cd.score, 0) as comment_score
+from user_ u
+left join (
+ select
+ p.creator_id as creator_id,
+ count(distinct p.id) as posts,
+ sum(pl.score) as score
+ from post p
+ join post_like pl on p.id = pl.post_id
+ group by p.creator_id
+) pd on u.id = pd.creator_id
+left join (
+ select
+ c.creator_id,
+ count(distinct c.id) as comments,
+ sum(cl.score) as score
+ from comment c
+ join comment_like cl on c.id = cl.comment_id
+ group by c.creator_id
+) cd on u.id = cd.creator_id;
+
+create table user_fast as select * from user_view;
+alter table user_fast add primary key (id);
+
+-- Post fast
+
+create view post_aggregates_view as
+select
+ p.*,
+ -- creator details
+ u.actor_id as creator_actor_id,
+ u."local" as creator_local,
+ u."name" as creator_name,
+ u.published as creator_published,
+ u.avatar as creator_avatar,
+ u.banned as banned,
+ cb.id::bool as banned_from_community,
+ -- community details
+ c.actor_id as community_actor_id,
+ c."local" as community_local,
+ c."name" as community_name,
+ c.removed as community_removed,
+ c.deleted as community_deleted,
+ c.nsfw as community_nsfw,
+ -- post score data/comment count
+ coalesce(ct.comments, 0) as number_of_comments,
+ coalesce(pl.score, 0) as score,
+ coalesce(pl.upvotes, 0) as upvotes,
+ coalesce(pl.downvotes, 0) as downvotes,
+ hot_rank(
+ coalesce(pl.score , 0), (
+ case
+ when (p.published < ('now'::timestamp - '1 month'::interval))
+ then p.published
+ else greatest(ct.recent_comment_time, p.published)
+ end
+ )
+ ) as hot_rank,
+ (
+ case
+ when (p.published < ('now'::timestamp - '1 month'::interval))
+ then p.published
+ else greatest(ct.recent_comment_time, p.published)
+ end
+ ) as newest_activity_time
+from post p
+left join user_ u on p.creator_id = u.id
+left join community_user_ban cb on p.creator_id = cb.user_id and p.community_id = cb.community_id
+left join community c on p.community_id = c.id
+left join (
+ select
+ post_id,
+ count(*) as comments,
+ max(published) as recent_comment_time
+ from comment
+ group by post_id
+) ct on ct.post_id = p.id
+left join (
+ select
+ post_id,
+ sum(score) as score,
+ sum(score) filter (where score = 1) as upvotes,
+ -sum(score) filter (where score = -1) as downvotes
+ from post_like
+ group by post_id
+) pl on pl.post_id = p.id
+order by p.id;
+
+create view post_view as
+select
+ pav.*,
+ us.id as user_id,
+ us.user_vote as my_vote,
+ us.is_subbed::bool as subscribed,
+ us.is_read::bool as read,
+ us.is_saved::bool as saved
+from post_aggregates_view pav
+cross join lateral (
+ select
+ u.id,
+ coalesce(cf.community_id, 0) as is_subbed,
+ coalesce(pr.post_id, 0) as is_read,
+ coalesce(ps.post_id, 0) as is_saved,
+ coalesce(pl.score, 0) as user_vote
+ from user_ u
+ left join community_user_ban cb on u.id = cb.user_id and cb.community_id = pav.community_id
+ left join community_follower cf on u.id = cf.user_id and cf.community_id = pav.community_id
+ left join post_read pr on u.id = pr.user_id and pr.post_id = pav.id
+ left join post_saved ps on u.id = ps.user_id and ps.post_id = pav.id
+ left join post_like pl on u.id = pl.user_id and pav.id = pl.post_id
+) as us
+
+union all
+
+select
+pav.*,
+null as user_id,
+null as my_vote,
+null as subscribed,
+null as read,
+null as saved
+from post_aggregates_view pav;
+
+create table post_aggregates_fast as select * from post_aggregates_view;
+alter table post_aggregates_fast add primary key (id);
+
+create view post_fast_view as
+select
+ pav.*,
+ us.id as user_id,
+ us.user_vote as my_vote,
+ us.is_subbed::bool as subscribed,
+ us.is_read::bool as read,
+ us.is_saved::bool as saved
+from post_aggregates_fast pav
+cross join lateral (
+ select
+ u.id,
+ coalesce(cf.community_id, 0) as is_subbed,
+ coalesce(pr.post_id, 0) as is_read,
+ coalesce(ps.post_id, 0) as is_saved,
+ coalesce(pl.score, 0) as user_vote
+ from user_ u
+ left join community_user_ban cb on u.id = cb.user_id and cb.community_id = pav.community_id
+ left join community_follower cf on u.id = cf.user_id and cf.community_id = pav.community_id
+ left join post_read pr on u.id = pr.user_id and pr.post_id = pav.id
+ left join post_saved ps on u.id = ps.user_id and ps.post_id = pav.id
+ left join post_like pl on u.id = pl.user_id and pav.id = pl.post_id
+) as us
+
+union all
+
+select
+pav.*,
+null as user_id,
+null as my_vote,
+null as subscribed,
+null as read,
+null as saved
+from post_aggregates_fast pav;
+
+-- Community
+create view community_aggregates_view as
+select
+ c.id,
+ c.name,
+ c.title,
+ c.description,
+ c.category_id,
+ c.creator_id,
+ c.removed,
+ c.published,
+ c.updated,
+ c.deleted,
+ c.nsfw,
+ c.actor_id,
+ c.local,
+ c.last_refreshed_at,
+ u.actor_id as creator_actor_id,
+ u.local as creator_local,
+ u.name as creator_name,
+ u.avatar as creator_avatar,
+ cat.name as category_name,
+ coalesce(cf.subs, 0) as number_of_subscribers,
+ coalesce(cd.posts, 0) as number_of_posts,
+ coalesce(cd.comments, 0) as number_of_comments,
+ hot_rank(cf.subs, c.published) as hot_rank
+from community c
+left join user_ u on c.creator_id = u.id
+left join category cat on c.category_id = cat.id
+left join (
+ select
+ p.community_id,
+ count(distinct p.id) as posts,
+ count(distinct ct.id) as comments
+ from post p
+ join comment ct on p.id = ct.post_id
+ group by p.community_id
+) cd on cd.community_id = c.id
+left join (
+ select
+ community_id,
+ count(*) as subs
+ from community_follower
+ group by community_id
+) cf on cf.community_id = c.id;
+
+create view community_view as
+select
+ cv.*,
+ us.user as user_id,
+ us.is_subbed::bool as subscribed
+from community_aggregates_view cv
+cross join lateral (
+ select
+ u.id as user,
+ coalesce(cf.community_id, 0) as is_subbed
+ from user_ u
+ left join community_follower cf on u.id = cf.user_id and cf.community_id = cv.id
+) as us
+
+union all
+
+select
+ cv.*,
+ null as user_id,
+ null as subscribed
+from community_aggregates_view cv;
+
+create view community_moderator_view as
+select
+ cm.*,
+ u.actor_id as user_actor_id,
+ u.local as user_local,
+ u.name as user_name,
+ u.avatar as avatar,
+ c.actor_id as community_actor_id,
+ c.local as community_local,
+ c.name as community_name
+from community_moderator cm
+left join user_ u on cm.user_id = u.id
+left join community c on cm.community_id = c.id;
+
+create view community_follower_view as
+select
+ cf.*,
+ u.actor_id as user_actor_id,
+ u.local as user_local,
+ u.name as user_name,
+ u.avatar as avatar,
+ c.actor_id as community_actor_id,
+ c.local as community_local,
+ c.name as community_name
+from community_follower cf
+left join user_ u on cf.user_id = u.id
+left join community c on cf.community_id = c.id;
+
+create view community_user_ban_view as
+select
+ cb.*,
+ u.actor_id as user_actor_id,
+ u.local as user_local,
+ u.name as user_name,
+ u.avatar as avatar,
+ c.actor_id as community_actor_id,
+ c.local as community_local,
+ c.name as community_name
+from community_user_ban cb
+left join user_ u on cb.user_id = u.id
+left join community c on cb.community_id = c.id;
+
+-- The community fast table
+
+create table community_aggregates_fast as select * from community_aggregates_view;
+alter table community_aggregates_fast add primary key (id);
+
+create view community_fast_view as
+select
+ac.*,
+u.id as user_id,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed
+from user_ u
+cross join (
+ select
+ ca.*
+ from community_aggregates_fast ca
+) ac
+
+union all
+
+select
+caf.*,
+null as user_id,
+null as subscribed
+from community_aggregates_fast caf;
+
+
+-- Private message
+create view private_message_view as
+select
+pm.*,
+u.name as creator_name,
+u.avatar as creator_avatar,
+u.actor_id as creator_actor_id,
+u.local as creator_local,
+u2.name as recipient_name,
+u2.avatar as recipient_avatar,
+u2.actor_id as recipient_actor_id,
+u2.local as recipient_local
+from private_message pm
+inner join user_ u on u.id = pm.creator_id
+inner join user_ u2 on u2.id = pm.recipient_id;
+
+
+-- Comments, mentions, replies
+
+create view comment_aggregates_view as
+select
+ ct.*,
+ -- post details
+ p."name" as post_name,
+ p.community_id,
+ -- community details
+ c.actor_id as community_actor_id,
+ c."local" as community_local,
+ c."name" as community_name,
+ -- creator details
+ u.banned as banned,
+ coalesce(cb.id, 0)::bool as banned_from_community,
+ u.actor_id as creator_actor_id,
+ u.local as creator_local,
+ u.name as creator_name,
+ u.published as creator_published,
+ u.avatar as creator_avatar,
+ -- score details
+ coalesce(cl.total, 0) as score,
+ coalesce(cl.up, 0) as upvotes,
+ coalesce(cl.down, 0) as downvotes,
+ hot_rank(coalesce(cl.total, 0), ct.published) as hot_rank
+from comment ct
+left join post p on ct.post_id = p.id
+left join community c on p.community_id = c.id
+left join user_ u on ct.creator_id = u.id
+left join community_user_ban cb on ct.creator_id = cb.user_id and p.id = ct.post_id and p.community_id = cb.community_id
+left join (
+ select
+ l.comment_id as id,
+ sum(l.score) as total,
+ count(case when l.score = 1 then 1 else null end) as up,
+ count(case when l.score = -1 then 1 else null end) as down
+ from comment_like l
+ group by comment_id
+) as cl on cl.id = ct.id;
+
+create or replace view comment_view as (
+select
+ cav.*,
+ us.user_id as user_id,
+ us.my_vote as my_vote,
+ us.is_subbed::bool as subscribed,
+ us.is_saved::bool as saved
+from comment_aggregates_view cav
+cross join lateral (
+ select
+ u.id as user_id,
+ coalesce(cl.score, 0) as my_vote,
+ coalesce(cf.id, 0) as is_subbed,
+ coalesce(cs.id, 0) as is_saved
+ from user_ u
+ left join comment_like cl on u.id = cl.user_id and cav.id = cl.comment_id
+ left join comment_saved cs on u.id = cs.user_id and cs.comment_id = cav.id
+ left join community_follower cf on u.id = cf.user_id and cav.community_id = cf.community_id
+) as us
+
+union all
+
+select
+ cav.*,
+ null as user_id,
+ null as my_vote,
+ null as subscribed,
+ null as saved
+from comment_aggregates_view cav
+);
+
+create table comment_aggregates_fast as select * from comment_aggregates_view;
+alter table comment_aggregates_fast add primary key (id);
+
+create view comment_fast_view as
+select
+ cav.*,
+ us.user_id as user_id,
+ us.my_vote as my_vote,
+ us.is_subbed::bool as subscribed,
+ us.is_saved::bool as saved
+from comment_aggregates_fast cav
+cross join lateral (
+ select
+ u.id as user_id,
+ coalesce(cl.score, 0) as my_vote,
+ coalesce(cf.id, 0) as is_subbed,
+ coalesce(cs.id, 0) as is_saved
+ from user_ u
+ left join comment_like cl on u.id = cl.user_id and cav.id = cl.comment_id
+ left join comment_saved cs on u.id = cs.user_id and cs.comment_id = cav.id
+ left join community_follower cf on u.id = cf.user_id and cav.community_id = cf.community_id
+) as us
+
+union all
+
+select
+ cav.*,
+ null as user_id,
+ null as my_vote,
+ null as subscribed,
+ null as saved
+from comment_aggregates_fast cav;
+
+create view user_mention_view as
+select
+ c.id,
+ um.id as user_mention_id,
+ c.creator_id,
+ c.creator_actor_id,
+ c.creator_local,
+ c.post_id,
+ c.post_name,
+ c.parent_id,
+ c.content,
+ c.removed,
+ um.read,
+ c.published,
+ c.updated,
+ c.deleted,
+ c.community_id,
+ c.community_actor_id,
+ c.community_local,
+ c.community_name,
+ c.banned,
+ c.banned_from_community,
+ c.creator_name,
+ c.creator_avatar,
+ c.score,
+ c.upvotes,
+ c.downvotes,
+ c.hot_rank,
+ c.user_id,
+ c.my_vote,
+ c.saved,
+ um.recipient_id,
+ (select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
+ (select local from user_ u where u.id = um.recipient_id) as recipient_local
+from user_mention um, comment_view c
+where um.comment_id = c.id;
+
+create view user_mention_fast_view as
+select
+ ac.id,
+ um.id as user_mention_id,
+ ac.creator_id,
+ ac.creator_actor_id,
+ ac.creator_local,
+ ac.post_id,
+ ac.post_name,
+ ac.parent_id,
+ ac.content,
+ ac.removed,
+ um.read,
+ ac.published,
+ ac.updated,
+ ac.deleted,
+ ac.community_id,
+ ac.community_actor_id,
+ ac.community_local,
+ ac.community_name,
+ ac.banned,
+ ac.banned_from_community,
+ ac.creator_name,
+ ac.creator_avatar,
+ ac.score,
+ ac.upvotes,
+ ac.downvotes,
+ ac.hot_rank,
+ u.id as user_id,
+ coalesce(cl.score, 0) as my_vote,
+ (select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved,
+ um.recipient_id,
+ (select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
+ (select local from user_ u where u.id = um.recipient_id) as recipient_local
+from user_ u
+cross join (
+ select
+ ca.*
+ from comment_aggregates_fast ca
+) ac
+left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
+left join user_mention um on um.comment_id = ac.id
+
+union all
+
+select
+ ac.id,
+ um.id as user_mention_id,
+ ac.creator_id,
+ ac.creator_actor_id,
+ ac.creator_local,
+ ac.post_id,
+ ac.post_name,
+ ac.parent_id,
+ ac.content,
+ ac.removed,
+ um.read,
+ ac.published,
+ ac.updated,
+ ac.deleted,
+ ac.community_id,
+ ac.community_actor_id,
+ ac.community_local,
+ ac.community_name,
+ ac.banned,
+ ac.banned_from_community,
+ ac.creator_name,
+ ac.creator_avatar,
+ ac.score,
+ ac.upvotes,
+ ac.downvotes,
+ ac.hot_rank,
+ null as user_id,
+ null as my_vote,
+ null as saved,
+ um.recipient_id,
+ (select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
+ (select local from user_ u where u.id = um.recipient_id) as recipient_local
+from comment_aggregates_fast ac
+left join user_mention um on um.comment_id = ac.id
+;
+
+-- Do the reply_view referencing the comment_fast_view
+create view reply_fast_view as
+with closereply as (
+ select
+ c2.id,
+ c2.creator_id as sender_id,
+ c.creator_id as recipient_id
+ from comment c
+ inner join comment c2 on c.id = c2.parent_id
+ where c2.creator_id != c.creator_id
+ -- Do union where post is null
+ union
+ select
+ c.id,
+ c.creator_id as sender_id,
+ p.creator_id as recipient_id
+ from comment c, post p
+ where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id
+)
+select cv.*,
+closereply.recipient_id
+from comment_fast_view cv, closereply
+where closereply.id = cv.id
+;
+
+-- redoing the triggers
+create or replace function refresh_post()
+returns trigger language plpgsql
+as $$
+begin
+ IF (TG_OP = 'DELETE') THEN
+ delete from post_aggregates_fast where id = OLD.id;
+
+ -- Update community number of posts
+ update community_aggregates_fast set number_of_posts = number_of_posts - 1 where id = OLD.community_id;
+ ELSIF (TG_OP = 'UPDATE') THEN
+ delete from post_aggregates_fast where id = OLD.id;
+ insert into post_aggregates_fast select * from post_aggregates_view where id = NEW.id;
+ ELSIF (TG_OP = 'INSERT') THEN
+ insert into post_aggregates_fast select * from post_aggregates_view where id = NEW.id;
+
+ -- Update that users number of posts, post score
+ delete from user_fast where id = NEW.creator_id;
+ insert into user_fast select * from user_view where id = NEW.creator_id;
+
+ -- Update community number of posts
+ update community_aggregates_fast set number_of_posts = number_of_posts + 1 where id = NEW.community_id;
+
+ -- Update the hot rank on the post table
+ -- TODO this might not correctly update it, using a 1 week interval
+ update post_aggregates_fast as paf
+ set hot_rank = pav.hot_rank
+ from post_aggregates_view as pav
+ where paf.id = pav.id and (pav.published > ('now'::timestamp - '1 week'::interval));
+ END IF;
+
+ return null;
+end $$;
+
+create or replace function refresh_comment()
+returns trigger language plpgsql
+as $$
+begin
+ IF (TG_OP = 'DELETE') THEN
+ delete from comment_aggregates_fast where id = OLD.id;
+
+ -- Update community number of comments
+ update community_aggregates_fast as caf
+ set number_of_comments = number_of_comments - 1
+ from post as p
+ where caf.id = p.community_id and p.id = OLD.post_id;
+
+ ELSIF (TG_OP = 'UPDATE') THEN
+ delete from comment_aggregates_fast where id = OLD.id;
+ insert into comment_aggregates_fast select * from comment_aggregates_view where id = NEW.id;
+ ELSIF (TG_OP = 'INSERT') THEN
+ insert into comment_aggregates_fast select * from comment_aggregates_view where id = NEW.id;
+
+ -- Update user view due to comment count
+ update user_fast
+ set number_of_comments = number_of_comments + 1
+ where id = NEW.creator_id;
+
+ -- Update post view due to comment count, new comment activity time, but only on new posts
+ -- TODO this could be done more efficiently
+ delete from post_aggregates_fast where id = NEW.post_id;
+ insert into post_aggregates_fast select * from post_aggregates_view where id = NEW.post_id;
+
+ -- Force the hot rank as zero on week-older posts
+ update post_aggregates_fast as paf
+ set hot_rank = 0
+ where paf.id = NEW.post_id and (paf.published < ('now'::timestamp - '1 week'::interval));
+
+ -- Update community number of comments
+ update community_aggregates_fast as caf
+ set number_of_comments = number_of_comments + 1
+ from post as p
+ where caf.id = p.community_id and p.id = NEW.post_id;
+
+ END IF;
+
+ return null;
+end $$;
--- /dev/null
+-- This adds the following columns, as well as updates the views:
+-- Site icon
+-- Site banner
+-- Community icon
+-- Community Banner
+-- User Banner (User avatar is already there)
+-- User preferred name (already in table, needs to be added to view)
+
+-- It also adds hot_rank_active to post_view
+
+alter table site
+ add column icon text,
+ add column banner text;
+
+alter table community
+ add column icon text,
+ add column banner text;
+
+alter table user_ add column banner text;
+
+drop view site_view;
+create view site_view as
+select s.*,
+u.name as creator_name,
+u.preferred_username as creator_preferred_username,
+u.avatar as creator_avatar,
+(select count(*) from user_) as number_of_users,
+(select count(*) from post) as number_of_posts,
+(select count(*) from comment) as number_of_comments,
+(select count(*) from community) as number_of_communities
+from site s
+left join user_ u on s.creator_id = u.id;
+
+-- User
+drop table user_fast;
+drop view user_view;
+create view user_view as
+select
+ u.id,
+ u.actor_id,
+ u.name,
+ u.preferred_username,
+ u.avatar,
+ u.banner,
+ u.email,
+ u.matrix_user_id,
+ u.bio,
+ u.local,
+ u.admin,
+ u.banned,
+ u.show_avatars,
+ u.send_notifications_to_email,
+ u.published,
+ coalesce(pd.posts, 0) as number_of_posts,
+ coalesce(pd.score, 0) as post_score,
+ coalesce(cd.comments, 0) as number_of_comments,
+ coalesce(cd.score, 0) as comment_score
+from user_ u
+left join (
+ select
+ p.creator_id as creator_id,
+ count(distinct p.id) as posts,
+ sum(pl.score) as score
+ from post p
+ join post_like pl on p.id = pl.post_id
+ group by p.creator_id
+) pd on u.id = pd.creator_id
+left join (
+ select
+ c.creator_id,
+ count(distinct c.id) as comments,
+ sum(cl.score) as score
+ from comment c
+ join comment_like cl on c.id = cl.comment_id
+ group by c.creator_id
+) cd on u.id = cd.creator_id;
+
+create table user_fast as select * from user_view;
+alter table user_fast add primary key (id);
+
+-- private message
+drop view private_message_view;
+create view private_message_view as
+select
+pm.*,
+u.name as creator_name,
+u.preferred_username as creator_preferred_username,
+u.avatar as creator_avatar,
+u.actor_id as creator_actor_id,
+u.local as creator_local,
+u2.name as recipient_name,
+u2.preferred_username as recipient_preferred_username,
+u2.avatar as recipient_avatar,
+u2.actor_id as recipient_actor_id,
+u2.local as recipient_local
+from private_message pm
+inner join user_ u on u.id = pm.creator_id
+inner join user_ u2 on u2.id = pm.recipient_id;
+
+-- Post fast
+drop view post_fast_view;
+drop table post_aggregates_fast;
+drop view post_view;
+drop view post_aggregates_view;
+
+create view post_aggregates_view as
+select
+ p.*,
+ -- creator details
+ u.actor_id as creator_actor_id,
+ u."local" as creator_local,
+ u."name" as creator_name,
+ u."preferred_username" as creator_preferred_username,
+ u.published as creator_published,
+ u.avatar as creator_avatar,
+ u.banned as banned,
+ cb.id::bool as banned_from_community,
+ -- community details
+ c.actor_id as community_actor_id,
+ c."local" as community_local,
+ c."name" as community_name,
+ c.icon as community_icon,
+ c.removed as community_removed,
+ c.deleted as community_deleted,
+ c.nsfw as community_nsfw,
+ -- post score data/comment count
+ coalesce(ct.comments, 0) as number_of_comments,
+ coalesce(pl.score, 0) as score,
+ coalesce(pl.upvotes, 0) as upvotes,
+ coalesce(pl.downvotes, 0) as downvotes,
+ hot_rank(coalesce(pl.score, 1), p.published) as hot_rank,
+ hot_rank(coalesce(pl.score, 1), greatest(ct.recent_comment_time, p.published)) as hot_rank_active,
+ greatest(ct.recent_comment_time, p.published) as newest_activity_time
+from post p
+left join user_ u on p.creator_id = u.id
+left join community_user_ban cb on p.creator_id = cb.user_id and p.community_id = cb.community_id
+left join community c on p.community_id = c.id
+left join (
+ select
+ post_id,
+ count(*) as comments,
+ max(published) as recent_comment_time
+ from comment
+ group by post_id
+) ct on ct.post_id = p.id
+left join (
+ select
+ post_id,
+ sum(score) as score,
+ sum(score) filter (where score = 1) as upvotes,
+ -sum(score) filter (where score = -1) as downvotes
+ from post_like
+ group by post_id
+) pl on pl.post_id = p.id
+order by p.id;
+
+create view post_view as
+select
+ pav.*,
+ us.id as user_id,
+ us.user_vote as my_vote,
+ us.is_subbed::bool as subscribed,
+ us.is_read::bool as read,
+ us.is_saved::bool as saved
+from post_aggregates_view pav
+cross join lateral (
+ select
+ u.id,
+ coalesce(cf.community_id, 0) as is_subbed,
+ coalesce(pr.post_id, 0) as is_read,
+ coalesce(ps.post_id, 0) as is_saved,
+ coalesce(pl.score, 0) as user_vote
+ from user_ u
+ left join community_user_ban cb on u.id = cb.user_id and cb.community_id = pav.community_id
+ left join community_follower cf on u.id = cf.user_id and cf.community_id = pav.community_id
+ left join post_read pr on u.id = pr.user_id and pr.post_id = pav.id
+ left join post_saved ps on u.id = ps.user_id and ps.post_id = pav.id
+ left join post_like pl on u.id = pl.user_id and pav.id = pl.post_id
+) as us
+
+union all
+
+select
+pav.*,
+null as user_id,
+null as my_vote,
+null as subscribed,
+null as read,
+null as saved
+from post_aggregates_view pav;
+
+create table post_aggregates_fast as select * from post_aggregates_view;
+alter table post_aggregates_fast add primary key (id);
+
+-- For the hot rank resorting
+create index idx_post_aggregates_fast_hot_rank_published on post_aggregates_fast (hot_rank desc, published desc);
+create index idx_post_aggregates_fast_hot_rank_active_published on post_aggregates_fast (hot_rank_active desc, published desc);
+
+create view post_fast_view as
+select
+ pav.*,
+ us.id as user_id,
+ us.user_vote as my_vote,
+ us.is_subbed::bool as subscribed,
+ us.is_read::bool as read,
+ us.is_saved::bool as saved
+from post_aggregates_fast pav
+cross join lateral (
+ select
+ u.id,
+ coalesce(cf.community_id, 0) as is_subbed,
+ coalesce(pr.post_id, 0) as is_read,
+ coalesce(ps.post_id, 0) as is_saved,
+ coalesce(pl.score, 0) as user_vote
+ from user_ u
+ left join community_user_ban cb on u.id = cb.user_id and cb.community_id = pav.community_id
+ left join community_follower cf on u.id = cf.user_id and cf.community_id = pav.community_id
+ left join post_read pr on u.id = pr.user_id and pr.post_id = pav.id
+ left join post_saved ps on u.id = ps.user_id and ps.post_id = pav.id
+ left join post_like pl on u.id = pl.user_id and pav.id = pl.post_id
+) as us
+
+union all
+
+select
+pav.*,
+null as user_id,
+null as my_vote,
+null as subscribed,
+null as read,
+null as saved
+from post_aggregates_fast pav;
+
+-- Community
+drop view community_moderator_view;
+drop view community_follower_view;
+drop view community_user_ban_view;
+drop view community_view;
+drop view community_aggregates_view;
+drop view community_fast_view;
+drop table community_aggregates_fast;
+
+create view community_aggregates_view as
+select
+ c.id,
+ c.name,
+ c.title,
+ c.icon,
+ c.banner,
+ c.description,
+ c.category_id,
+ c.creator_id,
+ c.removed,
+ c.published,
+ c.updated,
+ c.deleted,
+ c.nsfw,
+ c.actor_id,
+ c.local,
+ c.last_refreshed_at,
+ u.actor_id as creator_actor_id,
+ u.local as creator_local,
+ u.name as creator_name,
+ u.preferred_username as creator_preferred_username,
+ u.avatar as creator_avatar,
+ cat.name as category_name,
+ coalesce(cf.subs, 0) as number_of_subscribers,
+ coalesce(cd.posts, 0) as number_of_posts,
+ coalesce(cd.comments, 0) as number_of_comments,
+ hot_rank(cf.subs, c.published) as hot_rank
+from community c
+left join user_ u on c.creator_id = u.id
+left join category cat on c.category_id = cat.id
+left join (
+ select
+ p.community_id,
+ count(distinct p.id) as posts,
+ count(distinct ct.id) as comments
+ from post p
+ join comment ct on p.id = ct.post_id
+ group by p.community_id
+) cd on cd.community_id = c.id
+left join (
+ select
+ community_id,
+ count(*) as subs
+ from community_follower
+ group by community_id
+) cf on cf.community_id = c.id;
+
+create view community_view as
+select
+ cv.*,
+ us.user as user_id,
+ us.is_subbed::bool as subscribed
+from community_aggregates_view cv
+cross join lateral (
+ select
+ u.id as user,
+ coalesce(cf.community_id, 0) as is_subbed
+ from user_ u
+ left join community_follower cf on u.id = cf.user_id and cf.community_id = cv.id
+) as us
+
+union all
+
+select
+ cv.*,
+ null as user_id,
+ null as subscribed
+from community_aggregates_view cv;
+
+create view community_moderator_view as
+select
+ cm.*,
+ u.actor_id as user_actor_id,
+ u.local as user_local,
+ u.name as user_name,
+ u.preferred_username as user_preferred_username,
+ u.avatar as avatar,
+ c.actor_id as community_actor_id,
+ c.local as community_local,
+ c.name as community_name,
+ c.icon as community_icon
+from community_moderator cm
+left join user_ u on cm.user_id = u.id
+left join community c on cm.community_id = c.id;
+
+create view community_follower_view as
+select
+ cf.*,
+ u.actor_id as user_actor_id,
+ u.local as user_local,
+ u.name as user_name,
+ u.preferred_username as user_preferred_username,
+ u.avatar as avatar,
+ c.actor_id as community_actor_id,
+ c.local as community_local,
+ c.name as community_name,
+ c.icon as community_icon
+from community_follower cf
+left join user_ u on cf.user_id = u.id
+left join community c on cf.community_id = c.id;
+
+create view community_user_ban_view as
+select
+ cb.*,
+ u.actor_id as user_actor_id,
+ u.local as user_local,
+ u.name as user_name,
+ u.preferred_username as user_preferred_username,
+ u.avatar as avatar,
+ c.actor_id as community_actor_id,
+ c.local as community_local,
+ c.name as community_name,
+ c.icon as community_icon
+from community_user_ban cb
+left join user_ u on cb.user_id = u.id
+left join community c on cb.community_id = c.id;
+
+-- The community fast table
+
+create table community_aggregates_fast as select * from community_aggregates_view;
+alter table community_aggregates_fast add primary key (id);
+
+create view community_fast_view as
+select
+ac.*,
+u.id as user_id,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed
+from user_ u
+cross join (
+ select
+ ca.*
+ from community_aggregates_fast ca
+) ac
+
+union all
+
+select
+caf.*,
+null as user_id,
+null as subscribed
+from community_aggregates_fast caf;
+
+-- Comments, mentions, replies
+drop view user_mention_view;
+drop view reply_fast_view;
+drop view comment_fast_view;
+drop view comment_view;
+drop view user_mention_fast_view;
+drop table comment_aggregates_fast;
+drop view comment_aggregates_view;
+
+create view comment_aggregates_view as
+select
+ ct.*,
+ -- post details
+ p."name" as post_name,
+ p.community_id,
+ -- community details
+ c.actor_id as community_actor_id,
+ c."local" as community_local,
+ c."name" as community_name,
+ c.icon as community_icon,
+ -- creator details
+ u.banned as banned,
+ coalesce(cb.id, 0)::bool as banned_from_community,
+ u.actor_id as creator_actor_id,
+ u.local as creator_local,
+ u.name as creator_name,
+ u.preferred_username as creator_preferred_username,
+ u.published as creator_published,
+ u.avatar as creator_avatar,
+ -- score details
+ coalesce(cl.total, 0) as score,
+ coalesce(cl.up, 0) as upvotes,
+ coalesce(cl.down, 0) as downvotes,
+ hot_rank(coalesce(cl.total, 1), p.published) as hot_rank,
+ hot_rank(coalesce(cl.total, 1), ct.published) as hot_rank_active
+from comment ct
+left join post p on ct.post_id = p.id
+left join community c on p.community_id = c.id
+left join user_ u on ct.creator_id = u.id
+left join community_user_ban cb on ct.creator_id = cb.user_id and p.id = ct.post_id and p.community_id = cb.community_id
+left join (
+ select
+ l.comment_id as id,
+ sum(l.score) as total,
+ count(case when l.score = 1 then 1 else null end) as up,
+ count(case when l.score = -1 then 1 else null end) as down
+ from comment_like l
+ group by comment_id
+) as cl on cl.id = ct.id;
+
+create or replace view comment_view as (
+select
+ cav.*,
+ us.user_id as user_id,
+ us.my_vote as my_vote,
+ us.is_subbed::bool as subscribed,
+ us.is_saved::bool as saved
+from comment_aggregates_view cav
+cross join lateral (
+ select
+ u.id as user_id,
+ coalesce(cl.score, 0) as my_vote,
+ coalesce(cf.id, 0) as is_subbed,
+ coalesce(cs.id, 0) as is_saved
+ from user_ u
+ left join comment_like cl on u.id = cl.user_id and cav.id = cl.comment_id
+ left join comment_saved cs on u.id = cs.user_id and cs.comment_id = cav.id
+ left join community_follower cf on u.id = cf.user_id and cav.community_id = cf.community_id
+) as us
+
+union all
+
+select
+ cav.*,
+ null as user_id,
+ null as my_vote,
+ null as subscribed,
+ null as saved
+from comment_aggregates_view cav
+);
+
+create table comment_aggregates_fast as select * from comment_aggregates_view;
+alter table comment_aggregates_fast add primary key (id);
+
+create view comment_fast_view as
+select
+ cav.*,
+ us.user_id as user_id,
+ us.my_vote as my_vote,
+ us.is_subbed::bool as subscribed,
+ us.is_saved::bool as saved
+from comment_aggregates_fast cav
+cross join lateral (
+ select
+ u.id as user_id,
+ coalesce(cl.score, 0) as my_vote,
+ coalesce(cf.id, 0) as is_subbed,
+ coalesce(cs.id, 0) as is_saved
+ from user_ u
+ left join comment_like cl on u.id = cl.user_id and cav.id = cl.comment_id
+ left join comment_saved cs on u.id = cs.user_id and cs.comment_id = cav.id
+ left join community_follower cf on u.id = cf.user_id and cav.community_id = cf.community_id
+) as us
+
+union all
+
+select
+ cav.*,
+ null as user_id,
+ null as my_vote,
+ null as subscribed,
+ null as saved
+from comment_aggregates_fast cav;
+
+create view user_mention_view as
+select
+ c.id,
+ um.id as user_mention_id,
+ c.creator_id,
+ c.creator_actor_id,
+ c.creator_local,
+ c.post_id,
+ c.post_name,
+ c.parent_id,
+ c.content,
+ c.removed,
+ um.read,
+ c.published,
+ c.updated,
+ c.deleted,
+ c.community_id,
+ c.community_actor_id,
+ c.community_local,
+ c.community_name,
+ c.community_icon,
+ c.banned,
+ c.banned_from_community,
+ c.creator_name,
+ c.creator_preferred_username,
+ c.creator_avatar,
+ c.score,
+ c.upvotes,
+ c.downvotes,
+ c.hot_rank,
+ c.hot_rank_active,
+ c.user_id,
+ c.my_vote,
+ c.saved,
+ um.recipient_id,
+ (select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
+ (select local from user_ u where u.id = um.recipient_id) as recipient_local
+from user_mention um, comment_view c
+where um.comment_id = c.id;
+
+create view user_mention_fast_view as
+select
+ ac.id,
+ um.id as user_mention_id,
+ ac.creator_id,
+ ac.creator_actor_id,
+ ac.creator_local,
+ ac.post_id,
+ ac.post_name,
+ ac.parent_id,
+ ac.content,
+ ac.removed,
+ um.read,
+ ac.published,
+ ac.updated,
+ ac.deleted,
+ ac.community_id,
+ ac.community_actor_id,
+ ac.community_local,
+ ac.community_name,
+ ac.community_icon,
+ ac.banned,
+ ac.banned_from_community,
+ ac.creator_name,
+ ac.creator_preferred_username,
+ ac.creator_avatar,
+ ac.score,
+ ac.upvotes,
+ ac.downvotes,
+ ac.hot_rank,
+ ac.hot_rank_active,
+ u.id as user_id,
+ coalesce(cl.score, 0) as my_vote,
+ (select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved,
+ um.recipient_id,
+ (select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
+ (select local from user_ u where u.id = um.recipient_id) as recipient_local
+from user_ u
+cross join (
+ select
+ ca.*
+ from comment_aggregates_fast ca
+) ac
+left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
+left join user_mention um on um.comment_id = ac.id
+
+union all
+
+select
+ ac.id,
+ um.id as user_mention_id,
+ ac.creator_id,
+ ac.creator_actor_id,
+ ac.creator_local,
+ ac.post_id,
+ ac.post_name,
+ ac.parent_id,
+ ac.content,
+ ac.removed,
+ um.read,
+ ac.published,
+ ac.updated,
+ ac.deleted,
+ ac.community_id,
+ ac.community_actor_id,
+ ac.community_local,
+ ac.community_name,
+ ac.community_icon,
+ ac.banned,
+ ac.banned_from_community,
+ ac.creator_name,
+ ac.creator_preferred_username,
+ ac.creator_avatar,
+ ac.score,
+ ac.upvotes,
+ ac.downvotes,
+ ac.hot_rank,
+ ac.hot_rank_active,
+ null as user_id,
+ null as my_vote,
+ null as saved,
+ um.recipient_id,
+ (select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
+ (select local from user_ u where u.id = um.recipient_id) as recipient_local
+from comment_aggregates_fast ac
+left join user_mention um on um.comment_id = ac.id
+;
+
+-- Do the reply_view referencing the comment_fast_view
+create view reply_fast_view as
+with closereply as (
+ select
+ c2.id,
+ c2.creator_id as sender_id,
+ c.creator_id as recipient_id
+ from comment c
+ inner join comment c2 on c.id = c2.parent_id
+ where c2.creator_id != c.creator_id
+ -- Do union where post is null
+ union
+ select
+ c.id,
+ c.creator_id as sender_id,
+ p.creator_id as recipient_id
+ from comment c, post p
+ where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id
+)
+select cv.*,
+closereply.recipient_id
+from comment_fast_view cv, closereply
+where closereply.id = cv.id
+;
+
+-- Adding hot rank active to the triggers
+create or replace function refresh_post()
+returns trigger language plpgsql
+as $$
+begin
+ IF (TG_OP = 'DELETE') THEN
+ delete from post_aggregates_fast where id = OLD.id;
+
+ -- Update community number of posts
+ update community_aggregates_fast set number_of_posts = number_of_posts - 1 where id = OLD.community_id;
+ ELSIF (TG_OP = 'UPDATE') THEN
+ delete from post_aggregates_fast where id = OLD.id;
+ insert into post_aggregates_fast select * from post_aggregates_view where id = NEW.id;
+ ELSIF (TG_OP = 'INSERT') THEN
+ insert into post_aggregates_fast select * from post_aggregates_view where id = NEW.id;
+
+ -- Update that users number of posts, post score
+ delete from user_fast where id = NEW.creator_id;
+ insert into user_fast select * from user_view where id = NEW.creator_id;
+
+ -- Update community number of posts
+ update community_aggregates_fast set number_of_posts = number_of_posts + 1 where id = NEW.community_id;
+
+ -- Update the hot rank on the post table
+ -- TODO this might not correctly update it, using a 1 week interval
+ update post_aggregates_fast as paf
+ set
+ hot_rank = pav.hot_rank,
+ hot_rank_active = pav.hot_rank_active
+ from post_aggregates_view as pav
+ where paf.id = pav.id and (pav.published > ('now'::timestamp - '1 week'::interval));
+ END IF;
+
+ return null;
+end $$;
+
+create or replace function refresh_comment()
+returns trigger language plpgsql
+as $$
+begin
+ IF (TG_OP = 'DELETE') THEN
+ delete from comment_aggregates_fast where id = OLD.id;
+
+ -- Update community number of comments
+ update community_aggregates_fast as caf
+ set number_of_comments = number_of_comments - 1
+ from post as p
+ where caf.id = p.community_id and p.id = OLD.post_id;
+
+ ELSIF (TG_OP = 'UPDATE') THEN
+ delete from comment_aggregates_fast where id = OLD.id;
+ insert into comment_aggregates_fast select * from comment_aggregates_view where id = NEW.id;
+ ELSIF (TG_OP = 'INSERT') THEN
+ insert into comment_aggregates_fast select * from comment_aggregates_view where id = NEW.id;
+
+ -- Update user view due to comment count
+ update user_fast
+ set number_of_comments = number_of_comments + 1
+ where id = NEW.creator_id;
+
+ -- Update post view due to comment count, new comment activity time, but only on new posts
+ -- TODO this could be done more efficiently
+ delete from post_aggregates_fast where id = NEW.post_id;
+ insert into post_aggregates_fast select * from post_aggregates_view where id = NEW.post_id;
+
+ -- Update the comment hot_ranks as of last week
+ update comment_aggregates_fast as caf
+ set
+ hot_rank = cav.hot_rank,
+ hot_rank_active = cav.hot_rank_active
+ from comment_aggregates_view as cav
+ where caf.id = cav.id and (cav.published > ('now'::timestamp - '1 week'::interval));
+
+ -- Update the post ranks
+ update post_aggregates_fast as paf
+ set
+ hot_rank = pav.hot_rank,
+ hot_rank_active = pav.hot_rank_active
+ from post_aggregates_view as pav
+ where paf.id = pav.id and (pav.published > ('now'::timestamp - '1 week'::interval));
+
+ -- Force the hot rank active as zero on 2 day-older posts (necro-bump)
+ update post_aggregates_fast as paf
+ set hot_rank_active = 0
+ where paf.id = NEW.post_id and (paf.published < ('now'::timestamp - '2 days'::interval));
+
+ -- Update community number of comments
+ update community_aggregates_fast as caf
+ set number_of_comments = number_of_comments + 1
+ from post as p
+ where caf.id = p.community_id and p.id = NEW.post_id;
+
+ END IF;
+
+ return null;
+end $$;
--- /dev/null
+-- Drop first
+drop view community_view;
+drop view community_aggregates_view;
+drop view community_fast_view;
+drop table community_aggregates_fast;
+
+create view community_aggregates_view as
+select
+ c.id,
+ c.name,
+ c.title,
+ c.icon,
+ c.banner,
+ c.description,
+ c.category_id,
+ c.creator_id,
+ c.removed,
+ c.published,
+ c.updated,
+ c.deleted,
+ c.nsfw,
+ c.actor_id,
+ c.local,
+ c.last_refreshed_at,
+ u.actor_id as creator_actor_id,
+ u.local as creator_local,
+ u.name as creator_name,
+ u.preferred_username as creator_preferred_username,
+ u.avatar as creator_avatar,
+ cat.name as category_name,
+ coalesce(cf.subs, 0) as number_of_subscribers,
+ coalesce(cd.posts, 0) as number_of_posts,
+ coalesce(cd.comments, 0) as number_of_comments,
+ hot_rank(cf.subs, c.published) as hot_rank
+from community c
+left join user_ u on c.creator_id = u.id
+left join category cat on c.category_id = cat.id
+left join (
+ select
+ p.community_id,
+ count(distinct p.id) as posts,
+ count(distinct ct.id) as comments
+ from post p
+ join comment ct on p.id = ct.post_id
+ group by p.community_id
+) cd on cd.community_id = c.id
+left join (
+ select
+ community_id,
+ count(*) as subs
+ from community_follower
+ group by community_id
+) cf on cf.community_id = c.id;
+
+create view community_view as
+select
+ cv.*,
+ us.user as user_id,
+ us.is_subbed::bool as subscribed
+from community_aggregates_view cv
+cross join lateral (
+ select
+ u.id as user,
+ coalesce(cf.community_id, 0) as is_subbed
+ from user_ u
+ left join community_follower cf on u.id = cf.user_id and cf.community_id = cv.id
+) as us
+
+union all
+
+select
+ cv.*,
+ null as user_id,
+ null as subscribed
+from community_aggregates_view cv;
+
+-- The community fast table
+
+create table community_aggregates_fast as select * from community_aggregates_view;
+alter table community_aggregates_fast add primary key (id);
+
+create view community_fast_view as
+select
+ac.*,
+u.id as user_id,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed
+from user_ u
+cross join (
+ select
+ ca.*
+ from community_aggregates_fast ca
+) ac
+
+union all
+
+select
+caf.*,
+null as user_id,
+null as subscribed
+from community_aggregates_fast caf;
\ No newline at end of file
--- /dev/null
+-- Drop first
+drop view community_view;
+drop view community_aggregates_view;
+drop view community_fast_view;
+drop table community_aggregates_fast;
+
+create view community_aggregates_view as
+select
+ c.id,
+ c.name,
+ c.title,
+ c.icon,
+ c.banner,
+ c.description,
+ c.category_id,
+ c.creator_id,
+ c.removed,
+ c.published,
+ c.updated,
+ c.deleted,
+ c.nsfw,
+ c.actor_id,
+ c.local,
+ c.last_refreshed_at,
+ u.actor_id as creator_actor_id,
+ u.local as creator_local,
+ u.name as creator_name,
+ u.preferred_username as creator_preferred_username,
+ u.avatar as creator_avatar,
+ cat.name as category_name,
+ coalesce(cf.subs, 0) as number_of_subscribers,
+ coalesce(cd.posts, 0) as number_of_posts,
+ coalesce(cd.comments, 0) as number_of_comments,
+ hot_rank(cf.subs, c.published) as hot_rank
+from community c
+left join user_ u on c.creator_id = u.id
+left join category cat on c.category_id = cat.id
+left join (
+ select
+ p.community_id,
+ count(distinct p.id) as posts,
+ count(distinct ct.id) as comments
+ from post p
+ left join comment ct on p.id = ct.post_id
+ group by p.community_id
+) cd on cd.community_id = c.id
+left join (
+ select
+ community_id,
+ count(*) as subs
+ from community_follower
+ group by community_id
+) cf on cf.community_id = c.id;
+
+create view community_view as
+select
+ cv.*,
+ us.user as user_id,
+ us.is_subbed::bool as subscribed
+from community_aggregates_view cv
+cross join lateral (
+ select
+ u.id as user,
+ coalesce(cf.community_id, 0) as is_subbed
+ from user_ u
+ left join community_follower cf on u.id = cf.user_id and cf.community_id = cv.id
+) as us
+
+union all
+
+select
+ cv.*,
+ null as user_id,
+ null as subscribed
+from community_aggregates_view cv;
+
+-- The community fast table
+
+create table community_aggregates_fast as select * from community_aggregates_view;
+alter table community_aggregates_fast add primary key (id);
+
+create view community_fast_view as
+select
+ac.*,
+u.id as user_id,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed
+from user_ u
+cross join (
+ select
+ ca.*
+ from community_aggregates_fast ca
+) ac
+
+union all
+
+select
+caf.*,
+null as user_id,
+null as subscribed
+from community_aggregates_fast caf;
\ No newline at end of file
-use diesel::{result::Error, PgConnection};
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation};
-use lemmy_db::{user::User_, Crud};
-use lemmy_utils::{is_email_regex, settings::Settings};
+use lemmy_db::user::User_;
+use lemmy_utils::settings::Settings;
use serde::{Deserialize, Serialize};
type Jwt = String;
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub id: i32,
- pub username: String,
pub iss: String,
- pub show_nsfw: bool,
- pub theme: String,
- pub default_sort_type: i16,
- pub default_listing_type: i16,
- pub lang: String,
- pub avatar: Option<String>,
- pub show_avatars: bool,
}
impl Claims {
)
}
- pub fn jwt(user: User_, hostname: String) -> Jwt {
+ pub fn jwt(user: User_, hostname: String) -> Result<Jwt, jsonwebtoken::errors::Error> {
let my_claims = Claims {
id: user.id,
- username: user.name.to_owned(),
iss: hostname,
- show_nsfw: user.show_nsfw,
- theme: user.theme.to_owned(),
- default_sort_type: user.default_sort_type,
- default_listing_type: user.default_listing_type,
- lang: user.lang.to_owned(),
- avatar: user.avatar.to_owned(),
- show_avatars: user.show_avatars.to_owned(),
};
encode(
&Header::default(),
&my_claims,
&EncodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
)
- .unwrap()
- }
-
- // TODO: move these into user?
- pub fn find_by_email_or_username(
- conn: &PgConnection,
- username_or_email: &str,
- ) -> Result<User_, Error> {
- if is_email_regex(username_or_email) {
- User_::find_by_email(conn, username_or_email)
- } else {
- User_::find_by_username(conn, username_or_email)
- }
- }
-
- pub fn find_by_jwt(conn: &PgConnection, jwt: &str) -> Result<User_, Error> {
- let claims: Claims = Claims::decode(&jwt).expect("Invalid token").claims;
- User_::read(&conn, claims.id)
}
}
use crate::{
- api::{claims::Claims, APIError, Oper, Perform},
+ api::{
+ check_community_ban,
+ get_post,
+ get_user_from_jwt,
+ get_user_from_jwt_opt,
+ is_mod_or_admin,
+ APIError,
+ Perform,
+ },
apub::{ApubLikeableType, ApubObjectType},
blocking,
websocket::{
server::{JoinCommunityRoom, SendComment},
UserOperation,
- WebsocketInfo,
},
+ ConnectionId,
DbPool,
+ LemmyContext,
LemmyError,
};
+use actix_web::web::Data;
use lemmy_db::{
comment::*,
comment_view::*,
- community_view::*,
moderator::*,
- naive_now,
post::*,
site_view::*,
user::*,
user_mention::*,
- user_view::*,
Crud,
Likeable,
ListingType,
pub struct CreateComment {
content: String,
parent_id: Option<i32>,
- edit_id: Option<i32>, // TODO this isn't used
pub post_id: i32,
+ form_id: Option<String>,
auth: String,
}
#[derive(Serialize, Deserialize)]
pub struct EditComment {
content: String,
- parent_id: Option<i32>, // TODO why are the parent_id, creator_id, post_id, etc fields required? They aren't going to change
edit_id: i32,
- creator_id: i32,
- pub post_id: i32,
- removed: Option<bool>,
- deleted: Option<bool>,
+ form_id: Option<String>,
+ auth: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct DeleteComment {
+ edit_id: i32,
+ deleted: bool,
+ auth: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct RemoveComment {
+ edit_id: i32,
+ removed: bool,
reason: Option<String>,
- read: Option<bool>,
+ auth: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct MarkCommentAsRead {
+ edit_id: i32,
+ read: bool,
auth: String,
}
pub struct CommentResponse {
pub comment: CommentView,
pub recipient_ids: Vec<i32>,
+ pub form_id: Option<String>,
}
#[derive(Serialize, Deserialize)]
pub struct CreateCommentLike {
comment_id: i32,
- pub post_id: i32,
score: i16,
auth: String,
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<CreateComment> {
+impl Perform for CreateComment {
type Response = CommentResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<CommentResponse, LemmyError> {
- let data: &CreateComment = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let data: &CreateComment = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let content_slurs_removed = remove_slurs(&data.content.to_owned());
content: content_slurs_removed,
parent_id: data.parent_id.to_owned(),
post_id: data.post_id,
- creator_id: user_id,
+ creator_id: user.id,
removed: None,
deleted: None,
read: None,
// Check for a community ban
let post_id = data.post_id;
- let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
+ let post = get_post(post_id, context.pool()).await?;
- let community_id = post.community_id;
- let is_banned =
- move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
- if blocking(pool, is_banned).await? {
- return Err(APIError::err("community_ban").into());
- }
+ check_community_ban(user.id, post.community_id, context.pool()).await?;
- // Check for a site ban
- let user = blocking(pool, move |conn| User_::read(&conn, user_id)).await??;
- if user.banned {
- return Err(APIError::err("site_ban").into());
+ // Check if post is locked, no new comments
+ if post.locked {
+ return Err(APIError::err("locked").into());
}
+ // Create the comment
let comment_form2 = comment_form.clone();
- let inserted_comment =
- match blocking(pool, move |conn| Comment::create(&conn, &comment_form2)).await? {
- Ok(comment) => comment,
- Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
- };
+ let inserted_comment = match blocking(context.pool(), move |conn| {
+ Comment::create(&conn, &comment_form2)
+ })
+ .await?
+ {
+ Ok(comment) => comment,
+ Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
+ };
+ // Necessary to update the ap_id
let inserted_comment_id = inserted_comment.id;
- let updated_comment: Comment = match blocking(pool, move |conn| {
+ let updated_comment: Comment = match blocking(context.pool(), move |conn| {
let apub_id =
make_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string()).to_string();
Comment::update_ap_id(&conn, inserted_comment_id, apub_id)
Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
};
- updated_comment
- .send_create(&user, &self.client, pool)
- .await?;
+ updated_comment.send_create(&user, context).await?;
// Scan the comment for user mentions, add those rows
let mentions = scrape_text_for_mentions(&comment_form.content);
- let recipient_ids =
- send_local_notifs(mentions, updated_comment.clone(), user.clone(), post, pool).await?;
+ let recipient_ids = send_local_notifs(
+ mentions,
+ updated_comment.clone(),
+ &user,
+ post,
+ context.pool(),
+ true,
+ )
+ .await?;
// You like your own comment by default
let like_form = CommentLikeForm {
comment_id: inserted_comment.id,
post_id: data.post_id,
- user_id,
+ user_id: user.id,
score: 1,
};
let like = move |conn: &'_ _| CommentLike::like(&conn, &like_form);
- if blocking(pool, like).await?.is_err() {
+ if blocking(context.pool(), like).await?.is_err() {
return Err(APIError::err("couldnt_like_comment").into());
}
- updated_comment.send_like(&user, &self.client, pool).await?;
+ updated_comment.send_like(&user, context).await?;
- let comment_view = blocking(pool, move |conn| {
+ let user_id = user.id;
+ let comment_view = blocking(context.pool(), move |conn| {
CommentView::read(&conn, inserted_comment.id, Some(user_id))
})
.await??;
let mut res = CommentResponse {
comment: comment_view,
recipient_ids,
+ form_id: data.form_id.to_owned(),
};
- if let Some(ws) = websocket_info {
- ws.chatserver.do_send(SendComment {
- op: UserOperation::CreateComment,
- comment: res.clone(),
- my_id: ws.id,
- });
+ context.chat_server().do_send(SendComment {
+ op: UserOperation::CreateComment,
+ comment: res.clone(),
+ websocket_id,
+ });
- // strip out the recipient_ids, so that
- // users don't get double notifs
- res.recipient_ids = Vec::new();
- }
+ // strip out the recipient_ids, so that
+ // users don't get double notifs
+ res.recipient_ids = Vec::new();
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<EditComment> {
+impl Perform for EditComment {
type Response = CommentResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<CommentResponse, LemmyError> {
- let data: &EditComment = &self.data;
+ let data: &EditComment = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
+ let edit_id = data.edit_id;
+ let orig_comment = blocking(context.pool(), move |conn| {
+ CommentView::read(&conn, edit_id, None)
+ })
+ .await??;
+
+ check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
+
+ // Verify that only the creator can edit
+ if user.id != orig_comment.creator_id {
+ return Err(APIError::err("no_comment_edit_allowed").into());
+ }
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
+ // Do the update
+ let content_slurs_removed = remove_slurs(&data.content.to_owned());
+ let edit_id = data.edit_id;
+ let updated_comment = match blocking(context.pool(), move |conn| {
+ Comment::update_content(conn, edit_id, &content_slurs_removed)
+ })
+ .await?
+ {
+ Ok(comment) => comment,
+ Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
};
- let user_id = claims.id;
+ // Send the apub update
+ updated_comment.send_update(&user, context).await?;
+
+ // Do the mentions / recipients
+ let post_id = orig_comment.post_id;
+ let post = get_post(post_id, context.pool()).await?;
+
+ let updated_comment_content = updated_comment.content.to_owned();
+ let mentions = scrape_text_for_mentions(&updated_comment_content);
+ let recipient_ids = send_local_notifs(
+ mentions,
+ updated_comment,
+ &user,
+ post,
+ context.pool(),
+ false,
+ )
+ .await?;
- let user = blocking(pool, move |conn| User_::read(&conn, user_id)).await??;
+ let edit_id = data.edit_id;
+ let user_id = user.id;
+ let comment_view = blocking(context.pool(), move |conn| {
+ CommentView::read(conn, edit_id, Some(user_id))
+ })
+ .await??;
+
+ let mut res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ form_id: data.form_id.to_owned(),
+ };
+
+ context.chat_server().do_send(SendComment {
+ op: UserOperation::EditComment,
+ comment: res.clone(),
+ websocket_id,
+ });
+
+ // strip out the recipient_ids, so that
+ // users don't get double notifs
+ res.recipient_ids = Vec::new();
+
+ Ok(res)
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for DeleteComment {
+ type Response = CommentResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<CommentResponse, LemmyError> {
+ let data: &DeleteComment = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let edit_id = data.edit_id;
- let orig_comment =
- blocking(pool, move |conn| CommentView::read(&conn, edit_id, None)).await??;
-
- let mut editors: Vec<i32> = vec![orig_comment.creator_id];
- let mut moderators: Vec<i32> = vec![];
-
- let community_id = orig_comment.community_id;
- moderators.append(
- &mut blocking(pool, move |conn| {
- CommunityModeratorView::for_community(&conn, community_id)
- .map(|v| v.into_iter().map(|m| m.user_id).collect())
- })
- .await??,
- );
- moderators.append(
- &mut blocking(pool, move |conn| {
- UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect())
- })
- .await??,
- );
-
- editors.extend(&moderators);
- // You are allowed to mark the comment as read even if you're banned.
- if data.read.is_none() {
- // Verify its the creator or a mod, or an admin
-
- if !editors.contains(&user_id) {
- return Err(APIError::err("no_comment_edit_allowed").into());
- }
+ let orig_comment = blocking(context.pool(), move |conn| {
+ CommentView::read(&conn, edit_id, None)
+ })
+ .await??;
- // Check for a community ban
- let community_id = orig_comment.community_id;
- let is_banned =
- move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
- if blocking(pool, is_banned).await? {
- return Err(APIError::err("community_ban").into());
- }
+ check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
- // Check for a site ban
- if user.banned {
- return Err(APIError::err("site_ban").into());
- }
- } else {
- // check that user can mark as read
- let parent_id = orig_comment.parent_id;
- match parent_id {
- Some(pid) => {
- let parent_comment =
- blocking(pool, move |conn| CommentView::read(&conn, pid, None)).await??;
- if user_id != parent_comment.creator_id {
- return Err(APIError::err("no_comment_edit_allowed").into());
- }
- }
- None => {
- let parent_post_id = orig_comment.post_id;
- let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??;
- if user_id != parent_post.creator_id {
- return Err(APIError::err("no_comment_edit_allowed").into());
- }
- }
- }
+ // Verify that only the creator can delete
+ if user.id != orig_comment.creator_id {
+ return Err(APIError::err("no_comment_edit_allowed").into());
}
- let content_slurs_removed = remove_slurs(&data.content.to_owned());
+ // Do the delete
+ let deleted = data.deleted;
+ let updated_comment = match blocking(context.pool(), move |conn| {
+ Comment::update_deleted(conn, edit_id, deleted)
+ })
+ .await?
+ {
+ Ok(comment) => comment,
+ Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
+ };
+
+ // Send the apub message
+ if deleted {
+ updated_comment.send_delete(&user, context).await?;
+ } else {
+ updated_comment.send_undo_delete(&user, context).await?;
+ }
+ // Refetch it
let edit_id = data.edit_id;
- let read_comment = blocking(pool, move |conn| Comment::read(conn, edit_id)).await??;
-
- let comment_form = {
- if data.read.is_none() {
- // the ban etc checks should been made and have passed
- // the comment can be properly edited
- let post_removed = if moderators.contains(&user_id) {
- data.removed
- } else {
- Some(read_comment.removed)
- };
-
- CommentForm {
- content: content_slurs_removed,
- parent_id: read_comment.parent_id,
- post_id: read_comment.post_id,
- creator_id: read_comment.creator_id,
- removed: post_removed.to_owned(),
- deleted: data.deleted.to_owned(),
- read: Some(read_comment.read),
- published: None,
- updated: Some(naive_now()),
- ap_id: read_comment.ap_id,
- local: read_comment.local,
- }
- } else {
- // the only field that can be updated it the read field
- CommentForm {
- content: read_comment.content,
- parent_id: read_comment.parent_id,
- post_id: read_comment.post_id,
- creator_id: read_comment.creator_id,
- removed: Some(read_comment.removed).to_owned(),
- deleted: Some(read_comment.deleted).to_owned(),
- read: data.read.to_owned(),
- published: None,
- updated: orig_comment.updated,
- ap_id: read_comment.ap_id,
- local: read_comment.local,
- }
- }
+ let user_id = user.id;
+ let comment_view = blocking(context.pool(), move |conn| {
+ CommentView::read(conn, edit_id, Some(user_id))
+ })
+ .await??;
+
+ // Build the recipients
+ let post_id = comment_view.post_id;
+ let post = get_post(post_id, context.pool()).await?;
+ let mentions = vec![];
+ let recipient_ids = send_local_notifs(
+ mentions,
+ updated_comment,
+ &user,
+ post,
+ context.pool(),
+ false,
+ )
+ .await?;
+
+ let mut res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ form_id: None,
};
+ context.chat_server().do_send(SendComment {
+ op: UserOperation::DeleteComment,
+ comment: res.clone(),
+ websocket_id,
+ });
+
+ // strip out the recipient_ids, so that
+ // users don't get double notifs
+ res.recipient_ids = Vec::new();
+
+ Ok(res)
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for RemoveComment {
+ type Response = CommentResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<CommentResponse, LemmyError> {
+ let data: &RemoveComment = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
let edit_id = data.edit_id;
- let comment_form2 = comment_form.clone();
- let updated_comment = match blocking(pool, move |conn| {
- Comment::update(conn, edit_id, &comment_form2)
+ let orig_comment = blocking(context.pool(), move |conn| {
+ CommentView::read(&conn, edit_id, None)
+ })
+ .await??;
+
+ check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
+
+ // Verify that only a mod or admin can remove
+ is_mod_or_admin(context.pool(), user.id, orig_comment.community_id).await?;
+
+ // Do the remove
+ let removed = data.removed;
+ let updated_comment = match blocking(context.pool(), move |conn| {
+ Comment::update_removed(conn, edit_id, removed)
})
.await?
{
Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
};
- if data.read.is_none() {
- if let Some(deleted) = data.deleted.to_owned() {
- if deleted {
- updated_comment
- .send_delete(&user, &self.client, pool)
- .await?;
- } else {
- updated_comment
- .send_undo_delete(&user, &self.client, pool)
- .await?;
- }
- } else if let Some(removed) = data.removed.to_owned() {
- if moderators.contains(&user_id) {
- if removed {
- updated_comment
- .send_remove(&user, &self.client, pool)
- .await?;
- } else {
- updated_comment
- .send_undo_remove(&user, &self.client, pool)
- .await?;
- }
- }
- } else {
- updated_comment
- .send_update(&user, &self.client, pool)
- .await?;
- }
+ // Mod tables
+ let form = ModRemoveCommentForm {
+ mod_user_id: user.id,
+ comment_id: data.edit_id,
+ removed: Some(removed),
+ reason: data.reason.to_owned(),
+ };
+ blocking(context.pool(), move |conn| {
+ ModRemoveComment::create(conn, &form)
+ })
+ .await??;
- // Mod tables
- if moderators.contains(&user_id) {
- if let Some(removed) = data.removed.to_owned() {
- let form = ModRemoveCommentForm {
- mod_user_id: user_id,
- comment_id: data.edit_id,
- removed: Some(removed),
- reason: data.reason.to_owned(),
- };
- blocking(pool, move |conn| ModRemoveComment::create(conn, &form)).await??;
- }
- }
+ // Send the apub message
+ if removed {
+ updated_comment.send_remove(&user, context).await?;
+ } else {
+ updated_comment.send_undo_remove(&user, context).await?;
}
- let post_id = data.post_id;
- let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
-
- let mentions = scrape_text_for_mentions(&comment_form.content);
- let recipient_ids = send_local_notifs(mentions, updated_comment, user, post, pool).await?;
-
+ // Refetch it
let edit_id = data.edit_id;
- let comment_view = blocking(pool, move |conn| {
+ let user_id = user.id;
+ let comment_view = blocking(context.pool(), move |conn| {
CommentView::read(conn, edit_id, Some(user_id))
})
.await??;
+ // Build the recipients
+ let post_id = comment_view.post_id;
+ let post = get_post(post_id, context.pool()).await?;
+ let mentions = vec![];
+ let recipient_ids = send_local_notifs(
+ mentions,
+ updated_comment,
+ &user,
+ post,
+ context.pool(),
+ false,
+ )
+ .await?;
+
let mut res = CommentResponse {
comment: comment_view,
recipient_ids,
+ form_id: None,
};
- if let Some(ws) = websocket_info {
- ws.chatserver.do_send(SendComment {
- op: UserOperation::EditComment,
- comment: res.clone(),
- my_id: ws.id,
- });
+ context.chat_server().do_send(SendComment {
+ op: UserOperation::RemoveComment,
+ comment: res.clone(),
+ websocket_id,
+ });
- // strip out the recipient_ids, so that
- // users don't get double notifs
- res.recipient_ids = Vec::new();
- }
+ // strip out the recipient_ids, so that
+ // users don't get double notifs
+ res.recipient_ids = Vec::new();
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<SaveComment> {
+impl Perform for MarkCommentAsRead {
type Response = CommentResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<CommentResponse, LemmyError> {
- let data: &SaveComment = &self.data;
+ let data: &MarkCommentAsRead = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
+ let edit_id = data.edit_id;
+ let orig_comment = blocking(context.pool(), move |conn| {
+ CommentView::read(&conn, edit_id, None)
+ })
+ .await??;
+
+ check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
+
+ // Verify that only the recipient can mark as read
+ // Needs to fetch the parent comment / post to get the recipient
+ let parent_id = orig_comment.parent_id;
+ match parent_id {
+ Some(pid) => {
+ let parent_comment = blocking(context.pool(), move |conn| {
+ CommentView::read(&conn, pid, None)
+ })
+ .await??;
+ if user.id != parent_comment.creator_id {
+ return Err(APIError::err("no_comment_edit_allowed").into());
+ }
+ }
+ None => {
+ let parent_post_id = orig_comment.post_id;
+ let parent_post =
+ blocking(context.pool(), move |conn| Post::read(conn, parent_post_id)).await??;
+ if user.id != parent_post.creator_id {
+ return Err(APIError::err("no_comment_edit_allowed").into());
+ }
+ }
+ }
+
+ // Do the mark as read
+ let read = data.read;
+ match blocking(context.pool(), move |conn| {
+ Comment::update_read(conn, edit_id, read)
+ })
+ .await?
+ {
+ Ok(comment) => comment,
+ Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
+ };
+
+ // Refetch it
+ let edit_id = data.edit_id;
+ let user_id = user.id;
+ let comment_view = blocking(context.pool(), move |conn| {
+ CommentView::read(conn, edit_id, Some(user_id))
+ })
+ .await??;
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids: Vec::new(),
+ form_id: None,
};
- let user_id = claims.id;
+ Ok(res)
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for SaveComment {
+ type Response = CommentResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<CommentResponse, LemmyError> {
+ let data: &SaveComment = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let comment_saved_form = CommentSavedForm {
comment_id: data.comment_id,
- user_id,
+ user_id: user.id,
};
if data.save {
let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form);
- if blocking(pool, save_comment).await?.is_err() {
+ if blocking(context.pool(), save_comment).await?.is_err() {
return Err(APIError::err("couldnt_save_comment").into());
}
} else {
let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form);
- if blocking(pool, unsave_comment).await?.is_err() {
+ if blocking(context.pool(), unsave_comment).await?.is_err() {
return Err(APIError::err("couldnt_save_comment").into());
}
}
let comment_id = data.comment_id;
- let comment_view = blocking(pool, move |conn| {
+ let user_id = user.id;
+ let comment_view = blocking(context.pool(), move |conn| {
CommentView::read(conn, comment_id, Some(user_id))
})
.await??;
Ok(CommentResponse {
comment: comment_view,
recipient_ids: Vec::new(),
+ form_id: None,
})
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<CreateCommentLike> {
+impl Perform for CreateCommentLike {
type Response = CommentResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<CommentResponse, LemmyError> {
- let data: &CreateCommentLike = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let data: &CreateCommentLike = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let mut recipient_ids = Vec::new();
// Don't do a downvote if site has downvotes disabled
if data.score == -1 {
- let site = blocking(pool, move |conn| SiteView::read(conn)).await??;
+ let site = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
if !site.enable_downvotes {
return Err(APIError::err("downvotes_disabled").into());
}
}
- // Check for a community ban
- let post_id = data.post_id;
- let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
- let community_id = post.community_id;
- let is_banned =
- move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
- if blocking(pool, is_banned).await? {
- return Err(APIError::err("community_ban").into());
- }
+ let comment_id = data.comment_id;
+ let orig_comment = blocking(context.pool(), move |conn| {
+ CommentView::read(&conn, comment_id, None)
+ })
+ .await??;
- // Check for a site ban
- let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
- if user.banned {
- return Err(APIError::err("site_ban").into());
- }
+ let post_id = orig_comment.post_id;
+ let post = get_post(post_id, context.pool()).await?;
+ check_community_ban(user.id, post.community_id, context.pool()).await?;
let comment_id = data.comment_id;
- let comment = blocking(pool, move |conn| Comment::read(conn, comment_id)).await??;
+ let comment = blocking(context.pool(), move |conn| Comment::read(conn, comment_id)).await??;
// Add to recipient ids
match comment.parent_id {
Some(parent_id) => {
- let parent_comment = blocking(pool, move |conn| Comment::read(conn, parent_id)).await??;
- if parent_comment.creator_id != user_id {
- let parent_user = blocking(pool, move |conn| {
+ let parent_comment =
+ blocking(context.pool(), move |conn| Comment::read(conn, parent_id)).await??;
+ if parent_comment.creator_id != user.id {
+ let parent_user = blocking(context.pool(), move |conn| {
User_::read(conn, parent_comment.creator_id)
})
.await??;
let like_form = CommentLikeForm {
comment_id: data.comment_id,
- post_id: data.post_id,
- user_id,
+ post_id,
+ user_id: user.id,
score: data.score,
};
// Remove any likes first
- let like_form2 = like_form.clone();
- blocking(pool, move |conn| CommentLike::remove(conn, &like_form2)).await??;
+ let user_id = user.id;
+ blocking(context.pool(), move |conn| {
+ CommentLike::remove(conn, user_id, comment_id)
+ })
+ .await??;
// Only add the like if the score isnt 0
let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
if do_add {
let like_form2 = like_form.clone();
let like = move |conn: &'_ _| CommentLike::like(conn, &like_form2);
- if blocking(pool, like).await?.is_err() {
+ if blocking(context.pool(), like).await?.is_err() {
return Err(APIError::err("couldnt_like_comment").into());
}
if like_form.score == 1 {
- comment.send_like(&user, &self.client, pool).await?;
+ comment.send_like(&user, context).await?;
} else if like_form.score == -1 {
- comment.send_dislike(&user, &self.client, pool).await?;
+ comment.send_dislike(&user, context).await?;
}
} else {
- comment.send_undo_like(&user, &self.client, pool).await?;
+ comment.send_undo_like(&user, context).await?;
}
// Have to refetch the comment to get the current state
let comment_id = data.comment_id;
- let liked_comment = blocking(pool, move |conn| {
+ let user_id = user.id;
+ let liked_comment = blocking(context.pool(), move |conn| {
CommentView::read(conn, comment_id, Some(user_id))
})
.await??;
let mut res = CommentResponse {
comment: liked_comment,
recipient_ids,
+ form_id: None,
};
- if let Some(ws) = websocket_info {
- ws.chatserver.do_send(SendComment {
- op: UserOperation::CreateCommentLike,
- comment: res.clone(),
- my_id: ws.id,
- });
+ context.chat_server().do_send(SendComment {
+ op: UserOperation::CreateCommentLike,
+ comment: res.clone(),
+ websocket_id,
+ });
- // strip out the recipient_ids, so that
- // users don't get double notifs
- res.recipient_ids = Vec::new();
- }
+ // strip out the recipient_ids, so that
+ // users don't get double notifs
+ res.recipient_ids = Vec::new();
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<GetComments> {
+impl Perform for GetComments {
type Response = GetCommentsResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<GetCommentsResponse, LemmyError> {
- let data: &GetComments = &self.data;
-
- let user_claims: Option<Claims> = match &data.auth {
- Some(auth) => match Claims::decode(&auth) {
- Ok(claims) => Some(claims.claims),
- Err(_e) => None,
- },
- None => None,
- };
-
- let user_id = match &user_claims {
- Some(claims) => Some(claims.id),
- None => None,
- };
+ let data: &GetComments = &self;
+ let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
+ let user_id = user.map(|u| u.id);
let type_ = ListingType::from_str(&data.type_)?;
let sort = SortType::from_str(&data.sort)?;
let community_id = data.community_id;
let page = data.page;
let limit = data.limit;
- let comments = blocking(pool, move |conn| {
+ let comments = blocking(context.pool(), move |conn| {
CommentQueryBuilder::create(conn)
.listing_type(type_)
.sort(&sort)
Err(_) => return Err(APIError::err("couldnt_get_comments").into()),
};
- if let Some(ws) = websocket_info {
+ if let Some(id) = websocket_id {
// You don't need to join the specific community room, bc this is already handled by
// GetCommunity
if data.community_id.is_none() {
- if let Some(id) = ws.id {
- // 0 is the "all" community
- ws.chatserver.do_send(JoinCommunityRoom {
- community_id: 0,
- id,
- });
- }
+ // 0 is the "all" community
+ context.chat_server().do_send(JoinCommunityRoom {
+ community_id: 0,
+ id,
+ });
}
}
pub async fn send_local_notifs(
mentions: Vec<MentionData>,
comment: Comment,
- user: User_,
+ user: &User_,
post: Post,
pool: &DbPool,
+ do_send_email: bool,
) -> Result<Vec<i32>, LemmyError> {
+ let user2 = user.clone();
let ids = blocking(pool, move |conn| {
- do_send_local_notifs(conn, &mentions, &comment, &user, &post)
+ do_send_local_notifs(conn, &mentions, &comment, &user2, &post, do_send_email)
})
.await?;
comment: &Comment,
user: &User_,
post: &Post,
+ do_send_email: bool,
) -> Vec<i32> {
let mut recipient_ids = Vec::new();
let hostname = &format!("https://{}", Settings::get().hostname);
};
// Send an email to those users that have notifications on
- if mention_user.send_notifications_to_email {
+ if do_send_email && mention_user.send_notifications_to_email {
if let Some(mention_email) = mention_user.email {
let subject = &format!("{} - Mentioned by {}", Settings::get().hostname, user.name,);
let html = &format!(
if let Ok(parent_user) = User_::read(&conn, parent_comment.creator_id) {
recipient_ids.push(parent_user.id);
- if parent_user.send_notifications_to_email {
+ if do_send_email && parent_user.send_notifications_to_email {
if let Some(comment_reply_email) = parent_user.email {
let subject = &format!("{} - Reply from {}", Settings::get().hostname, user.name,);
let html = &format!(
if let Ok(parent_user) = User_::read(&conn, post.creator_id) {
recipient_ids.push(parent_user.id);
- if parent_user.send_notifications_to_email {
+ if do_send_email && parent_user.send_notifications_to_email {
if let Some(post_reply_email) = parent_user.email {
let subject = &format!("{} - Reply from {}", Settings::get().hostname, user.name,);
let html = &format!(
use super::*;
use crate::{
- api::{claims::Claims, APIError, Oper, Perform},
+ api::{is_admin, is_mod_or_admin, APIError, Perform},
apub::ActorType,
blocking,
websocket::{
- server::{JoinCommunityRoom, SendCommunityRoomMessage},
+ server::{GetCommunityUsersOnline, JoinCommunityRoom, SendCommunityRoomMessage},
UserOperation,
- WebsocketInfo,
},
- DbPool,
+ ConnectionId,
+};
+use anyhow::Context;
+use lemmy_db::{
+ comment::Comment,
+ comment_view::CommentQueryBuilder,
+ diesel_option_overwrite,
+ naive_now,
+ post::Post,
+ Bannable,
+ Crud,
+ Followable,
+ Joinable,
+ SortType,
};
-use lemmy_db::{naive_now, Bannable, Crud, Followable, Joinable, SortType};
use lemmy_utils::{
generate_actor_keypair,
is_valid_community_name,
+ location_info,
make_apub_endpoint,
naive_from_unix,
- slur_check,
- slurs_vec_to_str,
EndpointType,
};
use serde::{Deserialize, Serialize};
pub struct GetCommunityResponse {
pub community: CommunityView,
pub moderators: Vec<CommunityModeratorView>,
- pub admins: Vec<UserView>,
pub online: usize,
}
name: String,
title: String,
description: Option<String>,
+ icon: Option<String>,
+ banner: Option<String>,
category_id: i32,
nsfw: bool,
auth: String,
pub community_id: i32,
user_id: i32,
ban: bool,
+ remove_data: Option<bool>,
reason: Option<String>,
expires: Option<i64>,
auth: String,
#[derive(Serialize, Deserialize)]
pub struct EditCommunity {
pub edit_id: i32,
- name: String,
title: String,
description: Option<String>,
+ icon: Option<String>,
+ banner: Option<String>,
category_id: i32,
- removed: Option<bool>,
- deleted: Option<bool>,
nsfw: bool,
+ auth: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct DeleteCommunity {
+ pub edit_id: i32,
+ deleted: bool,
+ auth: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct RemoveCommunity {
+ pub edit_id: i32,
+ removed: bool,
reason: Option<String>,
expires: Option<i64>,
auth: String,
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<GetCommunity> {
+impl Perform for GetCommunity {
type Response = GetCommunityResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<GetCommunityResponse, LemmyError> {
- let data: &GetCommunity = &self.data;
-
- let user_id: Option<i32> = match &data.auth {
- Some(auth) => match Claims::decode(&auth) {
- Ok(claims) => {
- let user_id = claims.claims.id;
- Some(user_id)
- }
- Err(_e) => None,
- },
- None => None,
- };
+ let data: &GetCommunity = &self;
+ let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
+ let user_id = user.map(|u| u.id);
let name = data.name.to_owned().unwrap_or_else(|| "main".to_string());
let community = match data.id {
- Some(id) => blocking(pool, move |conn| Community::read(conn, id)).await??,
- None => match blocking(pool, move |conn| Community::read_from_name(conn, &name)).await? {
+ Some(id) => blocking(context.pool(), move |conn| Community::read(conn, id)).await??,
+ None => match blocking(context.pool(), move |conn| {
+ Community::read_from_name(conn, &name)
+ })
+ .await?
+ {
Ok(community) => community,
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
},
};
let community_id = community.id;
- let community_view = match blocking(pool, move |conn| {
+ let community_view = match blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, user_id)
})
.await?
};
let community_id = community.id;
- let moderators: Vec<CommunityModeratorView> = match blocking(pool, move |conn| {
+ let moderators: Vec<CommunityModeratorView> = match blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await?
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
};
- let site = blocking(pool, move |conn| Site::read(conn, 1)).await??;
- let site_creator_id = site.creator_id;
- let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
- let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
- let creator_user = admins.remove(creator_index);
- admins.insert(0, creator_user);
-
- let online = if let Some(ws) = websocket_info {
- if let Some(id) = ws.id {
- ws.chatserver.do_send(JoinCommunityRoom {
- community_id: community.id,
- id,
- });
- }
+ if let Some(id) = websocket_id {
+ context
+ .chat_server()
+ .do_send(JoinCommunityRoom { community_id, id });
+ }
- // TODO
- 1
- // let fut = async {
- // ws.chatserver.send(GetCommunityUsersOnline {community_id}).await.unwrap()
- // };
- // Runtime::new().unwrap().block_on(fut)
- } else {
- 0
- };
+ let online = context
+ .chat_server()
+ .send(GetCommunityUsersOnline { community_id })
+ .await
+ .unwrap_or(1);
let res = GetCommunityResponse {
community: community_view,
moderators,
- admins,
online,
};
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<CreateCommunity> {
+impl Perform for CreateCommunity {
type Response = CommunityResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> {
- let data: &CreateCommunity = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- if let Err(slurs) = slur_check(&data.name) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
+ let data: &CreateCommunity = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
- if let Err(slurs) = slur_check(&data.title) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
-
- if let Some(description) = &data.description {
- if let Err(slurs) = slur_check(description) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
- }
+ check_slurs(&data.name)?;
+ check_slurs(&data.title)?;
+ check_slurs_opt(&data.description)?;
if !is_valid_community_name(&data.name) {
return Err(APIError::err("invalid_community_name").into());
}
- let user_id = claims.id;
-
- // Check for a site ban
- let user_view = blocking(pool, move |conn| UserView::read(conn, user_id)).await??;
- if user_view.banned {
- return Err(APIError::err("site_ban").into());
+ // Double check for duplicate community actor_ids
+ let actor_id = make_apub_endpoint(EndpointType::Community, &data.name).to_string();
+ let actor_id_cloned = actor_id.to_owned();
+ let community_dupe = blocking(context.pool(), move |conn| {
+ Community::read_from_actor_id(conn, &actor_id_cloned)
+ })
+ .await?;
+ if community_dupe.is_ok() {
+ return Err(APIError::err("community_already_exists").into());
}
// When you create a community, make sure the user becomes a moderator and a follower
name: data.name.to_owned(),
title: data.title.to_owned(),
description: data.description.to_owned(),
+ icon: Some(data.icon.to_owned()),
+ banner: Some(data.banner.to_owned()),
category_id: data.category_id,
- creator_id: user_id,
+ creator_id: user.id,
removed: None,
deleted: None,
nsfw: data.nsfw,
updated: None,
- actor_id: make_apub_endpoint(EndpointType::Community, &data.name).to_string(),
+ actor_id,
local: true,
private_key: Some(keypair.private_key),
public_key: Some(keypair.public_key),
published: None,
};
- let inserted_community =
- match blocking(pool, move |conn| Community::create(conn, &community_form)).await? {
- Ok(community) => community,
- Err(_e) => return Err(APIError::err("community_already_exists").into()),
- };
+ let inserted_community = match blocking(context.pool(), move |conn| {
+ Community::create(conn, &community_form)
+ })
+ .await?
+ {
+ Ok(community) => community,
+ Err(_e) => return Err(APIError::err("community_already_exists").into()),
+ };
let community_moderator_form = CommunityModeratorForm {
community_id: inserted_community.id,
- user_id,
+ user_id: user.id,
};
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
- if blocking(pool, join).await?.is_err() {
+ if blocking(context.pool(), join).await?.is_err() {
return Err(APIError::err("community_moderator_already_exists").into());
}
let community_follower_form = CommunityFollowerForm {
community_id: inserted_community.id,
- user_id,
+ user_id: user.id,
};
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
- if blocking(pool, follow).await?.is_err() {
+ if blocking(context.pool(), follow).await?.is_err() {
return Err(APIError::err("community_follower_already_exists").into());
}
- let community_view = blocking(pool, move |conn| {
+ let user_id = user.id;
+ let community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, inserted_community.id, Some(user_id))
})
.await??;
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<EditCommunity> {
+impl Perform for EditCommunity {
type Response = CommunityResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> {
- let data: &EditCommunity = &self.data;
-
- if let Err(slurs) = slur_check(&data.name) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
-
- if let Err(slurs) = slur_check(&data.title) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
-
- if let Some(description) = &data.description {
- if let Err(slurs) = slur_check(description) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
- }
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- if !is_valid_community_name(&data.name) {
- return Err(APIError::err("invalid_community_name").into());
- }
-
- let user_id = claims.id;
+ let data: &EditCommunity = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
- // Check for a site ban
- let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
- if user.banned {
- return Err(APIError::err("site_ban").into());
- }
+ check_slurs(&data.title)?;
+ check_slurs_opt(&data.description)?;
- // Verify its a mod
+ // Verify its a mod (only mods can edit it)
let edit_id = data.edit_id;
- let mut editors: Vec<i32> = Vec::new();
- editors.append(
- &mut blocking(pool, move |conn| {
- CommunityModeratorView::for_community(conn, edit_id)
- .map(|v| v.into_iter().map(|m| m.user_id).collect())
- })
- .await??,
- );
- editors.append(
- &mut blocking(pool, move |conn| {
- UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect())
- })
- .await??,
- );
- if !editors.contains(&user_id) {
- return Err(APIError::err("no_community_edit_allowed").into());
+ let mods: Vec<i32> = blocking(context.pool(), move |conn| {
+ CommunityModeratorView::for_community(conn, edit_id)
+ .map(|v| v.into_iter().map(|m| m.user_id).collect())
+ })
+ .await??;
+ if !mods.contains(&user.id) {
+ return Err(APIError::err("not_a_moderator").into());
}
let edit_id = data.edit_id;
- let read_community = blocking(pool, move |conn| Community::read(conn, edit_id)).await??;
+ let read_community =
+ blocking(context.pool(), move |conn| Community::read(conn, edit_id)).await??;
+
+ let icon = diesel_option_overwrite(&data.icon);
+ let banner = diesel_option_overwrite(&data.banner);
let community_form = CommunityForm {
- name: data.name.to_owned(),
+ name: read_community.name,
title: data.title.to_owned(),
description: data.description.to_owned(),
+ icon,
+ banner,
category_id: data.category_id.to_owned(),
creator_id: read_community.creator_id,
- removed: data.removed.to_owned(),
- deleted: data.deleted.to_owned(),
+ removed: Some(read_community.removed),
+ deleted: Some(read_community.deleted),
nsfw: data.nsfw,
updated: Some(naive_now()),
actor_id: read_community.actor_id,
};
let edit_id = data.edit_id;
- let updated_community = match blocking(pool, move |conn| {
+ match blocking(context.pool(), move |conn| {
Community::update(conn, edit_id, &community_form)
})
.await?
Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
};
- // Mod tables
- if let Some(removed) = data.removed.to_owned() {
- let expires = match data.expires {
- Some(time) => Some(naive_from_unix(time)),
- None => None,
- };
- let form = ModRemoveCommunityForm {
- mod_user_id: user_id,
- community_id: data.edit_id,
- removed: Some(removed),
- reason: data.reason.to_owned(),
- expires,
- };
- blocking(pool, move |conn| ModRemoveCommunity::create(conn, &form)).await??;
+ // TODO there needs to be some kind of an apub update
+ // process for communities and users
+
+ let edit_id = data.edit_id;
+ let user_id = user.id;
+ let community_view = blocking(context.pool(), move |conn| {
+ CommunityView::read(conn, edit_id, Some(user_id))
+ })
+ .await??;
+
+ let res = CommunityResponse {
+ community: community_view,
+ };
+
+ send_community_websocket(&res, context, websocket_id, UserOperation::EditCommunity);
+
+ Ok(res)
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for DeleteCommunity {
+ type Response = CommunityResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<CommunityResponse, LemmyError> {
+ let data: &DeleteCommunity = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
+ // Verify its the creator (only a creator can delete the community)
+ let edit_id = data.edit_id;
+ let read_community =
+ blocking(context.pool(), move |conn| Community::read(conn, edit_id)).await??;
+ if read_community.creator_id != user.id {
+ return Err(APIError::err("no_community_edit_allowed").into());
}
- if let Some(deleted) = data.deleted.to_owned() {
- if deleted {
- updated_community
- .send_delete(&user, &self.client, pool)
- .await?;
- } else {
- updated_community
- .send_undo_delete(&user, &self.client, pool)
- .await?;
- }
- } else if let Some(removed) = data.removed.to_owned() {
- if removed {
- updated_community
- .send_remove(&user, &self.client, pool)
- .await?;
- } else {
- updated_community
- .send_undo_remove(&user, &self.client, pool)
- .await?;
- }
+ // Do the delete
+ let edit_id = data.edit_id;
+ let deleted = data.deleted;
+ let updated_community = match blocking(context.pool(), move |conn| {
+ Community::update_deleted(conn, edit_id, deleted)
+ })
+ .await?
+ {
+ Ok(community) => community,
+ Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
+ };
+
+ // Send apub messages
+ if deleted {
+ updated_community.send_delete(&user, context).await?;
+ } else {
+ updated_community.send_undo_delete(&user, context).await?;
}
let edit_id = data.edit_id;
- let community_view = blocking(pool, move |conn| {
+ let user_id = user.id;
+ let community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, edit_id, Some(user_id))
})
.await??;
community: community_view,
};
- if let Some(ws) = websocket_info {
- // Strip out the user id and subscribed when sending to others
- let mut res_sent = res.clone();
- res_sent.community.user_id = None;
- res_sent.community.subscribed = None;
-
- ws.chatserver.do_send(SendCommunityRoomMessage {
- op: UserOperation::EditCommunity,
- response: res_sent,
- community_id: data.edit_id,
- my_id: ws.id,
- });
- }
+ send_community_websocket(&res, context, websocket_id, UserOperation::DeleteCommunity);
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<ListCommunities> {
- type Response = ListCommunitiesResponse;
+impl Perform for RemoveCommunity {
+ type Response = CommunityResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
- ) -> Result<ListCommunitiesResponse, LemmyError> {
- let data: &ListCommunities = &self.data;
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<CommunityResponse, LemmyError> {
+ let data: &RemoveCommunity = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
- let user_claims: Option<Claims> = match &data.auth {
- Some(auth) => match Claims::decode(&auth) {
- Ok(claims) => Some(claims.claims),
- Err(_e) => None,
- },
+ // Verify its an admin (only an admin can remove a community)
+ is_admin(context.pool(), user.id).await?;
+
+ // Do the remove
+ let edit_id = data.edit_id;
+ let removed = data.removed;
+ let updated_community = match blocking(context.pool(), move |conn| {
+ Community::update_removed(conn, edit_id, removed)
+ })
+ .await?
+ {
+ Ok(community) => community,
+ Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
+ };
+
+ // Mod tables
+ let expires = match data.expires {
+ Some(time) => Some(naive_from_unix(time)),
None => None,
};
+ let form = ModRemoveCommunityForm {
+ mod_user_id: user.id,
+ community_id: data.edit_id,
+ removed: Some(removed),
+ reason: data.reason.to_owned(),
+ expires,
+ };
+ blocking(context.pool(), move |conn| {
+ ModRemoveCommunity::create(conn, &form)
+ })
+ .await??;
+
+ // Apub messages
+ if removed {
+ updated_community.send_remove(&user, context).await?;
+ } else {
+ updated_community.send_undo_remove(&user, context).await?;
+ }
+
+ let edit_id = data.edit_id;
+ let user_id = user.id;
+ let community_view = blocking(context.pool(), move |conn| {
+ CommunityView::read(conn, edit_id, Some(user_id))
+ })
+ .await??;
+
+ let res = CommunityResponse {
+ community: community_view,
+ };
- let user_id = match &user_claims {
- Some(claims) => Some(claims.id),
+ send_community_websocket(&res, context, websocket_id, UserOperation::RemoveCommunity);
+
+ Ok(res)
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for ListCommunities {
+ type Response = ListCommunitiesResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<ListCommunitiesResponse, LemmyError> {
+ let data: &ListCommunities = &self;
+ let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
+
+ let user_id = match &user {
+ Some(user) => Some(user.id),
None => None,
};
- let show_nsfw = match &user_claims {
- Some(claims) => claims.show_nsfw,
+ let show_nsfw = match &user {
+ Some(user) => user.show_nsfw,
None => false,
};
let page = data.page;
let limit = data.limit;
- let communities = blocking(pool, move |conn| {
+ let communities = blocking(context.pool(), move |conn| {
CommunityQueryBuilder::create(conn)
.sort(&sort)
.for_user(user_id)
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<FollowCommunity> {
+impl Perform for FollowCommunity {
type Response = CommunityResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> {
- let data: &FollowCommunity = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let data: &FollowCommunity = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_id = data.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
let community_follower_form = CommunityFollowerForm {
community_id: data.community_id,
- user_id,
+ user_id: user.id,
};
if community.local {
if data.follow {
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
- if blocking(pool, follow).await?.is_err() {
+ if blocking(context.pool(), follow).await?.is_err() {
return Err(APIError::err("community_follower_already_exists").into());
}
} else {
let unfollow =
move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
- if blocking(pool, unfollow).await?.is_err() {
+ if blocking(context.pool(), unfollow).await?.is_err() {
return Err(APIError::err("community_follower_already_exists").into());
}
}
+ } else if data.follow {
+ // Dont actually add to the community followers here, because you need
+ // to wait for the accept
+ user.send_follow(&community.actor_id()?, context).await?;
} else {
- let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
-
- if data.follow {
- // Dont actually add to the community followers here, because you need
- // to wait for the accept
- user
- .send_follow(&community.actor_id, &self.client, pool)
- .await?;
- } else {
- user
- .send_unfollow(&community.actor_id, &self.client, pool)
- .await?;
- let unfollow =
- move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
- if blocking(pool, unfollow).await?.is_err() {
- return Err(APIError::err("community_follower_already_exists").into());
- }
+ user.send_unfollow(&community.actor_id()?, context).await?;
+ let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
+ if blocking(context.pool(), unfollow).await?.is_err() {
+ return Err(APIError::err("community_follower_already_exists").into());
}
- // TODO: this needs to return a "pending" state, until Accept is received from the remote server
}
+ // TODO: this needs to return a "pending" state, until Accept is received from the remote server
let community_id = data.community_id;
- let community_view = blocking(pool, move |conn| {
+ let user_id = user.id;
+ let community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, Some(user_id))
})
.await??;
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<GetFollowedCommunities> {
+impl Perform for GetFollowedCommunities {
type Response = GetFollowedCommunitiesResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<GetFollowedCommunitiesResponse, LemmyError> {
- let data: &GetFollowedCommunities = &self.data;
+ let data: &GetFollowedCommunities = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
-
- let communities = match blocking(pool, move |conn| {
+ let user_id = user.id;
+ let communities = match blocking(context.pool(), move |conn| {
CommunityFollowerView::for_user(conn, user_id)
})
.await?
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<BanFromCommunity> {
+impl Perform for BanFromCommunity {
type Response = BanFromCommunityResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<BanFromCommunityResponse, LemmyError> {
- let data: &BanFromCommunity = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
-
- let mut community_moderators: Vec<i32> = vec![];
+ let data: &BanFromCommunity = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_id = data.community_id;
+ let banned_user_id = data.user_id;
- community_moderators.append(
- &mut blocking(pool, move |conn| {
- CommunityModeratorView::for_community(&conn, community_id)
- .map(|v| v.into_iter().map(|m| m.user_id).collect())
- })
- .await??,
- );
- community_moderators.append(
- &mut blocking(pool, move |conn| {
- UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect())
- })
- .await??,
- );
-
- if !community_moderators.contains(&user_id) {
- return Err(APIError::err("couldnt_update_community").into());
- }
+ // Verify that only mods or admins can ban
+ is_mod_or_admin(context.pool(), user.id, community_id).await?;
let community_user_ban_form = CommunityUserBanForm {
community_id: data.community_id,
if data.ban {
let ban = move |conn: &'_ _| CommunityUserBan::ban(conn, &community_user_ban_form);
- if blocking(pool, ban).await?.is_err() {
+ if blocking(context.pool(), ban).await?.is_err() {
return Err(APIError::err("community_user_already_banned").into());
}
} else {
let unban = move |conn: &'_ _| CommunityUserBan::unban(conn, &community_user_ban_form);
- if blocking(pool, unban).await?.is_err() {
+ if blocking(context.pool(), unban).await?.is_err() {
return Err(APIError::err("community_user_already_banned").into());
}
}
+ // Remove/Restore their data if that's desired
+ if let Some(remove_data) = data.remove_data {
+ // Posts
+ blocking(context.pool(), move |conn: &'_ _| {
+ Post::update_removed_for_creator(conn, banned_user_id, Some(community_id), remove_data)
+ })
+ .await??;
+
+ // Comments
+ // Diesel doesn't allow updates with joins, so this has to be a loop
+ let comments = blocking(context.pool(), move |conn| {
+ CommentQueryBuilder::create(conn)
+ .for_creator_id(banned_user_id)
+ .for_community_id(community_id)
+ .limit(std::i64::MAX)
+ .list()
+ })
+ .await??;
+
+ for comment in &comments {
+ let comment_id = comment.id;
+ blocking(context.pool(), move |conn: &'_ _| {
+ Comment::update_removed(conn, comment_id, remove_data)
+ })
+ .await??;
+ }
+ }
+
// Mod tables
+ // TODO eventually do correct expires
let expires = match data.expires {
Some(time) => Some(naive_from_unix(time)),
None => None,
};
let form = ModBanFromCommunityForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
other_user_id: data.user_id,
community_id: data.community_id,
reason: data.reason.to_owned(),
banned: Some(data.ban),
expires,
};
- blocking(pool, move |conn| ModBanFromCommunity::create(conn, &form)).await??;
+ blocking(context.pool(), move |conn| {
+ ModBanFromCommunity::create(conn, &form)
+ })
+ .await??;
let user_id = data.user_id;
- let user_view = blocking(pool, move |conn| UserView::read(conn, user_id)).await??;
+ let user_view = blocking(context.pool(), move |conn| {
+ UserView::get_user_secure(conn, user_id)
+ })
+ .await??;
let res = BanFromCommunityResponse {
user: user_view,
banned: data.ban,
};
- if let Some(ws) = websocket_info {
- ws.chatserver.do_send(SendCommunityRoomMessage {
- op: UserOperation::BanFromCommunity,
- response: res.clone(),
- community_id: data.community_id,
- my_id: ws.id,
- });
- }
+ context.chat_server().do_send(SendCommunityRoomMessage {
+ op: UserOperation::BanFromCommunity,
+ response: res.clone(),
+ community_id,
+ websocket_id,
+ });
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<AddModToCommunity> {
+impl Perform for AddModToCommunity {
type Response = AddModToCommunityResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<AddModToCommunityResponse, LemmyError> {
- let data: &AddModToCommunity = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let data: &AddModToCommunity = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_moderator_form = CommunityModeratorForm {
community_id: data.community_id,
user_id: data.user_id,
};
- let mut community_moderators: Vec<i32> = vec![];
-
let community_id = data.community_id;
- community_moderators.append(
- &mut blocking(pool, move |conn| {
- CommunityModeratorView::for_community(&conn, community_id)
- .map(|v| v.into_iter().map(|m| m.user_id).collect())
- })
- .await??,
- );
- community_moderators.append(
- &mut blocking(pool, move |conn| {
- UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect())
- })
- .await??,
- );
-
- if !community_moderators.contains(&user_id) {
- return Err(APIError::err("couldnt_update_community").into());
- }
+ // Verify that only mods or admins can add mod
+ is_mod_or_admin(context.pool(), user.id, community_id).await?;
if data.added {
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
- if blocking(pool, join).await?.is_err() {
+ if blocking(context.pool(), join).await?.is_err() {
return Err(APIError::err("community_moderator_already_exists").into());
}
} else {
let leave = move |conn: &'_ _| CommunityModerator::leave(conn, &community_moderator_form);
- if blocking(pool, leave).await?.is_err() {
+ if blocking(context.pool(), leave).await?.is_err() {
return Err(APIError::err("community_moderator_already_exists").into());
}
}
// Mod tables
let form = ModAddCommunityForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
other_user_id: data.user_id,
community_id: data.community_id,
removed: Some(!data.added),
};
- blocking(pool, move |conn| ModAddCommunity::create(conn, &form)).await??;
+ blocking(context.pool(), move |conn| {
+ ModAddCommunity::create(conn, &form)
+ })
+ .await??;
let community_id = data.community_id;
- let moderators = blocking(pool, move |conn| {
+ let moderators = blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await??;
let res = AddModToCommunityResponse { moderators };
- if let Some(ws) = websocket_info {
- ws.chatserver.do_send(SendCommunityRoomMessage {
- op: UserOperation::AddModToCommunity,
- response: res.clone(),
- community_id: data.community_id,
- my_id: ws.id,
- });
- }
+ context.chat_server().do_send(SendCommunityRoomMessage {
+ op: UserOperation::AddModToCommunity,
+ response: res.clone(),
+ community_id,
+ websocket_id,
+ });
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<TransferCommunity> {
+impl Perform for TransferCommunity {
type Response = GetCommunityResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<GetCommunityResponse, LemmyError> {
- let data: &TransferCommunity = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let data: &TransferCommunity = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_id = data.community_id;
- let read_community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
+ let read_community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
- let site_creator_id =
- blocking(pool, move |conn| Site::read(conn, 1).map(|s| s.creator_id)).await??;
+ let site_creator_id = blocking(context.pool(), move |conn| {
+ Site::read(conn, 1).map(|s| s.creator_id)
+ })
+ .await??;
- let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
+ let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
- let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
+ let creator_index = admins
+ .iter()
+ .position(|r| r.id == site_creator_id)
+ .context(location_info!())?;
let creator_user = admins.remove(creator_index);
admins.insert(0, creator_user);
// Make sure user is the creator, or an admin
- if user_id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user_id) {
+ if user.id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user.id) {
return Err(APIError::err("not_an_admin").into());
}
- let community_form = CommunityForm {
- name: read_community.name,
- title: read_community.title,
- description: read_community.description,
- category_id: read_community.category_id,
- creator_id: data.user_id, // This makes the new user the community creator
- removed: None,
- deleted: None,
- nsfw: read_community.nsfw,
- updated: Some(naive_now()),
- actor_id: read_community.actor_id,
- local: read_community.local,
- private_key: read_community.private_key,
- public_key: read_community.public_key,
- last_refreshed_at: None,
- published: None,
- };
-
let community_id = data.community_id;
- let update = move |conn: &'_ _| Community::update(conn, community_id, &community_form);
- if blocking(pool, update).await?.is_err() {
+ let new_creator = data.user_id;
+ let update = move |conn: &'_ _| Community::update_creator(conn, community_id, new_creator);
+ if blocking(context.pool(), update).await?.is_err() {
return Err(APIError::err("couldnt_update_community").into());
};
// You also have to re-do the community_moderator table, reordering it.
let community_id = data.community_id;
- let mut community_mods = blocking(pool, move |conn| {
+ let mut community_mods = blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await??;
let creator_index = community_mods
.iter()
.position(|r| r.user_id == data.user_id)
- .unwrap();
+ .context(location_info!())?;
let creator_user = community_mods.remove(creator_index);
community_mods.insert(0, creator_user);
let community_id = data.community_id;
- blocking(pool, move |conn| {
+ blocking(context.pool(), move |conn| {
CommunityModerator::delete_for_community(conn, community_id)
})
.await??;
};
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
- if blocking(pool, join).await?.is_err() {
+ if blocking(context.pool(), join).await?.is_err() {
return Err(APIError::err("community_moderator_already_exists").into());
}
}
// Mod tables
let form = ModAddCommunityForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
other_user_id: data.user_id,
community_id: data.community_id,
removed: Some(false),
};
- blocking(pool, move |conn| ModAddCommunity::create(conn, &form)).await??;
+ blocking(context.pool(), move |conn| {
+ ModAddCommunity::create(conn, &form)
+ })
+ .await??;
let community_id = data.community_id;
- let community_view = match blocking(pool, move |conn| {
+ let user_id = user.id;
+ let community_view = match blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, Some(user_id))
})
.await?
};
let community_id = data.community_id;
- let moderators = match blocking(pool, move |conn| {
+ let moderators = match blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await?
Ok(GetCommunityResponse {
community: community_view,
moderators,
- admins,
online: 0,
})
}
}
+
+pub fn send_community_websocket(
+ res: &CommunityResponse,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ op: UserOperation,
+) {
+ // Strip out the user id and subscribed when sending to others
+ let mut res_sent = res.clone();
+ res_sent.community.user_id = None;
+ res_sent.community.subscribed = None;
+
+ context.chat_server().do_send(SendCommunityRoomMessage {
+ op,
+ response: res_sent,
+ community_id: res.community.id,
+ websocket_id,
+ });
+}
-use crate::{websocket::WebsocketInfo, DbPool, LemmyError};
-use actix_web::client::Client;
-use lemmy_db::{community::*, community_view::*, moderator::*, site::*, user::*, user_view::*};
+use crate::{api::claims::Claims, blocking, ConnectionId, DbPool, LemmyContext, LemmyError};
+use actix_web::web::Data;
+use lemmy_db::{
+ community::*,
+ community_view::*,
+ moderator::*,
+ post::Post,
+ site::*,
+ user::*,
+ user_view::*,
+ Crud,
+};
+use lemmy_utils::{slur_check, slurs_vec_to_str};
+use thiserror::Error;
pub mod claims;
pub mod comment;
pub mod site;
pub mod user;
-#[derive(Fail, Debug)]
-#[fail(display = "{{\"error\":\"{}\"}}", message)]
+#[derive(Debug, Error)]
+#[error("{{\"error\":\"{message}\"}}")]
pub struct APIError {
pub message: String,
}
}
}
-pub struct Oper<T> {
- data: T,
- client: Client,
-}
-
-impl<Data> Oper<Data> {
- pub fn new(data: Data, client: Client) -> Oper<Data> {
- Oper { data, client }
- }
-}
-
#[async_trait::async_trait(?Send)]
pub trait Perform {
type Response: serde::ser::Serialize + Send;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<Self::Response, LemmyError>;
}
+
+pub(in crate::api) async fn is_mod_or_admin(
+ pool: &DbPool,
+ user_id: i32,
+ community_id: i32,
+) -> Result<(), LemmyError> {
+ let is_mod_or_admin = blocking(pool, move |conn| {
+ Community::is_mod_or_admin(conn, user_id, community_id)
+ })
+ .await?;
+ if !is_mod_or_admin {
+ return Err(APIError::err("not_a_mod_or_admin").into());
+ }
+ Ok(())
+}
+pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> {
+ let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
+ if !user.admin {
+ return Err(APIError::err("not_an_admin").into());
+ }
+ Ok(())
+}
+
+pub(in crate::api) async fn get_post(post_id: i32, pool: &DbPool) -> Result<Post, LemmyError> {
+ match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
+ Ok(post) => Ok(post),
+ Err(_e) => Err(APIError::err("couldnt_find_post").into()),
+ }
+}
+
+pub(in crate::api) async fn get_user_from_jwt(
+ jwt: &str,
+ pool: &DbPool,
+) -> Result<User_, LemmyError> {
+ let claims = match Claims::decode(&jwt) {
+ Ok(claims) => claims.claims,
+ Err(_e) => return Err(APIError::err("not_logged_in").into()),
+ };
+ let user_id = claims.id;
+ let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
+ // Check for a site ban
+ if user.banned {
+ return Err(APIError::err("site_ban").into());
+ }
+ Ok(user)
+}
+
+pub(in crate::api) async fn get_user_from_jwt_opt(
+ jwt: &Option<String>,
+ pool: &DbPool,
+) -> Result<Option<User_>, LemmyError> {
+ match jwt {
+ Some(jwt) => Ok(Some(get_user_from_jwt(jwt, pool).await?)),
+ None => Ok(None),
+ }
+}
+
+pub(in crate) fn check_slurs(text: &str) -> Result<(), APIError> {
+ if let Err(slurs) = slur_check(text) {
+ Err(APIError::err(&slurs_vec_to_str(slurs)))
+ } else {
+ Ok(())
+ }
+}
+pub(in crate) fn check_slurs_opt(text: &Option<String>) -> Result<(), APIError> {
+ match text {
+ Some(t) => check_slurs(t),
+ None => Ok(()),
+ }
+}
+pub(in crate::api) async fn check_community_ban(
+ user_id: i32,
+ community_id: i32,
+ pool: &DbPool,
+) -> Result<(), LemmyError> {
+ let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
+ if blocking(pool, is_banned).await? {
+ Err(APIError::err("community_ban").into())
+ } else {
+ Ok(())
+ }
+}
use crate::{
- api::{claims::Claims, APIError, Oper, Perform},
+ api::{
+ check_community_ban,
+ check_slurs,
+ check_slurs_opt,
+ get_user_from_jwt,
+ get_user_from_jwt_opt,
+ is_mod_or_admin,
+ APIError,
+ Perform,
+ },
apub::{ApubLikeableType, ApubObjectType},
blocking,
fetch_iframely_and_pictrs_data,
websocket::{
- server::{JoinCommunityRoom, JoinPostRoom, SendPost},
+ server::{GetPostUsersOnline, JoinCommunityRoom, JoinPostRoom, SendPost},
UserOperation,
- WebsocketInfo,
},
- DbPool,
+ ConnectionId,
+ LemmyContext,
LemmyError,
};
+use actix_web::web::Data;
use lemmy_db::{
comment_view::*,
community_view::*,
naive_now,
post::*,
post_view::*,
- site::*,
site_view::*,
- user::*,
- user_view::*,
Crud,
Likeable,
ListingType,
Saveable,
SortType,
};
-use lemmy_utils::{
- is_valid_post_title,
- make_apub_endpoint,
- slur_check,
- slurs_vec_to_str,
- EndpointType,
-};
+use lemmy_utils::{is_valid_post_title, make_apub_endpoint, EndpointType};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
+use url::Url;
#[derive(Serialize, Deserialize, Debug)]
pub struct CreatePost {
comments: Vec<CommentView>,
community: CommunityView,
moderators: Vec<CommunityModeratorView>,
- admins: Vec<UserView>,
pub online: usize,
}
#[derive(Serialize, Deserialize)]
pub struct EditPost {
pub edit_id: i32,
- creator_id: i32,
- community_id: i32,
name: String,
url: Option<String>,
body: Option<String>,
- removed: Option<bool>,
- deleted: Option<bool>,
nsfw: bool,
- locked: Option<bool>,
- stickied: Option<bool>,
+ auth: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct DeletePost {
+ pub edit_id: i32,
+ deleted: bool,
+ auth: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct RemovePost {
+ pub edit_id: i32,
+ removed: bool,
reason: Option<String>,
auth: String,
}
+#[derive(Serialize, Deserialize)]
+pub struct LockPost {
+ pub edit_id: i32,
+ locked: bool,
+ auth: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct StickyPost {
+ pub edit_id: i32,
+ stickied: bool,
+ auth: String,
+}
+
#[derive(Serialize, Deserialize)]
pub struct SavePost {
post_id: i32,
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<CreatePost> {
+impl Perform for CreatePost {
type Response = PostResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<PostResponse, LemmyError> {
- let data: &CreatePost = &self.data;
+ let data: &CreatePost = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- if let Err(slurs) = slur_check(&data.name) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
-
- if let Some(body) = &data.body {
- if let Err(slurs) = slur_check(body) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
- }
+ check_slurs(&data.name)?;
+ check_slurs_opt(&data.body)?;
if !is_valid_post_title(&data.name) {
return Err(APIError::err("invalid_post_title").into());
}
- let user_id = claims.id;
-
- // Check for a community ban
- let community_id = data.community_id;
- let is_banned =
- move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
- if blocking(pool, is_banned).await? {
- return Err(APIError::err("community_ban").into());
- }
+ check_community_ban(user.id, data.community_id, context.pool()).await?;
- // Check for a site ban
- let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
- if user.banned {
- return Err(APIError::err("site_ban").into());
+ if let Some(url) = data.url.as_ref() {
+ match Url::parse(url) {
+ Ok(_t) => (),
+ Err(_e) => return Err(APIError::err("invalid_url").into()),
+ }
}
// Fetch Iframely and pictrs cached image
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
- fetch_iframely_and_pictrs_data(&self.client, data.url.to_owned()).await;
+ fetch_iframely_and_pictrs_data(context.client(), data.url.to_owned()).await;
let post_form = PostForm {
name: data.name.trim().to_owned(),
url: data.url.to_owned(),
body: data.body.to_owned(),
community_id: data.community_id,
- creator_id: user_id,
+ creator_id: user.id,
removed: None,
deleted: None,
nsfw: data.nsfw,
published: None,
};
- let inserted_post = match blocking(pool, move |conn| Post::create(conn, &post_form)).await? {
- Ok(post) => post,
- Err(e) => {
- let err_type = if e.to_string() == "value too long for type character varying(200)" {
- "post_title_too_long"
- } else {
- "couldnt_create_post"
- };
-
- return Err(APIError::err(err_type).into());
- }
- };
+ let inserted_post =
+ match blocking(context.pool(), move |conn| Post::create(conn, &post_form)).await? {
+ Ok(post) => post,
+ Err(e) => {
+ let err_type = if e.to_string() == "value too long for type character varying(200)" {
+ "post_title_too_long"
+ } else {
+ "couldnt_create_post"
+ };
+
+ return Err(APIError::err(err_type).into());
+ }
+ };
let inserted_post_id = inserted_post.id;
- let updated_post = match blocking(pool, move |conn| {
+ let updated_post = match blocking(context.pool(), move |conn| {
let apub_id =
make_apub_endpoint(EndpointType::Post, &inserted_post_id.to_string()).to_string();
Post::update_ap_id(conn, inserted_post_id, apub_id)
Err(_e) => return Err(APIError::err("couldnt_create_post").into()),
};
- updated_post.send_create(&user, &self.client, pool).await?;
+ updated_post.send_create(&user, context).await?;
// They like their own post by default
let like_form = PostLikeForm {
post_id: inserted_post.id,
- user_id,
+ user_id: user.id,
score: 1,
};
let like = move |conn: &'_ _| PostLike::like(conn, &like_form);
- if blocking(pool, like).await?.is_err() {
+ if blocking(context.pool(), like).await?.is_err() {
return Err(APIError::err("couldnt_like_post").into());
}
- updated_post.send_like(&user, &self.client, pool).await?;
+ updated_post.send_like(&user, context).await?;
// Refetch the view
let inserted_post_id = inserted_post.id;
- let post_view = match blocking(pool, move |conn| {
- PostView::read(conn, inserted_post_id, Some(user_id))
+ let post_view = match blocking(context.pool(), move |conn| {
+ PostView::read(conn, inserted_post_id, Some(user.id))
})
.await?
{
let res = PostResponse { post: post_view };
- if let Some(ws) = websocket_info {
- ws.chatserver.do_send(SendPost {
- op: UserOperation::CreatePost,
- post: res.clone(),
- my_id: ws.id,
- });
- }
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::CreatePost,
+ post: res.clone(),
+ websocket_id,
+ });
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<GetPost> {
+impl Perform for GetPost {
type Response = GetPostResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<GetPostResponse, LemmyError> {
- let data: &GetPost = &self.data;
-
- let user_id: Option<i32> = match &data.auth {
- Some(auth) => match Claims::decode(&auth) {
- Ok(claims) => {
- let user_id = claims.claims.id;
- Some(user_id)
- }
- Err(_e) => None,
- },
- None => None,
- };
+ let data: &GetPost = &self;
+ let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
+ let user_id = user.map(|u| u.id);
let id = data.id;
- let post_view = match blocking(pool, move |conn| PostView::read(conn, id, user_id)).await? {
+ let post_view = match blocking(context.pool(), move |conn| {
+ PostView::read(conn, id, user_id)
+ })
+ .await?
+ {
Ok(post) => post,
Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
};
let id = data.id;
- let comments = blocking(pool, move |conn| {
+ let comments = blocking(context.pool(), move |conn| {
CommentQueryBuilder::create(conn)
.for_post_id(id)
.my_user_id(user_id)
.await??;
let community_id = post_view.community_id;
- let community = blocking(pool, move |conn| {
+ let community = blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, user_id)
})
.await??;
let community_id = post_view.community_id;
- let moderators = blocking(pool, move |conn| {
+ let moderators = blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await??;
- let site_creator_id =
- blocking(pool, move |conn| Site::read(conn, 1).map(|s| s.creator_id)).await??;
-
- let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
- let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
- let creator_user = admins.remove(creator_index);
- admins.insert(0, creator_user);
-
- let online = if let Some(ws) = websocket_info {
- if let Some(id) = ws.id {
- ws.chatserver.do_send(JoinPostRoom {
- post_id: data.id,
- id,
- });
- }
+ if let Some(id) = websocket_id {
+ context.chat_server().do_send(JoinPostRoom {
+ post_id: data.id,
+ id,
+ });
+ }
- // TODO
- 1
- // let fut = async {
- // ws.chatserver.send(GetPostUsersOnline {post_id: data.id}).await.unwrap()
- // };
- // Runtime::new().unwrap().block_on(fut)
- } else {
- 0
- };
+ let online = context
+ .chat_server()
+ .send(GetPostUsersOnline { post_id: data.id })
+ .await
+ .unwrap_or(1);
// Return the jwt
Ok(GetPostResponse {
comments,
community,
moderators,
- admins,
online,
})
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<GetPosts> {
+impl Perform for GetPosts {
type Response = GetPostsResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<GetPostsResponse, LemmyError> {
- let data: &GetPosts = &self.data;
+ let data: &GetPosts = &self;
+ let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
- let user_claims: Option<Claims> = match &data.auth {
- Some(auth) => match Claims::decode(&auth) {
- Ok(claims) => Some(claims.claims),
- Err(_e) => None,
- },
+ let user_id = match &user {
+ Some(user) => Some(user.id),
None => None,
};
- let user_id = match &user_claims {
- Some(claims) => Some(claims.id),
- None => None,
- };
-
- let show_nsfw = match &user_claims {
- Some(claims) => claims.show_nsfw,
+ let show_nsfw = match &user {
+ Some(user) => user.show_nsfw,
None => false,
};
let limit = data.limit;
let community_id = data.community_id;
let community_name = data.community_name.to_owned();
- let posts = match blocking(pool, move |conn| {
+ let posts = match blocking(context.pool(), move |conn| {
PostQueryBuilder::create(conn)
.listing_type(type_)
.sort(&sort)
Err(_e) => return Err(APIError::err("couldnt_get_posts").into()),
};
- if let Some(ws) = websocket_info {
+ if let Some(id) = websocket_id {
// You don't need to join the specific community room, bc this is already handled by
// GetCommunity
if data.community_id.is_none() {
- if let Some(id) = ws.id {
- // 0 is the "all" community
- ws.chatserver.do_send(JoinCommunityRoom {
- community_id: 0,
- id,
- });
- }
+ // 0 is the "all" community
+ context.chat_server().do_send(JoinCommunityRoom {
+ community_id: 0,
+ id,
+ });
}
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<CreatePostLike> {
+impl Perform for CreatePostLike {
type Response = PostResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<PostResponse, LemmyError> {
- let data: &CreatePostLike = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let data: &CreatePostLike = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// Don't do a downvote if site has downvotes disabled
if data.score == -1 {
- let site = blocking(pool, move |conn| SiteView::read(conn)).await??;
+ let site = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
if !site.enable_downvotes {
return Err(APIError::err("downvotes_disabled").into());
}
// Check for a community ban
let post_id = data.post_id;
- let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
+ let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
- let community_id = post.community_id;
- let is_banned =
- move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
- if blocking(pool, is_banned).await? {
- return Err(APIError::err("community_ban").into());
- }
-
- // Check for a site ban
- let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
- if user.banned {
- return Err(APIError::err("site_ban").into());
- }
+ check_community_ban(user.id, post.community_id, context.pool()).await?;
let like_form = PostLikeForm {
post_id: data.post_id,
- user_id,
+ user_id: user.id,
score: data.score,
};
// Remove any likes first
- let like_form2 = like_form.clone();
- blocking(pool, move |conn| PostLike::remove(conn, &like_form2)).await??;
+ let user_id = user.id;
+ blocking(context.pool(), move |conn| {
+ PostLike::remove(conn, user_id, post_id)
+ })
+ .await??;
// Only add the like if the score isnt 0
let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
if do_add {
let like_form2 = like_form.clone();
let like = move |conn: &'_ _| PostLike::like(conn, &like_form2);
- if blocking(pool, like).await?.is_err() {
+ if blocking(context.pool(), like).await?.is_err() {
return Err(APIError::err("couldnt_like_post").into());
}
if like_form.score == 1 {
- post.send_like(&user, &self.client, pool).await?;
+ post.send_like(&user, context).await?;
} else if like_form.score == -1 {
- post.send_dislike(&user, &self.client, pool).await?;
+ post.send_dislike(&user, context).await?;
}
} else {
- post.send_undo_like(&user, &self.client, pool).await?;
+ post.send_undo_like(&user, context).await?;
}
let post_id = data.post_id;
- let post_view = match blocking(pool, move |conn| {
+ let user_id = user.id;
+ let post_view = match blocking(context.pool(), move |conn| {
PostView::read(conn, post_id, Some(user_id))
})
.await?
let res = PostResponse { post: post_view };
- if let Some(ws) = websocket_info {
- ws.chatserver.do_send(SendPost {
- op: UserOperation::CreatePostLike,
- post: res.clone(),
- my_id: ws.id,
- });
- }
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::CreatePostLike,
+ post: res.clone(),
+ websocket_id,
+ });
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<EditPost> {
+impl Perform for EditPost {
type Response = PostResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<PostResponse, LemmyError> {
- let data: &EditPost = &self.data;
-
- if let Err(slurs) = slur_check(&data.name) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
+ let data: &EditPost = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
- if let Some(body) = &data.body {
- if let Err(slurs) = slur_check(body) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
- }
+ check_slurs(&data.name)?;
+ check_slurs_opt(&data.body)?;
if !is_valid_post_title(&data.name) {
return Err(APIError::err("invalid_post_title").into());
}
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
-
let edit_id = data.edit_id;
- let read_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??;
-
- // Verify its the creator or a mod or admin
- let community_id = read_post.community_id;
- let mut editors: Vec<i32> = vec![read_post.creator_id];
- let mut moderators: Vec<i32> = vec![];
-
- moderators.append(
- &mut blocking(pool, move |conn| {
- CommunityModeratorView::for_community(conn, community_id)
- .map(|v| v.into_iter().map(|m| m.user_id).collect())
- })
- .await??,
- );
- moderators.append(
- &mut blocking(pool, move |conn| {
- UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect())
- })
- .await??,
- );
-
- editors.extend(&moderators);
-
- if !editors.contains(&user_id) {
- return Err(APIError::err("no_post_edit_allowed").into());
- }
+ let orig_post = blocking(context.pool(), move |conn| Post::read(conn, edit_id)).await??;
- // Check for a community ban
- let community_id = read_post.community_id;
- let is_banned =
- move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
- if blocking(pool, is_banned).await? {
- return Err(APIError::err("community_ban").into());
- }
+ check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
- // Check for a site ban
- let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
- if user.banned {
- return Err(APIError::err("site_ban").into());
+ // Verify that only the creator can edit
+ if !Post::is_post_creator(user.id, orig_post.creator_id) {
+ return Err(APIError::err("no_post_edit_allowed").into());
}
// Fetch Iframely and Pictrs cached image
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
- fetch_iframely_and_pictrs_data(&self.client, data.url.to_owned()).await;
-
- let post_form = {
- // only modify some properties if they are a moderator
- if moderators.contains(&user_id) {
- PostForm {
- name: data.name.trim().to_owned(),
- url: data.url.to_owned(),
- body: data.body.to_owned(),
- creator_id: read_post.creator_id.to_owned(),
- community_id: read_post.community_id,
- removed: data.removed.to_owned(),
- deleted: data.deleted.to_owned(),
- nsfw: data.nsfw,
- locked: data.locked.to_owned(),
- stickied: data.stickied.to_owned(),
- updated: Some(naive_now()),
- embed_title: iframely_title,
- embed_description: iframely_description,
- embed_html: iframely_html,
- thumbnail_url: pictrs_thumbnail,
- ap_id: read_post.ap_id,
- local: read_post.local,
- published: None,
- }
- } else {
- PostForm {
- name: read_post.name.trim().to_owned(),
- url: data.url.to_owned(),
- body: data.body.to_owned(),
- creator_id: read_post.creator_id.to_owned(),
- community_id: read_post.community_id,
- removed: Some(read_post.removed),
- deleted: data.deleted.to_owned(),
- nsfw: data.nsfw,
- locked: Some(read_post.locked),
- stickied: Some(read_post.stickied),
- updated: Some(naive_now()),
- embed_title: iframely_title,
- embed_description: iframely_description,
- embed_html: iframely_html,
- thumbnail_url: pictrs_thumbnail,
- ap_id: read_post.ap_id,
- local: read_post.local,
- published: None,
- }
- }
+ fetch_iframely_and_pictrs_data(context.client(), data.url.to_owned()).await;
+
+ let post_form = PostForm {
+ name: data.name.trim().to_owned(),
+ url: data.url.to_owned(),
+ body: data.body.to_owned(),
+ nsfw: data.nsfw,
+ creator_id: orig_post.creator_id.to_owned(),
+ community_id: orig_post.community_id,
+ removed: Some(orig_post.removed),
+ deleted: Some(orig_post.deleted),
+ locked: Some(orig_post.locked),
+ stickied: Some(orig_post.stickied),
+ updated: Some(naive_now()),
+ embed_title: iframely_title,
+ embed_description: iframely_description,
+ embed_html: iframely_html,
+ thumbnail_url: pictrs_thumbnail,
+ ap_id: orig_post.ap_id,
+ local: orig_post.local,
+ published: None,
};
let edit_id = data.edit_id;
- let res = blocking(pool, move |conn| Post::update(conn, edit_id, &post_form)).await?;
+ let res = blocking(context.pool(), move |conn| {
+ Post::update(conn, edit_id, &post_form)
+ })
+ .await?;
let updated_post: Post = match res {
Ok(post) => post,
Err(e) => {
}
};
- if moderators.contains(&user_id) {
- // Mod tables
- if let Some(removed) = data.removed.to_owned() {
- let form = ModRemovePostForm {
- mod_user_id: user_id,
- post_id: data.edit_id,
- removed: Some(removed),
- reason: data.reason.to_owned(),
- };
- blocking(pool, move |conn| ModRemovePost::create(conn, &form)).await??;
- }
+ // Send apub update
+ updated_post.send_update(&user, context).await?;
- if let Some(locked) = data.locked.to_owned() {
- let form = ModLockPostForm {
- mod_user_id: user_id,
- post_id: data.edit_id,
- locked: Some(locked),
- };
- blocking(pool, move |conn| ModLockPost::create(conn, &form)).await??;
- }
+ let edit_id = data.edit_id;
+ let post_view = blocking(context.pool(), move |conn| {
+ PostView::read(conn, edit_id, Some(user.id))
+ })
+ .await??;
- if let Some(stickied) = data.stickied.to_owned() {
- let form = ModStickyPostForm {
- mod_user_id: user_id,
- post_id: data.edit_id,
- stickied: Some(stickied),
- };
- blocking(pool, move |conn| ModStickyPost::create(conn, &form)).await??;
- }
+ let res = PostResponse { post: post_view };
+
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::EditPost,
+ post: res.clone(),
+ websocket_id,
+ });
+
+ Ok(res)
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for DeletePost {
+ type Response = PostResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<PostResponse, LemmyError> {
+ let data: &DeletePost = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
+ let edit_id = data.edit_id;
+ let orig_post = blocking(context.pool(), move |conn| Post::read(conn, edit_id)).await??;
+
+ check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
+
+ // Verify that only the creator can delete
+ if !Post::is_post_creator(user.id, orig_post.creator_id) {
+ return Err(APIError::err("no_post_edit_allowed").into());
}
- if let Some(deleted) = data.deleted.to_owned() {
- if deleted {
- updated_post.send_delete(&user, &self.client, pool).await?;
- } else {
- updated_post
- .send_undo_delete(&user, &self.client, pool)
- .await?;
- }
- } else if let Some(removed) = data.removed.to_owned() {
- if moderators.contains(&user_id) {
- if removed {
- updated_post.send_remove(&user, &self.client, pool).await?;
- } else {
- updated_post
- .send_undo_remove(&user, &self.client, pool)
- .await?;
- }
- }
+ // Update the post
+ let edit_id = data.edit_id;
+ let deleted = data.deleted;
+ let updated_post = blocking(context.pool(), move |conn| {
+ Post::update_deleted(conn, edit_id, deleted)
+ })
+ .await??;
+
+ // apub updates
+ if deleted {
+ updated_post.send_delete(&user, context).await?;
} else {
- updated_post.send_update(&user, &self.client, pool).await?;
+ updated_post.send_undo_delete(&user, context).await?;
}
+ // Refetch the post
let edit_id = data.edit_id;
- let post_view = blocking(pool, move |conn| {
- PostView::read(conn, edit_id, Some(user_id))
+ let post_view = blocking(context.pool(), move |conn| {
+ PostView::read(conn, edit_id, Some(user.id))
})
.await??;
let res = PostResponse { post: post_view };
- if let Some(ws) = websocket_info {
- ws.chatserver.do_send(SendPost {
- op: UserOperation::EditPost,
- post: res.clone(),
- my_id: ws.id,
- });
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::DeletePost,
+ post: res.clone(),
+ websocket_id,
+ });
+
+ Ok(res)
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for RemovePost {
+ type Response = PostResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<PostResponse, LemmyError> {
+ let data: &RemovePost = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
+ let edit_id = data.edit_id;
+ let orig_post = blocking(context.pool(), move |conn| Post::read(conn, edit_id)).await??;
+
+ check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
+
+ // Verify that only the mods can remove
+ is_mod_or_admin(context.pool(), user.id, orig_post.community_id).await?;
+
+ // Update the post
+ let edit_id = data.edit_id;
+ let removed = data.removed;
+ let updated_post = blocking(context.pool(), move |conn| {
+ Post::update_removed(conn, edit_id, removed)
+ })
+ .await??;
+
+ // Mod tables
+ let form = ModRemovePostForm {
+ mod_user_id: user.id,
+ post_id: data.edit_id,
+ removed: Some(removed),
+ reason: data.reason.to_owned(),
+ };
+ blocking(context.pool(), move |conn| {
+ ModRemovePost::create(conn, &form)
+ })
+ .await??;
+
+ // apub updates
+ if removed {
+ updated_post.send_remove(&user, context).await?;
+ } else {
+ updated_post.send_undo_remove(&user, context).await?;
}
+ // Refetch the post
+ let edit_id = data.edit_id;
+ let user_id = user.id;
+ let post_view = blocking(context.pool(), move |conn| {
+ PostView::read(conn, edit_id, Some(user_id))
+ })
+ .await??;
+
+ let res = PostResponse { post: post_view };
+
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::RemovePost,
+ post: res.clone(),
+ websocket_id,
+ });
+
+ Ok(res)
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for LockPost {
+ type Response = PostResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<PostResponse, LemmyError> {
+ let data: &LockPost = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
+ let edit_id = data.edit_id;
+ let orig_post = blocking(context.pool(), move |conn| Post::read(conn, edit_id)).await??;
+
+ check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
+
+ // Verify that only the mods can lock
+ is_mod_or_admin(context.pool(), user.id, orig_post.community_id).await?;
+
+ // Update the post
+ let edit_id = data.edit_id;
+ let locked = data.locked;
+ let updated_post = blocking(context.pool(), move |conn| {
+ Post::update_locked(conn, edit_id, locked)
+ })
+ .await??;
+
+ // Mod tables
+ let form = ModLockPostForm {
+ mod_user_id: user.id,
+ post_id: data.edit_id,
+ locked: Some(locked),
+ };
+ blocking(context.pool(), move |conn| ModLockPost::create(conn, &form)).await??;
+
+ // apub updates
+ updated_post.send_update(&user, context).await?;
+
+ // Refetch the post
+ let edit_id = data.edit_id;
+ let post_view = blocking(context.pool(), move |conn| {
+ PostView::read(conn, edit_id, Some(user.id))
+ })
+ .await??;
+
+ let res = PostResponse { post: post_view };
+
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::LockPost,
+ post: res.clone(),
+ websocket_id,
+ });
+
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<SavePost> {
+impl Perform for StickyPost {
type Response = PostResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<PostResponse, LemmyError> {
- let data: &SavePost = &self.data;
+ let data: &StickyPost = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
+ let edit_id = data.edit_id;
+ let orig_post = blocking(context.pool(), move |conn| Post::read(conn, edit_id)).await??;
+
+ check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
+ // Verify that only the mods can sticky
+ is_mod_or_admin(context.pool(), user.id, orig_post.community_id).await?;
+
+ // Update the post
+ let edit_id = data.edit_id;
+ let stickied = data.stickied;
+ let updated_post = blocking(context.pool(), move |conn| {
+ Post::update_stickied(conn, edit_id, stickied)
+ })
+ .await??;
+
+ // Mod tables
+ let form = ModStickyPostForm {
+ mod_user_id: user.id,
+ post_id: data.edit_id,
+ stickied: Some(stickied),
};
+ blocking(context.pool(), move |conn| {
+ ModStickyPost::create(conn, &form)
+ })
+ .await??;
+
+ // Apub updates
+ // TODO stickied should pry work like locked for ease of use
+ updated_post.send_update(&user, context).await?;
- let user_id = claims.id;
+ // Refetch the post
+ let edit_id = data.edit_id;
+ let post_view = blocking(context.pool(), move |conn| {
+ PostView::read(conn, edit_id, Some(user.id))
+ })
+ .await??;
+
+ let res = PostResponse { post: post_view };
+
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::StickyPost,
+ post: res.clone(),
+ websocket_id,
+ });
+
+ Ok(res)
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for SavePost {
+ type Response = PostResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<PostResponse, LemmyError> {
+ let data: &SavePost = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let post_saved_form = PostSavedForm {
post_id: data.post_id,
- user_id,
+ user_id: user.id,
};
if data.save {
let save = move |conn: &'_ _| PostSaved::save(conn, &post_saved_form);
- if blocking(pool, save).await?.is_err() {
+ if blocking(context.pool(), save).await?.is_err() {
return Err(APIError::err("couldnt_save_post").into());
}
} else {
let unsave = move |conn: &'_ _| PostSaved::unsave(conn, &post_saved_form);
- if blocking(pool, unsave).await?.is_err() {
+ if blocking(context.pool(), unsave).await?.is_err() {
return Err(APIError::err("couldnt_save_post").into());
}
}
let post_id = data.post_id;
- let post_view = blocking(pool, move |conn| {
+ let user_id = user.id;
+ let post_view = blocking(context.pool(), move |conn| {
PostView::read(conn, post_id, Some(user_id))
})
.await??;
use super::user::Register;
use crate::{
- api::{claims::Claims, APIError, Oper, Perform},
+ api::{
+ check_slurs,
+ check_slurs_opt,
+ get_user_from_jwt,
+ get_user_from_jwt_opt,
+ is_admin,
+ APIError,
+ Perform,
+ },
apub::fetcher::search_by_apub_id,
blocking,
- websocket::{server::SendAllMessage, UserOperation, WebsocketInfo},
- DbPool,
+ version,
+ websocket::{
+ server::{GetUsersOnline, SendAllMessage},
+ UserOperation,
+ },
+ ConnectionId,
+ LemmyContext,
LemmyError,
};
+use actix_web::web::Data;
+use anyhow::Context;
use lemmy_db::{
category::*,
comment_view::*,
community_view::*,
+ diesel_option_overwrite,
moderator::*,
moderator_views::*,
naive_now,
post_view::*,
site::*,
site_view::*,
+ user::*,
user_view::*,
Crud,
SearchType,
SortType,
};
-use lemmy_utils::{settings::Settings, slur_check, slurs_vec_to_str};
+use lemmy_utils::{location_info, settings::Settings};
use log::{debug, info};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
pub struct CreateSite {
pub name: String,
pub description: Option<String>,
+ pub icon: Option<String>,
+ pub banner: Option<String>,
pub enable_downvotes: bool,
pub open_registration: bool,
pub enable_nsfw: bool,
pub struct EditSite {
name: String,
description: Option<String>,
+ icon: Option<String>,
+ banner: Option<String>,
enable_downvotes: bool,
open_registration: bool,
enable_nsfw: bool,
}
#[derive(Serialize, Deserialize)]
-pub struct GetSite {}
+pub struct GetSite {
+ auth: Option<String>,
+}
#[derive(Serialize, Deserialize, Clone)]
pub struct SiteResponse {
admins: Vec<UserView>,
banned: Vec<UserView>,
pub online: usize,
+ version: String,
+ my_user: Option<User_>,
+ federated_instances: Vec<String>,
}
#[derive(Serialize, Deserialize)]
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<ListCategories> {
+impl Perform for ListCategories {
type Response = ListCategoriesResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<ListCategoriesResponse, LemmyError> {
- let _data: &ListCategories = &self.data;
+ let _data: &ListCategories = &self;
- let categories = blocking(pool, move |conn| Category::list_all(conn)).await??;
+ let categories = blocking(context.pool(), move |conn| Category::list_all(conn)).await??;
// Return the jwt
Ok(ListCategoriesResponse { categories })
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<GetModlog> {
+impl Perform for GetModlog {
type Response = GetModlogResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<GetModlogResponse, LemmyError> {
- let data: &GetModlog = &self.data;
+ let data: &GetModlog = &self;
let community_id = data.community_id;
let mod_user_id = data.mod_user_id;
let page = data.page;
let limit = data.limit;
- let removed_posts = blocking(pool, move |conn| {
+ let removed_posts = blocking(context.pool(), move |conn| {
ModRemovePostView::list(conn, community_id, mod_user_id, page, limit)
})
.await??;
- let locked_posts = blocking(pool, move |conn| {
+ let locked_posts = blocking(context.pool(), move |conn| {
ModLockPostView::list(conn, community_id, mod_user_id, page, limit)
})
.await??;
- let stickied_posts = blocking(pool, move |conn| {
+ let stickied_posts = blocking(context.pool(), move |conn| {
ModStickyPostView::list(conn, community_id, mod_user_id, page, limit)
})
.await??;
- let removed_comments = blocking(pool, move |conn| {
+ let removed_comments = blocking(context.pool(), move |conn| {
ModRemoveCommentView::list(conn, community_id, mod_user_id, page, limit)
})
.await??;
- let banned_from_community = blocking(pool, move |conn| {
+ let banned_from_community = blocking(context.pool(), move |conn| {
ModBanFromCommunityView::list(conn, community_id, mod_user_id, page, limit)
})
.await??;
- let added_to_community = blocking(pool, move |conn| {
+ let added_to_community = blocking(context.pool(), move |conn| {
ModAddCommunityView::list(conn, community_id, mod_user_id, page, limit)
})
.await??;
// These arrays are only for the full modlog, when a community isn't given
let (removed_communities, banned, added) = if data.community_id.is_none() {
- blocking(pool, move |conn| {
+ blocking(context.pool(), move |conn| {
Ok((
ModRemoveCommunityView::list(conn, mod_user_id, page, limit)?,
ModBanView::list(conn, mod_user_id, page, limit)?,
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<CreateSite> {
+impl Perform for CreateSite {
type Response = SiteResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<SiteResponse, LemmyError> {
- let data: &CreateSite = &self.data;
+ let data: &CreateSite = &self;
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
- if let Err(slurs) = slur_check(&data.name) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
-
- if let Some(description) = &data.description {
- if let Err(slurs) = slur_check(description) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
- }
-
- let user_id = claims.id;
+ check_slurs(&data.name)?;
+ check_slurs_opt(&data.description)?;
// Make sure user is an admin
- let user = blocking(pool, move |conn| UserView::read(conn, user_id)).await??;
- if !user.admin {
- return Err(APIError::err("not_an_admin").into());
- }
+ is_admin(context.pool(), user.id).await?;
let site_form = SiteForm {
name: data.name.to_owned(),
description: data.description.to_owned(),
- creator_id: user_id,
+ icon: Some(data.icon.to_owned()),
+ banner: Some(data.banner.to_owned()),
+ creator_id: user.id,
enable_downvotes: data.enable_downvotes,
open_registration: data.open_registration,
enable_nsfw: data.enable_nsfw,
};
let create_site = move |conn: &'_ _| Site::create(conn, &site_form);
- if blocking(pool, create_site).await?.is_err() {
+ if blocking(context.pool(), create_site).await?.is_err() {
return Err(APIError::err("site_already_exists").into());
}
- let site_view = blocking(pool, move |conn| SiteView::read(conn)).await??;
+ let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
Ok(SiteResponse { site: site_view })
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<EditSite> {
+impl Perform for EditSite {
type Response = SiteResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<SiteResponse, LemmyError> {
- let data: &EditSite = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- if let Err(slurs) = slur_check(&data.name) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
-
- if let Some(description) = &data.description {
- if let Err(slurs) = slur_check(description) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
- }
+ let data: &EditSite = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
- let user_id = claims.id;
+ check_slurs(&data.name)?;
+ check_slurs_opt(&data.description)?;
// Make sure user is an admin
- let user = blocking(pool, move |conn| UserView::read(conn, user_id)).await??;
- if !user.admin {
- return Err(APIError::err("not_an_admin").into());
- }
+ is_admin(context.pool(), user.id).await?;
+
+ let found_site = blocking(context.pool(), move |conn| Site::read(conn, 1)).await??;
- let found_site = blocking(pool, move |conn| Site::read(conn, 1)).await??;
+ let icon = diesel_option_overwrite(&data.icon);
+ let banner = diesel_option_overwrite(&data.banner);
let site_form = SiteForm {
name: data.name.to_owned(),
description: data.description.to_owned(),
+ icon,
+ banner,
creator_id: found_site.creator_id,
updated: Some(naive_now()),
enable_downvotes: data.enable_downvotes,
};
let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form);
- if blocking(pool, update_site).await?.is_err() {
+ if blocking(context.pool(), update_site).await?.is_err() {
return Err(APIError::err("couldnt_update_site").into());
}
- let site_view = blocking(pool, move |conn| SiteView::read(conn)).await??;
+ let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
let res = SiteResponse { site: site_view };
- if let Some(ws) = websocket_info {
- ws.chatserver.do_send(SendAllMessage {
- op: UserOperation::EditSite,
- response: res.clone(),
- my_id: ws.id,
- });
- }
+ context.chat_server().do_send(SendAllMessage {
+ op: UserOperation::EditSite,
+ response: res.clone(),
+ websocket_id,
+ });
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<GetSite> {
+impl Perform for GetSite {
type Response = GetSiteResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<GetSiteResponse, LemmyError> {
- let _data: &GetSite = &self.data;
+ let data: &GetSite = &self;
// TODO refactor this a little
- let res = blocking(pool, move |conn| Site::read(conn, 1)).await?;
+ let res = blocking(context.pool(), move |conn| Site::read(conn, 1)).await?;
let site_view = if res.is_ok() {
- Some(blocking(pool, move |conn| SiteView::read(conn)).await??)
+ Some(blocking(context.pool(), move |conn| SiteView::read(conn)).await??)
} else if let Some(setup) = Settings::get().setup.as_ref() {
let register = Register {
username: setup.admin_username.to_owned(),
password_verify: setup.admin_password.to_owned(),
admin: true,
show_nsfw: true,
+ captcha_uuid: None,
+ captcha_answer: None,
};
- let login_response = Oper::new(register, self.client.clone())
- .perform(pool, websocket_info.clone())
- .await?;
+ let login_response = register.perform(context, websocket_id).await?;
info!("Admin {} created", setup.admin_username);
let create_site = CreateSite {
name: setup.site_name.to_owned(),
description: None,
+ icon: None,
+ banner: None,
enable_downvotes: true,
open_registration: true,
enable_nsfw: true,
auth: login_response.jwt,
};
- Oper::new(create_site, self.client.clone())
- .perform(pool, websocket_info.clone())
- .await?;
+ create_site.perform(context, websocket_id).await?;
info!("Site {} created", setup.site_name);
- Some(blocking(pool, move |conn| SiteView::read(conn)).await??)
+ Some(blocking(context.pool(), move |conn| SiteView::read(conn)).await??)
} else {
None
};
- let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
+ let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
// Make sure the site creator is the top admin
if let Some(site_view) = site_view.to_owned() {
}
}
- let banned = blocking(pool, move |conn| UserView::banned(conn)).await??;
-
- let online = if let Some(_ws) = websocket_info {
- // TODO
- 1
- // let fut = async {
- // ws.chatserver.send(GetUsersOnline).await.unwrap()
- // };
- // Runtime::new().unwrap().block_on(fut)
- } else {
- 0
- };
+ let banned = blocking(context.pool(), move |conn| UserView::banned(conn)).await??;
+
+ let online = context
+ .chat_server()
+ .send(GetUsersOnline)
+ .await
+ .unwrap_or(1);
+
+ let my_user = get_user_from_jwt_opt(&data.auth, context.pool())
+ .await?
+ .map(|mut u| {
+ u.password_encrypted = "".to_string();
+ u.private_key = None;
+ u.public_key = None;
+ u
+ });
Ok(GetSiteResponse {
site: site_view,
admins,
banned,
online,
+ version: version::VERSION.to_string(),
+ my_user,
+ federated_instances: Settings::get().get_allowed_instances(),
})
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<Search> {
+impl Perform for Search {
type Response = SearchResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<SearchResponse, LemmyError> {
- let data: &Search = &self.data;
+ let data: &Search = &self;
dbg!(&data);
- match search_by_apub_id(&data.q, &self.client, pool).await {
+ match search_by_apub_id(&data.q, context).await {
Ok(r) => return Ok(r),
Err(e) => debug!("Failed to resolve search query as activitypub ID: {}", e),
}
- let user_id: Option<i32> = match &data.auth {
- Some(auth) => match Claims::decode(&auth) {
- Ok(claims) => {
- let user_id = claims.claims.id;
- Some(user_id)
- }
- Err(_e) => None,
- },
- None => None,
- };
+ let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
+ let user_id = user.map(|u| u.id);
let type_ = SearchType::from_str(&data.type_)?;
let community_id = data.community_id;
match type_ {
SearchType::Posts => {
- posts = blocking(pool, move |conn| {
+ posts = blocking(context.pool(), move |conn| {
PostQueryBuilder::create(conn)
.sort(&sort)
.show_nsfw(true)
.await??;
}
SearchType::Comments => {
- comments = blocking(pool, move |conn| {
+ comments = blocking(context.pool(), move |conn| {
CommentQueryBuilder::create(&conn)
.sort(&sort)
.search_term(q)
.await??;
}
SearchType::Communities => {
- communities = blocking(pool, move |conn| {
+ communities = blocking(context.pool(), move |conn| {
CommunityQueryBuilder::create(conn)
.sort(&sort)
.search_term(q)
.await??;
}
SearchType::Users => {
- users = blocking(pool, move |conn| {
+ users = blocking(context.pool(), move |conn| {
UserQueryBuilder::create(conn)
.sort(&sort)
.search_term(q)
.await??;
}
SearchType::All => {
- posts = blocking(pool, move |conn| {
+ posts = blocking(context.pool(), move |conn| {
PostQueryBuilder::create(conn)
.sort(&sort)
.show_nsfw(true)
let q = data.q.to_owned();
let sort = SortType::from_str(&data.sort)?;
- comments = blocking(pool, move |conn| {
+ comments = blocking(context.pool(), move |conn| {
CommentQueryBuilder::create(conn)
.sort(&sort)
.search_term(q)
let q = data.q.to_owned();
let sort = SortType::from_str(&data.sort)?;
- communities = blocking(pool, move |conn| {
+ communities = blocking(context.pool(), move |conn| {
CommunityQueryBuilder::create(conn)
.sort(&sort)
.search_term(q)
let q = data.q.to_owned();
let sort = SortType::from_str(&data.sort)?;
- users = blocking(pool, move |conn| {
+ users = blocking(context.pool(), move |conn| {
UserQueryBuilder::create(conn)
.sort(&sort)
.search_term(q)
.await??;
}
SearchType::Url => {
- posts = blocking(pool, move |conn| {
+ posts = blocking(context.pool(), move |conn| {
PostQueryBuilder::create(conn)
.sort(&sort)
.show_nsfw(true)
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<TransferSite> {
+impl Perform for TransferSite {
type Response = GetSiteResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<GetSiteResponse, LemmyError> {
- let data: &TransferSite = &self.data;
+ let data: &TransferSite = &self;
+ let mut user = get_user_from_jwt(&data.auth, context.pool()).await?;
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
+ // TODO add a User_::read_safe() for this.
+ user.password_encrypted = "".to_string();
+ user.private_key = None;
+ user.public_key = None;
- let user_id = claims.id;
-
- let read_site = blocking(pool, move |conn| Site::read(conn, 1)).await??;
+ let read_site = blocking(context.pool(), move |conn| Site::read(conn, 1)).await??;
// Make sure user is the creator
- if read_site.creator_id != user_id {
+ if read_site.creator_id != user.id {
return Err(APIError::err("not_an_admin").into());
}
- let site_form = SiteForm {
- name: read_site.name,
- description: read_site.description,
- creator_id: data.user_id,
- updated: Some(naive_now()),
- enable_downvotes: read_site.enable_downvotes,
- open_registration: read_site.open_registration,
- enable_nsfw: read_site.enable_nsfw,
- };
-
- let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form);
- if blocking(pool, update_site).await?.is_err() {
+ let new_creator_id = data.user_id;
+ let transfer_site = move |conn: &'_ _| Site::transfer(conn, new_creator_id);
+ if blocking(context.pool(), transfer_site).await?.is_err() {
return Err(APIError::err("couldnt_update_site").into());
};
// Mod tables
let form = ModAddForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
other_user_id: data.user_id,
removed: Some(false),
};
- blocking(pool, move |conn| ModAdd::create(conn, &form)).await??;
+ blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
- let site_view = blocking(pool, move |conn| SiteView::read(conn)).await??;
+ let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
- let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
+ let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
let creator_index = admins
.iter()
.position(|r| r.id == site_view.creator_id)
- .unwrap();
+ .context(location_info!())?;
let creator_user = admins.remove(creator_index);
admins.insert(0, creator_user);
- let banned = blocking(pool, move |conn| UserView::banned(conn)).await??;
+ let banned = blocking(context.pool(), move |conn| UserView::banned(conn)).await??;
Ok(GetSiteResponse {
site: Some(site_view),
admins,
banned,
online: 0,
+ version: version::VERSION.to_string(),
+ my_user: Some(user),
+ federated_instances: Settings::get().get_allowed_instances(),
})
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<GetSiteConfig> {
+impl Perform for GetSiteConfig {
type Response = GetSiteConfigResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<GetSiteConfigResponse, LemmyError> {
- let data: &GetSiteConfig = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let data: &GetSiteConfig = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// Only let admins read this
- let admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
- let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect();
-
- if !admin_ids.contains(&user_id) {
- return Err(APIError::err("not_an_admin").into());
- }
+ is_admin(context.pool(), user.id).await?;
let config_hjson = Settings::read_config_file()?;
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<SaveSiteConfig> {
+impl Perform for SaveSiteConfig {
type Response = GetSiteConfigResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<GetSiteConfigResponse, LemmyError> {
- let data: &SaveSiteConfig = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let data: &SaveSiteConfig = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// Only let admins read this
- let admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
+ let admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect();
- if !admin_ids.contains(&user_id) {
+ if !admin_ids.contains(&user.id) {
return Err(APIError::err("not_an_admin").into());
}
use crate::{
- api::{claims::Claims, APIError, Oper, Perform},
+ api::{
+ check_slurs,
+ claims::Claims,
+ get_user_from_jwt,
+ get_user_from_jwt_opt,
+ is_admin,
+ APIError,
+ Perform,
+ },
apub::ApubObjectType,
blocking,
+ captcha_espeak_wav_base64,
websocket::{
- server::{JoinUserRoom, SendAllMessage, SendUserRoomMessage},
+ server::{CaptchaItem, CheckCaptcha, JoinUserRoom, SendAllMessage, SendUserRoomMessage},
UserOperation,
- WebsocketInfo,
},
- DbPool,
+ ConnectionId,
+ LemmyContext,
LemmyError,
};
+use actix_web::web::Data;
+use anyhow::Context;
use bcrypt::verify;
+use captcha::{gen, Difficulty};
+use chrono::Duration;
use lemmy_db::{
comment::*,
comment_view::*,
community::*,
community_view::*,
+ diesel_option_overwrite,
moderator::*,
naive_now,
password_reset_request::*,
use lemmy_utils::{
generate_actor_keypair,
generate_random_string,
+ is_valid_preferred_username,
is_valid_username,
+ location_info,
make_apub_endpoint,
naive_from_unix,
remove_slurs,
send_email,
settings::Settings,
- slur_check,
- slurs_vec_to_str,
EndpointType,
};
use log::error;
pub password_verify: String,
pub admin: bool,
pub show_nsfw: bool,
+ pub captcha_uuid: Option<String>,
+ pub captcha_answer: Option<String>,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct GetCaptcha {}
+
+#[derive(Serialize, Deserialize)]
+pub struct GetCaptchaResponse {
+ ok: Option<CaptchaResponse>,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct CaptchaResponse {
+ png: String, // A Base64 encoded png
+ wav: Option<String>, // A Base64 encoded wav audio
+ uuid: String,
}
#[derive(Serialize, Deserialize)]
default_listing_type: i16,
lang: String,
avatar: Option<String>,
+ banner: Option<String>,
+ preferred_username: Option<String>,
email: Option<String>,
+ bio: Option<String>,
matrix_user_id: Option<String>,
new_password: Option<String>,
new_password_verify: Option<String>,
moderates: Vec<CommunityModeratorView>,
comments: Vec<CommentView>,
posts: Vec<PostView>,
- admins: Vec<UserView>,
}
#[derive(Serialize, Deserialize)]
pub struct BanUser {
user_id: i32,
ban: bool,
+ remove_data: Option<bool>,
reason: Option<String>,
expires: Option<i64>,
auth: String,
}
#[derive(Serialize, Deserialize)]
-pub struct EditUserMention {
+pub struct MarkUserMentionAsRead {
user_mention_id: i32,
- read: Option<bool>,
+ read: bool,
auth: String,
}
#[derive(Serialize, Deserialize)]
pub struct EditPrivateMessage {
edit_id: i32,
- content: Option<String>,
- deleted: Option<bool>,
- read: Option<bool>,
+ content: String,
+ auth: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct DeletePrivateMessage {
+ edit_id: i32,
+ deleted: bool,
+ auth: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct MarkPrivateMessageAsRead {
+ edit_id: i32,
+ read: bool,
auth: String,
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<Login> {
+impl Perform for Login {
type Response = LoginResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<LoginResponse, LemmyError> {
- let data: &Login = &self.data;
+ let data: &Login = &self;
// Fetch that username / email
let username_or_email = data.username_or_email.clone();
- let user = match blocking(pool, move |conn| {
- Claims::find_by_email_or_username(conn, &username_or_email)
+ let user = match blocking(context.pool(), move |conn| {
+ User_::find_by_email_or_username(conn, &username_or_email)
})
.await?
{
// Return the jwt
Ok(LoginResponse {
- jwt: Claims::jwt(user, Settings::get().hostname),
+ jwt: Claims::jwt(user, Settings::get().hostname)?,
})
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<Register> {
+impl Perform for Register {
type Response = LoginResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<LoginResponse, LemmyError> {
- let data: &Register = &self.data;
+ let data: &Register = &self;
// Make sure site has open registration
- if let Ok(site) = blocking(pool, move |conn| SiteView::read(conn)).await? {
+ if let Ok(site) = blocking(context.pool(), move |conn| SiteView::read(conn)).await? {
let site: SiteView = site;
if !site.open_registration {
return Err(APIError::err("registration_closed").into());
return Err(APIError::err("passwords_dont_match").into());
}
- if let Err(slurs) = slur_check(&data.username) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
+ // If its not the admin, check the captcha
+ if !data.admin && Settings::get().captcha.enabled {
+ let check = context
+ .chat_server()
+ .send(CheckCaptcha {
+ uuid: data
+ .captcha_uuid
+ .to_owned()
+ .unwrap_or_else(|| "".to_string()),
+ answer: data
+ .captcha_answer
+ .to_owned()
+ .unwrap_or_else(|| "".to_string()),
+ })
+ .await?;
+ if !check {
+ return Err(APIError::err("captcha_incorrect").into());
+ }
}
+ check_slurs(&data.username)?;
+
// Make sure there are no admins
- let any_admins = blocking(pool, move |conn| {
+ let any_admins = blocking(context.pool(), move |conn| {
UserView::admins(conn).map(|a| a.is_empty())
})
.await??;
// Register the new user
let user_form = UserForm {
name: data.username.to_owned(),
- email: data.email.to_owned(),
+ email: Some(data.email.to_owned()),
matrix_user_id: None,
avatar: None,
+ banner: None,
password_encrypted: data.password.to_owned(),
preferred_username: None,
updated: None,
banned: false,
show_nsfw: data.show_nsfw,
theme: "darkly".into(),
- default_sort_type: SortType::Hot as i16,
+ default_sort_type: SortType::Active as i16,
default_listing_type: ListingType::Subscribed as i16,
lang: "browser".into(),
show_avatars: true,
};
// Create the user
- let inserted_user = match blocking(pool, move |conn| User_::register(conn, &user_form)).await? {
+ let inserted_user = match blocking(context.pool(), move |conn| {
+ User_::register(conn, &user_form)
+ })
+ .await?
+ {
Ok(user) => user,
Err(e) => {
let err_type = if e.to_string()
let main_community_keypair = generate_actor_keypair()?;
// Create the main community if it doesn't exist
- let main_community = match blocking(pool, move |conn| Community::read(conn, 2)).await? {
+ let main_community = match blocking(context.pool(), move |conn| Community::read(conn, 2))
+ .await?
+ {
Ok(c) => c,
Err(_e) => {
let default_community_name = "main";
public_key: Some(main_community_keypair.public_key),
last_refreshed_at: None,
published: None,
+ icon: None,
+ banner: None,
};
- blocking(pool, move |conn| Community::create(conn, &community_form)).await??
+ blocking(context.pool(), move |conn| {
+ Community::create(conn, &community_form)
+ })
+ .await??
}
};
};
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
- if blocking(pool, follow).await?.is_err() {
+ if blocking(context.pool(), follow).await?.is_err() {
return Err(APIError::err("community_follower_already_exists").into());
};
};
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
- if blocking(pool, join).await?.is_err() {
+ if blocking(context.pool(), join).await?.is_err() {
return Err(APIError::err("community_moderator_already_exists").into());
}
}
// Return the jwt
Ok(LoginResponse {
- jwt: Claims::jwt(inserted_user, Settings::get().hostname),
+ jwt: Claims::jwt(inserted_user, Settings::get().hostname)?,
})
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<SaveUserSettings> {
- type Response = LoginResponse;
+impl Perform for GetCaptcha {
+ type Response = GetCaptchaResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
- ) -> Result<LoginResponse, LemmyError> {
- let data: &SaveUserSettings = &self.data;
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<Self::Response, LemmyError> {
+ let captcha_settings = Settings::get().captcha;
+
+ if !captcha_settings.enabled {
+ return Ok(GetCaptchaResponse { ok: None });
+ }
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
+ let captcha = match captcha_settings.difficulty.as_str() {
+ "easy" => gen(Difficulty::Easy),
+ "medium" => gen(Difficulty::Medium),
+ "hard" => gen(Difficulty::Hard),
+ _ => gen(Difficulty::Medium),
};
- let user_id = claims.id;
+ let answer = captcha.chars_as_string();
- let read_user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
+ let png_byte_array = captcha.as_png().expect("failed to generate captcha");
- let email = match &data.email {
- Some(email) => Some(email.to_owned()),
- None => read_user.email,
+ let png = base64::encode(png_byte_array);
+
+ let uuid = uuid::Uuid::new_v4().to_string();
+
+ let wav = captcha_espeak_wav_base64(&answer).ok();
+
+ let captcha_item = CaptchaItem {
+ answer,
+ uuid: uuid.to_owned(),
+ expires: naive_now() + Duration::minutes(10), // expires in 10 minutes
+ };
+
+ // Stores the captcha item on the queue
+ context.chat_server().do_send(captcha_item);
+
+ Ok(GetCaptchaResponse {
+ ok: Some(CaptchaResponse { png, uuid, wav }),
+ })
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for SaveUserSettings {
+ type Response = LoginResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<LoginResponse, LemmyError> {
+ let data: &SaveUserSettings = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
+ let user_id = user.id;
+ let read_user = blocking(context.pool(), move |conn| User_::read(conn, user_id)).await??;
+
+ let bio = match &data.bio {
+ Some(bio) => {
+ if bio.chars().count() <= 300 {
+ Some(bio.to_owned())
+ } else {
+ return Err(APIError::err("bio_length_overflow").into());
+ }
+ }
+ None => read_user.bio,
};
- let avatar = match &data.avatar {
- Some(avatar) => Some(avatar.to_owned()),
- None => read_user.avatar,
+ let avatar = diesel_option_overwrite(&data.avatar);
+ let banner = diesel_option_overwrite(&data.banner);
+ let email = diesel_option_overwrite(&data.email);
+
+ // The DB constraint should stop too many characters
+ let preferred_username = match &data.preferred_username {
+ Some(preferred_username) => {
+ if !is_valid_preferred_username(preferred_username.trim()) {
+ return Err(APIError::err("invalid_username").into());
+ }
+ Some(preferred_username.trim().to_string())
+ }
+ None => read_user.preferred_username,
};
let password_encrypted = match &data.new_password {
return Err(APIError::err("password_incorrect").into());
}
let new_password = new_password.to_owned();
- let user = blocking(pool, move |conn| {
+ let user = blocking(context.pool(), move |conn| {
User_::update_password(conn, user_id, &new_password)
})
.await??;
email,
matrix_user_id: data.matrix_user_id.to_owned(),
avatar,
+ banner,
password_encrypted,
- preferred_username: read_user.preferred_username,
+ preferred_username,
updated: Some(naive_now()),
admin: read_user.admin,
banned: read_user.banned,
show_avatars: data.show_avatars,
send_notifications_to_email: data.send_notifications_to_email,
actor_id: read_user.actor_id,
- bio: read_user.bio,
+ bio,
local: read_user.local,
private_key: read_user.private_key,
public_key: read_user.public_key,
last_refreshed_at: None,
};
- let res = blocking(pool, move |conn| User_::update(conn, user_id, &user_form)).await?;
+ let res = blocking(context.pool(), move |conn| {
+ User_::update(conn, user_id, &user_form)
+ })
+ .await?;
let updated_user: User_ = match res {
Ok(user) => user,
Err(e) => {
// Return the jwt
Ok(LoginResponse {
- jwt: Claims::jwt(updated_user, Settings::get().hostname),
+ jwt: Claims::jwt(updated_user, Settings::get().hostname)?,
})
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<GetUserDetails> {
+impl Perform for GetUserDetails {
type Response = GetUserDetailsResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<GetUserDetailsResponse, LemmyError> {
- let data: &GetUserDetails = &self.data;
-
- let user_claims: Option<Claims> = match &data.auth {
- Some(auth) => match Claims::decode(&auth) {
- Ok(claims) => Some(claims.claims),
- Err(_e) => None,
- },
- None => None,
- };
-
- let user_id = match &user_claims {
- Some(claims) => Some(claims.id),
- None => None,
- };
+ let data: &GetUserDetails = &self;
+ let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
- let show_nsfw = match &user_claims {
- Some(claims) => claims.show_nsfw,
+ let show_nsfw = match &user {
+ Some(user) => user.show_nsfw,
None => false,
};
let user_details_id = match data.user_id {
Some(id) => id,
None => {
- let user = blocking(pool, move |conn| User_::read_from_name(conn, &username)).await?;
+ let user = blocking(context.pool(), move |conn| {
+ User_::read_from_name(conn, &username)
+ })
+ .await?;
match user {
Ok(user) => user.id,
Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
}
};
- let mut user_view = blocking(pool, move |conn| UserView::read(conn, user_details_id)).await??;
+ let user_view = blocking(context.pool(), move |conn| {
+ UserView::get_user_secure(conn, user_details_id)
+ })
+ .await??;
let page = data.page;
let limit = data.limit;
let saved_only = data.saved_only;
let community_id = data.community_id;
- let (posts, comments) = blocking(pool, move |conn| {
+ let user_id = user.map(|u| u.id);
+ let (posts, comments) = blocking(context.pool(), move |conn| {
let mut posts_query = PostQueryBuilder::create(conn)
.sort(&sort)
.show_nsfw(show_nsfw)
})
.await??;
- let follows = blocking(pool, move |conn| {
+ let follows = blocking(context.pool(), move |conn| {
CommunityFollowerView::for_user(conn, user_details_id)
})
.await??;
- let moderates = blocking(pool, move |conn| {
+ let moderates = blocking(context.pool(), move |conn| {
CommunityModeratorView::for_user(conn, user_details_id)
})
.await??;
- let site_creator_id =
- blocking(pool, move |conn| Site::read(conn, 1).map(|s| s.creator_id)).await??;
-
- let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
- let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
- let creator_user = admins.remove(creator_index);
- admins.insert(0, creator_user);
-
- // If its not the same user, remove the email
- if let Some(user_id) = user_id {
- if user_details_id != user_id {
- user_view.email = None;
- }
- } else {
- user_view.email = None;
- }
-
// Return the jwt
Ok(GetUserDetailsResponse {
user: user_view,
moderates,
comments,
posts,
- admins,
})
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<AddAdmin> {
+impl Perform for AddAdmin {
type Response = AddAdminResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<AddAdminResponse, LemmyError> {
- let data: &AddAdmin = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let data: &AddAdmin = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// Make sure user is an admin
- let is_admin = move |conn: &'_ _| UserView::read(conn, user_id).map(|u| u.admin);
- if !blocking(pool, is_admin).await?? {
- return Err(APIError::err("not_an_admin").into());
- }
+ is_admin(context.pool(), user.id).await?;
let added = data.added;
let added_user_id = data.user_id;
let add_admin = move |conn: &'_ _| User_::add_admin(conn, added_user_id, added);
- if blocking(pool, add_admin).await?.is_err() {
+ if blocking(context.pool(), add_admin).await?.is_err() {
return Err(APIError::err("couldnt_update_user").into());
}
// Mod tables
let form = ModAddForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
other_user_id: data.user_id,
removed: Some(!data.added),
};
- blocking(pool, move |conn| ModAdd::create(conn, &form)).await??;
+ blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
- let site_creator_id =
- blocking(pool, move |conn| Site::read(conn, 1).map(|s| s.creator_id)).await??;
+ let site_creator_id = blocking(context.pool(), move |conn| {
+ Site::read(conn, 1).map(|s| s.creator_id)
+ })
+ .await??;
- let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
- let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
+ let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
+ let creator_index = admins
+ .iter()
+ .position(|r| r.id == site_creator_id)
+ .context(location_info!())?;
let creator_user = admins.remove(creator_index);
admins.insert(0, creator_user);
let res = AddAdminResponse { admins };
- if let Some(ws) = websocket_info {
- ws.chatserver.do_send(SendAllMessage {
- op: UserOperation::AddAdmin,
- response: res.clone(),
- my_id: ws.id,
- });
- }
+ context.chat_server().do_send(SendAllMessage {
+ op: UserOperation::AddAdmin,
+ response: res.clone(),
+ websocket_id,
+ });
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<BanUser> {
+impl Perform for BanUser {
type Response = BanUserResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<BanUserResponse, LemmyError> {
- let data: &BanUser = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let data: &BanUser = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// Make sure user is an admin
- let is_admin = move |conn: &'_ _| UserView::read(conn, user_id).map(|u| u.admin);
- if !blocking(pool, is_admin).await?? {
- return Err(APIError::err("not_an_admin").into());
- }
+ is_admin(context.pool(), user.id).await?;
let ban = data.ban;
let banned_user_id = data.user_id;
let ban_user = move |conn: &'_ _| User_::ban_user(conn, banned_user_id, ban);
- if blocking(pool, ban_user).await?.is_err() {
+ if blocking(context.pool(), ban_user).await?.is_err() {
return Err(APIError::err("couldnt_update_user").into());
}
+ // Remove their data if that's desired
+ if let Some(remove_data) = data.remove_data {
+ // Posts
+ blocking(context.pool(), move |conn: &'_ _| {
+ Post::update_removed_for_creator(conn, banned_user_id, None, remove_data)
+ })
+ .await??;
+
+ // Communities
+ blocking(context.pool(), move |conn: &'_ _| {
+ Community::update_removed_for_creator(conn, banned_user_id, remove_data)
+ })
+ .await??;
+
+ // Comments
+ blocking(context.pool(), move |conn: &'_ _| {
+ Comment::update_removed_for_creator(conn, banned_user_id, remove_data)
+ })
+ .await??;
+ }
+
// Mod tables
let expires = match data.expires {
Some(time) => Some(naive_from_unix(time)),
};
let form = ModBanForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
other_user_id: data.user_id,
reason: data.reason.to_owned(),
banned: Some(data.ban),
expires,
};
- blocking(pool, move |conn| ModBan::create(conn, &form)).await??;
+ blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
let user_id = data.user_id;
- let user_view = blocking(pool, move |conn| UserView::read(conn, user_id)).await??;
+ let user_view = blocking(context.pool(), move |conn| {
+ UserView::get_user_secure(conn, user_id)
+ })
+ .await??;
let res = BanUserResponse {
user: user_view,
banned: data.ban,
};
- if let Some(ws) = websocket_info {
- ws.chatserver.do_send(SendAllMessage {
- op: UserOperation::BanUser,
- response: res.clone(),
- my_id: ws.id,
- });
- }
+ context.chat_server().do_send(SendAllMessage {
+ op: UserOperation::BanUser,
+ response: res.clone(),
+ websocket_id,
+ });
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<GetReplies> {
+impl Perform for GetReplies {
type Response = GetRepliesResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<GetRepliesResponse, LemmyError> {
- let data: &GetReplies = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let data: &GetReplies = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let sort = SortType::from_str(&data.sort)?;
let page = data.page;
let limit = data.limit;
let unread_only = data.unread_only;
- let replies = blocking(pool, move |conn| {
+ let user_id = user.id;
+ let replies = blocking(context.pool(), move |conn| {
ReplyQueryBuilder::create(conn, user_id)
.sort(&sort)
.unread_only(unread_only)
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<GetUserMentions> {
+impl Perform for GetUserMentions {
type Response = GetUserMentionsResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<GetUserMentionsResponse, LemmyError> {
- let data: &GetUserMentions = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let data: &GetUserMentions = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let sort = SortType::from_str(&data.sort)?;
let page = data.page;
let limit = data.limit;
let unread_only = data.unread_only;
- let mentions = blocking(pool, move |conn| {
+ let user_id = user.id;
+ let mentions = blocking(context.pool(), move |conn| {
UserMentionQueryBuilder::create(conn, user_id)
.sort(&sort)
.unread_only(unread_only)
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<EditUserMention> {
+impl Perform for MarkUserMentionAsRead {
type Response = UserMentionResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<UserMentionResponse, LemmyError> {
- let data: &EditUserMention = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let data: &MarkUserMentionAsRead = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let user_mention_id = data.user_mention_id;
- let read_user_mention =
- blocking(pool, move |conn| UserMention::read(conn, user_mention_id)).await??;
+ let read_user_mention = blocking(context.pool(), move |conn| {
+ UserMention::read(conn, user_mention_id)
+ })
+ .await??;
- if user_id != read_user_mention.recipient_id {
+ if user.id != read_user_mention.recipient_id {
return Err(APIError::err("couldnt_update_comment").into());
}
- let user_mention_form = UserMentionForm {
- recipient_id: read_user_mention.recipient_id,
- comment_id: read_user_mention.comment_id,
- read: data.read.to_owned(),
- };
-
let user_mention_id = read_user_mention.id;
- let update_mention =
- move |conn: &'_ _| UserMention::update(conn, user_mention_id, &user_mention_form);
- if blocking(pool, update_mention).await?.is_err() {
+ let read = data.read;
+ let update_mention = move |conn: &'_ _| UserMention::update_read(conn, user_mention_id, read);
+ if blocking(context.pool(), update_mention).await?.is_err() {
return Err(APIError::err("couldnt_update_comment").into());
};
let user_mention_id = read_user_mention.id;
- let user_mention_view = blocking(pool, move |conn| {
+ let user_id = user.id;
+ let user_mention_view = blocking(context.pool(), move |conn| {
UserMentionView::read(conn, user_mention_id, user_id)
})
.await??;
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<MarkAllAsRead> {
+impl Perform for MarkAllAsRead {
type Response = GetRepliesResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<GetRepliesResponse, LemmyError> {
- let data: &MarkAllAsRead = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let data: &MarkAllAsRead = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
- let replies = blocking(pool, move |conn| {
+ let user_id = user.id;
+ let replies = blocking(context.pool(), move |conn| {
ReplyQueryBuilder::create(conn, user_id)
.unread_only(true)
.page(1)
.await??;
// TODO: this should probably be a bulk operation
+ // Not easy to do as a bulk operation,
+ // because recipient_id isn't in the comment table
for reply in &replies {
let reply_id = reply.id;
- let mark_as_read = move |conn: &'_ _| Comment::mark_as_read(conn, reply_id);
- if blocking(pool, mark_as_read).await?.is_err() {
+ let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
+ if blocking(context.pool(), mark_as_read).await?.is_err() {
return Err(APIError::err("couldnt_update_comment").into());
}
}
- // Mentions
- let mentions = blocking(pool, move |conn| {
- UserMentionQueryBuilder::create(conn, user_id)
- .unread_only(true)
- .page(1)
- .limit(999)
- .list()
- })
- .await??;
-
- // TODO: this should probably be a bulk operation
- for mention in &mentions {
- let mention_form = UserMentionForm {
- recipient_id: mention.to_owned().recipient_id,
- comment_id: mention.to_owned().id,
- read: Some(true),
- };
-
- let user_mention_id = mention.user_mention_id;
- let update_mention =
- move |conn: &'_ _| UserMention::update(conn, user_mention_id, &mention_form);
- if blocking(pool, update_mention).await?.is_err() {
- return Err(APIError::err("couldnt_update_comment").into());
- }
+ // Mark all user mentions as read
+ let update_user_mentions = move |conn: &'_ _| UserMention::mark_all_as_read(conn, user_id);
+ if blocking(context.pool(), update_user_mentions)
+ .await?
+ .is_err()
+ {
+ return Err(APIError::err("couldnt_update_comment").into());
}
- // messages
- let messages = blocking(pool, move |conn| {
- PrivateMessageQueryBuilder::create(conn, user_id)
- .page(1)
- .limit(999)
- .unread_only(true)
- .list()
- })
- .await??;
-
- // TODO: this should probably be a bulk operation
- for message in &messages {
- let private_message_form = PrivateMessageForm {
- content: message.to_owned().content,
- creator_id: message.to_owned().creator_id,
- recipient_id: message.to_owned().recipient_id,
- deleted: None,
- read: Some(true),
- updated: None,
- ap_id: message.to_owned().ap_id,
- local: message.local,
- published: None,
- };
-
- let message_id = message.id;
- let update_pm =
- move |conn: &'_ _| PrivateMessage::update(conn, message_id, &private_message_form);
- if blocking(pool, update_pm).await?.is_err() {
- return Err(APIError::err("couldnt_update_private_message").into());
- }
+ // Mark all private_messages as read
+ let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, user_id);
+ if blocking(context.pool(), update_pm).await?.is_err() {
+ return Err(APIError::err("couldnt_update_private_message").into());
}
Ok(GetRepliesResponse { replies: vec![] })
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<DeleteAccount> {
+impl Perform for DeleteAccount {
type Response = LoginResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<LoginResponse, LemmyError> {
- let data: &DeleteAccount = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
-
- let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
+ let data: &DeleteAccount = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// Verify the password
let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
}
// Comments
- let comments = blocking(pool, move |conn| {
- CommentQueryBuilder::create(conn)
- .for_creator_id(user_id)
- .limit(std::i64::MAX)
- .list()
- })
- .await??;
-
- // TODO: this should probably be a bulk operation
- for comment in &comments {
- let comment_id = comment.id;
- let permadelete = move |conn: &'_ _| Comment::permadelete(conn, comment_id);
- if blocking(pool, permadelete).await?.is_err() {
- return Err(APIError::err("couldnt_update_comment").into());
- }
+ let user_id = user.id;
+ let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, user_id);
+ if blocking(context.pool(), permadelete).await?.is_err() {
+ return Err(APIError::err("couldnt_update_comment").into());
}
// Posts
- let posts = blocking(pool, move |conn| {
- PostQueryBuilder::create(conn)
- .sort(&SortType::New)
- .for_creator_id(user_id)
- .limit(std::i64::MAX)
- .list()
- })
- .await??;
-
- // TODO: this should probably be a bulk operation
- for post in &posts {
- let post_id = post.id;
- let permadelete = move |conn: &'_ _| Post::permadelete(conn, post_id);
- if blocking(pool, permadelete).await?.is_err() {
- return Err(APIError::err("couldnt_update_post").into());
- }
+ let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, user_id);
+ if blocking(context.pool(), permadelete).await?.is_err() {
+ return Err(APIError::err("couldnt_update_post").into());
}
Ok(LoginResponse {
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<PasswordReset> {
+impl Perform for PasswordReset {
type Response = PasswordResetResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<PasswordResetResponse, LemmyError> {
- let data: &PasswordReset = &self.data;
+ let data: &PasswordReset = &self;
// Fetch that email
let email = data.email.clone();
- let user = match blocking(pool, move |conn| User_::find_by_email(conn, &email)).await? {
+ let user = match blocking(context.pool(), move |conn| {
+ User_::find_by_email(conn, &email)
+ })
+ .await?
+ {
Ok(user) => user,
Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
};
// Insert the row
let token2 = token.clone();
let user_id = user.id;
- blocking(pool, move |conn| {
+ blocking(context.pool(), move |conn| {
PasswordResetRequest::create_token(conn, user_id, &token2)
})
.await??;
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<PasswordChange> {
+impl Perform for PasswordChange {
type Response = LoginResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
) -> Result<LoginResponse, LemmyError> {
- let data: &PasswordChange = &self.data;
+ let data: &PasswordChange = &self;
// Fetch the user_id from the token
let token = data.token.clone();
- let user_id = blocking(pool, move |conn| {
+ let user_id = blocking(context.pool(), move |conn| {
PasswordResetRequest::read_from_token(conn, &token).map(|p| p.user_id)
})
.await??;
// Update the user with the new password
let password = data.password.clone();
- let updated_user = match blocking(pool, move |conn| {
+ let updated_user = match blocking(context.pool(), move |conn| {
User_::update_password(conn, user_id, &password)
})
.await?
// Return the jwt
Ok(LoginResponse {
- jwt: Claims::jwt(updated_user, Settings::get().hostname),
+ jwt: Claims::jwt(updated_user, Settings::get().hostname)?,
})
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<CreatePrivateMessage> {
+impl Perform for CreatePrivateMessage {
type Response = PrivateMessageResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<PrivateMessageResponse, LemmyError> {
- let data: &CreatePrivateMessage = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let data: &CreatePrivateMessage = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let hostname = &format!("https://{}", Settings::get().hostname);
- // Check for a site ban
- let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
- if user.banned {
- return Err(APIError::err("site_ban").into());
- }
-
let content_slurs_removed = remove_slurs(&data.content.to_owned());
let private_message_form = PrivateMessageForm {
content: content_slurs_removed.to_owned(),
- creator_id: user_id,
+ creator_id: user.id,
recipient_id: data.recipient_id,
deleted: None,
read: None,
published: None,
};
- let inserted_private_message = match blocking(pool, move |conn| {
+ let inserted_private_message = match blocking(context.pool(), move |conn| {
PrivateMessage::create(conn, &private_message_form)
})
.await?
};
let inserted_private_message_id = inserted_private_message.id;
- let updated_private_message = match blocking(pool, move |conn| {
+ let updated_private_message = match blocking(context.pool(), move |conn| {
let apub_id = make_apub_endpoint(
EndpointType::PrivateMessage,
&inserted_private_message_id.to_string(),
Err(_e) => return Err(APIError::err("couldnt_create_private_message").into()),
};
- updated_private_message
- .send_create(&user, &self.client, pool)
- .await?;
+ updated_private_message.send_create(&user, context).await?;
// Send notifications to the recipient
let recipient_id = data.recipient_id;
- let recipient_user = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
+ let recipient_user =
+ blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
if recipient_user.send_notifications_to_email {
if let Some(email) = recipient_user.email {
let subject = &format!(
"{} - Private Message from {}",
Settings::get().hostname,
- claims.username
+ user.name,
);
let html = &format!(
"<h1>Private Message</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
- claims.username, &content_slurs_removed, hostname
+ user.name, &content_slurs_removed, hostname
);
match send_email(subject, &email, &recipient_user.name, html) {
Ok(_o) => _o,
}
}
- let message = blocking(pool, move |conn| {
+ let message = blocking(context.pool(), move |conn| {
PrivateMessageView::read(conn, inserted_private_message.id)
})
.await??;
let res = PrivateMessageResponse { message };
- if let Some(ws) = websocket_info {
- ws.chatserver.do_send(SendUserRoomMessage {
- op: UserOperation::CreatePrivateMessage,
- response: res.clone(),
- recipient_id: recipient_user.id,
- my_id: ws.id,
- });
- }
+ context.chat_server().do_send(SendUserRoomMessage {
+ op: UserOperation::CreatePrivateMessage,
+ response: res.clone(),
+ recipient_id,
+ websocket_id,
+ });
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<EditPrivateMessage> {
+impl Perform for EditPrivateMessage {
type Response = PrivateMessageResponse;
async fn perform(
&self,
- pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<PrivateMessageResponse, LemmyError> {
- let data: &EditPrivateMessage = &self.data;
+ let data: &EditPrivateMessage = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
+ // Checking permissions
+ let edit_id = data.edit_id;
+ let orig_private_message = blocking(context.pool(), move |conn| {
+ PrivateMessage::read(conn, edit_id)
+ })
+ .await??;
+ if user.id != orig_private_message.creator_id {
+ return Err(APIError::err("no_private_message_edit_allowed").into());
+ }
+
+ // Doing the update
+ let content_slurs_removed = remove_slurs(&data.content);
+ let edit_id = data.edit_id;
+ let updated_private_message = match blocking(context.pool(), move |conn| {
+ PrivateMessage::update_content(conn, edit_id, &content_slurs_removed)
+ })
+ .await?
+ {
+ Ok(private_message) => private_message,
+ Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
};
- let user_id = claims.id;
+ // Send the apub update
+ updated_private_message.send_update(&user, context).await?;
let edit_id = data.edit_id;
- let orig_private_message =
- blocking(pool, move |conn| PrivateMessage::read(conn, edit_id)).await??;
+ let message = blocking(context.pool(), move |conn| {
+ PrivateMessageView::read(conn, edit_id)
+ })
+ .await??;
+ let recipient_id = message.recipient_id;
- // Check for a site ban
- let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
- if user.banned {
- return Err(APIError::err("site_ban").into());
- }
+ let res = PrivateMessageResponse { message };
- // Check to make sure they are the creator (or the recipient marking as read
- if !(data.read.is_some() && orig_private_message.recipient_id.eq(&user_id)
- || orig_private_message.creator_id.eq(&user_id))
- {
- return Err(APIError::err("no_private_message_edit_allowed").into());
- }
+ context.chat_server().do_send(SendUserRoomMessage {
+ op: UserOperation::EditPrivateMessage,
+ response: res.clone(),
+ recipient_id,
+ websocket_id,
+ });
- let content_slurs_removed = match &data.content {
- Some(content) => remove_slurs(content),
- None => orig_private_message.content.clone(),
- };
+ Ok(res)
+ }
+}
- let private_message_form = {
- if data.read.is_some() {
- PrivateMessageForm {
- content: orig_private_message.content.to_owned(),
- creator_id: orig_private_message.creator_id,
- recipient_id: orig_private_message.recipient_id,
- read: data.read.to_owned(),
- updated: orig_private_message.updated,
- deleted: Some(orig_private_message.deleted),
- ap_id: orig_private_message.ap_id,
- local: orig_private_message.local,
- published: None,
- }
- } else {
- PrivateMessageForm {
- content: content_slurs_removed,
- creator_id: orig_private_message.creator_id,
- recipient_id: orig_private_message.recipient_id,
- deleted: data.deleted.to_owned(),
- read: Some(orig_private_message.read),
- updated: Some(naive_now()),
- ap_id: orig_private_message.ap_id,
- local: orig_private_message.local,
- published: None,
- }
- }
- };
+#[async_trait::async_trait(?Send)]
+impl Perform for DeletePrivateMessage {
+ type Response = PrivateMessageResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<PrivateMessageResponse, LemmyError> {
+ let data: &DeletePrivateMessage = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+
+ // Checking permissions
+ let edit_id = data.edit_id;
+ let orig_private_message = blocking(context.pool(), move |conn| {
+ PrivateMessage::read(conn, edit_id)
+ })
+ .await??;
+ if user.id != orig_private_message.creator_id {
+ return Err(APIError::err("no_private_message_edit_allowed").into());
+ }
+ // Doing the update
let edit_id = data.edit_id;
- let updated_private_message = match blocking(pool, move |conn| {
- PrivateMessage::update(conn, edit_id, &private_message_form)
+ let deleted = data.deleted;
+ let updated_private_message = match blocking(context.pool(), move |conn| {
+ PrivateMessage::update_deleted(conn, edit_id, deleted)
})
.await?
{
Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
};
- if data.read.is_none() {
- if let Some(deleted) = data.deleted.to_owned() {
- if deleted {
- updated_private_message
- .send_delete(&user, &self.client, pool)
- .await?;
- } else {
- updated_private_message
- .send_undo_delete(&user, &self.client, pool)
- .await?;
- }
- } else {
- updated_private_message
- .send_update(&user, &self.client, pool)
- .await?;
- }
+ // Send the apub update
+ if data.deleted {
+ updated_private_message.send_delete(&user, context).await?;
} else {
updated_private_message
- .send_update(&user, &self.client, pool)
+ .send_undo_delete(&user, context)
.await?;
}
let edit_id = data.edit_id;
- let message = blocking(pool, move |conn| PrivateMessageView::read(conn, edit_id)).await??;
+ let message = blocking(context.pool(), move |conn| {
+ PrivateMessageView::read(conn, edit_id)
+ })
+ .await??;
+ let recipient_id = message.recipient_id;
let res = PrivateMessageResponse { message };
- if let Some(ws) = websocket_info {
- ws.chatserver.do_send(SendUserRoomMessage {
- op: UserOperation::EditPrivateMessage,
- response: res.clone(),
- recipient_id: orig_private_message.recipient_id,
- my_id: ws.id,
- });
- }
+ context.chat_server().do_send(SendUserRoomMessage {
+ op: UserOperation::DeletePrivateMessage,
+ response: res.clone(),
+ recipient_id,
+ websocket_id,
+ });
Ok(res)
}
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<GetPrivateMessages> {
- type Response = PrivateMessagesResponse;
+impl Perform for MarkPrivateMessageAsRead {
+ type Response = PrivateMessageResponse;
async fn perform(
&self,
- pool: &DbPool,
- _websocket_info: Option<WebsocketInfo>,
- ) -> Result<PrivateMessagesResponse, LemmyError> {
- let data: &GetPrivateMessages = &self.data;
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<PrivateMessageResponse, LemmyError> {
+ let data: &MarkPrivateMessageAsRead = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
+ // Checking permissions
+ let edit_id = data.edit_id;
+ let orig_private_message = blocking(context.pool(), move |conn| {
+ PrivateMessage::read(conn, edit_id)
+ })
+ .await??;
+ if user.id != orig_private_message.recipient_id {
+ return Err(APIError::err("couldnt_update_private_message").into());
+ }
+
+ // Doing the update
+ let edit_id = data.edit_id;
+ let read = data.read;
+ match blocking(context.pool(), move |conn| {
+ PrivateMessage::update_read(conn, edit_id, read)
+ })
+ .await?
+ {
+ Ok(private_message) => private_message,
+ Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
};
- let user_id = claims.id;
+ // No need to send an apub update
+
+ let edit_id = data.edit_id;
+ let message = blocking(context.pool(), move |conn| {
+ PrivateMessageView::read(conn, edit_id)
+ })
+ .await??;
+ let recipient_id = message.recipient_id;
+
+ let res = PrivateMessageResponse { message };
+
+ context.chat_server().do_send(SendUserRoomMessage {
+ op: UserOperation::MarkPrivateMessageAsRead,
+ response: res.clone(),
+ recipient_id,
+ websocket_id,
+ });
+
+ Ok(res)
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+impl Perform for GetPrivateMessages {
+ type Response = PrivateMessagesResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<PrivateMessagesResponse, LemmyError> {
+ let data: &GetPrivateMessages = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+ let user_id = user.id;
let page = data.page;
let limit = data.limit;
let unread_only = data.unread_only;
- let messages = blocking(pool, move |conn| {
+ let messages = blocking(context.pool(), move |conn| {
PrivateMessageQueryBuilder::create(&conn, user_id)
.page(page)
.limit(limit)
}
#[async_trait::async_trait(?Send)]
-impl Perform for Oper<UserJoin> {
+impl Perform for UserJoin {
type Response = UserJoinResponse;
async fn perform(
&self,
- _pool: &DbPool,
- websocket_info: Option<WebsocketInfo>,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
) -> Result<UserJoinResponse, LemmyError> {
- let data: &UserJoin = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
+ let data: &UserJoin = &self;
+ let user = get_user_from_jwt(&data.auth, context.pool()).await?;
- let user_id = claims.id;
-
- if let Some(ws) = websocket_info {
- if let Some(id) = ws.id {
- ws.chatserver.do_send(JoinUserRoom { user_id, id });
- }
+ if let Some(ws_id) = websocket_id {
+ context.chat_server().do_send(JoinUserRoom {
+ user_id: user.id,
+ id: ws_id,
+ });
}
- Ok(UserJoinResponse { user_id })
+ Ok(UserJoinResponse { user_id: user.id })
}
}
use crate::{
apub::{
+ check_is_apub_id_valid,
community::do_announce,
extensions::signatures::sign,
insert_activity,
- is_apub_id_valid,
ActorType,
},
request::retry_custom,
- DbPool,
+ LemmyContext,
LemmyError,
};
-use activitystreams::{context, object::properties::ObjectProperties, public, Activity, Base};
+use activitystreams::base::AnyBase;
use actix_web::client::Client;
use lemmy_db::{community::Community, user::User_};
+use lemmy_utils::{get_apub_protocol_string, settings::Settings};
use log::debug;
-use serde::Serialize;
-use std::fmt::Debug;
-use url::Url;
+use url::{ParseError, Url};
+use uuid::Uuid;
-pub fn populate_object_props(
- props: &mut ObjectProperties,
- addressed_ccs: Vec<String>,
- object_id: &str,
-) -> Result<(), LemmyError> {
- props
- .set_context_xsd_any_uri(context())?
- // TODO: the activity needs a seperate id from the object
- .set_id(object_id)?
- // TODO: should to/cc go on the Create, or on the Post? or on both?
- // TODO: handle privacy on the receiving side (at least ignore anything thats not public)
- .set_to_xsd_any_uri(public())?
- .set_many_cc_xsd_any_uris(addressed_ccs)?;
- Ok(())
-}
-
-pub async fn send_activity_to_community<A>(
+pub async fn send_activity_to_community(
creator: &User_,
community: &Community,
- to: Vec<String>,
- activity: A,
- client: &Client,
- pool: &DbPool,
-) -> Result<(), LemmyError>
-where
- A: Activity + Base + Serialize + Debug + Clone + Send + 'static,
-{
- insert_activity(creator.id, activity.clone(), true, pool).await?;
+ to: Vec<Url>,
+ activity: AnyBase,
+ context: &LemmyContext,
+) -> Result<(), LemmyError> {
+ insert_activity(creator.id, activity.clone(), true, context.pool()).await?;
// if this is a local community, we need to do an announce from the community instead
if community.local {
- do_announce(activity, &community, creator, client, pool).await?;
+ do_announce(activity, &community, creator, context).await?;
} else {
- send_activity(client, &activity, creator, to).await?;
+ send_activity(context.client(), &activity, creator, to).await?;
}
Ok(())
}
/// Send an activity to a list of recipients, using the correct headers etc.
-pub async fn send_activity<A>(
+pub async fn send_activity(
client: &Client,
- activity: &A,
+ activity: &AnyBase,
actor: &dyn ActorType,
- to: Vec<String>,
-) -> Result<(), LemmyError>
-where
- A: Serialize,
-{
+ to: Vec<Url>,
+) -> Result<(), LemmyError> {
+ if !Settings::get().federation.enabled {
+ return Ok(());
+ }
+
let activity = serde_json::to_string(&activity)?;
debug!("Sending activitypub activity {} to {:?}", activity, to);
- for t in to {
- let to_url = Url::parse(&t)?;
- if !is_apub_id_valid(&to_url) {
- debug!("Not sending activity to {} (invalid or blocklisted)", t);
- continue;
- }
+ for to_url in to {
+ check_is_apub_id_valid(&to_url)?;
let res = retry_custom(|| async {
- let request = client.post(&t).header("Content-Type", "application/json");
+ let request = client
+ .post(to_url.as_str())
+ .header("Content-Type", "application/json");
match sign(request, actor, activity.clone()).await {
Ok(signed) => Ok(signed.send().await),
Ok(())
}
+
+pub(in crate::apub) fn generate_activity_id<T>(kind: T) -> Result<Url, ParseError>
+where
+ T: ToString,
+{
+ let id = format!(
+ "{}://{}/activities/{}/{}",
+ get_apub_protocol_string(),
+ Settings::get().hostname,
+ kind.to_string().to_lowercase(),
+ Uuid::new_v4()
+ );
+ Url::parse(&id)
+}
use crate::{
apub::{
- activities::{populate_object_props, send_activity_to_community},
+ activities::{generate_activity_id, send_activity_to_community},
+ check_actor_domain,
create_apub_response,
create_apub_tombstone_response,
create_tombstone,
fetch_webfinger_url,
fetcher::{
- get_or_fetch_and_insert_remote_comment,
- get_or_fetch_and_insert_remote_post,
- get_or_fetch_and_upsert_remote_user,
+ get_or_fetch_and_insert_comment,
+ get_or_fetch_and_insert_post,
+ get_or_fetch_and_upsert_user,
},
ActorType,
ApubLikeableType,
ToApub,
},
blocking,
- routes::DbPoolParam,
DbPool,
+ LemmyContext,
LemmyError,
};
use activitystreams::{
- activity::{Create, Delete, Dislike, Like, Remove, Undo, Update},
- context,
+ activity::{
+ kind::{CreateType, DeleteType, DislikeType, LikeType, RemoveType, UndoType, UpdateType},
+ Create,
+ Delete,
+ Dislike,
+ Like,
+ Remove,
+ Undo,
+ Update,
+ },
+ base::AnyBase,
link::Mention,
- object::{kind::NoteType, properties::ObjectProperties, Note},
+ object::{kind::NoteType, Note, Tombstone},
+ prelude::*,
+ public,
};
-use activitystreams_new::object::Tombstone;
-use actix_web::{body::Body, client::Client, web::Path, HttpResponse};
+use actix_web::{body::Body, web, web::Path, HttpResponse};
+use anyhow::Context;
use itertools::Itertools;
use lemmy_db::{
comment::{Comment, CommentForm},
user::User_,
Crud,
};
-use lemmy_utils::{convert_datetime, scrape_text_for_mentions, MentionData};
+use lemmy_utils::{
+ convert_datetime,
+ location_info,
+ remove_slurs,
+ scrape_text_for_mentions,
+ MentionData,
+};
use log::debug;
use serde::Deserialize;
+use serde_json::Error;
+use url::Url;
#[derive(Deserialize)]
pub struct CommentQuery {
/// Return the post json over HTTP.
pub async fn get_apub_comment(
info: Path<CommentQuery>,
- db: DbPoolParam,
+ context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, LemmyError> {
let id = info.comment_id.parse::<i32>()?;
- let comment = blocking(&db, move |conn| Comment::read(conn, id)).await??;
+ let comment = blocking(context.pool(), move |conn| Comment::read(conn, id)).await??;
if !comment.deleted {
- Ok(create_apub_response(&comment.to_apub(&db).await?))
+ Ok(create_apub_response(
+ &comment.to_apub(context.pool()).await?,
+ ))
} else {
Ok(create_apub_tombstone_response(&comment.to_tombstone()?))
}
type Response = Note;
async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
- let mut comment = Note::default();
- let oprops: &mut ObjectProperties = comment.as_mut();
+ let mut comment = Note::new();
let creator_id = self.creator_id;
let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
in_reply_to_vec.push(parent_comment.ap_id);
}
- oprops
+ comment
// Not needed when the Post is embedded in a collection (like for community outbox)
- .set_context_xsd_any_uri(context())?
- .set_id(self.ap_id.to_owned())?
- .set_published(convert_datetime(self.published))?
- .set_to_xsd_any_uri(community.actor_id)?
- .set_many_in_reply_to_xsd_any_uris(in_reply_to_vec)?
- .set_content_xsd_string(self.content.to_owned())?
- .set_attributed_to_xsd_any_uri(creator.actor_id)?;
+ .set_context(activitystreams::context())
+ .set_id(Url::parse(&self.ap_id)?)
+ .set_published(convert_datetime(self.published))
+ .set_to(community.actor_id)
+ .set_many_in_reply_tos(in_reply_to_vec)
+ .set_content(self.content.to_owned())
+ .set_attributed_to(creator.actor_id);
if let Some(u) = self.updated {
- oprops.set_updated(convert_datetime(u))?;
+ comment.set_updated(convert_datetime(u));
}
Ok(comment)
}
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
- create_tombstone(
- self.deleted,
- &self.ap_id,
- self.updated,
- NoteType.to_string(),
- )
+ create_tombstone(self.deleted, &self.ap_id, self.updated, NoteType::Note)
}
}
/// Parse an ActivityPub note received from another instance into a Lemmy comment
async fn from_apub(
note: &Note,
- client: &Client,
- pool: &DbPool,
+ context: &LemmyContext,
+ expected_domain: Option<Url>,
) -> Result<CommentForm, LemmyError> {
- let oprops = ¬e.object_props;
- let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string();
-
- let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, client, pool).await?;
-
- let mut in_reply_tos = oprops.get_many_in_reply_to_xsd_any_uris().unwrap();
- let post_ap_id = in_reply_tos.next().unwrap().to_string();
+ let creator_actor_id = ¬e
+ .attributed_to()
+ .context(location_info!())?
+ .as_single_xsd_any_uri()
+ .context(location_info!())?;
+
+ let creator = get_or_fetch_and_upsert_user(creator_actor_id, context).await?;
+
+ let mut in_reply_tos = note
+ .in_reply_to()
+ .as_ref()
+ .context(location_info!())?
+ .as_many()
+ .context(location_info!())?
+ .iter()
+ .map(|i| i.as_xsd_any_uri().context(""));
+ let post_ap_id = in_reply_tos.next().context(location_info!())??;
// This post, or the parent comment might not yet exist on this server yet, fetch them.
- let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?;
+ let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
// The 2nd item, if it exists, is the parent comment apub_id
// For deeply nested comments, FromApub automatically gets called recursively
let parent_id: Option<i32> = match in_reply_tos.next() {
Some(parent_comment_uri) => {
- let parent_comment_ap_id = &parent_comment_uri.to_string();
+ let parent_comment_ap_id = &parent_comment_uri?;
let parent_comment =
- get_or_fetch_and_insert_remote_comment(&parent_comment_ap_id, client, pool).await?;
+ get_or_fetch_and_insert_comment(&parent_comment_ap_id, context).await?;
Some(parent_comment.id)
}
None => None,
};
+ let content = note
+ .content()
+ .context(location_info!())?
+ .as_single_xsd_string()
+ .context(location_info!())?
+ .to_string();
+ let content_slurs_removed = remove_slurs(&content);
Ok(CommentForm {
creator_id: creator.id,
post_id: post.id,
parent_id,
- content: oprops
- .get_content_xsd_string()
- .map(|c| c.to_string())
- .unwrap(),
+ content: content_slurs_removed,
removed: None,
read: None,
- published: oprops
- .get_published()
- .map(|u| u.as_ref().to_owned().naive_local()),
- updated: oprops
- .get_updated()
- .map(|u| u.as_ref().to_owned().naive_local()),
+ published: note.published().map(|u| u.to_owned().naive_local()),
+ updated: note.updated().map(|u| u.to_owned().naive_local()),
deleted: None,
- ap_id: oprops.get_id().unwrap().to_string(),
+ ap_id: check_actor_domain(note, expected_domain)?,
local: false,
})
}
#[async_trait::async_trait(?Send)]
impl ApubObjectType for Comment {
/// Send out information about a newly created comment, to the followers of the community.
- async fn send_create(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let note = self.to_apub(pool).await?;
+ async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let note = self.to_apub(context.pool()).await?;
let post_id = self.post_id;
- let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
+ let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
- let maa =
- collect_non_local_mentions_and_addresses(&self.content, &community, client, pool).await?;
-
- let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut create = Create::new();
- populate_object_props(&mut create.object_props, maa.addressed_ccs, &id)?;
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
- // Set the mention tags
- create.object_props.set_many_tag_base_boxes(maa.tags)?;
+ let maa = collect_non_local_mentions_and_addresses(&self.content, &community, context).await?;
+ let mut create = Create::new(creator.actor_id.to_owned(), note.into_any_base()?);
create
- .create_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(note)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(CreateType::Create)?)
+ .set_to(public())
+ .set_many_ccs(maa.addressed_ccs.to_owned())
+ // Set the mention tags
+ .set_many_tags(maa.get_tags()?);
- send_activity_to_community(&creator, &community, maa.inboxes, create, client, pool).await?;
+ send_activity_to_community(
+ &creator,
+ &community,
+ maa.inboxes,
+ create.into_any_base()?,
+ context,
+ )
+ .await?;
Ok(())
}
/// Send out information about an edited post, to the followers of the community.
- async fn send_update(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let note = self.to_apub(pool).await?;
+ async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let note = self.to_apub(context.pool()).await?;
let post_id = self.post_id;
- let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
+ let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
- let maa =
- collect_non_local_mentions_and_addresses(&self.content, &community, client, pool).await?;
-
- let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut update = Update::new();
- populate_object_props(&mut update.object_props, maa.addressed_ccs, &id)?;
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
- // Set the mention tags
- update.object_props.set_many_tag_base_boxes(maa.tags)?;
+ let maa = collect_non_local_mentions_and_addresses(&self.content, &community, context).await?;
+ let mut update = Update::new(creator.actor_id.to_owned(), note.into_any_base()?);
update
- .update_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(note)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(UpdateType::Update)?)
+ .set_to(public())
+ .set_many_ccs(maa.addressed_ccs.to_owned())
+ // Set the mention tags
+ .set_many_tags(maa.get_tags()?);
- send_activity_to_community(&creator, &community, maa.inboxes, update, client, pool).await?;
+ send_activity_to_community(
+ &creator,
+ &community,
+ maa.inboxes,
+ update.into_any_base()?,
+ context,
+ )
+ .await?;
Ok(())
}
- async fn send_delete(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let note = self.to_apub(pool).await?;
+ async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let note = self.to_apub(context.pool()).await?;
let post_id = self.post_id;
- let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
+ let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
- let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut delete = Delete::default();
-
- populate_object_props(
- &mut delete.object_props,
- vec![community.get_followers_url()],
- &id,
- )?;
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
+ let mut delete = Delete::new(creator.actor_id.to_owned(), note.into_any_base()?);
delete
- .delete_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(note)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(DeleteType::Delete)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
send_activity_to_community(
&creator,
&community,
- vec![community.get_shared_inbox_url()],
- delete,
- client,
- pool,
+ vec![community.get_shared_inbox_url()?],
+ delete.into_any_base()?,
+ context,
)
.await?;
Ok(())
async fn send_undo_delete(
&self,
creator: &User_,
- client: &Client,
- pool: &DbPool,
+ context: &LemmyContext,
) -> Result<(), LemmyError> {
- let note = self.to_apub(pool).await?;
+ let note = self.to_apub(context.pool()).await?;
let post_id = self.post_id;
- let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
+ let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
// Generate a fake delete activity, with the correct object
- let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut delete = Delete::default();
-
- populate_object_props(
- &mut delete.object_props,
- vec![community.get_followers_url()],
- &id,
- )?;
-
+ let mut delete = Delete::new(creator.actor_id.to_owned(), note.into_any_base()?);
delete
- .delete_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(note)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(DeleteType::Delete)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
- // TODO
// Undo that fake activity
- let undo_id = format!("{}/undo/delete/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut undo = Undo::default();
-
- populate_object_props(
- &mut undo.object_props,
- vec![community.get_followers_url()],
- &undo_id,
- )?;
-
+ let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
undo
- .undo_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(delete)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(UndoType::Undo)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
send_activity_to_community(
&creator,
&community,
- vec![community.get_shared_inbox_url()],
- undo,
- client,
- pool,
+ vec![community.get_shared_inbox_url()?],
+ undo.into_any_base()?,
+ context,
)
.await?;
Ok(())
}
- async fn send_remove(
- &self,
- mod_: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let note = self.to_apub(pool).await?;
+ async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let note = self.to_apub(context.pool()).await?;
let post_id = self.post_id;
- let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
+ let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
- let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut remove = Remove::default();
-
- populate_object_props(
- &mut remove.object_props,
- vec![community.get_followers_url()],
- &id,
- )?;
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
+ let mut remove = Remove::new(mod_.actor_id.to_owned(), note.into_any_base()?);
remove
- .remove_props
- .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
- .set_object_base_box(note)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(RemoveType::Remove)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
send_activity_to_community(
&mod_,
&community,
- vec![community.get_shared_inbox_url()],
- remove,
- client,
- pool,
+ vec![community.get_shared_inbox_url()?],
+ remove.into_any_base()?,
+ context,
)
.await?;
Ok(())
}
- async fn send_undo_remove(
- &self,
- mod_: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let note = self.to_apub(pool).await?;
+ async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let note = self.to_apub(context.pool()).await?;
let post_id = self.post_id;
- let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
+ let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
// Generate a fake delete activity, with the correct object
- let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut remove = Remove::default();
-
- populate_object_props(
- &mut remove.object_props,
- vec![community.get_followers_url()],
- &id,
- )?;
-
+ let mut remove = Remove::new(mod_.actor_id.to_owned(), note.into_any_base()?);
remove
- .remove_props
- .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
- .set_object_base_box(note)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(RemoveType::Remove)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
// Undo that fake activity
- let undo_id = format!("{}/undo/remove/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut undo = Undo::default();
-
- populate_object_props(
- &mut undo.object_props,
- vec![community.get_followers_url()],
- &undo_id,
- )?;
-
+ let mut undo = Undo::new(mod_.actor_id.to_owned(), remove.into_any_base()?);
undo
- .undo_props
- .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
- .set_object_base_box(remove)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(UndoType::Undo)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
send_activity_to_community(
&mod_,
&community,
- vec![community.get_shared_inbox_url()],
- undo,
- client,
- pool,
+ vec![community.get_shared_inbox_url()?],
+ undo.into_any_base()?,
+ context,
)
.await?;
Ok(())
#[async_trait::async_trait(?Send)]
impl ApubLikeableType for Comment {
- async fn send_like(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let note = self.to_apub(pool).await?;
+ async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let note = self.to_apub(context.pool()).await?;
let post_id = self.post_id;
- let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
+ let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
- let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4());
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
- let mut like = Like::new();
- populate_object_props(
- &mut like.object_props,
- vec![community.get_followers_url()],
- &id,
- )?;
+ let mut like = Like::new(creator.actor_id.to_owned(), note.into_any_base()?);
like
- .like_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(note)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(LikeType::Like)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
send_activity_to_community(
&creator,
&community,
- vec![community.get_shared_inbox_url()],
- like,
- client,
- pool,
+ vec![community.get_shared_inbox_url()?],
+ like.into_any_base()?,
+ context,
)
.await?;
Ok(())
}
- async fn send_dislike(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let note = self.to_apub(pool).await?;
+ async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let note = self.to_apub(context.pool()).await?;
let post_id = self.post_id;
- let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
+ let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
- let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4());
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
- let mut dislike = Dislike::new();
- populate_object_props(
- &mut dislike.object_props,
- vec![community.get_followers_url()],
- &id,
- )?;
+ let mut dislike = Dislike::new(creator.actor_id.to_owned(), note.into_any_base()?);
dislike
- .dislike_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(note)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(DislikeType::Dislike)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
send_activity_to_community(
&creator,
&community,
- vec![community.get_shared_inbox_url()],
- dislike,
- client,
- pool,
+ vec![community.get_shared_inbox_url()?],
+ dislike.into_any_base()?,
+ context,
)
.await?;
Ok(())
async fn send_undo_like(
&self,
creator: &User_,
- client: &Client,
- pool: &DbPool,
+ context: &LemmyContext,
) -> Result<(), LemmyError> {
- let note = self.to_apub(pool).await?;
+ let note = self.to_apub(context.pool()).await?;
let post_id = self.post_id;
- let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
+ let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
- let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4());
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
- let mut like = Like::new();
- populate_object_props(
- &mut like.object_props,
- vec![community.get_followers_url()],
- &id,
- )?;
+ let mut like = Like::new(creator.actor_id.to_owned(), note.into_any_base()?);
like
- .like_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(note)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(DislikeType::Dislike)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
- // TODO
// Undo that fake activity
- let undo_id = format!("{}/undo/like/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut undo = Undo::default();
-
- populate_object_props(
- &mut undo.object_props,
- vec![community.get_followers_url()],
- &undo_id,
- )?;
-
+ let mut undo = Undo::new(creator.actor_id.to_owned(), like.into_any_base()?);
undo
- .undo_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(like)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(UndoType::Undo)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
send_activity_to_community(
&creator,
&community,
- vec![community.get_shared_inbox_url()],
- undo,
- client,
- pool,
+ vec![community.get_shared_inbox_url()?],
+ undo.into_any_base()?,
+ context,
)
.await?;
Ok(())
}
struct MentionsAndAddresses {
- addressed_ccs: Vec<String>,
- inboxes: Vec<String>,
+ addressed_ccs: Vec<Url>,
+ inboxes: Vec<Url>,
tags: Vec<Mention>,
}
+impl MentionsAndAddresses {
+ fn get_tags(&self) -> Result<Vec<AnyBase>, Error> {
+ self
+ .tags
+ .iter()
+ .map(|t| t.to_owned().into_any_base())
+ .collect::<Result<Vec<AnyBase>, Error>>()
+ }
+}
+
/// This takes a comment, and builds a list of to_addresses, inboxes,
/// and mention tags, so they know where to be sent to.
/// Addresses are the users / addresses that go in the cc field.
async fn collect_non_local_mentions_and_addresses(
content: &str,
community: &Community,
- client: &Client,
- pool: &DbPool,
+ context: &LemmyContext,
) -> Result<MentionsAndAddresses, LemmyError> {
- let mut addressed_ccs = vec![community.get_followers_url()];
+ let mut addressed_ccs = vec![community.get_followers_url()?];
// Add the mention tag
let mut tags = Vec::new();
.filter(|m| !m.is_local())
.collect::<Vec<MentionData>>();
- let mut mention_inboxes = Vec::new();
+ let mut mention_inboxes: Vec<Url> = Vec::new();
for mention in &mentions {
// TODO should it be fetching it every time?
- if let Ok(actor_id) = fetch_webfinger_url(mention, client).await {
+ if let Ok(actor_id) = fetch_webfinger_url(mention, context.client()).await {
debug!("mention actor_id: {}", actor_id);
- addressed_ccs.push(actor_id.to_owned());
+ addressed_ccs.push(actor_id.to_owned().to_string().parse()?);
- let mention_user = get_or_fetch_and_upsert_remote_user(&actor_id, client, pool).await?;
- let shared_inbox = mention_user.get_shared_inbox_url();
+ let mention_user = get_or_fetch_and_upsert_user(&actor_id, context).await?;
+ let shared_inbox = mention_user.get_shared_inbox_url()?;
mention_inboxes.push(shared_inbox);
let mut mention_tag = Mention::new();
- mention_tag
- .link_props
- .set_href(actor_id)?
- .set_name_xsd_string(mention.full_name())?;
+ mention_tag.set_href(actor_id).set_name(mention.full_name());
tags.push(mention_tag);
}
}
- let mut inboxes = vec![community.get_shared_inbox_url()];
+ let mut inboxes = vec![community.get_shared_inbox_url()?];
inboxes.extend(mention_inboxes);
inboxes = inboxes.into_iter().unique().collect();
use crate::{
+ api::{check_slurs, check_slurs_opt},
apub::{
- activities::{populate_object_props, send_activity},
+ activities::{generate_activity_id, send_activity},
+ check_actor_domain,
create_apub_response,
create_apub_tombstone_response,
create_tombstone,
extensions::group_extensions::GroupExtension,
- fetcher::get_or_fetch_and_upsert_remote_user,
- get_shared_inbox,
+ fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_user},
insert_activity,
ActorType,
FromApub,
ToApub,
},
blocking,
- routes::DbPoolParam,
DbPool,
+ LemmyContext,
LemmyError,
};
use activitystreams::{
- activity::{Accept, Announce, Delete, Remove, Undo},
- Activity,
- Base,
- BaseBox,
-};
-use activitystreams_ext::Ext2;
-use activitystreams_new::{
- activity::Follow,
+ activity::{
+ kind::{AcceptType, AnnounceType, DeleteType, LikeType, RemoveType, UndoType},
+ Accept,
+ Announce,
+ Delete,
+ Follow,
+ Remove,
+ Undo,
+ },
actor::{kind::GroupType, ApActor, Endpoints, Group},
- base::BaseExt,
- collection::UnorderedCollection,
- context,
- object::Tombstone,
+ base::{AnyBase, BaseExt},
+ collection::{OrderedCollection, UnorderedCollection},
+ object::{Image, Tombstone},
prelude::*,
- primitives::{XsdAnyUri, XsdDateTime},
+ public,
};
-use actix_web::{body::Body, client::Client, web, HttpResponse};
+use activitystreams_ext::Ext2;
+use actix_web::{body::Body, web, HttpResponse};
+use anyhow::Context;
use itertools::Itertools;
use lemmy_db::{
community::{Community, CommunityForm},
community_view::{CommunityFollowerView, CommunityModeratorView},
naive_now,
+ post::Post,
user::User_,
};
-use lemmy_utils::convert_datetime;
-use serde::{Deserialize, Serialize};
-use std::{fmt::Debug, str::FromStr};
+use lemmy_utils::{convert_datetime, get_apub_protocol_string, location_info};
+use serde::Deserialize;
+use url::Url;
#[derive(Deserialize)]
pub struct CommunityQuery {
let mut group = Group::new();
group
- .set_context(context())
- .set_id(XsdAnyUri::from_str(&self.actor_id)?)
+ .set_context(activitystreams::context())
+ .set_id(Url::parse(&self.actor_id)?)
.set_name(self.name.to_owned())
- .set_published(XsdDateTime::from(convert_datetime(self.published)))
+ .set_published(convert_datetime(self.published))
.set_many_attributed_tos(moderators);
if let Some(u) = self.updated.to_owned() {
- group.set_updated(XsdDateTime::from(convert_datetime(u)));
+ group.set_updated(convert_datetime(u));
}
if let Some(d) = self.description.to_owned() {
// TODO: this should be html, also add source field with raw markdown
group.set_content(d);
}
- let mut ap_actor = ApActor::new(self.get_inbox_url().parse()?, group);
+ let mut ap_actor = ApActor::new(self.get_inbox_url()?, group);
ap_actor
.set_preferred_username(self.title.to_owned())
- .set_outbox(self.get_outbox_url().parse()?)
- .set_followers(self.get_followers_url().parse()?)
+ .set_outbox(self.get_outbox_url()?)
+ .set_followers(self.get_followers_url()?)
.set_following(self.get_following_url().parse()?)
.set_liked(self.get_liked_url().parse()?)
.set_endpoints(Endpoints {
- shared_inbox: Some(self.get_shared_inbox_url().parse()?),
+ shared_inbox: Some(self.get_shared_inbox_url()?),
..Default::default()
});
Ok(Ext2::new(
ap_actor,
group_extension,
- self.get_public_key_ext(),
+ self.get_public_key_ext()?,
))
}
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
- create_tombstone(
- self.deleted,
- &self.actor_id,
- self.updated,
- GroupType.to_string(),
- )
+ create_tombstone(self.deleted, &self.actor_id, self.updated, GroupType::Group)
}
}
#[async_trait::async_trait(?Send)]
impl ActorType for Community {
- fn actor_id(&self) -> String {
+ fn actor_id_str(&self) -> String {
self.actor_id.to_owned()
}
- fn public_key(&self) -> String {
- self.public_key.to_owned().unwrap()
+ fn public_key(&self) -> Option<String> {
+ self.public_key.to_owned()
}
- fn private_key(&self) -> String {
- self.private_key.to_owned().unwrap()
+ fn private_key(&self) -> Option<String> {
+ self.private_key.to_owned()
}
/// As a local community, accept the follow request from a remote user.
async fn send_accept_follow(
&self,
- follow: &Follow,
- client: &Client,
- pool: &DbPool,
+ follow: Follow,
+ context: &LemmyContext,
) -> Result<(), LemmyError> {
- let actor_uri = follow.actor.as_single_xsd_any_uri().unwrap().to_string();
- let id = format!("{}/accept/{}", self.actor_id, uuid::Uuid::new_v4());
-
- let mut accept = Accept::new();
- accept
- .object_props
- .set_context_xsd_any_uri(context())?
- .set_id(id)?;
+ let actor_uri = follow
+ .actor()?
+ .as_single_xsd_any_uri()
+ .context(location_info!())?;
+ let actor = get_or_fetch_and_upsert_actor(actor_uri, context).await?;
+
+ let mut accept = Accept::new(self.actor_id.to_owned(), follow.into_any_base()?);
+ let to = actor.get_inbox_url()?;
accept
- .accept_props
- .set_actor_xsd_any_uri(self.actor_id.to_owned())?
- .set_object_base_box(BaseBox::from_concrete(follow.clone())?)?;
- let to = format!("{}/inbox", actor_uri);
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(AcceptType::Accept)?)
+ .set_to(to.clone());
- insert_activity(self.creator_id, accept.clone(), true, pool).await?;
+ insert_activity(self.creator_id, accept.clone(), true, context.pool()).await?;
- send_activity(client, &accept, self, vec![to]).await?;
+ send_activity(context.client(), &accept.into_any_base()?, self, vec![to]).await?;
Ok(())
}
- async fn send_delete(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let group = self.to_apub(pool).await?;
-
- let id = format!("{}/delete/{}", self.actor_id, uuid::Uuid::new_v4());
-
- let mut delete = Delete::default();
- populate_object_props(
- &mut delete.object_props,
- vec![self.get_followers_url()],
- &id,
- )?;
+ async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let group = self.to_apub(context.pool()).await?;
+ let mut delete = Delete::new(creator.actor_id.to_owned(), group.into_any_base()?);
delete
- .delete_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(BaseBox::from_concrete(group)?)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(DeleteType::Delete)?)
+ .set_to(public())
+ .set_many_ccs(vec![self.get_followers_url()?]);
- insert_activity(self.creator_id, delete.clone(), true, pool).await?;
+ insert_activity(self.creator_id, delete.clone(), true, context.pool()).await?;
- let inboxes = self.get_follower_inboxes(pool).await?;
+ let inboxes = self.get_follower_inboxes(context.pool()).await?;
// Note: For an accept, since it was automatic, no one pushed a button,
// the community was the actor.
// But for delete, the creator is the actor, and does the signing
- send_activity(client, &delete, creator, inboxes).await?;
+ send_activity(context.client(), &delete.into_any_base()?, creator, inboxes).await?;
Ok(())
}
async fn send_undo_delete(
&self,
creator: &User_,
- client: &Client,
- pool: &DbPool,
+ context: &LemmyContext,
) -> Result<(), LemmyError> {
- let group = self.to_apub(pool).await?;
-
- let id = format!("{}/delete/{}", self.actor_id, uuid::Uuid::new_v4());
-
- let mut delete = Delete::default();
- populate_object_props(
- &mut delete.object_props,
- vec![self.get_followers_url()],
- &id,
- )?;
+ let group = self.to_apub(context.pool()).await?;
+ let mut delete = Delete::new(creator.actor_id.to_owned(), group.into_any_base()?);
delete
- .delete_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(BaseBox::from_concrete(group)?)?;
-
- // TODO
- // Undo that fake activity
- let undo_id = format!("{}/undo/delete/{}", self.actor_id, uuid::Uuid::new_v4());
- let mut undo = Undo::default();
-
- populate_object_props(
- &mut undo.object_props,
- vec![self.get_followers_url()],
- &undo_id,
- )?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(DeleteType::Delete)?)
+ .set_to(public())
+ .set_many_ccs(vec![self.get_followers_url()?]);
+ let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
undo
- .undo_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(delete)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(UndoType::Undo)?)
+ .set_to(public())
+ .set_many_ccs(vec![self.get_followers_url()?]);
- insert_activity(self.creator_id, undo.clone(), true, pool).await?;
+ insert_activity(self.creator_id, undo.clone(), true, context.pool()).await?;
- let inboxes = self.get_follower_inboxes(pool).await?;
+ let inboxes = self.get_follower_inboxes(context.pool()).await?;
// Note: For an accept, since it was automatic, no one pushed a button,
// the community was the actor.
// But for delete, the creator is the actor, and does the signing
- send_activity(client, &undo, creator, inboxes).await?;
+ send_activity(context.client(), &undo.into_any_base()?, creator, inboxes).await?;
Ok(())
}
- async fn send_remove(
- &self,
- mod_: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let group = self.to_apub(pool).await?;
-
- let id = format!("{}/remove/{}", self.actor_id, uuid::Uuid::new_v4());
-
- let mut remove = Remove::default();
- populate_object_props(
- &mut remove.object_props,
- vec![self.get_followers_url()],
- &id,
- )?;
+ async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let group = self.to_apub(context.pool()).await?;
+ let mut remove = Remove::new(mod_.actor_id.to_owned(), group.into_any_base()?);
remove
- .remove_props
- .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
- .set_object_base_box(BaseBox::from_concrete(group)?)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(RemoveType::Remove)?)
+ .set_to(public())
+ .set_many_ccs(vec![self.get_followers_url()?]);
- insert_activity(mod_.id, remove.clone(), true, pool).await?;
+ insert_activity(mod_.id, remove.clone(), true, context.pool()).await?;
- let inboxes = self.get_follower_inboxes(pool).await?;
+ let inboxes = self.get_follower_inboxes(context.pool()).await?;
// Note: For an accept, since it was automatic, no one pushed a button,
// the community was the actor.
// But for delete, the creator is the actor, and does the signing
- send_activity(client, &remove, mod_, inboxes).await?;
+ send_activity(context.client(), &remove.into_any_base()?, mod_, inboxes).await?;
Ok(())
}
- async fn send_undo_remove(
- &self,
- mod_: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let group = self.to_apub(pool).await?;
-
- let id = format!("{}/remove/{}", self.actor_id, uuid::Uuid::new_v4());
-
- let mut remove = Remove::default();
- populate_object_props(
- &mut remove.object_props,
- vec![self.get_followers_url()],
- &id,
- )?;
+ async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let group = self.to_apub(context.pool()).await?;
+ let mut remove = Remove::new(mod_.actor_id.to_owned(), group.into_any_base()?);
remove
- .remove_props
- .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
- .set_object_base_box(BaseBox::from_concrete(group)?)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(RemoveType::Remove)?)
+ .set_to(public())
+ .set_many_ccs(vec![self.get_followers_url()?]);
// Undo that fake activity
- let undo_id = format!("{}/undo/remove/{}", self.actor_id, uuid::Uuid::new_v4());
- let mut undo = Undo::default();
-
- populate_object_props(
- &mut undo.object_props,
- vec![self.get_followers_url()],
- &undo_id,
- )?;
-
+ let mut undo = Undo::new(mod_.actor_id.to_owned(), remove.into_any_base()?);
undo
- .undo_props
- .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
- .set_object_base_box(remove)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(LikeType::Like)?)
+ .set_to(public())
+ .set_many_ccs(vec![self.get_followers_url()?]);
- insert_activity(mod_.id, undo.clone(), true, pool).await?;
+ insert_activity(mod_.id, undo.clone(), true, context.pool()).await?;
- let inboxes = self.get_follower_inboxes(pool).await?;
+ let inboxes = self.get_follower_inboxes(context.pool()).await?;
// Note: For an accept, since it was automatic, no one pushed a button,
// the community was the actor.
// But for remove , the creator is the actor, and does the signing
- send_activity(client, &undo, mod_, inboxes).await?;
+ send_activity(context.client(), &undo.into_any_base()?, mod_, inboxes).await?;
Ok(())
}
/// For a given community, returns the inboxes of all followers.
- async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<String>, LemmyError> {
+ ///
+ /// TODO: this function is very badly implemented, we should just store shared_inbox_url in
+ /// CommunityFollowerView
+ async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError> {
let id = self.id;
let inboxes = blocking(pool, move |conn| {
.await??;
let inboxes = inboxes
.into_iter()
- .map(|c| get_shared_inbox(&c.user_actor_id))
- .filter(|s| !s.is_empty())
+ .map(|u| -> Result<Url, LemmyError> {
+ let url = Url::parse(&u.user_actor_id)?;
+ let domain = url.domain().context(location_info!())?;
+ let port = if let Some(port) = url.port() {
+ format!(":{}", port)
+ } else {
+ "".to_string()
+ };
+ Ok(Url::parse(&format!(
+ "{}://{}{}/inbox",
+ get_apub_protocol_string(),
+ domain,
+ port,
+ ))?)
+ })
+ .filter_map(Result::ok)
.unique()
.collect();
async fn send_follow(
&self,
- _follow_actor_id: &str,
- _client: &Client,
- _pool: &DbPool,
+ _follow_actor_id: &Url,
+ _context: &LemmyContext,
) -> Result<(), LemmyError> {
unimplemented!()
}
async fn send_unfollow(
&self,
- _follow_actor_id: &str,
- _client: &Client,
- _pool: &DbPool,
+ _follow_actor_id: &Url,
+ _context: &LemmyContext,
) -> Result<(), LemmyError> {
unimplemented!()
}
+
+ fn user_id(&self) -> i32 {
+ self.creator_id
+ }
}
#[async_trait::async_trait(?Send)]
type ApubType = GroupExt;
/// Parse an ActivityPub group received from another instance into a Lemmy community.
- async fn from_apub(group: &GroupExt, client: &Client, pool: &DbPool) -> Result<Self, LemmyError> {
- let creator_and_moderator_uris = group.attributed_to().unwrap();
+ async fn from_apub(
+ group: &GroupExt,
+ context: &LemmyContext,
+ expected_domain: Option<Url>,
+ ) -> Result<Self, LemmyError> {
+ let creator_and_moderator_uris = group.inner.attributed_to().context(location_info!())?;
let creator_uri = creator_and_moderator_uris
.as_many()
- .unwrap()
+ .context(location_info!())?
.iter()
.next()
- .unwrap()
+ .context(location_info!())?
.as_xsd_any_uri()
- .unwrap();
-
- let creator = get_or_fetch_and_upsert_remote_user(creator_uri.as_str(), client, pool).await?;
+ .context(location_info!())?;
+
+ let creator = get_or_fetch_and_upsert_user(creator_uri, context).await?;
+ let name = group
+ .inner
+ .name()
+ .context(location_info!())?
+ .as_one()
+ .context(location_info!())?
+ .as_xsd_string()
+ .context(location_info!())?
+ .to_string();
+ let title = group
+ .inner
+ .preferred_username()
+ .context(location_info!())?
+ .to_string();
+ // TODO: should be parsed as html and tags like <script> removed (or use markdown source)
+ // -> same for post.content etc
+ let description = group
+ .inner
+ .content()
+ .map(|s| s.as_single_xsd_string())
+ .flatten()
+ .map(|s| s.to_string());
+ check_slurs(&name)?;
+ check_slurs(&title)?;
+ check_slurs_opt(&description)?;
+
+ let icon = match group.icon() {
+ Some(any_image) => Some(
+ Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
+ .context(location_info!())?
+ .context(location_info!())?
+ .url()
+ .context(location_info!())?
+ .as_single_xsd_any_uri()
+ .map(|u| u.to_string()),
+ ),
+ None => None,
+ };
+
+ let banner = match group.image() {
+ Some(any_image) => Some(
+ Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
+ .context(location_info!())?
+ .context(location_info!())?
+ .url()
+ .context(location_info!())?
+ .as_single_xsd_any_uri()
+ .map(|u| u.to_string()),
+ ),
+ None => None,
+ };
Ok(CommunityForm {
- name: group.name().unwrap().as_single_xsd_string().unwrap().into(),
- title: group.inner.preferred_username().unwrap().to_string(),
- // TODO: should be parsed as html and tags like <script> removed (or use markdown source)
- // -> same for post.content etc
- description: group
- .content()
- .map(|s| s.as_single_xsd_string().unwrap().into()),
+ name,
+ title,
+ description,
category_id: group.ext_one.category.identifier.parse::<i32>()?,
creator_id: creator.id,
removed: None,
- published: group
- .published()
- .map(|u| u.as_ref().to_owned().naive_local()),
- updated: group.updated().map(|u| u.as_ref().to_owned().naive_local()),
+ published: group.inner.published().map(|u| u.to_owned().naive_local()),
+ updated: group.inner.updated().map(|u| u.to_owned().naive_local()),
deleted: None,
nsfw: group.ext_one.sensitive,
- actor_id: group.id().unwrap().to_string(),
+ actor_id: check_actor_domain(group, expected_domain)?,
local: false,
private_key: None,
public_key: Some(group.ext_two.to_owned().public_key.public_key_pem),
last_refreshed_at: Some(naive_now()),
+ icon,
+ banner,
})
}
}
/// Return the community json over HTTP.
pub async fn get_apub_community_http(
info: web::Path<CommunityQuery>,
- db: DbPoolParam,
+ context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, LemmyError> {
- let community = blocking(&db, move |conn| {
+ let community = blocking(context.pool(), move |conn| {
Community::read_from_name(conn, &info.community_name)
})
.await??;
if !community.deleted {
- let apub = community.to_apub(&db).await?;
+ let apub = community.to_apub(context.pool()).await?;
Ok(create_apub_response(&apub))
} else {
/// Returns an empty followers collection, only populating the size (for privacy).
pub async fn get_apub_community_followers(
info: web::Path<CommunityQuery>,
- db: DbPoolParam,
+ context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, LemmyError> {
- let community = blocking(&db, move |conn| {
+ let community = blocking(context.pool(), move |conn| {
Community::read_from_name(&conn, &info.community_name)
})
.await??;
let community_id = community.id;
- let community_followers = blocking(&db, move |conn| {
+ let community_followers = blocking(context.pool(), move |conn| {
CommunityFollowerView::for_community(&conn, community_id)
})
.await??;
- let mut collection = UnorderedCollection::new(vec![]);
+ let mut collection = UnorderedCollection::new();
collection
- .set_context(context())
- // TODO: this needs its own ID
- .set_id(community.actor_id.parse()?)
+ .set_context(activitystreams::context())
+ .set_id(community.get_followers_url()?)
.set_total_items(community_followers.len() as u64);
Ok(create_apub_response(&collection))
}
-pub async fn do_announce<A>(
- activity: A,
+pub async fn get_apub_community_outbox(
+ info: web::Path<CommunityQuery>,
+ context: web::Data<LemmyContext>,
+) -> Result<HttpResponse<Body>, LemmyError> {
+ let community = blocking(context.pool(), move |conn| {
+ Community::read_from_name(&conn, &info.community_name)
+ })
+ .await??;
+
+ let community_id = community.id;
+ let posts = blocking(context.pool(), move |conn| {
+ Post::list_for_community(conn, community_id)
+ })
+ .await??;
+
+ let mut pages: Vec<AnyBase> = vec![];
+ for p in posts {
+ pages.push(p.to_apub(context.pool()).await?.into_any_base()?);
+ }
+
+ let len = pages.len();
+ let mut collection = OrderedCollection::new();
+ collection
+ .set_many_items(pages)
+ .set_context(activitystreams::context())
+ .set_id(community.get_outbox_url()?)
+ .set_total_items(len as u64);
+ Ok(create_apub_response(&collection))
+}
+
+pub async fn do_announce(
+ activity: AnyBase,
community: &Community,
- sender: &dyn ActorType,
- client: &Client,
- pool: &DbPool,
-) -> Result<HttpResponse, LemmyError>
-where
- A: Activity + Base + Serialize + Debug,
-{
- let mut announce = Announce::default();
- populate_object_props(
- &mut announce.object_props,
- vec![community.get_followers_url()],
- &format!("{}/announce/{}", community.actor_id, uuid::Uuid::new_v4()),
- )?;
+ sender: &User_,
+ context: &LemmyContext,
+) -> Result<(), LemmyError> {
+ let mut announce = Announce::new(community.actor_id.to_owned(), activity);
announce
- .announce_props
- .set_actor_xsd_any_uri(community.actor_id.to_owned())?
- .set_object_base_box(BaseBox::from_concrete(activity)?)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(AnnounceType::Announce)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
- insert_activity(community.creator_id, announce.clone(), true, pool).await?;
+ insert_activity(community.creator_id, announce.clone(), true, context.pool()).await?;
- // dont send to the instance where the activity originally came from, because that would result
- // in a database error (same data inserted twice)
- let mut to = community.get_follower_inboxes(pool).await?;
+ let mut to: Vec<Url> = community.get_follower_inboxes(context.pool()).await?;
+ // dont send to the local instance, nor to the instance where the activity originally came from,
+ // because that would result in a database error (same data inserted twice)
// this seems to be the "easiest" stable alternative for remove_item()
- to.retain(|x| *x != sender.get_shared_inbox_url());
+ let sender_shared_inbox = sender.get_shared_inbox_url()?;
+ to.retain(|x| x != &sender_shared_inbox);
+ let community_shared_inbox = community.get_shared_inbox_url()?;
+ to.retain(|x| x != &community_shared_inbox);
- send_activity(client, &announce, community, to).await?;
+ send_activity(context.client(), &announce.into_any_base()?, community, to).await?;
- Ok(HttpResponse::Ok().finish())
+ Ok(())
}
+++ /dev/null
-use crate::{
- apub::{
- extensions::signatures::verify,
- fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user},
- insert_activity,
- ActorType,
- },
- blocking,
- routes::{ChatServerParam, DbPoolParam},
- LemmyError,
-};
-use activitystreams::activity::Undo;
-use activitystreams_new::activity::Follow;
-use actix_web::{client::Client, web, HttpRequest, HttpResponse};
-use lemmy_db::{
- community::{Community, CommunityFollower, CommunityFollowerForm},
- user::User_,
- Followable,
-};
-use log::debug;
-use serde::Deserialize;
-use std::fmt::Debug;
-
-#[serde(untagged)]
-#[derive(Deserialize, Debug)]
-pub enum CommunityAcceptedObjects {
- Follow(Follow),
- Undo(Undo),
-}
-
-impl CommunityAcceptedObjects {
- fn follow(&self) -> Result<Follow, LemmyError> {
- match self {
- CommunityAcceptedObjects::Follow(f) => Ok(f.to_owned()),
- CommunityAcceptedObjects::Undo(u) => Ok(
- u.undo_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Follow>()?,
- ),
- }
- }
-}
-
-/// Handler for all incoming activities to community inboxes.
-pub async fn community_inbox(
- request: HttpRequest,
- input: web::Json<CommunityAcceptedObjects>,
- path: web::Path<String>,
- db: DbPoolParam,
- client: web::Data<Client>,
- _chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let input = input.into_inner();
-
- let path = path.into_inner();
- let community = blocking(&db, move |conn| Community::read_from_name(&conn, &path)).await??;
-
- if !community.local {
- return Err(
- format_err!(
- "Received activity is addressed to remote community {}",
- &community.actor_id
- )
- .into(),
- );
- }
- debug!(
- "Community {} received activity {:?}",
- &community.name, &input
- );
- let follow = input.follow()?;
- let user_uri = follow.actor.as_single_xsd_any_uri().unwrap().to_string();
- let community_uri = follow.object.as_single_xsd_any_uri().unwrap().to_string();
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, &client, &db).await?;
- let community = get_or_fetch_and_upsert_remote_community(&community_uri, &client, &db).await?;
-
- verify(&request, &user)?;
-
- match input {
- CommunityAcceptedObjects::Follow(f) => handle_follow(f, user, community, &client, db).await,
- CommunityAcceptedObjects::Undo(u) => handle_undo_follow(u, user, community, db).await,
- }
-}
-
-/// Handle a follow request from a remote user, adding it to the local database and returning an
-/// Accept activity.
-async fn handle_follow(
- follow: Follow,
- user: User_,
- community: Community,
- client: &Client,
- db: DbPoolParam,
-) -> Result<HttpResponse, LemmyError> {
- insert_activity(user.id, follow.clone(), false, &db).await?;
-
- let community_follower_form = CommunityFollowerForm {
- community_id: community.id,
- user_id: user.id,
- };
-
- // This will fail if they're already a follower, but ignore the error.
- blocking(&db, move |conn| {
- CommunityFollower::follow(&conn, &community_follower_form).ok()
- })
- .await?;
-
- community.send_accept_follow(&follow, &client, &db).await?;
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn handle_undo_follow(
- undo: Undo,
- user: User_,
- community: Community,
- db: DbPoolParam,
-) -> Result<HttpResponse, LemmyError> {
- insert_activity(user.id, undo, false, &db).await?;
-
- let community_follower_form = CommunityFollowerForm {
- community_id: community.id,
- user_id: user.id,
- };
-
- // This will fail if they aren't a follower, but ignore the error.
- blocking(&db, move |conn| {
- CommunityFollower::unfollow(&conn, &community_follower_form).ok()
- })
- .await?;
-
- Ok(HttpResponse::Ok().finish())
-}
use crate::LemmyError;
-use activitystreams::{ext::Extension, Actor};
+use activitystreams::unparsed::UnparsedMutExt;
+use activitystreams_ext::UnparsedExtension;
use diesel::PgConnection;
use lemmy_db::{category::Category, Crud};
use serde::{Deserialize, Serialize};
}
}
-impl<T> Extension<T> for GroupExtension where T: Actor {}
+impl<U> UnparsedExtension<U> for GroupExtension
+where
+ U: UnparsedMutExt,
+{
+ type Error = serde_json::Error;
+
+ fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
+ Ok(GroupExtension {
+ category: unparsed_mut.remove("category")?,
+ sensitive: unparsed_mut.remove("sensitive")?,
+ })
+ }
+
+ fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
+ unparsed_mut.insert("category", self.category)?;
+ unparsed_mut.insert("sensitive", self.sensitive)?;
+ Ok(())
+ }
+}
-use activitystreams::{ext::Extension, Base};
+use activitystreams::unparsed::UnparsedMutExt;
+use activitystreams_ext::UnparsedExtension;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct PageExtension {
pub comments_enabled: bool,
pub sensitive: bool,
+ pub stickied: bool,
}
-impl<T> Extension<T> for PageExtension where T: Base {}
+impl<U> UnparsedExtension<U> for PageExtension
+where
+ U: UnparsedMutExt,
+{
+ type Error = serde_json::Error;
+
+ fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
+ Ok(PageExtension {
+ comments_enabled: unparsed_mut.remove("commentsEnabled")?,
+ sensitive: unparsed_mut.remove("sensitive")?,
+ stickied: unparsed_mut.remove("stickied")?,
+ })
+ }
+
+ fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
+ unparsed_mut.insert("commentsEnabled", self.comments_enabled)?;
+ unparsed_mut.insert("sensitive", self.sensitive)?;
+ unparsed_mut.insert("stickied", self.stickied)?;
+ Ok(())
+ }
+}
use crate::{apub::ActorType, LemmyError};
-use activitystreams::ext::Extension;
+use activitystreams::unparsed::UnparsedMutExt;
+use activitystreams_ext::UnparsedExtension;
use actix_web::{client::ClientRequest, HttpRequest};
+use anyhow::{anyhow, Context};
use http_signature_normalization_actix::{
digest::{DigestClient, SignExt},
Config,
};
+use lemmy_utils::location_info;
use log::debug;
use openssl::{
hash::MessageDigest,
actor: &dyn ActorType,
activity: String,
) -> Result<DigestClient<String>, LemmyError> {
- let signing_key_id = format!("{}#main-key", actor.actor_id());
- let private_key = actor.private_key();
+ let signing_key_id = format!("{}#main-key", actor.actor_id()?);
+ let private_key = actor.private_key().context(location_info!())?;
let digest_client = request
.signature_with_digest(
activity,
move |signing_string| {
let private_key = PKey::private_key_from_pem(private_key.as_bytes())?;
- let mut signer = Signer::new(MessageDigest::sha256(), &private_key).unwrap();
- signer.update(signing_string.as_bytes()).unwrap();
+ let mut signer = Signer::new(MessageDigest::sha256(), &private_key)?;
+ signer.update(signing_string.as_bytes())?;
Ok(base64::encode(signer.sign_to_vec()?)) as Result<_, LemmyError>
},
}
pub fn verify(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), LemmyError> {
+ let public_key = actor.public_key().context(location_info!())?;
let verified = HTTP_SIG_CONFIG
.begin_verify(
request.method(),
.verify(|signature, signing_string| -> Result<bool, LemmyError> {
debug!(
"Verifying with key {}, message {}",
- &actor.public_key(),
- &signing_string
+ &public_key, &signing_string
);
- let public_key = PKey::public_key_from_pem(actor.public_key().as_bytes())?;
- let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key).unwrap();
- verifier.update(&signing_string.as_bytes()).unwrap();
+ let public_key = PKey::public_key_from_pem(public_key.as_bytes())?;
+ let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key)?;
+ verifier.update(&signing_string.as_bytes())?;
Ok(verifier.verify(&base64::decode(signature)?)?)
})?;
debug!("verified signature for {}", &request.uri());
Ok(())
} else {
- Err(format_err!("Invalid signature on request: {}", &request.uri()).into())
+ Err(anyhow!("Invalid signature on request: {}", &request.uri()).into())
}
}
}
}
-impl<T> Extension<T> for PublicKeyExtension where T: activitystreams::Actor {}
+impl<U> UnparsedExtension<U> for PublicKeyExtension
+where
+ U: UnparsedMutExt,
+{
+ type Error = serde_json::Error;
+
+ fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
+ Ok(PublicKeyExtension {
+ public_key: unparsed_mut.remove("publicKey")?,
+ })
+ }
+
+ fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
+ unparsed_mut.insert("publicKey", self.public_key)?;
+ Ok(())
+ }
+}
use crate::{
api::site::SearchResponse,
- apub::{is_apub_id_valid, FromApub, GroupExt, PageExt, PersonExt, APUB_JSON_CONTENT_TYPE},
+ apub::{
+ check_is_apub_id_valid,
+ ActorType,
+ FromApub,
+ GroupExt,
+ PageExt,
+ PersonExt,
+ APUB_JSON_CONTENT_TYPE,
+ },
blocking,
request::{retry, RecvError},
- routes::nodeinfo::{NodeInfo, NodeInfoWellKnown},
- DbPool,
+ LemmyContext,
LemmyError,
};
-use activitystreams::object::Note;
-use activitystreams_new::{base::BaseExt, prelude::*, primitives::XsdAnyUri};
+use activitystreams::{base::BaseExt, collection::OrderedCollection, object::Note, prelude::*};
use actix_web::client::Client;
+use anyhow::{anyhow, Context};
use chrono::NaiveDateTime;
-use diesel::{result::Error::NotFound, PgConnection};
+use diesel::result::Error::NotFound;
use lemmy_db::{
comment::{Comment, CommentForm},
comment_view::CommentView,
Joinable,
SearchType,
};
-use lemmy_utils::get_apub_protocol_string;
+use lemmy_utils::{get_apub_protocol_string, location_info};
use log::debug;
use serde::Deserialize;
use std::{fmt::Debug, time::Duration};
use url::Url;
static ACTOR_REFETCH_INTERVAL_SECONDS: i64 = 24 * 60 * 60;
-
-// Fetch nodeinfo metadata from a remote instance.
-async fn _fetch_node_info(client: &Client, domain: &str) -> Result<NodeInfo, LemmyError> {
- let well_known_uri = Url::parse(&format!(
- "{}://{}/.well-known/nodeinfo",
- get_apub_protocol_string(),
- domain
- ))?;
-
- let well_known = fetch_remote_object::<NodeInfoWellKnown>(client, &well_known_uri).await?;
- let nodeinfo = fetch_remote_object::<NodeInfo>(client, &well_known.links.href).await?;
-
- Ok(nodeinfo)
-}
+static ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG: i64 = 10;
/// Fetch any type of ActivityPub object, handling things like HTTP headers, deserialisation,
/// timeouts etc.
where
Response: for<'de> Deserialize<'de>,
{
- if !is_apub_id_valid(&url) {
- return Err(format_err!("Activitypub uri invalid or blocked: {}", url).into());
- }
+ check_is_apub_id_valid(&url)?;
let timeout = Duration::from_secs(60);
/// http://lemmy_alpha:8540/comment/2
pub async fn search_by_apub_id(
query: &str,
- client: &Client,
- pool: &DbPool,
+ context: &LemmyContext,
) -> Result<SearchResponse, LemmyError> {
// Parse the shorthand query url
let query_url = if query.contains('@') {
let split2 = split[0].split('!').collect::<Vec<&str>>();
(format!("/c/{}", split2[1]), split[1])
} else {
- return Err(format_err!("Invalid search query: {}", query).into());
+ return Err(anyhow!("Invalid search query: {}", query).into());
}
} else {
- return Err(format_err!("Invalid search query: {}", query).into());
+ return Err(anyhow!("Invalid search query: {}", query).into());
};
let url = format!("{}://{}{}", get_apub_protocol_string(), instance, name);
users: vec![],
};
- let response = match fetch_remote_object::<SearchAcceptedObjects>(client, &query_url).await? {
- SearchAcceptedObjects::Person(p) => {
- let user_uri = p.inner.id().unwrap().to_string();
+ let domain = query_url.domain().context("url has no domain")?;
+ let response =
+ match fetch_remote_object::<SearchAcceptedObjects>(context.client(), &query_url).await? {
+ SearchAcceptedObjects::Person(p) => {
+ let user_uri = p.inner.id(domain)?.context("person has no id")?;
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
+ let user = get_or_fetch_and_upsert_user(&user_uri, context).await?;
- response.users = vec![blocking(pool, move |conn| UserView::read(conn, user.id)).await??];
+ response.users = vec![
+ blocking(context.pool(), move |conn| {
+ UserView::get_user_secure(conn, user.id)
+ })
+ .await??,
+ ];
- response
- }
- SearchAcceptedObjects::Group(g) => {
- let community_uri = g.inner.id().unwrap().to_string();
+ response
+ }
+ SearchAcceptedObjects::Group(g) => {
+ let community_uri = g.inner.id(domain)?.context("group has no id")?;
- let community =
- get_or_fetch_and_upsert_remote_community(&community_uri, client, pool).await?;
+ let community = get_or_fetch_and_upsert_community(community_uri, context).await?;
- // TODO Maybe at some point in the future, fetch all the history of a community
- // fetch_community_outbox(&c, conn)?;
- response.communities = vec![
- blocking(pool, move |conn| {
- CommunityView::read(conn, community.id, None)
- })
- .await??,
- ];
+ response.communities = vec![
+ blocking(context.pool(), move |conn| {
+ CommunityView::read(conn, community.id, None)
+ })
+ .await??,
+ ];
- response
- }
- SearchAcceptedObjects::Page(p) => {
- let post_form = PostForm::from_apub(&p, client, pool).await?;
+ response
+ }
+ SearchAcceptedObjects::Page(p) => {
+ let post_form = PostForm::from_apub(&p, context, Some(query_url)).await?;
- let p = blocking(pool, move |conn| upsert_post(&post_form, conn)).await??;
- response.posts = vec![blocking(pool, move |conn| PostView::read(conn, p.id, None)).await??];
+ let p = blocking(context.pool(), move |conn| Post::upsert(conn, &post_form)).await??;
+ response.posts =
+ vec![blocking(context.pool(), move |conn| PostView::read(conn, p.id, None)).await??];
- response
- }
- SearchAcceptedObjects::Comment(c) => {
- let post_url = c
- .object_props
- .get_many_in_reply_to_xsd_any_uris()
- .unwrap()
- .next()
- .unwrap()
- .to_string();
-
- // TODO: also fetch parent comments if any
- let post = fetch_remote_object(client, &Url::parse(&post_url)?).await?;
- let post_form = PostForm::from_apub(&post, client, pool).await?;
- let comment_form = CommentForm::from_apub(&c, client, pool).await?;
-
- blocking(pool, move |conn| upsert_post(&post_form, conn)).await??;
- let c = blocking(pool, move |conn| upsert_comment(&comment_form, conn)).await??;
- response.comments =
- vec![blocking(pool, move |conn| CommentView::read(conn, c.id, None)).await??];
-
- response
- }
- };
+ response
+ }
+ SearchAcceptedObjects::Comment(c) => {
+ let comment_form = CommentForm::from_apub(&c, context, Some(query_url)).await?;
+
+ let c = blocking(context.pool(), move |conn| {
+ Comment::upsert(conn, &comment_form)
+ })
+ .await??;
+ response.comments = vec![
+ blocking(context.pool(), move |conn| {
+ CommentView::read(conn, c.id, None)
+ })
+ .await??,
+ ];
+
+ response
+ }
+ };
Ok(response)
}
+pub async fn get_or_fetch_and_upsert_actor(
+ apub_id: &Url,
+ context: &LemmyContext,
+) -> Result<Box<dyn ActorType>, LemmyError> {
+ let user = get_or_fetch_and_upsert_user(apub_id, context).await;
+ let actor: Box<dyn ActorType> = match user {
+ Ok(u) => Box::new(u),
+ Err(_) => Box::new(get_or_fetch_and_upsert_community(apub_id, context).await?),
+ };
+ Ok(actor)
+}
+
/// Check if a remote user exists, create if not found, if its too old update it.Fetch a user, insert/update it in the database and return the user.
-pub async fn get_or_fetch_and_upsert_remote_user(
- apub_id: &str,
- client: &Client,
- pool: &DbPool,
+pub async fn get_or_fetch_and_upsert_user(
+ apub_id: &Url,
+ context: &LemmyContext,
) -> Result<User_, LemmyError> {
let apub_id_owned = apub_id.to_owned();
- let user = blocking(pool, move |conn| {
- User_::read_from_actor_id(conn, &apub_id_owned)
+ let user = blocking(context.pool(), move |conn| {
+ User_::read_from_actor_id(conn, apub_id_owned.as_ref())
})
.await?;
// If its older than a day, re-fetch it
Ok(u) if !u.local && should_refetch_actor(u.last_refreshed_at) => {
debug!("Fetching and updating from remote user: {}", apub_id);
- let person = fetch_remote_object::<PersonExt>(client, &Url::parse(apub_id)?).await?;
+ let person = fetch_remote_object::<PersonExt>(context.client(), apub_id).await?;
- let mut uf = UserForm::from_apub(&person, client, pool).await?;
+ let mut uf = UserForm::from_apub(&person, context, Some(apub_id.to_owned())).await?;
uf.last_refreshed_at = Some(naive_now());
- let user = blocking(pool, move |conn| User_::update(conn, u.id, &uf)).await??;
+ let user = blocking(context.pool(), move |conn| User_::update(conn, u.id, &uf)).await??;
Ok(user)
}
Ok(u) => Ok(u),
Err(NotFound {}) => {
debug!("Fetching and creating remote user: {}", apub_id);
- let person = fetch_remote_object::<PersonExt>(client, &Url::parse(apub_id)?).await?;
+ let person = fetch_remote_object::<PersonExt>(context.client(), apub_id).await?;
- let uf = UserForm::from_apub(&person, client, pool).await?;
- let user = blocking(pool, move |conn| User_::create(conn, &uf)).await??;
+ let uf = UserForm::from_apub(&person, context, Some(apub_id.to_owned())).await?;
+ let user = blocking(context.pool(), move |conn| User_::create(conn, &uf)).await??;
Ok(user)
}
/// TODO it won't pick up new avatars, summaries etc until a day after.
/// Actors need an "update" activity pushed to other servers to fix this.
fn should_refetch_actor(last_refreshed: NaiveDateTime) -> bool {
- if cfg!(debug_assertions) {
- true
+ let update_interval = if cfg!(debug_assertions) {
+ // avoid infinite loop when fetching community outbox
+ chrono::Duration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG)
} else {
- let update_interval = chrono::Duration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS);
- last_refreshed.lt(&(naive_now() - update_interval))
- }
+ chrono::Duration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS)
+ };
+ last_refreshed.lt(&(naive_now() - update_interval))
}
/// Check if a remote community exists, create if not found, if its too old update it.Fetch a community, insert/update it in the database and return the community.
-pub async fn get_or_fetch_and_upsert_remote_community(
- apub_id: &str,
- client: &Client,
- pool: &DbPool,
+pub async fn get_or_fetch_and_upsert_community(
+ apub_id: &Url,
+ context: &LemmyContext,
) -> Result<Community, LemmyError> {
let apub_id_owned = apub_id.to_owned();
- let community = blocking(pool, move |conn| {
- Community::read_from_actor_id(conn, &apub_id_owned)
+ let community = blocking(context.pool(), move |conn| {
+ Community::read_from_actor_id(conn, apub_id_owned.as_str())
})
.await?;
match community {
Ok(c) if !c.local && should_refetch_actor(c.last_refreshed_at) => {
debug!("Fetching and updating from remote community: {}", apub_id);
- let group = fetch_remote_object::<GroupExt>(client, &Url::parse(apub_id)?).await?;
-
- let mut cf = CommunityForm::from_apub(&group, client, pool).await?;
- cf.last_refreshed_at = Some(naive_now());
- let community = blocking(pool, move |conn| Community::update(conn, c.id, &cf)).await??;
-
- Ok(community)
+ fetch_remote_community(apub_id, context, Some(c.id)).await
}
Ok(c) => Ok(c),
Err(NotFound {}) => {
debug!("Fetching and creating remote community: {}", apub_id);
- let group = fetch_remote_object::<GroupExt>(client, &Url::parse(apub_id)?).await?;
+ fetch_remote_community(apub_id, context, None).await
+ }
+ Err(e) => Err(e.into()),
+ }
+}
- let cf = CommunityForm::from_apub(&group, client, pool).await?;
- let community = blocking(pool, move |conn| Community::create(conn, &cf)).await??;
+async fn fetch_remote_community(
+ apub_id: &Url,
+ context: &LemmyContext,
+ community_id: Option<i32>,
+) -> Result<Community, LemmyError> {
+ let group = fetch_remote_object::<GroupExt>(context.client(), apub_id).await?;
- // Also add the community moderators too
- let attributed_to = group.inner.attributed_to().unwrap();
- let creator_and_moderator_uris: Vec<&XsdAnyUri> = attributed_to
- .as_many()
- .unwrap()
- .iter()
- .map(|a| a.as_xsd_any_uri().unwrap())
- .collect();
+ let cf = CommunityForm::from_apub(&group, context, Some(apub_id.to_owned())).await?;
+ let community = blocking(context.pool(), move |conn| {
+ if let Some(cid) = community_id {
+ Community::update(conn, cid, &cf)
+ } else {
+ Community::create(conn, &cf)
+ }
+ })
+ .await??;
- let mut creator_and_moderators = Vec::new();
+ // Also add the community moderators too
+ let attributed_to = group.inner.attributed_to().context(location_info!())?;
+ let creator_and_moderator_uris: Vec<&Url> = attributed_to
+ .as_many()
+ .context(location_info!())?
+ .iter()
+ .map(|a| a.as_xsd_any_uri().context(""))
+ .collect::<Result<Vec<&Url>, anyhow::Error>>()?;
- for uri in creator_and_moderator_uris {
- let c_or_m = get_or_fetch_and_upsert_remote_user(uri.as_str(), client, pool).await?;
+ let mut creator_and_moderators = Vec::new();
- creator_and_moderators.push(c_or_m);
- }
+ for uri in creator_and_moderator_uris {
+ let c_or_m = get_or_fetch_and_upsert_user(uri, context).await?;
- let community_id = community.id;
- blocking(pool, move |conn| {
- for mod_ in creator_and_moderators {
- let community_moderator_form = CommunityModeratorForm {
- community_id,
- user_id: mod_.id,
- };
-
- CommunityModerator::join(conn, &community_moderator_form)?;
- }
- Ok(()) as Result<(), LemmyError>
- })
- .await??;
+ creator_and_moderators.push(c_or_m);
+ }
- Ok(community)
- }
- Err(e) => Err(e.into()),
+ // TODO: need to make this work to update mods of existing communities
+ if community_id.is_none() {
+ let community_id = community.id;
+ blocking(context.pool(), move |conn| {
+ for mod_ in creator_and_moderators {
+ let community_moderator_form = CommunityModeratorForm {
+ community_id,
+ user_id: mod_.id,
+ };
+
+ CommunityModerator::join(conn, &community_moderator_form)?;
+ }
+ Ok(()) as Result<(), LemmyError>
+ })
+ .await??;
}
-}
-fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result<Post, LemmyError> {
- let existing = Post::read_from_apub_id(conn, &post_form.ap_id);
- match existing {
- Err(NotFound {}) => Ok(Post::create(conn, &post_form)?),
- Ok(p) => Ok(Post::update(conn, p.id, &post_form)?),
- Err(e) => Err(e.into()),
+ // fetch outbox (maybe make this conditional)
+ let outbox =
+ fetch_remote_object::<OrderedCollection>(context.client(), &community.get_outbox_url()?)
+ .await?;
+ let outbox_items = outbox.items().context(location_info!())?.clone();
+ let mut outbox_items = outbox_items.many().context(location_info!())?;
+ if outbox_items.len() > 20 {
+ outbox_items = outbox_items[0..20].to_vec();
+ }
+ for o in outbox_items {
+ let page = PageExt::from_any_base(o)?.context(location_info!())?;
+ let post = PostForm::from_apub(&page, context, None).await?;
+ let post_ap_id = post.ap_id.clone();
+ // Check whether the post already exists in the local db
+ let existing = blocking(context.pool(), move |conn| {
+ Post::read_from_apub_id(conn, &post_ap_id)
+ })
+ .await?;
+ match existing {
+ Ok(e) => blocking(context.pool(), move |conn| Post::update(conn, e.id, &post)).await??,
+ Err(_) => blocking(context.pool(), move |conn| Post::create(conn, &post)).await??,
+ };
+ // TODO: we need to send a websocket update here
}
+
+ Ok(community)
}
-pub async fn get_or_fetch_and_insert_remote_post(
- post_ap_id: &str,
- client: &Client,
- pool: &DbPool,
+pub async fn get_or_fetch_and_insert_post(
+ post_ap_id: &Url,
+ context: &LemmyContext,
) -> Result<Post, LemmyError> {
let post_ap_id_owned = post_ap_id.to_owned();
- let post = blocking(pool, move |conn| {
- Post::read_from_apub_id(conn, &post_ap_id_owned)
+ let post = blocking(context.pool(), move |conn| {
+ Post::read_from_apub_id(conn, post_ap_id_owned.as_str())
})
.await?;
Ok(p) => Ok(p),
Err(NotFound {}) => {
debug!("Fetching and creating remote post: {}", post_ap_id);
- let post = fetch_remote_object::<PageExt>(client, &Url::parse(post_ap_id)?).await?;
- let post_form = PostForm::from_apub(&post, client, pool).await?;
+ let post = fetch_remote_object::<PageExt>(context.client(), post_ap_id).await?;
+ let post_form = PostForm::from_apub(&post, context, Some(post_ap_id.to_owned())).await?;
- let post = blocking(pool, move |conn| Post::create(conn, &post_form)).await??;
+ let post = blocking(context.pool(), move |conn| Post::create(conn, &post_form)).await??;
Ok(post)
}
}
}
-fn upsert_comment(comment_form: &CommentForm, conn: &PgConnection) -> Result<Comment, LemmyError> {
- let existing = Comment::read_from_apub_id(conn, &comment_form.ap_id);
- match existing {
- Err(NotFound {}) => Ok(Comment::create(conn, &comment_form)?),
- Ok(p) => Ok(Comment::update(conn, p.id, &comment_form)?),
- Err(e) => Err(e.into()),
- }
-}
-
-pub async fn get_or_fetch_and_insert_remote_comment(
- comment_ap_id: &str,
- client: &Client,
- pool: &DbPool,
+pub async fn get_or_fetch_and_insert_comment(
+ comment_ap_id: &Url,
+ context: &LemmyContext,
) -> Result<Comment, LemmyError> {
let comment_ap_id_owned = comment_ap_id.to_owned();
- let comment = blocking(pool, move |conn| {
- Comment::read_from_apub_id(conn, &comment_ap_id_owned)
+ let comment = blocking(context.pool(), move |conn| {
+ Comment::read_from_apub_id(conn, comment_ap_id_owned.as_str())
})
.await?;
"Fetching and creating remote comment and its parents: {}",
comment_ap_id
);
- let comment = fetch_remote_object::<Note>(client, &Url::parse(comment_ap_id)?).await?;
- let comment_form = CommentForm::from_apub(&comment, client, pool).await?;
+ let comment = fetch_remote_object::<Note>(context.client(), comment_ap_id).await?;
+ let comment_form =
+ CommentForm::from_apub(&comment, context, Some(comment_ap_id.to_owned())).await?;
- let comment = blocking(pool, move |conn| Comment::create(conn, &comment_form)).await??;
+ let comment = blocking(context.pool(), move |conn| {
+ Comment::create(conn, &comment_form)
+ })
+ .await??;
Ok(comment)
}
Err(e) => Err(e.into()),
}
}
-
-// TODO It should not be fetching data from a community outbox.
-// All posts, comments, comment likes, etc should be posts to our community_inbox
-// The only data we should be periodically fetching (if it hasn't been fetched in the last day
-// maybe), is community and user actors
-// and user actors
-// Fetch all posts in the outbox of the given user, and insert them into the database.
-// fn fetch_community_outbox(community: &Community, conn: &PgConnection) -> Result<Vec<Post>, LemmyError> {
-// let outbox_url = Url::parse(&community.get_outbox_url())?;
-// let outbox = fetch_remote_object::<OrderedCollection>(&outbox_url)?;
-// let items = outbox.collection_props.get_many_items_base_boxes();
-
-// Ok(
-// items
-// .unwrap()
-// .map(|obox: &BaseBox| -> Result<PostForm, LemmyError> {
-// let page = obox.clone().to_concrete::<Page>()?;
-// PostForm::from_page(&page, conn)
-// })
-// .map(|pf| upsert_post(&pf?, conn))
-// .collect::<Result<Vec<Post>, LemmyError>>()?,
-// )
-// }
--- /dev/null
+use crate::{
+ apub::inbox::{
+ activities::{
+ create::receive_create,
+ delete::receive_delete,
+ dislike::receive_dislike,
+ like::receive_like,
+ remove::receive_remove,
+ undo::receive_undo,
+ update::receive_update,
+ },
+ shared_inbox::{get_community_id_from_activity, receive_unhandled_activity},
+ },
+ LemmyContext,
+ LemmyError,
+};
+use activitystreams::{
+ activity::*,
+ base::{AnyBase, BaseExt},
+ prelude::ExtendsExt,
+};
+use actix_web::HttpResponse;
+use anyhow::Context;
+use lemmy_utils::location_info;
+
+pub async fn receive_announce(
+ activity: AnyBase,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let announce = Announce::from_any_base(activity)?.context(location_info!())?;
+
+ // ensure that announce and community come from the same instance
+ let community = get_community_id_from_activity(&announce)?;
+ announce.id(community.domain().context(location_info!())?)?;
+
+ let kind = announce.object().as_single_kind_str();
+ let object = announce.object();
+ let object2 = object.clone().one().context(location_info!())?;
+ match kind {
+ Some("Create") => receive_create(object2, context).await,
+ Some("Update") => receive_update(object2, context).await,
+ Some("Like") => receive_like(object2, context).await,
+ Some("Dislike") => receive_dislike(object2, context).await,
+ Some("Delete") => receive_delete(object2, context).await,
+ Some("Remove") => receive_remove(object2, context).await,
+ Some("Undo") => receive_undo(object2, context).await,
+ _ => receive_unhandled_activity(announce),
+ }
+}
--- /dev/null
+use crate::{
+ api::{
+ comment::{send_local_notifs, CommentResponse},
+ post::PostResponse,
+ },
+ apub::{
+ inbox::shared_inbox::{
+ announce_if_community_is_local,
+ get_user_from_activity,
+ receive_unhandled_activity,
+ },
+ ActorType,
+ FromApub,
+ PageExt,
+ },
+ blocking,
+ websocket::{
+ server::{SendComment, SendPost},
+ UserOperation,
+ },
+ LemmyContext,
+ LemmyError,
+};
+use activitystreams::{activity::Create, base::AnyBase, object::Note, prelude::*};
+use actix_web::HttpResponse;
+use anyhow::Context;
+use lemmy_db::{
+ comment::{Comment, CommentForm},
+ comment_view::CommentView,
+ post::{Post, PostForm},
+ post_view::PostView,
+};
+use lemmy_utils::{location_info, scrape_text_for_mentions};
+
+pub async fn receive_create(
+ activity: AnyBase,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let create = Create::from_any_base(activity)?.context(location_info!())?;
+
+ // ensure that create and actor come from the same instance
+ let user = get_user_from_activity(&create, context).await?;
+ create.id(user.actor_id()?.domain().context(location_info!())?)?;
+
+ match create.object().as_single_kind_str() {
+ Some("Page") => receive_create_post(create, context).await,
+ Some("Note") => receive_create_comment(create, context).await,
+ _ => receive_unhandled_activity(create),
+ }
+}
+
+async fn receive_create_post(
+ create: Create,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let user = get_user_from_activity(&create, context).await?;
+ let page = PageExt::from_any_base(create.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let post = PostForm::from_apub(&page, context, Some(user.actor_id()?)).await?;
+
+ // Using an upsert, since likes (which fetch the post), sometimes come in before the create
+ // resulting in double posts.
+ let inserted_post = blocking(context.pool(), move |conn| Post::upsert(conn, &post)).await??;
+
+ // Refetch the view
+ let inserted_post_id = inserted_post.id;
+ let post_view = blocking(context.pool(), move |conn| {
+ PostView::read(conn, inserted_post_id, None)
+ })
+ .await??;
+
+ let res = PostResponse { post: post_view };
+
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::CreatePost,
+ post: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(create, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_create_comment(
+ create: Create,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let user = get_user_from_activity(&create, context).await?;
+ let note = Note::from_any_base(create.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let comment = CommentForm::from_apub(¬e, context, Some(user.actor_id()?)).await?;
+
+ let inserted_comment =
+ blocking(context.pool(), move |conn| Comment::upsert(conn, &comment)).await??;
+
+ let post_id = inserted_comment.post_id;
+ let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
+
+ // Note:
+ // Although mentions could be gotten from the post tags (they are included there), or the ccs,
+ // Its much easier to scrape them from the comment body, since the API has to do that
+ // anyway.
+ let mentions = scrape_text_for_mentions(&inserted_comment.content);
+ let recipient_ids = send_local_notifs(
+ mentions,
+ inserted_comment.clone(),
+ &user,
+ post,
+ context.pool(),
+ true,
+ )
+ .await?;
+
+ // Refetch the view
+ let comment_view = blocking(context.pool(), move |conn| {
+ CommentView::read(conn, inserted_comment.id, None)
+ })
+ .await??;
+
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ form_id: None,
+ };
+
+ context.chat_server().do_send(SendComment {
+ op: UserOperation::CreateComment,
+ comment: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(create, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
--- /dev/null
+use crate::{
+ api::{comment::CommentResponse, community::CommunityResponse, post::PostResponse},
+ apub::{
+ fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
+ inbox::shared_inbox::{
+ announce_if_community_is_local,
+ get_user_from_activity,
+ receive_unhandled_activity,
+ },
+ ActorType,
+ FromApub,
+ GroupExt,
+ PageExt,
+ },
+ blocking,
+ websocket::{
+ server::{SendComment, SendCommunityRoomMessage, SendPost},
+ UserOperation,
+ },
+ LemmyContext,
+ LemmyError,
+};
+use activitystreams::{activity::Delete, base::AnyBase, object::Note, prelude::*};
+use actix_web::HttpResponse;
+use anyhow::Context;
+use lemmy_db::{
+ comment::{Comment, CommentForm},
+ comment_view::CommentView,
+ community::{Community, CommunityForm},
+ community_view::CommunityView,
+ naive_now,
+ post::{Post, PostForm},
+ post_view::PostView,
+ Crud,
+};
+use lemmy_utils::location_info;
+
+pub async fn receive_delete(
+ activity: AnyBase,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let delete = Delete::from_any_base(activity)?.context(location_info!())?;
+ match delete.object().as_single_kind_str() {
+ Some("Page") => receive_delete_post(delete, context).await,
+ Some("Note") => receive_delete_comment(delete, context).await,
+ Some("Group") => receive_delete_community(delete, context).await,
+ _ => receive_unhandled_activity(delete),
+ }
+}
+
+async fn receive_delete_post(
+ delete: Delete,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let user = get_user_from_activity(&delete, context).await?;
+ let page = PageExt::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let post_ap_id = PostForm::from_apub(&page, context, Some(user.actor_id()?))
+ .await?
+ .get_ap_id()?;
+
+ let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
+
+ let post_form = PostForm {
+ name: post.name.to_owned(),
+ url: post.url.to_owned(),
+ body: post.body.to_owned(),
+ creator_id: post.creator_id.to_owned(),
+ community_id: post.community_id,
+ removed: None,
+ deleted: Some(true),
+ nsfw: post.nsfw,
+ locked: None,
+ stickied: None,
+ updated: Some(naive_now()),
+ embed_title: post.embed_title,
+ embed_description: post.embed_description,
+ embed_html: post.embed_html,
+ thumbnail_url: post.thumbnail_url,
+ ap_id: post.ap_id,
+ local: post.local,
+ published: None,
+ };
+ let post_id = post.id;
+ blocking(context.pool(), move |conn| {
+ Post::update(conn, post_id, &post_form)
+ })
+ .await??;
+
+ // Refetch the view
+ let post_id = post.id;
+ let post_view = blocking(context.pool(), move |conn| {
+ PostView::read(conn, post_id, None)
+ })
+ .await??;
+
+ let res = PostResponse { post: post_view };
+
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::EditPost,
+ post: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(delete, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_delete_comment(
+ delete: Delete,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let user = get_user_from_activity(&delete, context).await?;
+ let note = Note::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let comment_ap_id = CommentForm::from_apub(¬e, context, Some(user.actor_id()?))
+ .await?
+ .get_ap_id()?;
+
+ let comment = get_or_fetch_and_insert_comment(&comment_ap_id, context).await?;
+
+ let comment_form = CommentForm {
+ content: comment.content.to_owned(),
+ parent_id: comment.parent_id,
+ post_id: comment.post_id,
+ creator_id: comment.creator_id,
+ removed: None,
+ deleted: Some(true),
+ read: None,
+ published: None,
+ updated: Some(naive_now()),
+ ap_id: comment.ap_id,
+ local: comment.local,
+ };
+ let comment_id = comment.id;
+ blocking(context.pool(), move |conn| {
+ Comment::update(conn, comment_id, &comment_form)
+ })
+ .await??;
+
+ // Refetch the view
+ let comment_id = comment.id;
+ let comment_view = blocking(context.pool(), move |conn| {
+ CommentView::read(conn, comment_id, None)
+ })
+ .await??;
+
+ // TODO get those recipient actor ids from somewhere
+ let recipient_ids = vec![];
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ form_id: None,
+ };
+
+ context.chat_server().do_send(SendComment {
+ op: UserOperation::EditComment,
+ comment: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(delete, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_delete_community(
+ delete: Delete,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let group = GroupExt::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+ let user = get_user_from_activity(&delete, context).await?;
+
+ let community_actor_id = CommunityForm::from_apub(&group, context, Some(user.actor_id()?))
+ .await?
+ .actor_id;
+
+ let community = blocking(context.pool(), move |conn| {
+ Community::read_from_actor_id(conn, &community_actor_id)
+ })
+ .await??;
+
+ let community_form = CommunityForm {
+ name: community.name.to_owned(),
+ title: community.title.to_owned(),
+ description: community.description.to_owned(),
+ category_id: community.category_id, // Note: need to keep this due to foreign key constraint
+ creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
+ removed: None,
+ published: None,
+ updated: Some(naive_now()),
+ deleted: Some(true),
+ nsfw: community.nsfw,
+ actor_id: community.actor_id,
+ local: community.local,
+ private_key: community.private_key,
+ public_key: community.public_key,
+ last_refreshed_at: None,
+ icon: Some(community.icon.to_owned()),
+ banner: Some(community.banner.to_owned()),
+ };
+
+ let community_id = community.id;
+ blocking(context.pool(), move |conn| {
+ Community::update(conn, community_id, &community_form)
+ })
+ .await??;
+
+ let community_id = community.id;
+ let res = CommunityResponse {
+ community: blocking(context.pool(), move |conn| {
+ CommunityView::read(conn, community_id, None)
+ })
+ .await??,
+ };
+
+ let community_id = res.community.id;
+
+ context.chat_server().do_send(SendCommunityRoomMessage {
+ op: UserOperation::EditCommunity,
+ response: res,
+ community_id,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(delete, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
--- /dev/null
+use crate::{
+ api::{comment::CommentResponse, post::PostResponse},
+ apub::{
+ fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
+ inbox::shared_inbox::{
+ announce_if_community_is_local,
+ get_user_from_activity,
+ receive_unhandled_activity,
+ },
+ FromApub,
+ PageExt,
+ },
+ blocking,
+ websocket::{
+ server::{SendComment, SendPost},
+ UserOperation,
+ },
+ LemmyContext,
+ LemmyError,
+};
+use activitystreams::{activity::Dislike, base::AnyBase, object::Note, prelude::*};
+use actix_web::HttpResponse;
+use anyhow::Context;
+use lemmy_db::{
+ comment::{CommentForm, CommentLike, CommentLikeForm},
+ comment_view::CommentView,
+ post::{PostForm, PostLike, PostLikeForm},
+ post_view::PostView,
+ Likeable,
+};
+use lemmy_utils::location_info;
+
+pub async fn receive_dislike(
+ activity: AnyBase,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let dislike = Dislike::from_any_base(activity)?.context(location_info!())?;
+ match dislike.object().as_single_kind_str() {
+ Some("Page") => receive_dislike_post(dislike, context).await,
+ Some("Note") => receive_dislike_comment(dislike, context).await,
+ _ => receive_unhandled_activity(dislike),
+ }
+}
+
+async fn receive_dislike_post(
+ dislike: Dislike,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let user = get_user_from_activity(&dislike, context).await?;
+ let page = PageExt::from_any_base(
+ dislike
+ .object()
+ .to_owned()
+ .one()
+ .context(location_info!())?,
+ )?
+ .context(location_info!())?;
+
+ let post = PostForm::from_apub(&page, context, None).await?;
+
+ let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context)
+ .await?
+ .id;
+
+ let like_form = PostLikeForm {
+ post_id,
+ user_id: user.id,
+ score: -1,
+ };
+ let user_id = user.id;
+ blocking(context.pool(), move |conn| {
+ PostLike::remove(conn, user_id, post_id)?;
+ PostLike::like(conn, &like_form)
+ })
+ .await??;
+
+ // Refetch the view
+ let post_view = blocking(context.pool(), move |conn| {
+ PostView::read(conn, post_id, None)
+ })
+ .await??;
+
+ let res = PostResponse { post: post_view };
+
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::CreatePostLike,
+ post: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(dislike, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_dislike_comment(
+ dislike: Dislike,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let note = Note::from_any_base(
+ dislike
+ .object()
+ .to_owned()
+ .one()
+ .context(location_info!())?,
+ )?
+ .context(location_info!())?;
+ let user = get_user_from_activity(&dislike, context).await?;
+
+ let comment = CommentForm::from_apub(¬e, context, None).await?;
+
+ let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context)
+ .await?
+ .id;
+
+ let like_form = CommentLikeForm {
+ comment_id,
+ post_id: comment.post_id,
+ user_id: user.id,
+ score: -1,
+ };
+ let user_id = user.id;
+ blocking(context.pool(), move |conn| {
+ CommentLike::remove(conn, user_id, comment_id)?;
+ CommentLike::like(conn, &like_form)
+ })
+ .await??;
+
+ // Refetch the view
+ let comment_view = blocking(context.pool(), move |conn| {
+ CommentView::read(conn, comment_id, None)
+ })
+ .await??;
+
+ // TODO get those recipient actor ids from somewhere
+ let recipient_ids = vec![];
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ form_id: None,
+ };
+
+ context.chat_server().do_send(SendComment {
+ op: UserOperation::CreateCommentLike,
+ comment: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(dislike, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
--- /dev/null
+use crate::{
+ api::{comment::CommentResponse, post::PostResponse},
+ apub::{
+ fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
+ inbox::shared_inbox::{
+ announce_if_community_is_local,
+ get_user_from_activity,
+ receive_unhandled_activity,
+ },
+ FromApub,
+ PageExt,
+ },
+ blocking,
+ websocket::{
+ server::{SendComment, SendPost},
+ UserOperation,
+ },
+ LemmyContext,
+ LemmyError,
+};
+use activitystreams::{activity::Like, base::AnyBase, object::Note, prelude::*};
+use actix_web::HttpResponse;
+use anyhow::Context;
+use lemmy_db::{
+ comment::{CommentForm, CommentLike, CommentLikeForm},
+ comment_view::CommentView,
+ post::{PostForm, PostLike, PostLikeForm},
+ post_view::PostView,
+ Likeable,
+};
+use lemmy_utils::location_info;
+
+pub async fn receive_like(
+ activity: AnyBase,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let like = Like::from_any_base(activity)?.context(location_info!())?;
+ match like.object().as_single_kind_str() {
+ Some("Page") => receive_like_post(like, context).await,
+ Some("Note") => receive_like_comment(like, context).await,
+ _ => receive_unhandled_activity(like),
+ }
+}
+
+async fn receive_like_post(like: Like, context: &LemmyContext) -> Result<HttpResponse, LemmyError> {
+ let user = get_user_from_activity(&like, context).await?;
+ let page = PageExt::from_any_base(like.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let post = PostForm::from_apub(&page, context, None).await?;
+
+ let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context)
+ .await?
+ .id;
+
+ let like_form = PostLikeForm {
+ post_id,
+ user_id: user.id,
+ score: 1,
+ };
+ let user_id = user.id;
+ blocking(context.pool(), move |conn| {
+ PostLike::remove(conn, user_id, post_id)?;
+ PostLike::like(conn, &like_form)
+ })
+ .await??;
+
+ // Refetch the view
+ let post_view = blocking(context.pool(), move |conn| {
+ PostView::read(conn, post_id, None)
+ })
+ .await??;
+
+ let res = PostResponse { post: post_view };
+
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::CreatePostLike,
+ post: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(like, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_like_comment(
+ like: Like,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let note = Note::from_any_base(like.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+ let user = get_user_from_activity(&like, context).await?;
+
+ let comment = CommentForm::from_apub(¬e, context, None).await?;
+
+ let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context)
+ .await?
+ .id;
+
+ let like_form = CommentLikeForm {
+ comment_id,
+ post_id: comment.post_id,
+ user_id: user.id,
+ score: 1,
+ };
+ let user_id = user.id;
+ blocking(context.pool(), move |conn| {
+ CommentLike::remove(conn, user_id, comment_id)?;
+ CommentLike::like(conn, &like_form)
+ })
+ .await??;
+
+ // Refetch the view
+ let comment_view = blocking(context.pool(), move |conn| {
+ CommentView::read(conn, comment_id, None)
+ })
+ .await??;
+
+ // TODO get those recipient actor ids from somewhere
+ let recipient_ids = vec![];
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ form_id: None,
+ };
+
+ context.chat_server().do_send(SendComment {
+ op: UserOperation::CreateCommentLike,
+ comment: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(like, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
--- /dev/null
+pub mod announce;
+pub mod create;
+pub mod delete;
+pub mod dislike;
+pub mod like;
+pub mod remove;
+pub mod undo;
+pub mod update;
--- /dev/null
+use crate::{
+ api::{comment::CommentResponse, community::CommunityResponse, post::PostResponse},
+ apub::{
+ fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
+ inbox::shared_inbox::{
+ announce_if_community_is_local,
+ get_community_id_from_activity,
+ get_user_from_activity,
+ receive_unhandled_activity,
+ },
+ ActorType,
+ FromApub,
+ GroupExt,
+ PageExt,
+ },
+ blocking,
+ websocket::{
+ server::{SendComment, SendCommunityRoomMessage, SendPost},
+ UserOperation,
+ },
+ LemmyContext,
+ LemmyError,
+};
+use activitystreams::{activity::Remove, base::AnyBase, object::Note, prelude::*};
+use actix_web::HttpResponse;
+use anyhow::{anyhow, Context};
+use lemmy_db::{
+ comment::{Comment, CommentForm},
+ comment_view::CommentView,
+ community::{Community, CommunityForm},
+ community_view::CommunityView,
+ naive_now,
+ post::{Post, PostForm},
+ post_view::PostView,
+ Crud,
+};
+use lemmy_utils::location_info;
+
+pub async fn receive_remove(
+ activity: AnyBase,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let remove = Remove::from_any_base(activity)?.context(location_info!())?;
+ let actor = get_user_from_activity(&remove, context).await?;
+ let community = get_community_id_from_activity(&remove)?;
+ if actor.actor_id()?.domain() != community.domain() {
+ return Err(anyhow!("Remove activities are only allowed on local objects").into());
+ }
+
+ match remove.object().as_single_kind_str() {
+ Some("Page") => receive_remove_post(remove, context).await,
+ Some("Note") => receive_remove_comment(remove, context).await,
+ Some("Group") => receive_remove_community(remove, context).await,
+ _ => receive_unhandled_activity(remove),
+ }
+}
+
+async fn receive_remove_post(
+ remove: Remove,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let mod_ = get_user_from_activity(&remove, context).await?;
+ let page = PageExt::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let post_ap_id = PostForm::from_apub(&page, context, None)
+ .await?
+ .get_ap_id()?;
+
+ let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
+
+ let post_form = PostForm {
+ name: post.name.to_owned(),
+ url: post.url.to_owned(),
+ body: post.body.to_owned(),
+ creator_id: post.creator_id.to_owned(),
+ community_id: post.community_id,
+ removed: Some(true),
+ deleted: None,
+ nsfw: post.nsfw,
+ locked: None,
+ stickied: None,
+ updated: Some(naive_now()),
+ embed_title: post.embed_title,
+ embed_description: post.embed_description,
+ embed_html: post.embed_html,
+ thumbnail_url: post.thumbnail_url,
+ ap_id: post.ap_id,
+ local: post.local,
+ published: None,
+ };
+ let post_id = post.id;
+ blocking(context.pool(), move |conn| {
+ Post::update(conn, post_id, &post_form)
+ })
+ .await??;
+
+ // Refetch the view
+ let post_id = post.id;
+ let post_view = blocking(context.pool(), move |conn| {
+ PostView::read(conn, post_id, None)
+ })
+ .await??;
+
+ let res = PostResponse { post: post_view };
+
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::EditPost,
+ post: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(remove, &mod_, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_remove_comment(
+ remove: Remove,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let mod_ = get_user_from_activity(&remove, context).await?;
+ let note = Note::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let comment_ap_id = CommentForm::from_apub(¬e, context, None)
+ .await?
+ .get_ap_id()?;
+
+ let comment = get_or_fetch_and_insert_comment(&comment_ap_id, context).await?;
+
+ let comment_form = CommentForm {
+ content: comment.content.to_owned(),
+ parent_id: comment.parent_id,
+ post_id: comment.post_id,
+ creator_id: comment.creator_id,
+ removed: Some(true),
+ deleted: None,
+ read: None,
+ published: None,
+ updated: Some(naive_now()),
+ ap_id: comment.ap_id,
+ local: comment.local,
+ };
+ let comment_id = comment.id;
+ blocking(context.pool(), move |conn| {
+ Comment::update(conn, comment_id, &comment_form)
+ })
+ .await??;
+
+ // Refetch the view
+ let comment_id = comment.id;
+ let comment_view = blocking(context.pool(), move |conn| {
+ CommentView::read(conn, comment_id, None)
+ })
+ .await??;
+
+ // TODO get those recipient actor ids from somewhere
+ let recipient_ids = vec![];
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ form_id: None,
+ };
+
+ context.chat_server().do_send(SendComment {
+ op: UserOperation::EditComment,
+ comment: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(remove, &mod_, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_remove_community(
+ remove: Remove,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let mod_ = get_user_from_activity(&remove, context).await?;
+ let group = GroupExt::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let community_actor_id = CommunityForm::from_apub(&group, context, Some(mod_.actor_id()?))
+ .await?
+ .actor_id;
+
+ let community = blocking(context.pool(), move |conn| {
+ Community::read_from_actor_id(conn, &community_actor_id)
+ })
+ .await??;
+
+ let community_form = CommunityForm {
+ name: community.name.to_owned(),
+ title: community.title.to_owned(),
+ description: community.description.to_owned(),
+ category_id: community.category_id, // Note: need to keep this due to foreign key constraint
+ creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
+ removed: Some(true),
+ published: None,
+ updated: Some(naive_now()),
+ deleted: None,
+ nsfw: community.nsfw,
+ actor_id: community.actor_id,
+ local: community.local,
+ private_key: community.private_key,
+ public_key: community.public_key,
+ last_refreshed_at: None,
+ icon: Some(community.icon.to_owned()),
+ banner: Some(community.banner.to_owned()),
+ };
+
+ let community_id = community.id;
+ blocking(context.pool(), move |conn| {
+ Community::update(conn, community_id, &community_form)
+ })
+ .await??;
+
+ let community_id = community.id;
+ let res = CommunityResponse {
+ community: blocking(context.pool(), move |conn| {
+ CommunityView::read(conn, community_id, None)
+ })
+ .await??,
+ };
+
+ let community_id = res.community.id;
+
+ context.chat_server().do_send(SendCommunityRoomMessage {
+ op: UserOperation::EditCommunity,
+ response: res,
+ community_id,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(remove, &mod_, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
--- /dev/null
+use crate::{
+ api::{comment::CommentResponse, community::CommunityResponse, post::PostResponse},
+ apub::{
+ fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
+ inbox::shared_inbox::{
+ announce_if_community_is_local,
+ get_user_from_activity,
+ receive_unhandled_activity,
+ },
+ ActorType,
+ FromApub,
+ GroupExt,
+ PageExt,
+ },
+ blocking,
+ websocket::{
+ server::{SendComment, SendCommunityRoomMessage, SendPost},
+ UserOperation,
+ },
+ LemmyContext,
+ LemmyError,
+};
+use activitystreams::{
+ activity::*,
+ base::{AnyBase, AsBase},
+ object::Note,
+ prelude::*,
+};
+use actix_web::HttpResponse;
+use anyhow::{anyhow, Context};
+use lemmy_db::{
+ comment::{Comment, CommentForm, CommentLike},
+ comment_view::CommentView,
+ community::{Community, CommunityForm},
+ community_view::CommunityView,
+ naive_now,
+ post::{Post, PostForm, PostLike},
+ post_view::PostView,
+ Crud,
+ Likeable,
+};
+use lemmy_utils::location_info;
+
+pub async fn receive_undo(
+ activity: AnyBase,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let undo = Undo::from_any_base(activity)?.context(location_info!())?;
+ match undo.object().as_single_kind_str() {
+ Some("Delete") => receive_undo_delete(undo, context).await,
+ Some("Remove") => receive_undo_remove(undo, context).await,
+ Some("Like") => receive_undo_like(undo, context).await,
+ Some("Dislike") => receive_undo_dislike(undo, context).await,
+ _ => receive_unhandled_activity(undo),
+ }
+}
+
+fn check_is_undo_valid<T, A>(outer_activity: &Undo, inner_activity: &T) -> Result<(), LemmyError>
+where
+ T: AsBase<A> + ActorAndObjectRef,
+{
+ let outer_actor = outer_activity.actor()?;
+ let outer_actor_uri = outer_actor
+ .as_single_xsd_any_uri()
+ .context(location_info!())?;
+
+ let inner_actor = inner_activity.actor()?;
+ let inner_actor_uri = inner_actor
+ .as_single_xsd_any_uri()
+ .context(location_info!())?;
+
+ if outer_actor_uri.domain() != inner_actor_uri.domain() {
+ Err(anyhow!("Cant undo activities from a different instance").into())
+ } else {
+ Ok(())
+ }
+}
+
+async fn receive_undo_delete(
+ undo: Undo,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+ check_is_undo_valid(&undo, &delete)?;
+ let type_ = delete
+ .object()
+ .as_single_kind_str()
+ .context(location_info!())?;
+ match type_ {
+ "Note" => receive_undo_delete_comment(undo, &delete, context).await,
+ "Page" => receive_undo_delete_post(undo, &delete, context).await,
+ "Group" => receive_undo_delete_community(undo, &delete, context).await,
+ d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
+ }
+}
+
+async fn receive_undo_remove(
+ undo: Undo,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let remove = Remove::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+ check_is_undo_valid(&undo, &remove)?;
+
+ let type_ = remove
+ .object()
+ .as_single_kind_str()
+ .context(location_info!())?;
+ match type_ {
+ "Note" => receive_undo_remove_comment(undo, &remove, context).await,
+ "Page" => receive_undo_remove_post(undo, &remove, context).await,
+ "Group" => receive_undo_remove_community(undo, &remove, context).await,
+ d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
+ }
+}
+
+async fn receive_undo_like(undo: Undo, context: &LemmyContext) -> Result<HttpResponse, LemmyError> {
+ let like = Like::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+ check_is_undo_valid(&undo, &like)?;
+
+ let type_ = like
+ .object()
+ .as_single_kind_str()
+ .context(location_info!())?;
+ match type_ {
+ "Note" => receive_undo_like_comment(undo, &like, context).await,
+ "Page" => receive_undo_like_post(undo, &like, context).await,
+ d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
+ }
+}
+
+async fn receive_undo_dislike(
+ undo: Undo,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let dislike = Dislike::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+ check_is_undo_valid(&undo, &dislike)?;
+
+ let type_ = dislike
+ .object()
+ .as_single_kind_str()
+ .context(location_info!())?;
+ match type_ {
+ "Note" => receive_undo_dislike_comment(undo, &dislike, context).await,
+ "Page" => receive_undo_dislike_post(undo, &dislike, context).await,
+ d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
+ }
+}
+
+async fn receive_undo_delete_comment(
+ undo: Undo,
+ delete: &Delete,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let user = get_user_from_activity(delete, context).await?;
+ let note = Note::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let comment_ap_id = CommentForm::from_apub(¬e, context, Some(user.actor_id()?))
+ .await?
+ .get_ap_id()?;
+
+ let comment = get_or_fetch_and_insert_comment(&comment_ap_id, context).await?;
+
+ let comment_form = CommentForm {
+ content: comment.content.to_owned(),
+ parent_id: comment.parent_id,
+ post_id: comment.post_id,
+ creator_id: comment.creator_id,
+ removed: None,
+ deleted: Some(false),
+ read: None,
+ published: None,
+ updated: Some(naive_now()),
+ ap_id: comment.ap_id,
+ local: comment.local,
+ };
+ let comment_id = comment.id;
+ blocking(context.pool(), move |conn| {
+ Comment::update(conn, comment_id, &comment_form)
+ })
+ .await??;
+
+ // Refetch the view
+ let comment_id = comment.id;
+ let comment_view = blocking(context.pool(), move |conn| {
+ CommentView::read(conn, comment_id, None)
+ })
+ .await??;
+
+ // TODO get those recipient actor ids from somewhere
+ let recipient_ids = vec![];
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ form_id: None,
+ };
+
+ context.chat_server().do_send(SendComment {
+ op: UserOperation::EditComment,
+ comment: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(undo, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_undo_remove_comment(
+ undo: Undo,
+ remove: &Remove,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let mod_ = get_user_from_activity(remove, context).await?;
+ let note = Note::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let comment_ap_id = CommentForm::from_apub(¬e, context, None)
+ .await?
+ .get_ap_id()?;
+
+ let comment = get_or_fetch_and_insert_comment(&comment_ap_id, context).await?;
+
+ let comment_form = CommentForm {
+ content: comment.content.to_owned(),
+ parent_id: comment.parent_id,
+ post_id: comment.post_id,
+ creator_id: comment.creator_id,
+ removed: Some(false),
+ deleted: None,
+ read: None,
+ published: None,
+ updated: Some(naive_now()),
+ ap_id: comment.ap_id,
+ local: comment.local,
+ };
+ let comment_id = comment.id;
+ blocking(context.pool(), move |conn| {
+ Comment::update(conn, comment_id, &comment_form)
+ })
+ .await??;
+
+ // Refetch the view
+ let comment_id = comment.id;
+ let comment_view = blocking(context.pool(), move |conn| {
+ CommentView::read(conn, comment_id, None)
+ })
+ .await??;
+
+ // TODO get those recipient actor ids from somewhere
+ let recipient_ids = vec![];
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ form_id: None,
+ };
+
+ context.chat_server().do_send(SendComment {
+ op: UserOperation::EditComment,
+ comment: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(undo, &mod_, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_undo_delete_post(
+ undo: Undo,
+ delete: &Delete,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let user = get_user_from_activity(delete, context).await?;
+ let page = PageExt::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let post_ap_id = PostForm::from_apub(&page, context, Some(user.actor_id()?))
+ .await?
+ .get_ap_id()?;
+
+ let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
+
+ let post_form = PostForm {
+ name: post.name.to_owned(),
+ url: post.url.to_owned(),
+ body: post.body.to_owned(),
+ creator_id: post.creator_id.to_owned(),
+ community_id: post.community_id,
+ removed: None,
+ deleted: Some(false),
+ nsfw: post.nsfw,
+ locked: None,
+ stickied: None,
+ updated: Some(naive_now()),
+ embed_title: post.embed_title,
+ embed_description: post.embed_description,
+ embed_html: post.embed_html,
+ thumbnail_url: post.thumbnail_url,
+ ap_id: post.ap_id,
+ local: post.local,
+ published: None,
+ };
+ let post_id = post.id;
+ blocking(context.pool(), move |conn| {
+ Post::update(conn, post_id, &post_form)
+ })
+ .await??;
+
+ // Refetch the view
+ let post_id = post.id;
+ let post_view = blocking(context.pool(), move |conn| {
+ PostView::read(conn, post_id, None)
+ })
+ .await??;
+
+ let res = PostResponse { post: post_view };
+
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::EditPost,
+ post: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(undo, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_undo_remove_post(
+ undo: Undo,
+ remove: &Remove,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let mod_ = get_user_from_activity(remove, context).await?;
+ let page = PageExt::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let post_ap_id = PostForm::from_apub(&page, context, None)
+ .await?
+ .get_ap_id()?;
+
+ let post = get_or_fetch_and_insert_post(&post_ap_id, context).await?;
+
+ let post_form = PostForm {
+ name: post.name.to_owned(),
+ url: post.url.to_owned(),
+ body: post.body.to_owned(),
+ creator_id: post.creator_id.to_owned(),
+ community_id: post.community_id,
+ removed: Some(false),
+ deleted: None,
+ nsfw: post.nsfw,
+ locked: None,
+ stickied: None,
+ updated: Some(naive_now()),
+ embed_title: post.embed_title,
+ embed_description: post.embed_description,
+ embed_html: post.embed_html,
+ thumbnail_url: post.thumbnail_url,
+ ap_id: post.ap_id,
+ local: post.local,
+ published: None,
+ };
+ let post_id = post.id;
+ blocking(context.pool(), move |conn| {
+ Post::update(conn, post_id, &post_form)
+ })
+ .await??;
+
+ // Refetch the view
+ let post_id = post.id;
+ let post_view = blocking(context.pool(), move |conn| {
+ PostView::read(conn, post_id, None)
+ })
+ .await??;
+
+ let res = PostResponse { post: post_view };
+
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::EditPost,
+ post: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(undo, &mod_, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_undo_delete_community(
+ undo: Undo,
+ delete: &Delete,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let user = get_user_from_activity(delete, context).await?;
+ let group = GroupExt::from_any_base(delete.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let community_actor_id = CommunityForm::from_apub(&group, context, Some(user.actor_id()?))
+ .await?
+ .actor_id;
+
+ let community = blocking(context.pool(), move |conn| {
+ Community::read_from_actor_id(conn, &community_actor_id)
+ })
+ .await??;
+
+ let community_form = CommunityForm {
+ name: community.name.to_owned(),
+ title: community.title.to_owned(),
+ description: community.description.to_owned(),
+ category_id: community.category_id, // Note: need to keep this due to foreign key constraint
+ creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
+ removed: None,
+ published: None,
+ updated: Some(naive_now()),
+ deleted: Some(false),
+ nsfw: community.nsfw,
+ actor_id: community.actor_id,
+ local: community.local,
+ private_key: community.private_key,
+ public_key: community.public_key,
+ last_refreshed_at: None,
+ icon: Some(community.icon.to_owned()),
+ banner: Some(community.banner.to_owned()),
+ };
+
+ let community_id = community.id;
+ blocking(context.pool(), move |conn| {
+ Community::update(conn, community_id, &community_form)
+ })
+ .await??;
+
+ let community_id = community.id;
+ let res = CommunityResponse {
+ community: blocking(context.pool(), move |conn| {
+ CommunityView::read(conn, community_id, None)
+ })
+ .await??,
+ };
+
+ let community_id = res.community.id;
+
+ context.chat_server().do_send(SendCommunityRoomMessage {
+ op: UserOperation::EditCommunity,
+ response: res,
+ community_id,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(undo, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_undo_remove_community(
+ undo: Undo,
+ remove: &Remove,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let mod_ = get_user_from_activity(remove, context).await?;
+ let group = GroupExt::from_any_base(remove.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let community_actor_id = CommunityForm::from_apub(&group, context, Some(mod_.actor_id()?))
+ .await?
+ .actor_id;
+
+ let community = blocking(context.pool(), move |conn| {
+ Community::read_from_actor_id(conn, &community_actor_id)
+ })
+ .await??;
+
+ let community_form = CommunityForm {
+ name: community.name.to_owned(),
+ title: community.title.to_owned(),
+ description: community.description.to_owned(),
+ category_id: community.category_id, // Note: need to keep this due to foreign key constraint
+ creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
+ removed: Some(false),
+ published: None,
+ updated: Some(naive_now()),
+ deleted: None,
+ nsfw: community.nsfw,
+ actor_id: community.actor_id,
+ local: community.local,
+ private_key: community.private_key,
+ public_key: community.public_key,
+ last_refreshed_at: None,
+ icon: Some(community.icon.to_owned()),
+ banner: Some(community.banner.to_owned()),
+ };
+
+ let community_id = community.id;
+ blocking(context.pool(), move |conn| {
+ Community::update(conn, community_id, &community_form)
+ })
+ .await??;
+
+ let community_id = community.id;
+ let res = CommunityResponse {
+ community: blocking(context.pool(), move |conn| {
+ CommunityView::read(conn, community_id, None)
+ })
+ .await??,
+ };
+
+ let community_id = res.community.id;
+
+ context.chat_server().do_send(SendCommunityRoomMessage {
+ op: UserOperation::EditCommunity,
+ response: res,
+ community_id,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(undo, &mod_, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_undo_like_comment(
+ undo: Undo,
+ like: &Like,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let user = get_user_from_activity(like, context).await?;
+ let note = Note::from_any_base(like.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let comment = CommentForm::from_apub(¬e, context, None).await?;
+
+ let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context)
+ .await?
+ .id;
+
+ let user_id = user.id;
+ blocking(context.pool(), move |conn| {
+ CommentLike::remove(conn, user_id, comment_id)
+ })
+ .await??;
+
+ // Refetch the view
+ let comment_view = blocking(context.pool(), move |conn| {
+ CommentView::read(conn, comment_id, None)
+ })
+ .await??;
+
+ // TODO get those recipient actor ids from somewhere
+ let recipient_ids = vec![];
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ form_id: None,
+ };
+
+ context.chat_server().do_send(SendComment {
+ op: UserOperation::CreateCommentLike,
+ comment: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(undo, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_undo_like_post(
+ undo: Undo,
+ like: &Like,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let user = get_user_from_activity(like, context).await?;
+ let page = PageExt::from_any_base(like.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let post = PostForm::from_apub(&page, context, None).await?;
+
+ let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context)
+ .await?
+ .id;
+
+ let user_id = user.id;
+ blocking(context.pool(), move |conn| {
+ PostLike::remove(conn, user_id, post_id)
+ })
+ .await??;
+
+ // Refetch the view
+ let post_view = blocking(context.pool(), move |conn| {
+ PostView::read(conn, post_id, None)
+ })
+ .await??;
+
+ let res = PostResponse { post: post_view };
+
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::CreatePostLike,
+ post: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(undo, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_undo_dislike_comment(
+ undo: Undo,
+ dislike: &Dislike,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let user = get_user_from_activity(dislike, context).await?;
+ let note = Note::from_any_base(
+ dislike
+ .object()
+ .to_owned()
+ .one()
+ .context(location_info!())?,
+ )?
+ .context(location_info!())?;
+
+ let comment = CommentForm::from_apub(¬e, context, None).await?;
+
+ let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context)
+ .await?
+ .id;
+
+ let user_id = user.id;
+ blocking(context.pool(), move |conn| {
+ CommentLike::remove(conn, user_id, comment_id)
+ })
+ .await??;
+
+ // Refetch the view
+ let comment_view = blocking(context.pool(), move |conn| {
+ CommentView::read(conn, comment_id, None)
+ })
+ .await??;
+
+ // TODO get those recipient actor ids from somewhere
+ let recipient_ids = vec![];
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ form_id: None,
+ };
+
+ context.chat_server().do_send(SendComment {
+ op: UserOperation::CreateCommentLike,
+ comment: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(undo, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_undo_dislike_post(
+ undo: Undo,
+ dislike: &Dislike,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let user = get_user_from_activity(dislike, context).await?;
+ let page = PageExt::from_any_base(
+ dislike
+ .object()
+ .to_owned()
+ .one()
+ .context(location_info!())?,
+ )?
+ .context(location_info!())?;
+
+ let post = PostForm::from_apub(&page, context, None).await?;
+
+ let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context)
+ .await?
+ .id;
+
+ let user_id = user.id;
+ blocking(context.pool(), move |conn| {
+ PostLike::remove(conn, user_id, post_id)
+ })
+ .await??;
+
+ // Refetch the view
+ let post_view = blocking(context.pool(), move |conn| {
+ PostView::read(conn, post_id, None)
+ })
+ .await??;
+
+ let res = PostResponse { post: post_view };
+
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::CreatePostLike,
+ post: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(undo, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
--- /dev/null
+use crate::{
+ api::{
+ comment::{send_local_notifs, CommentResponse},
+ post::PostResponse,
+ },
+ apub::{
+ fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
+ inbox::shared_inbox::{
+ announce_if_community_is_local,
+ get_user_from_activity,
+ receive_unhandled_activity,
+ },
+ ActorType,
+ FromApub,
+ PageExt,
+ },
+ blocking,
+ websocket::{
+ server::{SendComment, SendPost},
+ UserOperation,
+ },
+ LemmyContext,
+ LemmyError,
+};
+use activitystreams::{activity::Update, base::AnyBase, object::Note, prelude::*};
+use actix_web::HttpResponse;
+use anyhow::Context;
+use lemmy_db::{
+ comment::{Comment, CommentForm},
+ comment_view::CommentView,
+ post::{Post, PostForm},
+ post_view::PostView,
+ Crud,
+};
+use lemmy_utils::{location_info, scrape_text_for_mentions};
+
+pub async fn receive_update(
+ activity: AnyBase,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let update = Update::from_any_base(activity)?.context(location_info!())?;
+
+ // ensure that update and actor come from the same instance
+ let user = get_user_from_activity(&update, context).await?;
+ update.id(user.actor_id()?.domain().context(location_info!())?)?;
+
+ match update.object().as_single_kind_str() {
+ Some("Page") => receive_update_post(update, context).await,
+ Some("Note") => receive_update_comment(update, context).await,
+ _ => receive_unhandled_activity(update),
+ }
+}
+
+async fn receive_update_post(
+ update: Update,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let user = get_user_from_activity(&update, context).await?;
+ let page = PageExt::from_any_base(update.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+
+ let post = PostForm::from_apub(&page, context, Some(user.actor_id()?)).await?;
+
+ let original_post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context)
+ .await?
+ .id;
+
+ blocking(context.pool(), move |conn| {
+ Post::update(conn, original_post_id, &post)
+ })
+ .await??;
+
+ // Refetch the view
+ let post_view = blocking(context.pool(), move |conn| {
+ PostView::read(conn, original_post_id, None)
+ })
+ .await??;
+
+ let res = PostResponse { post: post_view };
+
+ context.chat_server().do_send(SendPost {
+ op: UserOperation::EditPost,
+ post: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(update, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_update_comment(
+ update: Update,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let note = Note::from_any_base(update.object().to_owned().one().context(location_info!())?)?
+ .context(location_info!())?;
+ let user = get_user_from_activity(&update, context).await?;
+
+ let comment = CommentForm::from_apub(¬e, context, Some(user.actor_id()?)).await?;
+
+ let original_comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context)
+ .await?
+ .id;
+
+ let updated_comment = blocking(context.pool(), move |conn| {
+ Comment::update(conn, original_comment_id, &comment)
+ })
+ .await??;
+
+ let post_id = updated_comment.post_id;
+ let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
+
+ let mentions = scrape_text_for_mentions(&updated_comment.content);
+ let recipient_ids = send_local_notifs(
+ mentions,
+ updated_comment,
+ &user,
+ post,
+ context.pool(),
+ false,
+ )
+ .await?;
+
+ // Refetch the view
+ let comment_view = blocking(context.pool(), move |conn| {
+ CommentView::read(conn, original_comment_id, None)
+ })
+ .await??;
+
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ form_id: None,
+ };
+
+ context.chat_server().do_send(SendComment {
+ op: UserOperation::EditComment,
+ comment: res,
+ websocket_id: None,
+ });
+
+ announce_if_community_is_local(update, &user, context).await?;
+ Ok(HttpResponse::Ok().finish())
+}
--- /dev/null
+use crate::{
+ apub::{
+ check_is_apub_id_valid,
+ extensions::signatures::verify,
+ fetcher::get_or_fetch_and_upsert_user,
+ insert_activity,
+ ActorType,
+ },
+ blocking,
+ LemmyContext,
+ LemmyError,
+};
+use activitystreams::{
+ activity::{ActorAndObject, Follow, Undo},
+ base::AnyBase,
+ prelude::*,
+};
+use actix_web::{web, HttpRequest, HttpResponse};
+use anyhow::{anyhow, Context};
+use lemmy_db::{
+ community::{Community, CommunityFollower, CommunityFollowerForm},
+ user::User_,
+ Followable,
+};
+use lemmy_utils::location_info;
+use log::debug;
+use serde::{Deserialize, Serialize};
+use std::fmt::Debug;
+
+#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
+#[serde(rename_all = "PascalCase")]
+pub enum ValidTypes {
+ Follow,
+ Undo,
+}
+
+pub type AcceptedActivities = ActorAndObject<ValidTypes>;
+
+/// Handler for all incoming activities to community inboxes.
+pub async fn community_inbox(
+ request: HttpRequest,
+ input: web::Json<AcceptedActivities>,
+ path: web::Path<String>,
+ context: web::Data<LemmyContext>,
+) -> Result<HttpResponse, LemmyError> {
+ let activity = input.into_inner();
+
+ let path = path.into_inner();
+ let community = blocking(&context.pool(), move |conn| {
+ Community::read_from_name(&conn, &path)
+ })
+ .await??;
+
+ if !community.local {
+ return Err(
+ anyhow!(
+ "Received activity is addressed to remote community {}",
+ &community.actor_id
+ )
+ .into(),
+ );
+ }
+ debug!(
+ "Community {} received activity {:?}",
+ &community.name, &activity
+ );
+ let user_uri = activity
+ .actor()?
+ .as_single_xsd_any_uri()
+ .context(location_info!())?;
+ check_is_apub_id_valid(user_uri)?;
+
+ let user = get_or_fetch_and_upsert_user(&user_uri, &context).await?;
+
+ verify(&request, &user)?;
+
+ let any_base = activity.clone().into_any_base()?;
+ let kind = activity.kind().context(location_info!())?;
+ let user_id = user.id;
+ let res = match kind {
+ ValidTypes::Follow => handle_follow(any_base, user, community, &context).await,
+ ValidTypes::Undo => handle_undo_follow(any_base, user, community, &context).await,
+ };
+
+ insert_activity(user_id, activity.clone(), false, context.pool()).await?;
+ res
+}
+
+/// Handle a follow request from a remote user, adding it to the local database and returning an
+/// Accept activity.
+async fn handle_follow(
+ activity: AnyBase,
+ user: User_,
+ community: Community,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let follow = Follow::from_any_base(activity)?.context(location_info!())?;
+ let community_follower_form = CommunityFollowerForm {
+ community_id: community.id,
+ user_id: user.id,
+ };
+
+ // This will fail if they're already a follower, but ignore the error.
+ blocking(&context.pool(), move |conn| {
+ CommunityFollower::follow(&conn, &community_follower_form).ok()
+ })
+ .await?;
+
+ community.send_accept_follow(follow, context).await?;
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn handle_undo_follow(
+ activity: AnyBase,
+ user: User_,
+ community: Community,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let _undo = Undo::from_any_base(activity)?.context(location_info!())?;
+
+ let community_follower_form = CommunityFollowerForm {
+ community_id: community.id,
+ user_id: user.id,
+ };
+
+ // This will fail if they aren't a follower, but ignore the error.
+ blocking(&context.pool(), move |conn| {
+ CommunityFollower::unfollow(&conn, &community_follower_form).ok()
+ })
+ .await?;
+
+ Ok(HttpResponse::Ok().finish())
+}
--- /dev/null
+pub mod activities;
+pub mod community_inbox;
+pub mod shared_inbox;
+pub mod user_inbox;
--- /dev/null
+use crate::{
+ apub::{
+ check_is_apub_id_valid,
+ community::do_announce,
+ extensions::signatures::verify,
+ fetcher::{
+ get_or_fetch_and_upsert_actor,
+ get_or_fetch_and_upsert_community,
+ get_or_fetch_and_upsert_user,
+ },
+ inbox::activities::{
+ announce::receive_announce,
+ create::receive_create,
+ delete::receive_delete,
+ dislike::receive_dislike,
+ like::receive_like,
+ remove::receive_remove,
+ undo::receive_undo,
+ update::receive_update,
+ },
+ insert_activity,
+ },
+ LemmyContext,
+ LemmyError,
+};
+use activitystreams::{
+ activity::{ActorAndObject, ActorAndObjectRef},
+ base::{AsBase, Extends},
+ object::AsObject,
+ prelude::*,
+};
+use actix_web::{web, HttpRequest, HttpResponse};
+use anyhow::Context;
+use lemmy_db::user::User_;
+use lemmy_utils::location_info;
+use log::debug;
+use serde::{Deserialize, Serialize};
+use std::fmt::Debug;
+use url::Url;
+
+#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
+#[serde(rename_all = "PascalCase")]
+pub enum ValidTypes {
+ Create,
+ Update,
+ Like,
+ Dislike,
+ Delete,
+ Undo,
+ Remove,
+ Announce,
+}
+
+// TODO: this isnt entirely correct, cause some of these activities are not ActorAndObject,
+// but it might still work due to the anybase conversion
+pub type AcceptedActivities = ActorAndObject<ValidTypes>;
+
+/// Handler for all incoming activities to user inboxes.
+pub async fn shared_inbox(
+ request: HttpRequest,
+ input: web::Json<AcceptedActivities>,
+ context: web::Data<LemmyContext>,
+) -> Result<HttpResponse, LemmyError> {
+ let activity = input.into_inner();
+
+ let json = serde_json::to_string(&activity)?;
+ debug!("Shared inbox received activity: {}", json);
+
+ let sender = &activity
+ .actor()?
+ .to_owned()
+ .single_xsd_any_uri()
+ .context(location_info!())?;
+ let community = get_community_id_from_activity(&activity)?;
+
+ check_is_apub_id_valid(sender)?;
+ check_is_apub_id_valid(&community)?;
+
+ let actor = get_or_fetch_and_upsert_actor(sender, &context).await?;
+ verify(&request, actor.as_ref())?;
+
+ let any_base = activity.clone().into_any_base()?;
+ let kind = activity.kind().context(location_info!())?;
+ let res = match kind {
+ ValidTypes::Announce => receive_announce(any_base, &context).await,
+ ValidTypes::Create => receive_create(any_base, &context).await,
+ ValidTypes::Update => receive_update(any_base, &context).await,
+ ValidTypes::Like => receive_like(any_base, &context).await,
+ ValidTypes::Dislike => receive_dislike(any_base, &context).await,
+ ValidTypes::Remove => receive_remove(any_base, &context).await,
+ ValidTypes::Delete => receive_delete(any_base, &context).await,
+ ValidTypes::Undo => receive_undo(any_base, &context).await,
+ };
+
+ insert_activity(actor.user_id(), activity.clone(), false, context.pool()).await?;
+ res
+}
+
+pub(in crate::apub::inbox) fn receive_unhandled_activity<A>(
+ activity: A,
+) -> Result<HttpResponse, LemmyError>
+where
+ A: Debug,
+{
+ debug!("received unhandled activity type: {:?}", activity);
+ Ok(HttpResponse::NotImplemented().finish())
+}
+
+pub(in crate::apub::inbox) async fn get_user_from_activity<T, A>(
+ activity: &T,
+ context: &LemmyContext,
+) -> Result<User_, LemmyError>
+where
+ T: AsBase<A> + ActorAndObjectRef,
+{
+ let actor = activity.actor()?;
+ let user_uri = actor.as_single_xsd_any_uri().context(location_info!())?;
+ get_or_fetch_and_upsert_user(&user_uri, context).await
+}
+
+pub(in crate::apub::inbox) fn get_community_id_from_activity<T, A>(
+ activity: &T,
+) -> Result<Url, LemmyError>
+where
+ T: AsBase<A> + ActorAndObjectRef + AsObject<A>,
+{
+ let cc = activity.cc().context(location_info!())?;
+ let cc = cc.as_many().context(location_info!())?;
+ Ok(
+ cc.first()
+ .context(location_info!())?
+ .as_xsd_any_uri()
+ .context(location_info!())?
+ .to_owned(),
+ )
+}
+
+pub(in crate::apub::inbox) async fn announce_if_community_is_local<T, Kind>(
+ activity: T,
+ user: &User_,
+ context: &LemmyContext,
+) -> Result<(), LemmyError>
+where
+ T: AsObject<Kind>,
+ T: Extends<Kind>,
+ Kind: Serialize,
+ <T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
+{
+ let cc = activity.cc().context(location_info!())?;
+ let cc = cc.as_many().context(location_info!())?;
+ let community_followers_uri = cc
+ .first()
+ .context(location_info!())?
+ .as_xsd_any_uri()
+ .context(location_info!())?;
+ // TODO: this is hacky but seems to be the only way to get the community ID
+ let community_uri = community_followers_uri
+ .to_string()
+ .replace("/followers", "");
+ let community = get_or_fetch_and_upsert_community(&Url::parse(&community_uri)?, context).await?;
+
+ if community.local {
+ do_announce(activity.into_any_base()?, &community, &user, context).await?;
+ }
+ Ok(())
+}
--- /dev/null
+use crate::{
+ api::user::PrivateMessageResponse,
+ apub::{
+ check_is_apub_id_valid,
+ extensions::signatures::verify,
+ fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_community},
+ insert_activity,
+ FromApub,
+ },
+ blocking,
+ websocket::{server::SendUserRoomMessage, UserOperation},
+ LemmyContext,
+ LemmyError,
+};
+use activitystreams::{
+ activity::{Accept, ActorAndObject, Create, Delete, Undo, Update},
+ base::AnyBase,
+ object::Note,
+ prelude::*,
+};
+use actix_web::{web, HttpRequest, HttpResponse};
+use anyhow::Context;
+use lemmy_db::{
+ community::{CommunityFollower, CommunityFollowerForm},
+ naive_now,
+ private_message::{PrivateMessage, PrivateMessageForm},
+ private_message_view::PrivateMessageView,
+ user::User_,
+ Crud,
+ Followable,
+};
+use lemmy_utils::location_info;
+use log::debug;
+use serde::{Deserialize, Serialize};
+use std::fmt::Debug;
+
+#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
+#[serde(rename_all = "PascalCase")]
+pub enum ValidTypes {
+ Accept,
+ Create,
+ Update,
+ Delete,
+ Undo,
+}
+
+pub type AcceptedActivities = ActorAndObject<ValidTypes>;
+
+/// Handler for all incoming activities to user inboxes.
+pub async fn user_inbox(
+ request: HttpRequest,
+ input: web::Json<AcceptedActivities>,
+ path: web::Path<String>,
+ context: web::Data<LemmyContext>,
+) -> Result<HttpResponse, LemmyError> {
+ let activity = input.into_inner();
+ let username = path.into_inner();
+ debug!("User {} received activity: {:?}", &username, &activity);
+
+ let actor_uri = activity
+ .actor()?
+ .as_single_xsd_any_uri()
+ .context(location_info!())?;
+
+ check_is_apub_id_valid(actor_uri)?;
+
+ let actor = get_or_fetch_and_upsert_actor(actor_uri, &context).await?;
+ verify(&request, actor.as_ref())?;
+
+ let any_base = activity.clone().into_any_base()?;
+ let kind = activity.kind().context(location_info!())?;
+ let res = match kind {
+ ValidTypes::Accept => receive_accept(any_base, username, &context).await,
+ ValidTypes::Create => receive_create_private_message(any_base, &context).await,
+ ValidTypes::Update => receive_update_private_message(any_base, &context).await,
+ ValidTypes::Delete => receive_delete_private_message(any_base, &context).await,
+ ValidTypes::Undo => receive_undo_delete_private_message(any_base, &context).await,
+ };
+
+ insert_activity(actor.user_id(), activity.clone(), false, context.pool()).await?;
+ res
+}
+
+/// Handle accepted follows.
+async fn receive_accept(
+ activity: AnyBase,
+ username: String,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let accept = Accept::from_any_base(activity)?.context(location_info!())?;
+ let community_uri = accept
+ .actor()?
+ .to_owned()
+ .single_xsd_any_uri()
+ .context(location_info!())?;
+
+ let community = get_or_fetch_and_upsert_community(&community_uri, context).await?;
+
+ let user = blocking(&context.pool(), move |conn| {
+ User_::read_from_name(conn, &username)
+ })
+ .await??;
+
+ // Now you need to add this to the community follower
+ let community_follower_form = CommunityFollowerForm {
+ community_id: community.id,
+ user_id: user.id,
+ };
+
+ // This will fail if they're already a follower
+ blocking(&context.pool(), move |conn| {
+ CommunityFollower::follow(conn, &community_follower_form).ok()
+ })
+ .await?;
+
+ // TODO: make sure that we actually requested a follow
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_create_private_message(
+ activity: AnyBase,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let create = Create::from_any_base(activity)?.context(location_info!())?;
+ let note = Note::from_any_base(
+ create
+ .object()
+ .as_one()
+ .context(location_info!())?
+ .to_owned(),
+ )?
+ .context(location_info!())?;
+
+ let domain = Some(create.id_unchecked().context(location_info!())?.to_owned());
+ let private_message = PrivateMessageForm::from_apub(¬e, context, domain).await?;
+
+ let inserted_private_message = blocking(&context.pool(), move |conn| {
+ PrivateMessage::create(conn, &private_message)
+ })
+ .await??;
+
+ let message = blocking(&context.pool(), move |conn| {
+ PrivateMessageView::read(conn, inserted_private_message.id)
+ })
+ .await??;
+
+ let res = PrivateMessageResponse { message };
+
+ let recipient_id = res.message.recipient_id;
+
+ context.chat_server().do_send(SendUserRoomMessage {
+ op: UserOperation::CreatePrivateMessage,
+ response: res,
+ recipient_id,
+ websocket_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_update_private_message(
+ activity: AnyBase,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let update = Update::from_any_base(activity)?.context(location_info!())?;
+ let note = Note::from_any_base(
+ update
+ .object()
+ .as_one()
+ .context(location_info!())?
+ .to_owned(),
+ )?
+ .context(location_info!())?;
+
+ let domain = Some(update.id_unchecked().context(location_info!())?.to_owned());
+ let private_message_form = PrivateMessageForm::from_apub(¬e, context, domain).await?;
+
+ let private_message_ap_id = private_message_form.ap_id.clone();
+ let private_message = blocking(&context.pool(), move |conn| {
+ PrivateMessage::read_from_apub_id(conn, &private_message_ap_id)
+ })
+ .await??;
+
+ let private_message_id = private_message.id;
+ blocking(&context.pool(), move |conn| {
+ PrivateMessage::update(conn, private_message_id, &private_message_form)
+ })
+ .await??;
+
+ let private_message_id = private_message.id;
+ let message = blocking(&context.pool(), move |conn| {
+ PrivateMessageView::read(conn, private_message_id)
+ })
+ .await??;
+
+ let res = PrivateMessageResponse { message };
+
+ let recipient_id = res.message.recipient_id;
+
+ context.chat_server().do_send(SendUserRoomMessage {
+ op: UserOperation::EditPrivateMessage,
+ response: res,
+ recipient_id,
+ websocket_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_delete_private_message(
+ activity: AnyBase,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let delete = Delete::from_any_base(activity)?.context(location_info!())?;
+ let note = Note::from_any_base(
+ delete
+ .object()
+ .as_one()
+ .context(location_info!())?
+ .to_owned(),
+ )?
+ .context(location_info!())?;
+
+ let domain = Some(delete.id_unchecked().context(location_info!())?.to_owned());
+ let private_message_form = PrivateMessageForm::from_apub(¬e, context, domain).await?;
+
+ let private_message_ap_id = private_message_form.ap_id;
+ let private_message = blocking(&context.pool(), move |conn| {
+ PrivateMessage::read_from_apub_id(conn, &private_message_ap_id)
+ })
+ .await??;
+
+ let private_message_form = PrivateMessageForm {
+ content: private_message_form.content,
+ recipient_id: private_message.recipient_id,
+ creator_id: private_message.creator_id,
+ deleted: Some(true),
+ read: None,
+ ap_id: private_message.ap_id,
+ local: private_message.local,
+ published: None,
+ updated: Some(naive_now()),
+ };
+
+ let private_message_id = private_message.id;
+ blocking(&context.pool(), move |conn| {
+ PrivateMessage::update(conn, private_message_id, &private_message_form)
+ })
+ .await??;
+
+ let private_message_id = private_message.id;
+ let message = blocking(&context.pool(), move |conn| {
+ PrivateMessageView::read(&conn, private_message_id)
+ })
+ .await??;
+
+ let res = PrivateMessageResponse { message };
+
+ let recipient_id = res.message.recipient_id;
+
+ context.chat_server().do_send(SendUserRoomMessage {
+ op: UserOperation::EditPrivateMessage,
+ response: res,
+ recipient_id,
+ websocket_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+async fn receive_undo_delete_private_message(
+ activity: AnyBase,
+ context: &LemmyContext,
+) -> Result<HttpResponse, LemmyError> {
+ let undo = Undo::from_any_base(activity)?.context(location_info!())?;
+ let delete = Delete::from_any_base(undo.object().as_one().context(location_info!())?.to_owned())?
+ .context(location_info!())?;
+ let note = Note::from_any_base(
+ delete
+ .object()
+ .as_one()
+ .context(location_info!())?
+ .to_owned(),
+ )?
+ .context(location_info!())?;
+
+ let domain = Some(undo.id_unchecked().context(location_info!())?.to_owned());
+ let private_message = PrivateMessageForm::from_apub(¬e, context, domain).await?;
+
+ let private_message_ap_id = private_message.ap_id.clone();
+ let private_message_id = blocking(&context.pool(), move |conn| {
+ PrivateMessage::read_from_apub_id(conn, &private_message_ap_id).map(|pm| pm.id)
+ })
+ .await??;
+
+ let private_message_form = PrivateMessageForm {
+ content: private_message.content,
+ recipient_id: private_message.recipient_id,
+ creator_id: private_message.creator_id,
+ deleted: Some(false),
+ read: None,
+ ap_id: private_message.ap_id,
+ local: private_message.local,
+ published: None,
+ updated: Some(naive_now()),
+ };
+
+ blocking(&context.pool(), move |conn| {
+ PrivateMessage::update(conn, private_message_id, &private_message_form)
+ })
+ .await??;
+
+ let message = blocking(&context.pool(), move |conn| {
+ PrivateMessageView::read(&conn, private_message_id)
+ })
+ .await??;
+
+ let res = PrivateMessageResponse { message };
+
+ let recipient_id = res.message.recipient_id;
+
+ context.chat_server().do_send(SendUserRoomMessage {
+ op: UserOperation::EditPrivateMessage,
+ response: res,
+ recipient_id,
+ websocket_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
pub mod activities;
pub mod comment;
pub mod community;
-pub mod community_inbox;
pub mod extensions;
pub mod fetcher;
+pub mod inbox;
pub mod post;
pub mod private_message;
-pub mod shared_inbox;
pub mod user;
-pub mod user_inbox;
use crate::{
apub::extensions::{
request::{retry, RecvError},
routes::webfinger::WebFingerResponse,
DbPool,
+ LemmyContext,
LemmyError,
};
-use activitystreams::object::Page;
-use activitystreams_ext::{Ext1, Ext2};
-use activitystreams_new::{
+use activitystreams::{
activity::Follow,
actor::{ApActor, Group, Person},
- object::Tombstone,
+ base::AsBase,
+ markers::Base,
+ object::{Page, Tombstone},
prelude::*,
};
+use activitystreams_ext::{Ext1, Ext2};
use actix_web::{body::Body, client::Client, HttpResponse};
+use anyhow::{anyhow, Context};
use chrono::NaiveDateTime;
-use failure::_core::fmt::Debug;
use lemmy_db::{activity::do_insert_activity, user::User_};
-use lemmy_utils::{convert_datetime, get_apub_protocol_string, settings::Settings, MentionData};
+use lemmy_utils::{
+ convert_datetime,
+ get_apub_protocol_string,
+ location_info,
+ settings::Settings,
+ MentionData,
+};
use log::debug;
use serde::Serialize;
-use url::Url;
+use url::{ParseError, Url};
type GroupExt = Ext2<ApActor<Group>, GroupExtension, PublicKeyExtension>;
type PersonExt = Ext1<ApActor<Person>, PublicKeyExtension>;
}
// Checks if the ID has a valid format, correct scheme, and is in the allowed instance list.
-fn is_apub_id_valid(apub_id: &Url) -> bool {
- debug!("Checking {}", apub_id);
+fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
+ let settings = Settings::get();
+ let domain = apub_id.domain().context(location_info!())?.to_string();
+ let local_instance = settings
+ .hostname
+ .split(':')
+ .collect::<Vec<&str>>()
+ .first()
+ .context(location_info!())?
+ .to_string();
+
+ if !settings.federation.enabled {
+ return if domain == local_instance {
+ Ok(())
+ } else {
+ Err(
+ anyhow!(
+ "Trying to connect with {}, but federation is disabled",
+ domain
+ )
+ .into(),
+ )
+ };
+ }
+
if apub_id.scheme() != get_apub_protocol_string() {
- debug!("invalid scheme: {:?}", apub_id.scheme());
- return false;
+ return Err(anyhow!("invalid apub id scheme: {:?}", apub_id.scheme()).into());
}
- let allowed_instances: Vec<String> = Settings::get()
- .federation
- .allowed_instances
- .split(',')
- .map(|d| d.to_string())
- .collect();
- match apub_id.domain() {
- Some(d) => {
- let contains = allowed_instances.contains(&d.to_owned());
-
- if !contains {
- debug!("{} not in {:?}", d, allowed_instances);
- }
+ let mut allowed_instances = Settings::get().get_allowed_instances();
+ let blocked_instances = Settings::get().get_blocked_instances();
- contains
+ if !allowed_instances.is_empty() {
+ // need to allow this explicitly because apub activities might contain objects from our local
+ // instance. split is needed to remove the port in our federation test setup.
+ allowed_instances.push(local_instance);
+
+ if allowed_instances.contains(&domain) {
+ Ok(())
+ } else {
+ Err(anyhow!("{} not in federation allowlist", domain).into())
}
- None => {
- debug!("missing domain");
- false
+ } else if !blocked_instances.is_empty() {
+ if blocked_instances.contains(&domain) {
+ Err(anyhow!("{} is in federation blocklist", domain).into())
+ } else {
+ Ok(())
}
+ } else {
+ panic!("Invalid config, both allowed_instances and blocked_instances are specified");
}
}
}
/// Updated is actually the deletion time
-fn create_tombstone(
+fn create_tombstone<T>(
deleted: bool,
object_id: &str,
updated: Option<NaiveDateTime>,
- former_type: String,
-) -> Result<Tombstone, LemmyError> {
+ former_type: T,
+) -> Result<Tombstone, LemmyError>
+where
+ T: ToString,
+{
if deleted {
if let Some(updated) = updated {
let mut tombstone = Tombstone::new();
tombstone.set_id(object_id.parse()?);
- tombstone.set_former_type(former_type);
- tombstone.set_deleted(convert_datetime(updated).into());
+ tombstone.set_former_type(former_type.to_string());
+ tombstone.set_deleted(convert_datetime(updated));
Ok(tombstone)
} else {
- Err(format_err!("Cant convert to tombstone because updated time was None.").into())
+ Err(anyhow!("Cant convert to tombstone because updated time was None.").into())
}
} else {
- Err(format_err!("Cant convert object to tombstone if it wasnt deleted").into())
+ Err(anyhow!("Cant convert object to tombstone if it wasnt deleted").into())
}
}
#[async_trait::async_trait(?Send)]
pub trait FromApub {
type ApubType;
+ /// Converts an object from ActivityPub type to Lemmy internal type.
+ ///
+ /// * `apub` The object to read from
+ /// * `context` LemmyContext which holds DB pool, HTTP client etc
+ /// * `expected_domain` If present, ensure that the apub object comes from the same domain as
+ /// this URL
+ ///
async fn from_apub(
apub: &Self::ApubType,
- client: &Client,
- pool: &DbPool,
+ context: &LemmyContext,
+ expected_domain: Option<Url>,
) -> Result<Self, LemmyError>
where
Self: Sized;
#[async_trait::async_trait(?Send)]
pub trait ApubObjectType {
- async fn send_create(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError>;
- async fn send_update(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError>;
- async fn send_delete(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError>;
+ async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
+ async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
+ async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
async fn send_undo_delete(
&self,
creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError>;
- async fn send_remove(
- &self,
- mod_: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError>;
- async fn send_undo_remove(
- &self,
- mod_: &User_,
- client: &Client,
- pool: &DbPool,
+ context: &LemmyContext,
) -> Result<(), LemmyError>;
+ async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
+ async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
}
-#[async_trait::async_trait(?Send)]
-pub trait ApubLikeableType {
- async fn send_like(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError>;
- async fn send_dislike(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError>;
- async fn send_undo_like(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError>;
+pub(in crate::apub) fn check_actor_domain<T, Kind>(
+ apub: &T,
+ expected_domain: Option<Url>,
+) -> Result<String, LemmyError>
+where
+ T: Base + AsBase<Kind>,
+{
+ let actor_id = if let Some(url) = expected_domain {
+ let domain = url.domain().context(location_info!())?;
+ apub.id(domain)?.context(location_info!())?
+ } else {
+ let actor_id = apub.id_unchecked().context(location_info!())?;
+ check_is_apub_id_valid(&actor_id)?;
+ actor_id
+ };
+ Ok(actor_id.to_string())
}
-pub fn get_shared_inbox(actor_id: &str) -> String {
- let url = Url::parse(actor_id).unwrap();
- format!(
- "{}://{}{}/inbox",
- &url.scheme(),
- &url.host_str().unwrap(),
- if let Some(port) = url.port() {
- format!(":{}", port)
- } else {
- "".to_string()
- },
- )
+#[async_trait::async_trait(?Send)]
+pub trait ApubLikeableType {
+ async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
+ async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
+ async fn send_undo_like(&self, creator: &User_, context: &LemmyContext)
+ -> Result<(), LemmyError>;
}
#[async_trait::async_trait(?Send)]
pub trait ActorType {
- fn actor_id(&self) -> String;
+ fn actor_id_str(&self) -> String;
+
+ // TODO: every actor should have a public key, so this shouldnt be an option (needs to be fixed in db)
+ fn public_key(&self) -> Option<String>;
+ fn private_key(&self) -> Option<String>;
- fn public_key(&self) -> String;
- fn private_key(&self) -> String;
+ /// numeric id in the database, used for insert_activity
+ fn user_id(&self) -> i32;
// These two have default impls, since currently a community can't follow anything,
// and a user can't be followed (yet)
#[allow(unused_variables)]
async fn send_follow(
&self,
- follow_actor_id: &str,
- client: &Client,
- pool: &DbPool,
+ follow_actor_id: &Url,
+ context: &LemmyContext,
) -> Result<(), LemmyError>;
async fn send_unfollow(
&self,
- follow_actor_id: &str,
- client: &Client,
- pool: &DbPool,
+ follow_actor_id: &Url,
+ context: &LemmyContext,
) -> Result<(), LemmyError>;
#[allow(unused_variables)]
async fn send_accept_follow(
&self,
- follow: &Follow,
- client: &Client,
- pool: &DbPool,
+ follow: Follow,
+ context: &LemmyContext,
) -> Result<(), LemmyError>;
- async fn send_delete(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError>;
+ async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
async fn send_undo_delete(
&self,
creator: &User_,
- client: &Client,
- pool: &DbPool,
+ context: &LemmyContext,
) -> Result<(), LemmyError>;
- async fn send_remove(
- &self,
- mod_: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError>;
- async fn send_undo_remove(
- &self,
- mod_: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError>;
+ async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
+ async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
/// For a given community, returns the inboxes of all followers.
- async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<String>, LemmyError>;
+ async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError>;
+
+ fn actor_id(&self) -> Result<Url, ParseError> {
+ Url::parse(&self.actor_id_str())
+ }
// TODO move these to the db rows
- fn get_inbox_url(&self) -> String {
- format!("{}/inbox", &self.actor_id())
+ fn get_inbox_url(&self) -> Result<Url, ParseError> {
+ Url::parse(&format!("{}/inbox", &self.actor_id_str()))
}
- fn get_shared_inbox_url(&self) -> String {
- get_shared_inbox(&self.actor_id())
+ fn get_shared_inbox_url(&self) -> Result<Url, LemmyError> {
+ let actor_id = self.actor_id()?;
+ let url = format!(
+ "{}://{}{}/inbox",
+ &actor_id.scheme(),
+ &actor_id.host_str().context(location_info!())?,
+ if let Some(port) = actor_id.port() {
+ format!(":{}", port)
+ } else {
+ "".to_string()
+ },
+ );
+ Ok(Url::parse(&url)?)
}
- fn get_outbox_url(&self) -> String {
- format!("{}/outbox", &self.actor_id())
+ fn get_outbox_url(&self) -> Result<Url, ParseError> {
+ Url::parse(&format!("{}/outbox", &self.actor_id_str()))
}
- fn get_followers_url(&self) -> String {
- format!("{}/followers", &self.actor_id())
+ fn get_followers_url(&self) -> Result<Url, ParseError> {
+ Url::parse(&format!("{}/followers", &self.actor_id_str()))
}
fn get_following_url(&self) -> String {
- format!("{}/following", &self.actor_id())
+ format!("{}/following", &self.actor_id_str())
}
fn get_liked_url(&self) -> String {
- format!("{}/liked", &self.actor_id())
+ format!("{}/liked", &self.actor_id_str())
}
- fn get_public_key_ext(&self) -> PublicKeyExtension {
- PublicKey {
- id: format!("{}#main-key", self.actor_id()),
- owner: self.actor_id(),
- public_key_pem: self.public_key(),
- }
- .to_ext()
+ fn get_public_key_ext(&self) -> Result<PublicKeyExtension, LemmyError> {
+ Ok(
+ PublicKey {
+ id: format!("{}#main-key", self.actor_id_str()),
+ owner: self.actor_id_str(),
+ public_key_pem: self.public_key().context(location_info!())?,
+ }
+ .to_ext(),
+ )
}
}
pub async fn fetch_webfinger_url(
mention: &MentionData,
client: &Client,
-) -> Result<String, LemmyError> {
+) -> Result<Url, LemmyError> {
let fetch_url = format!(
"{}://{}/.well-known/webfinger?resource=acct:{}@{}",
get_apub_protocol_string(),
.links
.iter()
.find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
- .ok_or_else(|| format_err!("No application/activity+json link found."))?;
+ .ok_or_else(|| anyhow!("No application/activity+json link found."))?;
link
.href
.to_owned()
- .ok_or_else(|| format_err!("No href found.").into())
+ .map(|u| Url::parse(&u))
+ .transpose()?
+ .ok_or_else(|| anyhow!("No href found.").into())
}
pub async fn insert_activity<T>(
pool: &DbPool,
) -> Result<(), LemmyError>
where
- T: Serialize + Debug + Send + 'static,
+ T: Serialize + std::fmt::Debug + Send + 'static,
{
blocking(pool, move |conn| {
do_insert_activity(conn, user_id, &data, local)
use crate::{
+ api::check_slurs,
apub::{
- activities::{populate_object_props, send_activity_to_community},
+ activities::{generate_activity_id, send_activity_to_community},
+ check_actor_domain,
create_apub_response,
create_apub_tombstone_response,
create_tombstone,
extensions::page_extension::PageExtension,
- fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user},
+ fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
ActorType,
ApubLikeableType,
ApubObjectType,
ToApub,
},
blocking,
- routes::DbPoolParam,
DbPool,
+ LemmyContext,
LemmyError,
};
use activitystreams::{
- activity::{Create, Delete, Dislike, Like, Remove, Undo, Update},
- context,
- object::{kind::PageType, properties::ObjectProperties, AnyImage, Image, Page},
- BaseBox,
+ activity::{
+ kind::{CreateType, DeleteType, DislikeType, LikeType, RemoveType, UndoType, UpdateType},
+ Create,
+ Delete,
+ Dislike,
+ Like,
+ Remove,
+ Undo,
+ Update,
+ },
+ object::{kind::PageType, Image, Object, Page, Tombstone},
+ prelude::*,
+ public,
};
use activitystreams_ext::Ext1;
-use activitystreams_new::object::Tombstone;
-use actix_web::{body::Body, client::Client, web, HttpResponse};
+use actix_web::{body::Body, web, HttpResponse};
+use anyhow::Context;
use lemmy_db::{
community::Community,
post::{Post, PostForm},
user::User_,
Crud,
};
-use lemmy_utils::{convert_datetime, get_apub_protocol_string, settings::Settings};
+use lemmy_utils::{convert_datetime, location_info, remove_slurs};
use serde::Deserialize;
+use url::Url;
#[derive(Deserialize)]
pub struct PostQuery {
/// Return the post json over HTTP.
pub async fn get_apub_post(
info: web::Path<PostQuery>,
- db: DbPoolParam,
+ context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, LemmyError> {
let id = info.post_id.parse::<i32>()?;
- let post = blocking(&db, move |conn| Post::read(conn, id)).await??;
+ let post = blocking(context.pool(), move |conn| Post::read(conn, id)).await??;
if !post.deleted {
- Ok(create_apub_response(&post.to_apub(&db).await?))
+ Ok(create_apub_response(&post.to_apub(context.pool()).await?))
} else {
Ok(create_apub_tombstone_response(&post.to_tombstone()?))
}
// Turn a Lemmy post into an ActivityPub page that can be sent out over the network.
async fn to_apub(&self, pool: &DbPool) -> Result<PageExt, LemmyError> {
- let mut page = Page::default();
- let oprops: &mut ObjectProperties = page.as_mut();
+ let mut page = Page::new();
let creator_id = self.creator_id;
let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
let community_id = self.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
- oprops
+ page
// Not needed when the Post is embedded in a collection (like for community outbox)
// TODO: need to set proper context defining sensitive/commentsEnabled fields
// https://git.asonix.dog/Aardwolf/activitystreams/issues/5
- .set_context_xsd_any_uri(context())?
- .set_id(self.ap_id.to_owned())?
+ .set_context(activitystreams::context())
+ .set_id(self.ap_id.parse::<Url>()?)
// Use summary field to be consistent with mastodon content warning.
// https://mastodon.xyz/@Louisa/103987265222901387.json
- .set_summary_xsd_string(self.name.to_owned())?
- .set_published(convert_datetime(self.published))?
- .set_to_xsd_any_uri(community.actor_id)?
- .set_attributed_to_xsd_any_uri(creator.actor_id)?;
+ .set_summary(self.name.to_owned())
+ .set_published(convert_datetime(self.published))
+ .set_to(community.actor_id)
+ .set_attributed_to(creator.actor_id);
if let Some(body) = &self.body {
- oprops.set_content_xsd_string(body.to_owned())?;
+ page.set_content(body.to_owned());
}
// TODO: hacky code because we get self.url == Some("")
// https://github.com/LemmyNet/lemmy/issues/602
let url = self.url.as_ref().filter(|u| !u.is_empty());
if let Some(u) = url {
- oprops.set_url_xsd_any_uri(u.to_owned())?;
+ page.set_url(u.to_owned());
// Embeds
let mut page_preview = Page::new();
- page_preview
- .object_props
- .set_url_xsd_any_uri(u.to_owned())?;
+ page_preview.set_url(u.to_owned());
if let Some(embed_title) = &self.embed_title {
- page_preview
- .object_props
- .set_name_xsd_string(embed_title.to_owned())?;
+ page_preview.set_name(embed_title.to_owned());
}
if let Some(embed_description) = &self.embed_description {
- page_preview
- .object_props
- .set_summary_xsd_string(embed_description.to_owned())?;
+ page_preview.set_summary(embed_description.to_owned());
}
if let Some(embed_html) = &self.embed_html {
- page_preview
- .object_props
- .set_content_xsd_string(embed_html.to_owned())?;
+ page_preview.set_content(embed_html.to_owned());
}
- oprops.set_preview_base_box(page_preview)?;
+ page.set_preview(page_preview.into_any_base()?);
}
if let Some(thumbnail_url) = &self.thumbnail_url {
- let full_url = format!(
- "{}://{}/pictshare/{}",
- get_apub_protocol_string(),
- Settings::get().hostname,
- thumbnail_url
- );
-
let mut image = Image::new();
- image.object_props.set_url_xsd_any_uri(full_url)?;
- let any_image = AnyImage::from_concrete(image)?;
- oprops.set_image_any_image(any_image)?;
+ image.set_url(thumbnail_url.to_string());
+ page.set_image(image.into_any_base()?);
}
if let Some(u) = self.updated {
- oprops.set_updated(convert_datetime(u))?;
+ page.set_updated(convert_datetime(u));
}
let ext = PageExtension {
comments_enabled: !self.locked,
sensitive: self.nsfw,
+ stickied: self.stickied,
};
Ok(Ext1::new(page, ext))
}
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
- create_tombstone(
- self.deleted,
- &self.ap_id,
- self.updated,
- PageType.to_string(),
- )
+ create_tombstone(self.deleted, &self.ap_id, self.updated, PageType::Page)
+ }
+}
+
+struct EmbedType {
+ title: Option<String>,
+ description: Option<String>,
+ html: Option<String>,
+}
+
+fn extract_embed_from_apub(
+ page: &Ext1<Object<PageType>, PageExtension>,
+) -> Result<EmbedType, LemmyError> {
+ match page.inner.preview() {
+ Some(preview) => {
+ let preview_page = Page::from_any_base(preview.one().context(location_info!())?.to_owned())?
+ .context(location_info!())?;
+ let title = preview_page
+ .name()
+ .map(|n| n.one())
+ .flatten()
+ .map(|s| s.as_xsd_string())
+ .flatten()
+ .map(|s| s.to_string());
+ let description = preview_page
+ .summary()
+ .map(|s| s.as_single_xsd_string())
+ .flatten()
+ .map(|s| s.to_string());
+ let html = preview_page
+ .content()
+ .map(|c| c.as_single_xsd_string())
+ .flatten()
+ .map(|s| s.to_string());
+ Ok(EmbedType {
+ title,
+ description,
+ html,
+ })
+ }
+ None => Ok(EmbedType {
+ title: None,
+ description: None,
+ html: None,
+ }),
}
}
/// Parse an ActivityPub page received from another instance into a Lemmy post.
async fn from_apub(
page: &PageExt,
- client: &Client,
- pool: &DbPool,
+ context: &LemmyContext,
+ expected_domain: Option<Url>,
) -> Result<PostForm, LemmyError> {
let ext = &page.ext_one;
- let oprops = &page.inner.object_props;
- let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string();
-
- let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, client, pool).await?;
-
- let community_actor_id = &oprops.get_to_xsd_any_uri().unwrap().to_string();
-
- let community =
- get_or_fetch_and_upsert_remote_community(&community_actor_id, client, pool).await?;
-
- let thumbnail_url = match oprops.get_image_any_image() {
- Some(any_image) => any_image
- .to_owned()
- .into_concrete::<Image>()?
- .object_props
- .get_url_xsd_any_uri()
- .map(|u| u.to_string()),
+ let creator_actor_id = page
+ .inner
+ .attributed_to()
+ .as_ref()
+ .context(location_info!())?
+ .as_single_xsd_any_uri()
+ .context(location_info!())?;
+
+ let creator = get_or_fetch_and_upsert_user(creator_actor_id, context).await?;
+
+ let community_actor_id = page
+ .inner
+ .to()
+ .as_ref()
+ .context(location_info!())?
+ .as_single_xsd_any_uri()
+ .context(location_info!())?;
+
+ let community = get_or_fetch_and_upsert_community(community_actor_id, context).await?;
+
+ let thumbnail_url = match &page.inner.image() {
+ Some(any_image) => Image::from_any_base(
+ any_image
+ .to_owned()
+ .as_one()
+ .context(location_info!())?
+ .to_owned(),
+ )?
+ .context(location_info!())?
+ .url()
+ .context(location_info!())?
+ .as_single_xsd_any_uri()
+ .map(|u| u.to_string()),
None => None,
};
- let url = oprops.get_url_xsd_any_uri().map(|u| u.to_string());
- let (embed_title, embed_description, embed_html) = match oprops.get_preview_base_box() {
- Some(preview) => {
- let preview_page = preview.to_owned().into_concrete::<Page>()?;
- let name = preview_page
- .object_props
- .get_name_xsd_string()
- .map(|n| n.to_string());
- let summary = preview_page
- .object_props
- .get_summary_xsd_string()
- .map(|s| s.to_string());
- let content = preview_page
- .object_props
- .get_content_xsd_string()
- .map(|c| c.to_string());
- (name, summary, content)
- }
- None => (None, None, None),
- };
-
+ let embed = extract_embed_from_apub(page)?;
+
+ let name = page
+ .inner
+ .summary()
+ .as_ref()
+ .context(location_info!())?
+ .as_single_xsd_string()
+ .context(location_info!())?
+ .to_string();
+ let url = page
+ .inner
+ .url()
+ .as_ref()
+ .map(|u| u.as_single_xsd_string())
+ .flatten()
+ .map(|s| s.to_string());
+ let body = page
+ .inner
+ .content()
+ .as_ref()
+ .map(|c| c.as_single_xsd_string())
+ .flatten()
+ .map(|s| s.to_string());
+ check_slurs(&name)?;
+ let body_slurs_removed = body.map(|b| remove_slurs(&b));
Ok(PostForm {
- name: oprops.get_summary_xsd_string().unwrap().to_string(),
+ name,
url,
- body: oprops.get_content_xsd_string().map(|c| c.to_string()),
+ body: body_slurs_removed,
creator_id: creator.id,
community_id: community.id,
removed: None,
locked: Some(!ext.comments_enabled),
- published: oprops
- .get_published()
- .map(|u| u.as_ref().to_owned().naive_local()),
- updated: oprops
- .get_updated()
- .map(|u| u.as_ref().to_owned().naive_local()),
+ published: page
+ .inner
+ .published()
+ .as_ref()
+ .map(|u| u.to_owned().naive_local()),
+ updated: page
+ .inner
+ .updated()
+ .as_ref()
+ .map(|u| u.to_owned().naive_local()),
deleted: None,
nsfw: ext.sensitive,
- stickied: None, // -> put it in "featured" collection of the community
- embed_title,
- embed_description,
- embed_html,
+ stickied: Some(ext.stickied),
+ embed_title: embed.title,
+ embed_description: embed.description,
+ embed_html: embed.html,
thumbnail_url,
- ap_id: oprops.get_id().unwrap().to_string(),
+ ap_id: check_actor_domain(page, expected_domain)?,
local: false,
})
}
#[async_trait::async_trait(?Send)]
impl ApubObjectType for Post {
/// Send out information about a newly created post, to the followers of the community.
- async fn send_create(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let page = self.to_apub(pool).await?;
+ async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let page = self.to_apub(context.pool()).await?;
let community_id = self.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
- let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4());
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
- let mut create = Create::new();
- populate_object_props(
- &mut create.object_props,
- vec![community.get_followers_url()],
- &id,
- )?;
+ let mut create = Create::new(creator.actor_id.to_owned(), page.into_any_base()?);
create
- .create_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(BaseBox::from_concrete(page)?)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(CreateType::Create)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
send_activity_to_community(
creator,
&community,
- vec![community.get_shared_inbox_url()],
- create,
- client,
- pool,
+ vec![community.get_shared_inbox_url()?],
+ create.into_any_base()?,
+ context,
)
.await?;
Ok(())
}
/// Send out information about an edited post, to the followers of the community.
- async fn send_update(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let page = self.to_apub(pool).await?;
+ async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let page = self.to_apub(context.pool()).await?;
let community_id = self.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
- let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4());
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
- let mut update = Update::new();
- populate_object_props(
- &mut update.object_props,
- vec![community.get_followers_url()],
- &id,
- )?;
+ let mut update = Update::new(creator.actor_id.to_owned(), page.into_any_base()?);
update
- .update_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(BaseBox::from_concrete(page)?)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(UpdateType::Update)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
send_activity_to_community(
creator,
&community,
- vec![community.get_shared_inbox_url()],
- update,
- client,
- pool,
+ vec![community.get_shared_inbox_url()?],
+ update.into_any_base()?,
+ context,
)
.await?;
Ok(())
}
- async fn send_delete(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let page = self.to_apub(pool).await?;
+ async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let page = self.to_apub(context.pool()).await?;
let community_id = self.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
- let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut delete = Delete::default();
-
- populate_object_props(
- &mut delete.object_props,
- vec![community.get_followers_url()],
- &id,
- )?;
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
+ let mut delete = Delete::new(creator.actor_id.to_owned(), page.into_any_base()?);
delete
- .delete_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(BaseBox::from_concrete(page)?)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(DeleteType::Delete)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
send_activity_to_community(
creator,
&community,
- vec![community.get_shared_inbox_url()],
- delete,
- client,
- pool,
+ vec![community.get_shared_inbox_url()?],
+ delete.into_any_base()?,
+ context,
)
.await?;
Ok(())
async fn send_undo_delete(
&self,
creator: &User_,
- client: &Client,
- pool: &DbPool,
+ context: &LemmyContext,
) -> Result<(), LemmyError> {
- let page = self.to_apub(pool).await?;
+ let page = self.to_apub(context.pool()).await?;
let community_id = self.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
- let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut delete = Delete::default();
-
- populate_object_props(
- &mut delete.object_props,
- vec![community.get_followers_url()],
- &id,
- )?;
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
+ let mut delete = Delete::new(creator.actor_id.to_owned(), page.into_any_base()?);
delete
- .delete_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(BaseBox::from_concrete(page)?)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(DeleteType::Delete)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
- // TODO
// Undo that fake activity
- let undo_id = format!("{}/undo/delete/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut undo = Undo::default();
-
- populate_object_props(
- &mut undo.object_props,
- vec![community.get_followers_url()],
- &undo_id,
- )?;
-
+ let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
undo
- .undo_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(delete)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(UndoType::Undo)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
send_activity_to_community(
creator,
&community,
- vec![community.get_shared_inbox_url()],
- undo,
- client,
- pool,
+ vec![community.get_shared_inbox_url()?],
+ undo.into_any_base()?,
+ context,
)
.await?;
Ok(())
}
- async fn send_remove(
- &self,
- mod_: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let page = self.to_apub(pool).await?;
+ async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let page = self.to_apub(context.pool()).await?;
let community_id = self.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
- let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut remove = Remove::default();
-
- populate_object_props(
- &mut remove.object_props,
- vec![community.get_followers_url()],
- &id,
- )?;
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
+ let mut remove = Remove::new(mod_.actor_id.to_owned(), page.into_any_base()?);
remove
- .remove_props
- .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
- .set_object_base_box(BaseBox::from_concrete(page)?)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(RemoveType::Remove)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
send_activity_to_community(
mod_,
&community,
- vec![community.get_shared_inbox_url()],
- remove,
- client,
- pool,
+ vec![community.get_shared_inbox_url()?],
+ remove.into_any_base()?,
+ context,
)
.await?;
Ok(())
}
- async fn send_undo_remove(
- &self,
- mod_: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let page = self.to_apub(pool).await?;
+ async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let page = self.to_apub(context.pool()).await?;
let community_id = self.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
- let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut remove = Remove::default();
-
- populate_object_props(
- &mut remove.object_props,
- vec![community.get_followers_url()],
- &id,
- )?;
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
+ let mut remove = Remove::new(mod_.actor_id.to_owned(), page.into_any_base()?);
remove
- .remove_props
- .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
- .set_object_base_box(BaseBox::from_concrete(page)?)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(RemoveType::Remove)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
// Undo that fake activity
- let undo_id = format!("{}/undo/remove/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut undo = Undo::default();
-
- populate_object_props(
- &mut undo.object_props,
- vec![community.get_followers_url()],
- &undo_id,
- )?;
-
+ let mut undo = Undo::new(mod_.actor_id.to_owned(), remove.into_any_base()?);
undo
- .undo_props
- .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
- .set_object_base_box(remove)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(UndoType::Undo)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
send_activity_to_community(
mod_,
&community,
- vec![community.get_shared_inbox_url()],
- undo,
- client,
- pool,
+ vec![community.get_shared_inbox_url()?],
+ undo.into_any_base()?,
+ context,
)
.await?;
Ok(())
#[async_trait::async_trait(?Send)]
impl ApubLikeableType for Post {
- async fn send_like(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let page = self.to_apub(pool).await?;
+ async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let page = self.to_apub(context.pool()).await?;
let community_id = self.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
- let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4());
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
- let mut like = Like::new();
- populate_object_props(
- &mut like.object_props,
- vec![community.get_followers_url()],
- &id,
- )?;
+ let mut like = Like::new(creator.actor_id.to_owned(), page.into_any_base()?);
like
- .like_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(BaseBox::from_concrete(page)?)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(LikeType::Like)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
send_activity_to_community(
&creator,
&community,
- vec![community.get_shared_inbox_url()],
- like,
- client,
- pool,
+ vec![community.get_shared_inbox_url()?],
+ like.into_any_base()?,
+ context,
)
.await?;
Ok(())
}
- async fn send_dislike(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let page = self.to_apub(pool).await?;
+ async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let page = self.to_apub(context.pool()).await?;
let community_id = self.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
- let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4());
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
- let mut dislike = Dislike::new();
- populate_object_props(
- &mut dislike.object_props,
- vec![community.get_followers_url()],
- &id,
- )?;
+ let mut dislike = Dislike::new(creator.actor_id.to_owned(), page.into_any_base()?);
dislike
- .dislike_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(BaseBox::from_concrete(page)?)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(DislikeType::Dislike)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
send_activity_to_community(
&creator,
&community,
- vec![community.get_shared_inbox_url()],
- dislike,
- client,
- pool,
+ vec![community.get_shared_inbox_url()?],
+ dislike.into_any_base()?,
+ context,
)
.await?;
Ok(())
async fn send_undo_like(
&self,
creator: &User_,
- client: &Client,
- pool: &DbPool,
+ context: &LemmyContext,
) -> Result<(), LemmyError> {
- let page = self.to_apub(pool).await?;
+ let page = self.to_apub(context.pool()).await?;
let community_id = self.community_id;
- let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
-
- let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4());
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
- let mut like = Like::new();
- populate_object_props(
- &mut like.object_props,
- vec![community.get_followers_url()],
- &id,
- )?;
+ let mut like = Like::new(creator.actor_id.to_owned(), page.into_any_base()?);
like
- .like_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(BaseBox::from_concrete(page)?)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(LikeType::Like)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
- // TODO
// Undo that fake activity
- let undo_id = format!("{}/undo/like/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut undo = Undo::default();
-
- populate_object_props(
- &mut undo.object_props,
- vec![community.get_followers_url()],
- &undo_id,
- )?;
-
+ let mut undo = Undo::new(creator.actor_id.to_owned(), like.into_any_base()?);
undo
- .undo_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(like)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(UndoType::Undo)?)
+ .set_to(public())
+ .set_many_ccs(vec![community.get_followers_url()?]);
send_activity_to_community(
&creator,
&community,
- vec![community.get_shared_inbox_url()],
- undo,
- client,
- pool,
+ vec![community.get_shared_inbox_url()?],
+ undo.into_any_base()?,
+ context,
)
.await?;
Ok(())
use crate::{
apub::{
- activities::send_activity,
+ activities::{generate_activity_id, send_activity},
+ check_actor_domain,
+ check_is_apub_id_valid,
create_tombstone,
- fetcher::get_or_fetch_and_upsert_remote_user,
+ fetcher::get_or_fetch_and_upsert_user,
insert_activity,
+ ActorType,
ApubObjectType,
FromApub,
ToApub,
},
blocking,
DbPool,
+ LemmyContext,
LemmyError,
};
use activitystreams::{
- activity::{Create, Delete, Undo, Update},
- context,
- object::{kind::NoteType, properties::ObjectProperties, Note},
+ activity::{
+ kind::{CreateType, DeleteType, UndoType, UpdateType},
+ Create,
+ Delete,
+ Undo,
+ Update,
+ },
+ object::{kind::NoteType, Note, Tombstone},
+ prelude::*,
};
-use activitystreams_new::object::Tombstone;
-use actix_web::client::Client;
+use anyhow::Context;
use lemmy_db::{
private_message::{PrivateMessage, PrivateMessageForm},
user::User_,
Crud,
};
-use lemmy_utils::convert_datetime;
+use lemmy_utils::{convert_datetime, location_info};
+use url::Url;
#[async_trait::async_trait(?Send)]
impl ToApub for PrivateMessage {
type Response = Note;
async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
- let mut private_message = Note::default();
- let oprops: &mut ObjectProperties = private_message.as_mut();
+ let mut private_message = Note::new();
let creator_id = self.creator_id;
let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
let recipient_id = self.recipient_id;
let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
- oprops
- .set_context_xsd_any_uri(context())?
- .set_id(self.ap_id.to_owned())?
- .set_published(convert_datetime(self.published))?
- .set_content_xsd_string(self.content.to_owned())?
- .set_to_xsd_any_uri(recipient.actor_id)?
- .set_attributed_to_xsd_any_uri(creator.actor_id)?;
+ private_message
+ .set_context(activitystreams::context())
+ .set_id(Url::parse(&self.ap_id.to_owned())?)
+ .set_published(convert_datetime(self.published))
+ .set_content(self.content.to_owned())
+ .set_to(recipient.actor_id)
+ .set_attributed_to(creator.actor_id);
if let Some(u) = self.updated {
- oprops.set_updated(convert_datetime(u))?;
+ private_message.set_updated(convert_datetime(u));
}
Ok(private_message)
}
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
- create_tombstone(
- self.deleted,
- &self.ap_id,
- self.updated,
- NoteType.to_string(),
- )
+ create_tombstone(self.deleted, &self.ap_id, self.updated, NoteType::Note)
}
}
/// Parse an ActivityPub note received from another instance into a Lemmy Private message
async fn from_apub(
note: &Note,
- client: &Client,
- pool: &DbPool,
+ context: &LemmyContext,
+ expected_domain: Option<Url>,
) -> Result<PrivateMessageForm, LemmyError> {
- let oprops = ¬e.object_props;
- let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string();
-
- let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, client, pool).await?;
-
- let recipient_actor_id = &oprops.get_to_xsd_any_uri().unwrap().to_string();
-
- let recipient = get_or_fetch_and_upsert_remote_user(&recipient_actor_id, client, pool).await?;
+ let creator_actor_id = note
+ .attributed_to()
+ .context(location_info!())?
+ .clone()
+ .single_xsd_any_uri()
+ .context(location_info!())?;
+
+ let creator = get_or_fetch_and_upsert_user(&creator_actor_id, context).await?;
+ let recipient_actor_id = note
+ .to()
+ .context(location_info!())?
+ .clone()
+ .single_xsd_any_uri()
+ .context(location_info!())?;
+ let recipient = get_or_fetch_and_upsert_user(&recipient_actor_id, context).await?;
+ let ap_id = note.id_unchecked().context(location_info!())?.to_string();
+ check_is_apub_id_valid(&Url::parse(&ap_id)?)?;
Ok(PrivateMessageForm {
creator_id: creator.id,
recipient_id: recipient.id,
- content: oprops
- .get_content_xsd_string()
- .map(|c| c.to_string())
- .unwrap(),
- published: oprops
- .get_published()
- .map(|u| u.as_ref().to_owned().naive_local()),
- updated: oprops
- .get_updated()
- .map(|u| u.as_ref().to_owned().naive_local()),
+ content: note
+ .content()
+ .context(location_info!())?
+ .as_single_xsd_string()
+ .context(location_info!())?
+ .to_string(),
+ published: note.published().map(|u| u.to_owned().naive_local()),
+ updated: note.updated().map(|u| u.to_owned().naive_local()),
deleted: None,
read: None,
- ap_id: oprops.get_id().unwrap().to_string(),
+ ap_id: check_actor_domain(note, expected_domain)?,
local: false,
})
}
#[async_trait::async_trait(?Send)]
impl ApubObjectType for PrivateMessage {
/// Send out information about a newly created private message
- async fn send_create(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let note = self.to_apub(pool).await?;
- let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4());
+ async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let note = self.to_apub(context.pool()).await?;
let recipient_id = self.recipient_id;
- let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
+ let recipient = blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
- let mut create = Create::new();
+ let mut create = Create::new(creator.actor_id.to_owned(), note.into_any_base()?);
+ let to = recipient.get_inbox_url()?;
create
- .object_props
- .set_context_xsd_any_uri(context())?
- .set_id(id)?;
- let to = format!("{}/inbox", recipient.actor_id);
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(CreateType::Create)?)
+ .set_to(to.clone());
- create
- .create_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(note)?;
-
- insert_activity(creator.id, create.clone(), true, pool).await?;
+ insert_activity(creator.id, create.clone(), true, context.pool()).await?;
- send_activity(client, &create, creator, vec![to]).await?;
+ send_activity(
+ context.client(),
+ &create.into_any_base()?,
+ creator,
+ vec![to],
+ )
+ .await?;
Ok(())
}
/// Send out information about an edited post, to the followers of the community.
- async fn send_update(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let note = self.to_apub(pool).await?;
- let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4());
+ async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let note = self.to_apub(context.pool()).await?;
let recipient_id = self.recipient_id;
- let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
-
- let mut update = Update::new();
- update
- .object_props
- .set_context_xsd_any_uri(context())?
- .set_id(id)?;
- let to = format!("{}/inbox", recipient.actor_id);
+ let recipient = blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
+ let mut update = Update::new(creator.actor_id.to_owned(), note.into_any_base()?);
+ let to = recipient.get_inbox_url()?;
update
- .update_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(note)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(UpdateType::Update)?)
+ .set_to(to.clone());
- insert_activity(creator.id, update.clone(), true, pool).await?;
+ insert_activity(creator.id, update.clone(), true, context.pool()).await?;
- send_activity(client, &update, creator, vec![to]).await?;
+ send_activity(
+ context.client(),
+ &update.into_any_base()?,
+ creator,
+ vec![to],
+ )
+ .await?;
Ok(())
}
- async fn send_delete(
- &self,
- creator: &User_,
- client: &Client,
- pool: &DbPool,
- ) -> Result<(), LemmyError> {
- let note = self.to_apub(pool).await?;
- let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
+ let note = self.to_apub(context.pool()).await?;
let recipient_id = self.recipient_id;
- let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
+ let recipient = blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
- let mut delete = Delete::new();
+ let mut delete = Delete::new(creator.actor_id.to_owned(), note.into_any_base()?);
+ let to = recipient.get_inbox_url()?;
delete
- .object_props
- .set_context_xsd_any_uri(context())?
- .set_id(id)?;
- let to = format!("{}/inbox", recipient.actor_id);
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(DeleteType::Delete)?)
+ .set_to(to.clone());
- delete
- .delete_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(note)?;
-
- insert_activity(creator.id, delete.clone(), true, pool).await?;
+ insert_activity(creator.id, delete.clone(), true, context.pool()).await?;
- send_activity(client, &delete, creator, vec![to]).await?;
+ send_activity(
+ context.client(),
+ &delete.into_any_base()?,
+ creator,
+ vec![to],
+ )
+ .await?;
Ok(())
}
async fn send_undo_delete(
&self,
creator: &User_,
- client: &Client,
- pool: &DbPool,
+ context: &LemmyContext,
) -> Result<(), LemmyError> {
- let note = self.to_apub(pool).await?;
- let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ let note = self.to_apub(context.pool()).await?;
let recipient_id = self.recipient_id;
- let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
+ let recipient = blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
- let mut delete = Delete::new();
+ let mut delete = Delete::new(creator.actor_id.to_owned(), note.into_any_base()?);
+ let to = recipient.get_inbox_url()?;
delete
- .object_props
- .set_context_xsd_any_uri(context())?
- .set_id(id)?;
- let to = format!("{}/inbox", recipient.actor_id);
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(DeleteType::Delete)?)
+ .set_to(to.clone());
- delete
- .delete_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(note)?;
-
- // TODO
// Undo that fake activity
- let undo_id = format!("{}/undo/delete/{}", self.ap_id, uuid::Uuid::new_v4());
- let mut undo = Undo::default();
-
- undo
- .object_props
- .set_context_xsd_any_uri(context())?
- .set_id(undo_id)?;
-
+ let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
undo
- .undo_props
- .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
- .set_object_base_box(delete)?;
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(UndoType::Undo)?)
+ .set_to(to.clone());
- insert_activity(creator.id, undo.clone(), true, pool).await?;
+ insert_activity(creator.id, undo.clone(), true, context.pool()).await?;
- send_activity(client, &undo, creator, vec![to]).await?;
+ send_activity(context.client(), &undo.into_any_base()?, creator, vec![to]).await?;
Ok(())
}
- async fn send_remove(
- &self,
- _mod_: &User_,
- _client: &Client,
- _pool: &DbPool,
- ) -> Result<(), LemmyError> {
+ async fn send_remove(&self, _mod_: &User_, _context: &LemmyContext) -> Result<(), LemmyError> {
unimplemented!()
}
async fn send_undo_remove(
&self,
_mod_: &User_,
- _client: &Client,
- _pool: &DbPool,
+ _context: &LemmyContext,
) -> Result<(), LemmyError> {
unimplemented!()
}
+++ /dev/null
-use crate::{
- api::{
- comment::{send_local_notifs, CommentResponse},
- community::CommunityResponse,
- post::PostResponse,
- },
- apub::{
- community::do_announce,
- extensions::signatures::verify,
- fetcher::{
- get_or_fetch_and_insert_remote_comment,
- get_or_fetch_and_insert_remote_post,
- get_or_fetch_and_upsert_remote_community,
- get_or_fetch_and_upsert_remote_user,
- },
- insert_activity,
- FromApub,
- GroupExt,
- PageExt,
- },
- blocking,
- routes::{ChatServerParam, DbPoolParam},
- websocket::{
- server::{SendComment, SendCommunityRoomMessage, SendPost},
- UserOperation,
- },
- DbPool,
- LemmyError,
-};
-use activitystreams::{
- activity::{Announce, Create, Delete, Dislike, Like, Remove, Undo, Update},
- object::Note,
- Activity,
- Base,
- BaseBox,
-};
-use actix_web::{client::Client, web, HttpRequest, HttpResponse};
-use lemmy_db::{
- comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
- comment_view::CommentView,
- community::{Community, CommunityForm},
- community_view::CommunityView,
- naive_now,
- post::{Post, PostForm, PostLike, PostLikeForm},
- post_view::PostView,
- Crud,
- Likeable,
-};
-use lemmy_utils::scrape_text_for_mentions;
-use log::debug;
-use serde::{Deserialize, Serialize};
-use std::fmt::Debug;
-
-#[serde(untagged)]
-#[derive(Serialize, Deserialize, Debug)]
-pub enum SharedAcceptedObjects {
- Create(Box<Create>),
- Update(Box<Update>),
- Like(Box<Like>),
- Dislike(Box<Dislike>),
- Delete(Box<Delete>),
- Undo(Box<Undo>),
- Remove(Box<Remove>),
- Announce(Box<Announce>),
-}
-
-impl SharedAcceptedObjects {
- fn object(&self) -> Option<&BaseBox> {
- match self {
- SharedAcceptedObjects::Create(c) => c.create_props.get_object_base_box(),
- SharedAcceptedObjects::Update(u) => u.update_props.get_object_base_box(),
- SharedAcceptedObjects::Like(l) => l.like_props.get_object_base_box(),
- SharedAcceptedObjects::Dislike(d) => d.dislike_props.get_object_base_box(),
- SharedAcceptedObjects::Delete(d) => d.delete_props.get_object_base_box(),
- SharedAcceptedObjects::Undo(d) => d.undo_props.get_object_base_box(),
- SharedAcceptedObjects::Remove(r) => r.remove_props.get_object_base_box(),
- SharedAcceptedObjects::Announce(a) => a.announce_props.get_object_base_box(),
- }
- }
- fn sender(&self) -> String {
- let uri = match self {
- SharedAcceptedObjects::Create(c) => c.create_props.get_actor_xsd_any_uri(),
- SharedAcceptedObjects::Update(u) => u.update_props.get_actor_xsd_any_uri(),
- SharedAcceptedObjects::Like(l) => l.like_props.get_actor_xsd_any_uri(),
- SharedAcceptedObjects::Dislike(d) => d.dislike_props.get_actor_xsd_any_uri(),
- SharedAcceptedObjects::Delete(d) => d.delete_props.get_actor_xsd_any_uri(),
- SharedAcceptedObjects::Undo(d) => d.undo_props.get_actor_xsd_any_uri(),
- SharedAcceptedObjects::Remove(r) => r.remove_props.get_actor_xsd_any_uri(),
- SharedAcceptedObjects::Announce(a) => a.announce_props.get_actor_xsd_any_uri(),
- };
- uri.unwrap().clone().to_string()
- }
- fn cc(&self) -> String {
- // TODO: there is probably an easier way to do this
- let oprops = match self {
- SharedAcceptedObjects::Create(c) => &c.object_props,
- SharedAcceptedObjects::Update(u) => &u.object_props,
- SharedAcceptedObjects::Like(l) => &l.object_props,
- SharedAcceptedObjects::Dislike(d) => &d.object_props,
- SharedAcceptedObjects::Delete(d) => &d.object_props,
- SharedAcceptedObjects::Undo(d) => &d.object_props,
- SharedAcceptedObjects::Remove(r) => &r.object_props,
- SharedAcceptedObjects::Announce(a) => &a.object_props,
- };
- oprops
- .get_many_cc_xsd_any_uris()
- .unwrap()
- .next()
- .unwrap()
- .to_string()
- }
-}
-
-/// Handler for all incoming activities to user inboxes.
-pub async fn shared_inbox(
- request: HttpRequest,
- input: web::Json<SharedAcceptedObjects>,
- client: web::Data<Client>,
- pool: DbPoolParam,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let activity = input.into_inner();
- let pool = &pool;
- let client = &client;
-
- let json = serde_json::to_string(&activity)?;
- debug!("Shared inbox received activity: {}", json);
-
- let object = activity.object().cloned().unwrap();
- let sender = &activity.sender();
- let cc = &activity.cc();
- // TODO: this is hacky, we should probably send the community id directly somehow
- let to = cc.replace("/followers", "");
-
- // TODO: this is ugly
- match get_or_fetch_and_upsert_remote_user(&sender.to_string(), &client, pool).await {
- Ok(u) => verify(&request, &u)?,
- Err(_) => {
- let c = get_or_fetch_and_upsert_remote_community(&sender.to_string(), &client, pool).await?;
- verify(&request, &c)?;
- }
- }
-
- match (activity, object.kind()) {
- (SharedAcceptedObjects::Create(c), Some("Page")) => {
- receive_create_post((*c).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Create>(*c, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Update(u), Some("Page")) => {
- receive_update_post((*u).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Update>(*u, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Like(l), Some("Page")) => {
- receive_like_post((*l).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Like>(*l, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Dislike(d), Some("Page")) => {
- receive_dislike_post((*d).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Dislike>(*d, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Delete(d), Some("Page")) => {
- receive_delete_post((*d).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Delete>(*d, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Remove(r), Some("Page")) => {
- receive_remove_post((*r).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Remove>(*r, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Create(c), Some("Note")) => {
- receive_create_comment((*c).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Create>(*c, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Update(u), Some("Note")) => {
- receive_update_comment((*u).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Update>(*u, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Like(l), Some("Note")) => {
- receive_like_comment((*l).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Like>(*l, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Dislike(d), Some("Note")) => {
- receive_dislike_comment((*d).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Dislike>(*d, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Delete(d), Some("Note")) => {
- receive_delete_comment((*d).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Delete>(*d, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Remove(r), Some("Note")) => {
- receive_remove_comment((*r).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Remove>(*r, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Delete(d), Some("Group")) => {
- receive_delete_community((*d).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Delete>(*d, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Remove(r), Some("Group")) => {
- receive_remove_community((*r).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Remove>(*r, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Undo(u), Some("Delete")) => {
- receive_undo_delete((*u).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Undo>(*u, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Undo(u), Some("Remove")) => {
- receive_undo_remove((*u).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Undo>(*u, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Undo(u), Some("Like")) => {
- receive_undo_like((*u).clone(), client, pool, chat_server).await?;
- announce_activity_if_valid::<Undo>(*u, &to, sender, client, pool).await
- }
- (SharedAcceptedObjects::Announce(a), _) => receive_announce(a, client, pool, chat_server).await,
- (a, _) => receive_unhandled_activity(a),
- }
-}
-
-// TODO: should pass in sender as ActorType, but thats a bit tricky in shared_inbox()
-async fn announce_activity_if_valid<A>(
- activity: A,
- community_uri: &str,
- sender: &str,
- client: &Client,
- pool: &DbPool,
-) -> Result<HttpResponse, LemmyError>
-where
- A: Activity + Base + Serialize + Debug,
-{
- let community_uri = community_uri.to_owned();
- let community = blocking(pool, move |conn| {
- Community::read_from_actor_id(conn, &community_uri)
- })
- .await??;
-
- if community.local {
- let sending_user = get_or_fetch_and_upsert_remote_user(sender, client, pool).await?;
-
- do_announce(activity, &community, &sending_user, client, pool).await
- } else {
- Ok(HttpResponse::NotFound().finish())
- }
-}
-
-async fn receive_announce(
- announce: Box<Announce>,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let object = announce
- .announce_props
- .get_object_base_box()
- .unwrap()
- .to_owned();
- // TODO: too much copy paste
- match object.kind() {
- Some("Create") => {
- let create = object.into_concrete::<Create>()?;
- let inner_object = create.create_props.get_object_base_box().unwrap();
- match inner_object.kind() {
- Some("Page") => receive_create_post(create, client, pool, chat_server).await,
- Some("Note") => receive_create_comment(create, client, pool, chat_server).await,
- _ => receive_unhandled_activity(announce),
- }
- }
- Some("Update") => {
- let update = object.into_concrete::<Update>()?;
- let inner_object = update.update_props.get_object_base_box().unwrap();
- match inner_object.kind() {
- Some("Page") => receive_update_post(update, client, pool, chat_server).await,
- Some("Note") => receive_update_comment(update, client, pool, chat_server).await,
- _ => receive_unhandled_activity(announce),
- }
- }
- Some("Like") => {
- let like = object.into_concrete::<Like>()?;
- let inner_object = like.like_props.get_object_base_box().unwrap();
- match inner_object.kind() {
- Some("Page") => receive_like_post(like, client, pool, chat_server).await,
- Some("Note") => receive_like_comment(like, client, pool, chat_server).await,
- _ => receive_unhandled_activity(announce),
- }
- }
- Some("Dislike") => {
- let dislike = object.into_concrete::<Dislike>()?;
- let inner_object = dislike.dislike_props.get_object_base_box().unwrap();
- match inner_object.kind() {
- Some("Page") => receive_dislike_post(dislike, client, pool, chat_server).await,
- Some("Note") => receive_dislike_comment(dislike, client, pool, chat_server).await,
- _ => receive_unhandled_activity(announce),
- }
- }
- Some("Delete") => {
- let delete = object.into_concrete::<Delete>()?;
- let inner_object = delete.delete_props.get_object_base_box().unwrap();
- match inner_object.kind() {
- Some("Page") => receive_delete_post(delete, client, pool, chat_server).await,
- Some("Note") => receive_delete_comment(delete, client, pool, chat_server).await,
- _ => receive_unhandled_activity(announce),
- }
- }
- Some("Remove") => {
- let remove = object.into_concrete::<Remove>()?;
- let inner_object = remove.remove_props.get_object_base_box().unwrap();
- match inner_object.kind() {
- Some("Page") => receive_remove_post(remove, client, pool, chat_server).await,
- Some("Note") => receive_remove_comment(remove, client, pool, chat_server).await,
- _ => receive_unhandled_activity(announce),
- }
- }
- Some("Undo") => {
- let undo = object.into_concrete::<Undo>()?;
- let inner_object = undo.undo_props.get_object_base_box().unwrap();
- match inner_object.kind() {
- Some("Delete") => receive_undo_delete(undo, client, pool, chat_server).await,
- Some("Remove") => receive_undo_remove(undo, client, pool, chat_server).await,
- Some("Like") => receive_undo_like(undo, client, pool, chat_server).await,
- _ => receive_unhandled_activity(announce),
- }
- }
- _ => receive_unhandled_activity(announce),
- }
-}
-
-fn receive_unhandled_activity<A>(activity: A) -> Result<HttpResponse, LemmyError>
-where
- A: Debug,
-{
- debug!("received unhandled activity type: {:?}", activity);
- Ok(HttpResponse::NotImplemented().finish())
-}
-
-async fn receive_create_post(
- create: Create,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let page = create
- .create_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<PageExt>()?;
-
- let user_uri = create
- .create_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
-
- insert_activity(user.id, create, false, pool).await?;
-
- let post = PostForm::from_apub(&page, client, pool).await?;
-
- let inserted_post = blocking(pool, move |conn| Post::create(conn, &post)).await??;
-
- // Refetch the view
- let inserted_post_id = inserted_post.id;
- let post_view = blocking(pool, move |conn| {
- PostView::read(conn, inserted_post_id, None)
- })
- .await??;
-
- let res = PostResponse { post: post_view };
-
- chat_server.do_send(SendPost {
- op: UserOperation::CreatePost,
- post: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_create_comment(
- create: Create,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let note = create
- .create_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Note>()?;
-
- let user_uri = create
- .create_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
-
- insert_activity(user.id, create, false, pool).await?;
-
- let comment = CommentForm::from_apub(¬e, client, pool).await?;
-
- let inserted_comment = blocking(pool, move |conn| Comment::create(conn, &comment)).await??;
-
- let post_id = inserted_comment.post_id;
- let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
-
- // Note:
- // Although mentions could be gotten from the post tags (they are included there), or the ccs,
- // Its much easier to scrape them from the comment body, since the API has to do that
- // anyway.
- let mentions = scrape_text_for_mentions(&inserted_comment.content);
- let recipient_ids =
- send_local_notifs(mentions, inserted_comment.clone(), user, post, pool).await?;
-
- // Refetch the view
- let comment_view = blocking(pool, move |conn| {
- CommentView::read(conn, inserted_comment.id, None)
- })
- .await??;
-
- let res = CommentResponse {
- comment: comment_view,
- recipient_ids,
- };
-
- chat_server.do_send(SendComment {
- op: UserOperation::CreateComment,
- comment: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_update_post(
- update: Update,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let page = update
- .update_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<PageExt>()?;
-
- let user_uri = update
- .update_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
-
- insert_activity(user.id, update, false, pool).await?;
-
- let post = PostForm::from_apub(&page, client, pool).await?;
-
- let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, client, pool)
- .await?
- .id;
-
- blocking(pool, move |conn| Post::update(conn, post_id, &post)).await??;
-
- // Refetch the view
- let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??;
-
- let res = PostResponse { post: post_view };
-
- chat_server.do_send(SendPost {
- op: UserOperation::EditPost,
- post: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_like_post(
- like: Like,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let page = like
- .like_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<PageExt>()?;
-
- let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string();
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
-
- insert_activity(user.id, like, false, pool).await?;
-
- let post = PostForm::from_apub(&page, client, pool).await?;
-
- let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, client, pool)
- .await?
- .id;
-
- let like_form = PostLikeForm {
- post_id,
- user_id: user.id,
- score: 1,
- };
- blocking(pool, move |conn| {
- PostLike::remove(conn, &like_form)?;
- PostLike::like(conn, &like_form)
- })
- .await??;
-
- // Refetch the view
- let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??;
-
- let res = PostResponse { post: post_view };
-
- chat_server.do_send(SendPost {
- op: UserOperation::CreatePostLike,
- post: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_dislike_post(
- dislike: Dislike,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let page = dislike
- .dislike_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<PageExt>()?;
-
- let user_uri = dislike
- .dislike_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
-
- insert_activity(user.id, dislike, false, pool).await?;
-
- let post = PostForm::from_apub(&page, client, pool).await?;
-
- let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, client, pool)
- .await?
- .id;
-
- let like_form = PostLikeForm {
- post_id,
- user_id: user.id,
- score: -1,
- };
- blocking(pool, move |conn| {
- PostLike::remove(conn, &like_form)?;
- PostLike::like(conn, &like_form)
- })
- .await??;
-
- // Refetch the view
- let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??;
-
- let res = PostResponse { post: post_view };
-
- chat_server.do_send(SendPost {
- op: UserOperation::CreatePostLike,
- post: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_update_comment(
- update: Update,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let note = update
- .update_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Note>()?;
-
- let user_uri = update
- .update_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
-
- insert_activity(user.id, update, false, pool).await?;
-
- let comment = CommentForm::from_apub(¬e, client, pool).await?;
-
- let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, client, pool)
- .await?
- .id;
-
- let updated_comment = blocking(pool, move |conn| {
- Comment::update(conn, comment_id, &comment)
- })
- .await??;
-
- let post_id = updated_comment.post_id;
- let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
-
- let mentions = scrape_text_for_mentions(&updated_comment.content);
- let recipient_ids = send_local_notifs(mentions, updated_comment, user, post, pool).await?;
-
- // Refetch the view
- let comment_view =
- blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??;
-
- let res = CommentResponse {
- comment: comment_view,
- recipient_ids,
- };
-
- chat_server.do_send(SendComment {
- op: UserOperation::EditComment,
- comment: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_like_comment(
- like: Like,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let note = like
- .like_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Note>()?;
-
- let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string();
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
-
- insert_activity(user.id, like, false, pool).await?;
-
- let comment = CommentForm::from_apub(¬e, client, pool).await?;
-
- let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, client, pool)
- .await?
- .id;
-
- let like_form = CommentLikeForm {
- comment_id,
- post_id: comment.post_id,
- user_id: user.id,
- score: 1,
- };
- blocking(pool, move |conn| {
- CommentLike::remove(conn, &like_form)?;
- CommentLike::like(conn, &like_form)
- })
- .await??;
-
- // Refetch the view
- let comment_view =
- blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??;
-
- // TODO get those recipient actor ids from somewhere
- let recipient_ids = vec![];
- let res = CommentResponse {
- comment: comment_view,
- recipient_ids,
- };
-
- chat_server.do_send(SendComment {
- op: UserOperation::CreateCommentLike,
- comment: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_dislike_comment(
- dislike: Dislike,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let note = dislike
- .dislike_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Note>()?;
-
- let user_uri = dislike
- .dislike_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
-
- insert_activity(user.id, dislike, false, pool).await?;
-
- let comment = CommentForm::from_apub(¬e, client, pool).await?;
-
- let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, client, pool)
- .await?
- .id;
-
- let like_form = CommentLikeForm {
- comment_id,
- post_id: comment.post_id,
- user_id: user.id,
- score: -1,
- };
- blocking(pool, move |conn| {
- CommentLike::remove(conn, &like_form)?;
- CommentLike::like(conn, &like_form)
- })
- .await??;
-
- // Refetch the view
- let comment_view =
- blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??;
-
- // TODO get those recipient actor ids from somewhere
- let recipient_ids = vec![];
- let res = CommentResponse {
- comment: comment_view,
- recipient_ids,
- };
-
- chat_server.do_send(SendComment {
- op: UserOperation::CreateCommentLike,
- comment: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_delete_community(
- delete: Delete,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let user_uri = delete
- .delete_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let group = delete
- .delete_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<GroupExt>()?;
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
-
- insert_activity(user.id, delete, false, pool).await?;
-
- let community_actor_id = CommunityForm::from_apub(&group, client, pool)
- .await?
- .actor_id;
-
- let community = blocking(pool, move |conn| {
- Community::read_from_actor_id(conn, &community_actor_id)
- })
- .await??;
-
- let community_form = CommunityForm {
- name: community.name.to_owned(),
- title: community.title.to_owned(),
- description: community.description.to_owned(),
- category_id: community.category_id, // Note: need to keep this due to foreign key constraint
- creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
- removed: None,
- published: None,
- updated: Some(naive_now()),
- deleted: Some(true),
- nsfw: community.nsfw,
- actor_id: community.actor_id,
- local: community.local,
- private_key: community.private_key,
- public_key: community.public_key,
- last_refreshed_at: None,
- };
-
- let community_id = community.id;
- blocking(pool, move |conn| {
- Community::update(conn, community_id, &community_form)
- })
- .await??;
-
- let community_id = community.id;
- let res = CommunityResponse {
- community: blocking(pool, move |conn| {
- CommunityView::read(conn, community_id, None)
- })
- .await??,
- };
-
- let community_id = res.community.id;
-
- chat_server.do_send(SendCommunityRoomMessage {
- op: UserOperation::EditCommunity,
- response: res,
- community_id,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_remove_community(
- remove: Remove,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let mod_uri = remove
- .remove_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let group = remove
- .remove_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<GroupExt>()?;
-
- let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, client, pool).await?;
-
- insert_activity(mod_.id, remove, false, pool).await?;
-
- let community_actor_id = CommunityForm::from_apub(&group, client, pool)
- .await?
- .actor_id;
-
- let community = blocking(pool, move |conn| {
- Community::read_from_actor_id(conn, &community_actor_id)
- })
- .await??;
-
- let community_form = CommunityForm {
- name: community.name.to_owned(),
- title: community.title.to_owned(),
- description: community.description.to_owned(),
- category_id: community.category_id, // Note: need to keep this due to foreign key constraint
- creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
- removed: Some(true),
- published: None,
- updated: Some(naive_now()),
- deleted: None,
- nsfw: community.nsfw,
- actor_id: community.actor_id,
- local: community.local,
- private_key: community.private_key,
- public_key: community.public_key,
- last_refreshed_at: None,
- };
-
- let community_id = community.id;
- blocking(pool, move |conn| {
- Community::update(conn, community_id, &community_form)
- })
- .await??;
-
- let community_id = community.id;
- let res = CommunityResponse {
- community: blocking(pool, move |conn| {
- CommunityView::read(conn, community_id, None)
- })
- .await??,
- };
-
- let community_id = res.community.id;
-
- chat_server.do_send(SendCommunityRoomMessage {
- op: UserOperation::EditCommunity,
- response: res,
- community_id,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_delete_post(
- delete: Delete,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let user_uri = delete
- .delete_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let page = delete
- .delete_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<PageExt>()?;
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
-
- insert_activity(user.id, delete, false, pool).await?;
-
- let post_ap_id = PostForm::from_apub(&page, client, pool).await?.ap_id;
-
- let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?;
-
- let post_form = PostForm {
- name: post.name.to_owned(),
- url: post.url.to_owned(),
- body: post.body.to_owned(),
- creator_id: post.creator_id.to_owned(),
- community_id: post.community_id,
- removed: None,
- deleted: Some(true),
- nsfw: post.nsfw,
- locked: None,
- stickied: None,
- updated: Some(naive_now()),
- embed_title: post.embed_title,
- embed_description: post.embed_description,
- embed_html: post.embed_html,
- thumbnail_url: post.thumbnail_url,
- ap_id: post.ap_id,
- local: post.local,
- published: None,
- };
- let post_id = post.id;
- blocking(pool, move |conn| Post::update(conn, post_id, &post_form)).await??;
-
- // Refetch the view
- let post_id = post.id;
- let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??;
-
- let res = PostResponse { post: post_view };
-
- chat_server.do_send(SendPost {
- op: UserOperation::EditPost,
- post: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_remove_post(
- remove: Remove,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let mod_uri = remove
- .remove_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let page = remove
- .remove_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<PageExt>()?;
-
- let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, client, pool).await?;
-
- insert_activity(mod_.id, remove, false, pool).await?;
-
- let post_ap_id = PostForm::from_apub(&page, client, pool).await?.ap_id;
-
- let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?;
-
- let post_form = PostForm {
- name: post.name.to_owned(),
- url: post.url.to_owned(),
- body: post.body.to_owned(),
- creator_id: post.creator_id.to_owned(),
- community_id: post.community_id,
- removed: Some(true),
- deleted: None,
- nsfw: post.nsfw,
- locked: None,
- stickied: None,
- updated: Some(naive_now()),
- embed_title: post.embed_title,
- embed_description: post.embed_description,
- embed_html: post.embed_html,
- thumbnail_url: post.thumbnail_url,
- ap_id: post.ap_id,
- local: post.local,
- published: None,
- };
- let post_id = post.id;
- blocking(pool, move |conn| Post::update(conn, post_id, &post_form)).await??;
-
- // Refetch the view
- let post_id = post.id;
- let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??;
-
- let res = PostResponse { post: post_view };
-
- chat_server.do_send(SendPost {
- op: UserOperation::EditPost,
- post: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_delete_comment(
- delete: Delete,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let user_uri = delete
- .delete_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let note = delete
- .delete_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Note>()?;
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
-
- insert_activity(user.id, delete, false, pool).await?;
-
- let comment_ap_id = CommentForm::from_apub(¬e, client, pool).await?.ap_id;
-
- let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?;
-
- let comment_form = CommentForm {
- content: comment.content.to_owned(),
- parent_id: comment.parent_id,
- post_id: comment.post_id,
- creator_id: comment.creator_id,
- removed: None,
- deleted: Some(true),
- read: None,
- published: None,
- updated: Some(naive_now()),
- ap_id: comment.ap_id,
- local: comment.local,
- };
- let comment_id = comment.id;
- blocking(pool, move |conn| {
- Comment::update(conn, comment_id, &comment_form)
- })
- .await??;
-
- // Refetch the view
- let comment_id = comment.id;
- let comment_view =
- blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??;
-
- // TODO get those recipient actor ids from somewhere
- let recipient_ids = vec![];
- let res = CommentResponse {
- comment: comment_view,
- recipient_ids,
- };
-
- chat_server.do_send(SendComment {
- op: UserOperation::EditComment,
- comment: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_remove_comment(
- remove: Remove,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let mod_uri = remove
- .remove_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let note = remove
- .remove_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Note>()?;
-
- let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, client, pool).await?;
-
- insert_activity(mod_.id, remove, false, pool).await?;
-
- let comment_ap_id = CommentForm::from_apub(¬e, client, pool).await?.ap_id;
-
- let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?;
-
- let comment_form = CommentForm {
- content: comment.content.to_owned(),
- parent_id: comment.parent_id,
- post_id: comment.post_id,
- creator_id: comment.creator_id,
- removed: Some(true),
- deleted: None,
- read: None,
- published: None,
- updated: Some(naive_now()),
- ap_id: comment.ap_id,
- local: comment.local,
- };
- let comment_id = comment.id;
- blocking(pool, move |conn| {
- Comment::update(conn, comment_id, &comment_form)
- })
- .await??;
-
- // Refetch the view
- let comment_id = comment.id;
- let comment_view =
- blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??;
-
- // TODO get those recipient actor ids from somewhere
- let recipient_ids = vec![];
- let res = CommentResponse {
- comment: comment_view,
- recipient_ids,
- };
-
- chat_server.do_send(SendComment {
- op: UserOperation::EditComment,
- comment: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_delete(
- undo: Undo,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let delete = undo
- .undo_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Delete>()?;
-
- let type_ = delete
- .delete_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .kind()
- .unwrap();
-
- match type_ {
- "Note" => receive_undo_delete_comment(delete, client, pool, chat_server).await,
- "Page" => receive_undo_delete_post(delete, client, pool, chat_server).await,
- "Group" => receive_undo_delete_community(delete, client, pool, chat_server).await,
- d => Err(format_err!("Undo Delete type {} not supported", d).into()),
- }
-}
-
-async fn receive_undo_remove(
- undo: Undo,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let remove = undo
- .undo_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Remove>()?;
-
- let type_ = remove
- .remove_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .kind()
- .unwrap();
-
- match type_ {
- "Note" => receive_undo_remove_comment(remove, client, pool, chat_server).await,
- "Page" => receive_undo_remove_post(remove, client, pool, chat_server).await,
- "Group" => receive_undo_remove_community(remove, client, pool, chat_server).await,
- d => Err(format_err!("Undo Delete type {} not supported", d).into()),
- }
-}
-
-async fn receive_undo_delete_comment(
- delete: Delete,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let user_uri = delete
- .delete_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let note = delete
- .delete_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Note>()?;
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
-
- insert_activity(user.id, delete, false, pool).await?;
-
- let comment_ap_id = CommentForm::from_apub(¬e, client, pool).await?.ap_id;
-
- let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?;
-
- let comment_form = CommentForm {
- content: comment.content.to_owned(),
- parent_id: comment.parent_id,
- post_id: comment.post_id,
- creator_id: comment.creator_id,
- removed: None,
- deleted: Some(false),
- read: None,
- published: None,
- updated: Some(naive_now()),
- ap_id: comment.ap_id,
- local: comment.local,
- };
- let comment_id = comment.id;
- blocking(pool, move |conn| {
- Comment::update(conn, comment_id, &comment_form)
- })
- .await??;
-
- // Refetch the view
- let comment_id = comment.id;
- let comment_view =
- blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??;
-
- // TODO get those recipient actor ids from somewhere
- let recipient_ids = vec![];
- let res = CommentResponse {
- comment: comment_view,
- recipient_ids,
- };
-
- chat_server.do_send(SendComment {
- op: UserOperation::EditComment,
- comment: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_remove_comment(
- remove: Remove,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let mod_uri = remove
- .remove_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let note = remove
- .remove_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Note>()?;
-
- let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, client, pool).await?;
-
- insert_activity(mod_.id, remove, false, pool).await?;
-
- let comment_ap_id = CommentForm::from_apub(¬e, client, pool).await?.ap_id;
-
- let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, client, pool).await?;
-
- let comment_form = CommentForm {
- content: comment.content.to_owned(),
- parent_id: comment.parent_id,
- post_id: comment.post_id,
- creator_id: comment.creator_id,
- removed: Some(false),
- deleted: None,
- read: None,
- published: None,
- updated: Some(naive_now()),
- ap_id: comment.ap_id,
- local: comment.local,
- };
- let comment_id = comment.id;
- blocking(pool, move |conn| {
- Comment::update(conn, comment_id, &comment_form)
- })
- .await??;
-
- // Refetch the view
- let comment_id = comment.id;
- let comment_view =
- blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??;
-
- // TODO get those recipient actor ids from somewhere
- let recipient_ids = vec![];
- let res = CommentResponse {
- comment: comment_view,
- recipient_ids,
- };
-
- chat_server.do_send(SendComment {
- op: UserOperation::EditComment,
- comment: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_delete_post(
- delete: Delete,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let user_uri = delete
- .delete_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let page = delete
- .delete_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<PageExt>()?;
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
-
- insert_activity(user.id, delete, false, pool).await?;
-
- let post_ap_id = PostForm::from_apub(&page, client, pool).await?.ap_id;
-
- let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?;
-
- let post_form = PostForm {
- name: post.name.to_owned(),
- url: post.url.to_owned(),
- body: post.body.to_owned(),
- creator_id: post.creator_id.to_owned(),
- community_id: post.community_id,
- removed: None,
- deleted: Some(false),
- nsfw: post.nsfw,
- locked: None,
- stickied: None,
- updated: Some(naive_now()),
- embed_title: post.embed_title,
- embed_description: post.embed_description,
- embed_html: post.embed_html,
- thumbnail_url: post.thumbnail_url,
- ap_id: post.ap_id,
- local: post.local,
- published: None,
- };
- let post_id = post.id;
- blocking(pool, move |conn| Post::update(conn, post_id, &post_form)).await??;
-
- // Refetch the view
- let post_id = post.id;
- let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??;
-
- let res = PostResponse { post: post_view };
-
- chat_server.do_send(SendPost {
- op: UserOperation::EditPost,
- post: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_remove_post(
- remove: Remove,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let mod_uri = remove
- .remove_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let page = remove
- .remove_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<PageExt>()?;
-
- let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, client, pool).await?;
-
- insert_activity(mod_.id, remove, false, pool).await?;
-
- let post_ap_id = PostForm::from_apub(&page, client, pool).await?.ap_id;
-
- let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?;
-
- let post_form = PostForm {
- name: post.name.to_owned(),
- url: post.url.to_owned(),
- body: post.body.to_owned(),
- creator_id: post.creator_id.to_owned(),
- community_id: post.community_id,
- removed: Some(false),
- deleted: None,
- nsfw: post.nsfw,
- locked: None,
- stickied: None,
- updated: Some(naive_now()),
- embed_title: post.embed_title,
- embed_description: post.embed_description,
- embed_html: post.embed_html,
- thumbnail_url: post.thumbnail_url,
- ap_id: post.ap_id,
- local: post.local,
- published: None,
- };
- let post_id = post.id;
- blocking(pool, move |conn| Post::update(conn, post_id, &post_form)).await??;
-
- // Refetch the view
- let post_id = post.id;
- let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??;
-
- let res = PostResponse { post: post_view };
-
- chat_server.do_send(SendPost {
- op: UserOperation::EditPost,
- post: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_delete_community(
- delete: Delete,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let user_uri = delete
- .delete_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let group = delete
- .delete_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<GroupExt>()?;
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
-
- insert_activity(user.id, delete, false, pool).await?;
-
- let community_actor_id = CommunityForm::from_apub(&group, client, pool)
- .await?
- .actor_id;
-
- let community = blocking(pool, move |conn| {
- Community::read_from_actor_id(conn, &community_actor_id)
- })
- .await??;
-
- let community_form = CommunityForm {
- name: community.name.to_owned(),
- title: community.title.to_owned(),
- description: community.description.to_owned(),
- category_id: community.category_id, // Note: need to keep this due to foreign key constraint
- creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
- removed: None,
- published: None,
- updated: Some(naive_now()),
- deleted: Some(false),
- nsfw: community.nsfw,
- actor_id: community.actor_id,
- local: community.local,
- private_key: community.private_key,
- public_key: community.public_key,
- last_refreshed_at: None,
- };
-
- let community_id = community.id;
- blocking(pool, move |conn| {
- Community::update(conn, community_id, &community_form)
- })
- .await??;
-
- let community_id = community.id;
- let res = CommunityResponse {
- community: blocking(pool, move |conn| {
- CommunityView::read(conn, community_id, None)
- })
- .await??,
- };
-
- let community_id = res.community.id;
-
- chat_server.do_send(SendCommunityRoomMessage {
- op: UserOperation::EditCommunity,
- response: res,
- community_id,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_remove_community(
- remove: Remove,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let mod_uri = remove
- .remove_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let group = remove
- .remove_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<GroupExt>()?;
-
- let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, client, pool).await?;
-
- insert_activity(mod_.id, remove, false, pool).await?;
-
- let community_actor_id = CommunityForm::from_apub(&group, client, pool)
- .await?
- .actor_id;
-
- let community = blocking(pool, move |conn| {
- Community::read_from_actor_id(conn, &community_actor_id)
- })
- .await??;
-
- let community_form = CommunityForm {
- name: community.name.to_owned(),
- title: community.title.to_owned(),
- description: community.description.to_owned(),
- category_id: community.category_id, // Note: need to keep this due to foreign key constraint
- creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
- removed: Some(false),
- published: None,
- updated: Some(naive_now()),
- deleted: None,
- nsfw: community.nsfw,
- actor_id: community.actor_id,
- local: community.local,
- private_key: community.private_key,
- public_key: community.public_key,
- last_refreshed_at: None,
- };
-
- let community_id = community.id;
- blocking(pool, move |conn| {
- Community::update(conn, community_id, &community_form)
- })
- .await??;
-
- let community_id = community.id;
- let res = CommunityResponse {
- community: blocking(pool, move |conn| {
- CommunityView::read(conn, community_id, None)
- })
- .await??,
- };
-
- let community_id = res.community.id;
-
- chat_server.do_send(SendCommunityRoomMessage {
- op: UserOperation::EditCommunity,
- response: res,
- community_id,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_like(
- undo: Undo,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let like = undo
- .undo_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Like>()?;
-
- let type_ = like
- .like_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .kind()
- .unwrap();
-
- match type_ {
- "Note" => receive_undo_like_comment(like, client, pool, chat_server).await,
- "Page" => receive_undo_like_post(like, client, pool, chat_server).await,
- d => Err(format_err!("Undo Delete type {} not supported", d).into()),
- }
-}
-
-async fn receive_undo_like_comment(
- like: Like,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let note = like
- .like_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Note>()?;
-
- let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string();
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
-
- insert_activity(user.id, like, false, pool).await?;
-
- let comment = CommentForm::from_apub(¬e, client, pool).await?;
-
- let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, client, pool)
- .await?
- .id;
-
- let like_form = CommentLikeForm {
- comment_id,
- post_id: comment.post_id,
- user_id: user.id,
- score: 0,
- };
- blocking(pool, move |conn| CommentLike::remove(conn, &like_form)).await??;
-
- // Refetch the view
- let comment_view =
- blocking(pool, move |conn| CommentView::read(conn, comment_id, None)).await??;
-
- // TODO get those recipient actor ids from somewhere
- let recipient_ids = vec![];
- let res = CommentResponse {
- comment: comment_view,
- recipient_ids,
- };
-
- chat_server.do_send(SendComment {
- op: UserOperation::CreateCommentLike,
- comment: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_like_post(
- like: Like,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let page = like
- .like_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<PageExt>()?;
-
- let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string();
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
-
- insert_activity(user.id, like, false, pool).await?;
-
- let post = PostForm::from_apub(&page, client, pool).await?;
-
- let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, client, pool)
- .await?
- .id;
-
- let like_form = PostLikeForm {
- post_id,
- user_id: user.id,
- score: 1,
- };
- blocking(pool, move |conn| PostLike::remove(conn, &like_form)).await??;
-
- // Refetch the view
- let post_view = blocking(pool, move |conn| PostView::read(conn, post_id, None)).await??;
-
- let res = PostResponse { post: post_view };
-
- chat_server.do_send(SendPost {
- op: UserOperation::CreatePostLike,
- post: res,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
use crate::{
- api::claims::Claims,
+ api::{check_slurs, check_slurs_opt},
apub::{
- activities::send_activity,
+ activities::{generate_activity_id, send_activity},
+ check_actor_domain,
create_apub_response,
+ fetcher::get_or_fetch_and_upsert_actor,
insert_activity,
ActorType,
FromApub,
ToApub,
},
blocking,
- routes::DbPoolParam,
DbPool,
+ LemmyContext,
LemmyError,
};
-use activitystreams_ext::Ext1;
-use activitystreams_new::{
- activity::{Follow, Undo},
+use activitystreams::{
+ activity::{
+ kind::{FollowType, UndoType},
+ Follow,
+ Undo,
+ },
actor::{ApActor, Endpoints, Person},
- context,
object::{Image, Tombstone},
prelude::*,
- primitives::{XsdAnyUri, XsdDateTime},
};
-use actix_web::{body::Body, client::Client, web, HttpResponse};
-use failure::_core::str::FromStr;
+use activitystreams_ext::Ext1;
+use actix_web::{body::Body, web, HttpResponse};
+use anyhow::Context;
use lemmy_db::{
naive_now,
user::{UserForm, User_},
};
-use lemmy_utils::convert_datetime;
+use lemmy_utils::{convert_datetime, location_info};
use serde::Deserialize;
+use url::Url;
#[derive(Deserialize)]
pub struct UserQuery {
// TODO go through all these to_string and to_owned()
let mut person = Person::new();
person
- .set_context(context())
- .set_id(XsdAnyUri::from_str(&self.actor_id)?)
+ .set_context(activitystreams::context())
+ .set_id(Url::parse(&self.actor_id)?)
.set_name(self.name.to_owned())
- .set_published(XsdDateTime::from(convert_datetime(self.published)));
+ .set_published(convert_datetime(self.published));
if let Some(u) = self.updated {
- person.set_updated(XsdDateTime::from(convert_datetime(u)));
+ person.set_updated(convert_datetime(u));
}
if let Some(avatar_url) = &self.avatar {
person.set_icon(image.into_any_base()?);
}
- let mut ap_actor = ApActor::new(self.get_inbox_url().parse()?, person);
+ if let Some(banner_url) = &self.banner {
+ let mut image = Image::new();
+ image.set_url(banner_url.to_owned());
+ person.set_image(image.into_any_base()?);
+ }
+
+ if let Some(bio) = &self.bio {
+ person.set_summary(bio.to_owned());
+ }
+
+ let mut ap_actor = ApActor::new(self.get_inbox_url()?, person);
ap_actor
- .set_outbox(self.get_outbox_url().parse()?)
- .set_followers(self.get_followers_url().parse()?)
+ .set_outbox(self.get_outbox_url()?)
+ .set_followers(self.get_followers_url()?)
.set_following(self.get_following_url().parse()?)
.set_liked(self.get_liked_url().parse()?)
.set_endpoints(Endpoints {
- shared_inbox: Some(self.get_shared_inbox_url().parse()?),
+ shared_inbox: Some(self.get_shared_inbox_url()?),
..Default::default()
});
ap_actor.set_preferred_username(i.to_owned());
}
- Ok(Ext1::new(ap_actor, self.get_public_key_ext()))
+ Ok(Ext1::new(ap_actor, self.get_public_key_ext()?))
}
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
unimplemented!()
#[async_trait::async_trait(?Send)]
impl ActorType for User_ {
- fn actor_id(&self) -> String {
+ fn actor_id_str(&self) -> String {
self.actor_id.to_owned()
}
- fn public_key(&self) -> String {
- self.public_key.to_owned().unwrap()
+ fn public_key(&self) -> Option<String> {
+ self.public_key.to_owned()
}
- fn private_key(&self) -> String {
- self.private_key.to_owned().unwrap()
+ fn private_key(&self) -> Option<String> {
+ self.private_key.to_owned()
}
/// As a given local user, send out a follow request to a remote community.
async fn send_follow(
&self,
- follow_actor_id: &str,
- client: &Client,
- pool: &DbPool,
+ follow_actor_id: &Url,
+ context: &LemmyContext,
) -> Result<(), LemmyError> {
- let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4());
- let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id);
- follow.set_context(context()).set_id(id.parse()?);
- let to = format!("{}/inbox", follow_actor_id);
+ let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id.as_str());
+ follow
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(FollowType::Follow)?);
+ let follow_actor = get_or_fetch_and_upsert_actor(follow_actor_id, context).await?;
+ let to = follow_actor.get_inbox_url()?;
- insert_activity(self.id, follow.clone(), true, pool).await?;
+ insert_activity(self.id, follow.clone(), true, context.pool()).await?;
- send_activity(client, &follow, self, vec![to]).await?;
+ send_activity(context.client(), &follow.into_any_base()?, self, vec![to]).await?;
Ok(())
}
async fn send_unfollow(
&self,
- follow_actor_id: &str,
- client: &Client,
- pool: &DbPool,
+ follow_actor_id: &Url,
+ context: &LemmyContext,
) -> Result<(), LemmyError> {
- let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4());
- let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id);
- follow.set_context(context()).set_id(id.parse()?);
+ let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id.as_str());
+ follow
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(FollowType::Follow)?);
+ let follow_actor = get_or_fetch_and_upsert_actor(follow_actor_id, context).await?;
- let to = format!("{}/inbox", follow_actor_id);
+ let to = follow_actor.get_inbox_url()?;
- // TODO
// Undo that fake activity
- let undo_id = format!("{}/undo/follow/{}", self.actor_id, uuid::Uuid::new_v4());
- let mut undo = Undo::new(self.actor_id.parse::<XsdAnyUri>()?, follow.into_any_base()?);
- undo.set_context(context()).set_id(undo_id.parse()?);
+ let mut undo = Undo::new(Url::parse(&self.actor_id)?, follow.into_any_base()?);
+ undo
+ .set_context(activitystreams::context())
+ .set_id(generate_activity_id(UndoType::Undo)?);
- insert_activity(self.id, undo.clone(), true, pool).await?;
+ insert_activity(self.id, undo.clone(), true, context.pool()).await?;
- send_activity(client, &undo, self, vec![to]).await?;
+ send_activity(context.client(), &undo.into_any_base()?, self, vec![to]).await?;
Ok(())
}
- async fn send_delete(
- &self,
- _creator: &User_,
- _client: &Client,
- _pool: &DbPool,
- ) -> Result<(), LemmyError> {
+ async fn send_delete(&self, _creator: &User_, _context: &LemmyContext) -> Result<(), LemmyError> {
unimplemented!()
}
async fn send_undo_delete(
&self,
_creator: &User_,
- _client: &Client,
- _pool: &DbPool,
+ _context: &LemmyContext,
) -> Result<(), LemmyError> {
unimplemented!()
}
- async fn send_remove(
- &self,
- _creator: &User_,
- _client: &Client,
- _pool: &DbPool,
- ) -> Result<(), LemmyError> {
+ async fn send_remove(&self, _creator: &User_, _context: &LemmyContext) -> Result<(), LemmyError> {
unimplemented!()
}
async fn send_undo_remove(
&self,
_creator: &User_,
- _client: &Client,
- _pool: &DbPool,
+ _context: &LemmyContext,
) -> Result<(), LemmyError> {
unimplemented!()
}
async fn send_accept_follow(
&self,
- _follow: &Follow,
- _client: &Client,
- _pool: &DbPool,
+ _follow: Follow,
+ _context: &LemmyContext,
) -> Result<(), LemmyError> {
unimplemented!()
}
- async fn get_follower_inboxes(&self, _pool: &DbPool) -> Result<Vec<String>, LemmyError> {
+ async fn get_follower_inboxes(&self, _pool: &DbPool) -> Result<Vec<Url>, LemmyError> {
unimplemented!()
}
+
+ fn user_id(&self) -> i32 {
+ self.id
+ }
}
#[async_trait::async_trait(?Send)]
impl FromApub for UserForm {
type ApubType = PersonExt;
/// Parse an ActivityPub person received from another instance into a Lemmy user.
- async fn from_apub(person: &PersonExt, _: &Client, _: &DbPool) -> Result<Self, LemmyError> {
+ async fn from_apub(
+ person: &PersonExt,
+ _context: &LemmyContext,
+ expected_domain: Option<Url>,
+ ) -> Result<Self, LemmyError> {
let avatar = match person.icon() {
- Some(any_image) => Image::from_any_base(any_image.as_one().unwrap().clone())
- .unwrap()
- .unwrap()
- .url
- .unwrap()
- .as_single_xsd_any_uri()
- .map(|u| u.to_string()),
+ Some(any_image) => Some(
+ Image::from_any_base(any_image.as_one().context(location_info!())?.clone())?
+ .context(location_info!())?
+ .url()
+ .context(location_info!())?
+ .as_single_xsd_any_uri()
+ .map(|u| u.to_string()),
+ ),
None => None,
};
+ let banner = match person.image() {
+ Some(any_image) => Some(
+ Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
+ .context(location_info!())?
+ .context(location_info!())?
+ .url()
+ .context(location_info!())?
+ .as_single_xsd_any_uri()
+ .map(|u| u.to_string()),
+ ),
+ None => None,
+ };
+
+ let name = person
+ .name()
+ .context(location_info!())?
+ .one()
+ .context(location_info!())?
+ .as_xsd_string()
+ .context(location_info!())?
+ .to_string();
+ let preferred_username = person.inner.preferred_username().map(|u| u.to_string());
+ let bio = person
+ .inner
+ .summary()
+ .map(|s| s.as_single_xsd_string())
+ .flatten()
+ .map(|s| s.to_string());
+ check_slurs(&name)?;
+ check_slurs_opt(&preferred_username)?;
+ check_slurs_opt(&bio)?;
+
Ok(UserForm {
- name: person
- .name()
- .unwrap()
- .as_single_xsd_string()
- .unwrap()
- .into(),
- preferred_username: person.inner.preferred_username().map(|u| u.to_string()),
+ name,
+ preferred_username,
password_encrypted: "".to_string(),
admin: false,
banned: false,
email: None,
avatar,
- updated: person
- .updated()
- .map(|u| u.as_ref().to_owned().naive_local()),
+ banner,
+ updated: person.updated().map(|u| u.to_owned().naive_local()),
show_nsfw: false,
theme: "".to_string(),
default_sort_type: 0,
show_avatars: false,
send_notifications_to_email: false,
matrix_user_id: None,
- actor_id: person.id().unwrap().to_string(),
- bio: person
- .summary()
- .map(|s| s.as_single_xsd_string().unwrap().into()),
+ actor_id: check_actor_domain(person, expected_domain)?,
+ bio,
local: false,
private_key: None,
public_key: Some(person.ext_one.public_key.to_owned().public_key_pem),
/// Return the user json over HTTP.
pub async fn get_apub_user_http(
info: web::Path<UserQuery>,
- db: DbPoolParam,
+ context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, LemmyError> {
let user_name = info.into_inner().user_name;
- let user = blocking(&db, move |conn| {
- Claims::find_by_email_or_username(conn, &user_name)
+ let user = blocking(context.pool(), move |conn| {
+ User_::find_by_email_or_username(conn, &user_name)
})
.await??;
- let u = user.to_apub(&db).await?;
+ let u = user.to_apub(context.pool()).await?;
Ok(create_apub_response(&u))
}
+++ /dev/null
-use crate::{
- api::user::PrivateMessageResponse,
- apub::{
- extensions::signatures::verify,
- fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user},
- insert_activity,
- FromApub,
- },
- blocking,
- routes::{ChatServerParam, DbPoolParam},
- websocket::{server::SendUserRoomMessage, UserOperation},
- DbPool,
- LemmyError,
-};
-use activitystreams::{
- activity::{Accept, Create, Delete, Undo, Update},
- object::Note,
-};
-use actix_web::{client::Client, web, HttpRequest, HttpResponse};
-use lemmy_db::{
- community::{CommunityFollower, CommunityFollowerForm},
- naive_now,
- private_message::{PrivateMessage, PrivateMessageForm},
- private_message_view::PrivateMessageView,
- user::User_,
- Crud,
- Followable,
-};
-use log::debug;
-use serde::Deserialize;
-use std::fmt::Debug;
-
-#[serde(untagged)]
-#[derive(Deserialize, Debug)]
-pub enum UserAcceptedObjects {
- Accept(Box<Accept>),
- Create(Box<Create>),
- Update(Box<Update>),
- Delete(Box<Delete>),
- Undo(Box<Undo>),
-}
-
-/// Handler for all incoming activities to user inboxes.
-pub async fn user_inbox(
- request: HttpRequest,
- input: web::Json<UserAcceptedObjects>,
- path: web::Path<String>,
- client: web::Data<Client>,
- db: DbPoolParam,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- // TODO: would be nice if we could do the signature check here, but we cant access the actor property
- let input = input.into_inner();
- let username = path.into_inner();
- debug!("User {} received activity: {:?}", &username, &input);
-
- match input {
- UserAcceptedObjects::Accept(a) => receive_accept(*a, &request, &username, &client, &db).await,
- UserAcceptedObjects::Create(c) => {
- receive_create_private_message(*c, &request, &client, &db, chat_server).await
- }
- UserAcceptedObjects::Update(u) => {
- receive_update_private_message(*u, &request, &client, &db, chat_server).await
- }
- UserAcceptedObjects::Delete(d) => {
- receive_delete_private_message(*d, &request, &client, &db, chat_server).await
- }
- UserAcceptedObjects::Undo(u) => {
- receive_undo_delete_private_message(*u, &request, &client, &db, chat_server).await
- }
- }
-}
-
-/// Handle accepted follows.
-async fn receive_accept(
- accept: Accept,
- request: &HttpRequest,
- username: &str,
- client: &Client,
- pool: &DbPool,
-) -> Result<HttpResponse, LemmyError> {
- let community_uri = accept
- .accept_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let community = get_or_fetch_and_upsert_remote_community(&community_uri, client, pool).await?;
- verify(request, &community)?;
-
- let username = username.to_owned();
- let user = blocking(pool, move |conn| User_::read_from_name(conn, &username)).await??;
-
- insert_activity(community.creator_id, accept, false, pool).await?;
-
- // Now you need to add this to the community follower
- let community_follower_form = CommunityFollowerForm {
- community_id: community.id,
- user_id: user.id,
- };
-
- // This will fail if they're already a follower
- blocking(pool, move |conn| {
- CommunityFollower::follow(conn, &community_follower_form)
- })
- .await??;
-
- // TODO: make sure that we actually requested a follow
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_create_private_message(
- create: Create,
- request: &HttpRequest,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let note = create
- .create_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Note>()?;
-
- let user_uri = create
- .create_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
- verify(request, &user)?;
-
- insert_activity(user.id, create, false, pool).await?;
-
- let private_message = PrivateMessageForm::from_apub(¬e, client, pool).await?;
-
- let inserted_private_message = blocking(pool, move |conn| {
- PrivateMessage::create(conn, &private_message)
- })
- .await??;
-
- let message = blocking(pool, move |conn| {
- PrivateMessageView::read(conn, inserted_private_message.id)
- })
- .await??;
-
- let res = PrivateMessageResponse { message };
-
- let recipient_id = res.message.recipient_id;
-
- chat_server.do_send(SendUserRoomMessage {
- op: UserOperation::CreatePrivateMessage,
- response: res,
- recipient_id,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_update_private_message(
- update: Update,
- request: &HttpRequest,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let note = update
- .update_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Note>()?;
-
- let user_uri = update
- .update_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
- verify(request, &user)?;
-
- insert_activity(user.id, update, false, pool).await?;
-
- let private_message_form = PrivateMessageForm::from_apub(¬e, client, pool).await?;
-
- let private_message_ap_id = private_message_form.ap_id.clone();
- let private_message = blocking(pool, move |conn| {
- PrivateMessage::read_from_apub_id(conn, &private_message_ap_id)
- })
- .await??;
-
- let private_message_id = private_message.id;
- blocking(pool, move |conn| {
- PrivateMessage::update(conn, private_message_id, &private_message_form)
- })
- .await??;
-
- let private_message_id = private_message.id;
- let message = blocking(pool, move |conn| {
- PrivateMessageView::read(conn, private_message_id)
- })
- .await??;
-
- let res = PrivateMessageResponse { message };
-
- let recipient_id = res.message.recipient_id;
-
- chat_server.do_send(SendUserRoomMessage {
- op: UserOperation::EditPrivateMessage,
- response: res,
- recipient_id,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_delete_private_message(
- delete: Delete,
- request: &HttpRequest,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let note = delete
- .delete_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Note>()?;
-
- let user_uri = delete
- .delete_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
- verify(request, &user)?;
-
- insert_activity(user.id, delete, false, pool).await?;
-
- let private_message_form = PrivateMessageForm::from_apub(¬e, client, pool).await?;
-
- let private_message_ap_id = private_message_form.ap_id;
- let private_message = blocking(pool, move |conn| {
- PrivateMessage::read_from_apub_id(conn, &private_message_ap_id)
- })
- .await??;
-
- let private_message_form = PrivateMessageForm {
- content: private_message_form.content,
- recipient_id: private_message.recipient_id,
- creator_id: private_message.creator_id,
- deleted: Some(true),
- read: None,
- ap_id: private_message.ap_id,
- local: private_message.local,
- published: None,
- updated: Some(naive_now()),
- };
-
- let private_message_id = private_message.id;
- blocking(pool, move |conn| {
- PrivateMessage::update(conn, private_message_id, &private_message_form)
- })
- .await??;
-
- let private_message_id = private_message.id;
- let message = blocking(pool, move |conn| {
- PrivateMessageView::read(&conn, private_message_id)
- })
- .await??;
-
- let res = PrivateMessageResponse { message };
-
- let recipient_id = res.message.recipient_id;
-
- chat_server.do_send(SendUserRoomMessage {
- op: UserOperation::EditPrivateMessage,
- response: res,
- recipient_id,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn receive_undo_delete_private_message(
- undo: Undo,
- request: &HttpRequest,
- client: &Client,
- pool: &DbPool,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse, LemmyError> {
- let delete = undo
- .undo_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Delete>()?;
-
- let note = delete
- .delete_props
- .get_object_base_box()
- .to_owned()
- .unwrap()
- .to_owned()
- .into_concrete::<Note>()?;
-
- let user_uri = delete
- .delete_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
-
- let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
- verify(request, &user)?;
-
- insert_activity(user.id, delete, false, pool).await?;
-
- let private_message = PrivateMessageForm::from_apub(¬e, client, pool).await?;
-
- let private_message_ap_id = private_message.ap_id.clone();
- let private_message_id = blocking(pool, move |conn| {
- PrivateMessage::read_from_apub_id(conn, &private_message_ap_id).map(|pm| pm.id)
- })
- .await??;
-
- let private_message_form = PrivateMessageForm {
- content: private_message.content,
- recipient_id: private_message.recipient_id,
- creator_id: private_message.creator_id,
- deleted: Some(false),
- read: None,
- ap_id: private_message.ap_id,
- local: private_message.local,
- published: None,
- updated: Some(naive_now()),
- };
-
- blocking(pool, move |conn| {
- PrivateMessage::update(conn, private_message_id, &private_message_form)
- })
- .await??;
-
- let message = blocking(pool, move |conn| {
- PrivateMessageView::read(&conn, private_message_id)
- })
- .await??;
-
- let res = PrivateMessageResponse { message };
-
- let recipient_id = res.message.recipient_id;
-
- chat_server.do_send(SendUserRoomMessage {
- op: UserOperation::EditPrivateMessage,
- response: res,
- recipient_id,
- my_id: None,
- });
-
- Ok(HttpResponse::Ok().finish())
-}
// This is for db migrations that require code
use crate::LemmyError;
-use diesel::*;
+use diesel::{
+ sql_types::{Nullable, Text},
+ *,
+};
use lemmy_db::{
comment::Comment,
community::{Community, CommunityForm},
user::{UserForm, User_},
Crud,
};
-use lemmy_utils::{generate_actor_keypair, make_apub_endpoint, EndpointType};
+use lemmy_utils::{
+ generate_actor_keypair,
+ get_apub_protocol_string,
+ make_apub_endpoint,
+ settings::Settings,
+ EndpointType,
+};
use log::info;
pub fn run_advanced_migrations(conn: &PgConnection) -> Result<(), LemmyError> {
post_updates_2020_04_03(&conn)?;
comment_updates_2020_04_03(&conn)?;
private_message_updates_2020_05_05(&conn)?;
+ post_thumbnail_url_updates_2020_07_27(&conn)?;
Ok(())
}
// Update the actor_id, private_key, and public_key, last_refreshed_at
let incorrect_users = user_
- .filter(actor_id.eq("http://fake.com"))
+ .filter(actor_id.like("changeme_%"))
.filter(local.eq(true))
.load::<User_>(conn)?;
let form = UserForm {
name: cuser.name.to_owned(),
- email: cuser.email.to_owned(),
+ email: Some(cuser.email.to_owned()),
matrix_user_id: cuser.matrix_user_id.to_owned(),
- avatar: cuser.avatar.to_owned(),
+ avatar: Some(cuser.avatar.to_owned()),
+ banner: Some(cuser.banner.to_owned()),
password_encrypted: cuser.password_encrypted.to_owned(),
preferred_username: cuser.preferred_username.to_owned(),
updated: None,
// Update the actor_id, private_key, and public_key, last_refreshed_at
let incorrect_communities = community
- .filter(actor_id.eq("http://fake.com"))
+ .filter(actor_id.like("changeme_%"))
.filter(local.eq(true))
.load::<Community>(conn)?;
public_key: Some(keypair.public_key),
last_refreshed_at: Some(naive_now()),
published: None,
+ icon: Some(ccommunity.icon.to_owned()),
+ banner: Some(ccommunity.banner.to_owned()),
};
Community::update(&conn, ccommunity.id, &form)?;
Ok(())
}
+
+fn post_thumbnail_url_updates_2020_07_27(conn: &PgConnection) -> Result<(), LemmyError> {
+ use lemmy_db::schema::post::dsl::*;
+
+ info!("Running post_thumbnail_url_updates_2020_07_27");
+
+ let domain_prefix = format!(
+ "{}://{}/pictrs/image/",
+ get_apub_protocol_string(),
+ Settings::get().hostname
+ );
+
+ let incorrect_thumbnails = post.filter(thumbnail_url.not_like("http%"));
+
+ // Prepend the rows with the update
+ let res = diesel::update(incorrect_thumbnails)
+ .set(
+ thumbnail_url.eq(
+ domain_prefix
+ .into_sql::<Nullable<Text>>()
+ .concat(thumbnail_url),
+ ),
+ )
+ .get_results::<Post>(conn)?;
+
+ info!("{} Post thumbnail_url rows updated.", res.len());
+
+ Ok(())
+}
pub extern crate strum_macros;
#[macro_use]
pub extern crate lazy_static;
-#[macro_use]
-pub extern crate failure;
pub extern crate actix;
pub extern crate actix_web;
+pub extern crate base64;
pub extern crate bcrypt;
+pub extern crate captcha;
pub extern crate chrono;
pub extern crate diesel;
pub extern crate dotenv;
pub mod version;
pub mod websocket;
-use crate::request::{retry, RecvError};
+use crate::{
+ request::{retry, RecvError},
+ websocket::server::ChatServer,
+};
+use actix::Addr;
use actix_web::{client::Client, dev::ConnectionInfo};
+use anyhow::anyhow;
+use lemmy_utils::{get_apub_protocol_string, settings::Settings};
use log::error;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use serde::Deserialize;
+use std::process::Command;
pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
pub type ConnectionId = usize;
#[derive(Debug)]
pub struct LemmyError {
- inner: failure::Error,
+ inner: anyhow::Error,
}
impl<T> From<T> for LemmyError
where
- T: Into<failure::Error>,
+ T: Into<anyhow::Error>,
{
fn from(t: T) -> Self {
LemmyError { inner: t.into() }
impl actix_web::error::ResponseError for LemmyError {}
+pub struct LemmyContext {
+ pub pool: DbPool,
+ pub chat_server: Addr<ChatServer>,
+ pub client: Client,
+}
+
+impl LemmyContext {
+ pub fn create(pool: DbPool, chat_server: Addr<ChatServer>, client: Client) -> LemmyContext {
+ LemmyContext {
+ pool,
+ chat_server,
+ client,
+ }
+ }
+ pub fn pool(&self) -> &DbPool {
+ &self.pool
+ }
+ pub fn chat_server(&self) -> &Addr<ChatServer> {
+ &self.chat_server
+ }
+ pub fn client(&self) -> &Client {
+ &self.client
+ }
+}
+
+impl Clone for LemmyContext {
+ fn clone(&self) -> Self {
+ LemmyContext {
+ pool: self.pool.clone(),
+ chat_server: self.chat_server.clone(),
+ client: self.client.clone(),
+ }
+ }
+}
+
#[derive(Deserialize, Debug)]
pub struct IframelyResponse {
title: Option<String>,
if response.msg == "ok" {
Ok(response)
} else {
- Err(format_err!("{}", &response.msg).into())
+ Err(anyhow!("{}", &response.msg).into())
}
}
};
// Fetch pictrs thumbnail
- let pictrs_thumbnail = match iframely_thumbnail_url {
+ let pictrs_hash = match iframely_thumbnail_url {
Some(iframely_thumbnail_url) => match fetch_pictrs(client, &iframely_thumbnail_url).await {
Ok(res) => Some(res.files[0].file.to_owned()),
Err(e) => {
},
};
+ // The full urls are necessary for federation
+ let pictrs_thumbnail = if let Some(pictrs_hash) = pictrs_hash {
+ Some(format!(
+ "{}://{}/pictrs/image/{}",
+ get_apub_protocol_string(),
+ Settings::get().hostname,
+ pictrs_hash
+ ))
+ } else {
+ None
+ };
+
(
iframely_title,
iframely_description,
if response
.headers()
.get("Content-Type")
- .ok_or_else(|| format_err!("No Content-Type header"))?
+ .ok_or_else(|| anyhow!("No Content-Type header"))?
.to_str()?
.starts_with("image/")
{
Ok(())
} else {
- Err(format_err!("Not an image type.").into())
+ Err(anyhow!("Not an image type.").into())
}
}
Ok(res)
}
+pub fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
+ let mut built_text = String::new();
+
+ // Building proper speech text for espeak
+ for mut c in captcha.chars() {
+ let new_str = if c.is_alphabetic() {
+ if c.is_lowercase() {
+ c.make_ascii_uppercase();
+ format!("lower case {} ... ", c)
+ } else {
+ c.make_ascii_uppercase();
+ format!("capital {} ... ", c)
+ }
+ } else {
+ format!("{} ...", c)
+ };
+
+ built_text.push_str(&new_str);
+ }
+
+ espeak_wav_base64(&built_text)
+}
+
+pub fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
+ // Make a temp file path
+ let uuid = uuid::Uuid::new_v4().to_string();
+ let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid);
+
+ // Write the wav file
+ Command::new("espeak")
+ .arg("-w")
+ .arg(&file_path)
+ .arg(text)
+ .status()?;
+
+ // Read the wav file bytes
+ let bytes = std::fs::read(&file_path)?;
+
+ // Delete the file
+ std::fs::remove_file(file_path)?;
+
+ // Convert to base64
+ let base64 = base64::encode(bytes);
+
+ Ok(base64)
+}
+
#[cfg(test)]
mod tests {
- use crate::is_image_content_type;
+ use crate::{captcha_espeak_wav_base64, is_image_content_type};
#[test]
fn test_image() {
});
}
+ #[test]
+ fn test_espeak() {
+ assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())
+ }
+
// These helped with testing
// #[test]
// fn test_iframely() {
-extern crate lemmy_server;
#[macro_use]
extern crate diesel_migrations;
#[macro_use]
pub extern crate lazy_static;
-pub type DbPool = Pool<ConnectionManager<PgConnection>>;
-
-use crate::lemmy_server::actix_web::dev::Service;
use actix::prelude::*;
use actix_web::{
body::Body,
client::Client,
- dev::{ServiceRequest, ServiceResponse},
+ dev::{Service, ServiceRequest, ServiceResponse},
http::{
header::{CACHE_CONTROL, CONTENT_TYPE},
HeaderValue,
blocking,
code_migrations::run_advanced_migrations,
rate_limit::{rate_limiter::RateLimiter, RateLimit},
- routes::{api, federation, feeds, index, nodeinfo, webfinger},
+ routes::*,
websocket::server::*,
+ LemmyContext,
LemmyError,
};
use lemmy_utils::{settings::Settings, CACHE_CONTROL_REGEX};
rate_limiter: Arc::new(Mutex::new(RateLimiter::default())),
};
- // Set up websocket server
- let server = ChatServer::startup(pool.clone(), rate_limiter.clone(), Client::default()).start();
-
println!(
"Starting http server at {}:{}",
settings.bind, settings.port
);
+ let chat_server =
+ ChatServer::startup(pool.clone(), rate_limiter.clone(), Client::default()).start();
+
// Create Http server with websocket support
HttpServer::new(move || {
+ let context = LemmyContext::create(pool.clone(), chat_server.to_owned(), Client::default());
let settings = Settings::get();
let rate_limiter = rate_limiter.clone();
App::new()
.wrap_fn(add_cache_headers)
.wrap(middleware::Logger::default())
- .data(pool.clone())
- .data(server.clone())
- .data(Client::default())
+ .data(context)
// The routes
- .configure(move |cfg| api::config(cfg, &rate_limiter))
+ .configure(|cfg| api::config(cfg, &rate_limiter))
.configure(federation::config)
.configure(feeds::config)
+ .configure(|cfg| images::config(cfg, &rate_limiter))
.configure(index::config)
.configure(nodeinfo::config)
.configure(webfinger::config)
self.kind(RateLimitType::Register)
}
+ pub fn image(&self) -> RateLimited {
+ self.kind(RateLimitType::Image)
+ }
+
fn kind(&self, type_: RateLimitType) -> RateLimited {
RateLimited {
rate_limiter: self.rate_limiter.clone(),
true,
)?;
}
+ RateLimitType::Image => {
+ limiter.check_rate_limit_full(
+ self.type_,
+ &ip_addr,
+ rate_limit.image,
+ rate_limit.image_per_second,
+ false,
+ )?;
+ }
};
}
Message,
Register,
Post,
+ Image,
}
/// Rate limiting based on rate type and IP addr
use crate::LemmyError;
+use anyhow::anyhow;
use std::future::Future;
+use thiserror::Error;
-#[derive(Clone, Debug, Fail)]
-#[fail(display = "Error sending request, {}", _0)]
+#[derive(Clone, Debug, Error)]
+#[error("Error sending request, {0}")]
struct SendError(pub String);
-#[derive(Clone, Debug, Fail)]
-#[fail(display = "Error receiving response, {}", _0)]
+#[derive(Clone, Debug, Error)]
+#[error("Error receiving response, {0}")]
pub struct RecvError(pub String);
pub async fn retry<F, Fut, T>(f: F) -> Result<T, LemmyError>
F: Fn() -> Fut,
Fut: Future<Output = Result<Result<T, actix_web::client::SendRequestError>, LemmyError>>,
{
- let mut response = Err(format_err!("connect timeout").into());
+ let mut response = Err(anyhow!("connect timeout").into());
for _ in 0u8..3 {
match (f)().await? {
use crate::{
- api::{comment::*, community::*, post::*, site::*, user::*, Oper, Perform},
+ api::{comment::*, community::*, post::*, site::*, user::*, Perform},
rate_limit::RateLimit,
- routes::{ChatServerParam, DbPoolParam},
- websocket::WebsocketInfo,
+ LemmyContext,
};
-use actix_web::{client::Client, error::ErrorBadRequest, *};
+use actix_web::{error::ErrorBadRequest, *};
use serde::Serialize;
pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
.route("", web::put().to(route_post::<EditCommunity>))
.route("/list", web::get().to(route_get::<ListCommunities>))
.route("/follow", web::post().to(route_post::<FollowCommunity>))
+ .route("/delete", web::post().to(route_post::<DeleteCommunity>))
// Mod Actions
+ .route("/remove", web::post().to(route_post::<RemoveCommunity>))
.route("/transfer", web::post().to(route_post::<TransferCommunity>))
.route("/ban_user", web::post().to(route_post::<BanFromCommunity>))
.route("/mod", web::post().to(route_post::<AddModToCommunity>)),
.wrap(rate_limit.message())
.route("", web::get().to(route_get::<GetPost>))
.route("", web::put().to(route_post::<EditPost>))
+ .route("/delete", web::post().to(route_post::<DeletePost>))
+ .route("/remove", web::post().to(route_post::<RemovePost>))
+ .route("/lock", web::post().to(route_post::<LockPost>))
+ .route("/sticky", web::post().to(route_post::<StickyPost>))
.route("/list", web::get().to(route_get::<GetPosts>))
.route("/like", web::post().to(route_post::<CreatePostLike>))
.route("/save", web::put().to(route_post::<SavePost>)),
.wrap(rate_limit.message())
.route("", web::post().to(route_post::<CreateComment>))
.route("", web::put().to(route_post::<EditComment>))
+ .route("/delete", web::post().to(route_post::<DeleteComment>))
+ .route("/remove", web::post().to(route_post::<RemoveComment>))
+ .route(
+ "/mark_as_read",
+ web::post().to(route_post::<MarkCommentAsRead>),
+ )
.route("/like", web::post().to(route_post::<CreateCommentLike>))
- .route("/save", web::put().to(route_post::<SaveComment>)),
+ .route("/save", web::put().to(route_post::<SaveComment>))
+ .route("/list", web::get().to(route_get::<GetComments>)),
)
// Private Message
.service(
.wrap(rate_limit.message())
.route("/list", web::get().to(route_get::<GetPrivateMessages>))
.route("", web::post().to(route_post::<CreatePrivateMessage>))
- .route("", web::put().to(route_post::<EditPrivateMessage>)),
+ .route("", web::put().to(route_post::<EditPrivateMessage>))
+ .route(
+ "/delete",
+ web::post().to(route_post::<DeletePrivateMessage>),
+ )
+ .route(
+ "/mark_as_read",
+ web::post().to(route_post::<MarkPrivateMessageAsRead>),
+ ),
)
// User
.service(
.wrap(rate_limit.message())
.route("", web::get().to(route_get::<GetUserDetails>))
.route("/mention", web::get().to(route_get::<GetUserMentions>))
- .route("/mention", web::put().to(route_post::<EditUserMention>))
+ .route(
+ "/mention/mark_as_read",
+ web::post().to(route_post::<MarkUserMentionAsRead>),
+ )
.route("/replies", web::get().to(route_get::<GetReplies>))
.route(
"/followed_communities",
web::get().to(route_get::<GetFollowedCommunities>),
)
+ .route("/join", web::post().to(route_post::<UserJoin>))
// Admin action. I don't like that it's in /user
.route("/ban", web::post().to(route_post::<BanUser>))
// Account actions. I don't like that they're in /user maybe /accounts
.route("/login", web::post().to(route_post::<Login>))
+ .route("/get_captcha", web::get().to(route_post::<GetCaptcha>))
.route(
"/delete_account",
web::post().to(route_post::<DeleteAccount>),
async fn perform<Request>(
data: Request,
- client: &Client,
- db: DbPoolParam,
- chat_server: ChatServerParam,
+ context: web::Data<LemmyContext>,
) -> Result<HttpResponse, Error>
where
- Oper<Request>: Perform,
+ Request: Perform,
Request: Send + 'static,
{
- let ws_info = WebsocketInfo {
- chatserver: chat_server.get_ref().to_owned(),
- id: None,
- };
-
- let oper: Oper<Request> = Oper::new(data, client.clone());
-
- let res = oper
- .perform(&db, Some(ws_info))
+ let res = data
+ .perform(&context, None)
.await
.map(|json| HttpResponse::Ok().json(json))
.map_err(ErrorBadRequest)?;
async fn route_get<Data>(
data: web::Query<Data>,
- client: web::Data<Client>,
- db: DbPoolParam,
- chat_server: ChatServerParam,
+ context: web::Data<LemmyContext>,
) -> Result<HttpResponse, Error>
where
- Data: Serialize + Send + 'static,
- Oper<Data>: Perform,
+ Data: Serialize + Send + 'static + Perform,
{
- perform::<Data>(data.0, &client, db, chat_server).await
+ perform::<Data>(data.0, context).await
}
async fn route_post<Data>(
data: web::Json<Data>,
- client: web::Data<Client>,
- db: DbPoolParam,
- chat_server: ChatServerParam,
+ context: web::Data<LemmyContext>,
) -> Result<HttpResponse, Error>
where
- Data: Serialize + Send + 'static,
- Oper<Data>: Perform,
+ Data: Serialize + Send + 'static + Perform,
{
- perform::<Data>(data.0, &client, db, chat_server).await
+ perform::<Data>(data.0, context).await
}
use crate::apub::{
comment::get_apub_comment,
community::*,
- community_inbox::community_inbox,
+ inbox::{community_inbox::community_inbox, shared_inbox::shared_inbox, user_inbox::user_inbox},
post::get_apub_post,
- shared_inbox::shared_inbox,
user::*,
- user_inbox::user_inbox,
APUB_JSON_CONTENT_TYPE,
};
use actix_web::*;
"/c/{community_name}/followers",
web::get().to(get_apub_community_followers),
)
- // TODO This is only useful for history which we aren't doing right now
- // .route(
- // "/c/{community_name}/outbox",
- // web::get().to(get_apub_community_outbox),
- // )
+ .route(
+ "/c/{community_name}/outbox",
+ web::get().to(get_apub_community_outbox),
+ )
.route("/u/{user_name}", web::get().to(get_apub_user_http))
.route("/post/{post_id}", web::get().to(get_apub_post))
.route("/comment/{comment_id}", web::get().to(get_apub_comment)),
-use crate::{api::claims::Claims, blocking, routes::DbPoolParam, LemmyError};
+use crate::{api::claims::Claims, blocking, LemmyContext, LemmyError};
use actix_web::{error::ErrorBadRequest, *};
+use anyhow::anyhow;
use chrono::{DateTime, NaiveDateTime, Utc};
-use diesel::{
- r2d2::{ConnectionManager, Pool},
- PgConnection,
-};
+use diesel::PgConnection;
use lemmy_db::{
comment_view::{ReplyQueryBuilder, ReplyView},
community::Community,
.route("/feeds/all.xml", web::get().to(get_all_feed));
}
-async fn get_all_feed(info: web::Query<Params>, db: DbPoolParam) -> Result<HttpResponse, Error> {
+async fn get_all_feed(
+ info: web::Query<Params>,
+ context: web::Data<LemmyContext>,
+) -> Result<HttpResponse, Error> {
let sort_type = get_sort_type(info).map_err(ErrorBadRequest)?;
- let rss = blocking(&db, move |conn| get_feed_all_data(conn, &sort_type))
- .await?
- .map_err(ErrorBadRequest)?;
+ let rss = blocking(context.pool(), move |conn| {
+ get_feed_all_data(conn, &sort_type)
+ })
+ .await?
+ .map_err(ErrorBadRequest)?;
Ok(
HttpResponse::Ok()
.sort(sort_type)
.list()?;
- let items = create_post_items(posts);
+ let items = create_post_items(posts)?;
let mut channel_builder = ChannelBuilder::default();
channel_builder
channel_builder.description(&site_desc);
}
- Ok(channel_builder.build().unwrap().to_string())
+ Ok(channel_builder.build().map_err(|e| anyhow!(e))?.to_string())
}
async fn get_feed(
path: web::Path<(String, String)>,
info: web::Query<Params>,
- db: web::Data<Pool<ConnectionManager<PgConnection>>>,
+ context: web::Data<LemmyContext>,
) -> Result<HttpResponse, Error> {
let sort_type = get_sort_type(info).map_err(ErrorBadRequest)?;
"c" => RequestType::Community,
"front" => RequestType::Front,
"inbox" => RequestType::Inbox,
- _ => return Err(ErrorBadRequest(LemmyError::from(format_err!("wrong_type")))),
+ _ => return Err(ErrorBadRequest(LemmyError::from(anyhow!("wrong_type")))),
};
let param = path.1.to_owned();
- let builder = blocking(&db, move |conn| match request_type {
+ let builder = blocking(context.pool(), move |conn| match request_type {
RequestType::User => get_feed_user(conn, &sort_type, param),
RequestType::Community => get_feed_community(conn, &sort_type, param),
RequestType::Front => get_feed_front(conn, &sort_type, param),
.for_creator_id(user.id)
.list()?;
- let items = create_post_items(posts);
+ let items = create_post_items(posts)?;
let mut channel_builder = ChannelBuilder::default();
channel_builder
.for_community_id(community.id)
.list()?;
- let items = create_post_items(posts);
+ let items = create_post_items(posts)?;
let mut channel_builder = ChannelBuilder::default();
channel_builder
.my_user_id(user_id)
.list()?;
- let items = create_post_items(posts);
+ let items = create_post_items(posts)?;
let mut channel_builder = ChannelBuilder::default();
channel_builder
.sort(&sort)
.list()?;
- let items = create_reply_and_mention_items(replies, mentions);
+ let items = create_reply_and_mention_items(replies, mentions)?;
let mut channel_builder = ChannelBuilder::default();
channel_builder
fn create_reply_and_mention_items(
replies: Vec<ReplyView>,
mentions: Vec<UserMentionView>,
-) -> Vec<Item> {
+) -> Result<Vec<Item>, LemmyError> {
let mut reply_items: Vec<Item> = replies
.iter()
.map(|r| {
);
build_item(&r.creator_name, &r.published, &reply_url, &r.content)
})
- .collect();
+ .collect::<Result<Vec<Item>, LemmyError>>()?;
let mut mention_items: Vec<Item> = mentions
.iter()
);
build_item(&m.creator_name, &m.published, &mention_url, &m.content)
})
- .collect();
+ .collect::<Result<Vec<Item>, LemmyError>>()?;
reply_items.append(&mut mention_items);
- reply_items
+ Ok(reply_items)
}
-fn build_item(creator_name: &str, published: &NaiveDateTime, url: &str, content: &str) -> Item {
+fn build_item(
+ creator_name: &str,
+ published: &NaiveDateTime,
+ url: &str,
+ content: &str,
+) -> Result<Item, LemmyError> {
let mut i = ItemBuilder::default();
i.title(format!("Reply from {}", creator_name));
let author_url = format!("https://{}/u/{}", Settings::get().hostname, creator_name);
let dt = DateTime::<Utc>::from_utc(*published, Utc);
i.pub_date(dt.to_rfc2822());
i.comments(url.to_owned());
- let guid = GuidBuilder::default().permalink(true).value(url).build();
- i.guid(guid.unwrap());
+ let guid = GuidBuilder::default()
+ .permalink(true)
+ .value(url)
+ .build()
+ .map_err(|e| anyhow!(e))?;
+ i.guid(guid);
i.link(url.to_owned());
// TODO add images
let html = markdown_to_html(&content.to_string());
i.description(html);
- i.build().unwrap()
+ Ok(i.build().map_err(|e| anyhow!(e))?)
}
-fn create_post_items(posts: Vec<PostView>) -> Vec<Item> {
+fn create_post_items(posts: Vec<PostView>) -> Result<Vec<Item>, LemmyError> {
let mut items: Vec<Item> = Vec::new();
for p in posts {
let guid = GuidBuilder::default()
.permalink(true)
.value(&post_url)
- .build();
- i.guid(guid.unwrap());
+ .build()
+ .map_err(|e| anyhow!(e))?;
+ i.guid(guid);
let community_url = format!(
"https://{}/c/{}",
p.community_name, community_url
))
.domain(Settings::get().hostname.to_owned())
- .build();
- i.categories(vec![category.unwrap()]);
+ .build()
+ .map_err(|e| anyhow!(e))?;
+
+ i.categories(vec![category]);
if let Some(url) = p.url {
i.link(url);
i.description(description);
- items.push(i.build().unwrap());
+ items.push(i.build().map_err(|e| anyhow!(e))?);
}
- items
+ Ok(items)
}
--- /dev/null
+use crate::rate_limit::RateLimit;
+use actix::clock::Duration;
+use actix_web::{body::BodyStream, http::StatusCode, *};
+use awc::Client;
+use lemmy_utils::settings::Settings;
+use serde::{Deserialize, Serialize};
+
+pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
+ let client = Client::build()
+ .header("User-Agent", "pict-rs-frontend, v0.1.0")
+ .timeout(Duration::from_secs(30))
+ .finish();
+
+ cfg
+ .data(client)
+ .service(
+ web::resource("/pictrs/image")
+ .wrap(rate_limit.image())
+ .route(web::post().to(upload)),
+ )
+ .service(web::resource("/pictrs/image/{filename}").route(web::get().to(full_res)))
+ .service(
+ web::resource("/pictrs/image/thumbnail{size}/{filename}").route(web::get().to(thumbnail)),
+ )
+ .service(web::resource("/pictrs/image/delete/{token}/{filename}").route(web::get().to(delete)));
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Image {
+ file: String,
+ delete_token: String,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Images {
+ msg: String,
+ files: Option<Vec<Image>>,
+}
+
+async fn upload(
+ req: HttpRequest,
+ body: web::Payload,
+ client: web::Data<Client>,
+) -> Result<HttpResponse, Error> {
+ // TODO: check auth and rate limit here
+
+ let mut res = client
+ .request_from(format!("{}/image", Settings::get().pictrs_url), req.head())
+ .if_some(req.head().peer_addr, |addr, req| {
+ req.header("X-Forwarded-For", addr.to_string())
+ })
+ .send_stream(body)
+ .await?;
+
+ let images = res.json::<Images>().await?;
+
+ Ok(HttpResponse::build(res.status()).json(images))
+}
+
+async fn full_res(
+ filename: web::Path<String>,
+ req: HttpRequest,
+ client: web::Data<Client>,
+) -> Result<HttpResponse, Error> {
+ let url = format!(
+ "{}/image/{}",
+ Settings::get().pictrs_url,
+ &filename.into_inner()
+ );
+ image(url, req, client).await
+}
+
+async fn thumbnail(
+ parts: web::Path<(u64, String)>,
+ req: HttpRequest,
+ client: web::Data<Client>,
+) -> Result<HttpResponse, Error> {
+ let (size, file) = parts.into_inner();
+
+ let url = format!(
+ "{}/image/thumbnail{}/{}",
+ Settings::get().pictrs_url,
+ size,
+ &file
+ );
+
+ image(url, req, client).await
+}
+
+async fn image(
+ url: String,
+ req: HttpRequest,
+ client: web::Data<Client>,
+) -> Result<HttpResponse, Error> {
+ let res = client
+ .request_from(url, req.head())
+ .if_some(req.head().peer_addr, |addr, req| {
+ req.header("X-Forwarded-For", addr.to_string())
+ })
+ .no_decompress()
+ .send()
+ .await?;
+
+ if res.status() == StatusCode::NOT_FOUND {
+ return Ok(HttpResponse::NotFound().finish());
+ }
+
+ let mut client_res = HttpResponse::build(res.status());
+
+ for (name, value) in res.headers().iter().filter(|(h, _)| *h != "connection") {
+ client_res.header(name.clone(), value.clone());
+ }
+
+ Ok(client_res.body(BodyStream::new(res)))
+}
+
+async fn delete(
+ components: web::Path<(String, String)>,
+ req: HttpRequest,
+ client: web::Data<Client>,
+) -> Result<HttpResponse, Error> {
+ let (token, file) = components.into_inner();
+
+ let url = format!(
+ "{}/image/delete/{}/{}",
+ Settings::get().pictrs_url,
+ &token,
+ &file
+ );
+ let res = client
+ .request_from(url, req.head())
+ .if_some(req.head().peer_addr, |addr, req| {
+ req.header("X-Forwarded-For", addr.to_string())
+ })
+ .no_decompress()
+ .send()
+ .await?;
+
+ Ok(HttpResponse::build(res.status()).body(BodyStream::new(res)))
+}
)
.route("/search", web::get().to(index))
.route("/sponsors", web::get().to(index))
- .route("/password_change/{token}", web::get().to(index));
+ .route("/password_change/{token}", web::get().to(index))
+ .route("/instances", web::get().to(index));
}
async fn index() -> Result<NamedFile, Error> {
pub mod api;
pub mod federation;
pub mod feeds;
+pub mod images;
pub mod index;
pub mod nodeinfo;
pub mod webfinger;
pub mod websocket;
-
-use crate::{rate_limit::rate_limiter::RateLimiter, websocket::server::ChatServer};
-use actix::prelude::*;
-use actix_web::*;
-use diesel::{
- r2d2::{ConnectionManager, Pool},
- PgConnection,
-};
-use std::sync::{Arc, Mutex};
-
-pub type DbPoolParam = web::Data<Pool<ConnectionManager<PgConnection>>>;
-pub type RateLimitParam = web::Data<Arc<Mutex<RateLimiter>>>;
-pub type ChatServerParam = web::Data<Addr<ChatServer>>;
-use crate::{blocking, routes::DbPoolParam, version, LemmyError};
+use crate::{blocking, version, LemmyContext, LemmyError};
use actix_web::{body::Body, error::ErrorBadRequest, *};
+use anyhow::anyhow;
use lemmy_db::site_view::SiteView;
use lemmy_utils::{get_apub_protocol_string, settings::Settings};
use serde::{Deserialize, Serialize};
Ok(HttpResponse::Ok().json(node_info))
}
-async fn node_info(db: DbPoolParam) -> Result<HttpResponse, Error> {
- let site_view = blocking(&db, SiteView::read)
+async fn node_info(context: web::Data<LemmyContext>) -> Result<HttpResponse, Error> {
+ let site_view = blocking(context.pool(), SiteView::read)
.await?
- .map_err(|_| ErrorBadRequest(LemmyError::from(format_err!("not_found"))))?;
+ .map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?;
let protocols = if Settings::get().federation.enabled {
vec!["activitypub".to_string()]
-use crate::{blocking, routes::DbPoolParam, LemmyError};
+use crate::{blocking, LemmyContext, LemmyError};
use actix_web::{error::ErrorBadRequest, web::Query, *};
+use anyhow::anyhow;
use lemmy_db::{community::Community, user::User_};
use lemmy_utils::{settings::Settings, WEBFINGER_COMMUNITY_REGEX, WEBFINGER_USER_REGEX};
use serde::{Deserialize, Serialize};
/// https://radical.town/.well-known/webfinger?resource=acct:felix@radical.town
async fn get_webfinger_response(
info: Query<Params>,
- db: DbPoolParam,
+ context: web::Data<LemmyContext>,
) -> Result<HttpResponse, Error> {
let community_regex_parsed = WEBFINGER_COMMUNITY_REGEX
.captures(&info.resource)
let url = if let Some(community_name) = community_regex_parsed {
let community_name = community_name.as_str().to_owned();
// Make sure the requested community exists.
- blocking(&db, move |conn| {
+ blocking(context.pool(), move |conn| {
Community::read_from_name(conn, &community_name)
})
.await?
- .map_err(|_| ErrorBadRequest(LemmyError::from(format_err!("not_found"))))?
+ .map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?
.actor_id
} else if let Some(user_name) = user_regex_parsed {
let user_name = user_name.as_str().to_owned();
// Make sure the requested user exists.
- blocking(&db, move |conn| User_::read_from_name(conn, &user_name))
- .await?
- .map_err(|_| ErrorBadRequest(LemmyError::from(format_err!("not_found"))))?
- .actor_id
+ blocking(context.pool(), move |conn| {
+ User_::read_from_name(conn, &user_name)
+ })
+ .await?
+ .map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?
+ .actor_id
} else {
- return Err(ErrorBadRequest(LemmyError::from(format_err!("not_found"))));
+ return Err(ErrorBadRequest(LemmyError::from(anyhow!("not_found"))));
};
let json = WebFingerResponse {
use crate::{
get_ip,
websocket::server::{ChatServer, *},
+ LemmyContext,
};
use actix::prelude::*;
use actix_web::*;
pub async fn chat_route(
req: HttpRequest,
stream: web::Payload,
- chat_server: web::Data<Addr<ChatServer>>,
+ context: web::Data<LemmyContext>,
) -> Result<HttpResponse, Error> {
ws::start(
WSSession {
- cs_addr: chat_server.get_ref().to_owned(),
+ cs_addr: context.chat_server().to_owned(),
id: 0,
hb: Instant::now(),
ip: get_ip(&req.connection_info()),
-pub const VERSION: &str = "v0.7.21";
+pub const VERSION: &str = "v0.7.55";
pub mod server;
-use crate::ConnectionId;
use actix::prelude::*;
use diesel::{
r2d2::{ConnectionManager, Pool},
use rand::{rngs::ThreadRng, Rng};
use serde::{Deserialize, Serialize};
use serde_json::Value;
-use server::ChatServer;
use std::{
collections::{HashMap, HashSet},
str::FromStr,
pub enum UserOperation {
Login,
Register,
+ GetCaptcha,
CreateCommunity,
CreatePost,
ListCommunities,
GetCommunity,
CreateComment,
EditComment,
+ DeleteComment,
+ RemoveComment,
+ MarkCommentAsRead,
SaveComment,
CreateCommentLike,
GetPosts,
CreatePostLike,
EditPost,
+ DeletePost,
+ RemovePost,
+ LockPost,
+ StickyPost,
SavePost,
EditCommunity,
+ DeleteCommunity,
+ RemoveCommunity,
FollowCommunity,
GetFollowedCommunities,
GetUserDetails,
GetReplies,
GetUserMentions,
- EditUserMention,
+ MarkUserMentionAsRead,
GetModlog,
BanFromCommunity,
AddModToCommunity,
PasswordChange,
CreatePrivateMessage,
EditPrivateMessage,
+ DeletePrivateMessage,
+ MarkPrivateMessageAsRead,
GetPrivateMessages,
UserJoin,
GetComments,
GetSiteConfig,
SaveSiteConfig,
}
-
-#[derive(Clone)]
-pub struct WebsocketInfo {
- pub chatserver: Addr<ChatServer>,
- pub id: Option<ConnectionId>,
-}
websocket::UserOperation,
CommunityId,
ConnectionId,
- DbPool,
IPAddr,
+ LemmyContext,
LemmyError,
PostId,
UserId,
};
-use actix_web::client::Client;
+use actix_web::{client::Client, web};
+use anyhow::Context as acontext;
+use lemmy_db::naive_now;
+use lemmy_utils::location_info;
/// Chat server sends this messages to session
#[derive(Message)]
pub struct SendAllMessage<Response> {
pub op: UserOperation,
pub response: Response,
- pub my_id: Option<ConnectionId>,
+ pub websocket_id: Option<ConnectionId>,
}
#[derive(Message)]
pub op: UserOperation,
pub response: Response,
pub recipient_id: UserId,
- pub my_id: Option<ConnectionId>,
+ pub websocket_id: Option<ConnectionId>,
}
#[derive(Message)]
pub op: UserOperation,
pub response: Response,
pub community_id: CommunityId,
- pub my_id: Option<ConnectionId>,
+ pub websocket_id: Option<ConnectionId>,
}
#[derive(Message)]
pub struct SendPost {
pub op: UserOperation,
pub post: PostResponse,
- pub my_id: Option<ConnectionId>,
+ pub websocket_id: Option<ConnectionId>,
}
#[derive(Message)]
pub struct SendComment {
pub op: UserOperation,
pub comment: CommentResponse,
- pub my_id: Option<ConnectionId>,
+ pub websocket_id: Option<ConnectionId>,
}
#[derive(Message)]
pub ip: IPAddr,
}
+#[derive(Message, Debug)]
+#[rtype(result = "()")]
+pub struct CaptchaItem {
+ pub uuid: String,
+ pub answer: String,
+ pub expires: chrono::NaiveDateTime,
+}
+
+#[derive(Message)]
+#[rtype(bool)]
+pub struct CheckCaptcha {
+ pub uuid: String,
+ pub answer: String,
+}
+
/// `ChatServer` manages chat rooms and responsible for coordinating chat
/// session.
pub struct ChatServer {
/// Rate limiting based on rate type and IP addr
rate_limiter: RateLimit,
+ /// A list of the current captchas
+ captchas: Vec<CaptchaItem>,
+
/// An HTTP Client
client: Client,
}
rng: rand::thread_rng(),
pool,
rate_limiter,
+ captchas: Vec::new(),
client,
}
}
- pub fn join_community_room(&mut self, community_id: CommunityId, id: ConnectionId) {
+ pub fn join_community_room(
+ &mut self,
+ community_id: CommunityId,
+ id: ConnectionId,
+ ) -> Result<(), LemmyError> {
// remove session from all rooms
for sessions in self.community_rooms.values_mut() {
sessions.remove(&id);
self
.community_rooms
.get_mut(&community_id)
- .unwrap()
+ .context(location_info!())?
.insert(id);
+ Ok(())
}
- pub fn join_post_room(&mut self, post_id: PostId, id: ConnectionId) {
+ pub fn join_post_room(&mut self, post_id: PostId, id: ConnectionId) -> Result<(), LemmyError> {
// remove session from all rooms
for sessions in self.post_rooms.values_mut() {
sessions.remove(&id);
// Also leave all communities
// This avoids double messages
+ // TODO found a bug, whereby community messages like
+ // delete and remove aren't sent, because
+ // you left the community room
for sessions in self.community_rooms.values_mut() {
sessions.remove(&id);
}
self.post_rooms.insert(post_id, HashSet::new());
}
- self.post_rooms.get_mut(&post_id).unwrap().insert(id);
+ self
+ .post_rooms
+ .get_mut(&post_id)
+ .context(location_info!())?
+ .insert(id);
+
+ Ok(())
}
- pub fn join_user_room(&mut self, user_id: UserId, id: ConnectionId) {
+ pub fn join_user_room(&mut self, user_id: UserId, id: ConnectionId) -> Result<(), LemmyError> {
// remove session from all rooms
for sessions in self.user_rooms.values_mut() {
sessions.remove(&id);
self.user_rooms.insert(user_id, HashSet::new());
}
- self.user_rooms.get_mut(&user_id).unwrap().insert(id);
+ self
+ .user_rooms
+ .get_mut(&user_id)
+ .context(location_info!())?
+ .insert(id);
+
+ Ok(())
}
fn send_post_room_message<Response>(
op: &UserOperation,
response: &Response,
post_id: PostId,
- my_id: Option<ConnectionId>,
+ websocket_id: Option<ConnectionId>,
) -> Result<(), LemmyError>
where
Response: Serialize,
let res_str = &to_json_string(op, response)?;
if let Some(sessions) = self.post_rooms.get(&post_id) {
for id in sessions {
- if let Some(my_id) = my_id {
+ if let Some(my_id) = websocket_id {
if *id == my_id {
continue;
}
op: &UserOperation,
response: &Response,
community_id: CommunityId,
- my_id: Option<ConnectionId>,
+ websocket_id: Option<ConnectionId>,
) -> Result<(), LemmyError>
where
Response: Serialize,
let res_str = &to_json_string(op, response)?;
if let Some(sessions) = self.community_rooms.get(&community_id) {
for id in sessions {
- if let Some(my_id) = my_id {
+ if let Some(my_id) = websocket_id {
if *id == my_id {
continue;
}
&self,
op: &UserOperation,
response: &Response,
- my_id: Option<ConnectionId>,
+ websocket_id: Option<ConnectionId>,
) -> Result<(), LemmyError>
where
Response: Serialize,
{
let res_str = &to_json_string(op, response)?;
for id in self.sessions.keys() {
- if let Some(my_id) = my_id {
+ if let Some(my_id) = websocket_id {
if *id == my_id {
continue;
}
op: &UserOperation,
response: &Response,
recipient_id: UserId,
- my_id: Option<ConnectionId>,
+ websocket_id: Option<ConnectionId>,
) -> Result<(), LemmyError>
where
Response: Serialize,
let res_str = &to_json_string(op, response)?;
if let Some(sessions) = self.user_rooms.get(&recipient_id) {
for id in sessions {
- if let Some(my_id) = my_id {
+ if let Some(my_id) = websocket_id {
if *id == my_id {
continue;
}
&self,
user_operation: &UserOperation,
comment: &CommentResponse,
- my_id: Option<ConnectionId>,
+ websocket_id: Option<ConnectionId>,
) -> Result<(), LemmyError> {
let mut comment_reply_sent = comment.clone();
comment_reply_sent.comment.my_vote = None;
user_operation,
&comment_post_sent,
comment_post_sent.comment.post_id,
- my_id,
+ websocket_id,
)?;
// Send it to the recipient(s) including the mentioned users
for recipient_id in &comment_reply_sent.recipient_ids {
- self.send_user_room_message(user_operation, &comment_reply_sent, *recipient_id, my_id)?;
+ self.send_user_room_message(
+ user_operation,
+ &comment_reply_sent,
+ *recipient_id,
+ websocket_id,
+ )?;
}
// Send it to the community too
- self.send_community_room_message(user_operation, &comment_post_sent, 0, my_id)?;
+ self.send_community_room_message(user_operation, &comment_post_sent, 0, websocket_id)?;
self.send_community_room_message(
user_operation,
&comment_post_sent,
comment.comment.community_id,
- my_id,
+ websocket_id,
)?;
Ok(())
&self,
user_operation: &UserOperation,
post: &PostResponse,
- my_id: Option<ConnectionId>,
+ websocket_id: Option<ConnectionId>,
) -> Result<(), LemmyError> {
let community_id = post.post.community_id;
post_sent.post.user_id = None;
// Send it to /c/all and that community
- self.send_community_room_message(user_operation, &post_sent, 0, my_id)?;
- self.send_community_room_message(user_operation, &post_sent, community_id, my_id)?;
+ self.send_community_room_message(user_operation, &post_sent, 0, websocket_id)?;
+ self.send_community_room_message(user_operation, &post_sent, community_id, websocket_id)?;
// Send it to the post room
- self.send_post_room_message(user_operation, &post_sent, post.post.id, my_id)?;
+ self.send_post_room_message(user_operation, &post_sent, post.post.id, websocket_id)?;
Ok(())
}
let user_operation: UserOperation = UserOperation::from_str(&op)?;
- let args = Args {
- client,
+ let context = LemmyContext {
pool,
+ chat_server: addr,
+ client,
+ };
+ let args = Args {
+ context,
rate_limiter,
- chatserver: addr,
id: msg.id,
ip,
op: user_operation.clone(),
// User ops
UserOperation::Login => do_user_operation::<Login>(args).await,
UserOperation::Register => do_user_operation::<Register>(args).await,
+ UserOperation::GetCaptcha => do_user_operation::<GetCaptcha>(args).await,
UserOperation::GetUserDetails => do_user_operation::<GetUserDetails>(args).await,
UserOperation::GetReplies => do_user_operation::<GetReplies>(args).await,
UserOperation::AddAdmin => do_user_operation::<AddAdmin>(args).await,
UserOperation::BanUser => do_user_operation::<BanUser>(args).await,
UserOperation::GetUserMentions => do_user_operation::<GetUserMentions>(args).await,
- UserOperation::EditUserMention => do_user_operation::<EditUserMention>(args).await,
+ UserOperation::MarkUserMentionAsRead => {
+ do_user_operation::<MarkUserMentionAsRead>(args).await
+ }
UserOperation::MarkAllAsRead => do_user_operation::<MarkAllAsRead>(args).await,
UserOperation::DeleteAccount => do_user_operation::<DeleteAccount>(args).await,
UserOperation::PasswordReset => do_user_operation::<PasswordReset>(args).await,
UserOperation::PasswordChange => do_user_operation::<PasswordChange>(args).await,
+ UserOperation::UserJoin => do_user_operation::<UserJoin>(args).await,
+ UserOperation::SaveUserSettings => do_user_operation::<SaveUserSettings>(args).await,
+
+ // Private Message ops
UserOperation::CreatePrivateMessage => {
do_user_operation::<CreatePrivateMessage>(args).await
}
UserOperation::EditPrivateMessage => do_user_operation::<EditPrivateMessage>(args).await,
+ UserOperation::DeletePrivateMessage => {
+ do_user_operation::<DeletePrivateMessage>(args).await
+ }
+ UserOperation::MarkPrivateMessageAsRead => {
+ do_user_operation::<MarkPrivateMessageAsRead>(args).await
+ }
UserOperation::GetPrivateMessages => do_user_operation::<GetPrivateMessages>(args).await,
- UserOperation::UserJoin => do_user_operation::<UserJoin>(args).await,
- UserOperation::SaveUserSettings => do_user_operation::<SaveUserSettings>(args).await,
// Site ops
UserOperation::GetModlog => do_user_operation::<GetModlog>(args).await,
UserOperation::ListCommunities => do_user_operation::<ListCommunities>(args).await,
UserOperation::CreateCommunity => do_user_operation::<CreateCommunity>(args).await,
UserOperation::EditCommunity => do_user_operation::<EditCommunity>(args).await,
+ UserOperation::DeleteCommunity => do_user_operation::<DeleteCommunity>(args).await,
+ UserOperation::RemoveCommunity => do_user_operation::<RemoveCommunity>(args).await,
UserOperation::FollowCommunity => do_user_operation::<FollowCommunity>(args).await,
UserOperation::GetFollowedCommunities => {
do_user_operation::<GetFollowedCommunities>(args).await
UserOperation::GetPost => do_user_operation::<GetPost>(args).await,
UserOperation::GetPosts => do_user_operation::<GetPosts>(args).await,
UserOperation::EditPost => do_user_operation::<EditPost>(args).await,
+ UserOperation::DeletePost => do_user_operation::<DeletePost>(args).await,
+ UserOperation::RemovePost => do_user_operation::<RemovePost>(args).await,
+ UserOperation::LockPost => do_user_operation::<LockPost>(args).await,
+ UserOperation::StickyPost => do_user_operation::<StickyPost>(args).await,
UserOperation::CreatePostLike => do_user_operation::<CreatePostLike>(args).await,
UserOperation::SavePost => do_user_operation::<SavePost>(args).await,
// Comment ops
UserOperation::CreateComment => do_user_operation::<CreateComment>(args).await,
UserOperation::EditComment => do_user_operation::<EditComment>(args).await,
+ UserOperation::DeleteComment => do_user_operation::<DeleteComment>(args).await,
+ UserOperation::RemoveComment => do_user_operation::<RemoveComment>(args).await,
+ UserOperation::MarkCommentAsRead => do_user_operation::<MarkCommentAsRead>(args).await,
UserOperation::SaveComment => do_user_operation::<SaveComment>(args).await,
UserOperation::GetComments => do_user_operation::<GetComments>(args).await,
UserOperation::CreateCommentLike => do_user_operation::<CreateCommentLike>(args).await,
}
struct Args<'a> {
- client: Client,
- pool: DbPool,
+ context: LemmyContext,
rate_limiter: RateLimit,
- chatserver: Addr<ChatServer>,
id: ConnectionId,
ip: IPAddr,
op: UserOperation,
async fn do_user_operation<'a, 'b, Data>(args: Args<'b>) -> Result<String, LemmyError>
where
for<'de> Data: Deserialize<'de> + 'a,
- Oper<Data>: Perform,
+ Data: Perform,
{
let Args {
- client,
- pool,
+ context,
rate_limiter,
- chatserver,
id,
ip,
op,
data,
} = args;
- let ws_info = WebsocketInfo {
- chatserver,
- id: Some(id),
- };
-
let data = data.to_string();
let op2 = op.clone();
- let client = client.clone();
let fut = async move {
- let pool = pool.clone();
let parsed_data: Data = serde_json::from_str(&data)?;
- let res = Oper::new(parsed_data, client)
- .perform(&pool, Some(ws_info))
+ let res = parsed_data
+ .perform(&web::Data::new(context), Some(id))
.await?;
to_json_string(&op, &res)
};
Box::pin(async move {
match fut.await {
Ok(m) => {
- info!("Message Sent: {}", m);
+ // info!("Message Sent: {}", m);
Ok(m)
}
Err(e) => {
fn handle(&mut self, msg: SendAllMessage<Response>, _: &mut Context<Self>) {
self
- .send_all_message(&msg.op, &msg.response, msg.my_id)
- .unwrap();
+ .send_all_message(&msg.op, &msg.response, msg.websocket_id)
+ .ok();
}
}
fn handle(&mut self, msg: SendUserRoomMessage<Response>, _: &mut Context<Self>) {
self
- .send_user_room_message(&msg.op, &msg.response, msg.recipient_id, msg.my_id)
- .unwrap();
+ .send_user_room_message(&msg.op, &msg.response, msg.recipient_id, msg.websocket_id)
+ .ok();
}
}
fn handle(&mut self, msg: SendCommunityRoomMessage<Response>, _: &mut Context<Self>) {
self
- .send_community_room_message(&msg.op, &msg.response, msg.community_id, msg.my_id)
- .unwrap();
+ .send_community_room_message(&msg.op, &msg.response, msg.community_id, msg.websocket_id)
+ .ok();
}
}
type Result = ();
fn handle(&mut self, msg: SendPost, _: &mut Context<Self>) {
- self.send_post(&msg.op, &msg.post, msg.my_id).unwrap();
+ self.send_post(&msg.op, &msg.post, msg.websocket_id).ok();
}
}
type Result = ();
fn handle(&mut self, msg: SendComment, _: &mut Context<Self>) {
- self.send_comment(&msg.op, &msg.comment, msg.my_id).unwrap();
+ self
+ .send_comment(&msg.op, &msg.comment, msg.websocket_id)
+ .ok();
}
}
type Result = ();
fn handle(&mut self, msg: JoinUserRoom, _: &mut Context<Self>) {
- self.join_user_room(msg.user_id, msg.id);
+ self.join_user_room(msg.user_id, msg.id).ok();
}
}
type Result = ();
fn handle(&mut self, msg: JoinCommunityRoom, _: &mut Context<Self>) {
- self.join_community_room(msg.community_id, msg.id);
+ self.join_community_room(msg.community_id, msg.id).ok();
}
}
type Result = ();
fn handle(&mut self, msg: JoinPostRoom, _: &mut Context<Self>) {
- self.join_post_room(msg.post_id, msg.id);
+ self.join_post_room(msg.post_id, msg.id).ok();
}
}
};
Ok(serde_json::to_string(&response)?)
}
+
+impl Handler<CaptchaItem> for ChatServer {
+ type Result = ();
+
+ fn handle(&mut self, msg: CaptchaItem, _: &mut Context<Self>) {
+ self.captchas.push(msg);
+ }
+}
+
+impl Handler<CheckCaptcha> for ChatServer {
+ type Result = bool;
+
+ fn handle(&mut self, msg: CheckCaptcha, _: &mut Context<Self>) -> Self::Result {
+ // Remove all the ones that are past the expire time
+ self.captchas.retain(|x| x.expires.gt(&naive_now()));
+
+ let check = self
+ .captchas
+ .iter()
+ .any(|r| r.uuid == msg.uuid && r.answer == msg.answer);
+
+ // Remove this uuid so it can't be re-checked (Checks only work once)
+ self.captchas.retain(|x| x.uuid != msg.uuid);
+
+ check
+ }
+}
border: 0px;
}
+.navbar-expand-lg .navbar-nav .nav-link {
+ padding-right: .75rem !important;
+ padding-left: .75rem !important;
+}
+
.pointer {
cursor: pointer;
}
margin-top: -10px;
}
+.custom-select {
+ -moz-appearance: none;
+}
+
.md-div p {
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.0;
}
+.post-title a:visited {
+ color: var(--gray) !important;
+}
+
.icon {
display: inline-flex;
width: 1em;
.thumbnail {
object-fit: cover;
+ min-height: 60px;
max-height: 80px;
width: 100%;
}
-svg.thumbnail {
- height: 40px;
+.thumbnail svg {
+ height: 1.25rem;
+ width: 1.25rem;
}
.no-s-hows {
margin-top: 1rem;
}
+.banner {
+ object-fit: cover;
+ width: 100%;
+ max-height: 240px;
+}
+
+.avatar-overlay {
+ width: 20%;
+ height: 20%;
+ max-width: 120px;
+ max-height: 120px;
+}
+
+.avatar-pushup {
+ margin-top: -60px;
+}
--- /dev/null
+
+$white: #fff;
+$gray-100: #f8f9fa;
+$gray-200: #ebebeb;
+$gray-300: #dee2e6;
+$gray-400: #ced4da;
+$gray-500: #adb5bd;
+$gray-600: #888;
+$gray-700: #444;
+$gray-800: #303030;
+$gray-900: #222;
+$black: #000;
+$blue: #375a7f;
+$indigo: #6610f2;
+$purple: #6f42c1;
+$pink: #e83e8c;
+$red: #e74c3c;
+$orange: #fd7e14;
+$yellow: #f39c12;
+$green: #00bc8c;
+$teal: #20c997;
+$cyan: #3498db;
+$primary: $blue;
+$secondary: $gray-700;
+$success: $green;
+$info: $cyan;
+$warning: $yellow;
+$danger: $red;
+$dark: $gray-300;
+$yiq-contrasted-threshold: 175;
+$body-bg: $gray-900;
+$body-color: $gray-300;
+$link-color: $success;
+$font-family-sans-serif: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+$font-size-base: 0.9375rem;
+$h1-font-size: 3rem;
+$h2-font-size: 2.5rem;
+$h3-font-size: 2rem;
+$text-muted: $gray-600;
+$table-accent-bg: $gray-800;
+$table-border-color: $gray-700;
+$input-border-color: $body-bg;
+$input-group-addon-color: $gray-500;
+$input-group-addon-bg: $gray-700;
+$custom-file-color: $gray-500;
+$custom-file-border-color: $body-bg;
+$dropdown-bg: $gray-900;
+$dropdown-border-color: $gray-700;
+$dropdown-divider-bg: $gray-700;
+$dropdown-link-color: $white;
+$dropdown-link-hover-color: $white;
+$dropdown-link-hover-bg: $primary;
+$nav-link-padding-x: 2rem;
+$nav-link-disabled-color: $gray-500;
+$nav-tabs-border-color: $gray-700;
+$nav-tabs-link-hover-border-color: $nav-tabs-border-color $nav-tabs-border-color transparent;
+$nav-tabs-link-active-color: $white;
+$nav-tabs-link-active-border-color: $nav-tabs-border-color $nav-tabs-border-color transparent;
+$navbar-padding-y: 1rem;
+$navbar-dark-color: rgba($white,.6);
+$navbar-dark-hover-color: $white;
+$navbar-light-color: rgba($white,.6);
+$navbar-light-hover-color: $white;
+$navbar-light-active-color: $white;
+$navbar-light-toggler-border-color: rgba($gray-900, .1);
+$pagination-color: $white;
+$pagination-bg: $success;
+$pagination-border-width: 0;
+$pagination-border-color: transparent;
+$pagination-hover-color: $white;
+$pagination-hover-bg: lighten($success, 10%);
+$pagination-hover-border-color: transparent;
+$pagination-active-bg: $pagination-hover-bg;
+$pagination-active-border-color: transparent;
+$pagination-disabled-color: $white;
+$pagination-disabled-bg: darken($success, 15%);
+$pagination-disabled-border-color: transparent;
+$jumbotron-bg: $gray-800;
+$card-cap-bg: $gray-700;
+$card-bg: $gray-800;
+$popover-bg: $gray-800;
+$popover-header-bg: $gray-700;
+$toast-background-color: $gray-700;
+$toast-header-background-color: $gray-800;
+$modal-content-bg: $gray-800;
+$modal-content-border-color: $gray-700;
+$modal-header-border-color: $gray-700;
+$progress-bg: $gray-700;
+$list-group-bg: $gray-800;
+$list-group-border-color: $gray-700;
+$list-group-hover-bg: $gray-700;
+$breadcrumb-bg: $gray-700;
+$close-color: $white;
+$close-text-shadow: none;
+$pre-color: inherit;
+$mark-bg: #333;
+$custom-select-bg: $secondary;
+$custom-select-color: $white;
+$input-bg: $secondary;
+$input-color: $white;
+$input-disabled-bg: darken($secondary, 10%);;
+$light: $gray-800;
+$navbar-light-brand-color: $navbar-dark-active-color;
+$navbar-light-brand-hover-color: $navbar-dark-active-color;
\ No newline at end of file
$white: #ffffff;
-$orange: #faa077;
+$orange: #f1641e;
$cyan: #02bdc2;
-$green: #d4e9d7;
+$green: #00C853;
$secondary: $green;
$body-color: $gray-700;
-$link-color: theme-color("danger");;
+$link-color: theme-color("primary");;
$primary: $orange;
$red: #d8486a;
-$border-radius: 1.5rem;
-$border-radius-lg: 1.5rem;
+$border-radius: 0.5rem;
+$border-radius-lg: 0.5rem;
$border-radius-sm: 1rem;
-$font-family-sans-serif: Guardian-EgypTT,serif,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+$font-family-sans-serif: -apple-system,BlinkMacSystemFont,"Droid Sans","Segoe UI","Helvetica",Arial,sans-serif;
$headings-color: $gray-700;
$input-btn-focus-color: rgba($component-active-bg, .75);
$form-feedback-valid-color: theme-color("info");
$navbar-light-active-color: $gray-900;
$card-color: $gray-700;
$card-cap-color: $gray-700;
-$info: darken($green, 25%);;
-$body-bg: #f2f0f0;
-$success: darken($green, 25%);;
+$info: $blue;
+$body-bg: #fff;
+$success: $indigo;
$danger: darken($primary, 25%);
$navbar-light-hover-color: $gray-900;
$card-bg: $gray-100;
-$border-color: $gray-700;
\ No newline at end of file
+$border-color: $gray-700;
+$mark-bg: rgb(255, 252, 239);
+$font-weight-bold: 600;
+$rounded-pill: 0.25rem;
-/*!
- * Bootswatch v4.3.1
- * Homepage: https://bootswatch.com
- * Copyright 2012-2019 Thomas Park
- * Licensed under MIT
- * Based on Bootstrap
-*//*!
- * Bootstrap v4.3.1 (https://getbootstrap.com/)
- * Copyright 2011-2019 The Bootstrap Authors
- * Copyright 2011-2019 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- */:root{--blue: #375a7f;--indigo: #6610f2;--purple: #6f42c1;--pink: #e83e8c;--red: #E74C3C;--orange: #fd7e14;--yellow: #F39C12;--green: #00bc8c;--teal: #20c997;--cyan: #3498DB;--white: #fff;--gray: #999;--gray-dark: #303030;--primary: #375a7f;--secondary: #444;--success: #00bc8c;--info: #3498DB;--warning: #F39C12;--danger: #E74C3C;--light: #303030;--dark: #adb5bd;--breakpoint-xs: 0;--breakpoint-sm: 576px;--breakpoint-md: 768px;--breakpoint-lg: 992px;--breakpoint-xl: 1200px;--font-family-sans-serif: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace}*,*::before,*::after{-webkit-box-sizing:border-box;box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:"Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";font-size:0.9375rem;font-weight:400;line-height:1.5;color:#fff;text-align:left;background-color:#222}[tabindex="-1"]:focus{outline:0 !important}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:0.5rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-original-title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#00bc8c;text-decoration:none;background-color:transparent}a:hover{color:#007053;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):hover,a:not([href]):not([tabindex]):focus{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}pre,code,kbd,samp{font-family:SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:0.75rem;padding-bottom:0.75rem;color:#999;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:0.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}button,[type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button:not(:disabled),[type="button"]:not(:disabled),[type="reset"]:not(:disabled),[type="submit"]:not(:disabled){cursor:pointer}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{padding:0;border-style:none}input[type="radio"],input[type="checkbox"]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="date"],input[type="time"],input[type="datetime-local"],input[type="month"]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{outline-offset:-2px;-webkit-appearance:none}[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none !important}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{margin-bottom:0.5rem;font-weight:500;line-height:1.2}h1,.h1{font-size:3rem}h2,.h2{font-size:2.5rem}h3,.h3{font-size:2rem}h4,.h4{font-size:1.40625rem}h5,.h5{font-size:1.171875rem}h6,.h6{font-size:0.9375rem}.lead{font-size:1.171875rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,0.1)}small,.small{font-size:80%;font-weight:400}mark,.mark{padding:0.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:0.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.171875rem}.blockquote-footer{display:block;font-size:80%;color:#999}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:0.25rem;background-color:#222;border:1px solid #dee2e6;border-radius:0.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:0.5rem;line-height:1}.figure-caption{font-size:90%;color:#999}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:0.2rem 0.4rem;font-size:87.5%;color:#fff;background-color:#222;border-radius:0.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:inherit}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width: 576px){.container{max-width:540px}}@media (min-width: 768px){.container{max-width:720px}}@media (min-width: 992px){.container{max-width:960px}}@media (min-width: 1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*="col-"]{padding-right:0;padding-left:0}.col-1,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-10,.col-11,.col-12,.col,.col-auto,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm,.col-sm-auto,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-md,.col-md-auto,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg,.col-lg-auto,.col-xl-1,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-webkit-box-flex:0;-ms-flex:0 0 8.3333333333%;flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-2{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-5{-webkit-box-flex:0;-ms-flex:0 0 41.6666666667%;flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-webkit-box-flex:0;-ms-flex:0 0 58.3333333333%;flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-8{-webkit-box-flex:0;-ms-flex:0 0 66.6666666667%;flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-webkit-box-flex:0;-ms-flex:0 0 83.3333333333%;flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-11{-webkit-box-flex:0;-ms-flex:0 0 91.6666666667%;flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-1{margin-left:8.3333333333%}.offset-2{margin-left:16.6666666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.3333333333%}.offset-5{margin-left:41.6666666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.3333333333%}.offset-8{margin-left:66.6666666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.3333333333%}.offset-11{margin-left:91.6666666667%}@media (min-width: 576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-webkit-box-flex:0;-ms-flex:0 0 8.3333333333%;flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-sm-2{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-sm-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-sm-5{-webkit-box-flex:0;-ms-flex:0 0 41.6666666667%;flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-sm-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-webkit-box-flex:0;-ms-flex:0 0 58.3333333333%;flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-sm-8{-webkit-box-flex:0;-ms-flex:0 0 66.6666666667%;flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-sm-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-webkit-box-flex:0;-ms-flex:0 0 83.3333333333%;flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-sm-11{-webkit-box-flex:0;-ms-flex:0 0 91.6666666667%;flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-sm-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-sm-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-sm-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-sm-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-sm-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-sm-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-sm-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-sm-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-sm-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-sm-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-sm-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-sm-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-sm-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-sm-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-sm-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.3333333333%}.offset-sm-2{margin-left:16.6666666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.3333333333%}.offset-sm-5{margin-left:41.6666666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.3333333333%}.offset-sm-8{margin-left:66.6666666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.3333333333%}.offset-sm-11{margin-left:91.6666666667%}}@media (min-width: 768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-webkit-box-flex:0;-ms-flex:0 0 8.3333333333%;flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-md-2{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-md-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-md-5{-webkit-box-flex:0;-ms-flex:0 0 41.6666666667%;flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-md-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-webkit-box-flex:0;-ms-flex:0 0 58.3333333333%;flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-md-8{-webkit-box-flex:0;-ms-flex:0 0 66.6666666667%;flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-md-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-webkit-box-flex:0;-ms-flex:0 0 83.3333333333%;flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-md-11{-webkit-box-flex:0;-ms-flex:0 0 91.6666666667%;flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-md-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-md-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-md-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-md-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-md-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-md-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-md-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-md-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-md-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-md-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-md-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-md-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-md-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-md-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-md-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.3333333333%}.offset-md-2{margin-left:16.6666666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.3333333333%}.offset-md-5{margin-left:41.6666666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.3333333333%}.offset-md-8{margin-left:66.6666666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.3333333333%}.offset-md-11{margin-left:91.6666666667%}}@media (min-width: 992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-webkit-box-flex:0;-ms-flex:0 0 8.3333333333%;flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-lg-2{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-lg-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-lg-5{-webkit-box-flex:0;-ms-flex:0 0 41.6666666667%;flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-lg-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-webkit-box-flex:0;-ms-flex:0 0 58.3333333333%;flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-lg-8{-webkit-box-flex:0;-ms-flex:0 0 66.6666666667%;flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-lg-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-webkit-box-flex:0;-ms-flex:0 0 83.3333333333%;flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-lg-11{-webkit-box-flex:0;-ms-flex:0 0 91.6666666667%;flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-lg-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-lg-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-lg-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-lg-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-lg-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-lg-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-lg-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-lg-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-lg-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-lg-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-lg-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-lg-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-lg-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-lg-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-lg-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.3333333333%}.offset-lg-2{margin-left:16.6666666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.3333333333%}.offset-lg-5{margin-left:41.6666666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.3333333333%}.offset-lg-8{margin-left:66.6666666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.3333333333%}.offset-lg-11{margin-left:91.6666666667%}}@media (min-width: 1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-webkit-box-flex:0;-ms-flex:0 0 8.3333333333%;flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-xl-2{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-xl-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-xl-5{-webkit-box-flex:0;-ms-flex:0 0 41.6666666667%;flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-xl-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-webkit-box-flex:0;-ms-flex:0 0 58.3333333333%;flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-xl-8{-webkit-box-flex:0;-ms-flex:0 0 66.6666666667%;flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-xl-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-webkit-box-flex:0;-ms-flex:0 0 83.3333333333%;flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-xl-11{-webkit-box-flex:0;-ms-flex:0 0 91.6666666667%;flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-xl-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-xl-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-xl-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-xl-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-xl-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-xl-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-xl-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-xl-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-xl-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-xl-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-xl-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-xl-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-xl-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-xl-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-xl-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.3333333333%}.offset-xl-2{margin-left:16.6666666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.3333333333%}.offset-xl-5{margin-left:41.6666666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.3333333333%}.offset-xl-8{margin-left:66.6666666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.3333333333%}.offset-xl-11{margin-left:91.6666666667%}}.table{width:100%;margin-bottom:1rem;color:#fff}.table th,.table td{padding:0.75rem;vertical-align:top;border-top:1px solid #444}.table thead th{vertical-align:bottom;border-bottom:2px solid #444}.table tbody+tbody{border-top:2px solid #444}.table-sm th,.table-sm td{padding:0.3rem}.table-bordered{border:1px solid #444}.table-bordered th,.table-bordered td{border:1px solid #444}.table-bordered thead th,.table-bordered thead td{border-bottom-width:2px}.table-borderless th,.table-borderless td,.table-borderless thead th,.table-borderless tbody+tbody{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:#303030}.table-hover tbody tr:hover{color:#fff;background-color:rgba(0,0,0,0.075)}.table-primary,.table-primary>th,.table-primary>td{background-color:#c7d1db}.table-primary th,.table-primary td,.table-primary thead th,.table-primary tbody+tbody{border-color:#97a9bc}.table-hover .table-primary:hover{background-color:#b7c4d1}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#b7c4d1}.table-secondary,.table-secondary>th,.table-secondary>td{background-color:#cbcbcb}.table-secondary th,.table-secondary td,.table-secondary thead th,.table-secondary tbody+tbody{border-color:#9e9e9e}.table-hover .table-secondary:hover{background-color:#bebebe}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#bebebe}.table-success,.table-success>th,.table-success>td{background-color:#b8ecdf}.table-success th,.table-success td,.table-success thead th,.table-success tbody+tbody{border-color:#7adcc3}.table-hover .table-success:hover{background-color:#a4e7d6}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#a4e7d6}.table-info,.table-info>th,.table-info>td{background-color:#c6e2f5}.table-info th,.table-info td,.table-info thead th,.table-info tbody+tbody{border-color:#95c9ec}.table-hover .table-info:hover{background-color:#b0d7f1}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#b0d7f1}.table-warning,.table-warning>th,.table-warning>td{background-color:#fce3bd}.table-warning th,.table-warning td,.table-warning thead th,.table-warning tbody+tbody{border-color:#f9cc84}.table-hover .table-warning:hover{background-color:#fbd9a5}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#fbd9a5}.table-danger,.table-danger>th,.table-danger>td{background-color:#f8cdc8}.table-danger th,.table-danger td,.table-danger thead th,.table-danger tbody+tbody{border-color:#f3a29a}.table-hover .table-danger:hover{background-color:#f5b8b1}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f5b8b1}.table-light,.table-light>th,.table-light>td{background-color:#c5c5c5}.table-light th,.table-light td,.table-light thead th,.table-light tbody+tbody{border-color:#939393}.table-hover .table-light:hover{background-color:#b8b8b8}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#b8b8b8}.table-dark,.table-dark>th,.table-dark>td{background-color:#e8eaed}.table-dark th,.table-dark td,.table-dark thead th,.table-dark tbody+tbody{border-color:#d4d9dd}.table-hover .table-dark:hover{background-color:#dadde2}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#dadde2}.table-active,.table-active>th,.table-active>td{background-color:rgba(0,0,0,0.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,0.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,0.075)}.table .thead-dark th{color:#222;background-color:#adb5bd;border-color:#98a2ac}.table .thead-light th{color:#444;background-color:#ebebeb;border-color:#444}.table-dark{color:#222;background-color:#adb5bd}.table-dark th,.table-dark td,.table-dark thead th{border-color:#98a2ac}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,0.05)}.table-dark.table-hover tbody tr:hover{color:#222;background-color:rgba(255,255,255,0.075)}@media (max-width: 575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width: 767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width: 991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width: 1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + 0.75rem + 2px);padding:0.375rem 0.75rem;font-size:0.9375rem;font-weight:400;line-height:1.5;color:#444;background-color:#fff;background-clip:padding-box;border:1px solid transparent;border-radius:0.25rem;-webkit-transition:border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-control{-webkit-transition:none;transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#444;background-color:#fff;border-color:#739ac2;outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25)}.form-control::-webkit-input-placeholder{color:#999;opacity:1}.form-control::-ms-input-placeholder{color:#999;opacity:1}.form-control::placeholder{color:#999;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#ebebeb;opacity:1}select.form-control:focus::-ms-value{color:#444;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(0.375rem + 1px);padding-bottom:calc(0.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(0.5rem + 1px);padding-bottom:calc(0.5rem + 1px);font-size:1.171875rem;line-height:1.5}.col-form-label-sm{padding-top:calc(0.25rem + 1px);padding-bottom:calc(0.25rem + 1px);font-size:0.8203125rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:0.375rem;padding-bottom:0.375rem;margin-bottom:0;line-height:1.5;color:#fff;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + 0.5rem + 2px);padding:0.25rem 0.5rem;font-size:0.8203125rem;line-height:1.5;border-radius:0.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:0.5rem 1rem;font-size:1.171875rem;line-height:1.5;border-radius:0.3rem}select.form-control[size],select.form-control[multiple]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:0.25rem}.form-row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*="col-"]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:0.3rem;margin-left:-1.25rem}.form-check-input:disabled ~ .form-check-label{color:#999}.form-check-label{margin-bottom:0}.form-check-inline{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:0.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:0.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:0.25rem;font-size:80%;color:#00bc8c}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:0.25rem 0.5rem;margin-top:.1rem;font-size:0.8203125rem;line-height:1.5;color:#fff;background-color:rgba(0,188,140,0.9);border-radius:0.25rem}.was-validated .form-control:valid,.form-control.is-valid{border-color:#00bc8c;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2300bc8c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:center right calc(0.375em + 0.1875rem);background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#00bc8c;-webkit-box-shadow:0 0 0 0.2rem rgba(0,188,140,0.25);box-shadow:0 0 0 0.2rem rgba(0,188,140,0.25)}.was-validated .form-control:valid ~ .valid-feedback,.was-validated .form-control:valid ~ .valid-tooltip,.form-control.is-valid ~ .valid-feedback,.form-control.is-valid ~ .valid-tooltip{display:block}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .custom-select:valid,.custom-select.is-valid{border-color:#00bc8c;padding-right:calc((1em + 0.75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23303030' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2300bc8c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .custom-select:valid:focus,.custom-select.is-valid:focus{border-color:#00bc8c;-webkit-box-shadow:0 0 0 0.2rem rgba(0,188,140,0.25);box-shadow:0 0 0 0.2rem rgba(0,188,140,0.25)}.was-validated .custom-select:valid ~ .valid-feedback,.was-validated .custom-select:valid ~ .valid-tooltip,.custom-select.is-valid ~ .valid-feedback,.custom-select.is-valid ~ .valid-tooltip{display:block}.was-validated .form-control-file:valid ~ .valid-feedback,.was-validated .form-control-file:valid ~ .valid-tooltip,.form-control-file.is-valid ~ .valid-feedback,.form-control-file.is-valid ~ .valid-tooltip{display:block}.was-validated .form-check-input:valid ~ .form-check-label,.form-check-input.is-valid ~ .form-check-label{color:#00bc8c}.was-validated .form-check-input:valid ~ .valid-feedback,.was-validated .form-check-input:valid ~ .valid-tooltip,.form-check-input.is-valid ~ .valid-feedback,.form-check-input.is-valid ~ .valid-tooltip{display:block}.was-validated .custom-control-input:valid ~ .custom-control-label,.custom-control-input.is-valid ~ .custom-control-label{color:#00bc8c}.was-validated .custom-control-input:valid ~ .custom-control-label::before,.custom-control-input.is-valid ~ .custom-control-label::before{border-color:#00bc8c}.was-validated .custom-control-input:valid ~ .valid-feedback,.was-validated .custom-control-input:valid ~ .valid-tooltip,.custom-control-input.is-valid ~ .valid-feedback,.custom-control-input.is-valid ~ .valid-tooltip{display:block}.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before,.custom-control-input.is-valid:checked ~ .custom-control-label::before{border-color:#00efb2;background-color:#00efb2}.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before,.custom-control-input.is-valid:focus ~ .custom-control-label::before{-webkit-box-shadow:0 0 0 0.2rem rgba(0,188,140,0.25);box-shadow:0 0 0 0.2rem rgba(0,188,140,0.25)}.was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before,.custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before{border-color:#00bc8c}.was-validated .custom-file-input:valid ~ .custom-file-label,.custom-file-input.is-valid ~ .custom-file-label{border-color:#00bc8c}.was-validated .custom-file-input:valid ~ .valid-feedback,.was-validated .custom-file-input:valid ~ .valid-tooltip,.custom-file-input.is-valid ~ .valid-feedback,.custom-file-input.is-valid ~ .valid-tooltip{display:block}.was-validated .custom-file-input:valid:focus ~ .custom-file-label,.custom-file-input.is-valid:focus ~ .custom-file-label{border-color:#00bc8c;-webkit-box-shadow:0 0 0 0.2rem rgba(0,188,140,0.25);box-shadow:0 0 0 0.2rem rgba(0,188,140,0.25)}.invalid-feedback{display:none;width:100%;margin-top:0.25rem;font-size:80%;color:#E74C3C}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:0.25rem 0.5rem;margin-top:.1rem;font-size:0.8203125rem;line-height:1.5;color:#fff;background-color:rgba(231,76,60,0.9);border-radius:0.25rem}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#E74C3C;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23E74C3C' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23E74C3C' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E");background-repeat:no-repeat;background-position:center right calc(0.375em + 0.1875rem);background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#E74C3C;-webkit-box-shadow:0 0 0 0.2rem rgba(231,76,60,0.25);box-shadow:0 0 0 0.2rem rgba(231,76,60,0.25)}.was-validated .form-control:invalid ~ .invalid-feedback,.was-validated .form-control:invalid ~ .invalid-tooltip,.form-control.is-invalid ~ .invalid-feedback,.form-control.is-invalid ~ .invalid-tooltip{display:block}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .custom-select:invalid,.custom-select.is-invalid{border-color:#E74C3C;padding-right:calc((1em + 0.75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23303030' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23E74C3C' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23E74C3C' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .custom-select:invalid:focus,.custom-select.is-invalid:focus{border-color:#E74C3C;-webkit-box-shadow:0 0 0 0.2rem rgba(231,76,60,0.25);box-shadow:0 0 0 0.2rem rgba(231,76,60,0.25)}.was-validated .custom-select:invalid ~ .invalid-feedback,.was-validated .custom-select:invalid ~ .invalid-tooltip,.custom-select.is-invalid ~ .invalid-feedback,.custom-select.is-invalid ~ .invalid-tooltip{display:block}.was-validated .form-control-file:invalid ~ .invalid-feedback,.was-validated .form-control-file:invalid ~ .invalid-tooltip,.form-control-file.is-invalid ~ .invalid-feedback,.form-control-file.is-invalid ~ .invalid-tooltip{display:block}.was-validated .form-check-input:invalid ~ .form-check-label,.form-check-input.is-invalid ~ .form-check-label{color:#E74C3C}.was-validated .form-check-input:invalid ~ .invalid-feedback,.was-validated .form-check-input:invalid ~ .invalid-tooltip,.form-check-input.is-invalid ~ .invalid-feedback,.form-check-input.is-invalid ~ .invalid-tooltip{display:block}.was-validated .custom-control-input:invalid ~ .custom-control-label,.custom-control-input.is-invalid ~ .custom-control-label{color:#E74C3C}.was-validated .custom-control-input:invalid ~ .custom-control-label::before,.custom-control-input.is-invalid ~ .custom-control-label::before{border-color:#E74C3C}.was-validated .custom-control-input:invalid ~ .invalid-feedback,.was-validated .custom-control-input:invalid ~ .invalid-tooltip,.custom-control-input.is-invalid ~ .invalid-feedback,.custom-control-input.is-invalid ~ .invalid-tooltip{display:block}.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before,.custom-control-input.is-invalid:checked ~ .custom-control-label::before{border-color:#ed7669;background-color:#ed7669}.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before,.custom-control-input.is-invalid:focus ~ .custom-control-label::before{-webkit-box-shadow:0 0 0 0.2rem rgba(231,76,60,0.25);box-shadow:0 0 0 0.2rem rgba(231,76,60,0.25)}.was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before,.custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before{border-color:#E74C3C}.was-validated .custom-file-input:invalid ~ .custom-file-label,.custom-file-input.is-invalid ~ .custom-file-label{border-color:#E74C3C}.was-validated .custom-file-input:invalid ~ .invalid-feedback,.was-validated .custom-file-input:invalid ~ .invalid-tooltip,.custom-file-input.is-invalid ~ .invalid-feedback,.custom-file-input.is-invalid ~ .invalid-tooltip{display:block}.was-validated .custom-file-input:invalid:focus ~ .custom-file-label,.custom-file-input.is-invalid:focus ~ .custom-file-label{border-color:#E74C3C;-webkit-box-shadow:0 0 0 0.2rem rgba(231,76,60,0.25);box-shadow:0 0 0 0.2rem rgba(231,76,60,0.25)}.form-inline{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width: 576px){.form-inline label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .input-group,.form-inline .custom-select{width:auto}.form-inline .form-check{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:0.25rem;margin-left:0}.form-inline .custom-control{-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#fff;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:0.375rem 0.75rem;font-size:0.9375rem;line-height:1.5;border-radius:0.25rem;-webkit-transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.btn{-webkit-transition:none;transition:none}}.btn:hover{color:#fff;text-decoration:none}.btn:focus,.btn.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25)}.btn.disabled,.btn:disabled{opacity:0.65}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#375a7f;border-color:#375a7f}.btn-primary:hover{color:#fff;background-color:#2b4764;border-color:#28415b}.btn-primary:focus,.btn-primary.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(85,115,146,0.5);box-shadow:0 0 0 0.2rem rgba(85,115,146,0.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#375a7f;border-color:#375a7f}.btn-primary:not(:disabled):not(.disabled):active,.btn-primary:not(:disabled):not(.disabled).active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#28415b;border-color:#243a53}.btn-primary:not(:disabled):not(.disabled):active:focus,.btn-primary:not(:disabled):not(.disabled).active:focus,.show>.btn-primary.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(85,115,146,0.5);box-shadow:0 0 0 0.2rem rgba(85,115,146,0.5)}.btn-secondary{color:#fff;background-color:#444;border-color:#444}.btn-secondary:hover{color:#fff;background-color:#313131;border-color:#2b2a2a}.btn-secondary:focus,.btn-secondary.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(96,96,96,0.5);box-shadow:0 0 0 0.2rem rgba(96,96,96,0.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#444;border-color:#444}.btn-secondary:not(:disabled):not(.disabled):active,.btn-secondary:not(:disabled):not(.disabled).active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#2b2a2a;border-color:#242424}.btn-secondary:not(:disabled):not(.disabled):active:focus,.btn-secondary:not(:disabled):not(.disabled).active:focus,.show>.btn-secondary.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(96,96,96,0.5);box-shadow:0 0 0 0.2rem rgba(96,96,96,0.5)}.btn-success{color:#fff;background-color:#00bc8c;border-color:#00bc8c}.btn-success:hover{color:#fff;background-color:#009670;border-color:#008966}.btn-success:focus,.btn-success.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(38,198,157,0.5);box-shadow:0 0 0 0.2rem rgba(38,198,157,0.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#00bc8c;border-color:#00bc8c}.btn-success:not(:disabled):not(.disabled):active,.btn-success:not(:disabled):not(.disabled).active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#008966;border-color:#007c5d}.btn-success:not(:disabled):not(.disabled):active:focus,.btn-success:not(:disabled):not(.disabled).active:focus,.show>.btn-success.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(38,198,157,0.5);box-shadow:0 0 0 0.2rem rgba(38,198,157,0.5)}.btn-info{color:#fff;background-color:#3498DB;border-color:#3498DB}.btn-info:hover{color:#fff;background-color:#2384c6;border-color:#217dbb}.btn-info:focus,.btn-info.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(82,167,224,0.5);box-shadow:0 0 0 0.2rem rgba(82,167,224,0.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#3498DB;border-color:#3498DB}.btn-info:not(:disabled):not(.disabled):active,.btn-info:not(:disabled):not(.disabled).active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#217dbb;border-color:#1f76b0}.btn-info:not(:disabled):not(.disabled):active:focus,.btn-info:not(:disabled):not(.disabled).active:focus,.show>.btn-info.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(82,167,224,0.5);box-shadow:0 0 0 0.2rem rgba(82,167,224,0.5)}.btn-warning{color:#fff;background-color:#F39C12;border-color:#F39C12}.btn-warning:hover{color:#fff;background-color:#d4860b;border-color:#c87f0a}.btn-warning:focus,.btn-warning.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(245,171,54,0.5);box-shadow:0 0 0 0.2rem rgba(245,171,54,0.5)}.btn-warning.disabled,.btn-warning:disabled{color:#fff;background-color:#F39C12;border-color:#F39C12}.btn-warning:not(:disabled):not(.disabled):active,.btn-warning:not(:disabled):not(.disabled).active,.show>.btn-warning.dropdown-toggle{color:#fff;background-color:#c87f0a;border-color:#bc770a}.btn-warning:not(:disabled):not(.disabled):active:focus,.btn-warning:not(:disabled):not(.disabled).active:focus,.show>.btn-warning.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(245,171,54,0.5);box-shadow:0 0 0 0.2rem rgba(245,171,54,0.5)}.btn-danger{color:#fff;background-color:#E74C3C;border-color:#E74C3C}.btn-danger:hover{color:#fff;background-color:#e12e1c;border-color:#d62c1a}.btn-danger:focus,.btn-danger.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(235,103,89,0.5);box-shadow:0 0 0 0.2rem rgba(235,103,89,0.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#E74C3C;border-color:#E74C3C}.btn-danger:not(:disabled):not(.disabled):active,.btn-danger:not(:disabled):not(.disabled).active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#d62c1a;border-color:#ca2a19}.btn-danger:not(:disabled):not(.disabled):active:focus,.btn-danger:not(:disabled):not(.disabled).active:focus,.show>.btn-danger.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(235,103,89,0.5);box-shadow:0 0 0 0.2rem rgba(235,103,89,0.5)}.btn-light{color:#fff;background-color:#303030;border-color:#303030}.btn-light:hover{color:#fff;background-color:#1d1d1d;border-color:#171616}.btn-light:focus,.btn-light.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(79,79,79,0.5);box-shadow:0 0 0 0.2rem rgba(79,79,79,0.5)}.btn-light.disabled,.btn-light:disabled{color:#fff;background-color:#303030;border-color:#303030}.btn-light:not(:disabled):not(.disabled):active,.btn-light:not(:disabled):not(.disabled).active,.show>.btn-light.dropdown-toggle{color:#fff;background-color:#171616;border-color:#101010}.btn-light:not(:disabled):not(.disabled):active:focus,.btn-light:not(:disabled):not(.disabled).active:focus,.show>.btn-light.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(79,79,79,0.5);box-shadow:0 0 0 0.2rem rgba(79,79,79,0.5)}.btn-dark{color:#222;background-color:#adb5bd;border-color:#adb5bd}.btn-dark:hover{color:#fff;background-color:#98a2ac;border-color:#919ca6}.btn-dark:focus,.btn-dark.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(152,159,166,0.5);box-shadow:0 0 0 0.2rem rgba(152,159,166,0.5)}.btn-dark.disabled,.btn-dark:disabled{color:#222;background-color:#adb5bd;border-color:#adb5bd}.btn-dark:not(:disabled):not(.disabled):active,.btn-dark:not(:disabled):not(.disabled).active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#919ca6;border-color:#8a95a1}.btn-dark:not(:disabled):not(.disabled):active:focus,.btn-dark:not(:disabled):not(.disabled).active:focus,.show>.btn-dark.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(152,159,166,0.5);box-shadow:0 0 0 0.2rem rgba(152,159,166,0.5)}.btn-outline-primary{color:#375a7f;border-color:#375a7f}.btn-outline-primary:hover{color:#fff;background-color:#375a7f;border-color:#375a7f}.btn-outline-primary:focus,.btn-outline-primary.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.5);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#375a7f;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled):active,.btn-outline-primary:not(:disabled):not(.disabled).active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#375a7f;border-color:#375a7f}.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.5);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.5)}.btn-outline-secondary{color:#444;border-color:#444}.btn-outline-secondary:hover{color:#fff;background-color:#444;border-color:#444}.btn-outline-secondary:focus,.btn-outline-secondary.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(68,68,68,0.5);box-shadow:0 0 0 0.2rem rgba(68,68,68,0.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#444;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled):active,.btn-outline-secondary:not(:disabled):not(.disabled).active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#444;border-color:#444}.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(68,68,68,0.5);box-shadow:0 0 0 0.2rem rgba(68,68,68,0.5)}.btn-outline-success{color:#00bc8c;border-color:#00bc8c}.btn-outline-success:hover{color:#fff;background-color:#00bc8c;border-color:#00bc8c}.btn-outline-success:focus,.btn-outline-success.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(0,188,140,0.5);box-shadow:0 0 0 0.2rem rgba(0,188,140,0.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#00bc8c;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled):active,.btn-outline-success:not(:disabled):not(.disabled).active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#00bc8c;border-color:#00bc8c}.btn-outline-success:not(:disabled):not(.disabled):active:focus,.btn-outline-success:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-success.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(0,188,140,0.5);box-shadow:0 0 0 0.2rem rgba(0,188,140,0.5)}.btn-outline-info{color:#3498DB;border-color:#3498DB}.btn-outline-info:hover{color:#fff;background-color:#3498DB;border-color:#3498DB}.btn-outline-info:focus,.btn-outline-info.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(52,152,219,0.5);box-shadow:0 0 0 0.2rem rgba(52,152,219,0.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#3498DB;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled):active,.btn-outline-info:not(:disabled):not(.disabled).active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#3498DB;border-color:#3498DB}.btn-outline-info:not(:disabled):not(.disabled):active:focus,.btn-outline-info:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-info.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(52,152,219,0.5);box-shadow:0 0 0 0.2rem rgba(52,152,219,0.5)}.btn-outline-warning{color:#F39C12;border-color:#F39C12}.btn-outline-warning:hover{color:#fff;background-color:#F39C12;border-color:#F39C12}.btn-outline-warning:focus,.btn-outline-warning.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(243,156,18,0.5);box-shadow:0 0 0 0.2rem rgba(243,156,18,0.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#F39C12;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled):active,.btn-outline-warning:not(:disabled):not(.disabled).active,.show>.btn-outline-warning.dropdown-toggle{color:#fff;background-color:#F39C12;border-color:#F39C12}.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(243,156,18,0.5);box-shadow:0 0 0 0.2rem rgba(243,156,18,0.5)}.btn-outline-danger{color:#E74C3C;border-color:#E74C3C}.btn-outline-danger:hover{color:#fff;background-color:#E74C3C;border-color:#E74C3C}.btn-outline-danger:focus,.btn-outline-danger.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(231,76,60,0.5);box-shadow:0 0 0 0.2rem rgba(231,76,60,0.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#E74C3C;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled):active,.btn-outline-danger:not(:disabled):not(.disabled).active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#E74C3C;border-color:#E74C3C}.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(231,76,60,0.5);box-shadow:0 0 0 0.2rem rgba(231,76,60,0.5)}.btn-outline-light{color:#303030;border-color:#303030}.btn-outline-light:hover{color:#fff;background-color:#303030;border-color:#303030}.btn-outline-light:focus,.btn-outline-light.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(48,48,48,0.5);box-shadow:0 0 0 0.2rem rgba(48,48,48,0.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#303030;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled):active,.btn-outline-light:not(:disabled):not(.disabled).active,.show>.btn-outline-light.dropdown-toggle{color:#fff;background-color:#303030;border-color:#303030}.btn-outline-light:not(:disabled):not(.disabled):active:focus,.btn-outline-light:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-light.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(48,48,48,0.5);box-shadow:0 0 0 0.2rem rgba(48,48,48,0.5)}.btn-outline-dark{color:#adb5bd;border-color:#adb5bd}.btn-outline-dark:hover{color:#222;background-color:#adb5bd;border-color:#adb5bd}.btn-outline-dark:focus,.btn-outline-dark.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(173,181,189,0.5);box-shadow:0 0 0 0.2rem rgba(173,181,189,0.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#adb5bd;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled):active,.btn-outline-dark:not(:disabled):not(.disabled).active,.show>.btn-outline-dark.dropdown-toggle{color:#222;background-color:#adb5bd;border-color:#adb5bd}.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(173,181,189,0.5);box-shadow:0 0 0 0.2rem rgba(173,181,189,0.5)}.btn-link{font-weight:400;color:#00bc8c;text-decoration:none}.btn-link:hover{color:#007053;text-decoration:underline}.btn-link:focus,.btn-link.focus{text-decoration:underline;-webkit-box-shadow:none;box-shadow:none}.btn-link:disabled,.btn-link.disabled{color:#999;pointer-events:none}.btn-lg,.btn-group-lg>.btn{padding:0.5rem 1rem;font-size:1.171875rem;line-height:1.5;border-radius:0.3rem}.btn-sm,.btn-group-sm>.btn{padding:0.25rem 0.5rem;font-size:0.8203125rem;line-height:1.5;border-radius:0.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:0.5rem}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{-webkit-transition:opacity 0.15s linear;transition:opacity 0.15s linear}@media (prefers-reduced-motion: reduce){.fade{-webkit-transition:none;transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;transition:height 0.35s ease}@media (prefers-reduced-motion: reduce){.collapsing{-webkit-transition:none;transition:none}}.dropup,.dropright,.dropdown,.dropleft{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:0.255em;vertical-align:0.255em;content:"";border-top:0.3em solid;border-right:0.3em solid transparent;border-bottom:0;border-left:0.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:0.5rem 0;margin:0.125rem 0 0;font-size:0.9375rem;color:#fff;text-align:left;list-style:none;background-color:#222;background-clip:padding-box;border:1px solid #444;border-radius:0.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width: 576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width: 768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width: 992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width: 1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:0.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:0.255em;vertical-align:0.255em;content:"";border-top:0;border-right:0.3em solid transparent;border-bottom:0.3em solid;border-left:0.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:0.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:0.255em;vertical-align:0.255em;content:"";border-top:0.3em solid transparent;border-right:0;border-bottom:0.3em solid transparent;border-left:0.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:0.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:0.255em;vertical-align:0.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:0.255em;vertical-align:0.255em;content:"";border-top:0.3em solid transparent;border-right:0.3em solid;border-bottom:0.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^="top"],.dropdown-menu[x-placement^="right"],.dropdown-menu[x-placement^="bottom"],.dropdown-menu[x-placement^="left"]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:0.5rem 0;overflow:hidden;border-top:1px solid #444}.dropdown-item{display:block;width:100%;padding:0.25rem 1.5rem;clear:both;font-weight:400;color:#fff;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:hover,.dropdown-item:focus{color:#fff;text-decoration:none;background-color:#375a7f}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#375a7f}.dropdown-item.disabled,.dropdown-item:disabled{color:#999;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:0.5rem 1.5rem;margin-bottom:0;font-size:0.8203125rem;color:#999;white-space:nowrap}.dropdown-item-text{display:block;padding:0.25rem 1.5rem;color:#fff}.btn-group,.btn-group-vertical{position:relative;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover{z-index:1}.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:0.5625rem;padding-left:0.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:0.375rem;padding-left:0.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:0.75rem;padding-left:0.75rem}.btn-group-vertical{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type="radio"],.btn-group-toggle>.btn input[type="checkbox"],.btn-group-toggle>.btn-group>.btn input[type="radio"],.btn-group-toggle>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-control-plaintext,.input-group>.custom-select,.input-group>.custom-file{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.form-control+.form-control,.input-group>.form-control+.custom-select,.input-group>.form-control+.custom-file,.input-group>.form-control-plaintext+.form-control,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.custom-file,.input-group>.custom-select+.form-control,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.custom-file,.input-group>.custom-file+.form-control,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.custom-file{margin-left:-1px}.input-group>.form-control:focus,.input-group>.custom-select:focus,.input-group>.custom-file .custom-file-input:focus ~ .custom-file-label{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.form-control:not(:last-child),.input-group>.custom-select:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.form-control:not(:first-child),.input-group>.custom-select:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-prepend,.input-group-append{display:-webkit-box;display:-ms-flexbox;display:flex}.input-group-prepend .btn,.input-group-append .btn{position:relative;z-index:2}.input-group-prepend .btn:focus,.input-group-append .btn:focus{z-index:3}.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.input-group-text,.input-group-append .input-group-text+.btn{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:0.375rem 0.75rem;margin-bottom:0;font-size:0.9375rem;font-weight:400;line-height:1.5;color:#adb5bd;text-align:center;white-space:nowrap;background-color:#444;border:1px solid transparent;border-radius:0.25rem}.input-group-text input[type="radio"],.input-group-text input[type="checkbox"]{margin-top:0}.input-group-lg>.form-control:not(textarea),.input-group-lg>.custom-select{height:calc(1.5em + 1rem + 2px)}.input-group-lg>.form-control,.input-group-lg>.custom-select,.input-group-lg>.input-group-prepend>.input-group-text,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-append>.btn{padding:0.5rem 1rem;font-size:1.171875rem;line-height:1.5;border-radius:0.3rem}.input-group-sm>.form-control:not(textarea),.input-group-sm>.custom-select{height:calc(1.5em + 0.5rem + 2px)}.input-group-sm>.form-control,.input-group-sm>.custom-select,.input-group-sm>.input-group-prepend>.input-group-text,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-append>.btn{padding:0.25rem 0.5rem;font-size:0.8203125rem;line-height:1.5;border-radius:0.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text,.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.40625rem;padding-left:1.5rem}.custom-control-inline{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked ~ .custom-control-label::before{color:#fff;border-color:#375a7f;background-color:#375a7f}.custom-control-input:focus ~ .custom-control-label::before{-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25)}.custom-control-input:focus:not(:checked) ~ .custom-control-label::before{border-color:#739ac2}.custom-control-input:not(:disabled):active ~ .custom-control-label::before{color:#fff;background-color:#97b3d2;border-color:#97b3d2}.custom-control-input:disabled ~ .custom-control-label{color:#999}.custom-control-input:disabled ~ .custom-control-label::before{background-color:#ebebeb}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:0.203125rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:0.203125rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50% / 50% 50%}.custom-checkbox .custom-control-label::before{border-radius:0.25rem}.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before{border-color:#375a7f;background-color:#375a7f}.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before{background-color:rgba(55,90,127,0.5)}.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before{background-color:rgba(55,90,127,0.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked ~ .custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before{background-color:rgba(55,90,127,0.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:0.5rem}.custom-switch .custom-control-label::after{top:calc(0.203125rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:0.5rem;-webkit-transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.custom-switch .custom-control-label::after{-webkit-transition:none;transition:none}}.custom-switch .custom-control-input:checked ~ .custom-control-label::after{background-color:#fff;-webkit-transform:translateX(0.75rem);transform:translateX(0.75rem)}.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before{background-color:rgba(55,90,127,0.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + 0.75rem + 2px);padding:0.375rem 1.75rem 0.375rem 0.75rem;font-size:0.9375rem;font-weight:400;line-height:1.5;color:#444;vertical-align:middle;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23303030' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px;background-color:#fff;border:1px solid transparent;border-radius:0.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#739ac2;outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25)}.custom-select:focus::-ms-value{color:#444;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:0.75rem;background-image:none}.custom-select:disabled{color:#999;background-color:#ebebeb}.custom-select::-ms-expand{display:none}.custom-select-sm{height:calc(1.5em + 0.5rem + 2px);padding-top:0.25rem;padding-bottom:0.25rem;padding-left:0.5rem;font-size:0.8203125rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:0.5rem;padding-bottom:0.5rem;padding-left:1rem;font-size:1.171875rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + 0.75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + 0.75rem + 2px);margin:0;opacity:0}.custom-file-input:focus ~ .custom-file-label{border-color:#739ac2;-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25)}.custom-file-input:disabled ~ .custom-file-label{background-color:#ebebeb}.custom-file-input:lang(en) ~ .custom-file-label::after{content:"Browse"}.custom-file-input ~ .custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + 0.75rem + 2px);padding:0.375rem 0.75rem;font-weight:400;line-height:1.5;color:#adb5bd;background-color:#fff;border:1px solid #444;border-radius:0.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + 0.75rem);padding:0.375rem 0.75rem;line-height:1.5;color:#adb5bd;content:"Browse";background-color:#444;border-left:inherit;border-radius:0 0.25rem 0.25rem 0}.custom-range{width:100%;height:calc(1rem + 0.4rem);padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:none}.custom-range:focus::-webkit-slider-thumb{-webkit-box-shadow:0 0 0 1px #222,0 0 0 0.2rem rgba(55,90,127,0.25);box-shadow:0 0 0 1px #222,0 0 0 0.2rem rgba(55,90,127,0.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #222,0 0 0 0.2rem rgba(55,90,127,0.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #222,0 0 0 0.2rem rgba(55,90,127,0.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;background-color:#375a7f;border:0;border-radius:1rem;-webkit-transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion: reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#97b3d2}.custom-range::-webkit-slider-runnable-track{width:100%;height:0.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#375a7f;border:0;border-radius:1rem;-webkit-transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion: reduce){.custom-range::-moz-range-thumb{-webkit-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#97b3d2}.custom-range::-moz-range-track{width:100%;height:0.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:0.2rem;margin-left:0.2rem;background-color:#375a7f;border:0;border-radius:1rem;-webkit-transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;appearance:none}@media (prefers-reduced-motion: reduce){.custom-range::-ms-thumb{-webkit-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#97b3d2}.custom-range::-ms-track{width:100%;height:0.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:0.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{-webkit-transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.custom-control-label::before,.custom-file-label,.custom-select{-webkit-transition:none;transition:none}}.nav{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:0.5rem 2rem}.nav-link:hover,.nav-link:focus{text-decoration:none}.nav-link.disabled{color:#adb5bd;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #444}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:0.25rem;border-top-right-radius:0.25rem}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{border-color:#444 #444 transparent}.nav-tabs .nav-link.disabled{color:#adb5bd;background-color:transparent;border-color:transparent}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:#fff;background-color:#222;border-color:#444 #444 transparent}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:0.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#375a7f}.nav-fill .nav-item{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:0.32421875rem;padding-bottom:0.32421875rem;margin-right:1rem;font-size:1.171875rem;line-height:inherit;white-space:nowrap}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-nav{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:0.5rem;padding-bottom:0.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:0.25rem 0.75rem;font-size:1.171875rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:0.25rem}.navbar-toggler:hover,.navbar-toggler:focus{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width: 575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width: 576px){.navbar-expand-sm{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width: 767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width: 768px){.navbar-expand-md{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width: 991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width: 992px){.navbar-expand-lg{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width: 1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width: 1200px){.navbar-expand-xl{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:#fff}.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:focus{color:#fff}.navbar-light .navbar-nav .nav-link{color:rgba(255,255,255,0.5)}.navbar-light .navbar-nav .nav-link:hover,.navbar-light .navbar-nav .nav-link:focus{color:#fff}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(255,255,255,0.3)}.navbar-light .navbar-nav .show>.nav-link,.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .nav-link.active{color:#fff}.navbar-light .navbar-toggler{color:rgba(255,255,255,0.5);border-color:rgba(255,255,255,0.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(255,255,255,0.5)}.navbar-light .navbar-text a{color:#fff}.navbar-light .navbar-text a:hover,.navbar-light .navbar-text a:focus{color:#fff}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-brand:focus{color:#fff}.navbar-dark .navbar-nav .nav-link{color:#fff}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .nav-link:focus{color:#00bc8c}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,0.25)}.navbar-dark .navbar-nav .show>.nav-link,.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .nav-link.active{color:#fff}.navbar-dark .navbar-toggler{color:#fff;border-color:rgba(255,255,255,0.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:#fff}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:hover,.navbar-dark .navbar-text a:focus{color:#fff}.card{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#303030;background-clip:border-box;border:1px solid rgba(0,0,0,0.125);border-radius:0.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:0.25rem;border-top-right-radius:0.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:0.25rem;border-bottom-left-radius:0.25rem}.card-body{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:0.75rem}.card-subtitle{margin-top:-0.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:0.75rem 1.25rem;margin-bottom:0;background-color:#444;border-bottom:1px solid rgba(0,0,0,0.125)}.card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:0.75rem 1.25rem;background-color:#444;border-top:1px solid rgba(0,0,0,0.125)}.card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.card-header-tabs{margin-right:-0.625rem;margin-bottom:-0.75rem;margin-left:-0.625rem;border-bottom:0}.card-header-pills{margin-right:-0.625rem;margin-left:-0.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(0.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.card-deck{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width: 576px){.card-deck{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1 0 0%;flex:1 0 0%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width: 576px){.card-group{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-webkit-box-flex:1;-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-img-top,.card-group>.card:not(:last-child) .card-header{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-img-bottom,.card-group>.card:not(:last-child) .card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-img-top,.card-group>.card:not(:first-child) .card-header{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-img-bottom,.card-group>.card:not(:first-child) .card-footer{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:0.75rem}@media (min-width: 576px){.card-columns{-webkit-column-count:3;column-count:3;-webkit-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:first-of-type) .card-header:first-child{border-radius:0}.accordion>.card:not(:first-of-type):not(:last-of-type){border-bottom:0;border-radius:0}.accordion>.card:first-of-type{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:last-of-type{border-top-left-radius:0;border-top-right-radius:0}.accordion>.card .card-header{margin-bottom:-1px}.breadcrumb{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#444;border-radius:0.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:0.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:0.5rem;color:#999;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#999}.pagination{display:-webkit-box;display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:0.25rem}.page-link{position:relative;display:block;padding:0.5rem 0.75rem;margin-left:0;line-height:1.25;color:#fff;background-color:#00bc8c;border:0 solid transparent}.page-link:hover{z-index:2;color:#fff;text-decoration:none;background-color:#00efb2;border-color:transparent}.page-link:focus{z-index:2;outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:0.25rem;border-bottom-left-radius:0.25rem}.page-item:last-child .page-link{border-top-right-radius:0.25rem;border-bottom-right-radius:0.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#00efb2;border-color:transparent}.page-item.disabled .page-link{color:#fff;pointer-events:none;cursor:auto;background-color:#007053;border-color:transparent}.pagination-lg .page-link{padding:0.75rem 1.5rem;font-size:1.171875rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:0.3rem;border-bottom-left-radius:0.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:0.3rem;border-bottom-right-radius:0.3rem}.pagination-sm .page-link{padding:0.25rem 0.5rem;font-size:0.8203125rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:0.2rem;border-bottom-left-radius:0.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:0.2rem;border-bottom-right-radius:0.2rem}.badge{display:inline-block;padding:0.25em 0.4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:0.25rem;-webkit-transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.badge{-webkit-transition:none;transition:none}}a.badge:hover,a.badge:focus{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:0.6em;padding-left:0.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#375a7f}a.badge-primary:hover,a.badge-primary:focus{color:#fff;background-color:#28415b}a.badge-primary:focus,a.badge-primary.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(55,90,127,0.5);box-shadow:0 0 0 0.2rem rgba(55,90,127,0.5)}.badge-secondary{color:#fff;background-color:#444}a.badge-secondary:hover,a.badge-secondary:focus{color:#fff;background-color:#2b2a2a}a.badge-secondary:focus,a.badge-secondary.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(68,68,68,0.5);box-shadow:0 0 0 0.2rem rgba(68,68,68,0.5)}.badge-success{color:#fff;background-color:#00bc8c}a.badge-success:hover,a.badge-success:focus{color:#fff;background-color:#008966}a.badge-success:focus,a.badge-success.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(0,188,140,0.5);box-shadow:0 0 0 0.2rem rgba(0,188,140,0.5)}.badge-info{color:#fff;background-color:#3498DB}a.badge-info:hover,a.badge-info:focus{color:#fff;background-color:#217dbb}a.badge-info:focus,a.badge-info.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(52,152,219,0.5);box-shadow:0 0 0 0.2rem rgba(52,152,219,0.5)}.badge-warning{color:#fff;background-color:#F39C12}a.badge-warning:hover,a.badge-warning:focus{color:#fff;background-color:#c87f0a}a.badge-warning:focus,a.badge-warning.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(243,156,18,0.5);box-shadow:0 0 0 0.2rem rgba(243,156,18,0.5)}.badge-danger{color:#fff;background-color:#E74C3C}a.badge-danger:hover,a.badge-danger:focus{color:#fff;background-color:#d62c1a}a.badge-danger:focus,a.badge-danger.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(231,76,60,0.5);box-shadow:0 0 0 0.2rem rgba(231,76,60,0.5)}.badge-light{color:#fff;background-color:#303030}a.badge-light:hover,a.badge-light:focus{color:#fff;background-color:#171616}a.badge-light:focus,a.badge-light.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(48,48,48,0.5);box-shadow:0 0 0 0.2rem rgba(48,48,48,0.5)}.badge-dark{color:#222;background-color:#adb5bd}a.badge-dark:hover,a.badge-dark:focus{color:#222;background-color:#919ca6}a.badge-dark:focus,a.badge-dark.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(173,181,189,0.5);box-shadow:0 0 0 0.2rem rgba(173,181,189,0.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#303030;border-radius:0.3rem}@media (min-width: 576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:0.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:0.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3.90625rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:0.75rem 1.25rem;color:inherit}.alert-primary{color:#1d2f42;background-color:#d7dee5;border-color:#c7d1db}.alert-primary hr{border-top-color:#b7c4d1}.alert-primary .alert-link{color:#0d161f}.alert-secondary{color:#232323;background-color:#dadada;border-color:#cbcbcb}.alert-secondary hr{border-top-color:#bebebe}.alert-secondary .alert-link{color:#0a0909}.alert-success{color:#006249;background-color:#ccf2e8;border-color:#b8ecdf}.alert-success hr{border-top-color:#a4e7d6}.alert-success .alert-link{color:#002f23}.alert-info{color:#1b4f72;background-color:#d6eaf8;border-color:#c6e2f5}.alert-info hr{border-top-color:#b0d7f1}.alert-info .alert-link{color:#113249}.alert-warning{color:#7e5109;background-color:#fdebd0;border-color:#fce3bd}.alert-warning hr{border-top-color:#fbd9a5}.alert-warning .alert-link{color:#4e3206}.alert-danger{color:#78281f;background-color:#fadbd8;border-color:#f8cdc8}.alert-danger hr{border-top-color:#f5b8b1}.alert-danger .alert-link{color:#4f1a15}.alert-light{color:#191919;background-color:#d6d6d6;border-color:#c5c5c5}.alert-light hr{border-top-color:#b8b8b8}.alert-light .alert-link{color:black}.alert-dark{color:#5a5e62;background-color:#eff0f2;border-color:#e8eaed}.alert-dark hr{border-top-color:#dadde2}.alert-dark .alert-link{color:#424547}@-webkit-keyframes progress-bar-stripes{from{background-position:0.625rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:0.625rem 0}to{background-position:0 0}}.progress{display:-webkit-box;display:-ms-flexbox;display:flex;height:0.625rem;overflow:hidden;font-size:0.625rem;background-color:#444;border-radius:0.25rem}.progress-bar{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#375a7f;-webkit-transition:width 0.6s ease;transition:width 0.6s ease}@media (prefers-reduced-motion: reduce){.progress-bar{-webkit-transition:none;transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:0.625rem 0.625rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion: reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.media-body{-webkit-box-flex:1;-ms-flex:1;flex:1}.list-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#444;text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:#444;text-decoration:none;background-color:#444}.list-group-item-action:active{color:#fff;background-color:#ebebeb}.list-group-item{position:relative;display:block;padding:0.75rem 1.25rem;margin-bottom:-1px;background-color:#303030;border:1px solid #444}.list-group-item:first-child{border-top-left-radius:0.25rem;border-top-right-radius:0.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:0.25rem;border-bottom-left-radius:0.25rem}.list-group-item.disabled,.list-group-item:disabled{color:#999;pointer-events:none;background-color:#303030}.list-group-item.active{z-index:2;color:#fff;background-color:#375a7f;border-color:#375a7f}.list-group-horizontal{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.list-group-horizontal .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal .list-group-item:first-child{border-top-left-radius:0.25rem;border-bottom-left-radius:0.25rem;border-top-right-radius:0}.list-group-horizontal .list-group-item:last-child{margin-right:0;border-top-right-radius:0.25rem;border-bottom-right-radius:0.25rem;border-bottom-left-radius:0}@media (min-width: 576px){.list-group-horizontal-sm{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-sm .list-group-item:first-child{border-top-left-radius:0.25rem;border-bottom-left-radius:0.25rem;border-top-right-radius:0}.list-group-horizontal-sm .list-group-item:last-child{margin-right:0;border-top-right-radius:0.25rem;border-bottom-right-radius:0.25rem;border-bottom-left-radius:0}}@media (min-width: 768px){.list-group-horizontal-md{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-md .list-group-item:first-child{border-top-left-radius:0.25rem;border-bottom-left-radius:0.25rem;border-top-right-radius:0}.list-group-horizontal-md .list-group-item:last-child{margin-right:0;border-top-right-radius:0.25rem;border-bottom-right-radius:0.25rem;border-bottom-left-radius:0}}@media (min-width: 992px){.list-group-horizontal-lg{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-lg .list-group-item:first-child{border-top-left-radius:0.25rem;border-bottom-left-radius:0.25rem;border-top-right-radius:0}.list-group-horizontal-lg .list-group-item:last-child{margin-right:0;border-top-right-radius:0.25rem;border-bottom-right-radius:0.25rem;border-bottom-left-radius:0}}@media (min-width: 1200px){.list-group-horizontal-xl{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-xl .list-group-item:first-child{border-top-left-radius:0.25rem;border-bottom-left-radius:0.25rem;border-top-right-radius:0}.list-group-horizontal-xl .list-group-item:last-child{margin-right:0;border-top-right-radius:0.25rem;border-bottom-right-radius:0.25rem;border-bottom-left-radius:0}}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush .list-group-item:last-child{margin-bottom:-1px}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{margin-bottom:0;border-bottom:0}.list-group-item-primary{color:#1d2f42;background-color:#c7d1db}.list-group-item-primary.list-group-item-action:hover,.list-group-item-primary.list-group-item-action:focus{color:#1d2f42;background-color:#b7c4d1}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#1d2f42;border-color:#1d2f42}.list-group-item-secondary{color:#232323;background-color:#cbcbcb}.list-group-item-secondary.list-group-item-action:hover,.list-group-item-secondary.list-group-item-action:focus{color:#232323;background-color:#bebebe}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#232323;border-color:#232323}.list-group-item-success{color:#006249;background-color:#b8ecdf}.list-group-item-success.list-group-item-action:hover,.list-group-item-success.list-group-item-action:focus{color:#006249;background-color:#a4e7d6}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#006249;border-color:#006249}.list-group-item-info{color:#1b4f72;background-color:#c6e2f5}.list-group-item-info.list-group-item-action:hover,.list-group-item-info.list-group-item-action:focus{color:#1b4f72;background-color:#b0d7f1}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#1b4f72;border-color:#1b4f72}.list-group-item-warning{color:#7e5109;background-color:#fce3bd}.list-group-item-warning.list-group-item-action:hover,.list-group-item-warning.list-group-item-action:focus{color:#7e5109;background-color:#fbd9a5}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#7e5109;border-color:#7e5109}.list-group-item-danger{color:#78281f;background-color:#f8cdc8}.list-group-item-danger.list-group-item-action:hover,.list-group-item-danger.list-group-item-action:focus{color:#78281f;background-color:#f5b8b1}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#78281f;border-color:#78281f}.list-group-item-light{color:#191919;background-color:#c5c5c5}.list-group-item-light.list-group-item-action:hover,.list-group-item-light.list-group-item-action:focus{color:#191919;background-color:#b8b8b8}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#191919;border-color:#191919}.list-group-item-dark{color:#5a5e62;background-color:#e8eaed}.list-group-item-dark.list-group-item-action:hover,.list-group-item-dark.list-group-item-action:focus{color:#5a5e62;background-color:#dadde2}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#5a5e62;border-color:#5a5e62}.close{float:right;font-size:1.40625rem;font-weight:700;line-height:1;color:#fff;text-shadow:none;opacity:.5}.close:hover{color:#fff;text-decoration:none}.close:not(:disabled):not(.disabled):hover,.close:not(:disabled):not(.disabled):focus{opacity:.75}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:0.875rem;background-color:rgba(255,255,255,0.85);background-clip:padding-box;border:1px solid rgba(0,0,0,0.1);-webkit-box-shadow:0 0.25rem 0.75rem rgba(0,0,0,0.1);box-shadow:0 0.25rem 0.75rem rgba(0,0,0,0.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0;border-radius:0.25rem}.toast:not(:last-child){margin-bottom:0.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:0.25rem 0.75rem;color:#999;background-color:rgba(255,255,255,0.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,0.05)}.toast-body{padding:0.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:0.5rem;pointer-events:none}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform 0.3s ease-out;transition:-webkit-transform 0.3s ease-out;transition:transform 0.3s ease-out;transition:transform 0.3s ease-out, -webkit-transform 0.3s ease-out;-webkit-transform:translate(0, -50px);transform:translate(0, -50px)}@media (prefers-reduced-motion: reduce){.modal.fade .modal-dialog{-webkit-transition:none;transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal-dialog-scrollable{display:-webkit-box;display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-header,.modal-dialog-scrollable .modal-footer{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);content:""}.modal-dialog-centered.modal-dialog-scrollable{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#303030;background-clip:padding-box;border:1px solid #444;border-radius:0.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:0.5}.modal-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #444;border-top-left-radius:0.3rem;border-top-right-radius:0.3rem}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #444;border-bottom-right-radius:0.3rem;border-bottom-left-radius:0.3rem}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width: 576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width: 992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width: 1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:"Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.8203125rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:0.9}.tooltip .arrow{position:absolute;display:block;width:0.8rem;height:0.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-top,.bs-tooltip-auto[x-placement^="top"]{padding:0.4rem 0}.bs-tooltip-top .arrow,.bs-tooltip-auto[x-placement^="top"] .arrow{bottom:0}.bs-tooltip-top .arrow::before,.bs-tooltip-auto[x-placement^="top"] .arrow::before{top:0;border-width:0.4rem 0.4rem 0;border-top-color:#000}.bs-tooltip-right,.bs-tooltip-auto[x-placement^="right"]{padding:0 0.4rem}.bs-tooltip-right .arrow,.bs-tooltip-auto[x-placement^="right"] .arrow{left:0;width:0.4rem;height:0.8rem}.bs-tooltip-right .arrow::before,.bs-tooltip-auto[x-placement^="right"] .arrow::before{right:0;border-width:0.4rem 0.4rem 0.4rem 0;border-right-color:#000}.bs-tooltip-bottom,.bs-tooltip-auto[x-placement^="bottom"]{padding:0.4rem 0}.bs-tooltip-bottom .arrow,.bs-tooltip-auto[x-placement^="bottom"] .arrow{top:0}.bs-tooltip-bottom .arrow::before,.bs-tooltip-auto[x-placement^="bottom"] .arrow::before{bottom:0;border-width:0 0.4rem 0.4rem;border-bottom-color:#000}.bs-tooltip-left,.bs-tooltip-auto[x-placement^="left"]{padding:0 0.4rem}.bs-tooltip-left .arrow,.bs-tooltip-auto[x-placement^="left"] .arrow{right:0;width:0.4rem;height:0.8rem}.bs-tooltip-left .arrow::before,.bs-tooltip-auto[x-placement^="left"] .arrow::before{left:0;border-width:0.4rem 0 0.4rem 0.4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:0.25rem 0.5rem;color:#fff;text-align:center;background-color:#000;border-radius:0.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:"Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.8203125rem;word-wrap:break-word;background-color:#303030;background-clip:padding-box;border:1px solid rgba(0,0,0,0.2);border-radius:0.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:0.5rem;margin:0 0.3rem}.popover .arrow::before,.popover .arrow::after{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-top,.bs-popover-auto[x-placement^="top"]{margin-bottom:0.5rem}.bs-popover-top>.arrow,.bs-popover-auto[x-placement^="top"]>.arrow{bottom:calc((0.5rem + 1px) * -1)}.bs-popover-top>.arrow::before,.bs-popover-auto[x-placement^="top"]>.arrow::before{bottom:0;border-width:0.5rem 0.5rem 0;border-top-color:rgba(0,0,0,0.25)}.bs-popover-top>.arrow::after,.bs-popover-auto[x-placement^="top"]>.arrow::after{bottom:1px;border-width:0.5rem 0.5rem 0;border-top-color:#303030}.bs-popover-right,.bs-popover-auto[x-placement^="right"]{margin-left:0.5rem}.bs-popover-right>.arrow,.bs-popover-auto[x-placement^="right"]>.arrow{left:calc((0.5rem + 1px) * -1);width:0.5rem;height:1rem;margin:0.3rem 0}.bs-popover-right>.arrow::before,.bs-popover-auto[x-placement^="right"]>.arrow::before{left:0;border-width:0.5rem 0.5rem 0.5rem 0;border-right-color:rgba(0,0,0,0.25)}.bs-popover-right>.arrow::after,.bs-popover-auto[x-placement^="right"]>.arrow::after{left:1px;border-width:0.5rem 0.5rem 0.5rem 0;border-right-color:#303030}.bs-popover-bottom,.bs-popover-auto[x-placement^="bottom"]{margin-top:0.5rem}.bs-popover-bottom>.arrow,.bs-popover-auto[x-placement^="bottom"]>.arrow{top:calc((0.5rem + 1px) * -1)}.bs-popover-bottom>.arrow::before,.bs-popover-auto[x-placement^="bottom"]>.arrow::before{top:0;border-width:0 0.5rem 0.5rem 0.5rem;border-bottom-color:rgba(0,0,0,0.25)}.bs-popover-bottom>.arrow::after,.bs-popover-auto[x-placement^="bottom"]>.arrow::after{top:1px;border-width:0 0.5rem 0.5rem 0.5rem;border-bottom-color:#303030}.bs-popover-bottom .popover-header::before,.bs-popover-auto[x-placement^="bottom"] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-0.5rem;content:"";border-bottom:1px solid #444}.bs-popover-left,.bs-popover-auto[x-placement^="left"]{margin-right:0.5rem}.bs-popover-left>.arrow,.bs-popover-auto[x-placement^="left"]>.arrow{right:calc((0.5rem + 1px) * -1);width:0.5rem;height:1rem;margin:0.3rem 0}.bs-popover-left>.arrow::before,.bs-popover-auto[x-placement^="left"]>.arrow::before{right:0;border-width:0.5rem 0 0.5rem 0.5rem;border-left-color:rgba(0,0,0,0.25)}.bs-popover-left>.arrow::after,.bs-popover-auto[x-placement^="left"]>.arrow::after{right:1px;border-width:0.5rem 0 0.5rem 0.5rem;border-left-color:#303030}.popover-header{padding:0.5rem 0.75rem;margin-bottom:0;font-size:0.9375rem;background-color:#444;border-bottom:1px solid #373737;border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:0.5rem 0.75rem;color:#fff}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transition:-webkit-transform 0.6s ease-in-out;transition:-webkit-transform 0.6s ease-in-out;transition:transform 0.6s ease-in-out;transition:transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out}@media (prefers-reduced-motion: reduce){.carousel-item{-webkit-transition:none;transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-left),.active.carousel-item-right{-webkit-transform:translateX(100%);transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-right),.active.carousel-item-left{-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;-webkit-transition-property:opacity;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;-webkit-transition:0s 0.6s opacity;transition:0s 0.6s opacity}@media (prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{-webkit-transition:none;transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:0.5;-webkit-transition:opacity 0.15s ease;transition:opacity 0.15s ease}@media (prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{-webkit-transition:none;transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:0.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50% / 100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;-webkit-transition:opacity 0.6s ease;transition:opacity 0.6s ease}@media (prefers-reduced-motion: reduce){.carousel-indicators li{-webkit-transition:none;transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:0.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:0.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.bg-primary{background-color:#375a7f !important}a.bg-primary:hover,a.bg-primary:focus,button.bg-primary:hover,button.bg-primary:focus{background-color:#28415b !important}.bg-secondary{background-color:#444 !important}a.bg-secondary:hover,a.bg-secondary:focus,button.bg-secondary:hover,button.bg-secondary:focus{background-color:#2b2a2a !important}.bg-success{background-color:#00bc8c !important}a.bg-success:hover,a.bg-success:focus,button.bg-success:hover,button.bg-success:focus{background-color:#008966 !important}.bg-info{background-color:#3498DB !important}a.bg-info:hover,a.bg-info:focus,button.bg-info:hover,button.bg-info:focus{background-color:#217dbb !important}.bg-warning{background-color:#F39C12 !important}a.bg-warning:hover,a.bg-warning:focus,button.bg-warning:hover,button.bg-warning:focus{background-color:#c87f0a !important}.bg-danger{background-color:#E74C3C !important}a.bg-danger:hover,a.bg-danger:focus,button.bg-danger:hover,button.bg-danger:focus{background-color:#d62c1a !important}.bg-light{background-color:#303030 !important}a.bg-light:hover,a.bg-light:focus,button.bg-light:hover,button.bg-light:focus{background-color:#171616 !important}.bg-dark{background-color:#adb5bd !important}a.bg-dark:hover,a.bg-dark:focus,button.bg-dark:hover,button.bg-dark:focus{background-color:#919ca6 !important}.bg-white{background-color:#fff !important}.bg-transparent{background-color:transparent !important}.border{border:1px solid #dee2e6 !important}.border-top{border-top:1px solid #dee2e6 !important}.border-right{border-right:1px solid #dee2e6 !important}.border-bottom{border-bottom:1px solid #dee2e6 !important}.border-left{border-left:1px solid #dee2e6 !important}.border-0{border:0 !important}.border-top-0{border-top:0 !important}.border-right-0{border-right:0 !important}.border-bottom-0{border-bottom:0 !important}.border-left-0{border-left:0 !important}.border-primary{border-color:#375a7f !important}.border-secondary{border-color:#444 !important}.border-success{border-color:#00bc8c !important}.border-info{border-color:#3498DB !important}.border-warning{border-color:#F39C12 !important}.border-danger{border-color:#E74C3C !important}.border-light{border-color:#303030 !important}.border-dark{border-color:#adb5bd !important}.border-white{border-color:#fff !important}.rounded-sm{border-radius:0.2rem !important}.rounded{border-radius:0.25rem !important}.rounded-top{border-top-left-radius:0.25rem !important;border-top-right-radius:0.25rem !important}.rounded-right{border-top-right-radius:0.25rem !important;border-bottom-right-radius:0.25rem !important}.rounded-bottom{border-bottom-right-radius:0.25rem !important;border-bottom-left-radius:0.25rem !important}.rounded-left{border-top-left-radius:0.25rem !important;border-bottom-left-radius:0.25rem !important}.rounded-lg{border-radius:0.3rem !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.rounded-0{border-radius:0 !important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}@media (min-width: 576px){.d-sm-none{display:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-sm-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}}@media (min-width: 768px){.d-md-none{display:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-md-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}}@media (min-width: 992px){.d-lg-none{display:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-lg-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}}@media (min-width: 1200px){.d-xl-none{display:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-xl-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}}@media print{.d-print-none{display:none !important}.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-print-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.8571428571%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-webkit-box-orient:horizontal !important;-webkit-box-direction:normal !important;-ms-flex-direction:row !important;flex-direction:row !important}.flex-column{-webkit-box-orient:vertical !important;-webkit-box-direction:normal !important;-ms-flex-direction:column !important;flex-direction:column !important}.flex-row-reverse{-webkit-box-orient:horizontal !important;-webkit-box-direction:reverse !important;-ms-flex-direction:row-reverse !important;flex-direction:row-reverse !important}.flex-column-reverse{-webkit-box-orient:vertical !important;-webkit-box-direction:reverse !important;-ms-flex-direction:column-reverse !important;flex-direction:column-reverse !important}.flex-wrap{-ms-flex-wrap:wrap !important;flex-wrap:wrap !important}.flex-nowrap{-ms-flex-wrap:nowrap !important;flex-wrap:nowrap !important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse !important;flex-wrap:wrap-reverse !important}.flex-fill{-webkit-box-flex:1 !important;-ms-flex:1 1 auto !important;flex:1 1 auto !important}.flex-grow-0{-webkit-box-flex:0 !important;-ms-flex-positive:0 !important;flex-grow:0 !important}.flex-grow-1{-webkit-box-flex:1 !important;-ms-flex-positive:1 !important;flex-grow:1 !important}.flex-shrink-0{-ms-flex-negative:0 !important;flex-shrink:0 !important}.flex-shrink-1{-ms-flex-negative:1 !important;flex-shrink:1 !important}.justify-content-start{-webkit-box-pack:start !important;-ms-flex-pack:start !important;justify-content:flex-start !important}.justify-content-end{-webkit-box-pack:end !important;-ms-flex-pack:end !important;justify-content:flex-end !important}.justify-content-center{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}.justify-content-between{-webkit-box-pack:justify !important;-ms-flex-pack:justify !important;justify-content:space-between !important}.justify-content-around{-ms-flex-pack:distribute !important;justify-content:space-around !important}.align-items-start{-webkit-box-align:start !important;-ms-flex-align:start !important;align-items:flex-start !important}.align-items-end{-webkit-box-align:end !important;-ms-flex-align:end !important;align-items:flex-end !important}.align-items-center{-webkit-box-align:center !important;-ms-flex-align:center !important;align-items:center !important}.align-items-baseline{-webkit-box-align:baseline !important;-ms-flex-align:baseline !important;align-items:baseline !important}.align-items-stretch{-webkit-box-align:stretch !important;-ms-flex-align:stretch !important;align-items:stretch !important}.align-content-start{-ms-flex-line-pack:start !important;align-content:flex-start !important}.align-content-end{-ms-flex-line-pack:end !important;align-content:flex-end !important}.align-content-center{-ms-flex-line-pack:center !important;align-content:center !important}.align-content-between{-ms-flex-line-pack:justify !important;align-content:space-between !important}.align-content-around{-ms-flex-line-pack:distribute !important;align-content:space-around !important}.align-content-stretch{-ms-flex-line-pack:stretch !important;align-content:stretch !important}.align-self-auto{-ms-flex-item-align:auto !important;align-self:auto !important}.align-self-start{-ms-flex-item-align:start !important;align-self:flex-start !important}.align-self-end{-ms-flex-item-align:end !important;align-self:flex-end !important}.align-self-center{-ms-flex-item-align:center !important;align-self:center !important}.align-self-baseline{-ms-flex-item-align:baseline !important;align-self:baseline !important}.align-self-stretch{-ms-flex-item-align:stretch !important;align-self:stretch !important}@media (min-width: 576px){.flex-sm-row{-webkit-box-orient:horizontal !important;-webkit-box-direction:normal !important;-ms-flex-direction:row !important;flex-direction:row !important}.flex-sm-column{-webkit-box-orient:vertical !important;-webkit-box-direction:normal !important;-ms-flex-direction:column !important;flex-direction:column !important}.flex-sm-row-reverse{-webkit-box-orient:horizontal !important;-webkit-box-direction:reverse !important;-ms-flex-direction:row-reverse !important;flex-direction:row-reverse !important}.flex-sm-column-reverse{-webkit-box-orient:vertical !important;-webkit-box-direction:reverse !important;-ms-flex-direction:column-reverse !important;flex-direction:column-reverse !important}.flex-sm-wrap{-ms-flex-wrap:wrap !important;flex-wrap:wrap !important}.flex-sm-nowrap{-ms-flex-wrap:nowrap !important;flex-wrap:nowrap !important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse !important;flex-wrap:wrap-reverse !important}.flex-sm-fill{-webkit-box-flex:1 !important;-ms-flex:1 1 auto !important;flex:1 1 auto !important}.flex-sm-grow-0{-webkit-box-flex:0 !important;-ms-flex-positive:0 !important;flex-grow:0 !important}.flex-sm-grow-1{-webkit-box-flex:1 !important;-ms-flex-positive:1 !important;flex-grow:1 !important}.flex-sm-shrink-0{-ms-flex-negative:0 !important;flex-shrink:0 !important}.flex-sm-shrink-1{-ms-flex-negative:1 !important;flex-shrink:1 !important}.justify-content-sm-start{-webkit-box-pack:start !important;-ms-flex-pack:start !important;justify-content:flex-start !important}.justify-content-sm-end{-webkit-box-pack:end !important;-ms-flex-pack:end !important;justify-content:flex-end !important}.justify-content-sm-center{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}.justify-content-sm-between{-webkit-box-pack:justify !important;-ms-flex-pack:justify !important;justify-content:space-between !important}.justify-content-sm-around{-ms-flex-pack:distribute !important;justify-content:space-around !important}.align-items-sm-start{-webkit-box-align:start !important;-ms-flex-align:start !important;align-items:flex-start !important}.align-items-sm-end{-webkit-box-align:end !important;-ms-flex-align:end !important;align-items:flex-end !important}.align-items-sm-center{-webkit-box-align:center !important;-ms-flex-align:center !important;align-items:center !important}.align-items-sm-baseline{-webkit-box-align:baseline !important;-ms-flex-align:baseline !important;align-items:baseline !important}.align-items-sm-stretch{-webkit-box-align:stretch !important;-ms-flex-align:stretch !important;align-items:stretch !important}.align-content-sm-start{-ms-flex-line-pack:start !important;align-content:flex-start !important}.align-content-sm-end{-ms-flex-line-pack:end !important;align-content:flex-end !important}.align-content-sm-center{-ms-flex-line-pack:center !important;align-content:center !important}.align-content-sm-between{-ms-flex-line-pack:justify !important;align-content:space-between !important}.align-content-sm-around{-ms-flex-line-pack:distribute !important;align-content:space-around !important}.align-content-sm-stretch{-ms-flex-line-pack:stretch !important;align-content:stretch !important}.align-self-sm-auto{-ms-flex-item-align:auto !important;align-self:auto !important}.align-self-sm-start{-ms-flex-item-align:start !important;align-self:flex-start !important}.align-self-sm-end{-ms-flex-item-align:end !important;align-self:flex-end !important}.align-self-sm-center{-ms-flex-item-align:center !important;align-self:center !important}.align-self-sm-baseline{-ms-flex-item-align:baseline !important;align-self:baseline !important}.align-self-sm-stretch{-ms-flex-item-align:stretch !important;align-self:stretch !important}}@media (min-width: 768px){.flex-md-row{-webkit-box-orient:horizontal !important;-webkit-box-direction:normal !important;-ms-flex-direction:row !important;flex-direction:row !important}.flex-md-column{-webkit-box-orient:vertical !important;-webkit-box-direction:normal !important;-ms-flex-direction:column !important;flex-direction:column !important}.flex-md-row-reverse{-webkit-box-orient:horizontal !important;-webkit-box-direction:reverse !important;-ms-flex-direction:row-reverse !important;flex-direction:row-reverse !important}.flex-md-column-reverse{-webkit-box-orient:vertical !important;-webkit-box-direction:reverse !important;-ms-flex-direction:column-reverse !important;flex-direction:column-reverse !important}.flex-md-wrap{-ms-flex-wrap:wrap !important;flex-wrap:wrap !important}.flex-md-nowrap{-ms-flex-wrap:nowrap !important;flex-wrap:nowrap !important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse !important;flex-wrap:wrap-reverse !important}.flex-md-fill{-webkit-box-flex:1 !important;-ms-flex:1 1 auto !important;flex:1 1 auto !important}.flex-md-grow-0{-webkit-box-flex:0 !important;-ms-flex-positive:0 !important;flex-grow:0 !important}.flex-md-grow-1{-webkit-box-flex:1 !important;-ms-flex-positive:1 !important;flex-grow:1 !important}.flex-md-shrink-0{-ms-flex-negative:0 !important;flex-shrink:0 !important}.flex-md-shrink-1{-ms-flex-negative:1 !important;flex-shrink:1 !important}.justify-content-md-start{-webkit-box-pack:start !important;-ms-flex-pack:start !important;justify-content:flex-start !important}.justify-content-md-end{-webkit-box-pack:end !important;-ms-flex-pack:end !important;justify-content:flex-end !important}.justify-content-md-center{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}.justify-content-md-between{-webkit-box-pack:justify !important;-ms-flex-pack:justify !important;justify-content:space-between !important}.justify-content-md-around{-ms-flex-pack:distribute !important;justify-content:space-around !important}.align-items-md-start{-webkit-box-align:start !important;-ms-flex-align:start !important;align-items:flex-start !important}.align-items-md-end{-webkit-box-align:end !important;-ms-flex-align:end !important;align-items:flex-end !important}.align-items-md-center{-webkit-box-align:center !important;-ms-flex-align:center !important;align-items:center !important}.align-items-md-baseline{-webkit-box-align:baseline !important;-ms-flex-align:baseline !important;align-items:baseline !important}.align-items-md-stretch{-webkit-box-align:stretch !important;-ms-flex-align:stretch !important;align-items:stretch !important}.align-content-md-start{-ms-flex-line-pack:start !important;align-content:flex-start !important}.align-content-md-end{-ms-flex-line-pack:end !important;align-content:flex-end !important}.align-content-md-center{-ms-flex-line-pack:center !important;align-content:center !important}.align-content-md-between{-ms-flex-line-pack:justify !important;align-content:space-between !important}.align-content-md-around{-ms-flex-line-pack:distribute !important;align-content:space-around !important}.align-content-md-stretch{-ms-flex-line-pack:stretch !important;align-content:stretch !important}.align-self-md-auto{-ms-flex-item-align:auto !important;align-self:auto !important}.align-self-md-start{-ms-flex-item-align:start !important;align-self:flex-start !important}.align-self-md-end{-ms-flex-item-align:end !important;align-self:flex-end !important}.align-self-md-center{-ms-flex-item-align:center !important;align-self:center !important}.align-self-md-baseline{-ms-flex-item-align:baseline !important;align-self:baseline !important}.align-self-md-stretch{-ms-flex-item-align:stretch !important;align-self:stretch !important}}@media (min-width: 992px){.flex-lg-row{-webkit-box-orient:horizontal !important;-webkit-box-direction:normal !important;-ms-flex-direction:row !important;flex-direction:row !important}.flex-lg-column{-webkit-box-orient:vertical !important;-webkit-box-direction:normal !important;-ms-flex-direction:column !important;flex-direction:column !important}.flex-lg-row-reverse{-webkit-box-orient:horizontal !important;-webkit-box-direction:reverse !important;-ms-flex-direction:row-reverse !important;flex-direction:row-reverse !important}.flex-lg-column-reverse{-webkit-box-orient:vertical !important;-webkit-box-direction:reverse !important;-ms-flex-direction:column-reverse !important;flex-direction:column-reverse !important}.flex-lg-wrap{-ms-flex-wrap:wrap !important;flex-wrap:wrap !important}.flex-lg-nowrap{-ms-flex-wrap:nowrap !important;flex-wrap:nowrap !important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse !important;flex-wrap:wrap-reverse !important}.flex-lg-fill{-webkit-box-flex:1 !important;-ms-flex:1 1 auto !important;flex:1 1 auto !important}.flex-lg-grow-0{-webkit-box-flex:0 !important;-ms-flex-positive:0 !important;flex-grow:0 !important}.flex-lg-grow-1{-webkit-box-flex:1 !important;-ms-flex-positive:1 !important;flex-grow:1 !important}.flex-lg-shrink-0{-ms-flex-negative:0 !important;flex-shrink:0 !important}.flex-lg-shrink-1{-ms-flex-negative:1 !important;flex-shrink:1 !important}.justify-content-lg-start{-webkit-box-pack:start !important;-ms-flex-pack:start !important;justify-content:flex-start !important}.justify-content-lg-end{-webkit-box-pack:end !important;-ms-flex-pack:end !important;justify-content:flex-end !important}.justify-content-lg-center{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}.justify-content-lg-between{-webkit-box-pack:justify !important;-ms-flex-pack:justify !important;justify-content:space-between !important}.justify-content-lg-around{-ms-flex-pack:distribute !important;justify-content:space-around !important}.align-items-lg-start{-webkit-box-align:start !important;-ms-flex-align:start !important;align-items:flex-start !important}.align-items-lg-end{-webkit-box-align:end !important;-ms-flex-align:end !important;align-items:flex-end !important}.align-items-lg-center{-webkit-box-align:center !important;-ms-flex-align:center !important;align-items:center !important}.align-items-lg-baseline{-webkit-box-align:baseline !important;-ms-flex-align:baseline !important;align-items:baseline !important}.align-items-lg-stretch{-webkit-box-align:stretch !important;-ms-flex-align:stretch !important;align-items:stretch !important}.align-content-lg-start{-ms-flex-line-pack:start !important;align-content:flex-start !important}.align-content-lg-end{-ms-flex-line-pack:end !important;align-content:flex-end !important}.align-content-lg-center{-ms-flex-line-pack:center !important;align-content:center !important}.align-content-lg-between{-ms-flex-line-pack:justify !important;align-content:space-between !important}.align-content-lg-around{-ms-flex-line-pack:distribute !important;align-content:space-around !important}.align-content-lg-stretch{-ms-flex-line-pack:stretch !important;align-content:stretch !important}.align-self-lg-auto{-ms-flex-item-align:auto !important;align-self:auto !important}.align-self-lg-start{-ms-flex-item-align:start !important;align-self:flex-start !important}.align-self-lg-end{-ms-flex-item-align:end !important;align-self:flex-end !important}.align-self-lg-center{-ms-flex-item-align:center !important;align-self:center !important}.align-self-lg-baseline{-ms-flex-item-align:baseline !important;align-self:baseline !important}.align-self-lg-stretch{-ms-flex-item-align:stretch !important;align-self:stretch !important}}@media (min-width: 1200px){.flex-xl-row{-webkit-box-orient:horizontal !important;-webkit-box-direction:normal !important;-ms-flex-direction:row !important;flex-direction:row !important}.flex-xl-column{-webkit-box-orient:vertical !important;-webkit-box-direction:normal !important;-ms-flex-direction:column !important;flex-direction:column !important}.flex-xl-row-reverse{-webkit-box-orient:horizontal !important;-webkit-box-direction:reverse !important;-ms-flex-direction:row-reverse !important;flex-direction:row-reverse !important}.flex-xl-column-reverse{-webkit-box-orient:vertical !important;-webkit-box-direction:reverse !important;-ms-flex-direction:column-reverse !important;flex-direction:column-reverse !important}.flex-xl-wrap{-ms-flex-wrap:wrap !important;flex-wrap:wrap !important}.flex-xl-nowrap{-ms-flex-wrap:nowrap !important;flex-wrap:nowrap !important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse !important;flex-wrap:wrap-reverse !important}.flex-xl-fill{-webkit-box-flex:1 !important;-ms-flex:1 1 auto !important;flex:1 1 auto !important}.flex-xl-grow-0{-webkit-box-flex:0 !important;-ms-flex-positive:0 !important;flex-grow:0 !important}.flex-xl-grow-1{-webkit-box-flex:1 !important;-ms-flex-positive:1 !important;flex-grow:1 !important}.flex-xl-shrink-0{-ms-flex-negative:0 !important;flex-shrink:0 !important}.flex-xl-shrink-1{-ms-flex-negative:1 !important;flex-shrink:1 !important}.justify-content-xl-start{-webkit-box-pack:start !important;-ms-flex-pack:start !important;justify-content:flex-start !important}.justify-content-xl-end{-webkit-box-pack:end !important;-ms-flex-pack:end !important;justify-content:flex-end !important}.justify-content-xl-center{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}.justify-content-xl-between{-webkit-box-pack:justify !important;-ms-flex-pack:justify !important;justify-content:space-between !important}.justify-content-xl-around{-ms-flex-pack:distribute !important;justify-content:space-around !important}.align-items-xl-start{-webkit-box-align:start !important;-ms-flex-align:start !important;align-items:flex-start !important}.align-items-xl-end{-webkit-box-align:end !important;-ms-flex-align:end !important;align-items:flex-end !important}.align-items-xl-center{-webkit-box-align:center !important;-ms-flex-align:center !important;align-items:center !important}.align-items-xl-baseline{-webkit-box-align:baseline !important;-ms-flex-align:baseline !important;align-items:baseline !important}.align-items-xl-stretch{-webkit-box-align:stretch !important;-ms-flex-align:stretch !important;align-items:stretch !important}.align-content-xl-start{-ms-flex-line-pack:start !important;align-content:flex-start !important}.align-content-xl-end{-ms-flex-line-pack:end !important;align-content:flex-end !important}.align-content-xl-center{-ms-flex-line-pack:center !important;align-content:center !important}.align-content-xl-between{-ms-flex-line-pack:justify !important;align-content:space-between !important}.align-content-xl-around{-ms-flex-line-pack:distribute !important;align-content:space-around !important}.align-content-xl-stretch{-ms-flex-line-pack:stretch !important;align-content:stretch !important}.align-self-xl-auto{-ms-flex-item-align:auto !important;align-self:auto !important}.align-self-xl-start{-ms-flex-item-align:start !important;align-self:flex-start !important}.align-self-xl-end{-ms-flex-item-align:end !important;align-self:flex-end !important}.align-self-xl-center{-ms-flex-item-align:center !important;align-self:center !important}.align-self-xl-baseline{-ms-flex-item-align:baseline !important;align-self:baseline !important}.align-self-xl-stretch{-ms-flex-item-align:stretch !important;align-self:stretch !important}}.float-left{float:left !important}.float-right{float:right !important}.float-none{float:none !important}@media (min-width: 576px){.float-sm-left{float:left !important}.float-sm-right{float:right !important}.float-sm-none{float:none !important}}@media (min-width: 768px){.float-md-left{float:left !important}.float-md-right{float:right !important}.float-md-none{float:none !important}}@media (min-width: 992px){.float-lg-left{float:left !important}.float-lg-right{float:right !important}.float-lg-none{float:none !important}}@media (min-width: 1200px){.float-xl-left{float:left !important}.float-xl-right{float:right !important}.float-xl-none{float:none !important}}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:-webkit-sticky !important;position:sticky !important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports (position: -webkit-sticky) or (position: sticky){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{-webkit-box-shadow:0 0.125rem 0.25rem rgba(0,0,0,0.075) !important;box-shadow:0 0.125rem 0.25rem rgba(0,0,0,0.075) !important}.shadow{-webkit-box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15) !important;box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15) !important}.shadow-lg{-webkit-box-shadow:0 1rem 3rem rgba(0,0,0,0.175) !important;box-shadow:0 1rem 3rem rgba(0,0,0,0.175) !important}.shadow-none{-webkit-box-shadow:none !important;box-shadow:none !important}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mw-100{max-width:100% !important}.mh-100{max-height:100% !important}.min-vw-100{min-width:100vw !important}.min-vh-100{min-height:100vh !important}.vw-100{width:100vw !important}.vh-100{height:100vh !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.m-0{margin:0 !important}.mt-0,.my-0{margin-top:0 !important}.mr-0,.mx-0{margin-right:0 !important}.mb-0,.my-0{margin-bottom:0 !important}.ml-0,.mx-0{margin-left:0 !important}.m-1{margin:0.25rem !important}.mt-1,.my-1{margin-top:0.25rem !important}.mr-1,.mx-1{margin-right:0.25rem !important}.mb-1,.my-1{margin-bottom:0.25rem !important}.ml-1,.mx-1{margin-left:0.25rem !important}.m-2{margin:0.5rem !important}.mt-2,.my-2{margin-top:0.5rem !important}.mr-2,.mx-2{margin-right:0.5rem !important}.mb-2,.my-2{margin-bottom:0.5rem !important}.ml-2,.mx-2{margin-left:0.5rem !important}.m-3{margin:1rem !important}.mt-3,.my-3{margin-top:1rem !important}.mr-3,.mx-3{margin-right:1rem !important}.mb-3,.my-3{margin-bottom:1rem !important}.ml-3,.mx-3{margin-left:1rem !important}.m-4{margin:1.5rem !important}.mt-4,.my-4{margin-top:1.5rem !important}.mr-4,.mx-4{margin-right:1.5rem !important}.mb-4,.my-4{margin-bottom:1.5rem !important}.ml-4,.mx-4{margin-left:1.5rem !important}.m-5{margin:3rem !important}.mt-5,.my-5{margin-top:3rem !important}.mr-5,.mx-5{margin-right:3rem !important}.mb-5,.my-5{margin-bottom:3rem !important}.ml-5,.mx-5{margin-left:3rem !important}.p-0{padding:0 !important}.pt-0,.py-0{padding-top:0 !important}.pr-0,.px-0{padding-right:0 !important}.pb-0,.py-0{padding-bottom:0 !important}.pl-0,.px-0{padding-left:0 !important}.p-1{padding:0.25rem !important}.pt-1,.py-1{padding-top:0.25rem !important}.pr-1,.px-1{padding-right:0.25rem !important}.pb-1,.py-1{padding-bottom:0.25rem !important}.pl-1,.px-1{padding-left:0.25rem !important}.p-2{padding:0.5rem !important}.pt-2,.py-2{padding-top:0.5rem !important}.pr-2,.px-2{padding-right:0.5rem !important}.pb-2,.py-2{padding-bottom:0.5rem !important}.pl-2,.px-2{padding-left:0.5rem !important}.p-3{padding:1rem !important}.pt-3,.py-3{padding-top:1rem !important}.pr-3,.px-3{padding-right:1rem !important}.pb-3,.py-3{padding-bottom:1rem !important}.pl-3,.px-3{padding-left:1rem !important}.p-4{padding:1.5rem !important}.pt-4,.py-4{padding-top:1.5rem !important}.pr-4,.px-4{padding-right:1.5rem !important}.pb-4,.py-4{padding-bottom:1.5rem !important}.pl-4,.px-4{padding-left:1.5rem !important}.p-5{padding:3rem !important}.pt-5,.py-5{padding-top:3rem !important}.pr-5,.px-5{padding-right:3rem !important}.pb-5,.py-5{padding-bottom:3rem !important}.pl-5,.px-5{padding-left:3rem !important}.m-n1{margin:-0.25rem !important}.mt-n1,.my-n1{margin-top:-0.25rem !important}.mr-n1,.mx-n1{margin-right:-0.25rem !important}.mb-n1,.my-n1{margin-bottom:-0.25rem !important}.ml-n1,.mx-n1{margin-left:-0.25rem !important}.m-n2{margin:-0.5rem !important}.mt-n2,.my-n2{margin-top:-0.5rem !important}.mr-n2,.mx-n2{margin-right:-0.5rem !important}.mb-n2,.my-n2{margin-bottom:-0.5rem !important}.ml-n2,.mx-n2{margin-left:-0.5rem !important}.m-n3{margin:-1rem !important}.mt-n3,.my-n3{margin-top:-1rem !important}.mr-n3,.mx-n3{margin-right:-1rem !important}.mb-n3,.my-n3{margin-bottom:-1rem !important}.ml-n3,.mx-n3{margin-left:-1rem !important}.m-n4{margin:-1.5rem !important}.mt-n4,.my-n4{margin-top:-1.5rem !important}.mr-n4,.mx-n4{margin-right:-1.5rem !important}.mb-n4,.my-n4{margin-bottom:-1.5rem !important}.ml-n4,.mx-n4{margin-left:-1.5rem !important}.m-n5{margin:-3rem !important}.mt-n5,.my-n5{margin-top:-3rem !important}.mr-n5,.mx-n5{margin-right:-3rem !important}.mb-n5,.my-n5{margin-bottom:-3rem !important}.ml-n5,.mx-n5{margin-left:-3rem !important}.m-auto{margin:auto !important}.mt-auto,.my-auto{margin-top:auto !important}.mr-auto,.mx-auto{margin-right:auto !important}.mb-auto,.my-auto{margin-bottom:auto !important}.ml-auto,.mx-auto{margin-left:auto !important}@media (min-width: 576px){.m-sm-0{margin:0 !important}.mt-sm-0,.my-sm-0{margin-top:0 !important}.mr-sm-0,.mx-sm-0{margin-right:0 !important}.mb-sm-0,.my-sm-0{margin-bottom:0 !important}.ml-sm-0,.mx-sm-0{margin-left:0 !important}.m-sm-1{margin:0.25rem !important}.mt-sm-1,.my-sm-1{margin-top:0.25rem !important}.mr-sm-1,.mx-sm-1{margin-right:0.25rem !important}.mb-sm-1,.my-sm-1{margin-bottom:0.25rem !important}.ml-sm-1,.mx-sm-1{margin-left:0.25rem !important}.m-sm-2{margin:0.5rem !important}.mt-sm-2,.my-sm-2{margin-top:0.5rem !important}.mr-sm-2,.mx-sm-2{margin-right:0.5rem !important}.mb-sm-2,.my-sm-2{margin-bottom:0.5rem !important}.ml-sm-2,.mx-sm-2{margin-left:0.5rem !important}.m-sm-3{margin:1rem !important}.mt-sm-3,.my-sm-3{margin-top:1rem !important}.mr-sm-3,.mx-sm-3{margin-right:1rem !important}.mb-sm-3,.my-sm-3{margin-bottom:1rem !important}.ml-sm-3,.mx-sm-3{margin-left:1rem !important}.m-sm-4{margin:1.5rem !important}.mt-sm-4,.my-sm-4{margin-top:1.5rem !important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem !important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem !important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem !important}.m-sm-5{margin:3rem !important}.mt-sm-5,.my-sm-5{margin-top:3rem !important}.mr-sm-5,.mx-sm-5{margin-right:3rem !important}.mb-sm-5,.my-sm-5{margin-bottom:3rem !important}.ml-sm-5,.mx-sm-5{margin-left:3rem !important}.p-sm-0{padding:0 !important}.pt-sm-0,.py-sm-0{padding-top:0 !important}.pr-sm-0,.px-sm-0{padding-right:0 !important}.pb-sm-0,.py-sm-0{padding-bottom:0 !important}.pl-sm-0,.px-sm-0{padding-left:0 !important}.p-sm-1{padding:0.25rem !important}.pt-sm-1,.py-sm-1{padding-top:0.25rem !important}.pr-sm-1,.px-sm-1{padding-right:0.25rem !important}.pb-sm-1,.py-sm-1{padding-bottom:0.25rem !important}.pl-sm-1,.px-sm-1{padding-left:0.25rem !important}.p-sm-2{padding:0.5rem !important}.pt-sm-2,.py-sm-2{padding-top:0.5rem !important}.pr-sm-2,.px-sm-2{padding-right:0.5rem !important}.pb-sm-2,.py-sm-2{padding-bottom:0.5rem !important}.pl-sm-2,.px-sm-2{padding-left:0.5rem !important}.p-sm-3{padding:1rem !important}.pt-sm-3,.py-sm-3{padding-top:1rem !important}.pr-sm-3,.px-sm-3{padding-right:1rem !important}.pb-sm-3,.py-sm-3{padding-bottom:1rem !important}.pl-sm-3,.px-sm-3{padding-left:1rem !important}.p-sm-4{padding:1.5rem !important}.pt-sm-4,.py-sm-4{padding-top:1.5rem !important}.pr-sm-4,.px-sm-4{padding-right:1.5rem !important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem !important}.pl-sm-4,.px-sm-4{padding-left:1.5rem !important}.p-sm-5{padding:3rem !important}.pt-sm-5,.py-sm-5{padding-top:3rem !important}.pr-sm-5,.px-sm-5{padding-right:3rem !important}.pb-sm-5,.py-sm-5{padding-bottom:3rem !important}.pl-sm-5,.px-sm-5{padding-left:3rem !important}.m-sm-n1{margin:-0.25rem !important}.mt-sm-n1,.my-sm-n1{margin-top:-0.25rem !important}.mr-sm-n1,.mx-sm-n1{margin-right:-0.25rem !important}.mb-sm-n1,.my-sm-n1{margin-bottom:-0.25rem !important}.ml-sm-n1,.mx-sm-n1{margin-left:-0.25rem !important}.m-sm-n2{margin:-0.5rem !important}.mt-sm-n2,.my-sm-n2{margin-top:-0.5rem !important}.mr-sm-n2,.mx-sm-n2{margin-right:-0.5rem !important}.mb-sm-n2,.my-sm-n2{margin-bottom:-0.5rem !important}.ml-sm-n2,.mx-sm-n2{margin-left:-0.5rem !important}.m-sm-n3{margin:-1rem !important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem !important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem !important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem !important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem !important}.m-sm-n4{margin:-1.5rem !important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem !important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem !important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem !important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem !important}.m-sm-n5{margin:-3rem !important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem !important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem !important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem !important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem !important}.m-sm-auto{margin:auto !important}.mt-sm-auto,.my-sm-auto{margin-top:auto !important}.mr-sm-auto,.mx-sm-auto{margin-right:auto !important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto !important}.ml-sm-auto,.mx-sm-auto{margin-left:auto !important}}@media (min-width: 768px){.m-md-0{margin:0 !important}.mt-md-0,.my-md-0{margin-top:0 !important}.mr-md-0,.mx-md-0{margin-right:0 !important}.mb-md-0,.my-md-0{margin-bottom:0 !important}.ml-md-0,.mx-md-0{margin-left:0 !important}.m-md-1{margin:0.25rem !important}.mt-md-1,.my-md-1{margin-top:0.25rem !important}.mr-md-1,.mx-md-1{margin-right:0.25rem !important}.mb-md-1,.my-md-1{margin-bottom:0.25rem !important}.ml-md-1,.mx-md-1{margin-left:0.25rem !important}.m-md-2{margin:0.5rem !important}.mt-md-2,.my-md-2{margin-top:0.5rem !important}.mr-md-2,.mx-md-2{margin-right:0.5rem !important}.mb-md-2,.my-md-2{margin-bottom:0.5rem !important}.ml-md-2,.mx-md-2{margin-left:0.5rem !important}.m-md-3{margin:1rem !important}.mt-md-3,.my-md-3{margin-top:1rem !important}.mr-md-3,.mx-md-3{margin-right:1rem !important}.mb-md-3,.my-md-3{margin-bottom:1rem !important}.ml-md-3,.mx-md-3{margin-left:1rem !important}.m-md-4{margin:1.5rem !important}.mt-md-4,.my-md-4{margin-top:1.5rem !important}.mr-md-4,.mx-md-4{margin-right:1.5rem !important}.mb-md-4,.my-md-4{margin-bottom:1.5rem !important}.ml-md-4,.mx-md-4{margin-left:1.5rem !important}.m-md-5{margin:3rem !important}.mt-md-5,.my-md-5{margin-top:3rem !important}.mr-md-5,.mx-md-5{margin-right:3rem !important}.mb-md-5,.my-md-5{margin-bottom:3rem !important}.ml-md-5,.mx-md-5{margin-left:3rem !important}.p-md-0{padding:0 !important}.pt-md-0,.py-md-0{padding-top:0 !important}.pr-md-0,.px-md-0{padding-right:0 !important}.pb-md-0,.py-md-0{padding-bottom:0 !important}.pl-md-0,.px-md-0{padding-left:0 !important}.p-md-1{padding:0.25rem !important}.pt-md-1,.py-md-1{padding-top:0.25rem !important}.pr-md-1,.px-md-1{padding-right:0.25rem !important}.pb-md-1,.py-md-1{padding-bottom:0.25rem !important}.pl-md-1,.px-md-1{padding-left:0.25rem !important}.p-md-2{padding:0.5rem !important}.pt-md-2,.py-md-2{padding-top:0.5rem !important}.pr-md-2,.px-md-2{padding-right:0.5rem !important}.pb-md-2,.py-md-2{padding-bottom:0.5rem !important}.pl-md-2,.px-md-2{padding-left:0.5rem !important}.p-md-3{padding:1rem !important}.pt-md-3,.py-md-3{padding-top:1rem !important}.pr-md-3,.px-md-3{padding-right:1rem !important}.pb-md-3,.py-md-3{padding-bottom:1rem !important}.pl-md-3,.px-md-3{padding-left:1rem !important}.p-md-4{padding:1.5rem !important}.pt-md-4,.py-md-4{padding-top:1.5rem !important}.pr-md-4,.px-md-4{padding-right:1.5rem !important}.pb-md-4,.py-md-4{padding-bottom:1.5rem !important}.pl-md-4,.px-md-4{padding-left:1.5rem !important}.p-md-5{padding:3rem !important}.pt-md-5,.py-md-5{padding-top:3rem !important}.pr-md-5,.px-md-5{padding-right:3rem !important}.pb-md-5,.py-md-5{padding-bottom:3rem !important}.pl-md-5,.px-md-5{padding-left:3rem !important}.m-md-n1{margin:-0.25rem !important}.mt-md-n1,.my-md-n1{margin-top:-0.25rem !important}.mr-md-n1,.mx-md-n1{margin-right:-0.25rem !important}.mb-md-n1,.my-md-n1{margin-bottom:-0.25rem !important}.ml-md-n1,.mx-md-n1{margin-left:-0.25rem !important}.m-md-n2{margin:-0.5rem !important}.mt-md-n2,.my-md-n2{margin-top:-0.5rem !important}.mr-md-n2,.mx-md-n2{margin-right:-0.5rem !important}.mb-md-n2,.my-md-n2{margin-bottom:-0.5rem !important}.ml-md-n2,.mx-md-n2{margin-left:-0.5rem !important}.m-md-n3{margin:-1rem !important}.mt-md-n3,.my-md-n3{margin-top:-1rem !important}.mr-md-n3,.mx-md-n3{margin-right:-1rem !important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem !important}.ml-md-n3,.mx-md-n3{margin-left:-1rem !important}.m-md-n4{margin:-1.5rem !important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem !important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem !important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem !important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem !important}.m-md-n5{margin:-3rem !important}.mt-md-n5,.my-md-n5{margin-top:-3rem !important}.mr-md-n5,.mx-md-n5{margin-right:-3rem !important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem !important}.ml-md-n5,.mx-md-n5{margin-left:-3rem !important}.m-md-auto{margin:auto !important}.mt-md-auto,.my-md-auto{margin-top:auto !important}.mr-md-auto,.mx-md-auto{margin-right:auto !important}.mb-md-auto,.my-md-auto{margin-bottom:auto !important}.ml-md-auto,.mx-md-auto{margin-left:auto !important}}@media (min-width: 992px){.m-lg-0{margin:0 !important}.mt-lg-0,.my-lg-0{margin-top:0 !important}.mr-lg-0,.mx-lg-0{margin-right:0 !important}.mb-lg-0,.my-lg-0{margin-bottom:0 !important}.ml-lg-0,.mx-lg-0{margin-left:0 !important}.m-lg-1{margin:0.25rem !important}.mt-lg-1,.my-lg-1{margin-top:0.25rem !important}.mr-lg-1,.mx-lg-1{margin-right:0.25rem !important}.mb-lg-1,.my-lg-1{margin-bottom:0.25rem !important}.ml-lg-1,.mx-lg-1{margin-left:0.25rem !important}.m-lg-2{margin:0.5rem !important}.mt-lg-2,.my-lg-2{margin-top:0.5rem !important}.mr-lg-2,.mx-lg-2{margin-right:0.5rem !important}.mb-lg-2,.my-lg-2{margin-bottom:0.5rem !important}.ml-lg-2,.mx-lg-2{margin-left:0.5rem !important}.m-lg-3{margin:1rem !important}.mt-lg-3,.my-lg-3{margin-top:1rem !important}.mr-lg-3,.mx-lg-3{margin-right:1rem !important}.mb-lg-3,.my-lg-3{margin-bottom:1rem !important}.ml-lg-3,.mx-lg-3{margin-left:1rem !important}.m-lg-4{margin:1.5rem !important}.mt-lg-4,.my-lg-4{margin-top:1.5rem !important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem !important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem !important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem !important}.m-lg-5{margin:3rem !important}.mt-lg-5,.my-lg-5{margin-top:3rem !important}.mr-lg-5,.mx-lg-5{margin-right:3rem !important}.mb-lg-5,.my-lg-5{margin-bottom:3rem !important}.ml-lg-5,.mx-lg-5{margin-left:3rem !important}.p-lg-0{padding:0 !important}.pt-lg-0,.py-lg-0{padding-top:0 !important}.pr-lg-0,.px-lg-0{padding-right:0 !important}.pb-lg-0,.py-lg-0{padding-bottom:0 !important}.pl-lg-0,.px-lg-0{padding-left:0 !important}.p-lg-1{padding:0.25rem !important}.pt-lg-1,.py-lg-1{padding-top:0.25rem !important}.pr-lg-1,.px-lg-1{padding-right:0.25rem !important}.pb-lg-1,.py-lg-1{padding-bottom:0.25rem !important}.pl-lg-1,.px-lg-1{padding-left:0.25rem !important}.p-lg-2{padding:0.5rem !important}.pt-lg-2,.py-lg-2{padding-top:0.5rem !important}.pr-lg-2,.px-lg-2{padding-right:0.5rem !important}.pb-lg-2,.py-lg-2{padding-bottom:0.5rem !important}.pl-lg-2,.px-lg-2{padding-left:0.5rem !important}.p-lg-3{padding:1rem !important}.pt-lg-3,.py-lg-3{padding-top:1rem !important}.pr-lg-3,.px-lg-3{padding-right:1rem !important}.pb-lg-3,.py-lg-3{padding-bottom:1rem !important}.pl-lg-3,.px-lg-3{padding-left:1rem !important}.p-lg-4{padding:1.5rem !important}.pt-lg-4,.py-lg-4{padding-top:1.5rem !important}.pr-lg-4,.px-lg-4{padding-right:1.5rem !important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem !important}.pl-lg-4,.px-lg-4{padding-left:1.5rem !important}.p-lg-5{padding:3rem !important}.pt-lg-5,.py-lg-5{padding-top:3rem !important}.pr-lg-5,.px-lg-5{padding-right:3rem !important}.pb-lg-5,.py-lg-5{padding-bottom:3rem !important}.pl-lg-5,.px-lg-5{padding-left:3rem !important}.m-lg-n1{margin:-0.25rem !important}.mt-lg-n1,.my-lg-n1{margin-top:-0.25rem !important}.mr-lg-n1,.mx-lg-n1{margin-right:-0.25rem !important}.mb-lg-n1,.my-lg-n1{margin-bottom:-0.25rem !important}.ml-lg-n1,.mx-lg-n1{margin-left:-0.25rem !important}.m-lg-n2{margin:-0.5rem !important}.mt-lg-n2,.my-lg-n2{margin-top:-0.5rem !important}.mr-lg-n2,.mx-lg-n2{margin-right:-0.5rem !important}.mb-lg-n2,.my-lg-n2{margin-bottom:-0.5rem !important}.ml-lg-n2,.mx-lg-n2{margin-left:-0.5rem !important}.m-lg-n3{margin:-1rem !important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem !important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem !important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem !important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem !important}.m-lg-n4{margin:-1.5rem !important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem !important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem !important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem !important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem !important}.m-lg-n5{margin:-3rem !important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem !important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem !important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem !important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem !important}.m-lg-auto{margin:auto !important}.mt-lg-auto,.my-lg-auto{margin-top:auto !important}.mr-lg-auto,.mx-lg-auto{margin-right:auto !important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto !important}.ml-lg-auto,.mx-lg-auto{margin-left:auto !important}}@media (min-width: 1200px){.m-xl-0{margin:0 !important}.mt-xl-0,.my-xl-0{margin-top:0 !important}.mr-xl-0,.mx-xl-0{margin-right:0 !important}.mb-xl-0,.my-xl-0{margin-bottom:0 !important}.ml-xl-0,.mx-xl-0{margin-left:0 !important}.m-xl-1{margin:0.25rem !important}.mt-xl-1,.my-xl-1{margin-top:0.25rem !important}.mr-xl-1,.mx-xl-1{margin-right:0.25rem !important}.mb-xl-1,.my-xl-1{margin-bottom:0.25rem !important}.ml-xl-1,.mx-xl-1{margin-left:0.25rem !important}.m-xl-2{margin:0.5rem !important}.mt-xl-2,.my-xl-2{margin-top:0.5rem !important}.mr-xl-2,.mx-xl-2{margin-right:0.5rem !important}.mb-xl-2,.my-xl-2{margin-bottom:0.5rem !important}.ml-xl-2,.mx-xl-2{margin-left:0.5rem !important}.m-xl-3{margin:1rem !important}.mt-xl-3,.my-xl-3{margin-top:1rem !important}.mr-xl-3,.mx-xl-3{margin-right:1rem !important}.mb-xl-3,.my-xl-3{margin-bottom:1rem !important}.ml-xl-3,.mx-xl-3{margin-left:1rem !important}.m-xl-4{margin:1.5rem !important}.mt-xl-4,.my-xl-4{margin-top:1.5rem !important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem !important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem !important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem !important}.m-xl-5{margin:3rem !important}.mt-xl-5,.my-xl-5{margin-top:3rem !important}.mr-xl-5,.mx-xl-5{margin-right:3rem !important}.mb-xl-5,.my-xl-5{margin-bottom:3rem !important}.ml-xl-5,.mx-xl-5{margin-left:3rem !important}.p-xl-0{padding:0 !important}.pt-xl-0,.py-xl-0{padding-top:0 !important}.pr-xl-0,.px-xl-0{padding-right:0 !important}.pb-xl-0,.py-xl-0{padding-bottom:0 !important}.pl-xl-0,.px-xl-0{padding-left:0 !important}.p-xl-1{padding:0.25rem !important}.pt-xl-1,.py-xl-1{padding-top:0.25rem !important}.pr-xl-1,.px-xl-1{padding-right:0.25rem !important}.pb-xl-1,.py-xl-1{padding-bottom:0.25rem !important}.pl-xl-1,.px-xl-1{padding-left:0.25rem !important}.p-xl-2{padding:0.5rem !important}.pt-xl-2,.py-xl-2{padding-top:0.5rem !important}.pr-xl-2,.px-xl-2{padding-right:0.5rem !important}.pb-xl-2,.py-xl-2{padding-bottom:0.5rem !important}.pl-xl-2,.px-xl-2{padding-left:0.5rem !important}.p-xl-3{padding:1rem !important}.pt-xl-3,.py-xl-3{padding-top:1rem !important}.pr-xl-3,.px-xl-3{padding-right:1rem !important}.pb-xl-3,.py-xl-3{padding-bottom:1rem !important}.pl-xl-3,.px-xl-3{padding-left:1rem !important}.p-xl-4{padding:1.5rem !important}.pt-xl-4,.py-xl-4{padding-top:1.5rem !important}.pr-xl-4,.px-xl-4{padding-right:1.5rem !important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem !important}.pl-xl-4,.px-xl-4{padding-left:1.5rem !important}.p-xl-5{padding:3rem !important}.pt-xl-5,.py-xl-5{padding-top:3rem !important}.pr-xl-5,.px-xl-5{padding-right:3rem !important}.pb-xl-5,.py-xl-5{padding-bottom:3rem !important}.pl-xl-5,.px-xl-5{padding-left:3rem !important}.m-xl-n1{margin:-0.25rem !important}.mt-xl-n1,.my-xl-n1{margin-top:-0.25rem !important}.mr-xl-n1,.mx-xl-n1{margin-right:-0.25rem !important}.mb-xl-n1,.my-xl-n1{margin-bottom:-0.25rem !important}.ml-xl-n1,.mx-xl-n1{margin-left:-0.25rem !important}.m-xl-n2{margin:-0.5rem !important}.mt-xl-n2,.my-xl-n2{margin-top:-0.5rem !important}.mr-xl-n2,.mx-xl-n2{margin-right:-0.5rem !important}.mb-xl-n2,.my-xl-n2{margin-bottom:-0.5rem !important}.ml-xl-n2,.mx-xl-n2{margin-left:-0.5rem !important}.m-xl-n3{margin:-1rem !important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem !important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem !important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem !important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem !important}.m-xl-n4{margin:-1.5rem !important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem !important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem !important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem !important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem !important}.m-xl-n5{margin:-3rem !important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem !important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem !important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem !important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem !important}.m-xl-auto{margin:auto !important}.mt-xl-auto,.my-xl-auto{margin-top:auto !important}.mr-xl-auto,.mx-xl-auto{margin-right:auto !important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto !important}.ml-xl-auto,.mx-xl-auto{margin-left:auto !important}}.text-monospace{font-family:SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important}.text-justify{text-align:justify !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left !important}.text-right{text-align:right !important}.text-center{text-align:center !important}@media (min-width: 576px){.text-sm-left{text-align:left !important}.text-sm-right{text-align:right !important}.text-sm-center{text-align:center !important}}@media (min-width: 768px){.text-md-left{text-align:left !important}.text-md-right{text-align:right !important}.text-md-center{text-align:center !important}}@media (min-width: 992px){.text-lg-left{text-align:left !important}.text-lg-right{text-align:right !important}.text-lg-center{text-align:center !important}}@media (min-width: 1200px){.text-xl-left{text-align:left !important}.text-xl-right{text-align:right !important}.text-xl-center{text-align:center !important}}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.font-weight-light{font-weight:300 !important}.font-weight-lighter{font-weight:lighter !important}.font-weight-normal{font-weight:400 !important}.font-weight-bold{font-weight:700 !important}.font-weight-bolder{font-weight:bolder !important}.font-italic{font-style:italic !important}.text-white{color:#fff !important}.text-primary{color:#375a7f !important}a.text-primary:hover,a.text-primary:focus{color:#20344a !important}.text-secondary{color:#444 !important}a.text-secondary:hover,a.text-secondary:focus{color:#1e1e1e !important}.text-success{color:#00bc8c !important}a.text-success:hover,a.text-success:focus{color:#007053 !important}.text-info{color:#3498DB !important}a.text-info:hover,a.text-info:focus{color:#1d6fa5 !important}.text-warning{color:#F39C12 !important}a.text-warning:hover,a.text-warning:focus{color:#b06f09 !important}.text-danger{color:#E74C3C !important}a.text-danger:hover,a.text-danger:focus{color:#bf2718 !important}.text-light{color:#303030 !important}a.text-light:hover,a.text-light:focus{color:#0a0a0a !important}.text-dark{color:#adb5bd !important}a.text-dark:hover,a.text-dark:focus{color:#838f9b !important}.text-body{color:#fff !important}.text-muted{color:#999 !important}.text-black-50{color:rgba(0,0,0,0.5) !important}.text-white-50{color:rgba(255,255,255,0.5) !important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none !important}.text-break{word-break:break-word !important;overflow-wrap:break-word !important}.text-reset{color:inherit !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}@media print{*,*::before,*::after{text-shadow:none !important;-webkit-box-shadow:none !important;box-shadow:none !important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap !important}pre,blockquote{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px !important}.container{min-width:992px !important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #dee2e6 !important}.table-dark{color:inherit}.table-dark th,.table-dark td,.table-dark thead th,.table-dark tbody+tbody{border-color:#444}.table .thead-dark th{color:inherit;border-color:#444}}.bg-primary .navbar-nav .active>.nav-link{color:#00bc8c !important}.bg-dark{background-color:#00bc8c !important}.bg-dark.navbar-dark .navbar-nav .nav-link:focus,.bg-dark.navbar-dark .navbar-nav .nav-link:hover,.bg-dark.navbar-dark .navbar-nav .active>.nav-link{color:#375a7f !important}.blockquote-footer{color:#999}.table-primary,.table-primary>th,.table-primary>td{background-color:#375a7f}.table-secondary,.table-secondary>th,.table-secondary>td{background-color:#444}.table-light,.table-light>th,.table-light>td{background-color:#303030}.table-dark,.table-dark>th,.table-dark>td{background-color:#adb5bd}.table-success,.table-success>th,.table-success>td{background-color:#00bc8c}.table-info,.table-info>th,.table-info>td{background-color:#3498DB}.table-danger,.table-danger>th,.table-danger>td{background-color:#E74C3C}.table-warning,.table-warning>th,.table-warning>td{background-color:#F39C12}.table-active,.table-active>th,.table-active>td{background-color:rgba(0,0,0,0.075)}.table-hover .table-primary:hover,.table-hover .table-primary:hover>th,.table-hover .table-primary:hover>td{background-color:#2f4d6d}.table-hover .table-secondary:hover,.table-hover .table-secondary:hover>th,.table-hover .table-secondary:hover>td{background-color:#373737}.table-hover .table-light:hover,.table-hover .table-light:hover>th,.table-hover .table-light:hover>td{background-color:#232323}.table-hover .table-dark:hover,.table-hover .table-dark:hover>th,.table-hover .table-dark:hover>td{background-color:#9fa8b2}.table-hover .table-success:hover,.table-hover .table-success:hover>th,.table-hover .table-success:hover>td{background-color:#00a379}.table-hover .table-info:hover,.table-hover .table-info:hover>th,.table-hover .table-info:hover>td{background-color:#258cd1}.table-hover .table-danger:hover,.table-hover .table-danger:hover>th,.table-hover .table-danger:hover>td{background-color:#e43725}.table-hover .table-warning:hover,.table-hover .table-warning:hover>th,.table-hover .table-warning:hover>td{background-color:#e08e0b}.table-hover .table-active:hover,.table-hover .table-active:hover>th,.table-hover .table-active:hover>td{background-color:rgba(0,0,0,0.075)}.input-group-addon{color:#fff}.nav-tabs .nav-link,.nav-tabs .nav-link.active,.nav-tabs .nav-link.active:focus,.nav-tabs .nav-link.active:hover,.nav-tabs .nav-item.open .nav-link,.nav-tabs .nav-item.open .nav-link:focus,.nav-tabs .nav-item.open .nav-link:hover,.nav-pills .nav-link,.nav-pills .nav-link.active,.nav-pills .nav-link.active:focus,.nav-pills .nav-link.active:hover,.nav-pills .nav-item.open .nav-link,.nav-pills .nav-item.open .nav-link:focus,.nav-pills .nav-item.open .nav-link:hover{color:#fff}.breadcrumb a{color:#fff}.pagination a:hover{text-decoration:none}.close{opacity:0.4}.close:hover,.close:focus{opacity:1}.alert{border:none;color:#fff}.alert a,.alert .alert-link{color:#fff;text-decoration:underline}.alert-primary{background-color:#375a7f}.alert-secondary{background-color:#444}.alert-success{background-color:#00bc8c}.alert-info{background-color:#3498DB}.alert-warning{background-color:#F39C12}.alert-danger{background-color:#E74C3C}.alert-light{background-color:#303030}.alert-dark{background-color:#adb5bd}.list-group-item-action{color:#fff}.list-group-item-action:hover,.list-group-item-action:focus{background-color:#444;color:#fff}.list-group-item-action .list-group-item-heading{color:#fff}
-
-body, .text-body, .navbar-brand, .badge-light, .btn-secondary {
- color: #dedede !important;
-}
-
-.form-control, .form-control:focus {
- background-color: var(--secondary);
- color: #fff;
-}
-
-.form-control:disabled {
- background-color: var(--secondary);
- opacity: .5;
-}
-
-.custom-select {
- color: #fff;
- background-color: var(--secondary);
-}
-
-.mark {
- background-color: #333;
-}
+:root{--blue:#375a7f;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#e74c3c;--orange:#fd7e14;--yellow:#f39c12;--green:#00bc8c;--teal:#20c997;--cyan:#3498db;--white:#fff;--gray:#888;--gray-dark:#303030;--primary:#375a7f;--secondary:#444;--success:#00bc8c;--info:#3498db;--warning:#f39c12;--danger:#e74c3c;--light:#303030;--dark:#dee2e6;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:"Lato",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:Lato,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:.9375rem;font-weight:400;line-height:1.5;color:#dee2e6;text-align:left;background-color:#222}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;text-decoration:underline dotted;cursor:help;border-bottom:0;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#00bc8c;text-decoration:none;background-color:transparent}a:hover{color:#007053;text-decoration:underline}a:not([href]){color:inherit;text-decoration:none}a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#888;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:3rem}.h2,h2{font-size:2.5rem}.h3,h3{font-size:2rem}.h4,h4{font-size:1.40625rem}.h5,h5{font-size:1.17188rem}.h6,h6{font-size:.9375rem}.lead{font-size:1.17188rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#333}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.17188rem}.blockquote-footer{display:block;font-size:80%;color:#888}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#222;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#888}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#222;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:inherit}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:flex;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-1>*{flex:0 0 100%;max-width:100%}.row-cols-2>*{flex:0 0 50%;max-width:50%}.row-cols-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-4>*{flex:0 0 25%;max-width:25%}.row-cols-5>*{flex:0 0 20%;max-width:20%}.row-cols-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-auto{flex:0 0 auto;width:auto;max-width:100%}.col-1{flex:0 0 8.33333%;max-width:8.33333%}.col-2{flex:0 0 16.66667%;max-width:16.66667%}.col-3{flex:0 0 25%;max-width:25%}.col-4{flex:0 0 33.33333%;max-width:33.33333%}.col-5{flex:0 0 41.66667%;max-width:41.66667%}.col-6{flex:0 0 50%;max-width:50%}.col-7{flex:0 0 58.33333%;max-width:58.33333%}.col-8{flex:0 0 66.66667%;max-width:66.66667%}.col-9{flex:0 0 75%;max-width:75%}.col-10{flex:0 0 83.33333%;max-width:83.33333%}.col-11{flex:0 0 91.66667%;max-width:91.66667%}.col-12{flex:0 0 100%;max-width:100%}.order-first{order:-1}.order-last{order:13}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-9{order:9}.order-10{order:10}.order-11{order:11}.order-12{order:12}.offset-1{margin-left:8.33333%}.offset-2{margin-left:16.66667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333%}.offset-5{margin-left:41.66667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333%}.offset-8{margin-left:66.66667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333%}.offset-11{margin-left:91.66667%}@media (min-width:576px){.col-sm{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-sm-1>*{flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-sm-4>*{flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-sm-auto{flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{flex:0 0 8.33333%;max-width:8.33333%}.col-sm-2{flex:0 0 16.66667%;max-width:16.66667%}.col-sm-3{flex:0 0 25%;max-width:25%}.col-sm-4{flex:0 0 33.33333%;max-width:33.33333%}.col-sm-5{flex:0 0 41.66667%;max-width:41.66667%}.col-sm-6{flex:0 0 50%;max-width:50%}.col-sm-7{flex:0 0 58.33333%;max-width:58.33333%}.col-sm-8{flex:0 0 66.66667%;max-width:66.66667%}.col-sm-9{flex:0 0 75%;max-width:75%}.col-sm-10{flex:0 0 83.33333%;max-width:83.33333%}.col-sm-11{flex:0 0 91.66667%;max-width:91.66667%}.col-sm-12{flex:0 0 100%;max-width:100%}.order-sm-first{order:-1}.order-sm-last{order:13}.order-sm-0{order:0}.order-sm-1{order:1}.order-sm-2{order:2}.order-sm-3{order:3}.order-sm-4{order:4}.order-sm-5{order:5}.order-sm-6{order:6}.order-sm-7{order:7}.order-sm-8{order:8}.order-sm-9{order:9}.order-sm-10{order:10}.order-sm-11{order:11}.order-sm-12{order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333%}.offset-sm-2{margin-left:16.66667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333%}.offset-sm-5{margin-left:41.66667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333%}.offset-sm-8{margin-left:66.66667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333%}.offset-sm-11{margin-left:91.66667%}}@media (min-width:768px){.col-md{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-md-1>*{flex:0 0 100%;max-width:100%}.row-cols-md-2>*{flex:0 0 50%;max-width:50%}.row-cols-md-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-md-4>*{flex:0 0 25%;max-width:25%}.row-cols-md-5>*{flex:0 0 20%;max-width:20%}.row-cols-md-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-md-auto{flex:0 0 auto;width:auto;max-width:100%}.col-md-1{flex:0 0 8.33333%;max-width:8.33333%}.col-md-2{flex:0 0 16.66667%;max-width:16.66667%}.col-md-3{flex:0 0 25%;max-width:25%}.col-md-4{flex:0 0 33.33333%;max-width:33.33333%}.col-md-5{flex:0 0 41.66667%;max-width:41.66667%}.col-md-6{flex:0 0 50%;max-width:50%}.col-md-7{flex:0 0 58.33333%;max-width:58.33333%}.col-md-8{flex:0 0 66.66667%;max-width:66.66667%}.col-md-9{flex:0 0 75%;max-width:75%}.col-md-10{flex:0 0 83.33333%;max-width:83.33333%}.col-md-11{flex:0 0 91.66667%;max-width:91.66667%}.col-md-12{flex:0 0 100%;max-width:100%}.order-md-first{order:-1}.order-md-last{order:13}.order-md-0{order:0}.order-md-1{order:1}.order-md-2{order:2}.order-md-3{order:3}.order-md-4{order:4}.order-md-5{order:5}.order-md-6{order:6}.order-md-7{order:7}.order-md-8{order:8}.order-md-9{order:9}.order-md-10{order:10}.order-md-11{order:11}.order-md-12{order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333%}.offset-md-2{margin-left:16.66667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333%}.offset-md-5{margin-left:41.66667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333%}.offset-md-8{margin-left:66.66667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333%}.offset-md-11{margin-left:91.66667%}}@media (min-width:992px){.col-lg{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-lg-1>*{flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-lg-4>*{flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-lg-auto{flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{flex:0 0 8.33333%;max-width:8.33333%}.col-lg-2{flex:0 0 16.66667%;max-width:16.66667%}.col-lg-3{flex:0 0 25%;max-width:25%}.col-lg-4{flex:0 0 33.33333%;max-width:33.33333%}.col-lg-5{flex:0 0 41.66667%;max-width:41.66667%}.col-lg-6{flex:0 0 50%;max-width:50%}.col-lg-7{flex:0 0 58.33333%;max-width:58.33333%}.col-lg-8{flex:0 0 66.66667%;max-width:66.66667%}.col-lg-9{flex:0 0 75%;max-width:75%}.col-lg-10{flex:0 0 83.33333%;max-width:83.33333%}.col-lg-11{flex:0 0 91.66667%;max-width:91.66667%}.col-lg-12{flex:0 0 100%;max-width:100%}.order-lg-first{order:-1}.order-lg-last{order:13}.order-lg-0{order:0}.order-lg-1{order:1}.order-lg-2{order:2}.order-lg-3{order:3}.order-lg-4{order:4}.order-lg-5{order:5}.order-lg-6{order:6}.order-lg-7{order:7}.order-lg-8{order:8}.order-lg-9{order:9}.order-lg-10{order:10}.order-lg-11{order:11}.order-lg-12{order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333%}.offset-lg-2{margin-left:16.66667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333%}.offset-lg-5{margin-left:41.66667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333%}.offset-lg-8{margin-left:66.66667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333%}.offset-lg-11{margin-left:91.66667%}}@media (min-width:1200px){.col-xl{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-xl-1>*{flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-xl-4>*{flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-xl-auto{flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{flex:0 0 8.33333%;max-width:8.33333%}.col-xl-2{flex:0 0 16.66667%;max-width:16.66667%}.col-xl-3{flex:0 0 25%;max-width:25%}.col-xl-4{flex:0 0 33.33333%;max-width:33.33333%}.col-xl-5{flex:0 0 41.66667%;max-width:41.66667%}.col-xl-6{flex:0 0 50%;max-width:50%}.col-xl-7{flex:0 0 58.33333%;max-width:58.33333%}.col-xl-8{flex:0 0 66.66667%;max-width:66.66667%}.col-xl-9{flex:0 0 75%;max-width:75%}.col-xl-10{flex:0 0 83.33333%;max-width:83.33333%}.col-xl-11{flex:0 0 91.66667%;max-width:91.66667%}.col-xl-12{flex:0 0 100%;max-width:100%}.order-xl-first{order:-1}.order-xl-last{order:13}.order-xl-0{order:0}.order-xl-1{order:1}.order-xl-2{order:2}.order-xl-3{order:3}.order-xl-4{order:4}.order-xl-5{order:5}.order-xl-6{order:6}.order-xl-7{order:7}.order-xl-8{order:8}.order-xl-9{order:9}.order-xl-10{order:10}.order-xl-11{order:11}.order-xl-12{order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333%}.offset-xl-2{margin-left:16.66667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333%}.offset-xl-5{margin-left:41.66667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333%}.offset-xl-8{margin-left:66.66667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333%}.offset-xl-11{margin-left:91.66667%}}.table{width:100%;margin-bottom:1rem;color:#dee2e6}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #444}.table thead th{vertical-align:bottom;border-bottom:2px solid #444}.table tbody+tbody{border-top:2px solid #444}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #444}.table-bordered td,.table-bordered th{border:1px solid #444}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:#303030}.table-hover tbody tr:hover{color:#dee2e6;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#c7d1db}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#97a9bc}.table-hover .table-primary:hover{background-color:#b7c4d1}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#b7c4d1}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#cbcbcb}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#9e9e9e}.table-hover .table-secondary:hover{background-color:#bebebe}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#bebebe}.table-success,.table-success>td,.table-success>th{background-color:#b8ecdf}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#7adcc3}.table-hover .table-success:hover{background-color:#a4e7d6}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#a4e7d6}.table-info,.table-info>td,.table-info>th{background-color:#c6e2f5}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#95c9ec}.table-hover .table-info:hover{background-color:#b0d7f1}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#b0d7f1}.table-warning,.table-warning>td,.table-warning>th{background-color:#fce3bd}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#f9cc84}.table-hover .table-warning:hover{background-color:#fbd9a5}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#fbd9a5}.table-danger,.table-danger>td,.table-danger>th{background-color:#f8cdc8}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#f3a29a}.table-hover .table-danger:hover{background-color:#f5b8b1}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f5b8b1}.table-light,.table-light>td,.table-light>th{background-color:#c5c5c5}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#939393}.table-hover .table-light:hover{background-color:#b8b8b8}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#b8b8b8}.table-dark,.table-dark>td,.table-dark>th{background-color:#f6f7f8}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#eef0f2}.table-hover .table-dark:hover{background-color:#e8eaed}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#e8eaed}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#303030;border-color:#434343}.table .thead-light th{color:#444;background-color:#ebebeb;border-color:#444}.table-dark{color:#fff;background-color:#303030}.table-dark td,.table-dark th,.table-dark thead th{border-color:#434343}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:.9375rem;font-weight:400;line-height:1.5;color:#fff;background-color:#444;background-clip:padding-box;border:1px solid #222;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #fff}.form-control:focus{color:#fff;background-color:#444;border-color:#739ac2;outline:0;box-shadow:0 0 0 .2rem rgba(55,90,127,.25)}.form-control::placeholder{color:#888;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#2b2b2b;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{appearance:none}select.form-control:focus::-ms-value{color:#fff;background-color:#444}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.17188rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.82031rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:.9375rem;line-height:1.5;color:#dee2e6;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.82031rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.17188rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:flex;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#888}.form-check-label{margin-bottom:0}.form-check-inline{display:inline-flex;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#00bc8c}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.82031rem;line-height:1.5;color:#fff;background-color:rgba(0,188,140,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#00bc8c;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2300bc8c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#00bc8c;box-shadow:0 0 0 .2rem rgba(0,188,140,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#00bc8c;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23303030' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2300bc8c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #444 no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#00bc8c;box-shadow:0 0 0 .2rem rgba(0,188,140,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#00bc8c}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#00bc8c}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#00bc8c}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#00efb2;background-color:#00efb2}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,188,140,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#00bc8c}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#00bc8c}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#00bc8c;box-shadow:0 0 0 .2rem rgba(0,188,140,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#e74c3c}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.82031rem;line-height:1.5;color:#fff;background-color:rgba(231,76,60,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#e74c3c;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23e74c3c' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23e74c3c' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#e74c3c;box-shadow:0 0 0 .2rem rgba(231,76,60,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#e74c3c;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23303030' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23e74c3c' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23e74c3c' stroke='none'/%3e%3c/svg%3e") #444 no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#e74c3c;box-shadow:0 0 0 .2rem rgba(231,76,60,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#e74c3c}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#e74c3c}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#e74c3c}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#ed7669;background-color:#ed7669}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(231,76,60,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#e74c3c}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#e74c3c}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#e74c3c;box-shadow:0 0 0 .2rem rgba(231,76,60,.25)}.form-inline{display:flex;flex-flow:row wrap;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:flex;align-items:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:flex;flex:0 0 auto;flex-flow:row wrap;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:flex;align-items:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{align-items:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#dee2e6;text-align:center;vertical-align:middle;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:.9375rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#dee2e6;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(55,90,127,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#375a7f;border-color:#375a7f}.btn-primary:hover{color:#fff;background-color:#2b4764;border-color:#28415b}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#2b4764;border-color:#28415b;box-shadow:0 0 0 .2rem rgba(85,115,146,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#375a7f;border-color:#375a7f}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#28415b;border-color:#243a53}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(85,115,146,.5)}.btn-secondary{color:#fff;background-color:#444;border-color:#444}.btn-secondary:hover{color:#fff;background-color:#313131;border-color:#2b2b2b}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#313131;border-color:#2b2b2b;box-shadow:0 0 0 .2rem rgba(96,96,96,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#444;border-color:#444}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#2b2b2b;border-color:#242424}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(96,96,96,.5)}.btn-success{color:#fff;background-color:#00bc8c;border-color:#00bc8c}.btn-success:hover{color:#fff;background-color:#009670;border-color:#008966}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#009670;border-color:#008966;box-shadow:0 0 0 .2rem rgba(38,198,157,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#00bc8c;border-color:#00bc8c}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#008966;border-color:#007c5d}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,198,157,.5)}.btn-info{color:#fff;background-color:#3498db;border-color:#3498db}.btn-info:hover{color:#fff;background-color:#2384c6;border-color:#217dbb}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#2384c6;border-color:#217dbb;box-shadow:0 0 0 .2rem rgba(82,167,224,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#3498db;border-color:#3498db}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#217dbb;border-color:#1f76b0}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,167,224,.5)}.btn-warning{color:#fff;background-color:#f39c12;border-color:#f39c12}.btn-warning:hover{color:#fff;background-color:#d4860b;border-color:#c87f0a}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#d4860b;border-color:#c87f0a;box-shadow:0 0 0 .2rem rgba(245,171,54,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#fff;background-color:#f39c12;border-color:#f39c12}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#fff;background-color:#c87f0a;border-color:#bc770a}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(245,171,54,.5)}.btn-danger{color:#fff;background-color:#e74c3c;border-color:#e74c3c}.btn-danger:hover{color:#fff;background-color:#e12e1c;border-color:#d62c1a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#e12e1c;border-color:#d62c1a;box-shadow:0 0 0 .2rem rgba(235,103,89,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#e74c3c;border-color:#e74c3c}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#d62c1a;border-color:#ca2a19}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(235,103,89,.5)}.btn-light{color:#fff;background-color:#303030;border-color:#303030}.btn-light:hover{color:#fff;background-color:#1d1d1d;border-color:#171717}.btn-light.focus,.btn-light:focus{color:#fff;background-color:#1d1d1d;border-color:#171717;box-shadow:0 0 0 .2rem rgba(79,79,79,.5)}.btn-light.disabled,.btn-light:disabled{color:#fff;background-color:#303030;border-color:#303030}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#fff;background-color:#171717;border-color:#101010}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(79,79,79,.5)}.btn-dark{color:#222;background-color:#dee2e6;border-color:#dee2e6}.btn-dark:hover{color:#222;background-color:#c8cfd6;border-color:#c1c9d0}.btn-dark.focus,.btn-dark:focus{color:#222;background-color:#c8cfd6;border-color:#c1c9d0;box-shadow:0 0 0 .2rem rgba(194,197,201,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#222;background-color:#dee2e6;border-color:#dee2e6}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#222;background-color:#c1c9d0;border-color:#bac2cb}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(194,197,201,.5)}.btn-outline-primary{color:#375a7f;border-color:#375a7f}.btn-outline-primary:hover{color:#fff;background-color:#375a7f;border-color:#375a7f}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(55,90,127,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#375a7f;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#375a7f;border-color:#375a7f}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(55,90,127,.5)}.btn-outline-secondary{color:#444;border-color:#444}.btn-outline-secondary:hover{color:#fff;background-color:#444;border-color:#444}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(68,68,68,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#444;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#444;border-color:#444}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(68,68,68,.5)}.btn-outline-success{color:#00bc8c;border-color:#00bc8c}.btn-outline-success:hover{color:#fff;background-color:#00bc8c;border-color:#00bc8c}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(0,188,140,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#00bc8c;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#00bc8c;border-color:#00bc8c}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,188,140,.5)}.btn-outline-info{color:#3498db;border-color:#3498db}.btn-outline-info:hover{color:#fff;background-color:#3498db;border-color:#3498db}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(52,152,219,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#3498db;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#3498db;border-color:#3498db}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,152,219,.5)}.btn-outline-warning{color:#f39c12;border-color:#f39c12}.btn-outline-warning:hover{color:#fff;background-color:#f39c12;border-color:#f39c12}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(243,156,18,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#f39c12;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#fff;background-color:#f39c12;border-color:#f39c12}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(243,156,18,.5)}.btn-outline-danger{color:#e74c3c;border-color:#e74c3c}.btn-outline-danger:hover{color:#fff;background-color:#e74c3c;border-color:#e74c3c}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(231,76,60,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#e74c3c;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#e74c3c;border-color:#e74c3c}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(231,76,60,.5)}.btn-outline-light{color:#303030;border-color:#303030}.btn-outline-light:hover{color:#fff;background-color:#303030;border-color:#303030}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(48,48,48,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#303030;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#fff;background-color:#303030;border-color:#303030}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(48,48,48,.5)}.btn-outline-dark{color:#dee2e6;border-color:#dee2e6}.btn-outline-dark:hover{color:#222;background-color:#dee2e6;border-color:#dee2e6}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(222,226,230,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#dee2e6;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#222;background-color:#dee2e6;border-color:#dee2e6}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,226,230,.5)}.btn-link{font-weight:400;color:#00bc8c;text-decoration:none}.btn-link:hover{color:#007053;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#888;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.17188rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.82031rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:.9375rem;color:#dee2e6;text-align:left;list-style:none;background-color:#222;background-clip:padding-box;border:1px solid #444;border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #444}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#fff;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#fff;text-decoration:none;background-color:#375a7f}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#375a7f}.dropdown-item.disabled,.dropdown-item:disabled{color:#888;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.82031rem;color:#888;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#fff}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:flex;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:.9375rem;font-weight:400;line-height:1.5;color:#adb5bd;text-align:center;white-space:nowrap;background-color:#444;border:1px solid #222;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.17188rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.82031rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.40625rem;padding-left:1.5rem}.custom-control-inline{display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.20312rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#375a7f;background-color:#375a7f}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(55,90,127,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#739ac2}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#97b3d2;border-color:#97b3d2}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#888}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#2b2b2b}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.20312rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#444;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.20312rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#375a7f;background-color:#375a7f}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(55,90,127,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(55,90,127,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(55,90,127,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.20312rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#444;transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(55,90,127,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:.9375rem;font-weight:400;line-height:1.5;color:#fff;vertical-align:middle;background:#444 url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23303030' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;border:1px solid #222;border-radius:.25rem;appearance:none}.custom-select:focus{border-color:#739ac2;outline:0;box-shadow:0 0 0 .2rem rgba(55,90,127,.25)}.custom-select:focus::-ms-value{color:#fff;background-color:#444}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#888;background-color:#ebebeb}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #fff}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.82031rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.17188rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#739ac2;box-shadow:0 0 0 .2rem rgba(55,90,127,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#2b2b2b}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#adb5bd;background-color:#444;border:1px solid #222;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#adb5bd;content:"Browse";background-color:#444;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #222,0 0 0 .2rem rgba(55,90,127,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #222,0 0 0 .2rem rgba(55,90,127,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #222,0 0 0 .2rem rgba(55,90,127,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#375a7f;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#97b3d2}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#375a7f;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#97b3d2}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#375a7f;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#97b3d2}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 2rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#adb5bd;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #444}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#444 #444 transparent}.nav-tabs .nav-link.disabled{color:#adb5bd;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#fff;background-color:#222;border-color:#444 #444 transparent}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#375a7f}.nav-fill .nav-item{flex:1 1 auto;text-align:center}.nav-justified .nav-item{flex-basis:0;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:1rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.32422rem;padding-bottom:.32422rem;margin-right:1rem;font-size:1.17188rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.17188rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:#fff}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:#fff}.navbar-light .navbar-nav .nav-link{color:rgba(255,255,255,.6)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:#fff}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:#fff}.navbar-light .navbar-toggler{color:rgba(255,255,255,.6);border-color:rgba(34,34,34,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.6%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(255,255,255,.6)}.navbar-light .navbar-text a{color:#fff}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:#fff}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.6)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:#fff}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.6);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.6%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.6)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#303030;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-body{flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:#444;border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:#444;border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img,.card-img-bottom,.card-img-top{flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:flex;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{column-count:3;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:flex;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#444;border-radius:.25rem}.breadcrumb-item{display:flex}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#888;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#888}.pagination{display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:0;line-height:1.25;color:#fff;background-color:#00bc8c;border:0 solid transparent}.page-link:hover{z-index:2;color:#fff;text-decoration:none;background-color:#00efb2;border-color:transparent}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(55,90,127,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#00efb2;border-color:transparent}.page-item.disabled .page-link{color:#fff;pointer-events:none;cursor:auto;background-color:#007053;border-color:transparent}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.17188rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.82031rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#375a7f}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#28415b}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(55,90,127,.5)}.badge-secondary{color:#fff;background-color:#444}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#2b2b2b}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(68,68,68,.5)}.badge-success{color:#fff;background-color:#00bc8c}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#008966}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,188,140,.5)}.badge-info{color:#fff;background-color:#3498db}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#217dbb}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,152,219,.5)}.badge-warning{color:#fff;background-color:#f39c12}a.badge-warning:focus,a.badge-warning:hover{color:#fff;background-color:#c87f0a}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(243,156,18,.5)}.badge-danger{color:#fff;background-color:#e74c3c}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#d62c1a}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(231,76,60,.5)}.badge-light{color:#fff;background-color:#303030}a.badge-light:focus,a.badge-light:hover{color:#fff;background-color:#171717}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(48,48,48,.5)}.badge-dark{color:#222;background-color:#dee2e6}a.badge-dark:focus,a.badge-dark:hover{color:#222;background-color:#c1c9d0}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(222,226,230,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#303030;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3.90625rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#1d2f42;background-color:#d7dee5;border-color:#c7d1db}.alert-primary hr{border-top-color:#b7c4d1}.alert-primary .alert-link{color:#0d161f}.alert-secondary{color:#232323;background-color:#dadada;border-color:#cbcbcb}.alert-secondary hr{border-top-color:#bebebe}.alert-secondary .alert-link{color:#0a0a0a}.alert-success{color:#006249;background-color:#ccf2e8;border-color:#b8ecdf}.alert-success hr{border-top-color:#a4e7d6}.alert-success .alert-link{color:#002f23}.alert-info{color:#1b4f72;background-color:#d6eaf8;border-color:#c6e2f5}.alert-info hr{border-top-color:#b0d7f1}.alert-info .alert-link{color:#113249}.alert-warning{color:#7e5109;background-color:#fdebd0;border-color:#fce3bd}.alert-warning hr{border-top-color:#fbd9a5}.alert-warning .alert-link{color:#4e3206}.alert-danger{color:#78281f;background-color:#fadbd8;border-color:#f8cdc8}.alert-danger hr{border-top-color:#f5b8b1}.alert-danger .alert-link{color:#4f1a15}.alert-light{color:#191919;background-color:#d6d6d6;border-color:#c5c5c5}.alert-light hr{border-top-color:#b8b8b8}.alert-light .alert-link{color:#000}.alert-dark{color:#737678;background-color:#f8f9fa;border-color:#f6f7f8}.alert-dark hr{border-top-color:#e8eaed}.alert-dark .alert-link{color:#5a5c5e}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.70312rem;background-color:#444;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#375a7f;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.media{display:flex;align-items:flex-start}.media-body{flex:1}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#444;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#444;text-decoration:none;background-color:#444}.list-group-item-action:active{color:#dee2e6;background-color:#ebebeb}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#303030;border:1px solid #444}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#888;pointer-events:none;background-color:#303030}.list-group-item.active{z-index:2;color:#fff;background-color:#375a7f;border-color:#375a7f}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#1d2f42;background-color:#c7d1db}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#1d2f42;background-color:#b7c4d1}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#1d2f42;border-color:#1d2f42}.list-group-item-secondary{color:#232323;background-color:#cbcbcb}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#232323;background-color:#bebebe}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#232323;border-color:#232323}.list-group-item-success{color:#006249;background-color:#b8ecdf}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#006249;background-color:#a4e7d6}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#006249;border-color:#006249}.list-group-item-info{color:#1b4f72;background-color:#c6e2f5}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#1b4f72;background-color:#b0d7f1}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#1b4f72;border-color:#1b4f72}.list-group-item-warning{color:#7e5109;background-color:#fce3bd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#7e5109;background-color:#fbd9a5}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#7e5109;border-color:#7e5109}.list-group-item-danger{color:#78281f;background-color:#f8cdc8}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#78281f;background-color:#f5b8b1}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#78281f;border-color:#78281f}.list-group-item-light{color:#191919;background-color:#c5c5c5}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#191919;background-color:#b8b8b8}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#191919;border-color:#191919}.list-group-item-dark{color:#737678;background-color:#f6f7f8}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#737678;background-color:#e8eaed}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#737678;border-color:#737678}.close{float:right;font-size:1.40625rem;font-weight:700;line-height:1;color:#fff;text-shadow:none;opacity:.5}.close:hover{color:#fff;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:#444;background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:flex;align-items:center;padding:.25rem .75rem;color:#888;background-color:#303030;background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{flex-direction:column;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#303030;background-clip:padding-box;border:1px solid #444;border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;align-items:flex-start;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #444;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #444;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:Lato,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.82031rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:Lato,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.82031rem;word-wrap:break-word;background-color:#303030;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#303030}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#303030}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#303030}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #444}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#303030}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:.9375rem;background-color:#444;border-bottom:1px solid #373737;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#dee2e6}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:flex;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#375a7f!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#28415b!important}.bg-secondary{background-color:#444!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#2b2b2b!important}.bg-success{background-color:#00bc8c!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#008966!important}.bg-info{background-color:#3498db!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#217dbb!important}.bg-warning{background-color:#f39c12!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#c87f0a!important}.bg-danger{background-color:#e74c3c!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#d62c1a!important}.bg-light{background-color:#303030!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#171717!important}.bg-dark{background-color:#dee2e6!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#c1c9d0!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#375a7f!important}.border-secondary{border-color:#444!important}.border-success{border-color:#00bc8c!important}.border-info{border-color:#3498db!important}.border-warning{border-color:#f39c12!important}.border-danger{border-color:#e74c3c!important}.border-light{border-color:#303030!important}.border-dark{border-color:#dee2e6!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.85714%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-fill{flex:1 1 auto!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}@media (min-width:576px){.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}}@media (min-width:768px){.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{user-select:all!important}.user-select-auto{user-select:auto!important}.user-select-none{user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports (position:sticky){.sticky-top{position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#375a7f!important}a.text-primary:focus,a.text-primary:hover{color:#20344a!important}.text-secondary{color:#444!important}a.text-secondary:focus,a.text-secondary:hover{color:#1e1e1e!important}.text-success{color:#00bc8c!important}a.text-success:focus,a.text-success:hover{color:#007053!important}.text-info{color:#3498db!important}a.text-info:focus,a.text-info:hover{color:#1d6fa5!important}.text-warning{color:#f39c12!important}a.text-warning:focus,a.text-warning:hover{color:#b06f09!important}.text-danger{color:#e74c3c!important}a.text-danger:focus,a.text-danger:hover{color:#bf2718!important}.text-light{color:#303030!important}a.text-light:focus,a.text-light:hover{color:#0a0a0a!important}.text-dark{color:#dee2e6!important}a.text-dark:focus,a.text-dark:hover{color:#b2bcc5!important}.text-body{color:#dee2e6!important}.text-muted{color:#888!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#444}.table .thead-dark th{color:inherit;border-color:#444}}
-:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#d8486a;--orange:#fac57c;--yellow:#ffc107;--green:#bad2be;--teal:#20c997;--cyan:#02bdc2;--white:#ffffff;--gray:#6c757d;--gray-dark:#343a40;--primary:#fac57c;--secondary:#bad2be;--success:#38553d;--info:#49ce5f;--warning:#ffc107;--danger:#ed8d09;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Droid Sans","Segoe UI","Helvetica",Arial,sans-serif;--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(34,34,34,0)}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Droid Sans","Segoe UI",Helvetica,Arial,sans-serif;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:left;background-color:#f2f0f0}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;text-decoration:underline dotted;cursor:help;border-bottom:0;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#ed8d09;text-decoration:none;background-color:transparent}a:hover{color:#a46206;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2;color:#495057}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(34,34,34,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fffcef}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#f2f0f0;border:1px solid #dee2e6;border-radius:1.5rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:1rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:flex;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{flex-basis:0;flex-grow:1;max-width:100%}.col-auto{flex:0 0 auto;width:auto;max-width:100%}.col-1{flex:0 0 8.33333%;max-width:8.33333%}.col-2{flex:0 0 16.66667%;max-width:16.66667%}.col-3{flex:0 0 25%;max-width:25%}.col-4{flex:0 0 33.33333%;max-width:33.33333%}.col-5{flex:0 0 41.66667%;max-width:41.66667%}.col-6{flex:0 0 50%;max-width:50%}.col-7{flex:0 0 58.33333%;max-width:58.33333%}.col-8{flex:0 0 66.66667%;max-width:66.66667%}.col-9{flex:0 0 75%;max-width:75%}.col-10{flex:0 0 83.33333%;max-width:83.33333%}.col-11{flex:0 0 91.66667%;max-width:91.66667%}.col-12{flex:0 0 100%;max-width:100%}.order-first{order:-1}.order-last{order:13}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-9{order:9}.order-10{order:10}.order-11{order:11}.order-12{order:12}.offset-1{margin-left:8.33333%}.offset-2{margin-left:16.66667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333%}.offset-5{margin-left:41.66667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333%}.offset-8{margin-left:66.66667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333%}.offset-11{margin-left:91.66667%}@media (min-width:576px){.col-sm{flex-basis:0;flex-grow:1;max-width:100%}.col-sm-auto{flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{flex:0 0 8.33333%;max-width:8.33333%}.col-sm-2{flex:0 0 16.66667%;max-width:16.66667%}.col-sm-3{flex:0 0 25%;max-width:25%}.col-sm-4{flex:0 0 33.33333%;max-width:33.33333%}.col-sm-5{flex:0 0 41.66667%;max-width:41.66667%}.col-sm-6{flex:0 0 50%;max-width:50%}.col-sm-7{flex:0 0 58.33333%;max-width:58.33333%}.col-sm-8{flex:0 0 66.66667%;max-width:66.66667%}.col-sm-9{flex:0 0 75%;max-width:75%}.col-sm-10{flex:0 0 83.33333%;max-width:83.33333%}.col-sm-11{flex:0 0 91.66667%;max-width:91.66667%}.col-sm-12{flex:0 0 100%;max-width:100%}.order-sm-first{order:-1}.order-sm-last{order:13}.order-sm-0{order:0}.order-sm-1{order:1}.order-sm-2{order:2}.order-sm-3{order:3}.order-sm-4{order:4}.order-sm-5{order:5}.order-sm-6{order:6}.order-sm-7{order:7}.order-sm-8{order:8}.order-sm-9{order:9}.order-sm-10{order:10}.order-sm-11{order:11}.order-sm-12{order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333%}.offset-sm-2{margin-left:16.66667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333%}.offset-sm-5{margin-left:41.66667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333%}.offset-sm-8{margin-left:66.66667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333%}.offset-sm-11{margin-left:91.66667%}}@media (min-width:768px){.col-md{flex-basis:0;flex-grow:1;max-width:100%}.col-md-auto{flex:0 0 auto;width:auto;max-width:100%}.col-md-1{flex:0 0 8.33333%;max-width:8.33333%}.col-md-2{flex:0 0 16.66667%;max-width:16.66667%}.col-md-3{flex:0 0 25%;max-width:25%}.col-md-4{flex:0 0 33.33333%;max-width:33.33333%}.col-md-5{flex:0 0 41.66667%;max-width:41.66667%}.col-md-6{flex:0 0 50%;max-width:50%}.col-md-7{flex:0 0 58.33333%;max-width:58.33333%}.col-md-8{flex:0 0 66.66667%;max-width:66.66667%}.col-md-9{flex:0 0 75%;max-width:75%}.col-md-10{flex:0 0 83.33333%;max-width:83.33333%}.col-md-11{flex:0 0 91.66667%;max-width:91.66667%}.col-md-12{flex:0 0 100%;max-width:100%}.order-md-first{order:-1}.order-md-last{order:13}.order-md-0{order:0}.order-md-1{order:1}.order-md-2{order:2}.order-md-3{order:3}.order-md-4{order:4}.order-md-5{order:5}.order-md-6{order:6}.order-md-7{order:7}.order-md-8{order:8}.order-md-9{order:9}.order-md-10{order:10}.order-md-11{order:11}.order-md-12{order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333%}.offset-md-2{margin-left:16.66667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333%}.offset-md-5{margin-left:41.66667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333%}.offset-md-8{margin-left:66.66667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333%}.offset-md-11{margin-left:91.66667%}}@media (min-width:992px){.col-lg{flex-basis:0;flex-grow:1;max-width:100%}.col-lg-auto{flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{flex:0 0 8.33333%;max-width:8.33333%}.col-lg-2{flex:0 0 16.66667%;max-width:16.66667%}.col-lg-3{flex:0 0 25%;max-width:25%}.col-lg-4{flex:0 0 33.33333%;max-width:33.33333%}.col-lg-5{flex:0 0 41.66667%;max-width:41.66667%}.col-lg-6{flex:0 0 50%;max-width:50%}.col-lg-7{flex:0 0 58.33333%;max-width:58.33333%}.col-lg-8{flex:0 0 66.66667%;max-width:66.66667%}.col-lg-9{flex:0 0 75%;max-width:75%}.col-lg-10{flex:0 0 83.33333%;max-width:83.33333%}.col-lg-11{flex:0 0 91.66667%;max-width:91.66667%}.col-lg-12{flex:0 0 100%;max-width:100%}.order-lg-first{order:-1}.order-lg-last{order:13}.order-lg-0{order:0}.order-lg-1{order:1}.order-lg-2{order:2}.order-lg-3{order:3}.order-lg-4{order:4}.order-lg-5{order:5}.order-lg-6{order:6}.order-lg-7{order:7}.order-lg-8{order:8}.order-lg-9{order:9}.order-lg-10{order:10}.order-lg-11{order:11}.order-lg-12{order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333%}.offset-lg-2{margin-left:16.66667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333%}.offset-lg-5{margin-left:41.66667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333%}.offset-lg-8{margin-left:66.66667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333%}.offset-lg-11{margin-left:91.66667%}}@media (min-width:1200px){.col-xl{flex-basis:0;flex-grow:1;max-width:100%}.col-xl-auto{flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{flex:0 0 8.33333%;max-width:8.33333%}.col-xl-2{flex:0 0 16.66667%;max-width:16.66667%}.col-xl-3{flex:0 0 25%;max-width:25%}.col-xl-4{flex:0 0 33.33333%;max-width:33.33333%}.col-xl-5{flex:0 0 41.66667%;max-width:41.66667%}.col-xl-6{flex:0 0 50%;max-width:50%}.col-xl-7{flex:0 0 58.33333%;max-width:58.33333%}.col-xl-8{flex:0 0 66.66667%;max-width:66.66667%}.col-xl-9{flex:0 0 75%;max-width:75%}.col-xl-10{flex:0 0 83.33333%;max-width:83.33333%}.col-xl-11{flex:0 0 91.66667%;max-width:91.66667%}.col-xl-12{flex:0 0 100%;max-width:100%}.order-xl-first{order:-1}.order-xl-last{order:13}.order-xl-0{order:0}.order-xl-1{order:1}.order-xl-2{order:2}.order-xl-3{order:3}.order-xl-4{order:4}.order-xl-5{order:5}.order-xl-6{order:6}.order-xl-7{order:7}.order-xl-8{order:8}.order-xl-9{order:9}.order-xl-10{order:10}.order-xl-11{order:11}.order-xl-12{order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333%}.offset-xl-2{margin-left:16.66667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333%}.offset-xl-5{margin-left:41.66667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333%}.offset-xl-8{margin-left:66.66667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333%}.offset-xl-11{margin-left:91.66667%}}.table{width:100%;margin-bottom:1rem;color:#495057}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #495057}.table thead th{vertical-align:bottom;border-bottom:2px solid #495057}.table tbody+tbody{border-top:2px solid #495057}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #495057}.table-bordered td,.table-bordered th{border:1px solid #495057}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(34,34,34,.05)}.table-hover tbody tr:hover{color:#495057;background-color:rgba(34,34,34,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#feefda}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#fce1bb}.table-hover .table-primary:hover{background-color:#fde4c1}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#fde4c1}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#ecf2ed}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#dbe8dd}.table-hover .table-secondary:hover{background-color:#dde8df}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#dde8df}.table-success,.table-success>td,.table-success>th{background-color:#c7cfc9}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#97a79a}.table-hover .table-success:hover{background-color:#b9c3bc}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b9c3bc}.table-info,.table-info>td,.table-info>th{background-color:#ccf1d2}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#a0e6ac}.table-hover .table-info:hover{background-color:#b8ecc0}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#b8ecc0}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#fadfba}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#f6c47f}.table-hover .table-danger:hover{background-color:#f8d4a2}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f8d4a2}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(34,34,34,.075)}.table-hover .table-active:hover{background-color:rgba(21,21,21,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(21,21,21,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#495057}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:1.5rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#fffbf7;outline:0;box-shadow:0 0 0 .2rem rgba(250,197,124,.75)}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;color:#495057;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:1rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:1.5rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:flex;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:inline-flex;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#49ce5f}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#212529;background-color:rgba(73,206,95,.9);border-radius:1.5rem}.form-control.is-valid,.was-validated .form-control:valid{border-color:#49ce5f;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2349ce5f' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#49ce5f;box-shadow:0 0 0 .2rem rgba(73,206,95,.25)}.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#49ce5f;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2349ce5f' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#49ce5f;box-shadow:0 0 0 .2rem rgba(73,206,95,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip{display:block}.form-control-file.is-valid~.valid-feedback,.form-control-file.is-valid~.valid-tooltip,.was-validated .form-control-file:valid~.valid-feedback,.was-validated .form-control-file:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#49ce5f}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#49ce5f}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#49ce5f}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#71d982;background-color:#71d982}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(73,206,95,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#49ce5f}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#49ce5f}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#49ce5f;box-shadow:0 0 0 .2rem rgba(73,206,95,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#ed8d09}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#212529;background-color:rgba(237,141,9,.9);border-radius:1.5rem}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#ed8d09;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23ed8d09' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23ed8d09' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#ed8d09;box-shadow:0 0 0 .2rem rgba(237,141,9,.25)}.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#ed8d09;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23ed8d09' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23ed8d09' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#ed8d09;box-shadow:0 0 0 .2rem rgba(237,141,9,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip{display:block}.form-control-file.is-invalid~.invalid-feedback,.form-control-file.is-invalid~.invalid-tooltip,.was-validated .form-control-file:invalid~.invalid-feedback,.was-validated .form-control-file:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#ed8d09}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#ed8d09}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#ed8d09}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#f7a432;background-color:#f7a432}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(237,141,9,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#ed8d09}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#ed8d09}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#ed8d09;box-shadow:0 0 0 .2rem rgba(237,141,9,.25)}.form-inline{display:flex;flex-flow:row wrap;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:flex;align-items:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:flex;flex:0 0 auto;flex-flow:row wrap;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:flex;align-items:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{align-items:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#495057;text-align:center;vertical-align:middle;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:1.5rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#495057;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(250,197,124,.75)}.btn.disabled,.btn:disabled{opacity:.65}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#212529;background-color:#fac57c;border-color:#fac57c}.btn-primary:hover{color:#212529;background-color:#f9b557;border-color:#f8af4b}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(217,173,112,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#212529;background-color:#fac57c;border-color:#fac57c}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#212529;background-color:#f8af4b;border-color:#f8aa3f}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(217,173,112,.5)}.btn-secondary{color:#212529;background-color:#bad2be;border-color:#bad2be}.btn-secondary:hover{color:#212529;background-color:#a3c3a8;border-color:#9bbea1}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(163,184,168,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#212529;background-color:#bad2be;border-color:#bad2be}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#212529;background-color:#9bbea1;border-color:#93b99a}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(163,184,168,.5)}.btn-success{color:#fff;background-color:#38553d;border-color:#38553d}.btn-success:hover{color:#fff;background-color:#293e2c;border-color:#243627}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(86,111,90,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#38553d;border-color:#38553d}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#243627;border-color:#1e2f21}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(86,111,90,.5)}.btn-info{color:#212529;background-color:#49ce5f;border-color:#49ce5f}.btn-info:hover{color:#fff;background-color:#33be4a;border-color:#30b446}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(67,181,87,.5)}.btn-info.disabled,.btn-info:disabled{color:#212529;background-color:#49ce5f;border-color:#49ce5f}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#30b446;border-color:#2eaa42}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(67,181,87,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#212529;background-color:#ed8d09;border-color:#ed8d09}.btn-danger:hover{color:#fff;background-color:#c97708;border-color:#bc7007}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(207,126,14,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#212529;background-color:#ed8d09;border-color:#ed8d09}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bc7007;border-color:#b06907}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(207,126,14,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#fac57c;border-color:#fac57c}.btn-outline-primary:hover{color:#212529;background-color:#fac57c;border-color:#fac57c}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(250,197,124,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#fac57c;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#212529;background-color:#fac57c;border-color:#fac57c}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(250,197,124,.5)}.btn-outline-secondary{color:#bad2be;border-color:#bad2be}.btn-outline-secondary:hover{color:#212529;background-color:#bad2be;border-color:#bad2be}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(186,210,190,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#bad2be;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#212529;background-color:#bad2be;border-color:#bad2be}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(186,210,190,.5)}.btn-outline-success{color:#38553d;border-color:#38553d}.btn-outline-success:hover{color:#fff;background-color:#38553d;border-color:#38553d}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(56,85,61,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#38553d;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#38553d;border-color:#38553d}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(56,85,61,.5)}.btn-outline-info{color:#49ce5f;border-color:#49ce5f}.btn-outline-info:hover{color:#212529;background-color:#49ce5f;border-color:#49ce5f}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(73,206,95,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#49ce5f;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#212529;background-color:#49ce5f;border-color:#49ce5f}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(73,206,95,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#ed8d09;border-color:#ed8d09}.btn-outline-danger:hover{color:#212529;background-color:#ed8d09;border-color:#ed8d09}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(237,141,9,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#ed8d09;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#212529;background-color:#ed8d09;border-color:#ed8d09}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(237,141,9,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#ed8d09;text-decoration:none}.btn-link:hover{color:#a46206;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:1.5rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:1rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#495057;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(34,34,34,.15);border-radius:1.5rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#fac57c}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:flex;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:1.5rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:1.5rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:1rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#fac57c;background-color:#fac57c}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(250,197,124,.75)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#fffbf7}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#fff;border-color:#fff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:1.5rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23ffffff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#fac57c;background-color:#fac57c}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23ffffff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(250,197,124,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(250,197,124,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23ffffff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(250,197,124,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(250,197,124,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;background-color:#fff;border:1px solid #ced4da;border-radius:1.5rem;appearance:none}.custom-select:focus{border-color:#fffbf7;outline:0;box-shadow:0 0 0 .2rem rgba(250,197,124,.75)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#fffbf7;box-shadow:0 0 0 .2rem rgba(250,197,124,.75)}.custom-file-input:disabled~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:1.5rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 1.5rem 1.5rem 0}.custom-range{width:100%;height:calc(1rem + .4rem);padding:0;background-color:transparent;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #f2f0f0,0 0 0 .2rem rgba(250,197,124,.75)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #f2f0f0,0 0 0 .2rem rgba(250,197,124,.75)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #f2f0f0,0 0 0 .2rem rgba(250,197,124,.75)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#fac57c;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#fff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#fac57c;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#fff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#fac57c;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#fff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:1.5rem;border-top-right-radius:1.5rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#f2f0f0;border-color:#dee2e6 #dee2e6 #f2f0f0}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:1.5rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#fac57c}.nav-fill .nav-item{flex:1 1 auto;text-align:center}.nav-justified .nav-item{flex-basis:0;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:1.5rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:#212529}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:#212529}.navbar-light .navbar-nav .nav-link{color:#6c757d}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:#212529}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(34,34,34,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:#212529}.navbar-light .navbar-toggler{color:#6c757d;border-color:rgba(34,34,34,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='%236c757d' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:#6c757d}.navbar-light .navbar-text a{color:#212529}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:#212529}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(34,34,34,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#f8f9fa;background-clip:border-box;border:1px solid rgba(34,34,34,.125);border-radius:1.5rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:1.5rem;border-top-right-radius:1.5rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:1.5rem;border-bottom-left-radius:1.5rem}.card-body{flex:1 1 auto;padding:1.25rem;color:#495057}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;color:#495057;background-color:rgba(34,34,34,.03);border-bottom:1px solid rgba(34,34,34,.125)}.card-header:first-child{border-radius:calc(1.5rem - 1px) calc(1.5rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(34,34,34,.03);border-top:1px solid rgba(34,34,34,.125)}.card-footer:last-child{border-radius:0 0 calc(1.5rem - 1px) calc(1.5rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(1.5rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(1.5rem - 1px);border-top-right-radius:calc(1.5rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(1.5rem - 1px);border-bottom-left-radius:calc(1.5rem - 1px)}.card-deck{display:flex;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:flex;flex:1 0 0%;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:flex;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{column-count:3;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:first-of-type) .card-header:first-child{border-radius:0}.accordion>.card:not(:first-of-type):not(:last-of-type){border-bottom:0;border-radius:0}.accordion>.card:first-of-type{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:last-of-type{border-top-left-radius:0;border-top-right-radius:0}.accordion>.card .card-header{margin-bottom:-1px}.breadcrumb{display:flex;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:1.5rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none;border-radius:1.5rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#ed8d09;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#a46206;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(250,197,124,.75)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:1.5rem;border-bottom-left-radius:1.5rem}.page-item:last-child .page-link{border-top-right-radius:1.5rem;border-bottom-right-radius:1.5rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#fac57c;border-color:#fac57c}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:1.5rem;border-bottom-left-radius:1.5rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:1.5rem;border-bottom-right-radius:1.5rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:1rem;border-bottom-left-radius:1rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:1rem;border-bottom-right-radius:1rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:1.5rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#212529;background-color:#fac57c}a.badge-primary:focus,a.badge-primary:hover{color:#212529;background-color:#f8af4b}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(250,197,124,.5)}.badge-secondary{color:#212529;background-color:#bad2be}a.badge-secondary:focus,a.badge-secondary:hover{color:#212529;background-color:#9bbea1}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(186,210,190,.5)}.badge-success{color:#fff;background-color:#38553d}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#243627}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(56,85,61,.5)}.badge-info{color:#212529;background-color:#49ce5f}a.badge-info:focus,a.badge-info:hover{color:#212529;background-color:#30b446}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(73,206,95,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#212529;background-color:#ed8d09}a.badge-danger:focus,a.badge-danger:hover{color:#212529;background-color:#bc7007}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(237,141,9,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:1.5rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:1.5rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#927751;background-color:#fef3e5;border-color:#feefda}.alert-primary hr{border-top-color:#fde4c1}.alert-primary .alert-link{color:#715c3f}.alert-secondary{color:#717e73;background-color:#f1f6f2;border-color:#ecf2ed}.alert-secondary hr{border-top-color:#dde8df}.alert-secondary .alert-link{color:#59635a}.alert-success{color:#2d3d30;background-color:#d7ddd8;border-color:#c7cfc9}.alert-success hr{border-top-color:#b9c3bc}.alert-success .alert-link{color:#172019}.alert-info{color:#367b42;background-color:#dbf5df;border-color:#ccf1d2}.alert-info hr{border-top-color:#b8ecc0}.alert-info .alert-link{color:#26582f}.alert-warning{color:#957514;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#68520e}.alert-danger{color:#8c5a15;background-color:#fbe8ce;border-color:#fadfba}.alert-danger hr{border-top-color:#f8d4a2}.alert-danger .alert-link{color:#603d0e}.alert-light{color:#919292;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#777979}.alert-dark{color:#2b2e32;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#131517}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:1.5rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#fac57c;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.media{display:flex;align-items:flex-start}.media-body{flex:1}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#495057;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(34,34,34,.125)}.list-group-item:first-child{border-top-left-radius:1.5rem;border-top-right-radius:1.5rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:1.5rem;border-bottom-left-radius:1.5rem}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#fac57c;border-color:#fac57c}.list-group-horizontal{flex-direction:row}.list-group-horizontal .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal .list-group-item:first-child{border-top-left-radius:1.5rem;border-bottom-left-radius:1.5rem;border-top-right-radius:0}.list-group-horizontal .list-group-item:last-child{margin-right:0;border-top-right-radius:1.5rem;border-bottom-right-radius:1.5rem;border-bottom-left-radius:0}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-sm .list-group-item:first-child{border-top-left-radius:1.5rem;border-bottom-left-radius:1.5rem;border-top-right-radius:0}.list-group-horizontal-sm .list-group-item:last-child{margin-right:0;border-top-right-radius:1.5rem;border-bottom-right-radius:1.5rem;border-bottom-left-radius:0}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-md .list-group-item:first-child{border-top-left-radius:1.5rem;border-bottom-left-radius:1.5rem;border-top-right-radius:0}.list-group-horizontal-md .list-group-item:last-child{margin-right:0;border-top-right-radius:1.5rem;border-bottom-right-radius:1.5rem;border-bottom-left-radius:0}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-lg .list-group-item:first-child{border-top-left-radius:1.5rem;border-bottom-left-radius:1.5rem;border-top-right-radius:0}.list-group-horizontal-lg .list-group-item:last-child{margin-right:0;border-top-right-radius:1.5rem;border-bottom-right-radius:1.5rem;border-bottom-left-radius:0}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-xl .list-group-item:first-child{border-top-left-radius:1.5rem;border-bottom-left-radius:1.5rem;border-top-right-radius:0}.list-group-horizontal-xl .list-group-item:last-child{margin-right:0;border-top-right-radius:1.5rem;border-bottom-right-radius:1.5rem;border-bottom-left-radius:0}}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush .list-group-item:last-child{margin-bottom:-1px}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{margin-bottom:0;border-bottom:0}.list-group-item-primary{color:#927751;background-color:#feefda}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#927751;background-color:#fde4c1}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#927751;border-color:#927751}.list-group-item-secondary{color:#717e73;background-color:#ecf2ed}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#717e73;background-color:#dde8df}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#717e73;border-color:#717e73}.list-group-item-success{color:#2d3d30;background-color:#c7cfc9}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#2d3d30;background-color:#b9c3bc}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#2d3d30;border-color:#2d3d30}.list-group-item-info{color:#367b42;background-color:#ccf1d2}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#367b42;background-color:#b8ecc0}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#367b42;border-color:#367b42}.list-group-item-warning{color:#957514;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#957514;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#957514;border-color:#957514}.list-group-item-danger{color:#8c5a15;background-color:#fadfba}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#8c5a15;background-color:#f8d4a2}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#8c5a15;border-color:#8c5a15}.list-group-item-light{color:#919292;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#919292;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#919292;border-color:#919292}.list-group-item-dark{color:#2b2e32;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#2b2e32;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#2b2e32;border-color:#2b2e32}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#222;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#222;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0;appearance:none}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(34,34,34,.1);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:flex;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal-dialog-scrollable{display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);content:""}.modal-dialog-centered.modal-dialog-scrollable{flex-direction:column;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(34,34,34,.2);border-radius:1.5rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#222}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;align-items:flex-start;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #495057;border-top-left-radius:1.5rem;border-top-right-radius:1.5rem}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;align-items:center;justify-content:flex-end;padding:1rem;border-top:1px solid #495057;border-bottom-right-radius:1.5rem;border-bottom-left-radius:1.5rem}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Droid Sans","Segoe UI",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#222}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#222}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#222}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#222}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#222;border-radius:1.5rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Droid Sans","Segoe UI",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(34,34,34,.2);border-radius:1.5rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 1.5rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(34,34,34,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:1.5rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(34,34,34,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(34,34,34,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:1.5rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(34,34,34,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;color:#495057;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(1.5rem - 1px);border-top-right-radius:calc(1.5rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#495057}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:0s .6s opacity}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23ffffff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23ffffff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:flex;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#fac57c!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#f8af4b!important}.bg-secondary{background-color:#bad2be!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#9bbea1!important}.bg-success{background-color:#38553d!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#243627!important}.bg-info{background-color:#49ce5f!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#30b446!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#ed8d09!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bc7007!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #495057!important}.border-top{border-top:1px solid #495057!important}.border-right{border-right:1px solid #495057!important}.border-bottom{border-bottom:1px solid #495057!important}.border-left{border-left:1px solid #495057!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#fac57c!important}.border-secondary{border-color:#bad2be!important}.border-success{border-color:#38553d!important}.border-info{border-color:#49ce5f!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#ed8d09!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:1rem!important}.rounded{border-radius:1.5rem!important}.rounded-top{border-top-left-radius:1.5rem!important;border-top-right-radius:1.5rem!important}.rounded-right{border-top-right-radius:1.5rem!important;border-bottom-right-radius:1.5rem!important}.rounded-bottom{border-bottom-right-radius:1.5rem!important;border-bottom-left-radius:1.5rem!important}.rounded-left{border-top-left-radius:1.5rem!important;border-bottom-left-radius:1.5rem!important}.rounded-lg{border-radius:1.5rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.85714%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-fill{flex:1 1 auto!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}@media (min-width:576px){.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}}@media (min-width:768px){.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports (position:sticky){.sticky-top{position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(34,34,34,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(34,34,34,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(34,34,34,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#fac57c!important}a.text-primary:focus,a.text-primary:hover{color:#f7a432!important}.text-secondary{color:#bad2be!important}a.text-secondary:focus,a.text-secondary:hover{color:#8cb492!important}.text-success{color:#38553d!important}a.text-success:focus,a.text-success:hover{color:#19271c!important}.text-info{color:#49ce5f!important}a.text-info:focus,a.text-info:hover{color:#2ba03e!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#ed8d09!important}a.text-danger:focus,a.text-danger:hover{color:#a46206!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#495057!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(34,34,34,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;overflow-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #222}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#495057}.table .thead-dark th{color:inherit;border-color:#495057}}
\ No newline at end of file
+:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#d8486a;--orange:#f1641e;--yellow:#ffc107;--green:#00C853;--teal:#20c997;--cyan:#02bdc2;--white:#ffffff;--gray:#6c757d;--gray-dark:#343a40;--primary:#f1641e;--secondary:#00C853;--success:#6610f2;--info:#007bff;--warning:#ffc107;--danger:#873208;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Droid Sans","Segoe UI","Helvetica",Arial,sans-serif;--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(34,34,34,0)}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Droid Sans","Segoe UI",Helvetica,Arial,sans-serif;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;text-decoration:underline dotted;cursor:help;border-bottom:0;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:600}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#f1641e;text-decoration:none;background-color:transparent}a:hover{color:#b7440b;text-decoration:underline}a:not([href]){color:inherit;text-decoration:none}a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2;color:#495057}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(34,34,34,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fffcef}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.5rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:1rem}kbd kbd{padding:0;font-size:100%;font-weight:600}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:flex;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-1>*{flex:0 0 100%;max-width:100%}.row-cols-2>*{flex:0 0 50%;max-width:50%}.row-cols-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-4>*{flex:0 0 25%;max-width:25%}.row-cols-5>*{flex:0 0 20%;max-width:20%}.row-cols-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-auto{flex:0 0 auto;width:auto;max-width:100%}.col-1{flex:0 0 8.33333%;max-width:8.33333%}.col-2{flex:0 0 16.66667%;max-width:16.66667%}.col-3{flex:0 0 25%;max-width:25%}.col-4{flex:0 0 33.33333%;max-width:33.33333%}.col-5{flex:0 0 41.66667%;max-width:41.66667%}.col-6{flex:0 0 50%;max-width:50%}.col-7{flex:0 0 58.33333%;max-width:58.33333%}.col-8{flex:0 0 66.66667%;max-width:66.66667%}.col-9{flex:0 0 75%;max-width:75%}.col-10{flex:0 0 83.33333%;max-width:83.33333%}.col-11{flex:0 0 91.66667%;max-width:91.66667%}.col-12{flex:0 0 100%;max-width:100%}.order-first{order:-1}.order-last{order:13}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-9{order:9}.order-10{order:10}.order-11{order:11}.order-12{order:12}.offset-1{margin-left:8.33333%}.offset-2{margin-left:16.66667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333%}.offset-5{margin-left:41.66667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333%}.offset-8{margin-left:66.66667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333%}.offset-11{margin-left:91.66667%}@media (min-width:576px){.col-sm{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-sm-1>*{flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-sm-4>*{flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-sm-auto{flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{flex:0 0 8.33333%;max-width:8.33333%}.col-sm-2{flex:0 0 16.66667%;max-width:16.66667%}.col-sm-3{flex:0 0 25%;max-width:25%}.col-sm-4{flex:0 0 33.33333%;max-width:33.33333%}.col-sm-5{flex:0 0 41.66667%;max-width:41.66667%}.col-sm-6{flex:0 0 50%;max-width:50%}.col-sm-7{flex:0 0 58.33333%;max-width:58.33333%}.col-sm-8{flex:0 0 66.66667%;max-width:66.66667%}.col-sm-9{flex:0 0 75%;max-width:75%}.col-sm-10{flex:0 0 83.33333%;max-width:83.33333%}.col-sm-11{flex:0 0 91.66667%;max-width:91.66667%}.col-sm-12{flex:0 0 100%;max-width:100%}.order-sm-first{order:-1}.order-sm-last{order:13}.order-sm-0{order:0}.order-sm-1{order:1}.order-sm-2{order:2}.order-sm-3{order:3}.order-sm-4{order:4}.order-sm-5{order:5}.order-sm-6{order:6}.order-sm-7{order:7}.order-sm-8{order:8}.order-sm-9{order:9}.order-sm-10{order:10}.order-sm-11{order:11}.order-sm-12{order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333%}.offset-sm-2{margin-left:16.66667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333%}.offset-sm-5{margin-left:41.66667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333%}.offset-sm-8{margin-left:66.66667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333%}.offset-sm-11{margin-left:91.66667%}}@media (min-width:768px){.col-md{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-md-1>*{flex:0 0 100%;max-width:100%}.row-cols-md-2>*{flex:0 0 50%;max-width:50%}.row-cols-md-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-md-4>*{flex:0 0 25%;max-width:25%}.row-cols-md-5>*{flex:0 0 20%;max-width:20%}.row-cols-md-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-md-auto{flex:0 0 auto;width:auto;max-width:100%}.col-md-1{flex:0 0 8.33333%;max-width:8.33333%}.col-md-2{flex:0 0 16.66667%;max-width:16.66667%}.col-md-3{flex:0 0 25%;max-width:25%}.col-md-4{flex:0 0 33.33333%;max-width:33.33333%}.col-md-5{flex:0 0 41.66667%;max-width:41.66667%}.col-md-6{flex:0 0 50%;max-width:50%}.col-md-7{flex:0 0 58.33333%;max-width:58.33333%}.col-md-8{flex:0 0 66.66667%;max-width:66.66667%}.col-md-9{flex:0 0 75%;max-width:75%}.col-md-10{flex:0 0 83.33333%;max-width:83.33333%}.col-md-11{flex:0 0 91.66667%;max-width:91.66667%}.col-md-12{flex:0 0 100%;max-width:100%}.order-md-first{order:-1}.order-md-last{order:13}.order-md-0{order:0}.order-md-1{order:1}.order-md-2{order:2}.order-md-3{order:3}.order-md-4{order:4}.order-md-5{order:5}.order-md-6{order:6}.order-md-7{order:7}.order-md-8{order:8}.order-md-9{order:9}.order-md-10{order:10}.order-md-11{order:11}.order-md-12{order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333%}.offset-md-2{margin-left:16.66667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333%}.offset-md-5{margin-left:41.66667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333%}.offset-md-8{margin-left:66.66667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333%}.offset-md-11{margin-left:91.66667%}}@media (min-width:992px){.col-lg{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-lg-1>*{flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-lg-4>*{flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-lg-auto{flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{flex:0 0 8.33333%;max-width:8.33333%}.col-lg-2{flex:0 0 16.66667%;max-width:16.66667%}.col-lg-3{flex:0 0 25%;max-width:25%}.col-lg-4{flex:0 0 33.33333%;max-width:33.33333%}.col-lg-5{flex:0 0 41.66667%;max-width:41.66667%}.col-lg-6{flex:0 0 50%;max-width:50%}.col-lg-7{flex:0 0 58.33333%;max-width:58.33333%}.col-lg-8{flex:0 0 66.66667%;max-width:66.66667%}.col-lg-9{flex:0 0 75%;max-width:75%}.col-lg-10{flex:0 0 83.33333%;max-width:83.33333%}.col-lg-11{flex:0 0 91.66667%;max-width:91.66667%}.col-lg-12{flex:0 0 100%;max-width:100%}.order-lg-first{order:-1}.order-lg-last{order:13}.order-lg-0{order:0}.order-lg-1{order:1}.order-lg-2{order:2}.order-lg-3{order:3}.order-lg-4{order:4}.order-lg-5{order:5}.order-lg-6{order:6}.order-lg-7{order:7}.order-lg-8{order:8}.order-lg-9{order:9}.order-lg-10{order:10}.order-lg-11{order:11}.order-lg-12{order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333%}.offset-lg-2{margin-left:16.66667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333%}.offset-lg-5{margin-left:41.66667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333%}.offset-lg-8{margin-left:66.66667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333%}.offset-lg-11{margin-left:91.66667%}}@media (min-width:1200px){.col-xl{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-xl-1>*{flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-xl-4>*{flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-xl-auto{flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{flex:0 0 8.33333%;max-width:8.33333%}.col-xl-2{flex:0 0 16.66667%;max-width:16.66667%}.col-xl-3{flex:0 0 25%;max-width:25%}.col-xl-4{flex:0 0 33.33333%;max-width:33.33333%}.col-xl-5{flex:0 0 41.66667%;max-width:41.66667%}.col-xl-6{flex:0 0 50%;max-width:50%}.col-xl-7{flex:0 0 58.33333%;max-width:58.33333%}.col-xl-8{flex:0 0 66.66667%;max-width:66.66667%}.col-xl-9{flex:0 0 75%;max-width:75%}.col-xl-10{flex:0 0 83.33333%;max-width:83.33333%}.col-xl-11{flex:0 0 91.66667%;max-width:91.66667%}.col-xl-12{flex:0 0 100%;max-width:100%}.order-xl-first{order:-1}.order-xl-last{order:13}.order-xl-0{order:0}.order-xl-1{order:1}.order-xl-2{order:2}.order-xl-3{order:3}.order-xl-4{order:4}.order-xl-5{order:5}.order-xl-6{order:6}.order-xl-7{order:7}.order-xl-8{order:8}.order-xl-9{order:9}.order-xl-10{order:10}.order-xl-11{order:11}.order-xl-12{order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333%}.offset-xl-2{margin-left:16.66667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333%}.offset-xl-5{margin-left:41.66667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333%}.offset-xl-8{margin-left:66.66667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333%}.offset-xl-11{margin-left:91.66667%}}.table{width:100%;margin-bottom:1rem;color:#495057}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #495057}.table thead th{vertical-align:bottom;border-bottom:2px solid #495057}.table tbody+tbody{border-top:2px solid #495057}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #495057}.table-bordered td,.table-bordered th{border:1px solid #495057}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(34,34,34,.05)}.table-hover tbody tr:hover{color:#495057;background-color:rgba(34,34,34,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#fbd4c0}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#f8ae8a}.table-hover .table-primary:hover{background-color:#f9c4a8}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#f9c4a8}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#b8f0cf}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#7ae2a6}.table-hover .table-secondary:hover{background-color:#a3ecc1}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#a3ecc1}.table-success,.table-success>td,.table-success>th{background-color:#d4bcfb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#af83f8}.table-hover .table-success:hover{background-color:#c5a4fa}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#c5a4fa}.table-info,.table-info>td,.table-info>th{background-color:#b8daff}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#7abaff}.table-hover .table-info:hover{background-color:#9fcdff}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#9fcdff}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#ddc6ba}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#c1957f}.table-hover .table-danger:hover{background-color:#d5b8a9}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#d5b8a9}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(34,34,34,.075)}.table-hover .table-active:hover{background-color:rgba(21,21,21,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(21,21,21,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#495057}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.5rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.form-control:focus{color:#495057;background-color:#fff;border-color:#f8b796;outline:0;box-shadow:0 0 0 .2rem rgba(241,100,30,.75)}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{appearance:none}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#495057;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:1rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.5rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:flex;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:inline-flex;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#007bff}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(0,123,255,.9);border-radius:.5rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#007bff;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23007bff' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#007bff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#007bff;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23007bff' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#007bff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#007bff}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#007bff}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#007bff}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#3395ff;background-color:#3395ff}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#007bff}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#007bff}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#007bff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#873208}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(135,50,8,.9);border-radius:.5rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#873208;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23873208' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23873208' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#873208;box-shadow:0 0 0 .2rem rgba(135,50,8,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#873208;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23873208' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23873208' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#873208;box-shadow:0 0 0 .2rem rgba(135,50,8,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#873208}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#873208}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#873208}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#b7440b;background-color:#b7440b}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(135,50,8,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#873208}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#873208}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#873208;box-shadow:0 0 0 .2rem rgba(135,50,8,.25)}.form-inline{display:flex;flex-flow:row wrap;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:flex;align-items:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:flex;flex:0 0 auto;flex-flow:row wrap;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:flex;align-items:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{align-items:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#495057;text-align:center;vertical-align:middle;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.5rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#495057;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(241,100,30,.75)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#f1641e;border-color:#f1641e}.btn-primary:hover{color:#fff;background-color:#db520e;border-color:#cf4d0d}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#db520e;border-color:#cf4d0d;box-shadow:0 0 0 .2rem rgba(243,123,64,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#f1641e;border-color:#f1641e}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#cf4d0d;border-color:#c3490c}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(243,123,64,.5)}.btn-secondary{color:#fff;background-color:#00c853;border-color:#00c853}.btn-secondary:hover{color:#fff;background-color:#00a243;border-color:#00953e}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#00a243;border-color:#00953e;box-shadow:0 0 0 .2rem rgba(38,208,109,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#00c853;border-color:#00c853}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#00953e;border-color:#008839}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,208,109,.5)}.btn-success{color:#fff;background-color:#6610f2;border-color:#6610f2}.btn-success:hover{color:#fff;background-color:#560bd0;border-color:#510bc4}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#560bd0;border-color:#510bc4;box-shadow:0 0 0 .2rem rgba(125,52,244,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#6610f2;border-color:#6610f2}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#510bc4;border-color:#4c0ab8}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(125,52,244,.5)}.btn-info{color:#fff;background-color:#007bff;border-color:#007bff}.btn-info:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#212529;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#873208;border-color:#873208}.btn-danger:hover{color:#fff;background-color:#632506;border-color:#572105}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#632506;border-color:#572105;box-shadow:0 0 0 .2rem rgba(153,81,45,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#873208;border-color:#873208}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#572105;border-color:#4b1c05}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(153,81,45,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{color:#212529;background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#f1641e;border-color:#f1641e}.btn-outline-primary:hover{color:#fff;background-color:#f1641e;border-color:#f1641e}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(241,100,30,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#f1641e;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#f1641e;border-color:#f1641e}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(241,100,30,.5)}.btn-outline-secondary{color:#00c853;border-color:#00c853}.btn-outline-secondary:hover{color:#fff;background-color:#00c853;border-color:#00c853}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(0,200,83,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#00c853;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#00c853;border-color:#00c853}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,200,83,.5)}.btn-outline-success{color:#6610f2;border-color:#6610f2}.btn-outline-success:hover{color:#fff;background-color:#6610f2;border-color:#6610f2}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(102,16,242,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#6610f2;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#6610f2;border-color:#6610f2}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(102,16,242,.5)}.btn-outline-info{color:#007bff;border-color:#007bff}.btn-outline-info:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#007bff;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#873208;border-color:#873208}.btn-outline-danger:hover{color:#fff;background-color:#873208;border-color:#873208}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(135,50,8,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#873208;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#873208;border-color:#873208}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(135,50,8,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#f1641e;text-decoration:none}.btn-link:hover{color:#b7440b;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.5rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:1rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#495057;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(34,34,34,.15);border-radius:.5rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#f1641e}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:flex;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.5rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.5rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:1rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#f1641e;background-color:#f1641e}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(241,100,30,.75)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#f8b796}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#fbd8c6;border-color:#fbd8c6}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.5rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23ffffff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#f1641e;background-color:#f1641e}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23ffffff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(241,100,30,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(241,100,30,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23ffffff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(241,100,30,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(241,100,30,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;border:1px solid #ced4da;border-radius:.5rem;appearance:none}.custom-select:focus{border-color:#f8b796;outline:0;box-shadow:0 0 0 .2rem rgba(241,100,30,.75)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#f8b796;box-shadow:0 0 0 .2rem rgba(241,100,30,.75)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.5rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .5rem .5rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(241,100,30,.75)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(241,100,30,.75)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(241,100,30,.75)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#f1641e;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#fbd8c6}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#f1641e;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#fbd8c6}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#f1641e;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#fbd8c6}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.5rem;border-top-right-radius:.5rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.5rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#f1641e}.nav-fill .nav-item{flex:1 1 auto;text-align:center}.nav-justified .nav-item{flex-basis:0;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.5rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:#212529}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:#212529}.navbar-light .navbar-nav .nav-link{color:#6c757d}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:#212529}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(34,34,34,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:#212529}.navbar-light .navbar-toggler{color:#6c757d;border-color:rgba(34,34,34,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='%236c757d' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:#6c757d}.navbar-light .navbar-text a{color:#212529}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:#212529}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(34,34,34,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#f8f9fa;background-clip:border-box;border:1px solid rgba(34,34,34,.125);border-radius:.5rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.5rem - 1px);border-top-right-radius:calc(.5rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.5rem - 1px);border-bottom-left-radius:calc(.5rem - 1px)}.card-body{flex:1 1 auto;min-height:1px;padding:1.25rem;color:#495057}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;color:#495057;background-color:rgba(34,34,34,.03);border-bottom:1px solid rgba(34,34,34,.125)}.card-header:first-child{border-radius:calc(.5rem - 1px) calc(.5rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;color:#495057;background-color:rgba(34,34,34,.03);border-top:1px solid rgba(34,34,34,.125)}.card-footer:last-child{border-radius:0 0 calc(.5rem - 1px) calc(.5rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img,.card-img-bottom,.card-img-top{flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.5rem - 1px);border-top-right-radius:calc(.5rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.5rem - 1px);border-bottom-left-radius:calc(.5rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:flex;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{column-count:3;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:flex;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.5rem}.breadcrumb-item{display:flex}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none;border-radius:.5rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#f1641e;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#b7440b;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(241,100,30,.75)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.5rem;border-bottom-left-radius:.5rem}.page-item:last-child .page-link{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#f1641e;border-color:#f1641e}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.5rem;border-bottom-left-radius:.5rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:1rem;border-bottom-left-radius:1rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:1rem;border-bottom-right-radius:1rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:600;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.5rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#f1641e}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#cf4d0d}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(241,100,30,.5)}.badge-secondary{color:#fff;background-color:#00c853}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#00953e}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,200,83,.5)}.badge-success{color:#fff;background-color:#6610f2}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#510bc4}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(102,16,242,.5)}.badge-info{color:#fff;background-color:#007bff}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#0062cc}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#873208}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#572105}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(135,50,8,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.5rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.5rem}.alert-heading{color:inherit}.alert-link{font-weight:600}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#8e4420;background-color:#fce0d2;border-color:#fbd4c0}.alert-primary hr{border-top-color:#f9c4a8}.alert-primary .alert-link{color:#643017}.alert-secondary{color:#10783b;background-color:#ccf4dd;border-color:#b8f0cf}.alert-secondary hr{border-top-color:#a3ecc1}.alert-secondary .alert-link{color:#0a4b25}.alert-success{color:#45198e;background-color:#e0cffc;border-color:#d4bcfb}.alert-success hr{border-top-color:#c5a4fa}.alert-success .alert-link{color:#301163}.alert-info{color:#105095;background-color:#cce5ff;border-color:#b8daff}.alert-info hr{border-top-color:#9fcdff}.alert-info .alert-link{color:#0b3767}.alert-warning{color:#957514;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#68520e}.alert-danger{color:#572b15;background-color:#e7d6ce;border-color:#ddc6ba}.alert-danger hr{border-top-color:#d5b8a9}.alert-danger .alert-link{color:#2e170b}.alert-light{color:#919292;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#777979}.alert-dark{color:#2b2e32;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#131517}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.5rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#f1641e;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.media{display:flex;align-items:flex-start}.media-body{flex:1}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.5rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#495057;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(34,34,34,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#f1641e;border-color:#f1641e}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.5rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.5rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.5rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.5rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.5rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.5rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.5rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.5rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.5rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.5rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#8e4420;background-color:#fbd4c0}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#8e4420;background-color:#f9c4a8}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#8e4420;border-color:#8e4420}.list-group-item-secondary{color:#10783b;background-color:#b8f0cf}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#10783b;background-color:#a3ecc1}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#10783b;border-color:#10783b}.list-group-item-success{color:#45198e;background-color:#d4bcfb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#45198e;background-color:#c5a4fa}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#45198e;border-color:#45198e}.list-group-item-info{color:#105095;background-color:#b8daff}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#105095;background-color:#9fcdff}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#105095;border-color:#105095}.list-group-item-warning{color:#957514;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#957514;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#957514;border-color:#957514}.list-group-item-danger{color:#572b15;background-color:#ddc6ba}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#572b15;background-color:#d5b8a9}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#572b15;border-color:#572b15}.list-group-item-light{color:#919292;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#919292;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#919292;border-color:#919292}.list-group-item-dark{color:#2b2e32;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#2b2e32;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#2b2e32;border-color:#2b2e32}.close{float:right;font-size:1.5rem;font-weight:600;line-height:1;color:#222;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#222;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(34,34,34,.1);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:flex;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{flex-direction:column;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(34,34,34,.2);border-radius:.5rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#222}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;align-items:flex-start;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #495057;border-top-left-radius:calc(.5rem - 1px);border-top-right-radius:calc(.5rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #495057;border-bottom-right-radius:calc(.5rem - 1px);border-bottom-left-radius:calc(.5rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Droid Sans","Segoe UI",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#222}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#222}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#222}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#222}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#222;border-radius:.5rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Droid Sans","Segoe UI",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(34,34,34,.2);border-radius:.5rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .5rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(34,34,34,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.5rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(34,34,34,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(34,34,34,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.5rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(34,34,34,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;color:#495057;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.5rem - 1px);border-top-right-radius:calc(.5rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#495057}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23ffffff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23ffffff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:flex;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#f1641e!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#cf4d0d!important}.bg-secondary{background-color:#00c853!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#00953e!important}.bg-success{background-color:#6610f2!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#510bc4!important}.bg-info{background-color:#007bff!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#0062cc!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#873208!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#572105!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #495057!important}.border-top{border-top:1px solid #495057!important}.border-right{border-right:1px solid #495057!important}.border-bottom{border-bottom:1px solid #495057!important}.border-left{border-left:1px solid #495057!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#f1641e!important}.border-secondary{border-color:#00c853!important}.border-success{border-color:#6610f2!important}.border-info{border-color:#007bff!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#873208!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:1rem!important}.rounded{border-radius:.5rem!important}.rounded-top{border-top-left-radius:.5rem!important;border-top-right-radius:.5rem!important}.rounded-right{border-top-right-radius:.5rem!important;border-bottom-right-radius:.5rem!important}.rounded-bottom{border-bottom-right-radius:.5rem!important;border-bottom-left-radius:.5rem!important}.rounded-left{border-top-left-radius:.5rem!important;border-bottom-left-radius:.5rem!important}.rounded-lg{border-radius:.5rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.85714%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-fill{flex:1 1 auto!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}@media (min-width:576px){.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}}@media (min-width:768px){.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{user-select:all!important}.user-select-auto{user-select:auto!important}.user-select-none{user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports (position:sticky){.sticky-top{position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(34,34,34,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(34,34,34,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(34,34,34,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:600!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#f1641e!important}a.text-primary:focus,a.text-primary:hover{color:#b7440b!important}.text-secondary{color:#00c853!important}a.text-secondary:focus,a.text-secondary:hover{color:#007c33!important}.text-success{color:#6610f2!important}a.text-success:focus,a.text-success:hover{color:#4709ac!important}.text-info{color:#007bff!important}a.text-info:focus,a.text-info:hover{color:#0056b3!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#873208!important}a.text-danger:focus,a.text-danger:hover{color:#3f1804!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#495057!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(34,34,34,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #222}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#495057}.table .thead-dark th{color:inherit;border-color:#495057}}
"license": "AGPL-3.0-or-later",
"main": "index.js",
"scripts": {
- "api-test": "jest src/api_tests/api.spec.ts",
+ "api-test": "jest src/api_tests/ -i --verbose",
"build": "node fuse prod",
"lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src",
"prebuild": "node generate_translations.js",
"keywords": [],
"dependencies": {
"@types/autosize": "^3.0.6",
+ "@types/jest": "^26.0.7",
"@types/js-cookie": "^2.2.6",
"@types/jwt-decode": "^2.2.1",
- "@types/markdown-it": "^0.0.9",
+ "@types/markdown-it": "^10.0.1",
"@types/markdown-it-container": "^2.0.2",
- "@types/node": "^13.11.1",
+ "@types/node": "^14.0.26",
+ "@types/node-fetch": "^2.5.6",
"autosize": "^4.0.2",
"bootswatch": "^4.3.1",
"choices.js": "^9.0.1",
"husky": "^4.2.5",
"i18next": "^19.4.1",
"inferno": "^7.4.2",
- "inferno-i18next": "nimbusec-oss/inferno-i18next",
+ "inferno-helmet": "^5.2.1",
+ "inferno-i18next": "github:nimbusec-oss/inferno-i18next#semver:^7.4.2",
"inferno-router": "^7.4.2",
"js-cookie": "^2.2.0",
"jwt-decode": "^2.2.0",
- "markdown-it": "^10.0.0",
- "markdown-it-container": "^2.0.0",
+ "lemmy-js-client": "^1.0.8",
+ "markdown-it": "^11.0.0",
+ "markdown-it-container": "^3.0.0",
"markdown-it-emoji": "^1.4.0",
+ "markdown-it-sub": "^1.0.0",
+ "markdown-it-sup": "^1.0.0",
"moment": "^2.24.0",
"node-fetch": "^2.6.0",
"prettier": "^2.0.4",
"reconnecting-websocket": "^4.4.0",
+ "register-service-worker": "^1.7.1",
"rxjs": "^6.5.5",
"terser": "^4.6.11",
"tippy.js": "^6.1.1",
"ws": "^7.2.3"
},
"devDependencies": {
- "@types/jest": "^25.2.1",
- "@types/node-fetch": "^2.5.6",
- "eslint": "^6.5.1",
+ "eslint": "^7.5.0",
"eslint-plugin-inferno": "^7.14.3",
- "eslint-plugin-jane": "^7.2.1",
+ "eslint-plugin-jane": "^8.0.4",
"fuse-box": "^3.1.3",
- "jest": "^25.4.0",
+ "jest": "^26.0.7",
"lint-staged": "^10.1.3",
"sortpack": "^2.1.4",
- "ts-jest": "^25.4.0",
+ "ts-jest": "^26.1.3",
"ts-node": "^8.8.2",
"ts-transform-classcat": "^1.0.0",
"ts-transform-inferno": "^4.0.3",
"engineStrict": true,
"husky": {
"hooks": {
- "pre-commit": "cargo clippy --manifest-path ../server/Cargo.toml --all-targets --workspace -- -D warnings && lint-staged"
+ "pre-commit": "cargo +nightly clippy --manifest-path ../server/Cargo.toml --all-targets --workspace -- -D warnings && lint-staged"
}
},
"lint-staged": {
"eslint --fix"
],
"../server/src/**/*.rs": [
- "rustfmt --config-path ../server/.rustfmt.toml"
+ "rustfmt +nightly --config-path ../server/.rustfmt.toml"
],
"package.json": [
"sortpack"
+++ /dev/null
-import fetch from 'node-fetch';
-
-import {
- LoginForm,
- LoginResponse,
- PostForm,
- PostResponse,
- SearchResponse,
- FollowCommunityForm,
- CommunityResponse,
- GetFollowedCommunitiesResponse,
- GetPostForm,
- GetPostResponse,
- CommentForm,
- CommentResponse,
- CommunityForm,
- GetCommunityForm,
- GetCommunityResponse,
- CommentLikeForm,
- CreatePostLikeForm,
- PrivateMessageForm,
- EditPrivateMessageForm,
- PrivateMessageResponse,
- PrivateMessagesResponse,
- GetUserMentionsResponse,
-} from '../interfaces';
-
-let lemmyAlphaUrl = 'http://localhost:8540';
-let lemmyAlphaApiUrl = `${lemmyAlphaUrl}/api/v1`;
-let lemmyAlphaAuth: string;
-
-let lemmyBetaUrl = 'http://localhost:8550';
-let lemmyBetaApiUrl = `${lemmyBetaUrl}/api/v1`;
-let lemmyBetaAuth: string;
-
-let lemmyGammaUrl = 'http://localhost:8560';
-let lemmyGammaApiUrl = `${lemmyGammaUrl}/api/v1`;
-let lemmyGammaAuth: string;
-
-// Workaround for tests being run before beforeAll() is finished
-// https://github.com/facebook/jest/issues/9527#issuecomment-592406108
-describe('main', () => {
- beforeAll(async () => {
- console.log('Logging in as lemmy_alpha');
- let form: LoginForm = {
- username_or_email: 'lemmy_alpha',
- password: 'lemmy',
- };
-
- let res: LoginResponse = await fetch(`${lemmyAlphaApiUrl}/user/login`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(form),
- }).then(d => d.json());
-
- lemmyAlphaAuth = res.jwt;
-
- console.log('Logging in as lemmy_beta');
- let formB = {
- username_or_email: 'lemmy_beta',
- password: 'lemmy',
- };
-
- let resB: LoginResponse = await fetch(`${lemmyBetaApiUrl}/user/login`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(formB),
- }).then(d => d.json());
-
- lemmyBetaAuth = resB.jwt;
-
- console.log('Logging in as lemmy_gamma');
- let formC = {
- username_or_email: 'lemmy_gamma',
- password: 'lemmy',
- };
-
- let resG: LoginResponse = await fetch(`${lemmyGammaApiUrl}/user/login`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(formC),
- }).then(d => d.json());
-
- lemmyGammaAuth = resG.jwt;
- });
-
- describe('post_search', () => {
- test('Create test post on alpha and fetch it on beta', async () => {
- let name = 'A jest test post';
- let postForm: PostForm = {
- name,
- auth: lemmyAlphaAuth,
- community_id: 2,
- creator_id: 2,
- nsfw: false,
- };
-
- let createPostRes: PostResponse = await fetch(
- `${lemmyAlphaApiUrl}/post`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(postForm),
- }
- ).then(d => d.json());
- expect(createPostRes.post.name).toBe(name);
-
- let searchUrl = `${lemmyBetaApiUrl}/search?q=${createPostRes.post.ap_id}&type_=All&sort=TopAll`;
- let searchResponse: SearchResponse = await fetch(searchUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- // TODO: check more fields
- expect(searchResponse.posts[0].name).toBe(name);
- });
- });
-
- describe('follow_accept', () => {
- test('/u/lemmy_alpha follows and accepts lemmy-beta/c/main', async () => {
- // Make sure lemmy-beta/c/main is cached on lemmy_alpha
- // Use short-hand search url
- let searchUrl = `${lemmyAlphaApiUrl}/search?q=!main@lemmy-beta:8550&type_=All&sort=TopAll`;
-
- let searchResponse: SearchResponse = await fetch(searchUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(searchResponse.communities[0].name).toBe('main');
-
- let followForm: FollowCommunityForm = {
- community_id: searchResponse.communities[0].id,
- follow: true,
- auth: lemmyAlphaAuth,
- };
-
- let followRes: CommunityResponse = await fetch(
- `${lemmyAlphaApiUrl}/community/follow`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(followForm),
- }
- ).then(d => d.json());
-
- // Make sure the follow response went through
- expect(followRes.community.local).toBe(false);
- expect(followRes.community.name).toBe('main');
-
- // Check that you are subscribed to it locally
- let followedCommunitiesUrl = `${lemmyAlphaApiUrl}/user/followed_communities?&auth=${lemmyAlphaAuth}`;
- let followedCommunitiesRes: GetFollowedCommunitiesResponse = await fetch(
- followedCommunitiesUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
-
- expect(followedCommunitiesRes.communities[1].community_local).toBe(false);
-
- // Test out unfollowing
- let unfollowForm: FollowCommunityForm = {
- community_id: searchResponse.communities[0].id,
- follow: false,
- auth: lemmyAlphaAuth,
- };
-
- let unfollowRes: CommunityResponse = await fetch(
- `${lemmyAlphaApiUrl}/community/follow`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(unfollowForm),
- }
- ).then(d => d.json());
- expect(unfollowRes.community.local).toBe(false);
-
- // Check that you are unsubscribed to it locally
- let followedCommunitiesResAgain: GetFollowedCommunitiesResponse = await fetch(
- followedCommunitiesUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
-
- expect(followedCommunitiesResAgain.communities.length).toBe(1);
-
- // Follow again, for other tests
- let followResAgain: CommunityResponse = await fetch(
- `${lemmyAlphaApiUrl}/community/follow`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(followForm),
- }
- ).then(d => d.json());
-
- // Make sure the follow response went through
- expect(followResAgain.community.local).toBe(false);
- expect(followResAgain.community.name).toBe('main');
-
- // Also make G follow B
-
- // Use short-hand search url
- let searchUrlG = `${lemmyGammaApiUrl}/search?q=!main@lemmy-beta:8550&type_=All&sort=TopAll`;
-
- let searchResponseG: SearchResponse = await fetch(searchUrlG, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(searchResponseG.communities[0].name).toBe('main');
-
- let followFormG: FollowCommunityForm = {
- community_id: searchResponseG.communities[0].id,
- follow: true,
- auth: lemmyGammaAuth,
- };
-
- let followResG: CommunityResponse = await fetch(
- `${lemmyGammaApiUrl}/community/follow`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(followFormG),
- }
- ).then(d => d.json());
-
- // Make sure the follow response went through
- expect(followResG.community.local).toBe(false);
- expect(followResG.community.name).toBe('main');
-
- // Check that you are subscribed to it locally
- let followedCommunitiesUrlG = `${lemmyGammaApiUrl}/user/followed_communities?&auth=${lemmyGammaAuth}`;
- let followedCommunitiesResG: GetFollowedCommunitiesResponse = await fetch(
- followedCommunitiesUrlG,
- {
- method: 'GET',
- }
- ).then(d => d.json());
-
- expect(followedCommunitiesResG.communities[1].community_local).toBe(
- false
- );
- });
- });
-
- describe('create test post', () => {
- test('/u/lemmy_alpha creates a post on /c/lemmy_beta/main, its on both instances', async () => {
- let name = 'A jest test federated post';
- let postForm: PostForm = {
- name,
- auth: lemmyAlphaAuth,
- community_id: 3,
- creator_id: 2,
- nsfw: false,
- };
-
- let createResponse: PostResponse = await fetch(
- `${lemmyAlphaApiUrl}/post`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(postForm),
- }
- ).then(d => d.json());
-
- let unlikePostForm: CreatePostLikeForm = {
- post_id: createResponse.post.id,
- score: 0,
- auth: lemmyAlphaAuth,
- };
- expect(createResponse.post.name).toBe(name);
- expect(createResponse.post.community_local).toBe(false);
- expect(createResponse.post.creator_local).toBe(true);
- expect(createResponse.post.score).toBe(1);
-
- let unlikePostRes: PostResponse = await fetch(
- `${lemmyAlphaApiUrl}/post/like`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(unlikePostForm),
- }
- ).then(d => d.json());
- expect(unlikePostRes.post.score).toBe(0);
-
- let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getPostRes.post.name).toBe(name);
- expect(getPostRes.post.community_local).toBe(true);
- expect(getPostRes.post.creator_local).toBe(false);
- expect(getPostRes.post.score).toBe(0);
- });
- });
-
- describe('update test post', () => {
- test('/u/lemmy_alpha updates a post on /c/lemmy_beta/main, the update is on both', async () => {
- let name = 'A jest test federated post, updated';
- let postForm: PostForm = {
- name,
- edit_id: 2,
- auth: lemmyAlphaAuth,
- community_id: 3,
- creator_id: 2,
- nsfw: false,
- };
-
- let updateResponse: PostResponse = await fetch(
- `${lemmyAlphaApiUrl}/post`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(postForm),
- }
- ).then(d => d.json());
-
- expect(updateResponse.post.name).toBe(name);
- expect(updateResponse.post.community_local).toBe(false);
- expect(updateResponse.post.creator_local).toBe(true);
-
- let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getPostRes.post.name).toBe(name);
- expect(getPostRes.post.community_local).toBe(true);
- expect(getPostRes.post.creator_local).toBe(false);
- });
- });
-
- describe('create test comment', () => {
- test('/u/lemmy_alpha creates a comment on /c/lemmy_beta/main, its on both instances', async () => {
- let content = 'A jest test federated comment';
- let commentForm: CommentForm = {
- content,
- post_id: 2,
- auth: lemmyAlphaAuth,
- };
-
- let createResponse: CommentResponse = await fetch(
- `${lemmyAlphaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(commentForm),
- }
- ).then(d => d.json());
-
- expect(createResponse.comment.content).toBe(content);
- expect(createResponse.comment.community_local).toBe(false);
- expect(createResponse.comment.creator_local).toBe(true);
- expect(createResponse.comment.score).toBe(1);
-
- // Do an unlike, to test it
- let unlikeCommentForm: CommentLikeForm = {
- comment_id: createResponse.comment.id,
- score: 0,
- post_id: 2,
- auth: lemmyAlphaAuth,
- };
-
- let unlikeCommentRes: CommentResponse = await fetch(
- `${lemmyAlphaApiUrl}/comment/like`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(unlikeCommentForm),
- }
- ).then(d => d.json());
-
- expect(unlikeCommentRes.comment.score).toBe(0);
-
- let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getPostRes.comments[0].content).toBe(content);
- expect(getPostRes.comments[0].community_local).toBe(true);
- expect(getPostRes.comments[0].creator_local).toBe(false);
- expect(getPostRes.comments[0].score).toBe(0);
-
- // Now do beta replying to that comment, as a child comment
- let contentBeta = 'A child federated comment from beta';
- let commentFormBeta: CommentForm = {
- content: contentBeta,
- post_id: getPostRes.post.id,
- parent_id: getPostRes.comments[0].id,
- auth: lemmyBetaAuth,
- };
-
- let createResponseBeta: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(commentFormBeta),
- }
- ).then(d => d.json());
-
- expect(createResponseBeta.comment.content).toBe(contentBeta);
- expect(createResponseBeta.comment.community_local).toBe(true);
- expect(createResponseBeta.comment.creator_local).toBe(true);
- expect(createResponseBeta.comment.parent_id).toBe(1);
- expect(createResponseBeta.comment.score).toBe(1);
-
- // Make sure lemmy alpha sees that new child comment from beta
- let getPostUrlAlpha = `${lemmyAlphaApiUrl}/post?id=2`;
- let getPostResAlpha: GetPostResponse = await fetch(getPostUrlAlpha, {
- method: 'GET',
- }).then(d => d.json());
-
- // The newest show up first
- expect(getPostResAlpha.comments[0].content).toBe(contentBeta);
- expect(getPostResAlpha.comments[0].community_local).toBe(false);
- expect(getPostResAlpha.comments[0].creator_local).toBe(false);
- expect(getPostResAlpha.comments[0].score).toBe(1);
-
- // Lemmy alpha responds to their own comment, but mentions lemmy beta.
- // Make sure lemmy beta gets that in their inbox.
- let mentionContent = 'A test mention of @lemmy_beta@lemmy-beta:8550';
- let mentionCommentForm: CommentForm = {
- content: mentionContent,
- post_id: 2,
- parent_id: createResponse.comment.id,
- auth: lemmyAlphaAuth,
- };
-
- let createMentionRes: CommentResponse = await fetch(
- `${lemmyAlphaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(mentionCommentForm),
- }
- ).then(d => d.json());
-
- expect(createMentionRes.comment.content).toBe(mentionContent);
- expect(createMentionRes.comment.community_local).toBe(false);
- expect(createMentionRes.comment.creator_local).toBe(true);
- expect(createMentionRes.comment.score).toBe(1);
-
- // Make sure lemmy beta sees that new mention
- let getMentionUrl = `${lemmyBetaApiUrl}/user/mention?sort=New&unread_only=false&auth=${lemmyBetaAuth}`;
- let getMentionsRes: GetUserMentionsResponse = await fetch(getMentionUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- // The newest show up first
- expect(getMentionsRes.mentions[0].content).toBe(mentionContent);
- expect(getMentionsRes.mentions[0].community_local).toBe(true);
- expect(getMentionsRes.mentions[0].creator_local).toBe(false);
- expect(getMentionsRes.mentions[0].score).toBe(1);
- });
- });
-
- describe('update test comment', () => {
- test('/u/lemmy_alpha updates a comment on /c/lemmy_beta/main, its on both instances', async () => {
- let content = 'A jest test federated comment update';
- let commentForm: CommentForm = {
- content,
- post_id: 2,
- edit_id: 1,
- auth: lemmyAlphaAuth,
- creator_id: 2,
- };
-
- let updateResponse: CommentResponse = await fetch(
- `${lemmyAlphaApiUrl}/comment`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(commentForm),
- }
- ).then(d => d.json());
-
- expect(updateResponse.comment.content).toBe(content);
- expect(updateResponse.comment.community_local).toBe(false);
- expect(updateResponse.comment.creator_local).toBe(true);
-
- let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getPostRes.comments[2].content).toBe(content);
- expect(getPostRes.comments[2].community_local).toBe(true);
- expect(getPostRes.comments[2].creator_local).toBe(false);
- });
- });
-
- describe('delete things', () => {
- test('/u/lemmy_beta deletes and undeletes a federated comment, post, and community, lemmy_alpha sees its deleted.', async () => {
- // Create a test community
- let communityName = 'test_community';
- let communityForm: CommunityForm = {
- name: communityName,
- title: communityName,
- category_id: 1,
- nsfw: false,
- auth: lemmyBetaAuth,
- };
-
- let createCommunityRes: CommunityResponse = await fetch(
- `${lemmyBetaApiUrl}/community`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(communityForm),
- }
- ).then(d => d.json());
-
- expect(createCommunityRes.community.name).toBe(communityName);
-
- // Cache it on lemmy_alpha
- let searchUrl = `${lemmyAlphaApiUrl}/search?q=http://lemmy-beta:8550/c/${communityName}&type_=All&sort=TopAll`;
- let searchResponse: SearchResponse = await fetch(searchUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- let communityOnAlphaId = searchResponse.communities[0].id;
-
- // Follow it
- let followForm: FollowCommunityForm = {
- community_id: communityOnAlphaId,
- follow: true,
- auth: lemmyAlphaAuth,
- };
-
- let followRes: CommunityResponse = await fetch(
- `${lemmyAlphaApiUrl}/community/follow`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(followForm),
- }
- ).then(d => d.json());
-
- // Make sure the follow response went through
- expect(followRes.community.local).toBe(false);
- expect(followRes.community.name).toBe(communityName);
-
- // Lemmy beta creates a test post
- let postName = 'A jest test post with delete';
- let createPostForm: PostForm = {
- name: postName,
- auth: lemmyBetaAuth,
- community_id: createCommunityRes.community.id,
- creator_id: 2,
- nsfw: false,
- };
-
- let createPostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(createPostForm),
- }).then(d => d.json());
- expect(createPostRes.post.name).toBe(postName);
-
- // Lemmy beta creates a test comment
- let commentContent = 'A jest test federated comment with delete';
- let createCommentForm: CommentForm = {
- content: commentContent,
- post_id: createPostRes.post.id,
- auth: lemmyBetaAuth,
- };
-
- let createCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(createCommentForm),
- }
- ).then(d => d.json());
-
- expect(createCommentRes.comment.content).toBe(commentContent);
-
- // lemmy_beta deletes the comment
- let deleteCommentForm: CommentForm = {
- content: commentContent,
- edit_id: createCommentRes.comment.id,
- post_id: createPostRes.post.id,
- deleted: true,
- auth: lemmyBetaAuth,
- creator_id: createCommentRes.comment.creator_id,
- };
-
- let deleteCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(deleteCommentForm),
- }
- ).then(d => d.json());
- expect(deleteCommentRes.comment.deleted).toBe(true);
-
- // lemmy_alpha sees that the comment is deleted
- let getPostUrl = `${lemmyAlphaApiUrl}/post?id=3`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostRes.comments[0].deleted).toBe(true);
-
- // lemmy_beta undeletes the comment
- let undeleteCommentForm: CommentForm = {
- content: commentContent,
- edit_id: createCommentRes.comment.id,
- post_id: createPostRes.post.id,
- deleted: false,
- auth: lemmyBetaAuth,
- creator_id: createCommentRes.comment.creator_id,
- };
-
- let undeleteCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(undeleteCommentForm),
- }
- ).then(d => d.json());
- expect(undeleteCommentRes.comment.deleted).toBe(false);
-
- // lemmy_alpha sees that the comment is undeleted
- let getPostUndeleteRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostUndeleteRes.comments[0].deleted).toBe(false);
-
- // lemmy_beta deletes the post
- let deletePostForm: PostForm = {
- name: postName,
- edit_id: createPostRes.post.id,
- auth: lemmyBetaAuth,
- community_id: createPostRes.post.community_id,
- creator_id: createPostRes.post.creator_id,
- nsfw: false,
- deleted: true,
- };
-
- let deletePostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(deletePostForm),
- }).then(d => d.json());
- expect(deletePostRes.post.deleted).toBe(true);
-
- // Make sure lemmy_alpha sees the post is deleted
- let getPostResAgain: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostResAgain.post.deleted).toBe(true);
-
- // lemmy_beta undeletes the post
- let undeletePostForm: PostForm = {
- name: postName,
- edit_id: createPostRes.post.id,
- auth: lemmyBetaAuth,
- community_id: createPostRes.post.community_id,
- creator_id: createPostRes.post.creator_id,
- nsfw: false,
- deleted: false,
- };
-
- let undeletePostRes: PostResponse = await fetch(
- `${lemmyBetaApiUrl}/post`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(undeletePostForm),
- }
- ).then(d => d.json());
- expect(undeletePostRes.post.deleted).toBe(false);
-
- // Make sure lemmy_alpha sees the post is undeleted
- let getPostResAgainTwo: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostResAgainTwo.post.deleted).toBe(false);
-
- // lemmy_beta deletes the community
- let deleteCommunityForm: CommunityForm = {
- name: communityName,
- title: communityName,
- category_id: 1,
- edit_id: createCommunityRes.community.id,
- nsfw: false,
- deleted: true,
- auth: lemmyBetaAuth,
- };
-
- let deleteResponse: CommunityResponse = await fetch(
- `${lemmyBetaApiUrl}/community`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(deleteCommunityForm),
- }
- ).then(d => d.json());
-
- // Make sure the delete went through
- expect(deleteResponse.community.deleted).toBe(true);
-
- // Re-get it from alpha, make sure its deleted there too
- let getCommunityUrl = `${lemmyAlphaApiUrl}/community?id=${communityOnAlphaId}&auth=${lemmyAlphaAuth}`;
- let getCommunityRes: GetCommunityResponse = await fetch(getCommunityUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getCommunityRes.community.deleted).toBe(true);
-
- // lemmy_beta undeletes the community
- let undeleteCommunityForm: CommunityForm = {
- name: communityName,
- title: communityName,
- category_id: 1,
- edit_id: createCommunityRes.community.id,
- nsfw: false,
- deleted: false,
- auth: lemmyBetaAuth,
- };
-
- let undeleteCommunityRes: CommunityResponse = await fetch(
- `${lemmyBetaApiUrl}/community`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(undeleteCommunityForm),
- }
- ).then(d => d.json());
-
- // Make sure the delete went through
- expect(undeleteCommunityRes.community.deleted).toBe(false);
-
- // Re-get it from alpha, make sure its deleted there too
- let getCommunityResAgain: GetCommunityResponse = await fetch(
- getCommunityUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
- expect(getCommunityResAgain.community.deleted).toBe(false);
- });
- });
-
- describe('remove things', () => {
- test('/u/lemmy_beta removes and unremoves a federated comment, post, and community, lemmy_alpha sees its removed.', async () => {
- // Create a test community
- let communityName = 'test_community_rem';
- let communityForm: CommunityForm = {
- name: communityName,
- title: communityName,
- category_id: 1,
- nsfw: false,
- auth: lemmyBetaAuth,
- };
-
- let createCommunityRes: CommunityResponse = await fetch(
- `${lemmyBetaApiUrl}/community`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(communityForm),
- }
- ).then(d => d.json());
-
- expect(createCommunityRes.community.name).toBe(communityName);
-
- // Cache it on lemmy_alpha
- let searchUrl = `${lemmyAlphaApiUrl}/search?q=http://lemmy-beta:8550/c/${communityName}&type_=All&sort=TopAll`;
- let searchResponse: SearchResponse = await fetch(searchUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- let communityOnAlphaId = searchResponse.communities[0].id;
-
- // Follow it
- let followForm: FollowCommunityForm = {
- community_id: communityOnAlphaId,
- follow: true,
- auth: lemmyAlphaAuth,
- };
-
- let followRes: CommunityResponse = await fetch(
- `${lemmyAlphaApiUrl}/community/follow`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(followForm),
- }
- ).then(d => d.json());
-
- // Make sure the follow response went through
- expect(followRes.community.local).toBe(false);
- expect(followRes.community.name).toBe(communityName);
-
- // Lemmy beta creates a test post
- let postName = 'A jest test post with remove';
- let createPostForm: PostForm = {
- name: postName,
- auth: lemmyBetaAuth,
- community_id: createCommunityRes.community.id,
- creator_id: 2,
- nsfw: false,
- };
-
- let createPostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(createPostForm),
- }).then(d => d.json());
- expect(createPostRes.post.name).toBe(postName);
-
- // Lemmy beta creates a test comment
- let commentContent = 'A jest test federated comment with remove';
- let createCommentForm: CommentForm = {
- content: commentContent,
- post_id: createPostRes.post.id,
- auth: lemmyBetaAuth,
- };
-
- let createCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(createCommentForm),
- }
- ).then(d => d.json());
-
- expect(createCommentRes.comment.content).toBe(commentContent);
-
- // lemmy_beta removes the comment
- let removeCommentForm: CommentForm = {
- content: commentContent,
- edit_id: createCommentRes.comment.id,
- post_id: createPostRes.post.id,
- removed: true,
- auth: lemmyBetaAuth,
- creator_id: createCommentRes.comment.creator_id,
- };
-
- let removeCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(removeCommentForm),
- }
- ).then(d => d.json());
- expect(removeCommentRes.comment.removed).toBe(true);
-
- // lemmy_alpha sees that the comment is removed
- let getPostUrl = `${lemmyAlphaApiUrl}/post?id=4`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostRes.comments[0].removed).toBe(true);
-
- // lemmy_beta undeletes the comment
- let unremoveCommentForm: CommentForm = {
- content: commentContent,
- edit_id: createCommentRes.comment.id,
- post_id: createPostRes.post.id,
- removed: false,
- auth: lemmyBetaAuth,
- creator_id: createCommentRes.comment.creator_id,
- };
-
- let unremoveCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(unremoveCommentForm),
- }
- ).then(d => d.json());
- expect(unremoveCommentRes.comment.removed).toBe(false);
-
- // lemmy_alpha sees that the comment is undeleted
- let getPostUnremoveRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostUnremoveRes.comments[0].removed).toBe(false);
-
- // lemmy_beta deletes the post
- let removePostForm: PostForm = {
- name: postName,
- edit_id: createPostRes.post.id,
- auth: lemmyBetaAuth,
- community_id: createPostRes.post.community_id,
- creator_id: createPostRes.post.creator_id,
- nsfw: false,
- removed: true,
- };
-
- let removePostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(removePostForm),
- }).then(d => d.json());
- expect(removePostRes.post.removed).toBe(true);
-
- // Make sure lemmy_alpha sees the post is deleted
- let getPostResAgain: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostResAgain.post.removed).toBe(true);
-
- // lemmy_beta unremoves the post
- let unremovePostForm: PostForm = {
- name: postName,
- edit_id: createPostRes.post.id,
- auth: lemmyBetaAuth,
- community_id: createPostRes.post.community_id,
- creator_id: createPostRes.post.creator_id,
- nsfw: false,
- removed: false,
- };
-
- let unremovePostRes: PostResponse = await fetch(
- `${lemmyBetaApiUrl}/post`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(unremovePostForm),
- }
- ).then(d => d.json());
- expect(unremovePostRes.post.removed).toBe(false);
-
- // Make sure lemmy_alpha sees the post is unremoved
- let getPostResAgainTwo: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostResAgainTwo.post.removed).toBe(false);
-
- // lemmy_beta deletes the community
- let removeCommunityForm: CommunityForm = {
- name: communityName,
- title: communityName,
- category_id: 1,
- edit_id: createCommunityRes.community.id,
- nsfw: false,
- removed: true,
- auth: lemmyBetaAuth,
- };
-
- let removeCommunityRes: CommunityResponse = await fetch(
- `${lemmyBetaApiUrl}/community`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(removeCommunityForm),
- }
- ).then(d => d.json());
-
- // Make sure the delete went through
- expect(removeCommunityRes.community.removed).toBe(true);
-
- // Re-get it from alpha, make sure its removed there too
- let getCommunityUrl = `${lemmyAlphaApiUrl}/community?id=${communityOnAlphaId}&auth=${lemmyAlphaAuth}`;
- let getCommunityRes: GetCommunityResponse = await fetch(getCommunityUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getCommunityRes.community.removed).toBe(true);
-
- // lemmy_beta unremoves the community
- let unremoveCommunityForm: CommunityForm = {
- name: communityName,
- title: communityName,
- category_id: 1,
- edit_id: createCommunityRes.community.id,
- nsfw: false,
- removed: false,
- auth: lemmyBetaAuth,
- };
-
- let unremoveCommunityRes: CommunityResponse = await fetch(
- `${lemmyBetaApiUrl}/community`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(unremoveCommunityForm),
- }
- ).then(d => d.json());
-
- // Make sure the delete went through
- expect(unremoveCommunityRes.community.removed).toBe(false);
-
- // Re-get it from alpha, make sure its deleted there too
- let getCommunityResAgain: GetCommunityResponse = await fetch(
- getCommunityUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
- expect(getCommunityResAgain.community.removed).toBe(false);
- });
- });
-
- describe('private message', () => {
- test('/u/lemmy_alpha creates/updates/deletes/undeletes a private_message to /u/lemmy_beta, its on both instances', async () => {
- let content = 'A jest test federated private message';
- let privateMessageForm: PrivateMessageForm = {
- content,
- recipient_id: 3,
- auth: lemmyAlphaAuth,
- };
-
- let createRes: PrivateMessageResponse = await fetch(
- `${lemmyAlphaApiUrl}/private_message`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(privateMessageForm),
- }
- ).then(d => d.json());
- expect(createRes.message.content).toBe(content);
- expect(createRes.message.local).toBe(true);
- expect(createRes.message.creator_local).toBe(true);
- expect(createRes.message.recipient_local).toBe(false);
-
- // Get it from beta
- let getPrivateMessagesUrl = `${lemmyBetaApiUrl}/private_message/list?auth=${lemmyBetaAuth}&unread_only=false`;
-
- let getPrivateMessagesRes: PrivateMessagesResponse = await fetch(
- getPrivateMessagesUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
-
- expect(getPrivateMessagesRes.messages[0].content).toBe(content);
- expect(getPrivateMessagesRes.messages[0].local).toBe(false);
- expect(getPrivateMessagesRes.messages[0].creator_local).toBe(false);
- expect(getPrivateMessagesRes.messages[0].recipient_local).toBe(true);
-
- // lemmy alpha updates the private message
- let updatedContent = 'A jest test federated private message edited';
- let updatePrivateMessageForm: EditPrivateMessageForm = {
- content: updatedContent,
- edit_id: createRes.message.id,
- auth: lemmyAlphaAuth,
- };
-
- let updateRes: PrivateMessageResponse = await fetch(
- `${lemmyAlphaApiUrl}/private_message`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(updatePrivateMessageForm),
- }
- ).then(d => d.json());
-
- expect(updateRes.message.content).toBe(updatedContent);
-
- // Fetch from beta again
- let getPrivateMessagesUpdatedRes: PrivateMessagesResponse = await fetch(
- getPrivateMessagesUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
-
- expect(getPrivateMessagesUpdatedRes.messages[0].content).toBe(
- updatedContent
- );
-
- // lemmy alpha deletes the private message
- let deletePrivateMessageForm: EditPrivateMessageForm = {
- deleted: true,
- edit_id: createRes.message.id,
- auth: lemmyAlphaAuth,
- };
-
- let deleteRes: PrivateMessageResponse = await fetch(
- `${lemmyAlphaApiUrl}/private_message`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(deletePrivateMessageForm),
- }
- ).then(d => d.json());
-
- expect(deleteRes.message.deleted).toBe(true);
-
- // Fetch from beta again
- let getPrivateMessagesDeletedRes: PrivateMessagesResponse = await fetch(
- getPrivateMessagesUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
-
- // The GetPrivateMessages filters out deleted,
- // even though they are in the actual database.
- // no reason to show them
- expect(getPrivateMessagesDeletedRes.messages.length).toBe(0);
-
- // lemmy alpha undeletes the private message
- let undeletePrivateMessageForm: EditPrivateMessageForm = {
- deleted: false,
- edit_id: createRes.message.id,
- auth: lemmyAlphaAuth,
- };
-
- let undeleteRes: PrivateMessageResponse = await fetch(
- `${lemmyAlphaApiUrl}/private_message`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(undeletePrivateMessageForm),
- }
- ).then(d => d.json());
-
- expect(undeleteRes.message.deleted).toBe(false);
-
- // Fetch from beta again
- let getPrivateMessagesUnDeletedRes: PrivateMessagesResponse = await fetch(
- getPrivateMessagesUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
-
- expect(getPrivateMessagesUnDeletedRes.messages[0].deleted).toBe(false);
- });
- });
-
- describe('comment_search', () => {
- test('Create comment on alpha and search it', async () => {
- let content = 'A jest test federated comment for search';
- let commentForm: CommentForm = {
- content,
- post_id: 1,
- auth: lemmyAlphaAuth,
- };
-
- let createResponse: CommentResponse = await fetch(
- `${lemmyAlphaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(commentForm),
- }
- ).then(d => d.json());
-
- let searchUrl = `${lemmyBetaApiUrl}/search?q=${createResponse.comment.ap_id}&type_=All&sort=TopAll`;
- let searchResponse: SearchResponse = await fetch(searchUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- // TODO: check more fields
- expect(searchResponse.comments[0].content).toBe(content);
- });
- });
-
- describe('announce', () => {
- test('A and G subscribe to B (center) A does action, it gets announced to G', async () => {
- // A and G are already subscribed to B earlier.
- //
- let postName = 'A jest test post for announce';
- let createPostForm: PostForm = {
- name: postName,
- auth: lemmyAlphaAuth,
- community_id: 2,
- creator_id: 2,
- nsfw: false,
- };
-
- let createPostRes: PostResponse = await fetch(
- `${lemmyAlphaApiUrl}/post`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(createPostForm),
- }
- ).then(d => d.json());
- expect(createPostRes.post.name).toBe(postName);
-
- // Make sure that post got announced to Gamma
- let searchUrl = `${lemmyGammaApiUrl}/search?q=${createPostRes.post.ap_id}&type_=All&sort=TopAll`;
- let searchResponse: SearchResponse = await fetch(searchUrl, {
- method: 'GET',
- }).then(d => d.json());
- let postId = searchResponse.posts[0].id;
- expect(searchResponse.posts[0].name).toBe(postName);
-
- // Create a test comment on Gamma, make sure it gets announced to alpha
- let commentContent =
- 'A jest test federated comment announce, lets mention @lemmy_beta@lemmy-beta:8550';
-
- let commentForm: CommentForm = {
- content: commentContent,
- post_id: postId,
- auth: lemmyGammaAuth,
- };
-
- let createCommentRes: CommentResponse = await fetch(
- `${lemmyGammaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(commentForm),
- }
- ).then(d => d.json());
-
- expect(createCommentRes.comment.content).toBe(commentContent);
- expect(createCommentRes.comment.community_local).toBe(false);
- expect(createCommentRes.comment.creator_local).toBe(true);
- expect(createCommentRes.comment.score).toBe(1);
-
- // Get the post from alpha, make sure it has gamma's comment
- let getPostUrl = `${lemmyAlphaApiUrl}/post?id=5`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getPostRes.comments[0].content).toBe(commentContent);
- expect(getPostRes.comments[0].community_local).toBe(true);
- expect(getPostRes.comments[0].creator_local).toBe(false);
- expect(getPostRes.comments[0].score).toBe(1);
- });
- });
-
- describe('fetch inreplytos', () => {
- test('A is unsubbed from B, B makes a post, and some embedded comments, A subs to B, B updates the lowest level comment, A fetches both the post and all the inreplyto comments for that post.', async () => {
- // Check that A is subscribed to B
- let followedCommunitiesUrl = `${lemmyAlphaApiUrl}/user/followed_communities?&auth=${lemmyAlphaAuth}`;
- let followedCommunitiesRes: GetFollowedCommunitiesResponse = await fetch(
- followedCommunitiesUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
- expect(followedCommunitiesRes.communities[1].community_local).toBe(false);
-
- // A unsubs from B (communities ids 3-5)
- for (let i = 3; i <= 5; i++) {
- let unfollowForm: FollowCommunityForm = {
- community_id: i,
- follow: false,
- auth: lemmyAlphaAuth,
- };
-
- let unfollowRes: CommunityResponse = await fetch(
- `${lemmyAlphaApiUrl}/community/follow`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(unfollowForm),
- }
- ).then(d => d.json());
- expect(unfollowRes.community.local).toBe(false);
- }
-
- // Check that you are unsubscribed from all of them locally
- let followedCommunitiesResAgain: GetFollowedCommunitiesResponse = await fetch(
- followedCommunitiesUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
- expect(followedCommunitiesResAgain.communities.length).toBe(1);
-
- // B creates a post, and two comments, should be invisible to A
- let betaPostName = 'Test post on B, invisible to A at first';
- let postForm: PostForm = {
- name: betaPostName,
- auth: lemmyBetaAuth,
- community_id: 2,
- creator_id: 2,
- nsfw: false,
- };
-
- let createPostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(postForm),
- }).then(d => d.json());
- expect(createPostRes.post.name).toBe(betaPostName);
-
- // B creates a comment, then a child one of that.
- let parentCommentContent = 'An invisible top level comment from beta';
- let createParentCommentForm: CommentForm = {
- content: parentCommentContent,
- post_id: createPostRes.post.id,
- auth: lemmyBetaAuth,
- };
-
- let createParentCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(createParentCommentForm),
- }
- ).then(d => d.json());
- expect(createParentCommentRes.comment.content).toBe(parentCommentContent);
-
- let childCommentContent = 'An invisible child comment from beta';
- let createChildCommentForm: CommentForm = {
- content: childCommentContent,
- parent_id: createParentCommentRes.comment.id,
- post_id: createPostRes.post.id,
- auth: lemmyBetaAuth,
- };
-
- let createChildCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(createChildCommentForm),
- }
- ).then(d => d.json());
- expect(createChildCommentRes.comment.content).toBe(childCommentContent);
-
- // Follow again, for other tests
- let searchUrl = `${lemmyAlphaApiUrl}/search?q=!main@lemmy-beta:8550&type_=All&sort=TopAll`;
-
- let searchResponse: SearchResponse = await fetch(searchUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(searchResponse.communities[0].name).toBe('main');
-
- let followForm: FollowCommunityForm = {
- community_id: searchResponse.communities[0].id,
- follow: true,
- auth: lemmyAlphaAuth,
- };
-
- let followResAgain: CommunityResponse = await fetch(
- `${lemmyAlphaApiUrl}/community/follow`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(followForm),
- }
- ).then(d => d.json());
-
- // Make sure the follow response went through
- expect(followResAgain.community.local).toBe(false);
- expect(followResAgain.community.name).toBe('main');
-
- let updatedCommentContent = 'An update child comment from beta';
- let updatedCommentForm: CommentForm = {
- content: updatedCommentContent,
- post_id: createPostRes.post.id,
- edit_id: createChildCommentRes.comment.id,
- auth: lemmyBetaAuth,
- creator_id: 2,
- };
-
- let updateResponse: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(updatedCommentForm),
- }
- ).then(d => d.json());
- expect(updateResponse.comment.content).toBe(updatedCommentContent);
-
- // Make sure that A picked up the post, parent comment, and child comment
- let getPostUrl = `${lemmyAlphaApiUrl}/post?id=6`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getPostRes.post.name).toBe(betaPostName);
- expect(getPostRes.comments[1].content).toBe(parentCommentContent);
- expect(getPostRes.comments[0].content).toBe(updatedCommentContent);
- expect(getPostRes.post.community_local).toBe(false);
- expect(getPostRes.post.creator_local).toBe(false);
- });
- });
-});
-
-function wrapper(form: any): string {
- return JSON.stringify(form);
-}
--- /dev/null
+import {
+ alpha,
+ beta,
+ gamma,
+ setupLogins,
+ createPost,
+ getPost,
+ searchComment,
+ likeComment,
+ followBeta,
+ searchForBetaCommunity,
+ createComment,
+ updateComment,
+ deleteComment,
+ removeComment,
+ getMentions,
+ searchPost,
+ unfollowRemotes,
+ createCommunity,
+ registerUser,
+ API,
+} from './shared';
+
+import { PostResponse } from 'lemmy-js-client';
+
+let postRes: PostResponse;
+
+beforeAll(async () => {
+ await setupLogins();
+ await followBeta(alpha);
+ await followBeta(gamma);
+ let search = await searchForBetaCommunity(alpha);
+ postRes = await createPost(
+ alpha,
+ search.communities.filter(c => c.local == false)[0].id
+ );
+});
+
+afterAll(async () => {
+ await unfollowRemotes(alpha);
+ await unfollowRemotes(gamma);
+});
+
+test('Create a comment', async () => {
+ let commentRes = await createComment(alpha, postRes.post.id);
+ expect(commentRes.comment.content).toBeDefined();
+ expect(commentRes.comment.community_local).toBe(false);
+ expect(commentRes.comment.creator_local).toBe(true);
+ expect(commentRes.comment.score).toBe(1);
+
+ // Make sure that comment is liked on beta
+ let searchBeta = await searchComment(beta, commentRes.comment);
+ let betaComment = searchBeta.comments[0];
+ expect(betaComment).toBeDefined();
+ expect(betaComment.community_local).toBe(true);
+ expect(betaComment.creator_local).toBe(false);
+ expect(betaComment.score).toBe(1);
+});
+
+test('Create a comment in a non-existent post', async () => {
+ let commentRes = await createComment(alpha, -1);
+ expect(commentRes).toStrictEqual({ error: 'couldnt_find_post' });
+});
+
+test('Update a comment', async () => {
+ let commentRes = await createComment(alpha, postRes.post.id);
+ let updateCommentRes = await updateComment(alpha, commentRes.comment.id);
+ expect(updateCommentRes.comment.content).toBe(
+ 'A jest test federated comment update'
+ );
+ expect(updateCommentRes.comment.community_local).toBe(false);
+ expect(updateCommentRes.comment.creator_local).toBe(true);
+
+ // Make sure that post is updated on beta
+ let searchBeta = await searchComment(beta, commentRes.comment);
+ let betaComment = searchBeta.comments[0];
+ expect(betaComment.content).toBe('A jest test federated comment update');
+});
+
+test('Delete a comment', async () => {
+ let commentRes = await createComment(alpha, postRes.post.id);
+ let deleteCommentRes = await deleteComment(
+ alpha,
+ true,
+ commentRes.comment.id
+ );
+ expect(deleteCommentRes.comment.deleted).toBe(true);
+
+ // Make sure that comment is deleted on beta
+ // The search doesnt work below, because it returns a tombstone / http::gone
+ // let searchBeta = await searchComment(beta, commentRes.comment);
+ // console.log(searchBeta);
+ // let betaComment = searchBeta.comments[0];
+ // Create a fake post, just to get the previous new post id
+ let createdBetaPostJustToGetId = await createPost(beta, 2);
+ let betaPost = await getPost(beta, createdBetaPostJustToGetId.post.id - 1);
+ let betaComment = betaPost.comments[0];
+ expect(betaComment.deleted).toBe(true);
+
+ let undeleteCommentRes = await deleteComment(
+ alpha,
+ false,
+ commentRes.comment.id
+ );
+ expect(undeleteCommentRes.comment.deleted).toBe(false);
+
+ // Make sure that comment is undeleted on beta
+ let searchBeta2 = await searchComment(beta, commentRes.comment);
+ let betaComment2 = searchBeta2.comments[0];
+ expect(betaComment2.deleted).toBe(false);
+});
+
+test('Remove a comment from admin and community on the same instance', async () => {
+ let commentRes = await createComment(alpha, postRes.post.id);
+
+ // Get the id for beta
+ let betaCommentId = (await searchComment(beta, commentRes.comment))
+ .comments[0].id;
+
+ // The beta admin removes it (the community lives on beta)
+ let removeCommentRes = await removeComment(beta, true, betaCommentId);
+ expect(removeCommentRes.comment.removed).toBe(true);
+
+ // Make sure that comment is removed on alpha (it gets pushed since an admin from beta removed it)
+ let refetchedPost = await getPost(alpha, postRes.post.id);
+ expect(refetchedPost.comments[0].removed).toBe(true);
+
+ let unremoveCommentRes = await removeComment(beta, false, betaCommentId);
+ expect(unremoveCommentRes.comment.removed).toBe(false);
+
+ // Make sure that comment is unremoved on beta
+ let refetchedPost2 = await getPost(alpha, postRes.post.id);
+ expect(refetchedPost2.comments[0].removed).toBe(false);
+});
+
+test('Remove a comment from admin and community on different instance', async () => {
+ let alphaUser = await registerUser(alpha);
+ let newAlphaApi: API = {
+ client: alpha.client,
+ auth: alphaUser.jwt,
+ };
+
+ // New alpha user creates a community, post, and comment.
+ let newCommunity = await createCommunity(newAlphaApi);
+ let newPost = await createPost(newAlphaApi, newCommunity.community.id);
+ let commentRes = await createComment(newAlphaApi, newPost.post.id);
+ expect(commentRes.comment.content).toBeDefined();
+
+ // Beta searches that to cache it, then removes it
+ let searchBeta = await searchComment(beta, commentRes.comment);
+ let betaComment = searchBeta.comments[0];
+ let removeCommentRes = await removeComment(beta, true, betaComment.id);
+ expect(removeCommentRes.comment.removed).toBe(true);
+
+ // Make sure its not removed on alpha
+ let refetchedPost = await getPost(newAlphaApi, newPost.post.id);
+ expect(refetchedPost.comments[0].removed).toBe(false);
+});
+
+test('Unlike a comment', async () => {
+ let commentRes = await createComment(alpha, postRes.post.id);
+ let unlike = await likeComment(alpha, 0, commentRes.comment);
+ expect(unlike.comment.score).toBe(0);
+
+ // Make sure that post is unliked on beta
+ let searchBeta = await searchComment(beta, commentRes.comment);
+ let betaComment = searchBeta.comments[0];
+ expect(betaComment).toBeDefined();
+ expect(betaComment.community_local).toBe(true);
+ expect(betaComment.creator_local).toBe(false);
+ expect(betaComment.score).toBe(0);
+});
+
+test('Federated comment like', async () => {
+ let commentRes = await createComment(alpha, postRes.post.id);
+
+ // Find the comment on beta
+ let searchBeta = await searchComment(beta, commentRes.comment);
+ let betaComment = searchBeta.comments[0];
+
+ let like = await likeComment(beta, 1, betaComment);
+ expect(like.comment.score).toBe(2);
+
+ // Get the post from alpha, check the likes
+ let post = await getPost(alpha, postRes.post.id);
+ expect(post.comments[0].score).toBe(2);
+});
+
+test('Reply to a comment', async () => {
+ // Create a comment on alpha, find it on beta
+ let commentRes = await createComment(alpha, postRes.post.id);
+ let searchBeta = await searchComment(beta, commentRes.comment);
+ let betaComment = searchBeta.comments[0];
+
+ // find that comment id on beta
+
+ // Reply from beta
+ let replyRes = await createComment(beta, betaComment.post_id, betaComment.id);
+ expect(replyRes.comment.content).toBeDefined();
+ expect(replyRes.comment.community_local).toBe(true);
+ expect(replyRes.comment.creator_local).toBe(true);
+ expect(replyRes.comment.parent_id).toBe(betaComment.id);
+ expect(replyRes.comment.score).toBe(1);
+
+ // Make sure that comment is seen on alpha
+ // TODO not sure why, but a searchComment back to alpha, for the ap_id of betas
+ // comment, isn't working.
+ // let searchAlpha = await searchComment(alpha, replyRes.comment);
+ let post = await getPost(alpha, postRes.post.id);
+ let alphaComment = post.comments[0];
+ expect(alphaComment.content).toBeDefined();
+ expect(alphaComment.parent_id).toBe(post.comments[1].id);
+ expect(alphaComment.community_local).toBe(false);
+ expect(alphaComment.creator_local).toBe(false);
+ expect(alphaComment.score).toBe(1);
+});
+
+test('Mention beta', async () => {
+ // Create a mention on alpha
+ let mentionContent = 'A test mention of @lemmy_beta@lemmy-beta:8550';
+ let commentRes = await createComment(alpha, postRes.post.id);
+ let mentionRes = await createComment(
+ alpha,
+ postRes.post.id,
+ commentRes.comment.id,
+ mentionContent
+ );
+ expect(mentionRes.comment.content).toBeDefined();
+ expect(mentionRes.comment.community_local).toBe(false);
+ expect(mentionRes.comment.creator_local).toBe(true);
+ expect(mentionRes.comment.score).toBe(1);
+
+ let mentionsRes = await getMentions(beta);
+ expect(mentionsRes.mentions[0].content).toBeDefined();
+ expect(mentionsRes.mentions[0].community_local).toBe(true);
+ expect(mentionsRes.mentions[0].creator_local).toBe(false);
+ expect(mentionsRes.mentions[0].score).toBe(1);
+});
+
+test('Comment Search', async () => {
+ let commentRes = await createComment(alpha, postRes.post.id);
+ let searchBeta = await searchComment(beta, commentRes.comment);
+ expect(searchBeta.comments[0].ap_id).toBe(commentRes.comment.ap_id);
+});
+
+test('A and G subscribe to B (center) A posts, G mentions B, it gets announced to A', async () => {
+ // Create a local post
+ let alphaPost = await createPost(alpha, 2);
+ expect(alphaPost.post.community_local).toBe(true);
+
+ // Make sure gamma sees it
+ let search = await searchPost(gamma, alphaPost.post);
+ let gammaPost = search.posts[0];
+
+ let commentContent =
+ 'A jest test federated comment announce, lets mention @lemmy_beta@lemmy-beta:8550';
+ let commentRes = await createComment(
+ gamma,
+ gammaPost.id,
+ undefined,
+ commentContent
+ );
+ expect(commentRes.comment.content).toBe(commentContent);
+ expect(commentRes.comment.community_local).toBe(false);
+ expect(commentRes.comment.creator_local).toBe(true);
+ expect(commentRes.comment.score).toBe(1);
+
+ // Make sure alpha sees it
+ let alphaPost2 = await getPost(alpha, alphaPost.post.id);
+ expect(alphaPost2.comments[0].content).toBe(commentContent);
+ expect(alphaPost2.comments[0].community_local).toBe(true);
+ expect(alphaPost2.comments[0].creator_local).toBe(false);
+ expect(alphaPost2.comments[0].score).toBe(1);
+
+ // Make sure beta has mentions
+ let mentionsRes = await getMentions(beta);
+ expect(mentionsRes.mentions[0].content).toBe(commentContent);
+ expect(mentionsRes.mentions[0].community_local).toBe(false);
+ expect(mentionsRes.mentions[0].creator_local).toBe(false);
+ // TODO this is failing because fetchInReplyTos aren't getting score
+ // expect(mentionsRes.mentions[0].score).toBe(1);
+});
+
+test('Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedded comments, A subs to B, B updates the lowest level comment, A fetches both the post and all the inreplyto comments for that post.', async () => {
+ // Unfollow all remote communities
+ let followed = await unfollowRemotes(alpha);
+ expect(
+ followed.communities.filter(c => c.community_local == false).length
+ ).toBe(0);
+
+ // B creates a post, and two comments, should be invisible to A
+ let postRes = await createPost(beta, 2);
+ expect(postRes.post.name).toBeDefined();
+
+ let parentCommentContent = 'An invisible top level comment from beta';
+ let parentCommentRes = await createComment(
+ beta,
+ postRes.post.id,
+ undefined,
+ parentCommentContent
+ );
+ expect(parentCommentRes.comment.content).toBe(parentCommentContent);
+
+ // B creates a comment, then a child one of that.
+ let childCommentContent = 'An invisible child comment from beta';
+ let childCommentRes = await createComment(
+ beta,
+ postRes.post.id,
+ parentCommentRes.comment.id,
+ childCommentContent
+ );
+ expect(childCommentRes.comment.content).toBe(childCommentContent);
+
+ // Follow beta again
+ let follow = await followBeta(alpha);
+ expect(follow.community.local).toBe(false);
+ expect(follow.community.name).toBe('main');
+
+ // An update to the child comment on beta, should push the post, parent, and child to alpha now
+ let updatedCommentContent = 'An update child comment from beta';
+ let updateRes = await updateComment(
+ beta,
+ childCommentRes.comment.id,
+ updatedCommentContent
+ );
+ expect(updateRes.comment.content).toBe(updatedCommentContent);
+
+ // Get the post from alpha
+ let createFakeAlphaPostToGetId = await createPost(alpha, 2);
+ let alphaPost = await getPost(alpha, createFakeAlphaPostToGetId.post.id - 1);
+ expect(alphaPost.post.name).toBeDefined();
+ expect(alphaPost.comments[1].content).toBe(parentCommentContent);
+ expect(alphaPost.comments[0].content).toBe(updatedCommentContent);
+ expect(alphaPost.post.community_local).toBe(false);
+ expect(alphaPost.post.creator_local).toBe(false);
+});
--- /dev/null
+import {
+ alpha,
+ beta,
+ setupLogins,
+ searchForBetaCommunity,
+ createCommunity,
+ deleteCommunity,
+ removeCommunity,
+} from './shared';
+
+beforeAll(async () => {
+ await setupLogins();
+});
+
+test('Create community', async () => {
+ let communityRes = await createCommunity(alpha);
+ expect(communityRes.community.name).toBeDefined();
+
+ // A dupe check
+ let prevName = communityRes.community.name;
+ let communityRes2 = await createCommunity(alpha, prevName);
+ expect(communityRes2['error']).toBe('community_already_exists');
+});
+
+test('Delete community', async () => {
+ let communityRes = await createCommunity(beta);
+ let deleteCommunityRes = await deleteCommunity(
+ beta,
+ true,
+ communityRes.community.id
+ );
+ expect(deleteCommunityRes.community.deleted).toBe(true);
+
+ // Make sure it got deleted on A
+ let search = await searchForBetaCommunity(alpha);
+ let communityA = search.communities[0];
+ // TODO this fails currently, because no updates are pushed
+ // expect(communityA.deleted).toBe(true);
+
+ // Undelete
+ let undeleteCommunityRes = await deleteCommunity(
+ beta,
+ false,
+ communityRes.community.id
+ );
+ expect(undeleteCommunityRes.community.deleted).toBe(false);
+
+ // Make sure it got undeleted on A
+ let search2 = await searchForBetaCommunity(alpha);
+ let communityA2 = search2.communities[0];
+ // TODO this fails currently, because no updates are pushed
+ // expect(communityA2.deleted).toBe(false);
+});
+
+test('Remove community', async () => {
+ let communityRes = await createCommunity(beta);
+ let removeCommunityRes = await removeCommunity(
+ beta,
+ true,
+ communityRes.community.id
+ );
+ expect(removeCommunityRes.community.removed).toBe(true);
+
+ // Make sure it got removed on A
+ let search = await searchForBetaCommunity(alpha);
+ let communityA = search.communities[0];
+ // TODO this fails currently, because no updates are pushed
+ // expect(communityA.removed).toBe(true);
+
+ // unremove
+ let unremoveCommunityRes = await removeCommunity(
+ beta,
+ false,
+ communityRes.community.id
+ );
+ expect(unremoveCommunityRes.community.removed).toBe(false);
+
+ // Make sure it got unremoved on A
+ let search2 = await searchForBetaCommunity(alpha);
+ let communityA2 = search2.communities[0];
+ // TODO this fails currently, because no updates are pushed
+ // expect(communityA2.removed).toBe(false);
+});
+
+test('Search for beta community', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ expect(search.communities[0].name).toBe('main');
+});
--- /dev/null
+import {
+ alpha,
+ setupLogins,
+ searchForBetaCommunity,
+ followCommunity,
+ checkFollowedCommunities,
+ unfollowRemotes,
+} from './shared';
+
+beforeAll(async () => {
+ await setupLogins();
+});
+
+afterAll(async () => {
+ await unfollowRemotes(alpha);
+});
+
+test('Follow federated community', async () => {
+ let search = await searchForBetaCommunity(alpha); // TODO sometimes this is returning null?
+ let follow = await followCommunity(alpha, true, search.communities[0].id);
+
+ // Make sure the follow response went through
+ expect(follow.community.local).toBe(false);
+ expect(follow.community.name).toBe('main');
+
+ // Check it from local
+ let followCheck = await checkFollowedCommunities(alpha);
+ let remoteCommunityId = followCheck.communities.filter(
+ c => c.community_local == false
+ )[0].community_id;
+ expect(remoteCommunityId).toBeDefined();
+
+ // Test an unfollow
+ let unfollow = await followCommunity(alpha, false, remoteCommunityId);
+ expect(unfollow.community.local).toBe(false);
+
+ // Make sure you are unsubbed locally
+ let unfollowCheck = await checkFollowedCommunities(alpha);
+ expect(unfollowCheck.communities.length).toBeGreaterThanOrEqual(1);
+});
--- /dev/null
+import {
+ alpha,
+ beta,
+ gamma,
+ delta,
+ epsilon,
+ setupLogins,
+ createPost,
+ updatePost,
+ stickyPost,
+ lockPost,
+ searchPost,
+ likePost,
+ followBeta,
+ searchForBetaCommunity,
+ createComment,
+ deletePost,
+ removePost,
+ getPost,
+ unfollowRemotes,
+} from './shared';
+
+beforeAll(async () => {
+ await setupLogins();
+ await followBeta(alpha);
+ await followBeta(gamma);
+ await followBeta(delta);
+ await followBeta(epsilon);
+});
+
+afterAll(async () => {
+ await unfollowRemotes(alpha);
+ await unfollowRemotes(gamma);
+ await unfollowRemotes(delta);
+ await unfollowRemotes(epsilon);
+});
+
+test('Create a post', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+ expect(postRes.post).toBeDefined();
+ expect(postRes.post.community_local).toBe(false);
+ expect(postRes.post.creator_local).toBe(true);
+ expect(postRes.post.score).toBe(1);
+
+ // Make sure that post is liked on beta
+ let searchBeta = await searchPost(beta, postRes.post);
+ let betaPost = searchBeta.posts[0];
+
+ expect(betaPost).toBeDefined();
+ expect(betaPost.community_local).toBe(true);
+ expect(betaPost.creator_local).toBe(false);
+ expect(betaPost.score).toBe(1);
+
+ // Delta only follows beta, so it should not see an alpha ap_id
+ let searchDelta = await searchPost(delta, postRes.post);
+ expect(searchDelta.posts[0]).toBeUndefined();
+
+ // Epsilon has alpha blocked, it should not see the alpha post
+ let searchEpsilon = await searchPost(epsilon, postRes.post);
+ expect(searchEpsilon.posts[0]).toBeUndefined();
+});
+
+test('Create a post in a non-existent community', async () => {
+ let postRes = await createPost(alpha, -2);
+ expect(postRes).toStrictEqual({ error: 'couldnt_create_post' });
+});
+
+test('Unlike a post', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+ let unlike = await likePost(alpha, 0, postRes.post);
+ expect(unlike.post.score).toBe(0);
+
+ // Try to unlike it again, make sure it stays at 0
+ let unlike2 = await likePost(alpha, 0, postRes.post);
+ expect(unlike2.post.score).toBe(0);
+
+ // Make sure that post is unliked on beta
+ let searchBeta = await searchPost(beta, postRes.post);
+ let betaPost = searchBeta.posts[0];
+
+ expect(betaPost).toBeDefined();
+ expect(betaPost.community_local).toBe(true);
+ expect(betaPost.creator_local).toBe(false);
+ expect(betaPost.score).toBe(0);
+});
+
+test('Update a post', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+
+ let updatedName = 'A jest test federated post, updated';
+ let updatedPost = await updatePost(alpha, postRes.post);
+ expect(updatedPost.post.name).toBe(updatedName);
+ expect(updatedPost.post.community_local).toBe(false);
+ expect(updatedPost.post.creator_local).toBe(true);
+
+ // Make sure that post is updated on beta
+ let searchBeta = await searchPost(beta, postRes.post);
+ let betaPost = searchBeta.posts[0];
+ expect(betaPost.community_local).toBe(true);
+ expect(betaPost.creator_local).toBe(false);
+ expect(betaPost.name).toBe(updatedName);
+
+ // Make sure lemmy beta cannot update the post
+ let updatedPostBeta = await updatePost(beta, betaPost);
+ expect(updatedPostBeta).toStrictEqual({ error: 'no_post_edit_allowed' });
+});
+
+test('Sticky a post', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+
+ let stickiedPostRes = await stickyPost(alpha, true, postRes.post);
+ expect(stickiedPostRes.post.stickied).toBe(true);
+
+ // Make sure that post is stickied on beta
+ let searchBeta = await searchPost(beta, postRes.post);
+ let betaPost = searchBeta.posts[0];
+ expect(betaPost.community_local).toBe(true);
+ expect(betaPost.creator_local).toBe(false);
+ expect(betaPost.stickied).toBe(true);
+
+ // Unsticky a post
+ let unstickiedPost = await stickyPost(alpha, false, postRes.post);
+ expect(unstickiedPost.post.stickied).toBe(false);
+
+ // Make sure that post is unstickied on beta
+ let searchBeta2 = await searchPost(beta, postRes.post);
+ let betaPost2 = searchBeta2.posts[0];
+ expect(betaPost2.community_local).toBe(true);
+ expect(betaPost2.creator_local).toBe(false);
+ expect(betaPost2.stickied).toBe(false);
+
+ // Make sure that gamma cannot sticky the post on beta
+ let searchGamma = await searchPost(gamma, postRes.post);
+ let gammaPost = searchGamma.posts[0];
+ let gammaTrySticky = await stickyPost(gamma, true, gammaPost);
+ let searchBeta3 = await searchPost(beta, postRes.post);
+ let betaPost3 = searchBeta3.posts[0];
+ expect(gammaTrySticky.post.stickied).toBe(true);
+ expect(betaPost3.stickied).toBe(false);
+});
+
+test('Lock a post', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+
+ let lockedPostRes = await lockPost(alpha, true, postRes.post);
+ expect(lockedPostRes.post.locked).toBe(true);
+
+ // Make sure that post is locked on beta
+ let searchBeta = await searchPost(beta, postRes.post);
+ let betaPost = searchBeta.posts[0];
+ expect(betaPost.community_local).toBe(true);
+ expect(betaPost.creator_local).toBe(false);
+ expect(betaPost.locked).toBe(true);
+
+ // Try to make a new comment there, on alpha
+ let comment = await createComment(alpha, postRes.post.id);
+ expect(comment['error']).toBe('locked');
+
+ // Try to create a new comment, on beta
+ let commentBeta = await createComment(beta, betaPost.id);
+ expect(commentBeta['error']).toBe('locked');
+
+ // Unlock a post
+ let unlockedPost = await lockPost(alpha, false, postRes.post);
+ expect(unlockedPost.post.locked).toBe(false);
+
+ // Make sure that post is unlocked on beta
+ let searchBeta2 = await searchPost(beta, postRes.post);
+ let betaPost2 = searchBeta2.posts[0];
+ expect(betaPost2.community_local).toBe(true);
+ expect(betaPost2.creator_local).toBe(false);
+ expect(betaPost2.locked).toBe(false);
+});
+
+test('Delete a post', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+
+ let deletedPost = await deletePost(alpha, true, postRes.post);
+ expect(deletedPost.post.deleted).toBe(true);
+
+ // Make sure lemmy beta sees post is deleted
+ let createFakeBetaPostToGetId = (await createPost(beta, 2)).post.id - 1;
+ let betaPost = await getPost(beta, createFakeBetaPostToGetId);
+ expect(betaPost.post.deleted).toBe(true);
+
+ // Undelete
+ let undeletedPost = await deletePost(alpha, false, postRes.post);
+ expect(undeletedPost.post.deleted).toBe(false);
+
+ // Make sure lemmy beta sees post is undeleted
+ let betaPost2 = await getPost(beta, createFakeBetaPostToGetId);
+ expect(betaPost2.post.deleted).toBe(false);
+
+ // Make sure lemmy beta cannot delete the post
+ let deletedPostBeta = await deletePost(beta, true, betaPost2.post);
+ expect(deletedPostBeta).toStrictEqual({ error: 'no_post_edit_allowed' });
+});
+
+test('Remove a post from admin and community on different instance', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+
+ let removedPost = await removePost(alpha, true, postRes.post);
+ expect(removedPost.post.removed).toBe(true);
+
+ // Make sure lemmy beta sees post is NOT removed
+ let createFakeBetaPostToGetId = (await createPost(beta, 2)).post.id - 1;
+ let betaPost = await getPost(beta, createFakeBetaPostToGetId);
+ expect(betaPost.post.removed).toBe(false);
+
+ // Undelete
+ let undeletedPost = await removePost(alpha, false, postRes.post);
+ expect(undeletedPost.post.removed).toBe(false);
+
+ // Make sure lemmy beta sees post is undeleted
+ let betaPost2 = await getPost(beta, createFakeBetaPostToGetId);
+ expect(betaPost2.post.removed).toBe(false);
+});
+
+test('Remove a post from admin and community on same instance', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+
+ // Get the id for beta
+ let createFakeBetaPostToGetId = (await createPost(beta, 2)).post.id - 1;
+ let betaPost = await getPost(beta, createFakeBetaPostToGetId);
+
+ // The beta admin removes it (the community lives on beta)
+ let removePostRes = await removePost(beta, true, betaPost.post);
+ expect(removePostRes.post.removed).toBe(true);
+
+ // Make sure lemmy alpha sees post is removed
+ let alphaPost = await getPost(alpha, postRes.post.id);
+ expect(alphaPost.post.removed).toBe(true);
+
+ // Undelete
+ let undeletedPost = await removePost(beta, false, betaPost.post);
+ expect(undeletedPost.post.removed).toBe(false);
+
+ // Make sure lemmy alpha sees post is undeleted
+ let alphaPost2 = await getPost(alpha, postRes.post.id);
+ expect(alphaPost2.post.removed).toBe(false);
+});
+
+test('Search for a post', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+ let searchBeta = await searchPost(beta, postRes.post);
+
+ expect(searchBeta.posts[0].name).toBeDefined();
+});
+
+test('A and G subscribe to B (center) A posts, it gets announced to G', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+
+ let search2 = await searchPost(gamma, postRes.post);
+ expect(search2.posts[0].name).toBeDefined();
+});
--- /dev/null
+import {
+ alpha,
+ beta,
+ setupLogins,
+ followBeta,
+ createPrivateMessage,
+ updatePrivateMessage,
+ listPrivateMessages,
+ deletePrivateMessage,
+ unfollowRemotes,
+} from './shared';
+
+let recipient_id: number;
+
+beforeAll(async () => {
+ await setupLogins();
+ recipient_id = (await followBeta(alpha)).community.creator_id;
+});
+
+afterAll(async () => {
+ await unfollowRemotes(alpha);
+});
+
+test('Create a private message', async () => {
+ let pmRes = await createPrivateMessage(alpha, recipient_id);
+ expect(pmRes.message.content).toBeDefined();
+ expect(pmRes.message.local).toBe(true);
+ expect(pmRes.message.creator_local).toBe(true);
+ expect(pmRes.message.recipient_local).toBe(false);
+
+ let betaPms = await listPrivateMessages(beta);
+ expect(betaPms.messages[0].content).toBeDefined();
+ expect(betaPms.messages[0].local).toBe(false);
+ expect(betaPms.messages[0].creator_local).toBe(false);
+ expect(betaPms.messages[0].recipient_local).toBe(true);
+});
+
+test('Update a private message', async () => {
+ let updatedContent = 'A jest test federated private message edited';
+
+ let pmRes = await createPrivateMessage(alpha, recipient_id);
+ let pmUpdated = await updatePrivateMessage(alpha, pmRes.message.id);
+ expect(pmUpdated.message.content).toBe(updatedContent);
+
+ let betaPms = await listPrivateMessages(beta);
+ expect(betaPms.messages[0].content).toBe(updatedContent);
+});
+
+test('Delete a private message', async () => {
+ let pmRes = await createPrivateMessage(alpha, recipient_id);
+ let betaPms1 = await listPrivateMessages(beta);
+ let deletedPmRes = await deletePrivateMessage(alpha, true, pmRes.message.id);
+ expect(deletedPmRes.message.deleted).toBe(true);
+
+ // The GetPrivateMessages filters out deleted,
+ // even though they are in the actual database.
+ // no reason to show them
+ let betaPms2 = await listPrivateMessages(beta);
+ expect(betaPms2.messages.length).toBe(betaPms1.messages.length - 1);
+
+ // Undelete
+ let undeletedPmRes = await deletePrivateMessage(
+ alpha,
+ false,
+ pmRes.message.id
+ );
+ expect(undeletedPmRes.message.deleted).toBe(false);
+
+ let betaPms3 = await listPrivateMessages(beta);
+ expect(betaPms3.messages.length).toBe(betaPms1.messages.length);
+});
--- /dev/null
+import {
+ LoginForm,
+ LoginResponse,
+ Post,
+ PostForm,
+ Comment,
+ DeletePostForm,
+ RemovePostForm,
+ StickyPostForm,
+ LockPostForm,
+ PostResponse,
+ SearchResponse,
+ FollowCommunityForm,
+ CommunityResponse,
+ GetFollowedCommunitiesResponse,
+ GetPostResponse,
+ RegisterForm,
+ CommentForm,
+ DeleteCommentForm,
+ RemoveCommentForm,
+ SearchForm,
+ CommentResponse,
+ CommunityForm,
+ DeleteCommunityForm,
+ RemoveCommunityForm,
+ GetUserMentionsForm,
+ CommentLikeForm,
+ CreatePostLikeForm,
+ PrivateMessageForm,
+ EditPrivateMessageForm,
+ DeletePrivateMessageForm,
+ GetFollowedCommunitiesForm,
+ GetPrivateMessagesForm,
+ GetSiteForm,
+ GetPostForm,
+ PrivateMessageResponse,
+ PrivateMessagesResponse,
+ GetUserMentionsResponse,
+ UserSettingsForm,
+ SortType,
+ ListingType,
+ GetSiteResponse,
+ SearchType,
+ LemmyHttp,
+} from 'lemmy-js-client';
+
+export interface API {
+ client: LemmyHttp;
+ auth?: string;
+}
+
+export let alpha: API = {
+ client: new LemmyHttp('http://localhost:8540/api/v1'),
+};
+
+export let beta: API = {
+ client: new LemmyHttp('http://localhost:8550/api/v1'),
+};
+
+export let gamma: API = {
+ client: new LemmyHttp('http://localhost:8560/api/v1'),
+};
+
+export let delta: API = {
+ client: new LemmyHttp('http://localhost:8570/api/v1'),
+};
+
+export let epsilon: API = {
+ client: new LemmyHttp('http://localhost:8580/api/v1'),
+};
+
+export async function setupLogins() {
+ let formAlpha: LoginForm = {
+ username_or_email: 'lemmy_alpha',
+ password: 'lemmy',
+ };
+ let resAlpha = alpha.client.login(formAlpha);
+
+ let formBeta = {
+ username_or_email: 'lemmy_beta',
+ password: 'lemmy',
+ };
+ let resBeta = beta.client.login(formBeta);
+
+ let formGamma = {
+ username_or_email: 'lemmy_gamma',
+ password: 'lemmy',
+ };
+ let resGamma = gamma.client.login(formGamma);
+
+ let formDelta = {
+ username_or_email: 'lemmy_delta',
+ password: 'lemmy',
+ };
+ let resDelta = delta.client.login(formDelta);
+
+ let formEpsilon = {
+ username_or_email: 'lemmy_epsilon',
+ password: 'lemmy',
+ };
+ let resEpsilon = epsilon.client.login(formEpsilon);
+
+ let res = await Promise.all([
+ resAlpha,
+ resBeta,
+ resGamma,
+ resDelta,
+ resEpsilon,
+ ]);
+
+ alpha.auth = res[0].jwt;
+ beta.auth = res[1].jwt;
+ gamma.auth = res[2].jwt;
+ delta.auth = res[3].jwt;
+ epsilon.auth = res[4].jwt;
+}
+
+export async function createPost(
+ api: API,
+ community_id: number
+): Promise<PostResponse> {
+ let name = 'A jest test post';
+ let form: PostForm = {
+ name,
+ auth: api.auth,
+ community_id,
+ nsfw: false,
+ };
+ return api.client.createPost(form);
+}
+
+export async function updatePost(api: API, post: Post): Promise<PostResponse> {
+ let name = 'A jest test federated post, updated';
+ let form: PostForm = {
+ name,
+ edit_id: post.id,
+ auth: api.auth,
+ nsfw: false,
+ };
+ return api.client.editPost(form);
+}
+
+export async function deletePost(
+ api: API,
+ deleted: boolean,
+ post: Post
+): Promise<PostResponse> {
+ let form: DeletePostForm = {
+ edit_id: post.id,
+ deleted: deleted,
+ auth: api.auth,
+ };
+ return api.client.deletePost(form);
+}
+
+export async function removePost(
+ api: API,
+ removed: boolean,
+ post: Post
+): Promise<PostResponse> {
+ let form: RemovePostForm = {
+ edit_id: post.id,
+ removed,
+ auth: api.auth,
+ };
+ return api.client.removePost(form);
+}
+
+export async function stickyPost(
+ api: API,
+ stickied: boolean,
+ post: Post
+): Promise<PostResponse> {
+ let form: StickyPostForm = {
+ edit_id: post.id,
+ stickied,
+ auth: api.auth,
+ };
+ return api.client.stickyPost(form);
+}
+
+export async function lockPost(
+ api: API,
+ locked: boolean,
+ post: Post
+): Promise<PostResponse> {
+ let form: LockPostForm = {
+ edit_id: post.id,
+ locked,
+ auth: api.auth,
+ };
+ return api.client.lockPost(form);
+}
+
+export async function searchPost(
+ api: API,
+ post: Post
+): Promise<SearchResponse> {
+ let form: SearchForm = {
+ q: post.ap_id,
+ type_: SearchType.All,
+ sort: SortType.TopAll,
+ };
+ return api.client.search(form);
+}
+
+export async function getPost(
+ api: API,
+ post_id: number
+): Promise<GetPostResponse> {
+ let form: GetPostForm = {
+ id: post_id,
+ };
+ return api.client.getPost(form);
+}
+
+export async function searchComment(
+ api: API,
+ comment: Comment
+): Promise<SearchResponse> {
+ let form: SearchForm = {
+ q: comment.ap_id,
+ type_: SearchType.All,
+ sort: SortType.TopAll,
+ };
+ return api.client.search(form);
+}
+
+export async function searchForBetaCommunity(
+ api: API
+): Promise<SearchResponse> {
+ // Make sure lemmy-beta/c/main is cached on lemmy_alpha
+ // Use short-hand search url
+ let form: SearchForm = {
+ q: '!main@lemmy-beta:8550',
+ type_: SearchType.All,
+ sort: SortType.TopAll,
+ };
+ return api.client.search(form);
+}
+
+export async function searchForUser(
+ api: API,
+ apShortname: string
+): Promise<SearchResponse> {
+ // Make sure lemmy-beta/c/main is cached on lemmy_alpha
+ // Use short-hand search url
+ let form: SearchForm = {
+ q: apShortname,
+ type_: SearchType.All,
+ sort: SortType.TopAll,
+ };
+ return api.client.search(form);
+}
+
+export async function followCommunity(
+ api: API,
+ follow: boolean,
+ community_id: number
+): Promise<CommunityResponse> {
+ let form: FollowCommunityForm = {
+ community_id,
+ follow,
+ auth: api.auth,
+ };
+ return api.client.followCommunity(form);
+}
+
+export async function checkFollowedCommunities(
+ api: API
+): Promise<GetFollowedCommunitiesResponse> {
+ let form: GetFollowedCommunitiesForm = {
+ auth: api.auth,
+ };
+ return api.client.getFollowedCommunities(form);
+}
+
+export async function likePost(
+ api: API,
+ score: number,
+ post: Post
+): Promise<PostResponse> {
+ let form: CreatePostLikeForm = {
+ post_id: post.id,
+ score: score,
+ auth: api.auth,
+ };
+
+ return api.client.likePost(form);
+}
+
+export async function createComment(
+ api: API,
+ post_id: number,
+ parent_id?: number,
+ content = 'a jest test comment'
+): Promise<CommentResponse> {
+ let form: CommentForm = {
+ content,
+ post_id,
+ parent_id,
+ auth: api.auth,
+ };
+ return api.client.createComment(form);
+}
+
+export async function updateComment(
+ api: API,
+ edit_id: number,
+ content = 'A jest test federated comment update'
+): Promise<CommentResponse> {
+ let form: CommentForm = {
+ content,
+ edit_id,
+ auth: api.auth,
+ };
+ return api.client.editComment(form);
+}
+
+export async function deleteComment(
+ api: API,
+ deleted: boolean,
+ edit_id: number
+): Promise<CommentResponse> {
+ let form: DeleteCommentForm = {
+ edit_id,
+ deleted,
+ auth: api.auth,
+ };
+ return api.client.deleteComment(form);
+}
+
+export async function removeComment(
+ api: API,
+ removed: boolean,
+ edit_id: number
+): Promise<CommentResponse> {
+ let form: RemoveCommentForm = {
+ edit_id,
+ removed,
+ auth: api.auth,
+ };
+ return api.client.removeComment(form);
+}
+
+export async function getMentions(api: API): Promise<GetUserMentionsResponse> {
+ let form: GetUserMentionsForm = {
+ sort: SortType.New,
+ unread_only: false,
+ auth: api.auth,
+ };
+ return api.client.getUserMentions(form);
+}
+
+export async function likeComment(
+ api: API,
+ score: number,
+ comment: Comment
+): Promise<CommentResponse> {
+ let form: CommentLikeForm = {
+ comment_id: comment.id,
+ score,
+ auth: api.auth,
+ };
+ return api.client.likeComment(form);
+}
+
+export async function createCommunity(
+ api: API,
+ name_: string = randomString(5)
+): Promise<CommunityResponse> {
+ let form: CommunityForm = {
+ name: name_,
+ title: name_,
+ category_id: 1,
+ nsfw: false,
+ auth: api.auth,
+ };
+ return api.client.createCommunity(form);
+}
+
+export async function deleteCommunity(
+ api: API,
+ deleted: boolean,
+ edit_id: number
+): Promise<CommunityResponse> {
+ let form: DeleteCommunityForm = {
+ edit_id,
+ deleted,
+ auth: api.auth,
+ };
+ return api.client.deleteCommunity(form);
+}
+
+export async function removeCommunity(
+ api: API,
+ removed: boolean,
+ edit_id: number
+): Promise<CommunityResponse> {
+ let form: RemoveCommunityForm = {
+ edit_id,
+ removed,
+ auth: api.auth,
+ };
+ return api.client.removeCommunity(form);
+}
+
+export async function createPrivateMessage(
+ api: API,
+ recipient_id: number
+): Promise<PrivateMessageResponse> {
+ let content = 'A jest test federated private message';
+ let form: PrivateMessageForm = {
+ content,
+ recipient_id,
+ auth: api.auth,
+ };
+ return api.client.createPrivateMessage(form);
+}
+
+export async function updatePrivateMessage(
+ api: API,
+ edit_id: number
+): Promise<PrivateMessageResponse> {
+ let updatedContent = 'A jest test federated private message edited';
+ let form: EditPrivateMessageForm = {
+ content: updatedContent,
+ edit_id,
+ auth: api.auth,
+ };
+ return api.client.editPrivateMessage(form);
+}
+
+export async function deletePrivateMessage(
+ api: API,
+ deleted: boolean,
+ edit_id: number
+): Promise<PrivateMessageResponse> {
+ let form: DeletePrivateMessageForm = {
+ deleted,
+ edit_id,
+ auth: api.auth,
+ };
+ return api.client.deletePrivateMessage(form);
+}
+
+export async function registerUser(
+ api: API,
+ username: string = randomString(5)
+): Promise<LoginResponse> {
+ let form: RegisterForm = {
+ username,
+ password: 'test',
+ password_verify: 'test',
+ admin: false,
+ show_nsfw: true,
+ };
+ return api.client.register(form);
+}
+
+export async function saveUserSettingsBio(
+ api: API,
+ auth: string
+): Promise<LoginResponse> {
+ let form: UserSettingsForm = {
+ show_nsfw: true,
+ theme: 'darkly',
+ default_sort_type: Object.keys(SortType).indexOf(SortType.Active),
+ default_listing_type: Object.keys(ListingType).indexOf(ListingType.All),
+ lang: 'en',
+ show_avatars: true,
+ send_notifications_to_email: false,
+ bio: 'a changed bio',
+ auth,
+ };
+ return api.client.saveUserSettings(form);
+}
+
+export async function getSite(
+ api: API,
+ auth: string
+): Promise<GetSiteResponse> {
+ let form: GetSiteForm = {
+ auth,
+ };
+ return api.client.getSite(form);
+}
+
+export async function listPrivateMessages(
+ api: API
+): Promise<PrivateMessagesResponse> {
+ let form: GetPrivateMessagesForm = {
+ auth: api.auth,
+ unread_only: false,
+ limit: 999,
+ };
+ return api.client.getPrivateMessages(form);
+}
+
+export async function unfollowRemotes(
+ api: API
+): Promise<GetFollowedCommunitiesResponse> {
+ // Unfollow all remote communities
+ let followed = await checkFollowedCommunities(api);
+ let remoteFollowed = followed.communities.filter(
+ c => c.community_local == false
+ );
+ for (let cu of remoteFollowed) {
+ await followCommunity(api, false, cu.community_id);
+ }
+ let followed2 = await checkFollowedCommunities(api);
+ return followed2;
+}
+
+export async function followBeta(api: API): Promise<CommunityResponse> {
+ await unfollowRemotes(api);
+
+ // Cache it
+ let search = await searchForBetaCommunity(api);
+ let com = search.communities.filter(c => c.local == false);
+ if (com[0]) {
+ let follow = await followCommunity(api, true, com[0].id);
+ return follow;
+ }
+}
+
+export function wrapper(form: any): string {
+ return JSON.stringify(form);
+}
+
+function randomString(length: number): string {
+ var result = '';
+ var characters = 'abcdefghijklmnopqrstuvwxyz0123456789_';
+ var charactersLength = characters.length;
+ for (var i = 0; i < length; i++) {
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
+ }
+ return result;
+}
--- /dev/null
+import {
+ alpha,
+ beta,
+ registerUser,
+ searchForUser,
+ saveUserSettingsBio,
+ getSite,
+} from './shared';
+
+let auth: string;
+let apShortname: string;
+
+test('Create user', async () => {
+ let userRes = await registerUser(alpha);
+ expect(userRes.jwt).toBeDefined();
+ auth = userRes.jwt;
+
+ let site = await getSite(alpha, auth);
+ expect(site.my_user).toBeDefined();
+ apShortname = `@${site.my_user.name}@lemmy-alpha:8540`;
+});
+
+test('Save user settings, check changed bio from beta', async () => {
+ let bio = 'a changed bio';
+ let userRes = await saveUserSettingsBio(alpha, auth);
+ expect(userRes.jwt).toBeDefined();
+
+ let site = await getSite(alpha, auth);
+ expect(site.my_user.bio).toBe(bio);
+
+ // Make sure beta sees this bio is changed
+ let search = await searchForUser(beta, apShortname);
+ expect(search.users[0].bio).toBe(bio);
+});
import { Component, linkEvent } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import {
SiteConfigForm,
GetSiteConfigResponse,
WebSocketJsonResponse,
-} from '../interfaces';
+} from 'lemmy-js-client';
import { WebSocketService } from '../services';
import { wsJsonToRes, capitalizeFirstLetter, toast, randomStr } from '../utils';
import autosize from 'autosize';
admins: [],
banned: [],
online: null,
+ version: null,
+ federated_instances: null,
},
siteConfigForm: {
config_hjson: null,
this.subscription.unsubscribe();
}
+ get documentTitle(): string {
+ if (this.state.siteRes.site.name) {
+ return `${i18n.t('admin_settings')} - ${this.state.siteRes.site.name}`;
+ } else {
+ return 'Lemmy';
+ }
+ }
+
render() {
return (
<div class="container">
+ <Helmet title={this.documentTitle} />
{this.state.loading ? (
<h5>
<svg class="icon icon-spinner spin">
) : (
<div class="row">
<div class="col-12 col-md-6">
- <SiteForm site={this.state.siteRes.site} />
+ {this.state.siteRes.site.id && (
+ <SiteForm site={this.state.siteRes.site} />
+ )}
{this.admins()}
{this.bannedUsers()}
</div>
<UserListing
user={{
name: admin.name,
+ preferred_username: admin.preferred_username,
avatar: admin.avatar,
id: admin.id,
local: admin.local,
<UserListing
user={{
name: banned.name,
+ preferred_username: banned.preferred_username,
avatar: banned.avatar,
id: banned.id,
local: banned.local,
}
this.state.siteRes = data;
this.setState(this.state);
- document.title = `${i18n.t('admin_settings')} - ${
- this.state.siteRes.site.name
- }`;
} else if (res.op == UserOperation.EditSite) {
let data = res.data as SiteResponse;
this.state.siteRes.site = data.site;
--- /dev/null
+import { Component } from 'inferno';
+
+interface BannerIconHeaderProps {
+ banner?: string;
+ icon?: string;
+}
+
+export class BannerIconHeader extends Component<BannerIconHeaderProps, any> {
+ constructor(props: any, context: any) {
+ super(props, context);
+ }
+
+ render() {
+ return (
+ <div class="position-relative mb-2">
+ {this.props.banner && (
+ <img src={this.props.banner} class="banner img-fluid" />
+ )}
+ {this.props.icon && (
+ <img
+ src={this.props.icon}
+ className={`ml-2 mb-0 ${
+ this.props.banner ? 'avatar-pushup' : ''
+ } rounded-circle avatar-overlay`}
+ />
+ )}
+ </div>
+ );
+ }
+}
-import { Component, linkEvent } from 'inferno';
+import { Component } from 'inferno';
import { Link } from 'inferno-router';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
-import { Prompt } from 'inferno-router';
import {
CommentNode as CommentNodeI,
CommentForm as CommentFormI,
WebSocketJsonResponse,
UserOperation,
CommentResponse,
-} from '../interfaces';
-import {
- capitalizeFirstLetter,
- mdToHtml,
- randomStr,
- markdownHelpUrl,
- toast,
- setupTribute,
- wsJsonToRes,
- pictrsDeleteToast,
-} from '../utils';
+} from 'lemmy-js-client';
+import { capitalizeFirstLetter, wsJsonToRes } from '../utils';
import { WebSocketService, UserService } from '../services';
-import autosize from 'autosize';
-import Tribute from 'tributejs/src/Tribute.js';
-import emojiShortName from 'emoji-short-name';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
+import { MarkdownTextArea } from './markdown-textarea';
interface CommentFormProps {
postId?: number;
interface CommentFormState {
commentForm: CommentFormI;
buttonTitle: string;
- previewMode: boolean;
- loading: boolean;
- imageLoading: boolean;
+ finished: boolean;
}
export class CommentForm extends Component<CommentFormProps, CommentFormState> {
- private id = `comment-textarea-${randomStr()}`;
- private formId = `comment-form-${randomStr()}`;
- private tribute: Tribute;
private subscription: Subscription;
private emptyState: CommentFormState = {
commentForm: {
: this.props.edit
? capitalizeFirstLetter(i18n.t('save'))
: capitalizeFirstLetter(i18n.t('reply')),
- previewMode: false,
- loading: false,
- imageLoading: false,
+ finished: false,
};
constructor(props: any, context: any) {
super(props, context);
- this.tribute = setupTribute();
+ this.handleCommentSubmit = this.handleCommentSubmit.bind(this);
+ this.handleReplyCancel = this.handleReplyCancel.bind(this);
this.state = this.emptyState;
);
}
- componentDidMount() {
- let textarea: any = document.getElementById(this.id);
- if (textarea) {
- autosize(textarea);
- this.tribute.attach(textarea);
- textarea.addEventListener('tribute-replaced', () => {
- this.state.commentForm.content = textarea.value;
- this.setState(this.state);
- autosize.update(textarea);
- });
-
- // Quoting of selected text
- let selectedText = window.getSelection().toString();
- if (selectedText) {
- let quotedText =
- selectedText
- .split('\n')
- .map(t => `> ${t}`)
- .join('\n') + '\n\n';
- this.state.commentForm.content = quotedText;
- this.setState(this.state);
- // Not sure why this needs a delay
- setTimeout(() => autosize.update(textarea), 10);
- }
-
- if (this.props.focus) {
- textarea.focus();
- }
- }
- }
-
- componentDidUpdate() {
- if (this.state.commentForm.content) {
- window.onbeforeunload = () => true;
- } else {
- window.onbeforeunload = undefined;
- }
- }
-
componentWillUnmount() {
this.subscription.unsubscribe();
- window.onbeforeunload = null;
}
render() {
return (
<div class="mb-3">
- <Prompt
- when={this.state.commentForm.content}
- message={i18n.t('block_leaving')}
- />
{UserService.Instance.user ? (
- <form
- id={this.formId}
- onSubmit={linkEvent(this, this.handleCommentSubmit)}
- >
- <div class="form-group row">
- <div className={`col-sm-12`}>
- <textarea
- id={this.id}
- className={`form-control ${
- this.state.previewMode && 'd-none'
- }`}
- value={this.state.commentForm.content}
- onInput={linkEvent(this, this.handleCommentContentChange)}
- onPaste={linkEvent(this, this.handleImageUploadPaste)}
- required
- disabled={this.props.disabled}
- rows={2}
- maxLength={10000}
- />
- {this.state.previewMode && (
- <div
- className="card card-body md-div"
- dangerouslySetInnerHTML={mdToHtml(
- this.state.commentForm.content
- )}
- />
- )}
- </div>
- </div>
- <div class="row">
- <div class="col-sm-12">
- <button
- type="submit"
- class="btn btn-sm btn-secondary mr-2"
- disabled={this.props.disabled || this.state.loading}
- >
- {this.state.loading ? (
- <svg class="icon icon-spinner spin">
- <use xlinkHref="#icon-spinner"></use>
- </svg>
- ) : (
- <span>{this.state.buttonTitle}</span>
- )}
- </button>
- {this.state.commentForm.content && (
- <button
- className={`btn btn-sm mr-2 btn-secondary ${
- this.state.previewMode && 'active'
- }`}
- onClick={linkEvent(this, this.handlePreviewToggle)}
- >
- {i18n.t('preview')}
- </button>
- )}
- {this.props.node && (
- <button
- type="button"
- class="btn btn-sm btn-secondary mr-2"
- onClick={linkEvent(this, this.handleReplyCancel)}
- >
- {i18n.t('cancel')}
- </button>
- )}
- <a
- href={markdownHelpUrl}
- target="_blank"
- class="d-inline-block float-right text-muted font-weight-bold"
- title={i18n.t('formatting_help')}
- rel="noopener"
- >
- <svg class="icon icon-inline">
- <use xlinkHref="#icon-help-circle"></use>
- </svg>
- </a>
- <form class="d-inline-block mr-3 float-right text-muted font-weight-bold">
- <label
- htmlFor={`file-upload-${this.id}`}
- className={`${UserService.Instance.user && 'pointer'}`}
- data-tippy-content={i18n.t('upload_image')}
- >
- <svg class="icon icon-inline">
- <use xlinkHref="#icon-image"></use>
- </svg>
- </label>
- <input
- id={`file-upload-${this.id}`}
- type="file"
- accept="image/*,video/*"
- name="file"
- class="d-none"
- disabled={!UserService.Instance.user}
- onChange={linkEvent(this, this.handleImageUpload)}
- />
- </form>
- {this.state.imageLoading && (
- <svg class="icon icon-spinner spin">
- <use xlinkHref="#icon-spinner"></use>
- </svg>
- )}
- </div>
- </div>
- </form>
+ <MarkdownTextArea
+ initialContent={this.state.commentForm.content}
+ buttonTitle={this.state.buttonTitle}
+ finished={this.state.finished}
+ replyType={!!this.props.node}
+ focus={this.props.focus}
+ disabled={this.props.disabled}
+ onSubmit={this.handleCommentSubmit}
+ onReplyCancel={this.handleReplyCancel}
+ />
) : (
<div class="alert alert-light" role="alert">
<svg class="icon icon-inline mr-2">
);
}
- handleFinished(op: UserOperation, data: CommentResponse) {
- let isReply =
- this.props.node !== undefined && data.comment.parent_id !== null;
- let xor =
- +!(data.comment.parent_id !== null) ^ +(this.props.node !== undefined);
-
- if (
- (data.comment.creator_id == UserService.Instance.user.id &&
- ((op == UserOperation.CreateComment &&
- // If its a reply, make sure parent child match
- isReply &&
- data.comment.parent_id == this.props.node.comment.id) ||
- // Otherwise, check the XOR of the two
- (!isReply && xor))) ||
- // If its a comment edit, only check that its from your user, and that its a
- // text edit only
-
- (data.comment.creator_id == UserService.Instance.user.id &&
- op == UserOperation.EditComment &&
- data.comment.content)
- ) {
- this.state.previewMode = false;
- this.state.loading = false;
- this.state.commentForm.content = '';
- this.setState(this.state);
- let form: any = document.getElementById(this.formId);
- form.reset();
- if (this.props.node) {
- this.props.onReplyCancel();
- }
- autosize.update(form);
- this.setState(this.state);
- }
- }
-
- handleCommentSubmit(i: CommentForm, event: any) {
- event.preventDefault();
- if (i.props.edit) {
- WebSocketService.Instance.editComment(i.state.commentForm);
+ handleCommentSubmit(msg: { val: string; formId: string }) {
+ this.state.commentForm.content = msg.val;
+ this.state.commentForm.form_id = msg.formId;
+ if (this.props.edit) {
+ WebSocketService.Instance.editComment(this.state.commentForm);
} else {
- WebSocketService.Instance.createComment(i.state.commentForm);
- }
-
- i.state.loading = true;
- i.setState(i.state);
- }
-
- handleCommentContentChange(i: CommentForm, event: any) {
- i.state.commentForm.content = event.target.value;
- i.setState(i.state);
- }
-
- handlePreviewToggle(i: CommentForm, event: any) {
- event.preventDefault();
- i.state.previewMode = !i.state.previewMode;
- i.setState(i.state);
- }
-
- handleReplyCancel(i: CommentForm) {
- i.props.onReplyCancel();
- }
-
- handleImageUploadPaste(i: CommentForm, event: any) {
- let image = event.clipboardData.files[0];
- if (image) {
- i.handleImageUpload(i, image);
+ WebSocketService.Instance.createComment(this.state.commentForm);
}
+ this.setState(this.state);
}
- handleImageUpload(i: CommentForm, event: any) {
- let file: any;
- if (event.target) {
- event.preventDefault();
- file = event.target.files[0];
- } else {
- file = event;
- }
-
- const imageUploadUrl = `/pictrs/image`;
- const formData = new FormData();
- formData.append('images[]', file);
-
- i.state.imageLoading = true;
- i.setState(i.state);
-
- fetch(imageUploadUrl, {
- method: 'POST',
- body: formData,
- })
- .then(res => res.json())
- .then(res => {
- console.log('pictrs upload:');
- console.log(res);
- if (res.msg == 'ok') {
- let hash = res.files[0].file;
- let url = `${window.location.origin}/pictrs/image/${hash}`;
- let deleteToken = res.files[0].delete_token;
- let deleteUrl = `${window.location.origin}/pictrs/image/delete/${deleteToken}/${hash}`;
- let imageMarkdown = `![](${url})`;
- let content = i.state.commentForm.content;
- content = content ? `${content}\n${imageMarkdown}` : imageMarkdown;
- i.state.commentForm.content = content;
- i.state.imageLoading = false;
- i.setState(i.state);
- let textarea: any = document.getElementById(i.id);
- autosize.update(textarea);
- pictrsDeleteToast(
- i18n.t('click_to_delete_picture'),
- i18n.t('picture_deleted'),
- deleteUrl
- );
- } else {
- i.state.imageLoading = false;
- i.setState(i.state);
- toast(JSON.stringify(res), 'danger');
- }
- })
- .catch(error => {
- i.state.imageLoading = false;
- i.setState(i.state);
- toast(error, 'danger');
- });
+ handleReplyCancel() {
+ this.props.onReplyCancel();
}
parseMessage(msg: WebSocketJsonResponse) {
// Only do the showing and hiding if logged in
if (UserService.Instance.user) {
- if (res.op == UserOperation.CreateComment) {
+ if (
+ res.op == UserOperation.CreateComment ||
+ res.op == UserOperation.EditComment
+ ) {
let data = res.data as CommentResponse;
- this.handleFinished(res.op, data);
- } else if (res.op == UserOperation.EditComment) {
- let data = res.data as CommentResponse;
- this.handleFinished(res.op, data);
+
+ // This only finishes this form, if the randomly generated form_id matches the one received
+ if (this.state.commentForm.form_id == data.form_id) {
+ this.setState({ finished: true });
+
+ // Necessary because it broke tribute for some reaso
+ this.setState({ finished: false });
+ }
}
}
}
import {
CommentNode as CommentNodeI,
CommentLikeForm,
- CommentForm as CommentFormI,
- EditUserMentionForm,
+ DeleteCommentForm,
+ RemoveCommentForm,
+ MarkCommentAsReadForm,
+ MarkUserMentionAsReadForm,
SaveCommentForm,
BanFromCommunityForm,
BanUserForm,
AddAdminForm,
TransferCommunityForm,
TransferSiteForm,
- BanType,
- CommentSortType,
SortType,
-} from '../interfaces';
+} from 'lemmy-js-client';
+import { CommentSortType, BanType } from '../interfaces';
import { WebSocketService, UserService } from '../services';
import {
mdToHtml,
showRemoveDialog: boolean;
removeReason: string;
showBanDialog: boolean;
+ removeData: boolean;
banReason: string;
banExpires: string;
banType: BanType;
interface CommentNodeProps {
node: CommentNodeI;
+ noBorder?: boolean;
noIndent?: boolean;
viewOnly?: boolean;
locked?: boolean;
showRemoveDialog: false,
removeReason: null,
showBanDialog: false,
+ removeData: null,
banReason: null,
banExpires: null,
banType: BanType.Community,
>
<div
id={`comment-${node.comment.id}`}
- className={`details comment-node border-top border-light py-2 ${
- this.isCommentNew ? 'mark' : ''
- }`}
+ className={`details comment-node py-2 ${
+ !this.props.noBorder ? 'border-top border-light' : ''
+ } ${this.isCommentNew ? 'mark' : ''}`}
style={
!this.props.noIndent &&
this.props.node.comment.parent_id &&
<UserListing
user={{
name: node.comment.creator_name,
+ preferred_username: node.comment.creator_preferred_username,
avatar: node.comment.creator_avatar,
id: node.comment.creator_id,
local: node.comment.creator_local,
id: node.comment.community_id,
local: node.comment.community_local,
actor_id: node.comment.community_actor_id,
+ icon: node.comment.community_icon,
}}
/>
<span class="mx-2">•</span>
</>
)}
<button
- class="btn btn-sm text-muted"
+ class="btn text-muted"
onClick={linkEvent(this, this.handleCommentCollapse)}
>
{this.state.collapsed ? (
{/* This is an expanding spacer for mobile */}
<div className="mr-lg-4 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"></div>
<button
- className={`btn btn-sm p-0 unselectable pointer ${this.scoreColor}`}
+ className={`btn p-0 unselectable pointer ${this.scoreColor}`}
onClick={linkEvent(node, this.handleCommentUpvote)}
data-tippy-content={this.pointsTippy}
>
</button>
))}
{!node.comment.banned_from_community &&
+ node.comment.creator_local &&
(!this.state.showConfirmAppointAsMod ? (
<button
class="btn btn-link btn-animate text-muted"
{/* Community creators and admins can transfer community to another mod */}
{(this.amCommunityCreator || this.canAdmin) &&
this.isMod &&
+ node.comment.creator_local &&
(!this.state.showConfirmTransferCommunity ? (
<button
class="btn btn-link btn-animate text-muted"
</button>
))}
{!node.comment.banned &&
+ node.comment.creator_local &&
(!this.state.showConfirmAppointAsAdmin ? (
<button
class="btn btn-link btn-animate text-muted"
{/* Site Creator can transfer to another admin */}
{this.amSiteCreator &&
this.isAdmin &&
+ node.comment.creator_local &&
(!this.state.showConfirmTransferSite ? (
<button
class="btn btn-link btn-animate text-muted"
value={this.state.banReason}
onInput={linkEvent(this, this.handleModBanReasonChange)}
/>
+ <div class="form-group">
+ <div class="form-check">
+ <input
+ class="form-check-input"
+ id="mod-ban-remove-data"
+ type="checkbox"
+ checked={this.state.removeData}
+ onChange={linkEvent(this, this.handleModRemoveDataChange)}
+ />
+ <label class="form-check-label" htmlFor="mod-ban-remove-data">
+ {i18n.t('remove_posts_comments')}
+ </label>
+ </div>
+ </div>
</div>
{/* TODO hold off on expires until later */}
{/* <div class="form-group row"> */}
}
handleDeleteClick(i: CommentNode) {
- let deleteForm: CommentFormI = {
- content: i.props.node.comment.content,
+ let deleteForm: DeleteCommentForm = {
edit_id: i.props.node.comment.id,
- creator_id: i.props.node.comment.creator_id,
- post_id: i.props.node.comment.post_id,
- parent_id: i.props.node.comment.parent_id,
deleted: !i.props.node.comment.deleted,
auth: null,
};
- WebSocketService.Instance.editComment(deleteForm);
+ WebSocketService.Instance.deleteComment(deleteForm);
}
handleSaveCommentClick(i: CommentNode) {
let form: CommentLikeForm = {
comment_id: i.comment.id,
- post_id: i.comment.post_id,
score: this.state.my_vote,
};
let form: CommentLikeForm = {
comment_id: i.comment.id,
- post_id: i.comment.post_id,
score: this.state.my_vote,
};
i.setState(i.state);
}
+ handleModRemoveDataChange(i: CommentNode, event: any) {
+ i.state.removeData = event.target.checked;
+ i.setState(i.state);
+ }
+
handleModRemoveSubmit(i: CommentNode) {
event.preventDefault();
- let form: CommentFormI = {
- content: i.props.node.comment.content,
+ let form: RemoveCommentForm = {
edit_id: i.props.node.comment.id,
- creator_id: i.props.node.comment.creator_id,
- post_id: i.props.node.comment.post_id,
- parent_id: i.props.node.comment.parent_id,
removed: !i.props.node.comment.removed,
reason: i.state.removeReason,
auth: null,
};
- WebSocketService.Instance.editComment(form);
+ WebSocketService.Instance.removeComment(form);
i.state.showRemoveDialog = false;
i.setState(i.state);
handleMarkRead(i: CommentNode) {
// if it has a user_mention_id field, then its a mention
if (i.props.node.comment.user_mention_id) {
- let form: EditUserMentionForm = {
+ let form: MarkUserMentionAsReadForm = {
user_mention_id: i.props.node.comment.user_mention_id,
read: !i.props.node.comment.read,
};
- WebSocketService.Instance.editUserMention(form);
+ WebSocketService.Instance.markUserMentionAsRead(form);
} else {
- let form: CommentFormI = {
- content: i.props.node.comment.content,
+ let form: MarkCommentAsReadForm = {
edit_id: i.props.node.comment.id,
- creator_id: i.props.node.comment.creator_id,
- post_id: i.props.node.comment.post_id,
- parent_id: i.props.node.comment.parent_id,
read: !i.props.node.comment.read,
auth: null,
};
- WebSocketService.Instance.editComment(form);
+ WebSocketService.Instance.markCommentAsRead(form);
}
i.state.readLoading = true;
event.preventDefault();
if (i.state.banType == BanType.Community) {
+ // If its an unban, restore all their data
+ let ban = !i.props.node.comment.banned_from_community;
+ if (ban == false) {
+ i.state.removeData = false;
+ }
let form: BanFromCommunityForm = {
user_id: i.props.node.comment.creator_id,
community_id: i.props.node.comment.community_id,
- ban: !i.props.node.comment.banned_from_community,
+ ban,
+ remove_data: i.state.removeData,
reason: i.state.banReason,
expires: getUnixTime(i.state.banExpires),
};
WebSocketService.Instance.banFromCommunity(form);
} else {
+ // If its an unban, restore all their data
+ let ban = !i.props.node.comment.banned;
+ if (ban == false) {
+ i.state.removeData = false;
+ }
let form: BanUserForm = {
user_id: i.props.node.comment.creator_id,
- ban: !i.props.node.comment.banned,
+ ban,
+ remove_data: i.state.removeData,
reason: i.state.banReason,
expires: getUnixTime(i.state.banExpires),
};
import { Component } from 'inferno';
+import { CommentSortType } from '../interfaces';
import {
CommentNode as CommentNodeI,
CommunityUser,
UserView,
- CommentSortType,
SortType,
-} from '../interfaces';
+} from 'lemmy-js-client';
import { commentSort, commentSortSortType } from '../utils';
import { CommentNode } from './comment-node';
moderators?: Array<CommunityUser>;
admins?: Array<UserView>;
postCreatorId?: number;
+ noBorder?: boolean;
noIndent?: boolean;
viewOnly?: boolean;
locked?: boolean;
<CommentNode
key={node.comment.id}
node={node}
+ noBorder={this.props.noBorder}
noIndent={this.props.noIndent}
viewOnly={this.props.viewOnly}
locked={this.props.locked}
import { Component, linkEvent } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import {
SortType,
WebSocketJsonResponse,
GetSiteResponse,
-} from '../interfaces';
+ Site,
+} from 'lemmy-js-client';
import { WebSocketService } from '../services';
import { wsJsonToRes, toast, getPageFromProps } from '../utils';
import { CommunityLink } from './community-link';
communities: Array<Community>;
page: number;
loading: boolean;
+ site: Site;
}
interface CommunitiesProps {
communities: [],
loading: true,
page: getPageFromProps(this.props),
+ site: undefined,
};
constructor(props: any, context: any) {
}
}
+ get documentTitle(): string {
+ if (this.state.site) {
+ return `${i18n.t('communities')} - ${this.state.site.name}`;
+ } else {
+ return 'Lemmy';
+ }
+ }
+
render() {
return (
<div class="container">
+ <Helmet title={this.documentTitle} />
{this.state.loading ? (
<h5 class="">
<svg class="icon icon-spinner spin">
<thead class="pointer">
<tr>
<th>{i18n.t('name')}</th>
- <th class="d-none d-lg-table-cell">{i18n.t('title')}</th>
<th>{i18n.t('category')}</th>
<th class="text-right">{i18n.t('subscribers')}</th>
<th class="text-right d-none d-lg-table-cell">
<td>
<CommunityLink community={community} />
</td>
- <td class="d-none d-lg-table-cell">{community.title}</td>
<td>{community.category_name}</td>
<td class="text-right">
{community.number_of_subscribers}
<div class="mt-2">
{this.state.page > 1 && (
<button
- class="btn btn-sm btn-secondary mr-1"
+ class="btn btn-secondary mr-1"
onClick={linkEvent(this, this.prevPage)}
>
{i18n.t('prev')}
{this.state.communities.length > 0 && (
<button
- class="btn btn-sm btn-secondary"
+ class="btn btn-secondary"
onClick={linkEvent(this, this.nextPage)}
>
{i18n.t('next')}
refetch() {
let listCommunitiesForm: ListCommunitiesForm = {
- sort: SortType[SortType.TopAll],
+ sort: SortType.TopAll,
limit: communityLimit,
page: this.state.page,
};
this.setState(this.state);
} else if (res.op == UserOperation.GetSite) {
let data = res.data as GetSiteResponse;
- document.title = `${i18n.t('communities')} - ${data.site.name}`;
+ this.state.site = data.site;
+ this.setState(this.state);
}
}
}
ListCategoriesResponse,
CommunityResponse,
WebSocketJsonResponse,
-} from '../interfaces';
+ Community,
+} from 'lemmy-js-client';
import { WebSocketService } from '../services';
-import {
- wsJsonToRes,
- capitalizeFirstLetter,
- toast,
- randomStr,
- setupTribute,
-} from '../utils';
-import Tribute from 'tributejs/src/Tribute.js';
-import autosize from 'autosize';
+import { wsJsonToRes, capitalizeFirstLetter, toast, randomStr } from '../utils';
import { i18n } from '../i18next';
-import { Community } from '../interfaces';
+import { MarkdownTextArea } from './markdown-textarea';
+import { ImageUploadForm } from './image-upload-form';
interface CommunityFormProps {
community?: Community; // If a community is given, that means this is an edit
CommunityFormState
> {
private id = `community-form-${randomStr()}`;
- private tribute: Tribute;
private subscription: Subscription;
private emptyState: CommunityFormState = {
title: null,
category_id: null,
nsfw: false,
+ icon: null,
+ banner: null,
},
categories: [],
loading: false,
constructor(props: any, context: any) {
super(props, context);
- this.tribute = setupTribute();
this.state = this.emptyState;
+ this.handleCommunityDescriptionChange = this.handleCommunityDescriptionChange.bind(
+ this
+ );
+
+ this.handleIconUpload = this.handleIconUpload.bind(this);
+ this.handleIconRemove = this.handleIconRemove.bind(this);
+
+ this.handleBannerUpload = this.handleBannerUpload.bind(this);
+ this.handleBannerRemove = this.handleBannerRemove.bind(this);
+
if (this.props.community) {
this.state.communityForm = {
name: this.props.community.name,
description: this.props.community.description,
edit_id: this.props.community.id,
nsfw: this.props.community.nsfw,
+ icon: this.props.community.icon,
+ banner: this.props.community.banner,
auth: null,
};
}
WebSocketService.Instance.listCategories();
}
- componentDidMount() {
- var textarea: any = document.getElementById(this.id);
- autosize(textarea);
- this.tribute.attach(textarea);
- textarea.addEventListener('tribute-replaced', () => {
- this.state.communityForm.description = textarea.value;
- this.setState(this.state);
- autosize.update(textarea);
- });
- }
-
componentDidUpdate() {
if (
!this.state.loading &&
message={i18n.t('block_leaving')}
/>
<form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
- <div class="form-group row">
- <label class="col-12 col-form-label" htmlFor="community-name">
- {i18n.t('name')}
- </label>
- <div class="col-12">
- <input
- type="text"
- id="community-name"
- class="form-control"
- value={this.state.communityForm.name}
- onInput={linkEvent(this, this.handleCommunityNameChange)}
- required
- minLength={3}
- maxLength={20}
- pattern="[a-z0-9_]+"
- title={i18n.t('community_reqs')}
- />
+ {!this.props.community && (
+ <div class="form-group row">
+ <label class="col-12 col-form-label" htmlFor="community-name">
+ {i18n.t('name')}
+ <span
+ class="pointer unselectable ml-2 text-muted"
+ data-tippy-content={i18n.t('name_explain')}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-help-circle"></use>
+ </svg>
+ </span>
+ </label>
+ <div class="col-12">
+ <input
+ type="text"
+ id="community-name"
+ class="form-control"
+ value={this.state.communityForm.name}
+ onInput={linkEvent(this, this.handleCommunityNameChange)}
+ required
+ minLength={3}
+ maxLength={20}
+ pattern="[a-z0-9_]+"
+ title={i18n.t('community_reqs')}
+ />
+ </div>
</div>
- </div>
-
+ )}
<div class="form-group row">
<label class="col-12 col-form-label" htmlFor="community-title">
- {i18n.t('title')}
+ {i18n.t('display_name')}
+ <span
+ class="pointer unselectable ml-2 text-muted"
+ data-tippy-content={i18n.t('display_name_explain')}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-help-circle"></use>
+ </svg>
+ </span>
</label>
<div class="col-12">
<input
/>
</div>
</div>
+ <div class="form-group">
+ <label>{i18n.t('icon')}</label>
+ <ImageUploadForm
+ uploadTitle={i18n.t('upload_icon')}
+ imageSrc={this.state.communityForm.icon}
+ onUpload={this.handleIconUpload}
+ onRemove={this.handleIconRemove}
+ rounded
+ />
+ </div>
+ <div class="form-group">
+ <label>{i18n.t('banner')}</label>
+ <ImageUploadForm
+ uploadTitle={i18n.t('upload_banner')}
+ imageSrc={this.state.communityForm.banner}
+ onUpload={this.handleBannerUpload}
+ onRemove={this.handleBannerRemove}
+ />
+ </div>
<div class="form-group row">
<label class="col-12 col-form-label" htmlFor={this.id}>
{i18n.t('sidebar')}
</label>
<div class="col-12">
- <textarea
- id={this.id}
- value={this.state.communityForm.description}
- onInput={linkEvent(this, this.handleCommunityDescriptionChange)}
- class="form-control"
- rows={3}
- maxLength={10000}
+ <MarkdownTextArea
+ initialContent={this.state.communityForm.description}
+ onContentChange={this.handleCommunityDescriptionChange}
/>
</div>
</div>
i.setState(i.state);
}
- handleCommunityDescriptionChange(i: CommunityForm, event: any) {
- i.state.communityForm.description = event.target.value;
- i.setState(i.state);
+ handleCommunityDescriptionChange(val: string) {
+ this.state.communityForm.description = val;
+ this.setState(this.state);
}
handleCommunityCategoryChange(i: CommunityForm, event: any) {
i.props.onCancel();
}
+ handleIconUpload(url: string) {
+ this.state.communityForm.icon = url;
+ this.setState(this.state);
+ }
+
+ handleIconRemove() {
+ this.state.communityForm.icon = '';
+ this.setState(this.state);
+ }
+
+ handleBannerUpload(url: string) {
+ this.state.communityForm.banner = url;
+ this.setState(this.state);
+ }
+
+ handleBannerRemove() {
+ this.state.communityForm.banner = '';
+ this.setState(this.state);
+ }
+
parseMessage(msg: WebSocketJsonResponse) {
let res = wsJsonToRes(msg);
console.log(msg);
let data = res.data as CommunityResponse;
this.state.loading = false;
this.props.onCreate(data.community);
- }
- // TODO is this necessary
- else if (res.op == UserOperation.EditCommunity) {
+ } else if (res.op == UserOperation.EditCommunity) {
let data = res.data as CommunityResponse;
this.state.loading = false;
this.props.onEdit(data.community);
import { Component } from 'inferno';
import { Link } from 'inferno-router';
-import { Community } from '../interfaces';
-import { hostname } from '../utils';
+import { Community } from 'lemmy-js-client';
+import { hostname, pictrsAvatarThumbnail, showAvatars } from '../utils';
interface CommunityOther {
name: string;
id?: number; // Necessary if its federated
+ icon?: string;
local?: boolean;
actor_id?: string;
}
interface CommunityLinkProps {
community: Community | CommunityOther;
realLink?: boolean;
+ useApubName?: boolean;
+ muted?: boolean;
+ hideAvatar?: boolean;
}
export class CommunityLink extends Component<CommunityLinkProps, any> {
? `/community/${community.id}`
: community.actor_id;
}
- return <Link to={link}>{name_}</Link>;
+
+ let apubName = `!${name_}`;
+ let displayName = this.props.useApubName ? apubName : name_;
+ return (
+ <Link
+ title={apubName}
+ className={`${this.props.muted ? 'text-muted' : ''}`}
+ to={link}
+ >
+ {!this.props.hideAvatar && community.icon && showAvatars() && (
+ <img
+ style="width: 2rem; height: 2rem;"
+ src={pictrsAvatarThumbnail(community.icon)}
+ class="rounded-circle mr-2"
+ />
+ )}
+ <span>{displayName}</span>
+ </Link>
+ );
}
}
import { Component, linkEvent } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
+import { DataType } from '../interfaces';
import {
UserOperation,
Community as CommunityI,
GetPostsForm,
GetCommunityForm,
ListingType,
- DataType,
GetPostsResponse,
PostResponse,
AddModToCommunityResponse,
WebSocketJsonResponse,
GetSiteResponse,
Site,
-} from '../interfaces';
+} from 'lemmy-js-client';
import { WebSocketService } from '../services';
import { PostListings } from './post-listings';
import { CommentNodes } from './comment-nodes';
import { SortSelect } from './sort-select';
import { DataTypeSelect } from './data-type-select';
import { Sidebar } from './sidebar';
+import { CommunityLink } from './community-link';
+import { BannerIconHeader } from './banner-icon-header';
import {
wsJsonToRes,
fetchLimit,
editPostFindRes,
commentsToFlatNodes,
setupTippy,
+ favIconUrl,
+ notifyPost,
} from '../utils';
import { i18n } from '../i18next';
interface UrlParams {
dataType?: string;
- sort?: string;
+ sort?: SortType;
page?: number;
}
enable_downvotes: undefined,
open_registration: undefined,
enable_nsfw: undefined,
+ icon: undefined,
+ banner: undefined,
+ creator_preferred_username: undefined,
},
};
}
}
+ get documentTitle(): string {
+ if (this.state.community.title) {
+ return `${this.state.community.title} - ${this.state.site.name}`;
+ } else {
+ return 'Lemmy';
+ }
+ }
+
+ get favIcon(): string {
+ return this.state.site.icon ? this.state.site.icon : favIconUrl;
+ }
+
render() {
return (
<div class="container">
- {this.selects()}
+ <Helmet title={this.documentTitle}>
+ <link
+ id="favicon"
+ rel="icon"
+ type="image/x-icon"
+ href={this.favIcon}
+ />
+ </Helmet>
{this.state.loading ? (
<h5>
<svg class="icon icon-spinner spin">
) : (
<div class="row">
<div class="col-12 col-md-8">
- <h5>
- {this.state.community.title}
- {this.state.community.removed && (
- <small className="ml-2 text-muted font-italic">
- {i18n.t('removed')}
- </small>
- )}
- {this.state.community.nsfw && (
- <small className="ml-2 text-muted font-italic">
- {i18n.t('nsfw')}
- </small>
- )}
- </h5>
+ {this.communityInfo()}
+ {this.selects()}
{this.listings()}
{this.paginator()}
</div>
);
}
+ communityInfo() {
+ return (
+ <div>
+ <BannerIconHeader
+ banner={this.state.community.banner}
+ icon={this.state.community.icon}
+ />
+ <h5 class="mb-0">{this.state.community.title}</h5>
+ <CommunityLink
+ community={this.state.community}
+ realLink
+ useApubName
+ muted
+ hideAvatar
+ />
+ <hr />
+ </div>
+ );
+ }
+
selects() {
return (
<div class="mb-3">
<SortSelect sort={this.state.sort} onChange={this.handleSortChange} />
</span>
<a
- href={`/feeds/c/${this.state.communityName}.xml?sort=${
- SortType[this.state.sort]
- }`}
+ href={`/feeds/c/${this.state.communityName}.xml?sort=${this.state.sort}`}
target="_blank"
title="RSS"
rel="noopener"
<div class="my-2">
{this.state.page > 1 && (
<button
- class="btn btn-sm btn-secondary mr-1"
+ class="btn btn-secondary mr-1"
onClick={linkEvent(this, this.prevPage)}
>
{i18n.t('prev')}
)}
{this.state.posts.length > 0 && (
<button
- class="btn btn-sm btn-secondary"
+ class="btn btn-secondary"
onClick={linkEvent(this, this.nextPage)}
>
{i18n.t('next')}
}
handleSortChange(val: SortType) {
- this.updateUrl({ sort: SortType[val].toLowerCase(), page: 1 });
+ this.updateUrl({ sort: val, page: 1 });
window.scrollTo(0, 0);
}
handleDataTypeChange(val: DataType) {
- this.updateUrl({ dataType: DataType[val].toLowerCase(), page: 1 });
+ this.updateUrl({ dataType: DataType[val], page: 1 });
window.scrollTo(0, 0);
}
updateUrl(paramUpdates: UrlParams) {
- const dataTypeStr =
- paramUpdates.dataType || DataType[this.state.dataType].toLowerCase();
- const sortStr =
- paramUpdates.sort || SortType[this.state.sort].toLowerCase();
+ const dataTypeStr = paramUpdates.dataType || DataType[this.state.dataType];
+ const sortStr = paramUpdates.sort || this.state.sort;
const page = paramUpdates.page || this.state.page;
this.props.history.push(
`/c/${this.state.community.name}/data_type/${dataTypeStr}/sort/${sortStr}/page/${page}`
let getPostsForm: GetPostsForm = {
page: this.state.page,
limit: fetchLimit,
- sort: SortType[this.state.sort],
- type_: ListingType[ListingType.Community],
+ sort: this.state.sort,
+ type_: ListingType.Community,
community_id: this.state.community.id,
};
WebSocketService.Instance.getPosts(getPostsForm);
let getCommentsForm: GetCommentsForm = {
page: this.state.page,
limit: fetchLimit,
- sort: SortType[this.state.sort],
- type_: ListingType[ListingType.Community],
+ sort: this.state.sort,
+ type_: ListingType.Community,
community_id: this.state.community.id,
};
WebSocketService.Instance.getComments(getCommentsForm);
let data = res.data as GetCommunityResponse;
this.state.community = data.community;
this.state.moderators = data.moderators;
- this.state.admins = data.admins;
this.state.online = data.online;
- document.title = `/c/${this.state.community.name} - ${this.state.site.name}`;
this.setState(this.state);
this.fetchData();
- } else if (res.op == UserOperation.EditCommunity) {
+ } else if (
+ res.op == UserOperation.EditCommunity ||
+ res.op == UserOperation.DeleteCommunity ||
+ res.op == UserOperation.RemoveCommunity
+ ) {
let data = res.data as CommunityResponse;
this.state.community = data.community;
this.setState(this.state);
this.state.loading = false;
this.setState(this.state);
setupTippy();
- } else if (res.op == UserOperation.EditPost) {
+ } else if (
+ res.op == UserOperation.EditPost ||
+ res.op == UserOperation.DeletePost ||
+ res.op == UserOperation.RemovePost ||
+ res.op == UserOperation.LockPost ||
+ res.op == UserOperation.StickyPost
+ ) {
let data = res.data as PostResponse;
editPostFindRes(data, this.state.posts);
this.setState(this.state);
} else if (res.op == UserOperation.CreatePost) {
let data = res.data as PostResponse;
this.state.posts.unshift(data.post);
+ notifyPost(data.post, this.context.router);
this.setState(this.state);
} else if (res.op == UserOperation.CreatePostLike) {
let data = res.data as PostResponse;
this.state.comments = data.comments;
this.state.loading = false;
this.setState(this.state);
- } else if (res.op == UserOperation.EditComment) {
+ } else if (
+ res.op == UserOperation.EditComment ||
+ res.op == UserOperation.DeleteComment ||
+ res.op == UserOperation.RemoveComment
+ ) {
let data = res.data as CommentResponse;
editCommentRes(data, this.state.comments);
this.setState(this.state);
} else if (res.op == UserOperation.GetSite) {
let data = res.data as GetSiteResponse;
this.state.site = data.site;
+ this.state.admins = data.admins;
this.setState(this.state);
}
}
import { Component } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import { CommunityForm } from './community-form';
UserOperation,
WebSocketJsonResponse,
GetSiteResponse,
-} from '../interfaces';
+ Site,
+} from 'lemmy-js-client';
import { toast, wsJsonToRes } from '../utils';
import { WebSocketService, UserService } from '../services';
import { i18n } from '../i18next';
interface CreateCommunityState {
- enableNsfw: boolean;
+ site: Site;
}
export class CreateCommunity extends Component<any, CreateCommunityState> {
private subscription: Subscription;
private emptyState: CreateCommunityState = {
- enableNsfw: null,
+ site: {
+ id: undefined,
+ name: undefined,
+ creator_id: undefined,
+ published: undefined,
+ creator_name: undefined,
+ number_of_users: undefined,
+ number_of_posts: undefined,
+ number_of_comments: undefined,
+ number_of_communities: undefined,
+ enable_downvotes: undefined,
+ open_registration: undefined,
+ enable_nsfw: undefined,
+ },
};
constructor(props: any, context: any) {
super(props, context);
this.subscription.unsubscribe();
}
+ get documentTitle(): string {
+ if (this.state.site.name) {
+ return `${i18n.t('create_community')} - ${this.state.site.name}`;
+ } else {
+ return 'Lemmy';
+ }
+ }
+
render() {
return (
<div class="container">
+ <Helmet title={this.documentTitle} />
<div class="row">
<div class="col-12 col-lg-6 offset-lg-3 mb-4">
<h5>{i18n.t('create_community')}</h5>
<CommunityForm
onCreate={this.handleCommunityCreate}
- enableNsfw={this.state.enableNsfw}
+ enableNsfw={this.state.site.enable_nsfw}
/>
</div>
</div>
console.log(msg);
let res = wsJsonToRes(msg);
if (msg.error) {
- toast(i18n.t(msg.error), 'danger');
+ // Toast errors are already handled by community-form
return;
} else if (res.op == UserOperation.GetSite) {
let data = res.data as GetSiteResponse;
- this.state.enableNsfw = data.site.enable_nsfw;
+ this.state.site = data.site;
this.setState(this.state);
- document.title = `${i18n.t('create_community')} - ${data.site.name}`;
}
}
}
import { Component } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import { PostForm } from './post-form';
WebSocketJsonResponse,
GetSiteResponse,
Site,
-} from '../interfaces';
+} from 'lemmy-js-client';
import { i18n } from '../i18next';
interface CreatePostState {
this.subscription.unsubscribe();
}
+ get documentTitle(): string {
+ if (this.state.site.name) {
+ return `${i18n.t('create_post')} - ${this.state.site.name}`;
+ } else {
+ return 'Lemmy';
+ }
+ }
+
render() {
return (
<div class="container">
+ <Helmet title={this.documentTitle} />
<div class="row">
<div class="col-12 col-lg-6 offset-lg-3 mb-4">
<h5>{i18n.t('create_post')}</h5>
return lastLocation.split('/c/')[1];
}
}
- return undefined;
+ return;
}
handlePostCreate(id: number) {
let data = res.data as GetSiteResponse;
this.state.site = data.site;
this.setState(this.state);
- document.title = `${i18n.t('create_post')} - ${data.site.name}`;
}
}
}
import { Component } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import { PrivateMessageForm } from './private-message-form';
UserOperation,
WebSocketJsonResponse,
GetSiteResponse,
+ Site,
PrivateMessageFormParams,
-} from '../interfaces';
+} from 'lemmy-js-client';
import { toast, wsJsonToRes } from '../utils';
import { i18n } from '../i18next';
-export class CreatePrivateMessage extends Component<any, any> {
+interface CreatePrivateMessageState {
+ site: Site;
+}
+
+export class CreatePrivateMessage extends Component<
+ any,
+ CreatePrivateMessageState
+> {
private subscription: Subscription;
+ private emptyState: CreatePrivateMessageState = {
+ site: undefined,
+ };
constructor(props: any, context: any) {
super(props, context);
+ this.state = this.emptyState;
this.handlePrivateMessageCreate = this.handlePrivateMessageCreate.bind(
this
);
this.subscription.unsubscribe();
}
+ get documentTitle(): string {
+ if (this.state.site) {
+ return `${i18n.t('create_private_message')} - ${this.state.site.name}`;
+ } else {
+ return 'Lemmy';
+ }
+ }
+
render() {
return (
<div class="container">
+ <Helmet title={this.documentTitle} />
<div class="row">
<div class="col-12 col-lg-6 offset-lg-3 mb-4">
<h5>{i18n.t('create_private_message')}</h5>
return;
} else if (res.op == UserOperation.GetSite) {
let data = res.data as GetSiteResponse;
- document.title = `${i18n.t('create_private_message')} - ${
- data.site.name
- }`;
+ this.state.site = data.site;
+ this.setState(this.state);
}
}
}
render() {
return (
- <div class="btn-group btn-group-toggle">
+ <div class="btn-group btn-group-toggle flex-wrap mb-2">
<label
- className={`pointer btn btn-sm btn-secondary
+ className={`pointer btn btn-outline-secondary
${this.state.type_ == DataType.Post && 'active'}
`}
>
{i18n.t('posts')}
</label>
<label
- className={`pointer btn btn-sm btn-secondary ${
+ className={`pointer btn btn-outline-secondary ${
this.state.type_ == DataType.Comment && 'active'
}`}
>
import { Component } from 'inferno';
import { Link } from 'inferno-router';
-import { repoUrl } from '../utils';
-import { version } from '../version';
import { i18n } from '../i18next';
+import { Subscription } from 'rxjs';
+import { retryWhen, delay, take } from 'rxjs/operators';
+import { WebSocketService } from '../services';
+import { repoUrl, wsJsonToRes } from '../utils';
+import {
+ UserOperation,
+ WebSocketJsonResponse,
+ GetSiteResponse,
+} from 'lemmy-js-client';
-export class Footer extends Component<any, any> {
+interface FooterState {
+ version: string;
+}
+
+export class Footer extends Component<any, FooterState> {
+ private wsSub: Subscription;
+ emptyState: FooterState = {
+ version: null,
+ };
constructor(props: any, context: any) {
super(props, context);
+
+ this.state = this.emptyState;
+
+ this.wsSub = WebSocketService.Instance.subject
+ .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
+ .subscribe(
+ msg => this.parseMessage(msg),
+ err => console.error(err),
+ () => console.log('complete')
+ );
+ }
+
+ componentWillUnmount() {
+ this.wsSub.unsubscribe();
}
render() {
<div className="navbar-collapse">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
- <span class="navbar-text">{version}</span>
+ <span class="navbar-text">{this.state.version}</span>
</li>
<li class="nav-item">
<Link class="nav-link" to="/modlog">
{i18n.t('modlog')}
</Link>
</li>
+ <li class="nav-item">
+ <Link class="nav-link" to="/instances">
+ {i18n.t('instances')}
+ </Link>
+ </li>
<li class="nav-item">
<a class="nav-link" href={'/docs/index.html'}>
{i18n.t('docs')}
</nav>
);
}
+ parseMessage(msg: WebSocketJsonResponse) {
+ let res = wsJsonToRes(msg);
+
+ if (res.op == UserOperation.GetSite) {
+ let data = res.data as GetSiteResponse;
+ this.setState({ version: data.version });
+ }
+ }
}
import { Component, linkEvent } from 'inferno';
-import { Post } from '../interfaces';
+import { Post } from 'lemmy-js-client';
import { mdToHtml } from '../utils';
import { i18n } from '../i18next';
return (
<>
{post.embed_title && !this.state.expanded && (
- <div class="card mt-3 mb-2">
+ <div class="card bg-transparent border-secondary mt-3 mb-2">
<div class="row">
<div class="col-12">
<div class="card-body">
--- /dev/null
+import { Component, linkEvent } from 'inferno';
+import { UserService } from '../services';
+import { toast, randomStr } from '../utils';
+
+interface ImageUploadFormProps {
+ uploadTitle: string;
+ imageSrc: string;
+ onUpload(url: string): any;
+ onRemove(): any;
+ rounded?: boolean;
+}
+
+interface ImageUploadFormState {
+ loading: boolean;
+}
+
+export class ImageUploadForm extends Component<
+ ImageUploadFormProps,
+ ImageUploadFormState
+> {
+ private id = `image-upload-form-${randomStr()}`;
+ private emptyState: ImageUploadFormState = {
+ loading: false,
+ };
+
+ constructor(props: any, context: any) {
+ super(props, context);
+ this.state = this.emptyState;
+ }
+
+ render() {
+ return (
+ <form class="d-inline">
+ <label
+ htmlFor={this.id}
+ class="pointer ml-4 text-muted small font-weight-bold"
+ >
+ {!this.props.imageSrc ? (
+ <span class="btn btn-secondary">{this.props.uploadTitle}</span>
+ ) : (
+ <span class="d-inline-block position-relative">
+ <img
+ src={this.props.imageSrc}
+ height={this.props.rounded ? 60 : ''}
+ width={this.props.rounded ? 60 : ''}
+ className={`img-fluid ${
+ this.props.rounded ? 'rounded-circle' : ''
+ }`}
+ />
+ <a onClick={linkEvent(this, this.handleRemoveImage)}>
+ <svg class="icon mini-overlay">
+ <use xlinkHref="#icon-x"></use>
+ </svg>
+ </a>
+ </span>
+ )}
+ </label>
+ <input
+ id={this.id}
+ type="file"
+ accept="image/*,video/*"
+ name={this.id}
+ class="d-none"
+ disabled={!UserService.Instance.user}
+ onChange={linkEvent(this, this.handleImageUpload)}
+ />
+ </form>
+ );
+ }
+
+ handleImageUpload(i: ImageUploadForm, event: any) {
+ event.preventDefault();
+ let file = event.target.files[0];
+ const imageUploadUrl = `/pictrs/image`;
+ const formData = new FormData();
+ formData.append('images[]', file);
+
+ i.state.loading = true;
+ i.setState(i.state);
+
+ fetch(imageUploadUrl, {
+ method: 'POST',
+ body: formData,
+ })
+ .then(res => res.json())
+ .then(res => {
+ console.log('pictrs upload:');
+ console.log(res);
+ if (res.msg == 'ok') {
+ let hash = res.files[0].file;
+ let url = `${window.location.origin}/pictrs/image/${hash}`;
+ i.state.loading = false;
+ i.setState(i.state);
+ i.props.onUpload(url);
+ } else {
+ i.state.loading = false;
+ i.setState(i.state);
+ toast(JSON.stringify(res), 'danger');
+ }
+ })
+ .catch(error => {
+ i.state.loading = false;
+ i.setState(i.state);
+ toast(error, 'danger');
+ });
+ }
+
+ handleRemoveImage(i: ImageUploadForm, event: any) {
+ event.preventDefault();
+ i.state.loading = true;
+ i.setState(i.state);
+ i.props.onRemove();
+ }
+}
import { Component, linkEvent } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import {
PrivateMessagesResponse,
PrivateMessageResponse,
GetSiteResponse,
-} from '../interfaces';
+ Site,
+} from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services';
import {
wsJsonToRes,
messages: Array<PrivateMessageI>;
sort: SortType;
page: number;
- enableDownvotes: boolean;
+ site: Site;
}
export class Inbox extends Component<any, InboxState> {
messages: [],
sort: SortType.New,
page: 1,
- enableDownvotes: undefined,
+ site: {
+ id: undefined,
+ name: undefined,
+ creator_id: undefined,
+ published: undefined,
+ creator_name: undefined,
+ number_of_users: undefined,
+ number_of_posts: undefined,
+ number_of_comments: undefined,
+ number_of_communities: undefined,
+ enable_downvotes: undefined,
+ open_registration: undefined,
+ enable_nsfw: undefined,
+ },
};
constructor(props: any, context: any) {
this.subscription.unsubscribe();
}
+ get documentTitle(): string {
+ if (this.state.site.name) {
+ return `@${UserService.Instance.user.name} ${i18n.t('inbox')} - ${
+ this.state.site.name
+ }`;
+ } else {
+ return 'Lemmy';
+ }
+ }
+
render() {
return (
<div class="container">
+ <Helmet title={this.documentTitle} />
<div class="row">
<div class="col-12">
<h5 class="mb-1">
unreadOrAllRadios() {
return (
- <div class="btn-group btn-group-toggle">
+ <div class="btn-group btn-group-toggle flex-wrap mb-2">
<label
- className={`btn btn-sm btn-secondary pointer
+ className={`btn btn-outline-secondary pointer
${this.state.unreadOrAll == UnreadOrAll.Unread && 'active'}
`}
>
{i18n.t('unread')}
</label>
<label
- className={`btn btn-sm btn-secondary pointer
+ className={`btn btn-outline-secondary pointer
${this.state.unreadOrAll == UnreadOrAll.All && 'active'}
`}
>
messageTypeRadios() {
return (
- <div class="btn-group btn-group-toggle">
+ <div class="btn-group btn-group-toggle flex-wrap mb-2">
<label
- className={`btn btn-sm btn-secondary pointer btn-outline-light
+ className={`btn btn-outline-secondary pointer
${this.state.messageType == MessageType.All && 'active'}
`}
>
{i18n.t('all')}
</label>
<label
- className={`btn btn-sm btn-secondary pointer btn-outline-light
+ className={`btn btn-outline-secondary pointer
${this.state.messageType == MessageType.Replies && 'active'}
`}
>
{i18n.t('replies')}
</label>
<label
- className={`btn btn-sm btn-secondary pointer btn-outline-light
+ className={`btn btn-outline-secondary pointer
${this.state.messageType == MessageType.Mentions && 'active'}
`}
>
{i18n.t('mentions')}
</label>
<label
- className={`btn btn-sm btn-secondary pointer btn-outline-light
+ className={`btn btn-outline-secondary pointer
${this.state.messageType == MessageType.Messages && 'active'}
`}
>
);
}
- all() {
- let combined: Array<ReplyType> = [];
-
- combined.push(...this.state.replies);
- combined.push(...this.state.mentions);
- combined.push(...this.state.messages);
-
- // Sort it
- combined.sort((a, b) => b.published.localeCompare(a.published));
+ combined(): Array<ReplyType> {
+ return [
+ ...this.state.replies,
+ ...this.state.mentions,
+ ...this.state.messages,
+ ].sort((a, b) => b.published.localeCompare(a.published));
+ }
+ all() {
return (
<div>
- {combined.map(i =>
+ {this.combined().map(i =>
isCommentType(i) ? (
<CommentNodes
+ key={i.id}
nodes={[{ comment: i }]}
noIndent
markable
showCommunity
showContext
- enableDownvotes={this.state.enableDownvotes}
+ enableDownvotes={this.state.site.enable_downvotes}
/>
) : (
- <PrivateMessage privateMessage={i} />
+ <PrivateMessage key={i.id} privateMessage={i} />
)
)}
</div>
markable
showCommunity
showContext
- enableDownvotes={this.state.enableDownvotes}
+ enableDownvotes={this.state.site.enable_downvotes}
/>
</div>
);
<div>
{this.state.mentions.map(mention => (
<CommentNodes
+ key={mention.id}
nodes={[{ comment: mention }]}
noIndent
markable
showCommunity
showContext
- enableDownvotes={this.state.enableDownvotes}
+ enableDownvotes={this.state.site.enable_downvotes}
/>
))}
</div>
return (
<div>
{this.state.messages.map(message => (
- <PrivateMessage privateMessage={message} />
+ <PrivateMessage key={message.id} privateMessage={message} />
))}
</div>
);
<div class="mt-2">
{this.state.page > 1 && (
<button
- class="btn btn-sm btn-secondary mr-1"
+ class="btn btn-secondary mr-1"
onClick={linkEvent(this, this.prevPage)}
>
{i18n.t('prev')}
)}
{this.unreadCount() > 0 && (
<button
- class="btn btn-sm btn-secondary"
+ class="btn btn-secondary"
onClick={linkEvent(this, this.nextPage)}
>
{i18n.t('next')}
refetch() {
let repliesForm: GetRepliesForm = {
- sort: SortType[this.state.sort],
+ sort: this.state.sort,
unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
page: this.state.page,
limit: fetchLimit,
WebSocketService.Instance.getReplies(repliesForm);
let userMentionsForm: GetUserMentionsForm = {
- sort: SortType[this.state.sort],
+ sort: this.state.sort,
unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
page: this.state.page,
limit: fetchLimit,
let found: PrivateMessageI = this.state.messages.find(
m => m.id === data.message.id
);
- found.content = data.message.content;
- found.updated = data.message.updated;
- found.deleted = data.message.deleted;
- // If youre in the unread view, just remove it from the list
- if (this.state.unreadOrAll == UnreadOrAll.Unread && data.message.read) {
- this.state.messages = this.state.messages.filter(
- r => r.id !== data.message.id
- );
- } else {
- let found = this.state.messages.find(c => c.id == data.message.id);
- found.read = data.message.read;
+ if (found) {
+ found.content = data.message.content;
+ found.updated = data.message.updated;
+ }
+ this.setState(this.state);
+ } else if (res.op == UserOperation.DeletePrivateMessage) {
+ let data = res.data as PrivateMessageResponse;
+ let found: PrivateMessageI = this.state.messages.find(
+ m => m.id === data.message.id
+ );
+ if (found) {
+ found.deleted = data.message.deleted;
+ found.updated = data.message.updated;
+ }
+ this.setState(this.state);
+ } else if (res.op == UserOperation.MarkPrivateMessageAsRead) {
+ let data = res.data as PrivateMessageResponse;
+ let found: PrivateMessageI = this.state.messages.find(
+ m => m.id === data.message.id
+ );
+
+ if (found) {
+ found.updated = data.message.updated;
+
+ // If youre in the unread view, just remove it from the list
+ if (this.state.unreadOrAll == UnreadOrAll.Unread && data.message.read) {
+ this.state.messages = this.state.messages.filter(
+ r => r.id !== data.message.id
+ );
+ } else {
+ let found = this.state.messages.find(c => c.id == data.message.id);
+ found.read = data.message.read;
+ }
}
this.sendUnreadCount();
- window.scrollTo(0, 0);
this.setState(this.state);
- setupTippy();
} else if (res.op == UserOperation.MarkAllAsRead) {
// Moved to be instant
- } else if (res.op == UserOperation.EditComment) {
+ } else if (
+ res.op == UserOperation.EditComment ||
+ res.op == UserOperation.DeleteComment ||
+ res.op == UserOperation.RemoveComment
+ ) {
let data = res.data as CommentResponse;
editCommentRes(data, this.state.replies);
+ this.setState(this.state);
+ } else if (res.op == UserOperation.MarkCommentAsRead) {
+ let data = res.data as CommentResponse;
// If youre in the unread view, just remove it from the list
if (this.state.unreadOrAll == UnreadOrAll.Unread && data.comment.read) {
this.sendUnreadCount();
this.setState(this.state);
setupTippy();
- } else if (res.op == UserOperation.EditUserMention) {
+ } else if (res.op == UserOperation.MarkUserMentionAsRead) {
let data = res.data as UserMentionResponse;
let found = this.state.mentions.find(c => c.id == data.mention.id);
} else if (data.comment.creator_id == UserService.Instance.user.id) {
toast(i18n.t('reply_sent'));
}
- this.setState(this.state);
} else if (res.op == UserOperation.CreatePrivateMessage) {
let data = res.data as PrivateMessageResponse;
if (data.message.recipient_id == UserService.Instance.user.id) {
this.setState(this.state);
} else if (res.op == UserOperation.GetSite) {
let data = res.data as GetSiteResponse;
- this.state.enableDownvotes = data.site.enable_downvotes;
+ this.state.site = data.site;
this.setState(this.state);
- document.title = `/u/${UserService.Instance.user.username} ${i18n.t(
- 'inbox'
- )} - ${data.site.name}`;
}
}
sendUnreadCount() {
- UserService.Instance.user.unreadCount = this.unreadCount();
- UserService.Instance.sub.next({
- user: UserService.Instance.user,
- });
+ UserService.Instance.unreadCountSub.next(this.unreadCount());
}
unreadCount(): number {
this.state.replies.filter(r => !r.read).length +
this.state.mentions.filter(r => !r.read).length +
this.state.messages.filter(
- r => !r.read && r.creator_id !== UserService.Instance.user.id
+ r =>
+ UserService.Instance.user &&
+ !r.read &&
+ r.creator_id !== UserService.Instance.user.id
).length
);
}
--- /dev/null
+import { Component } from 'inferno';
+import { Helmet } from 'inferno-helmet';
+import { Subscription } from 'rxjs';
+import { retryWhen, delay, take } from 'rxjs/operators';
+import {
+ UserOperation,
+ WebSocketJsonResponse,
+ GetSiteResponse,
+} from 'lemmy-js-client';
+import { WebSocketService } from '../services';
+import { wsJsonToRes, toast } from '../utils';
+import { i18n } from '../i18next';
+
+interface InstancesState {
+ loading: boolean;
+ siteRes: GetSiteResponse;
+}
+
+export class Instances extends Component<any, InstancesState> {
+ private subscription: Subscription;
+ private emptyState: InstancesState = {
+ loading: true,
+ siteRes: undefined,
+ };
+
+ constructor(props: any, context: any) {
+ super(props, context);
+ this.state = this.emptyState;
+ this.subscription = WebSocketService.Instance.subject
+ .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
+ .subscribe(
+ msg => this.parseMessage(msg),
+ err => console.error(err),
+ () => console.log('complete')
+ );
+
+ WebSocketService.Instance.getSite();
+ }
+
+ componentWillUnmount() {
+ this.subscription.unsubscribe();
+ }
+
+ get documentTitle(): string {
+ if (this.state.siteRes) {
+ return `${i18n.t('instances')} - ${this.state.siteRes.site.name}`;
+ } else {
+ return 'Lemmy';
+ }
+ }
+
+ render() {
+ return (
+ <div class="container">
+ <Helmet title={this.documentTitle} />
+ {this.state.loading ? (
+ <h5 class="">
+ <svg class="icon icon-spinner spin">
+ <use xlinkHref="#icon-spinner"></use>
+ </svg>
+ </h5>
+ ) : (
+ <div>
+ <h5>{i18n.t('linked_instances')}</h5>
+ {this.state.siteRes &&
+ this.state.siteRes.federated_instances.length ? (
+ <ul>
+ {this.state.siteRes.federated_instances.map(i => (
+ <li>
+ <a href={`https://${i}`} target="_blank" rel="noopener">
+ {i}
+ </a>
+ </li>
+ ))}
+ </ul>
+ ) : (
+ <div>{i18n.t('none_found')}</div>
+ )}
+ </div>
+ )}
+ </div>
+ );
+ }
+
+ parseMessage(msg: WebSocketJsonResponse) {
+ console.log(msg);
+ let res = wsJsonToRes(msg);
+ if (msg.error) {
+ toast(i18n.t(msg.error), 'danger');
+ return;
+ } else if (res.op == UserOperation.GetSite) {
+ let data = res.data as GetSiteResponse;
+ this.state.siteRes = data;
+ this.state.loading = false;
+ this.setState(this.state);
+ }
+ }
+}
import { Component, linkEvent } from 'inferno';
-import { ListingType } from '../interfaces';
+import { ListingType } from 'lemmy-js-client';
import { UserService } from '../services';
-
+import { randomStr } from '../utils';
import { i18n } from '../i18next';
interface ListingTypeSelectProps {
ListingTypeSelectProps,
ListingTypeSelectState
> {
+ private id = `listing-type-input-${randomStr()}`;
+
private emptyState: ListingTypeSelectState = {
type_: this.props.type_,
};
render() {
return (
- <div class="btn-group btn-group-toggle">
+ <div class="btn-group btn-group-toggle flex-wrap mb-2">
<label
- className={`btn btn-sm btn-secondary
+ className={`btn btn-outline-secondary
${this.state.type_ == ListingType.Subscribed && 'active'}
${UserService.Instance.user == undefined ? 'disabled' : 'pointer'}
`}
>
<input
+ id={`${this.id}-subscribed`}
type="radio"
value={ListingType.Subscribed}
checked={this.state.type_ == ListingType.Subscribed}
{i18n.t('subscribed')}
</label>
<label
- className={`pointer btn btn-sm btn-secondary ${
+ className={`pointer btn btn-outline-secondary ${
this.state.type_ == ListingType.All && 'active'
}`}
>
<input
+ id={`${this.id}-all`}
type="radio"
value={ListingType.All}
checked={this.state.type_ == ListingType.All}
}
handleTypeChange(i: ListingTypeSelect, event: any) {
- i.props.onChange(Number(event.target.value));
+ i.props.onChange(event.target.value);
}
}
import { Component, linkEvent } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import {
UserOperation,
PasswordResetForm,
GetSiteResponse,
+ GetCaptchaResponse,
WebSocketJsonResponse,
-} from '../interfaces';
+ Site,
+} from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services';
import { wsJsonToRes, validEmail, toast } from '../utils';
import { i18n } from '../i18next';
registerForm: RegisterForm;
loginLoading: boolean;
registerLoading: boolean;
- enable_nsfw: boolean;
- mathQuestion: {
- a: number;
- b: number;
- answer: number;
- };
+ captcha: GetCaptchaResponse;
+ captchaPlaying: boolean;
+ site: Site;
}
export class Login extends Component<any, State> {
password_verify: undefined,
admin: false,
show_nsfw: false,
+ captcha_uuid: undefined,
+ captcha_answer: undefined,
},
loginLoading: false,
registerLoading: false,
- enable_nsfw: undefined,
- mathQuestion: {
- a: Math.floor(Math.random() * 10) + 1,
- b: Math.floor(Math.random() * 10) + 1,
- answer: undefined,
+ captcha: undefined,
+ captchaPlaying: false,
+ site: {
+ id: undefined,
+ name: undefined,
+ creator_id: undefined,
+ published: undefined,
+ creator_name: undefined,
+ number_of_users: undefined,
+ number_of_posts: undefined,
+ number_of_comments: undefined,
+ number_of_communities: undefined,
+ enable_downvotes: undefined,
+ open_registration: undefined,
+ enable_nsfw: undefined,
},
};
);
WebSocketService.Instance.getSite();
+ WebSocketService.Instance.getCaptcha();
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
+ get documentTitle(): string {
+ if (this.state.site.name) {
+ return `${i18n.t('login')} - ${this.state.site.name}`;
+ } else {
+ return 'Lemmy';
+ }
+ }
+
render() {
return (
<div class="container">
+ <Helmet title={this.documentTitle} />
<div class="row">
<div class="col-12 col-lg-6 mb-4">{this.loginForm()}</div>
<div class="col-12 col-lg-6">{this.registerForm()}</div>
</div>
);
}
+
registerForm() {
return (
<form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
/>
</div>
</div>
- <div class="form-group row">
- <label class="col-sm-10 col-form-label" htmlFor="register-math">
- {i18n.t('what_is')}{' '}
- {`${this.state.mathQuestion.a} + ${this.state.mathQuestion.b}?`}
- </label>
- <div class="col-sm-2">
- <input
- type="number"
- id="register-math"
- class="form-control"
- value={this.state.mathQuestion.answer}
- onInput={linkEvent(this, this.handleMathAnswerChange)}
- required
- />
+ {this.state.captcha && (
+ <div class="form-group row">
+ <label class="col-sm-2" htmlFor="register-captcha">
+ <span class="mr-2">{i18n.t('enter_code')}</span>
+ <button
+ type="button"
+ class="btn btn-secondary"
+ onClick={linkEvent(this, this.handleRegenCaptcha)}
+ >
+ <svg class="icon icon-refresh-cw">
+ <use xlinkHref="#icon-refresh-cw"></use>
+ </svg>
+ </button>
+ </label>
+ {this.showCaptcha()}
+ <div class="col-sm-6">
+ <input
+ type="text"
+ class="form-control"
+ id="register-captcha"
+ value={this.state.registerForm.captcha_answer}
+ onInput={linkEvent(
+ this,
+ this.handleRegisterCaptchaAnswerChange
+ )}
+ required
+ />
+ </div>
</div>
- </div>
- {this.state.enable_nsfw && (
+ )}
+ {this.state.site.enable_nsfw && (
<div class="form-group row">
<div class="col-sm-10">
<div class="form-check">
)}
<div class="form-group row">
<div class="col-sm-10">
- <button
- type="submit"
- class="btn btn-secondary"
- disabled={this.mathCheck}
- >
+ <button type="submit" class="btn btn-secondary">
{this.state.registerLoading ? (
<svg class="icon icon-spinner spin">
<use xlinkHref="#icon-spinner"></use>
);
}
+ showCaptcha() {
+ return (
+ <div class="col-sm-4">
+ {this.state.captcha.ok && (
+ <>
+ <img
+ class="rounded-top img-fluid"
+ src={this.captchaPngSrc()}
+ style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;"
+ />
+ {this.state.captcha.ok.wav && (
+ <button
+ class="rounded-bottom btn btn-sm btn-secondary btn-block"
+ style="border-top-right-radius: 0; border-top-left-radius: 0;"
+ title={i18n.t('play_captcha_audio')}
+ onClick={linkEvent(this, this.handleCaptchaPlay)}
+ type="button"
+ disabled={this.state.captchaPlaying}
+ >
+ <svg class="icon icon-play">
+ <use xlinkHref="#icon-play"></use>
+ </svg>
+ </button>
+ )}
+ </>
+ )}
+ </div>
+ );
+ }
+
handleLoginSubmit(i: Login, event: any) {
event.preventDefault();
i.state.loginLoading = true;
event.preventDefault();
i.state.registerLoading = true;
i.setState(i.state);
-
- if (!i.mathCheck) {
- WebSocketService.Instance.register(i.state.registerForm);
- }
+ WebSocketService.Instance.register(i.state.registerForm);
}
handleRegisterUsernameChange(i: Login, event: any) {
i.setState(i.state);
}
- handleMathAnswerChange(i: Login, event: any) {
- i.state.mathQuestion.answer = event.target.value;
+ handleRegisterCaptchaAnswerChange(i: Login, event: any) {
+ i.state.registerForm.captcha_answer = event.target.value;
i.setState(i.state);
}
+ handleRegenCaptcha(_i: Login, _event: any) {
+ event.preventDefault();
+ WebSocketService.Instance.getCaptcha();
+ }
+
handlePasswordReset(i: Login) {
event.preventDefault();
let resetForm: PasswordResetForm = {
WebSocketService.Instance.passwordReset(resetForm);
}
- get mathCheck(): boolean {
- return (
- this.state.mathQuestion.answer !=
- this.state.mathQuestion.a + this.state.mathQuestion.b
- );
+ handleCaptchaPlay(i: Login) {
+ event.preventDefault();
+ let snd = new Audio('data:audio/wav;base64,' + i.state.captcha.ok.wav);
+ snd.play();
+ i.state.captchaPlaying = true;
+ i.setState(i.state);
+ snd.addEventListener('ended', () => {
+ snd.currentTime = 0;
+ i.state.captchaPlaying = false;
+ i.setState(this.state);
+ });
+ }
+
+ captchaPngSrc() {
+ return `data:image/png;base64,${this.state.captcha.ok.png}`;
}
parseMessage(msg: WebSocketJsonResponse) {
if (msg.error) {
toast(i18n.t(msg.error), 'danger');
this.state = this.emptyState;
+ this.state.registerForm.captcha_answer = undefined;
+ // Refetch another captcha
+ WebSocketService.Instance.getCaptcha();
this.setState(this.state);
return;
} else {
UserService.Instance.login(data);
WebSocketService.Instance.userJoin();
this.props.history.push('/communities');
+ } else if (res.op == UserOperation.GetCaptcha) {
+ let data = res.data as GetCaptchaResponse;
+ if (data.ok) {
+ this.state.captcha = data;
+ this.state.registerForm.captcha_uuid = data.ok.uuid;
+ this.setState(this.state);
+ }
} else if (res.op == UserOperation.PasswordReset) {
toast(i18n.t('reset_password_mail_sent'));
} else if (res.op == UserOperation.GetSite) {
let data = res.data as GetSiteResponse;
- this.state.enable_nsfw = data.site.enable_nsfw;
+ this.state.site = data.site;
this.setState(this.state);
- document.title = `${i18n.t('login')} - ${data.site.name}`;
}
}
}
import { Component, linkEvent } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Link } from 'inferno-router';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
SortType,
GetSiteResponse,
ListingType,
- DataType,
SiteResponse,
GetPostsResponse,
PostResponse,
AddAdminResponse,
BanUserResponse,
WebSocketJsonResponse,
-} from '../interfaces';
+} from 'lemmy-js-client';
+import { DataType } from '../interfaces';
import { WebSocketService, UserService } from '../services';
import { PostListings } from './post-listings';
import { CommentNodes } from './comment-nodes';
import { SiteForm } from './site-form';
import { UserListing } from './user-listing';
import { CommunityLink } from './community-link';
+import { BannerIconHeader } from './banner-icon-header';
import {
wsJsonToRes,
repoUrl,
editPostFindRes,
commentsToFlatNodes,
setupTippy,
+ favIconUrl,
+ notifyPost,
} from '../utils';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
}
interface UrlParams {
- listingType?: string;
+ listingType?: ListingType;
dataType?: string;
- sort?: string;
+ sort?: SortType;
page?: number;
}
enable_downvotes: null,
open_registration: null,
enable_nsfw: null,
+ icon: null,
+ banner: null,
+ creator_preferred_username: null,
},
admins: [],
banned: [],
online: null,
+ version: null,
+ federated_instances: null,
},
showEditSite: false,
loading: true,
}
let listCommunitiesForm: ListCommunitiesForm = {
- sort: SortType[SortType.Hot],
+ sort: SortType.Hot,
limit: 6,
};
}
}
+ get documentTitle(): string {
+ if (this.state.siteRes.site.name) {
+ return `${this.state.siteRes.site.name}`;
+ } else {
+ return 'Lemmy';
+ }
+ }
+
+ get favIcon(): string {
+ return this.state.siteRes.site.icon
+ ? this.state.siteRes.site.icon
+ : favIconUrl;
+ }
+
render() {
return (
<div class="container">
+ <Helmet title={this.documentTitle}>
+ <link
+ id="favicon"
+ rel="icon"
+ type="image/x-icon"
+ href={this.favIcon}
+ />
+ </Helmet>
<div class="row">
<main role="main" class="col-12 col-md-8">
{this.posts()}
</main>
- <aside class="col-12 col-md-4">{this.my_sidebar()}</aside>
+ <aside class="col-12 col-md-4">{this.mySidebar()}</aside>
</div>
</div>
);
}
- my_sidebar() {
+ mySidebar() {
return (
<div>
{!this.state.loading && (
<div>
- <div class="card border-secondary mb-3">
+ <div class="card bg-transparent border-secondary mb-3">
+ <div class="card-header bg-transparent border-secondary">
+ <div class="mb-2">
+ {this.siteName()}
+ {this.adminButtons()}
+ </div>
+ <BannerIconHeader banner={this.state.siteRes.site.banner} />
+ </div>
<div class="card-body">
{this.trendingCommunities()}
- {UserService.Instance.user &&
- this.state.subscribedCommunities.length > 0 && (
- <div>
- <h5>
- <T i18nKey="subscribed_to_communities">
- #
- <Link class="text-body" to="/communities">
- #
- </Link>
- </T>
- </h5>
- <ul class="list-inline">
- {this.state.subscribedCommunities.map(community => (
- <li class="list-inline-item">
- <CommunityLink
- community={{
- name: community.community_name,
- id: community.community_id,
- local: community.community_local,
- actor_id: community.community_actor_id,
- }}
- />
- </li>
- ))}
- </ul>
- </div>
- )}
- <Link
- class="btn btn-sm btn-secondary btn-block"
- to="/create_community"
- >
- {i18n.t('create_a_community')}
- </Link>
+ {this.createCommunityButton()}
+ {/*
+ {this.subscribedCommunities()}
+ */}
</div>
</div>
- {this.sidebar()}
- {this.landing()}
+
+ <div class="card bg-transparent border-secondary mb-3">
+ <div class="card-body">{this.sidebar()}</div>
+ </div>
+
+ <div class="card bg-transparent border-secondary">
+ <div class="card-body">{this.landing()}</div>
+ </div>
</div>
)}
</div>
);
}
+ createCommunityButton() {
+ return (
+ <Link class="btn btn-secondary btn-block" to="/create_community">
+ {i18n.t('create_a_community')}
+ </Link>
+ );
+ }
+
trendingCommunities() {
return (
<div>
);
}
+ subscribedCommunities() {
+ return (
+ UserService.Instance.user &&
+ this.state.subscribedCommunities.length > 0 && (
+ <div>
+ <h5>
+ <T i18nKey="subscribed_to_communities">
+ #
+ <Link class="text-body" to="/communities">
+ #
+ </Link>
+ </T>
+ </h5>
+ <ul class="list-inline">
+ {this.state.subscribedCommunities.map(community => (
+ <li class="list-inline-item">
+ <CommunityLink
+ community={{
+ name: community.community_name,
+ id: community.community_id,
+ local: community.community_local,
+ actor_id: community.community_actor_id,
+ icon: community.community_icon,
+ }}
+ />
+ </li>
+ ))}
+ </ul>
+ </div>
+ )
+ );
+ }
+
sidebar() {
return (
<div>
}
updateUrl(paramUpdates: UrlParams) {
- const listingTypeStr =
- paramUpdates.listingType ||
- ListingType[this.state.listingType].toLowerCase();
- const dataTypeStr =
- paramUpdates.dataType || DataType[this.state.dataType].toLowerCase();
- const sortStr =
- paramUpdates.sort || SortType[this.state.sort].toLowerCase();
+ const listingTypeStr = paramUpdates.listingType || this.state.listingType;
+ const dataTypeStr = paramUpdates.dataType || DataType[this.state.dataType];
+ const sortStr = paramUpdates.sort || this.state.sort;
const page = paramUpdates.page || this.state.page;
this.props.history.push(
`/home/data_type/${dataTypeStr}/listing_type/${listingTypeStr}/sort/${sortStr}/page/${page}`
siteInfo() {
return (
<div>
- <div class="card border-secondary mb-3">
- <div class="card-body">
- <h5 class="mb-0">{`${this.state.siteRes.site.name}`}</h5>
- {this.canAdmin && (
- <ul class="list-inline mb-1 text-muted font-weight-bold">
- <li className="list-inline-item-action">
- <span
- class="pointer"
- onClick={linkEvent(this, this.handleEditClick)}
- data-tippy-content={i18n.t('edit')}
- >
- <svg class="icon icon-inline">
- <use xlinkHref="#icon-edit"></use>
- </svg>
- </span>
- </li>
- </ul>
- )}
- <ul class="my-2 list-inline">
- {/*
- <li className="list-inline-item badge badge-secondary">
- {i18n.t('number_online', { count: this.state.siteRes.online })}
- </li>
- */}
- <li className="list-inline-item badge badge-secondary">
- {i18n.t('number_of_users', {
- count: this.state.siteRes.site.number_of_users,
- })}
- </li>
- <li className="list-inline-item badge badge-secondary">
- {i18n.t('number_of_communities', {
- count: this.state.siteRes.site.number_of_communities,
- })}
- </li>
- <li className="list-inline-item badge badge-secondary">
- {i18n.t('number_of_posts', {
- count: this.state.siteRes.site.number_of_posts,
- })}
- </li>
- <li className="list-inline-item badge badge-secondary">
- {i18n.t('number_of_comments', {
- count: this.state.siteRes.site.number_of_comments,
- })}
- </li>
- <li className="list-inline-item">
- <Link className="badge badge-secondary" to="/modlog">
- {i18n.t('modlog')}
- </Link>
- </li>
- </ul>
- <ul class="mt-1 list-inline small mb-0">
- <li class="list-inline-item">{i18n.t('admins')}:</li>
- {this.state.siteRes.admins.map(admin => (
- <li class="list-inline-item">
- <UserListing
- user={{
- name: admin.name,
- avatar: admin.avatar,
- local: admin.local,
- actor_id: admin.actor_id,
- id: admin.id,
- }}
- />
- </li>
- ))}
- </ul>
- </div>
- </div>
- {this.state.siteRes.site.description && (
- <div class="card border-secondary mb-3">
- <div class="card-body">
- <div
- className="md-div"
- dangerouslySetInnerHTML={mdToHtml(
- this.state.siteRes.site.description
- )}
- />
- </div>
- </div>
- )}
+ {this.state.siteRes.site.description && this.siteDescription()}
+ {this.badges()}
+ {this.admins()}
</div>
);
}
+ siteName() {
+ return <h5 class="mb-0">{`${this.state.siteRes.site.name}`}</h5>;
+ }
+
+ admins() {
+ return (
+ <ul class="mt-1 list-inline small mb-0">
+ <li class="list-inline-item">{i18n.t('admins')}:</li>
+ {this.state.siteRes.admins.map(admin => (
+ <li class="list-inline-item">
+ <UserListing
+ user={{
+ name: admin.name,
+ preferred_username: admin.preferred_username,
+ avatar: admin.avatar,
+ local: admin.local,
+ actor_id: admin.actor_id,
+ id: admin.id,
+ }}
+ />
+ </li>
+ ))}
+ </ul>
+ );
+ }
+
+ badges() {
+ return (
+ <ul class="my-2 list-inline">
+ <li className="list-inline-item badge badge-light">
+ {i18n.t('number_online', { count: this.state.siteRes.online })}
+ </li>
+ <li className="list-inline-item badge badge-light">
+ {i18n.t('number_of_users', {
+ count: this.state.siteRes.site.number_of_users,
+ })}
+ </li>
+ <li className="list-inline-item badge badge-light">
+ {i18n.t('number_of_communities', {
+ count: this.state.siteRes.site.number_of_communities,
+ })}
+ </li>
+ <li className="list-inline-item badge badge-light">
+ {i18n.t('number_of_posts', {
+ count: this.state.siteRes.site.number_of_posts,
+ })}
+ </li>
+ <li className="list-inline-item badge badge-light">
+ {i18n.t('number_of_comments', {
+ count: this.state.siteRes.site.number_of_comments,
+ })}
+ </li>
+ <li className="list-inline-item">
+ <Link className="badge badge-light" to="/modlog">
+ {i18n.t('modlog')}
+ </Link>
+ </li>
+ </ul>
+ );
+ }
+
+ adminButtons() {
+ return (
+ this.canAdmin && (
+ <ul class="list-inline mb-1 text-muted font-weight-bold">
+ <li className="list-inline-item-action">
+ <span
+ class="pointer"
+ onClick={linkEvent(this, this.handleEditClick)}
+ data-tippy-content={i18n.t('edit')}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-edit"></use>
+ </svg>
+ </span>
+ </li>
+ </ul>
+ )
+ );
+ }
+
+ siteDescription() {
+ return (
+ <div
+ className="md-div"
+ dangerouslySetInnerHTML={mdToHtml(this.state.siteRes.site.description)}
+ />
+ );
+ }
+
landing() {
return (
- <div class="card border-secondary">
- <div class="card-body">
- <h5>
- {i18n.t('powered_by')}
- <svg class="icon mx-2">
- <use xlinkHref="#icon-mouse">#</use>
- </svg>
- <a href={repoUrl}>
- Lemmy<sup>beta</sup>
+ <>
+ <h5>
+ {i18n.t('powered_by')}
+ <svg class="icon mx-2">
+ <use xlinkHref="#icon-mouse">#</use>
+ </svg>
+ <a href={repoUrl}>
+ Lemmy<sup>beta</sup>
+ </a>
+ </h5>
+ <p class="mb-0">
+ <T i18nKey="landing_0">
+ #
+ <a href="https://en.wikipedia.org/wiki/Social_network_aggregation">
+ #
</a>
- </h5>
- <p class="mb-0">
- <T i18nKey="landing_0">
+ <a href="https://en.wikipedia.org/wiki/Fediverse">#</a>
+ <br class="big"></br>
+ <code>#</code>
+ <br></br>
+ <b>#</b>
+ <br class="big"></br>
+ <a href={repoUrl}>#</a>
+ <br class="big"></br>
+ <a href="https://www.rust-lang.org">#</a>
+ <a href="https://actix.rs/">#</a>
+ <a href="https://infernojs.org">#</a>
+ <a href="https://www.typescriptlang.org/">#</a>
+ <br class="big"></br>
+ <a href="https://github.com/LemmyNet/lemmy/graphs/contributors?type=a">
#
- <a href="https://en.wikipedia.org/wiki/Social_network_aggregation">
- #
- </a>
- <a href="https://en.wikipedia.org/wiki/Fediverse">#</a>
- <br class="big"></br>
- <code>#</code>
- <br></br>
- <b>#</b>
- <br class="big"></br>
- <a href={repoUrl}>#</a>
- <br class="big"></br>
- <a href="https://www.rust-lang.org">#</a>
- <a href="https://actix.rs/">#</a>
- <a href="https://infernojs.org">#</a>
- <a href="https://www.typescriptlang.org/">#</a>
- <br class="big"></br>
- <a href="https://github.com/LemmyNet/lemmy/graphs/contributors?type=a">
- #
- </a>
- </T>
- </p>
- </div>
- </div>
+ </a>
+ </T>
+ </p>
+ </>
);
}
posts() {
return (
<div class="main-content-wrapper">
- {this.selects()}
{this.state.loading ? (
<h5>
<svg class="icon icon-spinner spin">
</h5>
) : (
<div>
+ {this.selects()}
{this.listings()}
{this.paginator()}
</div>
</span>
{this.state.listingType == ListingType.All && (
<a
- href={`/feeds/all.xml?sort=${SortType[this.state.sort]}`}
+ href={`/feeds/all.xml?sort=${this.state.sort}`}
target="_blank"
rel="noopener"
title="RSS"
{UserService.Instance.user &&
this.state.listingType == ListingType.Subscribed && (
<a
- href={`/feeds/front/${UserService.Instance.auth}.xml?sort=${
- SortType[this.state.sort]
- }`}
+ href={`/feeds/front/${UserService.Instance.auth}.xml?sort=${this.state.sort}`}
target="_blank"
title="RSS"
rel="noopener"
<div class="my-2">
{this.state.page > 1 && (
<button
- class="btn btn-sm btn-secondary mr-1"
+ class="btn btn-secondary mr-1"
onClick={linkEvent(this, this.prevPage)}
>
{i18n.t('prev')}
)}
{this.state.posts.length > 0 && (
<button
- class="btn btn-sm btn-secondary"
+ class="btn btn-secondary"
onClick={linkEvent(this, this.nextPage)}
>
{i18n.t('next')}
}
handleSortChange(val: SortType) {
- this.updateUrl({ sort: SortType[val].toLowerCase(), page: 1 });
+ this.updateUrl({ sort: val, page: 1 });
window.scrollTo(0, 0);
}
handleListingTypeChange(val: ListingType) {
- this.updateUrl({ listingType: ListingType[val].toLowerCase(), page: 1 });
+ this.updateUrl({ listingType: val, page: 1 });
window.scrollTo(0, 0);
}
handleDataTypeChange(val: DataType) {
- this.updateUrl({ dataType: DataType[val].toLowerCase(), page: 1 });
+ this.updateUrl({ dataType: DataType[val], page: 1 });
window.scrollTo(0, 0);
}
let getPostsForm: GetPostsForm = {
page: this.state.page,
limit: fetchLimit,
- sort: SortType[this.state.sort],
- type_: ListingType[this.state.listingType],
+ sort: this.state.sort,
+ type_: this.state.listingType,
};
WebSocketService.Instance.getPosts(getPostsForm);
} else {
let getCommentsForm: GetCommentsForm = {
page: this.state.page,
limit: fetchLimit,
- sort: SortType[this.state.sort],
- type_: ListingType[this.state.listingType],
+ sort: this.state.sort,
+ type_: this.state.listingType,
};
WebSocketService.Instance.getComments(getCommentsForm);
}
this.state.siteRes.banned = data.banned;
this.state.siteRes.online = data.online;
this.setState(this.state);
- document.title = `${this.state.siteRes.site.name}`;
} else if (res.op == UserOperation.EditSite) {
let data = res.data as SiteResponse;
this.state.siteRes.site = data.site;
.includes(data.post.community_id)
) {
this.state.posts.unshift(data.post);
+ notifyPost(data.post, this.context.router);
}
} else {
// NSFW posts
UserService.Instance.user.show_nsfw)
) {
this.state.posts.unshift(data.post);
+ notifyPost(data.post, this.context.router);
}
}
this.setState(this.state);
this.state.comments = data.comments;
this.state.loading = false;
this.setState(this.state);
- } else if (res.op == UserOperation.EditComment) {
+ } else if (
+ res.op == UserOperation.EditComment ||
+ res.op == UserOperation.DeleteComment ||
+ res.op == UserOperation.RemoveComment
+ ) {
let data = res.data as CommentResponse;
editCommentRes(data, this.state.comments);
this.setState(this.state);
--- /dev/null
+import { Component, linkEvent } from 'inferno';
+import { Prompt } from 'inferno-router';
+import {
+ mdToHtml,
+ randomStr,
+ markdownHelpUrl,
+ toast,
+ setupTribute,
+ pictrsDeleteToast,
+ setupTippy,
+} from '../utils';
+import { UserService } from '../services';
+import autosize from 'autosize';
+import Tribute from 'tributejs/src/Tribute.js';
+import { i18n } from '../i18next';
+
+interface MarkdownTextAreaProps {
+ initialContent: string;
+ finished?: boolean;
+ buttonTitle?: string;
+ replyType?: boolean;
+ focus?: boolean;
+ disabled?: boolean;
+ maxLength?: number;
+ onSubmit?(msg: { val: string; formId: string }): any;
+ onContentChange?(val: string): any;
+ onReplyCancel?(): any;
+ hideNavigationWarnings?: boolean;
+}
+
+interface MarkdownTextAreaState {
+ content: string;
+ previewMode: boolean;
+ loading: boolean;
+ imageLoading: boolean;
+}
+
+export class MarkdownTextArea extends Component<
+ MarkdownTextAreaProps,
+ MarkdownTextAreaState
+> {
+ private id = `comment-textarea-${randomStr()}`;
+ private formId = `comment-form-${randomStr()}`;
+ private tribute: Tribute;
+ private emptyState: MarkdownTextAreaState = {
+ content: this.props.initialContent,
+ previewMode: false,
+ loading: false,
+ imageLoading: false,
+ };
+
+ constructor(props: any, context: any) {
+ super(props, context);
+
+ this.tribute = setupTribute();
+ this.state = this.emptyState;
+ }
+
+ componentDidMount() {
+ let textarea: any = document.getElementById(this.id);
+ if (textarea) {
+ autosize(textarea);
+ this.tribute.attach(textarea);
+ textarea.addEventListener('tribute-replaced', () => {
+ this.state.content = textarea.value;
+ this.setState(this.state);
+ autosize.update(textarea);
+ });
+
+ this.quoteInsert();
+
+ if (this.props.focus) {
+ textarea.focus();
+ }
+
+ // TODO this is slow for some reason
+ setupTippy();
+ }
+ }
+
+ componentDidUpdate() {
+ if (!this.props.hideNavigationWarnings && this.state.content) {
+ window.onbeforeunload = () => true;
+ } else {
+ window.onbeforeunload = undefined;
+ }
+ }
+
+ componentWillReceiveProps(nextProps: MarkdownTextAreaProps) {
+ if (nextProps.finished) {
+ this.state.previewMode = false;
+ this.state.loading = false;
+ this.state.content = '';
+ this.setState(this.state);
+ if (this.props.replyType) {
+ this.props.onReplyCancel();
+ }
+
+ let textarea: any = document.getElementById(this.id);
+ let form: any = document.getElementById(this.formId);
+ form.reset();
+ setTimeout(() => autosize.update(textarea), 10);
+ this.setState(this.state);
+ }
+ }
+
+ componentWillUnmount() {
+ window.onbeforeunload = null;
+ }
+
+ render() {
+ return (
+ <form id={this.formId} onSubmit={linkEvent(this, this.handleSubmit)}>
+ <Prompt
+ when={!this.props.hideNavigationWarnings && this.state.content}
+ message={i18n.t('block_leaving')}
+ />
+ <div class="form-group row">
+ <div className={`col-sm-12`}>
+ <textarea
+ id={this.id}
+ className={`form-control ${this.state.previewMode && 'd-none'}`}
+ value={this.state.content}
+ onInput={linkEvent(this, this.handleContentChange)}
+ onPaste={linkEvent(this, this.handleImageUploadPaste)}
+ required
+ disabled={this.props.disabled}
+ rows={2}
+ maxLength={this.props.maxLength || 10000}
+ />
+ {this.state.previewMode && (
+ <div
+ className="card bg-transparent border-secondary card-body md-div"
+ dangerouslySetInnerHTML={mdToHtml(this.state.content)}
+ />
+ )}
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-sm-12 d-flex flex-wrap">
+ {this.props.buttonTitle && (
+ <button
+ type="submit"
+ class="btn btn-sm btn-secondary mr-2"
+ disabled={this.props.disabled || this.state.loading}
+ >
+ {this.state.loading ? (
+ <svg class="icon icon-spinner spin">
+ <use xlinkHref="#icon-spinner"></use>
+ </svg>
+ ) : (
+ <span>{this.props.buttonTitle}</span>
+ )}
+ </button>
+ )}
+ {this.props.replyType && (
+ <button
+ type="button"
+ class="btn btn-sm btn-secondary mr-2"
+ onClick={linkEvent(this, this.handleReplyCancel)}
+ >
+ {i18n.t('cancel')}
+ </button>
+ )}
+ {this.state.content && (
+ <button
+ className={`btn btn-sm btn-secondary mr-2 ${
+ this.state.previewMode && 'active'
+ }`}
+ onClick={linkEvent(this, this.handlePreviewToggle)}
+ >
+ {i18n.t('preview')}
+ </button>
+ )}
+ {/* A flex expander */}
+ <div class="flex-grow-1"></div>
+ <button
+ class="btn btn-sm text-muted"
+ data-tippy-content={i18n.t('bold')}
+ onClick={linkEvent(this, this.handleInsertBold)}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-bold"></use>
+ </svg>
+ </button>
+ <button
+ class="btn btn-sm text-muted"
+ data-tippy-content={i18n.t('italic')}
+ onClick={linkEvent(this, this.handleInsertItalic)}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-italic"></use>
+ </svg>
+ </button>
+ <button
+ class="btn btn-sm text-muted"
+ data-tippy-content={i18n.t('link')}
+ onClick={linkEvent(this, this.handleInsertLink)}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-link"></use>
+ </svg>
+ </button>
+ <form class="btn btn-sm text-muted font-weight-bold">
+ <label
+ htmlFor={`file-upload-${this.id}`}
+ className={`mb-0 ${UserService.Instance.user && 'pointer'}`}
+ data-tippy-content={i18n.t('upload_image')}
+ >
+ {this.state.imageLoading ? (
+ <svg class="icon icon-spinner spin">
+ <use xlinkHref="#icon-spinner"></use>
+ </svg>
+ ) : (
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-image"></use>
+ </svg>
+ )}
+ </label>
+ <input
+ id={`file-upload-${this.id}`}
+ type="file"
+ accept="image/*,video/*"
+ name="file"
+ class="d-none"
+ disabled={!UserService.Instance.user}
+ onChange={linkEvent(this, this.handleImageUpload)}
+ />
+ </form>
+ <button
+ class="btn btn-sm text-muted"
+ data-tippy-content={i18n.t('header')}
+ onClick={linkEvent(this, this.handleInsertHeader)}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-header"></use>
+ </svg>
+ </button>
+ <button
+ class="btn btn-sm text-muted"
+ data-tippy-content={i18n.t('strikethrough')}
+ onClick={linkEvent(this, this.handleInsertStrikethrough)}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-strikethrough"></use>
+ </svg>
+ </button>
+ <button
+ class="btn btn-sm text-muted"
+ data-tippy-content={i18n.t('quote')}
+ onClick={linkEvent(this, this.handleInsertQuote)}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-format_quote"></use>
+ </svg>
+ </button>
+ <button
+ class="btn btn-sm text-muted"
+ data-tippy-content={i18n.t('list')}
+ onClick={linkEvent(this, this.handleInsertList)}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-list"></use>
+ </svg>
+ </button>
+ <button
+ class="btn btn-sm text-muted"
+ data-tippy-content={i18n.t('code')}
+ onClick={linkEvent(this, this.handleInsertCode)}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-code"></use>
+ </svg>
+ </button>
+ <button
+ class="btn btn-sm text-muted"
+ data-tippy-content={i18n.t('subscript')}
+ onClick={linkEvent(this, this.handleInsertSubscript)}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-subscript"></use>
+ </svg>
+ </button>
+ <button
+ class="btn btn-sm text-muted"
+ data-tippy-content={i18n.t('superscript')}
+ onClick={linkEvent(this, this.handleInsertSuperscript)}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-superscript"></use>
+ </svg>
+ </button>
+ <button
+ class="btn btn-sm text-muted"
+ data-tippy-content={i18n.t('spoiler')}
+ onClick={linkEvent(this, this.handleInsertSpoiler)}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-alert-triangle"></use>
+ </svg>
+ </button>
+ <a
+ href={markdownHelpUrl}
+ target="_blank"
+ class="btn btn-sm text-muted font-weight-bold"
+ title={i18n.t('formatting_help')}
+ rel="noopener"
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-help-circle"></use>
+ </svg>
+ </a>
+ </div>
+ </div>
+ </form>
+ );
+ }
+
+ handleImageUploadPaste(i: MarkdownTextArea, event: any) {
+ let image = event.clipboardData.files[0];
+ if (image) {
+ i.handleImageUpload(i, image);
+ }
+ }
+
+ handleImageUpload(i: MarkdownTextArea, event: any) {
+ let file: any;
+ if (event.target) {
+ event.preventDefault();
+ file = event.target.files[0];
+ } else {
+ file = event;
+ }
+
+ const imageUploadUrl = `/pictrs/image`;
+ const formData = new FormData();
+ formData.append('images[]', file);
+
+ i.state.imageLoading = true;
+ i.setState(i.state);
+
+ fetch(imageUploadUrl, {
+ method: 'POST',
+ body: formData,
+ })
+ .then(res => res.json())
+ .then(res => {
+ console.log('pictrs upload:');
+ console.log(res);
+ if (res.msg == 'ok') {
+ let hash = res.files[0].file;
+ let url = `${window.location.origin}/pictrs/image/${hash}`;
+ let deleteToken = res.files[0].delete_token;
+ let deleteUrl = `${window.location.origin}/pictrs/image/delete/${deleteToken}/${hash}`;
+ let imageMarkdown = `![](${url})`;
+ let content = i.state.content;
+ content = content ? `${content}\n${imageMarkdown}` : imageMarkdown;
+ i.state.content = content;
+ i.state.imageLoading = false;
+ i.setState(i.state);
+ let textarea: any = document.getElementById(i.id);
+ autosize.update(textarea);
+ pictrsDeleteToast(
+ i18n.t('click_to_delete_picture'),
+ i18n.t('picture_deleted'),
+ deleteUrl
+ );
+ } else {
+ i.state.imageLoading = false;
+ i.setState(i.state);
+ toast(JSON.stringify(res), 'danger');
+ }
+ })
+ .catch(error => {
+ i.state.imageLoading = false;
+ i.setState(i.state);
+ toast(error, 'danger');
+ });
+ }
+
+ handleContentChange(i: MarkdownTextArea, event: any) {
+ i.state.content = event.target.value;
+ i.setState(i.state);
+ if (i.props.onContentChange) {
+ i.props.onContentChange(i.state.content);
+ }
+ }
+
+ handlePreviewToggle(i: MarkdownTextArea, event: any) {
+ event.preventDefault();
+ i.state.previewMode = !i.state.previewMode;
+ i.setState(i.state);
+ }
+
+ handleSubmit(i: MarkdownTextArea, event: any) {
+ event.preventDefault();
+ i.state.loading = true;
+ i.setState(i.state);
+ let msg = { val: i.state.content, formId: i.formId };
+ i.props.onSubmit(msg);
+ }
+
+ handleReplyCancel(i: MarkdownTextArea) {
+ i.props.onReplyCancel();
+ }
+
+ handleInsertLink(i: MarkdownTextArea, event: any) {
+ event.preventDefault();
+ if (!i.state.content) {
+ i.state.content = '';
+ }
+ let textarea: any = document.getElementById(i.id);
+ let start: number = textarea.selectionStart;
+ let end: number = textarea.selectionEnd;
+
+ if (start !== end) {
+ let selectedText = i.state.content.substring(start, end);
+ i.state.content = `${i.state.content.substring(
+ 0,
+ start
+ )} [${selectedText}]() ${i.state.content.substring(end)}`;
+ textarea.focus();
+ setTimeout(() => (textarea.selectionEnd = end + 4), 10);
+ } else {
+ i.state.content += '[]()';
+ textarea.focus();
+ setTimeout(() => (textarea.selectionEnd -= 1), 10);
+ }
+ i.setState(i.state);
+ }
+
+ simpleSurround(chars: string) {
+ this.simpleSurroundBeforeAfter(chars, chars);
+ }
+
+ simpleSurroundBeforeAfter(beforeChars: string, afterChars: string) {
+ if (!this.state.content) {
+ this.state.content = '';
+ }
+ let textarea: any = document.getElementById(this.id);
+ let start: number = textarea.selectionStart;
+ let end: number = textarea.selectionEnd;
+
+ if (start !== end) {
+ let selectedText = this.state.content.substring(start, end);
+ this.state.content = `${this.state.content.substring(
+ 0,
+ start - 1
+ )} ${beforeChars}${selectedText}${afterChars} ${this.state.content.substring(
+ end + 1
+ )}`;
+ } else {
+ this.state.content += `${beforeChars}___${afterChars}`;
+ }
+ this.setState(this.state);
+ setTimeout(() => {
+ autosize.update(textarea);
+ }, 10);
+ }
+
+ handleInsertBold(i: MarkdownTextArea, event: any) {
+ event.preventDefault();
+ i.simpleSurround('**');
+ }
+
+ handleInsertItalic(i: MarkdownTextArea, event: any) {
+ event.preventDefault();
+ i.simpleSurround('*');
+ }
+
+ handleInsertCode(i: MarkdownTextArea, event: any) {
+ event.preventDefault();
+ i.simpleSurround('`');
+ }
+
+ handleInsertStrikethrough(i: MarkdownTextArea, event: any) {
+ event.preventDefault();
+ i.simpleSurround('~~');
+ }
+
+ handleInsertList(i: MarkdownTextArea, event: any) {
+ event.preventDefault();
+ i.simpleInsert('-');
+ }
+
+ handleInsertQuote(i: MarkdownTextArea, event: any) {
+ event.preventDefault();
+ i.simpleInsert('>');
+ }
+
+ handleInsertHeader(i: MarkdownTextArea, event: any) {
+ event.preventDefault();
+ i.simpleInsert('#');
+ }
+
+ handleInsertSubscript(i: MarkdownTextArea, event: any) {
+ event.preventDefault();
+ i.simpleSurround('~');
+ }
+
+ handleInsertSuperscript(i: MarkdownTextArea, event: any) {
+ event.preventDefault();
+ i.simpleSurround('^');
+ }
+
+ simpleInsert(chars: string) {
+ if (!this.state.content) {
+ this.state.content = `${chars} `;
+ } else {
+ this.state.content += `\n${chars} `;
+ }
+
+ let textarea: any = document.getElementById(this.id);
+ textarea.focus();
+ setTimeout(() => {
+ autosize.update(textarea);
+ }, 10);
+ this.setState(this.state);
+ }
+
+ handleInsertSpoiler(i: MarkdownTextArea, event: any) {
+ event.preventDefault();
+ let beforeChars = `\n::: spoiler ${i18n.t('spoiler')}\n`;
+ let afterChars = '\n:::\n';
+ i.simpleSurroundBeforeAfter(beforeChars, afterChars);
+ }
+
+ quoteInsert() {
+ let textarea: any = document.getElementById(this.id);
+ let selectedText = window.getSelection().toString();
+ if (selectedText) {
+ let quotedText =
+ selectedText
+ .split('\n')
+ .map(t => `> ${t}`)
+ .join('\n') + '\n\n';
+ this.state.content = quotedText;
+ this.setState(this.state);
+ // Not sure why this needs a delay
+ setTimeout(() => autosize.update(textarea), 10);
+ }
+ }
+}
import { Component, linkEvent } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Link } from 'inferno-router';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
ModAdd,
WebSocketJsonResponse,
GetSiteResponse,
-} from '../interfaces';
+ Site,
+} from 'lemmy-js-client';
import { WebSocketService } from '../services';
import { wsJsonToRes, addTypeInfo, fetchLimit, toast } from '../utils';
import { MomentTime } from './moment-time';
communityId?: number;
communityName?: string;
page: number;
+ site: Site;
loading: boolean;
}
combined: [],
page: 1,
loading: true,
+ site: undefined,
};
constructor(props: any, context: any) {
);
}
+ get documentTitle(): string {
+ if (this.state.site) {
+ return `Modlog - ${this.state.site.name}`;
+ } else {
+ return 'Lemmy';
+ }
+ }
+
render() {
return (
<div class="container">
+ <Helmet title={this.documentTitle} />
{this.state.loading ? (
<h5 class="">
<svg class="icon icon-spinner spin">
<div class="mt-2">
{this.state.page > 1 && (
<button
- class="btn btn-sm btn-secondary mr-1"
+ class="btn btn-secondary mr-1"
onClick={linkEvent(this, this.prevPage)}
>
{i18n.t('prev')}
</button>
)}
<button
- class="btn btn-sm btn-secondary"
+ class="btn btn-secondary"
onClick={linkEvent(this, this.nextPage)}
>
{i18n.t('next')}
this.setCombined(data);
} else if (res.op == UserOperation.GetSite) {
let data = res.data as GetSiteResponse;
- document.title = `Modlog - ${data.site.name}`;
+ this.state.site = data.site;
+ this.setState(this.state);
}
}
}
Comment,
CommentResponse,
PrivateMessage,
- UserView,
PrivateMessageResponse,
WebSocketJsonResponse,
-} from '../interfaces';
+} from 'lemmy-js-client';
import {
wsJsonToRes,
pictrsAvatarThumbnail,
showAvatars,
fetchLimit,
- isCommentType,
toast,
- messageToastify,
- md,
+ setTheme,
+ getLanguage,
+ notifyComment,
+ notifyPrivateMessage,
} from '../utils';
-import { version } from '../version';
import { i18n } from '../i18next';
interface NavbarState {
mentions: Array<Comment>;
messages: Array<PrivateMessage>;
unreadCount: number;
- siteName: string;
- admins: Array<UserView>;
searchParam: string;
toggleSearch: boolean;
+ siteLoading: boolean;
+ siteRes: GetSiteResponse;
+ onSiteBanner?(url: string): any;
}
export class Navbar extends Component<any, NavbarState> {
private wsSub: Subscription;
private userSub: Subscription;
+ private unreadCountSub: Subscription;
private searchTextField: RefObject<HTMLInputElement>;
emptyState: NavbarState = {
- isLoggedIn: UserService.Instance.user !== undefined,
+ isLoggedIn: false,
unreadCount: 0,
replies: [],
mentions: [],
messages: [],
expanded: false,
- siteName: undefined,
- admins: [],
+ siteRes: {
+ site: {
+ id: null,
+ name: null,
+ creator_id: null,
+ creator_name: null,
+ published: null,
+ number_of_users: null,
+ number_of_posts: null,
+ number_of_comments: null,
+ number_of_communities: null,
+ enable_downvotes: null,
+ open_registration: null,
+ enable_nsfw: null,
+ icon: null,
+ banner: null,
+ creator_preferred_username: null,
+ },
+ my_user: null,
+ admins: [],
+ banned: [],
+ online: null,
+ version: null,
+ federated_instances: null,
+ },
searchParam: '',
toggleSearch: false,
+ siteLoading: true,
};
constructor(props: any, context: any) {
super(props, context);
this.state = this.emptyState;
- // Subscribe to user changes
- this.userSub = UserService.Instance.sub.subscribe(user => {
- this.state.isLoggedIn = user.user !== undefined;
- if (this.state.isLoggedIn) {
- this.state.unreadCount = user.user.unreadCount;
- this.requestNotificationPermission();
- }
- this.setState(this.state);
- });
-
this.wsSub = WebSocketService.Instance.subject
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
() => console.log('complete')
);
- if (this.state.isLoggedIn) {
- this.requestNotificationPermission();
- // TODO couldn't get re-logging in to re-fetch unreads
- this.fetchUnreads();
- }
-
WebSocketService.Instance.getSite();
this.searchTextField = createRef();
}
+ componentDidMount() {
+ // Subscribe to jwt changes
+ this.userSub = UserService.Instance.jwtSub.subscribe(res => {
+ // A login
+ if (res !== undefined) {
+ this.requestNotificationPermission();
+ } else {
+ this.state.isLoggedIn = false;
+ }
+ WebSocketService.Instance.getSite();
+ this.setState(this.state);
+ });
+
+ // Subscribe to unread count changes
+ this.unreadCountSub = UserService.Instance.unreadCountSub.subscribe(res => {
+ this.setState({ unreadCount: res });
+ });
+ }
+
handleSearchParam(i: Navbar, event: any) {
i.state.searchParam = event.target.value;
i.setState(i.state);
this.context.router.history.push(`/search/`);
} else {
this.context.router.history.push(
- `/search/q/${searchParam}/type/all/sort/topall/page/1`
+ `/search/q/${searchParam}/type/All/sort/TopAll/page/1`
);
}
}
componentWillUnmount() {
this.wsSub.unsubscribe();
this.userSub.unsubscribe();
+ this.unreadCountSub.unsubscribe();
}
// TODO class active corresponding to current page
navbar() {
+ let user = UserService.Instance.user;
return (
- <nav class="container-fluid navbar navbar-expand-md navbar-light shadow p-0 px-3">
- <Link title={version} class="navbar-brand" to="/">
- {this.state.siteName}
- </Link>
- {this.state.isLoggedIn && (
- <Link
- class="ml-auto p-0 navbar-toggler nav-link"
- to="/inbox"
- title={i18n.t('inbox')}
- >
- <svg class="icon">
- <use xlinkHref="#icon-bell"></use>
- </svg>
- {this.state.unreadCount > 0 && (
- <span class="ml-1 badge badge-light">
- {this.state.unreadCount}
- </span>
- )}
- </Link>
- )}
- <button
- class="navbar-toggler"
- type="button"
- aria-label="menu"
- onClick={linkEvent(this, this.expandNavbar)}
- data-tippy-content={i18n.t('expand_here')}
- >
- <span class="navbar-toggler-icon"></span>
- </button>
- <div
- className={`${!this.state.expanded && 'collapse'} navbar-collapse`}
- >
- <ul class="navbar-nav my-2 mr-auto">
- <li class="nav-item">
- <Link
- class="nav-link"
- to="/communities"
- title={i18n.t('communities')}
- >
- {i18n.t('communities')}
- </Link>
- </li>
- <li class="nav-item">
- <Link
- class="nav-link"
- to={{
- pathname: '/create_post',
- state: { prevPath: this.currentLocation },
- }}
- title={i18n.t('create_post')}
- >
- {i18n.t('create_post')}
- </Link>
- </li>
- <li class="nav-item">
- <Link
- class="nav-link"
- to="/create_community"
- title={i18n.t('create_community')}
- >
- {i18n.t('create_community')}
- </Link>
- </li>
- <li className="nav-item">
- <Link
- class="nav-link"
- to="/sponsors"
- title={i18n.t('donate_to_lemmy')}
- >
- <svg class="icon">
- <use xlinkHref="#icon-coffee"></use>
- </svg>
- </Link>
- </li>
- </ul>
- {!this.context.router.history.location.pathname.match(
- /^\/search/
- ) && (
- <form
- class="form-inline"
- onSubmit={linkEvent(this, this.handleSearchSubmit)}
+ <nav class="navbar navbar-expand-lg navbar-light shadow-sm p-0 px-3">
+ <div class="container">
+ {!this.state.siteLoading ? (
+ <Link
+ title={this.state.siteRes.version}
+ class="d-flex align-items-center navbar-brand mr-md-3"
+ to="/"
>
- <input
- class={`form-control mr-0 search-input ${
- this.state.toggleSearch ? 'show-input' : 'hide-input'
- }`}
- onInput={linkEvent(this, this.handleSearchParam)}
- value={this.state.searchParam}
- ref={this.searchTextField}
- type="text"
- placeholder={i18n.t('search')}
- onBlur={linkEvent(this, this.handleSearchBlur)}
- ></input>
- <button
- name="search-btn"
- onClick={linkEvent(this, this.handleSearchBtn)}
- class="btn btn-link"
- style="color: var(--gray)"
- >
- <svg class="icon">
- <use xlinkHref="#icon-search"></use>
- </svg>
- </button>
- </form>
+ {this.state.siteRes.site.icon && showAvatars() && (
+ <img
+ src={pictrsAvatarThumbnail(this.state.siteRes.site.icon)}
+ height="32"
+ width="32"
+ class="rounded-circle mr-2"
+ />
+ )}
+ {this.state.siteRes.site.name}
+ </Link>
+ ) : (
+ <div class="navbar-item">
+ <svg class="icon icon-spinner spin">
+ <use xlinkHref="#icon-spinner"></use>
+ </svg>
+ </div>
)}
- <ul class="navbar-nav my-2">
- {this.canAdmin && (
- <li className="nav-item">
- <Link
- class="nav-link"
- to={`/admin`}
- title={i18n.t('admin_settings')}
- >
- <svg class="icon">
- <use xlinkHref="#icon-settings"></use>
- </svg>
- </Link>
- </li>
- )}
- </ul>
- {this.state.isLoggedIn ? (
- <>
- <ul class="navbar-nav my-2">
- <li className="nav-item">
- <Link class="nav-link" to="/inbox" title={i18n.t('inbox')}>
- <svg class="icon">
- <use xlinkHref="#icon-bell"></use>
- </svg>
- {this.state.unreadCount > 0 && (
- <span class="ml-1 badge badge-light">
- {this.state.unreadCount}
- </span>
- )}
+ {this.state.isLoggedIn && (
+ <Link
+ class="ml-auto p-0 navbar-toggler nav-link border-0"
+ to="/inbox"
+ title={i18n.t('inbox')}
+ >
+ <svg class="icon">
+ <use xlinkHref="#icon-bell"></use>
+ </svg>
+ {this.state.unreadCount > 0 && (
+ <span class="mx-1 badge badge-light">
+ {this.state.unreadCount}
+ </span>
+ )}
+ </Link>
+ )}
+ <button
+ class="navbar-toggler border-0 p-1"
+ type="button"
+ aria-label="menu"
+ onClick={linkEvent(this, this.expandNavbar)}
+ data-tippy-content={i18n.t('expand_here')}
+ >
+ <span class="navbar-toggler-icon"></span>
+ </button>
+ {!this.state.siteLoading && (
+ <div
+ className={`${
+ !this.state.expanded && 'collapse'
+ } navbar-collapse`}
+ >
+ <ul class="navbar-nav my-2 mr-auto">
+ <li class="nav-item">
+ <Link
+ class="nav-link"
+ to="/communities"
+ title={i18n.t('communities')}
+ >
+ {i18n.t('communities')}
+ </Link>
+ </li>
+ <li class="nav-item">
+ <Link
+ class="nav-link"
+ to={{
+ pathname: '/create_post',
+ state: { prevPath: this.currentLocation },
+ }}
+ title={i18n.t('create_post')}
+ >
+ {i18n.t('create_post')}
+ </Link>
+ </li>
+ <li class="nav-item">
+ <Link
+ class="nav-link"
+ to="/create_community"
+ title={i18n.t('create_community')}
+ >
+ {i18n.t('create_community')}
</Link>
</li>
- </ul>
- <ul class="navbar-nav">
<li className="nav-item">
<Link
class="nav-link"
- to={`/u/${UserService.Instance.user.username}`}
- title={i18n.t('settings')}
+ to="/sponsors"
+ title={i18n.t('donate_to_lemmy')}
>
- <span>
- {UserService.Instance.user.avatar && showAvatars() && (
- <img
- src={pictrsAvatarThumbnail(
- UserService.Instance.user.avatar
- )}
- height="32"
- width="32"
- class="rounded-circle mr-2"
- />
- )}
- {UserService.Instance.user.username}
- </span>
+ <svg class="icon">
+ <use xlinkHref="#icon-coffee"></use>
+ </svg>
</Link>
</li>
</ul>
- </>
- ) : (
- <ul class="navbar-nav my-2">
- <li className="nav-item">
- <Link
- class="nav-link"
- to="/login"
- title={i18n.t('login_sign_up')}
+ <ul class="navbar-nav my-2">
+ {this.canAdmin && (
+ <li className="nav-item">
+ <Link
+ class="nav-link"
+ to={`/admin`}
+ title={i18n.t('admin_settings')}
+ >
+ <svg class="icon">
+ <use xlinkHref="#icon-settings"></use>
+ </svg>
+ </Link>
+ </li>
+ )}
+ </ul>
+ {!this.context.router.history.location.pathname.match(
+ /^\/search/
+ ) && (
+ <form
+ class="form-inline"
+ onSubmit={linkEvent(this, this.handleSearchSubmit)}
>
- {i18n.t('login_sign_up')}
- </Link>
- </li>
- </ul>
+ <input
+ class={`form-control mr-0 search-input ${
+ this.state.toggleSearch ? 'show-input' : 'hide-input'
+ }`}
+ onInput={linkEvent(this, this.handleSearchParam)}
+ value={this.state.searchParam}
+ ref={this.searchTextField}
+ type="text"
+ placeholder={i18n.t('search')}
+ onBlur={linkEvent(this, this.handleSearchBlur)}
+ ></input>
+ <button
+ name="search-btn"
+ onClick={linkEvent(this, this.handleSearchBtn)}
+ class="px-1 btn btn-link"
+ style="color: var(--gray)"
+ >
+ <svg class="icon">
+ <use xlinkHref="#icon-search"></use>
+ </svg>
+ </button>
+ </form>
+ )}
+ {this.state.isLoggedIn ? (
+ <>
+ <ul class="navbar-nav my-2">
+ <li className="nav-item">
+ <Link
+ class="nav-link"
+ to="/inbox"
+ title={i18n.t('inbox')}
+ >
+ <svg class="icon">
+ <use xlinkHref="#icon-bell"></use>
+ </svg>
+ {this.state.unreadCount > 0 && (
+ <span class="ml-1 badge badge-light">
+ {this.state.unreadCount}
+ </span>
+ )}
+ </Link>
+ </li>
+ </ul>
+ <ul class="navbar-nav">
+ <li className="nav-item">
+ <Link
+ class="nav-link"
+ to={`/u/${user.name}`}
+ title={i18n.t('settings')}
+ >
+ <span>
+ {user.avatar && showAvatars() && (
+ <img
+ src={pictrsAvatarThumbnail(user.avatar)}
+ height="32"
+ width="32"
+ class="rounded-circle mr-2"
+ />
+ )}
+ {user.preferred_username
+ ? user.preferred_username
+ : user.name}
+ </span>
+ </Link>
+ </li>
+ </ul>
+ </>
+ ) : (
+ <ul class="navbar-nav my-2">
+ <li className="ml-2 nav-item">
+ <Link
+ class="btn btn-success"
+ to="/login"
+ title={i18n.t('login_sign_up')}
+ >
+ {i18n.t('login_sign_up')}
+ </Link>
+ </li>
+ </ul>
+ )}
+ </div>
)}
</div>
</nav>
this.state.unreadCount++;
this.setState(this.state);
this.sendUnreadCount();
- this.notify(data.comment);
+ notifyComment(data.comment, this.context.router);
}
}
} else if (res.op == UserOperation.CreatePrivateMessage) {
this.state.unreadCount++;
this.setState(this.state);
this.sendUnreadCount();
- this.notify(data.message);
+ notifyPrivateMessage(data.message, this.context.router);
}
}
} else if (res.op == UserOperation.GetSite) {
let data = res.data as GetSiteResponse;
- if (data.site && !this.state.siteName) {
- this.state.siteName = data.site.name;
- this.state.admins = data.admins;
- this.setState(this.state);
+ this.state.siteRes = data;
+
+ // The login
+ if (data.my_user) {
+ UserService.Instance.user = data.my_user;
+ WebSocketService.Instance.userJoin();
+ // On the first load, check the unreads
+ if (this.state.isLoggedIn == false) {
+ this.requestNotificationPermission();
+ this.fetchUnreads();
+ setTheme(data.my_user.theme, true);
+ i18n.changeLanguage(getLanguage());
+ }
+ this.state.isLoggedIn = true;
}
+
+ this.state.siteLoading = false;
+ this.setState(this.state);
}
}
fetchUnreads() {
- if (this.state.isLoggedIn) {
- let repliesForm: GetRepliesForm = {
- sort: SortType[SortType.New],
- unread_only: true,
- page: 1,
- limit: fetchLimit,
- };
-
- let userMentionsForm: GetUserMentionsForm = {
- sort: SortType[SortType.New],
- unread_only: true,
- page: 1,
- limit: fetchLimit,
- };
-
- let privateMessagesForm: GetPrivateMessagesForm = {
- unread_only: true,
- page: 1,
- limit: fetchLimit,
- };
-
- if (this.currentLocation !== '/inbox') {
- WebSocketService.Instance.getReplies(repliesForm);
- WebSocketService.Instance.getUserMentions(userMentionsForm);
- WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
- }
+ console.log('Fetching unreads...');
+ let repliesForm: GetRepliesForm = {
+ sort: SortType.New,
+ unread_only: true,
+ page: 1,
+ limit: fetchLimit,
+ };
+
+ let userMentionsForm: GetUserMentionsForm = {
+ sort: SortType.New,
+ unread_only: true,
+ page: 1,
+ limit: fetchLimit,
+ };
+
+ let privateMessagesForm: GetPrivateMessagesForm = {
+ unread_only: true,
+ page: 1,
+ limit: fetchLimit,
+ };
+
+ if (this.currentLocation !== '/inbox') {
+ WebSocketService.Instance.getReplies(repliesForm);
+ WebSocketService.Instance.getUserMentions(userMentionsForm);
+ WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
}
}
}
sendUnreadCount() {
- UserService.Instance.user.unreadCount = this.state.unreadCount;
- UserService.Instance.sub.next({
- user: UserService.Instance.user,
- });
+ UserService.Instance.unreadCountSub.next(this.state.unreadCount);
}
calculateUnreadCount(): number {
get canAdmin(): boolean {
return (
UserService.Instance.user &&
- this.state.admins.map(a => a.id).includes(UserService.Instance.user.id)
+ this.state.siteRes.admins
+ .map(a => a.id)
+ .includes(UserService.Instance.user.id)
);
}
});
}
}
-
- notify(reply: Comment | PrivateMessage) {
- let creator_name = reply.creator_name;
- let creator_avatar = reply.creator_avatar
- ? reply.creator_avatar
- : `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`;
- let link = isCommentType(reply)
- ? `/post/${reply.post_id}/comment/${reply.id}`
- : `/inbox`;
- let htmlBody = md.render(reply.content);
- let body = reply.content; // Unfortunately the notifications API can't do html
-
- messageToastify(
- creator_name,
- creator_avatar,
- htmlBody,
- link,
- this.context.router
- );
-
- if (Notification.permission !== 'granted') Notification.requestPermission();
- else {
- var notification = new Notification(reply.creator_name, {
- icon: creator_avatar,
- body: body,
- });
-
- notification.onclick = () => {
- event.preventDefault();
- this.context.router.history.push(link);
- };
- }
- }
}
import { Component, linkEvent } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import {
PasswordChangeForm,
WebSocketJsonResponse,
GetSiteResponse,
-} from '../interfaces';
+ Site,
+} from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services';
import { wsJsonToRes, capitalizeFirstLetter, toast } from '../utils';
import { i18n } from '../i18next';
interface State {
passwordChangeForm: PasswordChangeForm;
loading: boolean;
+ site: Site;
}
export class PasswordChange extends Component<any, State> {
password_verify: undefined,
},
loading: false,
+ site: undefined,
};
constructor(props: any, context: any) {
this.subscription.unsubscribe();
}
+ get documentTitle(): string {
+ if (this.state.site) {
+ return `${i18n.t('password_change')} - ${this.state.site.name}`;
+ } else {
+ return 'Lemmy';
+ }
+ }
+
render() {
return (
<div class="container">
+ <Helmet title={this.documentTitle} />
<div class="row">
<div class="col-12 col-lg-6 offset-lg-3 mb-4">
<h5>{i18n.t('password_change')}</h5>
this.props.history.push('/');
} else if (res.op == UserOperation.GetSite) {
let data = res.data as GetSiteResponse;
- document.title = `${i18n.t('password_change')} - ${data.site.name}`;
+ this.state.site = data.site;
+ this.setState(this.state);
}
}
}
import { Component, linkEvent } from 'inferno';
import { Prompt } from 'inferno-router';
import { PostListings } from './post-listings';
+import { MarkdownTextArea } from './markdown-textarea';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import {
SearchType,
SearchResponse,
WebSocketJsonResponse,
-} from '../interfaces';
+} from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services';
import {
wsJsonToRes,
getPageTitle,
validURL,
capitalizeFirstLetter,
- markdownHelpUrl,
archiveUrl,
- mdToHtml,
debounce,
isImage,
toast,
randomStr,
- setupTribute,
setupTippy,
hostname,
pictrsDeleteToast,
validTitle,
} from '../utils';
-import autosize from 'autosize';
-import Tribute from 'tributejs/src/Tribute.js';
-import emojiShortName from 'emoji-short-name';
import Choices from 'choices.js';
import { i18n } from '../i18next';
export class PostForm extends Component<PostFormProps, PostFormState> {
private id = `post-form-${randomStr()}`;
- private tribute: Tribute;
private subscription: Subscription;
private choices: Choices;
private emptyState: PostFormState = {
nsfw: false,
auth: null,
community_id: null,
- creator_id: UserService.Instance.user
- ? UserService.Instance.user.id
- : null,
},
communities: [],
loading: false,
super(props, context);
this.fetchSimilarPosts = debounce(this.fetchSimilarPosts).bind(this);
this.fetchPageTitle = debounce(this.fetchPageTitle).bind(this);
-
- this.tribute = setupTribute();
+ this.handlePostBodyChange = this.handlePostBodyChange.bind(this);
this.state = this.emptyState;
name: this.props.post.name,
community_id: this.props.post.community_id,
edit_id: this.props.post.id,
- creator_id: this.props.post.creator_id,
url: this.props.post.url,
nsfw: this.props.post.nsfw,
auth: null,
);
let listCommunitiesForm: ListCommunitiesForm = {
- sort: SortType[SortType.TopAll],
+ sort: SortType.TopAll,
limit: 9999,
};
}
componentDidMount() {
- var textarea: any = document.getElementById(this.id);
- autosize(textarea);
- this.tribute.attach(textarea);
- textarea.addEventListener('tribute-replaced', () => {
- this.state.postForm.body = textarea.value;
- this.setState(this.state);
- autosize.update(textarea);
- });
setupTippy();
}
componentWillUnmount() {
this.subscription.unsubscribe();
- this.choices && this.choices.destroy();
+ /* this.choices && this.choices.destroy(); */
window.onbeforeunload = null;
}
{i18n.t('body')}
</label>
<div class="col-sm-10">
- <textarea
- id={this.id}
- value={this.state.postForm.body}
- onInput={linkEvent(this, this.handlePostBodyChange)}
- className={`form-control ${this.state.previewMode && 'd-none'}`}
- rows={4}
- maxLength={10000}
+ <MarkdownTextArea
+ initialContent={this.state.postForm.body}
+ onContentChange={this.handlePostBodyChange}
/>
- {this.state.previewMode && (
- <div
- className="card card-body md-div"
- dangerouslySetInnerHTML={mdToHtml(this.state.postForm.body)}
- />
- )}
- {this.state.postForm.body && (
- <button
- className={`mt-1 mr-2 btn btn-sm btn-secondary ${
- this.state.previewMode && 'active'
- }`}
- onClick={linkEvent(this, this.handlePreviewToggle)}
- >
- {i18n.t('preview')}
- </button>
- )}
- <a
- href={markdownHelpUrl}
- target="_blank"
- rel="noopener"
- class="d-inline-block float-right text-muted font-weight-bold"
- title={i18n.t('formatting_help')}
- >
- <svg class="icon icon-inline">
- <use xlinkHref="#icon-help-circle"></use>
- </svg>
- </a>
</div>
</div>
{!this.props.post && (
if (validURL(this.state.postForm.url)) {
let form: SearchForm = {
q: this.state.postForm.url,
- type_: SearchType[SearchType.Url],
- sort: SortType[SortType.TopAll],
+ type_: SearchType.Url,
+ sort: SortType.TopAll,
page: 1,
limit: 6,
};
fetchSimilarPosts() {
let form: SearchForm = {
q: this.state.postForm.name,
- type_: SearchType[SearchType.Posts],
- sort: SortType[SortType.TopAll],
+ type_: SearchType.Posts,
+ sort: SortType.TopAll,
community_id: this.state.postForm.community_id,
page: 1,
limit: 6,
this.setState(this.state);
}
- handlePostBodyChange(i: PostForm, event: any) {
- i.state.postForm.body = event.target.value;
- i.setState(i.state);
+ handlePostBodyChange(val: string) {
+ this.state.postForm.body = val;
+ this.setState(this.state);
}
handlePostCommunityChange(i: PostForm, event: any) {
import {
Post,
CreatePostLikeForm,
- PostForm as PostFormI,
+ DeletePostForm,
+ RemovePostForm,
+ LockPostForm,
+ StickyPostForm,
SavePostForm,
CommunityUser,
UserView,
- BanType,
BanFromCommunityForm,
BanUserForm,
AddModToCommunityForm,
AddAdminForm,
TransferSiteForm,
TransferCommunityForm,
-} from '../interfaces';
+} from 'lemmy-js-client';
+import { BanType } from '../interfaces';
import { MomentTime } from './moment-time';
import { PostForm } from './post-form';
import { IFramelyCard } from './iframely-card';
setupTippy,
hostname,
previewLines,
- toast,
} from '../utils';
import { i18n } from '../i18next';
showRemoveDialog: boolean;
removeReason: string;
showBanDialog: boolean;
+ removeData: boolean;
banReason: string;
banExpires: string;
banType: BanType;
showRemoveDialog: false,
removeReason: null,
showBanDialog: false,
+ removeData: null,
banReason: null,
banExpires: null,
banType: BanType.Community,
this.state.upvotes = nextProps.post.upvotes;
this.state.downvotes = nextProps.post.downvotes;
this.state.score = nextProps.post.score;
+ if (this.props.post.id !== nextProps.post.id) {
+ this.state.imageExpanded = false;
+ }
this.setState(this.state);
}
return (
<img
className={`img-fluid thumbnail rounded ${
- (post.nsfw || post.community_nsfw) && 'img-blur'
+ post.nsfw || post.community_nsfw ? 'img-blur' : ''
}`}
src={src}
/>
if (isImage(post.url)) {
return (
- <span
- class="text-body pointer"
+ <div
+ class="float-right text-body pointer d-inline-block position-relative"
data-tippy-content={i18n.t('expand_here')}
onClick={linkEvent(this, this.handleImageExpandClick)}
>
<svg class="icon mini-overlay">
<use xlinkHref="#icon-image"></use>
</svg>
- </span>
+ </div>
);
} else if (post.thumbnail_url) {
return (
<a
- className="text-body"
+ class="float-right text-body d-inline-block position-relative"
href={post.url}
target="_blank"
rel="noopener"
title={post.url}
rel="noopener"
>
- <svg class="icon thumbnail">
- <use xlinkHref="#icon-external-link"></use>
- </svg>
+ <div class="thumbnail rounded bg-light d-flex justify-content-center">
+ <svg class="icon d-flex align-items-center">
+ <use xlinkHref="#icon-external-link"></use>
+ </svg>
+ </div>
</a>
);
}
to={`/post/${post.id}`}
title={i18n.t('comments')}
>
- <svg class="icon thumbnail">
- <use xlinkHref="#icon-message-square"></use>
- </svg>
+ <div class="thumbnail rounded bg-light d-flex justify-content-center">
+ <svg class="icon d-flex align-items-center">
+ <use xlinkHref="#icon-message-square"></use>
+ </svg>
+ </div>
</Link>
);
}
}
- listing() {
+ createdLine() {
let post = this.props.post;
return (
- <div class="row">
- <div className={`vote-bar col-1 pr-0 small text-center`}>
+ <ul class="list-inline mb-1 text-muted small">
+ <li className="list-inline-item">
+ <UserListing
+ user={{
+ name: post.creator_name,
+ preferred_username: post.creator_preferred_username,
+ avatar: post.creator_avatar,
+ id: post.creator_id,
+ local: post.creator_local,
+ actor_id: post.creator_actor_id,
+ published: post.creator_published,
+ }}
+ />
+
+ {this.isMod && (
+ <span className="mx-1 badge badge-light">{i18n.t('mod')}</span>
+ )}
+ {this.isAdmin && (
+ <span className="mx-1 badge badge-light">{i18n.t('admin')}</span>
+ )}
+ {(post.banned_from_community || post.banned) && (
+ <span className="mx-1 badge badge-danger">{i18n.t('banned')}</span>
+ )}
+ {this.props.showCommunity && (
+ <span>
+ <span class="mx-1"> {i18n.t('to')} </span>
+ <CommunityLink
+ community={{
+ name: post.community_name,
+ id: post.community_id,
+ local: post.community_local,
+ actor_id: post.community_actor_id,
+ icon: post.community_icon,
+ }}
+ />
+ </span>
+ )}
+ </li>
+ <li className="list-inline-item">•</li>
+ {post.url && !(hostname(post.url) == window.location.hostname) && (
+ <>
+ <li className="list-inline-item">
+ <a
+ className="text-muted font-italic"
+ href={post.url}
+ target="_blank"
+ title={post.url}
+ rel="noopener"
+ >
+ {hostname(post.url)}
+ </a>
+ </li>
+ <li className="list-inline-item">•</li>
+ </>
+ )}
+ <li className="list-inline-item">
+ <span>
+ <MomentTime data={post} />
+ </span>
+ </li>
+ {post.body && (
+ <>
+ <li className="list-inline-item">•</li>
+ <li className="list-inline-item">
+ {/* Using a link with tippy doesn't work on touch devices unfortunately */}
+ <Link
+ className="text-muted"
+ data-tippy-content={md.render(previewLines(post.body))}
+ data-tippy-allowHtml={true}
+ to={`/post/${post.id}`}
+ >
+ <svg class="mr-1 icon icon-inline">
+ <use xlinkHref="#icon-book-open"></use>
+ </svg>
+ </Link>
+ </li>
+ </>
+ )}
+ </ul>
+ );
+ }
+
+ voteBar() {
+ return (
+ <div className={`vote-bar col-1 pr-0 small text-center`}>
+ <button
+ className={`btn-animate btn btn-link p-0 ${
+ this.state.my_vote == 1 ? 'text-info' : 'text-muted'
+ }`}
+ onClick={linkEvent(this, this.handlePostLike)}
+ data-tippy-content={i18n.t('upvote')}
+ >
+ <svg class="icon upvote">
+ <use xlinkHref="#icon-arrow-up1"></use>
+ </svg>
+ </button>
+ <div
+ class={`unselectable pointer font-weight-bold text-muted px-1`}
+ data-tippy-content={this.pointsTippy}
+ >
+ {this.state.score}
+ </div>
+ {this.props.enableDownvotes && (
<button
className={`btn-animate btn btn-link p-0 ${
- this.state.my_vote == 1 ? 'text-info' : 'text-muted'
+ this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
}`}
- onClick={linkEvent(this, this.handlePostLike)}
- data-tippy-content={i18n.t('upvote')}
+ onClick={linkEvent(this, this.handlePostDisLike)}
+ data-tippy-content={i18n.t('downvote')}
>
- <svg class="icon upvote">
- <use xlinkHref="#icon-arrow-up1"></use>
+ <svg class="icon downvote">
+ <use xlinkHref="#icon-arrow-down1"></use>
</svg>
</button>
- <div
- class={`unselectable pointer font-weight-bold text-muted px-1`}
- data-tippy-content={this.pointsTippy}
- >
- {this.state.score}
- </div>
- {this.props.enableDownvotes && (
- <button
- className={`btn-animate btn btn-link p-0 ${
- this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
- }`}
- onClick={linkEvent(this, this.handlePostDisLike)}
- data-tippy-content={i18n.t('downvote')}
+ )}
+ </div>
+ );
+ }
+
+ postTitleLine() {
+ let post = this.props.post;
+ return (
+ <div className="post-title overflow-hidden">
+ <h5>
+ {this.props.showBody && post.url ? (
+ <a
+ className={!post.stickied ? 'text-body' : 'text-primary'}
+ href={post.url}
+ target="_blank"
+ title={post.url}
+ rel="noopener"
+ >
+ {post.name}
+ </a>
+ ) : (
+ <Link
+ className={!post.stickied ? 'text-body' : 'text-primary'}
+ to={`/post/${post.id}`}
+ title={i18n.t('comments')}
+ >
+ {post.name}
+ </Link>
+ )}
+ {(isImage(post.url) || this.props.post.thumbnail_url) && (
+ <>
+ {!this.state.imageExpanded ? (
+ <span
+ class="text-monospace unselectable pointer ml-2 text-muted small"
+ data-tippy-content={i18n.t('expand_here')}
+ onClick={linkEvent(this, this.handleImageExpandClick)}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-plus-square"></use>
+ </svg>
+ </span>
+ ) : (
+ <span>
+ <span
+ class="text-monospace unselectable pointer ml-2 text-muted small"
+ onClick={linkEvent(this, this.handleImageExpandClick)}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-minus-square"></use>
+ </svg>
+ </span>
+ <div>
+ <span
+ class="pointer"
+ onClick={linkEvent(this, this.handleImageExpandClick)}
+ >
+ <img
+ class="img-fluid img-expanded"
+ src={this.getImage()}
+ />
+ </span>
+ </div>
+ </span>
+ )}
+ </>
+ )}
+ {post.removed && (
+ <small className="ml-2 text-muted font-italic">
+ {i18n.t('removed')}
+ </small>
+ )}
+ {post.deleted && (
+ <small
+ className="unselectable pointer ml-2 text-muted font-italic"
+ data-tippy-content={i18n.t('deleted')}
>
- <svg class="icon downvote">
- <use xlinkHref="#icon-arrow-down1"></use>
+ <svg class={`icon icon-inline text-danger`}>
+ <use xlinkHref="#icon-trash"></use>
</svg>
- </button>
+ </small>
)}
- </div>
- {!this.state.imageExpanded && (
- <div class="col-3 col-sm-2 pr-0 mt-1">
- <div class="position-relative">{this.thumbnail()}</div>
- </div>
+ {post.locked && (
+ <small
+ className="unselectable pointer ml-2 text-muted font-italic"
+ data-tippy-content={i18n.t('locked')}
+ >
+ <svg class={`icon icon-inline text-danger`}>
+ <use xlinkHref="#icon-lock"></use>
+ </svg>
+ </small>
+ )}
+ {post.stickied && (
+ <small
+ className="unselectable pointer ml-2 text-muted font-italic"
+ data-tippy-content={i18n.t('stickied')}
+ >
+ <svg class={`icon icon-inline text-primary`}>
+ <use xlinkHref="#icon-pin"></use>
+ </svg>
+ </small>
+ )}
+ {post.nsfw && (
+ <small className="ml-2 text-muted font-italic">
+ {i18n.t('nsfw')}
+ </small>
+ )}
+ </h5>
+ </div>
+ );
+ }
+
+ commentsLine(showVotes: boolean = false) {
+ let post = this.props.post;
+ return (
+ <ul class="d-flex align-items-center list-inline mb-1 text-muted small">
+ <li className="list-inline-item">
+ <Link
+ className="text-muted"
+ title={i18n.t('number_of_comments', {
+ count: post.number_of_comments,
+ })}
+ to={`/post/${post.id}`}
+ >
+ <svg class="mr-1 icon icon-inline">
+ <use xlinkHref="#icon-message-square"></use>
+ </svg>
+ {i18n.t('number_of_comments', {
+ count: post.number_of_comments,
+ })}
+ </Link>
+ </li>
+ {(showVotes || this.state.upvotes !== this.state.score) && (
+ <>
+ <span
+ class="unselectable pointer ml-3"
+ data-tippy-content={this.pointsTippy}
+ >
+ <li className="list-inline-item">
+ <a
+ className={`btn-animate btn btn-link p-0 ${
+ this.state.my_vote == 1 ? 'text-info' : 'text-muted'
+ }`}
+ onClick={linkEvent(this, this.handlePostLike)}
+ >
+ <svg class="small icon icon-inline mx-1">
+ <use xlinkHref="#icon-arrow-up1"></use>
+ </svg>
+ {this.state.upvotes}
+ </a>
+ </li>
+ <li className="list-inline-item">
+ <a
+ className={`btn-animate btn btn-link p-0 ${
+ this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
+ }`}
+ onClick={linkEvent(this, this.handlePostDisLike)}
+ >
+ <svg class="small icon icon-inline mx-1">
+ <use xlinkHref="#icon-arrow-down1"></use>
+ </svg>
+ {this.state.downvotes}
+ </a>
+ </li>
+ </span>
+ </>
)}
- <div
- class={`${this.state.imageExpanded ? 'col-12' : 'col-8 col-sm-9'}`}
- >
- <div class="row">
- <div className="col-12">
- <div className="post-title">
- <h5 className="mb-0 d-inline">
- {this.props.showBody && post.url ? (
- <a
- className="text-body"
- href={post.url}
- target="_blank"
- title={post.url}
- rel="noopener"
- >
- {post.name}
- </a>
- ) : (
- <Link
- className="text-body"
- to={`/post/${post.id}`}
- title={i18n.t('comments')}
- >
- {post.name}
- </Link>
- )}
- </h5>
- {post.url && !(hostname(post.url) == window.location.hostname) && (
- <small class="d-inline-block">
- <a
- className="ml-2 text-muted font-italic"
- href={post.url}
- target="_blank"
- title={post.url}
- rel="noopener"
- >
- {hostname(post.url)}
- <svg class="ml-1 icon icon-inline">
- <use xlinkHref="#icon-external-link"></use>
- </svg>
- </a>
- </small>
- )}
- {(isImage(post.url) || this.props.post.thumbnail_url) && (
- <>
- {!this.state.imageExpanded ? (
- <span
- class="text-monospace unselectable pointer ml-2 text-muted small"
- data-tippy-content={i18n.t('expand_here')}
- onClick={linkEvent(this, this.handleImageExpandClick)}
- >
- <svg class="icon icon-inline">
- <use xlinkHref="#icon-plus-square"></use>
- </svg>
- </span>
- ) : (
- <span>
- <span
- class="text-monospace unselectable pointer ml-2 text-muted small"
- onClick={linkEvent(this, this.handleImageExpandClick)}
- >
- <svg class="icon icon-inline">
- <use xlinkHref="#icon-minus-square"></use>
- </svg>
- </span>
- <div>
- <span
- class="pointer"
- onClick={linkEvent(
- this,
- this.handleImageExpandClick
- )}
- >
- <img
- class="img-fluid img-expanded"
- src={this.getImage()}
- />
- </span>
- </div>
- </span>
- )}
- </>
- )}
- {post.removed && (
- <small className="ml-2 text-muted font-italic">
- {i18n.t('removed')}
- </small>
- )}
- {post.deleted && (
- <small
- className="unselectable pointer ml-2 text-muted font-italic"
- data-tippy-content={i18n.t('deleted')}
+ </ul>
+ );
+ }
+
+ duplicatesLine() {
+ return (
+ this.props.post.duplicates && (
+ <ul class="list-inline mb-1 small text-muted">
+ <>
+ <li className="list-inline-item mr-2">
+ {i18n.t('cross_posted_to')}
+ </li>
+ {this.props.post.duplicates.map(post => (
+ <li className="list-inline-item mr-2">
+ <Link to={`/post/${post.id}`}>{post.community_name}</Link>
+ </li>
+ ))}
+ </>
+ </ul>
+ )
+ );
+ }
+
+ postActions() {
+ let post = this.props.post;
+ return (
+ <ul class="list-inline mb-1 text-muted font-weight-bold">
+ {UserService.Instance.user && (
+ <>
+ {this.props.showBody && (
+ <>
+ <li className="list-inline-item">
+ <button
+ class="btn btn-link btn-animate text-muted"
+ onClick={linkEvent(this, this.handleSavePostClick)}
+ data-tippy-content={
+ post.saved ? i18n.t('unsave') : i18n.t('save')
+ }
>
- <svg class={`icon icon-inline text-danger`}>
- <use xlinkHref="#icon-trash"></use>
+ <svg
+ class={`icon icon-inline ${post.saved && 'text-warning'}`}
+ >
+ <use xlinkHref="#icon-star"></use>
</svg>
- </small>
- )}
- {post.locked && (
- <small
- className="unselectable pointer ml-2 text-muted font-italic"
- data-tippy-content={i18n.t('locked')}
+ </button>
+ </li>
+ <li className="list-inline-item">
+ <Link
+ class="btn btn-link btn-animate text-muted"
+ to={`/create_post${this.crossPostParams}`}
+ title={i18n.t('cross_post')}
>
- <svg class={`icon icon-inline text-danger`}>
- <use xlinkHref="#icon-lock"></use>
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-copy"></use>
</svg>
- </small>
- )}
- {post.stickied && (
- <small
- className="unselectable pointer ml-2 text-muted font-italic"
- data-tippy-content={i18n.t('stickied')}
+ </Link>
+ </li>
+ </>
+ )}
+ {this.myPost && this.props.showBody && (
+ <>
+ <li className="list-inline-item">
+ <button
+ class="btn btn-link btn-animate text-muted"
+ onClick={linkEvent(this, this.handleEditClick)}
+ data-tippy-content={i18n.t('edit')}
>
- <svg class={`icon icon-inline text-success`}>
- <use xlinkHref="#icon-pin"></use>
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-edit"></use>
</svg>
- </small>
- )}
- {post.nsfw && (
- <small className="ml-2 text-muted font-italic">
- {i18n.t('nsfw')}
- </small>
- )}
- </div>
- </div>
- </div>
- <div class="row">
- <div className="details col-12">
- <ul class="list-inline mb-0 text-muted small">
- <li className="list-inline-item">
- <span>{i18n.t('by')} </span>
- <UserListing
- user={{
- name: post.creator_name,
- avatar: post.creator_avatar,
- id: post.creator_id,
- local: post.creator_local,
- actor_id: post.creator_actor_id,
- published: post.creator_published,
- }}
- />
-
- {this.isMod && (
- <span className="mx-1 badge badge-light">
- {i18n.t('mod')}
- </span>
- )}
- {this.isAdmin && (
- <span className="mx-1 badge badge-light">
- {i18n.t('admin')}
- </span>
- )}
- {(post.banned_from_community || post.banned) && (
- <span className="mx-1 badge badge-danger">
- {i18n.t('banned')}
- </span>
- )}
- {this.props.showCommunity && (
- <span>
- <span> {i18n.t('to')} </span>
- <CommunityLink
- community={{
- name: post.community_name,
- id: post.community_id,
- local: post.community_local,
- actor_id: post.community_actor_id,
- }}
- />
- </span>
- )}
+ </button>
</li>
- <li className="list-inline-item">•</li>
<li className="list-inline-item">
- <span>
- <MomentTime data={post} />
- </span>
+ <button
+ class="btn btn-link btn-animate text-muted"
+ onClick={linkEvent(this, this.handleDeleteClick)}
+ data-tippy-content={
+ !post.deleted ? i18n.t('delete') : i18n.t('restore')
+ }
+ >
+ <svg
+ class={`icon icon-inline ${
+ post.deleted && 'text-danger'
+ }`}
+ >
+ <use xlinkHref="#icon-trash"></use>
+ </svg>
+ </button>
</li>
- {post.body && (
+ </>
+ )}
+
+ {!this.state.showAdvanced && this.props.showBody ? (
+ <li className="list-inline-item">
+ <button
+ class="btn btn-link btn-animate text-muted"
+ onClick={linkEvent(this, this.handleShowAdvanced)}
+ data-tippy-content={i18n.t('more')}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-more-vertical"></use>
+ </svg>
+ </button>
+ </li>
+ ) : (
+ <>
+ {this.props.showBody && post.body && (
+ <li className="list-inline-item">
+ <button
+ class="btn btn-link btn-animate text-muted"
+ onClick={linkEvent(this, this.handleViewSource)}
+ data-tippy-content={i18n.t('view_source')}
+ >
+ <svg
+ class={`icon icon-inline ${
+ this.state.viewSource && 'text-success'
+ }`}
+ >
+ <use xlinkHref="#icon-file-text"></use>
+ </svg>
+ </button>
+ </li>
+ )}
+ {this.canModOnSelf && (
<>
- <li className="list-inline-item">•</li>
<li className="list-inline-item">
- {/* Using a link with tippy doesn't work on touch devices unfortunately */}
- <Link
- className="text-muted"
- data-tippy-content={md.render(previewLines(post.body))}
- data-tippy-allowHtml={true}
- to={`/post/${post.id}`}
+ <button
+ class="btn btn-link btn-animate text-muted"
+ onClick={linkEvent(this, this.handleModLock)}
+ data-tippy-content={
+ post.locked ? i18n.t('unlock') : i18n.t('lock')
+ }
+ >
+ <svg
+ class={`icon icon-inline ${
+ post.locked && 'text-danger'
+ }`}
+ >
+ <use xlinkHref="#icon-lock"></use>
+ </svg>
+ </button>
+ </li>
+ <li className="list-inline-item">
+ <button
+ class="btn btn-link btn-animate text-muted"
+ onClick={linkEvent(this, this.handleModSticky)}
+ data-tippy-content={
+ post.stickied ? i18n.t('unsticky') : i18n.t('sticky')
+ }
>
- <svg class="mr-1 icon icon-inline">
- <use xlinkHref="#icon-book-open"></use>
+ <svg
+ class={`icon icon-inline ${
+ post.stickied && 'text-success'
+ }`}
+ >
+ <use xlinkHref="#icon-pin"></use>
</svg>
- </Link>
+ </button>
</li>
</>
)}
- <li className="list-inline-item">•</li>
- {this.state.upvotes !== this.state.score && (
+ {/* Mods can ban from community, and appoint as mods to community */}
+ {(this.canMod || this.canAdmin) && (
+ <li className="list-inline-item">
+ {!post.removed ? (
+ <span
+ class="pointer"
+ onClick={linkEvent(this, this.handleModRemoveShow)}
+ >
+ {i18n.t('remove')}
+ </span>
+ ) : (
+ <span
+ class="pointer"
+ onClick={linkEvent(this, this.handleModRemoveSubmit)}
+ >
+ {i18n.t('restore')}
+ </span>
+ )}
+ </li>
+ )}
+ {this.canMod && (
<>
- <span
- class="unselectable pointer mr-2"
- data-tippy-content={this.pointsTippy}
- >
+ {!this.isMod && (
<li className="list-inline-item">
- <span className="text-muted">
- <svg class="small icon icon-inline mr-1">
- <use xlinkHref="#icon-arrow-up"></use>
- </svg>
- {this.state.upvotes}
- </span>
+ {!post.banned_from_community ? (
+ <span
+ class="pointer"
+ onClick={linkEvent(
+ this,
+ this.handleModBanFromCommunityShow
+ )}
+ >
+ {i18n.t('ban')}
+ </span>
+ ) : (
+ <span
+ class="pointer"
+ onClick={linkEvent(
+ this,
+ this.handleModBanFromCommunitySubmit
+ )}
+ >
+ {i18n.t('unban')}
+ </span>
+ )}
</li>
+ )}
+ {!post.banned_from_community && post.creator_local && (
<li className="list-inline-item">
- <span className="text-muted">
- <svg class="small icon icon-inline mr-1">
- <use xlinkHref="#icon-arrow-down"></use>
- </svg>
- {this.state.downvotes}
+ <span
+ class="pointer"
+ onClick={linkEvent(
+ this,
+ this.handleAddModToCommunity
+ )}
+ >
+ {this.isMod
+ ? i18n.t('remove_as_mod')
+ : i18n.t('appoint_as_mod')}
</span>
</li>
- </span>
- <li className="list-inline-item">•</li>
+ )}
</>
)}
- <li className="list-inline-item">
- <Link
- className="text-muted"
- title={i18n.t('number_of_comments', {
- count: post.number_of_comments,
- })}
- to={`/post/${post.id}`}
- >
- <svg class="mr-1 icon icon-inline">
- <use xlinkHref="#icon-message-square"></use>
- </svg>
- {post.number_of_comments}
- </Link>
- </li>
- </ul>
- {this.props.post.duplicates && (
- <ul class="list-inline mb-1 small text-muted">
- <>
- <li className="list-inline-item mr-2">
- {i18n.t('cross_posted_to')}
- </li>
- {this.props.post.duplicates.map(post => (
- <li className="list-inline-item mr-2">
- <Link to={`/post/${post.id}`}>
- {post.community_name}
- </Link>
- </li>
- ))}
- </>
- </ul>
- )}
- <ul class="list-inline mb-1 text-muted font-weight-bold">
- {UserService.Instance.user && (
- <>
- {this.props.showBody && (
- <>
- <li className="list-inline-item">
- <button
- class="btn btn-sm btn-link btn-animate text-muted"
- onClick={linkEvent(this, this.handleSavePostClick)}
- data-tippy-content={
- post.saved ? i18n.t('unsave') : i18n.t('save')
- }
+ {/* Community creators and admins can transfer community to another mod */}
+ {(this.amCommunityCreator || this.canAdmin) &&
+ this.isMod &&
+ post.creator_local && (
+ <li className="list-inline-item">
+ {!this.state.showConfirmTransferCommunity ? (
+ <span
+ class="pointer"
+ onClick={linkEvent(
+ this,
+ this.handleShowConfirmTransferCommunity
+ )}
+ >
+ {i18n.t('transfer_community')}
+ </span>
+ ) : (
+ <>
+ <span class="d-inline-block mr-1">
+ {i18n.t('are_you_sure')}
+ </span>
+ <span
+ class="pointer d-inline-block mr-1"
+ onClick={linkEvent(
+ this,
+ this.handleTransferCommunity
+ )}
>
- <svg
- class={`icon icon-inline ${
- post.saved && 'text-warning'
- }`}
- >
- <use xlinkHref="#icon-star"></use>
- </svg>
- </button>
- </li>
- <li className="list-inline-item">
- <Link
- class="btn btn-sm btn-link btn-animate text-muted"
- to={`/create_post${this.crossPostParams}`}
- title={i18n.t('cross_post')}
+ {i18n.t('yes')}
+ </span>
+ <span
+ class="pointer d-inline-block"
+ onClick={linkEvent(
+ this,
+ this.handleCancelShowConfirmTransferCommunity
+ )}
>
- <svg class="icon icon-inline">
- <use xlinkHref="#icon-copy"></use>
- </svg>
- </Link>
- </li>
- </>
- )}
- {this.myPost && this.props.showBody && (
- <>
- <li className="list-inline-item">
- <button
- class="btn btn-sm btn-link btn-animate text-muted"
- onClick={linkEvent(this, this.handleEditClick)}
- data-tippy-content={i18n.t('edit')}
+ {i18n.t('no')}
+ </span>
+ </>
+ )}
+ </li>
+ )}
+ {/* Admins can ban from all, and appoint other admins */}
+ {this.canAdmin && (
+ <>
+ {!this.isAdmin && (
+ <li className="list-inline-item">
+ {!post.banned ? (
+ <span
+ class="pointer"
+ onClick={linkEvent(this, this.handleModBanShow)}
>
- <svg class="icon icon-inline">
- <use xlinkHref="#icon-edit"></use>
- </svg>
- </button>
- </li>
- <li className="list-inline-item">
- <button
- class="btn btn-sm btn-link btn-animate text-muted"
- onClick={linkEvent(this, this.handleDeleteClick)}
- data-tippy-content={
- !post.deleted
- ? i18n.t('delete')
- : i18n.t('restore')
- }
+ {i18n.t('ban_from_site')}
+ </span>
+ ) : (
+ <span
+ class="pointer"
+ onClick={linkEvent(this, this.handleModBanSubmit)}
>
- <svg
- class={`icon icon-inline ${
- post.deleted && 'text-danger'
- }`}
- >
- <use xlinkHref="#icon-trash"></use>
- </svg>
- </button>
- </li>
- </>
+ {i18n.t('unban_from_site')}
+ </span>
+ )}
+ </li>
)}
-
- {!this.state.showAdvanced && this.props.showBody ? (
+ {!post.banned && post.creator_local && (
<li className="list-inline-item">
- <button
- class="btn btn-sm btn-link btn-animate text-muted"
- onClick={linkEvent(this, this.handleShowAdvanced)}
- data-tippy-content={i18n.t('more')}
+ <span
+ class="pointer"
+ onClick={linkEvent(this, this.handleAddAdmin)}
>
- <svg class="icon icon-inline">
- <use xlinkHref="#icon-more-vertical"></use>
- </svg>
- </button>
+ {this.isAdmin
+ ? i18n.t('remove_as_admin')
+ : i18n.t('appoint_as_admin')}
+ </span>
</li>
+ )}
+ </>
+ )}
+ {/* Site Creator can transfer to another admin */}
+ {this.amSiteCreator && this.isAdmin && (
+ <li className="list-inline-item">
+ {!this.state.showConfirmTransferSite ? (
+ <span
+ class="pointer"
+ onClick={linkEvent(
+ this,
+ this.handleShowConfirmTransferSite
+ )}
+ >
+ {i18n.t('transfer_site')}
+ </span>
) : (
<>
- {this.props.showBody && post.body && (
- <li className="list-inline-item">
- <button
- class="btn btn-sm btn-link btn-animate text-muted"
- onClick={linkEvent(this, this.handleViewSource)}
- data-tippy-content={i18n.t('view_source')}
- >
- <svg
- class={`icon icon-inline ${
- this.state.viewSource && 'text-success'
- }`}
- >
- <use xlinkHref="#icon-file-text"></use>
- </svg>
- </button>
- </li>
- )}
- {this.canModOnSelf && (
- <>
- <li className="list-inline-item">
- <button
- class="btn btn-sm btn-link btn-animate text-muted"
- onClick={linkEvent(this, this.handleModLock)}
- data-tippy-content={
- post.locked
- ? i18n.t('unlock')
- : i18n.t('lock')
- }
- >
- <svg
- class={`icon icon-inline ${
- post.locked && 'text-danger'
- }`}
- >
- <use xlinkHref="#icon-lock"></use>
- </svg>
- </button>
- </li>
- <li className="list-inline-item">
- <button
- class="btn btn-sm btn-link btn-animate text-muted"
- onClick={linkEvent(this, this.handleModSticky)}
- data-tippy-content={
- post.stickied
- ? i18n.t('unsticky')
- : i18n.t('sticky')
- }
- >
- <svg
- class={`icon icon-inline ${
- post.stickied && 'text-success'
- }`}
- >
- <use xlinkHref="#icon-pin"></use>
- </svg>
- </button>
- </li>
- </>
- )}
- {/* Mods can ban from community, and appoint as mods to community */}
- {(this.canMod || this.canAdmin) && (
- <li className="list-inline-item">
- {!post.removed ? (
- <span
- class="pointer"
- onClick={linkEvent(
- this,
- this.handleModRemoveShow
- )}
- >
- {i18n.t('remove')}
- </span>
- ) : (
- <span
- class="pointer"
- onClick={linkEvent(
- this,
- this.handleModRemoveSubmit
- )}
- >
- {i18n.t('restore')}
- </span>
- )}
- </li>
- )}
- {this.canMod && (
- <>
- {!this.isMod && (
- <li className="list-inline-item">
- {!post.banned_from_community ? (
- <span
- class="pointer"
- onClick={linkEvent(
- this,
- this.handleModBanFromCommunityShow
- )}
- >
- {i18n.t('ban')}
- </span>
- ) : (
- <span
- class="pointer"
- onClick={linkEvent(
- this,
- this.handleModBanFromCommunitySubmit
- )}
- >
- {i18n.t('unban')}
- </span>
- )}
- </li>
- )}
- {!post.banned_from_community && (
- <li className="list-inline-item">
- <span
- class="pointer"
- onClick={linkEvent(
- this,
- this.handleAddModToCommunity
- )}
- >
- {this.isMod
- ? i18n.t('remove_as_mod')
- : i18n.t('appoint_as_mod')}
- </span>
- </li>
- )}
- </>
- )}
- {/* Community creators and admins can transfer community to another mod */}
- {(this.amCommunityCreator || this.canAdmin) &&
- this.isMod && (
- <li className="list-inline-item">
- {!this.state.showConfirmTransferCommunity ? (
- <span
- class="pointer"
- onClick={linkEvent(
- this,
- this.handleShowConfirmTransferCommunity
- )}
- >
- {i18n.t('transfer_community')}
- </span>
- ) : (
- <>
- <span class="d-inline-block mr-1">
- {i18n.t('are_you_sure')}
- </span>
- <span
- class="pointer d-inline-block mr-1"
- onClick={linkEvent(
- this,
- this.handleTransferCommunity
- )}
- >
- {i18n.t('yes')}
- </span>
- <span
- class="pointer d-inline-block"
- onClick={linkEvent(
- this,
- this
- .handleCancelShowConfirmTransferCommunity
- )}
- >
- {i18n.t('no')}
- </span>
- </>
- )}
- </li>
+ <span class="d-inline-block mr-1">
+ {i18n.t('are_you_sure')}
+ </span>
+ <span
+ class="pointer d-inline-block mr-1"
+ onClick={linkEvent(this, this.handleTransferSite)}
+ >
+ {i18n.t('yes')}
+ </span>
+ <span
+ class="pointer d-inline-block"
+ onClick={linkEvent(
+ this,
+ this.handleCancelShowConfirmTransferSite
)}
- {/* Admins can ban from all, and appoint other admins */}
- {this.canAdmin && (
- <>
- {!this.isAdmin && (
- <li className="list-inline-item">
- {!post.banned ? (
- <span
- class="pointer"
- onClick={linkEvent(
- this,
- this.handleModBanShow
- )}
- >
- {i18n.t('ban_from_site')}
- </span>
- ) : (
- <span
- class="pointer"
- onClick={linkEvent(
- this,
- this.handleModBanSubmit
- )}
- >
- {i18n.t('unban_from_site')}
- </span>
- )}
- </li>
- )}
- {!post.banned && (
- <li className="list-inline-item">
- <span
- class="pointer"
- onClick={linkEvent(this, this.handleAddAdmin)}
- >
- {this.isAdmin
- ? i18n.t('remove_as_admin')
- : i18n.t('appoint_as_admin')}
- </span>
- </li>
- )}
- </>
- )}
- {/* Site Creator can transfer to another admin */}
- {this.amSiteCreator && this.isAdmin && (
- <li className="list-inline-item">
- {!this.state.showConfirmTransferSite ? (
- <span
- class="pointer"
- onClick={linkEvent(
- this,
- this.handleShowConfirmTransferSite
- )}
- >
- {i18n.t('transfer_site')}
- </span>
- ) : (
- <>
- <span class="d-inline-block mr-1">
- {i18n.t('are_you_sure')}
- </span>
- <span
- class="pointer d-inline-block mr-1"
- onClick={linkEvent(
- this,
- this.handleTransferSite
- )}
- >
- {i18n.t('yes')}
- </span>
- <span
- class="pointer d-inline-block"
- onClick={linkEvent(
- this,
- this.handleCancelShowConfirmTransferSite
- )}
- >
- {i18n.t('no')}
- </span>
- </>
- )}
- </li>
- )}
+ >
+ {i18n.t('no')}
+ </span>
</>
)}
- </>
+ </li>
)}
- </ul>
- {this.state.showRemoveDialog && (
- <form
- class="form-inline"
- onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
- >
+ </>
+ )}
+ </>
+ )}
+ </ul>
+ );
+ }
+
+ removeAndBanDialogs() {
+ let post = this.props.post;
+ return (
+ <>
+ {this.state.showRemoveDialog && (
+ <form
+ class="form-inline"
+ onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
+ >
+ <input
+ type="text"
+ class="form-control mr-2"
+ placeholder={i18n.t('reason')}
+ value={this.state.removeReason}
+ onInput={linkEvent(this, this.handleModRemoveReasonChange)}
+ />
+ <button type="submit" class="btn btn-secondary">
+ {i18n.t('remove_post')}
+ </button>
+ </form>
+ )}
+ {this.state.showBanDialog && (
+ <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
+ <div class="form-group row">
+ <label class="col-form-label" htmlFor="post-listing-reason">
+ {i18n.t('reason')}
+ </label>
+ <input
+ type="text"
+ id="post-listing-reason"
+ class="form-control mr-2"
+ placeholder={i18n.t('reason')}
+ value={this.state.banReason}
+ onInput={linkEvent(this, this.handleModBanReasonChange)}
+ />
+ <div class="form-group">
+ <div class="form-check">
<input
- type="text"
- class="form-control mr-2"
- placeholder={i18n.t('reason')}
- value={this.state.removeReason}
- onInput={linkEvent(this, this.handleModRemoveReasonChange)}
+ class="form-check-input"
+ id="mod-ban-remove-data"
+ type="checkbox"
+ checked={this.state.removeData}
+ onChange={linkEvent(this, this.handleModRemoveDataChange)}
/>
- <button type="submit" class="btn btn-secondary">
- {i18n.t('remove_post')}
- </button>
- </form>
- )}
- {this.state.showBanDialog && (
- <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
- <div class="form-group row">
- <label class="col-form-label" htmlFor="post-listing-reason">
- {i18n.t('reason')}
- </label>
- <input
- type="text"
- id="post-listing-reason"
- class="form-control mr-2"
- placeholder={i18n.t('reason')}
- value={this.state.banReason}
- onInput={linkEvent(this, this.handleModBanReasonChange)}
- />
- </div>
- {/* TODO hold off on expires until later */}
- {/* <div class="form-group row"> */}
- {/* <label class="col-form-label">Expires</label> */}
- {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
- {/* </div> */}
- <div class="form-group row">
- <button type="submit" class="btn btn-secondary">
- {i18n.t('ban')} {post.creator_name}
- </button>
- </div>
- </form>
- )}
+ <label class="form-check-label" htmlFor="mod-ban-remove-data">
+ {i18n.t('remove_posts_comments')}
+ </label>
+ </div>
+ </div>
</div>
- </div>
+ {/* TODO hold off on expires until later */}
+ {/* <div class="form-group row"> */}
+ {/* <label class="col-form-label">Expires</label> */}
+ {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
+ {/* </div> */}
+ <div class="form-group row">
+ <button type="submit" class="btn btn-secondary">
+ {i18n.t('ban')} {post.creator_name}
+ </button>
+ </div>
+ </form>
+ )}
+ </>
+ );
+ }
+
+ mobileThumbnail() {
+ return this.props.post.thumbnail_url || isImage(this.props.post.url) ? (
+ <div class="row">
+ <div className={`${this.state.imageExpanded ? 'col-12' : 'col-8'}`}>
+ {this.postTitleLine()}
+ </div>
+ <div class="col-4">
+ {/* Post body prev or thumbnail */}
+ {!this.state.imageExpanded && this.thumbnail()}
</div>
</div>
+ ) : (
+ this.postTitleLine()
+ );
+ }
+
+ showMobilePreview() {
+ return (
+ this.props.post.body &&
+ !this.props.showBody && (
+ <div
+ className="md-div mb-1"
+ dangerouslySetInnerHTML={{
+ __html: md.render(previewLines(this.props.post.body)),
+ }}
+ />
+ )
+ );
+ }
+
+ listing() {
+ return (
+ <>
+ {/* The mobile view*/}
+ <div class="d-block d-sm-none">
+ <div class="row">
+ <div class="col-12">
+ {this.createdLine()}
+
+ {/* If it has a thumbnail, do a right aligned thumbnail */}
+ {this.mobileThumbnail()}
+
+ {/* Show a preview of the post body */}
+ {this.showMobilePreview()}
+
+ {this.commentsLine(true)}
+ {this.duplicatesLine()}
+ {this.postActions()}
+ {this.removeAndBanDialogs()}
+ </div>
+ </div>
+ </div>
+
+ {/* The larger view*/}
+ <div class="d-none d-sm-block">
+ <div class="row">
+ {this.voteBar()}
+ {!this.state.imageExpanded && (
+ <div class="col-sm-2 pr-0">
+ <div class="">{this.thumbnail()}</div>
+ </div>
+ )}
+ <div
+ class={`${
+ this.state.imageExpanded ? 'col-12' : 'col-12 col-sm-9'
+ }`}
+ >
+ <div class="row">
+ <div className="col-12">
+ {this.postTitleLine()}
+ {this.createdLine()}
+ {this.commentsLine()}
+ {this.duplicatesLine()}
+ {this.postActions()}
+ {this.removeAndBanDialogs()}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </>
);
}
}
handleDeleteClick(i: PostListing) {
- let deleteForm: PostFormI = {
- body: i.props.post.body,
- community_id: i.props.post.community_id,
- name: i.props.post.name,
- url: i.props.post.url,
+ let deleteForm: DeletePostForm = {
edit_id: i.props.post.id,
- creator_id: i.props.post.creator_id,
deleted: !i.props.post.deleted,
- nsfw: i.props.post.nsfw,
auth: null,
};
- WebSocketService.Instance.editPost(deleteForm);
+ WebSocketService.Instance.deletePost(deleteForm);
}
handleSavePostClick(i: PostListing) {
let post = this.props.post;
if (post.url) {
- params += `&url=${post.url}`;
+ params += `&url=${encodeURIComponent(post.url)}`;
}
if (this.props.post.body) {
params += `&body=${this.props.post.body}`;
i.setState(i.state);
}
+ handleModRemoveDataChange(i: PostListing, event: any) {
+ i.state.removeData = event.target.checked;
+ i.setState(i.state);
+ }
+
handleModRemoveSubmit(i: PostListing) {
event.preventDefault();
- let form: PostFormI = {
- name: i.props.post.name,
- community_id: i.props.post.community_id,
+ let form: RemovePostForm = {
edit_id: i.props.post.id,
- creator_id: i.props.post.creator_id,
removed: !i.props.post.removed,
reason: i.state.removeReason,
- nsfw: i.props.post.nsfw,
auth: null,
};
- WebSocketService.Instance.editPost(form);
+ WebSocketService.Instance.removePost(form);
i.state.showRemoveDialog = false;
i.setState(i.state);
}
handleModLock(i: PostListing) {
- let form: PostFormI = {
- name: i.props.post.name,
- community_id: i.props.post.community_id,
+ let form: LockPostForm = {
edit_id: i.props.post.id,
- creator_id: i.props.post.creator_id,
- nsfw: i.props.post.nsfw,
locked: !i.props.post.locked,
auth: null,
};
- WebSocketService.Instance.editPost(form);
+ WebSocketService.Instance.lockPost(form);
}
handleModSticky(i: PostListing) {
- let form: PostFormI = {
- name: i.props.post.name,
- community_id: i.props.post.community_id,
+ let form: StickyPostForm = {
edit_id: i.props.post.id,
- creator_id: i.props.post.creator_id,
- nsfw: i.props.post.nsfw,
stickied: !i.props.post.stickied,
auth: null,
};
- WebSocketService.Instance.editPost(form);
+ WebSocketService.Instance.stickyPost(form);
}
handleModBanFromCommunityShow(i: PostListing) {
event.preventDefault();
if (i.state.banType == BanType.Community) {
+ // If its an unban, restore all their data
+ let ban = !i.props.post.banned_from_community;
+ if (ban == false) {
+ i.state.removeData = false;
+ }
let form: BanFromCommunityForm = {
user_id: i.props.post.creator_id,
community_id: i.props.post.community_id,
- ban: !i.props.post.banned_from_community,
+ ban,
+ remove_data: i.state.removeData,
reason: i.state.banReason,
expires: getUnixTime(i.state.banExpires),
};
WebSocketService.Instance.banFromCommunity(form);
} else {
+ // If its an unban, restore all their data
+ let ban = !i.props.post.banned;
+ if (ban == false) {
+ i.state.removeData = false;
+ }
let form: BanUserForm = {
user_id: i.props.post.creator_id,
- ban: !i.props.post.banned,
+ ban,
+ remove_data: i.state.removeData,
reason: i.state.banReason,
expires: getUnixTime(i.state.banExpires),
};
import { Component } from 'inferno';
import { Link } from 'inferno-router';
-import { Post, SortType } from '../interfaces';
+import { Post, SortType } from 'lemmy-js-client';
import { postSort } from '../utils';
import { PostListing } from './post-listing';
import { i18n } from '../i18next';
enableDownvotes={this.props.enableDownvotes}
enableNsfw={this.props.enableNsfw}
/>
- <hr class="my-2" />
+ <hr class="my-3" />
</>
))
) : (
import { Component, linkEvent } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import {
GetPostResponse,
PostResponse,
Comment,
- CommentForm as CommentFormI,
+ MarkCommentAsReadForm,
CommentResponse,
- CommentSortType,
- CommentViewType,
CommunityUser,
CommunityResponse,
CommentNode as CommentNodeI,
GetSiteResponse,
GetCommunityResponse,
WebSocketJsonResponse,
-} from '../interfaces';
+} from 'lemmy-js-client';
+import { CommentSortType, CommentViewType } from '../interfaces';
import { WebSocketService, UserService } from '../services';
import {
wsJsonToRes,
createPostLikeRes,
commentsToFlatNodes,
setupTippy,
+ favIconUrl,
} from '../utils';
import { PostListing } from './post-listing';
import { Sidebar } from './sidebar';
enable_downvotes: undefined,
open_registration: undefined,
enable_nsfw: undefined,
+ icon: undefined,
+ banner: undefined,
},
online: null,
+ version: null,
+ federated_instances: undefined,
},
};
UserService.Instance.user &&
UserService.Instance.user.id == parent_user_id
) {
- let form: CommentFormI = {
- content: found.content,
+ let form: MarkCommentAsReadForm = {
edit_id: found.id,
- creator_id: found.creator_id,
- post_id: found.post_id,
- parent_id: found.parent_id,
read: true,
auth: null,
};
- WebSocketService.Instance.editComment(form);
- UserService.Instance.user.unreadCount--;
- UserService.Instance.sub.next({
- user: UserService.Instance.user,
- });
+ WebSocketService.Instance.markCommentAsRead(form);
+ UserService.Instance.unreadCountSub.next(
+ UserService.Instance.unreadCountSub.value - 1
+ );
+ }
+ }
+
+ get documentTitle(): string {
+ if (this.state.post) {
+ return `${this.state.post.name} - ${this.state.siteRes.site.name}`;
+ } else {
+ return 'Lemmy';
}
}
+ get favIcon(): string {
+ return this.state.siteRes.site.icon
+ ? this.state.siteRes.site.icon
+ : favIconUrl;
+ }
+
render() {
return (
<div class="container">
+ <Helmet title={this.documentTitle}>
+ <link
+ id="favicon"
+ rel="icon"
+ type="image/x-icon"
+ href={this.favIcon}
+ />
+ </Helmet>
{this.state.loading ? (
<h5>
<svg class="icon icon-spinner spin">
sortRadios() {
return (
<>
- <div class="btn-group btn-group-toggle mr-3 mb-2">
+ <div class="btn-group btn-group-toggle flex-wrap mr-3 mb-2">
<label
- className={`btn btn-sm btn-secondary pointer ${
+ className={`btn btn-outline-secondary pointer ${
this.state.commentSort === CommentSortType.Hot && 'active'
}`}
>
/>
</label>
<label
- className={`btn btn-sm btn-secondary pointer ${
+ className={`btn btn-outline-secondary pointer ${
this.state.commentSort === CommentSortType.Top && 'active'
}`}
>
/>
</label>
<label
- className={`btn btn-sm btn-secondary pointer ${
+ className={`btn btn-outline-secondary pointer ${
this.state.commentSort === CommentSortType.New && 'active'
}`}
>
/>
</label>
<label
- className={`btn btn-sm btn-secondary pointer ${
+ className={`btn btn-outline-secondary pointer ${
this.state.commentSort === CommentSortType.Old && 'active'
}`}
>
/>
</label>
</div>
- <div class="btn-group btn-group-toggle mb-2">
+ <div class="btn-group btn-group-toggle flex-wrap mb-2">
<label
- className={`btn btn-sm btn-secondary pointer ${
+ className={`btn btn-outline-secondary pointer ${
this.state.commentViewType === CommentViewType.Chat && 'active'
}`}
>
admins={this.state.siteRes.admins}
online={this.state.online}
enableNsfw={this.state.siteRes.site.enable_nsfw}
+ showIcon
/>
</div>
);
this.state.comments = data.comments;
this.state.community = data.community;
this.state.moderators = data.moderators;
- this.state.siteRes.admins = data.admins;
this.state.online = data.online;
this.state.loading = false;
- document.title = `${this.state.post.name} - ${this.state.siteRes.site.name}`;
// Get cross-posts
if (this.state.post.url) {
let form: SearchForm = {
q: this.state.post.url,
- type_: SearchType[SearchType.Url],
- sort: SortType[SortType.TopAll],
+ type_: SearchType.Url,
+ sort: SortType.TopAll,
page: 1,
limit: 6,
};
this.state.comments.unshift(data.comment);
this.setState(this.state);
}
- } else if (res.op == UserOperation.EditComment) {
+ } else if (
+ res.op == UserOperation.EditComment ||
+ res.op == UserOperation.DeleteComment ||
+ res.op == UserOperation.RemoveComment
+ ) {
let data = res.data as CommentResponse;
editCommentRes(data, this.state.comments);
this.setState(this.state);
let data = res.data as PostResponse;
createPostLikeRes(data, this.state.post);
this.setState(this.state);
- } else if (res.op == UserOperation.EditPost) {
+ } else if (
+ res.op == UserOperation.EditPost ||
+ res.op == UserOperation.DeletePost ||
+ res.op == UserOperation.RemovePost ||
+ res.op == UserOperation.LockPost ||
+ res.op == UserOperation.StickyPost
+ ) {
let data = res.data as PostResponse;
this.state.post = data.post;
this.setState(this.state);
this.state.post = data.post;
this.setState(this.state);
setupTippy();
- } else if (res.op == UserOperation.EditCommunity) {
+ } else if (
+ res.op == UserOperation.EditCommunity ||
+ res.op == UserOperation.DeleteCommunity ||
+ res.op == UserOperation.RemoveCommunity
+ ) {
let data = res.data as CommunityResponse;
this.state.community = data.community;
this.state.post.community_id = data.community.id;
let data = res.data as GetCommunityResponse;
this.state.community = data.community;
this.state.moderators = data.moderators;
- this.state.siteRes.admins = data.admins;
this.setState(this.state);
}
}
GetUserDetailsForm,
SortType,
WebSocketJsonResponse,
-} from '../interfaces';
+} from 'lemmy-js-client';
import { WebSocketService } from '../services';
import {
capitalizeFirstLetter,
- markdownHelpUrl,
- mdToHtml,
wsJsonToRes,
toast,
- randomStr,
- setupTribute,
setupTippy,
} from '../utils';
import { UserListing } from './user-listing';
-import Tribute from 'tributejs/src/Tribute.js';
-import autosize from 'autosize';
+import { MarkdownTextArea } from './markdown-textarea';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
PrivateMessageFormProps,
PrivateMessageFormState
> {
- private id = `message-form-${randomStr()}`;
- private tribute: Tribute;
private subscription: Subscription;
private emptyState: PrivateMessageFormState = {
privateMessageForm: {
constructor(props: any, context: any) {
super(props, context);
- this.tribute = setupTribute();
this.state = this.emptyState;
+ this.handleContentChange = this.handleContentChange.bind(this);
+
if (this.props.privateMessage) {
this.state.privateMessageForm = {
content: this.props.privateMessage.content,
this.state.privateMessageForm.recipient_id = this.props.params.recipient_id;
let form: GetUserDetailsForm = {
user_id: this.state.privateMessageForm.recipient_id,
- sort: SortType[SortType.New],
+ sort: SortType.New,
saved_only: false,
};
WebSocketService.Instance.getUserDetails(form);
}
componentDidMount() {
- var textarea: any = document.getElementById(this.id);
- autosize(textarea);
- this.tribute.attach(textarea);
- textarea.addEventListener('tribute-replaced', () => {
- this.state.privateMessageForm.content = textarea.value;
- this.setState(this.state);
- autosize.update(textarea);
- });
setupTippy();
}
<UserListing
user={{
name: this.state.recipient.name,
+ preferred_username: this.state.recipient
+ .preferred_username,
avatar: this.state.recipient.avatar,
id: this.state.recipient.id,
local: this.state.recipient.local,
</div>
)}
<div class="form-group row">
- <label class="col-sm-2 col-form-label">{i18n.t('message')}</label>
+ <label class="col-sm-2 col-form-label">
+ {i18n.t('message')}
+ <span
+ onClick={linkEvent(this, this.handleShowDisclaimer)}
+ class="ml-2 pointer text-danger"
+ data-tippy-content={i18n.t('disclaimer')}
+ >
+ <svg class={`icon icon-inline`}>
+ <use xlinkHref="#icon-alert-triangle"></use>
+ </svg>
+ </span>
+ </label>
<div class="col-sm-10">
- <textarea
- id={this.id}
- value={this.state.privateMessageForm.content}
- onInput={linkEvent(this, this.handleContentChange)}
- className={`form-control ${this.state.previewMode && 'd-none'}`}
- rows={4}
- maxLength={10000}
+ <MarkdownTextArea
+ initialContent={this.state.privateMessageForm.content}
+ onContentChange={this.handleContentChange}
/>
- {this.state.previewMode && (
- <div
- className="card card-body md-div"
- dangerouslySetInnerHTML={mdToHtml(
- this.state.privateMessageForm.content
- )}
- />
- )}
</div>
</div>
class="alert-link"
target="_blank"
rel="noopener"
- href="https://about.riot.im/"
+ href="https://element.io/get-started"
>
#
</a>
capitalizeFirstLetter(i18n.t('send_message'))
)}
</button>
- {this.state.privateMessageForm.content && (
- <button
- className={`btn btn-secondary mr-2 ${
- this.state.previewMode && 'active'
- }`}
- onClick={linkEvent(this, this.handlePreviewToggle)}
- >
- {i18n.t('preview')}
- </button>
- )}
{this.props.privateMessage && (
<button
type="button"
</button>
)}
<ul class="d-inline-block float-right list-inline mb-1 text-muted font-weight-bold">
- <li class="list-inline-item">
- <span
- onClick={linkEvent(this, this.handleShowDisclaimer)}
- class="pointer"
- data-tippy-content={i18n.t('disclaimer')}
- >
- <svg class={`icon icon-inline`}>
- <use xlinkHref="#icon-alert-triangle"></use>
- </svg>
- </span>
- </li>
- <li class="list-inline-item">
- <a
- href={markdownHelpUrl}
- target="_blank"
- rel="noopener"
- class="text-muted"
- title={i18n.t('formatting_help')}
- >
- <svg class="icon icon-inline">
- <use xlinkHref="#icon-help-circle"></use>
- </svg>
- </a>
- </li>
+ <li class="list-inline-item"></li>
</ul>
</div>
</div>
i.setState(i.state);
}
- handleContentChange(i: PrivateMessageForm, event: any) {
- i.state.privateMessageForm.content = event.target.value;
- i.setState(i.state);
+ handleContentChange(val: string) {
+ this.state.privateMessageForm.content = val;
+ this.setState(this.state);
}
handleCancel(i: PrivateMessageForm) {
this.state.loading = false;
this.setState(this.state);
return;
- } else if (res.op == UserOperation.EditPrivateMessage) {
+ } else if (
+ res.op == UserOperation.EditPrivateMessage ||
+ res.op == UserOperation.DeletePrivateMessage ||
+ res.op == UserOperation.MarkPrivateMessageAsRead
+ ) {
let data = res.data as PrivateMessageResponse;
this.state.loading = false;
this.props.onEdit(data.message);
import { Link } from 'inferno-router';
import {
PrivateMessage as PrivateMessageI,
- EditPrivateMessageForm,
-} from '../interfaces';
+ DeletePrivateMessageForm,
+ MarkPrivateMessageAsReadForm,
+} from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services';
import { mdToHtml, pictrsAvatarThumbnail, showAvatars, toast } from '../utils';
import { MomentTime } from './moment-time';
import { PrivateMessageForm } from './private-message-form';
+import { UserListing, UserOther } from './user-listing';
import { i18n } from '../i18next';
interface PrivateMessageState {
}
get mine(): boolean {
- return UserService.Instance.user.id == this.props.privateMessage.creator_id;
+ return (
+ UserService.Instance.user &&
+ UserService.Instance.user.id == this.props.privateMessage.creator_id
+ );
}
render() {
let message = this.props.privateMessage;
+ let userOther: UserOther = this.mine
+ ? {
+ name: message.recipient_name,
+ preferred_username: message.recipient_preferred_username,
+ id: message.id,
+ avatar: message.recipient_avatar,
+ local: message.recipient_local,
+ actor_id: message.recipient_actor_id,
+ published: message.published,
+ }
+ : {
+ name: message.creator_name,
+ preferred_username: message.creator_preferred_username,
+ id: message.id,
+ avatar: message.creator_avatar,
+ local: message.creator_local,
+ actor_id: message.creator_actor_id,
+ published: message.published,
+ };
+
return (
<div class="border-top border-light">
<div>
{this.mine ? i18n.t('to') : i18n.t('from')}
</li>
<li className="list-inline-item">
- <Link
- className="text-body font-weight-bold"
- to={
- this.mine
- ? `/u/${message.recipient_name}`
- : `/u/${message.creator_name}`
- }
- >
- {(this.mine
- ? message.recipient_avatar
- : message.creator_avatar) &&
- showAvatars() && (
- <img
- height="32"
- width="32"
- src={pictrsAvatarThumbnail(
- this.mine
- ? message.recipient_avatar
- : message.creator_avatar
- )}
- class="rounded-circle mr-1"
- />
- )}
- <span>
- {this.mine ? message.recipient_name : message.creator_name}
- </span>
- </Link>
+ <UserListing user={userOther} />
</li>
<li className="list-inline-item">
<span>
<PrivateMessageForm
privateMessage={message}
onEdit={this.handlePrivateMessageEdit}
+ onCreate={this.handlePrivateMessageCreate}
onCancel={this.handleReplyCancel}
/>
)}
<>
<li className="list-inline-item">
<button
- class="btn btn-link btn-sm btn-animate text-muted"
+ class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleMarkRead)}
data-tippy-content={
message.read
</li>
<li className="list-inline-item">
<button
- class="btn btn-link btn-sm btn-animate text-muted"
+ class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleReplyClick)}
data-tippy-content={i18n.t('reply')}
>
<>
<li className="list-inline-item">
<button
- class="btn btn-link btn-sm btn-animate text-muted"
+ class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleEditClick)}
data-tippy-content={i18n.t('edit')}
>
</li>
<li className="list-inline-item">
<button
- class="btn btn-link btn-sm btn-animate text-muted"
+ class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleDeleteClick)}
data-tippy-content={
!message.deleted
)}
<li className="list-inline-item">
<button
- class="btn btn-link btn-sm btn-animate text-muted"
+ class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleViewSource)}
data-tippy-content={i18n.t('view_source')}
>
}
handleDeleteClick(i: PrivateMessage) {
- let form: EditPrivateMessageForm = {
+ let form: DeletePrivateMessageForm = {
edit_id: i.props.privateMessage.id,
deleted: !i.props.privateMessage.deleted,
};
- WebSocketService.Instance.editPrivateMessage(form);
+ WebSocketService.Instance.deletePrivateMessage(form);
}
handleReplyCancel() {
}
handleMarkRead(i: PrivateMessage) {
- let form: EditPrivateMessageForm = {
+ let form: MarkPrivateMessageAsReadForm = {
edit_id: i.props.privateMessage.id,
read: !i.props.privateMessage.read,
};
- WebSocketService.Instance.editPrivateMessage(form);
+ WebSocketService.Instance.markPrivateMessageAsRead(form);
}
handleMessageCollapse(i: PrivateMessage) {
this.setState(this.state);
}
- handlePrivateMessageCreate() {
- this.state.showReply = false;
- this.setState(this.state);
- toast(i18n.t('message_sent'));
+ handlePrivateMessageCreate(message: PrivateMessageI) {
+ if (
+ UserService.Instance.user &&
+ message.creator_id == UserService.Instance.user.id
+ ) {
+ this.state.showReply = false;
+ this.setState(this.state);
+ toast(i18n.t('message_sent'));
+ }
}
}
import { Component, linkEvent } from 'inferno';
-import { Link } from 'inferno-router';
+import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import {
WebSocketJsonResponse,
GetSiteResponse,
Site,
-} from '../interfaces';
+} from 'lemmy-js-client';
import { WebSocketService } from '../services';
import {
wsJsonToRes,
interface UrlParams {
q?: string;
- type_?: string;
- sort?: string;
+ type_?: SearchType;
+ sort?: SortType;
page?: number;
}
}
}
+ get documentTitle(): string {
+ if (this.state.site.name) {
+ if (this.state.q) {
+ return `${i18n.t('search')} - ${this.state.q} - ${
+ this.state.site.name
+ }`;
+ } else {
+ return `${i18n.t('search')} - ${this.state.site.name}`;
+ }
+ } else {
+ return 'Lemmy';
+ }
+ }
+
render() {
return (
<div class="container">
+ <Helmet title={this.documentTitle} />
<h5>{i18n.t('search')}</h5>
{this.selects()}
{this.searchForm()}
>
<input
type="text"
- class="form-control mr-2"
+ class="form-control mr-2 mb-2"
value={this.state.searchText}
placeholder={`${i18n.t('search')}...`}
onInput={linkEvent(this, this.handleQChange)}
required
minLength={3}
/>
- <button type="submit" class="btn btn-secondary mr-2">
+ <button type="submit" class="btn btn-secondary mr-2 mb-2">
{this.state.loading ? (
<svg class="icon icon-spinner spin">
<use xlinkHref="#icon-spinner"></use>
<select
value={this.state.type_}
onChange={linkEvent(this, this.handleTypeChange)}
- class="custom-select custom-select-sm w-auto"
+ class="custom-select w-auto mb-2"
>
<option disabled>{i18n.t('type')}</option>
<option value={SearchType.All}>{i18n.t('all')}</option>
<div class="col-12">
{i.type_ == 'posts' && (
<PostListing
+ key={(i.data as Post).id}
post={i.data as Post}
showCommunity
enableDownvotes={this.state.site.enable_downvotes}
)}
{i.type_ == 'comments' && (
<CommentNodes
+ key={(i.data as Comment).id}
nodes={[{ comment: i.data as Comment }]}
locked
noIndent
<UserListing
user={{
name: (i.data as UserView).name,
+ preferred_username: (i.data as UserView)
+ .preferred_username,
avatar: (i.data as UserView).avatar,
}}
/>
<div class="mt-2">
{this.state.page > 1 && (
<button
- class="btn btn-sm btn-secondary mr-1"
+ class="btn btn-secondary mr-1"
onClick={linkEvent(this, this.prevPage)}
>
{i18n.t('prev')}
{this.resultsCount() > 0 && (
<button
- class="btn btn-sm btn-secondary"
+ class="btn btn-secondary"
onClick={linkEvent(this, this.nextPage)}
>
{i18n.t('next')}
search() {
let form: SearchForm = {
q: this.state.q,
- type_: SearchType[this.state.type_],
- sort: SortType[this.state.sort],
+ type_: this.state.type_,
+ sort: this.state.sort,
page: this.state.page,
limit: fetchLimit,
};
}
handleSortChange(val: SortType) {
- this.updateUrl({ sort: SortType[val].toLowerCase(), page: 1 });
+ this.updateUrl({ sort: val, page: 1 });
}
handleTypeChange(i: Search, event: any) {
i.updateUrl({
- type_: SearchType[Number(event.target.value)].toLowerCase(),
+ type_: SearchType[event.target.value],
page: 1,
});
}
event.preventDefault();
i.updateUrl({
q: i.state.searchText,
- type_: SearchType[i.state.type_].toLowerCase(),
- sort: SortType[i.state.sort].toLowerCase(),
+ type_: i.state.type_,
+ sort: i.state.sort,
page: i.state.page,
});
}
updateUrl(paramUpdates: UrlParams) {
const qStr = paramUpdates.q || this.state.q;
- const typeStr =
- paramUpdates.type_ || SearchType[this.state.type_].toLowerCase();
- const sortStr =
- paramUpdates.sort || SortType[this.state.sort].toLowerCase();
+ const typeStr = paramUpdates.type_ || this.state.type_;
+ const sortStr = paramUpdates.sort || this.state.sort;
const page = paramUpdates.page || this.state.page;
this.props.history.push(
`/search/q/${qStr}/type/${typeStr}/sort/${sortStr}/page/${page}`
let data = res.data as SearchResponse;
this.state.searchResponse = data;
this.state.loading = false;
- document.title = `${i18n.t('search')} - ${this.state.q} - ${
- this.state.site.name
- }`;
window.scrollTo(0, 0);
this.setState(this.state);
} else if (res.op == UserOperation.CreateCommentLike) {
let data = res.data as GetSiteResponse;
this.state.site = data.site;
this.setState(this.state);
- document.title = `${i18n.t('search')} - ${data.site.name}`;
}
}
}
import { Component, linkEvent } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import {
LoginResponse,
UserOperation,
WebSocketJsonResponse,
-} from '../interfaces';
+} from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services';
import { wsJsonToRes, toast } from '../utils';
import { SiteForm } from './site-form';
password_verify: undefined,
admin: true,
show_nsfw: true,
+ // The first admin signup doesn't need a captcha
+ captcha_uuid: '',
+ captcha_answer: '',
},
doneRegisteringUser: false,
userLoading: false,
this.subscription.unsubscribe();
}
- componentDidMount() {
- document.title = `${i18n.t('setup')} - Lemmy`;
+ get documentTitle(): string {
+ return `${i18n.t('setup')} - Lemmy`;
}
render() {
return (
<div class="container">
+ <Helmet title={this.documentTitle} />
<div class="row">
<div class="col-12 offset-lg-3 col-lg-6">
<h3>{i18n.t('lemmy_instance_setup')}</h3>
Community,
CommunityUser,
FollowCommunityForm,
- CommunityForm as CommunityFormI,
+ DeleteCommunityForm,
+ RemoveCommunityForm,
UserView,
-} from '../interfaces';
+ AddModToCommunityForm,
+} from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services';
-import { mdToHtml, getUnixTime, hostname } from '../utils';
+import { mdToHtml, getUnixTime } from '../utils';
import { CommunityForm } from './community-form';
import { UserListing } from './user-listing';
import { CommunityLink } from './community-link';
+import { BannerIconHeader } from './banner-icon-header';
import { i18n } from '../i18next';
interface SidebarProps {
admins: Array<UserView>;
online: number;
enableNsfw: boolean;
+ showIcon?: boolean;
}
interface SidebarState {
showRemoveDialog: boolean;
removeReason: string;
removeExpires: string;
+ showConfirmLeaveModTeam: boolean;
}
export class Sidebar extends Component<SidebarProps, SidebarState> {
showRemoveDialog: false,
removeReason: null,
removeExpires: null,
+ showConfirmLeaveModTeam: false,
};
constructor(props: any, context: any) {
}
sidebar() {
- let community = this.props.community;
- let name_: string, link: string;
-
- if (community.local) {
- name_ = community.name;
- link = `/c/${community.name}`;
- } else {
- name_ = `${community.name}@${hostname(community.actor_id)}`;
- link = community.actor_id;
- }
return (
<div>
- <div class="card border-secondary mb-3">
+ <div class="card bg-transparent border-secondary mb-3">
+ <div class="card-header bg-transparent border-secondary">
+ {this.communityTitle()}
+ {this.adminButtons()}
+ </div>
+ <div class="card-body">{this.subscribes()}</div>
+ </div>
+ <div class="card bg-transparent border-secondary mb-3">
<div class="card-body">
- <h5 className="mb-0">
- <span>{community.title}</span>
- {community.removed && (
- <small className="ml-2 text-muted font-italic">
- {i18n.t('removed')}
- </small>
- )}
- {community.deleted && (
- <small className="ml-2 text-muted font-italic">
- {i18n.t('deleted')}
- </small>
- )}
- </h5>
- <CommunityLink community={community} realLink />
- <ul class="list-inline mb-1 text-muted font-weight-bold">
- {this.canMod && (
- <>
+ {this.description()}
+ {this.badges()}
+ {this.mods()}
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ communityTitle() {
+ let community = this.props.community;
+ return (
+ <div>
+ <h5 className="mb-0">
+ {this.props.showIcon && (
+ <BannerIconHeader icon={community.icon} banner={community.banner} />
+ )}
+ <span>{community.title}</span>
+ {community.removed && (
+ <small className="ml-2 text-muted font-italic">
+ {i18n.t('removed')}
+ </small>
+ )}
+ {community.deleted && (
+ <small className="ml-2 text-muted font-italic">
+ {i18n.t('deleted')}
+ </small>
+ )}
+ {community.nsfw && (
+ <small className="ml-2 text-muted font-italic">
+ {i18n.t('nsfw')}
+ </small>
+ )}
+ </h5>
+ <CommunityLink
+ community={community}
+ realLink
+ useApubName
+ muted
+ hideAvatar
+ />
+ </div>
+ );
+ }
+
+ badges() {
+ let community = this.props.community;
+ return (
+ <ul class="my-1 list-inline">
+ <li className="list-inline-item badge badge-light">
+ {i18n.t('number_online', { count: this.props.online })}
+ </li>
+ <li className="list-inline-item badge badge-light">
+ {i18n.t('number_of_subscribers', {
+ count: community.number_of_subscribers,
+ })}
+ </li>
+ <li className="list-inline-item badge badge-light">
+ {i18n.t('number_of_posts', {
+ count: community.number_of_posts,
+ })}
+ </li>
+ <li className="list-inline-item badge badge-light">
+ {i18n.t('number_of_comments', {
+ count: community.number_of_comments,
+ })}
+ </li>
+ <li className="list-inline-item">
+ <Link className="badge badge-light" to="/communities">
+ {community.category_name}
+ </Link>
+ </li>
+ <li className="list-inline-item">
+ <Link
+ className="badge badge-light"
+ to={`/modlog/community/${this.props.community.id}`}
+ >
+ {i18n.t('modlog')}
+ </Link>
+ </li>
+ <li className="list-inline-item badge badge-light">
+ <CommunityLink community={community} realLink />
+ </li>
+ </ul>
+ );
+ }
+
+ mods() {
+ return (
+ <ul class="list-inline small">
+ <li class="list-inline-item">{i18n.t('mods')}: </li>
+ {this.props.moderators.map(mod => (
+ <li class="list-inline-item">
+ <UserListing
+ user={{
+ name: mod.user_name,
+ preferred_username: mod.user_preferred_username,
+ avatar: mod.avatar,
+ id: mod.user_id,
+ local: mod.user_local,
+ actor_id: mod.user_actor_id,
+ }}
+ />
+ </li>
+ ))}
+ </ul>
+ );
+ }
+
+ subscribes() {
+ let community = this.props.community;
+ return (
+ <div class="d-flex flex-wrap">
+ <Link
+ class={`btn btn-secondary flex-fill mr-2 mb-2 ${
+ community.deleted || community.removed ? 'no-click' : ''
+ }`}
+ to={`/create_post?community=${community.name}`}
+ >
+ {i18n.t('create_a_post')}
+ </Link>
+ {community.subscribed ? (
+ <a
+ class="btn btn-secondary flex-fill mb-2"
+ href="#"
+ onClick={linkEvent(community.id, this.handleUnsubscribe)}
+ >
+ {i18n.t('unsubscribe')}
+ </a>
+ ) : (
+ <a
+ class="btn btn-secondary flex-fill mb-2"
+ href="#"
+ onClick={linkEvent(community.id, this.handleSubscribe)}
+ >
+ {i18n.t('subscribe')}
+ </a>
+ )}
+ </div>
+ );
+ }
+
+ description() {
+ let community = this.props.community;
+ return (
+ community.description && (
+ <div
+ className="md-div"
+ dangerouslySetInnerHTML={mdToHtml(community.description)}
+ />
+ )
+ );
+ }
+
+ adminButtons() {
+ let community = this.props.community;
+ return (
+ <>
+ <ul class="list-inline mb-1 text-muted font-weight-bold">
+ {this.canMod && (
+ <>
+ <li className="list-inline-item-action">
+ <span
+ class="pointer"
+ onClick={linkEvent(this, this.handleEditClick)}
+ data-tippy-content={i18n.t('edit')}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-edit"></use>
+ </svg>
+ </span>
+ </li>
+ {!this.amCreator &&
+ (!this.state.showConfirmLeaveModTeam ? (
<li className="list-inline-item-action">
<span
class="pointer"
- onClick={linkEvent(this, this.handleEditClick)}
- data-tippy-content={i18n.t('edit')}
+ onClick={linkEvent(
+ this,
+ this.handleShowConfirmLeaveModTeamClick
+ )}
>
- <svg class="icon icon-inline">
- <use xlinkHref="#icon-edit"></use>
- </svg>
+ {i18n.t('leave_mod_team')}
</span>
</li>
- {this.amCreator && (
+ ) : (
+ <>
+ <li className="list-inline-item-action">
+ {i18n.t('are_you_sure')}
+ </li>
<li className="list-inline-item-action">
<span
class="pointer"
- onClick={linkEvent(this, this.handleDeleteClick)}
- data-tippy-content={
- !community.deleted
- ? i18n.t('delete')
- : i18n.t('restore')
- }
+ onClick={linkEvent(this, this.handleLeaveModTeamClick)}
>
- <svg
- class={`icon icon-inline ${
- community.deleted && 'text-danger'
- }`}
- >
- <use xlinkHref="#icon-trash"></use>
- </svg>
+ {i18n.t('yes')}
</span>
</li>
- )}
- </>
- )}
- {this.canAdmin && (
- <li className="list-inline-item">
- {!this.props.community.removed ? (
- <span
- class="pointer"
- onClick={linkEvent(this, this.handleModRemoveShow)}
- >
- {i18n.t('remove')}
- </span>
- ) : (
- <span
- class="pointer"
- onClick={linkEvent(this, this.handleModRemoveSubmit)}
+ <li className="list-inline-item-action">
+ <span
+ class="pointer"
+ onClick={linkEvent(
+ this,
+ this.handleCancelLeaveModTeamClick
+ )}
+ >
+ {i18n.t('no')}
+ </span>
+ </li>
+ </>
+ ))}
+ {this.amCreator && (
+ <li className="list-inline-item-action">
+ <span
+ class="pointer"
+ onClick={linkEvent(this, this.handleDeleteClick)}
+ data-tippy-content={
+ !community.deleted ? i18n.t('delete') : i18n.t('restore')
+ }
+ >
+ <svg
+ class={`icon icon-inline ${
+ community.deleted && 'text-danger'
+ }`}
>
- {i18n.t('restore')}
- </span>
- )}
+ <use xlinkHref="#icon-trash"></use>
+ </svg>
+ </span>
</li>
)}
- </ul>
- {this.state.showRemoveDialog && (
- <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
- <div class="form-group row">
- <label class="col-form-label" htmlFor="remove-reason">
- {i18n.t('reason')}
- </label>
- <input
- type="text"
- id="remove-reason"
- class="form-control mr-2"
- placeholder={i18n.t('optional')}
- value={this.state.removeReason}
- onInput={linkEvent(this, this.handleModRemoveReasonChange)}
- />
- </div>
- {/* TODO hold off on expires for now */}
- {/* <div class="form-group row"> */}
- {/* <label class="col-form-label">Expires</label> */}
- {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
- {/* </div> */}
- <div class="form-group row">
- <button type="submit" class="btn btn-secondary">
- {i18n.t('remove_community')}
- </button>
- </div>
- </form>
- )}
- <ul class="my-1 list-inline">
- {/*
- <li className="list-inline-item badge badge-secondary">
- {i18n.t('number_online', { count: this.props.online })}
- </li>
- */}
- <li className="list-inline-item badge badge-secondary">
- {i18n.t('number_of_subscribers', {
- count: community.number_of_subscribers,
- })}
- </li>
- <li className="list-inline-item badge badge-secondary">
- {i18n.t('number_of_posts', {
- count: community.number_of_posts,
- })}
- </li>
- <li className="list-inline-item badge badge-secondary">
- {i18n.t('number_of_comments', {
- count: community.number_of_comments,
- })}
- </li>
- <li className="list-inline-item">
- <Link className="badge badge-secondary" to="/communities">
- {community.category_name}
- </Link>
- </li>
- <li className="list-inline-item">
- <Link
- className="badge badge-secondary"
- to={`/modlog/community/${this.props.community.id}`}
+ </>
+ )}
+ {this.canAdmin && (
+ <li className="list-inline-item">
+ {!this.props.community.removed ? (
+ <span
+ class="pointer"
+ onClick={linkEvent(this, this.handleModRemoveShow)}
>
- {i18n.t('modlog')}
- </Link>
- </li>
- </ul>
- <ul class="list-inline small">
- <li class="list-inline-item">{i18n.t('mods')}: </li>
- {this.props.moderators.map(mod => (
- <li class="list-inline-item">
- <UserListing
- user={{
- name: mod.user_name,
- avatar: mod.avatar,
- id: mod.user_id,
- local: mod.user_local,
- actor_id: mod.user_actor_id,
- }}
- />
- </li>
- ))}
- </ul>
- {/* TODO the to= needs to be able to handle community_ids as well, since they're federated */}
- <Link
- class={`btn btn-sm btn-secondary btn-block mb-3 ${
- (community.deleted || community.removed) && 'no-click'
- }`}
- to={`/create_post?community=${community.name}`}
- >
- {i18n.t('create_a_post')}
- </Link>
- <div>
- {community.subscribed ? (
- <button
- class="btn btn-sm btn-secondary btn-block"
- onClick={linkEvent(community.id, this.handleUnsubscribe)}
- >
- {i18n.t('unsubscribe')}
- </button>
+ {i18n.t('remove')}
+ </span>
) : (
- <button
- class="btn btn-sm btn-secondary btn-block"
- onClick={linkEvent(community.id, this.handleSubscribe)}
+ <span
+ class="pointer"
+ onClick={linkEvent(this, this.handleModRemoveSubmit)}
>
- {i18n.t('subscribe')}
- </button>
+ {i18n.t('restore')}
+ </span>
)}
- </div>
- </div>
- </div>
- {community.description && (
- <div class="card border-secondary">
- <div class="card-body">
- <div
- className="md-div"
- dangerouslySetInnerHTML={mdToHtml(community.description)}
+ </li>
+ )}
+ </ul>
+ {this.state.showRemoveDialog && (
+ <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
+ <div class="form-group row">
+ <label class="col-form-label" htmlFor="remove-reason">
+ {i18n.t('reason')}
+ </label>
+ <input
+ type="text"
+ id="remove-reason"
+ class="form-control mr-2"
+ placeholder={i18n.t('optional')}
+ value={this.state.removeReason}
+ onInput={linkEvent(this, this.handleModRemoveReasonChange)}
/>
</div>
- </div>
+ {/* TODO hold off on expires for now */}
+ {/* <div class="form-group row"> */}
+ {/* <label class="col-form-label">Expires</label> */}
+ {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
+ {/* </div> */}
+ <div class="form-group row">
+ <button type="submit" class="btn btn-secondary">
+ {i18n.t('remove_community')}
+ </button>
+ </div>
+ </form>
)}
- </div>
+ </>
);
}
handleDeleteClick(i: Sidebar) {
event.preventDefault();
- let deleteForm: CommunityFormI = {
- name: i.props.community.name,
- title: i.props.community.title,
- category_id: i.props.community.category_id,
+ let deleteForm: DeleteCommunityForm = {
edit_id: i.props.community.id,
deleted: !i.props.community.deleted,
- nsfw: i.props.community.nsfw,
- auth: null,
};
- WebSocketService.Instance.editCommunity(deleteForm);
+ WebSocketService.Instance.deleteCommunity(deleteForm);
+ }
+
+ handleShowConfirmLeaveModTeamClick(i: Sidebar) {
+ i.state.showConfirmLeaveModTeam = true;
+ i.setState(i.state);
+ }
+
+ handleLeaveModTeamClick(i: Sidebar) {
+ let form: AddModToCommunityForm = {
+ user_id: UserService.Instance.user.id,
+ community_id: i.props.community.id,
+ added: false,
+ };
+ WebSocketService.Instance.addModToCommunity(form);
+ i.state.showConfirmLeaveModTeam = false;
+ i.setState(i.state);
+ }
+
+ handleCancelLeaveModTeamClick(i: Sidebar) {
+ i.state.showConfirmLeaveModTeam = false;
+ i.setState(i.state);
}
handleUnsubscribe(communityId: number) {
+ event.preventDefault();
let form: FollowCommunityForm = {
community_id: communityId,
follow: false,
}
handleSubscribe(communityId: number) {
+ event.preventDefault();
let form: FollowCommunityForm = {
community_id: communityId,
follow: true,
handleModRemoveSubmit(i: Sidebar) {
event.preventDefault();
- let deleteForm: CommunityFormI = {
- name: i.props.community.name,
- title: i.props.community.title,
- category_id: i.props.community.category_id,
+ let removeForm: RemoveCommunityForm = {
edit_id: i.props.community.id,
removed: !i.props.community.removed,
reason: i.state.removeReason,
expires: getUnixTime(i.state.removeExpires),
- nsfw: i.props.community.nsfw,
- auth: null,
};
- WebSocketService.Instance.editCommunity(deleteForm);
+ WebSocketService.Instance.removeCommunity(removeForm);
i.state.showRemoveDialog = false;
i.setState(i.state);
import { Component, linkEvent } from 'inferno';
import { Prompt } from 'inferno-router';
-import { Site, SiteForm as SiteFormI } from '../interfaces';
+import { MarkdownTextArea } from './markdown-textarea';
+import { ImageUploadForm } from './image-upload-form';
+import { Site, SiteForm as SiteFormI } from 'lemmy-js-client';
import { WebSocketService } from '../services';
-import { capitalizeFirstLetter, randomStr, setupTribute } from '../utils';
-import autosize from 'autosize';
-import Tribute from 'tributejs/src/Tribute.js';
+import { capitalizeFirstLetter, randomStr } from '../utils';
import { i18n } from '../i18next';
interface SiteFormProps {
export class SiteForm extends Component<SiteFormProps, SiteFormState> {
private id = `site-form-${randomStr()}`;
- private tribute: Tribute;
private emptyState: SiteFormState = {
siteForm: {
enable_downvotes: true,
open_registration: true,
enable_nsfw: true,
name: null,
+ icon: null,
+ banner: null,
},
loading: false,
};
constructor(props: any, context: any) {
super(props, context);
- this.tribute = setupTribute();
this.state = this.emptyState;
+ this.handleSiteDescriptionChange = this.handleSiteDescriptionChange.bind(
+ this
+ );
+
+ this.handleIconUpload = this.handleIconUpload.bind(this);
+ this.handleIconRemove = this.handleIconRemove.bind(this);
+
+ this.handleBannerUpload = this.handleBannerUpload.bind(this);
+ this.handleBannerRemove = this.handleBannerRemove.bind(this);
if (this.props.site) {
this.state.siteForm = {
enable_downvotes: this.props.site.enable_downvotes,
open_registration: this.props.site.open_registration,
enable_nsfw: this.props.site.enable_nsfw,
+ icon: this.props.site.icon,
+ banner: this.props.site.banner,
};
}
}
- componentDidMount() {
- var textarea: any = document.getElementById(this.id);
- autosize(textarea);
- this.tribute.attach(textarea);
- textarea.addEventListener('tribute-replaced', () => {
- this.state.siteForm.description = textarea.value;
- this.setState(this.state);
- autosize.update(textarea);
- });
- }
-
// Necessary to stop the loading
componentWillReceiveProps() {
this.state.loading = false;
/>
</div>
</div>
+ <div class="form-group">
+ <label>{i18n.t('icon')}</label>
+ <ImageUploadForm
+ uploadTitle={i18n.t('upload_icon')}
+ imageSrc={this.state.siteForm.icon}
+ onUpload={this.handleIconUpload}
+ onRemove={this.handleIconRemove}
+ rounded
+ />
+ </div>
+ <div class="form-group">
+ <label>{i18n.t('banner')}</label>
+ <ImageUploadForm
+ uploadTitle={i18n.t('upload_banner')}
+ imageSrc={this.state.siteForm.banner}
+ onUpload={this.handleBannerUpload}
+ onRemove={this.handleBannerRemove}
+ />
+ </div>
<div class="form-group row">
<label class="col-12 col-form-label" htmlFor={this.id}>
{i18n.t('sidebar')}
</label>
<div class="col-12">
- <textarea
- id={this.id}
- value={this.state.siteForm.description}
- onInput={linkEvent(this, this.handleSiteDescriptionChange)}
- class="form-control"
- rows={3}
- maxLength={10000}
+ <MarkdownTextArea
+ initialContent={this.state.siteForm.description}
+ onContentChange={this.handleSiteDescriptionChange}
+ hideNavigationWarnings
/>
</div>
</div>
i.setState(i.state);
}
- handleSiteDescriptionChange(i: SiteForm, event: any) {
- i.state.siteForm.description = event.target.value;
- i.setState(i.state);
+ handleSiteDescriptionChange(val: string) {
+ this.state.siteForm.description = val;
+ this.setState(this.state);
}
handleSiteEnableNsfwChange(i: SiteForm, event: any) {
handleCancel(i: SiteForm) {
i.props.onCancel();
}
+
+ handleIconUpload(url: string) {
+ this.state.siteForm.icon = url;
+ this.setState(this.state);
+ }
+
+ handleIconRemove() {
+ this.state.siteForm.icon = '';
+ this.setState(this.state);
+ }
+
+ handleBannerUpload(url: string) {
+ this.state.siteForm.banner = url;
+ this.setState(this.state);
+ }
+
+ handleBannerRemove() {
+ this.state.siteForm.banner = '';
+ this.setState(this.state);
+ }
}
import { Component, linkEvent } from 'inferno';
-import { SortType } from '../interfaces';
-import { sortingHelpUrl } from '../utils';
+import { SortType } from 'lemmy-js-client';
+import { sortingHelpUrl, randomStr } from '../utils';
import { i18n } from '../i18next';
interface SortSelectProps {
}
export class SortSelect extends Component<SortSelectProps, SortSelectState> {
+ private id = `sort-select-${randomStr()}`;
private emptyState: SortSelectState = {
sort: this.props.sort,
};
return (
<>
<select
+ id={this.id}
+ name={this.id}
value={this.state.sort}
onChange={linkEvent(this, this.handleSortChange)}
- class="custom-select custom-select-sm w-auto mr-2"
+ class="custom-select w-auto mr-2 mb-2"
>
<option disabled>{i18n.t('sort_type')}</option>
{!this.props.hideHot && (
- <option value={SortType.Hot}>{i18n.t('hot')}</option>
+ <>
+ <option value={SortType.Active}>{i18n.t('active')}</option>
+ <option value={SortType.Hot}>{i18n.t('hot')}</option>
+ </>
)}
<option value={SortType.New}>{i18n.t('new')}</option>
<option disabled>─────</option>
import { Component } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import { WebSocketService } from '../services';
import {
GetSiteResponse,
+ Site,
WebSocketJsonResponse,
UserOperation,
-} from '../interfaces';
+} from 'lemmy-js-client';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
import { repoUrl, wsJsonToRes, toast } from '../utils';
}
let general = [
+ 'Brendan',
+ 'mexicanhalloween',
+ 'William Moore',
+ 'Rachel Schmitz',
+ 'comradeda',
'ybaumy',
'dude in phx',
'twilight loki',
'Andre Vallestero',
'NotTooHighToHack',
];
-let highlighted = ['DiscountFuneral', 'Oskenso Kashi', 'Alex Benishek'];
+let highlighted = ['DQW', 'DiscountFuneral', 'Oskenso Kashi', 'Alex Benishek'];
let silver: Array<SilverUser> = [
{
name: 'Redjoker',
// let gold = [];
// let latinum = [];
-export class Sponsors extends Component<any, any> {
+interface SponsorsState {
+ site: Site;
+}
+
+export class Sponsors extends Component<any, SponsorsState> {
private subscription: Subscription;
+ private emptyState: SponsorsState = {
+ site: undefined,
+ };
constructor(props: any, context: any) {
super(props, context);
+ this.state = this.emptyState;
this.subscription = WebSocketService.Instance.subject
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
this.subscription.unsubscribe();
}
+ get documentTitle(): string {
+ if (this.state.site) {
+ return `${i18n.t('sponsors')} - ${this.state.site.name}`;
+ } else {
+ return 'Lemmy';
+ }
+ }
+
render() {
return (
<div class="container text-center">
+ <Helmet title={this.documentTitle} />
{this.topMessage()}
<hr />
{this.sponsors()}
<div class="container">
<h5>{i18n.t('sponsors')}</h5>
<p>{i18n.t('silver_sponsors')}</p>
- <div class="row card-columns">
+ <div class="row justify-content-md-center card-columns">
{silver.map(s => (
<div class="card col-12 col-md-2">
<div>
))}
</div>
<p>{i18n.t('general_sponsors')}</p>
- <div class="row card-columns">
+ <div class="row justify-content-md-center card-columns">
{highlighted.map(s => (
<div class="card bg-primary col-12 col-md-2 font-weight-bold">
<div>{s}</div>
return;
} else if (res.op == UserOperation.GetSite) {
let data = res.data as GetSiteResponse;
- document.title = `${i18n.t('sponsors')} - ${data.site.name}`;
+ this.state.site = data.site;
+ this.setState(this.state);
}
}
}
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<defs>
+ <symbol id="icon-x" viewBox="0 0 24 24">
+ <path d="M5.293 6.707l5.293 5.293-5.293 5.293c-0.391 0.391-0.391 1.024 0 1.414s1.024 0.391 1.414 0l5.293-5.293 5.293 5.293c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414l-5.293-5.293 5.293-5.293c0.391-0.391 0.391-1.024 0-1.414s-1.024-0.391-1.414 0l-5.293 5.293-5.293-5.293c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414z"></path>
+ </symbol>
+ <symbol id="icon-refresh-cw" viewBox="0 0 24 24">
+ <path d="M4.453 9.334c0.737-2.083 2.247-3.669 4.096-4.552s4.032-1.059 6.114-0.322c1.186 0.42 2.206 1.088 2.983 1.88l2.83 2.66h-3.476c-0.552 0-1 0.448-1 1s0.448 1 1 1h5.997c0.005 0 0.009 0 0.014 0 0.137-0.001 0.268-0.031 0.386-0.082 0.119-0.051 0.229-0.126 0.324-0.225 0.012-0.013 0.024-0.026 0.036-0.039 0.075-0.087 0.133-0.183 0.173-0.285s0.064-0.211 0.069-0.326c0.001-0.015 0.001-0.029 0.001-0.043v-6c0-0.552-0.448-1-1-1s-1 0.448-1 1v3.689l-2.926-2.749c-0.992-1.010-2.271-1.843-3.743-2.364-2.603-0.921-5.335-0.699-7.643 0.402s-4.199 3.086-5.12 5.689c-0.185 0.52 0.088 1.091 0.608 1.276s1.092-0.088 1.276-0.609zM2 16.312l2.955 2.777c1.929 1.931 4.49 2.908 7.048 2.909s5.119-0.975 7.072-2.927c1.104-1.104 1.901-2.407 2.361-3.745 0.18-0.522-0.098-1.091-0.621-1.271s-1.091 0.098-1.271 0.621c-0.361 1.050-0.993 2.091-1.883 2.981-1.563 1.562-3.609 2.342-5.657 2.342s-4.094-0.782-5.679-2.366l-2.8-2.633h3.475c0.552 0 1-0.448 1-1s-0.448-1-1-1h-5.997c-0.005 0-0.009 0-0.014 0-0.137 0.001-0.268 0.031-0.386 0.082-0.119 0.051-0.229 0.126-0.324 0.225-0.012 0.013-0.024 0.026-0.036 0.039-0.075 0.087-0.133 0.183-0.173 0.285s-0.064 0.211-0.069 0.326c-0.001 0.015-0.001 0.029-0.001 0.043v6c0 0.552 0.448 1 1 1s1-0.448 1-1z"></path>
+ </symbol>
+ <symbol id="icon-play" viewBox="0 0 24 24">
+ <path d="M5.541 2.159c-0.153-0.1-0.34-0.159-0.541-0.159-0.552 0-1 0.448-1 1v18c-0.001 0.182 0.050 0.372 0.159 0.541 0.299 0.465 0.917 0.599 1.382 0.3l14-9c0.114-0.072 0.219-0.174 0.3-0.3 0.299-0.465 0.164-1.083-0.3-1.382zM6 4.832l11.151 7.168-11.151 7.168z"></path>
+ </symbol>
+ <symbol id="icon-strikethrough" viewBox="0 0 28 28">
+ <path d="M27.5 14c0.281 0 0.5 0.219 0.5 0.5v1c0 0.281-0.219 0.5-0.5 0.5h-27c-0.281 0-0.5-0.219-0.5-0.5v-1c0-0.281 0.219-0.5 0.5-0.5h27zM7.547 13c-0.297-0.375-0.562-0.797-0.797-1.25-0.5-1.016-0.75-2-0.75-2.938 0-1.906 0.703-3.5 2.094-4.828s3.437-1.984 6.141-1.984c0.594 0 1.453 0.109 2.609 0.297 0.688 0.125 1.609 0.375 2.766 0.75 0.109 0.406 0.219 1.031 0.328 1.844 0.141 1.234 0.219 2.187 0.219 2.859 0 0.219-0.031 0.453-0.078 0.703l-0.187 0.047-1.313-0.094-0.219-0.031c-0.531-1.578-1.078-2.641-1.609-3.203-0.922-0.953-2.031-1.422-3.281-1.422-1.188 0-2.141 0.313-2.844 0.922s-1.047 1.375-1.047 2.281c0 0.766 0.344 1.484 1.031 2.188s2.141 1.375 4.359 2.016c0.75 0.219 1.641 0.562 2.703 1.031 0.562 0.266 1.062 0.531 1.484 0.812h-11.609zM15.469 17h6.422c0.078 0.438 0.109 0.922 0.109 1.437 0 1.125-0.203 2.234-0.641 3.313-0.234 0.578-0.594 1.109-1.109 1.625-0.375 0.359-0.938 0.781-1.703 1.266-0.781 0.469-1.563 0.828-2.391 1.031-0.828 0.219-1.875 0.328-3.172 0.328-0.859 0-1.891-0.031-3.047-0.359l-2.188-0.625c-0.609-0.172-0.969-0.313-1.125-0.438-0.063-0.063-0.125-0.172-0.125-0.344v-0.203c0-0.125 0.031-0.938-0.031-2.438-0.031-0.781 0.031-1.328 0.031-1.641v-0.688l1.594-0.031c0.578 1.328 0.844 2.125 1.016 2.406 0.375 0.609 0.797 1.094 1.25 1.469s1 0.672 1.641 0.891c0.625 0.234 1.328 0.344 2.063 0.344 0.656 0 1.391-0.141 2.172-0.422 0.797-0.266 1.437-0.719 1.906-1.344 0.484-0.625 0.734-1.297 0.734-2.016 0-0.875-0.422-1.687-1.266-2.453-0.344-0.297-1.062-0.672-2.141-1.109z"></path>
+ </symbol>
+ <symbol id="icon-header" viewBox="0 0 28 28">
+ <path d="M26.281 26c-1.375 0-2.766-0.109-4.156-0.109-1.375 0-2.75 0.109-4.125 0.109-0.531 0-0.781-0.578-0.781-1.031 0-1.391 1.563-0.797 2.375-1.328 0.516-0.328 0.516-1.641 0.516-2.188l-0.016-6.109c0-0.172 0-0.328-0.016-0.484-0.25-0.078-0.531-0.063-0.781-0.063h-10.547c-0.266 0-0.547-0.016-0.797 0.063-0.016 0.156-0.016 0.313-0.016 0.484l-0.016 5.797c0 0.594 0 2.219 0.578 2.562 0.812 0.5 2.656-0.203 2.656 1.203 0 0.469-0.219 1.094-0.766 1.094-1.453 0-2.906-0.109-4.344-0.109-1.328 0-2.656 0.109-3.984 0.109-0.516 0-0.75-0.594-0.75-1.031 0-1.359 1.437-0.797 2.203-1.328 0.5-0.344 0.516-1.687 0.516-2.234l-0.016-0.891v-12.703c0-0.75 0.109-3.156-0.594-3.578-0.781-0.484-2.453 0.266-2.453-1.141 0-0.453 0.203-1.094 0.75-1.094 1.437 0 2.891 0.109 4.328 0.109 1.313 0 2.641-0.109 3.953-0.109 0.562 0 0.781 0.625 0.781 1.094 0 1.344-1.547 0.688-2.312 1.172-0.547 0.328-0.547 1.937-0.547 2.5l0.016 5c0 0.172 0 0.328 0.016 0.5 0.203 0.047 0.406 0.047 0.609 0.047h10.922c0.187 0 0.391 0 0.594-0.047 0.016-0.172 0.016-0.328 0.016-0.5l0.016-5c0-0.578 0-2.172-0.547-2.5-0.781-0.469-2.344 0.156-2.344-1.172 0-0.469 0.219-1.094 0.781-1.094 1.375 0 2.75 0.109 4.125 0.109 1.344 0 2.688-0.109 4.031-0.109 0.562 0 0.781 0.625 0.781 1.094 0 1.359-1.609 0.672-2.391 1.156-0.531 0.344-0.547 1.953-0.547 2.516l0.016 14.734c0 0.516 0.031 1.875 0.531 2.188 0.797 0.5 2.484-0.141 2.484 1.219 0 0.453-0.203 1.094-0.75 1.094z"></path>
+ </symbol>
+ <symbol id="icon-list" viewBox="0 0 24 24">
+ <path d="M8 7h13c0.552 0 1-0.448 1-1s-0.448-1-1-1h-13c-0.552 0-1 0.448-1 1s0.448 1 1 1zM8 13h13c0.552 0 1-0.448 1-1s-0.448-1-1-1h-13c-0.552 0-1 0.448-1 1s0.448 1 1 1zM8 19h13c0.552 0 1-0.448 1-1s-0.448-1-1-1h-13c-0.552 0-1 0.448-1 1s0.448 1 1 1zM3 7c0.552 0 1-0.448 1-1s-0.448-1-1-1-1 0.448-1 1 0.448 1 1 1zM3 13c0.552 0 1-0.448 1-1s-0.448-1-1-1-1 0.448-1 1 0.448 1 1 1zM3 19c0.552 0 1-0.448 1-1s-0.448-1-1-1-1 0.448-1 1 0.448 1 1 1z"></path>
+ </symbol>
+ <symbol id="icon-italic" viewBox="0 0 24 24">
+ <path d="M13.557 5l-5.25 14h-3.307c-0.552 0-1 0.448-1 1s0.448 1 1 1h9c0.552 0 1-0.448 1-1s-0.448-1-1-1h-3.557l5.25-14h3.307c0.552 0 1-0.448 1-1s-0.448-1-1-1h-9c-0.552 0-1 0.448-1 1s0.448 1 1 1z"></path>
+ </symbol>
+ <symbol id="icon-code" viewBox="0 0 24 24">
+ <path d="M16.707 18.707l6-6c0.391-0.391 0.391-1.024 0-1.414l-6-6c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414l5.293 5.293-5.293 5.293c-0.391 0.391-0.391 1.024 0 1.414s1.024 0.391 1.414 0zM7.293 5.293l-6 6c-0.391 0.391-0.391 1.024 0 1.414l6 6c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414l-5.293-5.293 5.293-5.293c0.391-0.391 0.391-1.024 0-1.414s-1.024-0.391-1.414 0z"></path>
+ </symbol>
+ <symbol id="icon-bold" viewBox="0 0 24 24">
+ <path d="M7 11v-6h7c0.829 0 1.577 0.335 2.121 0.879s0.879 1.292 0.879 2.121-0.335 1.577-0.879 2.121-1.292 0.879-2.121 0.879zM5 12v8c0 0.552 0.448 1 1 1h9c1.38 0 2.632-0.561 3.536-1.464s1.464-2.156 1.464-3.536-0.561-2.632-1.464-3.536c-0.325-0.325-0.695-0.606-1.1-0.832 0.034-0.032 0.067-0.064 0.1-0.097 0.903-0.903 1.464-2.155 1.464-3.535s-0.561-2.632-1.464-3.536-2.156-1.464-3.536-1.464h-8c-0.552 0-1 0.448-1 1zM7 13h8c0.829 0 1.577 0.335 2.121 0.879s0.879 1.292 0.879 2.121-0.335 1.577-0.879 2.121-1.292 0.879-2.121 0.879h-8z"></path>
+ </symbol>
+ <symbol id="icon-format_quote" viewBox="0 0 24 24">
+ <path d="M14.016 17.016l1.969-4.031h-3v-6h6v6l-1.969 4.031h-3zM6 17.016l2.016-4.031h-3v-6h6v6l-2.016 4.031h-3z"></path>
+ </symbol>
<symbol id="icon-settings" viewBox="0 0 24 24">
<path d="M16 12c0-1.104-0.449-2.106-1.172-2.828s-1.724-1.172-2.828-1.172-2.106 0.449-2.828 1.172-1.172 1.724-1.172 2.828 0.449 2.106 1.172 2.828 1.724 1.172 2.828 1.172 2.106-0.449 2.828-1.172 1.172-1.724 1.172-2.828zM14 12c0 0.553-0.223 1.051-0.586 1.414s-0.861 0.586-1.414 0.586-1.051-0.223-1.414-0.586-0.586-0.861-0.586-1.414 0.223-1.051 0.586-1.414 0.861-0.586 1.414-0.586 1.051 0.223 1.414 0.586 0.586 0.861 0.586 1.414zM20.315 15.404c0.046-0.105 0.112-0.191 0.192-0.257 0.112-0.092 0.251-0.146 0.403-0.147h0.090c0.828 0 1.58-0.337 2.121-0.879s0.879-1.293 0.879-2.121-0.337-1.58-0.879-2.121-1.293-0.879-2.121-0.879h-0.159c-0.11-0.001-0.215-0.028-0.308-0.076-0.127-0.066-0.23-0.172-0.292-0.312-0.003-0.029-0.004-0.059-0.004-0.089-0.024-0.055-0.040-0.111-0.049-0.168 0.020-0.334 0.077-0.454 0.168-0.547l0.062-0.062c0.585-0.586 0.878-1.356 0.877-2.122s-0.294-1.536-0.881-2.122c-0.586-0.585-1.356-0.878-2.122-0.877s-1.536 0.294-2.12 0.879l-0.046 0.046c-0.083 0.080-0.183 0.136-0.288 0.166-0.14 0.039-0.291 0.032-0.438-0.033-0.101-0.044-0.187-0.11-0.253-0.19-0.092-0.112-0.146-0.251-0.147-0.403v-0.090c0-0.828-0.337-1.58-0.879-2.121s-1.293-0.879-2.121-0.879-1.58 0.337-2.121 0.879-0.879 1.293-0.879 2.121v0.159c-0.001 0.11-0.028 0.215-0.076 0.308-0.066 0.127-0.172 0.23-0.312 0.292-0.029 0.003-0.059 0.004-0.089 0.004-0.055 0.024-0.111 0.040-0.168 0.049-0.335-0.021-0.455-0.078-0.548-0.169l-0.062-0.062c-0.586-0.585-1.355-0.878-2.122-0.878s-1.535 0.294-2.122 0.882c-0.585 0.586-0.878 1.355-0.878 2.122s0.294 1.536 0.879 2.12l0.048 0.047c0.080 0.083 0.136 0.183 0.166 0.288 0.039 0.14 0.032 0.291-0.031 0.434-0.006 0.016-0.013 0.034-0.021 0.052-0.041 0.109-0.108 0.203-0.191 0.275-0.11 0.095-0.25 0.153-0.383 0.156h-0.090c-0.828 0-1.58 0.337-2.121 0.879s-0.879 1.294-0.879 2.122 0.337 1.58 0.879 2.121 1.293 0.879 2.121 0.879h0.159c0.11 0.001 0.215 0.028 0.308 0.076 0.128 0.067 0.233 0.174 0.296 0.321 0.024 0.055 0.040 0.111 0.049 0.168-0.020 0.334-0.077 0.454-0.168 0.547l-0.062 0.062c-0.585 0.586-0.878 1.356-0.877 2.122s0.294 1.536 0.881 2.122c0.586 0.585 1.356 0.878 2.122 0.877s1.536-0.294 2.12-0.879l0.047-0.048c0.083-0.080 0.183-0.136 0.288-0.166 0.14-0.039 0.291-0.032 0.434 0.031 0.016 0.006 0.034 0.013 0.052 0.021 0.109 0.041 0.203 0.108 0.275 0.191 0.095 0.11 0.153 0.25 0.156 0.383v0.092c0 0.828 0.337 1.58 0.879 2.121s1.293 0.879 2.121 0.879 1.58-0.337 2.121-0.879 0.879-1.293 0.879-2.121v-0.159c0.001-0.11 0.028-0.215 0.076-0.308 0.067-0.128 0.174-0.233 0.321-0.296 0.055-0.024 0.111-0.040 0.168-0.049 0.334 0.020 0.454 0.077 0.547 0.168l0.062 0.062c0.586 0.585 1.356 0.878 2.122 0.877s1.536-0.294 2.122-0.881c0.585-0.586 0.878-1.356 0.877-2.122s-0.294-1.536-0.879-2.12l-0.048-0.047c-0.080-0.083-0.136-0.183-0.166-0.288-0.039-0.14-0.032-0.291 0.031-0.434zM18.396 9.302c-0.012-0.201-0.038-0.297-0.076-0.382v0.080c0 0.043 0.003 0.084 0.008 0.125 0.021 0.060 0.043 0.119 0.068 0.177 0.004 0.090 0.005 0.091 0.005 0.092 0.249 0.581 0.684 1.030 1.208 1.303 0.371 0.193 0.785 0.298 1.211 0.303h0.18c0.276 0 0.525 0.111 0.707 0.293s0.293 0.431 0.293 0.707-0.111 0.525-0.293 0.707-0.431 0.293-0.707 0.293h-0.090c-0.637 0.003-1.22 0.228-1.675 0.603-0.323 0.266-0.581 0.607-0.75 0.993-0.257 0.582-0.288 1.21-0.127 1.782 0.119 0.423 0.341 0.814 0.652 1.136l0.072 0.073c0.196 0.196 0.294 0.45 0.294 0.707s-0.097 0.512-0.292 0.707c-0.197 0.197-0.451 0.295-0.709 0.295s-0.512-0.097-0.707-0.292l-0.061-0.061c-0.463-0.453-1.040-0.702-1.632-0.752-0.437-0.037-0.882 0.034-1.293 0.212-0.578 0.248-1.027 0.683-1.3 1.206-0.193 0.371-0.298 0.785-0.303 1.211v0.181c0 0.276-0.111 0.525-0.293 0.707s-0.43 0.292-0.706 0.292-0.525-0.111-0.707-0.293-0.293-0.431-0.293-0.707v-0.090c-0.015-0.66-0.255-1.242-0.644-1.692-0.284-0.328-0.646-0.585-1.058-0.744-0.575-0.247-1.193-0.274-1.756-0.116-0.423 0.119-0.814 0.341-1.136 0.652l-0.073 0.072c-0.196 0.196-0.45 0.294-0.707 0.294s-0.512-0.097-0.707-0.292c-0.197-0.197-0.295-0.451-0.295-0.709s0.097-0.512 0.292-0.707l0.061-0.061c0.453-0.463 0.702-1.040 0.752-1.632 0.037-0.437-0.034-0.882-0.212-1.293-0.248-0.578-0.683-1.027-1.206-1.3-0.371-0.193-0.785-0.298-1.211-0.303l-0.18 0.001c-0.276 0-0.525-0.111-0.707-0.293s-0.293-0.431-0.293-0.707 0.111-0.525 0.293-0.707 0.431-0.293 0.707-0.293h0.090c0.66-0.015 1.242-0.255 1.692-0.644 0.328-0.284 0.585-0.646 0.744-1.058 0.247-0.575 0.274-1.193 0.116-1.756-0.119-0.423-0.341-0.814-0.652-1.136l-0.073-0.073c-0.196-0.196-0.294-0.45-0.294-0.707s0.097-0.512 0.292-0.707c0.197-0.197 0.451-0.295 0.709-0.295s0.512 0.097 0.707 0.292l0.061 0.061c0.463 0.453 1.040 0.702 1.632 0.752 0.37 0.032 0.745-0.014 1.101-0.137 0.096-0.012 0.186-0.036 0.266-0.072-0.031 0.001-0.061 0.003-0.089 0.004-0.201 0.012-0.297 0.038-0.382 0.076h0.080c0.043 0 0.084-0.003 0.125-0.008 0.060-0.021 0.119-0.043 0.177-0.068 0.090-0.004 0.091-0.005 0.092-0.005 0.581-0.249 1.030-0.684 1.303-1.208 0.193-0.37 0.298-0.785 0.303-1.21v-0.181c0-0.276 0.111-0.525 0.293-0.707s0.431-0.293 0.707-0.293 0.525 0.111 0.707 0.293 0.293 0.431 0.293 0.707v0.090c0.003 0.637 0.228 1.22 0.603 1.675 0.266 0.323 0.607 0.581 0.996 0.751 0.578 0.255 1.206 0.286 1.778 0.125 0.423-0.119 0.814-0.341 1.136-0.652l0.073-0.072c0.196-0.196 0.45-0.294 0.707-0.294s0.512 0.097 0.707 0.292c0.197 0.197 0.295 0.451 0.295 0.709s-0.097 0.512-0.292 0.707l-0.061 0.061c-0.453 0.463-0.702 1.040-0.752 1.632-0.032 0.37 0.014 0.745 0.137 1.101 0.012 0.095 0.037 0.185 0.072 0.266-0.001-0.032-0.002-0.062-0.004-0.089z"></path>
</symbol>
<symbol id="icon-cake" viewBox="0 0 24 24">
<path d="M 23.296875 22.394531 L 22.082031 22.394531 L 22.082031 17.007812 C 22.453125 16.699219 22.664062 16.261719 22.664062 15.796875 L 22.664062 13.984375 C 22.664062 12.996094 21.785156 12.191406 20.703125 12.191406 L 19.785156 12.191406 L 19.785156 7.785156 C 19.785156 7.050781 19.1875 6.449219 18.449219 6.449219 L 18.367188 6.449219 L 18.367188 5.96875 C 19.199219 5.675781 19.796875 4.882812 19.796875 3.957031 C 19.796875 3.644531 19.703125 3.117188 18.996094 1.800781 C 18.632812 1.121094 18.273438 0.550781 18.257812 0.527344 C 18.128906 0.320312 17.90625 0.199219 17.664062 0.199219 C 17.421875 0.199219 17.199219 0.320312 17.070312 0.527344 C 17.054688 0.550781 16.695312 1.121094 16.332031 1.800781 C 15.621094 3.117188 15.53125 3.644531 15.53125 3.957031 C 15.53125 4.882812 16.128906 5.675781 16.960938 5.96875 L 16.960938 6.449219 L 16.878906 6.449219 C 16.140625 6.449219 15.542969 7.050781 15.542969 7.785156 L 15.542969 12.191406 L 14.121094 12.191406 L 14.121094 7.785156 C 14.121094 7.050781 13.523438 6.449219 12.785156 6.449219 L 12.703125 6.449219 L 12.703125 5.96875 C 13.535156 5.675781 14.132812 4.882812 14.132812 3.957031 C 14.132812 3.644531 14.039062 3.117188 13.332031 1.800781 C 12.96875 1.121094 12.609375 0.550781 12.59375 0.527344 C 12.464844 0.320312 12.242188 0.199219 12 0.199219 C 11.757812 0.199219 11.535156 0.320312 11.40625 0.527344 C 11.390625 0.550781 11.03125 1.121094 10.667969 1.800781 C 9.960938 3.117188 9.867188 3.644531 9.867188 3.957031 C 9.867188 4.882812 10.464844 5.675781 11.296875 5.96875 L 11.296875 6.449219 L 11.214844 6.449219 C 10.476562 6.449219 9.878906 7.050781 9.878906 7.785156 L 9.878906 12.191406 L 8.457031 12.191406 L 8.457031 7.785156 C 8.457031 7.050781 7.859375 6.449219 7.121094 6.449219 L 7.039062 6.449219 L 7.039062 5.96875 C 7.871094 5.675781 8.46875 4.882812 8.46875 3.957031 C 8.46875 3.644531 8.378906 3.117188 7.667969 1.800781 C 7.304688 1.121094 6.945312 0.550781 6.929688 0.527344 C 6.800781 0.320312 6.578125 0.199219 6.335938 0.199219 C 6.09375 0.199219 5.871094 0.320312 5.742188 0.527344 C 5.726562 0.550781 5.367188 1.121094 5.003906 1.800781 C 4.296875 3.117188 4.203125 3.644531 4.203125 3.957031 C 4.203125 4.882812 4.800781 5.675781 5.632812 5.96875 L 5.632812 6.449219 L 5.550781 6.449219 C 4.8125 6.449219 4.214844 7.050781 4.214844 7.785156 L 4.214844 12.191406 L 3.296875 12.191406 C 2.214844 12.191406 1.335938 12.996094 1.335938 13.984375 L 1.335938 15.796875 C 1.335938 16.261719 1.546875 16.699219 1.917969 17.007812 L 1.917969 22.394531 L 0.703125 22.394531 C 0.316406 22.394531 0 22.710938 0 23.097656 C 0 23.488281 0.316406 23.800781 0.703125 23.800781 L 23.296875 23.800781 C 23.683594 23.800781 24 23.488281 24 23.097656 C 24 22.710938 23.683594 22.394531 23.296875 22.394531 Z M 16.9375 3.957031 C 16.941406 3.730469 17.246094 3.054688 17.664062 2.289062 C 18.082031 3.054688 18.382812 3.730469 18.390625 3.957031 C 18.390625 4.355469 18.0625 4.679688 17.664062 4.679688 C 17.265625 4.679688 16.9375 4.355469 16.9375 3.957031 Z M 16.949219 7.855469 L 18.378906 7.855469 L 18.378906 12.1875 L 16.949219 12.1875 Z M 11.273438 3.957031 C 11.277344 3.730469 11.582031 3.054688 12 2.289062 C 12.417969 3.054688 12.722656 3.730469 12.726562 3.957031 C 12.726562 4.355469 12.398438 4.679688 12 4.679688 C 11.601562 4.679688 11.273438 4.355469 11.273438 3.957031 Z M 11.285156 7.855469 L 12.714844 7.855469 L 12.714844 12.1875 L 11.285156 12.1875 Z M 5.609375 3.957031 C 5.613281 3.730469 5.917969 3.054688 6.335938 2.289062 C 6.753906 3.054688 7.058594 3.730469 7.0625 3.957031 C 7.0625 4.355469 6.734375 4.679688 6.335938 4.679688 C 5.9375 4.679688 5.609375 4.355469 5.609375 3.957031 Z M 5.621094 7.855469 L 7.050781 7.855469 L 7.050781 12.1875 L 5.621094 12.1875 Z M 20.675781 22.394531 L 3.324219 22.394531 L 3.324219 17.414062 C 3.433594 17.398438 3.546875 17.378906 3.652344 17.347656 L 5.429688 16.820312 C 6.453125 16.515625 7.582031 16.515625 8.609375 16.820312 L 10.011719 17.234375 C 10.652344 17.425781 11.324219 17.519531 12 17.519531 C 12.675781 17.519531 13.347656 17.425781 13.988281 17.234375 L 15.390625 16.820312 C 16.417969 16.515625 17.546875 16.515625 18.570312 16.820312 L 20.347656 17.347656 C 20.453125 17.378906 20.5625 17.398438 20.675781 17.414062 Z M 21.257812 15.796875 C 21.257812 15.855469 21.210938 15.902344 21.171875 15.933594 C 21.082031 16 20.925781 16.050781 20.746094 15.996094 L 18.972656 15.472656 C 17.6875 15.09375 16.273438 15.09375 14.992188 15.472656 L 13.589844 15.886719 C 12.566406 16.191406 11.433594 16.191406 10.410156 15.886719 L 9.007812 15.472656 C 8.367188 15.28125 7.691406 15.1875 7.019531 15.1875 C 6.34375 15.1875 5.671875 15.28125 5.027344 15.472656 L 3.253906 15.996094 C 3.074219 16.050781 2.917969 16 2.828125 15.933594 C 2.789062 15.902344 2.742188 15.855469 2.742188 15.796875 L 2.742188 13.984375 C 2.742188 13.800781 2.96875 13.597656 3.296875 13.597656 L 20.703125 13.597656 C 21.03125 13.597656 21.257812 13.800781 21.257812 13.984375 Z M 21.257812 15.796875 " />
</symbol>
+ <symbol id="icon-subscript" viewBox="0 0 20 20">
+ <path d="M13.68 16h-2.42a.67.67 0 0 1-.46-.15 1.33 1.33 0 0 1-.28-.34l-2.77-4.44a2.65 2.65 0 0 1-.28.69L5 15.51a2.22 2.22 0 0 1-.29.34.58.58 0 0 1-.42.15H2l4.15-6.19L2.17 4h2.42a.81.81 0 0 1 .41.09.8.8 0 0 1 .24.26L8 8.59a2.71 2.71 0 0 1 .33-.74L10.6 4.4a.69.69 0 0 1 .6-.4h2.32l-4 5.71zm3.82-4h.5v-1h-.5a1.49 1.49 0 0 0-1 .39 1.49 1.49 0 0 0-1-.39H15v1h.5a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-.5.5H15v1h.5a1.49 1.49 0 0 0 1-.39 1.49 1.49 0 0 0 1 .39h.5v-1h-.5a.5.5 0 0 1-.5-.5v-6a.5.5 0 0 1 .5-.5z" />
+ </symbol>
+ <symbol id="icon-superscript" viewBox="0 0 20 20">
+ <path d="M17.5 1h.5V0h-.5a1.49 1.49 0 0 0-1 .39 1.49 1.49 0 0 0-1-.39H15v1h.5a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-.5.5H15v1h.5a1.49 1.49 0 0 0 1-.39 1.49 1.49 0 0 0 1 .39h.5V8h-.5a.5.5 0 0 1-.5-.5v-6a.5.5 0 0 1 .5-.5zm-3.82 15h-2.42a.67.67 0 0 1-.46-.15 1.33 1.33 0 0 1-.28-.34l-2.77-4.44a2.65 2.65 0 0 1-.28.69L5 15.51a2.22 2.22 0 0 1-.29.34.58.58 0 0 1-.42.15H2l4.15-6.19L2.17 4h2.42a.81.81 0 0 1 .41.09.8.8 0 0 1 .24.26L8 8.59a2.71 2.71 0 0 1 .33-.74L10.6 4.4a.69.69 0 0 1 .6-.4h2.32l-4 5.71z" />
+ </symbol>
</defs>
</svg>
);
import { Component, linkEvent } from 'inferno';
import { WebSocketService, UserService } from '../services';
import { Subscription } from 'rxjs';
-import { retryWhen, delay, take, last } from 'rxjs/operators';
+import { retryWhen, delay, take } from 'rxjs/operators';
import { i18n } from '../i18next';
import {
UserOperation,
UserDetailsResponse,
UserView,
WebSocketJsonResponse,
- UserDetailsView,
CommentResponse,
BanUserResponse,
PostResponse,
- AddAdminResponse,
-} from '../interfaces';
+} from 'lemmy-js-client';
+import { UserDetailsView } from '../interfaces';
import {
wsJsonToRes,
toast,
user_id?: number;
page: number;
limit: number;
- sort: string;
+ sort: SortType;
enableDownvotes: boolean;
enableNsfw: boolean;
view: UserDetailsView;
onPageChange(page: number): number | any;
+ admins: Array<UserView>;
}
interface UserDetailsState {
comments: Array<Comment>;
posts: Array<Post>;
saved?: Array<Post>;
- admins: Array<UserView>;
}
export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
comments: [],
posts: [],
saved: [],
- admins: [],
};
this.subscription = WebSocketService.Instance.subject
componentDidMount() {
this.fetchUserData();
+ setupTippy();
}
componentDidUpdate(lastProps: UserDetailsProps) {
break;
}
}
- setupTippy();
}
fetchUserData() {
];
// Sort it
- if (SortType[this.props.sort] === SortType.New) {
+ if (this.props.sort === SortType.New) {
combined.sort((a, b) => b.data.published.localeCompare(a.data.published));
} else {
combined.sort((a, b) => b.data.score - a.data.score);
return (
<div>
{combined.map(i => (
- <div>
- {i.type === 'posts' ? (
- <PostListing
- post={i.data as Post}
- admins={this.state.admins}
- showCommunity
- enableDownvotes={this.props.enableDownvotes}
- enableNsfw={this.props.enableNsfw}
- />
- ) : (
- <CommentNodes
- nodes={[{ comment: i.data as Comment }]}
- admins={this.state.admins}
- noIndent
- showContext
- enableDownvotes={this.props.enableDownvotes}
- />
- )}
- </div>
+ <>
+ <div>
+ {i.type === 'posts' ? (
+ <PostListing
+ key={(i.data as Post).id}
+ post={i.data as Post}
+ admins={this.props.admins}
+ showCommunity
+ enableDownvotes={this.props.enableDownvotes}
+ enableNsfw={this.props.enableNsfw}
+ />
+ ) : (
+ <CommentNodes
+ key={(i.data as Comment).id}
+ nodes={[{ comment: i.data as Comment }]}
+ admins={this.props.admins}
+ noBorder
+ noIndent
+ showCommunity
+ showContext
+ enableDownvotes={this.props.enableDownvotes}
+ />
+ )}
+ </div>
+ <hr class="my-3" />
+ </>
))}
</div>
);
<div>
<CommentNodes
nodes={commentsToFlatNodes(this.state.comments)}
- admins={this.state.admins}
+ admins={this.props.admins}
noIndent
+ showCommunity
showContext
enableDownvotes={this.props.enableDownvotes}
/>
return (
<div>
{this.state.posts.map(post => (
- <PostListing
- post={post}
- admins={this.state.admins}
- showCommunity
- enableDownvotes={this.props.enableDownvotes}
- enableNsfw={this.props.enableNsfw}
- />
+ <>
+ <PostListing
+ post={post}
+ admins={this.props.admins}
+ showCommunity
+ enableDownvotes={this.props.enableDownvotes}
+ enableNsfw={this.props.enableNsfw}
+ />
+ <hr class="my-3" />
+ </>
))}
</div>
);
<div class="my-2">
{this.props.page > 1 && (
<button
- class="btn btn-sm btn-secondary mr-1"
+ class="btn btn-secondary mr-1"
onClick={linkEvent(this, this.prevPage)}
>
{i18n.t('prev')}
)}
{this.state.comments.length + this.state.posts.length > 0 && (
<button
- class="btn btn-sm btn-secondary"
+ class="btn btn-secondary"
onClick={linkEvent(this, this.nextPage)}
>
{i18n.t('next')}
}
parseMessage(msg: WebSocketJsonResponse) {
+ console.log(msg);
const res = wsJsonToRes(msg);
if (msg.error) {
follows: data.follows,
moderates: data.moderates,
posts: data.posts,
- admins: data.admins,
});
} else if (res.op == UserOperation.CreateCommentLike) {
const data = res.data as CommentResponse;
this.setState({
comments: this.state.comments,
});
- } else if (res.op == UserOperation.EditComment) {
+ } else if (
+ res.op == UserOperation.EditComment ||
+ res.op == UserOperation.DeleteComment ||
+ res.op == UserOperation.RemoveComment
+ ) {
const data = res.data as CommentResponse;
editCommentRes(data, this.state.comments);
this.setState({
posts: this.state.posts,
comments: this.state.comments,
});
- } else if (res.op == UserOperation.AddAdmin) {
- const data = res.data as AddAdminResponse;
- this.setState({
- admins: data.admins,
- });
}
}
}
import { Component } from 'inferno';
import { Link } from 'inferno-router';
-import { UserView } from '../interfaces';
+import { UserView } from 'lemmy-js-client';
import {
pictrsAvatarThumbnail,
showAvatars,
} from '../utils';
import { CakeDay } from './cake-day';
-interface UserOther {
+export interface UserOther {
name: string;
+ preferred_username?: string;
id?: number; // Necessary if its federated
avatar?: string;
local?: boolean;
interface UserListingProps {
user: UserView | UserOther;
realLink?: boolean;
+ useApubName?: boolean;
+ muted?: boolean;
+ hideAvatar?: boolean;
}
export class UserListing extends Component<UserListingProps, any> {
render() {
let user = this.props.user;
let local = user.local == null ? true : user.local;
- let name_: string, link: string;
+ let apubName: string, link: string;
if (local) {
- name_ = user.name;
+ apubName = `@${user.name}`;
link = `/u/${user.name}`;
} else {
- name_ = `${user.name}@${hostname(user.actor_id)}`;
+ apubName = `@${user.name}@${hostname(user.actor_id)}`;
link = !this.props.realLink ? `/user/${user.id}` : user.actor_id;
}
+ let displayName = this.props.useApubName
+ ? apubName
+ : user.preferred_username
+ ? user.preferred_username
+ : apubName;
+
return (
<>
- <Link className="text-body font-weight-bold" to={link}>
- {user.avatar && showAvatars() && (
+ <Link
+ title={apubName}
+ className={this.props.muted ? 'text-muted' : 'text-info'}
+ to={link}
+ >
+ {!this.props.hideAvatar && user.avatar && showAvatars() && (
<img
- height="32"
- width="32"
+ style="width: 2rem; height: 2rem;"
src={pictrsAvatarThumbnail(user.avatar)}
class="rounded-circle mr-2"
/>
)}
- <span>{name_}</span>
+ <span>{displayName}</span>
</Link>
- {isCakeDay(user.published) && <CakeDay creatorName={name_} />}
+ {isCakeDay(user.published) && <CakeDay creatorName={apubName} />}
</>
);
}
import { Component, linkEvent } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Link } from 'inferno-router';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
DeleteAccountForm,
WebSocketJsonResponse,
GetSiteResponse,
- Site,
- UserDetailsView,
UserDetailsResponse,
-} from '../interfaces';
+ AddAdminResponse,
+} from 'lemmy-js-client';
+import { UserDetailsView } from '../interfaces';
import { WebSocketService, UserService } from '../services';
import {
wsJsonToRes,
themes,
setTheme,
languages,
- showAvatars,
toast,
setupTippy,
+ getLanguage,
+ mdToHtml,
+ elementUrl,
+ favIconUrl,
} from '../utils';
import { UserListing } from './user-listing';
import { SortSelect } from './sort-select';
import { i18n } from '../i18next';
import moment from 'moment';
import { UserDetails } from './user-details';
+import { MarkdownTextArea } from './markdown-textarea';
+import { ImageUploadForm } from './image-upload-form';
+import { BannerIconHeader } from './banner-icon-header';
interface UserState {
user: UserView;
sort: SortType;
page: number;
loading: boolean;
- avatarLoading: boolean;
userSettingsForm: UserSettingsForm;
userSettingsLoading: boolean;
deleteAccountLoading: boolean;
deleteAccountShowConfirm: boolean;
deleteAccountForm: DeleteAccountForm;
- site: Site;
+ siteRes: GetSiteResponse;
}
interface UserProps {
interface UrlParams {
view?: string;
- sort?: string;
+ sort?: SortType;
page?: number;
}
comment_score: null,
banned: null,
avatar: null,
- show_avatars: null,
- send_notifications_to_email: null,
actor_id: null,
local: null,
},
username: null,
follows: [],
moderates: [],
- loading: false,
- avatarLoading: false,
+ loading: true,
view: User.getViewFromProps(this.props.match.view),
sort: User.getSortTypeFromProps(this.props.match.sort),
page: User.getPageFromProps(this.props.match.page),
show_avatars: null,
send_notifications_to_email: null,
auth: null,
+ bio: null,
+ preferred_username: null,
},
userSettingsLoading: null,
deleteAccountLoading: null,
deleteAccountForm: {
password: null,
},
- site: {
- id: undefined,
- name: undefined,
- creator_id: undefined,
- published: undefined,
- creator_name: undefined,
- number_of_users: undefined,
- number_of_posts: undefined,
- number_of_comments: undefined,
- number_of_communities: undefined,
- enable_downvotes: undefined,
- open_registration: undefined,
- enable_nsfw: undefined,
+ siteRes: {
+ admins: [],
+ banned: [],
+ online: undefined,
+ site: {
+ id: undefined,
+ name: undefined,
+ creator_id: undefined,
+ published: undefined,
+ creator_name: undefined,
+ number_of_users: undefined,
+ number_of_posts: undefined,
+ number_of_comments: undefined,
+ number_of_communities: undefined,
+ enable_downvotes: undefined,
+ open_registration: undefined,
+ enable_nsfw: undefined,
+ icon: undefined,
+ banner: undefined,
+ creator_preferred_username: undefined,
+ },
+ version: undefined,
+ my_user: undefined,
+ federated_instances: undefined,
},
};
this
);
this.handlePageChange = this.handlePageChange.bind(this);
+ this.handleUserSettingsBioChange = this.handleUserSettingsBioChange.bind(
+ this
+ );
+
+ this.handleAvatarUpload = this.handleAvatarUpload.bind(this);
+ this.handleAvatarRemove = this.handleAvatarRemove.bind(this);
+
+ this.handleBannerUpload = this.handleBannerUpload.bind(this);
+ this.handleBannerRemove = this.handleBannerRemove.bind(this);
this.state.user_id = Number(this.props.match.params.id) || null;
this.state.username = this.props.match.params.username;
);
WebSocketService.Instance.getSite();
+ setupTippy();
}
get isCurrentUser() {
);
}
- static getViewFromProps(view: any): UserDetailsView {
- return view
- ? UserDetailsView[capitalizeFirstLetter(view)]
- : UserDetailsView.Overview;
+ static getViewFromProps(view: string): UserDetailsView {
+ return view ? UserDetailsView[view] : UserDetailsView.Overview;
}
- static getSortTypeFromProps(sort: any): SortType {
+ static getSortTypeFromProps(sort: string): SortType {
return sort ? routeSortTypeToEnum(sort) : SortType.New;
}
- static getPageFromProps(page: any): number {
+ static getPageFromProps(page: number): number {
return page ? Number(page) : 1;
}
// Couldnt get a refresh working. This does for now.
location.reload();
}
- document.title = `/u/${this.state.username} - ${this.state.site.name}`;
- setupTippy();
+ }
+
+ get documentTitle(): string {
+ if (this.state.siteRes.site.name) {
+ return `@${this.state.username} - ${this.state.siteRes.site.name}`;
+ } else {
+ return 'Lemmy';
+ }
+ }
+
+ get favIcon(): string {
+ return this.state.siteRes.site.icon
+ ? this.state.siteRes.site.icon
+ : favIconUrl;
}
render() {
return (
<div class="container">
- {this.state.loading ? (
- <h5>
- <svg class="icon icon-spinner spin">
- <use xlinkHref="#icon-spinner"></use>
- </svg>
- </h5>
- ) : (
- <div class="row">
- <div class="col-12 col-md-8">
+ <Helmet title={this.documentTitle}>
+ <link
+ id="favicon"
+ rel="icon"
+ type="image/x-icon"
+ href={this.favIcon}
+ />
+ </Helmet>
+ <div class="row">
+ <div class="col-12 col-md-8">
+ {this.state.loading ? (
<h5>
- {this.state.user.avatar && showAvatars() && (
- <img
- height="80"
- width="80"
- src={this.state.user.avatar}
- class="rounded-circle mr-2"
- />
- )}
- <span>/u/{this.state.username}</span>
+ <svg class="icon icon-spinner spin">
+ <use xlinkHref="#icon-spinner"></use>
+ </svg>
</h5>
- {this.selects()}
- <UserDetails
- user_id={this.state.user_id}
- username={this.state.username}
- sort={SortType[this.state.sort]}
- page={this.state.page}
- limit={fetchLimit}
- enableDownvotes={this.state.site.enable_downvotes}
- enableNsfw={this.state.site.enable_nsfw}
- view={this.state.view}
- onPageChange={this.handlePageChange}
- />
- </div>
+ ) : (
+ <>
+ {this.userInfo()}
+ <hr />
+ </>
+ )}
+ {!this.state.loading && this.selects()}
+ <UserDetails
+ user_id={this.state.user_id}
+ username={this.state.username}
+ sort={this.state.sort}
+ page={this.state.page}
+ limit={fetchLimit}
+ enableDownvotes={this.state.siteRes.site.enable_downvotes}
+ enableNsfw={this.state.siteRes.site.enable_nsfw}
+ admins={this.state.siteRes.admins}
+ view={this.state.view}
+ onPageChange={this.handlePageChange}
+ />
+ </div>
+
+ {!this.state.loading && (
<div class="col-12 col-md-4">
- {this.userInfo()}
{this.isCurrentUser && this.userSettings()}
{this.moderates()}
{this.follows()}
</div>
- </div>
- )}
+ )}
+ </div>
</div>
);
}
viewRadios() {
return (
- <div class="btn-group btn-group-toggle">
+ <div class="btn-group btn-group-toggle flex-wrap mb-2">
<label
- className={`btn btn-sm btn-secondary pointer btn-outline-light
+ className={`btn btn-outline-secondary pointer
${this.state.view == UserDetailsView.Overview && 'active'}
`}
>
{i18n.t('overview')}
</label>
<label
- className={`btn btn-sm btn-secondary pointer btn-outline-light
+ className={`btn btn-outline-secondary pointer
${this.state.view == UserDetailsView.Comments && 'active'}
`}
>
{i18n.t('comments')}
</label>
<label
- className={`btn btn-sm btn-secondary pointer btn-outline-light
+ className={`btn btn-outline-secondary pointer
${this.state.view == UserDetailsView.Posts && 'active'}
`}
>
{i18n.t('posts')}
</label>
<label
- className={`btn btn-sm btn-secondary pointer btn-outline-light
+ className={`btn btn-outline-secondary pointer
${this.state.view == UserDetailsView.Saved && 'active'}
`}
>
hideHot
/>
<a
- href={`/feeds/u/${this.state.username}.xml?sort=${
- SortType[this.state.sort]
- }`}
+ href={`/feeds/u/${this.state.username}.xml?sort=${this.state.sort}`}
target="_blank"
rel="noopener"
title="RSS"
userInfo() {
let user = this.state.user;
+
return (
<div>
- <div class="card border-secondary mb-3">
- <div class="card-body">
- <h5>
- <ul class="list-inline mb-0">
- <li className="list-inline-item">
- <UserListing user={user} realLink />
- </li>
- {user.banned && (
- <li className="list-inline-item badge badge-danger">
- {i18n.t('banned')}
- </li>
+ <BannerIconHeader
+ banner={this.state.user.banner}
+ icon={this.state.user.avatar}
+ />
+ <div class="mb-3">
+ <div class="">
+ <div class="mb-0 d-flex flex-wrap">
+ <div>
+ {user.preferred_username && (
+ <h5 class="mb-0">{user.preferred_username}</h5>
)}
+ <ul class="list-inline mb-2">
+ <li className="list-inline-item">
+ <UserListing
+ user={user}
+ realLink
+ useApubName
+ muted
+ hideAvatar
+ />
+ </li>
+ {user.banned && (
+ <li className="list-inline-item badge badge-danger">
+ {i18n.t('banned')}
+ </li>
+ )}
+ </ul>
+ </div>
+ <div className="flex-grow-1 unselectable pointer mx-2"></div>
+ {this.isCurrentUser ? (
+ <button
+ class="d-flex align-self-start btn btn-secondary ml-2"
+ onClick={linkEvent(this, this.handleLogoutClick)}
+ >
+ {i18n.t('logout')}
+ </button>
+ ) : (
+ <>
+ <a
+ className={`d-flex align-self-start btn btn-secondary ml-2 ${
+ !this.state.user.matrix_user_id && 'invisible'
+ }`}
+ target="_blank"
+ rel="noopener"
+ href={`https://matrix.to/#/${this.state.user.matrix_user_id}`}
+ >
+ {i18n.t('send_secure_message')}
+ </a>
+ <Link
+ class="d-flex align-self-start btn btn-secondary ml-2"
+ to={`/create_private_message?recipient_id=${this.state.user.id}`}
+ >
+ {i18n.t('send_message')}
+ </Link>
+ </>
+ )}
+ </div>
+ {user.bio && (
+ <div className="d-flex align-items-center mb-2">
+ <div
+ className="md-div"
+ dangerouslySetInnerHTML={mdToHtml(user.bio)}
+ />
+ </div>
+ )}
+ <div>
+ <ul class="list-inline mb-2">
+ <li className="list-inline-item badge badge-light">
+ {i18n.t('number_of_posts', { count: user.number_of_posts })}
+ </li>
+ <li className="list-inline-item badge badge-light">
+ {i18n.t('number_of_comments', {
+ count: user.number_of_comments,
+ })}
+ </li>
</ul>
- </h5>
- <div className="d-flex align-items-center mb-2">
+ </div>
+ <div class="text-muted">
+ {i18n.t('joined')} <MomentTime data={user} showAgo />
+ </div>
+ <div className="d-flex align-items-center text-muted mb-2">
<svg class="icon">
<use xlinkHref="#icon-cake"></use>
</svg>
{moment.utc(user.published).local().format('MMM DD, YYYY')}
</span>
</div>
- <div>
- {i18n.t('joined')} <MomentTime data={user} showAgo />
- </div>
- <div class="table-responsive mt-1">
- <table class="table table-bordered table-sm mt-2 mb-0">
- {/*
- <tr>
- <td class="text-center" colSpan={2}>
- {i18n.t('number_of_points', {
- count: user.post_score + user.comment_score,
- })}
- </td>
- </tr>
- */}
- <tr>
- {/*
- <td>
- {i18n.t('number_of_points', { count: user.post_score })}
- </td>
- */}
- <td>
- {i18n.t('number_of_posts', { count: user.number_of_posts })}
- </td>
- {/*
- </tr>
- <tr>
- <td>
- {i18n.t('number_of_points', { count: user.comment_score })}
- </td>
- */}
- <td>
- {i18n.t('number_of_comments', {
- count: user.number_of_comments,
- })}
- </td>
- </tr>
- </table>
- </div>
- {this.isCurrentUser ? (
- <button
- class="btn btn-block btn-secondary mt-3"
- onClick={linkEvent(this, this.handleLogoutClick)}
- >
- {i18n.t('logout')}
- </button>
- ) : (
- <>
- <a
- className={`btn btn-block btn-secondary mt-3 ${
- !this.state.user.matrix_user_id && 'disabled'
- }`}
- target="_blank"
- rel="noopener"
- href={`https://matrix.to/#/${this.state.user.matrix_user_id}`}
- >
- {i18n.t('send_secure_message')}
- </a>
- <Link
- class="btn btn-block btn-secondary mt-3"
- to={`/create_private_message?recipient_id=${this.state.user.id}`}
- >
- {i18n.t('send_message')}
- </Link>
- </>
- )}
</div>
</div>
</div>
userSettings() {
return (
<div>
- <div class="card border-secondary mb-3">
+ <div class="card bg-transparent border-secondary mb-3">
<div class="card-body">
<h5>{i18n.t('settings')}</h5>
<form onSubmit={linkEvent(this, this.handleUserSettingsSubmit)}>
<div class="form-group">
<label>{i18n.t('avatar')}</label>
- <form class="d-inline">
- <label
- htmlFor="file-upload"
- class="pointer ml-4 text-muted small font-weight-bold"
- >
- {!this.checkSettingsAvatar ? (
- <span class="btn btn-sm btn-secondary">
- {i18n.t('upload_avatar')}
- </span>
- ) : (
- <img
- height="80"
- width="80"
- src={this.state.userSettingsForm.avatar}
- class="rounded-circle"
- />
- )}
- </label>
- <input
- id="file-upload"
- type="file"
- accept="image/*,video/*"
- name="file"
- class="d-none"
- disabled={!UserService.Instance.user}
- onChange={linkEvent(this, this.handleImageUpload)}
- />
- </form>
+ <ImageUploadForm
+ uploadTitle={i18n.t('upload_avatar')}
+ imageSrc={this.state.userSettingsForm.avatar}
+ onUpload={this.handleAvatarUpload}
+ onRemove={this.handleAvatarRemove}
+ rounded
+ />
+ </div>
+ <div class="form-group">
+ <label>{i18n.t('banner')}</label>
+ <ImageUploadForm
+ uploadTitle={i18n.t('upload_banner')}
+ imageSrc={this.state.userSettingsForm.banner}
+ onUpload={this.handleBannerUpload}
+ onRemove={this.handleBannerRemove}
+ />
</div>
- {this.checkSettingsAvatar && (
- <div class="form-group">
- <button
- class="btn btn-secondary btn-block"
- onClick={linkEvent(this, this.removeAvatar)}
- >
- {`${capitalizeFirstLetter(i18n.t('remove'))} ${i18n.t(
- 'avatar'
- )}`}
- </button>
- </div>
- )}
<div class="form-group">
<label>{i18n.t('language')}</label>
<select
value={this.state.userSettingsForm.lang}
onChange={linkEvent(this, this.handleUserSettingsLangChange)}
- class="ml-2 custom-select custom-select-sm w-auto"
+ class="ml-2 custom-select w-auto"
>
<option disabled>{i18n.t('language')}</option>
<option value="browser">{i18n.t('browser_default')}</option>
<select
value={this.state.userSettingsForm.theme}
onChange={linkEvent(this, this.handleUserSettingsThemeChange)}
- class="ml-2 custom-select custom-select-sm w-auto"
+ class="ml-2 custom-select w-auto"
>
<option disabled>{i18n.t('theme')}</option>
{themes.map(theme => (
<div class="mr-2">{i18n.t('sort_type')}</div>
</label>
<ListingTypeSelect
- type_={this.state.userSettingsForm.default_listing_type}
+ type_={
+ Object.values(ListingType)[
+ this.state.userSettingsForm.default_listing_type
+ ]
+ }
onChange={this.handleUserSettingsListingTypeChange}
/>
</form>
<div class="mr-2">{i18n.t('type')}</div>
</label>
<SortSelect
- sort={this.state.userSettingsForm.default_sort_type}
+ sort={
+ Object.values(SortType)[
+ this.state.userSettingsForm.default_sort_type
+ ]
+ }
onChange={this.handleUserSettingsSortTypeChange}
/>
</form>
+ <div class="form-group row">
+ <label class="col-lg-5 col-form-label">
+ {i18n.t('display_name')}
+ </label>
+ <div class="col-lg-7">
+ <input
+ type="text"
+ class="form-control"
+ placeholder={i18n.t('optional')}
+ value={this.state.userSettingsForm.preferred_username}
+ onInput={linkEvent(
+ this,
+ this.handleUserSettingsPreferredUsernameChange
+ )}
+ pattern="^(?!@)(.+)$"
+ minLength={3}
+ maxLength={20}
+ />
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="col-lg-3 col-form-label" htmlFor="user-bio">
+ {i18n.t('bio')}
+ </label>
+ <div class="col-lg-9">
+ <MarkdownTextArea
+ initialContent={this.state.userSettingsForm.bio}
+ onContentChange={this.handleUserSettingsBioChange}
+ maxLength={300}
+ hideNavigationWarnings
+ />
+ </div>
+ </div>
<div class="form-group row">
<label class="col-lg-3 col-form-label" htmlFor="user-email">
{i18n.t('email')}
</div>
<div class="form-group row">
<label class="col-lg-5 col-form-label">
- <a
- href="https://about.riot.im/"
- target="_blank"
- rel="noopener"
- >
+ <a href={elementUrl} target="_blank" rel="noopener">
{i18n.t('matrix_user_id')}
</a>
</label>
/>
</div>
</div>
- {this.state.site.enable_nsfw && (
+ {this.state.siteRes.site.enable_nsfw && (
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
id="user-send-notifications-to-email"
type="checkbox"
- disabled={!this.state.user.email}
+ disabled={!this.state.userSettingsForm.email}
checked={
this.state.userSettingsForm.send_notifications_to_email
}
return (
<div>
{this.state.moderates.length > 0 && (
- <div class="card border-secondary mb-3">
+ <div class="card bg-transparent border-secondary mb-3">
<div class="card-body">
<h5>{i18n.t('moderates')}</h5>
<ul class="list-unstyled mb-0">
return (
<div>
{this.state.follows.length > 0 && (
- <div class="card border-secondary mb-3">
+ <div class="card bg-transparent border-secondary mb-3">
<div class="card-body">
<h5>{i18n.t('subscribed')}</h5>
<ul class="list-unstyled mb-0">
updateUrl(paramUpdates: UrlParams) {
const page = paramUpdates.page || this.state.page;
- const viewStr =
- paramUpdates.view || UserDetailsView[this.state.view].toLowerCase();
- const sortStr =
- paramUpdates.sort || SortType[this.state.sort].toLowerCase();
+ const viewStr = paramUpdates.view || UserDetailsView[this.state.view];
+ const sortStr = paramUpdates.sort || this.state.sort;
this.props.history.push(
`/u/${this.state.username}/view/${viewStr}/sort/${sortStr}/page/${page}`
);
}
handleSortChange(val: SortType) {
- this.updateUrl({ sort: SortType[val].toLowerCase(), page: 1 });
+ this.updateUrl({ sort: val, page: 1 });
}
handleViewChange(i: User, event: any) {
i.updateUrl({
- view: UserDetailsView[Number(event.target.value)].toLowerCase(),
+ view: UserDetailsView[Number(event.target.value)],
page: 1,
});
}
handleUserSettingsLangChange(i: User, event: any) {
i.state.userSettingsForm.lang = event.target.value;
- i18n.changeLanguage(i.state.userSettingsForm.lang);
+ i18n.changeLanguage(getLanguage(i.state.userSettingsForm.lang));
i.setState(i.state);
}
handleUserSettingsSortTypeChange(val: SortType) {
- this.state.userSettingsForm.default_sort_type = val;
+ this.state.userSettingsForm.default_sort_type = Object.keys(
+ SortType
+ ).indexOf(val);
this.setState(this.state);
}
handleUserSettingsListingTypeChange(val: ListingType) {
- this.state.userSettingsForm.default_listing_type = val;
+ this.state.userSettingsForm.default_listing_type = Object.keys(
+ ListingType
+ ).indexOf(val);
this.setState(this.state);
}
handleUserSettingsEmailChange(i: User, event: any) {
i.state.userSettingsForm.email = event.target.value;
- if (i.state.userSettingsForm.email == '' && !i.state.user.email) {
- i.state.userSettingsForm.email = undefined;
- }
+ i.setState(i.state);
+ }
+
+ handleUserSettingsBioChange(val: string) {
+ this.state.userSettingsForm.bio = val;
+ this.setState(this.state);
+ }
+
+ handleAvatarUpload(url: string) {
+ this.state.userSettingsForm.avatar = url;
+ this.setState(this.state);
+ }
+
+ handleAvatarRemove() {
+ this.state.userSettingsForm.avatar = '';
+ this.setState(this.state);
+ }
+
+ handleBannerUpload(url: string) {
+ this.state.userSettingsForm.banner = url;
+ this.setState(this.state);
+ }
+
+ handleBannerRemove() {
+ this.state.userSettingsForm.banner = '';
+ this.setState(this.state);
+ }
+
+ handleUserSettingsPreferredUsernameChange(i: User, event: any) {
+ i.state.userSettingsForm.preferred_username = event.target.value;
i.setState(i.state);
}
i.setState(i.state);
}
- handleImageUpload(i: User, event: any) {
- event.preventDefault();
- let file = event.target.files[0];
- const imageUploadUrl = `/pictrs/image`;
- const formData = new FormData();
- formData.append('images[]', file);
-
- i.state.avatarLoading = true;
- i.setState(i.state);
-
- fetch(imageUploadUrl, {
- method: 'POST',
- body: formData,
- })
- .then(res => res.json())
- .then(res => {
- console.log('pictrs upload:');
- console.log(res);
- if (res.msg == 'ok') {
- let hash = res.files[0].file;
- let url = `${window.location.origin}/pictrs/image/${hash}`;
- i.state.userSettingsForm.avatar = url;
- i.state.avatarLoading = false;
- i.setState(i.state);
- } else {
- i.state.avatarLoading = false;
- i.setState(i.state);
- toast(JSON.stringify(res), 'danger');
- }
- })
- .catch(error => {
- i.state.avatarLoading = false;
- i.setState(i.state);
- toast(error, 'danger');
- });
- }
-
- removeAvatar(i: User, event: any) {
- event.preventDefault();
- i.state.userSettingsLoading = true;
- i.state.userSettingsForm.avatar = '';
- i.setState(i.state);
-
- WebSocketService.Instance.saveUserSettings(i.state.userSettingsForm);
- }
-
- get checkSettingsAvatar(): boolean {
- return (
- this.state.userSettingsForm.avatar &&
- this.state.userSettingsForm.avatar != ''
- );
- }
-
handleUserSettingsSubmit(i: User, event: any) {
event.preventDefault();
i.state.userSettingsLoading = true;
}
parseMessage(msg: WebSocketJsonResponse) {
+ console.log(msg);
const res = wsJsonToRes(msg);
if (msg.error) {
toast(i18n.t(msg.error), 'danger');
}
this.setState({
deleteAccountLoading: false,
- avatarLoading: false,
userSettingsLoading: false,
});
return;
UserService.Instance.user.default_listing_type;
this.state.userSettingsForm.lang = UserService.Instance.user.lang;
this.state.userSettingsForm.avatar = UserService.Instance.user.avatar;
- this.state.userSettingsForm.email = this.state.user.email;
- this.state.userSettingsForm.send_notifications_to_email = this.state.user.send_notifications_to_email;
+ this.state.userSettingsForm.banner = UserService.Instance.user.banner;
+ this.state.userSettingsForm.preferred_username =
+ UserService.Instance.user.preferred_username;
this.state.userSettingsForm.show_avatars =
UserService.Instance.user.show_avatars;
- this.state.userSettingsForm.matrix_user_id = this.state.user.matrix_user_id;
+ this.state.userSettingsForm.email = UserService.Instance.user.email;
+ this.state.userSettingsForm.bio = UserService.Instance.user.bio;
+ this.state.userSettingsForm.send_notifications_to_email =
+ UserService.Instance.user.send_notifications_to_email;
+ this.state.userSettingsForm.matrix_user_id =
+ UserService.Instance.user.matrix_user_id;
}
+ this.state.loading = false;
this.setState(this.state);
}
} else if (res.op == UserOperation.SaveUserSettings) {
const data = res.data as LoginResponse;
UserService.Instance.login(data);
- this.setState({
- userSettingsLoading: false,
- });
+ this.state.user.bio = this.state.userSettingsForm.bio;
+ this.state.user.preferred_username = this.state.userSettingsForm.preferred_username;
+ this.state.user.banner = this.state.userSettingsForm.banner;
+ this.state.user.avatar = this.state.userSettingsForm.avatar;
+ this.state.userSettingsLoading = false;
+ this.setState(this.state);
+
window.scrollTo(0, 0);
} else if (res.op == UserOperation.DeleteAccount) {
this.setState({
this.context.router.history.push('/');
} else if (res.op == UserOperation.GetSite) {
const data = res.data as GetSiteResponse;
- this.setState({
- site: data.site,
- });
+ this.state.siteRes = data;
+ this.setState(this.state);
+ } else if (res.op == UserOperation.AddAdmin) {
+ const data = res.data as AddAdminResponse;
+ this.state.siteRes.admins = data.admins;
+ this.setState(this.state);
}
}
}
import { km } from './translations/km';
import { ga } from './translations/ga';
import { sr_Latn } from './translations/sr_Latn';
+import { ko } from './translations/ko';
// https://github.com/nimbusec-oss/inferno-i18next/blob/master/tests/T.test.js#L66
const resources = {
km,
ga,
sr_Latn,
+ ko,
};
function format(value: any, format: any, lng: any): any {
import { Inbox } from './components/inbox';
import { Search } from './components/search';
import { Sponsors } from './components/sponsors';
+import { Instances } from './components/instances';
import { Symbols } from './components/symbols';
import { i18n } from './i18next';
path={`/password_change/:token`}
component={PasswordChange}
/>
+ <Route path={`/instances`} component={Instances} />
</Switch>
<Symbols />
</div>
-export enum UserOperation {
- Login,
- Register,
- CreateCommunity,
- CreatePost,
- ListCommunities,
- ListCategories,
- GetPost,
- GetCommunity,
- CreateComment,
- EditComment,
- SaveComment,
- CreateCommentLike,
- GetPosts,
- CreatePostLike,
- EditPost,
- SavePost,
- EditCommunity,
- FollowCommunity,
- GetFollowedCommunities,
- GetUserDetails,
- GetReplies,
- GetUserMentions,
- EditUserMention,
- GetModlog,
- BanFromCommunity,
- AddModToCommunity,
- CreateSite,
- EditSite,
- GetSite,
- AddAdmin,
- BanUser,
- Search,
- MarkAllAsRead,
- SaveUserSettings,
- TransferCommunity,
- TransferSite,
- DeleteAccount,
- PasswordReset,
- PasswordChange,
- CreatePrivateMessage,
- EditPrivateMessage,
- GetPrivateMessages,
- UserJoin,
- GetComments,
- GetSiteConfig,
- SaveSiteConfig,
-}
-
export enum CommentSortType {
Hot,
Top,
Chat,
}
-export enum ListingType {
- All,
- Subscribed,
- Community,
-}
-
export enum DataType {
Post,
Comment,
}
-export enum SortType {
- Hot,
- New,
- TopDay,
- TopWeek,
- TopMonth,
- TopYear,
- TopAll,
-}
-
-export enum SearchType {
- All,
- Comments,
- Posts,
- Communities,
- Users,
- Url,
-}
-
-export interface User {
- id: number;
- iss: string;
- username: string;
- show_nsfw: boolean;
- theme: string;
- default_sort_type: SortType;
- default_listing_type: ListingType;
- lang: string;
- avatar?: string;
- show_avatars: boolean;
- unreadCount?: number;
-}
-
-export interface UserView {
- id: number;
- actor_id: string;
- name: string;
- avatar?: string;
- email?: string;
- matrix_user_id?: string;
- bio?: string;
- local: boolean;
- published: string;
- number_of_posts: number;
- post_score: number;
- number_of_comments: number;
- comment_score: number;
- banned: boolean;
- show_avatars: boolean;
- send_notifications_to_email: boolean;
-}
-
-export interface CommunityUser {
- id: number;
- user_id: number;
- user_actor_id: string;
- user_local: boolean;
- user_name: string;
- avatar?: string;
- community_id: number;
- community_actor_id: string;
- community_local: boolean;
- community_name: string;
- published: string;
-}
-
-export interface Community {
- id: number;
- actor_id: string;
- local: boolean;
- name: string;
- title: string;
- description?: string;
- category_id: number;
- creator_id: number;
- removed: boolean;
- deleted: boolean;
- nsfw: boolean;
- published: string;
- updated?: string;
- creator_actor_id: string;
- creator_local: boolean;
- last_refreshed_at: string;
- creator_name: string;
- creator_avatar?: string;
- category_name: string;
- number_of_subscribers: number;
- number_of_posts: number;
- number_of_comments: number;
- user_id?: number;
- subscribed?: boolean;
-}
-
-export interface Post {
- id: number;
- name: string;
- url?: string;
- body?: string;
- creator_id: number;
- community_id: number;
- removed: boolean;
- deleted: boolean;
- locked: boolean;
- stickied: boolean;
- embed_title?: string;
- embed_description?: string;
- embed_html?: string;
- thumbnail_url?: string;
- ap_id: string;
- local: boolean;
- nsfw: boolean;
- banned: boolean;
- banned_from_community: boolean;
- published: string;
- updated?: string;
- creator_actor_id: string;
- creator_local: boolean;
- creator_name: string;
- creator_published: string;
- creator_avatar?: string;
- community_actor_id: string;
- community_local: boolean;
- community_name: string;
- community_removed: boolean;
- community_deleted: boolean;
- community_nsfw: boolean;
- number_of_comments: number;
- score: number;
- upvotes: number;
- downvotes: number;
- hot_rank: number;
- newest_activity_time: string;
- user_id?: number;
- my_vote?: number;
- subscribed?: boolean;
- read?: boolean;
- saved?: boolean;
- duplicates?: Array<Post>;
-}
-
-export interface Comment {
- id: number;
- ap_id: string;
- local: boolean;
- creator_id: number;
- post_id: number;
- post_name: string;
- parent_id?: number;
- content: string;
- removed: boolean;
- deleted: boolean;
- read: boolean;
- published: string;
- updated?: string;
- community_id: number;
- community_actor_id: string;
- community_local: boolean;
- community_name: string;
- banned: boolean;
- banned_from_community: boolean;
- creator_actor_id: string;
- creator_local: boolean;
- creator_name: string;
- creator_avatar?: string;
- creator_published: string;
- score: number;
- upvotes: number;
- downvotes: number;
- hot_rank: number;
- user_id?: number;
- my_vote?: number;
- subscribed?: number;
- saved?: boolean;
- user_mention_id?: number; // For mention type
- recipient_id?: number;
- recipient_actor_id?: string;
- recipient_local?: boolean;
- depth?: number;
-}
-
-export interface Category {
- id: number;
- name: string;
-}
-
-export interface Site {
- id: number;
- name: string;
- description?: string;
- creator_id: number;
- published: string;
- updated?: string;
- creator_name: string;
- number_of_users: number;
- number_of_posts: number;
- number_of_comments: number;
- number_of_communities: number;
- enable_downvotes: boolean;
- open_registration: boolean;
- enable_nsfw: boolean;
-}
-
-export interface PrivateMessage {
- id: number;
- creator_id: number;
- recipient_id: number;
- content: string;
- deleted: boolean;
- read: boolean;
- published: string;
- updated?: string;
- ap_id: string;
- local: boolean;
- creator_name: string;
- creator_avatar?: string;
- creator_actor_id: string;
- creator_local: boolean;
- recipient_name: string;
- recipient_avatar?: string;
- recipient_actor_id: string;
- recipient_local: boolean;
-}
-
export enum BanType {
Community,
Site,
}
-export interface FollowCommunityForm {
- community_id: number;
- follow: boolean;
- auth?: string;
-}
-
-export interface GetFollowedCommunitiesForm {
- auth: string;
-}
-
-export interface GetFollowedCommunitiesResponse {
- communities: Array<CommunityUser>;
-}
-
-export interface GetUserDetailsForm {
- user_id?: number;
- username?: string;
- sort: string;
- page?: number;
- limit?: number;
- community_id?: number;
- saved_only: boolean;
-}
-
-export interface UserDetailsResponse {
- user: UserView;
- follows: Array<CommunityUser>;
- moderates: Array<CommunityUser>;
- comments: Array<Comment>;
- posts: Array<Post>;
- admins: Array<UserView>;
-}
-
-export interface GetRepliesForm {
- sort: string;
- page?: number;
- limit?: number;
- unread_only: boolean;
- auth?: string;
-}
-
-export interface GetRepliesResponse {
- replies: Array<Comment>;
-}
-
-export interface GetUserMentionsForm {
- sort: string;
- page?: number;
- limit?: number;
- unread_only: boolean;
- auth?: string;
-}
-
-export interface GetUserMentionsResponse {
- mentions: Array<Comment>;
-}
-
-export interface EditUserMentionForm {
- user_mention_id: number;
- read?: boolean;
- auth?: string;
-}
-
-export interface UserMentionResponse {
- mention: Comment;
-}
-
-export interface BanFromCommunityForm {
- community_id: number;
- user_id: number;
- ban: boolean;
- reason?: string;
- expires?: number;
- auth?: string;
-}
-
-export interface BanFromCommunityResponse {
- user: UserView;
- banned: boolean;
-}
-
-export interface AddModToCommunityForm {
- community_id: number;
- user_id: number;
- added: boolean;
- auth?: string;
-}
-
-export interface TransferCommunityForm {
- community_id: number;
- user_id: number;
- auth?: string;
-}
-
-export interface TransferSiteForm {
- user_id: number;
- auth?: string;
-}
-
-export interface AddModToCommunityResponse {
- moderators: Array<CommunityUser>;
-}
-
-export interface GetModlogForm {
- mod_user_id?: number;
- community_id?: number;
- page?: number;
- limit?: number;
-}
-
-export interface GetModlogResponse {
- removed_posts: Array<ModRemovePost>;
- locked_posts: Array<ModLockPost>;
- stickied_posts: Array<ModStickyPost>;
- removed_comments: Array<ModRemoveComment>;
- removed_communities: Array<ModRemoveCommunity>;
- banned_from_community: Array<ModBanFromCommunity>;
- banned: Array<ModBan>;
- added_to_community: Array<ModAddCommunity>;
- added: Array<ModAdd>;
-}
-
-export interface ModRemovePost {
- id: number;
- mod_user_id: number;
- post_id: number;
- reason?: string;
- removed?: boolean;
- when_: string;
- mod_user_name: string;
- post_name: string;
- community_id: number;
- community_name: string;
-}
-
-export interface ModLockPost {
- id: number;
- mod_user_id: number;
- post_id: number;
- locked?: boolean;
- when_: string;
- mod_user_name: string;
- post_name: string;
- community_id: number;
- community_name: string;
-}
-
-export interface ModStickyPost {
- id: number;
- mod_user_id: number;
- post_id: number;
- stickied?: boolean;
- when_: string;
- mod_user_name: string;
- post_name: string;
- community_id: number;
- community_name: string;
-}
-
-export interface ModRemoveComment {
- id: number;
- mod_user_id: number;
- comment_id: number;
- reason?: string;
- removed?: boolean;
- when_: string;
- mod_user_name: string;
- comment_user_id: number;
- comment_user_name: string;
- comment_content: string;
- post_id: number;
- post_name: string;
- community_id: number;
- community_name: string;
-}
-
-export interface ModRemoveCommunity {
- id: number;
- mod_user_id: number;
- community_id: number;
- reason?: string;
- removed?: boolean;
- expires?: number;
- when_: string;
- mod_user_name: string;
- community_name: string;
-}
-
-export interface ModBanFromCommunity {
- id: number;
- mod_user_id: number;
- other_user_id: number;
- community_id: number;
- reason?: string;
- banned?: boolean;
- expires?: number;
- when_: string;
- mod_user_name: string;
- other_user_name: string;
- community_name: string;
-}
-
-export interface ModBan {
- id: number;
- mod_user_id: number;
- other_user_id: number;
- reason?: string;
- banned?: boolean;
- expires?: number;
- when_: string;
- mod_user_name: string;
- other_user_name: string;
-}
-
-export interface ModAddCommunity {
- id: number;
- mod_user_id: number;
- other_user_id: number;
- community_id: number;
- removed?: boolean;
- when_: string;
- mod_user_name: string;
- other_user_name: string;
- community_name: string;
-}
-
-export interface ModAdd {
- id: number;
- mod_user_id: number;
- other_user_id: number;
- removed?: boolean;
- when_: string;
- mod_user_name: string;
- other_user_name: string;
-}
-
-export interface LoginForm {
- username_or_email: string;
- password: string;
-}
-
-export interface RegisterForm {
- username: string;
- email?: string;
- password: string;
- password_verify: string;
- admin: boolean;
- show_nsfw: boolean;
-}
-
-export interface LoginResponse {
- jwt: string;
-}
-
-export interface UserSettingsForm {
- show_nsfw: boolean;
- theme: string;
- default_sort_type: SortType;
- default_listing_type: ListingType;
- lang: string;
- avatar?: string;
- email?: string;
- matrix_user_id?: string;
- new_password?: string;
- new_password_verify?: string;
- old_password?: string;
- show_avatars: boolean;
- send_notifications_to_email: boolean;
- auth: string;
-}
-
-export interface CommunityForm {
- name: string;
- title: string;
- description?: string;
- category_id: number;
- edit_id?: number;
- removed?: boolean;
- deleted?: boolean;
- nsfw: boolean;
- reason?: string;
- expires?: number;
- auth?: string;
-}
-
-export interface GetCommunityForm {
- id?: number;
- name?: string;
- auth?: string;
-}
-
-export interface GetCommunityResponse {
- community: Community;
- moderators: Array<CommunityUser>;
- admins: Array<UserView>;
- online: number;
-}
-
-export interface CommunityResponse {
- community: Community;
-}
-
-export interface ListCommunitiesForm {
- sort: string;
- page?: number;
- limit?: number;
- auth?: string;
-}
-
-export interface ListCommunitiesResponse {
- communities: Array<Community>;
-}
-
-export interface ListCategoriesResponse {
- categories: Array<Category>;
-}
-
-export interface PostForm {
- name: string;
- url?: string;
- body?: string;
- community_id: number;
- updated?: number;
- edit_id?: number;
- creator_id: number;
- removed?: boolean;
- deleted?: boolean;
- nsfw: boolean;
- locked?: boolean;
- stickied?: boolean;
- reason?: string;
- auth: string;
-}
-
-export interface PostFormParams {
- name: string;
- url?: string;
- body?: string;
- community?: string;
-}
-
-export interface GetPostForm {
- id: number;
- auth?: string;
-}
-
-export interface GetPostResponse {
- post: Post;
- comments: Array<Comment>;
- community: Community;
- moderators: Array<CommunityUser>;
- admins: Array<UserView>;
- online: number;
-}
-
-export interface SavePostForm {
- post_id: number;
- save: boolean;
- auth?: string;
-}
-
-export interface PostResponse {
- post: Post;
-}
-
-export interface CommentForm {
- content: string;
- post_id: number;
- parent_id?: number;
- edit_id?: number;
- creator_id?: number;
- removed?: boolean;
- deleted?: boolean;
- reason?: string;
- read?: boolean;
- auth: string;
-}
-
-export interface SaveCommentForm {
- comment_id: number;
- save: boolean;
- auth?: string;
-}
-
-export interface CommentResponse {
- comment: Comment;
- recipient_ids: Array<number>;
-}
-
-export interface CommentLikeForm {
- comment_id: number;
- post_id: number;
- score: number;
- auth?: string;
-}
-
-export interface CommentNode {
- comment: Comment;
- children?: Array<CommentNode>;
-}
-
-export interface GetPostsForm {
- type_: string;
- sort: string;
- page?: number;
- limit?: number;
- community_id?: number;
- auth?: string;
-}
-
-export interface GetPostsResponse {
- posts: Array<Post>;
-}
-
-export interface GetCommentsForm {
- type_: string;
- sort: string;
- page?: number;
- limit: number;
- community_id?: number;
- auth?: string;
-}
-
-export interface GetCommentsResponse {
- comments: Array<Comment>;
-}
-
-export interface CreatePostLikeForm {
- post_id: number;
- score: number;
- auth?: string;
-}
-
-export interface SiteForm {
- name: string;
- description?: string;
- enable_downvotes: boolean;
- open_registration: boolean;
- enable_nsfw: boolean;
- auth?: string;
-}
-
-export interface GetSiteConfig {
- auth?: string;
-}
-
-export interface GetSiteConfigResponse {
- config_hjson: string;
-}
-
-export interface SiteConfigForm {
- config_hjson: string;
- auth?: string;
-}
-
-export interface GetSiteResponse {
- site: Site;
- admins: Array<UserView>;
- banned: Array<UserView>;
- online: number;
-}
-
-export interface SiteResponse {
- site: Site;
-}
-
-export interface BanUserForm {
- user_id: number;
- ban: boolean;
- reason?: string;
- expires?: number;
- auth?: string;
-}
-
-export interface BanUserResponse {
- user: UserView;
- banned: boolean;
-}
-
-export interface AddAdminForm {
- user_id: number;
- added: boolean;
- auth?: string;
-}
-
-export interface AddAdminResponse {
- admins: Array<UserView>;
-}
-
-export interface SearchForm {
- q: string;
- type_: string;
- community_id?: number;
- sort: string;
- page?: number;
- limit?: number;
- auth?: string;
-}
-
-export interface SearchResponse {
- type_: string;
- posts?: Array<Post>;
- comments?: Array<Comment>;
- communities: Array<Community>;
- users: Array<UserView>;
-}
-
-export interface DeleteAccountForm {
- password: string;
-}
-
-export interface PasswordResetForm {
- email: string;
-}
-
-// export interface PasswordResetResponse {
-// }
-
-export interface PasswordChangeForm {
- token: string;
- password: string;
- password_verify: string;
-}
-
-export interface PrivateMessageForm {
- content: string;
- recipient_id: number;
- auth?: string;
-}
-
-export interface PrivateMessageFormParams {
- recipient_id: number;
-}
-
-export interface EditPrivateMessageForm {
- edit_id: number;
- content?: string;
- deleted?: boolean;
- read?: boolean;
- auth?: string;
-}
-
-export interface GetPrivateMessagesForm {
- unread_only: boolean;
- page?: number;
- limit?: number;
- auth?: string;
-}
-
-export interface PrivateMessagesResponse {
- messages: Array<PrivateMessage>;
-}
-
-export interface PrivateMessageResponse {
- message: PrivateMessage;
-}
-
-export interface UserJoinForm {
- auth: string;
-}
-
-export interface UserJoinResponse {
- user_id: number;
-}
-
-export type MessageType =
- | EditPrivateMessageForm
- | LoginForm
- | RegisterForm
- | CommunityForm
- | FollowCommunityForm
- | ListCommunitiesForm
- | GetFollowedCommunitiesForm
- | PostForm
- | GetPostForm
- | GetPostsForm
- | GetCommunityForm
- | CommentForm
- | CommentLikeForm
- | SaveCommentForm
- | CreatePostLikeForm
- | BanFromCommunityForm
- | AddAdminForm
- | AddModToCommunityForm
- | TransferCommunityForm
- | TransferSiteForm
- | SaveCommentForm
- | BanUserForm
- | AddAdminForm
- | GetUserDetailsForm
- | GetRepliesForm
- | GetUserMentionsForm
- | EditUserMentionForm
- | GetModlogForm
- | SiteForm
- | SearchForm
- | UserSettingsForm
- | DeleteAccountForm
- | PasswordResetForm
- | PasswordChangeForm
- | PrivateMessageForm
- | EditPrivateMessageForm
- | GetPrivateMessagesForm
- | SiteConfigForm;
-
-type ResponseType =
- | SiteResponse
- | GetFollowedCommunitiesResponse
- | ListCommunitiesResponse
- | GetPostsResponse
- | PostResponse
- | GetRepliesResponse
- | GetUserMentionsResponse
- | ListCategoriesResponse
- | CommunityResponse
- | CommentResponse
- | UserMentionResponse
- | LoginResponse
- | GetModlogResponse
- | SearchResponse
- | BanFromCommunityResponse
- | AddModToCommunityResponse
- | BanUserResponse
- | AddAdminResponse
- | PrivateMessageResponse
- | PrivateMessagesResponse
- | GetSiteConfigResponse;
-
-export interface WebSocketResponse {
- op: UserOperation;
- data: ResponseType;
-}
-
-export interface WebSocketJsonResponse {
- op?: string;
- data?: ResponseType;
- error?: string;
- reconnect?: boolean;
-}
-
export enum UserDetailsView {
Overview,
Comments,
--- /dev/null
+import { register } from 'register-service-worker';
+
+register('/service-worker.js', {
+ registrationOptions: { scope: './' },
+ ready(registration) {
+ console.log('Service worker is active.');
+ },
+ registered(registration) {
+ console.log('Service worker has been registered.');
+ },
+ cached(registration) {
+ console.log('Content has been cached for offline use.');
+ },
+ updatefound(registration) {
+ console.log('New content is downloading.');
+ },
+ updated(registration) {
+ console.log('New content is available; please refresh.');
+ },
+ offline() {
+ console.log(
+ 'No internet connection found. App is running in offline mode.'
+ );
+ },
+ error(error) {
+ console.error('Error during service worker registration:', error);
+ },
+});
import Cookies from 'js-cookie';
-import { User, LoginResponse } from '../interfaces';
+import { User, LoginResponse } from 'lemmy-js-client';
import { setTheme } from '../utils';
import jwt_decode from 'jwt-decode';
-import { Subject } from 'rxjs';
+import { Subject, BehaviorSubject } from 'rxjs';
+
+interface Claims {
+ id: number;
+ iss: string;
+}
export class UserService {
private static _instance: UserService;
public user: User;
- public sub: Subject<{ user: User }> = new Subject<{
- user: User;
- }>();
+ public claims: Claims;
+ public jwtSub: Subject<string> = new Subject<string>();
+ public unreadCountSub: BehaviorSubject<number> = new BehaviorSubject<number>(
+ 0
+ );
private constructor() {
let jwt = Cookies.get('jwt');
if (jwt) {
- this.setUser(jwt);
+ this.setClaims(jwt);
} else {
setTheme();
console.log('No JWT cookie found.');
}
public login(res: LoginResponse) {
- this.setUser(res.jwt);
+ this.setClaims(res.jwt);
Cookies.set('jwt', res.jwt, { expires: 365 });
console.log('jwt cookie set');
}
public logout() {
+ this.claims = undefined;
this.user = undefined;
Cookies.remove('jwt');
setTheme();
- this.sub.next({ user: undefined });
+ this.jwtSub.next();
console.log('Logged out.');
}
return Cookies.get('jwt');
}
- private setUser(jwt: string) {
- this.user = jwt_decode(jwt);
- setTheme(this.user.theme, true);
- this.sub.next({ user: this.user });
- console.log(this.user);
+ private setClaims(jwt: string) {
+ this.claims = jwt_decode(jwt);
+ this.jwtSub.next(jwt);
}
public static get Instance() {
import { wsUri } from '../env';
import {
+ LemmyWebsocket,
LoginForm,
RegisterForm,
- UserOperation,
CommunityForm,
+ DeleteCommunityForm,
+ RemoveCommunityForm,
PostForm,
+ DeletePostForm,
+ RemovePostForm,
+ LockPostForm,
+ StickyPostForm,
SavePostForm,
CommentForm,
+ DeleteCommentForm,
+ RemoveCommentForm,
+ MarkCommentAsReadForm,
SaveCommentForm,
CommentLikeForm,
GetPostForm,
UserView,
GetRepliesForm,
GetUserMentionsForm,
- EditUserMentionForm,
+ MarkUserMentionAsReadForm,
SearchForm,
UserSettingsForm,
DeleteAccountForm,
PasswordChangeForm,
PrivateMessageForm,
EditPrivateMessageForm,
+ DeletePrivateMessageForm,
+ MarkPrivateMessageAsReadForm,
GetPrivateMessagesForm,
GetCommentsForm,
UserJoinForm,
GetSiteConfig,
+ GetSiteForm,
SiteConfigForm,
- MessageType,
+ MarkAllAsReadForm,
WebSocketJsonResponse,
-} from '../interfaces';
+} from 'lemmy-js-client';
import { UserService } from './';
import { i18n } from '../i18next';
import { toast } from '../utils';
public admins: Array<UserView>;
public banned: Array<UserView>;
+ private client = new LemmyWebsocket();
private constructor() {
this.ws = new ReconnectingWebSocket(wsUri);
this.ws.onopen = () => {
console.log(`Connected to ${wsUri}`);
- if (UserService.Instance.user) {
- this.userJoin();
- }
-
if (!firstConnect) {
let res: WebSocketJsonResponse = {
reconnect: true,
public userJoin() {
let form: UserJoinForm = { auth: UserService.Instance.auth };
- this.ws.send(this.wsSendWrapper(UserOperation.UserJoin, form));
+ this.ws.send(this.client.userJoin(form));
}
- public login(loginForm: LoginForm) {
- this.ws.send(this.wsSendWrapper(UserOperation.Login, loginForm));
+ public login(form: LoginForm) {
+ this.ws.send(this.client.login(form));
}
- public register(registerForm: RegisterForm) {
- this.ws.send(this.wsSendWrapper(UserOperation.Register, registerForm));
+ public register(form: RegisterForm) {
+ this.ws.send(this.client.register(form));
}
- public createCommunity(communityForm: CommunityForm) {
- this.setAuth(communityForm);
- this.ws.send(
- this.wsSendWrapper(UserOperation.CreateCommunity, communityForm)
- );
+ public getCaptcha() {
+ this.ws.send(this.client.getCaptcha());
}
- public editCommunity(communityForm: CommunityForm) {
- this.setAuth(communityForm);
- this.ws.send(
- this.wsSendWrapper(UserOperation.EditCommunity, communityForm)
- );
+ public createCommunity(form: CommunityForm) {
+ this.setAuth(form); // TODO all these setauths at some point would be good to make required
+ this.ws.send(this.client.createCommunity(form));
}
- public followCommunity(followCommunityForm: FollowCommunityForm) {
- this.setAuth(followCommunityForm);
- this.ws.send(
- this.wsSendWrapper(UserOperation.FollowCommunity, followCommunityForm)
- );
+ public editCommunity(form: CommunityForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.editCommunity(form));
+ }
+
+ public deleteCommunity(form: DeleteCommunityForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.deleteCommunity(form));
+ }
+
+ public removeCommunity(form: RemoveCommunityForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.removeCommunity(form));
+ }
+
+ public followCommunity(form: FollowCommunityForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.followCommunity(form));
}
public listCommunities(form: ListCommunitiesForm) {
this.setAuth(form, false);
- this.ws.send(this.wsSendWrapper(UserOperation.ListCommunities, form));
+ this.ws.send(this.client.listCommunities(form));
}
public getFollowedCommunities() {
let form: GetFollowedCommunitiesForm = { auth: UserService.Instance.auth };
- this.ws.send(
- this.wsSendWrapper(UserOperation.GetFollowedCommunities, form)
- );
+ this.ws.send(this.client.getFollowedCommunities(form));
}
public listCategories() {
- this.ws.send(this.wsSendWrapper(UserOperation.ListCategories, {}));
+ this.ws.send(this.client.listCategories());
}
- public createPost(postForm: PostForm) {
- this.setAuth(postForm);
- this.ws.send(this.wsSendWrapper(UserOperation.CreatePost, postForm));
+ public createPost(form: PostForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.createPost(form));
}
public getPost(form: GetPostForm) {
this.setAuth(form, false);
- this.ws.send(this.wsSendWrapper(UserOperation.GetPost, form));
+ this.ws.send(this.client.getPost(form));
}
public getCommunity(form: GetCommunityForm) {
this.setAuth(form, false);
- this.ws.send(this.wsSendWrapper(UserOperation.GetCommunity, form));
+ this.ws.send(this.client.getCommunity(form));
}
- public createComment(commentForm: CommentForm) {
- this.setAuth(commentForm);
- this.ws.send(this.wsSendWrapper(UserOperation.CreateComment, commentForm));
+ public createComment(form: CommentForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.createComment(form));
+ }
+
+ public editComment(form: CommentForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.editComment(form));
+ }
+
+ public deleteComment(form: DeleteCommentForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.deleteComment(form));
+ }
+
+ public removeComment(form: RemoveCommentForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.removeComment(form));
}
- public editComment(commentForm: CommentForm) {
- this.setAuth(commentForm);
- this.ws.send(this.wsSendWrapper(UserOperation.EditComment, commentForm));
+ public markCommentAsRead(form: MarkCommentAsReadForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.markCommentAsRead(form));
}
public likeComment(form: CommentLikeForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.CreateCommentLike, form));
+ this.ws.send(this.client.likeComment(form));
}
public saveComment(form: SaveCommentForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.SaveComment, form));
+ this.ws.send(this.client.saveComment(form));
}
public getPosts(form: GetPostsForm) {
this.setAuth(form, false);
- this.ws.send(this.wsSendWrapper(UserOperation.GetPosts, form));
+ this.ws.send(this.client.getPosts(form));
}
public getComments(form: GetCommentsForm) {
this.setAuth(form, false);
- this.ws.send(this.wsSendWrapper(UserOperation.GetComments, form));
+ this.ws.send(this.client.getComments(form));
}
public likePost(form: CreatePostLikeForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.CreatePostLike, form));
+ this.ws.send(this.client.likePost(form));
+ }
+
+ public editPost(form: PostForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.editPost(form));
}
- public editPost(postForm: PostForm) {
- this.setAuth(postForm);
- this.ws.send(this.wsSendWrapper(UserOperation.EditPost, postForm));
+ public deletePost(form: DeletePostForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.deletePost(form));
+ }
+
+ public removePost(form: RemovePostForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.removePost(form));
+ }
+
+ public lockPost(form: LockPostForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.lockPost(form));
+ }
+
+ public stickyPost(form: StickyPostForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.stickyPost(form));
}
public savePost(form: SavePostForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.SavePost, form));
+ this.ws.send(this.client.savePost(form));
}
public banFromCommunity(form: BanFromCommunityForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.BanFromCommunity, form));
+ this.ws.send(this.client.banFromCommunity(form));
}
public addModToCommunity(form: AddModToCommunityForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.AddModToCommunity, form));
+ this.ws.send(this.client.addModToCommunity(form));
}
public transferCommunity(form: TransferCommunityForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.TransferCommunity, form));
+ this.ws.send(this.client.transferCommunity(form));
}
public transferSite(form: TransferSiteForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.TransferSite, form));
+ this.ws.send(this.client.transferSite(form));
}
public banUser(form: BanUserForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.BanUser, form));
+ this.ws.send(this.client.banUser(form));
}
public addAdmin(form: AddAdminForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.AddAdmin, form));
+ this.ws.send(this.client.addAdmin(form));
}
public getUserDetails(form: GetUserDetailsForm) {
this.setAuth(form, false);
- this.ws.send(this.wsSendWrapper(UserOperation.GetUserDetails, form));
+ this.ws.send(this.client.getUserDetails(form));
}
public getReplies(form: GetRepliesForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.GetReplies, form));
+ this.ws.send(this.client.getReplies(form));
}
public getUserMentions(form: GetUserMentionsForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.GetUserMentions, form));
+ this.ws.send(this.client.getUserMentions(form));
}
- public editUserMention(form: EditUserMentionForm) {
+ public markUserMentionAsRead(form: MarkUserMentionAsReadForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.EditUserMention, form));
+ this.ws.send(this.client.markUserMentionAsRead(form));
}
public getModlog(form: GetModlogForm) {
- this.ws.send(this.wsSendWrapper(UserOperation.GetModlog, form));
+ this.ws.send(this.client.getModlog(form));
}
- public createSite(siteForm: SiteForm) {
- this.setAuth(siteForm);
- this.ws.send(this.wsSendWrapper(UserOperation.CreateSite, siteForm));
+ public createSite(form: SiteForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.createSite(form));
}
- public editSite(siteForm: SiteForm) {
- this.setAuth(siteForm);
- this.ws.send(this.wsSendWrapper(UserOperation.EditSite, siteForm));
+ public editSite(form: SiteForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.editSite(form));
}
- public getSite() {
- this.ws.send(this.wsSendWrapper(UserOperation.GetSite, {}));
+ public getSite(form: GetSiteForm = {}) {
+ this.setAuth(form, false);
+ this.ws.send(this.client.getSite(form));
}
public getSiteConfig() {
- let siteConfig: GetSiteConfig = {};
- this.setAuth(siteConfig);
- this.ws.send(this.wsSendWrapper(UserOperation.GetSiteConfig, siteConfig));
+ let form: GetSiteConfig = {};
+ this.setAuth(form);
+ this.ws.send(this.client.getSiteConfig(form));
}
public search(form: SearchForm) {
this.setAuth(form, false);
- this.ws.send(this.wsSendWrapper(UserOperation.Search, form));
+ this.ws.send(this.client.search(form));
}
public markAllAsRead() {
- let form = {};
+ let form: MarkAllAsReadForm;
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.MarkAllAsRead, form));
+ this.ws.send(this.client.markAllAsRead(form));
}
- public saveUserSettings(userSettingsForm: UserSettingsForm) {
- this.setAuth(userSettingsForm);
- this.ws.send(
- this.wsSendWrapper(UserOperation.SaveUserSettings, userSettingsForm)
- );
+ public saveUserSettings(form: UserSettingsForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.saveUserSettings(form));
}
public deleteAccount(form: DeleteAccountForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.DeleteAccount, form));
+ this.ws.send(this.client.deleteAccount(form));
}
public passwordReset(form: PasswordResetForm) {
- this.ws.send(this.wsSendWrapper(UserOperation.PasswordReset, form));
+ this.ws.send(this.client.passwordReset(form));
}
public passwordChange(form: PasswordChangeForm) {
- this.ws.send(this.wsSendWrapper(UserOperation.PasswordChange, form));
+ this.ws.send(this.client.passwordChange(form));
}
public createPrivateMessage(form: PrivateMessageForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.CreatePrivateMessage, form));
+ this.ws.send(this.client.createPrivateMessage(form));
}
public editPrivateMessage(form: EditPrivateMessageForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.EditPrivateMessage, form));
+ this.ws.send(this.client.editPrivateMessage(form));
}
- public getPrivateMessages(form: GetPrivateMessagesForm) {
+ public deletePrivateMessage(form: DeletePrivateMessageForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.GetPrivateMessages, form));
+ this.ws.send(this.client.deletePrivateMessage(form));
}
- public saveSiteConfig(form: SiteConfigForm) {
+ public markPrivateMessageAsRead(form: MarkPrivateMessageAsReadForm) {
this.setAuth(form);
- this.ws.send(this.wsSendWrapper(UserOperation.SaveSiteConfig, form));
+ this.ws.send(this.client.markPrivateMessageAsRead(form));
}
- private wsSendWrapper(op: UserOperation, data: MessageType) {
- let send = { op: UserOperation[op], data: data };
- console.log(send);
- return JSON.stringify(send);
+ public getPrivateMessages(form: GetPrivateMessagesForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.getPrivateMessages(form));
+ }
+
+ public saveSiteConfig(form: SiteConfigForm) {
+ this.setAuth(form);
+ this.ws.send(this.client.saveSiteConfig(form));
}
private setAuth(obj: any, throwErr: boolean = true) {
import 'moment/locale/km';
import 'moment/locale/ga';
import 'moment/locale/sr';
+import 'moment/locale/ko';
import {
UserOperation,
PrivateMessage,
User,
SortType,
- CommentSortType,
ListingType,
- DataType,
SearchType,
WebSocketResponse,
WebSocketJsonResponse,
SearchResponse,
CommentResponse,
PostResponse,
-} from './interfaces';
+} from 'lemmy-js-client';
+
+import { CommentSortType, DataType } from './interfaces';
import { UserService, WebSocketService } from './services';
import Tribute from 'tributejs/src/Tribute.js';
import markdown_it from 'markdown-it';
+import markdown_it_sub from 'markdown-it-sub';
+import markdown_it_sup from 'markdown-it-sup';
import markdownitEmoji from 'markdown-it-emoji/light';
import markdown_it_container from 'markdown-it-container';
import emojiShortName from 'emoji-short-name';
import tippy from 'tippy.js';
import moment from 'moment';
+export const favIconUrl = '/static/assets/favicon.svg';
+export const favIconPngUrl = '/static/assets/apple-touch-icon.png';
+export const defaultFavIcon = `${window.location.protocol}//${window.location.host}${favIconPngUrl}`;
export const repoUrl = 'https://github.com/LemmyNet/lemmy';
export const helpGuideUrl = '/docs/about_guide.html';
export const markdownHelpUrl = `${helpGuideUrl}#markdown-guide`;
export const sortingHelpUrl = `${helpGuideUrl}#sorting`;
export const archiveUrl = 'https://archive.is';
+export const elementUrl = 'https://element.io/';
export const postRefetchSeconds: number = 60 * 1000;
export const fetchLimit: number = 20;
{ code: 'gl', name: 'Galego' },
{ code: 'hu', name: 'Magyar Nyelv' },
{ code: 'ka', name: 'ქართული ენა' },
+ { code: 'ko', name: '한국어' },
{ code: 'km', name: 'ភាសាខ្មែរ' },
{ code: 'hi', name: 'मानक हिन्दी' },
{ code: 'fa', name: 'فارسی' },
linkify: true,
typographer: true,
})
+ .use(markdown_it_sub)
+ .use(markdown_it_sup)
.use(markdown_it_container, 'spoiler', {
validate: function (params: any) {
return params.trim().match(/^spoiler\s+(.*)$/);
}
export function routeSortTypeToEnum(sort: string): SortType {
- if (sort == 'new') {
- return SortType.New;
- } else if (sort == 'hot') {
- return SortType.Hot;
- } else if (sort == 'topday') {
- return SortType.TopDay;
- } else if (sort == 'topweek') {
- return SortType.TopWeek;
- } else if (sort == 'topmonth') {
- return SortType.TopMonth;
- } else if (sort == 'topyear') {
- return SortType.TopYear;
- } else if (sort == 'topall') {
- return SortType.TopAll;
- }
+ return SortType[sort];
}
export function routeListingTypeToEnum(type: string): ListingType {
- return ListingType[capitalizeFirstLetter(type)];
+ return ListingType[type];
}
export function routeDataTypeToEnum(type: string): DataType {
};
}
-export function getLanguage(): string {
+export function getLanguage(override?: string): string {
let user = UserService.Instance.user;
- let lang = user && user.lang ? user.lang : 'browser';
+ let lang = override || (user && user.lang ? user.lang : 'browser');
if (lang == 'browser') {
return getBrowserLanguage();
lang = 'ga';
} else if (lang.startsWith('sr')) {
lang = 'sr';
+ } else if (lang.startsWith('ko')) {
+ lang = 'ko';
} else {
lang = 'en';
}
return out;
}
-export function isCommentType(item: Comment | PrivateMessage): item is Comment {
- return (item as Comment).community_id !== undefined;
+export function isCommentType(
+ item: Comment | PrivateMessage | Post
+): item is Comment {
+ return (
+ (item as Comment).community_id !== undefined &&
+ (item as Comment).content !== undefined
+ );
+}
+
+export function isPostType(
+ item: Comment | PrivateMessage | Post
+): item is Post {
+ return (item as Post).stickied !== undefined;
}
export function toast(text: string, background: string = 'success') {
}).showToast();
}
-export function messageToastify(
- creator: string,
- avatar: string,
- body: string,
- link: string,
- router: any
-) {
+interface NotifyInfo {
+ name: string;
+ icon: string;
+ link: string;
+ body: string;
+}
+
+export function messageToastify(info: NotifyInfo, router: any) {
+ let htmlBody = info.body ? md.render(info.body) : '';
let backgroundColor = `var(--light)`;
let toast = Toastify({
- text: `${body}<br />${creator}`,
- avatar: avatar,
+ text: `${htmlBody}<br />${info.name}`,
+ avatar: info.icon,
backgroundColor: backgroundColor,
className: 'text-dark',
close: true,
onClick: () => {
if (toast) {
toast.hideToast();
- router.history.push(link);
+ router.history.push(info.link);
}
},
}).showToast();
}
+export function notifyPost(post: Post, router: any) {
+ let info: NotifyInfo = {
+ name: post.community_name,
+ icon: post.community_icon ? post.community_icon : defaultFavIcon,
+ link: `/post/${post.id}`,
+ body: post.name,
+ };
+ notify(info, router);
+}
+
+export function notifyComment(comment: Comment, router: any) {
+ let info: NotifyInfo = {
+ name: comment.creator_name,
+ icon: comment.creator_avatar ? comment.creator_avatar : defaultFavIcon,
+ link: `/post/${comment.post_id}/comment/${comment.id}`,
+ body: comment.content,
+ };
+ notify(info, router);
+}
+
+export function notifyPrivateMessage(pm: PrivateMessage, router: any) {
+ let info: NotifyInfo = {
+ name: pm.creator_name,
+ icon: pm.creator_avatar ? pm.creator_avatar : defaultFavIcon,
+ link: `/inbox`,
+ body: pm.content,
+ };
+ notify(info, router);
+}
+
+function notify(info: NotifyInfo, router: any) {
+ messageToastify(info, router);
+
+ if (Notification.permission !== 'granted') Notification.requestPermission();
+ else {
+ var notification = new Notification(info.name, {
+ icon: info.icon,
+ body: info.body,
+ });
+
+ notification.onclick = () => {
+ event.preventDefault();
+ router.history.push(info.link);
+ };
+ }
+}
+
export function setupTribute(): Tribute {
return new Tribute({
+ noMatchTemplate: function () {
+ return '';
+ },
collection: [
// Emojis
{
if (text) {
let form: SearchForm = {
q: text,
- type_: SearchType[SearchType.Users],
- sort: SortType[SortType.TopAll],
+ type_: SearchType.Users,
+ sort: SortType.TopAll,
page: 1,
limit: mentionDropdownFetchLimit,
};
WebSocketService.Instance.search(form);
- this.userSub = WebSocketService.Instance.subject.subscribe(
+ let userSub = WebSocketService.Instance.subject.subscribe(
msg => {
let res = wsJsonToRes(msg);
if (res.op == UserOperation.Search) {
};
});
cb(users);
- this.userSub.unsubscribe();
+ userSub.unsubscribe();
}
},
err => console.error(err),
if (text) {
let form: SearchForm = {
q: text,
- type_: SearchType[SearchType.Communities],
- sort: SortType[SortType.TopAll],
+ type_: SearchType.Communities,
+ sort: SortType.TopAll,
page: 1,
limit: mentionDropdownFetchLimit,
};
WebSocketService.Instance.search(form);
- this.communitySub = WebSocketService.Instance.subject.subscribe(
+ let communitySub = WebSocketService.Instance.subject.subscribe(
msg => {
let res = wsJsonToRes(msg);
if (res.op == UserOperation.Search) {
};
});
cb(communities);
- this.communitySub.unsubscribe();
+ communitySub.unsubscribe();
}
},
err => console.error(err),
return props.match.params.listing_type
? routeListingTypeToEnum(props.match.params.listing_type)
: UserService.Instance.user
- ? UserService.Instance.user.default_listing_type
+ ? Object.values(ListingType)[UserService.Instance.user.default_listing_type]
: ListingType.All;
}
return props.match.params.sort
? routeSortTypeToEnum(props.match.params.sort)
: UserService.Instance.user
- ? UserService.Instance.user.default_sort_type
- : SortType.Hot;
+ ? Object.values(SortType)[UserService.Instance.user.default_sort_type]
+ : SortType.Active;
}
export function getPageFromProps(props: any): number {
post.url = data.post.url;
post.name = data.post.name;
post.nsfw = data.post.nsfw;
+ post.deleted = data.post.deleted;
+ post.removed = data.post.removed;
+ post.stickied = data.post.stickied;
+ post.body = data.post.body;
+ post.locked = data.post.locked;
}
}
return CommentSortType.Top;
} else if (sort == SortType.New) {
return CommentSortType.New;
- } else if (sort == SortType.Hot) {
+ } else if (sort == SortType.Hot || sort == SortType.Active) {
return CommentSortType.Hot;
} else {
return CommentSortType.Hot;
(communityType && +b.stickied - +a.stickied) ||
b.hot_rank - a.hot_rank
);
+ } else if (sort == SortType.Active) {
+ posts.sort(
+ (a, b) =>
+ +a.removed - +b.removed ||
+ +a.deleted - +b.deleted ||
+ (communityType && +b.stickied - +a.stickied) ||
+ b.hot_rank_active - a.hot_rank_active
+ );
}
}
return `hsla(${Math.random() * 360}, 100%, 50%, 1)`;
}
-export function previewLines(text: string, lines: number = 3): string {
- // Use lines * 2 because markdown requires 2 lines
- return text
- .split('\n')
- .slice(0, lines * 2)
- .join('\n');
+export function previewLines(
+ text: string,
+ maxChars: number = 300,
+ maxLines: number = 1
+): string {
+ return (
+ text
+ .slice(0, maxChars)
+ .split('\n')
+ // Use lines * 2 because markdown requires 2 lines
+ .slice(0, maxLines * 2)
+ .join('\n') + '...'
+ );
}
export function hostname(url: string): string {
return regex.test(title);
}
+
+export function siteBannerCss(banner: string): string {
+ return ` \
+ background-image: linear-gradient( rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.8) ) ,url("${banner}"); \
+ background-attachment: fixed; \
+ background-position: top; \
+ background-repeat: no-repeat; \
+ background-size: 100% cover; \
+
+ width: 100%; \
+ max-height: 100vh; \
+ `;
+}
+++ /dev/null
-export const version: string = 'v0.7.21';
"number_of_comments": "{{count}} Kommentar",
"number_of_comments_plural": "{{count}} Kommentare",
"remove_comment": "Kommentar löschen",
- "communities": "Communitys",
+ "communities": "Communities",
"users": "Benutzer",
"create_a_community": "Eine Community anlegen",
"create_community": "Community erstellen",
"yes": "Ja",
"no": "Nein",
"powered_by": "Bereitgestellt durch",
- "landing_0": "Lemmy ist ein <1>Link-Aggregator</1> / Reddit Alternative im <2>Fediverse</2>.<3></3>Es ist selbst-hostbar, hat live-updates von Kommentar-threads und ist winzig (<4>~80kB</4>). Federation in das ActivityPub Netzwerk ist geplant. <5></5>Dies ist eine <6>sehr frühe Beta Version</6>, und viele Features funktionieren zurzeit nicht richtig oder fehlen. <7></7>Schlage neue Features vor oder melde Bugs <8>hier.</8><9></9>Gebaut mit <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
+ "landing": "Lemmy ist ein <1>Link-Aggregator</1> / Reddit Alternative im <2>Fediverse</2>.<3></3>Es ist selbst-hostbar, hat live-updates von Kommentar-threads und ist winzig (<4>~80kB</4>). Federation in das ActivityPub Netzwerk ist geplant. <5></5>Dies ist eine <6>sehr frühe Beta Version</6>, und viele Features funktionieren zurzeit nicht richtig oder fehlen. <7></7>Schlage neue Features vor oder melde Bugs <8>hier.</8><9></9>Gebaut mit <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>. <14></14> <15>Vielen Dank an unsere Mitwirkenden: </15> dessalines, Nutomic, asonix, zacanger, and iav.",
"not_logged_in": "Nicht eingeloggt.",
"community_ban": "Du wurdest von dieser Community gebannt.",
"site_ban": "Du wurdest von dieser Seite gebannt",
"messages": "Nachrichten",
"old_password": "Letztes Passwort",
"matrix_user_id": "Matrix Benutzer",
- "private_message_disclaimer": "Achtung: Private Nachrichten sind in Lemmy nicht sicher. Bitte erstelle einen <1>Riot.im</1> Account für sicheren Nachrichtenverkehr.",
+ "private_message_disclaimer": "Achtung: Private Nachrichten sind in Lemmy nicht verschlüsselt. Bitte erstelle einen<1>Element.io</1> Account für sicheren Nachrichtenverkehr.",
"send_notifications_to_email": "Sende Benachrichtigungen per Email",
"downvotes_disabled": "Downvotes deaktiviert",
"enable_downvotes": "Aktiviere Downvotes",
"click_to_delete_picture": "Klicke, um das Bild zu löschen.",
"picture_deleted": "Bild gelöscht.",
"select_a_community": "Wähle eine Community aus",
- "invalid_username": "Ungültiger Benutzername."
+ "invalid_username": "Ungültiger Benutzername.",
+ "bold": "fett",
+ "italic": "kursiv",
+ "subscript": "Tiefzeichen",
+ "superscript": "Hochzeichen",
+ "header": "Header",
+ "strikethrough": "durchgestrichen",
+ "quote": "Zitat",
+ "spoiler": "Spoiler",
+ "list": "Liste",
+ "not_a_moderator": "Kein Moderator.",
+ "invalid_url": "Ungültige URL.",
+ "must_login": "Du musst <1>eingeloggt oder registriert</1> sein um zu Kommentieren.",
+ "no_password_reset": "Du kannst dein Passwort ohne E-Mail nicht zurücksetzen.",
+ "cake_day_info": "Heute ist {{ creator_name }}'s cake day!",
+ "invalid_post_title": "Ungültiger Post Titel",
+ "cake_day_title": "Cake day:",
+ "what_is": "Was ist"
}
"verify_password": "Επαλήθευση κωδικού",
"old_password": "Παλιός κωδικός",
"forgot_password": "ξέχασα τον κωδικό μου",
- "reset_password_mail_sent": "Î\9cÏ\8cλιÏ\82 Ï\83Ï\84άλθηκε Îνα μήνÏ\85μα ηλεκÏ\84Ï\81ονικοÏ\8d Ï\84αÏ\87Ï\85δÏ\81ομείοÏ\85 για την επαναφορά του κωδικού σας.",
+ "reset_password_mail_sent": "ΣÏ\84άλθηκε μήνÏ\85μα Ï\83Ï\84ο ηλεκÏ\84Ï\81ονικÏ\8c Ï\84αÏ\87Ï\85δÏ\81ομείο για την επαναφορά του κωδικού σας.",
"password_change": "Αλλαγή κωδικού",
"new_password": "Νέος κωδικός",
- "no_email_setup": "Αυτός ο διακομιστής δεν έχει εγκαταστήσει σωστά το email.",
+ "no_email_setup": "Αυτός ο διακομιστής δεν έχει στήσει σωστά το email.",
"email": "Email",
"matrix_user_id": "Χρήστης Matrix",
- "private_message_disclaimer": "Προσοχή: τα προσωπικά μηνύματα στο Lemmy δεν είναι ασφαλή. Παρακαλούμε δημιουργήστε έναν λογαριασμό στο <1>Riot.im</1> για ασφαλή επικοινωνία.",
+ "private_message_disclaimer": "Προσοχή: τα προσωπικά μηνύματα στο Lemmy δεν είναι ασφαλή. Παρακαλούμε δημιουργήστε έναν λογαριασμό στο <1>Element.io</1> για ασφαλή επικοινωνία.",
"send_notifications_to_email": "Αποστολή ειδοποιήσεων στη διεύθυνση ηλεκτρονικού ταχυδρομείου",
"optional": "Προαιρετικό",
"expires": "Λήγει",
"transfer_site": "μεταφορά ιστότοπου",
"are_you_sure": "είστε σίγουρος;",
"powered_by": "Τροφοδοτείται από",
- "landing": "Το Lemmy είναι μια <1>ιστοσελίδα συγκέντρωσης συνδέσμων</1> / εναλλακτική του reddit, προορισμένη να δουλέψει μέσα στο <2>fediverse</2>.<3></3>Μπορεί να φιλοξενηθεί σε διακομιστή οποιουδήποτε, ανανεώνει ζωντανά (live) τα σχόλια, και είναι μικροσκοπικό σε μέγεθος (<4>~80kB</4>). Η ομοσπονδίωση με το ActivityPub δίκτυο βρίσκεται υπό εξέλιξη<5></5>Αυτή είναι μια <6>πολύ πρώιμη έκδοση beta</6>, συνεπώς πολλές λειτουργίες είναι προς το παρόν αναξιόπιστες ή ανύπαρκτες. <7></7>Προτείνετε καινούριες λειτουργίες ή αναφέρετε σφάλματα <8>εδώ.</8><9></9>Γραμμένο με <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
- "not_logged_in": "Î\9cη Ï\83Ï\85νδεμÎνος.",
+ "landing": "Το Lemmy είναι μια <1>ιστοσελίδα συγκέντρωσης συνδέσμων</1> / εναλλακτική του reddit, προορισμένη να δουλέψει μέσα στο <2>fediverse</2>.<3></3>Μπορεί να φιλοξενηθεί σε διακομιστή οποιουδήποτε, ανανεώνει ζωντανά (live) τα σχόλια, και είναι μικροσκοπικό σε μέγεθος (<4>~80kB</4>). Η ομοσπονδίωση με το ActivityPub δίκτυο βρίσκεται υπό εξέλιξη<5></5>Αυτή είναι μια <6>πολύ πρώιμη έκδοση beta</6>, συνεπώς πολλές λειτουργίες είναι προς το παρόν αναξιόπιστες ή ανύπαρκτες. <7></7>Προτείνετε καινούριες λειτουργίες ή αναφέρετε σφάλματα <8>εδώ.</8><9></9>Γραμμένο με <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>. <14></14> <15>Ευχαριστούμε τους συνεργάτες μας: </15> dessalines, Nutomic, asonix, zacanger, και iav.",
+ "not_logged_in": "Î\91Ï\80οÏ\83Ï\85νδεδÎμενος.",
"logged_in": "Συνδεμένος.",
"site_saved": "Ο ιστότοπος αποθηκεύτηκε.",
"community_ban": "Έχετε αποβληθεί από αυτή την κοινότητα.",
"site_ban": "Έχετε αποβληθεί από τον ιστότοπο",
- "couldnt_create_comment": "Î\94εν μÏ\80Ï\8cÏ\81εÏ\83ε να δημιοÏ\85Ï\81γηθεί Ï\84ο Ï\83Ï\87Ï\8cλιο.",
+ "couldnt_create_comment": "Î\91δÏ\85ναμία δημιοÏ\85Ï\81γίαÏ\82 Ï\83Ï\87Ï\8cλιοÏ\85.",
"couldnt_like_comment": "Δεν μπόρεσε να ψηφισθεί θετικά το σχόλιο.",
"couldnt_update_comment": "Δεν μπόρεσε να ενημερωθεί το σχόλιο.",
"couldnt_save_comment": "Δεν μπόρεσε να αποθηκευτεί το σχόλιο.",
"no": "όχι",
"top_day": "Κορυφαία σήμερα",
"joined": "Μέλος από",
- "couldnt_get_posts": "Î\94εν μÏ\80Ï\8cÏ\81εÏ\83αν να βÏ\81εθοÏ\8dν οι δημοÏ\83ιεÏ\8dÏ\83ειÏ\82",
- "couldnt_update_post": "Î\94εν μÏ\80Ï\8cÏ\81εÏ\83ε να ενημεÏ\81Ï\89θεί η δημοÏ\83ίεÏ\85Ï\83η",
+ "couldnt_get_posts": "Î\91δÏ\85ναμία εÏ\8dÏ\81εÏ\83ηÏ\82 δημοÏ\83ιεÏ\8dÏ\83Ï\89ν",
+ "couldnt_update_post": "Î\91δÏ\85ναμία ενημÎÏ\81Ï\89Ï\83ηÏ\82 δημοÏ\83ιεÏ\8dÏ\83ηÏ\82",
"couldnt_save_post": "Δεν μπόρεσε να αποθηκευτεί η δημοσίευση.",
"no_slurs": "Όχι προσβολές.",
"not_an_admin": "Ο χρήστης δεν είναι διαχειριστής.",
"site_already_exists": "Ο ιστότοπος υπάρχει ήδη.",
- "couldnt_update_site": "Î\94εν μÏ\80Ï\8cÏ\81εÏ\83ε να ενημεÏ\81Ï\89θεί ο ιÏ\83Ï\84Ï\8cÏ\84οÏ\80οÏ\82.",
- "couldnt_find_that_username_or_email": "Î\94εν μÏ\80Ï\8cÏ\81εÏ\83ε να βÏ\81εθεί αÏ\85Ï\84Ï\8c Ï\84ο Ï\8cνομα Ï\87Ï\81ήÏ\83Ï\84η ή η διεÏ\8dθÏ\85νÏ\83η ηλεκτρονικού ταχυδρομείου.",
+ "couldnt_update_site": "Î\91δÏ\85ναμία ενημÎÏ\81Ï\89Ï\83ηÏ\82 ιÏ\83Ï\84Ï\8cÏ\84οÏ\80οÏ\85.",
+ "couldnt_find_that_username_or_email": "Î\91δÏ\85ναμία εÏ\8dÏ\81εÏ\83ηÏ\82 Ï\87Ï\81ήÏ\83Ï\84η ή διεÏ\8dθÏ\85νÏ\83ηÏ\82 ηλεκτρονικού ταχυδρομείου.",
"password_incorrect": "Λάθος κωδικός.",
- "passwords_dont_match": "Οι κωδικοί δεν ταυτίζονται.",
+ "passwords_dont_match": "Οι κωδικοί δεν ταιριάζουν.",
"admin_already_created": "Δυστυχώς υπάρχει ήδη διαχειριστής.",
"user_already_exists": "Ο χρήστης υπάρχει ήδη.",
"email_already_exists": "Η διεύθυνση ηλεκτρονικού ταχυδρομείου υπάρχει ήδη.",
- "couldnt_update_user": "Î\94εν μÏ\80Ï\8cÏ\81εÏ\83ε να ενημεÏ\81Ï\89θεί ο Ï\87Ï\81ήÏ\83Ï\84ηÏ\82.",
+ "couldnt_update_user": "Î\91δÏ\85ναμία ενημÎÏ\81Ï\89Ï\83ηÏ\82 Ï\87Ï\81ήÏ\83Ï\84η.",
"system_err_login": "Σφάλμα στο σύστημα. Προσπαθήστε να αποσυνδεθείτε και να συνδεθείτε ξανά.",
- "couldnt_create_private_message": "Î\94εν μÏ\80Ï\8cÏ\81εÏ\83ε να δημιοÏ\85Ï\81γηθεί Ï\80Ï\81οÏ\83Ï\89Ï\80ικÏ\8c μήνÏ\85μα.",
+ "couldnt_create_private_message": "Î\91δÏ\85ναμία δημιοÏ\85Ï\81γίαÏ\82 Ï\80Ï\81οÏ\83Ï\89Ï\80ικοÏ\8d μηνÏ\8dμαÏ\84οÏ\82.",
"no_private_message_edit_allowed": "Δεν επιτρέπεται η επεξεργασία του προσωπικού μηνύματος.",
"time": "Χρόνος",
- "couldnt_update_private_message": "Î\94εν μÏ\80Ï\8cÏ\81εÏ\83ε να ενημεÏ\81Ï\89θεί Ï\84ο Ï\80Ï\81οÏ\83Ï\89Ï\80ικÏ\8c μήνÏ\85μα.",
+ "couldnt_update_private_message": "Î\91δÏ\85ναμία ενημÎÏ\81Ï\89Ï\83ηÏ\82 Ï\80Ï\81οÏ\83Ï\89Ï\80ικοÏ\8d μηνÏ\8dμαÏ\84οÏ\82.",
"action": "Δράση",
"emoji_picker": "Διαλογέας emoji",
"block_leaving": "Είστε σίγουρος ότι θέλετε να φύγετε;",
- "invalid_username": "Λάθος όνομα χρήστη."
+ "invalid_username": "Λάθος όνομα χρήστη.",
+ "bold": "έμφαση",
+ "italic": "πλάγια",
+ "subscript": "δείκτης",
+ "superscript": "εκθέτης",
+ "header": "επικεφαλίδα",
+ "strikethrough": "διαγράμμιση",
+ "quote": "παράθεση",
+ "spoiler": "σπόιλερ",
+ "list": "λίστα",
+ "not_a_moderator": "Δεν είναι συντονιστής.",
+ "invalid_url": "Μη έγκυρο URL.",
+ "must_login": "Πρέπει να <1>συνδεθείτε ή να κάνετε εγγραφή</1> για να σχολιάσετε.",
+ "no_password_reset": "Δεν θα μπορέσετε να κάνετε επαναφορά του κωδικού σας χωρίς διεύθυνση ηλεκτρονικού ταχυδρομείου.",
+ "cake_day_title": "Ημέρα cake:",
+ "cake_day_info": "Σήμερα είναι ημέρα cake του {{ creator_name }}!",
+ "invalid_post_title": "Μη έγκυρη επικεφαλίδα δημοσίευσης",
+ "what_is": "Τι είναι"
}
"number_of_comments": "{{count}} Comment",
"number_of_comments_plural": "{{count}} Comments",
"remove_comment": "Remove Comment",
+ "remove_posts_comments": "Remove Posts and Comments",
"communities": "Communities",
"users": "Users",
"create_a_community": "Create a community",
"upload_image": "upload image",
"avatar": "Avatar",
"upload_avatar": "Upload Avatar",
+ "banner": "Banner",
+ "upload_banner": "Upload Banner",
+ "icon": "Icon",
+ "upload_icon": "Upload Icon",
"show_avatars": "Show Avatars",
"show_context": "Show context",
"formatting_help": "formatting help",
"unsticky": "unsticky",
"link": "link",
"archive_link": "archive link",
+ "bold": "bold",
+ "italic": "italic",
+ "subscript": "subscript",
+ "superscript": "superscript",
+ "header": "header",
+ "strikethrough": "strikethrough",
+ "quote": "quote",
+ "spoiler": "spoiler",
+ "list": "list",
"mod": "mod",
"mods": "mods",
"moderates": "Moderates",
"site_config": "Site Configuration",
"remove_as_mod": "remove as mod",
"appoint_as_mod": "appoint as mod",
+ "leave_mod_team": "leave mod team",
"modlog": "Modlog",
"admin": "admin",
"admins": "admins",
"number_online": "{{count}} User Online",
"number_online_plural": "{{count}} Users Online",
"name": "Name",
+ "name_explain": "Name – used as the identifier for the community, cannot be changed.",
+ "display_name": "Display name",
+ "display_name_explain": "Display name — shown as the title on the community's page, can be changed.",
"title": "Title",
"category": "Category",
"subscribers": "Subscribers",
"sidebar": "Sidebar",
"sort_type": "Sort type",
"hot": "Hot",
+ "active": "Active",
"new": "New",
"old": "Old",
"top_day": "Top day",
"email": "Email",
"matrix_user_id": "Matrix User",
"private_message_disclaimer":
- "Warning: Private messages in Lemmy are not secure. Please create an account on <1>Riot.im</1> for secure messaging.",
+ "Warning: Private messages in Lemmy are not secure. Please create an account on <1>Element.io</1> for secure messaging.",
"send_notifications_to_email": "Send notifications to Email",
"optional": "Optional",
"expires": "Expires",
"landing_0":
"Lemmy is a <1>link aggregator</1> / reddit alternative, intended to work in the <2>fediverse</2>.<3></3>It's self-hostable, has live-updating comment threads, and is tiny (<4>~80kB</4>). Federation into the ActivityPub network is on the roadmap. <5></5>This is a <6>very early beta version</6>, and a lot of features are currently broken or missing. <7></7>Suggest new features or report bugs <8>here.</8><9></9>Made with <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>. <14></14> <15>Thank you to our contributors: </15> dessalines, Nutomic, asonix, zacanger, and iav.",
"not_logged_in": "Not logged in.",
+ "bio_length_overflow": "User bio cannot exceed 300 characters.",
"logged_in": "Logged in.",
"must_login": "You must <1>log in or register</1> to comment.",
"site_saved": "Site Saved.",
"couldnt_save_post": "Couldn't save post.",
"no_slurs": "No slurs.",
"not_an_admin": "Not an admin.",
+ "not_a_moderator": "Not a moderator.",
"site_already_exists": "Site already exists.",
"couldnt_update_site": "Couldn't update site.",
"couldnt_find_that_username_or_email":
"password_incorrect": "Password incorrect.",
"passwords_dont_match": "Passwords do not match.",
"no_password_reset": "You will not be able to reset your password without an email.",
+ "captcha_incorrect": "Captcha incorrect.",
+ "enter_code": "Enter Code",
"invalid_username": "Invalid username.",
"admin_already_created": "Sorry, there's already an admin.",
"user_already_exists": "User already exists.",
"what_is": "What is",
"cake_day_title": "Cake day:",
"cake_day_info": "It's {{ creator_name }}'s cake day today!",
- "invalid_post_title": "Invalid post title"
+ "invalid_post_title": "Invalid post title",
+ "invalid_url": "Invalid URL.",
+ "play_captcha_audio": "Play Captcha Audio",
+ "bio": "Bio",
+ "instances": "Instances",
+ "linked_instances": "Linked Instances",
+ "none_found": "None found."
}
"unlock": "malŝlosi",
"lock": "ŝlosi",
"link": "ligilo",
- "mod": "moderanto",
- "mods": "moderantoj",
- "moderates": "Moderigas",
+ "mod": "reguligisto",
+ "mods": "reguligistoj",
+ "moderates": "reguligas",
"settings": "Agordoj",
- "remove_as_mod": "forigi per moderanto",
- "appoint_as_mod": "nomumi per moderanto",
- "modlog": "Moderlogo",
+ "remove_as_mod": "forigi kiel reguligisto",
+ "appoint_as_mod": "nomumi reguligisto",
+ "modlog": "Protokolo de reguligado",
"admin": "administranto",
"admins": "administrantoj",
"remove_as_admin": "forigi kiel administranto",
"appoint_as_admin": "nomumi administranto",
"remove": "forigi",
- "removed": "fortirita",
+ "removed": "forigita de reguligisto",
"locked": "ŝlosita",
"reason": "Kialo",
"mark_as_read": "marki legita",
"next": "Pluen",
"sidebar": "Flankobreto",
"sort_type": "Ordigilo",
- "hot": "Varmaj",
+ "hot": "Furoraj",
"new": "Novaj",
"top_day": "Supraj tagaj",
"week": "Semajno",
"transfer_community": "transdoni la komunumon",
"transfer_site": "transdoni la retejon",
"powered_by": "Konstruita per",
- "landing": "Lemmy estas <1>amasigilo de ligiloj</1> / alternativo de Reddit, intencita funkcii en la <2>federuniverso</2>.<3></3>ĝi estas mem-gastigebla, havas tuj-ĝisdatigojn de komentaroj, kaj estas malgrandega (<4>~80kB</4>). Federado en la reto de ActivityPub estas planita. <5></5>Ĉi tio estas <6>tre frua beta-versio</6>, kaj multaj funkcioj estas nune difektaj aŭ mankaj. <7></7>Proponu novajn funkciojn aŭ raportu erarojn <8>ĉi tie.</8><9></9>Konstruita per <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
+ "landing": "Lemmy estas <1>amasigilo de ligiloj</1> / alternativo de Reddit, intencita funkcii en la <2>federuniverso</2>.<3></3>ĝi estas mem-gastigebla, havas tuj-ĝisdatigojn de komentaroj, kaj estas malgrandega (<4>~80kB</4>). Federado en la reto de ActivityPub estas planita. <5></5>Ĉi tio estas <6>tre frua beta-versio</6>, kaj multaj funkcioj estas nune difektaj aŭ mankaj. <7></7>Proponu novajn funkciojn aŭ raportu erarojn <8>ĉi tie.</8><9></9>Konstruita per <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>. <14></14> <15>Dankon al niaj kontribuintoj: </15> dessalines, Nutomic, asonix, zacanger, kaj iav.",
"not_logged_in": "Nesalutinta.",
"community_ban": "Vi estas forbarita de la komunumo.",
"site_ban": "Vi estas forbarita de la retejo",
"couldnt_find_community": "Ne povis trovi la komunumon.",
"couldnt_update_community": "Ne povis ĝisdatigi la komunumon.",
"community_already_exists": "Komunumo jam ekzistas.",
- "community_moderator_already_exists": "Komunuma moderanto jam ekzistas.",
+ "community_moderator_already_exists": "Reguligisto de komunumo jam ekzistas.",
"community_follower_already_exists": "Abonanto de komunumo jam ekzistas.",
"community_user_already_banned": "Uzanto de komunumo jam estas forbarita.",
"couldnt_create_post": "Ne povis krei la afiŝon.",
"number_of_upvotes_plural": "{{count}} porvoĉoj",
"downvote": "Kontraŭvoĉi",
"number_of_downvotes": "{{count}} kontraŭvoĉo",
- "number_of_downvotes_plural": "{{count}} kontraŭvoĉoj"
+ "number_of_downvotes_plural": "{{count}} kontraŭvoĉoj",
+ "what_is": "Kio estas",
+ "must_login": "Vi devas <1>saluti aŭ registriĝi</1> por komenti.",
+ "no_password_reset": "Vi ne povos restarigi vian pasvorton sen retpoŝtadreso.",
+ "cake_day_title": "Tortotago:",
+ "cake_day_info": "Hodiaŭ estas tortotago de {{ creator_name }}!",
+ "invalid_post_title": "Nevalida titolo de afiŝo"
}
"no_email_setup": "Este servidor no ha activado correctamente el correo.",
"email": "Correo electrónico",
"matrix_user_id": "Usuario Matricial",
- "private_message_disclaimer": "Aviso: Los mensajes privados en Lemmy no son seguros. Por favor cree una cuenta en <1>Riot.im</1> para mensajeria segura.",
+ "private_message_disclaimer": "Aviso: Los mensajes privados en Lemmy no son seguros. Por favor cree una cuenta en <1>Element.io</1> para mensajería segura.",
"send_notifications_to_email": "Enviar notificaciones al correo",
"optional": "Opcional",
"expires": "Expira",
"yes": "sí",
"no": "no",
"powered_by": "Impulsado por",
- "landing_0": "Lemmy es un <1>agregador de links</1> / alternativa a reddit, con la intención de funcionar en el <2>fediverso</2>.<3></3>Es alojable por uno mismo (sin necesidad de grandes compañías), tiene actualización en vivo de cadenas de comentarios, y es pequeño (<4>~80kB</4>). Federar con el sistema de redes ActivityPub forma parte de los objetivos del proyecto. <5></5>Esta es una <6>version beta muy prematura</6>, y actualmente muchas de las características están rotas o faltan. <7></7>Sugiere nuevas características o reporta errores <8>aquí</8>.<9></9>Hecho con <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
+ "landing": "Lemmy es un <1>agregador de links</1> / alternativa a reddit, con la intención de funcionar en el <2>fediverso</2>.<3></3>Es alojable por uno mismo (sin necesidad de grandes compañías), tiene actualización en vivo de cadenas de comentarios, y es pequeño (<4>~80kB</4>). Federar con el sistema de redes ActivityPub forma parte de los objetivos del proyecto. <5></5>Esta es una <6>version beta muy prematura</6>, y actualmente muchas de las características están rotas o faltan. <7></7>Sugiere nuevas características o reporta errores <8>aquí</8>.<9></9>Hecho con <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>. <14> </14><15>Gracias a todos los contribuyentes: </15> dessalines, Nutomic, asonix, zacanger, and iav.",
"not_logged_in": "No has iniciado sesión.",
"logged_in": "Has iniciado sesión.",
"community_ban": "Has sido expulsado de esta comunidad.",
"invalid_username": "Nombre de usuario inválido.",
"invalid_community_name": "Nombre no válido.",
"click_to_delete_picture": "Pulse para eliminar la imagen.",
- "picture_deleted": "Imagen eliminada."
+ "picture_deleted": "Imagen eliminada.",
+ "italic": "cursiva",
+ "subscript": "subíndice",
+ "superscript": "superíndice",
+ "header": "título",
+ "quote": "cita",
+ "spoiler": "spoiler",
+ "list": "lista",
+ "no_password_reset": "No podrás restablecer tu contraseña si no tienes correo.",
+ "what_is": "Cuanto es",
+ "bold": "negrita",
+ "strikethrough": "tachado",
+ "must_login": "Debes <1>iniciar sesión o registrarse</1> para hacer comentarios.",
+ "cake_day_info": "¡Hoy es el cumpleaños de {{ creator_name }}!"
}
"url": "URL",
"chat": "Txata",
"your_site": "zure gunea",
- "nsfw": "NSFW (eduki hunkigarria)",
+ "nsfw": "NSFW (eduki hunkigarriak)",
"block_leaving": "Ziur al zaude atera nahi duzula?",
"bitcoin": "Bitcoin",
"ethereum": "Ethereum",
"remove_community": "Ezabatu komunitatea",
"subscribed_to_communities": "<1>Komunitateetara</1> harpidetuta",
"trending_communities": "<1>Komunitateen</1> joerak",
- "list_of_communities": "Komunitate-zerrenda",
+ "list_of_communities": "Komunitateen zerrenda",
"community_reqs": "Letra xehez, azpimarratuta eta hutsunerik gabe.",
"create_private_message": "Sortu mezu pribatua",
"cancel": "Ezeztatu",
"stickied": "finkatuta",
"reason": "Arrazoia",
"mark_as_read": "markatu irakurrita gisa",
- "deleted": "sortzaileak ezabatua",
+ "deleted": "sortzaileak ezabatu du",
"delete_account_confirm": "Abisua: honek zure datu guztiak betirako ezabatu ditu. Sartu zure pasahitza baieztatzeko.",
"restore": "leheneratu",
"unban_from_site": "kendu debekua gunean",
"reset_password_mail_sent": "Eposta bat bidali da zure pasahitza berrezarri dezazun.",
"no_email_setup": "Zerbitzari honek ez du eposta ondo konfiguraturik.",
"matrix_user_id": "Matrix erabiltzailea",
- "private_message_disclaimer": "Abisua: Lemmyko mezu pribatuak ez dira seguruak. Mesedez, sortu kontu bat <1>Riot.im</1>en mezu seguruak trukatzeko.",
+ "private_message_disclaimer": "Abisua: Lemmyko mezu pribatuak ez dira seguruak. Mesedez, sortu kontu bat <1>Element.io</1>n mezu seguruak trukatzeko.",
"send_notifications_to_email": "Bidali jakinarazpenak epostara",
"optional": "Hautazkoa",
"browser_default": "Nabigatzaileko lehenetsia",
"number_of_upvotes_plural": "{{count}} aldeko bozka",
"open_registration": "Izen-ematea irekia",
"registration_closed": "Izen-ematea itxira",
- "enable_nsfw": "Gaitu NSFW (eduki hunkigarria)",
+ "enable_nsfw": "Gaitu NSFW (eduki hunkigarriak)",
"body": "Gorputza",
"copy_suggested_title": "kopiatu iradokitako izenburua: {{title}}",
"community": "Komunitatea",
"lemmy_instance_setup": "Lemmy instantziaren ezarpena",
"setup_admin": "Ezarri gunearen administratzailea",
"modified": "aldatuta",
- "show_nsfw": "Erakutsi eduki hunkigarria (NSFW)",
+ "show_nsfw": "Erakutsi eduki hunkigarriak (NSFW)",
"expires": "Noiz iraungitzen da:",
"theme": "Itxura",
"sponsors": "Babesleak",
"support_on_open_collective": "OpenCollective bitartez lagundu",
"donate_to_lemmy": "Egin dohaintza bat Lemmyri",
"donate": "Dohaintza egin",
- "general_sponsors": "Babesle orokorrak Lemmyri 10 eta 39 dolar artean eman zizkiotenak dira.",
- "silver_sponsors": "Zilarrezko babesleak Lemmyri 40 dolar eman zizkiotenak dira.",
+ "general_sponsors": "Babesle orokorrak Lemmyri 10 eta 39 dolar artean eman dizkiotenak dira.",
+ "silver_sponsors": "Zilarrezko babesleak Lemmyri 40 dolar eman dizkiotenak dira.",
"crypto": "Kriptomonetak",
"code": "Kodea",
"joined": "Batuta",
- "by": "egilea",
- "to": "nori",
+ "by": "egilea:",
+ "to": "non:",
"from": "nork",
"transfer_community": "transferentzia-komunitatea",
"transfer_site": "transferentzia-gunea",
"are_you_sure": "ziur al zaude?",
- "powered_by": "Egilea",
- "landing": "Lemmy <1>esteka-agregatzailea</1> / reddit-en ordezkoa da, eta <2>fedibertsoan</2> lan egiteko sortua da. <3></3>Norberak ostatu dezake, iruzkin-hari eguneratuak ditu eta txikia da (<4>~80kB</4>). ActivityPub sareko federazioa bide-orrian dago. <5></5>Hau <6>beta bertsio goiztiarra</6> da eta funtzionalitate asko hautsita edo egin gabe ditu oraindik. <7></7>Iradoki itzazu funtzionalitate berriak edo jakinarazi akatsak <8>hemen</8>.<9></9><10>Rust</10>, <11>Actix</11>, <12>Inferno</12> eta <13>Typescript</13>ekin egina.",
+ "powered_by": "Egilea:",
+ "landing": "Lemmy <1>esteka-agregatzailea</1> / reddit-en ordezkoa da, eta <2>fedibertsoan</2> lan egiteko sortua da. <3></3>Norberak ostatu dezake, iruzkin-hari eguneratuak ditu eta txikia da (<4>~80kB</4>). ActivityPub sareko federazioa bide-orrian dago. <5></5>Hau <6>beta bertsio goiztiarra</6> da eta funtzionalitate asko hautsita edo egin gabe ditu oraindik. <7></7>Iradoki itzazu funtzionalitate berriak edo jakinarazi akatsak <8>hemen</8>.<9></9><10>Rust</10>, <11>Actix</11>, <12>Inferno</12> eta <13>Typescript</13>ekin egina. <14></14> <15>Eskerrak ematen dizkiegu gure laguntzaileei: </15> dessalines, Nutomic, asonix, zacanger eta iav.",
"logged_in": "Saioa hasi duzu.",
"not_logged_in": "Ez duzu saiorik hasi.",
"site_saved": "Gunea gorde da.",
"couldnt_update_private_message": "Ezin izan da mezu pribatu hori eguneratu.",
"emoji_picker": "Emoji hautagailua",
"invalid_username": "Erabiltzaile-izen baliogabea.",
- "what_is": "Zer da"
+ "what_is": "Zenbat da",
+ "bold": "lodia",
+ "italic": "etzana",
+ "subscript": "Azpi-indizea",
+ "superscript": "Goi-indizea",
+ "header": "goiburua",
+ "quote": "aipua",
+ "strikethrough": "marratua",
+ "list": "zerrenda",
+ "spoiler": "spoiler",
+ "not_a_moderator": "Ez zara moderatzailea.",
+ "invalid_url": "URL baliogabea.",
+ "must_login": "<1>Saioa hasi edo izena eman</1> behar duzu iruzkinak egiteko.",
+ "no_password_reset": "Ezingo duzu zure pasahitza berrezarri epostarik ez baduzu.",
+ "invalid_post_title": "Bidalketa izenburu baliogabea",
+ "cake_day_title": "Izen-emate eguna:",
+ "cake_day_info": "{{ creator_name }}(e)ren urtebetetzea da gaur!"
}
"create_private_message": "Luo yksityisviesti",
"send_secure_message": "Lähetä suojattu viesti",
"send_message": "Lähetä viesti",
- "message": "Viesti",
+ "message": "Lähetä",
"edit": "muokkaa",
"reply": "vastaa",
"cancel": "Peru",
"mods": "moderaattorit",
"moderates": "Moderoi",
"settings": "Asetukset",
- "remove_as_mod": "Poista moderaattorina",
+ "remove_as_mod": "Poista moderaattorin asemasta",
"appoint_as_mod": "Nimitä moderaattoriksi",
"modlog": "Moderoinnin loki",
"admin": "ylläpitäjä",
"admins": "ylläpitäjät",
- "remove_as_admin": "poista ylläpitäjänä",
+ "remove_as_admin": "poista ylläpitäjän asemasta",
"appoint_as_admin": "nimitä ylläpitäjäksi",
"remove": "poista",
"removed": "poistettu",
"login_sign_up": "Kirjaudu sisään / Rekisteröidy",
"login": "Kirjaudu sisään",
"sign_up": "Rekisteröidy",
- "notifications_error": "Työpöydän ilmoitukset eivät ole saatavilla selaimellesi. Yritä Firefoxia tai Chromea.",
+ "notifications_error": "Työpöydän ilmoitukset eivät ole saatavilla selaimellesi. Kokeile Firefoxilla tai Chromella.",
"unread_messages": "Lukemattomat viestit",
"messages": "Viestit",
"password": "Salasana",
"theme": "Teema",
"sponsors": "Sponsorit",
"sponsors_of_lemmy": "Lemmy-sponsorit",
- "sponsor_message": "Lemmy on vapaa, <1>avoimen lähdekoodin</1> -ohjelmisto, eli mainontaa, rahantekemistä, tai pääomasijoitusta täällä ei tule ikinä olemaan. Lahjoituksesi tukevat suoraan projektin täysipäiväistä kehitystä. Kiitokset seuraaville ihmisille:",
+ "sponsor_message": "Lemmy on vapaa, <1>avoimen lähdekoodin</1> -ohjelmisto, eli tällä ei tulla tekemään rahaa. Lahjoituksesi tukevat suoraan projektin täysipäiväistä kehitystä. Kiitokset seuraaville lahjoittajille:",
"support_on_patreon": "Tue Patreonissa",
"donate_to_lemmy": "Lahjoita Lemmylle",
"donate": "Lahjoita",
- "general_sponsors": "Yleisiä sponsoreja ovat he, jotka lupaavat 10-39 dollaria Lemmylle.",
+ "general_sponsors": "Yleiset sponsorit lupaavat 10-39 dollaria Lemmylle.",
"crypto": "Krypto",
"bitcoin": "Bitcoin",
"ethereum": "Ethereum",
"monero": "Monero",
- "code": "Code",
+ "code": "Lähdekoodi",
"joined": "Liittyi",
"by": "käyttäjältä",
"to": "yhteisössä",
"from": "paikasta",
- "transfer_community": "siirron yhteisö",
- "transfer_site": "siirron määrä",
+ "transfer_community": "siirrä yhteisö",
+ "transfer_site": "siirrä sivusto",
"are_you_sure": "oletko varma?",
"yes": "kyllä",
"no": "ei",
"powered_by": "Vauhdittajana",
- "landing_0": "Lemmy on <1>linkinkerääjä</1> / Reddit-vaihtoehto, tarkoitettu toimimaan <2>fediversessä</2>.<3></3>Sitä voi isännöidä itse, siinä on tosiaikaisesti päivittyvät kommenttiketjut, ja se on pieni (<4>~80 kilotavua</4>). Federointi ActivityPub-verkkoon on suunnittelun alla. <5></5>Tämä on <6>hyvin varhainen betaversio</6>, ja monet ominaisuudet ovat toistaiseksi rikki tai poissa. <7></7>Ehdota uusia ominaisuuksia tai raportoi bugeja <8>tänne.</8><9></9>Tehty teknologioilla <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
+ "landing_0": "Lemmy on <2>fediversessä</2> toimiva <1>linkinkerääjä</1> / Reddit-vaihtoehto.<3></3>Sitä voi ylläpitää itse, siinä on tosiaikaisesti päivittyvät kommenttiketjut, ja se on pieni (<4>~80 kilotavua</4>). Federointi ActivityPub-verkkoon on suunnittelun alla. <5></5>Tämä on <6>hyvin varhainen betaversio</6>, ja monet ominaisuudet ovat toistaiseksi rikki tai poissa. <7></7>Ehdota uusia ominaisuuksia tai raportoi bugeja <8>tänne.</8><9></9>Tehty teknologioilla <10>Rust</10>, <11>Actix</11>, <12>Inferno</12> ja <13>Typescript</13>. Kiitoksia projektin kehitykseen osallistuneille käyttäjille dessalines, Nutomic, asonix, zacanger ja iav.",
"not_logged_in": "Ei kirjautunut sisään.",
"logged_in": "Kirjautunut sisään.",
"community_ban": "Sinulle on asetettu porttikielto tähän yhteisöön.",
- "site_ban": "Sinut on asetettu porttikieltoon tältä sivustolta",
+ "site_ban": "Sinut on asetettu porttikieltoon tällä sivustolla",
"couldnt_create_comment": "Kommenttia ei pystytty luomaan.",
"couldnt_like_comment": "Kommentista ei voitu tykätä.",
"couldnt_update_comment": "Kommenttia ei voitu päivittää.",
"couldnt_find_that_username_or_email": "Käyttäjänimeä tai sähköpostia ei onnistuttu löytämään.",
"password_incorrect": "Salasana on väärin.",
"passwords_dont_match": "Salasanat eivät täsmää.",
+ "no_password_reset": "Et voi nollata salasanaasi ilman sähköpostia.",
"admin_already_created": "Anteeksi, mutta täällä on jo ylläpitäjä.",
"user_already_exists": "Käyttäjä on jo olemassa.",
"email_already_exists": "Sähköposti on jo olemassa.",
"no_private_message_edit_allowed": "Sinulla ei ole oikeutta muokata yksityisviestiä.",
"couldnt_update_private_message": "Yksityisviestiä ei voitu päivittää.",
"more": "lisää",
- "cross_posted_to": "ristipostattu: ",
+ "cross_posted_to": "jaettu ristiin: ",
"sorting_help": "apua lajitteluun",
"show_context": "Näytä yhteys",
"admin_settings": "Ylläpitäjän asetukset",
"block_leaving": "Haluatko varmasti poistua?",
"silver_sponsors": "Hopeasponsoreita ovat ne, jotka lupaavat 40 dollaria Lemmylle.",
"post_title_too_long": "Viestin otsikko on liian pitkä.",
- "support_on_open_collective": "Tie OpenCollectivessa",
- "site_saved": "Sivu tallennettu."
+ "support_on_open_collective": "Tue OpenCollectivessa",
+ "site_saved": "Sivu tallennettu.",
+ "what_is": "Mikä on",
+ "cake_day_title": "Kakkupäivä:",
+ "cake_day_info": "Tänään on käyttäjän {{ creator_name }} kakkupäivä!",
+ "invalid_post_title": "Väärä viestin otsikko"
}
"yes": "oui",
"no": "non",
"powered_by": "Propulsé par",
- "landing": "Lemmy est un <1>aggrégateur de liens</1>, similaire à Reddit et conçu pour fonctionner sur le <2>Fédivers</2>.<3></3>Il est auto-hébergeable, se met à jour en direct et est léger (<4>~80kB</4>). La fédération via ActivityPub est prévue dans sa feuille de route. <5></5>Lemmy est une <6>version beta très précoce</6> et de nombreuses fonctionnalités sont manquantes ou non fonctionnelles. <7></7>Vous pouvez signaler des bugs et suggérer de nouvelles fonctionnalités <8>ici.</8><9></9>Créé avec <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
+ "landing": "Lemmy est un <1>aggrégateur de liens</1>, similaire à Reddit et conçu pour fonctionner sur le <2>Fédivers</2>.<3></3>Il est auto-hébergeable, se met à jour en direct et est léger (<4>~80kB</4>). La fédération via ActivityPub est prévue dans sa feuille de route. <5></5>Lemmy est une <6>version beta très précoce</6> et de nombreuses fonctionnalités sont manquantes ou non fonctionnelles. <7></7>Vous pouvez signaler des bugs et suggérer de nouvelles fonctionnalités <8>ici.</8><9></9>Créé avec <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>. <14></14> <15>Merci à nos contributeurs : </15> dessalines, Nutomic, asonix, zacanger, et iav.",
"not_logged_in": "Vous n’êtes pas connecté.",
"logged_in": "Vous êtes connecté.",
"community_ban": "Vous avez été banni de cette communauté.",
"invalid_username": "Nom d'utilisateur invalide.",
"invalid_community_name": "Nom invalide.",
"click_to_delete_picture": "Cliquer pour supprimer l'image.",
- "picture_deleted": "Image supprimée."
+ "picture_deleted": "Image supprimée.",
+ "invalid_post_title": "Titre du post invalide",
+ "must_login": "Vous devez vous être <1>connecté ou enregistré</1> pour commenter.",
+ "no_password_reset": "Vous ne pourrez pas réinitialiser votre mot de passe sans un e-mail.",
+ "what_is": "Combien font",
+ "cake_day_title": "Lemmyversaire :"
}
-{}
+{
+ "bold": "trom",
+ "italic": "iodálach",
+ "header": "ceanntásc",
+ "strikethrough": "stailc tríd",
+ "quote": "ceanglófar",
+ "spoiler": "milleadh scéil",
+ "list": "liosta",
+ "admin_settings": "Socruithe Riaracháin",
+ "site_config": "Cumraíocht Suímh",
+ "remove_as_mod": "bhaint mar modhnóir",
+ "appoint_as_mod": "ceapachán mar mhodhnóir",
+ "locked": "glasáilte",
+ "stickied": "bioráin",
+ "reason": "Cúis",
+ "mark_as_read": "marc mar a léitear",
+ "mark_as_unread": "marc mar neamhléite",
+ "delete": "scriosadh",
+ "deleted": "scriosta ag cruthaitheoir",
+ "delete_account": "Scrios Cuntas",
+ "click_to_delete_picture": "Cliceáil chun pictiúr a scriosadh.",
+ "picture_deleted": "Scriosadh an pictiúr.",
+ "restore": "athchóirigh",
+ "ban": "cosc",
+ "ban_from_site": "cosc ón suíomh",
+ "unban": "Cealaigh cosc",
+ "unban_from_site": "Cealaigh cosc ón suíomh",
+ "banned": "coisceadh",
+ "banned_users": "Úsáideoirí Coisceadh",
+ "save": "sábháil",
+ "unsave": "cealaigh sábháil",
+ "create": "cruthaigh",
+ "creator": "cruthaitheoir",
+ "email_or_username": "Ríomhphost nó Ainm Úsáideora",
+ "number_online_0": "{{count}} Úsáideoir Ar Líne",
+ "number_online_1": "{{count}} Úsáideoirí Ar Líne",
+ "number_online_2": "{{count}} Úsáideoirí Ar Líne",
+ "number_online_3": "{{count}} Úsáideoirí Ar Líne",
+ "number_online_4": "{{count}} Úsáideoirí Ar Líne",
+ "name": "Ainm",
+ "title": "Teideal",
+ "subscribers": "Síntiúsóirí",
+ "both": "Araon",
+ "saved": "Coinníodh",
+ "unsubscribe": "Díliostáil",
+ "subscribe": "Liostáil",
+ "subscribed": "Suibscríofa",
+ "prev": "Roimhe",
+ "next": "Chéad Eile",
+ "hot": "Te",
+ "new": "Nua",
+ "old": "Sean",
+ "top_day": "Lá barr",
+ "week": "Seachtain",
+ "month": "Mí",
+ "year": "Bliain",
+ "all": "Gach",
+ "top": "Barr",
+ "api": "API",
+ "docs": "Doic",
+ "inbox": "Bosca Isteach",
+ "inbox_for": "Bosca Isteach do <1>{{user}}</1>",
+ "mark_all_as_read": "marcáil gach rud mar atá léite",
+ "type": "Cineál",
+ "unread": "Gan léamh",
+ "replies": "Freagraí",
+ "mentions": "Luann",
+ "reply_sent": "Freagra seolta",
+ "message_sent": "Teachtaireacht seolta",
+ "search": "Cuardaigh",
+ "overview": "Forbhreathnú",
+ "view": "Amharc",
+ "logout": "Logáil Amach",
+ "login_sign_up": "Logáil Isteach / Cláraigh",
+ "login": "Logáil Isteach",
+ "unread_messages": "Teachtaireachtaí Neamhléite",
+ "messages": "Teachtaireachtaí",
+ "password": "Pasfhocal",
+ "verify_password": "Deimhnigh Pasfhocal",
+ "old_password": "Sean Pasfhocal",
+ "forgot_password": "Dearmad ar pasfhocal",
+ "reset_password_mail_sent": "Seol Ríomhphost chun do phasfhocal a athshocrú.",
+ "password_change": "Athrú Pasfhocal",
+ "new_password": "Focal Faire Nua",
+ "no_email_setup": "Níl ríomhphost curtha ar bun i gceart ag an bhfreastalaí seo.",
+ "email": "Ríomhphost",
+ "send_notifications_to_email": "Seol fógraí chuig Ríomhphost",
+ "expires": "In éag",
+ "joined": "Teanga",
+ "by": "le",
+ "to": "chun",
+ "from": "ó",
+ "transfer_community": "aistrithe pobal",
+ "transfer_site": "aistrithe suíomh",
+ "number_of_comments_0": "{{count}} Trácht",
+ "number_of_comments_1": "{{count}} Tráchtanna",
+ "number_of_comments_2": "{{count}} Tráchtanna",
+ "number_of_comments_3": "{{count}} Tráchtanna",
+ "number_of_comments_4": "{{count}} Tráchtanna",
+ "send_secure_message": "Seol Teachtaireacht Sábháilte",
+ "delete_account_confirm": "Rabhadh: scriosfaidh sé seo do chuid sonraí go buan. Iontráil do phasfhocal le deimhniú.",
+ "username": "Ainm Úsáideora",
+ "number_of_subscribers_0": "{{count}} Suibscríobhaí",
+ "number_of_subscribers_1": "{{count}} Síntiúsóirí",
+ "number_of_subscribers_2": "{{count}} Síntiúsóirí",
+ "number_of_subscribers_3": "{{count}} Síntiúsóirí",
+ "number_of_subscribers_4": "{{count}} Síntiúsóirí",
+ "sign_up": "Cláraigh",
+ "notifications_error": "Níl faisnéisí deisce ar fáil i do bhrabhsálaí. Bain triail as Firefox nó Chrome.",
+ "private_message_disclaimer": "Rabhadh: Níl teachtaireachtaí príobháideacha i Lemmy slán. Cruthaigh cuntas ar <1>Element.io</1> le haghaidh teachtaireachtaí slán.",
+ "landing": "Is <1>comhiomlánóir nasc</1> / reddit malartach é Lemmy atá beartaithe le bheith ag obair sa <2>fediverse </2>.<3> </3> Tá sé féin-hostable, tá snáitheanna tráchta ann a nuashonraíonn beo, agus beag bídeach (<4> ~ 80kB </4>). Cónaidhm isteach sa líonra ActivityPub tá sé ar an treochlár. <5> </5> Seo <6> leagan béite an-luath </6>, agus tá a lán gnéithe briste nó in easnamh faoi láthair. <7> </7> Mol gnéithe nua nó tuairiscigh fabhtanna <8> áit. </8> <9> </9> Déanta le <10> Meirge </10>, <11> Actix </11>, < 12> Inferno </12>, <13> Clóscríbhneoireacht </13>. <14> </14> <15> Go raibh maith agat dár rannpháirtithe: </15> dessalines, Nutomic, asonix, zacanger, agus iav.",
+ "post": "postáil",
+ "remove_post": "Postáil a Bhaint",
+ "no_posts": "Gan aon Postáil.",
+ "create_a_post": "Cruthaigh Postáil",
+ "create_post": "Cruthaigh Postáil",
+ "number_of_posts_0": "{{count}} Postáil",
+ "number_of_posts_1": "{{count}} Postálacha",
+ "number_of_posts_2": "{{count}} Postálacha",
+ "number_of_posts_3": "{{count}} Postálacha",
+ "number_of_posts_4": "{{count}} Postálacha",
+ "posts": "Postálacha",
+ "related_posts": "D’fhéadfadh baint a bheith ag na Poist seo",
+ "cross_posts": "Cuireadh an nasc seo sa phost freisin chuig:",
+ "cross_post": "tras-phost",
+ "cross_posted_to": "tras-phostáilte chuig: ",
+ "comments": "Tráchtanna",
+ "remove_comment": "Bain Trácht",
+ "communities": "Pobail",
+ "users": "Úsáideoirí",
+ "create_a_community": "Cruthaigh Pobal",
+ "select_a_community": "Roghnaigh Pobal",
+ "create_community": "Cruthaigh Pobal",
+ "remove_community": "Bain Pobal",
+ "subscribed_to_communities": "Suibscríofa le <1>pobail</1>",
+ "trending_communities": "Treocht <1>pobail</1>",
+ "list_of_communities": "Liosta na bPobal",
+ "community_reqs": "cás íochtair, fostríoc, agus gan aon spásanna.",
+ "number_of_communities_0": "{{count}} Pobal",
+ "number_of_communities_1": "{{count}} Pobail",
+ "number_of_communities_2": "{{count}} Pobail",
+ "number_of_communities_3": "{{count}} Pobail",
+ "number_of_communities_4": "{{count}} Pobail",
+ "invalid_community_name": "Ainm neamhbhailí.",
+ "create_private_message": "Cruthaigh Teachtaireacht Phríobháideach",
+ "send_message": "Seol Teachtaireacht",
+ "message": "Teachtaireacht",
+ "edit": "cuir in eagar",
+ "reply": "freagra",
+ "more": "tuilleadh",
+ "cancel": "Cealú",
+ "preview": "Réamhléiriú",
+ "upload_image": "íomhá a uaslódáil",
+ "avatar": "Abhatár",
+ "upload_avatar": "Uaslódáil Abhatár",
+ "show_avatars": "Taispeáin Abhatáranna",
+ "show_context": "Taispeáin comhthéacs",
+ "formatting_help": "formáidiú cabhrú",
+ "sorting_help": "cúnamh a shórtáil",
+ "view_source": "féachaint foinse",
+ "unlock": "dhíghlasáil",
+ "lock": "glas",
+ "sticky": "bioráin",
+ "unsticky": "cealaigh bioráin",
+ "link": "nasc",
+ "archive_link": "nasc cartlainne",
+ "mod": "modhnóir",
+ "mods": "modhnóirí",
+ "moderates": "Modhnóireacht",
+ "settings": "Socruithe",
+ "modlog": "Logamod",
+ "admin": "riarthóir",
+ "admins": "riarthóirí",
+ "remove_as_admin": "bhaint mar riarthóir",
+ "appoint_as_admin": "ceapachá mar riarthóir",
+ "remove": "bain",
+ "removed": "bainte ag an modhnóir",
+ "category": "Catagóir",
+ "sidebar": "Barrataobh",
+ "sort_type": "Cineál sórtála",
+ "matrix_user_id": "Úsáideoir Matrix",
+ "optional": "Roghnach",
+ "are_you_sure": "An bhfuil tú cinnte?",
+ "yes": "tá",
+ "no": "níl",
+ "powered_by": "Cumhachtaithe ag",
+ "not_logged_in": "Ní logáilte isteach.",
+ "logged_in": "Logáilte isteach.",
+ "number_of_users_0": "{{count}} Úsáideoir",
+ "number_of_users_1": "{{count}} Úsáideoirí",
+ "number_of_users_2": "{{count}} Úsáideoirí",
+ "number_of_users_3": "{{count}} Úsáideoirí",
+ "number_of_users_4": "{{count}} Úsáideoirí",
+ "number_of_points_0": "{{count}} Pointe",
+ "number_of_points_1": "{{count}} Pointí",
+ "number_of_points_2": "{{count}} Pointí",
+ "number_of_points_3": "{{count}} Pointí",
+ "number_of_points_4": "{{count}} Pointí",
+ "subscribe_to_communities": "Liostáil le roinnt <1>pobail</1>.",
+ "chat": "Comhrá",
+ "recent_comments": "Tráchtanna le Déanaí",
+ "no_results": "Gan torthaí.",
+ "setup": "Cumraigh",
+ "lemmy_instance_setup": "Lemmy Ásc Cumraigh",
+ "setup_admin": "Riarthóir Suímh a Bhunú",
+ "your_site": "do suíomh",
+ "modified": "modhnaithe",
+ "sponsors": "Urraitheoirí",
+ "sponsors_of_lemmy": "Urraitheoirí Lemmy",
+ "sponsor_message": "Tá Lemmy saor in aisce, <1>bogearraí foinse oscailte</1>, gan aon fhógraíocht, monetizing, ná caipiteal fiontair, riamh. Tacaíonn do shíntiúis go díreach le forbairt lánaimseartha an tionscadail. Go raibh maith agat do na daoine seo a leanas:",
+ "support_on_patreon": "Tacaíocht ar Patreon",
+ "support_on_liberapay": "Tacaíocht ar Liberapay",
+ "support_on_open_collective": "Tacaíocht ar OpenCollective",
+ "donate_to_lemmy": "Bronn do Lemmy",
+ "donate": "Bronn",
+ "general_sponsors": "Is iad Urraitheoirí Ginearálta iad siúd a gheall $10 go $39 chun Lemmy.",
+ "silver_sponsors": "Is iad Urraitheoirí Airgid iad siúd a gheall $ 40 chun Lemmy.",
+ "crypto": "Criptea",
+ "bitcoin": "Bonn Giotáin",
+ "ethereum": "Ethereum",
+ "monero": "Monero",
+ "code": "Cód",
+ "language": "Teanga",
+ "body": "Corp",
+ "copy_suggested_title": "cóip teideal molta: {{title}}",
+ "community": "Pobal",
+ "expand_here": "Leathnaigh anseo",
+ "browser_default": "Réamhshocrú Brabhsálaí",
+ "downvotes_disabled": "Síosvótaí faoi mhíchumas",
+ "enable_downvotes": "Cumasaigh Síosvótaí",
+ "open_registration": "Clárú Oscailte",
+ "registration_closed": "Clárú dúnta",
+ "enable_nsfw": "Cumasaigh NSFW",
+ "must_login": "Caithfidh tú <1>logáil isteach nó clárú</1> chun trácht a dhéanamh.",
+ "community_ban": "Cuireadh cosc ort ón bpobal seo.",
+ "site_ban": "Cuireadh cosc ort ón suíomh",
+ "couldnt_create_comment": "Níorbh fhéidir a chruthú trácht.",
+ "couldnt_like_comment": "Níorbh fhéidir a is maith trácht.",
+ "couldnt_update_comment": "Níorbh fhéidir trácht a nuashonrú.",
+ "couldnt_save_comment": "Níorbh fhéidir trácht a shábháil.",
+ "couldnt_get_comments": "Níorbh fhéidir tuairimí a fháil.",
+ "no_community_edit_allowed": "Ní cheadaítear an pobal a chur in eagar.",
+ "couldnt_find_community": "Níorbh fhéidir Pobal a aimsiú.",
+ "couldnt_update_community": "Níorbh fhéidir an Pobal a nuashonrú.",
+ "community_already_exists": "Pobal ann cheana féin.",
+ "community_moderator_already_exists": "Tá modhnóir pobail ann cheana féin.",
+ "community_follower_already_exists": "Tá leantóir pobail ann cheana féin.",
+ "community_user_already_banned": "Toirmisctear úsáideoir pobail cheana féin.",
+ "couldnt_create_post": "Níorbh fhéidir postáil a chruthú.",
+ "post_title_too_long": "Tá teideal an postáil ró-fhada.",
+ "couldnt_like_post": "Níorbh fhéidir a is maith post.",
+ "couldnt_find_post": "Níorbh fhéidir an post a aimsiú.",
+ "couldnt_get_posts": "Níorbh fhéidir an post a fháil",
+ "couldnt_update_post": "Níorbh fhéidir an post a nuashonrú",
+ "not_a_moderator": "Ní modhnóir.",
+ "system_err_login": "Earráid chórais. Bain triail as logáil amach agus ar ais isteach.",
+ "couldnt_create_private_message": "Níorbh fhéidir teachtaireacht phríobháideach a chruthú.",
+ "couldnt_update_private_message": "Níorbh fhéidir teachtaireacht phríobháideach a nuashonrú.",
+ "action": "Gníomh",
+ "emoji_picker": "Piocálaí Emoji",
+ "block_leaving": "An bhfuil tú cinnte gur mhaith leat imeacht?",
+ "what_is": "Cád é",
+ "cake_day_title": "Lá císte:",
+ "cake_day_info": "Lá císte {{ creator_name }} é atá ann inniu!",
+ "invalid_post_title": "Teideal poist neamhbhailí",
+ "invalid_url": "URL neamhbhailí.",
+ "couldnt_find_that_username_or_email": "Níorbh fhéidir an t-ainm úsáideora nó an ríomhphost sin a fháil.",
+ "admin_already_created": "Tá brón orm, tá riarthóir ann cheana féin.",
+ "number_of_downvotes_0": "{{count}} Síosvótáil",
+ "number_of_downvotes_1": "{{count}} Síosvótaí",
+ "number_of_downvotes_2": "{{count}} Síosvótaí",
+ "number_of_downvotes_3": "{{count}} Síosvótaí",
+ "number_of_downvotes_4": "{{count}} Síosvótaí",
+ "upvote": "Suasvótáil",
+ "downvote": "Síosvótáil",
+ "url": "URL",
+ "number_of_upvotes_0": "{{count}} Suasvótáil",
+ "number_of_upvotes_1": "{{count}} Suasvótaí",
+ "number_of_upvotes_2": "{{count}} Suasvótaí",
+ "number_of_upvotes_3": "{{count}} Suasvótaí",
+ "number_of_upvotes_4": "{{count}} Suasvótaí",
+ "nsfw": "NSFW",
+ "show_nsfw": "Taispeáin ábhar NSFW",
+ "theme": "Téama",
+ "site_saved": "Sábháil Suíomh.",
+ "no_private_message_edit_allowed": "Ní cheadaítear teachtaireacht phríobháideach a chur in eagar.",
+ "no_comment_edit_allowed": "Ní cheadaítear trácht a chur in eagar.",
+ "no_post_edit_allowed": "Ní cheadaítear an post a chur in eagar.",
+ "couldnt_save_post": "Níorbh fhéidir an post a shábháil.",
+ "no_slurs": "Uimh masla.",
+ "not_an_admin": "Ní riarthóir é.",
+ "site_already_exists": "Suíomh ann cheana.",
+ "couldnt_update_site": "Níorbh fhéidir an suíomh a nuashonrú.",
+ "password_incorrect": "Pasfhocal mícheart.",
+ "passwords_dont_match": "Ní hionann pasfhocail.",
+ "no_password_reset": "Ní bheidh tú in ann do phasfhocal a athshocrú gan ríomhphost.",
+ "invalid_username": "Ainm Úsáideora neamhbhailí.",
+ "user_already_exists": "Úsáideoir ann cheana.",
+ "email_already_exists": "Tá ríomhphost ann cheana féin.",
+ "couldnt_update_user": "Níorbh fhéidir an t-úsáideoir a nuashonrú.",
+ "time": "Am",
+ "subscript": "fo-script",
+ "superscript": "sár-script"
+}
"category": "Categoria",
"subscribers": "Iscritti",
"both": "Entrambi",
- "saved": "Salvato",
+ "saved": "Salvati",
"unsubscribe": "Disiscriviti",
"subscribe": "Iscriviti",
"subscribed": "Iscritto",
"yes": "sì",
"no": "no",
"powered_by": "Offerto da",
- "landing": "Lemmy è un <1>aggregatore di link</1> / alternativa a reddit, creato per integrarsi con il <2>fediverso</2>. <3></3>È self-hosted, i commenti sono aggiornati in tempo reale ed è molto piccolo (<4>~80kB</4>). La federazione con la rete ActivityPub sarà implementata nel futuro. <5></5>Questa versione è una <6>beta molto giovane</6> e molte funzionalità sono incomplete o mancanti. <7></7>Suggerisci nuove funzionalità o segnala errori a <8>questa pagina.</8><9></9>Sviluppato con <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
+ "landing": "Lemmy è un <1>aggregatore di link</1> / alternativa a reddit, creato per integrarsi con il <2>fediverso</2>. <3></3>È self-hosted, i commenti sono aggiornati in tempo reale ed è molto piccolo (<4>~80kB</4>). La federazione con la rete ActivityPub sarà implementata nel futuro. <5></5>Questa versione è una <6>beta molto giovane</6> e molte funzionalità sono incomplete o mancanti. <7></7>Suggerisci nuove funzionalità o segnala errori a <8>questa pagina.</8><9></9>Sviluppato con <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.<14></14> <15>Un grazie ai nostri sostenitori: </15> dessalines, Nutomic, asonix, zacanger, and iav.",
"not_logged_in": "Non hai effettuato l'accesso.",
"community_ban": "Sei stato escluso da questa comunità.",
"site_ban": "Sei stato escluso dal sito",
"old_password": "Vecchia Password",
"forgot_password": "password dimenticata",
"new_password": "Nuova Password",
- "private_message_disclaimer": "Attenzione: i messaggi privati su Lemmy non sono sicuri. Crea un account su <1>Riot.im</1> per una messaggistica sicura.",
+ "private_message_disclaimer": "Attenzione: i messaggi privati su Lemmy non sono sicuri. Crea un account su <1>Element.io</1> per una messaggistica sicura.",
"language": "Lingua",
"enable_downvotes": "Abilita voti negativi",
"enable_nsfw": "Abilita NSFW",
"downvotes_disabled": "Voti negativi disabilitati",
"post_title_too_long": "Titolo della pubblicazione troppo lungo.",
"email_already_exists": "Indirizzo email già presente.",
- "cross_posted_to": "pubblicato pure su: ",
+ "cross_posted_to": "pubblicato anche su: ",
"support_on_open_collective": "Sostieni su OpenCollective",
"admin_settings": "Impostazioni per Admin",
"site_config": "Configurazione del sito",
"picture_deleted": "Foto eliminata.",
"select_a_community": "Seleziona una comunità",
"invalid_username": "Nome utente non valido.",
- "what_is": "Cos'è"
+ "what_is": "Cos'è",
+ "must_login": "Devi <1>effettuare l'accesso o registrarti</1> per commentare.",
+ "no_password_reset": "Non sarai in grado di resettare la tua password senza una email.",
+ "cake_day_title": "Torta-giorno:",
+ "cake_day_info": "Oggi è il cake day di {{ creator_name }}!",
+ "invalid_post_title": "Titolo della pubblicazione non valido",
+ "bold": "grassetto",
+ "italic": "corsivo",
+ "subscript": "pedice",
+ "superscript": "apice",
+ "header": "intestazione",
+ "strikethrough": "barrato",
+ "quote": "citazione",
+ "spoiler": "spoiler",
+ "list": "lista",
+ "invalid_url": "URL non valido.",
+ "not_a_moderator": "Non moderatore."
}
"powered_by": "Powered by",
"landing_0": "Lemmy jest <1>agregatorem linków</1> / alternatywą dla reddita. Jest przeznaczony do działania w ramach cyfrowej przestrzeni nazywanej <2>fediverse</2>. <3></3>Opiera się na samodzielnym hostingu, posiada aktualizowane na żywo wątki z komentarzami, i zajmuje bardzo mało miejsce (<4>~80kB</4>). Federacja w ramach sieci ActivityPub jest w planach. <5></5>Ta wersja jest <6>bardzo wczesną wersją beta</6>, co oznacza, że wiele funkcji nadal nie działa tak jak powinny. <7></7><8>Pod tym adresem</8> można sugerować nową funkcjonalność i zgłaszać błędy.<9></9>Stworzono z wykorzystaniem <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
"not_logged_in": "Nie jesteś zalogowana/y.",
+ "bio_length_overflow": "To pole nie może przekraczać 300 znaków!",
"logged_in": "Zalogowano.",
"community_ban": "Zostałaś/eś zbanowana/y z tej społeczności.",
"site_ban": "Zostałaś/eś zbanowana/y z tej witryny",
"emoji_picker": "Wybór Emoji",
"silver_sponsors": "Srebrni Sponsorzy to ci, którzy wpłacili co najmniej $40 na Lemmiego.",
"select_a_community": "Wybierz społeczność",
- "invalid_username": "Nieprawidłowa nazwa użytkownika."
+ "invalid_username": "Nieprawidłowa nazwa użytkownika.",
+ "invalid_community_name": "Niepoprawna nazwa.",
+ "play_captcha_audio": "Odsłuchaj Captcha Audio",
+ "bio": "Bio"
}
"all": "Tudo",
"top": "Top",
"api": "API",
- "docs": "Docs",
+ "docs": "Documentação",
"inbox": "Caixa de entrada",
"inbox_for": "Caixa de entrada de <1>{{user}}</1>",
"mark_all_as_read": "marcar tudo como lido",
"yes": "sim",
"no": "não",
"powered_by": "Fornecido por",
- "landing_0": "Lemmy é um <1>agregador de links</1> / alternativa ao reddit, com a intenção de funcionar junto ao <2>fediverso</2>.<3></3>Pode ser hospedado em servidor próprio, tem atualização de comentários em tempo real e é minúsculo (<4>~80kB</4>). A federação com a rede ActivityPub está no roteiro do projeto. <5></5>Esta é uma <6>versão beta bastante antecipada</6>, e muitas funcionalidades ainda estão quebradas ou ausentes. <7></7>Sugira novas funcionalidades ou reporte erros <8>aqui.</8><9></9>Feito com <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
+ "landing": "Lemmy é um <1>agregador de links</1> / alternativa ao reddit, com a intenção de funcionar junto ao <2>fediverso</2>.<3></3>Pode ser hospedado em servidor próprio, tem atualização de comentários em tempo real e é minúsculo (<4>~80kB</4>). A federação com a rede ActivityPub está no roteiro do projeto. <5></5>Esta é uma <6>versão beta bastante antecipada</6>, e muitas funcionalidades ainda estão quebradas ou ausentes. <7></7>Sugira novas funcionalidades ou reporte erros <8>aqui.</8><9></9>Feito com <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>. <14></14> <15>Agradecemos aos nossos contribuidores: </15> dessalines, Nutomic, asonix, zacanger, e iav.",
"not_logged_in": "Não autenticado.",
"logged_in": "Autenticado.",
"community_ban": "Você foi banido desta comunidade.",
"site_saved": "Site Salvo.",
"emoji_picker": "Selecionador de Emoji",
"select_a_community": "Selecione uma comunidade",
- "invalid_username": "Nome de usuário inválido."
+ "invalid_username": "Nome de usuário inválido.",
+ "must_login": "Você precisa <1>entrar ou registrar-se</1> para comentar.",
+ "no_password_reset": "Você não conseguirá redefinir sua senha sem um e-mail.",
+ "invalid_post_title": "Título de publicação inválido",
+ "cake_day_info": "Hoje é o dia do bolo de {{ creator_name }}!",
+ "cake_day_title": "Dia do bolo:",
+ "what_is": "Quanto é"
}
"sponsors_of_lemmy": "Спонсоры Lemmy",
"sponsor_message": "Lemmy это бесплатное, <1>открытое</1> программное обеспечение, без рекламы, монетизации или венчурного капитала, никогда. Ваши пожертвования напрямую поддерживают развитие проекта. Спасибо нижеуказанным людям:",
"support_on_patreon": "Поддержать на Patreon",
- "general_sponsors": "Ð\93енеÑ\80алÑ\8cнÑ\8bе Ñ\81понÑ\81оÑ\80Ñ\8b - Ñ\8dÑ\82о Ñ\82е, кÑ\82о пообеÑ\89ал Lemmy от $10 до $39.",
+ "general_sponsors": "Ð\93енеÑ\80алÑ\8cнÑ\8bе Ñ\81понÑ\81оÑ\80Ñ\8b - Ñ\8dÑ\82о Ñ\82е, кÑ\82о пожеÑ\80Ñ\82вовал Lemmy от $10 до $39.",
"crypto": "Крипто",
"bitcoin": "Bitcoin",
"ethereum": "Ethereum",
"code": "Код",
"joined": "Присоединился",
"powered_by": "Работает на",
- "landing_0": "Lemmy - это <1>агрегатор ссылок</1> / альтернатива reddit, предназначенный для работы в <2>федиверсе</2>.<3></3>Это самодостаточная система, с обновляемыми комментариями, и эта система крошечная (<4>~80 Кб</4>). Федерация в сети ActivityPub находится в разработке. <5></5>Это <6>очень ранняя бета-версия</6>, и многие функции в настоящее время сломаны или отсутствуют. <7></7>Предлагать новые функции или сообщать об ошибках можно <8>здесь.</8><9></9>Сделано на <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
+ "landing": "Lemmy - это <1>агрегатор ссылок</1> / альтернатива reddit, предназначенный для работы в <2>федиверсе</2>.<3></3>Это самодостаточная система, с обновляемыми комментариями, и эта система крошечная (<4>~80 Кб</4>). Федерация в сети ActivityPub находится в разработке. <5></5>Это <6>очень ранняя бета-версия</6>, и многие функции в настоящее время сломаны или отсутствуют. <7></7>Предлагать новые функции или сообщать об ошибках можно <8>здесь.</8><9></9>Сделано на <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.<14></14><15>Спасибо нашим помощникам:</15>dessalines, Nutomic, asonix, zacanger, и iav.",
"not_logged_in": "Не авторизованы.",
"community_ban": "Вы были заблокированы на данном сообществе.",
"site_ban": "Вы были заблокированы на данном сайте",
"send_message": "Послать сообщение",
"message": "Сообщение",
"avatar": "Аватар",
- "show_avatars": "Показать Аватары",
+ "show_avatars": "Показывать аватары",
"formatting_help": "Помощь в верстке текста",
"sticky": "приклеить",
"stickied": "закрепленный пост",
"old_password": "Действующий пароль",
"forgot_password": "я забыл(а) пароль",
"reset_password_mail_sent": "Письмо для восстановления пароля было выслано.",
- "private_message_disclaimer": "Предупреждение: Приватные сообщения Lemmy на данный момент не зашифрованы. Для безопасной коммуникации создайте аккаунт на <1>Riot.im</1>.",
+ "private_message_disclaimer": "Предупреждение: Приватные сообщения Lemmy на данный момент не зашифрованы. Для безопасной коммуникации создайте аккаунт на <1>Element.io</1>.",
"send_notifications_to_email": "Посылать уведомления на e-mail адрес",
"language": "Язык",
"browser_default": "Браузер по умолчанию",
"messages": "Сообщения",
"new_password": "Новый пароль",
"theme": "Визуальная тема",
- "post_title_too_long": "Ð\94лина названиÑ\8f поÑ\81Ñ\82а превышает допустимый лимит.",
+ "post_title_too_long": "Ð\94лина названиÑ\8f запиÑ\81и превышает допустимый лимит.",
"time": "Время",
"action": "Действие",
"view_source": "исходный код сообщения",
"to": "в",
"admin_settings": "Настройки админа",
"banned_users": "Забаненные Пользователи",
- "support_on_open_collective": "Ð\9fоддеÑ\80жка на OpenCollective",
+ "support_on_open_collective": "Ð\9fоддеÑ\80жаÑ\82Ñ\8c на OpenCollective",
"site_saved": "Сайт Сохранен.",
"enable_nsfw": "Включить NSFW",
- "donate": "Пожертвование",
+ "donate": "Пожертвования",
"unsticky": "отклеить",
"site_config": "Конфигурация сайта",
"banned": "забаненный",
"password_change": "Смена пароля",
"no_email_setup": "Этот сервер неправильно настроил электронную почту.",
- "matrix_user_id": "Ð\9cаÑ\82Ñ\80иÑ\86а полÑ\8cзоваÑ\82елÑ\8f",
+ "matrix_user_id": "Ð\90дÑ\80еÑ\81 в Matrix",
"are_you_sure": "вы уверены?",
"archive_link": "архивировать ссылку",
- "logged_in": "Войти в систему.",
+ "logged_in": "Вошли в систему.",
"couldnt_get_comments": "Не удалось получить комментарии.",
"from": "от",
"transfer_site": "трансфер сайт",
"monero": "Monero",
"emoji_picker": "Сборщик эмодзи",
"select_a_community": "Выбрать сообщество",
- "invalid_username": "Неверное имя пользователя."
+ "invalid_username": "Неверное имя пользователя.",
+ "must_login": "Вы должны <1>авторизироваться или зарегестрироваться</1> что бы комментировать.",
+ "no_password_reset": "Вы не сможете сбросить ваш пароль без адреса электронной почты.",
+ "cake_day_title": "День торта:",
+ "what_is": "Что такое",
+ "superscript": "верхний индекс",
+ "cake_day_info": "Сегодня день торта у {{ creator_name }}!",
+ "invalid_post_title": "Недопустимый заголовок записи",
+ "bold": "жирный",
+ "italic": "курсив",
+ "subscript": "нижний индекс",
+ "header": "заголовок",
+ "strikethrough": "зачёркивание",
+ "quote": "цитата",
+ "spoiler": "спойлер",
+ "list": "список",
+ "not_a_moderator": "Не модератор.",
+ "invalid_url": "Недопустимый URL."
}
{
- "post": "inlägg",
- "remove_post": "Radera inlägg",
+ "post": "publicera",
+ "remove_post": "Ta bort inlägg",
"no_posts": "Inga inlägg.",
"create_a_post": "Skriv ett inlägg",
"create_post": "Skapa inlägg",
- "number_of_posts": "{{count}} Inlägg",
- "number_of_posts_plural": "{{count}} Inlägg",
+ "number_of_posts": "{{count}} inlägg",
+ "number_of_posts_plural": "{{count}} inlägg",
"posts": "Inlägg",
- "related_posts": "Dessa inlägg kan vara relaterade",
+ "related_posts": "Dessa inlägg kan höra samman",
"cross_posts": "Den här länken har även publicerats i:",
"cross_post": "tvärposta",
"comments": "Kommentarer",
- "number_of_comments": "{{count}} Kommentar",
- "number_of_comments_plural": "{{count}} Kommentarer",
- "remove_comment": "Radera kommentar",
+ "number_of_comments": "{{count}} kommentar",
+ "number_of_comments_plural": "{{count}} kommentarer",
+ "remove_comment": "Ta bort kommentar",
"communities": "Gemenskaper",
"users": "Användare",
"create_a_community": "Skapa en gemenskap",
"create_community": "Skapa gemenskap",
- "remove_community": "Radera gemenskap",
+ "remove_community": "Ta bort gemenskap",
"subscribed_to_communities": "Prenumererar på <1>gemenskaper</1>",
"trending_communities": "Populära <1>gemenskaper</1>",
"list_of_communities": "Lista över gemenskaper",
- "number_of_communities": "{{count}} Gemenskap",
- "number_of_communities_plural": "{{count}} Gemenskaper",
+ "number_of_communities": "{{count}} gemenskap",
+ "number_of_communities_plural": "{{count}} gemenskaper",
"community_reqs": "gemener, understreck och inga blanksteg.",
"edit": "redigera",
"reply": "svara",
"creator": "skapare",
"username": "Användarnamn",
"email_or_username": "E-postadress eller användarnamn",
- "number_of_users": "{{count}} Användare",
- "number_of_users_plural": "{{count}} Användare",
- "number_of_subscribers": "{{count}} Prenumerant",
- "number_of_subscribers_plural": "{{count}} Prenumeranter",
- "number_of_points": "{{count}} Poäng",
- "number_of_points_plural": "{{count}} Poäng",
- "number_online": "{{count}} Användare inloggad",
- "number_online_plural": "{{count}} Användare inloggade",
+ "number_of_users": "{{count}} användare",
+ "number_of_users_plural": "{{count}} användare",
+ "number_of_subscribers": "{{count}} prenumerant",
+ "number_of_subscribers_plural": "{{count}} prenumeranter",
+ "number_of_points": "{{count}} poäng",
+ "number_of_points_plural": "{{count}} poäng",
+ "number_online": "{{count}} användare inloggad",
+ "number_online_plural": "{{count}} användare inloggade",
"name": "Namn",
"title": "Titel",
"category": "Kategori",
"subscribers": "Prenumeranter",
"both": "Båda",
"saved": "Sparade",
- "unsubscribe": "Avbryt prenumeration",
+ "unsubscribe": "Avsluta prenumeration",
"subscribe": "Prenumerera",
"subscribed": "Prenumererar",
"prev": "Föregående",
"next": "Nästa",
- "sidebar": "Sidlist",
- "sort_type": "Sorteringstyp",
+ "sidebar": "Sidolist",
+ "sort_type": "Sortering",
"hot": "Hett",
"new": "Nytt",
"top_day": "Dagstoppen",
- "week": "Vecka",
- "month": "Månad",
- "year": "År",
- "all": "Samtliga",
+ "week": "Veckotoppen",
+ "month": "Månadstoppen",
+ "year": "Årstoppen",
+ "all": "Totaltoppen",
"top": "Topp",
"api": "API",
"inbox": "Inkorg",
"theme": "Utseende",
"sponsors": "Sponsorer",
"sponsors_of_lemmy": "Lemmys sponsorer",
- "sponsor_message": "Lemmy är fri mjukvara med <1>öppen källkod</1>, vilket innebär att ingen reklam, vinstindrivning eller venturekapital förekommer, någonsin. Dina donationer går direkt till att stöda utvecklingen av projektet. Stort tack till följande personer:",
+ "sponsor_message": "Lemmy är en fri mjukvara med <1>öppen källkod</1>, vilket innebär att ingen reklam, vinstindrivning eller venture-kapital förekommer, någonsin. Dina donationer går direkt till att stöda utvecklingen av projektet. Stort tack till följande personer:",
"support_on_patreon": "Stöd på Patreon",
"general_sponsors": "Allmänna sponsorer är de som donerat mellan 10 och 39 dollar till Lemmy.",
"crypto": "Kryptovaluta",
"yes": "ja",
"no": "nej",
"powered_by": "Drivs av",
- "landing_0": "Lemmy är en <1>länksamlare</1> och alternativ till reddit, ämnad att fungera i <2>Fediversumet</2>.<3></3>Lemmy kan drivas av vem som helst, har kommentarstrådar som updateras i realid och är mycket liten (<4>ca 80 kB</4>). Federering med ActivityPub-nätverket är planerat. <5></5>Detta är en <6>väldigt tidig betaversion</6> och många funktioner saknas därför eller är trasiga.<7></7>Föreslå nya funktioner eller anmäl buggar <8>här</8>.<9></9>Skapad i <10>Rust</10>, <11>Actix</11>, <12>Inferno</12> och <13>Typescript</13>.",
+ "landing": "Lemmy är en <1>länksamlare</1> och alternativ till reddit, ämnad att fungera i <2>Fediversumet</2>.<3></3>Lemmy kan drivas av vem som helst, har kommentarstrådar som uppdateras i realtid och är mycket liten (<4>ca 80 kB</4>). Federering med ActivityPub-nätverket är planerat. <5></5>Detta är en <6>väldigt tidig betaversion</6> och många funktioner saknas därför eller är trasiga.<7></7>Föreslå nya funktioner eller anmäl buggar <8>här</8>.<9></9>Skapad i <10>Rust</10>, <11>Actix</11>, <12>Inferno</12> och <13>Typescript</13>. <14></14> <15>Ett stort tack till våra bidragsgivare: </15> dessalines, Nutomic, asonix, zacanger, och iav.",
"not_logged_in": "Inte inloggad.",
"community_ban": "Du har blockerats från den här gemenskapen.",
"site_ban": "Du har blockerats från webbplatsen",
"system_err_login": "Systemfel. Försök att logga ut och sedan in igen.",
"invalid_community_name": "Ogiltigt namn.",
"click_to_delete_picture": "Klicka för att ta bort bild.",
- "picture_deleted": "Bild borttagen.",
- "upload_avatar": "Ladda upp avatar",
+ "picture_deleted": "Bilden har raderats.",
+ "upload_avatar": "Ladda upp profilbild",
"enable_nsfw": "Aktivera NSFW",
"sorting_help": "sorteringshjälp",
"more": "mer",
- "avatar": "Avatar",
+ "avatar": "Profilbild",
"cross_posted_to": "Tvärpostat till: ",
"send_secure_message": "Skicka säkert meddelande",
"send_message": "Skicka meddelande",
"message": "Meddelande",
- "create_private_message": "Skapa Privatmeddelande",
- "show_avatars": "Visa avatarer",
- "archive_link": "Arkivera länk",
+ "create_private_message": "Skriv privat meddelande",
+ "show_avatars": "Visa profilbilder",
+ "archive_link": "Arkivlänk",
"admin_settings": "Administratörsinställningar",
- "site_config": "Webbplats konfiguration",
- "old": "Gammal",
+ "site_config": "Webbplatsinställningar",
+ "old": "Gammalt",
"banned_users": "Blockerade användare",
"docs": "Dokumentation",
- "post_title_too_long": "Inläggstitel är för lång.",
+ "post_title_too_long": "Inläggstiteln är för lång.",
"replies": "Svar",
"mentions": "Nämner",
"message_sent": "Meddelande skickat",
"new_password": "Nytt lösenord",
"no_email_setup": "Denna server har inte satt upp e-post korrekt.",
"matrix_user_id": "Matrix-användare",
- "show_context": "Visa innehåll",
- "private_message_disclaimer": "Varning: Privata meddelanden på Lemmy är inte säkra. Vänligen skapa ett konto på <1>Riot.im</1> för att skicka säkra meddelanden.",
- "send_notifications_to_email": "Skicka aviseringar till E-post",
+ "show_context": "Visa sammanhang",
+ "private_message_disclaimer": "Varning: Privata meddelanden på Lemmy är inte säkra. Vänligen skapa ett konto på <1>Element.io</1> för att skicka säkra meddelanden.",
+ "send_notifications_to_email": "Skicka aviseringar till e-postadress",
"language": "Språk",
- "browser_default": "Webbläsarestandard",
- "downvotes_disabled": "Nedröstningar inaktiverat",
+ "browser_default": "Webbläsarens språk",
+ "downvotes_disabled": "Nedröstningar inaktiverade",
"enable_downvotes": "Aktivera nedröstningar",
- "upvote": "Upprösta",
- "number_of_upvotes": "{{count}} Uppröst",
- "number_of_upvotes_plural": "{{count}} Uppröstningar",
- "downvote": "Nedrösta",
- "number_of_downvotes": "{{count}} Nedröst",
- "number_of_downvotes_plural": "{{count}} Nedröstningar",
+ "upvote": "Rösta upp",
+ "number_of_upvotes": "{{count}} uppröst",
+ "number_of_upvotes_plural": "{{count}} uppröster",
+ "downvote": "Rösta ned",
+ "number_of_downvotes": "{{count}} nedröst",
+ "number_of_downvotes_plural": "{{count}} nedröster",
"open_registration": "Öppen registrering",
"registration_closed": "Registrering stängd",
"support_on_liberapay": "Stöd på Liberapay",
"couldnt_create_private_message": "Kunde inte skapa privat meddelande.",
"no_private_message_edit_allowed": "Inte tillåtet att redigera privata meddelanden.",
"couldnt_update_private_message": "Kunde inte uppdatera privat meddelande.",
- "time": "Tid",
+ "time": "Tidpunkt",
"emoji_picker": "Emoji-väljare",
"block_leaving": "Är du säker på att du vill lämna?",
"select_a_community": "Välj en gemenskap",
"from": "från",
- "invalid_username": "Ogiltigt användarnamn."
+ "invalid_username": "Ogiltigt användarnamn.",
+ "cake_day_info": "Idag firar vi {{ creator_name }} med tårta!",
+ "must_login": "Du måste <1>logga in eller registrera dig</1> för att kommentera.",
+ "no_password_reset": "Du kommer inte kunna återställa ditt lösenord utan en e-postadress.",
+ "what_is": "Vad är",
+ "cake_day_title": "Tårtdag:",
+ "invalid_post_title": "Ogiltig inläggstitel",
+ "bold": "fetstil",
+ "italic": "kursiv stil",
+ "header": "rubrik",
+ "quote": "citat",
+ "subscript": "nedsänkt (indexläge)",
+ "superscript": "upphöjt (exponentläge)",
+ "strikethrough": "genomstruket",
+ "spoiler": "innehållsvarning",
+ "list": "lista",
+ "not_a_moderator": "Inte en moderator.",
+ "invalid_url": "Ogiltig URL."
}
# yarn lockfile v1
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
- integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
+ integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==
dependencies:
- "@babel/highlight" "^7.8.3"
+ "@babel/highlight" "^7.10.4"
"@babel/core@^7.1.0", "@babel/core@^7.7.5":
- version "7.9.0"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e"
- integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==
- dependencies:
- "@babel/code-frame" "^7.8.3"
- "@babel/generator" "^7.9.0"
- "@babel/helper-module-transforms" "^7.9.0"
- "@babel/helpers" "^7.9.0"
- "@babel/parser" "^7.9.0"
- "@babel/template" "^7.8.6"
- "@babel/traverse" "^7.9.0"
- "@babel/types" "^7.9.0"
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.5.tgz#1f15e2cca8ad9a1d78a38ddba612f5e7cdbbd330"
+ integrity sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ "@babel/generator" "^7.10.5"
+ "@babel/helper-module-transforms" "^7.10.5"
+ "@babel/helpers" "^7.10.4"
+ "@babel/parser" "^7.10.5"
+ "@babel/template" "^7.10.4"
+ "@babel/traverse" "^7.10.5"
+ "@babel/types" "^7.10.5"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.1"
json5 "^2.1.2"
- lodash "^4.17.13"
+ lodash "^4.17.19"
resolve "^1.3.2"
semver "^5.4.1"
source-map "^0.5.0"
-"@babel/generator@^7.8.6":
- version "7.8.8"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.8.tgz#cdcd58caab730834cee9eeadb729e833b625da3e"
- integrity sha512-HKyUVu69cZoclptr8t8U5b6sx6zoWjh8jiUhnuj3MpZuKT2dJ8zPTuiy31luq32swhI0SpwItCIlU8XW7BZeJg==
- dependencies:
- "@babel/types" "^7.8.7"
- jsesc "^2.5.1"
- lodash "^4.17.13"
- source-map "^0.5.0"
-
-"@babel/generator@^7.9.0", "@babel/generator@^7.9.5":
- version "7.9.5"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9"
- integrity sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==
+"@babel/generator@^7.10.5":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.5.tgz#1b903554bc8c583ee8d25f1e8969732e6b829a69"
+ integrity sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==
dependencies:
- "@babel/types" "^7.9.5"
+ "@babel/types" "^7.10.5"
jsesc "^2.5.1"
- lodash "^4.17.13"
source-map "^0.5.0"
-"@babel/helper-function-name@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca"
- integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==
- dependencies:
- "@babel/helper-get-function-arity" "^7.8.3"
- "@babel/template" "^7.8.3"
- "@babel/types" "^7.8.3"
-
-"@babel/helper-function-name@^7.9.5":
- version "7.9.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c"
- integrity sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==
- dependencies:
- "@babel/helper-get-function-arity" "^7.8.3"
- "@babel/template" "^7.8.3"
- "@babel/types" "^7.9.5"
-
-"@babel/helper-get-function-arity@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5"
- integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==
- dependencies:
- "@babel/types" "^7.8.3"
-
-"@babel/helper-member-expression-to-functions@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c"
- integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==
- dependencies:
- "@babel/types" "^7.8.3"
-
-"@babel/helper-module-imports@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498"
- integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==
- dependencies:
- "@babel/types" "^7.8.3"
-
-"@babel/helper-module-transforms@^7.9.0":
- version "7.9.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5"
- integrity sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==
- dependencies:
- "@babel/helper-module-imports" "^7.8.3"
- "@babel/helper-replace-supers" "^7.8.6"
- "@babel/helper-simple-access" "^7.8.3"
- "@babel/helper-split-export-declaration" "^7.8.3"
- "@babel/template" "^7.8.6"
- "@babel/types" "^7.9.0"
- lodash "^4.17.13"
-
-"@babel/helper-optimise-call-expression@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9"
- integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==
- dependencies:
- "@babel/types" "^7.8.3"
-
-"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670"
- integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==
-
-"@babel/helper-replace-supers@^7.8.6":
- version "7.8.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8"
- integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==
- dependencies:
- "@babel/helper-member-expression-to-functions" "^7.8.3"
- "@babel/helper-optimise-call-expression" "^7.8.3"
- "@babel/traverse" "^7.8.6"
- "@babel/types" "^7.8.6"
-
-"@babel/helper-simple-access@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae"
- integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==
- dependencies:
- "@babel/template" "^7.8.3"
- "@babel/types" "^7.8.3"
-
-"@babel/helper-split-export-declaration@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9"
- integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==
- dependencies:
- "@babel/types" "^7.8.3"
-
-"@babel/helper-validator-identifier@^7.9.5":
- version "7.9.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80"
- integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==
-
-"@babel/helpers@^7.9.0":
- version "7.9.2"
- resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f"
- integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==
- dependencies:
- "@babel/template" "^7.8.3"
- "@babel/traverse" "^7.9.0"
- "@babel/types" "^7.9.0"
-
-"@babel/highlight@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797"
- integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==
- dependencies:
+"@babel/helper-function-name@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a"
+ integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==
+ dependencies:
+ "@babel/helper-get-function-arity" "^7.10.4"
+ "@babel/template" "^7.10.4"
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-get-function-arity@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2"
+ integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==
+ dependencies:
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-member-expression-to-functions@^7.10.4":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz#172f56e7a63e78112f3a04055f24365af702e7ee"
+ integrity sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==
+ dependencies:
+ "@babel/types" "^7.10.5"
+
+"@babel/helper-module-imports@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620"
+ integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==
+ dependencies:
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-module-transforms@^7.10.5":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz#120c271c0b3353673fcdfd8c053db3c544a260d6"
+ integrity sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA==
+ dependencies:
+ "@babel/helper-module-imports" "^7.10.4"
+ "@babel/helper-replace-supers" "^7.10.4"
+ "@babel/helper-simple-access" "^7.10.4"
+ "@babel/helper-split-export-declaration" "^7.10.4"
+ "@babel/template" "^7.10.4"
+ "@babel/types" "^7.10.5"
+ lodash "^4.17.19"
+
+"@babel/helper-optimise-call-expression@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673"
+ integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==
+ dependencies:
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375"
+ integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==
+
+"@babel/helper-replace-supers@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf"
+ integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==
+ dependencies:
+ "@babel/helper-member-expression-to-functions" "^7.10.4"
+ "@babel/helper-optimise-call-expression" "^7.10.4"
+ "@babel/traverse" "^7.10.4"
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-simple-access@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461"
+ integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==
+ dependencies:
+ "@babel/template" "^7.10.4"
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-split-export-declaration@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz#2c70576eaa3b5609b24cb99db2888cc3fc4251d1"
+ integrity sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==
+ dependencies:
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-validator-identifier@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2"
+ integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==
+
+"@babel/helpers@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044"
+ integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==
+ dependencies:
+ "@babel/template" "^7.10.4"
+ "@babel/traverse" "^7.10.4"
+ "@babel/types" "^7.10.4"
+
+"@babel/highlight@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143"
+ integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.10.4"
chalk "^2.0.0"
- esutils "^2.0.2"
js-tokens "^4.0.0"
-"@babel/parser@^7.1.0", "@babel/parser@^7.7.5", "@babel/parser@^7.9.0":
- version "7.9.4"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8"
- integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==
-
-"@babel/parser@^7.7.0", "@babel/parser@^7.8.6":
- version "7.8.8"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.8.tgz#4c3b7ce36db37e0629be1f0d50a571d2f86f6cd4"
- integrity sha512-mO5GWzBPsPf6865iIbzNE0AvkKF3NE+2S3eRUpE+FE07BOAkXh6G+GW/Pj01hhXjve1WScbaIO4UlY1JKeqCcA==
-
-"@babel/parser@^7.8.3":
- version "7.8.4"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.4.tgz#d1dbe64691d60358a974295fa53da074dd2ce8e8"
- integrity sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==
+"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.10.5", "@babel/parser@^7.7.0":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.5.tgz#e7c6bf5a7deff957cec9f04b551e2762909d826b"
+ integrity sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==
"@babel/plugin-syntax-async-generators@^7.8.4":
version "7.8.4"
"@babel/helper-plugin-utils" "^7.8.0"
"@babel/plugin-syntax-class-properties@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.8.3.tgz#6cb933a8872c8d359bfde69bbeaae5162fd1e8f7"
- integrity sha512-UcAyQWg2bAN647Q+O811tG9MrJ38Z10jjhQdKNAL8fsyPzE3cCN/uT+f55cFVY4aGO4jqJAvmqsuY3GQDwAoXg==
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz#6644e6a0baa55a61f9e3231f6c9eeb6ee46c124c"
+ integrity sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==
dependencies:
- "@babel/helper-plugin-utils" "^7.8.3"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-import-meta@^7.8.3":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51"
+ integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
"@babel/plugin-syntax-json-strings@^7.8.3":
version "7.8.3"
"@babel/helper-plugin-utils" "^7.8.0"
"@babel/plugin-syntax-logical-assignment-operators@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.8.3.tgz#3995d7d7ffff432f6ddc742b47e730c054599897"
- integrity sha512-Zpg2Sgc++37kuFl6ppq2Q7Awc6E6AIW671x5PY8E/f7MCIyPPGK/EoeZXvvY3P42exZ3Q4/t3YOzP/HiN79jDg==
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
+ integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==
dependencies:
- "@babel/helper-plugin-utils" "^7.8.3"
+ "@babel/helper-plugin-utils" "^7.10.4"
"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3":
version "7.8.3"
"@babel/helper-plugin-utils" "^7.8.0"
"@babel/plugin-syntax-numeric-separator@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz#0e3fb63e09bea1b11e96467271c8308007e7c41f"
- integrity sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97"
+ integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==
dependencies:
- "@babel/helper-plugin-utils" "^7.8.3"
+ "@babel/helper-plugin-utils" "^7.10.4"
"@babel/plugin-syntax-object-rest-spread@^7.8.3":
version "7.8.3"
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
-"@babel/runtime-corejs3@^7.7.4":
- version "7.8.4"
- resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.8.4.tgz#ccc4e042e2fae419c67fa709567e5d2179ed3940"
- integrity sha512-+wpLqy5+fbQhvbllvlJEVRIpYj+COUWnnsm+I4jZlA8Lo7/MJmBhGTCHyk1/RWfOqBRJ2MbadddG6QltTKTlrg==
- dependencies:
- core-js-pure "^3.0.0"
- regenerator-runtime "^0.13.2"
-
-"@babel/runtime-corejs3@^7.8.3":
- version "7.8.7"
- resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.8.7.tgz#8209d9dff2f33aa2616cb319c83fe159ffb07b8c"
- integrity sha512-sc7A+H4I8kTd7S61dgB9RomXu/C+F4IrRr4Ytze4dnfx7AXEpCrejSNpjx7vq6y/Bak9S6Kbk65a/WgMLtg43Q==
+"@babel/runtime-corejs3@^7.10.2", "@babel/runtime-corejs3@^7.8.3":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.5.tgz#a57fe6c13045ca33768a2aa527ead795146febe1"
+ integrity sha512-RMafpmrNB5E/bwdSphLr8a8++9TosnyJp98RZzI6VOx2R2CCMpsXXXRvmI700O9oEKpXdZat6oEK68/F0zjd4A==
dependencies:
core-js-pure "^3.0.0"
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.4":
- version "7.8.4"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308"
- integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==
- dependencies:
- regenerator-runtime "^0.13.2"
-
-"@babel/template@^7.7.4", "@babel/template@^7.8.6":
- version "7.8.6"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b"
- integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c"
+ integrity sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==
dependencies:
- "@babel/code-frame" "^7.8.3"
- "@babel/parser" "^7.8.6"
- "@babel/types" "^7.8.6"
+ regenerator-runtime "^0.13.4"
-"@babel/template@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8"
- integrity sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==
- dependencies:
- "@babel/code-frame" "^7.8.3"
- "@babel/parser" "^7.8.3"
- "@babel/types" "^7.8.3"
-
-"@babel/traverse@^7.1.0", "@babel/traverse@^7.7.4", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0":
- version "7.9.5"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2"
- integrity sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==
- dependencies:
- "@babel/code-frame" "^7.8.3"
- "@babel/generator" "^7.9.5"
- "@babel/helper-function-name" "^7.9.5"
- "@babel/helper-split-export-declaration" "^7.8.3"
- "@babel/parser" "^7.9.0"
- "@babel/types" "^7.9.5"
- debug "^4.1.0"
- globals "^11.1.0"
- lodash "^4.17.13"
-
-"@babel/traverse@^7.7.0":
- version "7.8.6"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.6.tgz#acfe0c64e1cd991b3e32eae813a6eb564954b5ff"
- integrity sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==
- dependencies:
- "@babel/code-frame" "^7.8.3"
- "@babel/generator" "^7.8.6"
- "@babel/helper-function-name" "^7.8.3"
- "@babel/helper-split-export-declaration" "^7.8.3"
- "@babel/parser" "^7.8.6"
- "@babel/types" "^7.8.6"
+"@babel/template@^7.10.4", "@babel/template@^7.3.3":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278"
+ integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ "@babel/parser" "^7.10.4"
+ "@babel/types" "^7.10.4"
+
+"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.10.5", "@babel/traverse@^7.7.0":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.5.tgz#77ce464f5b258be265af618d8fddf0536f20b564"
+ integrity sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ "@babel/generator" "^7.10.5"
+ "@babel/helper-function-name" "^7.10.4"
+ "@babel/helper-split-export-declaration" "^7.10.4"
+ "@babel/parser" "^7.10.5"
+ "@babel/types" "^7.10.5"
debug "^4.1.0"
globals "^11.1.0"
- lodash "^4.17.13"
-
-"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.9.0", "@babel/types@^7.9.5":
- version "7.9.5"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444"
- integrity sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==
- dependencies:
- "@babel/helper-validator-identifier" "^7.9.5"
- lodash "^4.17.13"
- to-fast-properties "^2.0.0"
-
-"@babel/types@^7.7.0", "@babel/types@^7.8.6", "@babel/types@^7.8.7":
- version "7.8.7"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.7.tgz#1fc9729e1acbb2337d5b6977a63979b4819f5d1d"
- integrity sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==
- dependencies:
- esutils "^2.0.2"
- lodash "^4.17.13"
- to-fast-properties "^2.0.0"
+ lodash "^4.17.19"
-"@babel/types@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c"
- integrity sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==
+"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.7.0":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.5.tgz#d88ae7e2fde86bfbfe851d4d81afa70a997b5d15"
+ integrity sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==
dependencies:
- esutils "^2.0.2"
- lodash "^4.17.13"
+ "@babel/helper-validator-identifier" "^7.10.4"
+ lodash "^4.17.19"
to-fast-properties "^2.0.0"
"@bcoe/v8-coverage@^0.2.3":
exec-sh "^0.3.2"
minimist "^1.2.0"
+"@iarna/cli@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@iarna/cli/-/cli-1.2.0.tgz#0f7af5e851afe895104583c4ca07377a8094d641"
+ integrity sha512-ukITQAqVs2n9HGmn3car/Ir7d3ta650iXhrG7pjr3EWdFmJuuOVWgYsu7ftsSe5VifEFFhjxVuX9+8F7L8hwcA==
+ dependencies:
+ signal-exit "^3.0.2"
+ update-notifier "^2.2.0"
+ yargs "^8.0.2"
+
"@istanbuljs/load-nyc-config@^1.0.0":
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz#10602de5570baea82f8afbfa2630b24e7a8cfe5b"
- integrity sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg==
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
+ integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==
dependencies:
camelcase "^5.3.1"
find-up "^4.1.0"
+ get-package-type "^0.1.0"
js-yaml "^3.13.1"
resolve-from "^5.0.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==
-"@jest/console@^25.4.0":
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/@jest/console/-/console-25.4.0.tgz#e2760b532701137801ba824dcff6bc822c961bac"
- integrity sha512-CfE0erx4hdJ6t7RzAcE1wLG6ZzsHSmybvIBQDoCkDM1QaSeWL9wJMzID/2BbHHa7ll9SsbbK43HjbERbBaFX2A==
+"@jest/console@^26.1.0":
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.1.0.tgz#f67c89e4f4d04dbcf7b052aed5ab9c74f915b954"
+ integrity sha512-+0lpTHMd/8pJp+Nd4lyip+/Iyf2dZJvcCqrlkeZQoQid+JlThA4M9vxHtheyrQ99jJTMQam+es4BcvZ5W5cC3A==
dependencies:
- "@jest/types" "^25.4.0"
- chalk "^3.0.0"
- jest-message-util "^25.4.0"
- jest-util "^25.4.0"
+ "@jest/types" "^26.1.0"
+ chalk "^4.0.0"
+ jest-message-util "^26.1.0"
+ jest-util "^26.1.0"
slash "^3.0.0"
-"@jest/core@^25.4.0":
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/@jest/core/-/core-25.4.0.tgz#cc1fe078df69b8f0fbb023bb0bcee23ef3b89411"
- integrity sha512-h1x9WSVV0+TKVtATGjyQIMJENs8aF6eUjnCoi4jyRemYZmekLr8EJOGQqTWEX8W6SbZ6Skesy9pGXrKeAolUJw==
+"@jest/core@^26.1.0":
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.1.0.tgz#4580555b522de412a7998b3938c851e4f9da1c18"
+ integrity sha512-zyizYmDJOOVke4OO/De//aiv8b07OwZzL2cfsvWF3q9YssfpcKfcnZAwDY8f+A76xXSMMYe8i/f/LPocLlByfw==
dependencies:
- "@jest/console" "^25.4.0"
- "@jest/reporters" "^25.4.0"
- "@jest/test-result" "^25.4.0"
- "@jest/transform" "^25.4.0"
- "@jest/types" "^25.4.0"
+ "@jest/console" "^26.1.0"
+ "@jest/reporters" "^26.1.0"
+ "@jest/test-result" "^26.1.0"
+ "@jest/transform" "^26.1.0"
+ "@jest/types" "^26.1.0"
ansi-escapes "^4.2.1"
- chalk "^3.0.0"
+ chalk "^4.0.0"
exit "^0.1.2"
- graceful-fs "^4.2.3"
- jest-changed-files "^25.4.0"
- jest-config "^25.4.0"
- jest-haste-map "^25.4.0"
- jest-message-util "^25.4.0"
- jest-regex-util "^25.2.6"
- jest-resolve "^25.4.0"
- jest-resolve-dependencies "^25.4.0"
- jest-runner "^25.4.0"
- jest-runtime "^25.4.0"
- jest-snapshot "^25.4.0"
- jest-util "^25.4.0"
- jest-validate "^25.4.0"
- jest-watcher "^25.4.0"
+ graceful-fs "^4.2.4"
+ jest-changed-files "^26.1.0"
+ jest-config "^26.1.0"
+ jest-haste-map "^26.1.0"
+ jest-message-util "^26.1.0"
+ jest-regex-util "^26.0.0"
+ jest-resolve "^26.1.0"
+ jest-resolve-dependencies "^26.1.0"
+ jest-runner "^26.1.0"
+ jest-runtime "^26.1.0"
+ jest-snapshot "^26.1.0"
+ jest-util "^26.1.0"
+ jest-validate "^26.1.0"
+ jest-watcher "^26.1.0"
micromatch "^4.0.2"
p-each-series "^2.1.0"
- realpath-native "^2.0.0"
rimraf "^3.0.0"
slash "^3.0.0"
strip-ansi "^6.0.0"
-"@jest/environment@^25.4.0":
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-25.4.0.tgz#45071f525f0d8c5a51ed2b04fd42b55a8f0c7cb3"
- integrity sha512-KDctiak4mu7b4J6BIoN/+LUL3pscBzoUCP+EtSPd2tK9fqyDY5OF+CmkBywkFWezS9tyH5ACOQNtpjtueEDH6Q==
+"@jest/environment@^26.1.0":
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.1.0.tgz#378853bcdd1c2443b4555ab908cfbabb851e96da"
+ integrity sha512-86+DNcGongbX7ai/KE/S3/NcUVZfrwvFzOOWX/W+OOTvTds7j07LtC+MgGydH5c8Ri3uIrvdmVgd1xFD5zt/xA==
+ dependencies:
+ "@jest/fake-timers" "^26.1.0"
+ "@jest/types" "^26.1.0"
+ jest-mock "^26.1.0"
+
+"@jest/fake-timers@^26.1.0":
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.1.0.tgz#9a76b7a94c351cdbc0ad53e5a748789f819a65fe"
+ integrity sha512-Y5F3kBVWxhau3TJ825iuWy++BAuQzK/xEa+wD9vDH3RytW9f2DbMVodfUQC54rZDX3POqdxCgcKdgcOL0rYUpA==
dependencies:
- "@jest/fake-timers" "^25.4.0"
- "@jest/types" "^25.4.0"
- jest-mock "^25.4.0"
+ "@jest/types" "^26.1.0"
+ "@sinonjs/fake-timers" "^6.0.1"
+ jest-message-util "^26.1.0"
+ jest-mock "^26.1.0"
+ jest-util "^26.1.0"
-"@jest/fake-timers@^25.4.0":
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.4.0.tgz#3a9a4289ba836abd084953dca406389a57e00fbd"
- integrity sha512-lI9z+VOmVX4dPPFzyj0vm+UtaB8dCJJ852lcDnY0uCPRvZAaVGnMwBBc1wxtf+h7Vz6KszoOvKAt4QijDnHDkg==
+"@jest/globals@^26.1.0":
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.1.0.tgz#6cc5d7cbb79b76b120f2403d7d755693cf063ab1"
+ integrity sha512-MKiHPNaT+ZoG85oMaYUmGHEqu98y3WO2yeIDJrs2sJqHhYOy3Z6F7F/luzFomRQ8SQ1wEkmahFAz2291Iv8EAw==
dependencies:
- "@jest/types" "^25.4.0"
- jest-message-util "^25.4.0"
- jest-mock "^25.4.0"
- jest-util "^25.4.0"
- lolex "^5.0.0"
+ "@jest/environment" "^26.1.0"
+ "@jest/types" "^26.1.0"
+ expect "^26.1.0"
-"@jest/reporters@^25.4.0":
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-25.4.0.tgz#836093433b32ce4e866298af2d6fcf6ed351b0b0"
- integrity sha512-bhx/buYbZgLZm4JWLcRJ/q9Gvmd3oUh7k2V7gA4ZYBx6J28pIuykIouclRdiAC6eGVX1uRZT+GK4CQJLd/PwPg==
+"@jest/reporters@^26.1.0":
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.1.0.tgz#08952e90c90282e14ff49e927bdf1873617dae78"
+ integrity sha512-SVAysur9FOIojJbF4wLP0TybmqwDkdnFxHSPzHMMIYyBtldCW9gG+Q5xWjpMFyErDiwlRuPyMSJSU64A67Pazg==
dependencies:
"@bcoe/v8-coverage" "^0.2.3"
- "@jest/console" "^25.4.0"
- "@jest/test-result" "^25.4.0"
- "@jest/transform" "^25.4.0"
- "@jest/types" "^25.4.0"
- chalk "^3.0.0"
+ "@jest/console" "^26.1.0"
+ "@jest/test-result" "^26.1.0"
+ "@jest/transform" "^26.1.0"
+ "@jest/types" "^26.1.0"
+ chalk "^4.0.0"
collect-v8-coverage "^1.0.0"
exit "^0.1.2"
glob "^7.1.2"
+ graceful-fs "^4.2.4"
istanbul-lib-coverage "^3.0.0"
- istanbul-lib-instrument "^4.0.0"
+ istanbul-lib-instrument "^4.0.3"
istanbul-lib-report "^3.0.0"
istanbul-lib-source-maps "^4.0.0"
istanbul-reports "^3.0.2"
- jest-haste-map "^25.4.0"
- jest-resolve "^25.4.0"
- jest-util "^25.4.0"
- jest-worker "^25.4.0"
+ jest-haste-map "^26.1.0"
+ jest-resolve "^26.1.0"
+ jest-util "^26.1.0"
+ jest-worker "^26.1.0"
slash "^3.0.0"
source-map "^0.6.0"
- string-length "^3.1.0"
+ string-length "^4.0.1"
terminal-link "^2.0.0"
v8-to-istanbul "^4.1.3"
optionalDependencies:
- node-notifier "^6.0.0"
+ node-notifier "^7.0.0"
-"@jest/source-map@^25.2.6":
- version "25.2.6"
- resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-25.2.6.tgz#0ef2209514c6d445ebccea1438c55647f22abb4c"
- integrity sha512-VuIRZF8M2zxYFGTEhkNSvQkUKafQro4y+mwUxy5ewRqs5N/ynSFUODYp3fy1zCnbCMy1pz3k+u57uCqx8QRSQQ==
+"@jest/source-map@^26.1.0":
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.1.0.tgz#a6a020d00e7d9478f4b690167c5e8b77e63adb26"
+ integrity sha512-XYRPYx4eEVX15cMT9mstnO7hkHP3krNtKfxUYd8L7gbtia8JvZZ6bMzSwa6IQJENbudTwKMw5R1BePRD+bkEmA==
dependencies:
callsites "^3.0.0"
- graceful-fs "^4.2.3"
+ graceful-fs "^4.2.4"
source-map "^0.6.0"
-"@jest/test-result@^25.4.0":
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-25.4.0.tgz#6f2ec2c8da9981ef013ad8651c1c6f0cb20c6324"
- integrity sha512-8BAKPaMCHlL941eyfqhWbmp3MebtzywlxzV+qtngQ3FH+RBqnoSAhNEPj4MG7d2NVUrMOVfrwuzGpVIK+QnMAA==
+"@jest/test-result@^26.1.0":
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.1.0.tgz#a93fa15b21ad3c7ceb21c2b4c35be2e407d8e971"
+ integrity sha512-Xz44mhXph93EYMA8aYDz+75mFbarTV/d/x0yMdI3tfSRs/vh4CqSxgzVmCps1fPkHDCtn0tU8IH9iCKgGeGpfw==
dependencies:
- "@jest/console" "^25.4.0"
- "@jest/types" "^25.4.0"
+ "@jest/console" "^26.1.0"
+ "@jest/types" "^26.1.0"
"@types/istanbul-lib-coverage" "^2.0.0"
collect-v8-coverage "^1.0.0"
-"@jest/test-sequencer@^25.4.0":
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-25.4.0.tgz#2b96f9d37f18dc3336b28e3c8070f97f9f55f43b"
- integrity sha512-240cI+nsM3attx2bMp9uGjjHrwrpvxxrZi8Tyqp/cfOzl98oZXVakXBgxODGyBYAy/UGXPKXLvNc2GaqItrsJg==
+"@jest/test-sequencer@^26.1.0":
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.1.0.tgz#41a6fc8b850c3f33f48288ea9ea517c047e7f14e"
+ integrity sha512-Z/hcK+rTq56E6sBwMoQhSRDVjqrGtj1y14e2bIgcowARaIE1SgOanwx6gvY4Q9gTKMoZQXbXvptji+q5GYxa6Q==
dependencies:
- "@jest/test-result" "^25.4.0"
- jest-haste-map "^25.4.0"
- jest-runner "^25.4.0"
- jest-runtime "^25.4.0"
+ "@jest/test-result" "^26.1.0"
+ graceful-fs "^4.2.4"
+ jest-haste-map "^26.1.0"
+ jest-runner "^26.1.0"
+ jest-runtime "^26.1.0"
-"@jest/transform@^25.4.0":
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-25.4.0.tgz#eef36f0367d639e2fd93dccd758550377fbb9962"
- integrity sha512-t1w2S6V1sk++1HHsxboWxPEuSpN8pxEvNrZN+Ud/knkROWtf8LeUmz73A4ezE8476a5AM00IZr9a8FO9x1+j3g==
+"@jest/transform@^26.1.0":
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.1.0.tgz#697f48898c2a2787c9b4cb71d09d7e617464e509"
+ integrity sha512-ICPm6sUXmZJieq45ix28k0s+d/z2E8CHDsq+WwtWI6kW8m7I8kPqarSEcUN86entHQ570ZBRci5OWaKL0wlAWw==
dependencies:
"@babel/core" "^7.1.0"
- "@jest/types" "^25.4.0"
+ "@jest/types" "^26.1.0"
babel-plugin-istanbul "^6.0.0"
- chalk "^3.0.0"
+ chalk "^4.0.0"
convert-source-map "^1.4.0"
fast-json-stable-stringify "^2.0.0"
- graceful-fs "^4.2.3"
- jest-haste-map "^25.4.0"
- jest-regex-util "^25.2.6"
- jest-util "^25.4.0"
+ graceful-fs "^4.2.4"
+ jest-haste-map "^26.1.0"
+ jest-regex-util "^26.0.0"
+ jest-util "^26.1.0"
micromatch "^4.0.2"
pirates "^4.0.1"
- realpath-native "^2.0.0"
slash "^3.0.0"
source-map "^0.6.1"
write-file-atomic "^3.0.0"
-"@jest/types@^25.4.0":
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.4.0.tgz#5afeb8f7e1cba153a28e5ac3c9fe3eede7206d59"
- integrity sha512-XBeaWNzw2PPnGW5aXvZt3+VO60M+34RY3XDsCK5tW7kyj3RK0XClRutCfjqcBuaR2aBQTbluEDME9b5MB9UAPw==
+"@jest/types@^25.5.0":
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
+ integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^1.1.1"
"@types/yargs" "^15.0.0"
chalk "^3.0.0"
-"@popperjs/core@^2.2.0":
- version "2.3.2"
- resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.3.2.tgz#1e56eb99bccddbda6a3e29aa4f3660f5b23edc43"
- integrity sha512-18Tz3QghwsuHUC4gTNoxcEw1ClsrJ+lRypYpm+aucQonYNnmskQYvDZZKLHMPvQ7OwthWJl715UEX+Tg2fJkJw==
-
-"@samverschueren/stream-to-observable@^0.3.0":
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
- integrity sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==
+"@jest/types@^26.1.0":
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.1.0.tgz#f8afaaaeeb23b5cad49dd1f7779689941dcb6057"
+ integrity sha512-GXigDDsp6ZlNMhXQDeuy/iYCDsRIHJabWtDzvnn36+aqFfG14JmFV0e/iXxY4SP9vbXSiPNOWdehU5MeqrYHBQ==
dependencies:
- any-observable "^0.3.0"
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ "@types/istanbul-reports" "^1.1.1"
+ "@types/yargs" "^15.0.0"
+ chalk "^4.0.0"
+
+"@popperjs/core@^2.4.4":
+ version "2.4.4"
+ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.4.4.tgz#11d5db19bd178936ec89cd84519c4de439574398"
+ integrity sha512-1oO6+dN5kdIA3sKPZhRGJTfGVP4SWV6KqlMOwry4J3HfyD68sl/3KmG7DeYUzvN+RbhXDnv/D8vNNB8168tAMg==
"@sinonjs/commons@^1.7.0":
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.2.tgz#505f55c74e0272b43f6c52d81946bed7058fc0e2"
- integrity sha512-+DUO6pnp3udV/v2VfUWgaY5BIE1IfT7lLfeDzPVeMT1XKkaAp9LgSI9x5RtrFQoZ9Oi0PgXQQHPaoKu7dCjVxw==
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217"
+ integrity sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==
dependencies:
type-detect "4.0.8"
+"@sinonjs/fake-timers@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40"
+ integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==
+ dependencies:
+ "@sinonjs/commons" "^1.7.0"
+
"@types/autosize@^3.0.6":
version "3.0.7"
resolved "https://registry.yarnpkg.com/@types/autosize/-/autosize-3.0.7.tgz#f5da28d7ea4532c8b60573d67ec04fc866fa13db"
dependencies:
"@types/jquery" "*"
-"@types/babel__core@^7.1.7":
- version "7.1.7"
- resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.7.tgz#1dacad8840364a57c98d0dd4855c6dd3752c6b89"
- integrity sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw==
+"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7":
+ version "7.1.9"
+ resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d"
+ integrity sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==
dependencies:
"@babel/parser" "^7.1.0"
"@babel/types" "^7.0.0"
"@babel/types" "^7.0.0"
"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6":
- version "7.0.10"
- resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.10.tgz#d9a99f017317d9b3d1abc2ced45d3bca68df0daf"
- integrity sha512-74fNdUGrWsgIB/V9kTO5FGHPWYY6Eqn+3Z7L6Hc4e/BxjYV7puvBqp5HwsVYYfLm6iURYBNCx4Ut37OF9yitCw==
+ version "7.0.13"
+ resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.13.tgz#1874914be974a492e1b4cb00585cabb274e8ba18"
+ integrity sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ==
dependencies:
"@babel/types" "^7.3.0"
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
+"@types/graceful-fs@^4.1.2":
+ version "4.1.3"
+ resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.3.tgz#039af35fe26bec35003e8d86d2ee9c586354348f"
+ integrity sha512-AiHRaEB50LQg0pZmm659vNBb9f4SJ0qrAnteuzhSeAUcJKxoYgEnprg/83kppCnc2zvtCKbdZry1a5pVY3lOTQ==
+ dependencies:
+ "@types/node" "*"
+
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
- integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
+ integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==
"@types/istanbul-lib-report@*":
version "3.0.0"
"@types/istanbul-lib-coverage" "*"
"@types/istanbul-reports@^1.1.1":
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a"
- integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz#e875cc689e47bce549ec81f3df5e6f6f11cfaeb2"
+ integrity sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==
dependencies:
"@types/istanbul-lib-coverage" "*"
"@types/istanbul-lib-report" "*"
-"@types/jest@^25.2.1":
- version "25.2.1"
- resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.2.1.tgz#9544cd438607955381c1bdbdb97767a249297db5"
- integrity sha512-msra1bCaAeEdkSyA0CZ6gW1ukMIvZ5YoJkdXw/qhQdsuuDlFTcEUrUw8CLCPt2rVRUfXlClVvK2gvPs9IokZaA==
+"@types/jest@^26.0.7":
+ version "26.0.7"
+ resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.7.tgz#495cb1d1818c1699dbc3b8b046baf1c86ef5e324"
+ integrity sha512-+x0077/LoN6MjqBcVOe1y9dpryWnfDZ+Xfo3EqGeBcfPRJlQp3Lw62RvNlWxuGv7kOEwlHriAa54updi3Jvvwg==
dependencies:
jest-diff "^25.2.1"
pretty-format "^25.2.1"
"@types/jquery@*":
- version "3.3.31"
- resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.31.tgz#27c706e4bf488474e1cb54a71d8303f37c93451b"
- integrity sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg==
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.0.tgz#ccb7dfd317d02d4227dd3803c75297d0c10dad68"
+ integrity sha512-C7qQUjpMWDUNYQRTXsP5nbYYwCwwgy84yPgoTT7fPN69NH92wLeCtFaMsWeolJD1AF/6uQw3pYt62rzv83sMmw==
dependencies:
"@types/sizzle" "*"
integrity sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw==
"@types/json-schema@^7.0.3":
- version "7.0.4"
- resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
- integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==
+ version "7.0.5"
+ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
+ integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==
+
+"@types/json5@^0.0.29":
+ version "0.0.29"
+ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
+ integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/jwt-decode@^2.2.1":
version "2.2.1"
integrity sha512-Q7DYAOi9O/+cLLhdaSvKdaumWyHbm7HAk/bFwwyTuU0arR5yyCeW5GOoqt4tJTpDRxhpx9Q8kQL6vMpuw9hDSw==
"@types/markdown-it-container@^2.0.2":
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/@types/markdown-it-container/-/markdown-it-container-2.0.2.tgz#0e624653415a1c2f088a5ae51f7bfff480c03f49"
- integrity sha512-T770GL+zJz8Ssh1NpLiOruYhrU96yb8ovPSegLrWY5XIkJc6PVVC7kH/oQaVD0rkePpWMFJK018OgS/pwviOMw==
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@types/markdown-it-container/-/markdown-it-container-2.0.3.tgz#436de4c019d7d71b60f759037fd4d03611569eb8"
+ integrity sha512-ouJluaEGWV7clX7NVMRjkQfS/a11hFXDG1U04l8vrS1P2UgAFPlgMpk1rAPgK0MWU1NhcBYWVW7w/SpgePLs0A==
dependencies:
"@types/markdown-it" "*"
-"@types/markdown-it@*", "@types/markdown-it@^0.0.9":
- version "0.0.9"
- resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.9.tgz#a5d552f95216c478e0a27a5acc1b28dcffd989ce"
- integrity sha512-IFSepyZXbF4dgSvsk8EsgaQ/8Msv1I5eTL0BZ0X3iGO9jw6tCVtPG8HchIPm3wrkmGdqZOD42kE0zplVi1gYDA==
+"@types/markdown-it@*", "@types/markdown-it@^10.0.1":
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-10.0.1.tgz#94e252ab689c8e9ceb9aff2946e0a458390105eb"
+ integrity sha512-L1ibTdA5IUe/cRBlf3N3syAOBQSN1WCMGtAWir6mKxibiRl4LmpZM4jLz+7zAqiMnhQuAP1sqZOF9wXgn2kpEg==
dependencies:
"@types/linkify-it" "*"
+ "@types/mdurl" "*"
+
+"@types/mdurl@*":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9"
+ integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
"@types/node-fetch@^2.5.6":
- version "2.5.6"
- resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.6.tgz#df8377a66e64ddf75b65b072e37b3c5c5425a96f"
- integrity sha512-2w0NTwMWF1d3NJMK0Uiq2UNN8htVCyOWOD0jIPjPgC5Ph/YP4dVhs9YxxcMcuLuwAslz0dVEcZQUaqkLs3IzOQ==
+ version "2.5.7"
+ resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c"
+ integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
-"@types/node@*":
- version "13.13.2"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.2.tgz#160d82623610db590a64e8ca81784e11117e5a54"
- integrity sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A==
-
-"@types/node@^13.11.1":
- version "13.11.1"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7"
- integrity sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==
+"@types/node@*", "@types/node@^14.0.26":
+ version "14.0.26"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.26.tgz#22a3b8a46510da8944b67bfc27df02c34a35331c"
+ integrity sha512-W+fpe5s91FBGE0pEa0lnqGLL4USgpLgs4nokw16SrBBco/gQxuua7KnArSEOd5iaMqbbSHV10vUDkJYJJqpXKA==
"@types/normalize-package-data@^2.4.0":
version "2.4.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
-"@types/prettier@^1.19.0":
- version "1.19.1"
- resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f"
- integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==
+"@types/prettier@^2.0.0":
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.0.2.tgz#5bb52ee68d0f8efa9cc0099920e56be6cc4e37f3"
+ integrity sha512-IkVfat549ggtkZUthUzEX49562eGikhSYeVGX97SkMFn+sTZrgRewXjQ4tPKFPCykZHkX1Zfd9OoELGqKU2jJA==
"@types/sizzle@*":
version "2.3.2"
integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==
"@types/yargs@^15.0.0":
- version "15.0.4"
- resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.4.tgz#7e5d0f8ca25e9d5849f2ea443cf7c402decd8299"
- integrity sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==
+ version "15.0.5"
+ resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.5.tgz#947e9a6561483bdee9adffc983e91a6902af8b79"
+ integrity sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==
dependencies:
"@types/yargs-parser" "*"
-"@typescript-eslint/eslint-plugin@2.24.0":
- version "2.24.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.24.0.tgz#a86cf618c965a462cddf3601f594544b134d6d68"
- integrity sha512-wJRBeaMeT7RLQ27UQkDFOu25MqFOBus8PtOa9KaT5ZuxC1kAsd7JEHqWt4YXuY9eancX0GK9C68i5OROnlIzBA==
+"@typescript-eslint/eslint-plugin@3.6.1":
+ version "3.6.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.6.1.tgz#5ced8fd2087fbb83a76973dea4a0d39d9cb4a642"
+ integrity sha512-06lfjo76naNeOMDl+mWG9Fh/a0UHKLGhin+mGaIw72FUMbMGBkdi/FEJmgEDzh4eE73KIYzHWvOCYJ0ak7nrJQ==
dependencies:
- "@typescript-eslint/experimental-utils" "2.24.0"
- eslint-utils "^1.4.3"
+ "@typescript-eslint/experimental-utils" "3.6.1"
+ debug "^4.1.1"
functional-red-black-tree "^1.0.1"
regexpp "^3.0.0"
+ semver "^7.3.2"
tsutils "^3.17.1"
-"@typescript-eslint/experimental-utils@2.24.0":
- version "2.24.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.24.0.tgz#a5cb2ed89fedf8b59638dc83484eb0c8c35e1143"
- integrity sha512-DXrwuXTdVh3ycNCMYmWhUzn/gfqu9N0VzNnahjiDJvcyhfBy4gb59ncVZVxdp5XzBC77dCncu0daQgOkbvPwBw==
+"@typescript-eslint/experimental-utils@3.6.1":
+ version "3.6.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.6.1.tgz#b5a2738ebbceb3fa90c5b07d50bb1225403c4a54"
+ integrity sha512-oS+hihzQE5M84ewXrTlVx7eTgc52eu+sVmG7ayLfOhyZmJ8Unvf3osyFQNADHP26yoThFfbxcibbO0d2FjnYhg==
dependencies:
"@types/json-schema" "^7.0.3"
- "@typescript-eslint/typescript-estree" "2.24.0"
+ "@typescript-eslint/types" "3.6.1"
+ "@typescript-eslint/typescript-estree" "3.6.1"
eslint-scope "^5.0.0"
+ eslint-utils "^2.0.0"
"@typescript-eslint/experimental-utils@^2.5.0":
- version "2.18.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.18.0.tgz#e4eab839082030282496c1439bbf9fdf2a4f3da8"
- integrity sha512-J6MopKPHuJYmQUkANLip7g9I82ZLe1naCbxZZW3O2sIxTiq/9YYoOELEKY7oPg0hJ0V/AQ225h2z0Yp+RRMXhw==
+ version "2.34.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f"
+ integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==
dependencies:
"@types/json-schema" "^7.0.3"
- "@typescript-eslint/typescript-estree" "2.18.0"
+ "@typescript-eslint/typescript-estree" "2.34.0"
eslint-scope "^5.0.0"
+ eslint-utils "^2.0.0"
-"@typescript-eslint/parser@2.24.0":
- version "2.24.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.24.0.tgz#2cf0eae6e6dd44d162486ad949c126b887f11eb8"
- integrity sha512-H2Y7uacwSSg8IbVxdYExSI3T7uM1DzmOn2COGtCahCC3g8YtM1xYAPi2MAHyfPs61VKxP/J/UiSctcRgw4G8aw==
+"@typescript-eslint/parser@3.6.1":
+ version "3.6.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.6.1.tgz#216e8adf4ee9c629f77c985476a2ea07fb80e1dc"
+ integrity sha512-SLihQU8RMe77YJ/jGTqOt0lMq7k3hlPVfp7v/cxMnXA9T0bQYoMDfTsNgHXpwSJM1Iq2aAJ8WqekxUwGv5F67Q==
dependencies:
"@types/eslint-visitor-keys" "^1.0.0"
- "@typescript-eslint/experimental-utils" "2.24.0"
- "@typescript-eslint/typescript-estree" "2.24.0"
+ "@typescript-eslint/experimental-utils" "3.6.1"
+ "@typescript-eslint/types" "3.6.1"
+ "@typescript-eslint/typescript-estree" "3.6.1"
eslint-visitor-keys "^1.1.0"
-"@typescript-eslint/typescript-estree@2.18.0":
- version "2.18.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.18.0.tgz#cfbd16ed1b111166617d718619c19b62764c8460"
- integrity sha512-gVHylf7FDb8VSi2ypFuEL3hOtoC4HkZZ5dOjXvVjoyKdRrvXAOPSzpNRnKMfaUUEiSLP8UF9j9X9EDLxC0lfZg==
+"@typescript-eslint/types@3.6.1":
+ version "3.6.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.6.1.tgz#87600fe79a1874235d3cc1cf5c7e1a12eea69eee"
+ integrity sha512-NPxd5yXG63gx57WDTW1rp0cF3XlNuuFFB5G+Kc48zZ+51ZnQn9yjDEsjTPQ+aWM+V+Z0I4kuTFKjKvgcT1F7xQ==
+
+"@typescript-eslint/typescript-estree@2.34.0":
+ version "2.34.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5"
+ integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==
dependencies:
debug "^4.1.1"
eslint-visitor-keys "^1.1.0"
glob "^7.1.6"
is-glob "^4.0.1"
lodash "^4.17.15"
- semver "^6.3.0"
+ semver "^7.3.2"
tsutils "^3.17.1"
-"@typescript-eslint/typescript-estree@2.24.0":
- version "2.24.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.24.0.tgz#38bbc8bb479790d2f324797ffbcdb346d897c62a"
- integrity sha512-RJ0yMe5owMSix55qX7Mi9V6z2FDuuDpN6eR5fzRJrp+8in9UF41IGNQHbg5aMK4/PjVaEQksLvz0IA8n+Mr/FA==
+"@typescript-eslint/typescript-estree@3.6.1":
+ version "3.6.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.6.1.tgz#a5c91fcc5497cce7922ff86bc37d5e5891dcdefa"
+ integrity sha512-G4XRe/ZbCZkL1fy09DPN3U0mR6SayIv1zSeBNquRFRk7CnVLgkC2ZPj8llEMJg5Y8dJ3T76SvTGtceytniaztQ==
dependencies:
+ "@typescript-eslint/types" "3.6.1"
+ "@typescript-eslint/visitor-keys" "3.6.1"
debug "^4.1.1"
- eslint-visitor-keys "^1.1.0"
glob "^7.1.6"
is-glob "^4.0.1"
lodash "^4.17.15"
- semver "^6.3.0"
+ semver "^7.3.2"
tsutils "^3.17.1"
-abab@^2.0.0:
+"@typescript-eslint/visitor-keys@3.6.1":
+ version "3.6.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.6.1.tgz#5c57a7772f4dd623cfeacc219303e7d46f963b37"
+ integrity sha512-qC8Olwz5ZyMTZrh4Wl3K4U6tfms0R/mzU4/5W3XeUZptVraGVmbptJbn6h2Ey6Rb3hOs3zWoAUebZk8t47KGiQ==
+ dependencies:
+ eslint-visitor-keys "^1.1.0"
+
+JSONStream@^1.3.2:
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
+ integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==
+ dependencies:
+ jsonparse "^1.2.0"
+ through ">=2.2.7 <3"
+
+abab@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a"
integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==
+abbrev@1, abbrev@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
+ integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
+
accepts@~1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
mime-types "~2.1.24"
negotiator "0.6.2"
-acorn-globals@^4.3.2:
- version "4.3.4"
- resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7"
- integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==
+acorn-globals@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45"
+ integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==
dependencies:
- acorn "^6.0.1"
- acorn-walk "^6.0.1"
+ acorn "^7.1.1"
+ acorn-walk "^7.1.1"
acorn-jsx@^4.0.1:
version "4.1.1"
dependencies:
acorn "^5.0.3"
-acorn-jsx@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384"
- integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==
+acorn-jsx@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe"
+ integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==
-acorn-walk@^6.0.1:
- version "6.2.0"
- resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c"
- integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==
+acorn-walk@^7.1.1:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
+ integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
acorn@^5.0.3, acorn@^5.7.3:
version "5.7.4"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==
-acorn@^6.0.1:
- version "6.4.1"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474"
- integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==
+acorn@^7.1.1, acorn@^7.3.1:
+ version "7.3.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd"
+ integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==
-acorn@^7.1.0:
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c"
- integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==
+agent-base@4, agent-base@^4.1.0, agent-base@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
+ integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
+ dependencies:
+ es6-promisify "^5.0.0"
+
+agent-base@~4.2.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
+ integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==
+ dependencies:
+ es6-promisify "^5.0.0"
+
+agentkeepalive@^3.3.0, agentkeepalive@^3.4.1:
+ version "3.5.2"
+ resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.2.tgz#a113924dd3fa24a0bc3b78108c450c2abee00f67"
+ integrity sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==
+ dependencies:
+ humanize-ms "^1.2.1"
+
+aggregate-error@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0"
+ integrity sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==
+ dependencies:
+ clean-stack "^2.0.0"
+ indent-string "^4.0.0"
ajax-request@^1.2.0:
version "1.2.3"
utils-extend "^1.0.7"
ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5:
- version "6.11.0"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9"
- integrity sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==
+ version "6.12.3"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706"
+ integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
+ansi-align@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f"
+ integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=
+ dependencies:
+ string-width "^2.0.0"
+
+ansi-colors@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
+ integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
+
ansi-escapes@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
-ansi-escapes@^4.2.1:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.0.tgz#a4ce2b33d6b214b7950d8595c212f12ac9cc569d"
- integrity sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==
+ansi-escapes@^4.2.1, ansi-escapes@^4.3.0:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
+ integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==
dependencies:
- type-fest "^0.8.1"
+ type-fest "^0.11.0"
ansi-regex@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
-ansi-regex@^3.0.0:
+ansi-regex@^3.0.0, ansi-regex@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
-ansi-styles@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
- integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=
-
ansi-styles@^3.2.0, ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
resolved "https://registry.yarnpkg.com/ansi/-/ansi-0.3.1.tgz#0c42d4fb17160d5a9af1e484bace1c66922c1b21"
integrity sha1-DELU+xcWDVqa8eSEus4cZpIsGyE=
-any-observable@^0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b"
- integrity sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==
+ansicolors@~0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979"
+ integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=
+
+ansistyles@~0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/ansistyles/-/ansistyles-0.1.3.tgz#5de60415bda071bb37127854c864f41b23254539"
+ integrity sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk=
anymatch@^1.3.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a"
integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==
+aproba@^1.0.3, aproba@^1.1.1, aproba@^1.1.2, aproba@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
+ integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
+
+"aproba@^1.1.2 || 2":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
+ integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
+
+archy@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40"
+ integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=
+
+are-we-there-yet@~1.1.2:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21"
+ integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==
+ dependencies:
+ delegates "^1.0.0"
+ readable-stream "^2.0.6"
+
arg@^4.1.0:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.2.tgz#e70c90579e02c63d80e3ad4e31d8bfdb8bd50064"
- integrity sha512-+ytCkGcBtHZ3V2r2Z06AncYO8jz46UEamcspGoU8lHcEbpn6J77QK0vdWvChsclg/tM5XIJC5tnjmPp7Eq6Obg==
+ version "4.1.3"
+ resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
+ integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
argparse@^1.0.7:
version "1.0.10"
dependencies:
sprintf-js "~1.0.2"
-aria-query@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc"
- integrity sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=
+aria-query@^4.2.2:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
+ integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==
dependencies:
- ast-types-flow "0.0.7"
- commander "^2.11.0"
+ "@babel/runtime" "^7.10.2"
+ "@babel/runtime-corejs3" "^7.10.2"
arr-diff@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
-array-equal@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
- integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=
-
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
-array-includes@^3.0.3, array-includes@^3.1.1:
+array-includes@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348"
integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
-array.prototype.flat@^1.2.1:
+array.prototype.flat@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b"
integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==
define-properties "^1.1.3"
es-abstract "^1.17.0-next.1"
+array.prototype.flatmap@^1.2.3:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz#1c13f84a178566042dd63de4414440db9222e443"
+ integrity sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg==
+ dependencies:
+ define-properties "^1.1.3"
+ es-abstract "^1.17.0-next.1"
+ function-bind "^1.1.1"
+
+asap@^2.0.0:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
+ integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
+
asn1@~0.2.3:
version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
-ast-types-flow@0.0.7, ast-types-flow@^0.0.7:
+ast-types-flow@^0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0=
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
+astral-regex@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
+ integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
+
async-each@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
aws4@^1.8.0:
- version "1.9.1"
- resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
- integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2"
+ integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==
-axobject-query@^2.0.2:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.1.1.tgz#2a3b1271ec722d48a4cd4b3fcc20c853326a49a7"
- integrity sha512-lF98xa/yvy6j3fBHAgQXIYl+J4eZadOSqsPojemUqClzNbBV38wWGpUbQbVEyf4eUF5yF7eHmGgGA2JiHyjeqw==
- dependencies:
- "@babel/runtime" "^7.7.4"
- "@babel/runtime-corejs3" "^7.7.4"
+axe-core@^3.5.4:
+ version "3.5.5"
+ resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-3.5.5.tgz#84315073b53fa3c0c51676c588d59da09a192227"
+ integrity sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q==
+
+axobject-query@^2.1.2:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
+ integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==
-babel-eslint@10.1.0:
+babel-eslint@10.1.0, babel-eslint@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232"
integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==
eslint-visitor-keys "^1.0.0"
resolve "^1.12.0"
-babel-jest@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-25.4.0.tgz#409eb3e2ddc2ad9a92afdbb00991f1633f8018d0"
- integrity sha512-p+epx4K0ypmHuCnd8BapfyOwWwosNCYhedetQey1awddtfmEX0MmdxctGl956uwUmjwXR5VSS5xJcGX9DvdIog==
+babel-jest@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.1.0.tgz#b20751185fc7569a0f135730584044d1cb934328"
+ integrity sha512-Nkqgtfe7j6PxLO6TnCQQlkMm8wdTdnIF8xrdpooHCuD5hXRzVEPbPneTJKknH5Dsv3L8ip9unHDAp48YQ54Dkg==
dependencies:
- "@jest/transform" "^25.4.0"
- "@jest/types" "^25.4.0"
+ "@jest/transform" "^26.1.0"
+ "@jest/types" "^26.1.0"
"@types/babel__core" "^7.1.7"
babel-plugin-istanbul "^6.0.0"
- babel-preset-jest "^25.4.0"
- chalk "^3.0.0"
+ babel-preset-jest "^26.1.0"
+ chalk "^4.0.0"
+ graceful-fs "^4.2.4"
slash "^3.0.0"
babel-plugin-istanbul@^6.0.0:
istanbul-lib-instrument "^4.0.0"
test-exclude "^6.0.0"
-babel-plugin-jest-hoist@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.4.0.tgz#0c122c1b93fb76f52d2465be2e8069e798e9d442"
- integrity sha512-M3a10JCtTyKevb0MjuH6tU+cP/NVQZ82QPADqI1RQYY1OphztsCeIeQmTsHmF/NS6m0E51Zl4QNsI3odXSQF5w==
+babel-plugin-jest-hoist@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.1.0.tgz#c6a774da08247a28285620a64dfadbd05dd5233a"
+ integrity sha512-qhqLVkkSlqmC83bdMhM8WW4Z9tB+JkjqAqlbbohS9sJLT5Ha2vfzuKqg5yenXrAjOPG2YC0WiXdH3a9PvB+YYw==
dependencies:
+ "@babel/template" "^7.3.3"
+ "@babel/types" "^7.3.3"
+ "@types/babel__core" "^7.0.0"
"@types/babel__traverse" "^7.0.6"
babel-preset-current-node-syntax@^0.1.2:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.2.tgz#fb4a4c51fe38ca60fede1dc74ab35eb843cb41d6"
- integrity sha512-u/8cS+dEiK1SFILbOC8/rUI3ml9lboKuuMvZ/4aQnQmhecQAgPw5ew066C1ObnEAUmlx7dv/s2z52psWEtLNiw==
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.3.tgz#b4b547acddbf963cba555ba9f9cbbb70bfd044da"
+ integrity sha512-uyexu1sVwcdFnyq9o8UQYsXwXflIh8LvrF5+cKrYam93ned1CStffB3+BEcsxGSgagoA3GEyjDqO4a/58hyPYQ==
dependencies:
"@babel/plugin-syntax-async-generators" "^7.8.4"
"@babel/plugin-syntax-bigint" "^7.8.3"
"@babel/plugin-syntax-class-properties" "^7.8.3"
+ "@babel/plugin-syntax-import-meta" "^7.8.3"
"@babel/plugin-syntax-json-strings" "^7.8.3"
"@babel/plugin-syntax-logical-assignment-operators" "^7.8.3"
"@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
"@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
"@babel/plugin-syntax-optional-chaining" "^7.8.3"
-babel-preset-jest@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-25.4.0.tgz#10037cc32b751b994b260964629e49dc479abf4c"
- integrity sha512-PwFiEWflHdu3JCeTr0Pb9NcHHE34qWFnPQRVPvqQITx4CsDCzs6o05923I10XvLvn9nNsRHuiVgB72wG/90ZHQ==
+babel-preset-jest@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.1.0.tgz#612f714e5b457394acfd863793c564cbcdb7d1c1"
+ integrity sha512-na9qCqFksknlEj5iSdw1ehMVR06LCCTkZLGKeEtxDDdhg8xpUF09m29Kvh1pRbZ07h7AQ5ttLYUwpXL4tO6w7w==
dependencies:
- babel-plugin-jest-hoist "^25.4.0"
+ babel-plugin-jest-hoist "^26.1.0"
babel-preset-current-node-syntax "^0.1.2"
balanced-match@^1.0.0:
dependencies:
tweetnacl "^0.14.3"
+bin-links@^1.1.0, bin-links@^1.1.2:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-1.1.8.tgz#bd39aadab5dc4bdac222a07df5baf1af745b2228"
+ integrity sha512-KgmVfx+QqggqP9dA3iIc5pA4T1qEEEL+hOhOhNPaUm77OTrJoOXE/C05SJLNJe6m/2wUK7F1tDSou7n5TfCDzQ==
+ dependencies:
+ bluebird "^3.5.3"
+ cmd-shim "^3.0.0"
+ gentle-fs "^2.3.0"
+ graceful-fs "^4.1.15"
+ npm-normalize-package-bin "^1.0.0"
+ write-file-atomic "^2.3.0"
+
binary-extensions@^1.0.0:
version "1.13.1"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
dependencies:
file-uri-to-path "1.0.0"
+block-stream@*:
+ version "0.0.9"
+ resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
+ integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=
+ dependencies:
+ inherits "~2.0.0"
+
+bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5:
+ version "3.7.2"
+ resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
+ integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
+
+bluebird@~3.5.1:
+ version "3.5.5"
+ resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f"
+ integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==
+
body-parser@1.19.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
type-is "~1.6.17"
bootswatch@^4.3.1:
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/bootswatch/-/bootswatch-4.4.1.tgz#f270618b4ebe07de8e1748acd88f104cc31bfec3"
- integrity sha512-Kx3z6+3Jpg9g6l/xZBCnc8d6KeJK0QawxCZWOomdcI5AuSZLZb+DoH5X9RJH+cOcSeMAxyzdIjkVUR01+Db5bQ==
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/bootswatch/-/bootswatch-4.5.0.tgz#1f4ea460118d0da8113418f1627ca755697d7e0f"
+ integrity sha512-kAfYTTWIsgUOA5nybJNM4yvOpwxm37eIb1DFZlD5jLgPttK+bLJTt9UpKWAoMQZRBQbWfC1O1w1P5PGU7lz83Q==
bowser@^2.0.0-beta.3:
- version "2.9.0"
- resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.9.0.tgz#3bed854233b419b9a7422d9ee3e85504373821c9"
- integrity sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA==
+ version "2.10.0"
+ resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.10.0.tgz#be3736f161c4bb8b10958027ab99465d2a811198"
+ integrity sha512-OCsqTQboTEWWsUjcp5jLSw2ZHsBiv2C105iFs61bOT0Hnwi9p7/uuXdd7mu8RYcarREfdjNN+8LitmEHATsLYg==
+
+boxen@^1.2.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"
+ integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==
+ dependencies:
+ ansi-align "^2.0.0"
+ camelcase "^4.0.0"
+ chalk "^2.0.1"
+ cli-boxes "^1.0.0"
+ string-width "^2.0.0"
+ term-size "^1.2.0"
+ widest-line "^2.0.0"
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
-browser-resolve@^1.11.3:
- version "1.11.3"
- resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6"
- integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==
- dependencies:
- resolve "1.1.7"
-
bs-logger@0.x:
version "0.2.6"
resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
+builtin-modules@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
+ integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
+
+builtins@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88"
+ integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og=
+
+byline@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1"
+ integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=
+
+byte-size@^4.0.2:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-4.0.4.tgz#29d381709f41aae0d89c631f1c81aec88cd40b23"
+ integrity sha512-82RPeneC6nqCdSwCX2hZUz3JPOvN5at/nTEw/CMf05Smu3Hrpo9Psb7LjN+k+XndNArG1EY8L4+BM3aTM4BCvw==
+
bytes@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
+cacache@^10.0.0, cacache@^10.0.4:
+ version "10.0.4"
+ resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460"
+ integrity sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==
+ dependencies:
+ bluebird "^3.5.1"
+ chownr "^1.0.1"
+ glob "^7.1.2"
+ graceful-fs "^4.1.11"
+ lru-cache "^4.1.1"
+ mississippi "^2.0.0"
+ mkdirp "^0.5.1"
+ move-concurrently "^1.0.1"
+ promise-inflight "^1.0.1"
+ rimraf "^2.6.2"
+ ssri "^5.2.4"
+ unique-filename "^1.1.0"
+ y18n "^4.0.0"
+
+cacache@^11.0.2, cacache@^11.3.3:
+ version "11.3.3"
+ resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.3.tgz#8bd29df8c6a718a6ebd2d010da4d7972ae3bbadc"
+ integrity sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==
+ dependencies:
+ bluebird "^3.5.5"
+ chownr "^1.1.1"
+ figgy-pudding "^3.5.1"
+ glob "^7.1.4"
+ graceful-fs "^4.1.15"
+ lru-cache "^5.1.1"
+ mississippi "^3.0.0"
+ mkdirp "^0.5.1"
+ move-concurrently "^1.0.1"
+ promise-inflight "^1.0.1"
+ rimraf "^2.6.3"
+ ssri "^6.0.1"
+ unique-filename "^1.1.1"
+ y18n "^4.0.0"
+
cache-base@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
union-value "^1.0.0"
unset-value "^1.0.0"
+call-limit@~1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/call-limit/-/call-limit-1.1.1.tgz#ef15f2670db3f1992557e2d965abc459e6e358d4"
+ integrity sha512-5twvci5b9eRBw2wCfPtN0GmlR2/gadZqyFpPhOK6CvMFoFgA+USnZ6Jpu1lhG9h85pQ3Ouil3PfXWRD4EUaRiQ==
+
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+camelcase@^4.0.0, camelcase@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
+ integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
+
camelcase@^5.0.0, camelcase@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
+camelcase@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e"
+ integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==
+
capture-exit@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
dependencies:
rsvp "^4.8.4"
+capture-stack-trace@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d"
+ integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==
+
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
resolved "https://registry.yarnpkg.com/chain-able/-/chain-able-3.0.0.tgz#dcffe8b04f3da210941a23843bc1332bb288ca9f"
integrity sha512-26MoELhta86n7gCsE2T1hGRyncZvPjFXTkB/DEp4+i/EJVSxXQNwXMDZZb2+SWcbPuow18wQtztaW7GXOel9DA==
-chalk@^1.0.0, chalk@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
- integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=
- dependencies:
- ansi-styles "^2.2.1"
- escape-string-regexp "^1.0.2"
- has-ansi "^2.0.0"
- strip-ansi "^3.0.0"
- supports-color "^2.0.0"
-
-chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2:
+chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
ansi-styles "^4.1.0"
supports-color "^7.1.0"
-chalk@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72"
- integrity sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==
+chalk@^4.0.0, chalk@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
+ integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+char-regex@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
+ integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
+
chardet@^0.4.0:
version "0.4.2"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=
-chardet@^0.7.0:
- version "0.7.0"
- resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
- integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
-
choices.js@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/choices.js/-/choices.js-9.0.1.tgz#745fb29af8670428fdc0bf1cc9dfaa404e9d0510"
optionalDependencies:
fsevents "^1.0.0"
+chownr@^1.0.1, chownr@^1.1.1, chownr@^1.1.2:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
+ integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
+
+chownr@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
+ integrity sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=
+
+ci-info@^1.5.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
+ integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==
+
ci-info@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
-class-utils@^0.3.5:
+cidr-regex@1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/cidr-regex/-/cidr-regex-1.0.6.tgz#74abfd619df370b9d54ab14475568e97dd64c0c1"
+ integrity sha1-dKv9YZ3zcLnVSrFEdVaOl91kwME=
+
+class-utils@^0.3.5:
version "0.3.6"
resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==
dependencies:
escape-string-regexp "^1.0.5"
-cli-cursor@^2.0.0, cli-cursor@^2.1.0:
+clean-stack@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
+ integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
+
+cli-boxes@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
+ integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM=
+
+cli-columns@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/cli-columns/-/cli-columns-3.1.2.tgz#6732d972979efc2ae444a1f08e08fa139c96a18e"
+ integrity sha1-ZzLZcpee/CrkRKHwjgj6E5yWoY4=
+ dependencies:
+ string-width "^2.0.0"
+ strip-ansi "^3.0.1"
+
+cli-cursor@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=
dependencies:
restore-cursor "^3.1.0"
-cli-truncate@^0.2.1:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574"
- integrity sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=
+cli-table2@~0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/cli-table2/-/cli-table2-0.2.0.tgz#2d1ef7f218a0e786e214540562d4bd177fe32d97"
+ integrity sha1-LR738hig54biFFQFYtS9F3/jLZc=
dependencies:
- slice-ansi "0.0.4"
+ lodash "^3.10.1"
string-width "^1.0.1"
+ optionalDependencies:
+ colors "^1.1.2"
+
+cli-table3@^0.5.0:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202"
+ integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==
+ dependencies:
+ object-assign "^4.1.0"
+ string-width "^2.1.1"
+ optionalDependencies:
+ colors "^1.1.2"
+
+cli-truncate@2.1.0, cli-truncate@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7"
+ integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==
+ dependencies:
+ slice-ansi "^3.0.0"
+ string-width "^4.2.0"
cli-width@^2.0.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
- integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
+ integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==
+
+cliui@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
+ integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=
+ dependencies:
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+ wrap-ansi "^2.0.0"
+
+cliui@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
+ integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
+ dependencies:
+ string-width "^3.1.0"
+ strip-ansi "^5.2.0"
+ wrap-ansi "^5.1.0"
cliui@^6.0.0:
version "6.0.0"
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
+clone@^1.0.2:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
+ integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
+
+cmd-shim@^3.0.0, cmd-shim@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-3.0.3.tgz#2c35238d3df37d98ecdd7d5f6b8dc6b21cadc7cb"
+ integrity sha512-DtGg+0xiFhQIntSBRzL2fRQBnmtAVwXIDo4Qq46HPpObYquxMaZS4sb82U9nH91qJrlosC1wa9gwr0QyL/HypA==
+ dependencies:
+ graceful-fs "^4.1.2"
+ mkdirp "~0.5.0"
+
+cmd-shim@~2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-2.0.2.tgz#6fcbda99483a8fd15d7d30a196ca69d688a2efdb"
+ integrity sha1-b8vamUg6j9FdfTChlspp1oii79s=
+ dependencies:
+ graceful-fs "^4.1.2"
+ mkdirp "~0.5.0"
+
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+colors@^1.1.2:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
+ integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
+
+columnify@~1.5.4:
+ version "1.5.4"
+ resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb"
+ integrity sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=
+ dependencies:
+ strip-ansi "^3.0.0"
+ wcwidth "^1.0.0"
+
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
dependencies:
delayed-stream "~1.0.0"
-commander@^2.11.0, commander@^2.20.0:
+commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
-commander@^4.0.1:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.0.tgz#545983a0603fe425bc672d66c9e3c89c42121a83"
- integrity sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw==
+commander@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
+ integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
compare-versions@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+concat-stream@^1.5.0, concat-stream@^1.5.2:
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
+ integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
+ dependencies:
+ buffer-from "^1.0.0"
+ inherits "^2.0.3"
+ readable-stream "^2.2.2"
+ typedarray "^0.0.6"
+
+config-chain@~1.1.11:
+ version "1.1.12"
+ resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"
+ integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==
+ dependencies:
+ ini "^1.3.4"
+ proto-list "~1.2.1"
+
+configstore@^3.0.0:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f"
+ integrity sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==
+ dependencies:
+ dot-prop "^4.1.0"
+ graceful-fs "^4.1.2"
+ make-dir "^1.0.0"
+ unique-string "^1.0.0"
+ write-file-atomic "^2.0.0"
+ xdg-basedir "^3.0.0"
+
+console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
+ integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
+
contains-path@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
+copy-concurrently@^1.0.0:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
+ integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==
+ dependencies:
+ aproba "^1.1.1"
+ fs-write-stream-atomic "^1.0.8"
+ iferr "^0.1.5"
+ mkdirp "^0.5.1"
+ rimraf "^2.5.4"
+ run-queue "^1.0.0"
+
copy-descriptor@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
core-js-pure@^3.0.0:
- version "3.6.4"
- resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a"
- integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw==
+ version "3.6.5"
+ resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
+ integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
path-type "^4.0.0"
yaml "^1.7.2"
-cross-spawn@^6.0.0, cross-spawn@^6.0.5:
+create-error-class@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
+ integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=
+ dependencies:
+ capture-stack-trace "^1.0.0"
+
+cross-spawn@^5.0.1:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
+ integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=
+ dependencies:
+ lru-cache "^4.0.1"
+ shebang-command "^1.2.0"
+ which "^1.2.9"
+
+cross-spawn@^6.0.0:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
shebang-command "^1.2.0"
which "^1.2.9"
-cross-spawn@^7.0.0:
- version "7.0.1"
- resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14"
- integrity sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==
+cross-spawn@^7.0.0, cross-spawn@^7.0.2:
+ version "7.0.3"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
+ integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
which "^2.0.1"
-cssom@^0.4.1:
+crypto-random-string@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
+ integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=
+
+cssom@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10"
integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a"
integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==
-cssstyle@^2.0.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.2.0.tgz#e4c44debccd6b7911ed617a4395e5754bba59992"
- integrity sha512-sEb3XFPx3jNnCAMtqrXPDeSgQr+jojtCeNf8cvMNMh1cG970+lljssvQDzPq6lmmJu2Vhqood/gtEomBiHOGnA==
+cssstyle@^2.2.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852"
+ integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==
dependencies:
cssom "~0.3.6"
-damerau-levenshtein@^1.0.4:
+cyclist@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
+ integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
+
+damerau-levenshtein@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791"
integrity sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==
dependencies:
assert-plus "^1.0.0"
-data-urls@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe"
- integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==
+data-urls@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b"
+ integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==
dependencies:
- abab "^2.0.0"
- whatwg-mimetype "^2.2.0"
- whatwg-url "^7.0.0"
-
-date-fns@^1.27.2:
- version "1.30.1"
- resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
- integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
+ abab "^2.0.3"
+ whatwg-mimetype "^2.3.0"
+ whatwg-url "^8.0.0"
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
version "2.6.9"
dependencies:
ms "2.0.0"
+debug@3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+ integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
+ dependencies:
+ ms "2.0.0"
+
+debug@^3.1.0:
+ version "3.2.6"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
+ integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
+ dependencies:
+ ms "^2.1.1"
+
debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
dependencies:
ms "^2.1.1"
-decamelize@^1.2.0:
+debuglog@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
+ integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=
+
+decamelize@^1.1.1, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
+decimal.js@^10.2.0:
+ version "10.2.0"
+ resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231"
+ integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==
+
decode-uri-component@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
-deep-is@~0.1.3:
+deep-equal@^1.0.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
+ integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==
+ dependencies:
+ is-arguments "^1.0.4"
+ is-date-object "^1.0.1"
+ is-regex "^1.0.4"
+ object-is "^1.0.1"
+ object-keys "^1.1.1"
+ regexp.prototype.flags "^1.2.0"
+
+deep-extend@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
+ integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+
+deep-is@^0.1.3, deep-is@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
+defaults@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
+ integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=
+ dependencies:
+ clone "^1.0.2"
+
define-properties@^1.1.2, define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
+delegates@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
+ integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
+
depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
+detect-indent@~5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
+ integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50=
+
+detect-newline@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
+ integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=
+
detect-newline@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
+dezalgo@^1.0.0, dezalgo@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456"
+ integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=
+ dependencies:
+ asap "^2.0.0"
+ wrappy "1"
+
diff-sequences@^25.2.6:
version "25.2.6"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==
+diff-sequences@^26.0.0:
+ version "26.0.0"
+ resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.0.0.tgz#0760059a5c287637b842bd7085311db7060e88a6"
+ integrity sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg==
+
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
dependencies:
esutils "^2.0.2"
-domexception@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"
- integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==
+domexception@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304"
+ integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==
dependencies:
- webidl-conversions "^4.0.2"
+ webidl-conversions "^5.0.0"
+
+dot-prop@^4.1.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57"
+ integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==
+ dependencies:
+ is-obj "^1.0.0"
+
+dotenv@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef"
+ integrity sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==
dotenv@^8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
+duplexer3@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
+ integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
+
+duplexify@^3.4.2, duplexify@^3.6.0:
+ version "3.7.1"
+ resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
+ integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==
+ dependencies:
+ end-of-stream "^1.0.0"
+ inherits "^2.0.1"
+ readable-stream "^2.0.0"
+ stream-shift "^1.0.0"
+
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
jsbn "~0.1.0"
safer-buffer "^2.1.0"
+editor@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/editor/-/editor-1.0.0.tgz#60c7f87bd62bcc6a894fa8ccd6afb7823a24f742"
+ integrity sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=
+
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
-elegant-spinner@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
- integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=
-
-emoji-regex@^7.0.1, emoji-regex@^7.0.2:
+emoji-regex@^7.0.1:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+emoji-regex@^9.0.0:
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.0.0.tgz#48a2309cc8a1d2e9d23bc6a67c39b63032e76ea4"
+ integrity sha512-6p1NII1Vm62wni/VR/cUMauVQoxmLVb9csqQlvLz+hO2gk8U2UYDfXHQSUYIBKmZwAKz867IDqG7B+u0mj+M6w==
+
emoji-short-name@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/emoji-short-name/-/emoji-short-name-1.0.0.tgz#82e6f543b6c68984d69bdc80eac735104fdd4af8"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
-end-of-stream@^1.1.0:
+encoding@^0.1.11:
+ version "0.1.13"
+ resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
+ integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
+ dependencies:
+ iconv-lite "^0.6.2"
+
+end-of-stream@^1.0.0, end-of-stream@^1.1.0:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
dependencies:
once "^1.4.0"
+enquirer@^2.3.5:
+ version "2.3.6"
+ resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
+ integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
+ dependencies:
+ ansi-colors "^4.1.1"
+
entities@~2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
- integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f"
+ integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==
+
+err-code@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960"
+ integrity sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=
+
+errno@~0.1.7:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
+ integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==
+ dependencies:
+ prr "~1.0.1"
error-ex@^1.2.0, error-ex@^1.3.1:
version "1.3.2"
dependencies:
is-arrayish "^0.2.1"
-es-abstract@^1.17.0, es-abstract@^1.17.0-next.1:
- version "1.17.4"
- resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184"
- integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==
+es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.5:
+ version "1.17.6"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a"
+ integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==
dependencies:
es-to-primitive "^1.2.1"
function-bind "^1.1.1"
has "^1.0.3"
has-symbols "^1.0.1"
- is-callable "^1.1.5"
- is-regex "^1.0.5"
+ is-callable "^1.2.0"
+ is-regex "^1.1.0"
object-inspect "^1.7.0"
object-keys "^1.1.1"
object.assign "^4.1.0"
- string.prototype.trimleft "^2.1.1"
- string.prototype.trimright "^2.1.1"
+ string.prototype.trimend "^1.0.1"
+ string.prototype.trimstart "^1.0.1"
es-to-primitive@^1.2.1:
version "1.2.1"
is-date-object "^1.0.1"
is-symbol "^1.0.2"
+es6-promise@^4.0.3:
+ version "4.2.8"
+ resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
+ integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
+
+es6-promisify@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
+ integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
+ dependencies:
+ es6-promise "^4.0.3"
+
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
-escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
-escodegen@^1.11.1:
- version "1.14.1"
- resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457"
- integrity sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==
- dependencies:
- esprima "^4.0.1"
- estraverse "^4.2.0"
- esutils "^2.0.2"
- optionator "^0.8.1"
- optionalDependencies:
- source-map "~0.6.1"
+escape-string-regexp@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
+ integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
-escodegen@^1.8.1:
- version "1.13.0"
- resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.13.0.tgz#c7adf9bd3f3cc675bb752f202f79a720189cab29"
- integrity sha512-eYk2dCkxR07DsHA/X2hRBj0CFAZeri/LyDMc0C8JT1Hqi6JnVpMhJ7XFITbb0+yZS3lVkaPL2oCkZ3AVmeVbMw==
+escodegen@^1.14.1, escodegen@^1.8.1:
+ version "1.14.3"
+ resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503"
+ integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==
dependencies:
esprima "^4.0.1"
estraverse "^4.2.0"
lodash.get "^4.4.2"
lodash.zip "^4.2.0"
-eslint-config-prettier@6.10.0:
- version "6.10.0"
- resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.10.0.tgz#7b15e303bf9c956875c948f6b21500e48ded6a7f"
- integrity sha512-AtndijGte1rPILInUdHjvKEGbIV06NuvPrqlIEaEaWtbtvJh464mDeyGMdZEQMsGvC0ZVkiex1fSNcC4HAbRGg==
+eslint-config-prettier@6.11.0:
+ version "6.11.0"
+ resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz#f6d2238c1290d01c859a8b5c1f7d352a0b0da8b1"
+ integrity sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==
dependencies:
get-stdin "^6.0.0"
-eslint-import-resolver-node@^0.3.2:
- version "0.3.3"
- resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404"
- integrity sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==
+eslint-import-resolver-node@^0.3.3:
+ version "0.3.4"
+ resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717"
+ integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==
dependencies:
debug "^2.6.9"
resolve "^1.13.1"
-eslint-module-utils@^2.4.1:
- version "2.5.2"
- resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz#7878f7504824e1b857dd2505b59a8e5eda26a708"
- integrity sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q==
+eslint-module-utils@^2.6.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6"
+ integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==
dependencies:
debug "^2.6.9"
pkg-dir "^2.0.0"
-eslint-plugin-babel@5.3.0:
- version "5.3.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-babel/-/eslint-plugin-babel-5.3.0.tgz#2e7f251ccc249326da760c1a4c948a91c32d0023"
- integrity sha512-HPuNzSPE75O+SnxHIafbW5QB45r2w78fxqwK3HmjqIUoPfPzVrq6rD+CINU3yzoDSzEhUkX07VUphbF73Lth/w==
+eslint-plugin-babel@5.3.1:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz#75a2413ffbf17e7be57458301c60291f2cfbf560"
+ integrity sha512-VsQEr6NH3dj664+EyxJwO4FCYm/00JhYb3Sk3ft8o+fpKuIfQ9TaW6uVUfvwMXHcf/lsnRIoyFPsLMyiWCSL/g==
dependencies:
eslint-rule-composer "^0.3.0"
eslint-plugin-es@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.0.tgz#98cb1bc8ab0aa807977855e11ad9d1c9422d014b"
- integrity sha512-6/Jb/J/ZvSebydwbBJO1R9E5ky7YeElfK56Veh7e4QGFHCXoIXGH9HhVz+ibJLM3XJ1XjP+T7rKBLUa/Y7eIng==
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893"
+ integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==
dependencies:
eslint-utils "^2.0.0"
regexpp "^3.0.0"
-eslint-plugin-import@2.20.1:
- version "2.20.1"
- resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz#802423196dcb11d9ce8435a5fc02a6d3b46939b3"
- integrity sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==
+eslint-plugin-import@2.22.0:
+ version "2.22.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e"
+ integrity sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==
dependencies:
- array-includes "^3.0.3"
- array.prototype.flat "^1.2.1"
+ array-includes "^3.1.1"
+ array.prototype.flat "^1.2.3"
contains-path "^0.1.0"
debug "^2.6.9"
doctrine "1.5.0"
- eslint-import-resolver-node "^0.3.2"
- eslint-module-utils "^2.4.1"
+ eslint-import-resolver-node "^0.3.3"
+ eslint-module-utils "^2.6.0"
has "^1.0.3"
minimatch "^3.0.4"
- object.values "^1.1.0"
+ object.values "^1.1.1"
read-pkg-up "^2.0.0"
- resolve "^1.12.0"
+ resolve "^1.17.0"
+ tsconfig-paths "^3.9.0"
eslint-plugin-inferno@^7.14.3:
- version "7.14.3"
- resolved "https://registry.yarnpkg.com/eslint-plugin-inferno/-/eslint-plugin-inferno-7.14.3.tgz#eb5afb3ee23039261dde44cd102f65154a6eaa0a"
- integrity sha512-BntEmVvbJSSZCV10OyiXXo620Hb6c5mnbBxfiIvAmE2KCy9Dmqu9/vSEAiOsINHwH8wjHHgnjibwB3J51TFQfw==
+ version "7.20.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-inferno/-/eslint-plugin-inferno-7.20.1.tgz#e01f9436b5db8c89e9ece3550850751a4e2d9be9"
+ integrity sha512-Cy19MA7ea5AGoeBxDbN3Uf4m/W5pr0SpbGcrfq5UhEk7qgbguAUFF22TSXQAVBhXPQ/ZW6aB3md8H63QPAWyIw==
dependencies:
- array-includes "^3.0.3"
+ array-includes "^3.1.1"
doctrine "^3.0.0"
has "^1.0.3"
- jsx-ast-utils "^2.2.1"
- object.entries "^1.1.0"
- object.fromentries "^2.0.0"
- object.values "^1.1.0"
- resolve "^1.12.0"
+ jsx-ast-utils "^2.3.0"
+ object.entries "^1.1.2"
+ object.fromentries "^2.0.2"
+ object.values "^1.1.1"
+ resolve "^1.17.0"
+ string.prototype.matchall "^4.0.2"
+ xregexp "^4.3.0"
-eslint-plugin-jane@^7.2.1:
- version "7.2.1"
- resolved "https://registry.yarnpkg.com/eslint-plugin-jane/-/eslint-plugin-jane-7.2.1.tgz#5ffba9ce75e0a5e5dbe3918fc0c5332d2cd89c13"
- integrity sha512-hUmhEkHTDq6lQ4oLWZV5cLut9L67fcTiy0USbTsEOx658i9Jdikedt8NJhtamRqO5OUHBGSPU0JkOqBtVNUD+A==
+eslint-plugin-jane@^8.0.4:
+ version "8.0.4"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-jane/-/eslint-plugin-jane-8.0.4.tgz#31c193cd30d2f851af91dfd58b85fe3b30aff987"
+ integrity sha512-h3vfVfARv1g8DeJiEegCeLKXXMxs/VHBrDPX4x5XECOZu/8Ngi7GUFPUguJIvYMouam9OoAbYCVQIWEOk48nBQ==
dependencies:
- "@typescript-eslint/eslint-plugin" "2.24.0"
- "@typescript-eslint/parser" "2.24.0"
+ "@typescript-eslint/eslint-plugin" "3.6.1"
+ "@typescript-eslint/parser" "3.6.1"
babel-eslint "10.1.0"
- eslint-config-prettier "6.10.0"
- eslint-plugin-babel "5.3.0"
- eslint-plugin-import "2.20.1"
- eslint-plugin-jest "23.8.2"
- eslint-plugin-jsx-a11y "6.2.3"
- eslint-plugin-node "11.0.0"
- eslint-plugin-prettier "3.1.2"
+ eslint-config-prettier "6.11.0"
+ eslint-plugin-babel "5.3.1"
+ eslint-plugin-import "2.22.0"
+ eslint-plugin-jest "23.18.0"
+ eslint-plugin-jsx-a11y "6.3.1"
+ eslint-plugin-node "11.1.0"
+ eslint-plugin-prettier "3.1.4"
eslint-plugin-promise "4.2.1"
- eslint-plugin-react "7.19.0"
- eslint-plugin-react-hooks "2.5.1"
- eslint-plugin-unicorn "17.2.0"
+ eslint-plugin-react "7.20.3"
+ eslint-plugin-react-hooks "4.0.8"
+ eslint-plugin-unicorn "20.1.0"
-eslint-plugin-jest@23.8.2:
- version "23.8.2"
- resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.8.2.tgz#6f28b41c67ef635f803ebd9e168f6b73858eb8d4"
- integrity sha512-xwbnvOsotSV27MtAe7s8uGWOori0nUsrXh2f1EnpmXua8sDfY6VZhHAhHg2sqK7HBNycRQExF074XSZ7DvfoFg==
+eslint-plugin-jest@23.18.0:
+ version "23.18.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.18.0.tgz#4813eacb181820ed13c5505f400956d176b25af8"
+ integrity sha512-wLPM/Rm1SGhxrFQ2TKM/BYsYPhn7ch6ZEK92S2o/vGkAAnDXM0I4nTIo745RIX+VlCRMFgBuJEax6XfTHMdeKg==
dependencies:
"@typescript-eslint/experimental-utils" "^2.5.0"
-eslint-plugin-jsx-a11y@6.2.3:
- version "6.2.3"
- resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz#b872a09d5de51af70a97db1eea7dc933043708aa"
- integrity sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==
+eslint-plugin-jsx-a11y@6.3.1:
+ version "6.3.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.3.1.tgz#99ef7e97f567cc6a5b8dd5ab95a94a67058a2660"
+ integrity sha512-i1S+P+c3HOlBJzMFORRbC58tHa65Kbo8b52/TwCwSKLohwvpfT5rm2GjGWzOHTEuq4xxf2aRlHHTtmExDQOP+g==
dependencies:
- "@babel/runtime" "^7.4.5"
- aria-query "^3.0.0"
- array-includes "^3.0.3"
+ "@babel/runtime" "^7.10.2"
+ aria-query "^4.2.2"
+ array-includes "^3.1.1"
ast-types-flow "^0.0.7"
- axobject-query "^2.0.2"
- damerau-levenshtein "^1.0.4"
- emoji-regex "^7.0.2"
+ axe-core "^3.5.4"
+ axobject-query "^2.1.2"
+ damerau-levenshtein "^1.0.6"
+ emoji-regex "^9.0.0"
has "^1.0.3"
- jsx-ast-utils "^2.2.1"
+ jsx-ast-utils "^2.4.1"
+ language-tags "^1.0.5"
-eslint-plugin-node@11.0.0:
- version "11.0.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.0.0.tgz#365944bb0804c5d1d501182a9bc41a0ffefed726"
- integrity sha512-chUs/NVID+sknFiJzxoN9lM7uKSOEta8GC8365hw1nDfwIPIjjpRSwwPvQanWv8dt/pDe9EV4anmVSwdiSndNg==
+eslint-plugin-node@11.1.0:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d"
+ integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==
dependencies:
eslint-plugin-es "^3.0.0"
eslint-utils "^2.0.0"
resolve "^1.10.1"
semver "^6.1.0"
-eslint-plugin-prettier@3.1.2:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz#432e5a667666ab84ce72f945c72f77d996a5c9ba"
- integrity sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==
+eslint-plugin-prettier@3.1.4:
+ version "3.1.4"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz#168ab43154e2ea57db992a2cd097c828171f75c2"
+ integrity sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg==
dependencies:
prettier-linter-helpers "^1.0.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
-eslint-plugin-react-hooks@2.5.1:
- version "2.5.1"
- resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.5.1.tgz#4ef5930592588ce171abeb26f400c7fbcbc23cd0"
- integrity sha512-Y2c4b55R+6ZzwtTppKwSmK/Kar8AdLiC2f9NADCuxbcTgPPg41Gyqa6b9GppgXSvCtkRw43ZE86CT5sejKC6/g==
+eslint-plugin-react-hooks@4.0.8:
+ version "4.0.8"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.8.tgz#a9b1e3d57475ccd18276882eff3d6cba00da7a56"
+ integrity sha512-6SSb5AiMCPd8FDJrzah+Z4F44P2CdOaK026cXFV+o/xSRzfOiV1FNFeLl2z6xm3yqWOQEZ5OfVgiec90qV2xrQ==
-eslint-plugin-react@7.19.0:
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz#6d08f9673628aa69c5559d33489e855d83551666"
- integrity sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ==
+eslint-plugin-react@7.20.3:
+ version "7.20.3"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.20.3.tgz#0590525e7eb83890ce71f73c2cf836284ad8c2f1"
+ integrity sha512-txbo090buDeyV0ugF3YMWrzLIUqpYTsWSDZV9xLSmExE1P/Kmgg9++PD931r+KEWS66O1c9R4srLVVHmeHpoAg==
dependencies:
array-includes "^3.1.1"
+ array.prototype.flatmap "^1.2.3"
doctrine "^2.1.0"
has "^1.0.3"
- jsx-ast-utils "^2.2.3"
- object.entries "^1.1.1"
+ jsx-ast-utils "^2.4.1"
+ object.entries "^1.1.2"
object.fromentries "^2.0.2"
object.values "^1.1.1"
prop-types "^15.7.2"
- resolve "^1.15.1"
- semver "^6.3.0"
+ resolve "^1.17.0"
string.prototype.matchall "^4.0.2"
- xregexp "^4.3.0"
-eslint-plugin-unicorn@17.2.0:
- version "17.2.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-17.2.0.tgz#8f147ba24d417dc5de948c7df7d006108a37a540"
- integrity sha512-0kYjrywf0kQxevFz571KrDfYMIRZ5Kq6dDgPU1EEBFeC181r+fAaPatBScWX+/hisKJ4+eCRFebxTeVylsSYmw==
+eslint-plugin-unicorn@20.1.0:
+ version "20.1.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-20.1.0.tgz#a43f60ffc98406d72ec2a5fcc6dad24ba0192bc9"
+ integrity sha512-XQxLBJT/gnwyRR6cfYsIK1AdekQchAt5tmcsnldevGjgR2xoZsRUa5/i6e0seNHy2RoT57CkTnbVHwHF8No8LA==
dependencies:
ci-info "^2.0.0"
clean-regexp "^1.0.0"
eslint-ast-utils "^1.1.0"
- eslint-template-visitor "^1.1.0"
+ eslint-template-visitor "^2.0.0"
+ eslint-utils "^2.0.0"
import-modules "^2.0.0"
lodash "^4.17.15"
+ pluralize "^8.0.0"
read-pkg-up "^7.0.1"
- regexp-tree "^0.1.20"
+ regexp-tree "^0.1.21"
reserved-words "^0.1.2"
safe-regex "^2.1.1"
- semver "^7.1.2"
+ semver "^7.3.2"
eslint-rule-composer@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9"
integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==
-eslint-scope@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9"
- integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==
+eslint-scope@^5.0.0, eslint-scope@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5"
+ integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==
dependencies:
esrecurse "^4.1.0"
estraverse "^4.1.1"
-eslint-template-visitor@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/eslint-template-visitor/-/eslint-template-visitor-1.1.0.tgz#f090d124d1a52e05552149fc50468ed59608b166"
- integrity sha512-Lmy6QVlmFiIGl5fPi+8ACnov3sare+0Ouf7deJAGGhmUfeWJ5fVarELUxZRpsZ9sHejiJUq8626d0dn9uvcZTw==
- dependencies:
- eslint-visitor-keys "^1.1.0"
- espree "^6.1.1"
- multimap "^1.0.2"
-
-eslint-utils@^1.4.3:
- version "1.4.3"
- resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f"
- integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==
+eslint-template-visitor@^2.0.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/eslint-template-visitor/-/eslint-template-visitor-2.2.1.tgz#2dccb1ab28fa7429e56ba6dd0144def2d89bc2d6"
+ integrity sha512-q3SxoBXz0XjPGkUpwGVAwIwIPIxzCAJX1uwfVc8tW3v7u/zS7WXNH3I2Mu2MDz2NgSITAyKLRaQFPHu/iyKxDQ==
dependencies:
- eslint-visitor-keys "^1.1.0"
+ babel-eslint "^10.1.0"
+ eslint-visitor-keys "^1.3.0"
+ esquery "^1.3.1"
+ multimap "^1.1.0"
-eslint-utils@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd"
- integrity sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==
+eslint-utils@^2.0.0, eslint-utils@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
+ integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==
dependencies:
eslint-visitor-keys "^1.1.0"
-eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
- integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==
+eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
+ integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
-eslint@^6.5.1:
- version "6.8.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb"
- integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==
+eslint@^7.5.0:
+ version "7.5.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.5.0.tgz#9ecbfad62216d223b82ac9ffea7ef3444671d135"
+ integrity sha512-vlUP10xse9sWt9SGRtcr1LAC67BENcQMFeV+w5EvLEoFe3xJ8cF1Skd0msziRx/VMC+72B4DxreCE+OR12OA6Q==
dependencies:
"@babel/code-frame" "^7.0.0"
ajv "^6.10.0"
- chalk "^2.1.0"
- cross-spawn "^6.0.5"
+ chalk "^4.0.0"
+ cross-spawn "^7.0.2"
debug "^4.0.1"
doctrine "^3.0.0"
- eslint-scope "^5.0.0"
- eslint-utils "^1.4.3"
- eslint-visitor-keys "^1.1.0"
- espree "^6.1.2"
- esquery "^1.0.1"
+ enquirer "^2.3.5"
+ eslint-scope "^5.1.0"
+ eslint-utils "^2.1.0"
+ eslint-visitor-keys "^1.3.0"
+ espree "^7.2.0"
+ esquery "^1.2.0"
esutils "^2.0.2"
file-entry-cache "^5.0.1"
functional-red-black-tree "^1.0.1"
ignore "^4.0.6"
import-fresh "^3.0.0"
imurmurhash "^0.1.4"
- inquirer "^7.0.0"
is-glob "^4.0.0"
js-yaml "^3.13.1"
json-stable-stringify-without-jsonify "^1.0.1"
- levn "^0.3.0"
- lodash "^4.17.14"
+ levn "^0.4.1"
+ lodash "^4.17.19"
minimatch "^3.0.4"
- mkdirp "^0.5.1"
natural-compare "^1.4.0"
- optionator "^0.8.3"
+ optionator "^0.9.1"
progress "^2.0.0"
- regexpp "^2.0.1"
- semver "^6.1.2"
- strip-ansi "^5.2.0"
- strip-json-comments "^3.0.1"
+ regexpp "^3.1.0"
+ semver "^7.2.1"
+ strip-ansi "^6.0.0"
+ strip-json-comments "^3.1.0"
table "^5.2.3"
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
-espree@^6.1.1, espree@^6.1.2:
- version "6.1.2"
- resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.2.tgz#6c272650932b4f91c3714e5e7b5f5e2ecf47262d"
- integrity sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==
+espree@^7.2.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-7.2.0.tgz#1c263d5b513dbad0ac30c4991b93ac354e948d69"
+ integrity sha512-H+cQ3+3JYRMEIOl87e7QdHX70ocly5iW4+dttuR8iYSPr/hXKFb+7dBsZ7+u1adC4VrnPlTkv0+OwuPnDop19g==
dependencies:
- acorn "^7.1.0"
- acorn-jsx "^5.1.0"
- eslint-visitor-keys "^1.1.0"
+ acorn "^7.3.1"
+ acorn-jsx "^5.2.0"
+ eslint-visitor-keys "^1.3.0"
esprima@^4.0.0, esprima@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
-esquery@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708"
- integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==
+esquery@^1.2.0, esquery@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57"
+ integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==
dependencies:
- estraverse "^4.0.0"
+ estraverse "^5.1.0"
esrecurse@^4.1.0:
version "4.2.1"
dependencies:
estraverse "^4.1.0"
-estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
+estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
+estraverse@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642"
+ integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==
+
esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5"
integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==
+execa@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
+ integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=
+ dependencies:
+ cross-spawn "^5.0.1"
+ get-stream "^3.0.0"
+ is-stream "^1.1.0"
+ npm-run-path "^2.0.0"
+ p-finally "^1.0.0"
+ signal-exit "^3.0.0"
+ strip-eof "^1.0.0"
+
execa@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
signal-exit "^3.0.0"
strip-eof "^1.0.0"
-execa@^3.2.0, execa@^3.4.0:
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/execa/-/execa-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89"
- integrity sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==
+execa@^4.0.0, execa@^4.0.1:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2"
+ integrity sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==
dependencies:
cross-spawn "^7.0.0"
get-stream "^5.0.0"
merge-stream "^2.0.0"
npm-run-path "^4.0.0"
onetime "^5.1.0"
- p-finally "^2.0.0"
signal-exit "^3.0.2"
strip-final-newline "^2.0.0"
+exenv@^1.2.1:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
+ integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=
+
exit@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
dependencies:
fill-range "^2.1.0"
-expect@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/expect/-/expect-25.4.0.tgz#0b16c17401906d1679d173e59f0d4580b22f8dc8"
- integrity sha512-7BDIX99BTi12/sNGJXA9KMRcby4iAmu1xccBOhyKCyEhjcVKS3hPmHdA/4nSI9QGIOkUropKqr3vv7WMDM5lvQ==
+expect@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/expect/-/expect-26.1.0.tgz#8c62e31d0f8d5a8ebb186ee81473d15dd2fbf7c8"
+ integrity sha512-QbH4LZXDsno9AACrN9eM0zfnby9G+OsdNgZUohjg/P0mLy1O+/bzTAJGT6VSIjVCe8yKM6SzEl/ckEOFBT7Vnw==
dependencies:
- "@jest/types" "^25.4.0"
+ "@jest/types" "^26.1.0"
ansi-styles "^4.0.0"
- jest-get-type "^25.2.6"
- jest-matcher-utils "^25.4.0"
- jest-message-util "^25.4.0"
- jest-regex-util "^25.2.6"
+ jest-get-type "^26.0.0"
+ jest-matcher-utils "^26.1.0"
+ jest-message-util "^26.1.0"
+ jest-regex-util "^26.0.0"
express@^4.14.0:
version "4.17.1"
iconv-lite "^0.4.17"
tmp "^0.0.33"
-external-editor@^3.0.3:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495"
- integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==
- dependencies:
- chardet "^0.7.0"
- iconv-lite "^0.4.24"
- tmp "^0.0.33"
-
extglob@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
fast-deep-equal@^3.1.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4"
- integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
+ integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-diff@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
-fast-levenshtein@~2.0.6:
+fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
dependencies:
bser "2.1.1"
-figures@^1.7.0:
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
- integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=
- dependencies:
- escape-string-regexp "^1.0.5"
- object-assign "^4.1.0"
+figgy-pudding@^3.0.0, figgy-pudding@^3.5.1:
+ version "3.5.2"
+ resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
+ integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==
figures@^2.0.0:
version "2.0.0"
dependencies:
escape-string-regexp "^1.0.5"
-figures@^3.0.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/figures/-/figures-3.1.0.tgz#4b198dd07d8d71530642864af2d45dd9e459c4ec"
- integrity sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==
+figures@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
+ integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
dependencies:
escape-string-regexp "^1.0.5"
statuses "~1.5.0"
unpipe "~1.0.0"
+find-npm-prefix@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/find-npm-prefix/-/find-npm-prefix-1.0.2.tgz#8d8ce2c78b3b4b9e66c8acc6a37c231eb841cfdf"
+ integrity sha512-KEftzJ+H90x6pcKtdXZEPsQse8/y/UnvzRKrOSQFprnrGaFuJ62fVkP34Iu2IYuMvyauCyoLTNkJZgrrGA2wkA==
+
find-up@^2.0.0, find-up@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
dependencies:
locate-path "^2.0.0"
+find-up@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
+ integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
+ dependencies:
+ locate-path "^3.0.0"
+
find-up@^4.0.0, find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
write "1.0.3"
flatted@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08"
- integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
+ integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
fliplog@^0.3.13:
version "0.3.13"
dependencies:
chain-able "^1.0.1"
+flush-write-stream@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
+ integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==
+ dependencies:
+ inherits "^2.0.3"
+ readable-stream "^2.3.6"
+
for-in@^1.0.1, for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
+from2@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/from2/-/from2-1.3.0.tgz#88413baaa5f9a597cfde9221d86986cd3c061dfd"
+ integrity sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=
+ dependencies:
+ inherits "~2.0.1"
+ readable-stream "~1.1.10"
+
+from2@^2.1.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
+ integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=
+ dependencies:
+ inherits "^2.0.1"
+ readable-stream "^2.0.0"
+
fs-extra@^7.0.0:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
jsonfile "^4.0.0"
universalify "^0.1.0"
-fs.realpath@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
- integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+fs-minipass@^1.2.5:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
+ integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==
+ dependencies:
+ minipass "^2.6.0"
+
+fs-vacuum@^1.2.10, fs-vacuum@~1.2.10:
+ version "1.2.10"
+ resolved "https://registry.yarnpkg.com/fs-vacuum/-/fs-vacuum-1.2.10.tgz#b7629bec07a4031a2548fdf99f5ecf1cc8b31e36"
+ integrity sha1-t2Kb7AekAxolSP35n17PHMizHjY=
+ dependencies:
+ graceful-fs "^4.1.2"
+ path-is-inside "^1.0.1"
+ rimraf "^2.5.2"
+
+fs-write-stream-atomic@^1.0.8, fs-write-stream-atomic@~1.0.10:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
+ integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=
+ dependencies:
+ graceful-fs "^4.1.2"
+ iferr "^0.1.5"
+ imurmurhash "^0.1.4"
+ readable-stream "1 || 2"
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
fsevents@^1.0.0:
- version "1.2.11"
- resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.11.tgz#67bf57f4758f02ede88fb2a1712fef4d15358be3"
- integrity sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==
+ version "1.2.13"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38"
+ integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==
dependencies:
bindings "^1.5.0"
nan "^2.12.1"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
+fstream@^1.0.0, fstream@^1.0.12:
+ version "1.0.12"
+ resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045"
+ integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==
+ dependencies:
+ graceful-fs "^4.1.2"
+ inherits "~2.0.0"
+ mkdirp ">=0.5 0"
+ rimraf "2"
+
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.6.1.tgz#7de85fdd6e1b3377c23ce010892656385fd9b10c"
integrity sha512-hT9yh/tiinkmirKrlv4KWOjztdoZo1mx9Qh4KvWqC7isoXwdUY3PNWUxceF4/qO9R6riA2C29jdTOeQOIROjgw==
+gauge@~2.7.3:
+ version "2.7.4"
+ resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
+ integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=
+ dependencies:
+ aproba "^1.0.3"
+ console-control-strings "^1.0.0"
+ has-unicode "^2.0.0"
+ object-assign "^4.1.0"
+ signal-exit "^3.0.0"
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+ wide-align "^1.1.0"
+
+genfun@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/genfun/-/genfun-5.0.0.tgz#9dd9710a06900a5c4a5bf57aca5da4e52fe76537"
+ integrity sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==
+
gensync@^1.0.0-beta.1:
version "1.0.0-beta.1"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269"
integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==
+gentle-fs@^2.0.1, gentle-fs@^2.3.0:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/gentle-fs/-/gentle-fs-2.3.1.tgz#11201bf66c18f930ddca72cf69460bdfa05727b1"
+ integrity sha512-OlwBBwqCFPcjm33rF2BjW+Pr6/ll2741l+xooiwTCeaX2CA1ZuclavyMBe0/KlR21/XGsgY6hzEQZ15BdNa13Q==
+ dependencies:
+ aproba "^1.1.2"
+ chownr "^1.1.2"
+ cmd-shim "^3.0.3"
+ fs-vacuum "^1.2.10"
+ graceful-fs "^4.1.11"
+ iferr "^0.1.5"
+ infer-owner "^1.0.4"
+ mkdirp "^0.5.1"
+ path-is-inside "^1.0.2"
+ read-cmd-shim "^1.0.1"
+ slide "^1.1.6"
+
+get-caller-file@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
+ integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
+
get-caller-file@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==
+get-package-type@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
+ integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
+
get-stdin@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
+get-stream@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
+ integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
+
get-stream@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
is-glob "^2.0.0"
glob-parent@^5.0.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2"
- integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
+ integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
dependencies:
is-glob "^4.0.1"
-glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
+glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.2:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
once "^1.3.0"
path-is-absolute "^1.0.0"
+global-dirs@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445"
+ integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=
+ dependencies:
+ ini "^1.3.4"
+
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
globals@^12.1.0:
- version "12.3.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-12.3.0.tgz#1e564ee5c4dded2ab098b0f88f24702a3c56be13"
- integrity sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==
+ version "12.4.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8"
+ integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==
dependencies:
type-fest "^0.8.1"
-graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.3:
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
- integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
+got@^6.7.1:
+ version "6.7.1"
+ resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
+ integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=
+ dependencies:
+ create-error-class "^3.0.0"
+ duplexer3 "^0.1.4"
+ get-stream "^3.0.0"
+ is-redirect "^1.0.0"
+ is-retry-allowed "^1.0.0"
+ is-stream "^1.0.0"
+ lowercase-keys "^1.0.0"
+ safe-buffer "^5.0.1"
+ timed-out "^4.0.0"
+ unzip-response "^2.0.1"
+ url-parse-lax "^1.0.0"
+
+graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4:
+ version "4.2.4"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
+ integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
+
+graceful-fs@~4.1.11:
+ version "4.1.15"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
+ integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
growly@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
-har-validator@~5.1.0, har-validator@~5.1.3:
+har-validator@~5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
ajv "^6.5.5"
har-schema "^2.0.0"
-has-ansi@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
- integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=
- dependencies:
- ansi-regex "^2.0.0"
-
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
+has-unicode@^2.0.0, has-unicode@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
+ integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
+
has-value@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
resolved "https://registry.yarnpkg.com/hoist-non-inferno-statics/-/hoist-non-inferno-statics-1.1.3.tgz#7d870f4160bfb6a59269b45c343c027f0e30ab35"
integrity sha1-fYcPQWC/tqWSabRcNDwCfw4wqzU=
-hosted-git-info@^2.1.4:
- version "2.8.5"
- resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c"
- integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==
+hosted-git-info@^2.1.4, hosted-git-info@^2.6.0, hosted-git-info@^2.7.1:
+ version "2.8.8"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
+ integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
-html-encoding-sniffer@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8"
- integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==
+html-encoding-sniffer@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"
+ integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==
dependencies:
- whatwg-encoding "^1.0.1"
+ whatwg-encoding "^1.0.5"
html-escaper@^2.0.0:
version "2.0.2"
dependencies:
void-elements "^2.0.1"
+http-cache-semantics@^3.8.0, http-cache-semantics@^3.8.1:
+ version "3.8.1"
+ resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
+ integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==
+
http-errors@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
+http-proxy-agent@^2.0.0, http-proxy-agent@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
+ integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
+ dependencies:
+ agent-base "4"
+ debug "3.1.0"
+
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
jsprim "^1.2.2"
sshpk "^1.7.0"
+https-proxy-agent@^2.1.0, https-proxy-agent@^2.2.0, https-proxy-agent@^2.2.1:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
+ integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
+ dependencies:
+ agent-base "^4.3.0"
+ debug "^3.1.0"
+
human-signals@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
+humanize-ms@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
+ integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=
+ dependencies:
+ ms "^2.0.0"
+
husky@^4.2.5:
version "4.2.5"
resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36"
which-pm-runs "^1.0.0"
i18next@^19.4.1:
- version "19.4.1"
- resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.4.1.tgz#4929d15d3d01e4712350a368d005cefa50ff5455"
- integrity sha512-dC3ue15jkLebN2je4xEjfjVYd/fSAo+UVK9f+JxvceCJRowkI+S0lGohgKejqU+FYLfvw9IAPylIIEWwR8Djrg==
+ version "19.6.3"
+ resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.6.3.tgz#ce2346161b35c4c5ab691b0674119c7b349c0817"
+ integrity sha512-eYr98kw/C5z6kY21ti745p4IvbOJwY8F2T9tf/Lvy5lFnYRqE45+bppSgMPmcZZqYNT+xO0N0x6rexVR2wtZZQ==
dependencies:
- "@babel/runtime" "^7.3.1"
+ "@babel/runtime" "^7.10.1"
-iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.24:
+iconv-lite@0.4.24, iconv-lite@^0.4.17:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
+iconv-lite@^0.6.2:
+ version "0.6.2"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01"
+ integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3.0.0"
+
ieee754@^1.1.8:
version "1.1.13"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
+iferr@^0.1.5, iferr@~0.1.5:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
+ integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
+
+ignore-walk@^3.0.1:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37"
+ integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==
+ dependencies:
+ minimatch "^3.0.4"
+
ignore@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
ignore@^5.1.1:
- version "5.1.4"
- resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf"
- integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==
+ version "5.1.8"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
+ integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
import-fresh@^3.0.0, import-fresh@^3.1.0:
version "3.2.1"
parent-module "^1.0.0"
resolve-from "^4.0.0"
+import-lazy@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
+ integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=
+
import-local@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
-indent-string@^3.0.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
- integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=
+indent-string@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
+ integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
+
+infer-owner@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
+ integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==
+
+inferno-clone-vnode@^7.4.2:
+ version "7.4.2"
+ resolved "https://registry.yarnpkg.com/inferno-clone-vnode/-/inferno-clone-vnode-7.4.2.tgz#071577098fd8ffdffd41cf81819207effa520bc1"
+ integrity sha512-pX5agEWfU+w6vYaVyKtzgFT4jlMK+eOEKL/LkyWu2dtOy0DXx174AFm6GSgOt66W6xmxn58sbWI7ngsWmp4f2w==
+ dependencies:
+ inferno "7.4.2"
-inferno-clone-vnode@^7.1.12:
- version "7.4.0"
- resolved "https://registry.yarnpkg.com/inferno-clone-vnode/-/inferno-clone-vnode-7.4.0.tgz#44a930ef0881f79d425c1c7f4bbd206513da905a"
- integrity sha512-rPp4tMhWZB1H2kx0MqgyPPBP4bWIXwkH+E/eNSWWtXLR5mKDGz19cguiBkR+U1uXQCi4/AkWvOVHxLQCfT/5Zw==
+inferno-create-element@^7.4.2:
+ version "7.4.2"
+ resolved "https://registry.yarnpkg.com/inferno-create-element/-/inferno-create-element-7.4.2.tgz#d3ac6c64c792f8d3c4784279825418ebd73cfe1c"
+ integrity sha512-FHNca1NR/9SRtaifr4DdAuA6dKBeWkCrYVVkTazMadJyjMtpPN3d+sKlkmdvorAjhUvdMMNcRQStrgRTpLcZyg==
dependencies:
- inferno "7.4.0"
+ inferno "7.4.2"
-inferno-create-element@^7.1.12:
- version "7.4.0"
- resolved "https://registry.yarnpkg.com/inferno-create-element/-/inferno-create-element-7.4.0.tgz#b431f293cdb8931f7f3604e0774500b66d6fe5c8"
- integrity sha512-gxwU899obmELIxfhWzyHBIGbxOXUPfB1SzW+K3XGU0exWKCVIJwSpBOGpJY5tlKf4lyg1UrCmfz2JZS1i2U2vg==
+inferno-helmet@^5.2.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/inferno-helmet/-/inferno-helmet-5.2.1.tgz#3717f325760aa14abeae82a78af7213f9055a3dc"
+ integrity sha512-9xzUGENVoz8qk67s0UhHlGNGZKG9Ia0mk5KoCNgkkIcGNhk7mNIINm7jJ5OOigVetz2DwI94jHzouTggb49AJg==
dependencies:
- inferno "7.4.0"
+ deep-equal "^1.0.1"
+ inferno-side-effect "^1.1.5"
+ object-assign "^4.1.1"
-inferno-i18next@nimbusec-oss/inferno-i18next:
- version "7.1.12"
- resolved "https://codeload.github.com/nimbusec-oss/inferno-i18next/tar.gz/f8c1403e60be70141c558e36f12f22c106cb7463"
+"inferno-i18next@github:nimbusec-oss/inferno-i18next#semver:^7.4.2":
+ version "7.4.2"
+ resolved "https://codeload.github.com/nimbusec-oss/inferno-i18next/tar.gz/54b9be591ccd62c53799ad23e35f17144a62f909"
dependencies:
html-parse-stringify2 "^2.0.1"
- inferno "^7.1.12"
- inferno-clone-vnode "^7.1.12"
- inferno-create-element "^7.1.12"
- inferno-shared "^7.1.12"
- inferno-vnode-flags "^7.1.12"
+ inferno "^7.4.2"
+ inferno-clone-vnode "^7.4.2"
+ inferno-create-element "^7.4.2"
+ inferno-shared "^7.4.2"
+ inferno-vnode-flags "^7.4.2"
inferno-router@^7.4.2:
version "7.4.2"
inferno "7.4.2"
path-to-regexp-es6 "1.7.0"
-inferno-shared@7.4.0, inferno-shared@^7.1.12:
- version "7.4.0"
- resolved "https://registry.yarnpkg.com/inferno-shared/-/inferno-shared-7.4.0.tgz#4491deb75348019939b160cd5655196afa13ced0"
- integrity sha512-6aa1fC/e4SP2lOLNg4ZS5Zz2SC+DnM7WxQbggmHhLSyOqZrsPrpZSlX25LbjR9lkhMrq6cmki3yInYFGuDzlRg==
-
-inferno-shared@7.4.2:
+inferno-shared@7.4.2, inferno-shared@^7.4.2:
version "7.4.2"
resolved "https://registry.yarnpkg.com/inferno-shared/-/inferno-shared-7.4.2.tgz#400cf6d19a077af9e5e852e1f189726391efa273"
integrity sha512-SULfgzj/PuyMd3rHThEXkeEdZorjcr/q+kaqbwr7C2XhIPCk4e5jcRKZujI6YCSahGA9WFwP2Mft1v5PsaaNeg==
-inferno-vnode-flags@7.4.0, inferno-vnode-flags@^7.1.12:
- version "7.4.0"
- resolved "https://registry.yarnpkg.com/inferno-vnode-flags/-/inferno-vnode-flags-7.4.0.tgz#5c049a73f3ff84a51458b06d279d6b18d09acdf0"
- integrity sha512-TMPrvAxR2uUVSowLKnGgH34eWXErIYCdJ4d5hj8cSc8ta8knN6dj0z47UIw13qvmWfNjHgwm0C2/cm+G6fckiA==
+inferno-side-effect@^1.1.5:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/inferno-side-effect/-/inferno-side-effect-1.1.5.tgz#a874c80dbc73602aafc1e0f3f3f1ec216a916271"
+ integrity sha512-Q2O2qExGjBTPRfwM1suQPBN5FBHhANccCcEvz/3Dr7VcMFelqxnE0qjnlXVQ248S409nA6VtpiBwT7xBz4WyqA==
+ dependencies:
+ exenv "^1.2.1"
+ npm "^5.8.0"
+ shallowequal "^1.0.1"
-inferno-vnode-flags@7.4.2:
+inferno-vnode-flags@7.4.2, inferno-vnode-flags@^7.4.2:
version "7.4.2"
resolved "https://registry.yarnpkg.com/inferno-vnode-flags/-/inferno-vnode-flags-7.4.2.tgz#54982dabe34f308853ba17de7de4241e23769135"
integrity sha512-sV4KqqvZH4MW9/dNbC9blHInnpQSqMWouU5VlanbJ+NhJ8ufPwsDy0/+jiA2aODpg2HFHwVMJFF1fsewPqNtLQ==
-inferno@7.4.0, inferno@^7.1.12:
- version "7.4.0"
- resolved "https://registry.yarnpkg.com/inferno/-/inferno-7.4.0.tgz#8d3dc03562c6851043a1a467fd509f222e9dbf85"
- integrity sha512-oEXx5iQmGXOvAPj1TZyCo6ndOc4qPg9zBLigMpkApAiV1SM/bri0M1eA/kD3e9jptcof9TwLBJD9bL6E6tq2tg==
- dependencies:
- inferno-shared "7.4.0"
- inferno-vnode-flags "7.4.0"
- opencollective-postinstall "^2.0.2"
-
inferno@7.4.2, inferno@^7.4.2:
version "7.4.2"
resolved "https://registry.yarnpkg.com/inferno/-/inferno-7.4.2.tgz#833cc423ee7b939fad705c59ea41924f31c92453"
inferno-vnode-flags "7.4.2"
opencollective-postinstall "^2.0.2"
-inflight@^1.0.4:
+inflight@^1.0.4, inflight@~1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
+ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
+ integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
+
+init-package-json@^1.10.3:
+ version "1.10.3"
+ resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-1.10.3.tgz#45ffe2f610a8ca134f2bd1db5637b235070f6cbe"
+ integrity sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==
+ dependencies:
+ glob "^7.1.1"
+ npm-package-arg "^4.0.0 || ^5.0.0 || ^6.0.0"
+ promzard "^0.3.0"
+ read "~1.0.1"
+ read-package-json "1 || 2"
+ semver "2.x || 3.x || 4 || 5"
+ validate-npm-package-license "^3.0.1"
+ validate-npm-package-name "^3.0.0"
+
inquirer@^3.0.6:
version "3.3.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9"
strip-ansi "^4.0.0"
through "^2.3.6"
-inquirer@^7.0.0:
- version "7.0.4"
- resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.4.tgz#99af5bde47153abca23f5c7fc30db247f39da703"
- integrity sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==
- dependencies:
- ansi-escapes "^4.2.1"
- chalk "^2.4.2"
- cli-cursor "^3.1.0"
- cli-width "^2.0.0"
- external-editor "^3.0.3"
- figures "^3.0.0"
- lodash "^4.17.15"
- mute-stream "0.0.8"
- run-async "^2.2.0"
- rxjs "^6.5.3"
- string-width "^4.1.0"
- strip-ansi "^5.1.0"
- through "^2.3.6"
-
internal-slot@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.2.tgz#9c2e9fb3cd8e5e4256c6f45fe310067fcfa378a3"
has "^1.0.3"
side-channel "^1.0.2"
+invert-kv@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
+ integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY=
+
ip-regex@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=
-ipaddr.js@1.9.0:
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
- integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
+ip@1.1.5, ip@^1.1.4:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
+ integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
+
+ipaddr.js@1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
+ integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
is-accessor-descriptor@^0.1.6:
version "0.1.6"
dependencies:
kind-of "^6.0.0"
+is-arguments@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3"
+ integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==
+
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
-is-callable@^1.1.4, is-callable@^1.1.5:
- version "1.1.5"
- resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab"
- integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==
+is-builtin-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
+ integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74=
+ dependencies:
+ builtin-modules "^1.0.0"
+
+is-callable@^1.1.4, is-callable@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb"
+ integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==
+
+is-ci@^1.0.10:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c"
+ integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==
+ dependencies:
+ ci-info "^1.5.0"
is-ci@^2.0.0:
version "2.0.0"
dependencies:
ci-info "^2.0.0"
+is-cidr@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-cidr/-/is-cidr-1.0.0.tgz#fb5aacf659255310359da32cae03e40c6a1c2afc"
+ integrity sha1-+1qs9lklUxA1naMsrgPkDGocKvw=
+ dependencies:
+ cidr-regex "1.0.6"
+
is-data-descriptor@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
is-data-descriptor "^1.0.0"
kind-of "^6.0.2"
+is-docker@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.0.0.tgz#2cb0df0e75e2d064fe1864c37cdeacb7b2dcf25b"
+ integrity sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==
+
is-dotfile@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
dependencies:
is-extglob "^2.1.1"
+is-installed-globally@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80"
+ integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=
+ dependencies:
+ global-dirs "^0.1.0"
+ is-path-inside "^1.0.0"
+
+is-npm@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4"
+ integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ=
+
is-number@^2.0.2, is-number@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
-is-obj@^1.0.1:
+is-obj@^1.0.0, is-obj@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
-is-observable@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-1.1.0.tgz#b3e986c8f44de950867cab5403f5a3465005975e"
- integrity sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==
+is-path-inside@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
+ integrity sha1-jvW33lBDej/cprToZe96pVy0gDY=
dependencies:
- symbol-observable "^1.1.0"
+ path-is-inside "^1.0.1"
is-plain-object@^2.0.3, is-plain-object@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=
+is-potential-custom-element-name@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397"
+ integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c=
+
is-primitive@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU=
-is-promise@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
- integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=
+is-redirect@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
+ integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=
-is-regex@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae"
- integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==
+is-regex@^1.0.4, is-regex@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff"
+ integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==
dependencies:
- has "^1.0.3"
+ has-symbols "^1.0.1"
is-regexp@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk=
-is-stream@^1.1.0:
+is-retry-allowed@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4"
+ integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==
+
+is-stream@^1.0.0, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
-is-wsl@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d"
- integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==
+is-wsl@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
+ integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
+ dependencies:
+ is-docker "^2.0.0"
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec"
integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==
-istanbul-lib-instrument@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz#61f13ac2c96cfefb076fe7131156cc05907874e6"
- integrity sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg==
+istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d"
+ integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==
dependencies:
"@babel/core" "^7.7.5"
- "@babel/parser" "^7.7.5"
- "@babel/template" "^7.7.4"
- "@babel/traverse" "^7.7.4"
"@istanbuljs/schema" "^0.1.2"
istanbul-lib-coverage "^3.0.0"
semver "^6.3.0"
html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0"
-jest-changed-files@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-25.4.0.tgz#e573db32c2fd47d2b90357ea2eda0622c5c5cbd6"
- integrity sha512-VR/rfJsEs4BVMkwOTuStRyS630fidFVekdw/lBaBQjx9KK3VZFOZ2c0fsom2fRp8pMCrCTP6LGna00o/DXGlqA==
+jest-changed-files@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.1.0.tgz#de66b0f30453bca2aff98e9400f75905da495305"
+ integrity sha512-HS5MIJp3B8t0NRKGMCZkcDUZo36mVRvrDETl81aqljT1S9tqiHRSpyoOvWg9ZilzZG9TDisDNaN1IXm54fLRZw==
dependencies:
- "@jest/types" "^25.4.0"
- execa "^3.2.0"
+ "@jest/types" "^26.1.0"
+ execa "^4.0.0"
throat "^5.0.0"
-jest-cli@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-25.4.0.tgz#5dac8be0fece6ce39f0d671395a61d1357322bab"
- integrity sha512-usyrj1lzCJZMRN1r3QEdnn8e6E6yCx/QN7+B1sLoA68V7f3WlsxSSQfy0+BAwRiF4Hz2eHauf11GZG3PIfWTXQ==
+jest-cli@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.1.0.tgz#eb9ec8a18cf3b6aa556d9deaa9e24be12b43ad87"
+ integrity sha512-Imumvjgi3rU7stq6SJ1JUEMaV5aAgJYXIs0jPqdUnF47N/Tk83EXfmtvNKQ+SnFVI6t6mDOvfM3aA9Sg6kQPSw==
dependencies:
- "@jest/core" "^25.4.0"
- "@jest/test-result" "^25.4.0"
- "@jest/types" "^25.4.0"
- chalk "^3.0.0"
+ "@jest/core" "^26.1.0"
+ "@jest/test-result" "^26.1.0"
+ "@jest/types" "^26.1.0"
+ chalk "^4.0.0"
exit "^0.1.2"
+ graceful-fs "^4.2.4"
import-local "^3.0.2"
is-ci "^2.0.0"
- jest-config "^25.4.0"
- jest-util "^25.4.0"
- jest-validate "^25.4.0"
+ jest-config "^26.1.0"
+ jest-util "^26.1.0"
+ jest-validate "^26.1.0"
prompts "^2.0.1"
- realpath-native "^2.0.0"
yargs "^15.3.1"
-jest-config@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-25.4.0.tgz#56e5df3679a96ff132114b44fb147389c8c0a774"
- integrity sha512-egT9aKYxMyMSQV1aqTgam0SkI5/I2P9qrKexN5r2uuM2+68ypnc+zPGmfUxK7p1UhE7dYH9SLBS7yb+TtmT1AA==
+jest-config@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.1.0.tgz#9074f7539acc185e0113ad6d22ed589c16a37a73"
+ integrity sha512-ONTGeoMbAwGCdq4WuKkMcdMoyfs5CLzHEkzFOlVvcDXufZSaIWh/OXMLa2fwKXiOaFcqEw8qFr4VOKJQfn4CVw==
dependencies:
"@babel/core" "^7.1.0"
- "@jest/test-sequencer" "^25.4.0"
- "@jest/types" "^25.4.0"
- babel-jest "^25.4.0"
- chalk "^3.0.0"
+ "@jest/test-sequencer" "^26.1.0"
+ "@jest/types" "^26.1.0"
+ babel-jest "^26.1.0"
+ chalk "^4.0.0"
deepmerge "^4.2.2"
glob "^7.1.1"
- jest-environment-jsdom "^25.4.0"
- jest-environment-node "^25.4.0"
- jest-get-type "^25.2.6"
- jest-jasmine2 "^25.4.0"
- jest-regex-util "^25.2.6"
- jest-resolve "^25.4.0"
- jest-util "^25.4.0"
- jest-validate "^25.4.0"
+ graceful-fs "^4.2.4"
+ jest-environment-jsdom "^26.1.0"
+ jest-environment-node "^26.1.0"
+ jest-get-type "^26.0.0"
+ jest-jasmine2 "^26.1.0"
+ jest-regex-util "^26.0.0"
+ jest-resolve "^26.1.0"
+ jest-util "^26.1.0"
+ jest-validate "^26.1.0"
micromatch "^4.0.2"
- pretty-format "^25.4.0"
- realpath-native "^2.0.0"
+ pretty-format "^26.1.0"
-jest-diff@^25.2.1, jest-diff@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.4.0.tgz#260b70f19a46c283adcad7f081cae71eb784a634"
- integrity sha512-kklLbJVXW0y8UKOWOdYhI6TH5MG6QAxrWiBMgQaPIuhj3dNFGirKCd+/xfplBXICQ7fI+3QcqHm9p9lWu1N6ug==
+jest-diff@^25.2.1:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9"
+ integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==
dependencies:
chalk "^3.0.0"
diff-sequences "^25.2.6"
jest-get-type "^25.2.6"
- pretty-format "^25.4.0"
+ pretty-format "^25.5.0"
+
+jest-diff@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.1.0.tgz#00a549bdc936c9691eb4dc25d1fbd78bf456abb2"
+ integrity sha512-GZpIcom339y0OXznsEKjtkfKxNdg7bVbEofK8Q6MnevTIiR1jNhDWKhRX6X0SDXJlwn3dy59nZ1z55fLkAqPWg==
+ dependencies:
+ chalk "^4.0.0"
+ diff-sequences "^26.0.0"
+ jest-get-type "^26.0.0"
+ pretty-format "^26.1.0"
-jest-docblock@^25.3.0:
- version "25.3.0"
- resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-25.3.0.tgz#8b777a27e3477cd77a168c05290c471a575623ef"
- integrity sha512-aktF0kCar8+zxRHxQZwxMy70stc9R1mOmrLsT5VO3pIT0uzGRSDAXxSlz4NqQWpuLjPpuMhPRl7H+5FRsvIQAg==
+jest-docblock@^26.0.0:
+ version "26.0.0"
+ resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5"
+ integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==
dependencies:
detect-newline "^3.0.0"
-jest-each@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-25.4.0.tgz#ad4e46164764e8e77058f169a0076a7f86f6b7d4"
- integrity sha512-lwRIJ8/vQU/6vq3nnSSUw1Y3nz5tkYSFIywGCZpUBd6WcRgpn8NmJoQICojbpZmsJOJNHm0BKdyuJ6Xdx+eDQQ==
+jest-each@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.1.0.tgz#e35449875009a22d74d1bda183b306db20f286f7"
+ integrity sha512-lYiSo4Igr81q6QRsVQq9LIkJW0hZcKxkIkHzNeTMPENYYDw/W/Raq28iJ0sLlNFYz2qxxeLnc5K2gQoFYlu2bA==
dependencies:
- "@jest/types" "^25.4.0"
- chalk "^3.0.0"
- jest-get-type "^25.2.6"
- jest-util "^25.4.0"
- pretty-format "^25.4.0"
-
-jest-environment-jsdom@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-25.4.0.tgz#bbfc7f85bb6ade99089062a830c79cb454565cf0"
- integrity sha512-KTitVGMDrn2+pt7aZ8/yUTuS333w3pWt1Mf88vMntw7ZSBNDkRS6/4XLbFpWXYfWfp1FjcjQTOKzbK20oIehWQ==
- dependencies:
- "@jest/environment" "^25.4.0"
- "@jest/fake-timers" "^25.4.0"
- "@jest/types" "^25.4.0"
- jest-mock "^25.4.0"
- jest-util "^25.4.0"
- jsdom "^15.2.1"
-
-jest-environment-node@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-25.4.0.tgz#188aef01ae6418e001c03fdd1c299961e1439082"
- integrity sha512-wryZ18vsxEAKFH7Z74zi/y/SyI1j6UkVZ6QsllBuT/bWlahNfQjLNwFsgh/5u7O957dYFoXj4yfma4n4X6kU9A==
- dependencies:
- "@jest/environment" "^25.4.0"
- "@jest/fake-timers" "^25.4.0"
- "@jest/types" "^25.4.0"
- jest-mock "^25.4.0"
- jest-util "^25.4.0"
- semver "^6.3.0"
+ "@jest/types" "^26.1.0"
+ chalk "^4.0.0"
+ jest-get-type "^26.0.0"
+ jest-util "^26.1.0"
+ pretty-format "^26.1.0"
+
+jest-environment-jsdom@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.1.0.tgz#9dc7313ffe1b59761dad1fedb76e2503e5d37c5b"
+ integrity sha512-dWfiJ+spunVAwzXbdVqPH1LbuJW/kDL+FyqgA5YzquisHqTi0g9hquKif9xKm7c1bKBj6wbmJuDkeMCnxZEpUw==
+ dependencies:
+ "@jest/environment" "^26.1.0"
+ "@jest/fake-timers" "^26.1.0"
+ "@jest/types" "^26.1.0"
+ jest-mock "^26.1.0"
+ jest-util "^26.1.0"
+ jsdom "^16.2.2"
+
+jest-environment-node@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.1.0.tgz#8bb387b3eefb132eab7826f9a808e4e05618960b"
+ integrity sha512-DNm5x1aQH0iRAe9UYAkZenuzuJ69VKzDCAYISFHQ5i9e+2Tbeu2ONGY7YStubCLH8a1wdKBgqScYw85+ySxqxg==
+ dependencies:
+ "@jest/environment" "^26.1.0"
+ "@jest/fake-timers" "^26.1.0"
+ "@jest/types" "^26.1.0"
+ jest-mock "^26.1.0"
+ jest-util "^26.1.0"
jest-get-type@^25.2.6:
version "25.2.6"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877"
integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==
-jest-haste-map@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-25.4.0.tgz#da7c309dd7071e0a80c953ba10a0ec397efb1ae2"
- integrity sha512-5EoCe1gXfGC7jmXbKzqxESrgRcaO3SzWXGCnvp9BcT0CFMyrB1Q6LIsjl9RmvmJGQgW297TCfrdgiy574Rl9HQ==
+jest-get-type@^26.0.0:
+ version "26.0.0"
+ resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.0.0.tgz#381e986a718998dbfafcd5ec05934be538db4039"
+ integrity sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg==
+
+jest-haste-map@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.1.0.tgz#ef31209be73f09b0d9445e7d213e1b53d0d1476a"
+ integrity sha512-WeBS54xCIz9twzkEdm6+vJBXgRBQfdbbXD0dk8lJh7gLihopABlJmIQFdWSDDtuDe4PRiObsjZSUjbJ1uhWEpA==
dependencies:
- "@jest/types" "^25.4.0"
+ "@jest/types" "^26.1.0"
+ "@types/graceful-fs" "^4.1.2"
anymatch "^3.0.3"
fb-watchman "^2.0.0"
- graceful-fs "^4.2.3"
- jest-serializer "^25.2.6"
- jest-util "^25.4.0"
- jest-worker "^25.4.0"
+ graceful-fs "^4.2.4"
+ jest-serializer "^26.1.0"
+ jest-util "^26.1.0"
+ jest-worker "^26.1.0"
micromatch "^4.0.2"
sane "^4.0.3"
walker "^1.0.7"
optionalDependencies:
fsevents "^2.1.2"
-jest-jasmine2@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-25.4.0.tgz#3d3d19514022e2326e836c2b66d68b4cb63c5861"
- integrity sha512-QccxnozujVKYNEhMQ1vREiz859fPN/XklOzfQjm2j9IGytAkUbSwjFRBtQbHaNZ88cItMpw02JnHGsIdfdpwxQ==
+jest-jasmine2@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.1.0.tgz#4dfe349b2b2d3c6b3a27c024fd4cb57ac0ed4b6f"
+ integrity sha512-1IPtoDKOAG+MeBrKvvuxxGPJb35MTTRSDglNdWWCndCB3TIVzbLThRBkwH9P081vXLgiJHZY8Bz3yzFS803xqQ==
dependencies:
"@babel/traverse" "^7.1.0"
- "@jest/environment" "^25.4.0"
- "@jest/source-map" "^25.2.6"
- "@jest/test-result" "^25.4.0"
- "@jest/types" "^25.4.0"
- chalk "^3.0.0"
+ "@jest/environment" "^26.1.0"
+ "@jest/source-map" "^26.1.0"
+ "@jest/test-result" "^26.1.0"
+ "@jest/types" "^26.1.0"
+ chalk "^4.0.0"
co "^4.6.0"
- expect "^25.4.0"
+ expect "^26.1.0"
is-generator-fn "^2.0.0"
- jest-each "^25.4.0"
- jest-matcher-utils "^25.4.0"
- jest-message-util "^25.4.0"
- jest-runtime "^25.4.0"
- jest-snapshot "^25.4.0"
- jest-util "^25.4.0"
- pretty-format "^25.4.0"
+ jest-each "^26.1.0"
+ jest-matcher-utils "^26.1.0"
+ jest-message-util "^26.1.0"
+ jest-runtime "^26.1.0"
+ jest-snapshot "^26.1.0"
+ jest-util "^26.1.0"
+ pretty-format "^26.1.0"
throat "^5.0.0"
-jest-leak-detector@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-25.4.0.tgz#cf94a160c78e53d810e7b2f40b5fd7ee263375b3"
- integrity sha512-7Y6Bqfv2xWsB+7w44dvZuLs5SQ//fzhETgOGG7Gq3TTGFdYvAgXGwV8z159RFZ6fXiCPm/szQ90CyfVos9JIFQ==
+jest-leak-detector@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.1.0.tgz#039c3a07ebcd8adfa984b6ac015752c35792e0a6"
+ integrity sha512-dsMnKF+4BVOZwvQDlgn3MG+Ns4JuLv8jNvXH56bgqrrboyCbI1rQg6EI5rs+8IYagVcfVP2yZFKfWNZy0rK0Hw==
dependencies:
- jest-get-type "^25.2.6"
- pretty-format "^25.4.0"
+ jest-get-type "^26.0.0"
+ pretty-format "^26.1.0"
-jest-matcher-utils@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.4.0.tgz#dc3e7aec402a1e567ed80b572b9ad285878895e6"
- integrity sha512-yPMdtj7YDgXhnGbc66bowk8AkQ0YwClbbwk3Kzhn5GVDrciiCr27U4NJRbrqXbTdtxjImONITg2LiRIw650k5A==
+jest-matcher-utils@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.1.0.tgz#cf75a41bd413dda784f022de5a65a2a5c73a5c92"
+ integrity sha512-PW9JtItbYvES/xLn5mYxjMd+Rk+/kIt88EfH3N7w9KeOrHWaHrdYPnVHndGbsFGRJ2d5gKtwggCvkqbFDoouQA==
dependencies:
- chalk "^3.0.0"
- jest-diff "^25.4.0"
- jest-get-type "^25.2.6"
- pretty-format "^25.4.0"
+ chalk "^4.0.0"
+ jest-diff "^26.1.0"
+ jest-get-type "^26.0.0"
+ pretty-format "^26.1.0"
-jest-message-util@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.4.0.tgz#2899e8bc43f5317acf8dfdfe89ea237d354fcdab"
- integrity sha512-LYY9hRcVGgMeMwmdfh9tTjeux1OjZHMusq/E5f3tJN+dAoVVkJtq5ZUEPIcB7bpxDUt2zjUsrwg0EGgPQ+OhXQ==
+jest-message-util@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.1.0.tgz#52573fbb8f5cea443c4d1747804d7a238a3e233c"
+ integrity sha512-dY0+UlldiAJwNDJ08SF0HdF32g9PkbF2NRK/+2iMPU40O6q+iSn1lgog/u0UH8ksWoPv0+gNq8cjhYO2MFtT0g==
dependencies:
"@babel/code-frame" "^7.0.0"
- "@jest/types" "^25.4.0"
+ "@jest/types" "^26.1.0"
"@types/stack-utils" "^1.0.1"
- chalk "^3.0.0"
+ chalk "^4.0.0"
+ graceful-fs "^4.2.4"
micromatch "^4.0.2"
slash "^3.0.0"
- stack-utils "^1.0.1"
+ stack-utils "^2.0.2"
-jest-mock@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.4.0.tgz#ded7d64b5328d81d78d2138c825d3a45e30ec8ca"
- integrity sha512-MdazSfcYAUjJjuVTTnusLPzE0pE4VXpOUzWdj8sbM+q6abUjm3bATVPXFqTXrxSieR8ocpvQ9v/QaQCftioQFg==
+jest-mock@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.1.0.tgz#80d8286da1f05a345fbad1bfd6fa49a899465d3d"
+ integrity sha512-1Rm8EIJ3ZFA8yCIie92UbxZWj9SuVmUGcyhLHyAhY6WI3NIct38nVcfOPWhJteqSn8V8e3xOMha9Ojfazfpovw==
dependencies:
- "@jest/types" "^25.4.0"
+ "@jest/types" "^26.1.0"
jest-pnp-resolver@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a"
- integrity sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==
-
-jest-regex-util@^25.2.6:
- version "25.2.6"
- resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-25.2.6.tgz#d847d38ba15d2118d3b06390056028d0f2fd3964"
- integrity sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw==
-
-jest-resolve-dependencies@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-25.4.0.tgz#783937544cfc40afcc7c569aa54748c4b3f83f5a"
- integrity sha512-A0eoZXx6kLiuG1Ui7wITQPl04HwjLErKIJTt8GR3c7UoDAtzW84JtCrgrJ6Tkw6c6MwHEyAaLk7dEPml5pf48A==
- dependencies:
- "@jest/types" "^25.4.0"
- jest-regex-util "^25.2.6"
- jest-snapshot "^25.4.0"
-
-jest-resolve@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-25.4.0.tgz#6f4540ce0d419c4c720e791e871da32ba4da7a60"
- integrity sha512-wOsKqVDFWUiv8BtLMCC6uAJ/pHZkfFgoBTgPtmYlsprAjkxrr2U++ZnB3l5ykBMd2O24lXvf30SMAjJIW6k2aA==
- dependencies:
- "@jest/types" "^25.4.0"
- browser-resolve "^1.11.3"
- chalk "^3.0.0"
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c"
+ integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==
+
+jest-regex-util@^26.0.0:
+ version "26.0.0"
+ resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28"
+ integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==
+
+jest-resolve-dependencies@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.1.0.tgz#1ce36472f864a5dadf7dc82fa158e1c77955691b"
+ integrity sha512-fQVEPHHQ1JjHRDxzlLU/buuQ9om+hqW6Vo928aa4b4yvq4ZHBtRSDsLdKQLuCqn5CkTVpYZ7ARh2fbA8WkRE6g==
+ dependencies:
+ "@jest/types" "^26.1.0"
+ jest-regex-util "^26.0.0"
+ jest-snapshot "^26.1.0"
+
+jest-resolve@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.1.0.tgz#a530eaa302b1f6fa0479079d1561dd69abc00e68"
+ integrity sha512-KsY1JV9FeVgEmwIISbZZN83RNGJ1CC+XUCikf/ZWJBX/tO4a4NvA21YixokhdR9UnmPKKAC4LafVixJBrwlmfg==
+ dependencies:
+ "@jest/types" "^26.1.0"
+ chalk "^4.0.0"
+ graceful-fs "^4.2.4"
jest-pnp-resolver "^1.2.1"
+ jest-util "^26.1.0"
read-pkg-up "^7.0.1"
- realpath-native "^2.0.0"
- resolve "^1.15.1"
+ resolve "^1.17.0"
slash "^3.0.0"
-jest-runner@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-25.4.0.tgz#6ca4a3d52e692bbc081228fa68f750012f1f29e5"
- integrity sha512-wWQSbVgj2e/1chFdMRKZdvlmA6p1IPujhpLT7TKNtCSl1B0PGBGvJjCaiBal/twaU2yfk8VKezHWexM8IliBfA==
+jest-runner@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.1.0.tgz#457f7fc522afe46ca6db1dccf19f87f500b3288d"
+ integrity sha512-elvP7y0fVDREnfqit0zAxiXkDRSw6dgCkzPCf1XvIMnSDZ8yogmSKJf192dpOgnUVykmQXwYYJnCx641uLTgcw==
dependencies:
- "@jest/console" "^25.4.0"
- "@jest/environment" "^25.4.0"
- "@jest/test-result" "^25.4.0"
- "@jest/types" "^25.4.0"
- chalk "^3.0.0"
+ "@jest/console" "^26.1.0"
+ "@jest/environment" "^26.1.0"
+ "@jest/test-result" "^26.1.0"
+ "@jest/types" "^26.1.0"
+ chalk "^4.0.0"
exit "^0.1.2"
- graceful-fs "^4.2.3"
- jest-config "^25.4.0"
- jest-docblock "^25.3.0"
- jest-haste-map "^25.4.0"
- jest-jasmine2 "^25.4.0"
- jest-leak-detector "^25.4.0"
- jest-message-util "^25.4.0"
- jest-resolve "^25.4.0"
- jest-runtime "^25.4.0"
- jest-util "^25.4.0"
- jest-worker "^25.4.0"
+ graceful-fs "^4.2.4"
+ jest-config "^26.1.0"
+ jest-docblock "^26.0.0"
+ jest-haste-map "^26.1.0"
+ jest-jasmine2 "^26.1.0"
+ jest-leak-detector "^26.1.0"
+ jest-message-util "^26.1.0"
+ jest-resolve "^26.1.0"
+ jest-runtime "^26.1.0"
+ jest-util "^26.1.0"
+ jest-worker "^26.1.0"
source-map-support "^0.5.6"
throat "^5.0.0"
-jest-runtime@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-25.4.0.tgz#1e5227a9e2159d26ae27dcd426ca6bc041983439"
- integrity sha512-lgNJlCDULtXu9FumnwCyWlOub8iytijwsPNa30BKrSNtgoT6NUMXOPrZvsH06U6v0wgD/Igwz13nKA2wEKU2VA==
- dependencies:
- "@jest/console" "^25.4.0"
- "@jest/environment" "^25.4.0"
- "@jest/source-map" "^25.2.6"
- "@jest/test-result" "^25.4.0"
- "@jest/transform" "^25.4.0"
- "@jest/types" "^25.4.0"
+jest-runtime@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.1.0.tgz#45a37af42115f123ed5c51f126c05502da2469cb"
+ integrity sha512-1qiYN+EZLmG1QV2wdEBRf+Ci8i3VSfIYLF02U18PiUDrMbhfpN/EAMMkJtT02jgJUoaEOpHAIXG6zS3QRMzRmA==
+ dependencies:
+ "@jest/console" "^26.1.0"
+ "@jest/environment" "^26.1.0"
+ "@jest/fake-timers" "^26.1.0"
+ "@jest/globals" "^26.1.0"
+ "@jest/source-map" "^26.1.0"
+ "@jest/test-result" "^26.1.0"
+ "@jest/transform" "^26.1.0"
+ "@jest/types" "^26.1.0"
"@types/yargs" "^15.0.0"
- chalk "^3.0.0"
+ chalk "^4.0.0"
collect-v8-coverage "^1.0.0"
exit "^0.1.2"
glob "^7.1.3"
- graceful-fs "^4.2.3"
- jest-config "^25.4.0"
- jest-haste-map "^25.4.0"
- jest-message-util "^25.4.0"
- jest-mock "^25.4.0"
- jest-regex-util "^25.2.6"
- jest-resolve "^25.4.0"
- jest-snapshot "^25.4.0"
- jest-util "^25.4.0"
- jest-validate "^25.4.0"
- realpath-native "^2.0.0"
+ graceful-fs "^4.2.4"
+ jest-config "^26.1.0"
+ jest-haste-map "^26.1.0"
+ jest-message-util "^26.1.0"
+ jest-mock "^26.1.0"
+ jest-regex-util "^26.0.0"
+ jest-resolve "^26.1.0"
+ jest-snapshot "^26.1.0"
+ jest-util "^26.1.0"
+ jest-validate "^26.1.0"
slash "^3.0.0"
strip-bom "^4.0.0"
yargs "^15.3.1"
-jest-serializer@^25.2.6:
- version "25.2.6"
- resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-25.2.6.tgz#3bb4cc14fe0d8358489dbbefbb8a4e708ce039b7"
- integrity sha512-RMVCfZsezQS2Ww4kB5HJTMaMJ0asmC0BHlnobQC6yEtxiFKIxohFA4QSXSabKwSggaNkqxn6Z2VwdFCjhUWuiQ==
+jest-serializer@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.1.0.tgz#72a394531fc9b08e173dc7d297440ac610d95022"
+ integrity sha512-eqZOQG/0+MHmr25b2Z86g7+Kzd5dG9dhCiUoyUNJPgiqi38DqbDEOlHcNijyfZoj74soGBohKBZuJFS18YTJ5w==
+ dependencies:
+ graceful-fs "^4.2.4"
-jest-snapshot@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-25.4.0.tgz#e0b26375e2101413fd2ccb4278a5711b1922545c"
- integrity sha512-J4CJ0X2SaGheYRZdLz9CRHn9jUknVmlks4UBeu270hPAvdsauFXOhx9SQP2JtRzhnR3cvro/9N9KP83/uvFfRg==
+jest-snapshot@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.1.0.tgz#c36ed1e0334bd7bd2fe5ad07e93a364ead7e1349"
+ integrity sha512-YhSbU7eMTVQO/iRbNs8j0mKRxGp4plo7sJ3GzOQ0IYjvsBiwg0T1o0zGQAYepza7lYHuPTrG5J2yDd0CE2YxSw==
dependencies:
"@babel/types" "^7.0.0"
- "@jest/types" "^25.4.0"
- "@types/prettier" "^1.19.0"
- chalk "^3.0.0"
- expect "^25.4.0"
- jest-diff "^25.4.0"
- jest-get-type "^25.2.6"
- jest-matcher-utils "^25.4.0"
- jest-message-util "^25.4.0"
- jest-resolve "^25.4.0"
- make-dir "^3.0.0"
+ "@jest/types" "^26.1.0"
+ "@types/prettier" "^2.0.0"
+ chalk "^4.0.0"
+ expect "^26.1.0"
+ graceful-fs "^4.2.4"
+ jest-diff "^26.1.0"
+ jest-get-type "^26.0.0"
+ jest-haste-map "^26.1.0"
+ jest-matcher-utils "^26.1.0"
+ jest-message-util "^26.1.0"
+ jest-resolve "^26.1.0"
natural-compare "^1.4.0"
- pretty-format "^25.4.0"
- semver "^6.3.0"
+ pretty-format "^26.1.0"
+ semver "^7.3.2"
-jest-util@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.4.0.tgz#6a093d09d86d2b41ef583e5fe7dd3976346e1acd"
- integrity sha512-WSZD59sBtAUjLv1hMeKbNZXmMcrLRWcYqpO8Dz8b4CeCTZpfNQw2q9uwrYAD+BbJoLJlu4ezVPwtAmM/9/SlZA==
+jest-util@26.x, jest-util@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.1.0.tgz#80e85d4ba820decacf41a691c2042d5276e5d8d8"
+ integrity sha512-rNMOwFQevljfNGvbzNQAxdmXQ+NawW/J72dmddsK0E8vgxXCMtwQ/EH0BiWEIxh0hhMcTsxwAxINt7Lh46Uzbg==
dependencies:
- "@jest/types" "^25.4.0"
- chalk "^3.0.0"
+ "@jest/types" "^26.1.0"
+ chalk "^4.0.0"
+ graceful-fs "^4.2.4"
is-ci "^2.0.0"
- make-dir "^3.0.0"
+ micromatch "^4.0.2"
-jest-validate@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-25.4.0.tgz#2e177a93b716a137110eaf2768f3d9095abd3f38"
- integrity sha512-hvjmes/EFVJSoeP1yOl8qR8mAtMR3ToBkZeXrD/ZS9VxRyWDqQ/E1C5ucMTeSmEOGLipvdlyipiGbHJ+R1MQ0g==
+jest-validate@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.1.0.tgz#942c85ad3d60f78250c488a7f85d8f11a29788e7"
+ integrity sha512-WPApOOnXsiwhZtmkDsxnpye+XLb/tUISP+H6cHjfUIXvlG+eKwP+isnivsxlHCPaO9Q5wvbhloIBkdF3qUn+Nw==
dependencies:
- "@jest/types" "^25.4.0"
- camelcase "^5.3.1"
- chalk "^3.0.0"
- jest-get-type "^25.2.6"
+ "@jest/types" "^26.1.0"
+ camelcase "^6.0.0"
+ chalk "^4.0.0"
+ jest-get-type "^26.0.0"
leven "^3.1.0"
- pretty-format "^25.4.0"
+ pretty-format "^26.1.0"
-jest-watcher@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-25.4.0.tgz#63ec0cd5c83bb9c9d1ac95be7558dd61c995ff05"
- integrity sha512-36IUfOSRELsKLB7k25j/wutx0aVuHFN6wO94gPNjQtQqFPa2rkOymmx9rM5EzbF3XBZZ2oqD9xbRVoYa2w86gw==
+jest-watcher@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.1.0.tgz#99812a0cd931f0cb3d153180426135ab83e4d8f2"
+ integrity sha512-ffEOhJl2EvAIki613oPsSG11usqnGUzIiK7MMX6hE4422aXOcVEG3ySCTDFLn1+LZNXGPE8tuJxhp8OBJ1pgzQ==
dependencies:
- "@jest/test-result" "^25.4.0"
- "@jest/types" "^25.4.0"
+ "@jest/test-result" "^26.1.0"
+ "@jest/types" "^26.1.0"
ansi-escapes "^4.2.1"
- chalk "^3.0.0"
- jest-util "^25.4.0"
- string-length "^3.1.0"
+ chalk "^4.0.0"
+ jest-util "^26.1.0"
+ string-length "^4.0.1"
-jest-worker@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.4.0.tgz#ee0e2ceee5a36ecddf5172d6d7e0ab00df157384"
- integrity sha512-ghAs/1FtfYpMmYQ0AHqxV62XPvKdUDIBBApMZfly+E9JEmYh2K45G0R5dWxx986RN12pRCxsViwQVtGl+N4whw==
+jest-worker@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.1.0.tgz#65d5641af74e08ccd561c240e7db61284f82f33d"
+ integrity sha512-Z9P5pZ6UC+kakMbNJn+tA2RdVdNX5WH1x+5UCBZ9MxIK24pjYtFt96fK+UwBTrjLYm232g1xz0L3eTh51OW+yQ==
dependencies:
merge-stream "^2.0.0"
supports-color "^7.0.0"
-jest@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/jest/-/jest-25.4.0.tgz#fb96892c5c4e4a6b9bcb12068849cddf4c5f8cc7"
- integrity sha512-XWipOheGB4wai5JfCYXd6vwsWNwM/dirjRoZgAa7H2wd8ODWbli2AiKjqG8AYhyx+8+5FBEdpO92VhGlBydzbw==
+jest@^26.0.7:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/jest/-/jest-26.1.0.tgz#2f3aa7bcffb9bfd025473f83bbbf46a3af026263"
+ integrity sha512-LIti8jppw5BcQvmNJe4w2g1N/3V68HUfAv9zDVm7v+VAtQulGhH0LnmmiVkbNE4M4I43Bj2fXPiBGKt26k9tHw==
dependencies:
- "@jest/core" "^25.4.0"
+ "@jest/core" "^26.1.0"
import-local "^3.0.2"
- jest-cli "^25.4.0"
+ jest-cli "^26.1.0"
js-cookie@^2.2.0:
version "2.2.1"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
js-yaml@^3.13.1:
- version "3.13.1"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
- integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
+ version "3.14.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
+ integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
-jsdom@^15.2.1:
- version "15.2.1"
- resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-15.2.1.tgz#d2feb1aef7183f86be521b8c6833ff5296d07ec5"
- integrity sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==
- dependencies:
- abab "^2.0.0"
- acorn "^7.1.0"
- acorn-globals "^4.3.2"
- array-equal "^1.0.0"
- cssom "^0.4.1"
- cssstyle "^2.0.0"
- data-urls "^1.1.0"
- domexception "^1.0.1"
- escodegen "^1.11.1"
- html-encoding-sniffer "^1.0.2"
+jsdom@^16.2.2:
+ version "16.3.0"
+ resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.3.0.tgz#75690b7dac36c67be49c336dcd7219bbbed0810c"
+ integrity sha512-zggeX5UuEknpdZzv15+MS1dPYG0J/TftiiNunOeNxSl3qr8Z6cIlQpN0IdJa44z9aFxZRIVqRncvEhQ7X5DtZg==
+ dependencies:
+ abab "^2.0.3"
+ acorn "^7.1.1"
+ acorn-globals "^6.0.0"
+ cssom "^0.4.4"
+ cssstyle "^2.2.0"
+ data-urls "^2.0.0"
+ decimal.js "^10.2.0"
+ domexception "^2.0.1"
+ escodegen "^1.14.1"
+ html-encoding-sniffer "^2.0.1"
+ is-potential-custom-element-name "^1.0.0"
nwsapi "^2.2.0"
- parse5 "5.1.0"
- pn "^1.1.0"
- request "^2.88.0"
- request-promise-native "^1.0.7"
- saxes "^3.1.9"
- symbol-tree "^3.2.2"
+ parse5 "5.1.1"
+ request "^2.88.2"
+ request-promise-native "^1.0.8"
+ saxes "^5.0.0"
+ symbol-tree "^3.2.4"
tough-cookie "^3.0.1"
- w3c-hr-time "^1.0.1"
- w3c-xmlserializer "^1.1.2"
- webidl-conversions "^4.0.2"
+ w3c-hr-time "^1.0.2"
+ w3c-xmlserializer "^2.0.0"
+ webidl-conversions "^6.1.0"
whatwg-encoding "^1.0.5"
whatwg-mimetype "^2.3.0"
- whatwg-url "^7.0.0"
- ws "^7.0.0"
+ whatwg-url "^8.0.0"
+ ws "^7.2.3"
xml-name-validator "^3.0.0"
jsesc@^2.5.1:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
-json-parse-better-errors@^1.0.1:
+json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
dependencies:
minimist "^1.2.5"
+json5@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
+ integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
+ dependencies:
+ minimist "^1.2.0"
+
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
optionalDependencies:
graceful-fs "^4.1.6"
+jsonparse@^1.2.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
+ integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=
+
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
json-schema "0.2.3"
verror "1.10.0"
-jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3:
- version "2.2.3"
- resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz#8a9364e402448a3ce7f14d357738310d9248054f"
- integrity sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA==
+jsx-ast-utils@^2.3.0, jsx-ast-utils@^2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e"
+ integrity sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==
dependencies:
- array-includes "^3.0.3"
+ array-includes "^3.1.1"
object.assign "^4.1.0"
jwt-decode@^2.2.0:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
+language-subtag-registry@~0.3.2:
+ version "0.3.20"
+ resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz#a00a37121894f224f763268e431c55556b0c0755"
+ integrity sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg==
+
+language-tags@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a"
+ integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=
+ dependencies:
+ language-subtag-registry "~0.3.2"
+
+latest-version@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15"
+ integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=
+ dependencies:
+ package-json "^4.0.0"
+
+lazy-property@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/lazy-property/-/lazy-property-1.0.0.tgz#84ddc4b370679ba8bd4cdcfa4c06b43d57111147"
+ integrity sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc=
+
+lcid@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
+ integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=
+ dependencies:
+ invert-kv "^1.0.0"
+
lego-api@^1.0.7:
version "1.0.8"
resolved "https://registry.yarnpkg.com/lego-api/-/lego-api-1.0.8.tgz#5e26be726c5e11d540f89e7c6b1abf8c5834bd01"
dependencies:
chain-able "^3.0.0"
+lemmy-js-client@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-1.0.8.tgz#98e34c8e3cd07427f883f60fad376dc4d6f46e7f"
+ integrity sha512-YZxD3+8RGz7cRKdI8EIe5iQqQIMm5WzdNz6zZ7/CdkMtXUv6YuMOEv8HLTvBoGuaWIJwlMJ+23NIarxlT26IEw==
+
leven@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
-levn@^0.3.0, levn@~0.3.0:
+levn@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
+ integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
+ dependencies:
+ prelude-ls "^1.2.1"
+ type-check "~0.4.0"
+
+levn@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=
prelude-ls "~1.1.2"
type-check "~0.3.2"
+libcipm@^1.6.2:
+ version "1.6.3"
+ resolved "https://registry.yarnpkg.com/libcipm/-/libcipm-1.6.3.tgz#dc4052d710941547782d85bbdb3c77eedec733ff"
+ integrity sha512-WUEjQk1aZDECb2MFnAbx6o7sJbBJWrWwt9rbinOmpc0cLKWgYJOvKNqCUN3sl2P9LFqPsnVT4Aj5SPw4/xKI5A==
+ dependencies:
+ bin-links "^1.1.2"
+ bluebird "^3.5.1"
+ find-npm-prefix "^1.0.2"
+ graceful-fs "^4.1.11"
+ lock-verify "^2.0.2"
+ npm-lifecycle "^2.0.3"
+ npm-logical-tree "^1.2.1"
+ npm-package-arg "^6.1.0"
+ pacote "^8.1.6"
+ protoduck "^5.0.0"
+ read-package-json "^2.0.13"
+ rimraf "^2.6.2"
+ worker-farm "^1.6.0"
+
+libnpx@^10.2.0:
+ version "10.2.4"
+ resolved "https://registry.yarnpkg.com/libnpx/-/libnpx-10.2.4.tgz#ef0e3258e29aef2ec7ee3276115e20e67f67d4ee"
+ integrity sha512-BPc0D1cOjBeS8VIBKUu5F80s6njm0wbVt7CsGMrIcJ+SI7pi7V0uVPGpEMH9H5L8csOcclTxAXFE2VAsJXUhfA==
+ dependencies:
+ dotenv "^5.0.1"
+ npm-package-arg "^6.0.0"
+ rimraf "^2.6.2"
+ safe-buffer "^5.1.0"
+ update-notifier "^2.3.0"
+ which "^1.3.0"
+ y18n "^4.0.0"
+ yargs "^14.2.3"
+
lines-and-columns@^1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
-linkify-it@^2.0.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf"
- integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==
+linkify-it@^3.0.1:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.2.tgz#f55eeb8bc1d3ae754049e124ab3bb56d97797fb8"
+ integrity sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ==
dependencies:
uc.micro "^1.0.1"
lint-staged@^10.1.3:
- version "10.1.3"
- resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.1.3.tgz#da27713d3ac519da305381b4de87d5f866b1d2f1"
- integrity sha512-o2OkLxgVns5RwSC5QF7waeAjJA5nz5gnUfqL311LkZcFipKV7TztrSlhNUK5nQX9H0E5NELAdduMQ+M/JPT7RQ==
+ version "10.2.11"
+ resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.11.tgz#713c80877f2dc8b609b05bc59020234e766c9720"
+ integrity sha512-LRRrSogzbixYaZItE2APaS4l2eJMjjf5MbclRZpLJtcQJShcvUzKXsNeZgsLIZ0H0+fg2tL4B59fU9wHIHtFIA==
dependencies:
- chalk "^3.0.0"
- commander "^4.0.1"
+ chalk "^4.0.0"
+ cli-truncate "2.1.0"
+ commander "^5.1.0"
cosmiconfig "^6.0.0"
debug "^4.1.1"
dedent "^0.7.0"
- execa "^3.4.0"
- listr "^0.14.3"
- log-symbols "^3.0.0"
+ enquirer "^2.3.5"
+ execa "^4.0.1"
+ listr2 "^2.1.0"
+ log-symbols "^4.0.0"
micromatch "^4.0.2"
normalize-path "^3.0.0"
please-upgrade-node "^3.2.0"
string-argv "0.3.1"
stringify-object "^3.3.0"
-listr-silent-renderer@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e"
- integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=
-
-listr-update-renderer@^0.5.0:
- version "0.5.0"
- resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz#4ea8368548a7b8aecb7e06d8c95cb45ae2ede6a2"
- integrity sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==
- dependencies:
- chalk "^1.1.3"
- cli-truncate "^0.2.1"
- elegant-spinner "^1.0.1"
- figures "^1.7.0"
- indent-string "^3.0.0"
- log-symbols "^1.0.2"
- log-update "^2.3.0"
- strip-ansi "^3.0.1"
-
-listr-verbose-renderer@^0.5.0:
- version "0.5.0"
- resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz#f1132167535ea4c1261102b9f28dac7cba1e03db"
- integrity sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==
- dependencies:
- chalk "^2.4.1"
- cli-cursor "^2.1.0"
- date-fns "^1.27.2"
- figures "^2.0.0"
-
-listr@^0.14.3:
- version "0.14.3"
- resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586"
- integrity sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==
+listr2@^2.1.0:
+ version "2.3.6"
+ resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.3.6.tgz#4248bbbfeb321357aff3587cf3c6dbae4942b83b"
+ integrity sha512-znchYUj4fahw/SxxJ2SOEytA7enDinu0HFeIS+Z+2o8h4sMyl6cUm6J9GBvF7ICwLjQJy7lDcphwYbjEDgJgcQ==
dependencies:
- "@samverschueren/stream-to-observable" "^0.3.0"
- is-observable "^1.1.0"
- is-promise "^2.1.0"
- is-stream "^1.1.0"
- listr-silent-renderer "^1.1.1"
- listr-update-renderer "^0.5.0"
- listr-verbose-renderer "^0.5.0"
- p-map "^2.0.0"
- rxjs "^6.3.3"
+ chalk "^4.1.0"
+ cli-truncate "^2.1.0"
+ figures "^3.2.0"
+ indent-string "^4.0.0"
+ log-update "^4.0.0"
+ p-map "^4.0.0"
+ rxjs "^6.6.0"
+ through "^2.3.8"
load-json-file@^2.0.0:
version "2.0.0"
p-locate "^2.0.0"
path-exists "^3.0.0"
+locate-path@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
+ integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
+ dependencies:
+ p-locate "^3.0.0"
+ path-exists "^3.0.0"
+
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
dependencies:
p-locate "^4.1.0"
-lodash.get@^4.4.2:
- version "4.4.2"
- resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
- integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
-
-lodash.memoize@4.x:
+lock-verify@^2.0.2:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/lock-verify/-/lock-verify-2.2.1.tgz#81107948c51ed16f97b96ff8b60675affb243fc1"
+ integrity sha512-n0Zw2DVupKfZMazy/HIFVNohJ1z8fIoZ77WBnyyBGG6ixw83uJNyrbiJvvHWe1QKkGiBCjj8RCPlymltliqEww==
+ dependencies:
+ "@iarna/cli" "^1.2.0"
+ npm-package-arg "^6.1.0"
+ semver "^5.4.1"
+
+lockfile@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/lockfile/-/lockfile-1.0.4.tgz#07f819d25ae48f87e538e6578b6964a4981a5609"
+ integrity sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==
+ dependencies:
+ signal-exit "^3.0.2"
+
+lodash._baseuniq@~4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8"
+ integrity sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=
+ dependencies:
+ lodash._createset "~4.0.0"
+ lodash._root "~3.0.0"
+
+lodash._createset@~4.0.0:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
+ integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=
+
+lodash._root@~3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
+ integrity sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=
+
+lodash.clonedeep@~4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
+ integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
+
+lodash.get@^4.4.2:
+ version "4.4.2"
+ resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
+ integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
+
+lodash.memoize@4.x:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
+lodash.union@~4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
+ integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=
+
+lodash.uniq@~4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
+ integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
+
+lodash.without@~4.4.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac"
+ integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=
+
lodash.zip@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.zip/-/lodash.zip-4.2.0.tgz#ec6662e4896408ed4ab6c542a3990b72cc080020"
integrity sha1-7GZi5IlkCO1KtsVCo5kLcswIACA=
-lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.3.0:
- version "4.17.15"
- resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
- integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
+lodash@^3.10.1:
+ version "3.10.1"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
+ integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=
-log-symbols@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
- integrity sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=
- dependencies:
- chalk "^1.0.0"
-
-log-symbols@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4"
- integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==
- dependencies:
- chalk "^2.4.2"
+lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.3.0:
+ version "4.17.19"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
+ integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
-log-update@^2.3.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708"
- integrity sha1-iDKP19HOeTiykoN0bwsbwSayRwg=
+log-symbols@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
+ integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
dependencies:
- ansi-escapes "^3.0.0"
- cli-cursor "^2.0.0"
- wrap-ansi "^3.0.1"
+ chalk "^4.0.0"
-lolex@^5.0.0:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367"
- integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==
+log-update@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
+ integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==
dependencies:
- "@sinonjs/commons" "^1.7.0"
+ ansi-escapes "^4.3.0"
+ cli-cursor "^3.1.0"
+ slice-ansi "^4.0.0"
+ wrap-ansi "^6.2.0"
loose-envify@^1.2.0, loose-envify@^1.4.0:
version "1.4.0"
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
+lowercase-keys@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
+ integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
+
+lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.2, lru-cache@^4.1.3:
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
+ integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
+ dependencies:
+ pseudomap "^1.0.2"
+ yallist "^2.1.2"
+
+lru-cache@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
+ integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
+ dependencies:
+ yallist "^3.0.2"
+
+make-dir@^1.0.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
+ integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
+ dependencies:
+ pify "^3.0.0"
+
make-dir@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
dependencies:
semver "^6.0.0"
-make-error@1.x:
+make-error@1.x, make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
-make-error@^1.1.1:
- version "1.3.5"
- resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8"
- integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==
+"make-fetch-happen@^2.5.0 || 3 || 4", make-fetch-happen@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-4.0.2.tgz#2d156b11696fb32bffbafe1ac1bc085dd6c78a79"
+ integrity sha512-YMJrAjHSb/BordlsDEcVcPyTbiJKkzqMf48N8dAJZT9Zjctrkb6Yg4TY9Sq2AwSIQJFn5qBBKVTYt3vP5FMIHA==
+ dependencies:
+ agentkeepalive "^3.4.1"
+ cacache "^11.3.3"
+ http-cache-semantics "^3.8.1"
+ http-proxy-agent "^2.1.0"
+ https-proxy-agent "^2.2.1"
+ lru-cache "^5.1.1"
+ mississippi "^3.0.0"
+ node-fetch-npm "^2.0.2"
+ promise-retry "^1.1.1"
+ socks-proxy-agent "^4.0.0"
+ ssri "^6.0.0"
+
+make-fetch-happen@^2.6.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-2.6.0.tgz#8474aa52198f6b1ae4f3094c04e8370d35ea8a38"
+ integrity sha512-FFq0lNI0ax+n9IWzWpH8A4JdgYiAp2DDYIZ3rsaav8JDe8I+72CzK6PQW/oom15YDZpV5bYW/9INd6nIJ2ZfZw==
+ dependencies:
+ agentkeepalive "^3.3.0"
+ cacache "^10.0.0"
+ http-cache-semantics "^3.8.0"
+ http-proxy-agent "^2.0.0"
+ https-proxy-agent "^2.1.0"
+ lru-cache "^4.1.1"
+ mississippi "^1.2.0"
+ node-fetch-npm "^2.0.2"
+ promise-retry "^1.1.1"
+ socks-proxy-agent "^3.0.1"
+ ssri "^5.0.0"
+
+make-fetch-happen@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-3.0.0.tgz#7b661d2372fc4710ab5cc8e1fa3c290eea69a961"
+ integrity sha512-FmWY7gC0mL6Z4N86vE14+m719JKE4H0A+pyiOH18B025gF/C113pyfb4gHDDYP5cqnRMHOz06JGdmffC/SES+w==
+ dependencies:
+ agentkeepalive "^3.4.1"
+ cacache "^10.0.4"
+ http-cache-semantics "^3.8.1"
+ http-proxy-agent "^2.1.0"
+ https-proxy-agent "^2.2.0"
+ lru-cache "^4.1.2"
+ mississippi "^3.0.0"
+ node-fetch-npm "^2.0.2"
+ promise-retry "^1.1.1"
+ socks-proxy-agent "^3.0.1"
+ ssri "^5.2.4"
makeerror@1.0.x:
version "1.0.11"
dependencies:
object-visit "^1.0.0"
-markdown-it-container@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/markdown-it-container/-/markdown-it-container-2.0.0.tgz#0019b43fd02eefece2f1960a2895fba81a404695"
- integrity sha1-ABm0P9Au7+zi8ZYKKJX7qBpARpU=
+markdown-it-container@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/markdown-it-container/-/markdown-it-container-3.0.0.tgz#1d19b06040a020f9a827577bb7dbf67aa5de9a5b"
+ integrity sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw==
markdown-it-emoji@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz#9bee0e9a990a963ba96df6980c4fddb05dfb4dcc"
integrity sha1-m+4OmpkKljupbfaYDE/dsF37Tcw=
-markdown-it@^10.0.0:
- version "10.0.0"
- resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc"
- integrity sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==
+markdown-it-sub@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz#375fd6026eae7ddcb012497f6411195ea1e3afe8"
+ integrity sha1-N1/WAm6ufdywEkl/ZBEZXqHjr+g=
+
+markdown-it-sup@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz#cb9c9ff91a5255ac08f3fd3d63286e15df0a1fc3"
+ integrity sha1-y5yf+RpSVawI8/09YyhuFd8KH8M=
+
+markdown-it@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-11.0.0.tgz#dbfc30363e43d756ebc52c38586b91b90046b876"
+ integrity sha512-+CvOnmbSubmQFSA9dKz1BRiaSMV7rhexl3sngKqFyXSagoA3fBdJQ8oZWtRy2knXdpDXaBw44euz37DeJQ9asg==
dependencies:
argparse "^1.0.7"
entities "~2.0.0"
- linkify-it "^2.0.0"
+ linkify-it "^3.0.1"
mdurl "^1.0.1"
uc.micro "^1.0.5"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
+meant@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/meant/-/meant-1.0.2.tgz#5d0c78310a3d8ae1408a16be0fe0bd42a969f560"
+ integrity sha512-KN+1uowN/NK+sT/Lzx7WSGIj2u+3xe5n2LbwObfjOhPZiA+cCfCm6idVl0RkEfjThkw5XJ96CyRcanq6GmKtUg==
+
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
+mem@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
+ integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=
+ dependencies:
+ mimic-fn "^1.0.0"
+
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
-micromatch@4.x, micromatch@^4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
- integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
- dependencies:
- braces "^3.0.1"
- picomatch "^2.0.5"
-
micromatch@^2.1.5:
version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
snapdragon "^0.8.1"
to-regex "^3.0.2"
-mime-db@1.43.0:
- version "1.43.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
- integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==
+micromatch@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
+ integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
+ dependencies:
+ braces "^3.0.1"
+ picomatch "^2.0.5"
+
+mime-db@1.44.0:
+ version "1.44.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
+ integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24:
- version "2.1.26"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
- integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==
+ version "2.1.27"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
+ integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
dependencies:
- mime-db "1.43.0"
+ mime-db "1.44.0"
mime@1.6.0:
version "1.6.0"
dependencies:
brace-expansion "^1.1.7"
-minimist@0.0.8:
- version "0.0.8"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
- integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
-
-minimist@^1.1.1, minimist@^1.2.5:
+minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
-minimist@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
- integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
+minipass@^2.3.3, minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6"
+ integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==
+ dependencies:
+ safe-buffer "^5.1.2"
+ yallist "^3.0.0"
+
+minizlib@^1.2.1:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d"
+ integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==
+ dependencies:
+ minipass "^2.9.0"
+
+mississippi@^1.2.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-1.3.1.tgz#2a8bb465e86550ac8b36a7b6f45599171d78671e"
+ integrity sha512-/6rB8YXFbAtsUVRphIRQqB0+9c7VaPHCjVtvto+JqwVxgz8Zz+I+f68/JgQ+Pb4VlZb2svA9OtdXnHHsZz7ltg==
+ dependencies:
+ concat-stream "^1.5.0"
+ duplexify "^3.4.2"
+ end-of-stream "^1.1.0"
+ flush-write-stream "^1.0.0"
+ from2 "^2.1.0"
+ parallel-transform "^1.1.0"
+ pump "^1.0.0"
+ pumpify "^1.3.3"
+ stream-each "^1.1.0"
+ through2 "^2.0.0"
+
+mississippi@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-2.0.0.tgz#3442a508fafc28500486feea99409676e4ee5a6f"
+ integrity sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==
+ dependencies:
+ concat-stream "^1.5.0"
+ duplexify "^3.4.2"
+ end-of-stream "^1.1.0"
+ flush-write-stream "^1.0.0"
+ from2 "^2.1.0"
+ parallel-transform "^1.1.0"
+ pump "^2.0.1"
+ pumpify "^1.3.3"
+ stream-each "^1.1.0"
+ through2 "^2.0.0"
+
+mississippi@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022"
+ integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==
+ dependencies:
+ concat-stream "^1.5.0"
+ duplexify "^3.4.2"
+ end-of-stream "^1.1.0"
+ flush-write-stream "^1.0.0"
+ from2 "^2.1.0"
+ parallel-transform "^1.1.0"
+ pump "^3.0.0"
+ pumpify "^1.3.3"
+ stream-each "^1.1.0"
+ through2 "^2.0.0"
mixin-deep@^1.2.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
-mkdirp@^0.5.1:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
- integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
+"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+ version "0.5.5"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
+ integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
dependencies:
- minimist "0.0.8"
+ minimist "^1.2.5"
moment@^2.24.0:
- version "2.24.0"
- resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
- integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
+ version "2.27.0"
+ resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d"
+ integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==
+
+move-concurrently@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
+ integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=
+ dependencies:
+ aproba "^1.1.1"
+ copy-concurrently "^1.0.0"
+ fs-write-stream-atomic "^1.0.8"
+ mkdirp "^0.5.1"
+ rimraf "^2.5.4"
+ run-queue "^1.0.3"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
-ms@^2.1.1:
+ms@^2.0.0, ms@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
-multimap@^1.0.2:
+multimap@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/multimap/-/multimap-1.1.0.tgz#5263febc085a1791c33b59bb3afc6a76a2a10ca8"
integrity sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw==
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
-mute-stream@0.0.8:
+mute-stream@~0.0.4:
version "0.0.8"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
nan@^2.12.1:
- version "2.14.0"
- resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
- integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
+ version "2.14.1"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
+ integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
nanomatch@^1.2.9:
version "1.2.13"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
+node-fetch-npm@^2.0.2:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz#6507d0e17a9ec0be3bec516958a497cec54bf5a4"
+ integrity sha512-iOuIQDWDyjhv9qSDrj9aq/klt6F9z1p2otB3AV7v3zBDcL/x+OfGsvGQZZCcMZbUf4Ujw1xGNQkjvGnVT22cKg==
+ dependencies:
+ encoding "^0.1.11"
+ json-parse-better-errors "^1.0.0"
+ safe-buffer "^5.1.1"
+
node-fetch@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
+node-gyp@^3.6.2:
+ version "3.8.0"
+ resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c"
+ integrity sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==
+ dependencies:
+ fstream "^1.0.0"
+ glob "^7.0.3"
+ graceful-fs "^4.1.2"
+ mkdirp "^0.5.0"
+ nopt "2 || 3"
+ npmlog "0 || 1 || 2 || 3 || 4"
+ osenv "0"
+ request "^2.87.0"
+ rimraf "2"
+ semver "~5.3.0"
+ tar "^2.0.0"
+ which "1"
+
+node-gyp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-4.0.0.tgz#972654af4e5dd0cd2a19081b4b46fe0442ba6f45"
+ integrity sha512-2XiryJ8sICNo6ej8d0idXDEMKfVfFK7kekGCtJAuelGsYHQxhj13KTf95swTCN2dZ/4lTfZ84Fu31jqJEEgjWA==
+ dependencies:
+ glob "^7.0.3"
+ graceful-fs "^4.1.2"
+ mkdirp "^0.5.0"
+ nopt "2 || 3"
+ npmlog "0 || 1 || 2 || 3 || 4"
+ osenv "0"
+ request "^2.87.0"
+ rimraf "2"
+ semver "~5.3.0"
+ tar "^4.4.8"
+ which "1"
+
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40"
integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
-node-notifier@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-6.0.0.tgz#cea319e06baa16deec8ce5cd7f133c4a46b68e12"
- integrity sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw==
+node-notifier@^7.0.0:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-7.0.2.tgz#3a70b1b70aca5e919d0b1b022530697466d9c675"
+ integrity sha512-ux+n4hPVETuTL8+daJXTOC6uKLgMsl1RYfFv7DKRzyvzBapqco0rZZ9g72ZN8VS6V+gvNYHYa/ofcCY8fkJWsA==
dependencies:
growly "^1.3.0"
- is-wsl "^2.1.1"
- semver "^6.3.0"
+ is-wsl "^2.2.0"
+ semver "^7.3.2"
shellwords "^0.1.1"
- which "^1.3.1"
+ uuid "^8.2.0"
+ which "^2.0.2"
-normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
+"nopt@2 || 3":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
+ integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k=
+ dependencies:
+ abbrev "1"
+
+nopt@~4.0.1:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48"
+ integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==
+ dependencies:
+ abbrev "1"
+ osenv "^0.1.4"
+
+normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0, "normalize-package-data@~1.0.1 || ^2.0.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
+normalize-package-data@~2.4.0:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.2.tgz#6b2abd85774e51f7936f1395e45acb905dc849b2"
+ integrity sha512-YcMnjqeoUckXTPKZSAsPjUPLxH85XotbpqK3w4RyCwdFQSU5FxxBys8buehkSfg0j9fKvV1hn7O0+8reEgkAiw==
+ dependencies:
+ hosted-git-info "^2.1.4"
+ is-builtin-module "^1.0.0"
+ semver "2 || 3 || 4 || 5"
+ validate-npm-package-license "^3.0.1"
+
normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+npm-audit-report@^1.0.9:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/npm-audit-report/-/npm-audit-report-1.3.3.tgz#8226deeb253b55176ed147592a3995442f2179ed"
+ integrity sha512-8nH/JjsFfAWMvn474HB9mpmMjrnKb1Hx/oTAdjv4PT9iZBvBxiZ+wtDUapHCJwLqYGQVPaAfs+vL5+5k9QndXw==
+ dependencies:
+ cli-table3 "^0.5.0"
+ console-control-strings "^1.1.0"
+
+npm-bundled@^1.0.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b"
+ integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==
+ dependencies:
+ npm-normalize-package-bin "^1.0.1"
+
+npm-cache-filename@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/npm-cache-filename/-/npm-cache-filename-1.0.2.tgz#ded306c5b0bfc870a9e9faf823bc5f283e05ae11"
+ integrity sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE=
+
+npm-install-checks@~3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-3.0.2.tgz#ab2e32ad27baa46720706908e5b14c1852de44d9"
+ integrity sha512-E4kzkyZDIWoin6uT5howP8VDvkM+E8IQDcHAycaAxMbwkqhIg5eEYALnXOl3Hq9MrkdQB/2/g1xwBINXdKSRkg==
+ dependencies:
+ semver "^2.3.0 || 3.x || 4 || 5"
+
+npm-lifecycle@^2.0.1, npm-lifecycle@^2.0.3:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-2.1.1.tgz#0027c09646f0fd346c5c93377bdaba59c6748fdf"
+ integrity sha512-+Vg6I60Z75V/09pdcH5iUo/99Q/vop35PaI99elvxk56azSVVsdsSsS/sXqKDNwbRRNN1qSxkcO45ZOu0yOWew==
+ dependencies:
+ byline "^5.0.0"
+ graceful-fs "^4.1.15"
+ node-gyp "^4.0.0"
+ resolve-from "^4.0.0"
+ slide "^1.1.6"
+ uid-number "0.0.6"
+ umask "^1.1.0"
+ which "^1.3.1"
+
+npm-logical-tree@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/npm-logical-tree/-/npm-logical-tree-1.2.1.tgz#44610141ca24664cad35d1e607176193fd8f5b88"
+ integrity sha512-AJI/qxDB2PWI4LG1CYN579AY1vCiNyWfkiquCsJWqntRu/WwimVrC8yXeILBFHDwxfOejxewlmnvW9XXjMlYIg==
+
+npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2"
+ integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==
+
+"npm-package-arg@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", "npm-package-arg@^4.0.0 || ^5.0.0 || ^6.0.0", npm-package-arg@^6.0.0, npm-package-arg@^6.1.0:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.1.tgz#02168cb0a49a2b75bf988a28698de7b529df5cb7"
+ integrity sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==
+ dependencies:
+ hosted-git-info "^2.7.1"
+ osenv "^0.1.5"
+ semver "^5.6.0"
+ validate-npm-package-name "^3.0.0"
+
+npm-packlist@^1.1.10:
+ version "1.4.8"
+ resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
+ integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==
+ dependencies:
+ ignore-walk "^3.0.1"
+ npm-bundled "^1.0.1"
+ npm-normalize-package-bin "^1.0.1"
+
+npm-packlist@~1.1.10:
+ version "1.1.12"
+ resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a"
+ integrity sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g==
+ dependencies:
+ ignore-walk "^3.0.1"
+ npm-bundled "^1.0.1"
+
+npm-pick-manifest@^2.1.0:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-2.2.3.tgz#32111d2a9562638bb2c8f2bf27f7f3092c8fae40"
+ integrity sha512-+IluBC5K201+gRU85vFlUwX3PFShZAbAgDNp2ewJdWMVSppdo/Zih0ul2Ecky/X7b51J7LrrUAP+XOmOCvYZqA==
+ dependencies:
+ figgy-pudding "^3.5.1"
+ npm-package-arg "^6.0.0"
+ semver "^5.4.1"
+
+npm-profile@^3.0.1:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/npm-profile/-/npm-profile-3.0.2.tgz#58d568f1b56ef769602fd0aed8c43fa0e0de0f57"
+ integrity sha512-rEJOFR6PbwOvvhGa2YTNOJQKNuc6RovJ6T50xPU7pS9h/zKPNCJ+VHZY2OFXyZvEi+UQYtHRTp8O/YM3tUD20A==
+ dependencies:
+ aproba "^1.1.2 || 2"
+ make-fetch-happen "^2.5.0 || 3 || 4"
+
+npm-registry-client@^8.5.1:
+ version "8.6.0"
+ resolved "https://registry.yarnpkg.com/npm-registry-client/-/npm-registry-client-8.6.0.tgz#7f1529f91450732e89f8518e0f21459deea3e4c4"
+ integrity sha512-Qs6P6nnopig+Y8gbzpeN/dkt+n7IyVd8f45NTMotGk6Qo7GfBmzwYx6jRLoOOgKiMnaQfYxsuyQlD8Mc3guBhg==
+ dependencies:
+ concat-stream "^1.5.2"
+ graceful-fs "^4.1.6"
+ normalize-package-data "~1.0.1 || ^2.0.0"
+ npm-package-arg "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
+ once "^1.3.3"
+ request "^2.74.0"
+ retry "^0.10.0"
+ safe-buffer "^5.1.1"
+ semver "2 >=2.2.1 || 3.x || 4 || 5"
+ slide "^1.1.3"
+ ssri "^5.2.4"
+ optionalDependencies:
+ npmlog "2 || ^3.1.0 || ^4.0.0"
+
+npm-registry-fetch@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-1.1.1.tgz#710bc5947d9ee2c549375072dab6d5d17baf2eb2"
+ integrity sha512-ev+zxOXsgAqRsR8Rk+ErjgWOlbrXcqGdme94/VNdjDo1q8TSy10Pp8xgDv/ZmMk2jG/KvGtXUNG4GS3+l6xbDw==
+ dependencies:
+ bluebird "^3.5.1"
+ figgy-pudding "^3.0.0"
+ lru-cache "^4.1.2"
+ make-fetch-happen "^3.0.0"
+ npm-package-arg "^6.0.0"
+ safe-buffer "^5.1.1"
+
npm-run-path@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
dependencies:
path-key "^3.0.0"
+npm-user-validate@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/npm-user-validate/-/npm-user-validate-1.0.0.tgz#8ceca0f5cea04d4e93519ef72d0557a75122e951"
+ integrity sha1-jOyg9c6gTU6TUZ73LQVXp1Ei6VE=
+
+npm@^5.8.0:
+ version "5.10.0"
+ resolved "https://registry.yarnpkg.com/npm/-/npm-5.10.0.tgz#3bec62312c94a9b0f48f208e00b98bf0304b40db"
+ integrity sha512-lvjvjgR5wG2RJ2uqak1xtZcVAWMwVOzN5HkUlUj/n8rU1f3A0fNn+7HwOzH9Lyf0Ppyu9ApgsEpHczOSnx1cwA==
+ dependencies:
+ JSONStream "^1.3.2"
+ abbrev "~1.1.1"
+ ansi-regex "~3.0.0"
+ ansicolors "~0.3.2"
+ ansistyles "~0.1.3"
+ aproba "~1.2.0"
+ archy "~1.0.0"
+ bin-links "^1.1.0"
+ bluebird "~3.5.1"
+ byte-size "^4.0.2"
+ cacache "^10.0.4"
+ call-limit "~1.1.0"
+ chownr "~1.0.1"
+ cli-columns "^3.1.2"
+ cli-table2 "~0.2.0"
+ cmd-shim "~2.0.2"
+ columnify "~1.5.4"
+ config-chain "~1.1.11"
+ detect-indent "~5.0.0"
+ detect-newline "^2.1.0"
+ dezalgo "~1.0.3"
+ editor "~1.0.0"
+ find-npm-prefix "^1.0.2"
+ fs-vacuum "~1.2.10"
+ fs-write-stream-atomic "~1.0.10"
+ gentle-fs "^2.0.1"
+ glob "~7.1.2"
+ graceful-fs "~4.1.11"
+ has-unicode "~2.0.1"
+ hosted-git-info "^2.6.0"
+ iferr "~0.1.5"
+ inflight "~1.0.6"
+ inherits "~2.0.3"
+ ini "^1.3.5"
+ init-package-json "^1.10.3"
+ is-cidr "~1.0.0"
+ json-parse-better-errors "^1.0.2"
+ lazy-property "~1.0.0"
+ libcipm "^1.6.2"
+ libnpx "^10.2.0"
+ lock-verify "^2.0.2"
+ lockfile "^1.0.4"
+ lodash._baseuniq "~4.6.0"
+ lodash.clonedeep "~4.5.0"
+ lodash.union "~4.6.0"
+ lodash.uniq "~4.5.0"
+ lodash.without "~4.4.0"
+ lru-cache "^4.1.2"
+ meant "~1.0.1"
+ mississippi "^3.0.0"
+ mkdirp "~0.5.1"
+ move-concurrently "^1.0.1"
+ node-gyp "^3.6.2"
+ nopt "~4.0.1"
+ normalize-package-data "~2.4.0"
+ npm-audit-report "^1.0.9"
+ npm-cache-filename "~1.0.2"
+ npm-install-checks "~3.0.0"
+ npm-lifecycle "^2.0.1"
+ npm-package-arg "^6.1.0"
+ npm-packlist "~1.1.10"
+ npm-profile "^3.0.1"
+ npm-registry-client "^8.5.1"
+ npm-registry-fetch "^1.1.0"
+ npm-user-validate "~1.0.0"
+ npmlog "~4.1.2"
+ once "~1.4.0"
+ opener "~1.4.3"
+ osenv "^0.1.5"
+ pacote "^7.6.1"
+ path-is-inside "~1.0.2"
+ promise-inflight "~1.0.1"
+ qrcode-terminal "^0.12.0"
+ query-string "^6.1.0"
+ qw "~1.0.1"
+ read "~1.0.7"
+ read-cmd-shim "~1.0.1"
+ read-installed "~4.0.3"
+ read-package-json "^2.0.13"
+ read-package-tree "^5.2.1"
+ readable-stream "^2.3.6"
+ request "^2.85.0"
+ retry "^0.12.0"
+ rimraf "~2.6.2"
+ safe-buffer "^5.1.2"
+ semver "^5.5.0"
+ sha "~2.0.1"
+ slide "~1.1.6"
+ sorted-object "~2.0.1"
+ sorted-union-stream "~2.1.3"
+ ssri "^5.3.0"
+ strip-ansi "~4.0.0"
+ tar "^4.4.2"
+ text-table "~0.2.0"
+ tiny-relative-date "^1.3.0"
+ uid-number "0.0.6"
+ umask "~1.1.0"
+ unique-filename "~1.1.0"
+ unpipe "~1.0.0"
+ update-notifier "^2.5.0"
+ uuid "^3.2.1"
+ validate-npm-package-license "^3.0.3"
+ validate-npm-package-name "~3.0.0"
+ which "~1.3.0"
+ worker-farm "^1.6.0"
+ wrappy "~1.0.2"
+ write-file-atomic "^2.3.0"
+
+"npmlog@0 || 1 || 2 || 3 || 4", "npmlog@2 || ^3.1.0 || ^4.0.0", npmlog@~4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
+ integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
+ dependencies:
+ are-we-there-yet "~1.1.2"
+ console-control-strings "~1.1.0"
+ gauge "~2.7.3"
+ set-blocking "~2.0.0"
+
number-is-nan@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
kind-of "^3.0.3"
object-inspect@^1.7.0:
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67"
- integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0"
+ integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==
+
+object-is@^1.0.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.2.tgz#c5d2e87ff9e119f78b7a088441519e2eec1573b6"
+ integrity sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==
+ dependencies:
+ define-properties "^1.1.3"
+ es-abstract "^1.17.5"
object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
version "1.1.1"
has-symbols "^1.0.0"
object-keys "^1.0.11"
-object.entries@^1.1.0, object.entries@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.1.tgz#ee1cf04153de02bb093fec33683900f57ce5399b"
- integrity sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==
+object.entries@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add"
+ integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==
dependencies:
define-properties "^1.1.3"
- es-abstract "^1.17.0-next.1"
- function-bind "^1.1.1"
+ es-abstract "^1.17.5"
has "^1.0.3"
-object.fromentries@^2.0.0, object.fromentries@^2.0.2:
+object.fromentries@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9"
integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==
function-bind "^1.1.1"
has "^1.0.3"
+object.getownpropertydescriptors@^2.0.3:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649"
+ integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==
+ dependencies:
+ define-properties "^1.1.3"
+ es-abstract "^1.17.0-next.1"
+
object.omit@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
dependencies:
isobject "^3.0.1"
-object.values@^1.1.0, object.values@^1.1.1:
+object.values@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e"
integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==
dependencies:
ee-first "1.1.1"
-once@^1.3.0, once@^1.3.1, once@^1.4.0:
+once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0, once@~1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
mimic-fn "^2.1.0"
opencollective-postinstall@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz#5657f1bede69b6e33a45939b061eb53d3c6c3a89"
- integrity sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
+ integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==
+
+opener@~1.4.3:
+ version "1.4.3"
+ resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
+ integrity sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=
-optionator@^0.8.1, optionator@^0.8.3:
+optionator@^0.8.1:
version "0.8.3"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==
type-check "~0.3.2"
word-wrap "~1.2.3"
+optionator@^0.9.1:
+ version "0.9.1"
+ resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
+ integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==
+ dependencies:
+ deep-is "^0.1.3"
+ fast-levenshtein "^2.0.6"
+ levn "^0.4.1"
+ prelude-ls "^1.2.1"
+ type-check "^0.4.0"
+ word-wrap "^1.2.3"
+
options@>=0.0.5:
version "0.0.6"
resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f"
integrity sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=
-os-tmpdir@~1.0.2:
+os-homedir@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
+ integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
+
+os-locale@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2"
+ integrity sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==
+ dependencies:
+ execa "^0.7.0"
+ lcid "^1.0.0"
+ mem "^1.1.0"
+
+os-tmpdir@^1.0.0, os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
+osenv@0, osenv@^0.1.4, osenv@^0.1.5:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
+ integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==
+ dependencies:
+ os-homedir "^1.0.0"
+ os-tmpdir "^1.0.0"
+
p-each-series@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48"
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
-p-finally@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561"
- integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==
-
p-limit@^1.1.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
dependencies:
p-try "^1.0.0"
-p-limit@^2.2.0:
- version "2.2.2"
- resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e"
- integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==
+p-limit@^2.0.0, p-limit@^2.2.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
+ integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
dependencies:
p-try "^2.0.0"
dependencies:
p-limit "^1.1.0"
+p-locate@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
+ integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
+ dependencies:
+ p-limit "^2.0.0"
+
p-locate@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
dependencies:
p-limit "^2.2.0"
-p-map@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
- integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
+p-map@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
+ integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==
+ dependencies:
+ aggregate-error "^3.0.0"
p-try@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
+package-json@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed"
+ integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=
+ dependencies:
+ got "^6.7.1"
+ registry-auth-token "^3.0.1"
+ registry-url "^3.0.3"
+ semver "^5.1.0"
+
+pacote@^7.6.1:
+ version "7.6.1"
+ resolved "https://registry.yarnpkg.com/pacote/-/pacote-7.6.1.tgz#d44621c89a5a61f173989b60236757728387c094"
+ integrity sha512-2kRIsHxjuYC1KRUIK80AFIXKWy0IgtFj76nKcaunozKAOSlfT+DFh3EfeaaKvNHCWixgi0G0rLg11lJeyEnp/Q==
+ dependencies:
+ bluebird "^3.5.1"
+ cacache "^10.0.4"
+ get-stream "^3.0.0"
+ glob "^7.1.2"
+ lru-cache "^4.1.1"
+ make-fetch-happen "^2.6.0"
+ minimatch "^3.0.4"
+ mississippi "^3.0.0"
+ mkdirp "^0.5.1"
+ normalize-package-data "^2.4.0"
+ npm-package-arg "^6.0.0"
+ npm-packlist "^1.1.10"
+ npm-pick-manifest "^2.1.0"
+ osenv "^0.1.5"
+ promise-inflight "^1.0.1"
+ promise-retry "^1.1.1"
+ protoduck "^5.0.0"
+ rimraf "^2.6.2"
+ safe-buffer "^5.1.1"
+ semver "^5.5.0"
+ ssri "^5.2.4"
+ tar "^4.4.0"
+ unique-filename "^1.1.0"
+ which "^1.3.0"
+
+pacote@^8.1.6:
+ version "8.1.6"
+ resolved "https://registry.yarnpkg.com/pacote/-/pacote-8.1.6.tgz#8e647564d38156367e7a9dc47a79ca1ab278d46e"
+ integrity sha512-wTOOfpaAQNEQNtPEx92x9Y9kRWVu45v583XT8x2oEV2xRB74+xdqMZIeGW4uFvAyZdmSBtye+wKdyyLaT8pcmw==
+ dependencies:
+ bluebird "^3.5.1"
+ cacache "^11.0.2"
+ get-stream "^3.0.0"
+ glob "^7.1.2"
+ lru-cache "^4.1.3"
+ make-fetch-happen "^4.0.1"
+ minimatch "^3.0.4"
+ minipass "^2.3.3"
+ mississippi "^3.0.0"
+ mkdirp "^0.5.1"
+ normalize-package-data "^2.4.0"
+ npm-package-arg "^6.1.0"
+ npm-packlist "^1.1.10"
+ npm-pick-manifest "^2.1.0"
+ osenv "^0.1.5"
+ promise-inflight "^1.0.1"
+ promise-retry "^1.1.1"
+ protoduck "^5.0.0"
+ rimraf "^2.6.2"
+ safe-buffer "^5.1.2"
+ semver "^5.5.0"
+ ssri "^6.0.0"
+ tar "^4.4.3"
+ unique-filename "^1.1.0"
+ which "^1.3.0"
+
+parallel-transform@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc"
+ integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==
+ dependencies:
+ cyclist "^1.0.1"
+ inherits "^2.0.3"
+ readable-stream "^2.1.5"
+
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
error-ex "^1.2.0"
parse-json@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f"
- integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.1.tgz#7cfe35c1ccd641bce3981467e6c2ece61b3b3878"
+ integrity sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ==
dependencies:
"@babel/code-frame" "^7.0.0"
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
lines-and-columns "^1.1.6"
-parse5@5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2"
- integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==
+parse5@5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
+ integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==
parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+path-is-inside@^1.0.1, path-is-inside@^1.0.2, path-is-inside@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
+ integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
+
path-key@^2.0.0, path-key@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
-picomatch@^2.0.4:
+picomatch@^2.0.4, picomatch@^2.0.5:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
-picomatch@^2.0.5:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a"
- integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==
-
pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
+pify@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
+ integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
+
pirates@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87"
dependencies:
semver-compare "^1.0.0"
-pn@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
- integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==
+pluralize@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
+ integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
posix-character-classes@^0.1.0:
version "0.1.1"
source-map "^0.6.1"
supports-color "^5.4.0"
+prelude-ls@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
+ integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
+prepend-http@^1.0.1:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
+ integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
+
preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
fast-diff "^1.1.2"
prettier@^2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.4.tgz#2d1bae173e355996ee355ec9830a7a1ee05457ef"
- integrity sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w==
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4"
+ integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==
-pretty-format@^25.2.1, pretty-format@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.4.0.tgz#c58801bb5c4926ff4a677fe43f9b8b99812c7830"
- integrity sha512-PI/2dpGjXK5HyXexLPZU/jw5T9Q6S1YVXxxVxco+LIqzUFHXIbKZKdUVt7GcX7QUCr31+3fzhi4gN4/wUYPVxQ==
+pretty-format@^25.2.1, pretty-format@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
+ integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==
dependencies:
- "@jest/types" "^25.4.0"
+ "@jest/types" "^25.5.0"
+ ansi-regex "^5.0.0"
+ ansi-styles "^4.0.0"
+ react-is "^16.12.0"
+
+pretty-format@^26.1.0:
+ version "26.1.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.1.0.tgz#272b9cd1f1a924ab5d443dc224899d7a65cb96ec"
+ integrity sha512-GmeO1PEYdM+non4BKCj+XsPJjFOJIPnsLewqhDVoqY1xo0yNmDas7tC2XwpMrRAHR3MaE2hPo37deX5OisJ2Wg==
+ dependencies:
+ "@jest/types" "^26.1.0"
ansi-regex "^5.0.0"
ansi-styles "^4.0.0"
react-is "^16.12.0"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
+promise-inflight@^1.0.1, promise-inflight@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
+ integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
+
+promise-retry@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d"
+ integrity sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=
+ dependencies:
+ err-code "^1.0.0"
+ retry "^0.10.0"
+
prompts@^2.0.1:
version "2.3.2"
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068"
kleur "^3.0.3"
sisteransi "^1.0.4"
+promzard@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee"
+ integrity sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=
+ dependencies:
+ read "1"
+
prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
object-assign "^4.1.1"
react-is "^16.8.1"
+proto-list@~1.2.1:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
+ integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
+
+protoduck@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/protoduck/-/protoduck-5.0.1.tgz#03c3659ca18007b69a50fd82a7ebcc516261151f"
+ integrity sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==
+ dependencies:
+ genfun "^5.0.0"
+
proxy-addr@~2.0.5:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34"
- integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
+ integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==
dependencies:
forwarded "~0.1.2"
- ipaddr.js "1.9.0"
+ ipaddr.js "1.9.1"
-psl@^1.1.24:
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c"
- integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==
+prr@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
+ integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY=
+
+pseudomap@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
+ integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
psl@^1.1.28:
version "1.8.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
+pump@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954"
+ integrity sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+pump@^2.0.0, pump@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
+ integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
end-of-stream "^1.1.0"
once "^1.3.1"
-punycode@^1.4.1:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
- integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
+pumpify@^1.3.3:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce"
+ integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==
+ dependencies:
+ duplexify "^3.6.0"
+ inherits "^2.0.3"
+ pump "^2.0.0"
punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
+qrcode-terminal@^0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819"
+ integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==
+
qs@6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
+query-string@^6.1.0:
+ version "6.13.1"
+ resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.1.tgz#d913ccfce3b4b3a713989fe6d39466d92e71ccad"
+ integrity sha512-RfoButmcK+yCta1+FuU8REvisx1oEzhMKwhLUNcepQTPGcNMp1sIqjnfCtfnvGSQZQEhaBHvccujtWoUV3TTbA==
+ dependencies:
+ decode-uri-component "^0.2.0"
+ split-on-first "^1.0.0"
+ strict-uri-encode "^2.0.0"
+
+qw@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/qw/-/qw-1.0.1.tgz#efbfdc740f9ad054304426acb183412cc8b996d4"
+ integrity sha1-77/cdA+a0FQwRCassYNBLMi5ltQ=
+
randomatic@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed"
iconv-lite "0.4.24"
unpipe "1.0.0"
-react-is@^16.12.0:
+rc@^1.0.1, rc@^1.1.6:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
+ integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
+ dependencies:
+ deep-extend "^0.6.0"
+ ini "~1.3.0"
+ minimist "^1.2.0"
+ strip-json-comments "~2.0.1"
+
+react-is@^16.12.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
-react-is@^16.8.1:
- version "16.12.0"
- resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
- integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==
+read-cmd-shim@^1.0.1, read-cmd-shim@~1.0.1:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz#87e43eba50098ba5a32d0ceb583ab8e43b961c16"
+ integrity sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==
+ dependencies:
+ graceful-fs "^4.1.2"
+
+read-installed@~4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/read-installed/-/read-installed-4.0.3.tgz#ff9b8b67f187d1e4c29b9feb31f6b223acd19067"
+ integrity sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=
+ dependencies:
+ debuglog "^1.0.1"
+ read-package-json "^2.0.0"
+ readdir-scoped-modules "^1.0.0"
+ semver "2 || 3 || 4 || 5"
+ slide "~1.1.3"
+ util-extend "^1.0.1"
+ optionalDependencies:
+ graceful-fs "^4.1.2"
+
+"read-package-json@1 || 2", read-package-json@^2.0.0, read-package-json@^2.0.13:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.1.1.tgz#16aa66c59e7d4dad6288f179dd9295fd59bb98f1"
+ integrity sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==
+ dependencies:
+ glob "^7.1.1"
+ json-parse-better-errors "^1.0.1"
+ normalize-package-data "^2.0.0"
+ npm-normalize-package-bin "^1.0.0"
+ optionalDependencies:
+ graceful-fs "^4.1.2"
+
+read-package-tree@^5.2.1:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/read-package-tree/-/read-package-tree-5.3.1.tgz#a32cb64c7f31eb8a6f31ef06f9cedf74068fe636"
+ integrity sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==
+ dependencies:
+ read-package-json "^2.0.0"
+ readdir-scoped-modules "^1.0.0"
+ util-promisify "^2.1.0"
read-pkg-up@^2.0.0:
version "2.0.0"
parse-json "^5.0.0"
type-fest "^0.6.0"
-readable-stream@^2.0.2:
+read@1, read@~1.0.1, read@~1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4"
+ integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=
+ dependencies:
+ mute-stream "~0.0.4"
+
+"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.6, readable-stream@~2.3.6:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
+readable-stream@~1.1.10:
+ version "1.1.14"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
+ integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "0.0.1"
+ string_decoder "~0.10.x"
+
+readdir-scoped-modules@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309"
+ integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==
+ dependencies:
+ debuglog "^1.0.1"
+ dezalgo "^1.0.0"
+ graceful-fs "^4.1.2"
+ once "^1.3.0"
+
readdirp@^2.0.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
app-root-path "^1.3.0"
mkdirp "^0.5.1"
-realpath-native@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-2.0.0.tgz#7377ac429b6e1fd599dc38d08ed942d0d7beb866"
- integrity sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q==
-
reconnecting-websocket@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783"
loose-envify "^1.4.0"
symbol-observable "^1.2.0"
-regenerate-unicode-properties@^8.1.0:
- version "8.1.0"
- resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e"
- integrity sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==
+regenerate-unicode-properties@^8.2.0:
+ version "8.2.0"
+ resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
+ integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==
dependencies:
regenerate "^1.4.0"
regenerate@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
- integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==
-
-regenerator-runtime@^0.13.2:
- version "0.13.3"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
- integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f"
+ integrity sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==
regenerator-runtime@^0.13.4:
- version "0.13.5"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
- integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
+ version "0.13.7"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
+ integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
regex-cache@^0.4.2:
version "0.4.4"
extend-shallow "^3.0.2"
safe-regex "^1.1.0"
-regexp-tree@^0.1.20:
+regexp-tree@^0.1.21, regexp-tree@~0.1.1:
version "0.1.21"
resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.21.tgz#55e2246b7f7d36f1b461490942fa780299c400d7"
integrity sha512-kUUXjX4AnqnR8KRTCrayAo9PzYMRKmVoGgaz2tBuz0MF3g1ZbGebmtW0yFHfFK9CmBjQKeYIgoL22pFLBJY7sw==
-regexp-tree@~0.1.1:
- version "0.1.18"
- resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.18.tgz#ed4819a9f03ec2de9613421d6eaf47512e7fdaf1"
- integrity sha512-mKLUfTDU1GE5jGR7cn2IEPDzYjmOviZOHYAR1XGe8Lg48Mdk684waD1Fqhv2Nef+TsDVdmIj08m/GUKTMk7J2Q==
-
-regexp.prototype.flags@^1.3.0:
+regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75"
integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==
define-properties "^1.1.3"
es-abstract "^1.17.0-next.1"
-regexpp@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
- integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==
-
-regexpp@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.0.0.tgz#dd63982ee3300e67b41c1956f850aa680d9d330e"
- integrity sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==
+regexpp@^3.0.0, regexpp@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2"
+ integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
regexpu-core@^4.1.3:
- version "4.6.0"
- resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6"
- integrity sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==
+ version "4.7.0"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938"
+ integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==
dependencies:
regenerate "^1.4.0"
- regenerate-unicode-properties "^8.1.0"
- regjsgen "^0.5.0"
- regjsparser "^0.6.0"
+ regenerate-unicode-properties "^8.2.0"
+ regjsgen "^0.5.1"
+ regjsparser "^0.6.4"
unicode-match-property-ecmascript "^1.0.4"
- unicode-match-property-value-ecmascript "^1.1.0"
+ unicode-match-property-value-ecmascript "^1.2.0"
-regjsgen@^0.5.0:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c"
- integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==
+registry-auth-token@^3.0.1:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e"
+ integrity sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==
+ dependencies:
+ rc "^1.1.6"
+ safe-buffer "^5.0.1"
-regjsparser@^0.6.0:
- version "0.6.2"
- resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.2.tgz#fd62c753991467d9d1ffe0a9f67f27a529024b96"
- integrity sha512-E9ghzUtoLwDekPT0DYCp+c4h+bvuUpe6rRHCTYn6eGoqj1LgKXxT6I0Il4WbjhQkOghzi/V+y03bPKvbllL93Q==
+registry-url@^3.0.3:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942"
+ integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI=
+ dependencies:
+ rc "^1.0.1"
+
+regjsgen@^0.5.1:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733"
+ integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==
+
+regjsparser@^0.6.4:
+ version "0.6.4"
+ resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272"
+ integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==
dependencies:
jsesc "~0.5.0"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
-request-promise-core@1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9"
- integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==
+request-promise-core@1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f"
+ integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==
dependencies:
- lodash "^4.17.15"
+ lodash "^4.17.19"
-request-promise-native@^1.0.7:
- version "1.0.8"
- resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36"
- integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==
+request-promise-native@^1.0.8:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28"
+ integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==
dependencies:
- request-promise-core "1.1.3"
+ request-promise-core "1.1.4"
stealthy-require "^1.1.1"
tough-cookie "^2.3.3"
-request@^2.79.0:
- version "2.88.0"
- resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
- integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
- dependencies:
- aws-sign2 "~0.7.0"
- aws4 "^1.8.0"
- caseless "~0.12.0"
- combined-stream "~1.0.6"
- extend "~3.0.2"
- forever-agent "~0.6.1"
- form-data "~2.3.2"
- har-validator "~5.1.0"
- http-signature "~1.2.0"
- is-typedarray "~1.0.0"
- isstream "~0.1.2"
- json-stringify-safe "~5.0.1"
- mime-types "~2.1.19"
- oauth-sign "~0.9.0"
- performance-now "^2.1.0"
- qs "~6.5.2"
- safe-buffer "^5.1.2"
- tough-cookie "~2.4.3"
- tunnel-agent "^0.6.0"
- uuid "^3.3.2"
-
-request@^2.88.0:
+request@^2.74.0, request@^2.79.0, request@^2.85.0, request@^2.87.0, request@^2.88.2:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
+require-main-filename@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
+ integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
+
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
-resolve@1.1.7:
- version "1.1.7"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
- integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
-
-resolve@1.x, resolve@^1.3.2:
- version "1.17.0"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
- integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
- dependencies:
- path-parse "^1.0.6"
-
-resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.13.1:
- version "1.15.0"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.0.tgz#1b7ca96073ebb52e741ffd799f6b39ea462c67f5"
- integrity sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==
- dependencies:
- path-parse "^1.0.6"
-
-resolve@^1.15.1:
- version "1.15.1"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8"
- integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==
+resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2:
+ version "1.17.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
+ integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
dependencies:
path-parse "^1.0.6"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
-rimraf@2.6.3:
+retry@^0.10.0:
+ version "0.10.1"
+ resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4"
+ integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=
+
+retry@^0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
+ integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
+
+rimraf@2, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3:
+ version "2.7.1"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
+ integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
+ dependencies:
+ glob "^7.1.3"
+
+rimraf@2.6.3, rimraf@~2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
run-async@^2.2.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
- integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA=
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
+ integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==
+
+run-queue@^1.0.0, run-queue@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
+ integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=
dependencies:
- is-promise "^2.1.0"
+ aproba "^1.1.1"
rx-lite-aggregates@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=
-rxjs@^6.3.3, rxjs@^6.5.3:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
- integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==
- dependencies:
- tslib "^1.9.0"
-
-rxjs@^6.5.5:
- version "6.5.5"
- resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec"
- integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==
+rxjs@^6.5.5, rxjs@^6.6.0:
+ version "6.6.0"
+ resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.0.tgz#af2901eedf02e3a83ffa7f886240ff9018bbec84"
+ integrity sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==
dependencies:
tslib "^1.9.0"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-safe-buffer@^5.0.1, safe-buffer@^5.1.2:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
- integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
+safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-regex@^1.1.0:
version "1.1.0"
dependencies:
regexp-tree "~0.1.1"
-"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
+"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
minimist "^1.1.1"
walker "~1.0.5"
-saxes@^3.1.9:
- version "3.1.11"
- resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b"
- integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==
+saxes@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d"
+ integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==
dependencies:
- xmlchars "^2.1.1"
+ xmlchars "^2.2.0"
semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
+semver-diff@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
+ integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=
+ dependencies:
+ semver "^5.0.3"
+
semver-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338"
integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==
-"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0:
+"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", "semver@^2.3.0 || 3.x || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
-semver@6.x, semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.3.0:
+semver@7.x, semver@^7.2.1, semver@^7.3.2:
+ version "7.3.2"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
+ integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
+
+semver@^6.0.0, semver@^6.1.0, semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
-semver@^7.1.2:
- version "7.1.3"
- resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.3.tgz#e4345ce73071c53f336445cfc19efb1c311df2a6"
- integrity sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==
+semver@~5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
+ integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8=
send@0.17.1:
version "0.17.1"
parseurl "~1.3.3"
send "0.17.1"
-set-blocking@^2.0.0:
+set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
+sha@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/sha/-/sha-2.0.1.tgz#6030822fbd2c9823949f8f72ed6411ee5cf25aae"
+ integrity sha1-YDCCL70smCOUn49y7WQR7lzyWq4=
+ dependencies:
+ graceful-fs "^4.1.2"
+ readable-stream "^2.0.2"
+
+shallowequal@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
+ integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
+
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
es-abstract "^1.17.0-next.1"
object-inspect "^1.7.0"
-signal-exit@^3.0.0:
+signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
-signal-exit@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
- integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
-
sisteransi@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
-slice-ansi@0.0.4:
- version "0.0.4"
- resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
- integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=
-
slice-ansi@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"
astral-regex "^1.0.0"
is-fullwidth-code-point "^2.0.0"
+slice-ansi@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
+ integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==
+ dependencies:
+ ansi-styles "^4.0.0"
+ astral-regex "^2.0.0"
+ is-fullwidth-code-point "^3.0.0"
+
+slice-ansi@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
+ integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
+ dependencies:
+ ansi-styles "^4.0.0"
+ astral-regex "^2.0.0"
+ is-fullwidth-code-point "^3.0.0"
+
+slide@^1.1.3, slide@^1.1.6, slide@~1.1.3, slide@~1.1.6:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
+ integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=
+
+smart-buffer@^1.0.13:
+ version "1.1.15"
+ resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-1.1.15.tgz#7f114b5b65fab3e2a35aa775bb12f0d1c649bf16"
+ integrity sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=
+
+smart-buffer@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba"
+ integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==
+
snapdragon-node@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
source-map-resolve "^0.5.0"
use "^3.1.0"
+socks-proxy-agent@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-3.0.1.tgz#2eae7cf8e2a82d34565761539a7f9718c5617659"
+ integrity sha512-ZwEDymm204mTzvdqyUqOdovVr2YRd2NYskrYrF2LXyZ9qDiMAoFESGK8CRphiO7rtbo2Y757k2Nia3x2hGtalA==
+ dependencies:
+ agent-base "^4.1.0"
+ socks "^1.1.10"
+
+socks-proxy-agent@^4.0.0:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz#3c8991f3145b2799e70e11bd5fbc8b1963116386"
+ integrity sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==
+ dependencies:
+ agent-base "~4.2.1"
+ socks "~2.3.2"
+
+socks@^1.1.10:
+ version "1.1.10"
+ resolved "https://registry.yarnpkg.com/socks/-/socks-1.1.10.tgz#5b8b7fc7c8f341c53ed056e929b7bf4de8ba7b5a"
+ integrity sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o=
+ dependencies:
+ ip "^1.1.4"
+ smart-buffer "^1.0.13"
+
+socks@~2.3.2:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/socks/-/socks-2.3.3.tgz#01129f0a5d534d2b897712ed8aceab7ee65d78e3"
+ integrity sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==
+ dependencies:
+ ip "1.1.5"
+ smart-buffer "^4.1.0"
+
+sorted-object@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/sorted-object/-/sorted-object-2.0.1.tgz#7d631f4bd3a798a24af1dffcfbfe83337a5df5fc"
+ integrity sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw=
+
+sorted-union-stream@~2.1.3:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/sorted-union-stream/-/sorted-union-stream-2.1.3.tgz#c7794c7e077880052ff71a8d4a2dbb4a9a638ac7"
+ integrity sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=
+ dependencies:
+ from2 "^1.3.0"
+ stream-iterate "^1.1.0"
+
sortpack@^2.1.4:
- version "2.1.4"
- resolved "https://registry.yarnpkg.com/sortpack/-/sortpack-2.1.4.tgz#a2e251c5868455135cc41d3c98a53756a6de5282"
- integrity sha512-RGD0l9kGmuPelXMT8WMMiSv1MkUkaqElB39nMkboIaqVkYns1aaNx263B2EE5QzF1YVUOrBlXnQpd7RX68SSow==
+ version "2.1.6"
+ resolved "https://registry.yarnpkg.com/sortpack/-/sortpack-2.1.6.tgz#fe6d1abb094a34d7501d91c17713b7ce659affc6"
+ integrity sha512-TkFfOF7ModMI7nKXprNGPsTZKUdqq5cUGrIIvyvcbxuPuV4dIDzjtRhn7LtYPKOUgmbRhPbAsLBCbOZtBQeQYQ==
source-map-resolve@^0.5.0:
version "0.5.3"
source-map-url "^0.4.0"
urix "^0.1.0"
-source-map-support@^0.5.6, source-map-support@~0.5.12:
- version "0.5.16"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
- integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==
+source-map-support@^0.5.17, source-map-support@^0.5.6, source-map-support@~0.5.12:
+ version "0.5.19"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
+ integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map "^0.7.3"
spdx-correct@^3.0.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4"
- integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
+ integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==
dependencies:
spdx-expression-parse "^3.0.0"
spdx-license-ids "^3.0.0"
spdx-exceptions@^2.1.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977"
- integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d"
+ integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==
spdx-expression-parse@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0"
- integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679"
+ integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==
dependencies:
spdx-exceptions "^2.1.0"
spdx-license-ids "^3.0.0"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654"
integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==
+split-on-first@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f"
+ integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==
+
split-string@^3.0.1, split-string@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
safer-buffer "^2.0.2"
tweetnacl "~0.14.0"
-stack-utils@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8"
- integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==
+ssri@^5.0.0, ssri@^5.2.4, ssri@^5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.3.0.tgz#ba3872c9c6d33a0704a7d71ff045e5ec48999d06"
+ integrity sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==
+ dependencies:
+ safe-buffer "^5.1.1"
+
+ssri@^6.0.0, ssri@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
+ integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
+ dependencies:
+ figgy-pudding "^3.5.1"
+
+stack-utils@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.2.tgz#5cf48b4557becb4638d0bc4f21d23f5d19586593"
+ integrity sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg==
+ dependencies:
+ escape-string-regexp "^2.0.0"
static-extend@^0.1.1:
version "0.1.2"
inherits "~2.0.1"
readable-stream "^2.0.2"
+stream-each@^1.1.0:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae"
+ integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==
+ dependencies:
+ end-of-stream "^1.1.0"
+ stream-shift "^1.0.0"
+
+stream-iterate@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/stream-iterate/-/stream-iterate-1.2.0.tgz#2bd7c77296c1702a46488b8ad41f79865eecd4e1"
+ integrity sha1-K9fHcpbBcCpGSIuK1B95hl7s1OE=
+ dependencies:
+ readable-stream "^2.1.5"
+ stream-shift "^1.0.0"
+
+stream-shift@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
+ integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==
+
+strict-uri-encode@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
+ integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
+
string-argv@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
-string-length@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/string-length/-/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837"
- integrity sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA==
+string-length@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.1.tgz#4a973bf31ef77c4edbceadd6af2611996985f8a1"
+ integrity sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==
dependencies:
- astral-regex "^1.0.0"
- strip-ansi "^5.2.0"
+ char-regex "^1.0.2"
+ strip-ansi "^6.0.0"
string-width@^1.0.1:
version "1.0.2"
is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0"
-string-width@^2.1.0, string-width@^2.1.1:
+"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
-string-width@^3.0.0:
+string-width@^3.0.0, string-width@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
regexp.prototype.flags "^1.3.0"
side-channel "^1.0.2"
-string.prototype.trimleft@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74"
- integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==
+string.prototype.trimend@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913"
+ integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==
dependencies:
define-properties "^1.1.3"
- function-bind "^1.1.1"
+ es-abstract "^1.17.5"
-string.prototype.trimright@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9"
- integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==
+string.prototype.trimstart@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54"
+ integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==
dependencies:
define-properties "^1.1.3"
- function-bind "^1.1.1"
+ es-abstract "^1.17.5"
+
+string_decoder@~0.10.x:
+ version "0.10.31"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+ integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
string_decoder@~1.1.1:
version "1.1.1"
dependencies:
ansi-regex "^2.0.0"
-strip-ansi@^4.0.0:
+strip-ansi@^4.0.0, strip-ansi@~4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
dependencies:
ansi-regex "^3.0.0"
-strip-ansi@^5.1.0, strip-ansi@^5.2.0:
+strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
-strip-json-comments@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
- integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
+strip-json-comments@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+ integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
-supports-color@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
- integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
+strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+ integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
supports-color@^5.3.0, supports-color@^5.4.0:
version "5.5.0"
has-flag "^4.0.0"
supports-color "^7.0.0"
-symbol-observable@^1.1.0, symbol-observable@^1.2.0:
+symbol-observable@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
-symbol-tree@^3.2.2:
+symbol-tree@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
slice-ansi "^2.1.0"
string-width "^3.0.0"
+tar@^2.0.0:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40"
+ integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==
+ dependencies:
+ block-stream "*"
+ fstream "^1.0.12"
+ inherits "2"
+
+tar@^4.4.0, tar@^4.4.2, tar@^4.4.3, tar@^4.4.8:
+ version "4.4.13"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
+ integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==
+ dependencies:
+ chownr "^1.1.1"
+ fs-minipass "^1.2.5"
+ minipass "^2.8.6"
+ minizlib "^1.2.1"
+ mkdirp "^0.5.0"
+ safe-buffer "^5.1.2"
+ yallist "^3.0.3"
+
+term-size@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"
+ integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=
+ dependencies:
+ execa "^0.7.0"
+
terminal-link@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
supports-hyperlinks "^2.0.0"
terser@^4.6.11:
- version "4.6.11"
- resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.11.tgz#12ff99fdd62a26de2a82f508515407eb6ccd8a9f"
- integrity sha512-76Ynm7OXUG5xhOpblhytE7X58oeNSmC8xnNhjWVo8CksHit0U0kO4hfNbPrrYwowLWFgM2n9L176VNx2QaHmtA==
+ version "4.8.0"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17"
+ integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==
dependencies:
commander "^2.20.0"
source-map "~0.6.1"
glob "^7.1.4"
minimatch "^3.0.4"
-text-table@^0.2.0:
+text-table@^0.2.0, text-table@~0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==
-through@^2.3.6:
+through2@^2.0.0:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
+ integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
+ dependencies:
+ readable-stream "~2.3.6"
+ xtend "~4.0.1"
+
+"through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
+timed-out@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
+ integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=
+
tiny-invariant@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
+tiny-relative-date@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07"
+ integrity sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A==
+
tiny-warning@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
tippy.js@^6.1.1:
- version "6.1.1"
- resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.1.1.tgz#9ed09aa4f9c47fb06a0e280e03055f898f5ddfff"
- integrity sha512-Sk+FPihack9XFbPOc2jRbn6iRLA9my2a8qhaGY6wwD3EeW57/xY5PAPkZOutKVYDWLyNZ/laCkJqg7QJG/gqQw==
+ version "6.2.5"
+ resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.2.5.tgz#5335e28228af5e22c524fe5e8a94654514f34b92"
+ integrity sha512-UIf8G99PMXGmdWPrr36s/DjQBdfxMPwzvPUXsxs3tDFDTZ1SgvKG+Jvt6RJ+aBqYL0oe/STxh3MNkCV3IWAKmw==
dependencies:
- "@popperjs/core" "^2.2.0"
+ "@popperjs/core" "^2.4.4"
tmp@^0.0.33:
version "0.0.33"
safe-regex "^1.1.0"
toastify-js@^1.7.0:
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/toastify-js/-/toastify-js-1.7.0.tgz#d6b44937ae2844a19c25fcc69ee5933165dbf666"
- integrity sha512-GmPy4zJ/ulCfmCHlfCtgcB+K2xhx2AXW3T/ZZOSjyjaIGevhz+uvR8HSCTay/wBq4tt2mUnBqlObP1sSWGlsnQ==
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/toastify-js/-/toastify-js-1.9.0.tgz#726963ce850d914ee836951b5e79bdf641fe979e"
+ integrity sha512-v+Y/EUtRNdwtORfIF4oJIZ2BJWTT27Y/83ccVwJLI+wjz+dsyrjdWzC6awhfLWu8KOnfky/ac5tB1sz60fy6sQ==
toidentifier@1.0.0:
version "1.0.0"
psl "^1.1.28"
punycode "^2.1.1"
-tough-cookie@~2.4.3:
- version "2.4.3"
- resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
- integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
- dependencies:
- psl "^1.1.24"
- punycode "^1.4.1"
-
-tr46@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
- integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=
+tr46@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.0.2.tgz#03273586def1595ae08fedb38d7733cee91d2479"
+ integrity sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==
dependencies:
- punycode "^2.1.0"
+ punycode "^2.1.1"
tributejs@^5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/tributejs/-/tributejs-5.1.3.tgz#980600fc72865be5868893078b4bfde721129eae"
integrity sha512-B5CXihaVzXw+1UHhNFyAwUTMDk1EfoLP5Tj1VhD9yybZ1I8DZJEv8tZ1l0RJo0t0tk9ZhR8eG5tEsaCvRigmdQ==
-ts-jest@^25.4.0:
- version "25.4.0"
- resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.4.0.tgz#5ad504299f8541d463a52e93e5e9d76876be0ba4"
- integrity sha512-+0ZrksdaquxGUBwSdTIcdX7VXdwLIlSRsyjivVA9gcO+Cvr6ByqDhu/mi5+HCcb6cMkiQp5xZ8qRO7/eCqLeyw==
+ts-jest@^26.1.3:
+ version "26.1.3"
+ resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.1.3.tgz#aac928a05fdf13e3e6dfbc8caec3847442667894"
+ integrity sha512-beUTSvuqR9SmKQEylewqJdnXWMVGJRFqSz2M8wKJe7GBMmLZ5zw6XXKSJckbHNMxn+zdB3guN2eOucSw2gBMnw==
dependencies:
bs-logger "0.x"
buffer-from "1.x"
fast-json-stable-stringify "2.x"
+ jest-util "26.x"
json5 "2.x"
lodash.memoize "4.x"
make-error "1.x"
- micromatch "4.x"
mkdirp "1.x"
- resolve "1.x"
- semver "6.x"
+ semver "7.x"
yargs-parser "18.x"
ts-node@^8.8.2:
- version "8.8.2"
- resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.8.2.tgz#0b39e690bee39ea5111513a9d2bcdc0bc121755f"
- integrity sha512-duVj6BpSpUpD/oM4MfhO98ozgkp3Gt9qIp3jGxwU2DFvl/3IRaEAvbLa8G60uS7C77457e/m5TMowjedeRxI1Q==
+ version "8.10.2"
+ resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d"
+ integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==
dependencies:
arg "^4.1.0"
diff "^4.0.1"
make-error "^1.1.1"
- source-map-support "^0.5.6"
+ source-map-support "^0.5.17"
yn "3.1.1"
ts-transform-classcat@^1.0.0:
resolved "https://registry.yarnpkg.com/ts-transform-inferno/-/ts-transform-inferno-4.0.3.tgz#2cc0eb125abdaff24b8298106a618ab7c6319edc"
integrity sha512-Pcg0PVQwJ7Fpv4+3R9obFNsrNKQyLbmUqsjeG7T7r4/4UTgIl0MSwurexjtuGpCp2iv2X/i9ffKPAfAOyYJ9og==
+tsconfig-paths@^3.9.0:
+ version "3.9.0"
+ resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b"
+ integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==
+ dependencies:
+ "@types/json5" "^0.0.29"
+ json5 "^1.0.1"
+ minimist "^1.2.0"
+ strip-bom "^3.0.0"
+
tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0:
- version "1.10.0"
- resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
- integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
+ version "1.13.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
+ integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
tsutils@^3.17.1:
version "3.17.1"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
+type-check@^0.4.0, type-check@~0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
+ integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
+ dependencies:
+ prelude-ls "^1.2.1"
+
type-check@~0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
+type-fest@^0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
+ integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
+
type-fest@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
dependencies:
is-typedarray "^1.0.0"
+typedarray@^0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
+ integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
+
typescript@^3.8.3:
- version "3.8.3"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061"
- integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==
+ version "3.9.7"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
+ integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
+uid-number@0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
+ integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=
+
ultron@1.0.x:
version "1.0.2"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
integrity sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=
+umask@^1.1.0, umask@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d"
+ integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=
+
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
unicode-canonical-property-names-ecmascript "^1.0.4"
unicode-property-aliases-ecmascript "^1.0.4"
-unicode-match-property-value-ecmascript@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277"
- integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==
+unicode-match-property-value-ecmascript@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531"
+ integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==
unicode-property-aliases-ecmascript@^1.0.4:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57"
- integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4"
+ integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==
union-value@^1.0.0:
version "1.0.1"
is-extendable "^0.1.1"
set-value "^2.0.1"
+unique-filename@^1.1.0, unique-filename@^1.1.1, unique-filename@~1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
+ integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==
+ dependencies:
+ unique-slug "^2.0.0"
+
+unique-slug@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c"
+ integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==
+ dependencies:
+ imurmurhash "^0.1.4"
+
+unique-string@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a"
+ integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=
+ dependencies:
+ crypto-random-string "^1.0.0"
+
universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
has-value "^0.3.1"
isobject "^3.0.0"
+unzip-response@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
+ integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=
+
+update-notifier@^2.2.0, update-notifier@^2.3.0, update-notifier@^2.5.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6"
+ integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==
+ dependencies:
+ boxen "^1.2.1"
+ chalk "^2.0.1"
+ configstore "^3.0.0"
+ import-lazy "^2.1.0"
+ is-ci "^1.0.10"
+ is-installed-globally "^0.1.0"
+ is-npm "^1.0.0"
+ latest-version "^3.0.0"
+ semver-diff "^2.0.0"
+ xdg-basedir "^3.0.0"
+
uri-js@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
+url-parse-lax@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
+ integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=
+ dependencies:
+ prepend-http "^1.0.1"
+
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
+util-extend@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/util-extend/-/util-extend-1.0.3.tgz#a7c216d267545169637b3b6edc6ca9119e2ff93f"
+ integrity sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=
+
+util-promisify@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/util-promisify/-/util-promisify-2.1.0.tgz#3c2236476c4d32c5ff3c47002add7c13b9a82a53"
+ integrity sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=
+ dependencies:
+ object.getownpropertydescriptors "^2.0.3"
+
utils-extend@^1.0.4, utils-extend@^1.0.6, utils-extend@^1.0.7:
version "1.0.8"
resolved "https://registry.yarnpkg.com/utils-extend/-/utils-extend-1.0.8.tgz#ccfd7b64540f8e90ee21eec57769d0651cab8a5f"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
-uuid@^3.3.2:
+uuid@^3.2.1, uuid@^3.3.2:
version "3.4.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
+uuid@^8.2.0:
+ version "8.2.0"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.2.0.tgz#cb10dd6b118e2dada7d0cd9730ba7417c93d920e"
+ integrity sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==
+
v8-compile-cache@^2.0.3:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
- integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"
+ integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==
v8-to-istanbul@^4.1.3:
- version "4.1.3"
- resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-4.1.3.tgz#22fe35709a64955f49a08a7c7c959f6520ad6f20"
- integrity sha512-sAjOC+Kki6aJVbUOXJbcR0MnbfjvBzwKZazEJymA2IX49uoOdEdk+4fBq5cXgYgiyKtAyrrJNtBZdOeDIF+Fng==
+ version "4.1.4"
+ resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-4.1.4.tgz#b97936f21c0e2d9996d4985e5c5156e9d4e49cd6"
+ integrity sha512-Rw6vJHj1mbdK8edjR7+zuJrpDtKIgNdAvTSAcpYfgMIw+u2dPDntD3dgN4XQFLU2/fvFQdzj+EeSGfd/jnY5fQ==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.1"
convert-source-map "^1.6.0"
source-map "^0.7.3"
-validate-npm-package-license@^3.0.1:
+validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.3:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
+validate-npm-package-name@^3.0.0, validate-npm-package-name@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e"
+ integrity sha1-X6kS2B630MdK/BQN5zF/DKffQ34=
+ dependencies:
+ builtins "^1.0.3"
+
value-equal@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
-w3c-hr-time@^1.0.1:
+w3c-hr-time@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==
dependencies:
browser-process-hrtime "^1.0.0"
-w3c-xmlserializer@^1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794"
- integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==
+w3c-xmlserializer@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a"
+ integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==
dependencies:
- domexception "^1.0.1"
- webidl-conversions "^4.0.2"
xml-name-validator "^3.0.0"
walker@^1.0.7, walker@~1.0.5:
exec-sh "^0.2.0"
minimist "^1.2.0"
-webidl-conversions@^4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
- integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
+wcwidth@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
+ integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=
+ dependencies:
+ defaults "^1.0.3"
+
+webidl-conversions@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
+ integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==
-whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5:
+webidl-conversions@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
+ integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==
+
+whatwg-encoding@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"
integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==
dependencies:
iconv-lite "0.4.24"
-whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0:
+whatwg-mimetype@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
-whatwg-url@^7.0.0:
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
- integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==
+whatwg-url@^8.0.0:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.1.0.tgz#c628acdcf45b82274ce7281ee31dd3c839791771"
+ integrity sha512-vEIkwNi9Hqt4TV9RdnaBPNt+E2Sgmo3gePebCRgZ1R7g6d23+53zCTnuB0amKI4AXq6VM8jj2DUAa0S1vjJxkw==
dependencies:
lodash.sortby "^4.7.0"
- tr46 "^1.0.1"
- webidl-conversions "^4.0.2"
+ tr46 "^2.0.2"
+ webidl-conversions "^5.0.0"
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
-which@^1.2.9, which@^1.3.1:
+which@1, which@^1.2.9, which@^1.3.0, which@^1.3.1, which@~1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
dependencies:
isexe "^2.0.0"
-word-wrap@~1.2.3:
+wide-align@^1.1.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
+ integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
+ dependencies:
+ string-width "^1.0.2 || 2"
+
+widest-line@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc"
+ integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==
+ dependencies:
+ string-width "^2.1.1"
+
+word-wrap@^1.2.3, word-wrap@~1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
-wrap-ansi@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba"
- integrity sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=
+worker-farm@^1.6.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"
+ integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==
dependencies:
- string-width "^2.1.1"
- strip-ansi "^4.0.0"
+ errno "~0.1.7"
+
+wrap-ansi@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
+ integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=
+ dependencies:
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+
+wrap-ansi@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
+ integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==
+ dependencies:
+ ansi-styles "^3.2.0"
+ string-width "^3.0.0"
+ strip-ansi "^5.0.0"
wrap-ansi@^6.2.0:
version "6.2.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
-wrappy@1:
+wrappy@1, wrappy@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+write-file-atomic@^2.0.0, write-file-atomic@^2.3.0:
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481"
+ integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==
+ dependencies:
+ graceful-fs "^4.1.11"
+ imurmurhash "^0.1.4"
+ signal-exit "^3.0.2"
+
write-file-atomic@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
options ">=0.0.5"
ultron "1.0.x"
-ws@^7.0.0, ws@^7.2.3:
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46"
- integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==
+ws@^7.2.3:
+ version "7.3.1"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8"
+ integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==
+
+xdg-basedir@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
+ integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=
xml-name-validator@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
-xmlchars@^2.1.1:
+xmlchars@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
dependencies:
"@babel/runtime-corejs3" "^7.8.3"
+xtend@~4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
+ integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
+
+y18n@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
+ integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
+
y18n@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
+yallist@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
+ integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
+
+yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
+ integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
+
yaml@^1.7.2:
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.7.2.tgz#f26aabf738590ab61efaca502358e48dc9f348b2"
- integrity sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw==
- dependencies:
- "@babel/runtime" "^7.6.3"
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
+ integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
-yargs-parser@18.x, yargs-parser@^18.1.1:
+yargs-parser@18.x, yargs-parser@^18.1.2:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
camelcase "^5.0.0"
decamelize "^1.2.0"
+yargs-parser@^15.0.1:
+ version "15.0.1"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-15.0.1.tgz#54786af40b820dcb2fb8025b11b4d659d76323b3"
+ integrity sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==
+ dependencies:
+ camelcase "^5.0.0"
+ decamelize "^1.2.0"
+
+yargs-parser@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9"
+ integrity sha1-jQrELxbqVd69MyyvTEA4s+P139k=
+ dependencies:
+ camelcase "^4.1.0"
+
+yargs@^14.2.3:
+ version "14.2.3"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.3.tgz#1a1c3edced1afb2a2fea33604bc6d1d8d688a414"
+ integrity sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==
+ dependencies:
+ cliui "^5.0.0"
+ decamelize "^1.2.0"
+ find-up "^3.0.0"
+ get-caller-file "^2.0.1"
+ require-directory "^2.1.1"
+ require-main-filename "^2.0.0"
+ set-blocking "^2.0.0"
+ string-width "^3.0.0"
+ which-module "^2.0.0"
+ y18n "^4.0.0"
+ yargs-parser "^15.0.1"
+
yargs@^15.3.1:
- version "15.3.1"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b"
- integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==
+ version "15.4.1"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
+ integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
dependencies:
cliui "^6.0.0"
decamelize "^1.2.0"
string-width "^4.2.0"
which-module "^2.0.0"
y18n "^4.0.0"
- yargs-parser "^18.1.1"
+ yargs-parser "^18.1.2"
+
+yargs@^8.0.2:
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360"
+ integrity sha1-YpmpBVsc78lp/355wdkY3Osiw2A=
+ dependencies:
+ camelcase "^4.1.0"
+ cliui "^3.2.0"
+ decamelize "^1.1.1"
+ get-caller-file "^1.0.1"
+ os-locale "^2.0.0"
+ read-pkg-up "^2.0.0"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^2.0.0"
+ which-module "^2.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^7.0.0"
yn@3.1.1:
version "3.1.1"