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 5a71dc2e5b
80 changed files with 3108 additions and 1324 deletions

31
README.md vendored
View File

@ -1,5 +1,5 @@
<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>
</p>
@ -113,7 +113,7 @@ docker-compose up -d
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
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
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 -->
lang | done | missing
---- | ---- | -------
ca | 100% | old
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
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
es | 100% | old
fi | 100% | old
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
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
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
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
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
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
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,time,action
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
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
es | 99% | old,time,action
fi | 99% | old,time,action
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
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
nl | 99% | time,action
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
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 -->
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]
# 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]
ansible_connection=ssh

14
ansible/lemmy.yml vendored
View File

@ -29,15 +29,19 @@
- { path: '/lemmy/' }
- { path: '/lemmy/volumes/' }
- name: add all template files
template: src={{item.src}} dest={{item.dest}}
- block:
- name: add template files
template: src={{item.src}} dest={{item.dest}} mode={{item.mode}}
with_items:
- { src: '../docker/prod/docker-compose.yml', dest: '/lemmy/docker-compose.yml' }
- { src: 'templates/config.hjson', dest: '/lemmy/lemmy.hjson' }
- { src: 'templates/nginx.conf', dest: '/etc/nginx/sites-enabled/lemmy.conf' }
- { 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') }}"
lemmy_docker_image: "dessalines/lemmy:{{ lookup('file', 'VERSION') }}"
- name: enable and start docker service
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 }}"
front_end_dir: "/app/dist"
email: {
smtp_server: "{{ smtp_server }}"
smtp_login: "{{ smtp_login }}"
smtp_password: "{{ smtp_password }}"
smtp_from_address: "{{ smtp_from_address }}"
smtp_server: "postfix:25"
smtp_from_address: "noreply@{{ domain }}"
use_tls: false
}
}

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" '
'$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
FROM rust:1.37 as rust
FROM rust:1.40 as rust
# Cache deps
WORKDIR /app
RUN USER=root cargo new server
WORKDIR /app/server
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
#RUN cargo build --release
RUN cargo build && \
rm -f ./target/release/deps/lemmy_server* ; rm -f ./target/debug/deps/lemmy_server*
RUN cargo build --release
#RUN cargo build && \
# rm -f ./target/release/deps/lemmy_server* ; rm -f ./target/debug/deps/lemmy_server*
COPY server/src ./src/
COPY server/migrations ./migrations/
# build for release
# workaround for https://github.com/rust-lang/rust/issues/62896
#RUN RUSTFLAGS='-Ccodegen-units=1' cargo build --release
#RUN cargo build --release --frozen
RUN cargo build --frozen
RUN cargo build --release --frozen
#RUN cargo build --frozen
# Get diesel-cli on there just in case
# RUN cargo install diesel_cli --no-default-features --features postgres
# 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/debug/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
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
@ -66,8 +78,9 @@ RUN adduser --disabled-password --shell /bin/sh --uid 1000 --ingroup lemmy lemmy
# Copy resources
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=docs /app/docs/book/ /app/dist/documentation/
COPY --from=rust /app/server/ready /app/lemmy
RUN chown lemmy:lemmy /app/lemmy
USER lemmy

View File

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

View File

@ -11,7 +11,7 @@ services:
- lemmy_db:/var/lib/postgresql/data
restart: always
lemmy:
image: dessalines/lemmy:v0.6.7
image: dessalines/lemmy:v0.6.10
ports:
- "127.0.0.1:8536:8536"
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)",
"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.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]]
@ -29,7 +29,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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)",
]
@ -44,7 +44,7 @@ dependencies = [
"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_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]]
@ -158,10 +158,10 @@ dependencies = [
"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)",
"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)",
"regex 1.3.1 (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.4 (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)",
"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)",
@ -185,7 +185,7 @@ dependencies = [
"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)",
"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)",
]
@ -315,9 +315,9 @@ dependencies = [
"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)",
"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_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)",
"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)",
@ -378,6 +378,11 @@ dependencies = [
"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]]
name = "arc-swap"
version = "0.4.4"
@ -420,6 +425,11 @@ name = "autocfg"
version = "0.1.7"
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]]
name = "awc"
version = "1.0.1"
@ -436,9 +446,9 @@ dependencies = [
"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)",
"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_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)",
]
@ -493,7 +503,7 @@ dependencies = [
"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)",
"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]]
@ -561,6 +571,11 @@ name = "bufstream"
version = "0.1.4"
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]]
name = "byte-tools"
version = "0.3.1"
@ -607,8 +622,8 @@ name = "chrono"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-integer 0.1.41 (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)",
"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)",
]
@ -631,7 +646,7 @@ dependencies = [
"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-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)",
"yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -683,68 +698,57 @@ dependencies = [
[[package]]
name = "darling"
version = "0.9.0"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"darling_macro 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.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "darling_core"
version = "0.9.0"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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)",
"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)",
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (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)",
"strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "darling_macro"
version = "0.9.0"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
"darling_core 0.10.2 (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)",
]
[[package]]
name = "derive_builder"
version = "0.7.2"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"derive_builder_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"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)",
"syn 0.15.44 (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.9.0 (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)",
]
[[package]]
name = "derive_builder_core"
version = "0.5.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"darling 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)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (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)",
"darling 0.10.2 (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)",
]
[[package]]
@ -926,7 +930,7 @@ dependencies = [
"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)",
"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)",
]
@ -1254,17 +1258,24 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "jsonwebtoken"
version = "6.0.1"
name = "js-sys"
version = "0.3.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"base64 0.10.1 (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)",
"wasm-bindgen 0.2.58 (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_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)",
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
"simple_asn1 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1311,16 +1322,16 @@ dependencies = [
"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)",
"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)",
"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)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rss 1.8.0 (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.4 (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_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
"sha2 0.8.0 (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.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)",
]
@ -1339,7 +1350,7 @@ dependencies = [
"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_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]]
@ -1560,12 +1571,22 @@ dependencies = [
]
[[package]]
name = "num-integer"
version = "0.1.41"
name = "num-bigint"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"autocfg 1.0.0 (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]]
@ -1573,15 +1594,15 @@ name = "num-traits"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "num-traits"
version = "0.2.10"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
@ -1650,6 +1671,16 @@ dependencies = [
"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]]
name = "percent-encoding"
version = "2.1.0"
@ -1716,14 +1747,6 @@ name = "proc-macro-nested"
version = "0.1.3"
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]]
name = "proc-macro2"
version = "1.0.7"
@ -1739,24 +1762,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "quick-xml"
version = "0.14.0"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
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)",
"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)",
]
[[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]]
name = "quote"
version = "1.0.2"
@ -1807,7 +1819,7 @@ dependencies = [
[[package]]
name = "rand"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1947,13 +1959,13 @@ dependencies = [
[[package]]
name = "regex"
version = "1.3.1"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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)",
"regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.6 (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 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1963,7 +1975,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "regex-syntax"
version = "0.6.12"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -1985,25 +1997,25 @@ dependencies = [
[[package]]
name = "ring"
version = "0.14.6"
version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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)",
"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)",
"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)",
]
[[package]]
name = "rss"
version = "1.8.0"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"derive_builder 0.7.2 (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.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
"derive_builder 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -2114,7 +2126,7 @@ dependencies = [
"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)",
"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)",
]
@ -2126,7 +2138,7 @@ dependencies = [
"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)",
"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)",
]
@ -2153,7 +2165,7 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.44"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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]]
name = "sha2"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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)",
]
[[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]]
name = "slab"
version = "0.4.2"
@ -2227,6 +2249,11 @@ dependencies = [
"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]]
name = "spin"
version = "0.5.2"
@ -2244,7 +2271,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "strsim"
version = "0.7.0"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -2263,16 +2290,6 @@ dependencies = [
"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]]
name = "syn"
version = "1.0.13"
@ -2301,7 +2318,7 @@ 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)",
"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)",
"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)",
@ -2352,7 +2369,7 @@ dependencies = [
[[package]]
name = "thread_local"
version = "0.3.6"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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)",
"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)",
"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)",
"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)",
@ -2488,11 +2505,6 @@ name = "unicode-segmentation"
version = "1.6.0"
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]]
name = "unicode-xid"
version = "0.2.0"
@ -2500,7 +2512,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "untrusted"
version = "0.6.2"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -2574,6 +2586,90 @@ name = "wasi"
version = "0.7.0"
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]]
name = "widestring"
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 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 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 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 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 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 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"
@ -2705,6 +2803,7 @@ dependencies = [
"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 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 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"
@ -2721,12 +2820,11 @@ dependencies = [
"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-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_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6afc018370c3bff3eb51f89256a6bdb18b4fdcda72d577982a14954a7a0b402c"
"checksum darling_macro 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d8dac1c6f1d29a41c4712b4400f878cb4fcc4c7628f298dd75038e024998d1"
"checksum derive_builder 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ac53fa6a3cda160df823a9346442525dcaf1e171999a1cf23e67067e4fd64d4"
"checksum derive_builder_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0288a23da9333c246bb18c143426074a6ae96747995c5819d2947b64cd942b37"
"checksum derive_more 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6d944ac6003ed268757ef1ee686753b57efc5fcf0ebe7b64c9fc81e7e32ff839"
"checksum darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
"checksum darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
"checksum darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
"checksum derive_builder 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
"checksum derive_builder_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
"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_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 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 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 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"
@ -2817,9 +2916,10 @@ dependencies = [
"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 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.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 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"
@ -2827,6 +2927,7 @@ dependencies = [
"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_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 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"
@ -2837,16 +2938,14 @@ dependencies = [
"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-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 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 quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
"checksum quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fe1e430bdcf30c9fdc25053b9c459bb1a4672af4617b6c783d7d91dc17c6bbb0"
"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 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.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.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"
@ -2862,13 +2961,13 @@ dependencies = [
"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 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.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 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 rss 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0706a43e890fbaf1714d495d12f69a7b34b70c6e903586d70311c2ce15ffe67"
"checksum ring 0.16.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6747f8da1f2b1fabbee1aaa4eb8a11abf9adef0bf58a41cee45db5d59cecdfac"
"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 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"
@ -2888,22 +2987,23 @@ dependencies = [
"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_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_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 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 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 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 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 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.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_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 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"
@ -2912,7 +3012,7 @@ dependencies = [
"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_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 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"
@ -2925,9 +3025,8 @@ dependencies = [
"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-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 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 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"
@ -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.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 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 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"

12
server/Cargo.toml vendored
View File

@ -12,7 +12,7 @@ bcrypt = "0.6.1"
activitypub = "0.2.0"
chrono = { version = "0.4.7", features = ["serde"] }
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"] }
actix = "0.9.0"
actix-web = "2.0.0"
@ -20,16 +20,16 @@ actix-files = "0.2.1"
actix-web-actors = "2.0.0"
actix-rt = "1.0.0"
env_logger = "0.7.1"
rand = "0.7.0"
rand = "0.7.3"
strum = "0.17.1"
strum_macros = "0.17.1"
jsonwebtoken = "6.0.1"
regex = "1.1.9"
jsonwebtoken = "7.0.1"
regex = "1.3.4"
lazy_static = "1.3.0"
lettre = "0.9.2"
lettre_email = "0.9.2"
sha2 = "0.8.0"
rss = "1.8.0"
sha2 = "0.8.1"
rss = "1.9.0"
htmlescape = "0.3.1"
config = "0.10.1"
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=(
"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

View File

@ -36,6 +36,7 @@ pub struct SaveComment {
#[derive(Serialize, Deserialize, Clone)]
pub struct CommentResponse {
pub comment: CommentView,
pub recipient_ids: Vec<i32>,
}
#[derive(Serialize, Deserialize)]
@ -88,6 +89,8 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
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
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
// This can cause two notifications, one for reply and the other for mention
if mention_user.id != user_id {
recipient_ids.push(mention_user.id);
let user_mention_form = UserMentionForm {
recipient_id: mention_user.id,
comment_id: inserted_comment.id,
@ -138,6 +143,8 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
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);
if parent_user.send_notifications_to_email {
if let Some(comment_reply_email) = parent_user.email {
let subject = &format!(
@ -161,6 +168,8 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
None => {
if post.creator_id != user_id {
let parent_user = User_::read(&conn, post.creator_id)?;
recipient_ids.push(parent_user.id);
if parent_user.send_notifications_to_email {
if let Some(post_reply_email) = parent_user.email {
let subject = &format!(
@ -199,6 +208,7 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
Ok(CommentResponse {
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()),
};
let mut recipient_ids = Vec::new();
// Scan the comment for user mentions, add those rows
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
// This can cause two notifications, one for reply and the other for mention
if mention_user_id != user_id {
recipient_ids.push(mention_user_id);
let user_mention_form = UserMentionForm {
recipient_id: mention_user_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
if let Some(removed) = data.removed.to_owned() {
let form = ModRemoveCommentForm {
@ -309,6 +338,7 @@ impl Perform<CommentResponse> for Oper<EditComment> {
Ok(CommentResponse {
comment: comment_view,
recipient_ids,
})
}
}
@ -345,6 +375,7 @@ impl Perform<CommentResponse> for Oper<SaveComment> {
Ok(CommentResponse {
comment: comment_view,
recipient_ids: Vec::new(),
})
}
}
@ -360,6 +391,8 @@ impl Perform<CommentResponse> for Oper<CreateCommentLike> {
let user_id = claims.id;
let mut recipient_ids = Vec::new();
// Don't do a downvote if site has downvotes disabled
if data.score == -1 {
let site = SiteView::read(&conn)?;
@ -379,6 +412,22 @@ impl Perform<CommentResponse> for Oper<CreateCommentLike> {
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 {
comment_id: data.comment_id,
post_id: data.post_id,
@ -403,6 +452,7 @@ impl Perform<CommentResponse> for Oper<CreateCommentLike> {
Ok(CommentResponse {
comment: liked_comment,
recipient_ids,
})
}
}

View File

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

View File

@ -17,7 +17,9 @@ use crate::db::user_mention::*;
use crate::db::user_mention_view::*;
use crate::db::user_view::*;
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 failure::Error;
use serde::{Deserialize, Serialize};

View File

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

View File

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

View File

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

View File

@ -98,7 +98,7 @@ impl<'a> PrivateMessageQueryBuilder<'a> {
pub fn list(self) -> Result<Vec<PrivateMessageView>, Error> {
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 self.unread_only {

View File

@ -3,7 +3,7 @@ use crate::schema::user_;
use crate::schema::user_::dsl::*;
use crate::{is_email_regex, Settings};
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)]
#[table_name = "user_"]
@ -115,7 +115,11 @@ impl Claims {
validate_exp: false,
..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(
&Header::default(),
&my_claims,
Settings::get().jwt_secret.as_ref(),
&EncodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
)
.unwrap()
}

View File

@ -36,7 +36,7 @@ use chrono::{DateTime, NaiveDateTime, Utc};
use lettre::smtp::authentication::{Credentials, Mechanism};
use lettre::smtp::extension::ClientId;
use lettre::smtp::ConnectionReuseParameters;
use lettre::{SmtpClient, Transport};
use lettre::{ClientSecurity, SmtpClient, Transport};
use lettre_email::Email;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
@ -62,8 +62,24 @@ pub fn remove_slurs(test: &str) -> String {
SLUR_REGEX.replace_all(test, "*removed*").to_string()
}
pub fn has_slurs(test: &str) -> bool {
SLUR_REGEX.is_match(test)
pub fn slur_check(test: &str) -> Result<(), Vec<&str>> {
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> {
@ -94,40 +110,42 @@ pub fn send_email(
let email = Email::builder()
.to((to_email, to_username))
.from((
email_config.smtp_login.to_owned(),
email_config.smtp_from_address.to_owned(),
))
.from(email_config.smtp_from_address.to_owned())
.subject(subject)
.html(html)
.build()
.unwrap();
let mut mailer = SmtpClient::new_simple(&email_config.smtp_server)
.unwrap()
let mailer = if email_config.use_tls {
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()))
.credentials(Credentials::new(
email_config.smtp_login.to_owned(),
email_config.smtp_password.to_owned(),
))
.smtp_utf8(true)
.authentication_mechanism(Mechanism::Plain)
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited)
.transport();
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited);
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());
mailer.close();
let mut transport = mailer.transport();
let result = transport.send(email.into());
transport.close();
match result {
Ok(_) => Ok(()),
Err(_) => Err("no_email_setup".to_string()),
Err(e) => Err(e.to_string()),
}
}
#[cfg(test)]
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]
fn test_email() {
@ -138,15 +156,29 @@ mod tests {
#[test]
fn test_slur_filter() {
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";
assert_eq!(
remove_slurs(&test),
"*removed* test *removed* *removed* *removed* *removed*. Capitalized *removed*. This is a bunch of other safe text."
.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]

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::get().to(route_get::<GetPost, GetPostResponse>))
.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>))
// Comment
.route("/api/v1/comment", web::post().to(route_post::<CreateComment, CommentResponse>))

View File

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

View File

@ -22,6 +22,12 @@ use crate::api::*;
use crate::websocket::UserOperation;
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
#[derive(Message)]
#[rtype(result = "()")]
@ -34,35 +40,22 @@ pub struct WSMessage(pub String);
#[rtype(usize)]
pub struct Connect {
pub addr: Recipient<WSMessage>,
pub ip: String,
pub ip: IPAddr,
}
/// Session is disconnected
#[derive(Message)]
#[rtype(result = "()")]
pub struct Disconnect {
pub id: usize,
pub ip: String,
}
// 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,
pub id: ConnectionId,
pub ip: IPAddr,
}
#[derive(Serialize, Deserialize, Message)]
#[rtype(String)]
pub struct StandardMessage {
/// Id of the client session
pub id: usize,
pub id: ConnectionId,
/// Peer message
pub msg: String,
}
@ -75,36 +68,93 @@ pub struct RateLimitBucket {
pub struct SessionInfo {
pub addr: Recipient<WSMessage>,
pub ip: String,
pub ip: IPAddr,
}
/// `ChatServer` manages chat rooms and responsible for coordinating chat
/// session. implementation is super primitive
/// session.
pub struct ChatServer {
sessions: HashMap<usize, SessionInfo>, // A map from generated random ID to session addr
rate_limits: HashMap<String, RateLimitBucket>,
rooms: HashMap<i32, HashSet<usize>>, // A map from room / post name to set of connectionIDs
/// A map from generated random ID to session addr
sessions: HashMap<ConnectionId, SessionInfo>,
/// 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,
db: Pool<ConnectionManager<PgConnection>>,
}
impl ChatServer {
pub fn startup(db: Pool<ConnectionManager<PgConnection>>) -> ChatServer {
// default room
let rooms = HashMap::new();
ChatServer {
sessions: HashMap::new(),
rate_limits: HashMap::new(),
rooms,
post_rooms: HashMap::new(),
community_rooms: HashMap::new(),
user_rooms: HashMap::new(),
rng: rand::thread_rng(),
db,
}
}
/// Send message to all users in the room
fn send_room_message(&self, room: i32, message: &str, skip_id: usize) {
if let Some(sessions) = self.rooms.get(&room) {
fn join_community_room(&mut self, community_id: CommunityId, id: ConnectionId) {
// remove session from all rooms
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 {
if *id != skip_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) {
// 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(
fn send_community_room_message(
&self,
community_id: i32,
community_id: CommunityId,
message: &str,
skip_id: usize,
) -> Result<(), Error> {
use crate::db::post_view::*;
use crate::db::*;
let conn = self.db.get()?;
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);
skip_id: ConnectionId,
) {
if let Some(sessions) = self.community_rooms.get(&community_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()));
}
}
}
}
}
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> {
@ -233,9 +338,6 @@ impl Handler<Connect> for ChatServer {
type Result = usize;
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
let id = self.rng.gen::<usize>();
println!("{} joined", &msg.ip);
@ -267,15 +369,18 @@ impl Handler<Disconnect> for ChatServer {
type Result = ();
fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) {
// let mut rooms: Vec<i32> = Vec::new();
// remove address
// Remove connections from sessions and all 3 scopes
if self.sessions.remove(&msg.id).is_some() {
// remove session from all rooms
for sessions in self.rooms.values_mut() {
if sessions.remove(&msg.id) {
// rooms.push(*id);
for sessions in self.user_rooms.values_mut() {
sessions.remove(&msg.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)
}
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 => {
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 => {
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)
}
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 => {
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.subscribed = None;
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)
}
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 res = Oper::new(ban_from_community).perform(&conn)?;
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)
}
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 res = Oper::new(mod_add_to_community).perform(&conn)?;
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)
}
UserOperation::ListCategories => {
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 => {
let get_post: GetPost = serde_json::from_str(data)?;
chat.join_room(get_post.id, msg.id);
let res = Oper::new(get_post).perform(&conn)?;
let post_id = get_post.id;
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)
}
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 => {
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 => {
let edit_post: EditPost = serde_json::from_str(data)?;
let res = Oper::new(edit_post).perform(&conn)?;
let mut post_sent = res.clone();
post_sent.post.my_vote = None;
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)
chat.post_sends(UserOperation::EditPost, res, msg.id)
}
UserOperation::SavePost => {
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 => {
chat.check_rate_limit_message(msg.id)?;
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 mut comment_sent = res.clone();
comment_sent.comment.my_vote = None;
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)
chat.comment_sends(UserOperation::CreateComment, res, msg.id)
}
UserOperation::EditComment => {
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 mut comment_sent = res.clone();
comment_sent.comment.my_vote = None;
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)
chat.comment_sends(UserOperation::EditComment, res, msg.id)
}
UserOperation::SaveComment => {
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 => {
chat.check_rate_limit_message(msg.id)?;
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 mut comment_sent = res.clone();
comment_sent.comment.my_vote = None;
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)
chat.comment_sends(UserOperation::CreateCommentLike, res, msg.id)
}
UserOperation::GetModlog => {
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)
}
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 => {
let online: usize = chat.sessions.len();
let get_site: GetSite = serde_json::from_str(data)?;
let mut res = Oper::new(get_site).perform(&conn)?;
res.online = online;
res.online = chat.sessions.len();
to_json_string(&user_operation, &res)
}
UserOperation::Search => {
@ -520,7 +650,13 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
}
UserOperation::CreatePrivateMessage => {
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 => {
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 => {
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/markdown-it": "^0.0.9",
"@types/markdown-it-container": "^2.0.2",
"@types/node": "^13.5.0",
"@types/node": "^13.7.0",
"autosize": "^4.0.2",
"bootswatch": "^4.3.1",
"classcat": "^1.1.3",
@ -35,6 +35,7 @@
"markdown-it-emoji": "^1.4.0",
"moment": "^2.24.0",
"prettier": "^1.18.2",
"reconnecting-websocket": "^4.3.0",
"rxjs": "^6.4.0",
"terser": "^4.6.3",
"toastify-js": "^1.6.2",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import { Component } from 'inferno';
import { Link } from 'inferno-router';
import { repoUrl } from '../utils';
import { version } from '../version';
import { T } from 'inferno-i18next';
import { i18n } from '../i18next';
export class Footer extends Component<any, any> {
constructor(props: any, context: any) {
@ -19,22 +19,22 @@ export class Footer extends Component<any, any> {
</li>
<li class="nav-item">
<Link class="nav-link" to="/modlog">
<T i18nKey="modlog">#</T>
{i18n.t('modlog')}
</Link>
</li>
<li class="nav-item">
<a class="nav-link" href={'/docs/index.html'}>
<T i18nKey="docs">#</T>
{i18n.t('docs')}
</a>
</li>
<li class="nav-item">
<Link class="nav-link" to="/sponsors">
<T i18nKey="donate">#</T>
{i18n.t('donate')}
</Link>
</li>
<li class="nav-item">
<a class="nav-link" href={repoUrl}>
<T i18nKey="code">#</T>
{i18n.t('code')}
</a>
</li>
</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">
<li className="list-inline-item">
<span class="pointer" onClick={this.markAllAsRead}>
<T i18nKey="mark_all_as_read">#</T>
{i18n.t('mark_all_as_read')}
</span>
</li>
</ul>
@ -147,36 +147,20 @@ export class Inbox extends Component<any, InboxState> {
onChange={linkEvent(this, this.handleUnreadOrAllChange)}
class="custom-select custom-select-sm w-auto mr-2"
>
<option disabled>
<T i18nKey="type">#</T>
</option>
<option value={UnreadOrAll.Unread}>
<T i18nKey="unread">#</T>
</option>
<option value={UnreadOrAll.All}>
<T i18nKey="all">#</T>
</option>
<option disabled>{i18n.t('type')}</option>
<option value={UnreadOrAll.Unread}>{i18n.t('unread')}</option>
<option value={UnreadOrAll.All}>{i18n.t('all')}</option>
</select>
<select
value={this.state.unreadType}
onChange={linkEvent(this, this.handleUnreadTypeChange)}
class="custom-select custom-select-sm w-auto mr-2"
>
<option disabled>
<T i18nKey="type">#</T>
</option>
<option value={UnreadType.All}>
<T i18nKey="all">#</T>
</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>
<option disabled>{i18n.t('type')}</option>
<option value={UnreadType.All}>{i18n.t('all')}</option>
<option value={UnreadType.Replies}>{i18n.t('replies')}</option>
<option value={UnreadType.Mentions}>{i18n.t('mentions')}</option>
<option value={UnreadType.Messages}>{i18n.t('messages')}</option>
</select>
<SortSelect
sort={this.state.sort}
@ -248,14 +232,14 @@ export class Inbox extends Component<any, InboxState> {
class="btn btn-sm btn-secondary mr-1"
onClick={linkEvent(this, this.prevPage)}
>
<T i18nKey="prev">#</T>
{i18n.t('prev')}
</button>
)}
<button
class="btn btn-sm btn-secondary"
onClick={linkEvent(this, this.nextPage)}
>
<T i18nKey="next">#</T>
{i18n.t('next')}
</button>
</div>
);
@ -421,10 +405,25 @@ export class Inbox extends Component<any, InboxState> {
this.sendUnreadCount();
this.setState(this.state);
} 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'));
// 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) {
let data = res.data as CommentResponse;
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 { wsJsonToRes, validEmail, toast } from '../utils';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
interface State {
loginForm: LoginForm;
@ -78,15 +77,19 @@ export class Login extends Component<any, State> {
return (
<div>
<form onSubmit={linkEvent(this, this.handleLoginSubmit)}>
<h5>Login</h5>
<h5>{i18n.t('login')}</h5>
<div class="form-group row">
<label class="col-sm-2 col-form-label">
<T i18nKey="email_or_username">#</T>
<label
class="col-sm-2 col-form-label"
htmlFor="login-email-or-username"
>
{i18n.t('email_or_username')}
</label>
<div class="col-sm-10">
<input
type="text"
class="form-control"
id="login-email-or-username"
value={this.state.loginForm.username_or_email}
onInput={linkEvent(this, this.handleLoginUsernameChange)}
required
@ -95,12 +98,13 @@ export class Login extends Component<any, State> {
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">
<T i18nKey="password">#</T>
<label class="col-sm-2 col-form-label" htmlFor="login-password">
{i18n.t('password')}
</label>
<div class="col-sm-10">
<input
type="password"
id="login-password"
value={this.state.loginForm.password}
onInput={linkEvent(this, this.handleLoginPasswordChange)}
class="form-control"
@ -111,7 +115,7 @@ export class Login extends Component<any, State> {
onClick={linkEvent(this, this.handlePasswordReset)}
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>
</div>
</div>
@ -135,16 +139,17 @@ export class Login extends Component<any, State> {
registerForm() {
return (
<form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
<h5>
<T i18nKey="sign_up">#</T>
</h5>
<h5>{i18n.t('sign_up')}</h5>
<div class="form-group row">
<label class="col-sm-2 col-form-label">
<T i18nKey="username">#</T>
<label class="col-sm-2 col-form-label" htmlFor="register-username">
{i18n.t('username')}
</label>
<div class="col-sm-10">
<input
type="text"
id="register-username"
class="form-control"
value={this.state.registerForm.username}
onInput={linkEvent(this, this.handleRegisterUsernameChange)}
@ -155,13 +160,15 @@ export class Login extends Component<any, State> {
/>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">
<T i18nKey="email">#</T>
<label class="col-sm-2 col-form-label" htmlFor="register-email">
{i18n.t('email')}
</label>
<div class="col-sm-10">
<input
type="email"
id="register-email"
class="form-control"
placeholder={i18n.t('optional')}
value={this.state.registerForm.email}
@ -170,13 +177,15 @@ export class Login extends Component<any, State> {
/>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">
<T i18nKey="password">#</T>
<label class="col-sm-2 col-form-label" htmlFor="register-password">
{i18n.t('password')}
</label>
<div class="col-sm-10">
<input
type="password"
id="register-password"
value={this.state.registerForm.password}
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
class="form-control"
@ -184,13 +193,18 @@ export class Login extends Component<any, State> {
/>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">
<T i18nKey="verify_password">#</T>
<label
class="col-sm-2 col-form-label"
htmlFor="register-verify-password"
>
{i18n.t('verify_password')}
</label>
<div class="col-sm-10">
<input
type="password"
id="register-verify-password"
value={this.state.registerForm.password_verify}
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
class="form-control"
@ -204,12 +218,13 @@ export class Login extends Component<any, State> {
<div class="form-check">
<input
class="form-check-input"
id="register-show-nsfw"
type="checkbox"
checked={this.state.registerForm.show_nsfw}
onChange={linkEvent(this, this.handleRegisterShowNsfwChange)}
/>
<label class="form-check-label">
<T i18nKey="show_nsfw">#</T>
<label class="form-check-label" htmlFor="register-show-nsfw">
{i18n.t('show_nsfw')}
</label>
</div>
</div>
@ -306,6 +321,7 @@ export class Login extends Component<any, State> {
this.state = this.emptyState;
this.setState(this.state);
UserService.Instance.login(data);
WebSocketService.Instance.userJoin();
toast(i18n.t('logged_in'));
this.props.history.push('/');
} else if (res.op == UserOperation.Register) {
@ -313,6 +329,7 @@ export class Login extends Component<any, State> {
this.state = this.emptyState;
this.setState(this.state);
UserService.Instance.login(data);
WebSocketService.Instance.userJoin();
this.props.history.push('/communities');
} else if (res.op == UserOperation.PasswordReset) {
toast(i18n.t('reset_password_mail_sent'));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,6 @@ import {
CommentForm as CommentFormI,
CommentResponse,
CommentSortType,
CreatePostLikeResponse,
CommunityUser,
CommunityResponse,
CommentNode as CommentNodeI,
@ -38,7 +37,6 @@ import { CommentForm } from './comment-form';
import { CommentNodes } from './comment-nodes';
import autosize from 'autosize';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
interface PostState {
post: PostI;
@ -47,6 +45,7 @@ interface PostState {
community: Community;
moderators: Array<CommunityUser>;
admins: Array<UserView>;
online: number;
scrolled?: boolean;
scrolled_comment_id?: number;
loading: boolean;
@ -62,6 +61,7 @@ export class Post extends Component<any, PostState> {
community: null,
moderators: [],
admins: [],
online: null,
scrolled: false,
loading: true,
crossPosts: [],
@ -173,7 +173,7 @@ export class Post extends Component<any, PostState> {
{this.state.crossPosts.length > 0 && (
<>
<div class="my-1 text-muted small font-weight-bold">
<T i18nKey="cross_posts">#</T>
{i18n.t('cross_posts')}
</div>
<PostListings showCommunity posts={this.state.crossPosts} />
</>
@ -255,9 +255,7 @@ export class Post extends Component<any, PostState> {
return (
<div class="d-none d-md-block new-comments mb-3 card border-secondary">
<div class="card-body small">
<h6>
<T i18nKey="recent_comments">#</T>
</h6>
<h6>{i18n.t('recent_comments')}</h6>
{this.state.comments.map(comment => (
<CommentNodes
nodes={[{ comment: comment }]}
@ -280,6 +278,7 @@ export class Post extends Component<any, PostState> {
community={this.state.community}
moderators={this.state.moderators}
admins={this.state.admins}
online={this.state.online}
/>
</div>
);
@ -378,6 +377,7 @@ export class Post extends Component<any, PostState> {
this.state.community = data.community;
this.state.moderators = data.moderators;
this.state.admins = data.admins;
this.state.online = data.online;
this.state.loading = false;
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);
} else if (res.op == UserOperation.CreateComment) {
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.setState(this.state);
}
} else if (res.op == UserOperation.EditComment) {
let data = res.data as CommentResponse;
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);
} else if (res.op == UserOperation.CreatePostLike) {
let data = res.data as CreatePostLikeResponse;
this.state.post.my_vote = data.post.my_vote;
let data = res.data as PostResponse;
this.state.post.score = data.post.score;
this.state.post.upvotes = data.post.upvotes;
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);
} else if (res.op == UserOperation.EditPost) {
let data = res.data as PostResponse;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,12 @@ import { WebSocketService } from '../services';
import { i18n } from '../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 silver = [];
// let gold = [];
@ -36,16 +41,14 @@ export class Sponsors extends Component<any, any> {
topMessage() {
return (
<div>
<h5>
<T i18nKey="donate_to_lemmy">#</T>
</h5>
<h5>{i18n.t('donate_to_lemmy')}</h5>
<p>
<T i18nKey="sponsor_message">
#<a href="https://github.com/dessalines/lemmy">#</a>
</T>
</p>
<a class="btn btn-secondary" href="https://www.patreon.com/dessalines">
<T i18nKey="support_on_patreon">#</T>
{i18n.t('support_on_patreon')}
</a>
</div>
);
@ -53,12 +56,8 @@ export class Sponsors extends Component<any, any> {
sponsors() {
return (
<div class="container">
<h5>
<T i18nKey="sponsors">#</T>
</h5>
<p>
<T i18nKey="general_sponsors">#</T>
</p>
<h5>{i18n.t('sponsors')}</h5>
<p>{i18n.t('general_sponsors')}</p>
<div class="row card-columns">
{highlighted.map(s => (
<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() {
return (
<div>
<h5>
<T i18nKey="crypto">#</T>
</h5>
<h5>{i18n.t('crypto')}</h5>
<div class="table-responsive">
<table class="table table-hover text-center">
<tbody>
<tr>
<td>
<T i18nKey="bitcoin">#</T>
</td>
<td>{i18n.t('bitcoin')}</td>
<td>
<code>1Hefs7miXS5ff5Ck5xvmjKjXf5242KzRtK</code>
</td>
</tr>
<tr>
<td>
<T i18nKey="ethereum">#</T>
</td>
<td>{i18n.t('ethereum')}</td>
<td>
<code>0x400c96c96acbC6E7B3B43B1dc1BB446540a88A01</code>
</td>
</tr>
<tr>
<td>
<T i18nKey="monero">#</T>
</td>
<td>{i18n.t('monero')}</td>
<td>
<code>
41taVyY6e1xApqKyMVDRVxJ76sPkfZhALLTjRvVKpaAh2pBd4wv9RgYj1tSPrx8wc6iE1uWUfjtQdTmTy2FGMeChGVKPQuV

View File

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

8
ui/src/env.ts vendored
View File

@ -1,5 +1,9 @@
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}`;
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 { fi } from './translations/fi';
import { ca } from './translations/ca';
import { fa } from './translations/fa';
// https://github.com/nimbusec-oss/inferno-i18next/blob/master/tests/T.test.js#L66
const resources = {
@ -27,6 +28,7 @@ const resources = {
it,
fi,
ca,
fa,
};
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,
EditPrivateMessage,
GetPrivateMessages,
UserJoin,
}
export enum CommentSortType {
@ -538,6 +539,7 @@ export interface GetCommunityResponse {
community: Community;
moderators: Array<CommunityUser>;
admins: Array<UserView>;
online: number;
}
export interface CommunityResponse {
@ -594,6 +596,7 @@ export interface GetPostResponse {
community: Community;
moderators: Array<CommunityUser>;
admins: Array<UserView>;
online: number;
}
export interface SavePostForm {
@ -627,6 +630,7 @@ export interface SaveCommentForm {
export interface CommentResponse {
comment: Comment;
recipient_ids: Array<number>;
}
export interface CommentLikeForm {
@ -660,10 +664,6 @@ export interface CreatePostLikeForm {
auth?: string;
}
export interface CreatePostLikeResponse {
post: Post;
}
export interface SiteForm {
name: string;
description?: string;
@ -775,6 +775,14 @@ export interface PrivateMessageResponse {
message: PrivateMessage;
}
export interface UserJoinForm {
auth: string;
}
export interface UserJoinResponse {
user_id: number;
}
export type MessageType =
| EditPrivateMessageForm
| LoginForm
@ -819,7 +827,7 @@ type ResponseType =
| GetFollowedCommunitiesResponse
| ListCommunitiesResponse
| GetPostsResponse
| CreatePostLikeResponse
| PostResponse
| GetRepliesResponse
| GetUserMentionsResponse
| ListCategoriesResponse

View File

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

View File

@ -15,9 +15,9 @@ export const de = {
remove_comment: 'Kommentar löschen',
communities: 'Communities',
users: 'Benutzer',
create_a_community: 'Eine community anlegen',
create_community: 'Community anlegen',
remove_community: 'Community entfernen',
create_a_community: 'Eine Gemeinschaft anlegen',
create_community: 'Gemeinschaft anlegen',
remove_community: 'Gemeinschaft entfernen',
subscribed_to_communities: 'Abonnierte <1>communities</1>',
trending_communities: 'Trending <1>communities</1>',
list_of_communities: 'Liste von communities',
@ -36,17 +36,17 @@ export const de = {
unsticky: 'nicht haftend',
link: 'link',
archive_link: 'Archiv-Link',
mod: 'mod',
mods: 'mods',
mod: 'Moderator',
mods: 'Moderatoren',
moderates: 'Moderiert',
settings: 'Einstellungen',
remove_as_mod: 'Als mod entfernen',
appoint_as_mod: 'Zum mod ernennen',
remove_as_mod: 'Als Moderator entfernen',
appoint_as_mod: 'Zum Moderator ernennen',
modlog: 'Modlog',
admin: 'admin',
admins: 'admins',
remove_as_admin: 'Als admin entfernen',
appoint_as_admin: 'Zum admin ernennen',
admin: 'Administrator',
admins: 'Administratoren',
remove_as_admin: 'Als Administrator entfernen',
appoint_as_admin: 'Zum Administrator ernennen',
remove: 'entfernen',
removed: 'entfernt',
locked: 'gesperrt',
@ -66,11 +66,11 @@ export const de = {
unban_from_site: 'Von der Seite entbannen',
banned: 'gesperrt',
save: 'speichern',
unsave: 'unsave',
unsave: 'nicht speichern',
create: 'anlegen',
creator: 'Ersteller',
username: 'Username',
email_or_username: 'Email oder Username',
username: 'Benutzername',
email_or_username: 'E-mail oder Username',
number_of_users: '{{count}} Benutzer',
number_of_subscribers: '{{count}} Abonnenten',
number_of_points: '{{count}} Punkte',
@ -86,7 +86,7 @@ export const de = {
subscribed: 'Abonniert',
prev: 'Zurück',
next: 'Weiter',
sidebar: 'Sidebar',
sidebar: 'Seitenleiste',
sort_type: 'Sortieren nach',
hot: 'Hot',
new: 'Neu',
@ -116,28 +116,29 @@ export const de = {
password: 'Passwort',
verify_password: 'Passwort überprüfen',
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',
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',
sign_up: 'Registrieren',
email: 'Email',
optional: 'Optional',
email: 'E-Mail',
optional: 'optional',
expires: 'Ablaufdatum',
language: 'Sprache',
browser_default: 'Standard-Browser',
url: 'URL',
body: 'Text',
copy_suggested_title: 'Vorgeschlagenen Titel übernehmen: {{title}}',
community: 'Community',
expand_here: 'Expand here',
community: 'Gemeinschaft',
expand_here: 'hier erweitern',
subscribe_to_communities: 'Abonniere ein paar <1>communities</1>.',
chat: 'Chat',
recent_comments: 'Neueste Kommentare',
no_results: 'Keine Ergebnisse.',
setup: 'Setup',
lemmy_instance_setup: 'Lemmy Instanz Setup',
setup: 'Einrichten',
lemmy_instance_setup: 'Lemmy Instanz Einrichten',
setup_admin: 'Seiten Administrator konfigurieren',
your_site: 'deine Seite',
modified: 'verändert',
@ -151,7 +152,7 @@ export const de = {
support_on_patreon: 'Auf Patreon unterstützen',
general_sponsors:
'Allgemeine Sponsoren sind die, die zwischen $10 und $39 zu Lemmy beitragen.',
crypto: 'Crypto',
crypto: 'Kryptowährung',
bitcoin: 'Bitcoin',
ethereum: 'Ethereum',
monero: 'Monero',
@ -159,16 +160,16 @@ export const de = {
joined: 'beigetreten',
by: 'von',
to: 'bis',
transfer_community: 'Transfer-Community',
transfer_community: 'Gemeinschaft übertragen',
transfer_site: 'Transferseite',
are_you_sure: 'Bist du sicher?',
yes: 'Ja',
no: 'Nein',
powered_by: 'Bereitgestellt durch',
landing_0:
'Lemmy ist ein <1>Link Aggregator</1> / Reddit Alternative im <2>Fediverse</2>.<3></3>Es ist selbst-hostbar, hat live-updates von Kommentar-threads und ist winzig (<4>~80kB</4>). Federation in das ActivityPub Netzwerk ist geplant. <5></5>Dies ist eine <6>sehr frühe Beta Version</6>, und viele Features funktionieren zurzeit nicht richtig oder fehlen. <7></7>Schlage neue Features vor oder melde Bugs <8>hier.</8><9></9>Gebaut mit <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.',
'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.',
community_ban: 'Du wurdest von dieser Community gebannt.',
community_ban: 'Du wurdest von dieser Gemeinschaft gebannt.',
site_ban: 'Du wurdest von dieser Seite gebannt',
couldnt_create_comment: 'Konnte Kommentar nicht anlegen.',
couldnt_like_comment: 'Konnte nicht liken.',
@ -176,14 +177,15 @@ export const de = {
couldnt_save_comment: 'Konnte Kommentar nicht speichern.',
no_comment_edit_allowed: 'Keine Erlaubnis Kommentar zu editieren.',
no_post_edit_allowed: 'Keine Erlaubnis Beitrag zu editieren.',
no_community_edit_allowed: 'Keine Erlaubnis Community zu editieren.',
couldnt_find_community: 'Konnte Community nicht finden.',
couldnt_update_community: 'Konnte Community nicht aktualisieren.',
community_already_exists: 'Community existiert bereits.',
no_community_edit_allowed: 'Keine Erlaubnis Gemeinschaft zu editieren.',
couldnt_find_community: 'Konnte Gemeinschaft nicht finden.',
couldnt_update_community: 'Konnte Gemeinschaft nicht aktualisieren.',
community_already_exists: 'Gemeinschaft existiert bereits.',
community_moderator_already_exists:
'Community Moderator existiert bereits.',
community_follower_already_exists: 'Community Follower existiert bereits.',
community_user_already_banned: 'Community Nutzer schon gebannt.',
'Gemeinschaft Moderator existiert bereits.',
community_follower_already_exists:
'Gemeinschaft Follower existiert bereits.',
community_user_already_banned: 'Gemeinschaft Nutzer schon gebannt.',
couldnt_create_post: 'Konnte Beitrag nicht anlegen.',
couldnt_like_post: 'Konnte Beitrag nicht liken.',
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.",
no_private_message_edit_allowed: 'Not allowed to edit 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.',
password_change: 'Cambio de 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',
matrix_user_id: 'Usuario Matricial',
private_message_disclaimer:
@ -204,9 +204,12 @@ export const es = {
couldnt_find_community: 'No se pudo encontrar la comunidad.',
couldnt_update_community: 'No se pudo actualizar la comunidad.',
community_already_exists: 'Esta comunidad ya existe.',
community_moderator_already_exists: 'Este moderador de la comunidad ya existe.',
community_follower_already_exists: 'Este seguidor de la comunidad ya existe.',
community_user_already_banned: 'Este usuario de la comunidad ya fue expulsado.',
community_moderator_already_exists:
'Este moderador de la comunidad ya existe.',
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_like_post: 'No se pudo gustar 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.',
email_already_exists: 'El correo ya está en uso.',
couldnt_update_user: 'No se pudo actualizar el usuario.',
system_err_login: 'Error del sistema. Intente cerrar sesión e ingresar de nuevo.',
couldnt_create_private_message: "No se pudo crear 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.",
system_err_login:
'Error del sistema. Intente cerrar sesión e ingresar de nuevo.',
couldnt_create_private_message: 'No se pudo crear 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>',
list_of_communities: 'Lista yhteisöistä',
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',
send_secure_message: 'Lähetä suojattu viesti',
send_message: 'Lähetä viesti',
@ -132,7 +133,7 @@ export const fi = {
reset_password_mail_sent: 'Sähköposti lähetettiin salasanan nollaamiseksi.',
password_change: 'Salasanan muutos',
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',
matrix_user_id: ' Matrix-käyttäjä',
private_message_disclaimer:
@ -189,45 +190,47 @@ export const fi = {
no: 'ei',
powered_by: 'Vauhdittajana',
landing_0:
"Lemmy on <1>linkinkerääjä</1> / Reddit-vaihtoehto, tarkoitettu toimimaan <2>fediversessä</2>.<3></3>Sitä voi isännöidä itse, siinä on tosiaikaisesti päivittyvät kommenttiketjut, ja se on pieni (<4>~80 kilotavua</4>). Federointi ActivityPub-verkkoon on suunnittelun alla. <5></5>Tämä on <6>hyvin varhainen betaversio</6>, ja monet ominaisuudet ovat toistaiseksi rikki tai poissa. <7></7>Ehdota uusia ominaisuuksia tai raportoi bugeja <8>tänne.</8><9></9>Tehty teknologioilla <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
'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.',
logged_in: 'Kirjautunut sisään.',
community_ban: 'Sinulle on asetettu porttikielto tähän yhteisöön.',
site_ban: 'Sinut on asetettu porttikieltoon tältä sivustolta',
couldnt_create_comment: "Kommenttia ei pystytty luomaan.",
couldnt_like_comment: "Kommentista ei voitu tykätä.",
couldnt_update_comment: "Kommenttia ei voitu päivittää.",
couldnt_save_comment: "Kommenttia ei voitu tallentaa.",
couldnt_create_comment: 'Kommenttia ei pystytty luomaan.',
couldnt_like_comment: 'Kommentista ei voitu tykätä.',
couldnt_update_comment: 'Kommenttia ei voitu päivittää.',
couldnt_save_comment: 'Kommenttia ei voitu tallentaa.',
no_comment_edit_allowed: 'Et ole sallittu muokkaamaan kommenttia.',
no_post_edit_allowed: 'Et ole sallittu muokkaamaan viestiä.',
no_community_edit_allowed: 'Et ole sallittu muokkaamaan yhteisöä.',
couldnt_find_community: "Yhteisöä ei voitu löytää.",
couldnt_update_community: "Yhteisöä ei voitu päivittää.",
couldnt_find_community: 'Yhteisöä ei voitu löytää.',
couldnt_update_community: 'Yhteisöä ei voitu päivittää.',
community_already_exists: 'Yhteisö on jo olemassa.',
community_moderator_already_exists: 'Yhteisön moderaattori 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.',
couldnt_create_post: "Ei voitu luoda viestiä.",
couldnt_like_post: "Viestistä ei voitu tykätä.",
couldnt_find_post: "Viestiä ei löytynyt.",
couldnt_get_posts: "Viestejä ei saatu",
couldnt_update_post: "Viestiä ei voitu päivittää",
couldnt_save_post: "Viestiä ei voitu tallentaa.",
couldnt_create_post: 'Ei voitu luoda viestiä.',
couldnt_like_post: 'Viestistä ei voitu tykätä.',
couldnt_find_post: 'Viestiä ei löytynyt.',
couldnt_get_posts: 'Viestejä ei saatu',
couldnt_update_post: 'Viestiä ei voitu päivittää',
couldnt_save_post: 'Viestiä ei voitu tallentaa.',
no_slurs: 'Ei loukkauksia.',
not_an_admin: 'Ei ole ylläpitäjä.',
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:
"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.',
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.',
email_already_exists: 'Sähköposti on jo olemassa.',
couldnt_update_user: "Käyttäjää ei voitu päivittää.",
system_err_login: 'Järjestelmävirhe. Yritä kirjautua ulos ja kirjautua uudestaan sisään.',
couldnt_create_private_message: "Yksityisviestiä ei voitu luoda.",
no_private_message_edit_allowed: 'Et ole sallittu muokkaamaan yksityisviestiä.',
couldnt_update_private_message: "Yksityisviestiä 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.',
couldnt_create_private_message: 'Yksityisviestiä ei voitu luoda.',
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',
registration_closed: 'Registratie gesloten',
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: '解锁',
lock: '加锁',
link: '链接',
mod: 'mod',
mods: 'mods',
moderates: 'Moderates',
remove_as_mod: 'remove as mod',
appoint_as_mod: 'appoint as mod',
modlog: 'Modlog',
admin: 'admin',
admins: 'admins',
mod: '监管人',
mods: '监管人',
moderates: '监管',
remove_as_mod: '添加监管人',
appoint_as_mod: '移除监管人',
modlog: '监管记录',
admin: '管理权限',
admins: '管理权限',
remove_as_admin: '移除管理权限',
appoint_as_admin: '添加管理权限',
remove: '移除',
@ -77,7 +77,7 @@ export const zh = {
year: '年',
all: '所有',
top: '最热',
api: 'API',
api: '应用程式介面',
inbox: '收件箱',
inbox_for: '<1>{{user}}</1> 收件箱',
mark_all_as_read: '标记所有已读',
@ -98,7 +98,7 @@ export const zh = {
email: '邮箱',
optional: '选项',
expires: '过期',
url: 'URL',
url: '网址',
body: '内容',
copy_suggested_title: '复制建议的标题: {{title}}',
community: '节点',
@ -111,11 +111,11 @@ export const zh = {
setup_admin: '设置管理员',
your_site: '你的站点',
modified: '修改',
sponsors: 'Sponsors',
sponsors_of_lemmy: 'Sponsors of Lemmy',
sponsors: '发起人',
sponsors_of_lemmy: 'Lemmy 的发起人',
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:',
support_on_patreon: 'Support on Patreon',
support_on_patreon: '在 Patreon 赞助',
general_sponsors:
'General Sponsors are those that pledged $10 to $39 to Lemmy.',
crypto: '加密',
@ -139,8 +139,8 @@ export const zh = {
couldnt_find_community: '不能找到节点.',
couldnt_update_community: '不能更新节点.',
community_already_exists: '节点已存在.',
community_moderator_already_exists: '节点 moderator 已存在.',
community_follower_already_exists: '节点 follower 已存在.',
community_moderator_already_exists: '节点监管人已存在.',
community_follower_already_exists: '节点追随者已存在.',
community_user_already_banned: '节点用户已禁止.',
couldnt_create_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/fi';
import 'moment/locale/ca';
import 'moment/locale/fa';
import {
UserOperation,
@ -258,6 +259,7 @@ export const languages = [
{ code: 'eo', name: 'Esperanto' },
{ code: 'es', name: 'Español' },
{ code: 'de', name: 'Deutsch' },
{ code: 'fa', name: 'فارسی' },
{ code: 'zh', name: '中文' },
{ code: 'fi', name: 'Suomi' },
{ code: 'fr', name: 'Français' },
@ -306,6 +308,8 @@ export function getMomentLanguage(): string {
lang = 'fi';
} else if (lang.startsWith('ca')) {
lang = 'ca';
} else if (lang.startsWith('fa')) {
lang = 'fa';
} else {
lang = 'en';
}
@ -314,6 +318,7 @@ export function getMomentLanguage(): string {
export const themes = [
'litera',
'materia',
'minty',
'solar',
'united',
@ -323,6 +328,7 @@ export const themes = [
'sketchy',
'vaporwave',
'vaporwave-dark',
'i386',
];
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 { es } from './src/translations/es';
import { de } from './src/translations/de';
import { fa } from './src/translations/fa';
import { zh } from './src/translations/zh';
import { fr } from './src/translations/fr';
import { sv } from './src/translations/sv';
@ -15,6 +16,7 @@ import fs from 'fs';
const files = [
{ t: ca, n: 'ca' },
{ t: de, n: 'de' },
{ t: fa, n: 'fa' },
{ t: eo, n: 'eo' },
{ t: es, n: 'es' },
{ t: fi, n: 'fi' },

261
ui/yarn.lock vendored
View File

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