Merge pull request #10 from dessalines/dev

Merge upstream
This commit is contained in:
Richie Zhang 2020-02-03 15:10:39 -08:00 committed by GitHub
commit 1e8b359571
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
80 changed files with 3108 additions and 1324 deletions

31
README.md vendored
View file

@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<a href="" rel="noopener"> <a href="https://dev.lemmy.ml/" rel="noopener">
<img width=200px height=200px src="ui/assets/favicon.svg"></a> <img width=200px height=200px src="ui/assets/favicon.svg"></a>
</p> </p>
@ -113,7 +113,7 @@ docker-compose up -d
and go to http://localhost:8536. and go to http://localhost:8536.
[A sample nginx config](/ansible/templates/nginx.conf), could be setup with: [A sample nginx config](/ansible/templates/nginx.conf) (Image uploading won't work without this), could be setup with:
```bash ```bash
wget https://raw.githubusercontent.com/dessalines/lemmy/master/ansible/templates/nginx.conf wget https://raw.githubusercontent.com/dessalines/lemmy/master/ansible/templates/nginx.conf
@ -159,25 +159,26 @@ Lemmy is free, open-source software, meaning no advertising, monetizing, or vent
## Translations ## Translations
If you'd like to add translations, take a look a look at the [English translation file](ui/src/translations/en.ts). If you'd like to add translations, take a look at the [English translation file](ui/src/translations/en.ts).
- Languages supported: Catalan, (`ca`), English (`en`), Chinese (`zh`), Dutch (`nl`), Esperanto (`eo`), Finnish (`fi`), French (`fr`), Spanish (`es`), Swedish (`sv`), German (`de`), Russian (`ru`), Italian (`it`). - Languages supported: Catalan, (`ca`), Farsi (`fa`), English (`en`), Chinese (`zh`), Dutch (`nl`), Esperanto (`eo`), Finnish (`fi`), French (`fr`), Spanish (`es`), Swedish (`sv`), German (`de`), Russian (`ru`), Italian (`it`).
<!-- translations --> <!-- translations -->
lang | done | missing lang | done | missing
---- | ---- | ------- ---- | ---- | -------
ca | 100% | old ca | 99% | old,time,action
de | 87% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,old,docs,message_sent,messages,old_password,matrix_user_id,private_message_disclaimer,send_notifications_to_email,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message de | 87% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,old,docs,message_sent,messages,old_password,matrix_user_id,private_message_disclaimer,send_notifications_to_email,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message,time,action
eo | 75% | number_of_communities,create_private_message,send_secure_message,send_message,message,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,old,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,donate_to_lemmy,donate,from,are_you_sure,yes,no,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message fa | 72% | cross_post,subscribed_to_communities,trending_communities,create_private_message,send_secure_message,send_message,message,mod,mods,moderates,remove_as_mod,appoint_as_mod,modlog,stickied,ban,ban_from_site,unban,unban_from_site,banned,number_of_subscribers,subscribers,both,saved,unsubscribe,subscribe,subscribed,old,api,docs,inbox,inbox_for,message_sent,notifications_error,messages,no_email_setup,matrix_user_id,private_message_disclaimer,url,body,copy_suggested_title,community,expand_here,subscribe_to_communities,theme,sponsor_message,general_sponsors,joined,by,to,from,landing_0,logged_in,community_moderator_already_exists,community_follower_already_exists,community_user_already_banned,no_slurs,admin_already_created,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message,time,action
es | 100% | old eo | 75% | number_of_communities,create_private_message,send_secure_message,send_message,message,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,old,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,donate_to_lemmy,donate,from,are_you_sure,yes,no,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message,time,action
fi | 100% | old es | 99% | old,time,action
fr | 83% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,archive_link,old,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message fi | 99% | old,time,action
it | 84% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,archive_link,old,docs,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message fr | 82% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,archive_link,old,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message,time,action
nl | 92% | create_private_message,send_secure_message,send_message,message,old,message_sent,messages,matrix_user_id,private_message_disclaimer,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message it | 83% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,archive_link,old,docs,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message,time,action
ru | 72% | cross_posts,cross_post,number_of_communities,create_private_message,send_secure_message,send_message,message,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,old,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,donate_to_lemmy,donate,monero,by,to,from,transfer_community,transfer_site,are_you_sure,yes,no,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message nl | 99% | time,action
sv | 83% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,archive_link,old,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message ru | 71% | cross_posts,cross_post,number_of_communities,create_private_message,send_secure_message,send_message,message,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,old,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,donate_to_lemmy,donate,monero,by,to,from,transfer_community,transfer_site,are_you_sure,yes,no,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message,time,action
zh | 70% | cross_posts,cross_post,users,number_of_communities,create_private_message,send_secure_message,send_message,message,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,old,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,donate_to_lemmy,donate,monero,by,to,from,transfer_community,transfer_site,are_you_sure,yes,no,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message sv | 82% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,archive_link,old,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message,time,action
zh | 69% | cross_posts,cross_post,users,number_of_communities,create_private_message,send_secure_message,send_message,message,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,old,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,donate_to_lemmy,donate,monero,by,to,from,transfer_community,transfer_site,are_you_sure,yes,no,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message,time,action
<!-- translationsstop --> <!-- translationsstop -->
If you'd like to update this report, run: If you'd like to update this report, run:

1
ansible/VERSION vendored Normal file
View file

@ -0,0 +1 @@
v0.6.10

View file

@ -1,6 +1,6 @@
[lemmy] [lemmy]
# define the username and hostname that you use for ssh connection, and specify the domain # define the username and hostname that you use for ssh connection, and specify the domain
myuser@example.com domain=example.com letsencrypt_contact_email=your@email.com smtp_server=smtp@example.com smtp_login=your@email.com smtp_password=pass smtp_from_address="Example.com Admin <notifications@example.com>" myuser@example.com domain=example.com letsencrypt_contact_email=your@email.com
[all:vars] [all:vars]
ansible_connection=ssh ansible_connection=ssh

14
ansible/lemmy.yml vendored
View file

@ -29,15 +29,19 @@
- { path: '/lemmy/' } - { path: '/lemmy/' }
- { path: '/lemmy/volumes/' } - { path: '/lemmy/volumes/' }
- name: add all template files - block:
template: src={{item.src}} dest={{item.dest}} - name: add template files
template: src={{item.src}} dest={{item.dest}} mode={{item.mode}}
with_items: with_items:
- { src: '../docker/prod/docker-compose.yml', dest: '/lemmy/docker-compose.yml' } - { src: 'templates/docker-compose.yml', dest: '/lemmy/docker-compose.yml', mode: '0600' }
- { src: 'templates/config.hjson', dest: '/lemmy/lemmy.hjson' } - { src: 'templates/nginx.conf', dest: '/etc/nginx/sites-enabled/lemmy.conf', mode: '0644' }
- { src: 'templates/nginx.conf', dest: '/etc/nginx/sites-enabled/lemmy.conf' }
- name: add config file (only during initial setup)
template: src='templates/config.hjson' dest='/lemmy/lemmy.hjson' mode='0600' force='no' owner='1000' group='1000'
vars: vars:
postgres_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/postgres chars=ascii_letters,digits') }}" postgres_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/postgres chars=ascii_letters,digits') }}"
jwt_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/jwt chars=ascii_letters,digits') }}" jwt_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/jwt chars=ascii_letters,digits') }}"
lemmy_docker_image: "dessalines/lemmy:{{ lookup('file', 'VERSION') }}"
- name: enable and start docker service - name: enable and start docker service
systemd: systemd:

100
ansible/lemmy_dev.yml vendored Normal file
View file

@ -0,0 +1,100 @@
---
- hosts: all
vars:
lemmy_docker_image: "lemmy:dev"
# Install python if required
# https://www.josharcher.uk/code/ansible-python-connection-failure-ubuntu-server-1604/
gather_facts: False
pre_tasks:
- name: install python for Ansible
raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal python-setuptools)
args:
executable: /bin/bash
register: output
changed_when: output.stdout != ""
- setup: # gather facts
tasks:
- name: install dependencies
apt:
pkg: ['nginx', 'docker-compose', 'docker.io', 'certbot', 'python-certbot-nginx']
- name: request initial letsencrypt certificate
command: certbot certonly --nginx --agree-tos -d '{{ domain }}' -m '{{ letsencrypt_contact_email }}'
args:
creates: '/etc/letsencrypt/live/{{domain}}/privkey.pem'
- name: create lemmy folder
file: path={{item.path}} state=directory
with_items:
- { path: '/lemmy/' }
- { path: '/lemmy/volumes/' }
- block:
- name: add template files
template: src={{item.src}} dest={{item.dest}} mode={{item.mode}}
with_items:
- { src: 'templates/docker-compose.yml', dest: '/lemmy/docker-compose.yml', mode: '0600' }
- { src: 'templates/nginx.conf', dest: '/etc/nginx/sites-enabled/lemmy.conf', mode: '0644' }
- name: add config file (only during initial setup)
template: src='templates/config.hjson' dest='/lemmy/lemmy.hjson' mode='0600' force='no' owner='1000' group='1000'
vars:
postgres_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/postgres chars=ascii_letters,digits') }}"
jwt_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/jwt chars=ascii_letters,digits') }}"
- name: build the dev docker image
local_action: shell cd .. && sudo docker build . -f docker/dev/Dockerfile -t lemmy:dev
register: image_build
- name: find hash of the new docker image
set_fact:
image_hash: "{{ image_build.stdout | regex_search('(?<=Successfully built )[0-9a-f]{12}') }}"
# this does not use become so that the output file is written as non-root user and is easy to delete later
- name: save dev docker image to file
local_action: shell sudo docker save lemmy:dev > lemmy-dev.tar
- name: copy dev docker image to server
copy: src=lemmy-dev.tar dest=/lemmy/lemmy-dev.tar
- name: import docker image
docker_image:
name: lemmy
tag: dev
load_path: /lemmy/lemmy-dev.tar
source: load
force_source: yes
register: image_import
- name: delete remote image file
file: path=/lemmy/lemmy-dev.tar state=absent
- name: delete local image file
local_action: file path=lemmy-dev.tar state=absent
- name: enable and start docker service
systemd:
name: docker
enabled: yes
state: started
# cant pull here because that fails due to lemmy:dev (without dessalines/) not being on docker hub, but that shouldnt
# be a problem for testing
- name: start docker-compose
docker_compose:
project_src: /lemmy/
state: present
recreate: always
ignore_errors: yes
- name: reload nginx with new config
shell: nginx -s reload
- name: certbot renewal cronjob
cron:
special_time=daily
name=certbot-renew-lemmy
user=root
job="certbot certonly --nginx -d '{{ domain }}' --deploy-hook 'docker-compose -f /peertube/docker-compose.yml exec nginx nginx -s reload'"

View file

@ -7,9 +7,8 @@
jwt_secret: "{{ jwt_password }}" jwt_secret: "{{ jwt_password }}"
front_end_dir: "/app/dist" front_end_dir: "/app/dist"
email: { email: {
smtp_server: "{{ smtp_server }}" smtp_server: "postfix:25"
smtp_login: "{{ smtp_login }}" smtp_from_address: "noreply@{{ domain }}"
smtp_password: "{{ smtp_password }}" use_tls: false
smtp_from_address: "{{ smtp_from_address }}"
} }
} }

40
ansible/templates/docker-compose.yml vendored Normal file
View file

@ -0,0 +1,40 @@
version: '3.3'
services:
lemmy:
image: {{ lemmy_docker_image }}
ports:
- "127.0.0.1:8536:8536"
restart: always
volumes:
- ./lemmy.hjson:/config/config.hjson:ro
depends_on:
- lemmy_db
- lemmy_pictshare
lemmy_db:
image: postgres:12-alpine
environment:
- POSTGRES_USER=lemmy
- POSTGRES_PASSWORD={{ postgres_password }}
- POSTGRES_DB=lemmy
volumes:
- lemmy_db:/var/lib/postgresql/data
restart: always
lemmy_pictshare:
image: shtripok/pictshare:latest
ports:
- "127.0.0.1:8537:80"
volumes:
- lemmy_pictshare:/usr/share/nginx/html/data
restart: always
postfix:
image: mwader/postfix-relay
environment:
- POSTFIX_myhostname={{ domain }}
restart: "always"
volumes:
lemmy_db:
lemmy_pictshare:

View file

@ -93,4 +93,4 @@ map $remote_addr $remote_addr_anon {
} }
log_format main '$remote_addr_anon - $remote_user [$time_local] "$request" ' log_format main '$remote_addr_anon - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent"'; '$status $body_bytes_sent "$http_referer" "$http_user_agent"';
access_log /dev/stdout main; access_log /var/log/nginx/access.log main;

48
ansible/uninstall.yml vendored Normal file
View file

@ -0,0 +1,48 @@
---
- hosts: all
vars_prompt:
- name: confirm_uninstall
prompt: "Do you really want to uninstall Lemmy? This will delete all data and can not be reverted [yes/no]"
private: no
- name: delete_certs
prompt: "Delete certificates? Select 'no' if you want to reinstall Lemmy [yes/no]"
private: no
tasks:
- name: end play if no confirmation was given
debug:
msg: "Uninstall cancelled, doing nothing"
when: not confirm_uninstall|bool
- meta: end_play
when: not confirm_uninstall|bool
- name: stop docker-compose
docker_compose:
project_src: /lemmy/
state: absent
- name: delete data
file: path={{item.path}} state=absent
with_items:
- { path: '/lemmy/' }
- { path: '/etc/nginx/sites-enabled/lemmy.conf' }
- name: Remove a volume
docker_volume: name={{item.name}} state=absent
with_items:
- { name: 'lemmy_lemmy_db' }
- { name: 'lemmy_lemmy_pictshare' }
- name: delete entire ecloud folder
file: path='/mnt/repo-base/' state=absent
when: delete_certs|bool
- name: remove certbot cronjob
cron:
name=certbot-renew-lemmy
state=absent

View file

@ -20,10 +20,11 @@ COPY ui /app/ui
RUN yarn build RUN yarn build
FROM rust:1.37 as rust FROM rust:1.40 as rust
# Cache deps # Cache deps
WORKDIR /app WORKDIR /app
RUN USER=root cargo new server RUN USER=root cargo new server
WORKDIR /app/server WORKDIR /app/server
COPY server/Cargo.toml server/Cargo.lock ./ COPY server/Cargo.toml server/Cargo.lock ./
@ -31,24 +32,35 @@ RUN mkdir -p ./src/bin \
&& echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs && echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs
#RUN cargo build --release RUN cargo build --release
RUN cargo build && \ #RUN cargo build && \
rm -f ./target/release/deps/lemmy_server* ; rm -f ./target/debug/deps/lemmy_server* # rm -f ./target/release/deps/lemmy_server* ; rm -f ./target/debug/deps/lemmy_server*
COPY server/src ./src/ COPY server/src ./src/
COPY server/migrations ./migrations/ COPY server/migrations ./migrations/
# build for release # build for release
# workaround for https://github.com/rust-lang/rust/issues/62896 # workaround for https://github.com/rust-lang/rust/issues/62896
#RUN RUSTFLAGS='-Ccodegen-units=1' cargo build --release #RUN RUSTFLAGS='-Ccodegen-units=1' cargo build --release
#RUN cargo build --release --frozen RUN cargo build --release --frozen
RUN cargo build --frozen #RUN cargo build --frozen
# Get diesel-cli on there just in case # Get diesel-cli on there just in case
# RUN cargo install diesel_cli --no-default-features --features postgres # RUN cargo install diesel_cli --no-default-features --features postgres
# make result place always the same for lemmy container # make result place always the same for lemmy container
#RUN cp /app/server/target/release/lemmy_server /app/server/ready RUN cp /app/server/target/release/lemmy_server /app/server/ready
RUN cp /app/server/target/debug/lemmy_server /app/server/ready #RUN cp /app/server/target/debug/lemmy_server /app/server/ready
FROM rust:1.40 as docs
WORKDIR /app
# Build docs
COPY docs ./docs
RUN cargo install mdbook
RUN mdbook build docs/
#FROM alpine:3.10 #FROM alpine:3.10
@ -66,8 +78,9 @@ RUN adduser --disabled-password --shell /bin/sh --uid 1000 --ingroup lemmy lemmy
# Copy resources # Copy resources
COPY server/config/defaults.hjson /config/defaults.hjson COPY server/config/defaults.hjson /config/defaults.hjson
COPY --from=rust /app/server/ready /app/lemmy
COPY --from=node /app/ui/dist /app/dist COPY --from=node /app/ui/dist /app/dist
COPY --from=docs /app/docs/book/ /app/dist/documentation/
COPY --from=rust /app/server/ready /app/lemmy
RUN chown lemmy:lemmy /app/lemmy RUN chown lemmy:lemmy /app/lemmy
USER lemmy USER lemmy

View file

@ -14,12 +14,17 @@ git add "ui/src/version.ts"
# Setting the version on the backend # Setting the version on the backend
echo "pub const VERSION: &str = \"$(git describe --tags)\";" > "server/src/version.rs" echo "pub const VERSION: &str = \"$(git describe --tags)\";" > "server/src/version.rs"
git add "server/src/version.rs" git add "server/src/version.rs"
# Setting the version for Ansible
git describe --tags > "ansible/VERSION"
git add "ansible/VERSION"
cd docker/dev cd docker/dev
# Changing the docker-compose prod # 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/" ../prod/docker-compose.yml
sed -i "s/dessalines\/lemmy:.*/dessalines\/lemmy:$new_tag/" ../../ansible/templates/docker-compose.yml
git add ../prod/docker-compose.yml git add ../prod/docker-compose.yml
git add ../../ansible/templates/docker-compose.yml
# The commit # The commit
git commit -m"Version $new_tag" git commit -m"Version $new_tag"

View file

@ -11,7 +11,7 @@ services:
- lemmy_db:/var/lib/postgresql/data - lemmy_db:/var/lib/postgresql/data
restart: always restart: always
lemmy: lemmy:
image: dessalines/lemmy:v0.6.7 image: dessalines/lemmy:v0.6.10
ports: ports:
- "127.0.0.1:8536:8536" - "127.0.0.1:8536:8536"
restart: always restart: always

409
server/Cargo.lock generated vendored
View file

@ -10,7 +10,7 @@ dependencies = [
"activitystreams-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "activitystreams-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -29,7 +29,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -44,7 +44,7 @@ dependencies = [
"mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -158,10 +158,10 @@ dependencies = [
"mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -185,7 +185,7 @@ dependencies = [
"bytestring 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "bytestring 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -315,9 +315,9 @@ dependencies = [
"mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -378,6 +378,11 @@ dependencies = [
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "anyhow"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "arc-swap" name = "arc-swap"
version = "0.4.4" version = "0.4.4"
@ -420,6 +425,11 @@ name = "autocfg"
version = "0.1.7" version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "awc" name = "awc"
version = "1.0.1" version = "1.0.1"
@ -436,9 +446,9 @@ dependencies = [
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -493,7 +503,7 @@ dependencies = [
"blowfish 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "blowfish 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -561,6 +571,11 @@ name = "bufstream"
version = "0.1.4" version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bumpalo"
version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "byte-tools" name = "byte-tools"
version = "0.3.1" version = "0.3.1"
@ -607,8 +622,8 @@ name = "chrono"
version = "0.4.10" version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -631,7 +646,7 @@ dependencies = [
"rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"serde-hjson 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde-hjson 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
"yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -683,68 +698,57 @@ dependencies = [
[[package]] [[package]]
name = "darling" name = "darling"
version = "0.9.0" version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"darling_macro 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "darling_core" name = "darling_core"
version = "0.9.0" version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "darling_macro" name = "darling_macro"
version = "0.9.0" version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "derive_builder" name = "derive_builder"
version = "0.7.2" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"derive_builder_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "derive_builder_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "derive_builder_core" name = "derive_builder_core"
version = "0.5.0" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "derive_more"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -926,7 +930,7 @@ dependencies = [
"atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -1254,17 +1258,24 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "jsonwebtoken" name = "js-sys"
version = "6.0.1" version = "0.3.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", ]
"ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)",
[[package]]
name = "jsonwebtoken"
version = "7.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pem 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ring 0.16.9 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "simple_asn1 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -1311,16 +1322,16 @@ dependencies = [
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"hjson 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "hjson 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonwebtoken 6.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonwebtoken 7.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lettre_email 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "lettre_email 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rss 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "rss 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
"sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strum 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)", "strum 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strum_macros 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)", "strum_macros 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -1339,7 +1350,7 @@ dependencies = [
"nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -1560,12 +1571,22 @@ dependencies = [
] ]
[[package]] [[package]]
name = "num-integer" name = "num-bigint"
version = "0.1.41" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-integer"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -1573,15 +1594,15 @@ name = "num-traits"
version = "0.1.43" version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.10" version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -1650,6 +1671,16 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "pem"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.1.0"
@ -1716,14 +1747,6 @@ name = "proc-macro-nested"
version = "0.1.3" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc-macro2"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.7" version = "1.0.7"
@ -1739,24 +1762,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.14.0" version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"derive_more 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
"encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)", "encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "quote"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.2" version = "1.0.2"
@ -1807,7 +1819,7 @@ dependencies = [
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.7.2" version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1947,13 +1959,13 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.3.1" version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -1963,7 +1975,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.6.12" version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@ -1985,25 +1997,25 @@ dependencies = [
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.14.6" version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", "cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"web-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "rss" name = "rss"
version = "1.8.0" version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"derive_builder 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "derive_builder 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)",
"quick-xml 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -2114,7 +2126,7 @@ dependencies = [
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -2126,7 +2138,7 @@ dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -2153,7 +2165,7 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.44" version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2188,7 +2200,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.8.0" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2206,6 +2218,16 @@ dependencies = [
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "simple_asn1"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
"num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.2" version = "0.4.2"
@ -2227,6 +2249,11 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "sourcefile"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "spin" name = "spin"
version = "0.5.2" version = "0.5.2"
@ -2244,7 +2271,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.7.0" version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@ -2263,16 +2290,6 @@ dependencies = [
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "syn"
version = "0.15.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.13" version = "1.0.13"
@ -2301,7 +2318,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
"remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2352,7 +2369,7 @@ dependencies = [
[[package]] [[package]]
name = "thread_local" name = "thread_local"
version = "0.3.6" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2429,7 +2446,7 @@ dependencies = [
"idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", "socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2488,11 +2505,6 @@ name = "unicode-segmentation"
version = "1.6.0" version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.0" version = "0.2.0"
@ -2500,7 +2512,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.6.2" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@ -2574,6 +2586,90 @@ name = "wasi"
version = "0.7.0" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "wasm-bindgen"
version = "0.2.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-macro 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bumpalo 3.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-shared 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-macro-support 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-backend 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-shared 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "wasm-bindgen-webidl"
version = "0.2.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-backend 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
"weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "web-sys"
version = "0.3.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
"js-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
"sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-webidl 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "weedle"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "widestring" name = "widestring"
version = "0.4.0" version = "0.4.0"
@ -2684,12 +2780,14 @@ dependencies = [
"checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" "checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2"
"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
"checksum anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c"
"checksum arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff" "checksum arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff"
"checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
"checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" "checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
"checksum async-trait 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "c8df72488e87761e772f14ae0c2480396810e51b2c2ade912f97f0f7e5b95e3c" "checksum async-trait 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "c8df72488e87761e772f14ae0c2480396810e51b2c2ade912f97f0f7e5b95e3c"
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
"checksum awc 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d7601d4d1d7ef2335d6597a41b5fe069f6ab799b85f53565ab390e7b7065aac5" "checksum awc 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d7601d4d1d7ef2335d6597a41b5fe069f6ab799b85f53565ab390e7b7065aac5"
"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" "checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea"
"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" "checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
@ -2705,6 +2803,7 @@ dependencies = [
"checksum brotli-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" "checksum brotli-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd"
"checksum brotli2 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" "checksum brotli2 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e"
"checksum bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" "checksum bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
"checksum bumpalo 3.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5fb8038c1ddc0a5f73787b130f4cc75151e96ed33e417fde765eb5a81e3532f4"
"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
"checksum bytes 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10004c15deb332055f7a4a208190aed362cf9a7c2f6ab70a305fba50e1105f38" "checksum bytes 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10004c15deb332055f7a4a208190aed362cf9a7c2f6ab70a305fba50e1105f38"
@ -2721,12 +2820,11 @@ dependencies = [
"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
"checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c" "checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c"
"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" "checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4"
"checksum darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fcfbcb0c5961907597a7d1148e3af036268f2b773886b8bb3eeb1e1281d3d3d6" "checksum darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
"checksum darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6afc018370c3bff3eb51f89256a6bdb18b4fdcda72d577982a14954a7a0b402c" "checksum darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
"checksum darling_macro 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d8dac1c6f1d29a41c4712b4400f878cb4fcc4c7628f298dd75038e024998d1" "checksum darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
"checksum derive_builder 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ac53fa6a3cda160df823a9346442525dcaf1e171999a1cf23e67067e4fd64d4" "checksum derive_builder 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
"checksum derive_builder_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0288a23da9333c246bb18c143426074a6ae96747995c5819d2947b64cd942b37" "checksum derive_builder_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
"checksum derive_more 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6d944ac6003ed268757ef1ee686753b57efc5fcf0ebe7b64c9fc81e7e32ff839"
"checksum derive_more 0.99.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2159be042979966de68315bce7034bb000c775f22e3e834e1c52ff78f041cae8" "checksum derive_more 0.99.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2159be042979966de68315bce7034bb000c775f22e3e834e1c52ff78f041cae8"
"checksum diesel 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9d7cc03b910de9935007861dce440881f69102aaaedfd4bc5a6f40340ca5840c" "checksum diesel 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9d7cc03b910de9935007861dce440881f69102aaaedfd4bc5a6f40340ca5840c"
"checksum diesel_derives 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" "checksum diesel_derives 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3"
@ -2787,7 +2885,8 @@ dependencies = [
"checksum ipconfig 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa79fa216fbe60834a9c0737d7fcd30425b32d1c58854663e24d4c4b328ed83f" "checksum ipconfig 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa79fa216fbe60834a9c0737d7fcd30425b32d1c58854663e24d4c4b328ed83f"
"checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1" "checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1"
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
"checksum jsonwebtoken 6.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a81d1812d731546d2614737bee92aa071d37e9afa1409bc374da9e5e70e70b22" "checksum js-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "7889c7c36282151f6bf465be4700359318aef36baa951462382eae49e9577cf9"
"checksum jsonwebtoken 7.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7577c6272114f9a75da574d2497509d7a73c25cd005a2df35e4a1845a6522ea4"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
@ -2817,9 +2916,10 @@ dependencies = [
"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" "checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
"checksum nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c618b63422da4401283884e6668d39f819a106ef51f5f59b81add00075da35ca" "checksum nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c618b63422da4401283884e6668d39f819a106ef51f5f59b81add00075da35ca"
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" "checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" "checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72" "checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72"
"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
"checksum openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585" "checksum openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585"
@ -2827,6 +2927,7 @@ dependencies = [
"checksum openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)" = "465d16ae7fc0e313318f7de5cecf57b2fbe7511fd213978b457e1c96ff46736f" "checksum openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)" = "465d16ae7fc0e313318f7de5cecf57b2fbe7511fd213978b457e1c96ff46736f"
"checksum parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" "checksum parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc"
"checksum parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" "checksum parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1"
"checksum pem 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a1581760c757a756a41f0ee3ff01256227bdf64cb752839779b95ffb01c59793"
"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
"checksum pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "94b90146c7216e4cb534069fb91366de4ea0ea353105ee45ed297e2d1619e469" "checksum pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "94b90146c7216e4cb534069fb91366de4ea0ea353105ee45ed297e2d1619e469"
"checksum pin-project-internal 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "44ca92f893f0656d3cba8158dd0f2b99b94de256a4a54e870bd6922fcc6c8355" "checksum pin-project-internal 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "44ca92f893f0656d3cba8158dd0f2b99b94de256a4a54e870bd6922fcc6c8355"
@ -2837,16 +2938,14 @@ dependencies = [
"checksum pq-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac25eee5a0582f45a67e837e350d784e7003bd29a5f460796772061ca49ffda" "checksum pq-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac25eee5a0582f45a67e837e350d784e7003bd29a5f460796772061ca49ffda"
"checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" "checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5"
"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" "checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e"
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
"checksum proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0319972dcae462681daf4da1adeeaa066e3ebd29c69be96c6abb1259d2ee2bcc" "checksum proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0319972dcae462681daf4da1adeeaa066e3ebd29c69be96c6abb1259d2ee2bcc"
"checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" "checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
"checksum quick-xml 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0a8b2062cd4735d683121dbd525f5961226936229b0ac6bbbc40b34155744a41" "checksum quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fe1e430bdcf30c9fdc25053b9c459bb1a4672af4617b6c783d7d91dc17c6bbb0"
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af" "checksum r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af"
"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" "checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
@ -2862,13 +2961,13 @@ dependencies = [
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" "checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
"checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" "checksum regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8"
"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" "checksum regex-syntax 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)" = "b28dfe3fe9badec5dbf0a79a9cccad2cfc2ab5484bdb3e44cbd1ae8b3ba2be06"
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
"checksum resolv-conf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b263b4aa1b5de9ffc0054a2386f96992058bb6870aab516f8cdeb8a667d56dcb" "checksum resolv-conf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b263b4aa1b5de9ffc0054a2386f96992058bb6870aab516f8cdeb8a667d56dcb"
"checksum ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)" = "426bc186e3e95cac1e4a4be125a4aca7e84c2d616ffc02244eef36e2a60a093c" "checksum ring 0.16.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6747f8da1f2b1fabbee1aaa4eb8a11abf9adef0bf58a41cee45db5d59cecdfac"
"checksum rss 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0706a43e890fbaf1714d495d12f69a7b34b70c6e903586d70311c2ce15ffe67" "checksum rss 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "99979205510c60f80a119dedbabd0b8426517384edf205322f8bcd51796bcef9"
"checksum rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" "checksum rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2"
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
@ -2888,22 +2987,23 @@ dependencies = [
"checksum serde-hjson 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" "checksum serde-hjson 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8"
"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" "checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
"checksum serde_json 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)" = "67f7d2e9edc3523a9c8ec8cd6ec481b3a27810aafee3e625d311febd3e656b4c" "checksum serde_json 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)" = "67f7d2e9edc3523a9c8ec8cd6ec481b3a27810aafee3e625d311febd3e656b4c"
"checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" "checksum serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "eab8f15f15d6c41a154c1b128a22f2dfabe350ef53c40953d84e36155c91192b"
"checksum serde_test 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" "checksum serde_test 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5"
"checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" "checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97"
"checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" "checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" "checksum sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0"
"checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" "checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41"
"checksum simple_asn1 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b25ecba7165254f0c97d6c22a64b1122a03634b18d20a34daf21e18f892e618"
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
"checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4" "checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4"
"checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" "checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85"
"checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3"
"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" "checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" "checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
"checksum strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "67f84c44fbb2f91db7fef94554e6b2ac05909c9c0b0bc23bb98d3a1aebfe7f7c" "checksum strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "67f84c44fbb2f91db7fef94554e6b2ac05909c9c0b0bc23bb98d3a1aebfe7f7c"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" "checksum strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
"checksum strum 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)" = "530efb820d53b712f4e347916c5e7ed20deb76a4f0457943b3182fb889b06d2c" "checksum strum 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)" = "530efb820d53b712f4e347916c5e7ed20deb76a4f0457943b3182fb889b06d2c"
"checksum strum_macros 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5e6e163a520367c465f59e0a61a23cfae3b10b6546d78b6f672a382be79f7110" "checksum strum_macros 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5e6e163a520367c465f59e0a61a23cfae3b10b6546d78b6f672a382be79f7110"
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
"checksum syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4ff033220a41d1a57d8125eab57bf5263783dfdcc18688b1dacc6ce9651ef8" "checksum syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4ff033220a41d1a57d8125eab57bf5263783dfdcc18688b1dacc6ce9651ef8"
"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" "checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
@ -2912,7 +3012,7 @@ dependencies = [
"checksum thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef" "checksum thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef"
"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
"checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
"checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865" "checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865"
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
"checksum tokio 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ffa2fdcfa937b20cb3c822a635ceecd5fc1a27a6a474527e5516aa24b8c8820a" "checksum tokio 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ffa2fdcfa937b20cb3c822a635ceecd5fc1a27a6a474527e5516aa24b8c8820a"
@ -2925,9 +3025,8 @@ dependencies = [
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
"checksum unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b561e267b2326bb4cebfc0ef9e68355c7abe6c6f522aeac2f5bf95d56c59bdcf" "checksum unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b561e267b2326bb4cebfc0ef9e68355c7abe6c6f522aeac2f5bf95d56c59bdcf"
"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" "checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f" "checksum untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60369ef7a31de49bcb3f6ca728d4ba7300d9a1658f94c727d4cab8c8d9f4aece"
"checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" "checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61"
"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
"checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
@ -2938,6 +3037,14 @@ dependencies = [
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" "checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" "checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d"
"checksum wasm-bindgen 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "5205e9afdf42282b192e2310a5b463a6d1c1d774e30dc3c791ac37ab42d2616c"
"checksum wasm-bindgen-backend 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "11cdb95816290b525b32587d76419facd99662a07e59d3cdb560488a819d9a45"
"checksum wasm-bindgen-macro 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "574094772ce6921576fb6f2e3f7497b8a76273b6db092be18fc48a082de09dc3"
"checksum wasm-bindgen-macro-support 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "e85031354f25eaebe78bb7db1c3d86140312a911a106b2e29f9cc440ce3e7668"
"checksum wasm-bindgen-shared 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "f5e7e61fc929f4c0dddb748b102ebf9f632e2b8d739f2016542b4de2965a9601"
"checksum wasm-bindgen-webidl 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "ef012a0d93fc0432df126a8eaf547b2dce25a8ce9212e1d3cbeef5c11157975d"
"checksum web-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "aaf97caf6aa8c2b1dac90faf0db529d9d63c93846cca4911856f78a83cebf53b"
"checksum weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164"
"checksum widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effc0e4ff8085673ea7b9b2e3c73f6bd4d118810c9009ed8f1e16bd96c331db6" "checksum widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effc0e4ff8085673ea7b9b2e3c73f6bd4d118810c9009ed8f1e16bd96c331db6"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"

12
server/Cargo.toml vendored
View file

@ -12,7 +12,7 @@ bcrypt = "0.6.1"
activitypub = "0.2.0" activitypub = "0.2.0"
chrono = { version = "0.4.7", features = ["serde"] } chrono = { version = "0.4.7", features = ["serde"] }
failure = "0.1.5" failure = "0.1.5"
serde_json = { version = "1.0.40", features = ["preserve_order"]} serde_json = { version = "1.0.45", features = ["preserve_order"]}
serde = { version = "1.0.94", features = ["derive"] } serde = { version = "1.0.94", features = ["derive"] }
actix = "0.9.0" actix = "0.9.0"
actix-web = "2.0.0" actix-web = "2.0.0"
@ -20,16 +20,16 @@ actix-files = "0.2.1"
actix-web-actors = "2.0.0" actix-web-actors = "2.0.0"
actix-rt = "1.0.0" actix-rt = "1.0.0"
env_logger = "0.7.1" env_logger = "0.7.1"
rand = "0.7.0" rand = "0.7.3"
strum = "0.17.1" strum = "0.17.1"
strum_macros = "0.17.1" strum_macros = "0.17.1"
jsonwebtoken = "6.0.1" jsonwebtoken = "7.0.1"
regex = "1.1.9" regex = "1.3.4"
lazy_static = "1.3.0" lazy_static = "1.3.0"
lettre = "0.9.2" lettre = "0.9.2"
lettre_email = "0.9.2" lettre_email = "0.9.2"
sha2 = "0.8.0" sha2 = "0.8.1"
rss = "1.8.0" rss = "1.9.0"
htmlescape = "0.3.1" htmlescape = "0.3.1"
config = "0.10.1" config = "0.10.1"
hjson = "0.8.2" hjson = "0.8.2"

View file

@ -0,0 +1,2 @@
drop index idx_user_name_lower;
drop index idx_user_email_lower;

View file

@ -0,0 +1,29 @@
-- Add case insensitive username and email uniqueness
-- An example of showing the dupes:
-- select
-- max(id) as id,
-- lower(name) as lname,
-- count(*)
-- from user_
-- group by lower(name)
-- having count(*) > 1;
-- Delete username dupes, keeping the first one
delete
from user_
where id not in (
select min(id)
from user_
group by lower(name), lower(fedi_name)
);
-- The user index
create unique index idx_user_name_lower on user_ (lower(name));
-- Email lower
create unique index idx_user_email_lower on user_ (lower(email));
-- Set empty emails properly to null
update user_ set email = null where email = '';

View file

@ -1,4 +1,5 @@
#!/bin/sh #!/bin/bash
set -e
declare -a arr=( declare -a arr=(
"https://mastodon.social/" "https://mastodon.social/"

34
server/query_testing/api_benchmark.sh vendored Executable file
View file

@ -0,0 +1,34 @@
#!/bin/bash
set -e
# By default, this script runs against `http://127.0.0.1:8536`, but you can pass a different Lemmy instance,
# eg `./api_benchmark.sh "https://example.com"`.
DOMAIN=${1:-"http://127.0.0.1:8536"}
declare -a arr=(
"/api/v1/site"
"/api/v1/categories"
"/api/v1/modlog"
"/api/v1/search?q=test&type_=Posts&sort=Hot"
"/api/v1/community"
"/api/v1/community/list?sort=Hot"
"/api/v1/post/list?sort=Hot&type_=All"
)
## now loop through the above array
for path in "${arr[@]}"
do
URL="$DOMAIN$path"
printf "\n\n\n"
echo "testing $URL"
curl --show-error --fail --silent "$URL" >/dev/null
ab -c 64 -t 10 "$URL" > out.abtest
grep "Server Hostname:" out.abtest
grep "Document Path:" out.abtest
grep "Requests per second" out.abtest
grep "(mean, across all concurrent requests)" out.abtest
grep "Transfer rate:" out.abtest
echo "---"
done
rm *.abtest

View file

@ -1,4 +1,5 @@
#!/bin/sh #!/bin/bash
set -e
# Do the views first # Do the views first

View file

@ -36,6 +36,7 @@ pub struct SaveComment {
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
pub struct CommentResponse { pub struct CommentResponse {
pub comment: CommentView, pub comment: CommentView,
pub recipient_ids: Vec<i32>,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -88,6 +89,8 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
Err(_e) => return Err(APIError::err("couldnt_create_comment").into()), Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
}; };
let mut recipient_ids = Vec::new();
// Scan the comment for user mentions, add those rows // Scan the comment for user mentions, add those rows
let extracted_usernames = extract_usernames(&comment_form.content); let extracted_usernames = extract_usernames(&comment_form.content);
@ -97,6 +100,8 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
// At some point, make it so you can't tag the parent creator either // At some point, make it so you can't tag the parent creator either
// This can cause two notifications, one for reply and the other for mention // This can cause two notifications, one for reply and the other for mention
if mention_user.id != user_id { if mention_user.id != user_id {
recipient_ids.push(mention_user.id);
let user_mention_form = UserMentionForm { let user_mention_form = UserMentionForm {
recipient_id: mention_user.id, recipient_id: mention_user.id,
comment_id: inserted_comment.id, comment_id: inserted_comment.id,
@ -138,6 +143,8 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
let parent_comment = Comment::read(&conn, parent_id)?; let parent_comment = Comment::read(&conn, parent_id)?;
if parent_comment.creator_id != user_id { if parent_comment.creator_id != user_id {
let parent_user = User_::read(&conn, parent_comment.creator_id)?; let parent_user = User_::read(&conn, parent_comment.creator_id)?;
recipient_ids.push(parent_user.id);
if parent_user.send_notifications_to_email { if parent_user.send_notifications_to_email {
if let Some(comment_reply_email) = parent_user.email { if let Some(comment_reply_email) = parent_user.email {
let subject = &format!( let subject = &format!(
@ -161,6 +168,8 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
None => { None => {
if post.creator_id != user_id { if post.creator_id != user_id {
let parent_user = User_::read(&conn, post.creator_id)?; let parent_user = User_::read(&conn, post.creator_id)?;
recipient_ids.push(parent_user.id);
if parent_user.send_notifications_to_email { if parent_user.send_notifications_to_email {
if let Some(post_reply_email) = parent_user.email { if let Some(post_reply_email) = parent_user.email {
let subject = &format!( let subject = &format!(
@ -199,6 +208,7 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
Ok(CommentResponse { Ok(CommentResponse {
comment: comment_view, comment: comment_view,
recipient_ids,
}) })
} }
} }
@ -265,6 +275,8 @@ impl Perform<CommentResponse> for Oper<EditComment> {
Err(_e) => return Err(APIError::err("couldnt_update_comment").into()), Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
}; };
let mut recipient_ids = Vec::new();
// Scan the comment for user mentions, add those rows // Scan the comment for user mentions, add those rows
let extracted_usernames = extract_usernames(&comment_form.content); let extracted_usernames = extract_usernames(&comment_form.content);
@ -278,6 +290,8 @@ impl Perform<CommentResponse> for Oper<EditComment> {
// At some point, make it so you can't tag the parent creator either // At some point, make it so you can't tag the parent creator either
// This can cause two notifications, one for reply and the other for mention // This can cause two notifications, one for reply and the other for mention
if mention_user_id != user_id { if mention_user_id != user_id {
recipient_ids.push(mention_user_id);
let user_mention_form = UserMentionForm { let user_mention_form = UserMentionForm {
recipient_id: mention_user_id, recipient_id: mention_user_id,
comment_id: data.edit_id, comment_id: data.edit_id,
@ -294,6 +308,21 @@ impl Perform<CommentResponse> for Oper<EditComment> {
} }
} }
// Add to recipient ids
match data.parent_id {
Some(parent_id) => {
let parent_comment = Comment::read(&conn, parent_id)?;
if parent_comment.creator_id != user_id {
let parent_user = User_::read(&conn, parent_comment.creator_id)?;
recipient_ids.push(parent_user.id);
}
}
None => {
let post = Post::read(&conn, data.post_id)?;
recipient_ids.push(post.creator_id);
}
}
// Mod tables // Mod tables
if let Some(removed) = data.removed.to_owned() { if let Some(removed) = data.removed.to_owned() {
let form = ModRemoveCommentForm { let form = ModRemoveCommentForm {
@ -309,6 +338,7 @@ impl Perform<CommentResponse> for Oper<EditComment> {
Ok(CommentResponse { Ok(CommentResponse {
comment: comment_view, comment: comment_view,
recipient_ids,
}) })
} }
} }
@ -345,6 +375,7 @@ impl Perform<CommentResponse> for Oper<SaveComment> {
Ok(CommentResponse { Ok(CommentResponse {
comment: comment_view, comment: comment_view,
recipient_ids: Vec::new(),
}) })
} }
} }
@ -360,6 +391,8 @@ impl Perform<CommentResponse> for Oper<CreateCommentLike> {
let user_id = claims.id; let user_id = claims.id;
let mut recipient_ids = Vec::new();
// Don't do a downvote if site has downvotes disabled // Don't do a downvote if site has downvotes disabled
if data.score == -1 { if data.score == -1 {
let site = SiteView::read(&conn)?; let site = SiteView::read(&conn)?;
@ -379,6 +412,22 @@ impl Perform<CommentResponse> for Oper<CreateCommentLike> {
return Err(APIError::err("site_ban").into()); return Err(APIError::err("site_ban").into());
} }
let comment = Comment::read(&conn, data.comment_id)?;
// Add to recipient ids
match comment.parent_id {
Some(parent_id) => {
let parent_comment = Comment::read(&conn, parent_id)?;
if parent_comment.creator_id != user_id {
let parent_user = User_::read(&conn, parent_comment.creator_id)?;
recipient_ids.push(parent_user.id);
}
}
None => {
recipient_ids.push(post.creator_id);
}
}
let like_form = CommentLikeForm { let like_form = CommentLikeForm {
comment_id: data.comment_id, comment_id: data.comment_id,
post_id: data.post_id, post_id: data.post_id,
@ -403,6 +452,7 @@ impl Perform<CommentResponse> for Oper<CreateCommentLike> {
Ok(CommentResponse { Ok(CommentResponse {
comment: liked_comment, comment: liked_comment,
recipient_ids,
}) })
} }
} }

View file

@ -11,9 +11,10 @@ pub struct GetCommunity {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct GetCommunityResponse { pub struct GetCommunityResponse {
community: CommunityView, pub community: CommunityView,
moderators: Vec<CommunityModeratorView>, moderators: Vec<CommunityModeratorView>,
admins: Vec<UserView>, admins: Vec<UserView>,
pub online: usize,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -161,6 +162,7 @@ impl Perform<GetCommunityResponse> for Oper<GetCommunity> {
community: community_view, community: community_view,
moderators, moderators,
admins, admins,
online: 0,
}) })
} }
} }
@ -174,11 +176,18 @@ impl Perform<CommunityResponse> for Oper<CreateCommunity> {
Err(_e) => return Err(APIError::err("not_logged_in").into()), Err(_e) => return Err(APIError::err("not_logged_in").into()),
}; };
if has_slurs(&data.name) if let Err(slurs) = slur_check(&data.name) {
|| has_slurs(&data.title) return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
|| (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap())) }
{
return Err(APIError::err("no_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 user_id = claims.id; let user_id = claims.id;
@ -240,8 +249,18 @@ impl Perform<CommunityResponse> for Oper<EditCommunity> {
fn perform(&self, conn: &PgConnection) -> Result<CommunityResponse, Error> { fn perform(&self, conn: &PgConnection) -> Result<CommunityResponse, Error> {
let data: &EditCommunity = &self.data; let data: &EditCommunity = &self.data;
if has_slurs(&data.name) || has_slurs(&data.title) { if let Err(slurs) = slur_check(&data.name) {
return Err(APIError::err("no_slurs").into()); 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) { let claims = match Claims::decode(&data.auth) {
@ -590,6 +609,7 @@ impl Perform<GetCommunityResponse> for Oper<TransferCommunity> {
community: community_view, community: community_view,
moderators, moderators,
admins, admins,
online: 0,
}) })
} }
} }

View file

@ -17,7 +17,9 @@ use crate::db::user_mention::*;
use crate::db::user_mention_view::*; use crate::db::user_mention_view::*;
use crate::db::user_view::*; use crate::db::user_view::*;
use crate::db::*; use crate::db::*;
use crate::{extract_usernames, has_slurs, naive_from_unix, naive_now, remove_slurs}; use crate::{
extract_usernames, naive_from_unix, naive_now, remove_slurs, slur_check, slurs_vec_to_str,
};
use diesel::PgConnection; use diesel::PgConnection;
use failure::Error; use failure::Error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View file

@ -8,7 +8,7 @@ pub struct CreatePost {
url: Option<String>, url: Option<String>,
body: Option<String>, body: Option<String>,
nsfw: bool, nsfw: bool,
community_id: i32, pub community_id: i32,
auth: String, auth: String,
} }
@ -30,6 +30,7 @@ pub struct GetPostResponse {
community: CommunityView, community: CommunityView,
moderators: Vec<CommunityModeratorView>, moderators: Vec<CommunityModeratorView>,
admins: Vec<UserView>, admins: Vec<UserView>,
pub online: usize,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -38,7 +39,7 @@ pub struct GetPosts {
sort: String, sort: String,
page: Option<i64>, page: Option<i64>,
limit: Option<i64>, limit: Option<i64>,
community_id: Option<i32>, pub community_id: Option<i32>,
auth: Option<String>, auth: Option<String>,
} }
@ -54,11 +55,6 @@ pub struct CreatePostLike {
auth: String, auth: String,
} }
#[derive(Serialize, Deserialize)]
pub struct CreatePostLikeResponse {
post: PostView,
}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct EditPost { pub struct EditPost {
pub edit_id: i32, pub edit_id: i32,
@ -92,8 +88,14 @@ impl Perform<PostResponse> for Oper<CreatePost> {
Err(_e) => return Err(APIError::err("not_logged_in").into()), Err(_e) => return Err(APIError::err("not_logged_in").into()),
}; };
if has_slurs(&data.name) || (data.body.is_some() && has_slurs(&data.body.to_owned().unwrap())) { if let Err(slurs) = slur_check(&data.name) {
return Err(APIError::err("no_slurs").into()); 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());
}
} }
let user_id = claims.id; let user_id = claims.id;
@ -193,6 +195,7 @@ impl Perform<GetPostResponse> for Oper<GetPost> {
community, community,
moderators, moderators,
admins, admins,
online: 0,
}) })
} }
} }
@ -240,8 +243,8 @@ impl Perform<GetPostsResponse> for Oper<GetPosts> {
} }
} }
impl Perform<CreatePostLikeResponse> for Oper<CreatePostLike> { impl Perform<PostResponse> for Oper<CreatePostLike> {
fn perform(&self, conn: &PgConnection) -> Result<CreatePostLikeResponse, Error> { fn perform(&self, conn: &PgConnection) -> Result<PostResponse, Error> {
let data: &CreatePostLike = &self.data; let data: &CreatePostLike = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -294,15 +297,22 @@ impl Perform<CreatePostLikeResponse> for Oper<CreatePostLike> {
}; };
// just output the score // just output the score
Ok(CreatePostLikeResponse { post: post_view }) Ok(PostResponse { post: post_view })
} }
} }
impl Perform<PostResponse> for Oper<EditPost> { impl Perform<PostResponse> for Oper<EditPost> {
fn perform(&self, conn: &PgConnection) -> Result<PostResponse, Error> { fn perform(&self, conn: &PgConnection) -> Result<PostResponse, Error> {
let data: &EditPost = &self.data; let data: &EditPost = &self.data;
if has_slurs(&data.name) || (data.body.is_some() && has_slurs(&data.body.to_owned().unwrap())) {
return Err(APIError::err("no_slurs").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());
}
} }
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {

View file

@ -3,7 +3,7 @@ use diesel::PgConnection;
use std::str::FromStr; use std::str::FromStr;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct ListCategories; pub struct ListCategories {}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct ListCategoriesResponse { pub struct ListCategoriesResponse {
@ -72,7 +72,7 @@ pub struct EditSite {
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct GetSite; pub struct GetSite {}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct SiteResponse { pub struct SiteResponse {
@ -186,10 +186,14 @@ impl Perform<SiteResponse> for Oper<CreateSite> {
Err(_e) => return Err(APIError::err("not_logged_in").into()), Err(_e) => return Err(APIError::err("not_logged_in").into()),
}; };
if has_slurs(&data.name) if let Err(slurs) = slur_check(&data.name) {
|| (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap())) return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
{ }
return Err(APIError::err("no_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; let user_id = claims.id;
@ -229,10 +233,14 @@ impl Perform<SiteResponse> for Oper<EditSite> {
Err(_e) => return Err(APIError::err("not_logged_in").into()), Err(_e) => return Err(APIError::err("not_logged_in").into()),
}; };
if has_slurs(&data.name) if let Err(slurs) = slur_check(&data.name) {
|| (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap())) return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
{ }
return Err(APIError::err("no_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; let user_id = claims.id;

View file

@ -162,7 +162,7 @@ pub struct PasswordChange {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct CreatePrivateMessage { pub struct CreatePrivateMessage {
content: String, content: String,
recipient_id: i32, pub recipient_id: i32,
auth: String, auth: String,
} }
@ -193,6 +193,16 @@ pub struct PrivateMessageResponse {
message: PrivateMessageView, message: PrivateMessageView,
} }
#[derive(Serialize, Deserialize, Debug)]
pub struct UserJoin {
auth: String,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct UserJoinResponse {
pub user_id: i32,
}
impl Perform<LoginResponse> for Oper<Login> { impl Perform<LoginResponse> for Oper<Login> {
fn perform(&self, conn: &PgConnection) -> Result<LoginResponse, Error> { fn perform(&self, conn: &PgConnection) -> Result<LoginResponse, Error> {
let data: &Login = &self.data; let data: &Login = &self.data;
@ -230,8 +240,8 @@ impl Perform<LoginResponse> for Oper<Register> {
return Err(APIError::err("passwords_dont_match").into()); return Err(APIError::err("passwords_dont_match").into());
} }
if has_slurs(&data.username) { if let Err(slurs) = slur_check(&data.username) {
return Err(APIError::err("no_slurs").into()); return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
} }
// Make sure there are no admins // Make sure there are no admins
@ -1071,3 +1081,17 @@ impl Perform<PrivateMessagesResponse> for Oper<GetPrivateMessages> {
Ok(PrivateMessagesResponse { messages }) Ok(PrivateMessagesResponse { messages })
} }
} }
impl Perform<UserJoinResponse> for Oper<UserJoin> {
fn perform(&self, _conn: &PgConnection) -> Result<UserJoinResponse, Error> {
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 user_id = claims.id;
Ok(UserJoinResponse { user_id })
}
}

View file

@ -348,7 +348,9 @@ impl<'a> ReplyQueryBuilder<'a> {
query = query query = query
.filter(user_id.eq(self.for_user_id)) .filter(user_id.eq(self.for_user_id))
.filter(recipient_id.eq(self.for_user_id)); .filter(recipient_id.eq(self.for_user_id))
.filter(deleted.eq(false))
.filter(removed.eq(false));
if self.unread_only { if self.unread_only {
query = query.filter(read.eq(false)); query = query.filter(read.eq(false));

View file

@ -98,7 +98,7 @@ impl<'a> PrivateMessageQueryBuilder<'a> {
pub fn list(self) -> Result<Vec<PrivateMessageView>, Error> { pub fn list(self) -> Result<Vec<PrivateMessageView>, Error> {
use super::private_message_view::private_message_mview::dsl::*; use super::private_message_view::private_message_mview::dsl::*;
let mut query = self.query; let mut query = self.query.filter(deleted.eq(false));
// If its unread, I only want the ones to me // If its unread, I only want the ones to me
if self.unread_only { if self.unread_only {

View file

@ -3,7 +3,7 @@ use crate::schema::user_;
use crate::schema::user_::dsl::*; use crate::schema::user_::dsl::*;
use crate::{is_email_regex, Settings}; use crate::{is_email_regex, Settings};
use bcrypt::{hash, DEFAULT_COST}; use bcrypt::{hash, DEFAULT_COST};
use jsonwebtoken::{decode, encode, Header, TokenData, Validation}; use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation};
#[derive(Queryable, Identifiable, PartialEq, Debug)] #[derive(Queryable, Identifiable, PartialEq, Debug)]
#[table_name = "user_"] #[table_name = "user_"]
@ -115,7 +115,11 @@ impl Claims {
validate_exp: false, validate_exp: false,
..Validation::default() ..Validation::default()
}; };
decode::<Claims>(&jwt, Settings::get().jwt_secret.as_ref(), &v) decode::<Claims>(
&jwt,
&DecodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
&v,
)
} }
} }
@ -137,7 +141,7 @@ impl User_ {
encode( encode(
&Header::default(), &Header::default(),
&my_claims, &my_claims,
Settings::get().jwt_secret.as_ref(), &EncodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
) )
.unwrap() .unwrap()
} }

View file

@ -36,7 +36,7 @@ use chrono::{DateTime, NaiveDateTime, Utc};
use lettre::smtp::authentication::{Credentials, Mechanism}; use lettre::smtp::authentication::{Credentials, Mechanism};
use lettre::smtp::extension::ClientId; use lettre::smtp::extension::ClientId;
use lettre::smtp::ConnectionReuseParameters; use lettre::smtp::ConnectionReuseParameters;
use lettre::{SmtpClient, Transport}; use lettre::{ClientSecurity, SmtpClient, Transport};
use lettre_email::Email; use lettre_email::Email;
use rand::distributions::Alphanumeric; use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
@ -62,8 +62,24 @@ pub fn remove_slurs(test: &str) -> String {
SLUR_REGEX.replace_all(test, "*removed*").to_string() SLUR_REGEX.replace_all(test, "*removed*").to_string()
} }
pub fn has_slurs(test: &str) -> bool { pub fn slur_check(test: &str) -> Result<(), Vec<&str>> {
SLUR_REGEX.is_match(test) let mut matches: Vec<&str> = SLUR_REGEX.find_iter(test).map(|mat| mat.as_str()).collect();
// Unique
matches.sort_unstable();
matches.dedup();
if matches.is_empty() {
Ok(())
} else {
Err(matches)
}
}
pub fn slurs_vec_to_str(slurs: Vec<&str>) -> String {
let start = "No slurs - ";
let combined = &slurs.join(", ");
[start, combined].concat()
} }
pub fn extract_usernames(test: &str) -> Vec<&str> { pub fn extract_usernames(test: &str) -> Vec<&str> {
@ -94,40 +110,42 @@ pub fn send_email(
let email = Email::builder() let email = Email::builder()
.to((to_email, to_username)) .to((to_email, to_username))
.from(( .from(email_config.smtp_from_address.to_owned())
email_config.smtp_login.to_owned(),
email_config.smtp_from_address.to_owned(),
))
.subject(subject) .subject(subject)
.html(html) .html(html)
.build() .build()
.unwrap(); .unwrap();
let mut mailer = SmtpClient::new_simple(&email_config.smtp_server) let mailer = if email_config.use_tls {
.unwrap() SmtpClient::new_simple(&email_config.smtp_server).unwrap()
} else {
SmtpClient::new(&email_config.smtp_server, ClientSecurity::None).unwrap()
}
.hello_name(ClientId::Domain(Settings::get().hostname.to_owned())) .hello_name(ClientId::Domain(Settings::get().hostname.to_owned()))
.credentials(Credentials::new(
email_config.smtp_login.to_owned(),
email_config.smtp_password.to_owned(),
))
.smtp_utf8(true) .smtp_utf8(true)
.authentication_mechanism(Mechanism::Plain) .authentication_mechanism(Mechanism::Plain)
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited) .connection_reuse(ConnectionReuseParameters::ReuseUnlimited);
.transport(); let mailer = if let (Some(login), Some(password)) =
(&email_config.smtp_login, &email_config.smtp_password)
{
mailer.credentials(Credentials::new(login.to_owned(), password.to_owned()))
} else {
mailer
};
let result = mailer.send(email.into()); let mut transport = mailer.transport();
let result = transport.send(email.into());
mailer.close(); transport.close();
match result { match result {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(_) => Err("no_email_setup".to_string()), Err(e) => Err(e.to_string()),
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{extract_usernames, has_slurs, is_email_regex, remove_slurs}; use crate::{extract_usernames, is_email_regex, remove_slurs, slur_check, slurs_vec_to_str};
#[test] #[test]
fn test_email() { fn test_email() {
@ -138,15 +156,29 @@ mod tests {
#[test] #[test]
fn test_slur_filter() { fn test_slur_filter() {
let test = let test =
"coons test dindu ladyboy tranny retardeds. Capitalized Niggerz. This is a bunch of other safe text.".to_string(); "coons test dindu ladyboy tranny retardeds. Capitalized Niggerz. This is a bunch of other safe text.";
let slur_free = "No slurs here"; let slur_free = "No slurs here";
assert_eq!( assert_eq!(
remove_slurs(&test), remove_slurs(&test),
"*removed* test *removed* *removed* *removed* *removed*. Capitalized *removed*. This is a bunch of other safe text." "*removed* test *removed* *removed* *removed* *removed*. Capitalized *removed*. This is a bunch of other safe text."
.to_string() .to_string()
); );
assert!(has_slurs(&test));
assert!(!has_slurs(slur_free)); let has_slurs_vec = vec![
"Niggerz",
"coons",
"dindu",
"ladyboy",
"retardeds",
"tranny",
];
let has_slurs_err_str = "No slurs - Niggerz, coons, dindu, ladyboy, retardeds, tranny";
assert_eq!(slur_check(test), Err(has_slurs_vec));
assert_eq!(slur_check(slur_free), Ok(()));
if let Err(slur_vec) = slur_check(test) {
assert_eq!(&slurs_vec_to_str(slur_vec), has_slurs_err_str);
}
} }
#[test] #[test]

View file

@ -31,7 +31,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
.route("/api/v1/post", web::put().to(route_post::<EditPost, PostResponse>)) .route("/api/v1/post", web::put().to(route_post::<EditPost, PostResponse>))
.route("/api/v1/post", web::get().to(route_get::<GetPost, GetPostResponse>)) .route("/api/v1/post", web::get().to(route_get::<GetPost, GetPostResponse>))
.route("/api/v1/post/list", web::get().to(route_get::<GetPosts, GetPostsResponse>)) .route("/api/v1/post/list", web::get().to(route_get::<GetPosts, GetPostsResponse>))
.route("/api/v1/post/like", web::post().to(route_post::<CreatePostLike, CreatePostLikeResponse>)) .route("/api/v1/post/like", web::post().to(route_post::<CreatePostLike, PostResponse>))
.route("/api/v1/post/save", web::put().to(route_post::<SavePost, PostResponse>)) .route("/api/v1/post/save", web::put().to(route_post::<SavePost, PostResponse>))
// Comment // Comment
.route("/api/v1/comment", web::post().to(route_post::<CreateComment, CommentResponse>)) .route("/api/v1/comment", web::post().to(route_post::<CreateComment, CommentResponse>))

View file

@ -33,9 +33,10 @@ pub struct RateLimitConfig {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct EmailConfig { pub struct EmailConfig {
pub smtp_server: String, pub smtp_server: String,
pub smtp_login: String, pub smtp_login: Option<String>,
pub smtp_password: String, pub smtp_password: Option<String>,
pub smtp_from_address: String, pub smtp_from_address: String,
pub use_tls: bool,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]

View file

@ -1 +1 @@
pub const VERSION: &str = "v0.6.7"; pub const VERSION: &str = "v0.6.10";

View file

@ -44,4 +44,5 @@ pub enum UserOperation {
CreatePrivateMessage, CreatePrivateMessage,
EditPrivateMessage, EditPrivateMessage,
GetPrivateMessages, GetPrivateMessages,
UserJoin,
} }

View file

@ -22,6 +22,12 @@ use crate::api::*;
use crate::websocket::UserOperation; use crate::websocket::UserOperation;
use crate::Settings; use crate::Settings;
type ConnectionId = usize;
type PostId = i32;
type CommunityId = i32;
type UserId = i32;
type IPAddr = String;
/// Chat server sends this messages to session /// Chat server sends this messages to session
#[derive(Message)] #[derive(Message)]
#[rtype(result = "()")] #[rtype(result = "()")]
@ -34,35 +40,22 @@ pub struct WSMessage(pub String);
#[rtype(usize)] #[rtype(usize)]
pub struct Connect { pub struct Connect {
pub addr: Recipient<WSMessage>, pub addr: Recipient<WSMessage>,
pub ip: String, pub ip: IPAddr,
} }
/// Session is disconnected /// Session is disconnected
#[derive(Message)] #[derive(Message)]
#[rtype(result = "()")] #[rtype(result = "()")]
pub struct Disconnect { pub struct Disconnect {
pub id: usize, pub id: ConnectionId,
pub ip: String, pub ip: IPAddr,
}
// TODO this is unused rn
/// Send message to specific room
#[derive(Message)]
#[rtype(result = "()")]
pub struct ClientMessage {
/// Id of the client session
pub id: usize,
/// Peer message
pub msg: String,
/// Room name
pub room: String,
} }
#[derive(Serialize, Deserialize, Message)] #[derive(Serialize, Deserialize, Message)]
#[rtype(String)] #[rtype(String)]
pub struct StandardMessage { pub struct StandardMessage {
/// Id of the client session /// Id of the client session
pub id: usize, pub id: ConnectionId,
/// Peer message /// Peer message
pub msg: String, pub msg: String,
} }
@ -75,36 +68,93 @@ pub struct RateLimitBucket {
pub struct SessionInfo { pub struct SessionInfo {
pub addr: Recipient<WSMessage>, pub addr: Recipient<WSMessage>,
pub ip: String, pub ip: IPAddr,
} }
/// `ChatServer` manages chat rooms and responsible for coordinating chat /// `ChatServer` manages chat rooms and responsible for coordinating chat
/// session. implementation is super primitive /// session.
pub struct ChatServer { pub struct ChatServer {
sessions: HashMap<usize, SessionInfo>, // A map from generated random ID to session addr /// A map from generated random ID to session addr
rate_limits: HashMap<String, RateLimitBucket>, sessions: HashMap<ConnectionId, SessionInfo>,
rooms: HashMap<i32, HashSet<usize>>, // A map from room / post name to set of connectionIDs
/// A map from post_id to set of connectionIDs
post_rooms: HashMap<PostId, HashSet<ConnectionId>>,
/// A map from community to set of connectionIDs
community_rooms: HashMap<CommunityId, HashSet<ConnectionId>>,
/// A map from user id to its connection ID for joined users. Remember a user can have multiple
/// sessions (IE clients)
user_rooms: HashMap<UserId, HashSet<ConnectionId>>,
/// Rate limiting based on IP addr
rate_limits: HashMap<IPAddr, RateLimitBucket>,
rng: ThreadRng, rng: ThreadRng,
db: Pool<ConnectionManager<PgConnection>>, db: Pool<ConnectionManager<PgConnection>>,
} }
impl ChatServer { impl ChatServer {
pub fn startup(db: Pool<ConnectionManager<PgConnection>>) -> ChatServer { pub fn startup(db: Pool<ConnectionManager<PgConnection>>) -> ChatServer {
// default room
let rooms = HashMap::new();
ChatServer { ChatServer {
sessions: HashMap::new(), sessions: HashMap::new(),
rate_limits: HashMap::new(), rate_limits: HashMap::new(),
rooms, post_rooms: HashMap::new(),
community_rooms: HashMap::new(),
user_rooms: HashMap::new(),
rng: rand::thread_rng(), rng: rand::thread_rng(),
db, db,
} }
} }
/// Send message to all users in the room fn join_community_room(&mut self, community_id: CommunityId, id: ConnectionId) {
fn send_room_message(&self, room: i32, message: &str, skip_id: usize) { // remove session from all rooms
if let Some(sessions) = self.rooms.get(&room) { for sessions in self.community_rooms.values_mut() {
sessions.remove(&id);
}
// If the room doesn't exist yet
if self.community_rooms.get_mut(&community_id).is_none() {
self.community_rooms.insert(community_id, HashSet::new());
}
self
.community_rooms
.get_mut(&community_id)
.unwrap()
.insert(id);
}
fn join_post_room(&mut self, post_id: PostId, id: ConnectionId) {
// remove session from all rooms
for sessions in self.post_rooms.values_mut() {
sessions.remove(&id);
}
// If the room doesn't exist yet
if self.post_rooms.get_mut(&post_id).is_none() {
self.post_rooms.insert(post_id, HashSet::new());
}
self.post_rooms.get_mut(&post_id).unwrap().insert(id);
}
fn join_user_room(&mut self, user_id: UserId, id: ConnectionId) {
// remove session from all rooms
for sessions in self.user_rooms.values_mut() {
sessions.remove(&id);
}
// If the room doesn't exist yet
if self.user_rooms.get_mut(&user_id).is_none() {
self.user_rooms.insert(user_id, HashSet::new());
}
self.user_rooms.get_mut(&user_id).unwrap().insert(id);
}
fn send_post_room_message(&self, post_id: PostId, message: &str, skip_id: ConnectionId) {
if let Some(sessions) = self.post_rooms.get(&post_id) {
for id in sessions { for id in sessions {
if *id != skip_id { if *id != skip_id {
if let Some(info) = self.sessions.get(id) { if let Some(info) = self.sessions.get(id) {
@ -115,43 +165,98 @@ impl ChatServer {
} }
} }
fn join_room(&mut self, room_id: i32, id: usize) { fn send_community_room_message(
// remove session from all rooms
for sessions in self.rooms.values_mut() {
sessions.remove(&id);
}
// If the room doesn't exist yet
if self.rooms.get_mut(&room_id).is_none() {
self.rooms.insert(room_id, HashSet::new());
}
self.rooms.get_mut(&room_id).unwrap().insert(id);
}
fn send_community_message(
&self, &self,
community_id: i32, community_id: CommunityId,
message: &str, message: &str,
skip_id: usize, skip_id: ConnectionId,
) -> Result<(), Error> { ) {
use crate::db::post_view::*; if let Some(sessions) = self.community_rooms.get(&community_id) {
use crate::db::*; for id in sessions {
if *id != skip_id {
let conn = self.db.get()?; if let Some(info) = self.sessions.get(id) {
let _ = info.addr.do_send(WSMessage(message.to_owned()));
let posts = PostQueryBuilder::create(&conn) }
.listing_type(ListingType::Community) }
.sort(&SortType::New) }
.for_community_id(community_id) }
.limit(9999)
.list()?;
for post in posts {
self.send_room_message(post.id, message, skip_id);
} }
Ok(()) fn send_user_room_message(&self, user_id: UserId, message: &str, skip_id: ConnectionId) {
if let Some(sessions) = self.user_rooms.get(&user_id) {
for id in sessions {
if *id != skip_id {
if let Some(info) = self.sessions.get(id) {
let _ = info.addr.do_send(WSMessage(message.to_owned()));
}
}
}
}
}
fn send_all_message(&self, message: &str, skip_id: ConnectionId) {
for id in self.sessions.keys() {
if *id != skip_id {
if let Some(info) = self.sessions.get(id) {
let _ = info.addr.do_send(WSMessage(message.to_owned()));
}
}
}
}
fn comment_sends(
&self,
user_operation: UserOperation,
comment: CommentResponse,
id: ConnectionId,
) -> Result<String, Error> {
let mut comment_reply_sent = comment.clone();
comment_reply_sent.comment.my_vote = None;
comment_reply_sent.comment.user_id = None;
// For the post room ones, and the directs back to the user
// strip out the recipient_ids, so that
// users don't get double notifs
let mut comment_user_sent = comment.clone();
comment_user_sent.recipient_ids = Vec::new();
let mut comment_post_sent = comment_reply_sent.clone();
comment_post_sent.recipient_ids = Vec::new();
let comment_reply_sent_str = to_json_string(&user_operation, &comment_reply_sent)?;
let comment_post_sent_str = to_json_string(&user_operation, &comment_post_sent)?;
let comment_user_sent_str = to_json_string(&user_operation, &comment_user_sent)?;
// Send it to the post room
self.send_post_room_message(comment.comment.post_id, &comment_post_sent_str, 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(recipient_id, &comment_reply_sent_str, id);
}
Ok(comment_user_sent_str)
}
fn post_sends(
&self,
user_operation: UserOperation,
post: PostResponse,
id: ConnectionId,
) -> Result<String, Error> {
let community_id = post.post.community_id;
// Don't send my data with it
let mut post_sent = post.clone();
post_sent.post.my_vote = None;
post_sent.post.user_id = None;
let post_sent_str = to_json_string(&user_operation, &post_sent)?;
// Send it to /c/all and that community
self.send_community_room_message(0, &post_sent_str, id);
self.send_community_room_message(community_id, &post_sent_str, id);
to_json_string(&user_operation, post)
} }
fn check_rate_limit_register(&mut self, id: usize) -> Result<(), Error> { fn check_rate_limit_register(&mut self, id: usize) -> Result<(), Error> {
@ -233,9 +338,6 @@ impl Handler<Connect> for ChatServer {
type Result = usize; type Result = usize;
fn handle(&mut self, msg: Connect, _ctx: &mut Context<Self>) -> Self::Result { fn handle(&mut self, msg: Connect, _ctx: &mut Context<Self>) -> Self::Result {
// notify all users in same room
// self.send_room_message(&"Main".to_owned(), "Someone joined", 0);
// register session with random id // register session with random id
let id = self.rng.gen::<usize>(); let id = self.rng.gen::<usize>();
println!("{} joined", &msg.ip); println!("{} joined", &msg.ip);
@ -267,15 +369,18 @@ impl Handler<Disconnect> for ChatServer {
type Result = (); type Result = ();
fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) { fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) {
// let mut rooms: Vec<i32> = Vec::new(); // Remove connections from sessions and all 3 scopes
// remove address
if self.sessions.remove(&msg.id).is_some() { if self.sessions.remove(&msg.id).is_some() {
// remove session from all rooms for sessions in self.user_rooms.values_mut() {
for sessions in self.rooms.values_mut() { sessions.remove(&msg.id);
if sessions.remove(&msg.id) {
// rooms.push(*id);
} }
for sessions in self.post_rooms.values_mut() {
sessions.remove(&msg.id);
}
for sessions in self.community_rooms.values_mut() {
sessions.remove(&msg.id);
} }
} }
} }
@ -354,10 +459,18 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
do_user_operation::<SaveUserSettings, LoginResponse>(user_operation, data, &conn) do_user_operation::<SaveUserSettings, LoginResponse>(user_operation, data, &conn)
} }
UserOperation::AddAdmin => { UserOperation::AddAdmin => {
do_user_operation::<AddAdmin, AddAdminResponse>(user_operation, data, &conn) let add_admin: AddAdmin = serde_json::from_str(data)?;
let res = Oper::new(add_admin).perform(&conn)?;
let res_str = to_json_string(&user_operation, &res)?;
chat.send_all_message(&res_str, msg.id);
Ok(res_str)
} }
UserOperation::BanUser => { UserOperation::BanUser => {
do_user_operation::<BanUser, BanUserResponse>(user_operation, data, &conn) let ban_user: BanUser = serde_json::from_str(data)?;
let res = Oper::new(ban_user).perform(&conn)?;
let res_str = to_json_string(&user_operation, &res)?;
chat.send_all_message(&res_str, msg.id);
Ok(res_str)
} }
UserOperation::GetReplies => { UserOperation::GetReplies => {
do_user_operation::<GetReplies, GetRepliesResponse>(user_operation, data, &conn) do_user_operation::<GetReplies, GetRepliesResponse>(user_operation, data, &conn)
@ -372,7 +485,19 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
do_user_operation::<MarkAllAsRead, GetRepliesResponse>(user_operation, data, &conn) do_user_operation::<MarkAllAsRead, GetRepliesResponse>(user_operation, data, &conn)
} }
UserOperation::GetCommunity => { UserOperation::GetCommunity => {
do_user_operation::<GetCommunity, GetCommunityResponse>(user_operation, data, &conn) let get_community: GetCommunity = serde_json::from_str(data)?;
let mut res = Oper::new(get_community).perform(&conn)?;
let community_id = res.community.id;
chat.join_community_room(community_id, msg.id);
res.online = if let Some(community_users) = chat.community_rooms.get(&community_id) {
community_users.len()
} else {
0
};
to_json_string(&user_operation, &res)
} }
UserOperation::ListCommunities => { UserOperation::ListCommunities => {
do_user_operation::<ListCommunities, ListCommunitiesResponse>(user_operation, data, &conn) do_user_operation::<ListCommunities, ListCommunitiesResponse>(user_operation, data, &conn)
@ -388,7 +513,7 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
community_sent.community.user_id = None; community_sent.community.user_id = None;
community_sent.community.subscribed = None; community_sent.community.subscribed = None;
let community_sent_str = to_json_string(&user_operation, &community_sent)?; let community_sent_str = to_json_string(&user_operation, &community_sent)?;
chat.send_community_message(community_sent.community.id, &community_sent_str, msg.id)?; chat.send_community_room_message(community_sent.community.id, &community_sent_str, msg.id);
to_json_string(&user_operation, &res) to_json_string(&user_operation, &res)
} }
UserOperation::FollowCommunity => { UserOperation::FollowCommunity => {
@ -403,7 +528,7 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
let community_id = ban_from_community.community_id; let community_id = ban_from_community.community_id;
let res = Oper::new(ban_from_community).perform(&conn)?; let res = Oper::new(ban_from_community).perform(&conn)?;
let res_str = to_json_string(&user_operation, &res)?; let res_str = to_json_string(&user_operation, &res)?;
chat.send_community_message(community_id, &res_str, msg.id)?; chat.send_community_room_message(community_id, &res_str, msg.id);
Ok(res_str) Ok(res_str)
} }
UserOperation::AddModToCommunity => { UserOperation::AddModToCommunity => {
@ -411,37 +536,54 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
let community_id = mod_add_to_community.community_id; let community_id = mod_add_to_community.community_id;
let res = Oper::new(mod_add_to_community).perform(&conn)?; let res = Oper::new(mod_add_to_community).perform(&conn)?;
let res_str = to_json_string(&user_operation, &res)?; let res_str = to_json_string(&user_operation, &res)?;
chat.send_community_message(community_id, &res_str, msg.id)?; chat.send_community_room_message(community_id, &res_str, msg.id);
Ok(res_str) Ok(res_str)
} }
UserOperation::ListCategories => { UserOperation::ListCategories => {
do_user_operation::<ListCategories, ListCategoriesResponse>(user_operation, data, &conn) do_user_operation::<ListCategories, ListCategoriesResponse>(user_operation, data, &conn)
} }
UserOperation::CreatePost => {
chat.check_rate_limit_post(msg.id)?;
do_user_operation::<CreatePost, PostResponse>(user_operation, data, &conn)
}
UserOperation::GetPost => { UserOperation::GetPost => {
let get_post: GetPost = serde_json::from_str(data)?; let get_post: GetPost = serde_json::from_str(data)?;
chat.join_room(get_post.id, msg.id); let post_id = get_post.id;
let res = Oper::new(get_post).perform(&conn)?; chat.join_post_room(post_id, msg.id);
let mut res = Oper::new(get_post).perform(&conn)?;
res.online = if let Some(post_users) = chat.post_rooms.get(&post_id) {
post_users.len()
} else {
0
};
to_json_string(&user_operation, &res) to_json_string(&user_operation, &res)
} }
UserOperation::GetPosts => { UserOperation::GetPosts => {
do_user_operation::<GetPosts, GetPostsResponse>(user_operation, data, &conn) let get_posts: GetPosts = serde_json::from_str(data)?;
if get_posts.community_id.is_none() {
// 0 is the "all" community
chat.join_community_room(0, msg.id);
}
let res = Oper::new(get_posts).perform(&conn)?;
to_json_string(&user_operation, &res)
}
UserOperation::CreatePost => {
chat.check_rate_limit_post(msg.id)?;
let create_post: CreatePost = serde_json::from_str(data)?;
let res = Oper::new(create_post).perform(&conn)?;
chat.post_sends(UserOperation::CreatePost, res, msg.id)
} }
UserOperation::CreatePostLike => { UserOperation::CreatePostLike => {
chat.check_rate_limit_message(msg.id)?; chat.check_rate_limit_message(msg.id)?;
do_user_operation::<CreatePostLike, CreatePostLikeResponse>(user_operation, data, &conn) let create_post_like: CreatePostLike = serde_json::from_str(data)?;
let res = Oper::new(create_post_like).perform(&conn)?;
chat.post_sends(UserOperation::CreatePostLike, res, msg.id)
} }
UserOperation::EditPost => { UserOperation::EditPost => {
let edit_post: EditPost = serde_json::from_str(data)?; let edit_post: EditPost = serde_json::from_str(data)?;
let res = Oper::new(edit_post).perform(&conn)?; let res = Oper::new(edit_post).perform(&conn)?;
let mut post_sent = res.clone();
post_sent.post.my_vote = None; chat.post_sends(UserOperation::EditPost, res, msg.id)
let post_sent_str = to_json_string(&user_operation, &post_sent)?;
chat.send_room_message(post_sent.post.id, &post_sent_str, msg.id);
to_json_string(&user_operation, &res)
} }
UserOperation::SavePost => { UserOperation::SavePost => {
do_user_operation::<SavePost, PostResponse>(user_operation, data, &conn) do_user_operation::<SavePost, PostResponse>(user_operation, data, &conn)
@ -449,25 +591,15 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
UserOperation::CreateComment => { UserOperation::CreateComment => {
chat.check_rate_limit_message(msg.id)?; chat.check_rate_limit_message(msg.id)?;
let create_comment: CreateComment = serde_json::from_str(data)?; let create_comment: CreateComment = serde_json::from_str(data)?;
let post_id = create_comment.post_id;
let res = Oper::new(create_comment).perform(&conn)?; let res = Oper::new(create_comment).perform(&conn)?;
let mut comment_sent = res.clone();
comment_sent.comment.my_vote = None; chat.comment_sends(UserOperation::CreateComment, res, msg.id)
comment_sent.comment.user_id = None;
let comment_sent_str = to_json_string(&user_operation, &comment_sent)?;
chat.send_room_message(post_id, &comment_sent_str, msg.id);
to_json_string(&user_operation, &res)
} }
UserOperation::EditComment => { UserOperation::EditComment => {
let edit_comment: EditComment = serde_json::from_str(data)?; let edit_comment: EditComment = serde_json::from_str(data)?;
let post_id = edit_comment.post_id;
let res = Oper::new(edit_comment).perform(&conn)?; let res = Oper::new(edit_comment).perform(&conn)?;
let mut comment_sent = res.clone();
comment_sent.comment.my_vote = None; chat.comment_sends(UserOperation::EditComment, res, msg.id)
comment_sent.comment.user_id = None;
let comment_sent_str = to_json_string(&user_operation, &comment_sent)?;
chat.send_room_message(post_id, &comment_sent_str, msg.id);
to_json_string(&user_operation, &res)
} }
UserOperation::SaveComment => { UserOperation::SaveComment => {
do_user_operation::<SaveComment, CommentResponse>(user_operation, data, &conn) do_user_operation::<SaveComment, CommentResponse>(user_operation, data, &conn)
@ -475,14 +607,9 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
UserOperation::CreateCommentLike => { UserOperation::CreateCommentLike => {
chat.check_rate_limit_message(msg.id)?; chat.check_rate_limit_message(msg.id)?;
let create_comment_like: CreateCommentLike = serde_json::from_str(data)?; let create_comment_like: CreateCommentLike = serde_json::from_str(data)?;
let post_id = create_comment_like.post_id;
let res = Oper::new(create_comment_like).perform(&conn)?; let res = Oper::new(create_comment_like).perform(&conn)?;
let mut comment_sent = res.clone();
comment_sent.comment.my_vote = None; chat.comment_sends(UserOperation::CreateCommentLike, res, msg.id)
comment_sent.comment.user_id = None;
let comment_sent_str = to_json_string(&user_operation, &comment_sent)?;
chat.send_room_message(post_id, &comment_sent_str, msg.id);
to_json_string(&user_operation, &res)
} }
UserOperation::GetModlog => { UserOperation::GetModlog => {
do_user_operation::<GetModlog, GetModlogResponse>(user_operation, data, &conn) do_user_operation::<GetModlog, GetModlogResponse>(user_operation, data, &conn)
@ -491,13 +618,16 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
do_user_operation::<CreateSite, SiteResponse>(user_operation, data, &conn) do_user_operation::<CreateSite, SiteResponse>(user_operation, data, &conn)
} }
UserOperation::EditSite => { UserOperation::EditSite => {
do_user_operation::<EditSite, SiteResponse>(user_operation, data, &conn) let edit_site: EditSite = serde_json::from_str(data)?;
let res = Oper::new(edit_site).perform(&conn)?;
let res_str = to_json_string(&user_operation, &res)?;
chat.send_all_message(&res_str, msg.id);
Ok(res_str)
} }
UserOperation::GetSite => { UserOperation::GetSite => {
let online: usize = chat.sessions.len();
let get_site: GetSite = serde_json::from_str(data)?; let get_site: GetSite = serde_json::from_str(data)?;
let mut res = Oper::new(get_site).perform(&conn)?; let mut res = Oper::new(get_site).perform(&conn)?;
res.online = online; res.online = chat.sessions.len();
to_json_string(&user_operation, &res) to_json_string(&user_operation, &res)
} }
UserOperation::Search => { UserOperation::Search => {
@ -520,7 +650,13 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
} }
UserOperation::CreatePrivateMessage => { UserOperation::CreatePrivateMessage => {
chat.check_rate_limit_message(msg.id)?; chat.check_rate_limit_message(msg.id)?;
do_user_operation::<CreatePrivateMessage, PrivateMessageResponse>(user_operation, data, &conn) let create_private_message: CreatePrivateMessage = serde_json::from_str(data)?;
let recipient_id = create_private_message.recipient_id;
let res = Oper::new(create_private_message).perform(&conn)?;
let res_str = to_json_string(&user_operation, &res)?;
chat.send_user_room_message(recipient_id, &res_str, msg.id);
Ok(res_str)
} }
UserOperation::EditPrivateMessage => { UserOperation::EditPrivateMessage => {
do_user_operation::<EditPrivateMessage, PrivateMessageResponse>(user_operation, data, &conn) do_user_operation::<EditPrivateMessage, PrivateMessageResponse>(user_operation, data, &conn)
@ -528,5 +664,11 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
UserOperation::GetPrivateMessages => { UserOperation::GetPrivateMessages => {
do_user_operation::<GetPrivateMessages, PrivateMessagesResponse>(user_operation, data, &conn) do_user_operation::<GetPrivateMessages, PrivateMessagesResponse>(user_operation, data, &conn)
} }
UserOperation::UserJoin => {
let user_join: UserJoin = serde_json::from_str(data)?;
let res = Oper::new(user_join).perform(&conn)?;
chat.join_user_room(res.user_id, msg.id);
to_json_string(&user_operation, &res)
}
} }
} }

View file

@ -0,0 +1,902 @@
//
// Variables
// --------------------------------------------------
//== Colors
//
//## Gray and brand colors for use across Bootstrap.
//// colors from bs-2
// Grays
// -------------------------
$black: #000;
$grayDark: #555;
$gray: #bbb;
$grayLight: #bbb;
$white: #FFF;
// Accent colors
// -------------------------
$blue: #5555Ff;
$cyan: #55FFFF;
$cyanDark: #00AAAA;
$blueDark: #000084;
$green: #55FF55;
$greenDark: #00AA00;
$magenta: #FF55FF;
$magentaDark: #AA00AA;
$red: #FF5555;
$redDark: #AA0000;
$yellow: #FEFE54;
$brown: #AA5500;
$orange: #A85400;
$pink: #FE54FE;
$purple: #FE5454;
// end colors
$gray-base: $gray;
$gray-darker: $grayDark;
$gray-dark: $grayDark;
$gray-light: $grayLight;
$gray-lighter: $grayLight;
$brand-primary: $gray;
$brand-primary-bg: $cyanDark;
$brand-success: $greenDark;
$brand-info: $brown;
$brand-warning: $magentaDark;
$brand-danger: $redDark;
//== Scaffolding
//
//## Settings for some of the most global styles.
//** Background color for `<body>`.
$body-bg: $blueDark;
//** Global text color on `<body>`.
$text-color: $gray-light;
//** Global textual link color.
$link-color: $brand-primary;
//** Link hover color set via `darken()` function.
$link-hover-color: $white;
//** Link hover decoration.
$link-hover-decoration: none;
//== Typography
//
//## Font, line-height, and color for body text, headings, and more.
$font-family-sans-serif: DOS, Monaco, Menlo, Consolas, "Courier New", monospace;
$font-family-serif: DOS, Monaco, Menlo, Consolas, "Courier New", monospace;
//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`.
$font-family-monospace: DOS, Monaco, Menlo, Consolas, "Courier New", monospace;
$font-family-base: $font-family-sans-serif;
$baseWidth: 10px;
$font-size-base: 18px;
$font-size-large: $font-size-base;
$font-size-small: $font-size-base;
$font-size-h1: $font-size-base;
$font-size-h2: $font-size-base;
$font-size-h3: $font-size-base;
$font-size-h4: $font-size-base;
$font-size-h5: $font-size-base;
$font-size-h6: $font-size-base;
//** Unit-less `line-height` for use in components like buttons.
$baseLineHeight: 19px;
$line-height-base: $baseLineHeight;
//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
$line-height-computed: $line-height-base;
//** By default, this inherits from the `<body>`.
$headings-font-family: inherit;
$headings-font-weight: normal;
$headings-line-height: $line-height-base;
$headings-color: inherit;
$space: $baseWidth;
$halfbaseLineHeight: ($baseLineHeight / 2);
$borderWidth: 2px;
$baseLineWidth: ($baseLineHeight / 2);
$halfSpace: ($baseWidth / 2);
$lhsNB: ($baseWidth / 2 + 1);
$rhsNB: ($baseWidth / 2 - 1);
$lhs: ($lhsNB - ($borderWidth));
$rhs: ($rhsNB - ($borderWidth / 2));
$tsNB: ($baseLineHeight / 2);
$bsNB: $tsNB;
$ts: ($tsNB - ($borderWidth / 2));
$bs: $ts;
$tsMargin: 3px;
//== Iconography
//
//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
//** Load fonts from this directory.
$icon-font-path: "../fonts/";
//** File name for all font files.
$icon-font-name: "glyphicons-halflings-regular";
//** Element ID within SVG icon file.
$icon-font-svg-id: "glyphicons_halflingsregular";
//== Components
//
//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
$padding-base-vertical: 0px;
$padding-base-horizontal: 0px;
$padding-large-vertical: 0px;
$padding-large-horizontal: $halfSpace;
$padding-small-vertical: 0px;
$padding-small-horizontal: 0px;
$padding-xs-vertical: 0px;
$padding-xs-horizontal: 0px;
$line-height-large: $baseLineHeight;
$line-height-small: $baseLineHeight;
$border-radius-base: 0;
$border-radius-large: 0;
$border-radius-small: 0;
//** Global color for active items (e.g., navs or dropdowns).
$component-active-color: $white;
//** Global background color for active items (e.g., navs or dropdowns).
$component-active-bg: $black;
//** Width of the `border` for generating carets that indicator dropdowns.
$caret-width-base: 4px;
//** Carets increase slightly in size for larger components.
$caret-width-large: 5px;
//== Tables
//
//## Customizes the `.table` component with basic values, each used across all table variations.
//** Padding for `<th>`s and `<td>`s.
$table-cell-padding: $ts $rhs $bs $lhs;
//** Padding for cells in `.table-condensed`.
$table-condensed-cell-padding: $ts $rhs $bs $lhs;
//** Default background color used for all tables.
$table-bg: transparent;
//** Background color used for `.table-striped`.
$table-bg-accent: $black;
//** Background color used for `.table-hover`.
$table-bg-hover: #f5f5f5;
$table-bg-active: $table-bg-hover;
//** Border color for table and cell borders.
$table-border-color: $gray;
//== Buttons
//
//## For each of Bootstrap's buttons, define text, background and border color.
$btn-font-weight: normal;
$btn-default-color: $black;
$btn-default-bg: $grayLight;
$btn-default-border: $grayLight;
$btn-primary-color: $black;
$btn-primary-bg: $cyanDark;
$btn-primary-border: $grayLight;
$btn-success-color: #fff;
$btn-success-bg: $brand-success;
$btn-success-border: $btn-success-bg;
$btn-info-color: #fff;
$btn-info-bg: $brand-info;
$btn-info-border: $btn-info-bg;
$btn-warning-color: #fff;
$btn-warning-bg: $brand-warning;
$btn-warning-border: $btn-warning-bg;
$btn-danger-color: #fff;
$btn-danger-bg: $brand-danger;
$btn-danger-border: $btn-danger-bg;
$btn-link-disabled-color: $gray-light;
//== Forms
//
//##
//** `<input>` background color
$input-bg: $cyanDark;
//** `<input disabled>` background color
$input-bg-disabled: $gray-lighter;
//** Text color for `<input>`s
$input-color: $white;
//** `<input>` border color
$input-border: #ccc;
// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4
//** Default `.form-control` border radius
// This has no effect on `<select>`s in some browsers, due to the limited stylability of `<select>`s in CSS.
$input-border-radius: $border-radius-base;
//** Large `.form-control` border radius
$input-border-radius-large: $border-radius-large;
//** Small `.form-control` border radius
$input-border-radius-small: $border-radius-small;
//** Border color for inputs on focus
$input-border-focus: $black;
//** Placeholder text color
$input-color-placeholder: $black;
//** Default `.form-control` height
$input-height-base: $line-height-computed;
//** Large `.form-control` height
$input-height-large: $input-height-base;
//** Small `.form-control` height
$input-height-small: $input-height-base;
$legend-color: $gray-dark;
$legend-border-color: #e5e5e5;
//** Background color for textual input addons
$input-group-addon-bg: $gray-lighter;
//** Border color for textual input addons
$input-group-addon-border-color: $input-border;
//** Disabled cursor for form controls and buttons.
$cursor-disabled: not-allowed;
//== Dropdowns
//
//## Dropdown menu container and contents.
//** Background for the dropdown menu.
$dropdown-bg: $gray;
//** Dropdown menu `border-color`.
$dropdown-border: rgb(0,0,0);
//** Dropdown menu `border-color` **for IE8**.
$dropdown-fallback-border: #ccc;
//** Divider color for between dropdown items.
$dropdown-divider-bg: $black;
//** Dropdown link text color.
$dropdown-link-color: $black;
//** Hover color for dropdown links.
$dropdown-link-hover-color: $gray;
//** Hover background for dropdown links.
$dropdown-link-hover-bg: $black;
//** Active dropdown menu item text color.
$dropdown-link-active-color: $component-active-color;
//** Active dropdown menu item background color.
$dropdown-link-active-bg: $component-active-bg;
//** Disabled dropdown menu item background color.
$dropdown-link-disabled-color: $gray-light;
//** Text color for headers within dropdown menus.
$dropdown-header-color: $black;
//** Deprecated `$dropdown-caret-color` as of v3.1.0
$dropdown-caret-color: #000;
//-- Z-index master list
//
// Warning: Avoid customizing these values. They're used for a bird's eye view
// of components dependent on the z-axis and are designed to all work together.
//
// Note: These variables are not generated into the Customizer.
$zindex-navbar: 1000;
$zindex-dropdown: 1000;
$zindex-popover: 1060;
$zindex-tooltip: 1070;
$zindex-navbar-fixed: 1030;
$zindex-modal: 1040;
//== Media queries breakpoints
//
//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
// Extra small screen / phone
//** Deprecated `$screen-xs` as of v3.0.1
$screen-xs: 480px;
//** Deprecated `$screen-xs-min` as of v3.2.0
$screen-xs-min: $screen-xs;
//** Deprecated `$screen-phone` as of v3.0.1
$screen-phone: $screen-xs-min;
// Small screen / tablet
//** Deprecated `$screen-sm` as of v3.0.1
$screen-sm: 768px;
$screen-sm-min: $screen-sm;
//** Deprecated `$screen-tablet` as of v3.0.1
$screen-tablet: $screen-sm-min;
// Medium screen / desktop
//** Deprecated `$screen-md` as of v3.0.1
$screen-md: 992px;
$screen-md-min: $screen-md;
//** Deprecated `$screen-desktop` as of v3.0.1
$screen-desktop: $screen-md-min;
// Large screen / wide desktop
//** Deprecated `$screen-lg` as of v3.0.1
$screen-lg: 1200px;
$screen-lg-min: $screen-lg;
//** Deprecated `$screen-lg-desktop` as of v3.0.1
$screen-lg-desktop: $screen-lg-min;
// So media queries don't overlap when required, provide a maximum
$screen-xs-max: ($screen-sm-min - 1);
$screen-sm-max: ($screen-md-min - 1);
$screen-md-max: ($screen-lg-min - 1);
//== Grid system
//
//## Define your custom responsive grid.
//** Number of columns in the grid.
$grid-columns: 12;
//** Padding between columns. Gets divided in half for the left and right.
$grid-gutter-width: ($baseWidth * 2);
// Navbar collapse
//** Point at which the navbar becomes uncollapsed.
$grid-float-breakpoint: $screen-sm-min;
//** Point at which the navbar begins collapsing.
$grid-float-breakpoint-max: ($grid-float-breakpoint);
//== Container sizes
//
//## Define the maximum width of `.container` for different screen sizes.
// Small screen / tablet
$container-tablet: (720px + $grid-gutter-width);
//** For `$screen-sm-min` and up.
$container-sm: $container-tablet;
// Medium screen / desktop
$container-desktop: (940px + $grid-gutter-width);
//** For `$screen-md-min` and up.
$container-md: $container-desktop;
// Large screen / wide desktop
$container-large-desktop: (1140px + $grid-gutter-width);
//** For `$screen-lg-min` and up.
$container-lg: $container-large-desktop;
//== Navbar
//
//##
// Basics of a navbar
$navbar-height: 0px;
$navbar-margin-bottom: $line-height-computed;
$navbar-border-radius: $border-radius-base;
$navbar-padding-horizontal: ($baseWidth * 2);
$navbar-padding-vertical: 0;
$navbar-collapse-max-height: 340px;
$navbar-default-color: $black;
$navbar-default-bg: $grayLight;
$navbar-default-border: $navbar-default-bg;
// Navbar links
$navbar-default-link-color: $black;
$navbar-default-link-hover-color: $white;
$navbar-default-link-hover-bg: $black;
$navbar-default-link-active-color: $white;
$navbar-default-link-active-bg: $black;
$navbar-default-link-disabled-color: $gray;
$navbar-default-link-disabled-bg: transparent;
// Navbar brand label
$navbar-default-brand-color: $navbar-default-link-color;
$navbar-default-brand-hover-color: $navbar-default-brand-color;
$navbar-default-brand-hover-bg: transparent;
// Navbar toggle
$navbar-default-toggle-hover-bg: #ddd;
$navbar-default-toggle-icon-bar-bg: #888;
$navbar-default-toggle-border-color: #ddd;
// Inverted navbar
// Reset inverted navbar basics
$navbar-inverse-color: $gray;
$navbar-inverse-bg: $black;
$navbar-inverse-border: $navbar-inverse-bg;
// Inverted navbar links
$navbar-inverse-link-color: $gray-light;
$navbar-inverse-link-hover-color: $black;
$navbar-inverse-link-hover-bg: $grayLight;
$navbar-inverse-link-active-color: $white;
$navbar-inverse-link-active-bg: $grayDark;
$navbar-inverse-link-disabled-color: $gray;
$navbar-inverse-link-disabled-bg: transparent;
// Inverted navbar brand label
$navbar-inverse-brand-color: $navbar-inverse-link-color;
$navbar-inverse-brand-hover-color: #fff;
$navbar-inverse-brand-hover-bg: transparent;
// Inverted navbar toggle
$navbar-inverse-toggle-hover-bg: $grayLight;
$navbar-inverse-toggle-icon-bar-bg: #fff;
$navbar-inverse-toggle-border-color: #333;
//== Navs
//
//##
//=== Shared nav styles
$nav-link-padding: 0 $baseWidth;
$nav-link-hover-bg: $gray-lighter;
$nav-disabled-link-color: $gray-light;
$nav-disabled-link-hover-color: $gray-light;
//== Tabs
$nav-tabs-border-color: #ddd;
$nav-tabs-link-hover-border-color: $gray-lighter;
$nav-tabs-active-link-hover-bg: $black;
$nav-tabs-active-link-hover-color: $white;
$nav-tabs-justified-active-link-border-color: $body-bg;
//== Pills
$nav-pills-border-radius: $border-radius-base;
$nav-pills-active-link-hover-bg: $component-active-bg;
$nav-pills-active-link-hover-color: $component-active-color;
//== Pagination
//
//##
$pagination-color: $black;
$pagination-bg: $gray;
$pagination-border: #ddd;
$pagination-hover-color: $link-hover-color;
$pagination-hover-bg: $gray-lighter;
$pagination-hover-border: #ddd;
$pagination-active-color: #fff;
$pagination-active-bg: $brand-primary;
$pagination-active-border: $brand-primary;
$pagination-disabled-color: $gray-light;
$pagination-disabled-bg: #fff;
$pagination-disabled-border: #ddd;
//== Pager
//
//##
$pager-bg: $pagination-bg;
$pager-border: $pagination-border;
$pager-border-radius: 0;
$pager-hover-bg: $pagination-hover-bg;
$pager-active-bg: $pagination-active-bg;
$pager-active-color: $pagination-active-color;
$pager-disabled-color: $pagination-disabled-color;
//== Jumbotron
//
//##
$jumbotron-padding: ($ts) ($rhs + $baseWidth) ($bs) ($lhs + $baseWidth);
$jumbotron-color: $white;
$jumbotron-bg: transparent;
$jumbotron-heading-color: inherit;
$jumbotron-font-size: $font-size-base;
//== Form states and alerts
//
//## Define colors for form feedback states and, by default, alerts.
$state-success-text: $green;
$state-success-bg: $greenDark;
$state-success-border: $state-success-bg;
$state-info-text: $yellow;
$state-info-bg: $brown;
$state-info-border: $state-info-bg;
$state-warning-text: $magenta;
$state-warning-bg: $magentaDark;
$state-warning-border: $state-warning-bg;
$state-danger-text: $red;
$state-danger-bg: $black;
$state-danger-border: $state-danger-bg;
//== Tooltips
//
//##
//** Tooltip max width
$tooltip-max-width: ($baseWidth * 25);
//** Tooltip text color
$tooltip-color: $white;
//** Tooltip background color
$tooltip-bg: $grayDark;
$tooltip-opacity: 1;
//** Tooltip arrow width
$tooltip-arrow-width: 0px;
//** Tooltip arrow color
$tooltip-arrow-color: $tooltip-bg;
//== Popovers
//
//##
//** Popover body background color
$popover-bg: $gray;
//** Popover maximum width
$popover-max-width: ($baseWidth * 20);
//** Popover border color
$popover-border-color: rgb(0,0,0);
//** Popover fallback border color
$popover-fallback-border-color: #ccc;
//** Popover title background color
$popover-title-bg: $greenDark;
//** Popover arrow width
$popover-arrow-width: 10px;
//** Popover arrow color
$popover-arrow-color: $popover-bg;
//** Popover outer arrow width
$popover-arrow-outer-width: ($popover-arrow-width + 1);
//** Popover outer arrow color
$popover-arrow-outer-color: $popover-border-color;
//** Popover outer arrow fallback color
$popover-arrow-outer-fallback-color: $popover-fallback-border-color;
//== Labels
//
//##
//** Default label background color
$label-default-bg: $gray-light;
//** Primary label background color
$label-primary-bg: $brand-primary-bg;
//** Success label background color
$label-success-bg: $brand-success;
//** Info label background color
$label-info-bg: $brand-info;
//** Warning label background color
$label-warning-bg: $brand-warning;
//** Danger label background color
$label-danger-bg: $brand-danger;
//** Default label text color
$label-color: #fff;
//** Default text color of a linked label
$label-link-hover-color: #fff;
//== Modals
//
//##
//** Padding applied to the modal body
$modal-inner-padding: 0 $baseWidth;
//** Padding applied to the modal title
$modal-title-padding: 0 $baseWidth;
//** Modal title line-height
$modal-title-line-height: $line-height-base;
//** Background color of modal content area
$modal-content-bg: $gray;
//** Modal content border color
$modal-content-border-color: rgb(0,0,0);
//** Modal content border color **for IE8**
$modal-content-fallback-border-color: #999;
//** Modal backdrop background color
$modal-backdrop-bg: #000;
//** Modal backdrop opacity
// $modal-backdrop-opacity: @include 5;
//** Modal header border color
$modal-header-border-color: #e5e5e5;
//** Modal footer border color
$modal-footer-border-color: $modal-header-border-color;
$modal-lg: 900px;
$modal-md: 600px;
$modal-sm: 300px;
//== Alerts
//
//## Define alert colors, border radius, and padding.
$alert-padding: $line-height-base ($baseWidth * 2);
$alert-border-radius: $border-radius-base;
$alert-link-font-weight: normal;
$alert-success-bg: $state-success-bg;
$alert-success-text: $state-success-text;
$alert-success-border: $state-success-border;
$alert-info-bg: $state-info-bg;
$alert-info-text: $state-info-text;
$alert-info-border: $state-info-border;
$alert-warning-bg: $state-warning-bg;
$alert-warning-text: $state-warning-text;
$alert-warning-border: $state-warning-border;
$alert-danger-bg: $state-danger-bg;
$alert-danger-text: $state-danger-text;
$alert-danger-border: $state-danger-border;
//== Progress bars
//
//##
//** Background color of the whole progress component
$progress-bg: $black;
//** Progress bar text color
$progress-bar-color: $black;
//** Variable for setting rounded corners on progress bar.
$progress-border-radius: $border-radius-base;
//** Default progress bar color
$progress-bar-bg: $brand-primary;
//** Success progress bar color
$progress-bar-success-bg: $brand-success;
//** Warning progress bar color
$progress-bar-warning-bg: $brand-warning;
//** Danger progress bar color
$progress-bar-danger-bg: $brand-danger;
//** Info progress bar color
$progress-bar-info-bg: $brand-info;
//== List group
//
//##
//** Background color on `.list-group-item`
$list-group-bg: $gray;
//** `.list-group-item` border color
$list-group-border: #ddd;
//** List group border radius
$list-group-border-radius: $border-radius-base;
//** Background color of single list items on hover
$list-group-hover-bg: $black;
//** Text color of active list items
$list-group-active-color: $component-active-color;
//** Background color of active list items
$list-group-active-bg: $component-active-bg;
//** Border color of active list elements
$list-group-active-border: $list-group-active-bg;
//** Text color for content within active list items
$list-group-active-text-color: $component-active-color;
//** Text color of disabled list items
$list-group-disabled-color: $gray-dark;
//** Background color of disabled list items
$list-group-disabled-bg: $gray-lighter;
//** Text color for content within disabled list items
$list-group-disabled-text-color: $list-group-disabled-color;
$list-group-link-color: $black;
$list-group-link-hover-color: $list-group-link-color;
$list-group-link-heading-color: #333;
//== Panels
//
//##
$panel-bg: $gray;
$panel-body-padding: 0 $rhsNB 0 $lhsNB;
$panel-heading-padding: 0 $rhsNB 0 $lhsNB;
$panel-footer-padding: $panel-heading-padding;
$panel-border-radius: $border-radius-base;
//** Border color for elements within panels
$panel-inner-border: #ddd;
$panel-footer-bg: #f5f5f5;
$panel-default-text: $white;
$panel-default-border: #ddd;
$panel-default-heading-bg: $grayDark;
$panel-primary-text: $white;
$panel-primary-border: $brand-primary;
$panel-primary-heading-bg: $cyanDark;
$panel-success-text: $state-success-text;
$panel-success-border: $state-success-border;
$panel-success-heading-bg: $state-success-bg;
$panel-info-text: $state-info-text;
$panel-info-border: $state-info-border;
$panel-info-heading-bg: $state-info-bg;
$panel-warning-text: $state-warning-text;
$panel-warning-border: $state-warning-border;
$panel-warning-heading-bg: $state-warning-bg;
$panel-danger-text: $state-danger-text;
$panel-danger-border: $state-danger-border;
$panel-danger-heading-bg: $state-danger-bg;
//== Thumbnails
//
//##
//** Padding around the thumbnail image
$thumbnail-padding: 4px;
//** Thumbnail background color
$thumbnail-bg: $body-bg;
//** Thumbnail border color
$thumbnail-border: #ddd;
//** Thumbnail border radius
$thumbnail-border-radius: $border-radius-base;
//** Custom text color for thumbnail captions
$thumbnail-caption-color: $text-color;
//** Padding around the thumbnail caption
$thumbnail-caption-padding: 9px;
//== Wells
//
//##
$well-bg: $greenDark;
$well-border: $well-bg;
//== Badges
//
//##
$badge-color: $black;
//** Linked badge text color on hover
$badge-link-hover-color: #fff;
$badge-bg: $gray-light;
//** Badge text color in active nav link
$badge-active-color: $link-color;
//** Badge background color in active nav link
$badge-active-bg: $black;
$badge-font-weight: normal;
$badge-line-height: $line-height-base;
$badge-border-radius: 0;
//== Breadcrumbs
//
//##
$breadcrumb-padding-vertical: 8px;
$breadcrumb-padding-horizontal: 15px;
//** Breadcrumb background color
$breadcrumb-bg: #f5f5f5;
//** Breadcrumb text color
$breadcrumb-color: #ccc;
//** Text color of current page in the breadcrumb
$breadcrumb-active-color: $gray-light;
//** Textual separator for between breadcrumb elements
$breadcrumb-separator: "/";
//== Carousel
//
//##
$carousel-text-shadow: none;
$carousel-control-color: #fff;
$carousel-control-width: 15%;
$carousel-control-opacity: 1;
$carousel-control-font-size: $font-size-base;
$carousel-indicator-active-bg: #fff;
$carousel-indicator-border-color: #fff;
$carousel-caption-color: #fff;
//== Close
//
//##
$close-font-weight: normal;
$close-color: #000;
$close-text-shadow: none;
//== Code
//
//##
$code-color: #c7254e;
$code-bg: #f9f2f4;
$kbd-color: #fff;
$kbd-bg: #333;
$pre-bg: #f5f5f5;
$pre-color: $gray-dark;
$pre-border-color: #ccc;
$pre-scrollable-max-height: 340px;
//== Type
//
//##
//** Horizontal offset for forms and lists.
$component-offset-horizontal: 180px;
//** Text muted color
$text-muted: $gray-dark;
//** Abbreviations and acronyms border color
$abbr-border-color: $gray-light;
//** Headings small color
$headings-small-color: $gray-light;
//** Blockquote small color
$blockquote-small-color: $gray-light;
//** Blockquote font size
$blockquote-font-size: $font-size-base;
//** Blockquote border color
$blockquote-border-color: $gray-lighter;
//** Page header border color
$page-header-border-color: $gray-lighter;
//** Width of horizontal description list titles
$dl-horizontal-offset: $component-offset-horizontal;
//** Horizontal line color.
$hr-border: $black;

View file

@ -0,0 +1,40 @@
$blue: #5555Ff;
$cyan: #55FFFF;
$green: #55FF55;
$indigo: #FF55FF;
$red: #FF5555;
$yellow: #FEFE54;
$orange: #A85400;
$pink: #FE54FE;
$purple: #FE5454;
$primary: #FEFE54;
$body-bg: #000084;
$gray-300: #bbb;
$body-color: $gray-300;
$link-hover-color: $white;
$font-family-sans-serif: DOS, Monaco, Menlo, Consolas, "Courier New", monospace;
$font-family-monospace: DOS, Monaco, Menlo, Consolas, "Courier New", monospace;
$navbar-dark-color: $gray-300;
$navbar-light-brand-color: $gray-300;
$success: #00AA00;
$danger: #AA0000;
$info: #00AAAA;
$warning: #AA00AA;
$navbar-dark-active-color: $gray-100;
$enable-rounded: false;
$input-color: $white;
$input-bg: rgb(102, 102, 102);
$input-disabled-bg: $gray-800;
$nav-tabs-link-active-color: $gray-100;
$navbar-dark-hover-color: rgba($gray-300, .75);
$light: $gray-800;
$navbar-light-disabled-color: $gray-800;
$navbar-light-active-color: $gray-100;
$navbar-light-hover-color: $gray-200;
$navbar-light-color: $gray-300;
$card-bg: $gray-800;
$card-border-color: $white;
$input-placeholder-color: $gray-500;
$mark-bg: #463b00;
$secondary: $gray-900;

1
ui/assets/css/themes/i386.min.css vendored Normal file

File diff suppressed because one or more lines are too long

12
ui/assets/css/themes/materia.min.css vendored Normal file

File diff suppressed because one or more lines are too long

3
ui/package.json vendored
View file

@ -17,7 +17,7 @@
"@types/jwt-decode": "^2.2.1", "@types/jwt-decode": "^2.2.1",
"@types/markdown-it": "^0.0.9", "@types/markdown-it": "^0.0.9",
"@types/markdown-it-container": "^2.0.2", "@types/markdown-it-container": "^2.0.2",
"@types/node": "^13.5.0", "@types/node": "^13.7.0",
"autosize": "^4.0.2", "autosize": "^4.0.2",
"bootswatch": "^4.3.1", "bootswatch": "^4.3.1",
"classcat": "^1.1.3", "classcat": "^1.1.3",
@ -35,6 +35,7 @@
"markdown-it-emoji": "^1.4.0", "markdown-it-emoji": "^1.4.0",
"moment": "^2.24.0", "moment": "^2.24.0",
"prettier": "^1.18.2", "prettier": "^1.18.2",
"reconnecting-websocket": "^4.3.0",
"rxjs": "^6.4.0", "rxjs": "^6.4.0",
"terser": "^4.6.3", "terser": "^4.6.3",
"toastify-js": "^1.6.2", "toastify-js": "^1.6.2",

View file

@ -15,7 +15,6 @@ import { WebSocketService, UserService } from '../services';
import autosize from 'autosize'; import autosize from 'autosize';
import Tribute from 'tributejs/src/Tribute.js'; import Tribute from 'tributejs/src/Tribute.js';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
interface CommentFormProps { interface CommentFormProps {
postId?: number; postId?: number;
@ -127,7 +126,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
.previewMode && 'active'}`} .previewMode && 'active'}`}
onClick={linkEvent(this, this.handlePreviewToggle)} onClick={linkEvent(this, this.handlePreviewToggle)}
> >
<T i18nKey="preview">#</T> {i18n.t('preview')}
</button> </button>
)} )}
{this.props.node && ( {this.props.node && (
@ -136,7 +135,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
class="btn btn-sm btn-secondary mr-2" class="btn btn-sm btn-secondary mr-2"
onClick={linkEvent(this, this.handleReplyCancel)} onClick={linkEvent(this, this.handleReplyCancel)}
> >
<T i18nKey="cancel">#</T> {i18n.t('cancel')}
</button> </button>
)} )}
<a <a
@ -144,14 +143,14 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
target="_blank" target="_blank"
class="d-inline-block float-right text-muted small font-weight-bold" class="d-inline-block float-right text-muted small font-weight-bold"
> >
<T i18nKey="formatting_help">#</T> {i18n.t('formatting_help')}
</a> </a>
<form class="d-inline-block mr-2 float-right text-muted small font-weight-bold"> <form class="d-inline-block mr-2 float-right text-muted small font-weight-bold">
<label <label
htmlFor={`file-upload-${this.id}`} htmlFor={`file-upload-${this.id}`}
className={`${UserService.Instance.user && 'pointer'}`} className={`${UserService.Instance.user && 'pointer'}`}
> >
<T i18nKey="upload_image">#</T> {i18n.t('upload_image')}
</label> </label>
<input <input
id={`file-upload-${this.id}`} id={`file-upload-${this.id}`}

View file

@ -30,7 +30,6 @@ import { MomentTime } from './moment-time';
import { CommentForm } from './comment-form'; import { CommentForm } from './comment-form';
import { CommentNodes } from './comment-nodes'; import { CommentNodes } from './comment-nodes';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
interface CommentNodeState { interface CommentNodeState {
showReply: boolean; showReply: boolean;
@ -117,7 +116,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
.viewOnly && 'no-click'}`} .viewOnly && 'no-click'}`}
> >
<button <button
className={`btn p-0 ${ className={`btn btn-link p-0 ${
node.comment.my_vote == 1 ? 'text-info' : 'text-muted' node.comment.my_vote == 1 ? 'text-info' : 'text-muted'
}`} }`}
onClick={linkEvent(node, this.handleCommentUpvote)} onClick={linkEvent(node, this.handleCommentUpvote)}
@ -137,7 +136,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
</div> </div>
{WebSocketService.Instance.site.enable_downvotes && ( {WebSocketService.Instance.site.enable_downvotes && (
<button <button
className={`btn p-0 ${ className={`btn btn-link p-0 ${
node.comment.my_vote == -1 ? 'text-danger' : 'text-muted' node.comment.my_vote == -1 ? 'text-danger' : 'text-muted'
}`} }`}
onClick={linkEvent(node, this.handleCommentDownvote)} onClick={linkEvent(node, this.handleCommentDownvote)}
@ -180,22 +179,22 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
</li> </li>
{this.isMod && ( {this.isMod && (
<li className="list-inline-item badge badge-light"> <li className="list-inline-item badge badge-light">
<T i18nKey="mod">#</T> {i18n.t('mod')}
</li> </li>
)} )}
{this.isAdmin && ( {this.isAdmin && (
<li className="list-inline-item badge badge-light"> <li className="list-inline-item badge badge-light">
<T i18nKey="admin">#</T> {i18n.t('admin')}
</li> </li>
)} )}
{this.isPostCreator && ( {this.isPostCreator && (
<li className="list-inline-item badge badge-light"> <li className="list-inline-item badge badge-light">
<T i18nKey="creator">#</T> {i18n.t('creator')}
</li> </li>
)} )}
{(node.comment.banned_from_community || node.comment.banned) && ( {(node.comment.banned_from_community || node.comment.banned) && (
<li className="list-inline-item badge badge-danger"> <li className="list-inline-item badge badge-danger">
<T i18nKey="banned">#</T> {i18n.t('banned')}
</li> </li>
)} )}
<li className="list-inline-item"> <li className="list-inline-item">
@ -258,7 +257,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleReplyClick)} onClick={linkEvent(this, this.handleReplyClick)}
> >
<T i18nKey="reply">#</T> {i18n.t('reply')}
</span> </span>
</li> </li>
<li className="list-inline-item mr-2"> <li className="list-inline-item mr-2">
@ -276,7 +275,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleEditClick)} onClick={linkEvent(this, this.handleEditClick)}
> >
<T i18nKey="edit">#</T> {i18n.t('edit')}
</span> </span>
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
@ -307,7 +306,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
className="pointer" className="pointer"
onClick={linkEvent(this, this.handleViewSource)} onClick={linkEvent(this, this.handleViewSource)}
> >
<T i18nKey="view_source">#</T> {i18n.t('view_source')}
</span> </span>
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
@ -315,7 +314,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
className="text-muted" className="text-muted"
to={`/post/${node.comment.post_id}/comment/${node.comment.id}`} to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}
> >
<T i18nKey="link">#</T> {i18n.t('link')}
</Link> </Link>
</li> </li>
{/* Admins and mods can remove comments */} {/* Admins and mods can remove comments */}
@ -331,7 +330,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.handleModRemoveShow this.handleModRemoveShow
)} )}
> >
<T i18nKey="remove">#</T> {i18n.t('remove')}
</span> </span>
) : ( ) : (
<span <span
@ -341,7 +340,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.handleModRemoveSubmit this.handleModRemoveSubmit
)} )}
> >
<T i18nKey="restore">#</T> {i18n.t('restore')}
</span> </span>
)} )}
</li> </li>
@ -360,7 +359,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.handleModBanFromCommunityShow this.handleModBanFromCommunityShow
)} )}
> >
<T i18nKey="ban">#</T> {i18n.t('ban')}
</span> </span>
) : ( ) : (
<span <span
@ -370,7 +369,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.handleModBanFromCommunitySubmit this.handleModBanFromCommunitySubmit
)} )}
> >
<T i18nKey="unban">#</T> {i18n.t('unban')}
</span> </span>
)} )}
</li> </li>
@ -392,7 +391,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
) : ( ) : (
<> <>
<span class="d-inline-block mr-1"> <span class="d-inline-block mr-1">
<T i18nKey="are_you_sure">#</T> {i18n.t('are_you_sure')}
</span> </span>
<span <span
class="pointer d-inline-block mr-1" class="pointer d-inline-block mr-1"
@ -401,7 +400,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.handleAddModToCommunity this.handleAddModToCommunity
)} )}
> >
<T i18nKey="yes">#</T> {i18n.t('yes')}
</span> </span>
<span <span
class="pointer d-inline-block" class="pointer d-inline-block"
@ -410,7 +409,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.handleCancelConfirmAppointAsMod this.handleCancelConfirmAppointAsMod
)} )}
> >
<T i18nKey="no">#</T> {i18n.t('no')}
</span> </span>
</> </>
)} )}
@ -429,12 +428,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.handleShowConfirmTransferCommunity this.handleShowConfirmTransferCommunity
)} )}
> >
<T i18nKey="transfer_community">#</T> {i18n.t('transfer_community')}
</span> </span>
) : ( ) : (
<> <>
<span class="d-inline-block mr-1"> <span class="d-inline-block mr-1">
<T i18nKey="are_you_sure">#</T> {i18n.t('are_you_sure')}
</span> </span>
<span <span
class="pointer d-inline-block mr-1" class="pointer d-inline-block mr-1"
@ -443,7 +442,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.handleTransferCommunity this.handleTransferCommunity
)} )}
> >
<T i18nKey="yes">#</T> {i18n.t('yes')}
</span> </span>
<span <span
class="pointer d-inline-block" class="pointer d-inline-block"
@ -452,7 +451,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.handleCancelShowConfirmTransferCommunity this.handleCancelShowConfirmTransferCommunity
)} )}
> >
<T i18nKey="no">#</T> {i18n.t('no')}
</span> </span>
</> </>
)} )}
@ -468,7 +467,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleModBanShow)} onClick={linkEvent(this, this.handleModBanShow)}
> >
<T i18nKey="ban_from_site">#</T> {i18n.t('ban_from_site')}
</span> </span>
) : ( ) : (
<span <span
@ -478,7 +477,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.handleModBanSubmit this.handleModBanSubmit
)} )}
> >
<T i18nKey="unban_from_site">#</T> {i18n.t('unban_from_site')}
</span> </span>
)} )}
</li> </li>
@ -500,13 +499,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
) : ( ) : (
<> <>
<span class="d-inline-block mr-1"> <span class="d-inline-block mr-1">
<T i18nKey="are_you_sure">#</T> {i18n.t('are_you_sure')}
</span> </span>
<span <span
class="pointer d-inline-block mr-1" class="pointer d-inline-block mr-1"
onClick={linkEvent(this, this.handleAddAdmin)} onClick={linkEvent(this, this.handleAddAdmin)}
> >
<T i18nKey="yes">#</T> {i18n.t('yes')}
</span> </span>
<span <span
class="pointer d-inline-block" class="pointer d-inline-block"
@ -515,7 +514,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.handleCancelConfirmAppointAsAdmin this.handleCancelConfirmAppointAsAdmin
)} )}
> >
<T i18nKey="no">#</T> {i18n.t('no')}
</span> </span>
</> </>
)} )}
@ -534,18 +533,18 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.handleShowConfirmTransferSite this.handleShowConfirmTransferSite
)} )}
> >
<T i18nKey="transfer_site">#</T> {i18n.t('transfer_site')}
</span> </span>
) : ( ) : (
<> <>
<span class="d-inline-block mr-1"> <span class="d-inline-block mr-1">
<T i18nKey="are_you_sure">#</T> {i18n.t('are_you_sure')}
</span> </span>
<span <span
class="pointer d-inline-block mr-1" class="pointer d-inline-block mr-1"
onClick={linkEvent(this, this.handleTransferSite)} onClick={linkEvent(this, this.handleTransferSite)}
> >
<T i18nKey="yes">#</T> {i18n.t('yes')}
</span> </span>
<span <span
class="pointer d-inline-block" class="pointer d-inline-block"
@ -554,7 +553,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.handleCancelShowConfirmTransferSite this.handleCancelShowConfirmTransferSite
)} )}
> >
<T i18nKey="no">#</T> {i18n.t('no')}
</span> </span>
</> </>
)} )}
@ -579,16 +578,14 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
onInput={linkEvent(this, this.handleModRemoveReasonChange)} onInput={linkEvent(this, this.handleModRemoveReasonChange)}
/> />
<button type="submit" class="btn btn-secondary"> <button type="submit" class="btn btn-secondary">
<T i18nKey="remove_comment">#</T> {i18n.t('remove_comment')}
</button> </button>
</form> </form>
)} )}
{this.state.showBanDialog && ( {this.state.showBanDialog && (
<form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}> <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
<div class="form-group row"> <div class="form-group row">
<label class="col-form-label"> <label class="col-form-label">{i18n.t('reason')}</label>
<T i18nKey="reason">#</T>
</label>
<input <input
type="text" type="text"
class="form-control mr-2" class="form-control mr-2"

View file

@ -15,7 +15,6 @@ import {
import { WebSocketService } from '../services'; import { WebSocketService } from '../services';
import { wsJsonToRes, toast } from '../utils'; import { wsJsonToRes, toast } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
declare const Sortable: any; declare const Sortable: any;
@ -83,30 +82,20 @@ export class Communities extends Component<any, CommunitiesState> {
</h5> </h5>
) : ( ) : (
<div> <div>
<h5> <h5>{i18n.t('list_of_communities')}</h5>
<T i18nKey="list_of_communities">#</T>
</h5>
<div class="table-responsive"> <div class="table-responsive">
<table id="community_table" class="table table-sm table-hover"> <table id="community_table" class="table table-sm table-hover">
<thead class="pointer"> <thead class="pointer">
<tr> <tr>
<th> <th>{i18n.t('name')}</th>
<T i18nKey="name">#</T> <th class="d-none d-lg-table-cell">{i18n.t('title')}</th>
</th> <th>{i18n.t('category')}</th>
<th class="d-none d-lg-table-cell"> <th class="text-right">{i18n.t('subscribers')}</th>
<T i18nKey="title">#</T> <th class="text-right d-none d-lg-table-cell">
</th> {i18n.t('posts')}
<th>
<T i18nKey="category">#</T>
</th>
<th class="text-right">
<T i18nKey="subscribers">#</T>
</th> </th>
<th class="text-right d-none d-lg-table-cell"> <th class="text-right d-none d-lg-table-cell">
<T i18nKey="posts">#</T> {i18n.t('comments')}
</th>
<th class="text-right d-none d-lg-table-cell">
<T i18nKey="comments">#</T>
</th> </th>
<th></th> <th></th>
</tr> </tr>
@ -139,7 +128,7 @@ export class Communities extends Component<any, CommunitiesState> {
this.handleUnsubscribe this.handleUnsubscribe
)} )}
> >
<T i18nKey="unsubscribe">#</T> {i18n.t('unsubscribe')}
</span> </span>
) : ( ) : (
<span <span
@ -149,7 +138,7 @@ export class Communities extends Component<any, CommunitiesState> {
this.handleSubscribe this.handleSubscribe
)} )}
> >
<T i18nKey="subscribe">#</T> {i18n.t('subscribe')}
</span> </span>
)} )}
</td> </td>
@ -173,15 +162,16 @@ export class Communities extends Component<any, CommunitiesState> {
class="btn btn-sm btn-secondary mr-1" class="btn btn-sm btn-secondary mr-1"
onClick={linkEvent(this, this.prevPage)} onClick={linkEvent(this, this.prevPage)}
> >
<T i18nKey="prev">#</T> {i18n.t('prev')}
</button> </button>
)} )}
{this.state.communities.length == communityLimit && ( {this.state.communities.length == communityLimit && (
<button <button
class="btn btn-sm btn-secondary" class="btn btn-sm btn-secondary"
onClick={linkEvent(this, this.nextPage)} onClick={linkEvent(this, this.nextPage)}
> >
<T i18nKey="next">#</T> {i18n.t('next')}
</button> </button>
)} )}
</div> </div>

View file

@ -21,7 +21,6 @@ import {
import Tribute from 'tributejs/src/Tribute.js'; import Tribute from 'tributejs/src/Tribute.js';
import autosize from 'autosize'; import autosize from 'autosize';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
import { Community } from '../interfaces'; import { Community } from '../interfaces';
@ -108,12 +107,13 @@ export class CommunityForm extends Component<
return ( return (
<form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}> <form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
<div class="form-group row"> <div class="form-group row">
<label class="col-12 col-form-label"> <label class="col-12 col-form-label" htmlFor="community-name">
<T i18nKey="name">#</T> {i18n.t('name')}
</label> </label>
<div class="col-12"> <div class="col-12">
<input <input
type="text" type="text"
id="community-name"
class="form-control" class="form-control"
value={this.state.communityForm.name} value={this.state.communityForm.name}
onInput={linkEvent(this, this.handleCommunityNameChange)} onInput={linkEvent(this, this.handleCommunityNameChange)}
@ -125,13 +125,15 @@ export class CommunityForm extends Component<
/> />
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-12 col-form-label"> <label class="col-12 col-form-label" htmlFor="community-title">
<T i18nKey="title">#</T> {i18n.t('title')}
</label> </label>
<div class="col-12"> <div class="col-12">
<input <input
type="text" type="text"
id="community-title"
value={this.state.communityForm.title} value={this.state.communityForm.title}
onInput={linkEvent(this, this.handleCommunityTitleChange)} onInput={linkEvent(this, this.handleCommunityTitleChange)}
class="form-control" class="form-control"
@ -142,8 +144,8 @@ export class CommunityForm extends Component<
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-12 col-form-label"> <label class="col-12 col-form-label" htmlFor={this.id}>
<T i18nKey="sidebar">#</T> {i18n.t('sidebar')}
</label> </label>
<div class="col-12"> <div class="col-12">
<textarea <textarea
@ -157,12 +159,13 @@ export class CommunityForm extends Component<
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-12 col-form-label"> <label class="col-12 col-form-label" htmlFor="community-category">
<T i18nKey="category">#</T> {i18n.t('category')}
</label> </label>
<div class="col-12"> <div class="col-12">
<select <select
class="form-control" class="form-control"
id="community-category"
value={this.state.communityForm.category_id} value={this.state.communityForm.category_id}
onInput={linkEvent(this, this.handleCommunityCategoryChange)} onInput={linkEvent(this, this.handleCommunityCategoryChange)}
> >
@ -179,12 +182,13 @@ export class CommunityForm extends Component<
<div class="form-check"> <div class="form-check">
<input <input
class="form-check-input" class="form-check-input"
id="community-nsfw"
type="checkbox" type="checkbox"
checked={this.state.communityForm.nsfw} checked={this.state.communityForm.nsfw}
onChange={linkEvent(this, this.handleCommunityNsfwChange)} onChange={linkEvent(this, this.handleCommunityNsfwChange)}
/> />
<label class="form-check-label"> <label class="form-check-label" htmlFor="community-nsfw">
<T i18nKey="nsfw">#</T> {i18n.t('nsfw')}
</label> </label>
</div> </div>
</div> </div>
@ -209,7 +213,7 @@ export class CommunityForm extends Component<
class="btn btn-secondary" class="btn btn-secondary"
onClick={linkEvent(this, this.handleCancel)} onClick={linkEvent(this, this.handleCancel)}
> >
<T i18nKey="cancel">#</T> {i18n.t('cancel')}
</button> </button>
)} )}
</div> </div>

View file

@ -14,21 +14,16 @@ import {
GetCommunityForm, GetCommunityForm,
ListingType, ListingType,
GetPostsResponse, GetPostsResponse,
CreatePostLikeResponse, PostResponse,
AddModToCommunityResponse,
BanFromCommunityResponse,
WebSocketJsonResponse, WebSocketJsonResponse,
} from '../interfaces'; } from '../interfaces';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { PostListings } from './post-listings'; import { PostListings } from './post-listings';
import { SortSelect } from './sort-select'; import { SortSelect } from './sort-select';
import { Sidebar } from './sidebar'; import { Sidebar } from './sidebar';
import { import { wsJsonToRes, routeSortTypeToEnum, fetchLimit, toast } from '../utils';
wsJsonToRes,
routeSortTypeToEnum,
fetchLimit,
postRefetchSeconds,
toast,
} from '../utils';
import { T } from 'inferno-i18next';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
interface State { interface State {
@ -37,6 +32,7 @@ interface State {
communityName: string; communityName: string;
moderators: Array<CommunityUser>; moderators: Array<CommunityUser>;
admins: Array<UserView>; admins: Array<UserView>;
online: number;
loading: boolean; loading: boolean;
posts: Array<Post>; posts: Array<Post>;
sort: SortType; sort: SortType;
@ -45,7 +41,6 @@ interface State {
export class Community extends Component<any, State> { export class Community extends Component<any, State> {
private subscription: Subscription; private subscription: Subscription;
private postFetcher: any;
private emptyState: State = { private emptyState: State = {
community: { community: {
id: null, id: null,
@ -67,6 +62,7 @@ export class Community extends Component<any, State> {
admins: [], admins: [],
communityId: Number(this.props.match.params.id), communityId: Number(this.props.match.params.id),
communityName: this.props.match.params.name, communityName: this.props.match.params.name,
online: null,
loading: true, loading: true,
posts: [], posts: [],
sort: this.getSortTypeFromProps(this.props), sort: this.getSortTypeFromProps(this.props),
@ -108,7 +104,6 @@ export class Community extends Component<any, State> {
componentWillUnmount() { componentWillUnmount() {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
clearInterval(this.postFetcher);
} }
// Necessary for back button for some reason // Necessary for back button for some reason
@ -140,12 +135,12 @@ export class Community extends Component<any, State> {
{this.state.community.title} {this.state.community.title}
{this.state.community.removed && ( {this.state.community.removed && (
<small className="ml-2 text-muted font-italic"> <small className="ml-2 text-muted font-italic">
<T i18nKey="removed">#</T> {i18n.t('removed')}
</small> </small>
)} )}
{this.state.community.nsfw && ( {this.state.community.nsfw && (
<small className="ml-2 text-muted font-italic"> <small className="ml-2 text-muted font-italic">
<T i18nKey="nsfw">#</T> {i18n.t('nsfw')}
</small> </small>
)} )}
</h5> </h5>
@ -158,6 +153,7 @@ export class Community extends Component<any, State> {
community={this.state.community} community={this.state.community}
moderators={this.state.moderators} moderators={this.state.moderators}
admins={this.state.admins} admins={this.state.admins}
online={this.state.online}
/> />
</div> </div>
</div> </div>
@ -192,7 +188,7 @@ export class Community extends Component<any, State> {
class="btn btn-sm btn-secondary mr-1" class="btn btn-sm btn-secondary mr-1"
onClick={linkEvent(this, this.prevPage)} onClick={linkEvent(this, this.prevPage)}
> >
<T i18nKey="prev">#</T> {i18n.t('prev')}
</button> </button>
)} )}
{this.state.posts.length == fetchLimit && ( {this.state.posts.length == fetchLimit && (
@ -200,7 +196,7 @@ export class Community extends Component<any, State> {
class="btn btn-sm btn-secondary" class="btn btn-sm btn-secondary"
onClick={linkEvent(this, this.nextPage)} onClick={linkEvent(this, this.nextPage)}
> >
<T i18nKey="next">#</T> {i18n.t('next')}
</button> </button>
)} )}
</div> </div>
@ -240,11 +236,6 @@ export class Community extends Component<any, State> {
); );
} }
keepFetchingPosts() {
this.fetchPosts();
this.postFetcher = setInterval(() => this.fetchPosts(), postRefetchSeconds);
}
fetchPosts() { fetchPosts() {
let getPostsForm: GetPostsForm = { let getPostsForm: GetPostsForm = {
page: this.state.page, page: this.state.page,
@ -268,9 +259,10 @@ export class Community extends Component<any, State> {
this.state.community = data.community; this.state.community = data.community;
this.state.moderators = data.moderators; this.state.moderators = data.moderators;
this.state.admins = data.admins; this.state.admins = data.admins;
this.state.online = data.online;
document.title = `/c/${this.state.community.name} - ${WebSocketService.Instance.site.name}`; document.title = `/c/${this.state.community.name} - ${WebSocketService.Instance.site.name}`;
this.setState(this.state); this.setState(this.state);
this.keepFetchingPosts(); this.fetchPosts();
} else if (res.op == UserOperation.EditCommunity) { } else if (res.op == UserOperation.EditCommunity) {
let data = res.data as CommunityResponse; let data = res.data as CommunityResponse;
this.state.community = data.community; this.state.community = data.community;
@ -286,13 +278,44 @@ export class Community extends Component<any, State> {
this.state.posts = data.posts; this.state.posts = data.posts;
this.state.loading = false; this.state.loading = false;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreatePostLike) { } else if (res.op == UserOperation.EditPost) {
let data = res.data as CreatePostLikeResponse; let data = res.data as PostResponse;
let found = this.state.posts.find(c => c.id == data.post.id); let found = this.state.posts.find(c => c.id == data.post.id);
found.my_vote = data.post.my_vote;
found.url = data.post.url;
found.name = data.post.name;
found.nsfw = data.post.nsfw;
this.setState(this.state);
} else if (res.op == UserOperation.CreatePost) {
let data = res.data as PostResponse;
this.state.posts.unshift(data.post);
this.setState(this.state);
} else if (res.op == UserOperation.CreatePostLike) {
let data = res.data as PostResponse;
let found = this.state.posts.find(c => c.id == data.post.id);
found.score = data.post.score; found.score = data.post.score;
found.upvotes = data.post.upvotes; found.upvotes = data.post.upvotes;
found.downvotes = data.post.downvotes; found.downvotes = data.post.downvotes;
if (data.post.my_vote !== null) {
found.my_vote = data.post.my_vote;
found.upvoteLoading = false;
found.downvoteLoading = false;
}
this.setState(this.state);
} else if (res.op == UserOperation.AddModToCommunity) {
let data = res.data as AddModToCommunityResponse;
this.state.moderators = data.moderators;
this.setState(this.state);
} else if (res.op == UserOperation.BanFromCommunity) {
let data = res.data as BanFromCommunityResponse;
this.state.posts
.filter(p => p.creator_id == data.user.id)
.forEach(p => (p.banned = data.banned));
this.setState(this.state); this.setState(this.state);
} }
} }

View file

@ -3,7 +3,6 @@ import { CommunityForm } from './community-form';
import { Community } from '../interfaces'; import { Community } from '../interfaces';
import { WebSocketService } from '../services'; import { WebSocketService } from '../services';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
export class CreateCommunity extends Component<any, any> { export class CreateCommunity extends Component<any, any> {
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -22,9 +21,7 @@ export class CreateCommunity extends Component<any, any> {
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12 col-lg-6 offset-lg-3 mb-4"> <div class="col-12 col-lg-6 offset-lg-3 mb-4">
<h5> <h5>{i18n.t('create_community')}</h5>
<T i18nKey="create_community">#</T>
</h5>
<CommunityForm onCreate={this.handleCommunityCreate} /> <CommunityForm onCreate={this.handleCommunityCreate} />
</div> </div>
</div> </div>

View file

@ -3,7 +3,6 @@ import { PostForm } from './post-form';
import { WebSocketService } from '../services'; import { WebSocketService } from '../services';
import { PostFormParams } from '../interfaces'; import { PostFormParams } from '../interfaces';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
export class CreatePost extends Component<any, any> { export class CreatePost extends Component<any, any> {
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -22,9 +21,7 @@ export class CreatePost extends Component<any, any> {
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12 col-lg-6 offset-lg-3 mb-4"> <div class="col-12 col-lg-6 offset-lg-3 mb-4">
<h5> <h5>{i18n.t('create_post')}</h5>
<T i18nKey="create_post">#</T>
</h5>
<PostForm onCreate={this.handlePostCreate} params={this.params} /> <PostForm onCreate={this.handlePostCreate} params={this.params} />
</div> </div>
</div> </div>

View file

@ -2,7 +2,7 @@ import { Component } from 'inferno';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { repoUrl } from '../utils'; import { repoUrl } from '../utils';
import { version } from '../version'; import { version } from '../version';
import { T } from 'inferno-i18next'; import { i18n } from '../i18next';
export class Footer extends Component<any, any> { export class Footer extends Component<any, any> {
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -19,22 +19,22 @@ export class Footer extends Component<any, any> {
</li> </li>
<li class="nav-item"> <li class="nav-item">
<Link class="nav-link" to="/modlog"> <Link class="nav-link" to="/modlog">
<T i18nKey="modlog">#</T> {i18n.t('modlog')}
</Link> </Link>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href={'/docs/index.html'}> <a class="nav-link" href={'/docs/index.html'}>
<T i18nKey="docs">#</T> {i18n.t('docs')}
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<Link class="nav-link" to="/sponsors"> <Link class="nav-link" to="/sponsors">
<T i18nKey="donate">#</T> {i18n.t('donate')}
</Link> </Link>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href={repoUrl}> <a class="nav-link" href={repoUrl}>
<T i18nKey="code">#</T> {i18n.t('code')}
</a> </a>
</li> </li>
</ul> </ul>

View file

@ -122,7 +122,7 @@ export class Inbox extends Component<any, InboxState> {
<ul class="list-inline mb-1 text-muted small font-weight-bold"> <ul class="list-inline mb-1 text-muted small font-weight-bold">
<li className="list-inline-item"> <li className="list-inline-item">
<span class="pointer" onClick={this.markAllAsRead}> <span class="pointer" onClick={this.markAllAsRead}>
<T i18nKey="mark_all_as_read">#</T> {i18n.t('mark_all_as_read')}
</span> </span>
</li> </li>
</ul> </ul>
@ -147,36 +147,20 @@ export class Inbox extends Component<any, InboxState> {
onChange={linkEvent(this, this.handleUnreadOrAllChange)} onChange={linkEvent(this, this.handleUnreadOrAllChange)}
class="custom-select custom-select-sm w-auto mr-2" class="custom-select custom-select-sm w-auto mr-2"
> >
<option disabled> <option disabled>{i18n.t('type')}</option>
<T i18nKey="type">#</T> <option value={UnreadOrAll.Unread}>{i18n.t('unread')}</option>
</option> <option value={UnreadOrAll.All}>{i18n.t('all')}</option>
<option value={UnreadOrAll.Unread}>
<T i18nKey="unread">#</T>
</option>
<option value={UnreadOrAll.All}>
<T i18nKey="all">#</T>
</option>
</select> </select>
<select <select
value={this.state.unreadType} value={this.state.unreadType}
onChange={linkEvent(this, this.handleUnreadTypeChange)} onChange={linkEvent(this, this.handleUnreadTypeChange)}
class="custom-select custom-select-sm w-auto mr-2" class="custom-select custom-select-sm w-auto mr-2"
> >
<option disabled> <option disabled>{i18n.t('type')}</option>
<T i18nKey="type">#</T> <option value={UnreadType.All}>{i18n.t('all')}</option>
</option> <option value={UnreadType.Replies}>{i18n.t('replies')}</option>
<option value={UnreadType.All}> <option value={UnreadType.Mentions}>{i18n.t('mentions')}</option>
<T i18nKey="all">#</T> <option value={UnreadType.Messages}>{i18n.t('messages')}</option>
</option>
<option value={UnreadType.Replies}>
<T i18nKey="replies">#</T>
</option>
<option value={UnreadType.Mentions}>
<T i18nKey="mentions">#</T>
</option>
<option value={UnreadType.Messages}>
<T i18nKey="messages">#</T>
</option>
</select> </select>
<SortSelect <SortSelect
sort={this.state.sort} sort={this.state.sort}
@ -248,14 +232,14 @@ export class Inbox extends Component<any, InboxState> {
class="btn btn-sm btn-secondary mr-1" class="btn btn-sm btn-secondary mr-1"
onClick={linkEvent(this, this.prevPage)} onClick={linkEvent(this, this.prevPage)}
> >
<T i18nKey="prev">#</T> {i18n.t('prev')}
</button> </button>
)} )}
<button <button
class="btn btn-sm btn-secondary" class="btn btn-sm btn-secondary"
onClick={linkEvent(this, this.nextPage)} onClick={linkEvent(this, this.nextPage)}
> >
<T i18nKey="next">#</T> {i18n.t('next')}
</button> </button>
</div> </div>
); );
@ -421,10 +405,25 @@ export class Inbox extends Component<any, InboxState> {
this.sendUnreadCount(); this.sendUnreadCount();
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreateComment) { } else if (res.op == UserOperation.CreateComment) {
// let res: CommentResponse = msg; let data = res.data as CommentResponse;
if (data.recipient_ids.includes(UserService.Instance.user.id)) {
this.state.replies.unshift(data.comment);
this.setState(this.state);
} else if (data.comment.creator_id == UserService.Instance.user.id) {
toast(i18n.t('reply_sent')); toast(i18n.t('reply_sent'));
// this.state.replies.unshift(res.comment); // TODO do this right }
// this.setState(this.state); 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.state.messages.unshift(data.message);
this.setState(this.state);
} else if (data.message.creator_id == UserService.Instance.user.id) {
toast(i18n.t('message_sent'));
}
this.setState(this.state);
} else if (res.op == UserOperation.SaveComment) { } else if (res.op == UserOperation.SaveComment) {
let data = res.data as CommentResponse; let data = res.data as CommentResponse;
let found = this.state.replies.find(c => c.id == data.comment.id); let found = this.state.replies.find(c => c.id == data.comment.id);

View file

@ -13,7 +13,6 @@ import {
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { wsJsonToRes, validEmail, toast } from '../utils'; import { wsJsonToRes, validEmail, toast } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
interface State { interface State {
loginForm: LoginForm; loginForm: LoginForm;
@ -78,15 +77,19 @@ export class Login extends Component<any, State> {
return ( return (
<div> <div>
<form onSubmit={linkEvent(this, this.handleLoginSubmit)}> <form onSubmit={linkEvent(this, this.handleLoginSubmit)}>
<h5>Login</h5> <h5>{i18n.t('login')}</h5>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label
<T i18nKey="email_or_username">#</T> class="col-sm-2 col-form-label"
htmlFor="login-email-or-username"
>
{i18n.t('email_or_username')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input <input
type="text" type="text"
class="form-control" class="form-control"
id="login-email-or-username"
value={this.state.loginForm.username_or_email} value={this.state.loginForm.username_or_email}
onInput={linkEvent(this, this.handleLoginUsernameChange)} onInput={linkEvent(this, this.handleLoginUsernameChange)}
required required
@ -95,12 +98,13 @@ export class Login extends Component<any, State> {
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label class="col-sm-2 col-form-label" htmlFor="login-password">
<T i18nKey="password">#</T> {i18n.t('password')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input <input
type="password" type="password"
id="login-password"
value={this.state.loginForm.password} value={this.state.loginForm.password}
onInput={linkEvent(this, this.handleLoginPasswordChange)} onInput={linkEvent(this, this.handleLoginPasswordChange)}
class="form-control" class="form-control"
@ -111,7 +115,7 @@ export class Login extends Component<any, State> {
onClick={linkEvent(this, this.handlePasswordReset)} onClick={linkEvent(this, this.handlePasswordReset)}
className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold" className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold"
> >
<T i18nKey="forgot_password">#</T> {i18n.t('forgot_password')}
</button> </button>
</div> </div>
</div> </div>
@ -135,16 +139,17 @@ export class Login extends Component<any, State> {
registerForm() { registerForm() {
return ( return (
<form onSubmit={linkEvent(this, this.handleRegisterSubmit)}> <form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
<h5> <h5>{i18n.t('sign_up')}</h5>
<T i18nKey="sign_up">#</T>
</h5>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label class="col-sm-2 col-form-label" htmlFor="register-username">
<T i18nKey="username">#</T> {i18n.t('username')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input <input
type="text" type="text"
id="register-username"
class="form-control" class="form-control"
value={this.state.registerForm.username} value={this.state.registerForm.username}
onInput={linkEvent(this, this.handleRegisterUsernameChange)} onInput={linkEvent(this, this.handleRegisterUsernameChange)}
@ -155,13 +160,15 @@ export class Login extends Component<any, State> {
/> />
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label class="col-sm-2 col-form-label" htmlFor="register-email">
<T i18nKey="email">#</T> {i18n.t('email')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input <input
type="email" type="email"
id="register-email"
class="form-control" class="form-control"
placeholder={i18n.t('optional')} placeholder={i18n.t('optional')}
value={this.state.registerForm.email} value={this.state.registerForm.email}
@ -170,13 +177,15 @@ export class Login extends Component<any, State> {
/> />
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label class="col-sm-2 col-form-label" htmlFor="register-password">
<T i18nKey="password">#</T> {i18n.t('password')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input <input
type="password" type="password"
id="register-password"
value={this.state.registerForm.password} value={this.state.registerForm.password}
onInput={linkEvent(this, this.handleRegisterPasswordChange)} onInput={linkEvent(this, this.handleRegisterPasswordChange)}
class="form-control" class="form-control"
@ -184,13 +193,18 @@ export class Login extends Component<any, State> {
/> />
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label
<T i18nKey="verify_password">#</T> class="col-sm-2 col-form-label"
htmlFor="register-verify-password"
>
{i18n.t('verify_password')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input <input
type="password" type="password"
id="register-verify-password"
value={this.state.registerForm.password_verify} value={this.state.registerForm.password_verify}
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)} onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
class="form-control" class="form-control"
@ -204,12 +218,13 @@ export class Login extends Component<any, State> {
<div class="form-check"> <div class="form-check">
<input <input
class="form-check-input" class="form-check-input"
id="register-show-nsfw"
type="checkbox" type="checkbox"
checked={this.state.registerForm.show_nsfw} checked={this.state.registerForm.show_nsfw}
onChange={linkEvent(this, this.handleRegisterShowNsfwChange)} onChange={linkEvent(this, this.handleRegisterShowNsfwChange)}
/> />
<label class="form-check-label"> <label class="form-check-label" htmlFor="register-show-nsfw">
<T i18nKey="show_nsfw">#</T> {i18n.t('show_nsfw')}
</label> </label>
</div> </div>
</div> </div>
@ -306,6 +321,7 @@ export class Login extends Component<any, State> {
this.state = this.emptyState; this.state = this.emptyState;
this.setState(this.state); this.setState(this.state);
UserService.Instance.login(data); UserService.Instance.login(data);
WebSocketService.Instance.userJoin();
toast(i18n.t('logged_in')); toast(i18n.t('logged_in'));
this.props.history.push('/'); this.props.history.push('/');
} else if (res.op == UserOperation.Register) { } else if (res.op == UserOperation.Register) {
@ -313,6 +329,7 @@ export class Login extends Component<any, State> {
this.state = this.emptyState; this.state = this.emptyState;
this.setState(this.state); this.setState(this.state);
UserService.Instance.login(data); UserService.Instance.login(data);
WebSocketService.Instance.userJoin();
this.props.history.push('/communities'); this.props.history.push('/communities');
} else if (res.op == UserOperation.PasswordReset) { } else if (res.op == UserOperation.PasswordReset) {
toast(i18n.t('reset_password_mail_sent')); toast(i18n.t('reset_password_mail_sent'));

View file

@ -14,9 +14,11 @@ import {
ListingType, ListingType,
SiteResponse, SiteResponse,
GetPostsResponse, GetPostsResponse,
CreatePostLikeResponse, PostResponse,
Post, Post,
GetPostsForm, GetPostsForm,
AddAdminResponse,
BanUserResponse,
WebSocketJsonResponse, WebSocketJsonResponse,
} from '../interfaces'; } from '../interfaces';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
@ -31,7 +33,6 @@ import {
fetchLimit, fetchLimit,
routeSortTypeToEnum, routeSortTypeToEnum,
routeListingTypeToEnum, routeListingTypeToEnum,
postRefetchSeconds,
pictshareAvatarThumbnail, pictshareAvatarThumbnail,
showAvatars, showAvatars,
toast, toast,
@ -42,7 +43,7 @@ import { T } from 'inferno-i18next';
interface MainState { interface MainState {
subscribedCommunities: Array<CommunityUser>; subscribedCommunities: Array<CommunityUser>;
trendingCommunities: Array<Community>; trendingCommunities: Array<Community>;
site: GetSiteResponse; siteRes: GetSiteResponse;
showEditSite: boolean; showEditSite: boolean;
loading: boolean; loading: boolean;
posts: Array<Post>; posts: Array<Post>;
@ -53,11 +54,10 @@ interface MainState {
export class Main extends Component<any, MainState> { export class Main extends Component<any, MainState> {
private subscription: Subscription; private subscription: Subscription;
private postFetcher: any;
private emptyState: MainState = { private emptyState: MainState = {
subscribedCommunities: [], subscribedCommunities: [],
trendingCommunities: [], trendingCommunities: [],
site: { siteRes: {
site: { site: {
id: null, id: null,
name: null, name: null,
@ -133,12 +133,11 @@ export class Main extends Component<any, MainState> {
WebSocketService.Instance.listCommunities(listCommunitiesForm); WebSocketService.Instance.listCommunities(listCommunitiesForm);
this.keepFetchingPosts(); this.fetchPosts();
} }
componentWillUnmount() { componentWillUnmount() {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
clearInterval(this.postFetcher);
} }
// Necessary for back button for some reason // Necessary for back button for some reason
@ -159,8 +158,10 @@ export class Main extends Component<any, MainState> {
return ( return (
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12 col-md-8">{this.posts()}</div> <main role="main" class="col-12 col-md-8">
<div class="col-12 col-md-4">{this.my_sidebar()}</div> {this.posts()}
</main>
<aside class="col-12 col-md-4">{this.my_sidebar()}</aside>
</div> </div>
</div> </div>
); );
@ -200,7 +201,7 @@ export class Main extends Component<any, MainState> {
class="btn btn-sm btn-secondary btn-block" class="btn btn-sm btn-secondary btn-block"
to="/create_community" to="/create_community"
> >
<T i18nKey="create_a_community">#</T> {i18n.t('create_a_community')}
</Link> </Link>
</div> </div>
</div> </div>
@ -241,7 +242,7 @@ export class Main extends Component<any, MainState> {
this.siteInfo() this.siteInfo()
) : ( ) : (
<SiteForm <SiteForm
site={this.state.site.site} site={this.state.siteRes.site}
onCancel={this.handleEditCancel} onCancel={this.handleEditCancel}
/> />
)} )}
@ -262,7 +263,7 @@ export class Main extends Component<any, MainState> {
<div> <div>
<div class="card border-secondary mb-3"> <div class="card border-secondary mb-3">
<div class="card-body"> <div class="card-body">
<h5 class="mb-0">{`${this.state.site.site.name}`}</h5> <h5 class="mb-0">{`${this.state.siteRes.site.name}`}</h5>
{this.canAdmin && ( {this.canAdmin && (
<ul class="list-inline mb-1 text-muted small font-weight-bold"> <ul class="list-inline mb-1 text-muted small font-weight-bold">
<li className="list-inline-item"> <li className="list-inline-item">
@ -270,74 +271,44 @@ export class Main extends Component<any, MainState> {
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleEditClick)} onClick={linkEvent(this, this.handleEditClick)}
> >
<T i18nKey="edit">#</T> {i18n.t('edit')}
</span> </span>
</li> </li>
</ul> </ul>
)} )}
<ul class="my-2 list-inline"> <ul class="my-2 list-inline">
<li className="list-inline-item badge badge-secondary"> <li className="list-inline-item badge badge-secondary">
<T {i18n.t('number_online', { count: this.state.siteRes.online })}
i18nKey="number_online"
interpolation={{ count: this.state.site.online }}
>
#
</T>
</li> </li>
<li className="list-inline-item badge badge-secondary"> <li className="list-inline-item badge badge-secondary">
<T {i18n.t('number_of_users', {
i18nKey="number_of_users" count: this.state.siteRes.site.number_of_users,
interpolation={{ })}
count: this.state.site.site.number_of_users,
}}
>
#
</T>
</li> </li>
<li className="list-inline-item badge badge-secondary"> <li className="list-inline-item badge badge-secondary">
<T {i18n.t('number_of_communities', {
i18nKey="number_of_communities" count: this.state.siteRes.site.number_of_communities,
interpolation={{ })}
count: this.state.site.site.number_of_communities,
}}
>
#
</T>
</li> </li>
<li className="list-inline-item badge badge-secondary"> <li className="list-inline-item badge badge-secondary">
<T {i18n.t('number_of_posts', {
i18nKey="number_of_posts" count: this.state.siteRes.site.number_of_posts,
interpolation={{ })}
count: this.state.site.site.number_of_posts,
}}
>
#
</T>
</li> </li>
<li className="list-inline-item badge badge-secondary"> <li className="list-inline-item badge badge-secondary">
<T {i18n.t('number_of_comments', {
i18nKey="number_of_comments" count: this.state.siteRes.site.number_of_comments,
interpolation={{ })}
count: this.state.site.site.number_of_comments,
}}
>
#
</T>
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
<Link className="badge badge-secondary" to="/modlog"> <Link className="badge badge-secondary" to="/modlog">
<T i18nKey="modlog">#</T> {i18n.t('modlog')}
</Link> </Link>
</li> </li>
</ul> </ul>
<ul class="mt-1 list-inline small mb-0"> <ul class="mt-1 list-inline small mb-0">
<li class="list-inline-item"> <li class="list-inline-item">{i18n.t('admins')}:</li>
<T i18nKey="admins" class="d-inline"> {this.state.siteRes.admins.map(admin => (
#
</T>
:
</li>
{this.state.site.admins.map(admin => (
<li class="list-inline-item"> <li class="list-inline-item">
<Link class="text-info" to={`/u/${admin.name}`}> <Link class="text-info" to={`/u/${admin.name}`}>
{admin.avatar && showAvatars() && ( {admin.avatar && showAvatars() && (
@ -355,13 +326,13 @@ export class Main extends Component<any, MainState> {
</ul> </ul>
</div> </div>
</div> </div>
{this.state.site.site.description && ( {this.state.siteRes.site.description && (
<div class="card border-secondary mb-3"> <div class="card border-secondary mb-3">
<div class="card-body"> <div class="card-body">
<div <div
className="md-div" className="md-div"
dangerouslySetInnerHTML={mdToHtml( dangerouslySetInnerHTML={mdToHtml(
this.state.site.site.description this.state.siteRes.site.description
)} )}
/> />
</div> </div>
@ -376,9 +347,7 @@ export class Main extends Component<any, MainState> {
<div class="card border-secondary"> <div class="card border-secondary">
<div class="card-body"> <div class="card-body">
<h5> <h5>
<T i18nKey="powered_by" class="d-inline"> {i18n.t('powered_by')}
#
</T>
<svg class="icon mx-2"> <svg class="icon mx-2">
<use xlinkHref="#icon-mouse">#</use> <use xlinkHref="#icon-mouse">#</use>
</svg> </svg>
@ -413,7 +382,7 @@ export class Main extends Component<any, MainState> {
posts() { posts() {
return ( return (
<div> <div class="main-content-wrapper">
{this.state.loading ? ( {this.state.loading ? (
<h5> <h5>
<svg class="icon icon-spinner spin"> <svg class="icon icon-spinner spin">
@ -476,7 +445,7 @@ export class Main extends Component<any, MainState> {
class="btn btn-sm btn-secondary mr-1" class="btn btn-sm btn-secondary mr-1"
onClick={linkEvent(this, this.prevPage)} onClick={linkEvent(this, this.prevPage)}
> >
<T i18nKey="prev">#</T> {i18n.t('prev')}
</button> </button>
)} )}
{this.state.posts.length == fetchLimit && ( {this.state.posts.length == fetchLimit && (
@ -484,7 +453,7 @@ export class Main extends Component<any, MainState> {
class="btn btn-sm btn-secondary" class="btn btn-sm btn-secondary"
onClick={linkEvent(this, this.nextPage)} onClick={linkEvent(this, this.nextPage)}
> >
<T i18nKey="next">#</T> {i18n.t('next')}
</button> </button>
)} )}
</div> </div>
@ -494,7 +463,7 @@ export class Main extends Component<any, MainState> {
get canAdmin(): boolean { get canAdmin(): boolean {
return ( return (
UserService.Instance.user && UserService.Instance.user &&
this.state.site.admins this.state.siteRes.admins
.map(a => a.id) .map(a => a.id)
.includes(UserService.Instance.user.id) .includes(UserService.Instance.user.id)
); );
@ -548,11 +517,6 @@ export class Main extends Component<any, MainState> {
window.scrollTo(0, 0); window.scrollTo(0, 0);
} }
keepFetchingPosts() {
this.fetchPosts();
this.postFetcher = setInterval(() => this.fetchPosts(), postRefetchSeconds);
}
fetchPosts() { fetchPosts() {
let getPostsForm: GetPostsForm = { let getPostsForm: GetPostsForm = {
page: this.state.page, page: this.state.page,
@ -584,15 +548,15 @@ export class Main extends Component<any, MainState> {
if (!data.site) { if (!data.site) {
this.context.router.history.push('/setup'); this.context.router.history.push('/setup');
} }
this.state.site.admins = data.admins; this.state.siteRes.admins = data.admins;
this.state.site.site = data.site; this.state.siteRes.site = data.site;
this.state.site.banned = data.banned; this.state.siteRes.banned = data.banned;
this.state.site.online = data.online; this.state.siteRes.online = data.online;
this.setState(this.state); this.setState(this.state);
document.title = `${WebSocketService.Instance.site.name}`; document.title = `${WebSocketService.Instance.site.name}`;
} else if (res.op == UserOperation.EditSite) { } else if (res.op == UserOperation.EditSite) {
let data = res.data as SiteResponse; let data = res.data as SiteResponse;
this.state.site.site = data.site; this.state.siteRes.site = data.site;
this.state.showEditSite = false; this.state.showEditSite = false;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.GetPosts) { } else if (res.op == UserOperation.GetPosts) {
@ -600,13 +564,67 @@ export class Main extends Component<any, MainState> {
this.state.posts = data.posts; this.state.posts = data.posts;
this.state.loading = false; this.state.loading = false;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreatePostLike) { } else if (res.op == UserOperation.CreatePost) {
let data = res.data as CreatePostLikeResponse; let data = res.data as PostResponse;
// If you're on subscribed, only push it if you're subscribed.
if (this.state.type_ == ListingType.Subscribed) {
if (
this.state.subscribedCommunities
.map(c => c.community_id)
.includes(data.post.community_id)
) {
this.state.posts.unshift(data.post);
}
} else {
this.state.posts.unshift(data.post);
}
this.setState(this.state);
} else if (res.op == UserOperation.EditPost) {
let data = res.data as PostResponse;
let found = this.state.posts.find(c => c.id == data.post.id); let found = this.state.posts.find(c => c.id == data.post.id);
found.my_vote = data.post.my_vote;
found.url = data.post.url;
found.name = data.post.name;
found.nsfw = data.post.nsfw;
this.setState(this.state);
} else if (res.op == UserOperation.CreatePostLike) {
let data = res.data as PostResponse;
let found = this.state.posts.find(c => c.id == data.post.id);
found.score = data.post.score; found.score = data.post.score;
found.upvotes = data.post.upvotes; found.upvotes = data.post.upvotes;
found.downvotes = data.post.downvotes; found.downvotes = data.post.downvotes;
if (data.post.my_vote !== null) {
found.my_vote = data.post.my_vote;
found.upvoteLoading = false;
found.downvoteLoading = false;
}
this.setState(this.state);
} else if (res.op == UserOperation.AddAdmin) {
let data = res.data as AddAdminResponse;
this.state.siteRes.admins = data.admins;
this.setState(this.state);
} else if (res.op == UserOperation.BanUser) {
let data = res.data as BanUserResponse;
let found = this.state.siteRes.banned.find(u => (u.id = data.user.id));
// Remove the banned if its found in the list, and the action is an unban
if (found && !data.banned) {
this.state.siteRes.banned = this.state.siteRes.banned.filter(
i => i.id !== data.user.id
);
} else {
this.state.siteRes.banned.push(data.user);
}
this.state.posts
.filter(p => p.creator_id == data.user.id)
.forEach(p => (p.banned = data.banned));
this.setState(this.state); this.setState(this.state);
} }
} }

View file

@ -15,6 +15,7 @@ import {
ModBan, ModBan,
ModAddCommunity, ModAddCommunity,
ModAdd, ModAdd,
WebSocketJsonResponse,
} from '../interfaces'; } from '../interfaces';
import { WebSocketService } from '../services'; import { WebSocketService } from '../services';
import { wsJsonToRes, addTypeInfo, fetchLimit, toast } from '../utils'; import { wsJsonToRes, addTypeInfo, fetchLimit, toast } from '../utils';
@ -359,15 +360,15 @@ export class Modlog extends Component<any, ModlogState> {
/c/{this.state.communityName}{' '} /c/{this.state.communityName}{' '}
</Link> </Link>
)} )}
<span>Modlog</span> <span>{i18n.t('modlog')}</span>
</h5> </h5>
<div class="table-responsive"> <div class="table-responsive">
<table id="modlog_table" class="table table-sm table-hover"> <table id="modlog_table" class="table table-sm table-hover">
<thead class="pointer"> <thead class="pointer">
<tr> <tr>
<th>Time</th> <th> {i18n.t('time')}</th>
<th>Mod</th> <th>{i18n.t('mod')}</th>
<th>Action</th> <th>{i18n.t('action')}</th>
</tr> </tr>
</thead> </thead>
{this.combined()} {this.combined()}
@ -388,14 +389,14 @@ export class Modlog extends Component<any, ModlogState> {
class="btn btn-sm btn-secondary mr-1" class="btn btn-sm btn-secondary mr-1"
onClick={linkEvent(this, this.prevPage)} onClick={linkEvent(this, this.prevPage)}
> >
Prev {i18n.t('prev')}
</button> </button>
)} )}
<button <button
class="btn btn-sm btn-secondary" class="btn btn-sm btn-secondary"
onClick={linkEvent(this, this.nextPage)} onClick={linkEvent(this, this.nextPage)}
> >
Next {i18n.t('next')}
</button> </button>
</div> </div>
); );

View file

@ -14,7 +14,9 @@ import {
SortType, SortType,
GetSiteResponse, GetSiteResponse,
Comment, Comment,
CommentResponse,
PrivateMessage, PrivateMessage,
PrivateMessageResponse,
WebSocketJsonResponse, WebSocketJsonResponse,
} from '../interfaces'; } from '../interfaces';
import { import {
@ -27,7 +29,6 @@ import {
} from '../utils'; } from '../utils';
import { version } from '../version'; import { version } from '../version';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
interface NavbarState { interface NavbarState {
isLoggedIn: boolean; isLoggedIn: boolean;
@ -35,7 +36,6 @@ interface NavbarState {
replies: Array<Comment>; replies: Array<Comment>;
mentions: Array<Comment>; mentions: Array<Comment>;
messages: Array<PrivateMessage>; messages: Array<PrivateMessage>;
fetchCount: number;
unreadCount: number; unreadCount: number;
siteName: string; siteName: string;
} }
@ -46,7 +46,6 @@ export class Navbar extends Component<any, NavbarState> {
emptyState: NavbarState = { emptyState: NavbarState = {
isLoggedIn: UserService.Instance.user !== undefined, isLoggedIn: UserService.Instance.user !== undefined,
unreadCount: 0, unreadCount: 0,
fetchCount: 0,
replies: [], replies: [],
mentions: [], mentions: [],
messages: [], messages: [],
@ -58,8 +57,6 @@ export class Navbar extends Component<any, NavbarState> {
super(props, context); super(props, context);
this.state = this.emptyState; this.state = this.emptyState;
this.keepFetchingUnreads();
// Subscribe to user changes // Subscribe to user changes
this.userSub = UserService.Instance.sub.subscribe(user => { this.userSub = UserService.Instance.sub.subscribe(user => {
this.state.isLoggedIn = user.user !== undefined; this.state.isLoggedIn = user.user !== undefined;
@ -78,13 +75,15 @@ export class Navbar extends Component<any, NavbarState> {
if (this.state.isLoggedIn) { if (this.state.isLoggedIn) {
this.requestNotificationPermission(); this.requestNotificationPermission();
// TODO couldn't get re-logging in to re-fetch unreads
this.fetchUnreads();
} }
WebSocketService.Instance.getSite(); WebSocketService.Instance.getSite();
} }
render() { render() {
return <div>{this.navbar()}</div>; return this.navbar();
} }
componentWillUnmount() { componentWillUnmount() {
@ -102,6 +101,7 @@ export class Navbar extends Component<any, NavbarState> {
<button <button
class="navbar-toggler" class="navbar-toggler"
type="button" type="button"
aria-label="menu"
onClick={linkEvent(this, this.expandNavbar)} onClick={linkEvent(this, this.expandNavbar)}
> >
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
@ -112,12 +112,12 @@ export class Navbar extends Component<any, NavbarState> {
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
<li class="nav-item"> <li class="nav-item">
<Link class="nav-link" to="/communities"> <Link class="nav-link" to="/communities">
<T i18nKey="communities">#</T> {i18n.t('communities')}
</Link> </Link>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<Link class="nav-link" to="/search"> <Link class="nav-link" to="/search">
<T i18nKey="search">#</T> {i18n.t('search')}
</Link> </Link>
</li> </li>
<li class="nav-item"> <li class="nav-item">
@ -128,12 +128,12 @@ export class Navbar extends Component<any, NavbarState> {
state: { prevPath: this.currentLocation }, state: { prevPath: this.currentLocation },
}} }}
> >
<T i18nKey="create_post">#</T> {i18n.t('create_post')}
</Link> </Link>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<Link class="nav-link" to="/create_community"> <Link class="nav-link" to="/create_community">
<T i18nKey="create_community">#</T> {i18n.t('create_community')}
</Link> </Link>
</li> </li>
<li className="nav-item"> <li className="nav-item">
@ -186,7 +186,7 @@ export class Navbar extends Component<any, NavbarState> {
</> </>
) : ( ) : (
<Link class="nav-link" to="/login"> <Link class="nav-link" to="/login">
<T i18nKey="login_sign_up">#</T> {i18n.t('login_sign_up')}
</Link> </Link>
)} )}
</ul> </ul>
@ -211,45 +211,51 @@ export class Navbar extends Component<any, NavbarState> {
} else if (res.op == UserOperation.GetReplies) { } else if (res.op == UserOperation.GetReplies) {
let data = res.data as GetRepliesResponse; let data = res.data as GetRepliesResponse;
let unreadReplies = data.replies.filter(r => !r.read); let unreadReplies = data.replies.filter(r => !r.read);
if (
unreadReplies.length > 0 &&
this.state.fetchCount > 1 &&
JSON.stringify(this.state.replies) !== JSON.stringify(unreadReplies)
) {
this.notify(unreadReplies);
}
this.state.replies = unreadReplies; this.state.replies = unreadReplies;
this.state.unreadCount = this.calculateUnreadCount();
this.setState(this.state); this.setState(this.state);
this.sendUnreadCount(); this.sendUnreadCount();
} else if (res.op == UserOperation.GetUserMentions) { } else if (res.op == UserOperation.GetUserMentions) {
let data = res.data as GetUserMentionsResponse; let data = res.data as GetUserMentionsResponse;
let unreadMentions = data.mentions.filter(r => !r.read); let unreadMentions = data.mentions.filter(r => !r.read);
if (
unreadMentions.length > 0 &&
this.state.fetchCount > 1 &&
JSON.stringify(this.state.mentions) !== JSON.stringify(unreadMentions)
) {
this.notify(unreadMentions);
}
this.state.mentions = unreadMentions; this.state.mentions = unreadMentions;
this.state.unreadCount = this.calculateUnreadCount();
this.setState(this.state); this.setState(this.state);
this.sendUnreadCount(); this.sendUnreadCount();
} else if (res.op == UserOperation.GetPrivateMessages) { } else if (res.op == UserOperation.GetPrivateMessages) {
let data = res.data as PrivateMessagesResponse; let data = res.data as PrivateMessagesResponse;
let unreadMessages = data.messages.filter(r => !r.read); let unreadMessages = data.messages.filter(r => !r.read);
if (
unreadMessages.length > 0 &&
this.state.fetchCount > 1 &&
JSON.stringify(this.state.messages) !== JSON.stringify(unreadMessages)
) {
this.notify(unreadMessages);
}
this.state.messages = unreadMessages; this.state.messages = unreadMessages;
this.state.unreadCount = this.calculateUnreadCount();
this.setState(this.state); this.setState(this.state);
this.sendUnreadCount(); this.sendUnreadCount();
} else if (res.op == UserOperation.CreateComment) {
let data = res.data as CommentResponse;
if (this.state.isLoggedIn) {
if (data.recipient_ids.includes(UserService.Instance.user.id)) {
this.state.replies.push(data.comment);
this.state.unreadCount++;
this.setState(this.state);
this.sendUnreadCount();
this.notify(data.comment);
}
}
} else if (res.op == UserOperation.CreatePrivateMessage) {
let data = res.data as PrivateMessageResponse;
if (this.state.isLoggedIn) {
if (data.message.recipient_id == UserService.Instance.user.id) {
this.state.messages.push(data.message);
this.state.unreadCount++;
this.setState(this.state);
this.sendUnreadCount();
this.notify(data.message);
}
}
} else if (res.op == UserOperation.GetSite) { } else if (res.op == UserOperation.GetSite) {
let data = res.data as GetSiteResponse; let data = res.data as GetSiteResponse;
@ -261,11 +267,6 @@ export class Navbar extends Component<any, NavbarState> {
} }
} }
keepFetchingUnreads() {
this.fetchUnreads();
setInterval(() => this.fetchUnreads(), 15000);
}
fetchUnreads() { fetchUnreads() {
if (this.state.isLoggedIn) { if (this.state.isLoggedIn) {
let repliesForm: GetRepliesForm = { let repliesForm: GetRepliesForm = {
@ -292,7 +293,6 @@ export class Navbar extends Component<any, NavbarState> {
WebSocketService.Instance.getReplies(repliesForm); WebSocketService.Instance.getReplies(repliesForm);
WebSocketService.Instance.getUserMentions(userMentionsForm); WebSocketService.Instance.getUserMentions(userMentionsForm);
WebSocketService.Instance.getPrivateMessages(privateMessagesForm); WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
this.state.fetchCount++;
} }
} }
} }
@ -304,11 +304,11 @@ export class Navbar extends Component<any, NavbarState> {
sendUnreadCount() { sendUnreadCount() {
UserService.Instance.sub.next({ UserService.Instance.sub.next({
user: UserService.Instance.user, user: UserService.Instance.user,
unreadCount: this.unreadCount, unreadCount: this.state.unreadCount,
}); });
} }
get unreadCount() { calculateUnreadCount(): number {
return ( return (
this.state.replies.filter(r => !r.read).length + this.state.replies.filter(r => !r.read).length +
this.state.mentions.filter(r => !r.read).length + this.state.mentions.filter(r => !r.read).length +
@ -330,24 +330,20 @@ export class Navbar extends Component<any, NavbarState> {
} }
} }
notify(replies: Array<Comment | PrivateMessage>) { notify(reply: Comment | PrivateMessage) {
let recentReply = replies[0];
if (Notification.permission !== 'granted') Notification.requestPermission(); if (Notification.permission !== 'granted') Notification.requestPermission();
else { else {
var notification = new Notification( var notification = new Notification(reply.creator_name, {
`${replies.length} ${i18n.t('unread_messages')}`, icon: reply.creator_avatar
{ ? reply.creator_avatar
icon: recentReply.creator_avatar
? recentReply.creator_avatar
: `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`, : `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`,
body: `${recentReply.creator_name}: ${recentReply.content}`, body: `${reply.content}`,
} });
);
notification.onclick = () => { notification.onclick = () => {
this.context.router.history.push( this.context.router.history.push(
isCommentType(recentReply) isCommentType(reply)
? `/post/${recentReply.post_id}/comment/${recentReply.id}` ? `/post/${reply.post_id}/comment/${reply.id}`
: `/inbox` : `/inbox`
); );
}; };

View file

@ -10,7 +10,6 @@ import {
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { wsJsonToRes, capitalizeFirstLetter, toast } from '../utils'; import { wsJsonToRes, capitalizeFirstLetter, toast } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
interface State { interface State {
passwordChangeForm: PasswordChangeForm; passwordChangeForm: PasswordChangeForm;
@ -58,9 +57,7 @@ export class PasswordChange extends Component<any, State> {
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12 col-lg-6 offset-lg-3 mb-4"> <div class="col-12 col-lg-6 offset-lg-3 mb-4">
<h5> <h5>{i18n.t('password_change')}</h5>
<T i18nKey="password_change">#</T>
</h5>
{this.passwordChangeForm()} {this.passwordChangeForm()}
</div> </div>
</div> </div>
@ -73,7 +70,7 @@ export class PasswordChange extends Component<any, State> {
<form onSubmit={linkEvent(this, this.handlePasswordChangeSubmit)}> <form onSubmit={linkEvent(this, this.handlePasswordChangeSubmit)}>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label class="col-sm-2 col-form-label">
<T i18nKey="new_password">#</T> {i18n.t('new_password')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input <input
@ -87,7 +84,7 @@ export class PasswordChange extends Component<any, State> {
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label class="col-sm-2 col-form-label">
<T i18nKey="verify_password">#</T> {i18n.t('verify_password')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input <input

View file

@ -36,7 +36,6 @@ import {
import autosize from 'autosize'; import autosize from 'autosize';
import Tribute from 'tributejs/src/Tribute.js'; import Tribute from 'tributejs/src/Tribute.js';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
interface PostFormProps { interface PostFormProps {
post?: Post; // If a post is given, that means this is an edit post?: Post; // If a post is given, that means this is an edit
@ -151,12 +150,13 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
<div> <div>
<form onSubmit={linkEvent(this, this.handlePostSubmit)}> <form onSubmit={linkEvent(this, this.handlePostSubmit)}>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label class="col-sm-2 col-form-label" htmlFor="post-url">
<T i18nKey="url">#</T> {i18n.t('url')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input <input
type="url" type="url"
id="post-url"
class="form-control" class="form-control"
value={this.state.postForm.url} value={this.state.postForm.url}
onInput={linkEvent(this, this.handlePostUrlChange)} onInput={linkEvent(this, this.handlePostUrlChange)}
@ -167,12 +167,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
class="mt-1 text-muted small font-weight-bold pointer" class="mt-1 text-muted small font-weight-bold pointer"
onClick={linkEvent(this, this.copySuggestedTitle)} onClick={linkEvent(this, this.copySuggestedTitle)}
> >
<T {i18n.t('copy_suggested_title', {
i18nKey="copy_suggested_title" title: this.state.suggestedTitle,
interpolation={{ title: this.state.suggestedTitle }} })}
>
#
</T>
</div> </div>
)} )}
<form> <form>
@ -181,7 +178,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
className={`${UserService.Instance.user && className={`${UserService.Instance.user &&
'pointer'} d-inline-block mr-2 float-right text-muted small font-weight-bold`} 'pointer'} d-inline-block mr-2 float-right text-muted small font-weight-bold`}
> >
<T i18nKey="upload_image">#</T> {i18n.t('upload_image')}
</label> </label>
<input <input
id="file-upload" id="file-upload"
@ -201,7 +198,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
target="_blank" target="_blank"
class="mr-2 d-inline-block float-right text-muted small font-weight-bold" class="mr-2 d-inline-block float-right text-muted small font-weight-bold"
> >
<T i18nKey="archive_link">#</T> {i18n.t('archive_link')}
</a> </a>
)} )}
{this.state.imageLoading && ( {this.state.imageLoading && (
@ -215,7 +212,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
{this.state.crossPosts.length > 0 && ( {this.state.crossPosts.length > 0 && (
<> <>
<div class="my-1 text-muted small font-weight-bold"> <div class="my-1 text-muted small font-weight-bold">
<T i18nKey="cross_posts">#</T> {i18n.t('cross_posts')}
</div> </div>
<PostListings showCommunity posts={this.state.crossPosts} /> <PostListings showCommunity posts={this.state.crossPosts} />
</> </>
@ -223,12 +220,13 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label class="col-sm-2 col-form-label" htmlFor="post-title">
<T i18nKey="title">#</T> {i18n.t('title')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<textarea <textarea
value={this.state.postForm.name} value={this.state.postForm.name}
id="post-title"
onInput={linkEvent(this, this.handlePostNameChange)} onInput={linkEvent(this, this.handlePostNameChange)}
class="form-control" class="form-control"
required required
@ -239,16 +237,17 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
{this.state.suggestedPosts.length > 0 && ( {this.state.suggestedPosts.length > 0 && (
<> <>
<div class="my-1 text-muted small font-weight-bold"> <div class="my-1 text-muted small font-weight-bold">
<T i18nKey="related_posts">#</T> {i18n.t('related_posts')}
</div> </div>
<PostListings posts={this.state.suggestedPosts} /> <PostListings posts={this.state.suggestedPosts} />
</> </>
)} )}
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label class="col-sm-2 col-form-label" htmlFor={this.id}>
<T i18nKey="body">#</T> {i18n.t('body')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<textarea <textarea
@ -271,7 +270,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
.previewMode && 'active'}`} .previewMode && 'active'}`}
onClick={linkEvent(this, this.handlePreviewToggle)} onClick={linkEvent(this, this.handlePreviewToggle)}
> >
<T i18nKey="preview">#</T> {i18n.t('preview')}
</button> </button>
)} )}
<a <a
@ -279,18 +278,19 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
target="_blank" target="_blank"
class="d-inline-block float-right text-muted small font-weight-bold" class="d-inline-block float-right text-muted small font-weight-bold"
> >
<T i18nKey="formatting_help">#</T> {i18n.t('formatting_help')}
</a> </a>
</div> </div>
</div> </div>
{!this.props.post && ( {!this.props.post && (
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label class="col-sm-2 col-form-label" htmlFor="post-community">
<T i18nKey="community">#</T> {i18n.t('community')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<select <select
class="form-control" class="form-control"
id="post-community"
value={this.state.postForm.community_id} value={this.state.postForm.community_id}
onInput={linkEvent(this, this.handlePostCommunityChange)} onInput={linkEvent(this, this.handlePostCommunityChange)}
> >
@ -307,12 +307,13 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
<div class="form-check"> <div class="form-check">
<input <input
class="form-check-input" class="form-check-input"
id="post-nsfw"
type="checkbox" type="checkbox"
checked={this.state.postForm.nsfw} checked={this.state.postForm.nsfw}
onChange={linkEvent(this, this.handlePostNsfwChange)} onChange={linkEvent(this, this.handlePostNsfwChange)}
/> />
<label class="form-check-label"> <label class="form-check-label" htmlFor="post-nsfw">
<T i18nKey="nsfw">#</T> {i18n.t('nsfw')}
</label> </label>
</div> </div>
</div> </div>
@ -337,7 +338,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
class="btn btn-secondary" class="btn btn-secondary"
onClick={linkEvent(this, this.handleCancel)} onClick={linkEvent(this, this.handleCancel)}
> >
<T i18nKey="cancel">#</T> {i18n.t('cancel')}
</button> </button>
)} )}
</div> </div>

View file

@ -30,7 +30,6 @@ import {
imageThumbnailer, imageThumbnailer,
} from '../utils'; } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
interface PostListingState { interface PostListingState {
showEdit: boolean; showEdit: boolean;
@ -119,7 +118,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<div class="listing col-12"> <div class="listing col-12">
<div className={`vote-bar mr-2 float-left small text-center`}> <div className={`vote-bar mr-2 float-left small text-center`}>
<button <button
className={`btn p-0 ${ className={`btn btn-link p-0 ${
post.my_vote == 1 ? 'text-info' : 'text-muted' post.my_vote == 1 ? 'text-info' : 'text-muted'
}`} }`}
onClick={linkEvent(this, this.handlePostLike)} onClick={linkEvent(this, this.handlePostLike)}
@ -137,7 +136,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<div class={`font-weight-bold text-muted`}>{post.score}</div> <div class={`font-weight-bold text-muted`}>{post.score}</div>
{WebSocketService.Instance.site.enable_downvotes && ( {WebSocketService.Instance.site.enable_downvotes && (
<button <button
className={`btn p-0 ${ className={`btn btn-link p-0 ${
post.my_vote == -1 ? 'text-danger' : 'text-muted' post.my_vote == -1 ? 'text-danger' : 'text-muted'
}`} }`}
onClick={linkEvent(this, this.handlePostDisLike)} onClick={linkEvent(this, this.handlePostDisLike)}
@ -247,27 +246,27 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
)} )}
{post.removed && ( {post.removed && (
<small className="ml-2 text-muted font-italic"> <small className="ml-2 text-muted font-italic">
<T i18nKey="removed">#</T> {i18n.t('removed')}
</small> </small>
)} )}
{post.deleted && ( {post.deleted && (
<small className="ml-2 text-muted font-italic"> <small className="ml-2 text-muted font-italic">
<T i18nKey="deleted">#</T> {i18n.t('deleted')}
</small> </small>
)} )}
{post.locked && ( {post.locked && (
<small className="ml-2 text-muted font-italic"> <small className="ml-2 text-muted font-italic">
<T i18nKey="locked">#</T> {i18n.t('locked')}
</small> </small>
)} )}
{post.stickied && ( {post.stickied && (
<small className="ml-2 text-muted font-italic"> <small className="ml-2 text-muted font-italic">
<T i18nKey="stickied">#</T> {i18n.t('stickied')}
</small> </small>
)} )}
{post.nsfw && ( {post.nsfw && (
<small className="ml-2 text-muted font-italic"> <small className="ml-2 text-muted font-italic">
<T i18nKey="nsfw">#</T> {i18n.t('nsfw')}
</small> </small>
)} )}
</div> </div>
@ -288,18 +287,16 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<span>{post.creator_name}</span> <span>{post.creator_name}</span>
</Link> </Link>
{this.isMod && ( {this.isMod && (
<span className="mx-1 badge badge-light"> <span className="mx-1 badge badge-light">{i18n.t('mod')}</span>
<T i18nKey="mod">#</T>
</span>
)} )}
{this.isAdmin && ( {this.isAdmin && (
<span className="mx-1 badge badge-light"> <span className="mx-1 badge badge-light">
<T i18nKey="admin">#</T> {i18n.t('admin')}
</span> </span>
)} )}
{(post.banned_from_community || post.banned) && ( {(post.banned_from_community || post.banned) && (
<span className="mx-1 badge badge-danger"> <span className="mx-1 badge badge-danger">
<T i18nKey="banned">#</T> {i18n.t('banned')}
</span> </span>
)} )}
{this.props.showCommunity && ( {this.props.showCommunity && (
@ -326,12 +323,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
<Link className="text-muted" to={`/post/${post.id}`}> <Link className="text-muted" to={`/post/${post.id}`}>
<T {i18n.t('number_of_comments', {
i18nKey="number_of_comments" count: post.number_of_comments,
interpolation={{ count: post.number_of_comments }} })}
>
#
</T>
</Link> </Link>
</li> </li>
</ul> </ul>
@ -353,7 +347,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
className="text-muted" className="text-muted"
to={`/create_post${this.crossPostParams}`} to={`/create_post${this.crossPostParams}`}
> >
<T i18nKey="cross_post">#</T> {i18n.t('cross_post')}
</Link> </Link>
</li> </li>
</> </>
@ -365,7 +359,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleEditClick)} onClick={linkEvent(this, this.handleEditClick)}
> >
<T i18nKey="edit">#</T> {i18n.t('edit')}
</span> </span>
</li> </li>
<li className="list-inline-item mr-2"> <li className="list-inline-item mr-2">
@ -406,14 +400,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleModRemoveShow)} onClick={linkEvent(this, this.handleModRemoveShow)}
> >
<T i18nKey="remove">#</T> {i18n.t('remove')}
</span> </span>
) : ( ) : (
<span <span
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleModRemoveSubmit)} onClick={linkEvent(this, this.handleModRemoveSubmit)}
> >
<T i18nKey="restore">#</T> {i18n.t('restore')}
</span> </span>
)} )}
</li> </li>
@ -430,7 +424,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this.handleModBanFromCommunityShow this.handleModBanFromCommunityShow
)} )}
> >
<T i18nKey="ban">#</T> {i18n.t('ban')}
</span> </span>
) : ( ) : (
<span <span
@ -440,7 +434,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this.handleModBanFromCommunitySubmit this.handleModBanFromCommunitySubmit
)} )}
> >
<T i18nKey="unban">#</T> {i18n.t('unban')}
</span> </span>
)} )}
</li> </li>
@ -473,12 +467,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this.handleShowConfirmTransferCommunity this.handleShowConfirmTransferCommunity
)} )}
> >
<T i18nKey="transfer_community">#</T> {i18n.t('transfer_community')}
</span> </span>
) : ( ) : (
<> <>
<span class="d-inline-block mr-1"> <span class="d-inline-block mr-1">
<T i18nKey="are_you_sure">#</T> {i18n.t('are_you_sure')}
</span> </span>
<span <span
class="pointer d-inline-block mr-1" class="pointer d-inline-block mr-1"
@ -487,7 +481,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this.handleTransferCommunity this.handleTransferCommunity
)} )}
> >
<T i18nKey="yes">#</T> {i18n.t('yes')}
</span> </span>
<span <span
class="pointer d-inline-block" class="pointer d-inline-block"
@ -496,7 +490,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this.handleCancelShowConfirmTransferCommunity this.handleCancelShowConfirmTransferCommunity
)} )}
> >
<T i18nKey="no">#</T> {i18n.t('no')}
</span> </span>
</> </>
)} )}
@ -512,14 +506,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleModBanShow)} onClick={linkEvent(this, this.handleModBanShow)}
> >
<T i18nKey="ban_from_site">#</T> {i18n.t('ban_from_site')}
</span> </span>
) : ( ) : (
<span <span
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleModBanSubmit)} onClick={linkEvent(this, this.handleModBanSubmit)}
> >
<T i18nKey="unban_from_site">#</T> {i18n.t('unban_from_site')}
</span> </span>
)} )}
</li> </li>
@ -549,18 +543,18 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this.handleShowConfirmTransferSite this.handleShowConfirmTransferSite
)} )}
> >
<T i18nKey="transfer_site">#</T> {i18n.t('transfer_site')}
</span> </span>
) : ( ) : (
<> <>
<span class="d-inline-block mr-1"> <span class="d-inline-block mr-1">
<T i18nKey="are_you_sure">#</T> {i18n.t('are_you_sure')}
</span> </span>
<span <span
class="pointer d-inline-block mr-1" class="pointer d-inline-block mr-1"
onClick={linkEvent(this, this.handleTransferSite)} onClick={linkEvent(this, this.handleTransferSite)}
> >
<T i18nKey="yes">#</T> {i18n.t('yes')}
</span> </span>
<span <span
class="pointer d-inline-block" class="pointer d-inline-block"
@ -569,7 +563,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this.handleCancelShowConfirmTransferSite this.handleCancelShowConfirmTransferSite
)} )}
> >
<T i18nKey="no">#</T> {i18n.t('no')}
</span> </span>
</> </>
)} )}
@ -583,7 +577,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
className="pointer" className="pointer"
onClick={linkEvent(this, this.handleViewSource)} onClick={linkEvent(this, this.handleViewSource)}
> >
<T i18nKey="view_source">#</T> {i18n.t('view_source')}
</span> </span>
</li> </li>
)} )}
@ -601,18 +595,19 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
onInput={linkEvent(this, this.handleModRemoveReasonChange)} onInput={linkEvent(this, this.handleModRemoveReasonChange)}
/> />
<button type="submit" class="btn btn-secondary"> <button type="submit" class="btn btn-secondary">
<T i18nKey="remove_post">#</T> {i18n.t('remove_post')}
</button> </button>
</form> </form>
)} )}
{this.state.showBanDialog && ( {this.state.showBanDialog && (
<form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}> <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
<div class="form-group row"> <div class="form-group row">
<label class="col-form-label"> <label class="col-form-label" htmlFor="post-listing-reason">
<T i18nKey="reason">#</T> {i18n.t('reason')}
</label> </label>
<input <input
type="text" type="text"
id="post-listing-reason"
class="form-control mr-2" class="form-control mr-2"
placeholder={i18n.t('reason')} placeholder={i18n.t('reason')}
value={this.state.banReason} value={this.state.banReason}

View file

@ -2,7 +2,7 @@ import { Component } from 'inferno';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { Post } from '../interfaces'; import { Post } from '../interfaces';
import { PostListing } from './post-listing'; import { PostListing } from './post-listing';
import { T } from 'inferno-i18next'; import { i18n } from '../i18next';
interface PostListingsProps { interface PostListingsProps {
posts: Array<Post>; posts: Array<Post>;
@ -30,14 +30,12 @@ export class PostListings extends Component<PostListingsProps, any> {
)) ))
) : ( ) : (
<> <>
<div> <div>{i18n.t('no_posts')}</div>
<T i18nKey="no_posts">#</T>
</div>
{this.props.showCommunity !== undefined && ( {this.props.showCommunity !== undefined && (
<div> <div>
<T i18nKey="subscribe_to_communities"> <Link to="/communities">
#<Link to="/communities">#</Link> {i18n.t('subscribe_to_communities')}
</T> </Link>
</div> </div>
)} )}
</> </>

View file

@ -11,7 +11,6 @@ import {
CommentForm as CommentFormI, CommentForm as CommentFormI,
CommentResponse, CommentResponse,
CommentSortType, CommentSortType,
CreatePostLikeResponse,
CommunityUser, CommunityUser,
CommunityResponse, CommunityResponse,
CommentNode as CommentNodeI, CommentNode as CommentNodeI,
@ -38,7 +37,6 @@ import { CommentForm } from './comment-form';
import { CommentNodes } from './comment-nodes'; import { CommentNodes } from './comment-nodes';
import autosize from 'autosize'; import autosize from 'autosize';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
interface PostState { interface PostState {
post: PostI; post: PostI;
@ -47,6 +45,7 @@ interface PostState {
community: Community; community: Community;
moderators: Array<CommunityUser>; moderators: Array<CommunityUser>;
admins: Array<UserView>; admins: Array<UserView>;
online: number;
scrolled?: boolean; scrolled?: boolean;
scrolled_comment_id?: number; scrolled_comment_id?: number;
loading: boolean; loading: boolean;
@ -62,6 +61,7 @@ export class Post extends Component<any, PostState> {
community: null, community: null,
moderators: [], moderators: [],
admins: [], admins: [],
online: null,
scrolled: false, scrolled: false,
loading: true, loading: true,
crossPosts: [], crossPosts: [],
@ -173,7 +173,7 @@ export class Post extends Component<any, PostState> {
{this.state.crossPosts.length > 0 && ( {this.state.crossPosts.length > 0 && (
<> <>
<div class="my-1 text-muted small font-weight-bold"> <div class="my-1 text-muted small font-weight-bold">
<T i18nKey="cross_posts">#</T> {i18n.t('cross_posts')}
</div> </div>
<PostListings showCommunity posts={this.state.crossPosts} /> <PostListings showCommunity posts={this.state.crossPosts} />
</> </>
@ -255,9 +255,7 @@ export class Post extends Component<any, PostState> {
return ( return (
<div class="d-none d-md-block new-comments mb-3 card border-secondary"> <div class="d-none d-md-block new-comments mb-3 card border-secondary">
<div class="card-body small"> <div class="card-body small">
<h6> <h6>{i18n.t('recent_comments')}</h6>
<T i18nKey="recent_comments">#</T>
</h6>
{this.state.comments.map(comment => ( {this.state.comments.map(comment => (
<CommentNodes <CommentNodes
nodes={[{ comment: comment }]} nodes={[{ comment: comment }]}
@ -280,6 +278,7 @@ export class Post extends Component<any, PostState> {
community={this.state.community} community={this.state.community}
moderators={this.state.moderators} moderators={this.state.moderators}
admins={this.state.admins} admins={this.state.admins}
online={this.state.online}
/> />
</div> </div>
); );
@ -378,6 +377,7 @@ export class Post extends Component<any, PostState> {
this.state.community = data.community; this.state.community = data.community;
this.state.moderators = data.moderators; this.state.moderators = data.moderators;
this.state.admins = data.admins; this.state.admins = data.admins;
this.state.online = data.online;
this.state.loading = false; this.state.loading = false;
document.title = `${this.state.post.name} - ${WebSocketService.Instance.site.name}`; document.title = `${this.state.post.name} - ${WebSocketService.Instance.site.name}`;
@ -396,8 +396,12 @@ export class Post extends Component<any, PostState> {
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreateComment) { } else if (res.op == UserOperation.CreateComment) {
let data = res.data as CommentResponse; let data = res.data as CommentResponse;
// Necessary since it might be a user reply
if (data.recipient_ids.length == 0) {
this.state.comments.unshift(data.comment); this.state.comments.unshift(data.comment);
this.setState(this.state); this.setState(this.state);
}
} else if (res.op == UserOperation.EditComment) { } else if (res.op == UserOperation.EditComment) {
let data = res.data as CommentResponse; let data = res.data as CommentResponse;
let found = this.state.comments.find(c => c.id == data.comment.id); let found = this.state.comments.find(c => c.id == data.comment.id);
@ -431,11 +435,16 @@ export class Post extends Component<any, PostState> {
} }
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreatePostLike) { } else if (res.op == UserOperation.CreatePostLike) {
let data = res.data as CreatePostLikeResponse; let data = res.data as PostResponse;
this.state.post.my_vote = data.post.my_vote;
this.state.post.score = data.post.score; this.state.post.score = data.post.score;
this.state.post.upvotes = data.post.upvotes; this.state.post.upvotes = data.post.upvotes;
this.state.post.downvotes = data.post.downvotes; this.state.post.downvotes = data.post.downvotes;
if (data.post.my_vote !== null) {
this.state.post.my_vote = data.post.my_vote;
this.state.post.upvoteLoading = false;
this.state.post.downvoteLoading = false;
}
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.EditPost) { } else if (res.op == UserOperation.EditPost) {
let data = res.data as PostResponse; let data = res.data as PostResponse;

View file

@ -14,7 +14,6 @@ import {
import { MomentTime } from './moment-time'; import { MomentTime } from './moment-time';
import { PrivateMessageForm } from './private-message-form'; import { PrivateMessageForm } from './private-message-form';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
interface PrivateMessageState { interface PrivateMessageState {
showReply: boolean; showReply: boolean;
@ -140,7 +139,7 @@ export class PrivateMessage extends Component<
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleReplyClick)} onClick={linkEvent(this, this.handleReplyClick)}
> >
<T i18nKey="reply">#</T> {i18n.t('reply')}
</span> </span>
</li> </li>
</> </>
@ -152,7 +151,7 @@ export class PrivateMessage extends Component<
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleEditClick)} onClick={linkEvent(this, this.handleEditClick)}
> >
<T i18nKey="edit">#</T> {i18n.t('edit')}
</span> </span>
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
@ -173,7 +172,7 @@ export class PrivateMessage extends Component<
className="pointer" className="pointer"
onClick={linkEvent(this, this.handleViewSource)} onClick={linkEvent(this, this.handleViewSource)}
> >
<T i18nKey="view_source">#</T> {i18n.t('view_source')}
</span> </span>
</li> </li>
</ul> </ul>

View file

@ -12,7 +12,7 @@ import {
SearchForm, SearchForm,
SearchResponse, SearchResponse,
SearchType, SearchType,
CreatePostLikeResponse, PostResponse,
CommentResponse, CommentResponse,
WebSocketJsonResponse, WebSocketJsonResponse,
} from '../interfaces'; } from '../interfaces';
@ -30,7 +30,6 @@ import { PostListing } from './post-listing';
import { SortSelect } from './sort-select'; import { SortSelect } from './sort-select';
import { CommentNodes } from './comment-nodes'; import { CommentNodes } from './comment-nodes';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
interface SearchState { interface SearchState {
q: string; q: string;
@ -126,9 +125,7 @@ export class Search extends Component<any, SearchState> {
render() { render() {
return ( return (
<div class="container"> <div class="container">
<h5> <h5>{i18n.t('search')}</h5>
<T i18nKey="search">#</T>
</h5>
{this.selects()} {this.selects()}
{this.searchForm()} {this.searchForm()}
{this.state.type_ == SearchType.All && this.all()} {this.state.type_ == SearchType.All && this.all()}
@ -163,9 +160,7 @@ export class Search extends Component<any, SearchState> {
<use xlinkHref="#icon-spinner"></use> <use xlinkHref="#icon-spinner"></use>
</svg> </svg>
) : ( ) : (
<span> <span>{i18n.t('search')}</span>
<T i18nKey="search">#</T>
</span>
)} )}
</button> </button>
</form> </form>
@ -180,24 +175,14 @@ export class Search extends Component<any, SearchState> {
onChange={linkEvent(this, this.handleTypeChange)} onChange={linkEvent(this, this.handleTypeChange)}
class="custom-select custom-select-sm w-auto" class="custom-select custom-select-sm w-auto"
> >
<option disabled> <option disabled>{i18n.t('type')}</option>
<T i18nKey="type">#</T> <option value={SearchType.All}>{i18n.t('all')}</option>
</option> <option value={SearchType.Comments}>{i18n.t('comments')}</option>
<option value={SearchType.All}> <option value={SearchType.Posts}>{i18n.t('posts')}</option>
<T i18nKey="all">#</T>
</option>
<option value={SearchType.Comments}>
<T i18nKey="comments">#</T>
</option>
<option value={SearchType.Posts}>
<T i18nKey="posts">#</T>
</option>
<option value={SearchType.Communities}> <option value={SearchType.Communities}>
<T i18nKey="communities">#</T> {i18n.t('communities')}
</option>
<option value={SearchType.Users}>
<T i18nKey="users">#</T>
</option> </option>
<option value={SearchType.Users}>{i18n.t('users')}</option>
</select> </select>
<span class="ml-2"> <span class="ml-2">
<SortSelect <SortSelect
@ -383,14 +368,14 @@ export class Search extends Component<any, SearchState> {
class="btn btn-sm btn-secondary mr-1" class="btn btn-sm btn-secondary mr-1"
onClick={linkEvent(this, this.prevPage)} onClick={linkEvent(this, this.prevPage)}
> >
<T i18nKey="prev">#</T> {i18n.t('prev')}
</button> </button>
)} )}
<button <button
class="btn btn-sm btn-secondary" class="btn btn-sm btn-secondary"
onClick={linkEvent(this, this.nextPage)} onClick={linkEvent(this, this.nextPage)}
> >
<T i18nKey="next">#</T> {i18n.t('next')}
</button> </button>
</div> </div>
); );
@ -404,11 +389,7 @@ export class Search extends Component<any, SearchState> {
res.posts.length == 0 && res.posts.length == 0 &&
res.comments.length == 0 && res.comments.length == 0 &&
res.communities.length == 0 && res.communities.length == 0 &&
res.users.length == 0 && ( res.users.length == 0 && <span>{i18n.t('no_results')}</span>}
<span>
<T i18nKey="no_results">#</T>
</span>
)}
</div> </div>
); );
} }
@ -506,7 +487,7 @@ export class Search extends Component<any, SearchState> {
} }
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreatePostLike) { } else if (res.op == UserOperation.CreatePostLike) {
let data = res.data as CreatePostLikeResponse; let data = res.data as PostResponse;
let found = this.state.searchResponse.posts.find( let found = this.state.searchResponse.posts.find(
c => c.id == data.post.id c => c.id == data.post.id
); );

View file

@ -11,7 +11,6 @@ import { WebSocketService, UserService } from '../services';
import { wsJsonToRes, toast } from '../utils'; import { wsJsonToRes, toast } from '../utils';
import { SiteForm } from './site-form'; import { SiteForm } from './site-form';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
interface State { interface State {
userForm: RegisterForm; userForm: RegisterForm;
@ -61,9 +60,7 @@ export class Setup extends Component<any, State> {
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12 offset-lg-3 col-lg-6"> <div class="col-12 offset-lg-3 col-lg-6">
<h3> <h3>{i18n.t('lemmy_instance_setup')}</h3>
<T i18nKey="lemmy_instance_setup">#</T>
</h3>
{!this.state.doneRegisteringUser ? ( {!this.state.doneRegisteringUser ? (
this.registerUser() this.registerUser()
) : ( ) : (
@ -78,17 +75,16 @@ export class Setup extends Component<any, State> {
registerUser() { registerUser() {
return ( return (
<form onSubmit={linkEvent(this, this.handleRegisterSubmit)}> <form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
<h5> <h5>{i18n.t('setup_admin')}</h5>
<T i18nKey="setup_admin">#</T>
</h5>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label class="col-sm-2 col-form-label" htmlFor="username">
<T i18nKey="username">#</T> {i18n.t('username')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input <input
type="text" type="text"
class="form-control" class="form-control"
id="username"
value={this.state.userForm.username} value={this.state.userForm.username}
onInput={linkEvent(this, this.handleRegisterUsernameChange)} onInput={linkEvent(this, this.handleRegisterUsernameChange)}
required required
@ -99,12 +95,14 @@ export class Setup extends Component<any, State> {
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label class="col-sm-2 col-form-label" htmlFor="email">
<T i18nKey="email">#</T> {i18n.t('email')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input <input
type="email" type="email"
id="email"
class="form-control" class="form-control"
placeholder={i18n.t('optional')} placeholder={i18n.t('optional')}
value={this.state.userForm.email} value={this.state.userForm.email}
@ -114,12 +112,13 @@ export class Setup extends Component<any, State> {
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label class="col-sm-2 col-form-label" htmlFor="password">
<T i18nKey="password">#</T> {i18n.t('password')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input <input
type="password" type="password"
id="password"
value={this.state.userForm.password} value={this.state.userForm.password}
onInput={linkEvent(this, this.handleRegisterPasswordChange)} onInput={linkEvent(this, this.handleRegisterPasswordChange)}
class="form-control" class="form-control"
@ -128,12 +127,13 @@ export class Setup extends Component<any, State> {
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label"> <label class="col-sm-2 col-form-label" htmlFor="verify-password">
<T i18nKey="verify_password">#</T> {i18n.t('verify_password')}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input <input
type="password" type="password"
id="verify-password"
value={this.state.userForm.password_verify} value={this.state.userForm.password_verify}
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)} onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
class="form-control" class="form-control"

View file

@ -16,12 +16,12 @@ import {
} from '../utils'; } from '../utils';
import { CommunityForm } from './community-form'; import { CommunityForm } from './community-form';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
interface SidebarProps { interface SidebarProps {
community: Community; community: Community;
moderators: Array<CommunityUser>; moderators: Array<CommunityUser>;
admins: Array<UserView>; admins: Array<UserView>;
online: number;
} }
interface SidebarState { interface SidebarState {
@ -72,12 +72,12 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<span>{community.title}</span> <span>{community.title}</span>
{community.removed && ( {community.removed && (
<small className="ml-2 text-muted font-italic"> <small className="ml-2 text-muted font-italic">
<T i18nKey="removed">#</T> {i18n.t('removed')}
</small> </small>
)} )}
{community.deleted && ( {community.deleted && (
<small className="ml-2 text-muted font-italic"> <small className="ml-2 text-muted font-italic">
<T i18nKey="deleted">#</T> {i18n.t('deleted')}
</small> </small>
)} )}
</h5> </h5>
@ -92,7 +92,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleEditClick)} onClick={linkEvent(this, this.handleEditClick)}
> >
<T i18nKey="edit">#</T> {i18n.t('edit')}
</span> </span>
</li> </li>
{this.amCreator && ( {this.amCreator && (
@ -116,14 +116,14 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleModRemoveShow)} onClick={linkEvent(this, this.handleModRemoveShow)}
> >
<T i18nKey="remove">#</T> {i18n.t('remove')}
</span> </span>
) : ( ) : (
<span <span
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleModRemoveSubmit)} onClick={linkEvent(this, this.handleModRemoveSubmit)}
> >
<T i18nKey="restore">#</T> {i18n.t('restore')}
</span> </span>
)} )}
</li> </li>
@ -132,11 +132,12 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
{this.state.showRemoveDialog && ( {this.state.showRemoveDialog && (
<form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}> <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
<div class="form-group row"> <div class="form-group row">
<label class="col-form-label"> <label class="col-form-label" htmlFor="remove-reason">
<T i18nKey="reason">#</T> {i18n.t('reason')}
</label> </label>
<input <input
type="text" type="text"
id="remove-reason"
class="form-control mr-2" class="form-control mr-2"
placeholder={i18n.t('optional')} placeholder={i18n.t('optional')}
value={this.state.removeReason} value={this.state.removeReason}
@ -150,47 +151,41 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
{/* </div> */} {/* </div> */}
<div class="form-group row"> <div class="form-group row">
<button type="submit" class="btn btn-secondary"> <button type="submit" class="btn btn-secondary">
<T i18nKey="remove_community">#</T> {i18n.t('remove_community')}
</button> </button>
</div> </div>
</form> </form>
)} )}
<ul class="my-1 list-inline"> <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"> <li className="list-inline-item">
<Link className="badge badge-secondary" to="/communities"> <Link className="badge badge-secondary" to="/communities">
{community.category_name} {community.category_name}
</Link> </Link>
</li> </li>
<li className="list-inline-item badge badge-secondary">
<T
i18nKey="number_of_subscribers"
interpolation={{ count: community.number_of_subscribers }}
>
#
</T>
</li>
<li className="list-inline-item badge badge-secondary">
<T
i18nKey="number_of_posts"
interpolation={{ count: community.number_of_posts }}
>
#
</T>
</li>
<li className="list-inline-item badge badge-secondary">
<T
i18nKey="number_of_comments"
interpolation={{ count: community.number_of_comments }}
>
#
</T>
</li>
<li className="list-inline-item"> <li className="list-inline-item">
<Link <Link
className="badge badge-secondary" className="badge badge-secondary"
to={`/modlog/community/${this.props.community.id}`} to={`/modlog/community/${this.props.community.id}`}
> >
<T i18nKey="modlog">#</T> {i18n.t('modlog')}
</Link> </Link>
</li> </li>
</ul> </ul>
@ -218,7 +213,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
'no-click'}`} 'no-click'}`}
to={`/create_post?community=${community.name}`} to={`/create_post?community=${community.name}`}
> >
<T i18nKey="create_a_post">#</T> {i18n.t('create_a_post')}
</Link> </Link>
<div> <div>
{community.subscribed ? ( {community.subscribed ? (
@ -226,14 +221,14 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
class="btn btn-sm btn-secondary btn-block" class="btn btn-sm btn-secondary btn-block"
onClick={linkEvent(community.id, this.handleUnsubscribe)} onClick={linkEvent(community.id, this.handleUnsubscribe)}
> >
<T i18nKey="unsubscribe">#</T> {i18n.t('unsubscribe')}
</button> </button>
) : ( ) : (
<button <button
class="btn btn-sm btn-secondary btn-block" class="btn btn-sm btn-secondary btn-block"
onClick={linkEvent(community.id, this.handleSubscribe)} onClick={linkEvent(community.id, this.handleSubscribe)}
> >
<T i18nKey="subscribe">#</T> {i18n.t('subscribe')}
</button> </button>
)} )}
</div> </div>

View file

@ -5,7 +5,6 @@ import { capitalizeFirstLetter, randomStr, setupTribute } from '../utils';
import autosize from 'autosize'; import autosize from 'autosize';
import Tribute from 'tributejs/src/Tribute.js'; import Tribute from 'tributejs/src/Tribute.js';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
interface SiteFormProps { interface SiteFormProps {
site?: Site; // If a site is given, that means this is an edit site?: Site; // If a site is given, that means this is an edit
@ -67,12 +66,13 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
: capitalizeFirstLetter(i18n.t('name')) : capitalizeFirstLetter(i18n.t('name'))
} ${i18n.t('your_site')}`}</h5> } ${i18n.t('your_site')}`}</h5>
<div class="form-group row"> <div class="form-group row">
<label class="col-12 col-form-label"> <label class="col-12 col-form-label" htmlFor="create-site-name">
<T i18nKey="name">#</T> {i18n.t('name')}
</label> </label>
<div class="col-12"> <div class="col-12">
<input <input
type="text" type="text"
id="create-site-name"
class="form-control" class="form-control"
value={this.state.siteForm.name} value={this.state.siteForm.name}
onInput={linkEvent(this, this.handleSiteNameChange)} onInput={linkEvent(this, this.handleSiteNameChange)}
@ -83,8 +83,8 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-12 col-form-label"> <label class="col-12 col-form-label" htmlFor={this.id}>
<T i18nKey="sidebar">#</T> {i18n.t('sidebar')}
</label> </label>
<div class="col-12"> <div class="col-12">
<textarea <textarea
@ -102,12 +102,13 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
<div class="form-check"> <div class="form-check">
<input <input
class="form-check-input" class="form-check-input"
id="create-site-downvotes"
type="checkbox" type="checkbox"
checked={this.state.siteForm.enable_downvotes} checked={this.state.siteForm.enable_downvotes}
onChange={linkEvent(this, this.handleSiteEnableDownvotesChange)} onChange={linkEvent(this, this.handleSiteEnableDownvotesChange)}
/> />
<label class="form-check-label"> <label class="form-check-label" htmlFor="create-site-downvotes">
<T i18nKey="enable_downvotes">#</T> {i18n.t('enable_downvotes')}
</label> </label>
</div> </div>
</div> </div>
@ -117,12 +118,13 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
<div class="form-check"> <div class="form-check">
<input <input
class="form-check-input" class="form-check-input"
id="create-site-enable-nsfw"
type="checkbox" type="checkbox"
checked={this.state.siteForm.enable_nsfw} checked={this.state.siteForm.enable_nsfw}
onChange={linkEvent(this, this.handleSiteEnableNsfwChange)} onChange={linkEvent(this, this.handleSiteEnableNsfwChange)}
/> />
<label class="form-check-label"> <label class="form-check-label" htmlFor="create-site-enable-nsfw">
<T i18nKey="enable_nsfw">#</T> {i18n.t('enable_nsfw')}
</label> </label>
</div> </div>
</div> </div>
@ -132,6 +134,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
<div class="form-check"> <div class="form-check">
<input <input
class="form-check-input" class="form-check-input"
id="create-site-open-registration"
type="checkbox" type="checkbox"
checked={this.state.siteForm.open_registration} checked={this.state.siteForm.open_registration}
onChange={linkEvent( onChange={linkEvent(
@ -139,8 +142,11 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
this.handleSiteOpenRegistrationChange this.handleSiteOpenRegistrationChange
)} )}
/> />
<label class="form-check-label"> <label
<T i18nKey="open_registration">#</T> class="form-check-label"
htmlFor="create-site-open-registration"
>
{i18n.t('open_registration')}
</label> </label>
</div> </div>
</div> </div>
@ -164,7 +170,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
class="btn btn-secondary" class="btn btn-secondary"
onClick={linkEvent(this, this.handleCancel)} onClick={linkEvent(this, this.handleCancel)}
> >
<T i18nKey="cancel">#</T> {i18n.t('cancel')}
</button> </button>
)} )}
</div> </div>

View file

@ -1,7 +1,6 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { SortType } from '../interfaces'; import { SortType } from '../interfaces';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
interface SortSelectProps { interface SortSelectProps {
sort: SortType; sort: SortType;
@ -30,33 +29,17 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
onChange={linkEvent(this, this.handleSortChange)} onChange={linkEvent(this, this.handleSortChange)}
class="custom-select custom-select-sm w-auto" class="custom-select custom-select-sm w-auto"
> >
<option disabled> <option disabled>{i18n.t('sort_type')}</option>
<T i18nKey="sort_type">#</T>
</option>
{!this.props.hideHot && ( {!this.props.hideHot && (
<option value={SortType.Hot}> <option value={SortType.Hot}>{i18n.t('hot')}</option>
<T i18nKey="hot">#</T>
</option>
)} )}
<option value={SortType.New}> <option value={SortType.New}>{i18n.t('new')}</option>
<T i18nKey="new">#</T>
</option>
<option disabled></option> <option disabled></option>
<option value={SortType.TopDay}> <option value={SortType.TopDay}>{i18n.t('top_day')}</option>
<T i18nKey="top_day">#</T> <option value={SortType.TopWeek}>{i18n.t('week')}</option>
</option> <option value={SortType.TopMonth}>{i18n.t('month')}</option>
<option value={SortType.TopWeek}> <option value={SortType.TopYear}>{i18n.t('year')}</option>
<T i18nKey="week">#</T> <option value={SortType.TopAll}>{i18n.t('all')}</option>
</option>
<option value={SortType.TopMonth}>
<T i18nKey="month">#</T>
</option>
<option value={SortType.TopYear}>
<T i18nKey="year">#</T>
</option>
<option value={SortType.TopAll}>
<T i18nKey="all">#</T>
</option>
</select> </select>
); );
} }

View file

@ -3,7 +3,12 @@ import { WebSocketService } from '../services';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next'; import { T } from 'inferno-i18next';
let general = ['Andre Vallestero', 'riccardo', 'NotTooHighToHack']; let general = [
'Nathan J. Goode',
'Andre Vallestero',
'riccardo',
'NotTooHighToHack',
];
let highlighted = ['Alex Benishek']; let highlighted = ['Alex Benishek'];
// let silver = []; // let silver = [];
// let gold = []; // let gold = [];
@ -36,16 +41,14 @@ export class Sponsors extends Component<any, any> {
topMessage() { topMessage() {
return ( return (
<div> <div>
<h5> <h5>{i18n.t('donate_to_lemmy')}</h5>
<T i18nKey="donate_to_lemmy">#</T>
</h5>
<p> <p>
<T i18nKey="sponsor_message"> <T i18nKey="sponsor_message">
#<a href="https://github.com/dessalines/lemmy">#</a> #<a href="https://github.com/dessalines/lemmy">#</a>
</T> </T>
</p> </p>
<a class="btn btn-secondary" href="https://www.patreon.com/dessalines"> <a class="btn btn-secondary" href="https://www.patreon.com/dessalines">
<T i18nKey="support_on_patreon">#</T> {i18n.t('support_on_patreon')}
</a> </a>
</div> </div>
); );
@ -53,12 +56,8 @@ export class Sponsors extends Component<any, any> {
sponsors() { sponsors() {
return ( return (
<div class="container"> <div class="container">
<h5> <h5>{i18n.t('sponsors')}</h5>
<T i18nKey="sponsors">#</T> <p>{i18n.t('general_sponsors')}</p>
</h5>
<p>
<T i18nKey="general_sponsors">#</T>
</p>
<div class="row card-columns"> <div class="row card-columns">
{highlighted.map(s => ( {highlighted.map(s => (
<div class="card bg-primary col-12 col-md-2 font-weight-bold"> <div class="card bg-primary col-12 col-md-2 font-weight-bold">
@ -78,32 +77,24 @@ export class Sponsors extends Component<any, any> {
bitcoin() { bitcoin() {
return ( return (
<div> <div>
<h5> <h5>{i18n.t('crypto')}</h5>
<T i18nKey="crypto">#</T>
</h5>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover text-center"> <table class="table table-hover text-center">
<tbody> <tbody>
<tr> <tr>
<td> <td>{i18n.t('bitcoin')}</td>
<T i18nKey="bitcoin">#</T>
</td>
<td> <td>
<code>1Hefs7miXS5ff5Ck5xvmjKjXf5242KzRtK</code> <code>1Hefs7miXS5ff5Ck5xvmjKjXf5242KzRtK</code>
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>{i18n.t('ethereum')}</td>
<T i18nKey="ethereum">#</T>
</td>
<td> <td>
<code>0x400c96c96acbC6E7B3B43B1dc1BB446540a88A01</code> <code>0x400c96c96acbC6E7B3B43B1dc1BB446540a88A01</code>
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>{i18n.t('monero')}</td>
<T i18nKey="monero">#</T>
</td>
<td> <td>
<code> <code>
41taVyY6e1xApqKyMVDRVxJ76sPkfZhALLTjRvVKpaAh2pBd4wv9RgYj1tSPrx8wc6iE1uWUfjtQdTmTy2FGMeChGVKPQuV 41taVyY6e1xApqKyMVDRVxJ76sPkfZhALLTjRvVKpaAh2pBd4wv9RgYj1tSPrx8wc6iE1uWUfjtQdTmTy2FGMeChGVKPQuV

View file

@ -18,7 +18,7 @@ import {
BanUserResponse, BanUserResponse,
AddAdminResponse, AddAdminResponse,
DeleteAccountForm, DeleteAccountForm,
CreatePostLikeResponse, PostResponse,
WebSocketJsonResponse, WebSocketJsonResponse,
} from '../interfaces'; } from '../interfaces';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
@ -39,7 +39,6 @@ import { ListingTypeSelect } from './listing-type-select';
import { CommentNodes } from './comment-nodes'; import { CommentNodes } from './comment-nodes';
import { MomentTime } from './moment-time'; import { MomentTime } from './moment-time';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
enum View { enum View {
Overview, Overview,
@ -245,21 +244,11 @@ export class User extends Component<any, UserState> {
onChange={linkEvent(this, this.handleViewChange)} onChange={linkEvent(this, this.handleViewChange)}
class="custom-select custom-select-sm w-auto" class="custom-select custom-select-sm w-auto"
> >
<option disabled> <option disabled>{i18n.t('view')}</option>
<T i18nKey="view">#</T> <option value={View.Overview}>{i18n.t('overview')}</option>
</option> <option value={View.Comments}>{i18n.t('comments')}</option>
<option value={View.Overview}> <option value={View.Posts}>{i18n.t('posts')}</option>
<T i18nKey="overview">#</T> <option value={View.Saved}>{i18n.t('saved')}</option>
</option>
<option value={View.Comments}>
<T i18nKey="comments">#</T>
</option>
<option value={View.Posts}>
<T i18nKey="posts">#</T>
</option>
<option value={View.Saved}>
<T i18nKey="saved">#</T>
</option>
</select> </select>
<span class="ml-2"> <span class="ml-2">
<SortSelect <SortSelect
@ -359,7 +348,7 @@ export class User extends Component<any, UserState> {
<li className="list-inline-item">{user.name}</li> <li className="list-inline-item">{user.name}</li>
{user.banned && ( {user.banned && (
<li className="list-inline-item badge badge-danger"> <li className="list-inline-item badge badge-danger">
<T i18nKey="banned">#</T> {i18n.t('banned')}
</li> </li>
)} )}
</ul> </ul>
@ -371,38 +360,20 @@ export class User extends Component<any, UserState> {
<table class="table table-bordered table-sm mt-2 mb-0"> <table class="table table-bordered table-sm mt-2 mb-0">
<tr> <tr>
<td> <td>
<T {i18n.t('number_of_points', { count: user.post_score })}
i18nKey="number_of_points"
interpolation={{ count: user.post_score }}
>
#
</T>
</td> </td>
<td> <td>
<T {i18n.t('number_of_posts', { count: user.number_of_posts })}
i18nKey="number_of_posts"
interpolation={{ count: user.number_of_posts }}
>
#
</T>
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<T {i18n.t('number_of_points', { count: user.comment_score })}
i18nKey="number_of_points"
interpolation={{ count: user.comment_score }}
>
#
</T>
</td> </td>
<td> <td>
<T {i18n.t('number_of_comments', {
i18nKey="number_of_comments" count: user.number_of_comments,
interpolation={{ count: user.number_of_comments }} })}
>
#
</T>
</td> </td>
</tr> </tr>
</table> </table>
@ -412,7 +383,7 @@ export class User extends Component<any, UserState> {
class="btn btn-block btn-secondary mt-3" class="btn btn-block btn-secondary mt-3"
onClick={linkEvent(this, this.handleLogoutClick)} onClick={linkEvent(this, this.handleLogoutClick)}
> >
<T i18nKey="logout">#</T> {i18n.t('logout')}
</button> </button>
) : ( ) : (
<> <>
@ -443,14 +414,10 @@ export class User extends Component<any, UserState> {
<div> <div>
<div class="card border-secondary mb-3"> <div class="card border-secondary mb-3">
<div class="card-body"> <div class="card-body">
<h5> <h5>{i18n.t('settings')}</h5>
<T i18nKey="settings">#</T>
</h5>
<form onSubmit={linkEvent(this, this.handleUserSettingsSubmit)}> <form onSubmit={linkEvent(this, this.handleUserSettingsSubmit)}>
<div class="form-group"> <div class="form-group">
<label> <label>{i18n.t('avatar')}</label>
<T i18nKey="avatar">#</T>
</label>
<form class="d-inline"> <form class="d-inline">
<label <label
htmlFor="file-upload" htmlFor="file-upload"
@ -458,7 +425,7 @@ export class User extends Component<any, UserState> {
> >
{!this.state.userSettingsForm.avatar ? ( {!this.state.userSettingsForm.avatar ? (
<span class="btn btn-sm btn-secondary"> <span class="btn btn-sm btn-secondary">
<T i18nKey="upload_avatar">#</T> {i18n.t('upload_avatar')}
</span> </span>
) : ( ) : (
<img <img
@ -481,20 +448,14 @@ export class User extends Component<any, UserState> {
</form> </form>
</div> </div>
<div class="form-group"> <div class="form-group">
<label> <label>{i18n.t('language')}</label>
<T i18nKey="language">#</T>
</label>
<select <select
value={this.state.userSettingsForm.lang} value={this.state.userSettingsForm.lang}
onChange={linkEvent(this, this.handleUserSettingsLangChange)} onChange={linkEvent(this, this.handleUserSettingsLangChange)}
class="ml-2 custom-select custom-select-sm w-auto" class="ml-2 custom-select custom-select-sm w-auto"
> >
<option disabled> <option disabled>{i18n.t('language')}</option>
<T i18nKey="language">#</T> <option value="browser">{i18n.t('browser_default')}</option>
</option>
<option value="browser">
<T i18nKey="browser_default">#</T>
</option>
<option disabled></option> <option disabled></option>
{languages.map(lang => ( {languages.map(lang => (
<option value={lang.code}>{lang.name}</option> <option value={lang.code}>{lang.name}</option>
@ -502,17 +463,13 @@ export class User extends Component<any, UserState> {
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label> <label>{i18n.t('theme')}</label>
<T i18nKey="theme">#</T>
</label>
<select <select
value={this.state.userSettingsForm.theme} value={this.state.userSettingsForm.theme}
onChange={linkEvent(this, this.handleUserSettingsThemeChange)} onChange={linkEvent(this, this.handleUserSettingsThemeChange)}
class="ml-2 custom-select custom-select-sm w-auto" class="ml-2 custom-select custom-select-sm w-auto"
> >
<option disabled> <option disabled>{i18n.t('theme')}</option>
<T i18nKey="theme">#</T>
</option>
{themes.map(theme => ( {themes.map(theme => (
<option value={theme}>{theme}</option> <option value={theme}>{theme}</option>
))} ))}
@ -520,9 +477,7 @@ export class User extends Component<any, UserState> {
</div> </div>
<form className="form-group"> <form className="form-group">
<label> <label>
<T i18nKey="sort_type" class="mr-2"> <div class="mr-2">{i18n.t('sort_type')}</div>
#
</T>
</label> </label>
<ListingTypeSelect <ListingTypeSelect
type_={this.state.userSettingsForm.default_listing_type} type_={this.state.userSettingsForm.default_listing_type}
@ -531,9 +486,7 @@ export class User extends Component<any, UserState> {
</form> </form>
<form className="form-group"> <form className="form-group">
<label> <label>
<T i18nKey="type" class="mr-2"> <div class="mr-2">{i18n.t('type')}</div>
#
</T>
</label> </label>
<SortSelect <SortSelect
sort={this.state.userSettingsForm.default_sort_type} sort={this.state.userSettingsForm.default_sort_type}
@ -541,12 +494,13 @@ export class User extends Component<any, UserState> {
/> />
</form> </form>
<div class="form-group row"> <div class="form-group row">
<label class="col-lg-3 col-form-label"> <label class="col-lg-3 col-form-label" htmlFor="user-email">
<T i18nKey="email">#</T> {i18n.t('email')}
</label> </label>
<div class="col-lg-9"> <div class="col-lg-9">
<input <input
type="email" type="email"
id="user-email"
class="form-control" class="form-control"
placeholder={i18n.t('optional')} placeholder={i18n.t('optional')}
value={this.state.userSettingsForm.email} value={this.state.userSettingsForm.email}
@ -579,12 +533,13 @@ export class User extends Component<any, UserState> {
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-lg-5 col-form-label"> <label class="col-lg-5 col-form-label" htmlFor="user-password">
<T i18nKey="new_password">#</T> {i18n.t('new_password')}
</label> </label>
<div class="col-lg-7"> <div class="col-lg-7">
<input <input
type="password" type="password"
id="user-password"
class="form-control" class="form-control"
value={this.state.userSettingsForm.new_password} value={this.state.userSettingsForm.new_password}
onInput={linkEvent( onInput={linkEvent(
@ -595,12 +550,16 @@ export class User extends Component<any, UserState> {
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-lg-5 col-form-label"> <label
<T i18nKey="verify_password">#</T> class="col-lg-5 col-form-label"
htmlFor="user-verify-password"
>
{i18n.t('verify_password')}
</label> </label>
<div class="col-lg-7"> <div class="col-lg-7">
<input <input
type="password" type="password"
id="user-verify-password"
class="form-control" class="form-control"
value={this.state.userSettingsForm.new_password_verify} value={this.state.userSettingsForm.new_password_verify}
onInput={linkEvent( onInput={linkEvent(
@ -611,12 +570,16 @@ export class User extends Component<any, UserState> {
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-lg-5 col-form-label"> <label
<T i18nKey="old_password">#</T> class="col-lg-5 col-form-label"
htmlFor="user-old-password"
>
{i18n.t('old_password')}
</label> </label>
<div class="col-lg-7"> <div class="col-lg-7">
<input <input
type="password" type="password"
id="user-old-password"
class="form-control" class="form-control"
value={this.state.userSettingsForm.old_password} value={this.state.userSettingsForm.old_password}
onInput={linkEvent( onInput={linkEvent(
@ -631,6 +594,7 @@ export class User extends Component<any, UserState> {
<div class="form-check"> <div class="form-check">
<input <input
class="form-check-input" class="form-check-input"
id="user-show-nsfw"
type="checkbox" type="checkbox"
checked={this.state.userSettingsForm.show_nsfw} checked={this.state.userSettingsForm.show_nsfw}
onChange={linkEvent( onChange={linkEvent(
@ -638,8 +602,8 @@ export class User extends Component<any, UserState> {
this.handleUserSettingsShowNsfwChange this.handleUserSettingsShowNsfwChange
)} )}
/> />
<label class="form-check-label"> <label class="form-check-label" htmlFor="user-show-nsfw">
<T i18nKey="show_nsfw">#</T> {i18n.t('show_nsfw')}
</label> </label>
</div> </div>
</div> </div>
@ -648,6 +612,7 @@ export class User extends Component<any, UserState> {
<div class="form-check"> <div class="form-check">
<input <input
class="form-check-input" class="form-check-input"
id="user-show-avatars"
type="checkbox" type="checkbox"
checked={this.state.userSettingsForm.show_avatars} checked={this.state.userSettingsForm.show_avatars}
onChange={linkEvent( onChange={linkEvent(
@ -655,8 +620,8 @@ export class User extends Component<any, UserState> {
this.handleUserSettingsShowAvatarsChange this.handleUserSettingsShowAvatarsChange
)} )}
/> />
<label class="form-check-label"> <label class="form-check-label" htmlFor="user-show-avatars">
<T i18nKey="show_avatars">#</T> {i18n.t('show_avatars')}
</label> </label>
</div> </div>
</div> </div>
@ -664,6 +629,7 @@ export class User extends Component<any, UserState> {
<div class="form-check"> <div class="form-check">
<input <input
class="form-check-input" class="form-check-input"
id="user-send-notifications-to-email"
type="checkbox" type="checkbox"
disabled={!this.state.user.email} disabled={!this.state.user.email}
checked={ checked={
@ -674,8 +640,11 @@ export class User extends Component<any, UserState> {
this.handleUserSettingsSendNotificationsToEmailChange this.handleUserSettingsSendNotificationsToEmailChange
)} )}
/> />
<label class="form-check-label"> <label
<T i18nKey="send_notifications_to_email">#</T> class="form-check-label"
htmlFor="user-send-notifications-to-email"
>
{i18n.t('send_notifications_to_email')}
</label> </label>
</div> </div>
</div> </div>
@ -699,12 +668,12 @@ export class User extends Component<any, UserState> {
this.handleDeleteAccountShowConfirmToggle this.handleDeleteAccountShowConfirmToggle
)} )}
> >
<T i18nKey="delete_account">#</T> {i18n.t('delete_account')}
</button> </button>
{this.state.deleteAccountShowConfirm && ( {this.state.deleteAccountShowConfirm && (
<> <>
<div class="my-2 alert alert-danger" role="alert"> <div class="my-2 alert alert-danger" role="alert">
<T i18nKey="delete_account_confirm">#</T> {i18n.t('delete_account_confirm')}
</div> </div>
<input <input
type="password" type="password"
@ -735,7 +704,7 @@ export class User extends Component<any, UserState> {
this.handleDeleteAccountShowConfirmToggle this.handleDeleteAccountShowConfirmToggle
)} )}
> >
<T i18nKey="cancel">#</T> {i18n.t('cancel')}
</button> </button>
</> </>
)} )}
@ -753,9 +722,7 @@ export class User extends Component<any, UserState> {
{this.state.moderates.length > 0 && ( {this.state.moderates.length > 0 && (
<div class="card border-secondary mb-3"> <div class="card border-secondary mb-3">
<div class="card-body"> <div class="card-body">
<h5> <h5>{i18n.t('moderates')}</h5>
<T i18nKey="moderates">#</T>
</h5>
<ul class="list-unstyled mb-0"> <ul class="list-unstyled mb-0">
{this.state.moderates.map(community => ( {this.state.moderates.map(community => (
<li> <li>
@ -778,9 +745,7 @@ export class User extends Component<any, UserState> {
{this.state.follows.length > 0 && ( {this.state.follows.length > 0 && (
<div class="card border-secondary mb-3"> <div class="card border-secondary mb-3">
<div class="card-body"> <div class="card-body">
<h5> <h5>{i18n.t('subscribed')}</h5>
<T i18nKey="subscribed">#</T>
</h5>
<ul class="list-unstyled mb-0"> <ul class="list-unstyled mb-0">
{this.state.follows.map(community => ( {this.state.follows.map(community => (
<li> <li>
@ -805,14 +770,14 @@ export class User extends Component<any, UserState> {
class="btn btn-sm btn-secondary mr-1" class="btn btn-sm btn-secondary mr-1"
onClick={linkEvent(this, this.prevPage)} onClick={linkEvent(this, this.prevPage)}
> >
<T i18nKey="prev">#</T> {i18n.t('prev')}
</button> </button>
)} )}
<button <button
class="btn btn-sm btn-secondary" class="btn btn-sm btn-secondary"
onClick={linkEvent(this, this.nextPage)} onClick={linkEvent(this, this.nextPage)}
> >
<T i18nKey="next">#</T> {i18n.t('next')}
</button> </button>
</div> </div>
); );
@ -1090,7 +1055,7 @@ export class User extends Component<any, UserState> {
if (data.comment.my_vote !== null) found.my_vote = data.comment.my_vote; if (data.comment.my_vote !== null) found.my_vote = data.comment.my_vote;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreatePostLike) { } else if (res.op == UserOperation.CreatePostLike) {
let data = res.data as CreatePostLikeResponse; let data = res.data as PostResponse;
let found = this.state.posts.find(c => c.id == data.post.id); let found = this.state.posts.find(c => c.id == data.post.id);
found.my_vote = data.post.my_vote; found.my_vote = data.post.my_vote;
found.score = data.post.score; found.score = data.post.score;

8
ui/src/env.ts vendored
View file

@ -1,5 +1,9 @@
const host = `${window.location.hostname}`; const host = `${window.location.hostname}`;
const port = `${window.location.port == '4444' ? '8536' : window.location.port}`; const port = `${
window.location.port == '4444' ? '8536' : window.location.port
}`;
const endpoint = `${host}:${port}`; const endpoint = `${host}:${port}`;
export const wsUri = `${window.location.protocol == 'https:' ? 'wss://' : 'ws://'}${endpoint}/api/v1/ws`; export const wsUri = `${
window.location.protocol == 'https:' ? 'wss://' : 'ws://'
}${endpoint}/api/v1/ws`;

2
ui/src/i18next.ts vendored
View file

@ -12,6 +12,7 @@ import { nl } from './translations/nl';
import { it } from './translations/it'; import { it } from './translations/it';
import { fi } from './translations/fi'; import { fi } from './translations/fi';
import { ca } from './translations/ca'; import { ca } from './translations/ca';
import { fa } from './translations/fa';
// https://github.com/nimbusec-oss/inferno-i18next/blob/master/tests/T.test.js#L66 // https://github.com/nimbusec-oss/inferno-i18next/blob/master/tests/T.test.js#L66
const resources = { const resources = {
@ -27,6 +28,7 @@ const resources = {
it, it,
fi, fi,
ca, ca,
fa,
}; };
function format(value: any, format: any, lng: any): any { function format(value: any, format: any, lng: any): any {

18
ui/src/interfaces.ts vendored
View file

@ -41,6 +41,7 @@ export enum UserOperation {
CreatePrivateMessage, CreatePrivateMessage,
EditPrivateMessage, EditPrivateMessage,
GetPrivateMessages, GetPrivateMessages,
UserJoin,
} }
export enum CommentSortType { export enum CommentSortType {
@ -538,6 +539,7 @@ export interface GetCommunityResponse {
community: Community; community: Community;
moderators: Array<CommunityUser>; moderators: Array<CommunityUser>;
admins: Array<UserView>; admins: Array<UserView>;
online: number;
} }
export interface CommunityResponse { export interface CommunityResponse {
@ -594,6 +596,7 @@ export interface GetPostResponse {
community: Community; community: Community;
moderators: Array<CommunityUser>; moderators: Array<CommunityUser>;
admins: Array<UserView>; admins: Array<UserView>;
online: number;
} }
export interface SavePostForm { export interface SavePostForm {
@ -627,6 +630,7 @@ export interface SaveCommentForm {
export interface CommentResponse { export interface CommentResponse {
comment: Comment; comment: Comment;
recipient_ids: Array<number>;
} }
export interface CommentLikeForm { export interface CommentLikeForm {
@ -660,10 +664,6 @@ export interface CreatePostLikeForm {
auth?: string; auth?: string;
} }
export interface CreatePostLikeResponse {
post: Post;
}
export interface SiteForm { export interface SiteForm {
name: string; name: string;
description?: string; description?: string;
@ -775,6 +775,14 @@ export interface PrivateMessageResponse {
message: PrivateMessage; message: PrivateMessage;
} }
export interface UserJoinForm {
auth: string;
}
export interface UserJoinResponse {
user_id: number;
}
export type MessageType = export type MessageType =
| EditPrivateMessageForm | EditPrivateMessageForm
| LoginForm | LoginForm
@ -819,7 +827,7 @@ type ResponseType =
| GetFollowedCommunitiesResponse | GetFollowedCommunitiesResponse
| ListCommunitiesResponse | ListCommunitiesResponse
| GetPostsResponse | GetPostsResponse
| CreatePostLikeResponse | PostResponse
| GetRepliesResponse | GetRepliesResponse
| GetUserMentionsResponse | GetUserMentionsResponse
| ListCategoriesResponse | ListCategoriesResponse

View file

@ -38,281 +38,268 @@ import {
PrivateMessageForm, PrivateMessageForm,
EditPrivateMessageForm, EditPrivateMessageForm,
GetPrivateMessagesForm, GetPrivateMessagesForm,
UserJoinForm,
MessageType, MessageType,
} from '../interfaces'; } from '../interfaces';
import { webSocket } from 'rxjs/webSocket';
import { Subject } from 'rxjs';
import { retryWhen, delay } from 'rxjs/operators';
import { UserService } from './'; import { UserService } from './';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { toast } from '../utils'; import { toast } from '../utils';
import { Observable } from 'rxjs';
import { share } from 'rxjs/operators';
import ReconnectingWebSocket from 'reconnecting-websocket';
export class WebSocketService { export class WebSocketService {
private static _instance: WebSocketService; private static _instance: WebSocketService;
public subject: Subject<any>; public ws: ReconnectingWebSocket;
public subject: Observable<any>;
public site: Site; public site: Site;
public admins: Array<UserView>; public admins: Array<UserView>;
public banned: Array<UserView>; public banned: Array<UserView>;
private constructor() { private constructor() {
this.subject = webSocket(wsUri); this.ws = new ReconnectingWebSocket(wsUri);
this.ws.onopen = () => {
// Necessary to not keep reconnecting
this.subject
.pipe(
retryWhen(errors =>
errors.pipe(
delay(1000)
// take(999)
)
)
)
.subscribe();
console.log(`Connected to ${wsUri}`); console.log(`Connected to ${wsUri}`);
if (UserService.Instance.user) {
this.userJoin();
}
};
this.subject = Observable.create((obs: any) => {
this.ws.onmessage = e => {
obs.next(JSON.parse(e.data));
};
}).pipe(share());
} }
public static get Instance() { public static get Instance() {
return this._instance || (this._instance = new this()); return this._instance || (this._instance = new this());
} }
public userJoin() {
let form: UserJoinForm = { auth: UserService.Instance.auth };
this.ws.send(this.wsSendWrapper(UserOperation.UserJoin, form));
}
public login(loginForm: LoginForm) { public login(loginForm: LoginForm) {
this.subject.next(this.wsSendWrapper(UserOperation.Login, loginForm)); this.ws.send(this.wsSendWrapper(UserOperation.Login, loginForm));
} }
public register(registerForm: RegisterForm) { public register(registerForm: RegisterForm) {
this.subject.next(this.wsSendWrapper(UserOperation.Register, registerForm)); this.ws.send(this.wsSendWrapper(UserOperation.Register, registerForm));
} }
public createCommunity(communityForm: CommunityForm) { public createCommunity(communityForm: CommunityForm) {
this.setAuth(communityForm); this.setAuth(communityForm);
this.subject.next( this.ws.send(
this.wsSendWrapper(UserOperation.CreateCommunity, communityForm) this.wsSendWrapper(UserOperation.CreateCommunity, communityForm)
); );
} }
public editCommunity(communityForm: CommunityForm) { public editCommunity(communityForm: CommunityForm) {
this.setAuth(communityForm); this.setAuth(communityForm);
this.subject.next( this.ws.send(
this.wsSendWrapper(UserOperation.EditCommunity, communityForm) this.wsSendWrapper(UserOperation.EditCommunity, communityForm)
); );
} }
public followCommunity(followCommunityForm: FollowCommunityForm) { public followCommunity(followCommunityForm: FollowCommunityForm) {
this.setAuth(followCommunityForm); this.setAuth(followCommunityForm);
this.subject.next( this.ws.send(
this.wsSendWrapper(UserOperation.FollowCommunity, followCommunityForm) this.wsSendWrapper(UserOperation.FollowCommunity, followCommunityForm)
); );
} }
public listCommunities(form: ListCommunitiesForm) { public listCommunities(form: ListCommunitiesForm) {
this.setAuth(form, false); this.setAuth(form, false);
this.subject.next(this.wsSendWrapper(UserOperation.ListCommunities, form)); this.ws.send(this.wsSendWrapper(UserOperation.ListCommunities, form));
} }
public getFollowedCommunities() { public getFollowedCommunities() {
let form: GetFollowedCommunitiesForm = { auth: UserService.Instance.auth }; let form: GetFollowedCommunitiesForm = { auth: UserService.Instance.auth };
this.subject.next( this.ws.send(
this.wsSendWrapper(UserOperation.GetFollowedCommunities, form) this.wsSendWrapper(UserOperation.GetFollowedCommunities, form)
); );
} }
public listCategories() { public listCategories() {
this.subject.next( this.ws.send(this.wsSendWrapper(UserOperation.ListCategories, {}));
this.wsSendWrapper(UserOperation.ListCategories, undefined)
);
} }
public createPost(postForm: PostForm) { public createPost(postForm: PostForm) {
this.setAuth(postForm); this.setAuth(postForm);
this.subject.next(this.wsSendWrapper(UserOperation.CreatePost, postForm)); this.ws.send(this.wsSendWrapper(UserOperation.CreatePost, postForm));
} }
public getPost(form: GetPostForm) { public getPost(form: GetPostForm) {
this.setAuth(form, false); this.setAuth(form, false);
this.subject.next(this.wsSendWrapper(UserOperation.GetPost, form)); this.ws.send(this.wsSendWrapper(UserOperation.GetPost, form));
} }
public getCommunity(form: GetCommunityForm) { public getCommunity(form: GetCommunityForm) {
this.setAuth(form, false); this.setAuth(form, false);
this.subject.next(this.wsSendWrapper(UserOperation.GetCommunity, form)); this.ws.send(this.wsSendWrapper(UserOperation.GetCommunity, form));
} }
public createComment(commentForm: CommentForm) { public createComment(commentForm: CommentForm) {
this.setAuth(commentForm); this.setAuth(commentForm);
this.subject.next( this.ws.send(this.wsSendWrapper(UserOperation.CreateComment, commentForm));
this.wsSendWrapper(UserOperation.CreateComment, commentForm)
);
} }
public editComment(commentForm: CommentForm) { public editComment(commentForm: CommentForm) {
this.setAuth(commentForm); this.setAuth(commentForm);
this.subject.next( this.ws.send(this.wsSendWrapper(UserOperation.EditComment, commentForm));
this.wsSendWrapper(UserOperation.EditComment, commentForm)
);
} }
public likeComment(form: CommentLikeForm) { public likeComment(form: CommentLikeForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next( this.ws.send(this.wsSendWrapper(UserOperation.CreateCommentLike, form));
this.wsSendWrapper(UserOperation.CreateCommentLike, form)
);
} }
public saveComment(form: SaveCommentForm) { public saveComment(form: SaveCommentForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.SaveComment, form)); this.ws.send(this.wsSendWrapper(UserOperation.SaveComment, form));
} }
public getPosts(form: GetPostsForm) { public getPosts(form: GetPostsForm) {
this.setAuth(form, false); this.setAuth(form, false);
this.subject.next(this.wsSendWrapper(UserOperation.GetPosts, form)); this.ws.send(this.wsSendWrapper(UserOperation.GetPosts, form));
} }
public likePost(form: CreatePostLikeForm) { public likePost(form: CreatePostLikeForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.CreatePostLike, form)); this.ws.send(this.wsSendWrapper(UserOperation.CreatePostLike, form));
} }
public editPost(postForm: PostForm) { public editPost(postForm: PostForm) {
this.setAuth(postForm); this.setAuth(postForm);
this.subject.next(this.wsSendWrapper(UserOperation.EditPost, postForm)); this.ws.send(this.wsSendWrapper(UserOperation.EditPost, postForm));
} }
public savePost(form: SavePostForm) { public savePost(form: SavePostForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.SavePost, form)); this.ws.send(this.wsSendWrapper(UserOperation.SavePost, form));
} }
public banFromCommunity(form: BanFromCommunityForm) { public banFromCommunity(form: BanFromCommunityForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.BanFromCommunity, form)); this.ws.send(this.wsSendWrapper(UserOperation.BanFromCommunity, form));
} }
public addModToCommunity(form: AddModToCommunityForm) { public addModToCommunity(form: AddModToCommunityForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next( this.ws.send(this.wsSendWrapper(UserOperation.AddModToCommunity, form));
this.wsSendWrapper(UserOperation.AddModToCommunity, form)
);
} }
public transferCommunity(form: TransferCommunityForm) { public transferCommunity(form: TransferCommunityForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next( this.ws.send(this.wsSendWrapper(UserOperation.TransferCommunity, form));
this.wsSendWrapper(UserOperation.TransferCommunity, form)
);
} }
public transferSite(form: TransferSiteForm) { public transferSite(form: TransferSiteForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.TransferSite, form)); this.ws.send(this.wsSendWrapper(UserOperation.TransferSite, form));
} }
public banUser(form: BanUserForm) { public banUser(form: BanUserForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.BanUser, form)); this.ws.send(this.wsSendWrapper(UserOperation.BanUser, form));
} }
public addAdmin(form: AddAdminForm) { public addAdmin(form: AddAdminForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.AddAdmin, form)); this.ws.send(this.wsSendWrapper(UserOperation.AddAdmin, form));
} }
public getUserDetails(form: GetUserDetailsForm) { public getUserDetails(form: GetUserDetailsForm) {
this.setAuth(form, false); this.setAuth(form, false);
this.subject.next(this.wsSendWrapper(UserOperation.GetUserDetails, form)); this.ws.send(this.wsSendWrapper(UserOperation.GetUserDetails, form));
} }
public getReplies(form: GetRepliesForm) { public getReplies(form: GetRepliesForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.GetReplies, form)); this.ws.send(this.wsSendWrapper(UserOperation.GetReplies, form));
} }
public getUserMentions(form: GetUserMentionsForm) { public getUserMentions(form: GetUserMentionsForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.GetUserMentions, form)); this.ws.send(this.wsSendWrapper(UserOperation.GetUserMentions, form));
} }
public editUserMention(form: EditUserMentionForm) { public editUserMention(form: EditUserMentionForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.EditUserMention, form)); this.ws.send(this.wsSendWrapper(UserOperation.EditUserMention, form));
} }
public getModlog(form: GetModlogForm) { public getModlog(form: GetModlogForm) {
this.subject.next(this.wsSendWrapper(UserOperation.GetModlog, form)); this.ws.send(this.wsSendWrapper(UserOperation.GetModlog, form));
} }
public createSite(siteForm: SiteForm) { public createSite(siteForm: SiteForm) {
this.setAuth(siteForm); this.setAuth(siteForm);
this.subject.next(this.wsSendWrapper(UserOperation.CreateSite, siteForm)); this.ws.send(this.wsSendWrapper(UserOperation.CreateSite, siteForm));
} }
public editSite(siteForm: SiteForm) { public editSite(siteForm: SiteForm) {
this.setAuth(siteForm); this.setAuth(siteForm);
this.subject.next(this.wsSendWrapper(UserOperation.EditSite, siteForm)); this.ws.send(this.wsSendWrapper(UserOperation.EditSite, siteForm));
} }
public getSite() { public getSite() {
this.subject.next(this.wsSendWrapper(UserOperation.GetSite, undefined)); this.ws.send(this.wsSendWrapper(UserOperation.GetSite, {}));
} }
public search(form: SearchForm) { public search(form: SearchForm) {
this.setAuth(form, false); this.setAuth(form, false);
this.subject.next(this.wsSendWrapper(UserOperation.Search, form)); this.ws.send(this.wsSendWrapper(UserOperation.Search, form));
} }
public markAllAsRead() { public markAllAsRead() {
let form = {}; let form = {};
this.setAuth(form); this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.MarkAllAsRead, form)); this.ws.send(this.wsSendWrapper(UserOperation.MarkAllAsRead, form));
} }
public saveUserSettings(userSettingsForm: UserSettingsForm) { public saveUserSettings(userSettingsForm: UserSettingsForm) {
this.setAuth(userSettingsForm); this.setAuth(userSettingsForm);
this.subject.next( this.ws.send(
this.wsSendWrapper(UserOperation.SaveUserSettings, userSettingsForm) this.wsSendWrapper(UserOperation.SaveUserSettings, userSettingsForm)
); );
} }
public deleteAccount(form: DeleteAccountForm) { public deleteAccount(form: DeleteAccountForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.DeleteAccount, form)); this.ws.send(this.wsSendWrapper(UserOperation.DeleteAccount, form));
} }
public passwordReset(form: PasswordResetForm) { public passwordReset(form: PasswordResetForm) {
this.subject.next(this.wsSendWrapper(UserOperation.PasswordReset, form)); this.ws.send(this.wsSendWrapper(UserOperation.PasswordReset, form));
} }
public passwordChange(form: PasswordChangeForm) { public passwordChange(form: PasswordChangeForm) {
this.subject.next(this.wsSendWrapper(UserOperation.PasswordChange, form)); this.ws.send(this.wsSendWrapper(UserOperation.PasswordChange, form));
} }
public createPrivateMessage(form: PrivateMessageForm) { public createPrivateMessage(form: PrivateMessageForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next( this.ws.send(this.wsSendWrapper(UserOperation.CreatePrivateMessage, form));
this.wsSendWrapper(UserOperation.CreatePrivateMessage, form)
);
} }
public editPrivateMessage(form: EditPrivateMessageForm) { public editPrivateMessage(form: EditPrivateMessageForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next( this.ws.send(this.wsSendWrapper(UserOperation.EditPrivateMessage, form));
this.wsSendWrapper(UserOperation.EditPrivateMessage, form)
);
} }
public getPrivateMessages(form: GetPrivateMessagesForm) { public getPrivateMessages(form: GetPrivateMessagesForm) {
this.setAuth(form); this.setAuth(form);
this.subject.next( this.ws.send(this.wsSendWrapper(UserOperation.GetPrivateMessages, form));
this.wsSendWrapper(UserOperation.GetPrivateMessages, form)
);
} }
private wsSendWrapper(op: UserOperation, data: MessageType) { private wsSendWrapper(op: UserOperation, data: MessageType) {
let send = { op: UserOperation[op], data: data }; let send = { op: UserOperation[op], data: data };
console.log(send); console.log(send);
return send; return JSON.stringify(send);
} }
private setAuth(obj: any, throwErr: boolean = true) { private setAuth(obj: any, throwErr: boolean = true) {
@ -325,6 +312,5 @@ export class WebSocketService {
} }
window.onbeforeunload = () => { window.onbeforeunload = () => {
WebSocketService.Instance.subject.unsubscribe(); WebSocketService.Instance.ws.close();
WebSocketService.Instance.subject = null;
}; };

View file

@ -15,9 +15,9 @@ export const de = {
remove_comment: 'Kommentar löschen', remove_comment: 'Kommentar löschen',
communities: 'Communities', communities: 'Communities',
users: 'Benutzer', users: 'Benutzer',
create_a_community: 'Eine community anlegen', create_a_community: 'Eine Gemeinschaft anlegen',
create_community: 'Community anlegen', create_community: 'Gemeinschaft anlegen',
remove_community: 'Community entfernen', remove_community: 'Gemeinschaft entfernen',
subscribed_to_communities: 'Abonnierte <1>communities</1>', subscribed_to_communities: 'Abonnierte <1>communities</1>',
trending_communities: 'Trending <1>communities</1>', trending_communities: 'Trending <1>communities</1>',
list_of_communities: 'Liste von communities', list_of_communities: 'Liste von communities',
@ -36,17 +36,17 @@ export const de = {
unsticky: 'nicht haftend', unsticky: 'nicht haftend',
link: 'link', link: 'link',
archive_link: 'Archiv-Link', archive_link: 'Archiv-Link',
mod: 'mod', mod: 'Moderator',
mods: 'mods', mods: 'Moderatoren',
moderates: 'Moderiert', moderates: 'Moderiert',
settings: 'Einstellungen', settings: 'Einstellungen',
remove_as_mod: 'Als mod entfernen', remove_as_mod: 'Als Moderator entfernen',
appoint_as_mod: 'Zum mod ernennen', appoint_as_mod: 'Zum Moderator ernennen',
modlog: 'Modlog', modlog: 'Modlog',
admin: 'admin', admin: 'Administrator',
admins: 'admins', admins: 'Administratoren',
remove_as_admin: 'Als admin entfernen', remove_as_admin: 'Als Administrator entfernen',
appoint_as_admin: 'Zum admin ernennen', appoint_as_admin: 'Zum Administrator ernennen',
remove: 'entfernen', remove: 'entfernen',
removed: 'entfernt', removed: 'entfernt',
locked: 'gesperrt', locked: 'gesperrt',
@ -66,11 +66,11 @@ export const de = {
unban_from_site: 'Von der Seite entbannen', unban_from_site: 'Von der Seite entbannen',
banned: 'gesperrt', banned: 'gesperrt',
save: 'speichern', save: 'speichern',
unsave: 'unsave', unsave: 'nicht speichern',
create: 'anlegen', create: 'anlegen',
creator: 'Ersteller', creator: 'Ersteller',
username: 'Username', username: 'Benutzername',
email_or_username: 'Email oder Username', email_or_username: 'E-mail oder Username',
number_of_users: '{{count}} Benutzer', number_of_users: '{{count}} Benutzer',
number_of_subscribers: '{{count}} Abonnenten', number_of_subscribers: '{{count}} Abonnenten',
number_of_points: '{{count}} Punkte', number_of_points: '{{count}} Punkte',
@ -86,7 +86,7 @@ export const de = {
subscribed: 'Abonniert', subscribed: 'Abonniert',
prev: 'Zurück', prev: 'Zurück',
next: 'Weiter', next: 'Weiter',
sidebar: 'Sidebar', sidebar: 'Seitenleiste',
sort_type: 'Sortieren nach', sort_type: 'Sortieren nach',
hot: 'Hot', hot: 'Hot',
new: 'Neu', new: 'Neu',
@ -116,28 +116,29 @@ export const de = {
password: 'Passwort', password: 'Passwort',
verify_password: 'Passwort überprüfen', verify_password: 'Passwort überprüfen',
forgot_password: 'Passwort vergessen', forgot_password: 'Passwort vergessen',
reset_password_mail_sent: 'Eine E-Mail wurde geschickt, um dein Passwort zurückzusetzen.', reset_password_mail_sent:
'Eine E-Mail wurde geschickt, um dein Passwort zurückzusetzen.',
password_change: 'Passwort geändert', password_change: 'Passwort geändert',
new_password: 'neues Passwort', new_password: 'neues Passwort',
no_email_setup: "Dieser Server hat E-Mails nicht korrekt eingerichtet.", no_email_setup: 'Dieser Server hat E-Mails nicht korrekt eingerichtet.',
login: 'Einloggen', login: 'Einloggen',
sign_up: 'Registrieren', sign_up: 'Registrieren',
email: 'Email', email: 'E-Mail',
optional: 'Optional', optional: 'optional',
expires: 'Ablaufdatum', expires: 'Ablaufdatum',
language: 'Sprache', language: 'Sprache',
browser_default: 'Standard-Browser', browser_default: 'Standard-Browser',
url: 'URL', url: 'URL',
body: 'Text', body: 'Text',
copy_suggested_title: 'Vorgeschlagenen Titel übernehmen: {{title}}', copy_suggested_title: 'Vorgeschlagenen Titel übernehmen: {{title}}',
community: 'Community', community: 'Gemeinschaft',
expand_here: 'Expand here', expand_here: 'hier erweitern',
subscribe_to_communities: 'Abonniere ein paar <1>communities</1>.', subscribe_to_communities: 'Abonniere ein paar <1>communities</1>.',
chat: 'Chat', chat: 'Chat',
recent_comments: 'Neueste Kommentare', recent_comments: 'Neueste Kommentare',
no_results: 'Keine Ergebnisse.', no_results: 'Keine Ergebnisse.',
setup: 'Setup', setup: 'Einrichten',
lemmy_instance_setup: 'Lemmy Instanz Setup', lemmy_instance_setup: 'Lemmy Instanz Einrichten',
setup_admin: 'Seiten Administrator konfigurieren', setup_admin: 'Seiten Administrator konfigurieren',
your_site: 'deine Seite', your_site: 'deine Seite',
modified: 'verändert', modified: 'verändert',
@ -151,7 +152,7 @@ export const de = {
support_on_patreon: 'Auf Patreon unterstützen', support_on_patreon: 'Auf Patreon unterstützen',
general_sponsors: general_sponsors:
'Allgemeine Sponsoren sind die, die zwischen $10 und $39 zu Lemmy beitragen.', 'Allgemeine Sponsoren sind die, die zwischen $10 und $39 zu Lemmy beitragen.',
crypto: 'Crypto', crypto: 'Kryptowährung',
bitcoin: 'Bitcoin', bitcoin: 'Bitcoin',
ethereum: 'Ethereum', ethereum: 'Ethereum',
monero: 'Monero', monero: 'Monero',
@ -159,16 +160,16 @@ export const de = {
joined: 'beigetreten', joined: 'beigetreten',
by: 'von', by: 'von',
to: 'bis', to: 'bis',
transfer_community: 'Transfer-Community', transfer_community: 'Gemeinschaft übertragen',
transfer_site: 'Transferseite', transfer_site: 'Transferseite',
are_you_sure: 'Bist du sicher?', are_you_sure: 'Bist du sicher?',
yes: 'Ja', yes: 'Ja',
no: 'Nein', no: 'Nein',
powered_by: 'Bereitgestellt durch', powered_by: 'Bereitgestellt durch',
landing_0: 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>.', '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>.',
not_logged_in: 'Nicht eingeloggt.', not_logged_in: 'Nicht eingeloggt.',
community_ban: 'Du wurdest von dieser Community gebannt.', community_ban: 'Du wurdest von dieser Gemeinschaft gebannt.',
site_ban: 'Du wurdest von dieser Seite gebannt', site_ban: 'Du wurdest von dieser Seite gebannt',
couldnt_create_comment: 'Konnte Kommentar nicht anlegen.', couldnt_create_comment: 'Konnte Kommentar nicht anlegen.',
couldnt_like_comment: 'Konnte nicht liken.', couldnt_like_comment: 'Konnte nicht liken.',
@ -176,14 +177,15 @@ export const de = {
couldnt_save_comment: 'Konnte Kommentar nicht speichern.', couldnt_save_comment: 'Konnte Kommentar nicht speichern.',
no_comment_edit_allowed: 'Keine Erlaubnis Kommentar zu editieren.', no_comment_edit_allowed: 'Keine Erlaubnis Kommentar zu editieren.',
no_post_edit_allowed: 'Keine Erlaubnis Beitrag zu editieren.', no_post_edit_allowed: 'Keine Erlaubnis Beitrag zu editieren.',
no_community_edit_allowed: 'Keine Erlaubnis Community zu editieren.', no_community_edit_allowed: 'Keine Erlaubnis Gemeinschaft zu editieren.',
couldnt_find_community: 'Konnte Community nicht finden.', couldnt_find_community: 'Konnte Gemeinschaft nicht finden.',
couldnt_update_community: 'Konnte Community nicht aktualisieren.', couldnt_update_community: 'Konnte Gemeinschaft nicht aktualisieren.',
community_already_exists: 'Community existiert bereits.', community_already_exists: 'Gemeinschaft existiert bereits.',
community_moderator_already_exists: community_moderator_already_exists:
'Community Moderator existiert bereits.', 'Gemeinschaft Moderator existiert bereits.',
community_follower_already_exists: 'Community Follower existiert bereits.', community_follower_already_exists:
community_user_already_banned: 'Community Nutzer schon gebannt.', 'Gemeinschaft Follower existiert bereits.',
community_user_already_banned: 'Gemeinschaft Nutzer schon gebannt.',
couldnt_create_post: 'Konnte Beitrag nicht anlegen.', couldnt_create_post: 'Konnte Beitrag nicht anlegen.',
couldnt_like_post: 'Konnte Beitrag nicht liken.', couldnt_like_post: 'Konnte Beitrag nicht liken.',
couldnt_find_post: 'Konnte Beitrag nicht finden.', couldnt_find_post: 'Konnte Beitrag nicht finden.',

View file

@ -230,5 +230,7 @@ export const en = {
couldnt_create_private_message: "Couldn't create private message.", couldnt_create_private_message: "Couldn't create private message.",
no_private_message_edit_allowed: 'Not allowed to edit private message.', no_private_message_edit_allowed: 'Not allowed to edit private message.',
couldnt_update_private_message: "Couldn't update private message.", couldnt_update_private_message: "Couldn't update private message.",
time: 'Time',
action: 'Action',
}, },
}; };

View file

@ -132,7 +132,7 @@ export const es = {
reset_password_mail_sent: 'Enviar correo para reestablecer la contraseña.', reset_password_mail_sent: 'Enviar correo para reestablecer la contraseña.',
password_change: 'Cambio de Contraseña', password_change: 'Cambio de Contraseña',
new_password: 'Nueva Contraseña', new_password: 'Nueva Contraseña',
no_email_setup: "Este servidor no ha activado correctamente el correo.", no_email_setup: 'Este servidor no ha activado correctamente el correo.',
email: 'Correo electrónico', email: 'Correo electrónico',
matrix_user_id: 'Usuario Matricial', matrix_user_id: 'Usuario Matricial',
private_message_disclaimer: private_message_disclaimer:
@ -204,9 +204,12 @@ export const es = {
couldnt_find_community: 'No se pudo encontrar la comunidad.', couldnt_find_community: 'No se pudo encontrar la comunidad.',
couldnt_update_community: 'No se pudo actualizar la comunidad.', couldnt_update_community: 'No se pudo actualizar la comunidad.',
community_already_exists: 'Esta comunidad ya existe.', community_already_exists: 'Esta comunidad ya existe.',
community_moderator_already_exists: 'Este moderador de la comunidad ya existe.', community_moderator_already_exists:
community_follower_already_exists: 'Este seguidor de la comunidad ya existe.', 'Este moderador de la comunidad ya existe.',
community_user_already_banned: 'Este usuario de la comunidad ya fue expulsado.', community_follower_already_exists:
'Este seguidor de la comunidad ya existe.',
community_user_already_banned:
'Este usuario de la comunidad ya fue expulsado.',
couldnt_create_post: 'No se pudo crear la publicación.', couldnt_create_post: 'No se pudo crear la publicación.',
couldnt_like_post: 'No se pudo gustar la publicación.', couldnt_like_post: 'No se pudo gustar la publicación.',
couldnt_find_post: 'No se pudo encontrar la publicación.', couldnt_find_post: 'No se pudo encontrar la publicación.',
@ -225,9 +228,11 @@ export const es = {
user_already_exists: 'El usuario ya existe.', user_already_exists: 'El usuario ya existe.',
email_already_exists: 'El correo ya está en uso.', email_already_exists: 'El correo ya está en uso.',
couldnt_update_user: 'No se pudo actualizar el usuario.', couldnt_update_user: 'No se pudo actualizar el usuario.',
system_err_login: 'Error del sistema. Intente cerrar sesión e ingresar de nuevo.', system_err_login:
couldnt_create_private_message: "No se pudo crear el mensaje privado.", 'Error del sistema. Intente cerrar sesión e ingresar de nuevo.',
no_private_message_edit_allowed: 'Sin permisos para editar el mensaje privado.', couldnt_create_private_message: 'No se pudo crear el mensaje privado.',
couldnt_update_private_message: "No se pudo actualizar el mensaje privado.", no_private_message_edit_allowed:
'Sin permisos para editar el mensaje privado.',
couldnt_update_private_message: 'No se pudo actualizar el mensaje privado.',
}, },
}; };

169
ui/src/translations/fa.ts vendored Normal file
View file

@ -0,0 +1,169 @@
export const fa = {
translation: {
post: 'مطلب',
remove_post: 'حذف مطلب',
no_posts: 'بدون مطلب.',
create_a_post: 'ایجاد یک مطلب',
create_post: 'ایجاد مطلب',
number_of_posts: '{{count}} مطلب',
posts: 'مطالب',
related_posts: 'این مطالب ممکن است مرتبط باشند',
cross_posts: 'این پیوند در اینجا هم منتشر شده:',
comments: 'نظرات',
number_of_comments: '{{count}} نظر',
remove_comment: 'حذف نظر',
communities: 'جوامع',
users: 'کاربران',
create_a_community: 'ایجاد یک جامعه جدید',
create_community: 'ایجاد جامعه',
remove_community: 'حذف جامعه',
list_of_communities: 'فهرست جوامع',
number_of_communities: '{{count}} جامعه',
community_reqs: 'حروف کوچک, زیرخط, و بدون فاصله.',
edit: 'ویرایش',
reply: 'پاسخ',
cancel: 'لغو',
preview: 'پیش‌نمایش',
upload_image: 'بارگذاری تصویر',
avatar: 'آواتار',
upload_avatar: 'بارگذاری آواتار',
show_avatars: 'نمایش آواتارها',
formatting_help: 'راهنمای قالب‌بندی',
view_source: 'نمایش منبع',
unlock: 'بازکردن قفل',
lock: 'قفل کردن',
sticky: 'چسبان',
unsticky: 'غیرچسبان',
link: 'پیوند',
archive_link: 'بایگاهی پیوند',
settings: 'تنظیمات',
admin: 'مدیر',
admins: 'مدیران',
remove_as_admin: 'حذف به عنوان مدیر',
appoint_as_admin: 'انتصاب به عنوان مدیر',
remove: 'حذف',
removed: 'حذف شد',
locked: 'قفل شد',
reason: 'دلیل',
mark_as_read: 'علامت‌گذاری به عنوان خوانده شده',
mark_as_unread: 'علامت‌گذاری به عنوان خوانده نشده',
delete: 'پاک کردن',
deleted: 'پاک شد',
delete_account: 'پاک کردن حساب',
delete_account_confirm:
'هشدار: این کنش، تمام اطلاعات شما را برای همیشه پاک می‌کند. برای تایید، گذرواژه خود را وارد کنید.',
restore: 'بازگردانی',
save: 'ذخیره',
unsave: 'عدم ذخیره',
create: 'ایجاد',
creator: 'سازنده',
username: 'نام‌کاربری',
email_or_username: 'رایانامه یا نام‌کاربری',
number_of_users: '{{count}} کاربر',
number_of_points: '{{count}} امتیاز',
number_online: '{{count}} کاربر برخط',
name: 'نام',
title: 'عنوان',
category: 'دسته‌بندی',
prev: 'پیش',
next: 'بعد',
sidebar: 'نوار کناری',
sort_type: 'نوع ترتیب',
hot: 'داغ',
new: 'تازه',
top_day: 'بهترین‌های روز',
week: 'هفته',
month: 'ماه',
year: 'سال',
all: 'همه',
top: 'بالاترین',
mark_all_as_read: 'علامت زدن همه به عنوان خوانده شده',
type: 'نوع',
unread: 'خوانده‌نشده',
replies: 'پاسخ‌ها',
mentions: 'اشاره‌ها',
reply_sent: 'پاسخ فرستاده شد',
search: 'جستجو',
overview: 'دید کلی',
view: 'نما',
logout: 'خروج',
login_sign_up: 'ورود / نام‌نویسی',
login: 'ورود',
sign_up: 'نام‌نویسی',
unread_messages: 'پیام‌های خوانده نشده',
password: 'گذرواژه',
verify_password: 'تایید گذرواژه',
old_password: 'پسورد پیشین',
forgot_password: 'گذرواژه را فراموش کرده‌ام',
reset_password_mail_sent: 'رایانامه‌ای برای بازنشانی گذرواژه فرستاده شد.',
password_change: 'تغییر گذرواژه',
new_password: 'گذرواژه جدید',
email: 'رایانامه',
send_notifications_to_email: 'فرستادن اعلانات به رایانامه',
optional: 'انتخابی',
expires: 'منقضی شود',
language: 'زبان',
browser_default: 'پیش‌فرض مرورگر',
downvotes_disabled: 'رای پایین غیرفعال است',
enable_downvotes: 'فعال‌سازی رای پایین',
open_registration: 'باز کردن نام‌نویسی',
registration_closed: 'نام‌نویسی بسته است',
enable_nsfw: 'فعال‌سازی NSFW',
chat: 'گپ',
recent_comments: 'نظرات اخیر',
no_results: 'بدون نتیجه.',
setup: 'نصب',
lemmy_instance_setup: 'نصب نمونهٔ لمی',
setup_admin: 'نصب مدیریت پایگاه',
your_site: 'پایگاه شما',
modified: 'تغییر یافت',
nsfw: 'NSFW',
show_nsfw: 'نمایش محتوای NSFW',
sponsors: 'حامیان',
sponsors_of_lemmy: 'حامیان لمی',
support_on_patreon: 'حمایت روی Patreon',
donate_to_lemmy: 'اعطای اعانه به لمی',
donate: 'اعانه',
crypto: 'رمزارز',
bitcoin: 'بیت‌کوین',
ethereum: 'اتریوم',
monero: 'مونرو',
code: 'کد',
transfer_community: 'انتقال جامعه',
transfer_site: 'انتقال پایگاه',
are_you_sure: 'مطمئنید؟',
yes: 'بله',
no: 'خیر',
powered_by: 'نیرو گرفته از',
not_logged_in: 'وارد نشده‌اید.',
community_ban: 'فعالیت شما در این جامعه ممنوع شده است.',
site_ban: 'فعالیت شما در این پایگاه ممنوع شده است',
couldnt_create_comment: 'ناتوانی در ایجاد نظر.',
couldnt_like_comment: 'ناتوانی در پسنیدن نظر.',
couldnt_update_comment: 'ناتوانی در به‌روزرسانی نظر.',
couldnt_save_comment: 'ناتوانی در ذخیره نظر.',
no_comment_edit_allowed: 'مجاز به ویرایش نظر نیستید.',
no_post_edit_allowed: 'مجاز به ویرایش مطلب نیستید.',
no_community_edit_allowed: 'مجاز به ویرایش جامعه نیستید.',
couldnt_find_community: 'ناتوانی در یافتن جامعه.',
couldnt_update_community: 'ناتوانی در به‌روزرسانی جامعه.',
community_already_exists: 'این جامعه از قبل وجود داشته است.',
couldnt_create_post: 'ناتوانی در ایجاد مطلب.',
couldnt_like_post: 'ناتوانی در پسندیدن مطلب.',
couldnt_find_post: 'ناتوانی در یافتن مطلب.',
couldnt_get_posts: 'ناتوانی در دریافت مطالب',
couldnt_update_post: 'ناتوای در به‌روزرسانی مطلب',
couldnt_save_post: 'ناتوانی در ذخیره مطلب.',
not_an_admin: 'مدیر نیستید.',
site_already_exists: 'این پایگاه از قبل وجود داشته است.',
couldnt_update_site: 'ناتوانی در به‌روزرسانی پایگاه.',
couldnt_find_that_username_or_email:
'ناتوانی در یافتن این نام کاربری یا رایانامه.',
password_incorrect: 'گذرواژه نادرست.',
passwords_dont_match: 'گذرواژه‌ها با هم منطبق نیستند.',
user_already_exists: 'این کاربر از قبل وجود دارد.',
email_already_exists: 'این رایانامه از قبل وجود دارد.',
couldnt_update_user: 'ناتوانی در به‌روزرسانی کاربر.',
system_err_login: 'خطای سامانه. سعی کنید خارج شده و دوباره وارد شوید.',
},
};

View file

@ -22,7 +22,8 @@ export const fi = {
trending_communities: 'Nousevat <1>yhteisöt</1>', trending_communities: 'Nousevat <1>yhteisöt</1>',
list_of_communities: 'Lista yhteisöistä', list_of_communities: 'Lista yhteisöistä',
number_of_communities: '{{count}} yhteisöä', number_of_communities: '{{count}} yhteisöä',
community_reqs: 'pienillä kirjaimilla, alleviivauksella, eikä välilyöntejä.', community_reqs:
'pienillä kirjaimilla, alleviivauksella, eikä välilyöntejä.',
create_private_message: 'Luo yksityisviesti', create_private_message: 'Luo yksityisviesti',
send_secure_message: 'Lähetä suojattu viesti', send_secure_message: 'Lähetä suojattu viesti',
send_message: 'Lähetä viesti', send_message: 'Lähetä viesti',
@ -132,7 +133,7 @@ export const fi = {
reset_password_mail_sent: 'Sähköposti lähetettiin salasanan nollaamiseksi.', reset_password_mail_sent: 'Sähköposti lähetettiin salasanan nollaamiseksi.',
password_change: 'Salasanan muutos', password_change: 'Salasanan muutos',
new_password: 'Uusi salasana', new_password: 'Uusi salasana',
no_email_setup: "Tämä palvelin ei ole asettanut sähköpostia oikein.", no_email_setup: 'Tämä palvelin ei ole asettanut sähköpostia oikein.',
email: 'Sähköposti', email: 'Sähköposti',
matrix_user_id: ' Matrix-käyttäjä', matrix_user_id: ' Matrix-käyttäjä',
private_message_disclaimer: private_message_disclaimer:
@ -189,45 +190,47 @@ export const fi = {
no: 'ei', no: 'ei',
powered_by: 'Vauhdittajana', powered_by: 'Vauhdittajana',
landing_0: 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>.", '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>.',
not_logged_in: 'Ei kirjautunut sisään.', not_logged_in: 'Ei kirjautunut sisään.',
logged_in: 'Kirjautunut sisään.', logged_in: 'Kirjautunut sisään.',
community_ban: 'Sinulle on asetettu porttikielto tähän yhteisöö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ältä sivustolta',
couldnt_create_comment: "Kommenttia ei pystytty luomaan.", couldnt_create_comment: 'Kommenttia ei pystytty luomaan.',
couldnt_like_comment: "Kommentista ei voitu tykätä.", couldnt_like_comment: 'Kommentista ei voitu tykätä.',
couldnt_update_comment: "Kommenttia ei voitu päivittää.", couldnt_update_comment: 'Kommenttia ei voitu päivittää.',
couldnt_save_comment: "Kommenttia ei voitu tallentaa.", couldnt_save_comment: 'Kommenttia ei voitu tallentaa.',
no_comment_edit_allowed: 'Et ole sallittu muokkaamaan kommenttia.', no_comment_edit_allowed: 'Et ole sallittu muokkaamaan kommenttia.',
no_post_edit_allowed: 'Et ole sallittu muokkaamaan viestiä.', no_post_edit_allowed: 'Et ole sallittu muokkaamaan viestiä.',
no_community_edit_allowed: 'Et ole sallittu muokkaamaan yhteisöä.', no_community_edit_allowed: 'Et ole sallittu muokkaamaan yhteisöä.',
couldnt_find_community: "Yhteisöä ei voitu löytää.", couldnt_find_community: 'Yhteisöä ei voitu löytää.',
couldnt_update_community: "Yhteisöä ei voitu päivittää.", couldnt_update_community: 'Yhteisöä ei voitu päivittää.',
community_already_exists: 'Yhteisö on jo olemassa.', community_already_exists: 'Yhteisö on jo olemassa.',
community_moderator_already_exists: 'Yhteisön moderaattori on jo olemassa.', community_moderator_already_exists: 'Yhteisön moderaattori on jo olemassa.',
community_follower_already_exists: 'Yhteisön seuraaja on jo olemassa.', community_follower_already_exists: 'Yhteisön seuraaja on jo olemassa.',
community_user_already_banned: 'Yhteisön käyttäjä on jo porttikiellossa.', community_user_already_banned: 'Yhteisön käyttäjä on jo porttikiellossa.',
couldnt_create_post: "Ei voitu luoda viestiä.", couldnt_create_post: 'Ei voitu luoda viestiä.',
couldnt_like_post: "Viestistä ei voitu tykätä.", couldnt_like_post: 'Viestistä ei voitu tykätä.',
couldnt_find_post: "Viestiä ei löytynyt.", couldnt_find_post: 'Viestiä ei löytynyt.',
couldnt_get_posts: "Viestejä ei saatu", couldnt_get_posts: 'Viestejä ei saatu',
couldnt_update_post: "Viestiä ei voitu päivittää", couldnt_update_post: 'Viestiä ei voitu päivittää',
couldnt_save_post: "Viestiä ei voitu tallentaa.", couldnt_save_post: 'Viestiä ei voitu tallentaa.',
no_slurs: 'Ei loukkauksia.', no_slurs: 'Ei loukkauksia.',
not_an_admin: 'Ei ole ylläpitäjä.', not_an_admin: 'Ei ole ylläpitäjä.',
site_already_exists: 'Sivusto on jo olemassa.', site_already_exists: 'Sivusto on jo olemassa.',
couldnt_update_site: "Sivustoa ei voitu päivittää.", couldnt_update_site: 'Sivustoa ei voitu päivittää.',
couldnt_find_that_username_or_email: couldnt_find_that_username_or_email:
"Käyttäjänimeä tai sähköpostia ei onnistuttu löytämään.", 'Käyttäjänimeä tai sähköpostia ei onnistuttu löytämään.',
password_incorrect: 'Salasana on väärin.', password_incorrect: 'Salasana on väärin.',
passwords_dont_match: 'Salasanat eivät täsmää.', passwords_dont_match: 'Salasanat eivät täsmää.',
admin_already_created: "Anteeksi, mutta täällä on jo ylläpitäjä.", admin_already_created: 'Anteeksi, mutta täällä on jo ylläpitäjä.',
user_already_exists: 'Käyttäjä on jo olemassa.', user_already_exists: 'Käyttäjä on jo olemassa.',
email_already_exists: 'Sähköposti on jo olemassa.', email_already_exists: 'Sähköposti on jo olemassa.',
couldnt_update_user: "Käyttäjää ei voitu päivittää.", couldnt_update_user: 'Käyttäjää ei voitu päivittää.',
system_err_login: 'Järjestelmävirhe. Yritä kirjautua ulos ja kirjautua uudestaan sisään.', system_err_login:
couldnt_create_private_message: "Yksityisviestiä ei voitu luoda.", 'Järjestelmävirhe. Yritä kirjautua ulos ja kirjautua uudestaan sisään.',
no_private_message_edit_allowed: 'Et ole sallittu muokkaamaan yksityisviestiä.', couldnt_create_private_message: 'Yksityisviestiä ei voitu luoda.',
couldnt_update_private_message: "Yksityisviestiä ei voitu päivittää.", no_private_message_edit_allowed:
'Et ole sallittu muokkaamaan yksityisviestiä.',
couldnt_update_private_message: 'Yksityisviestiä ei voitu päivittää.',
}, },
}; };

View file

@ -211,6 +211,23 @@ export const nl = {
open_registration: 'Open registratie', open_registration: 'Open registratie',
registration_closed: 'Registratie gesloten', registration_closed: 'Registratie gesloten',
enable_nsfw: 'NSFW toestaan', enable_nsfw: 'NSFW toestaan',
theme: 'Thema' theme: 'Thema',
create_private_message: 'Maak een beveiligd bericht',
send_secure_message: 'Verstuur beveiligd bericht',
send_message: 'Verstuur bericht',
message: 'Bericht',
old: 'Oud',
message_sent: 'Bericht verstuurd',
messages: 'Berichten',
matrix_user_id: 'Matrix gebruikers-id',
private_message_disclaimer: 'Waarschuwing: Privé berichten in Lemmy zijn niet beveiligd. Maak een account aan op <1>Riot.im</1> om veilig te communiceren',
donate_to_lemmy: 'Doneer aan Lemmy',
donate: 'Doneer',
from: 'van',
logged_in: 'Ingelogd',
email_already_exists: 'Email bestaat al',
couldnt_create_private_message: 'Kan beveiligd bericht niet maken',
no_private_message_edit_allowed: 'Niet toegestaan om privé berichten te wijzigen',
couldnt_update_private_message: 'Kan beveiligd bericht niet bijwerken'
}, },
}; };

View file

@ -25,14 +25,14 @@ export const zh = {
unlock: '解锁', unlock: '解锁',
lock: '加锁', lock: '加锁',
link: '链接', link: '链接',
mod: 'mod', mod: '监管人',
mods: 'mods', mods: '监管人',
moderates: 'Moderates', moderates: '监管',
remove_as_mod: 'remove as mod', remove_as_mod: '添加监管人',
appoint_as_mod: 'appoint as mod', appoint_as_mod: '移除监管人',
modlog: 'Modlog', modlog: '监管记录',
admin: 'admin', admin: '管理权限',
admins: 'admins', admins: '管理权限',
remove_as_admin: '移除管理权限', remove_as_admin: '移除管理权限',
appoint_as_admin: '添加管理权限', appoint_as_admin: '添加管理权限',
remove: '移除', remove: '移除',
@ -77,7 +77,7 @@ export const zh = {
year: '年', year: '年',
all: '所有', all: '所有',
top: '最热', top: '最热',
api: 'API', api: '应用程式介面',
inbox: '收件箱', inbox: '收件箱',
inbox_for: '<1>{{user}}</1> 收件箱', inbox_for: '<1>{{user}}</1> 收件箱',
mark_all_as_read: '标记所有已读', mark_all_as_read: '标记所有已读',
@ -98,7 +98,7 @@ export const zh = {
email: '邮箱', email: '邮箱',
optional: '选项', optional: '选项',
expires: '过期', expires: '过期',
url: 'URL', url: '网址',
body: '内容', body: '内容',
copy_suggested_title: '复制建议的标题: {{title}}', copy_suggested_title: '复制建议的标题: {{title}}',
community: '节点', community: '节点',
@ -111,11 +111,11 @@ export const zh = {
setup_admin: '设置管理员', setup_admin: '设置管理员',
your_site: '你的站点', your_site: '你的站点',
modified: '修改', modified: '修改',
sponsors: 'Sponsors', sponsors: '发起人',
sponsors_of_lemmy: 'Sponsors of Lemmy', sponsors_of_lemmy: 'Lemmy 的发起人',
sponsor_message: sponsor_message:
'Lemmy is free, <1>open-source</1> software, meaning no advertising, monetizing, or venture capital, ever. Your donations directly support full-time development of the project. Thank you to the following people:', 'Lemmy is free, <1>open-source</1> software, meaning no advertising, monetizing, or venture capital, ever. Your donations directly support full-time development of the project. Thank you to the following people:',
support_on_patreon: 'Support on Patreon', support_on_patreon: '在 Patreon 赞助',
general_sponsors: general_sponsors:
'General Sponsors are those that pledged $10 to $39 to Lemmy.', 'General Sponsors are those that pledged $10 to $39 to Lemmy.',
crypto: '加密', crypto: '加密',
@ -139,8 +139,8 @@ export const zh = {
couldnt_find_community: '不能找到节点.', couldnt_find_community: '不能找到节点.',
couldnt_update_community: '不能更新节点.', couldnt_update_community: '不能更新节点.',
community_already_exists: '节点已存在.', community_already_exists: '节点已存在.',
community_moderator_already_exists: '节点 moderator 已存在.', community_moderator_already_exists: '节点监管人已存在.',
community_follower_already_exists: '节点 follower 已存在.', community_follower_already_exists: '节点追随者已存在.',
community_user_already_banned: '节点用户已禁止.', community_user_already_banned: '节点用户已禁止.',
couldnt_create_post: '不能创建帖子.', couldnt_create_post: '不能创建帖子.',
couldnt_like_post: '不能收藏帖子.', couldnt_like_post: '不能收藏帖子.',

6
ui/src/utils.ts vendored
View file

@ -9,6 +9,7 @@ import 'moment/locale/nl';
import 'moment/locale/it'; import 'moment/locale/it';
import 'moment/locale/fi'; import 'moment/locale/fi';
import 'moment/locale/ca'; import 'moment/locale/ca';
import 'moment/locale/fa';
import { import {
UserOperation, UserOperation,
@ -258,6 +259,7 @@ export const languages = [
{ code: 'eo', name: 'Esperanto' }, { code: 'eo', name: 'Esperanto' },
{ code: 'es', name: 'Español' }, { code: 'es', name: 'Español' },
{ code: 'de', name: 'Deutsch' }, { code: 'de', name: 'Deutsch' },
{ code: 'fa', name: 'فارسی' },
{ code: 'zh', name: '中文' }, { code: 'zh', name: '中文' },
{ code: 'fi', name: 'Suomi' }, { code: 'fi', name: 'Suomi' },
{ code: 'fr', name: 'Français' }, { code: 'fr', name: 'Français' },
@ -306,6 +308,8 @@ export function getMomentLanguage(): string {
lang = 'fi'; lang = 'fi';
} else if (lang.startsWith('ca')) { } else if (lang.startsWith('ca')) {
lang = 'ca'; lang = 'ca';
} else if (lang.startsWith('fa')) {
lang = 'fa';
} else { } else {
lang = 'en'; lang = 'en';
} }
@ -314,6 +318,7 @@ export function getMomentLanguage(): string {
export const themes = [ export const themes = [
'litera', 'litera',
'materia',
'minty', 'minty',
'solar', 'solar',
'united', 'united',
@ -323,6 +328,7 @@ export const themes = [
'sketchy', 'sketchy',
'vaporwave', 'vaporwave',
'vaporwave-dark', 'vaporwave-dark',
'i386',
]; ];
export function setTheme(theme: string = 'darkly') { export function setTheme(theme: string = 'darkly') {

2
ui/src/version.ts vendored
View file

@ -1 +1 @@
export const version: string = 'v0.6.7'; export const version: string = 'v0.6.10';

View file

@ -2,6 +2,7 @@ import { en } from './src/translations/en';
import { eo } from './src/translations/eo'; import { eo } from './src/translations/eo';
import { es } from './src/translations/es'; import { es } from './src/translations/es';
import { de } from './src/translations/de'; import { de } from './src/translations/de';
import { fa } from './src/translations/fa';
import { zh } from './src/translations/zh'; import { zh } from './src/translations/zh';
import { fr } from './src/translations/fr'; import { fr } from './src/translations/fr';
import { sv } from './src/translations/sv'; import { sv } from './src/translations/sv';
@ -15,6 +16,7 @@ import fs from 'fs';
const files = [ const files = [
{ t: ca, n: 'ca' }, { t: ca, n: 'ca' },
{ t: de, n: 'de' }, { t: de, n: 'de' },
{ t: fa, n: 'fa' },
{ t: eo, n: 'eo' }, { t: eo, n: 'eo' },
{ t: es, n: 'es' }, { t: es, n: 'es' },
{ t: fi, n: 'fi' }, { t: fi, n: 'fi' },

261
ui/yarn.lock vendored
View file

@ -9,10 +9,10 @@
dependencies: dependencies:
"@babel/highlight" "^7.8.3" "@babel/highlight" "^7.8.3"
"@babel/generator@^7.8.3": "@babel/generator@^7.8.4":
version "7.8.3" version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.3.tgz#0e22c005b0a94c1c74eafe19ef78ce53a4d45c03" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.4.tgz#35bbc74486956fe4251829f9f6c48330e8d0985e"
integrity sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug== integrity sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==
dependencies: dependencies:
"@babel/types" "^7.8.3" "@babel/types" "^7.8.3"
jsesc "^2.5.1" jsesc "^2.5.1"
@ -51,23 +51,23 @@
esutils "^2.0.2" esutils "^2.0.2"
js-tokens "^4.0.0" js-tokens "^4.0.0"
"@babel/parser@^7.0.0", "@babel/parser@^7.8.3": "@babel/parser@^7.0.0", "@babel/parser@^7.8.3", "@babel/parser@^7.8.4":
version "7.8.3" version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.3.tgz#790874091d2001c9be6ec426c2eed47bc7679081" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.4.tgz#d1dbe64691d60358a974295fa53da074dd2ce8e8"
integrity sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ== integrity sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==
"@babel/runtime-corejs3@^7.7.4": "@babel/runtime-corejs3@^7.7.4":
version "7.8.3" version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.8.3.tgz#a2445836d0699e5ba77eea2c790ad9ea51e2cd27" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.8.4.tgz#ccc4e042e2fae419c67fa709567e5d2179ed3940"
integrity sha512-lrIU4aVbmlM/wQPzhEvzvNJskKyYptuXb0fGC0lTQTupTOYtR2Vqbu6/jf8vTr4M8Wt1nIzxVrSvPI5qESa/xA== integrity sha512-+wpLqy5+fbQhvbllvlJEVRIpYj+COUWnnsm+I4jZlA8Lo7/MJmBhGTCHyk1/RWfOqBRJ2MbadddG6QltTKTlrg==
dependencies: dependencies:
core-js-pure "^3.0.0" core-js-pure "^3.0.0"
regenerator-runtime "^0.13.2" regenerator-runtime "^0.13.2"
"@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": "@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.3" version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.3.tgz#0811944f73a6c926bb2ad35e918dcc1bfab279f1" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308"
integrity sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w== integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==
dependencies: dependencies:
regenerator-runtime "^0.13.2" regenerator-runtime "^0.13.2"
@ -81,15 +81,15 @@
"@babel/types" "^7.8.3" "@babel/types" "^7.8.3"
"@babel/traverse@^7.0.0": "@babel/traverse@^7.0.0":
version "7.8.3" version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.3.tgz#a826215b011c9b4f73f3a893afbc05151358bf9a" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.4.tgz#f0845822365f9d5b0e312ed3959d3f827f869e3c"
integrity sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg== integrity sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==
dependencies: dependencies:
"@babel/code-frame" "^7.8.3" "@babel/code-frame" "^7.8.3"
"@babel/generator" "^7.8.3" "@babel/generator" "^7.8.4"
"@babel/helper-function-name" "^7.8.3" "@babel/helper-function-name" "^7.8.3"
"@babel/helper-split-export-declaration" "^7.8.3" "@babel/helper-split-export-declaration" "^7.8.3"
"@babel/parser" "^7.8.3" "@babel/parser" "^7.8.4"
"@babel/types" "^7.8.3" "@babel/types" "^7.8.3"
debug "^4.1.0" debug "^4.1.0"
globals "^11.1.0" globals "^11.1.0"
@ -169,10 +169,10 @@
dependencies: dependencies:
"@types/linkify-it" "*" "@types/linkify-it" "*"
"@types/node@^13.5.0": "@types/node@^13.7.0":
version "13.5.0" version "13.7.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.5.0.tgz#4e498dbf355795a611a87ae5ef811a8660d42662" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.0.tgz#b417deda18cf8400f278733499ad5547ed1abec4"
integrity sha512-Onhn+z72D2O2Pb2ql2xukJ55rglumsVo1H6Fmyi8mlU9SvKdBk/pUSUAiBY/d9bAOF7VVWajX3sths/+g6ZiAQ== integrity sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ==
"@types/normalize-package-data@^2.4.0": "@types/normalize-package-data@^2.4.0":
version "2.4.0" version "2.4.0"
@ -189,62 +189,40 @@
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47" resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47"
integrity sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg== integrity sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==
"@typescript-eslint/eslint-plugin@2.16.0": "@typescript-eslint/eslint-plugin@2.18.0":
version "2.16.0" version "2.18.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.16.0.tgz#bf339b7db824c7cc3fd1ebedbc88dd17016471af" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.18.0.tgz#f8cf272dfb057ecf1ea000fea1e0b3f06a32f9cb"
integrity sha512-TKWbeFAKRPrvKiR9GNxErQ8sELKqg1ZvXi6uho07mcKShBnCnqNpDQWP01FEvWKf0bxM2g7uQEI5MNjSNqvUpQ== integrity sha512-kuO8WQjV+RCZvAXVRJfXWiJ8iYEtfHlKgcqqqXg9uUkIolEHuUaMmm8/lcO4xwCOtaw6mY0gStn2Lg4/eUXXYQ==
dependencies: dependencies:
"@typescript-eslint/experimental-utils" "2.16.0" "@typescript-eslint/experimental-utils" "2.18.0"
eslint-utils "^1.4.3" eslint-utils "^1.4.3"
functional-red-black-tree "^1.0.1" functional-red-black-tree "^1.0.1"
regexpp "^3.0.0" regexpp "^3.0.0"
tsutils "^3.17.1" tsutils "^3.17.1"
"@typescript-eslint/experimental-utils@2.16.0": "@typescript-eslint/experimental-utils@2.18.0", "@typescript-eslint/experimental-utils@^2.5.0":
version "2.16.0" version "2.18.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.16.0.tgz#bba65685728c532e0ddc811a0376e8d38e671f77" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.18.0.tgz#e4eab839082030282496c1439bbf9fdf2a4f3da8"
integrity sha512-bXTmAztXpqxliDKZgvWkl+5dHeRN+jqXVZ16peKKFzSXVzT6mz8kgBpHiVzEKO2NZ8OCU7dG61K9sRS/SkUUFQ== integrity sha512-J6MopKPHuJYmQUkANLip7g9I82ZLe1naCbxZZW3O2sIxTiq/9YYoOELEKY7oPg0hJ0V/AQ225h2z0Yp+RRMXhw==
dependencies: dependencies:
"@types/json-schema" "^7.0.3" "@types/json-schema" "^7.0.3"
"@typescript-eslint/typescript-estree" "2.16.0" "@typescript-eslint/typescript-estree" "2.18.0"
eslint-scope "^5.0.0" eslint-scope "^5.0.0"
"@typescript-eslint/experimental-utils@^2.5.0": "@typescript-eslint/parser@2.18.0":
version "2.17.0" version "2.18.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.17.0.tgz#12ed4a5d656e02ff47a93efc7d1ce1b8f1242351" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.18.0.tgz#d5f7fc1839abd4a985394e40e9d2454bd56aeb1f"
integrity sha512-2bNf+mZ/3mj5/3CP56v+ldRK3vFy9jOvmCPs/Gr2DeSJh+asPZrhFniv4QmQsHWQFPJFWhFHgkGgJeRmK4m8iQ== integrity sha512-SJJPxFMEYEWkM6pGfcnjLU+NJIPo+Ko1QrCBL+i0+zV30ggLD90huEmMMhKLHBpESWy9lVEeWlQibweNQzyc+A==
dependencies:
"@types/json-schema" "^7.0.3"
"@typescript-eslint/typescript-estree" "2.17.0"
eslint-scope "^5.0.0"
"@typescript-eslint/parser@2.16.0":
version "2.16.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.16.0.tgz#d0c0135a8fdb915f670802ddd7c1ba457c1b4f9d"
integrity sha512-+w8dMaYETM9v6il1yYYkApMSiwgnqXWJbXrA94LAWN603vXHACsZTirJduyeBOJjA9wT6xuXe5zZ1iCUzoxCfw==
dependencies: dependencies:
"@types/eslint-visitor-keys" "^1.0.0" "@types/eslint-visitor-keys" "^1.0.0"
"@typescript-eslint/experimental-utils" "2.16.0" "@typescript-eslint/experimental-utils" "2.18.0"
"@typescript-eslint/typescript-estree" "2.16.0" "@typescript-eslint/typescript-estree" "2.18.0"
eslint-visitor-keys "^1.1.0" eslint-visitor-keys "^1.1.0"
"@typescript-eslint/typescript-estree@2.16.0": "@typescript-eslint/typescript-estree@2.18.0":
version "2.16.0" version "2.18.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.16.0.tgz#b444943a76c716ed32abd08cbe96172d2ca0ab75" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.18.0.tgz#cfbd16ed1b111166617d718619c19b62764c8460"
integrity sha512-hyrCYjFHISos68Bk5KjUAXw0pP/455qq9nxqB1KkT67Pxjcfw+r6Yhcmqnp8etFL45UexCHUMrADHH7dI/m2WQ== integrity sha512-gVHylf7FDb8VSi2ypFuEL3hOtoC4HkZZ5dOjXvVjoyKdRrvXAOPSzpNRnKMfaUUEiSLP8UF9j9X9EDLxC0lfZg==
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"
tsutils "^3.17.1"
"@typescript-eslint/typescript-estree@2.17.0":
version "2.17.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.17.0.tgz#2ce1531ec0925ef8d22d7026235917c2638a82af"
integrity sha512-g0eVRULGnEEUakxRfJO0s0Hr1LLQqsI6OrkiCLpdHtdJJek+wyd8mb00vedqAoWldeDcOcP8plqw8/jx9Gr3Lw==
dependencies: dependencies:
debug "^4.1.1" debug "^4.1.1"
eslint-visitor-keys "^1.1.0" eslint-visitor-keys "^1.1.0"
@ -605,9 +583,9 @@ bootswatch@^4.3.1:
integrity sha512-Kx3z6+3Jpg9g6l/xZBCnc8d6KeJK0QawxCZWOomdcI5AuSZLZb+DoH5X9RJH+cOcSeMAxyzdIjkVUR01+Db5bQ== integrity sha512-Kx3z6+3Jpg9g6l/xZBCnc8d6KeJK0QawxCZWOomdcI5AuSZLZb+DoH5X9RJH+cOcSeMAxyzdIjkVUR01+Db5bQ==
bowser@^2.0.0-beta.3: bowser@^2.0.0-beta.3:
version "2.8.1" version "2.9.0"
resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.8.1.tgz#35b74165e17b80ba8af6aa4736c2861b001fc09e" resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.9.0.tgz#3bed854233b419b9a7422d9ee3e85504373821c9"
integrity sha512-FxxltGKqMHkVa3KtpA+kdnxH0caHPDewccyrK3vW1bsMw6Zco4vRPmMunowX0pXlDZqhxkKSpToADQI2Sk4OeQ== integrity sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA==
brace-expansion@^1.1.7: brace-expansion@^1.1.7:
version "1.1.11" version "1.1.11"
@ -769,9 +747,9 @@ classcat@^1.1.3:
integrity sha512-nuf6HJ5RlEgUUPqN/giIy1wsfA0LJwCHpo/aMGMwEIAxYypbLW/ZdPH4SNrF+OwdrkL3wxJmAs4GPyoE3ZkQ4w== integrity sha512-nuf6HJ5RlEgUUPqN/giIy1wsfA0LJwCHpo/aMGMwEIAxYypbLW/ZdPH4SNrF+OwdrkL3wxJmAs4GPyoE3ZkQ4w==
clean-css@^4.1.9: clean-css@^4.1.9:
version "4.2.1" version "4.2.3"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78"
integrity sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g== integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==
dependencies: dependencies:
source-map "~0.6.0" source-map "~0.6.0"
@ -952,9 +930,9 @@ cross-spawn@^7.0.0:
which "^2.0.1" which "^2.0.1"
damerau-levenshtein@^1.0.4: damerau-levenshtein@^1.0.4:
version "1.0.5" version "1.0.6"
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz#780cf7144eb2e8dbd1c3bb83ae31100ccc31a414" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791"
integrity sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA== integrity sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==
dashdash@^1.12.0: dashdash@^1.12.0:
version "1.14.1" version "1.14.1"
@ -1102,9 +1080,9 @@ emoji-regex@^8.0.0:
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
emoji-short-name@^0.1.0: emoji-short-name@^0.1.0:
version "0.1.3" version "0.1.4"
resolved "https://registry.yarnpkg.com/emoji-short-name/-/emoji-short-name-0.1.3.tgz#7cedc74b599483ba2bee8d3b3241299f4fdf924f" resolved "https://registry.yarnpkg.com/emoji-short-name/-/emoji-short-name-0.1.4.tgz#125a452adc22a399b089f802f9d8d46ecb6e5b08"
integrity sha512-Z9pe0l664P+mVh9C1+l45KSc8+nMNY43Hc1jltGPdGVpCnScvam+CZu2hl+xynxB0oqvghiSFanJhuUFhb1uYQ== integrity sha512-VTjEKkhN1UARtHLqlK70N5K3SwxuZAkmdm5sXvSjkV677kr0jt/O7mvB5eQqM+3rKCa+w3Qb5G7wwU/fezonKQ==
encodeurl@~1.0.2: encodeurl@~1.0.2:
version "1.0.2" version "1.0.2"
@ -1186,10 +1164,10 @@ eslint-ast-utils@^1.1.0:
lodash.get "^4.4.2" lodash.get "^4.4.2"
lodash.zip "^4.2.0" lodash.zip "^4.2.0"
eslint-config-prettier@6.9.0: eslint-config-prettier@6.10.0:
version "6.9.0" version "6.10.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.9.0.tgz#430d24822e82f7deb1e22a435bfa3999fae4ad64" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.10.0.tgz#7b15e303bf9c956875c948f6b21500e48ded6a7f"
integrity sha512-k4E14HBtcLv0uqThaI6I/n1LEqROp8XaPu6SO9Z32u5NlGRC07Enu1Bh2KEFw4FNHbekH8yzbIU9kUGxbiGmCA== integrity sha512-AtndijGte1rPILInUdHjvKEGbIV06NuvPrqlIEaEaWtbtvJh464mDeyGMdZEQMsGvC0ZVkiex1fSNcC4HAbRGg==
dependencies: dependencies:
get-stdin "^6.0.0" get-stdin "^6.0.0"
@ -1257,14 +1235,14 @@ eslint-plugin-inferno@^7.14.3:
resolve "^1.12.0" resolve "^1.12.0"
eslint-plugin-jane@^7.0.2: eslint-plugin-jane@^7.0.2:
version "7.0.2" version "7.1.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jane/-/eslint-plugin-jane-7.0.2.tgz#e6c6e402c95d87630f739ae7cca3837c32419757" resolved "https://registry.yarnpkg.com/eslint-plugin-jane/-/eslint-plugin-jane-7.1.0.tgz#ee087405329e6bc9bfe9316fc5881c1d4e27bc71"
integrity sha512-kVSIwAbwo8CFKwpLSzAeyT1izM2WUCgOrIiZQSX7dNwfqYh7Utl4rhUeBB0ItzRV8C+YeRTVZ742XqaFnJPIxw== integrity sha512-ScsxkkeTUnGYKLaiIk5zz/x7ZkDh7+rTj94daZboNmkJejdYka0sLFpfvDGm/7B8ImKacKdjRatQD0HjxlaPzA==
dependencies: dependencies:
"@typescript-eslint/eslint-plugin" "2.16.0" "@typescript-eslint/eslint-plugin" "2.18.0"
"@typescript-eslint/parser" "2.16.0" "@typescript-eslint/parser" "2.18.0"
babel-eslint "10.0.3" babel-eslint "10.0.3"
eslint-config-prettier "6.9.0" eslint-config-prettier "6.10.0"
eslint-plugin-babel "5.3.0" eslint-plugin-babel "5.3.0"
eslint-plugin-import "2.20.0" eslint-plugin-import "2.20.0"
eslint-plugin-jest "23.6.0" eslint-plugin-jest "23.6.0"
@ -2175,9 +2153,9 @@ husky@^4.2.1:
which-pm-runs "^1.0.0" which-pm-runs "^1.0.0"
i18next@^19.0.3: i18next@^19.0.3:
version "19.0.3" version "19.1.0"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.0.3.tgz#31fd3165762d9802e08a2a86932db4eff5c862e9" resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.1.0.tgz#fe1a1da3d208872946307c7d2d115da45d46159f"
integrity sha512-Ru4afr++b4cUApsIBifcMYyWG9Nx8wlFdq4DuOF+UuoPoQKfuh0iAVMekTjs6w1CZLUOVb5QZEuoYRLmu17EIA== integrity sha512-ISbmukX4L6Dz0QoH9+EW1AnBw7j+NRLoMu9uLPMaNSSTP9Eie9/oUL0dOyWX15baB3gYOpkHJpGZRHOqcnl0ew==
dependencies: dependencies:
"@babel/runtime" "^7.3.1" "@babel/runtime" "^7.3.1"
@ -2227,18 +2205,18 @@ indent-string@^3.0.0:
integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=
inferno-clone-vnode@^7.1.12: inferno-clone-vnode@^7.1.12:
version "7.3.3" version "7.4.0"
resolved "https://registry.yarnpkg.com/inferno-clone-vnode/-/inferno-clone-vnode-7.3.3.tgz#54815f8e48195b2ed4c301a4a4df0e16ecb772ea" resolved "https://registry.yarnpkg.com/inferno-clone-vnode/-/inferno-clone-vnode-7.4.0.tgz#44a930ef0881f79d425c1c7f4bbd206513da905a"
integrity sha512-kuUO7wWuQ3ktxRHXPpYejleJrs2XieOum4GW8WcW8ZugJj6gVnuE4pHMomiC59w6yqDacxqawX7oOWZ/K7qW5g== integrity sha512-rPp4tMhWZB1H2kx0MqgyPPBP4bWIXwkH+E/eNSWWtXLR5mKDGz19cguiBkR+U1uXQCi4/AkWvOVHxLQCfT/5Zw==
dependencies: dependencies:
inferno "7.3.3" inferno "7.4.0"
inferno-create-element@^7.1.12: inferno-create-element@^7.1.12:
version "7.3.3" version "7.4.0"
resolved "https://registry.yarnpkg.com/inferno-create-element/-/inferno-create-element-7.3.3.tgz#6a53084fad9689cb94aa40aede65ab15f690401a" resolved "https://registry.yarnpkg.com/inferno-create-element/-/inferno-create-element-7.4.0.tgz#b431f293cdb8931f7f3604e0774500b66d6fe5c8"
integrity sha512-96kUD6uQFESCvWr7tud6/AA1xhQgv/qT6HQU/vBPS9xB9HwEoLfi4eTTBB6igpTBpeEPuHRE2jMhELVP1yJapQ== integrity sha512-gxwU899obmELIxfhWzyHBIGbxOXUPfB1SzW+K3XGU0exWKCVIJwSpBOGpJY5tlKf4lyg1UrCmfz2JZS1i2U2vg==
dependencies: dependencies:
inferno "7.3.3" inferno "7.4.0"
inferno-i18next@nimbusec-oss/inferno-i18next: inferno-i18next@nimbusec-oss/inferno-i18next:
version "7.1.12" version "7.1.12"
@ -2252,32 +2230,32 @@ inferno-i18next@nimbusec-oss/inferno-i18next:
inferno-vnode-flags "^7.1.12" inferno-vnode-flags "^7.1.12"
inferno-router@^7.0.1: inferno-router@^7.0.1:
version "7.3.3" version "7.4.0"
resolved "https://registry.yarnpkg.com/inferno-router/-/inferno-router-7.3.3.tgz#8b7b2a5bdf1a91c31dac3053e63e622e70c520bb" resolved "https://registry.yarnpkg.com/inferno-router/-/inferno-router-7.4.0.tgz#0af6b931c58f426d0d7e7754d51a51300882364a"
integrity sha512-l7lBluSGnYPX0nmQ7OcM978LCWRMFpSJ0D6Lx1Ri4wbGk2+DpycyOQ0dKY3uhRumoN1j8jTBJFfePr9D5NvDsQ== integrity sha512-6Q76UjAiPd1mO/5sbDaEoEN9MdMHKkEXnYNOZ02sSudj5jWCFzJ/JnSF526uNxAHQpw2DKCh2pNiu6qf/b1vQQ==
dependencies: dependencies:
history "^4.10.1" history "^4.10.1"
hoist-non-inferno-statics "^1.1.3" hoist-non-inferno-statics "^1.1.3"
inferno "7.3.3" inferno "7.4.0"
path-to-regexp-es6 "1.7.0" path-to-regexp-es6 "1.7.0"
inferno-shared@7.3.3, inferno-shared@^7.1.12: inferno-shared@7.4.0, inferno-shared@^7.1.12:
version "7.3.3" version "7.4.0"
resolved "https://registry.yarnpkg.com/inferno-shared/-/inferno-shared-7.3.3.tgz#aa4b70a38d1f37498766f31c6a99f5c5dfc58b63" resolved "https://registry.yarnpkg.com/inferno-shared/-/inferno-shared-7.4.0.tgz#4491deb75348019939b160cd5655196afa13ced0"
integrity sha512-OPpYFEHLA6grY8phbdG21ST7mjkUNXjZMpfZKgHrPUORFxnnn+u+i57QDpht5RtUZgVpHIKNxNQypq6+/m4LEA== integrity sha512-6aa1fC/e4SP2lOLNg4ZS5Zz2SC+DnM7WxQbggmHhLSyOqZrsPrpZSlX25LbjR9lkhMrq6cmki3yInYFGuDzlRg==
inferno-vnode-flags@7.3.3, inferno-vnode-flags@^7.1.12: inferno-vnode-flags@7.4.0, inferno-vnode-flags@^7.1.12:
version "7.3.3" version "7.4.0"
resolved "https://registry.yarnpkg.com/inferno-vnode-flags/-/inferno-vnode-flags-7.3.3.tgz#aebaddea1569dd16512f44b92bf587837328db9d" resolved "https://registry.yarnpkg.com/inferno-vnode-flags/-/inferno-vnode-flags-7.4.0.tgz#5c049a73f3ff84a51458b06d279d6b18d09acdf0"
integrity sha512-LzLIRVrpv3OoH5gwWXOrHmgx3vMysI1fEG9PUBEc7Alz+vnD9rRBu9sP5AvGRN7Nxli7iLo6WcqF1nDIANGL7Q== integrity sha512-TMPrvAxR2uUVSowLKnGgH34eWXErIYCdJ4d5hj8cSc8ta8knN6dj0z47UIw13qvmWfNjHgwm0C2/cm+G6fckiA==
inferno@7.3.3, inferno@^7.0.1, inferno@^7.1.12: inferno@7.4.0, inferno@^7.0.1, inferno@^7.1.12:
version "7.3.3" version "7.4.0"
resolved "https://registry.yarnpkg.com/inferno/-/inferno-7.3.3.tgz#4098d5313c53281e44a857619764e74ab4438415" resolved "https://registry.yarnpkg.com/inferno/-/inferno-7.4.0.tgz#8d3dc03562c6851043a1a467fd509f222e9dbf85"
integrity sha512-FlTMi77+uF0dR3HDfrhysCmOPE6cj9/2jDLQzUSx0KciewVQq7N2KdsfsA0HVSzVb9Do1pjcRtnAIDXmfKzGfA== integrity sha512-oEXx5iQmGXOvAPj1TZyCo6ndOc4qPg9zBLigMpkApAiV1SM/bri0M1eA/kD3e9jptcof9TwLBJD9bL6E6tq2tg==
dependencies: dependencies:
inferno-shared "7.3.3" inferno-shared "7.4.0"
inferno-vnode-flags "7.3.3" inferno-vnode-flags "7.4.0"
opencollective-postinstall "^2.0.2" opencollective-postinstall "^2.0.2"
inflight@^1.0.4: inflight@^1.0.4:
@ -2319,9 +2297,9 @@ inquirer@^3.0.6:
through "^2.3.6" through "^2.3.6"
inquirer@^7.0.0: inquirer@^7.0.0:
version "7.0.3" version "7.0.4"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.3.tgz#f9b4cd2dff58b9f73e8d43759436ace15bed4567" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.4.tgz#99af5bde47153abca23f5c7fc30db247f39da703"
integrity sha512-+OiOVeVydu4hnCGLCSX+wedovR/Yzskv9BFqUNNKq9uU2qg7LCcCo3R86S2E7WLo0y/x2pnEZfZe1CoYnORUAw== integrity sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==
dependencies: dependencies:
ansi-escapes "^4.2.1" ansi-escapes "^4.2.1"
chalk "^2.4.2" chalk "^2.4.2"
@ -2763,9 +2741,9 @@ linkify-it@^2.0.0:
uc.micro "^1.0.1" uc.micro "^1.0.1"
lint-staged@^10.0.2: lint-staged@^10.0.2:
version "10.0.2" version "10.0.7"
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.0.2.tgz#cfdd9fa5080b05fc6e29536897da1795bc67c7f9" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.0.7.tgz#d205f92d9359419a23bc6aa3b6f8546b1998da64"
integrity sha512-ZldhtIfT7bynVa7nmU/1jbK05r9hYQXbIQqZSotqdBCAcGJDEUqaUB7kG3ZCdoe9Qkj6HUM3x2yjCGJRxPUQLA== integrity sha512-Byj0F4l7GYUpYYHEqyFH69NiI6ICTg0CeCKbhRorL+ickbzILKUlZLiyCkljZV02wnoh7yH7PmFyYm9PRNwk9g==
dependencies: dependencies:
chalk "^3.0.0" chalk "^3.0.0"
commander "^4.0.1" commander "^4.0.1"
@ -3753,6 +3731,11 @@ realm-utils@^1.0.9:
app-root-path "^1.3.0" app-root-path "^1.3.0"
mkdirp "^0.5.1" mkdirp "^0.5.1"
reconnecting-websocket@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.3.0.tgz#aaefbc7629a89450aa45324b89aec2276e728cc5"
integrity sha512-3eaHIEVYB9Zb0GfYy1xdEHKJLA2JaawAegByZ1AZ8Npb3AiRgUN5l89cvE2H+pHTsFcoC88t32ky9qET6DJ75Q==
regenerate-unicode-properties@^8.1.0: regenerate-unicode-properties@^8.1.0:
version "8.1.0" version "8.1.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e"
@ -3786,9 +3769,9 @@ regex-not@^1.0.0, regex-not@^1.0.2:
safe-regex "^1.1.0" safe-regex "^1.1.0"
regexp-tree@^0.1.17, regexp-tree@~0.1.1: regexp-tree@^0.1.17, regexp-tree@~0.1.1:
version "0.1.17" version "0.1.18"
resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.17.tgz#66d914a6ca21f95dd7660ed70a7dad47aeb2246a" resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.18.tgz#ed4819a9f03ec2de9613421d6eaf47512e7fdaf1"
integrity sha512-UnOJjFS/EPZmfISmYx+0PcDtPzyFKTe+cZTS5sM5hifnRUDRxoB1j4DAmGwqzxjwBGlwOkGfb2cDGHtjuEwqoA== integrity sha512-mKLUfTDU1GE5jGR7cn2IEPDzYjmOviZOHYAR1XGe8Lg48Mdk684waD1Fqhv2Nef+TsDVdmIj08m/GUKTMk7J2Q==
regexpp@^2.0.1: regexpp@^2.0.1:
version "2.0.1" version "2.0.1"
@ -4123,9 +4106,9 @@ snapdragon@^0.8.1:
use "^3.1.0" use "^3.1.0"
sortpack@^2.0.1: sortpack@^2.0.1:
version "2.0.4" version "2.1.1"
resolved "https://registry.yarnpkg.com/sortpack/-/sortpack-2.0.4.tgz#ca537fbf461351795eee5c2be483ee57e5664c69" resolved "https://registry.yarnpkg.com/sortpack/-/sortpack-2.1.1.tgz#e94280616a517851257728721dd6749619aca309"
integrity sha512-XwtYcxATWJTBWjCWakakFzDqeBqdG5XS0iyzCfOl2KznAOV1YWFzaSf9QQuedZ2i78VHF7Ix1RscrKJ9Dlcm0w== integrity sha512-/jtQAzl9JeTXZxzznW6L729M+Q7uv9k9Dm89eF0UxMj4Rna3CmO0IYT0MUS6aLyHUOTnwpT7kIDs4PQmMTEhLw==
source-map-resolve@^0.5.0: source-map-resolve@^0.5.0:
version "0.5.3" version "0.5.3"
@ -4416,9 +4399,9 @@ through@^2.3.6:
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
tiny-invariant@^1.0.2: tiny-invariant@^1.0.2:
version "1.0.6" version "1.1.0"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.6.tgz#b3f9b38835e36a41c843a3b0907a5a7b3755de73" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
integrity sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA== integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
tiny-warning@^1.0.0: tiny-warning@^1.0.0:
version "1.0.3" version "1.0.3"
@ -4539,19 +4522,19 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
twemoji-parser@12.1.1: twemoji-parser@12.1.3:
version "12.1.1" version "12.1.3"
resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-12.1.1.tgz#9532a869e3348dbb129d93ed1850f6e871bec2be" resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-12.1.3.tgz#916c0153e77bd5f1011e7a99cbeacf52e43c9371"
integrity sha512-XFUB4ReEvPbNPtiuyo/+crM4RldYbRRAhyE7Hw6EnfBdXECGydw7a49EGADayRvaeierP/m4DSv/OZQObh0LGA== integrity sha512-ND4LZXF4X92/PFrzSgGkq6KPPg8swy/U0yRw1k/+izWRVmq1HYi3khPwV3XIB6FRudgVICAaBhJfW8e8G3HC7Q==
twemoji@^12.1.2: twemoji@^12.1.2:
version "12.1.4" version "12.1.5"
resolved "https://registry.yarnpkg.com/twemoji/-/twemoji-12.1.4.tgz#bf61470cc70f9c18fa5c212de1fe2637cd159589" resolved "https://registry.yarnpkg.com/twemoji/-/twemoji-12.1.5.tgz#a961fb65a1afcb1f729ad7e59391f9fe969820b9"
integrity sha512-e37lUlVijmABF7wPCc09s1kKj3hcpzU8KL5zw2bBDIXOtOr4luLF+ODJaEqca8dZPmLR5ezrJYI93nhPovKBiQ== integrity sha512-B0PBVy5xomwb1M/WZxf/IqPZfnoIYy1skXnlHjMwLwTNfZ9ljh8VgWQktAPcJXu8080WoEh6YwQGPVhDVqvrVQ==
dependencies: dependencies:
fs-extra "^8.0.1" fs-extra "^8.0.1"
jsonfile "^5.0.0" jsonfile "^5.0.0"
twemoji-parser "12.1.1" twemoji-parser "12.1.3"
universalify "^0.1.2" universalify "^0.1.2"
type-check@~0.3.2: type-check@~0.3.2: