mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-26 19:31:05 +00:00
Merge branch 'dev' into federation
This commit is contained in:
commit
1336b4ed60
55 changed files with 1370 additions and 413 deletions
2
CODE_OF_CONDUCT.md
vendored
2
CODE_OF_CONDUCT.md
vendored
|
@ -30,6 +30,6 @@ In the Lemmy community we strive to go the extra step to look out for each other
|
|||
|
||||
And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could’ve communicated better — remember that it’s your responsibility to make others comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust.
|
||||
|
||||
The enforcement policies listed above apply to all official Lemmy venues; including git repositories under [github.com/dessalines/lemmy](https://github.com/dessalines/lemmy) and [yerbamate.dev/dessalines/lemmy](https://yerbamate.dev/dessalines/lemmy), the [Matrix channel](https://matrix.to/#/!BZVTUuEiNmRcbFeLeI:matrix.org?via=matrix.org&via=privacytools.io&via=permaweb.io); and all instances under lemmy.ml. For other projects adopting the Rust Code of Conduct, please contact the maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion.
|
||||
The enforcement policies listed above apply to all official Lemmy venues; including git repositories under [github.com/LemmyNet/lemmy](https://github.com/LemmyNet/lemmy) and [yerbamate.dev/dessalines/lemmy](https://yerbamate.dev/dessalines/lemmy), the [Matrix channel](https://matrix.to/#/!BZVTUuEiNmRcbFeLeI:matrix.org?via=matrix.org&via=privacytools.io&via=permaweb.io); and all instances under lemmy.ml. For other projects adopting the Rust Code of Conduct, please contact the maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion.
|
||||
|
||||
Adapted from the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct), which is based on the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).
|
||||
|
|
23
README.md
vendored
23
README.md
vendored
|
@ -1,12 +1,12 @@
|
|||
<div align="center">
|
||||
|
||||
![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/dessalines/lemmy.svg)
|
||||
[![Build Status](https://travis-ci.org/dessalines/lemmy.svg?branch=master)](https://travis-ci.org/dessalines/lemmy)
|
||||
[![GitHub issues](https://img.shields.io/github/issues-raw/dessalines/lemmy.svg)](https://github.com/dessalines/lemmy/issues)
|
||||
![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/LemmyNet/lemmy.svg)
|
||||
[![Build Status](https://travis-ci.org/LemmyNet/lemmy.svg?branch=master)](https://travis-ci.org/LemmyNet/lemmy)
|
||||
[![GitHub issues](https://img.shields.io/github/issues-raw/LemmyNet/lemmy.svg)](https://github.com/LemmyNet/lemmy/issues)
|
||||
[![Docker Pulls](https://img.shields.io/docker/pulls/dessalines/lemmy.svg)](https://cloud.docker.com/repository/docker/dessalines/lemmy/)
|
||||
[![Translation status](http://weblate.yerbamate.dev/widgets/lemmy/-/lemmy/svg-badge.svg)](http://weblate.yerbamate.dev/engage/lemmy/)
|
||||
[![License](https://img.shields.io/github/license/dessalines/lemmy.svg)](LICENSE)
|
||||
![GitHub stars](https://img.shields.io/github/stars/dessalines/lemmy?style=social)
|
||||
[![License](https://img.shields.io/github/license/LemmyNet/lemmy.svg)](LICENSE)
|
||||
![GitHub stars](https://img.shields.io/github/stars/LemmyNet/lemmy?style=social)
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
|
@ -22,11 +22,11 @@
|
|||
·
|
||||
<a href="https://dev.lemmy.ml/docs/index.html">Documentation</a>
|
||||
·
|
||||
<a href="https://github.com/dessalines/lemmy/issues">Report Bug</a>
|
||||
<a href="https://github.com/LemmyNet/lemmy/issues">Report Bug</a>
|
||||
·
|
||||
<a href="https://github.com/dessalines/lemmy/issues">Request Feature</a>
|
||||
<a href="https://github.com/LemmyNet/lemmy/issues">Request Feature</a>
|
||||
·
|
||||
<a href="https://github.com/dessalines/lemmy/blob/master/RELEASES.md">Releases</a>
|
||||
<a href="https://github.com/LemmyNet/lemmy/blob/master/RELEASES.md">Releases</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
|
@ -36,7 +36,7 @@ Front Page|Post
|
|||
---|---
|
||||
![main screen](https://i.imgur.com/kZSRcRu.png)|![chat screen](https://i.imgur.com/4XghNh6.png)
|
||||
|
||||
[Lemmy](https://github.com/dessalines/lemmy) is similar to sites like [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), [Raddle](https://raddle.me), or [Hacker News](https://news.ycombinator.com/): you subscribe to forums you're interested in, post links and discussions, then vote, and comment on them. Behind the scenes, it is very different; anyone can easily run a server, and all these servers are federated (think email), and connected to the same universe, called the [Fediverse](https://en.wikipedia.org/wiki/Fediverse).
|
||||
[Lemmy](https://github.com/LemmyNet/lemmy) is similar to sites like [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), [Raddle](https://raddle.me), or [Hacker News](https://news.ycombinator.com/): you subscribe to forums you're interested in, post links and discussions, then vote, and comment on them. Behind the scenes, it is very different; anyone can easily run a server, and all these servers are federated (think email), and connected to the same universe, called the [Fediverse](https://en.wikipedia.org/wiki/Fediverse).
|
||||
|
||||
For a link aggregator, this means a user registered on one server can subscribe to forums on any other server, and can have discussions with users registered elsewhere.
|
||||
|
||||
|
@ -108,8 +108,9 @@ Each lemmy server can set its own moderation policy; appointing site-wide admins
|
|||
|
||||
Lemmy is free, open-source software, meaning no advertising, monetizing, or venture capital, ever. Your donations directly support full-time development of the project.
|
||||
|
||||
- [Support on Liberapay.](https://liberapay.com/Lemmy)
|
||||
- [Support on Liberapay](https://liberapay.com/Lemmy).
|
||||
- [Support on Patreon](https://www.patreon.com/dessalines).
|
||||
- [Support on OpenCollective](https://opencollective.com/lemmy).
|
||||
- [List of Sponsors](https://dev.lemmy.ml/sponsors).
|
||||
|
||||
### Crypto
|
||||
|
@ -132,7 +133,7 @@ If you want to help with translating, take a look at [Weblate](https://weblate.y
|
|||
|
||||
- [Mastodon](https://mastodon.social/@LemmyDev) - [![Mastodon Follow](https://img.shields.io/mastodon/follow/810572?domain=https%3A%2F%2Fmastodon.social&style=social)](https://mastodon.social/@LemmyDev)
|
||||
- [Matrix](https://riot.im/app/#/room/#rust-reddit-fediverse:matrix.org) - [![Matrix](https://img.shields.io/matrix/rust-reddit-fediverse:matrix.org.svg?label=matrix-chat)](https://riot.im/app/#/room/#rust-reddit-fediverse:matrix.org)
|
||||
- [GitHub](https://github.com/dessalines/lemmy)
|
||||
- [GitHub](https://github.com/LemmyNet/lemmy)
|
||||
- [Gitea](https://yerbamate.dev/dessalines/lemmy)
|
||||
- [GitLab](https://gitlab.com/dessalines/lemmy)
|
||||
|
||||
|
|
4
RELEASES.md
vendored
4
RELEASES.md
vendored
|
@ -1,6 +1,6 @@
|
|||
# Lemmy v0.6.0 Release (2020-01-16)
|
||||
|
||||
`v0.6.0` is here, and we've closed [41 issues!](https://github.com/dessalines/lemmy/milestone/15?closed=1)
|
||||
`v0.6.0` is here, and we've closed [41 issues!](https://github.com/LemmyNet/lemmy/milestone/15?closed=1)
|
||||
|
||||
This is the biggest release by far:
|
||||
|
||||
|
@ -10,7 +10,7 @@ This is the biggest release by far:
|
|||
- Can set a custom language.
|
||||
- Lemmy-wide settings to disable downvotes, and close registration.
|
||||
- A better documentation system, hosted in lemmy itself.
|
||||
- [Huge DB performance gains](https://github.com/dessalines/lemmy/issues/411) (everthing down to < `30ms`) by using materialized views.
|
||||
- [Huge DB performance gains](https://github.com/LemmyNet/lemmy/issues/411) (everthing down to < `30ms`) by using materialized views.
|
||||
- Fixed major issue with similar post URL and title searching.
|
||||
- Upgraded to Actix `2.0`
|
||||
- Faster comment / post voting.
|
||||
|
|
2
ansible/VERSION
vendored
2
ansible/VERSION
vendored
|
@ -1 +1 @@
|
|||
v0.6.44
|
||||
v0.6.49
|
||||
|
|
4
docker/dev/deploy.sh
vendored
4
docker/dev/deploy.sh
vendored
|
@ -72,5 +72,5 @@ git push origin $new_tag
|
|||
git push
|
||||
|
||||
# Pushing to any ansible deploys
|
||||
cd ../../ansible || exit
|
||||
ansible-playbook lemmy.yml --become
|
||||
cd ../../../lemmy-ansible || exit
|
||||
ansible-playbook -i prod playbooks/site.yml --vault-password-file vault_pass
|
||||
|
|
12
docker/dev/dev_deploy.sh
vendored
12
docker/dev/dev_deploy.sh
vendored
|
@ -1,12 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Building from the dev branch for dev servers
|
||||
git checkout dev
|
||||
|
||||
# Rebuilding dev docker
|
||||
docker-compose build
|
||||
docker tag dev_lemmy:latest dessalines/lemmy:dev
|
||||
docker push dessalines/lemmy:dev
|
||||
|
||||
# SSH and pull it
|
||||
ssh $LEMMY_USER@$LEMMY_HOST "cd ~/git/lemmy/docker/dev && docker pull dessalines/lemmy:dev && docker-compose up -d"
|
2
docker/dev/docker-compose.yml
vendored
2
docker/dev/docker-compose.yml
vendored
|
@ -21,7 +21,7 @@ services:
|
|||
environment:
|
||||
- RUST_LOG=debug
|
||||
volumes:
|
||||
- ../lemmy.hjson:/config/config.hjson:ro
|
||||
- ../lemmy.hjson:/config/config.hjson
|
||||
depends_on:
|
||||
- postgres
|
||||
- pictshare
|
||||
|
|
11
docker/dev/test_deploy.sh
vendored
Executable file
11
docker/dev/test_deploy.sh
vendored
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Rebuilding dev docker
|
||||
docker-compose build
|
||||
docker tag dev_lemmy:latest dessalines/lemmy:test
|
||||
docker push dessalines/lemmy:test
|
||||
|
||||
# Run the playbook
|
||||
pushd ../../../lemmy-ansible
|
||||
ansible-playbook -i test playbooks/site.yml --vault-password-file vault_pass
|
||||
popd
|
4
docker/prod/docker-compose.yml
vendored
4
docker/prod/docker-compose.yml
vendored
|
@ -12,14 +12,14 @@ services:
|
|||
restart: always
|
||||
|
||||
lemmy:
|
||||
image: dessalines/lemmy:v0.6.44
|
||||
image: dessalines/lemmy:v0.6.49
|
||||
ports:
|
||||
- "127.0.0.1:8536:8536"
|
||||
restart: always
|
||||
environment:
|
||||
- RUST_LOG=error
|
||||
volumes:
|
||||
- ./lemmy.hjson:/config/config.hjson:ro
|
||||
- ./lemmy.hjson:/config/config.hjson
|
||||
depends_on:
|
||||
- postgres
|
||||
- pictshare
|
||||
|
|
1
docs/src/SUMMARY.md
vendored
1
docs/src/SUMMARY.md
vendored
|
@ -10,6 +10,7 @@
|
|||
- [Install with Ansible](administration_install_ansible.md)
|
||||
- [Install with Kubernetes](administration_install_kubernetes.md)
|
||||
- [Configuration](administration_configuration.md)
|
||||
- [Backup and Restore](administration_backup_and_restore.md)
|
||||
- [Contributing](contributing.md)
|
||||
- [Docker Development](contributing_docker_development.md)
|
||||
- [Local Development](contributing_local_development.md)
|
||||
|
|
2
docs/src/about.md
vendored
2
docs/src/about.md
vendored
|
@ -4,7 +4,7 @@ Front Page|Post
|
|||
---|---
|
||||
![main screen](https://i.imgur.com/kZSRcRu.png)|![chat screen](https://i.imgur.com/4XghNh6.png)
|
||||
|
||||
[Lemmy](https://github.com/dessalines/lemmy) is similar to sites like [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), [Raddle](https://raddle.me), or [Hacker News](https://news.ycombinator.com/): you subscribe to forums you're interested in, post links and discussions, then vote, and comment on them. Behind the scenes, it is very different; anyone can easily run a server, and all these servers are federated (think email), and connected to the same universe, called the [Fediverse](https://en.wikipedia.org/wiki/Fediverse).
|
||||
[Lemmy](https://github.com/LemmyNet/lemmy) is similar to sites like [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), [Raddle](https://raddle.me), or [Hacker News](https://news.ycombinator.com/): you subscribe to forums you're interested in, post links and discussions, then vote, and comment on them. Behind the scenes, it is very different; anyone can easily run a server, and all these servers are federated (think email), and connected to the same universe, called the [Fediverse](https://en.wikipedia.org/wiki/Fediverse).
|
||||
|
||||
For a link aggregator, this means a user registered on one server can subscribe to forums on any other server, and can have discussions with users registered elsewhere.
|
||||
|
||||
|
|
1
docs/src/about_goals.md
vendored
1
docs/src/about_goals.md
vendored
|
@ -51,3 +51,4 @@
|
|||
- [Activitypub implementers guide](https://socialhub.activitypub.rocks/t/draft-guide-for-new-activitypub-implementers/479)
|
||||
- [Data storage questions](https://socialhub.activitypub.rocks/t/data-storage-questions/579/3)
|
||||
- [Activitypub as it has been understood](https://flak.tedunangst.com/post/ActivityPub-as-it-has-been-understood)
|
||||
- [Asonix http signatures in rust](https://git.asonix.dog/Aardwolf/http-signature-normalization)
|
||||
|
|
44
docs/src/administration_backup_and_restore.md
vendored
Normal file
44
docs/src/administration_backup_and_restore.md
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
# Backup and Restore Guide
|
||||
|
||||
## Docker and Ansible
|
||||
|
||||
When using docker or ansible, there should be a `volumes` folder, which contains both the database, and all the pictures. Copy this folder to the new instance to restore your data.
|
||||
|
||||
### Incremental Database backup
|
||||
|
||||
To incrementally backup the DB to an `.sql` file, you can run:
|
||||
|
||||
```bash
|
||||
docker exec -t FOLDERNAME_postgres_1 pg_dumpall -c -U lemmy > lemmy_dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql
|
||||
```
|
||||
### A Sample backup script
|
||||
|
||||
```bash
|
||||
#!/bin/sh
|
||||
# DB Backup
|
||||
ssh MY_USER@MY_IP "docker exec -t FOLDERNAME_postgres_1 pg_dumpall -c -U lemmy" > ~/BACKUP_LOCATION/INSTANCE_NAME_dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql
|
||||
|
||||
# Volumes folder Backup
|
||||
rsync -avP -zz --rsync-path="sudo rsync" MY_USER@MY_IP:/LEMMY_LOCATION/volumes ~/BACKUP_LOCATION/FOLDERNAME
|
||||
```
|
||||
|
||||
### Restoring the DB
|
||||
|
||||
If you need to restore from a `pg_dumpall` file, you need to first clear out your existing database
|
||||
|
||||
```bash
|
||||
# Drop the existing DB
|
||||
docker exec -i FOLDERNAME_postgres_1 psql -U lemmy -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
|
||||
|
||||
# Restore from the .sql backup
|
||||
cat db_dump.sql | docker exec -i FOLDERNAME_postgres_1 psql -U lemmy # restores the db
|
||||
|
||||
# This also might be necessary when doing a db import with a different password.
|
||||
docker exec -i FOLDERNAME_postgres_1 psql -U lemmy -c "alter user lemmy with password 'bleh'"
|
||||
```
|
||||
|
||||
## More resources
|
||||
|
||||
- https://stackoverflow.com/questions/24718706/backup-restore-a-dockerized-postgresql-database
|
||||
|
||||
|
4
docs/src/administration_install_ansible.md
vendored
4
docs/src/administration_install_ansible.md
vendored
|
@ -7,7 +7,7 @@ First, you need to [install Ansible on your local computer](https://docs.ansible
|
|||
Then run the following commands on your local computer:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/dessalines/lemmy.git
|
||||
git clone https://github.com/LemmyNet/lemmy.git
|
||||
cd lemmy/ansible/
|
||||
cp inventory.example inventory
|
||||
nano inventory # enter your server, domain, contact email
|
||||
|
@ -19,4 +19,4 @@ To update to a new version, just run the following in your local Lemmy repo:
|
|||
git pull origin master
|
||||
cd ansible
|
||||
ansible-playbook lemmy.yml --become
|
||||
```
|
||||
```
|
||||
|
|
5
docs/src/contributing.md
vendored
5
docs/src/contributing.md
vendored
|
@ -4,13 +4,14 @@ Information about contributing to Lemmy, whether it is translating, testing, des
|
|||
|
||||
## Issue tracking / Repositories
|
||||
|
||||
- [GitHub (for issues)](https://github.com/dessalines/lemmy)
|
||||
- [GitHub (for issues)](https://github.com/LemmyNet/lemmy)
|
||||
- [Gitea](https://yerbamate.dev/dessalines/lemmy)
|
||||
- [GitLab](https://gitlab.com/dessalines/lemmy)
|
||||
|
||||
## Translating
|
||||
|
||||
Go [here](https://github.com/dessalines/lemmy#translations) for translation instructions.
|
||||
Check out [Lemmy's Weblate](https://weblate.yerbamate.dev/projects/lemmy/) for translations.
|
||||
|
||||
|
||||
## Architecture
|
||||
|
||||
|
|
2
docs/src/contributing_docker_development.md
vendored
2
docs/src/contributing_docker_development.md
vendored
|
@ -3,7 +3,7 @@
|
|||
## Running
|
||||
|
||||
```bash
|
||||
git clone https://github.com/dessalines/lemmy
|
||||
git clone https://github.com/LemmyNet/lemmy
|
||||
cd lemmy/docker/dev
|
||||
./docker_update.sh # This builds and runs it, updating for your changes
|
||||
```
|
||||
|
|
2
docs/src/contributing_local_development.md
vendored
2
docs/src/contributing_local_development.md
vendored
|
@ -22,7 +22,7 @@ export LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy
|
|||
#### Running
|
||||
|
||||
```bash
|
||||
git clone https://github.com/dessalines/lemmy
|
||||
git clone https://github.com/LemmyNet/lemmy
|
||||
cd lemmy
|
||||
./install.sh
|
||||
# For live coding, where both the front and back end, automagically reload on any save, do:
|
||||
|
|
99
docs/src/contributing_websocket_http_api.md
vendored
99
docs/src/contributing_websocket_http_api.md
vendored
|
@ -92,85 +92,93 @@
|
|||
- [Request](#request-17)
|
||||
- [Response](#response-17)
|
||||
- [HTTP](#http-18)
|
||||
* [Community](#community)
|
||||
+ [Get Community](#get-community)
|
||||
+ [Get Site Config](#get-site-config)
|
||||
- [Request](#request-18)
|
||||
- [Response](#response-18)
|
||||
- [HTTP](#http-19)
|
||||
+ [Create Community](#create-community)
|
||||
+ [Save Site Config](#save-site-config)
|
||||
- [Request](#request-19)
|
||||
- [Response](#response-19)
|
||||
- [HTTP](#http-20)
|
||||
+ [List Communities](#list-communities)
|
||||
* [Community](#community)
|
||||
+ [Get Community](#get-community)
|
||||
- [Request](#request-20)
|
||||
- [Response](#response-20)
|
||||
- [HTTP](#http-21)
|
||||
+ [Ban from Community](#ban-from-community)
|
||||
+ [Create Community](#create-community)
|
||||
- [Request](#request-21)
|
||||
- [Response](#response-21)
|
||||
- [HTTP](#http-22)
|
||||
+ [Add Mod to Community](#add-mod-to-community)
|
||||
+ [List Communities](#list-communities)
|
||||
- [Request](#request-22)
|
||||
- [Response](#response-22)
|
||||
- [HTTP](#http-23)
|
||||
+ [Edit Community](#edit-community)
|
||||
+ [Ban from Community](#ban-from-community)
|
||||
- [Request](#request-23)
|
||||
- [Response](#response-23)
|
||||
- [HTTP](#http-24)
|
||||
+ [Follow Community](#follow-community)
|
||||
+ [Add Mod to Community](#add-mod-to-community)
|
||||
- [Request](#request-24)
|
||||
- [Response](#response-24)
|
||||
- [HTTP](#http-25)
|
||||
+ [Get Followed Communities](#get-followed-communities)
|
||||
+ [Edit Community](#edit-community)
|
||||
- [Request](#request-25)
|
||||
- [Response](#response-25)
|
||||
- [HTTP](#http-26)
|
||||
+ [Transfer Community](#transfer-community)
|
||||
+ [Follow Community](#follow-community)
|
||||
- [Request](#request-26)
|
||||
- [Response](#response-26)
|
||||
- [HTTP](#http-27)
|
||||
* [Post](#post)
|
||||
+ [Create Post](#create-post)
|
||||
+ [Get Followed Communities](#get-followed-communities)
|
||||
- [Request](#request-27)
|
||||
- [Response](#response-27)
|
||||
- [HTTP](#http-28)
|
||||
+ [Get Post](#get-post)
|
||||
+ [Transfer Community](#transfer-community)
|
||||
- [Request](#request-28)
|
||||
- [Response](#response-28)
|
||||
- [HTTP](#http-29)
|
||||
+ [Get Posts](#get-posts)
|
||||
* [Post](#post)
|
||||
+ [Create Post](#create-post)
|
||||
- [Request](#request-29)
|
||||
- [Response](#response-29)
|
||||
- [HTTP](#http-30)
|
||||
+ [Create Post Like](#create-post-like)
|
||||
+ [Get Post](#get-post)
|
||||
- [Request](#request-30)
|
||||
- [Response](#response-30)
|
||||
- [HTTP](#http-31)
|
||||
+ [Edit Post](#edit-post)
|
||||
+ [Get Posts](#get-posts)
|
||||
- [Request](#request-31)
|
||||
- [Response](#response-31)
|
||||
- [HTTP](#http-32)
|
||||
+ [Save Post](#save-post)
|
||||
+ [Create Post Like](#create-post-like)
|
||||
- [Request](#request-32)
|
||||
- [Response](#response-32)
|
||||
- [HTTP](#http-33)
|
||||
* [Comment](#comment)
|
||||
+ [Create Comment](#create-comment)
|
||||
+ [Edit Post](#edit-post)
|
||||
- [Request](#request-33)
|
||||
- [Response](#response-33)
|
||||
- [HTTP](#http-34)
|
||||
+ [Edit Comment](#edit-comment)
|
||||
+ [Save Post](#save-post)
|
||||
- [Request](#request-34)
|
||||
- [Response](#response-34)
|
||||
- [HTTP](#http-35)
|
||||
+ [Save Comment](#save-comment)
|
||||
* [Comment](#comment)
|
||||
+ [Create Comment](#create-comment)
|
||||
- [Request](#request-35)
|
||||
- [Response](#response-35)
|
||||
- [HTTP](#http-36)
|
||||
+ [Create Comment Like](#create-comment-like)
|
||||
+ [Edit Comment](#edit-comment)
|
||||
- [Request](#request-36)
|
||||
- [Response](#response-36)
|
||||
- [HTTP](#http-37)
|
||||
+ [Save Comment](#save-comment)
|
||||
- [Request](#request-37)
|
||||
- [Response](#response-37)
|
||||
- [HTTP](#http-38)
|
||||
+ [Create Comment Like](#create-comment-like)
|
||||
- [Request](#request-38)
|
||||
- [Response](#response-38)
|
||||
- [HTTP](#http-39)
|
||||
* [RSS / Atom feeds](#rss--atom-feeds)
|
||||
+ [All](#all)
|
||||
+ [Community](#community-1)
|
||||
|
@ -779,6 +787,53 @@ Search types are `All, Comments, Posts, Communities, Users, Url`
|
|||
|
||||
`POST /site/transfer`
|
||||
|
||||
#### Get Site Config
|
||||
##### Request
|
||||
```rust
|
||||
{
|
||||
op: "GetSiteConfig",
|
||||
data: {
|
||||
auth: String
|
||||
}
|
||||
}
|
||||
```
|
||||
##### Response
|
||||
```rust
|
||||
{
|
||||
op: "GetSiteConfig",
|
||||
data: {
|
||||
config_hjson: String,
|
||||
}
|
||||
}
|
||||
```
|
||||
##### HTTP
|
||||
|
||||
`GET /site/config`
|
||||
|
||||
#### Save Site Config
|
||||
##### Request
|
||||
```rust
|
||||
{
|
||||
op: "SaveSiteConfig",
|
||||
data: {
|
||||
config_hjson: String,
|
||||
auth: String
|
||||
}
|
||||
}
|
||||
```
|
||||
##### Response
|
||||
```rust
|
||||
{
|
||||
op: "SaveSiteConfig",
|
||||
data: {
|
||||
config_hjson: String,
|
||||
}
|
||||
}
|
||||
```
|
||||
##### HTTP
|
||||
|
||||
`PUT /site/config`
|
||||
|
||||
### Community
|
||||
#### Get Community
|
||||
##### Request
|
||||
|
|
|
@ -97,6 +97,22 @@ pub struct TransferSite {
|
|||
auth: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct GetSiteConfig {
|
||||
auth: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct GetSiteConfigResponse {
|
||||
config_hjson: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SaveSiteConfig {
|
||||
config_hjson: String,
|
||||
auth: String,
|
||||
}
|
||||
|
||||
impl Perform<ListCategoriesResponse> for Oper<ListCategories> {
|
||||
fn perform(&self, conn: &PgConnection) -> Result<ListCategoriesResponse, Error> {
|
||||
let _data: &ListCategories = &self.data;
|
||||
|
@ -514,3 +530,57 @@ impl Perform<GetSiteResponse> for Oper<TransferSite> {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Perform<GetSiteConfigResponse> for Oper<GetSiteConfig> {
|
||||
fn perform(&self, conn: &PgConnection) -> Result<GetSiteConfigResponse, Error> {
|
||||
let data: &GetSiteConfig = &self.data;
|
||||
|
||||
let claims = match Claims::decode(&data.auth) {
|
||||
Ok(claims) => claims.claims,
|
||||
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||
};
|
||||
|
||||
let user_id = claims.id;
|
||||
|
||||
// Only let admins read this
|
||||
let admins = UserView::admins(&conn)?;
|
||||
let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect();
|
||||
|
||||
if !admin_ids.contains(&user_id) {
|
||||
return Err(APIError::err("not_an_admin").into());
|
||||
}
|
||||
|
||||
let config_hjson = Settings::read_config_file()?;
|
||||
|
||||
Ok(GetSiteConfigResponse { config_hjson })
|
||||
}
|
||||
}
|
||||
|
||||
impl Perform<GetSiteConfigResponse> for Oper<SaveSiteConfig> {
|
||||
fn perform(&self, conn: &PgConnection) -> Result<GetSiteConfigResponse, Error> {
|
||||
let data: &SaveSiteConfig = &self.data;
|
||||
|
||||
let claims = match Claims::decode(&data.auth) {
|
||||
Ok(claims) => claims.claims,
|
||||
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||
};
|
||||
|
||||
let user_id = claims.id;
|
||||
|
||||
// Only let admins read this
|
||||
let admins = UserView::admins(&conn)?;
|
||||
let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect();
|
||||
|
||||
if !admin_ids.contains(&user_id) {
|
||||
return Err(APIError::err("not_an_admin").into());
|
||||
}
|
||||
|
||||
// Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
|
||||
let config_hjson = match Settings::save_config_file(&data.config_hjson) {
|
||||
Ok(config_hjson) => config_hjson,
|
||||
Err(_e) => return Err(APIError::err("couldnt_update_site").into()),
|
||||
};
|
||||
|
||||
Ok(GetSiteConfigResponse { config_hjson })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@ pub fn send_email(
|
|||
to_username: &str,
|
||||
html: &str,
|
||||
) -> Result<(), String> {
|
||||
let email_config = Settings::get().email.as_ref().ok_or("no_email_setup")?;
|
||||
let email_config = Settings::get().email.ok_or("no_email_setup")?;
|
||||
|
||||
let email = Email::builder()
|
||||
.to((to_email, to_username))
|
||||
|
@ -130,7 +130,7 @@ pub fn send_email(
|
|||
} else {
|
||||
SmtpClient::new(&email_config.smtp_server, ClientSecurity::None).unwrap()
|
||||
}
|
||||
.hello_name(ClientId::Domain(Settings::get().hostname.to_owned()))
|
||||
.hello_name(ClientId::Domain(Settings::get().hostname))
|
||||
.smtp_utf8(true)
|
||||
.authentication_mechanism(Mechanism::Plain)
|
||||
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited);
|
||||
|
|
|
@ -57,6 +57,7 @@ async fn main() -> Result<(), Error> {
|
|||
// Create Http server with websocket support
|
||||
Ok(
|
||||
HttpServer::new(move || {
|
||||
let settings = Settings::get();
|
||||
App::new()
|
||||
.wrap(middleware::Logger::default())
|
||||
.data(pool.clone())
|
||||
|
|
|
@ -52,6 +52,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||
.route("/api/v1/site", web::post().to(route_post::<CreateSite, SiteResponse>))
|
||||
.route("/api/v1/site", web::put().to(route_post::<EditSite, SiteResponse>))
|
||||
.route("/api/v1/site/transfer", web::post().to(route_post::<TransferSite, GetSiteResponse>))
|
||||
.route("/api/v1/site/config", web::get().to(route_get::<GetSiteConfig, GetSiteConfigResponse>))
|
||||
.route("/api/v1/site/config", web::put().to(route_post::<SaveSiteConfig, GetSiteConfigResponse>))
|
||||
.route("/api/v1/admin/add", web::post().to(route_post::<AddAdmin, AddAdminResponse>))
|
||||
.route("/api/v1/user/ban", web::post().to(route_post::<BanUser, BanUserResponse>))
|
||||
// User account actions
|
||||
|
|
|
@ -33,6 +33,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||
.route("/modlog/community/{community_id}", web::get().to(index))
|
||||
.route("/modlog", web::get().to(index))
|
||||
.route("/setup", web::get().to(index))
|
||||
.route("/admin", web::get().to(index))
|
||||
.route(
|
||||
"/search/q/{q}/type/{type}/sort/{sort}/page/{page}",
|
||||
web::get().to(index),
|
||||
|
@ -44,6 +45,6 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||
|
||||
async fn index() -> Result<NamedFile, actix_web::error::Error> {
|
||||
Ok(NamedFile::open(
|
||||
Settings::get().front_end_dir.to_owned() + "/index.html",
|
||||
Settings::get().front_end_dir + "/index.html",
|
||||
)?)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
use config::{Config, ConfigError, Environment, File};
|
||||
use failure::Error;
|
||||
use serde::Deserialize;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::RwLock;
|
||||
|
||||
static CONFIG_FILE_DEFAULTS: &str = "config/defaults.hjson";
|
||||
static CONFIG_FILE: &str = "config/config.hjson";
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Settings {
|
||||
pub setup: Option<Setup>,
|
||||
pub database: Database,
|
||||
|
@ -20,7 +23,7 @@ pub struct Settings {
|
|||
pub federation: Federation,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Setup {
|
||||
pub admin_username: String,
|
||||
pub admin_password: String,
|
||||
|
@ -28,7 +31,7 @@ pub struct Setup {
|
|||
pub site_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct RateLimitConfig {
|
||||
pub message: i32,
|
||||
pub message_per_second: i32,
|
||||
|
@ -38,7 +41,7 @@ pub struct RateLimitConfig {
|
|||
pub register_per_second: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct EmailConfig {
|
||||
pub smtp_server: String,
|
||||
pub smtp_login: Option<String>,
|
||||
|
@ -47,7 +50,7 @@ pub struct EmailConfig {
|
|||
pub use_tls: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Database {
|
||||
pub user: String,
|
||||
pub password: String,
|
||||
|
@ -65,12 +68,10 @@ pub struct Federation {
|
|||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref SETTINGS: Settings = {
|
||||
match Settings::init() {
|
||||
Ok(c) => c,
|
||||
Err(e) => panic!("{}", e),
|
||||
}
|
||||
};
|
||||
static ref SETTINGS: RwLock<Settings> = RwLock::new(match Settings::init() {
|
||||
Ok(c) => c,
|
||||
Err(e) => panic!("{}", e),
|
||||
});
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
|
@ -96,8 +97,8 @@ impl Settings {
|
|||
}
|
||||
|
||||
/// Returns the config as a struct.
|
||||
pub fn get() -> &'static Self {
|
||||
&SETTINGS
|
||||
pub fn get() -> Self {
|
||||
SETTINGS.read().unwrap().to_owned()
|
||||
}
|
||||
|
||||
/// Returns the postgres connection url. If LEMMY_DATABASE_URL is set, that is used,
|
||||
|
@ -119,4 +120,22 @@ impl Settings {
|
|||
pub fn api_endpoint(&self) -> String {
|
||||
format!("{}/api/v1", self.hostname)
|
||||
}
|
||||
|
||||
pub fn read_config_file() -> Result<String, Error> {
|
||||
Ok(fs::read_to_string(CONFIG_FILE)?)
|
||||
}
|
||||
|
||||
pub fn save_config_file(data: &str) -> Result<String, Error> {
|
||||
fs::write(CONFIG_FILE, data)?;
|
||||
|
||||
// Reload the new settings
|
||||
// From https://stackoverflow.com/questions/29654927/how-do-i-assign-a-string-to-a-mutable-static-variable/47181804#47181804
|
||||
let mut new_settings = SETTINGS.write().unwrap();
|
||||
*new_settings = match Settings::init() {
|
||||
Ok(c) => c,
|
||||
Err(e) => panic!("{}", e),
|
||||
};
|
||||
|
||||
Self::read_config_file()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
pub const VERSION: &str = "v0.6.44";
|
||||
pub const VERSION: &str = "v0.6.49";
|
||||
|
|
|
@ -46,4 +46,6 @@ pub enum UserOperation {
|
|||
GetPrivateMessages,
|
||||
UserJoin,
|
||||
GetComments,
|
||||
GetSiteConfig,
|
||||
SaveSiteConfig,
|
||||
}
|
||||
|
|
|
@ -708,6 +708,16 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
|
|||
res.online = chat.sessions.len();
|
||||
to_json_string(&user_operation, &res)
|
||||
}
|
||||
UserOperation::GetSiteConfig => {
|
||||
let get_site_config: GetSiteConfig = serde_json::from_str(data)?;
|
||||
let res = Oper::new(get_site_config).perform(&conn)?;
|
||||
to_json_string(&user_operation, &res)
|
||||
}
|
||||
UserOperation::SaveSiteConfig => {
|
||||
let save_site_config: SaveSiteConfig = serde_json::from_str(data)?;
|
||||
let res = Oper::new(save_site_config).perform(&conn)?;
|
||||
to_json_string(&user_operation, &res)
|
||||
}
|
||||
UserOperation::Search => {
|
||||
do_user_operation::<Search, SearchResponse>(user_operation, data, &conn)
|
||||
}
|
||||
|
|
2
ui/assets/css/main.css
vendored
2
ui/assets/css/main.css
vendored
|
@ -156,7 +156,7 @@ hr {
|
|||
}
|
||||
|
||||
.emoji {
|
||||
height: 1.2em !important;
|
||||
max-height: 1.2em !important;
|
||||
}
|
||||
|
||||
.text-wrap-truncate {
|
||||
|
|
33
ui/package.json
vendored
33
ui/package.json
vendored
|
@ -14,19 +14,20 @@
|
|||
},
|
||||
"keywords": [],
|
||||
"dependencies": {
|
||||
"@joeattardi/emoji-button": "^2.12.1",
|
||||
"@types/autosize": "^3.0.6",
|
||||
"@types/js-cookie": "^2.2.5",
|
||||
"@types/js-cookie": "^2.2.6",
|
||||
"@types/jwt-decode": "^2.2.1",
|
||||
"@types/markdown-it": "^0.0.9",
|
||||
"@types/markdown-it-container": "^2.0.2",
|
||||
"@types/node": "^13.9.2",
|
||||
"@types/node": "^13.11.1",
|
||||
"autosize": "^4.0.2",
|
||||
"bootswatch": "^4.3.1",
|
||||
"classcat": "^1.1.3",
|
||||
"classcat": "^4.0.2",
|
||||
"dotenv": "^8.2.0",
|
||||
"emoji-short-name": "^1.0.0",
|
||||
"husky": "^4.2.3",
|
||||
"i18next": "^19.3.3",
|
||||
"husky": "^4.2.5",
|
||||
"i18next": "^19.4.1",
|
||||
"inferno": "^7.4.2",
|
||||
"inferno-i18next": "nimbusec-oss/inferno-i18next",
|
||||
"inferno-router": "^7.4.2",
|
||||
|
@ -37,26 +38,26 @@
|
|||
"markdown-it-emoji": "^1.4.0",
|
||||
"mobius1-selectr": "^2.4.13",
|
||||
"moment": "^2.24.0",
|
||||
"prettier": "^1.18.2",
|
||||
"prettier": "^2.0.4",
|
||||
"reconnecting-websocket": "^4.4.0",
|
||||
"rxjs": "^6.4.0",
|
||||
"terser": "^4.6.7",
|
||||
"tippy.js": "^6.1.0",
|
||||
"rxjs": "^6.5.5",
|
||||
"terser": "^4.6.11",
|
||||
"tippy.js": "^6.1.1",
|
||||
"toastify-js": "^1.7.0",
|
||||
"tributejs": "^5.1.2",
|
||||
"tributejs": "^5.1.3",
|
||||
"twemoji": "^12.1.2",
|
||||
"ws": "^7.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^6.5.1",
|
||||
"eslint-plugin-inferno": "^7.14.3",
|
||||
"eslint-plugin-jane": "^7.2.0",
|
||||
"eslint-plugin-jane": "^7.2.1",
|
||||
"fuse-box": "^3.1.3",
|
||||
"lint-staged": "^10.0.8",
|
||||
"sortpack": "^2.1.2",
|
||||
"ts-node": "^8.7.0",
|
||||
"ts-transform-classcat": "^0.0.2",
|
||||
"ts-transform-inferno": "^4.0.2",
|
||||
"lint-staged": "^10.1.3",
|
||||
"sortpack": "^2.1.4",
|
||||
"ts-node": "^8.8.2",
|
||||
"ts-transform-classcat": "^1.0.0",
|
||||
"ts-transform-inferno": "^4.0.3",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"engines": {
|
||||
|
|
241
ui/src/components/admin-settings.tsx
vendored
Normal file
241
ui/src/components/admin-settings.tsx
vendored
Normal file
|
@ -0,0 +1,241 @@
|
|||
import { Component, linkEvent } from 'inferno';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||
import {
|
||||
UserOperation,
|
||||
SiteResponse,
|
||||
GetSiteResponse,
|
||||
SiteConfigForm,
|
||||
GetSiteConfigResponse,
|
||||
WebSocketJsonResponse,
|
||||
} from '../interfaces';
|
||||
import { WebSocketService } from '../services';
|
||||
import { wsJsonToRes, capitalizeFirstLetter, toast, randomStr } from '../utils';
|
||||
import autosize from 'autosize';
|
||||
import { SiteForm } from './site-form';
|
||||
import { UserListing } from './user-listing';
|
||||
import { i18n } from '../i18next';
|
||||
|
||||
interface AdminSettingsState {
|
||||
siteRes: GetSiteResponse;
|
||||
siteConfigRes: GetSiteConfigResponse;
|
||||
siteConfigForm: SiteConfigForm;
|
||||
loading: boolean;
|
||||
siteConfigLoading: boolean;
|
||||
}
|
||||
|
||||
export class AdminSettings extends Component<any, AdminSettingsState> {
|
||||
private siteConfigTextAreaId = `site-config-${randomStr()}`;
|
||||
private subscription: Subscription;
|
||||
private emptyState: AdminSettingsState = {
|
||||
siteRes: {
|
||||
site: {
|
||||
id: null,
|
||||
name: null,
|
||||
creator_id: null,
|
||||
creator_name: null,
|
||||
published: null,
|
||||
number_of_users: null,
|
||||
number_of_posts: null,
|
||||
number_of_comments: null,
|
||||
number_of_communities: null,
|
||||
enable_downvotes: null,
|
||||
open_registration: null,
|
||||
enable_nsfw: null,
|
||||
},
|
||||
admins: [],
|
||||
banned: [],
|
||||
online: null,
|
||||
},
|
||||
siteConfigForm: {
|
||||
config_hjson: null,
|
||||
auth: null,
|
||||
},
|
||||
siteConfigRes: {
|
||||
config_hjson: null,
|
||||
},
|
||||
loading: true,
|
||||
siteConfigLoading: null,
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
|
||||
this.state = this.emptyState;
|
||||
|
||||
this.subscription = WebSocketService.Instance.subject
|
||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||
.subscribe(
|
||||
msg => this.parseMessage(msg),
|
||||
err => console.error(err),
|
||||
() => console.log('complete')
|
||||
);
|
||||
|
||||
WebSocketService.Instance.getSite();
|
||||
WebSocketService.Instance.getSiteConfig();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="container">
|
||||
{this.state.loading ? (
|
||||
<h5>
|
||||
<svg class="icon icon-spinner spin">
|
||||
<use xlinkHref="#icon-spinner"></use>
|
||||
</svg>
|
||||
</h5>
|
||||
) : (
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<SiteForm site={this.state.siteRes.site} />
|
||||
{this.admins()}
|
||||
{this.bannedUsers()}
|
||||
</div>
|
||||
<div class="col-12 col-md-6">{this.adminSettings()}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
admins() {
|
||||
return (
|
||||
<>
|
||||
<h5>{capitalizeFirstLetter(i18n.t('admins'))}</h5>
|
||||
<ul class="list-unstyled">
|
||||
{this.state.siteRes.admins.map(admin => (
|
||||
<li class="list-inline-item">
|
||||
<UserListing
|
||||
user={{
|
||||
name: admin.name,
|
||||
avatar: admin.avatar,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
bannedUsers() {
|
||||
return (
|
||||
<>
|
||||
<h5>{i18n.t('banned_users')}</h5>
|
||||
<ul class="list-unstyled">
|
||||
{this.state.siteRes.banned.map(banned => (
|
||||
<li class="list-inline-item">
|
||||
<UserListing
|
||||
user={{
|
||||
name: banned.name,
|
||||
avatar: banned.avatar,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
adminSettings() {
|
||||
return (
|
||||
<div>
|
||||
<h5>{i18n.t('admin_settings')}</h5>
|
||||
<form onSubmit={linkEvent(this, this.handleSiteConfigSubmit)}>
|
||||
<div class="form-group row">
|
||||
<label
|
||||
class="col-12 col-form-label"
|
||||
htmlFor={this.siteConfigTextAreaId}
|
||||
>
|
||||
{i18n.t('site_config')}
|
||||
</label>
|
||||
<div class="col-12">
|
||||
<textarea
|
||||
id={this.siteConfigTextAreaId}
|
||||
value={this.state.siteConfigForm.config_hjson}
|
||||
onInput={linkEvent(this, this.handleSiteConfigHjsonChange)}
|
||||
class="form-control text-monospace"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-secondary mr-2">
|
||||
{this.state.siteConfigLoading ? (
|
||||
<svg class="icon icon-spinner spin">
|
||||
<use xlinkHref="#icon-spinner"></use>
|
||||
</svg>
|
||||
) : (
|
||||
capitalizeFirstLetter(i18n.t('save'))
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleSiteConfigSubmit(i: AdminSettings, event: any) {
|
||||
event.preventDefault();
|
||||
i.state.siteConfigLoading = true;
|
||||
WebSocketService.Instance.saveSiteConfig(i.state.siteConfigForm);
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleSiteConfigHjsonChange(i: AdminSettings, event: any) {
|
||||
i.state.siteConfigForm.config_hjson = event.target.value;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
parseMessage(msg: WebSocketJsonResponse) {
|
||||
console.log(msg);
|
||||
let res = wsJsonToRes(msg);
|
||||
if (msg.error) {
|
||||
toast(i18n.t(msg.error), 'danger');
|
||||
this.context.router.history.push('/');
|
||||
this.state.loading = false;
|
||||
this.setState(this.state);
|
||||
return;
|
||||
} else if (msg.reconnect) {
|
||||
} else if (res.op == UserOperation.GetSite) {
|
||||
let data = res.data as GetSiteResponse;
|
||||
|
||||
// This means it hasn't been set up yet
|
||||
if (!data.site) {
|
||||
this.context.router.history.push('/setup');
|
||||
}
|
||||
this.state.siteRes = data;
|
||||
this.setState(this.state);
|
||||
document.title = `${i18n.t('admin_settings')} - ${
|
||||
this.state.siteRes.site.name
|
||||
}`;
|
||||
} else if (res.op == UserOperation.EditSite) {
|
||||
let data = res.data as SiteResponse;
|
||||
this.state.siteRes.site = data.site;
|
||||
this.setState(this.state);
|
||||
toast(i18n.t('site_saved'));
|
||||
} else if (res.op == UserOperation.GetSiteConfig) {
|
||||
let data = res.data as GetSiteConfigResponse;
|
||||
this.state.siteConfigRes = data;
|
||||
this.state.loading = false;
|
||||
this.state.siteConfigForm.config_hjson = this.state.siteConfigRes.config_hjson;
|
||||
this.setState(this.state);
|
||||
var textarea: any = document.getElementById(this.siteConfigTextAreaId);
|
||||
autosize(textarea);
|
||||
} else if (res.op == UserOperation.SaveSiteConfig) {
|
||||
let data = res.data as GetSiteConfigResponse;
|
||||
this.state.siteConfigRes = data;
|
||||
this.state.siteConfigForm.config_hjson = this.state.siteConfigRes.config_hjson;
|
||||
this.state.siteConfigLoading = false;
|
||||
toast(i18n.t('site_saved'));
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
}
|
36
ui/src/components/comment-form.tsx
vendored
36
ui/src/components/comment-form.tsx
vendored
|
@ -17,10 +17,12 @@ import {
|
|||
toast,
|
||||
setupTribute,
|
||||
wsJsonToRes,
|
||||
emojiPicker,
|
||||
} from '../utils';
|
||||
import { WebSocketService, UserService } from '../services';
|
||||
import autosize from 'autosize';
|
||||
import Tribute from 'tributejs/src/Tribute.js';
|
||||
import emojiShortName from 'emoji-short-name';
|
||||
import { i18n } from '../i18next';
|
||||
|
||||
interface CommentFormProps {
|
||||
|
@ -69,6 +71,8 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
|||
super(props, context);
|
||||
|
||||
this.tribute = setupTribute();
|
||||
this.setupEmojiPicker();
|
||||
|
||||
this.state = this.emptyState;
|
||||
|
||||
if (this.props.node) {
|
||||
|
@ -158,8 +162,9 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
|||
</button>
|
||||
{this.state.commentForm.content && (
|
||||
<button
|
||||
className={`btn btn-sm mr-2 btn-secondary ${this.state
|
||||
.previewMode && 'active'}`}
|
||||
className={`btn btn-sm mr-2 btn-secondary ${
|
||||
this.state.previewMode && 'active'
|
||||
}`}
|
||||
onClick={linkEvent(this, this.handlePreviewToggle)}
|
||||
>
|
||||
{i18n.t('preview')}
|
||||
|
@ -209,6 +214,15 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
|||
<use xlinkHref="#icon-spinner"></use>
|
||||
</svg>
|
||||
)}
|
||||
<span
|
||||
onClick={linkEvent(this, this.handleEmojiPickerClick)}
|
||||
class="pointer unselectable d-inline-block mr-3 float-right text-muted font-weight-bold"
|
||||
data-tippy-content={i18n.t('emoji_picker')}
|
||||
>
|
||||
<svg class="icon icon-inline">
|
||||
<use xlinkHref="#icon-smile"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -216,6 +230,20 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
|||
);
|
||||
}
|
||||
|
||||
setupEmojiPicker() {
|
||||
emojiPicker.on('emoji', twemojiHtmlStr => {
|
||||
if (this.state.commentForm.content == null) {
|
||||
this.state.commentForm.content = '';
|
||||
}
|
||||
var el = document.createElement('div');
|
||||
el.innerHTML = twemojiHtmlStr;
|
||||
let nativeUnicode = (el.childNodes[0] as HTMLElement).getAttribute('alt');
|
||||
let shortName = `:${emojiShortName[nativeUnicode]}:`;
|
||||
this.state.commentForm.content += shortName;
|
||||
this.setState(this.state);
|
||||
});
|
||||
}
|
||||
|
||||
handleFinished() {
|
||||
this.state.previewMode = false;
|
||||
this.state.loading = false;
|
||||
|
@ -242,6 +270,10 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
|||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleEmojiPickerClick(_i: CommentForm, event: any) {
|
||||
emojiPicker.togglePicker(event.target);
|
||||
}
|
||||
|
||||
handleCommentContentChange(i: CommentForm, event: any) {
|
||||
i.state.commentForm.content = event.target.value;
|
||||
i.setState(i.state);
|
||||
|
|
53
ui/src/components/comment-node.tsx
vendored
53
ui/src/components/comment-node.tsx
vendored
|
@ -24,8 +24,6 @@ import {
|
|||
getUnixTime,
|
||||
canMod,
|
||||
isMod,
|
||||
pictshareAvatarThumbnail,
|
||||
showAvatars,
|
||||
setupTippy,
|
||||
colorList,
|
||||
} from '../utils';
|
||||
|
@ -33,6 +31,7 @@ import moment from 'moment';
|
|||
import { MomentTime } from './moment-time';
|
||||
import { CommentForm } from './comment-form';
|
||||
import { CommentNodes } from './comment-nodes';
|
||||
import { UserListing } from './user-listing';
|
||||
import { i18n } from '../i18next';
|
||||
|
||||
interface CommentNodeState {
|
||||
|
@ -143,25 +142,21 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
}
|
||||
>
|
||||
<div
|
||||
class={`${!this.props.noIndent &&
|
||||
class={`${
|
||||
!this.props.noIndent &&
|
||||
this.props.node.comment.parent_id &&
|
||||
'ml-2'}`}
|
||||
'ml-2'
|
||||
}`}
|
||||
>
|
||||
<div class="d-flex flex-wrap align-items-center mb-1 mt-1 text-muted small">
|
||||
<Link
|
||||
className="mr-2 text-body font-weight-bold"
|
||||
to={`/u/${node.comment.creator_name}`}
|
||||
>
|
||||
{node.comment.creator_avatar && showAvatars() && (
|
||||
<img
|
||||
height="32"
|
||||
width="32"
|
||||
src={pictshareAvatarThumbnail(node.comment.creator_avatar)}
|
||||
class="rounded-circle mr-1"
|
||||
/>
|
||||
)}
|
||||
<span>{node.comment.creator_name}</span>
|
||||
</Link>
|
||||
<span class="mr-2">
|
||||
<UserListing
|
||||
user={{
|
||||
name: node.comment.creator_name,
|
||||
avatar: node.comment.creator_avatar,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
{this.isMod && (
|
||||
<div className="badge badge-light d-none d-sm-inline mr-2">
|
||||
{i18n.t('mod')}
|
||||
|
@ -191,7 +186,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
</>
|
||||
)}
|
||||
<div
|
||||
className="mr-lg-4 flex-grow-1 flex-lg-grow-0 unselectable pointer mr-2"
|
||||
className="mr-lg-4 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"
|
||||
onClick={linkEvent(this, this.handleCommentCollapse)}
|
||||
>
|
||||
{this.state.collapsed ? (
|
||||
|
@ -256,8 +251,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
this.loadingIcon
|
||||
) : (
|
||||
<svg
|
||||
class={`icon icon-inline ${node.comment.read &&
|
||||
'text-success'}`}
|
||||
class={`icon icon-inline ${
|
||||
node.comment.read && 'text-success'
|
||||
}`}
|
||||
>
|
||||
<use xlinkHref="#icon-check"></use>
|
||||
</svg>
|
||||
|
@ -309,8 +305,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
this.loadingIcon
|
||||
) : (
|
||||
<svg
|
||||
class={`icon icon-inline ${node.comment.saved &&
|
||||
'text-warning'}`}
|
||||
class={`icon icon-inline ${
|
||||
node.comment.saved && 'text-warning'
|
||||
}`}
|
||||
>
|
||||
<use xlinkHref="#icon-star"></use>
|
||||
</svg>
|
||||
|
@ -357,8 +354,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
data-tippy-content={i18n.t('view_source')}
|
||||
>
|
||||
<svg
|
||||
class={`icon icon-inline ${this.state
|
||||
.viewSource && 'text-success'}`}
|
||||
class={`icon icon-inline ${
|
||||
this.state.viewSource && 'text-success'
|
||||
}`}
|
||||
>
|
||||
<use xlinkHref="#icon-file-text"></use>
|
||||
</svg>
|
||||
|
@ -387,8 +385,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
}
|
||||
>
|
||||
<svg
|
||||
class={`icon icon-inline ${node.comment
|
||||
.deleted && 'text-danger'}`}
|
||||
class={`icon icon-inline ${
|
||||
node.comment.deleted && 'text-danger'
|
||||
}`}
|
||||
>
|
||||
<use xlinkHref="#icon-trash"></use>
|
||||
</svg>
|
||||
|
|
24
ui/src/components/main.tsx
vendored
24
ui/src/components/main.tsx
vendored
|
@ -33,13 +33,12 @@ import { SortSelect } from './sort-select';
|
|||
import { ListingTypeSelect } from './listing-type-select';
|
||||
import { DataTypeSelect } from './data-type-select';
|
||||
import { SiteForm } from './site-form';
|
||||
import { UserListing } from './user-listing';
|
||||
import {
|
||||
wsJsonToRes,
|
||||
repoUrl,
|
||||
mdToHtml,
|
||||
fetchLimit,
|
||||
pictshareAvatarThumbnail,
|
||||
showAvatars,
|
||||
toast,
|
||||
getListingTypeFromProps,
|
||||
getPageFromProps,
|
||||
|
@ -316,20 +315,12 @@ export class Main extends Component<any, MainState> {
|
|||
<li class="list-inline-item">{i18n.t('admins')}:</li>
|
||||
{this.state.siteRes.admins.map(admin => (
|
||||
<li class="list-inline-item">
|
||||
<Link
|
||||
class="text-body font-weight-bold"
|
||||
to={`/u/${admin.name}`}
|
||||
>
|
||||
{admin.avatar && showAvatars() && (
|
||||
<img
|
||||
height="32"
|
||||
width="32"
|
||||
src={pictshareAvatarThumbnail(admin.avatar)}
|
||||
class="rounded-circle mr-1"
|
||||
/>
|
||||
)}
|
||||
<span>{admin.name}</span>
|
||||
</Link>
|
||||
<UserListing
|
||||
user={{
|
||||
name: admin.name,
|
||||
avatar: admin.avatar,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
@ -619,6 +610,7 @@ export class Main extends Component<any, MainState> {
|
|||
this.state.siteRes.site = data.site;
|
||||
this.state.showEditSite = false;
|
||||
this.setState(this.state);
|
||||
toast(i18n.t('site_saved'));
|
||||
} else if (res.op == UserOperation.GetPosts) {
|
||||
let data = res.data as GetPostsResponse;
|
||||
this.state.posts = data.posts;
|
||||
|
|
28
ui/src/components/navbar.tsx
vendored
28
ui/src/components/navbar.tsx
vendored
|
@ -16,6 +16,7 @@ import {
|
|||
Comment,
|
||||
CommentResponse,
|
||||
PrivateMessage,
|
||||
UserView,
|
||||
PrivateMessageResponse,
|
||||
WebSocketJsonResponse,
|
||||
} from '../interfaces';
|
||||
|
@ -40,6 +41,7 @@ interface NavbarState {
|
|||
messages: Array<PrivateMessage>;
|
||||
unreadCount: number;
|
||||
siteName: string;
|
||||
admins: Array<UserView>;
|
||||
}
|
||||
|
||||
export class Navbar extends Component<any, NavbarState> {
|
||||
|
@ -53,6 +55,7 @@ export class Navbar extends Component<any, NavbarState> {
|
|||
messages: [],
|
||||
expanded: false,
|
||||
siteName: undefined,
|
||||
admins: [],
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
|
@ -179,6 +182,19 @@ export class Navbar extends Component<any, NavbarState> {
|
|||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav ml-auto">
|
||||
{this.canAdmin && (
|
||||
<li className="nav-item mt-1">
|
||||
<Link
|
||||
class="nav-link"
|
||||
to={`/admin`}
|
||||
title={i18n.t('admin_settings')}
|
||||
>
|
||||
<svg class="icon">
|
||||
<use xlinkHref="#icon-settings"></use>
|
||||
</svg>
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{this.state.isLoggedIn ? (
|
||||
<>
|
||||
<li className="nav-item mt-1">
|
||||
|
@ -298,7 +314,10 @@ export class Navbar extends Component<any, NavbarState> {
|
|||
|
||||
if (data.site && !this.state.siteName) {
|
||||
this.state.siteName = data.site.name;
|
||||
this.state.admins = data.admins;
|
||||
WebSocketService.Instance.site = data.site;
|
||||
WebSocketService.Instance.admins = data.admins;
|
||||
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
|
@ -353,9 +372,16 @@ export class Navbar extends Component<any, NavbarState> {
|
|||
);
|
||||
}
|
||||
|
||||
get canAdmin(): boolean {
|
||||
return (
|
||||
UserService.Instance.user &&
|
||||
this.state.admins.map(a => a.id).includes(UserService.Instance.user.id)
|
||||
);
|
||||
}
|
||||
|
||||
requestNotificationPermission() {
|
||||
if (UserService.Instance.user) {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
if (!Notification) {
|
||||
toast(i18n.t('notifications_error'), 'danger');
|
||||
return;
|
||||
|
|
43
ui/src/components/post-form.tsx
vendored
43
ui/src/components/post-form.tsx
vendored
|
@ -34,9 +34,11 @@ import {
|
|||
randomStr,
|
||||
setupTribute,
|
||||
setupTippy,
|
||||
emojiPicker,
|
||||
} from '../utils';
|
||||
import autosize from 'autosize';
|
||||
import Tribute from 'tributejs/src/Tribute.js';
|
||||
import emojiShortName from 'emoji-short-name';
|
||||
import Selectr from 'mobius1-selectr';
|
||||
import { i18n } from '../i18next';
|
||||
|
||||
|
@ -92,6 +94,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
this.fetchPageTitle = debounce(this.fetchPageTitle).bind(this);
|
||||
|
||||
this.tribute = setupTribute();
|
||||
this.setupEmojiPicker();
|
||||
|
||||
this.state = this.emptyState;
|
||||
|
||||
if (this.props.post) {
|
||||
|
@ -190,8 +194,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
<form>
|
||||
<label
|
||||
htmlFor="file-upload"
|
||||
className={`${UserService.Instance.user &&
|
||||
'pointer'} d-inline-block float-right text-muted h6 font-weight-bold`}
|
||||
className={`${
|
||||
UserService.Instance.user && 'pointer'
|
||||
} d-inline-block float-right text-muted font-weight-bold`}
|
||||
data-tippy-content={i18n.t('upload_image')}
|
||||
>
|
||||
<svg class="icon icon-inline">
|
||||
|
@ -284,8 +289,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
)}
|
||||
{this.state.postForm.body && (
|
||||
<button
|
||||
className={`mt-1 mr-2 btn btn-sm btn-secondary ${this.state
|
||||
.previewMode && 'active'}`}
|
||||
className={`mt-1 mr-2 btn btn-sm btn-secondary ${
|
||||
this.state.previewMode && 'active'
|
||||
}`}
|
||||
onClick={linkEvent(this, this.handlePreviewToggle)}
|
||||
>
|
||||
{i18n.t('preview')}
|
||||
|
@ -294,13 +300,22 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
<a
|
||||
href={markdownHelpUrl}
|
||||
target="_blank"
|
||||
class="d-inline-block float-right text-muted h6 font-weight-bold"
|
||||
class="d-inline-block float-right text-muted font-weight-bold"
|
||||
title={i18n.t('formatting_help')}
|
||||
>
|
||||
<svg class="icon icon-inline">
|
||||
<use xlinkHref="#icon-help-circle"></use>
|
||||
</svg>
|
||||
</a>
|
||||
<span
|
||||
onClick={linkEvent(this, this.handleEmojiPickerClick)}
|
||||
class="pointer unselectable d-inline-block mr-3 float-right text-muted font-weight-bold"
|
||||
data-tippy-content={i18n.t('emoji_picker')}
|
||||
>
|
||||
<svg class="icon icon-inline">
|
||||
<use xlinkHref="#icon-smile"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{!this.props.post && (
|
||||
|
@ -369,6 +384,20 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
);
|
||||
}
|
||||
|
||||
setupEmojiPicker() {
|
||||
emojiPicker.on('emoji', twemojiHtmlStr => {
|
||||
if (this.state.postForm.body == null) {
|
||||
this.state.postForm.body = '';
|
||||
}
|
||||
var el = document.createElement('div');
|
||||
el.innerHTML = twemojiHtmlStr;
|
||||
let nativeUnicode = (el.childNodes[0] as HTMLElement).getAttribute('alt');
|
||||
let shortName = `:${emojiShortName[nativeUnicode]}:`;
|
||||
this.state.postForm.body += shortName;
|
||||
this.setState(this.state);
|
||||
});
|
||||
}
|
||||
|
||||
handlePostSubmit(i: PostForm, event: any) {
|
||||
event.preventDefault();
|
||||
if (i.props.post) {
|
||||
|
@ -512,6 +541,10 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
});
|
||||
}
|
||||
|
||||
handleEmojiPickerClick(_i: PostForm, event: any) {
|
||||
emojiPicker.togglePicker(event.target);
|
||||
}
|
||||
|
||||
parseMessage(msg: WebSocketJsonResponse) {
|
||||
let res = wsJsonToRes(msg);
|
||||
if (msg.error) {
|
||||
|
|
43
ui/src/components/post-listing.tsx
vendored
43
ui/src/components/post-listing.tsx
vendored
|
@ -19,18 +19,19 @@ import {
|
|||
import { MomentTime } from './moment-time';
|
||||
import { PostForm } from './post-form';
|
||||
import { IFramelyCard } from './iframely-card';
|
||||
import { UserListing } from './user-listing';
|
||||
import {
|
||||
md,
|
||||
mdToHtml,
|
||||
canMod,
|
||||
isMod,
|
||||
isImage,
|
||||
isVideo,
|
||||
getUnixTime,
|
||||
pictshareAvatarThumbnail,
|
||||
showAvatars,
|
||||
pictshareImage,
|
||||
setupTippy,
|
||||
hostname,
|
||||
previewLines,
|
||||
} from '../utils';
|
||||
import { i18n } from '../i18next';
|
||||
|
||||
|
@ -415,20 +416,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
<ul class="list-inline mb-0 text-muted small">
|
||||
<li className="list-inline-item">
|
||||
<span>{i18n.t('by')} </span>
|
||||
<Link
|
||||
className="text-body font-weight-bold"
|
||||
to={`/u/${post.creator_name}`}
|
||||
>
|
||||
{post.creator_avatar && showAvatars() && (
|
||||
<img
|
||||
height="32"
|
||||
width="32"
|
||||
src={pictshareAvatarThumbnail(post.creator_avatar)}
|
||||
class="rounded-circle mr-1"
|
||||
/>
|
||||
)}
|
||||
<span>{post.creator_name}</span>
|
||||
</Link>
|
||||
<UserListing
|
||||
user={{
|
||||
name: post.creator_name,
|
||||
avatar: post.creator_avatar,
|
||||
}}
|
||||
/>
|
||||
{this.isMod && (
|
||||
<span className="mx-1 badge badge-light">
|
||||
{i18n.t('mod')}
|
||||
|
@ -465,6 +458,24 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
<MomentTime data={post} />
|
||||
</span>
|
||||
</li>
|
||||
{post.body && (
|
||||
<>
|
||||
<li className="list-inline-item">•</li>
|
||||
<li className="list-inline-item">
|
||||
{/* Using a link with tippy doesn't work on touch devices unfortunately */}
|
||||
<Link
|
||||
className="text-muted"
|
||||
data-tippy-content={md.render(previewLines(post.body))}
|
||||
data-tippy-allowHtml={true}
|
||||
to={`/post/${post.id}`}
|
||||
>
|
||||
<svg class="mr-1 icon icon-inline">
|
||||
<use xlinkHref="#icon-book-open"></use>
|
||||
</svg>
|
||||
</Link>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
<li className="list-inline-item">•</li>
|
||||
{this.state.upvotes !== this.state.score && (
|
||||
<>
|
||||
|
|
22
ui/src/components/post.tsx
vendored
22
ui/src/components/post.tsx
vendored
|
@ -213,8 +213,9 @@ export class Post extends Component<any, PostState> {
|
|||
return (
|
||||
<div class="btn-group btn-group-toggle mb-2">
|
||||
<label
|
||||
className={`btn btn-sm btn-secondary pointer ${this.state
|
||||
.commentSort === CommentSortType.Hot && 'active'}`}
|
||||
className={`btn btn-sm btn-secondary pointer ${
|
||||
this.state.commentSort === CommentSortType.Hot && 'active'
|
||||
}`}
|
||||
>
|
||||
{i18n.t('hot')}
|
||||
<input
|
||||
|
@ -225,8 +226,9 @@ export class Post extends Component<any, PostState> {
|
|||
/>
|
||||
</label>
|
||||
<label
|
||||
className={`btn btn-sm btn-secondary pointer ${this.state
|
||||
.commentSort === CommentSortType.Top && 'active'}`}
|
||||
className={`btn btn-sm btn-secondary pointer ${
|
||||
this.state.commentSort === CommentSortType.Top && 'active'
|
||||
}`}
|
||||
>
|
||||
{i18n.t('top')}
|
||||
<input
|
||||
|
@ -237,8 +239,9 @@ export class Post extends Component<any, PostState> {
|
|||
/>
|
||||
</label>
|
||||
<label
|
||||
className={`btn btn-sm btn-secondary pointer ${this.state
|
||||
.commentSort === CommentSortType.New && 'active'}`}
|
||||
className={`btn btn-sm btn-secondary pointer ${
|
||||
this.state.commentSort === CommentSortType.New && 'active'
|
||||
}`}
|
||||
>
|
||||
{i18n.t('new')}
|
||||
<input
|
||||
|
@ -249,8 +252,9 @@ export class Post extends Component<any, PostState> {
|
|||
/>
|
||||
</label>
|
||||
<label
|
||||
className={`btn btn-sm btn-secondary pointer ${this.state
|
||||
.commentSort === CommentSortType.Old && 'active'}`}
|
||||
className={`btn btn-sm btn-secondary pointer ${
|
||||
this.state.commentSort === CommentSortType.Old && 'active'
|
||||
}`}
|
||||
>
|
||||
{i18n.t('old')}
|
||||
<input
|
||||
|
@ -460,7 +464,7 @@ export class Post extends Component<any, PostState> {
|
|||
} else if (res.op == UserOperation.Search) {
|
||||
let data = res.data as SearchResponse;
|
||||
this.state.crossPosts = data.posts.filter(
|
||||
p => p.id != this.state.post.id
|
||||
p => p.id != Number(this.props.match.params.id)
|
||||
);
|
||||
this.setState(this.state);
|
||||
} else if (res.op == UserOperation.TransferSite) {
|
||||
|
|
30
ui/src/components/private-message-form.tsx
vendored
30
ui/src/components/private-message-form.tsx
vendored
|
@ -21,14 +21,13 @@ import {
|
|||
capitalizeFirstLetter,
|
||||
markdownHelpUrl,
|
||||
mdToHtml,
|
||||
showAvatars,
|
||||
pictshareAvatarThumbnail,
|
||||
wsJsonToRes,
|
||||
toast,
|
||||
randomStr,
|
||||
setupTribute,
|
||||
setupTippy,
|
||||
} from '../utils';
|
||||
import { UserListing } from './user-listing';
|
||||
import Tribute from 'tributejs/src/Tribute.js';
|
||||
import autosize from 'autosize';
|
||||
import { i18n } from '../i18next';
|
||||
|
@ -132,22 +131,12 @@ export class PrivateMessageForm extends Component<
|
|||
|
||||
{this.state.recipient && (
|
||||
<div class="col-sm-10 form-control-plaintext">
|
||||
<Link
|
||||
className="text-body font-weight-bold"
|
||||
to={`/u/${this.state.recipient.name}`}
|
||||
>
|
||||
{this.state.recipient.avatar && showAvatars() && (
|
||||
<img
|
||||
height="32"
|
||||
width="32"
|
||||
src={pictshareAvatarThumbnail(
|
||||
this.state.recipient.avatar
|
||||
)}
|
||||
class="rounded-circle mr-1"
|
||||
/>
|
||||
)}
|
||||
<span>{this.state.recipient.name}</span>
|
||||
</Link>
|
||||
<UserListing
|
||||
user={{
|
||||
name: this.state.recipient.name,
|
||||
avatar: this.state.recipient.avatar,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -233,8 +222,9 @@ export class PrivateMessageForm extends Component<
|
|||
</button>
|
||||
{this.state.privateMessageForm.content && (
|
||||
<button
|
||||
className={`btn btn-secondary mr-2 ${this.state.previewMode &&
|
||||
'active'}`}
|
||||
className={`btn btn-secondary mr-2 ${
|
||||
this.state.previewMode && 'active'
|
||||
}`}
|
||||
onClick={linkEvent(this, this.handlePreviewToggle)}
|
||||
>
|
||||
{i18n.t('preview')}
|
||||
|
|
16
ui/src/components/private-message.tsx
vendored
16
ui/src/components/private-message.tsx
vendored
|
@ -58,6 +58,7 @@ export class PrivateMessage extends Component<
|
|||
<div class="border-top border-light">
|
||||
<div>
|
||||
<ul class="list-inline mb-0 text-muted small">
|
||||
{/* TODO refactor this */}
|
||||
<li className="list-inline-item">
|
||||
{this.mine ? i18n.t('to') : i18n.t('from')}
|
||||
</li>
|
||||
|
@ -143,8 +144,9 @@ export class PrivateMessage extends Component<
|
|||
}
|
||||
>
|
||||
<svg
|
||||
class={`icon icon-inline ${message.read &&
|
||||
'text-success'}`}
|
||||
class={`icon icon-inline ${
|
||||
message.read && 'text-success'
|
||||
}`}
|
||||
>
|
||||
<use xlinkHref="#icon-check"></use>
|
||||
</svg>
|
||||
|
@ -187,8 +189,9 @@ export class PrivateMessage extends Component<
|
|||
}
|
||||
>
|
||||
<svg
|
||||
class={`icon icon-inline ${message.deleted &&
|
||||
'text-danger'}`}
|
||||
class={`icon icon-inline ${
|
||||
message.deleted && 'text-danger'
|
||||
}`}
|
||||
>
|
||||
<use xlinkHref="#icon-trash"></use>
|
||||
</svg>
|
||||
|
@ -203,8 +206,9 @@ export class PrivateMessage extends Component<
|
|||
data-tippy-content={i18n.t('view_source')}
|
||||
>
|
||||
<svg
|
||||
class={`icon icon-inline ${this.state.viewSource &&
|
||||
'text-success'}`}
|
||||
class={`icon icon-inline ${
|
||||
this.state.viewSource && 'text-success'
|
||||
}`}
|
||||
>
|
||||
<use xlinkHref="#icon-file-text"></use>
|
||||
</svg>
|
||||
|
|
23
ui/src/components/search.tsx
vendored
23
ui/src/components/search.tsx
vendored
|
@ -30,6 +30,7 @@ import {
|
|||
commentsToFlatNodes,
|
||||
} from '../utils';
|
||||
import { PostListing } from './post-listing';
|
||||
import { UserListing } from './user-listing';
|
||||
import { SortSelect } from './sort-select';
|
||||
import { CommentNodes } from './comment-nodes';
|
||||
import { i18n } from '../i18next';
|
||||
|
@ -266,22 +267,12 @@ export class Search extends Component<any, SearchState> {
|
|||
{i.type_ == 'users' && (
|
||||
<div>
|
||||
<span>
|
||||
<Link
|
||||
className="text-info"
|
||||
to={`/u/${(i.data as UserView).name}`}
|
||||
>
|
||||
{(i.data as UserView).avatar && showAvatars() && (
|
||||
<img
|
||||
height="32"
|
||||
width="32"
|
||||
src={pictshareAvatarThumbnail(
|
||||
(i.data as UserView).avatar
|
||||
)}
|
||||
class="rounded-circle mr-1"
|
||||
/>
|
||||
)}
|
||||
<span>{`/u/${(i.data as UserView).name}`}</span>
|
||||
</Link>
|
||||
<UserListing
|
||||
user={{
|
||||
name: (i.data as UserView).name,
|
||||
avatar: (i.data as UserView).avatar,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
<span>{` - ${
|
||||
(i.data as UserView).comment_score
|
||||
|
|
32
ui/src/components/sidebar.tsx
vendored
32
ui/src/components/sidebar.tsx
vendored
|
@ -15,6 +15,7 @@ import {
|
|||
showAvatars,
|
||||
} from '../utils';
|
||||
import { CommunityForm } from './community-form';
|
||||
import { UserListing } from './user-listing';
|
||||
import { i18n } from '../i18next';
|
||||
|
||||
interface SidebarProps {
|
||||
|
@ -110,8 +111,9 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
}
|
||||
>
|
||||
<svg
|
||||
class={`icon icon-inline ${community.deleted &&
|
||||
'text-danger'}`}
|
||||
class={`icon icon-inline ${
|
||||
community.deleted && 'text-danger'
|
||||
}`}
|
||||
>
|
||||
<use xlinkHref="#icon-trash"></use>
|
||||
</svg>
|
||||
|
@ -204,27 +206,19 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
<li class="list-inline-item">{i18n.t('mods')}: </li>
|
||||
{this.props.moderators.map(mod => (
|
||||
<li class="list-inline-item">
|
||||
<Link
|
||||
class="text-body font-weight-bold"
|
||||
to={`/u/${mod.user_name}`}
|
||||
>
|
||||
{mod.avatar && showAvatars() && (
|
||||
<img
|
||||
height="32"
|
||||
width="32"
|
||||
src={pictshareAvatarThumbnail(mod.avatar)}
|
||||
class="rounded-circle mr-1"
|
||||
/>
|
||||
)}
|
||||
<span>{mod.user_name}</span>
|
||||
</Link>
|
||||
<UserListing
|
||||
user={{
|
||||
name: mod.user_name,
|
||||
avatar: mod.avatar,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Link
|
||||
class={`btn btn-sm btn-secondary btn-block mb-3 ${(community.deleted ||
|
||||
community.removed) &&
|
||||
'no-click'}`}
|
||||
class={`btn btn-sm btn-secondary btn-block mb-3 ${
|
||||
(community.deleted || community.removed) && 'no-click'
|
||||
}`}
|
||||
to={`/create_post?community=${community.name}`}
|
||||
>
|
||||
{i18n.t('create_a_post')}
|
||||
|
|
7
ui/src/components/site-form.tsx
vendored
7
ui/src/components/site-form.tsx
vendored
|
@ -58,12 +58,19 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
});
|
||||
}
|
||||
|
||||
// Necessary to stop the loading
|
||||
componentWillReceiveProps() {
|
||||
this.state.loading = false;
|
||||
this.setState(this.state);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Prompt
|
||||
when={
|
||||
!this.state.loading &&
|
||||
!this.props.site &&
|
||||
(this.state.siteForm.name || this.state.siteForm.description)
|
||||
}
|
||||
message={i18n.t('block_leaving')}
|
||||
|
|
11
ui/src/components/sponsors.tsx
vendored
11
ui/src/components/sponsors.tsx
vendored
|
@ -2,11 +2,12 @@ import { Component } from 'inferno';
|
|||
import { WebSocketService } from '../services';
|
||||
import { i18n } from '../i18next';
|
||||
import { T } from 'inferno-i18next';
|
||||
import { repoUrl } from '../utils';
|
||||
|
||||
let general = [
|
||||
'alexx henry',
|
||||
'Nathan J. Goode',
|
||||
'Andre Vallestero',
|
||||
'riccardo',
|
||||
'NotTooHighToHack',
|
||||
];
|
||||
let highlighted = ['Alex Benishek'];
|
||||
|
@ -44,7 +45,7 @@ export class Sponsors extends Component<any, any> {
|
|||
<h5>{i18n.t('donate_to_lemmy')}</h5>
|
||||
<p>
|
||||
<T i18nKey="sponsor_message">
|
||||
#<a href="https://github.com/dessalines/lemmy">#</a>
|
||||
#<a href={repoUrl}>#</a>
|
||||
</T>
|
||||
</p>
|
||||
<a class="btn btn-secondary" href="https://liberapay.com/Lemmy/">
|
||||
|
@ -56,6 +57,12 @@ export class Sponsors extends Component<any, any> {
|
|||
>
|
||||
{i18n.t('support_on_patreon')}
|
||||
</a>
|
||||
<a
|
||||
class="btn btn-secondary ml-2"
|
||||
href="https://opencollective.com/lemmy"
|
||||
>
|
||||
{i18n.t('support_on_open_collective')}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
9
ui/src/components/symbols.tsx
vendored
9
ui/src/components/symbols.tsx
vendored
File diff suppressed because one or more lines are too long
36
ui/src/components/user-listing.tsx
vendored
Normal file
36
ui/src/components/user-listing.tsx
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { Component } from 'inferno';
|
||||
import { Link } from 'inferno-router';
|
||||
import { UserView } from '../interfaces';
|
||||
import { pictshareAvatarThumbnail, showAvatars } from '../utils';
|
||||
|
||||
interface UserOther {
|
||||
name: string;
|
||||
avatar?: string;
|
||||
}
|
||||
|
||||
interface UserListingProps {
|
||||
user: UserView | UserOther;
|
||||
}
|
||||
|
||||
export class UserListing extends Component<UserListingProps, any> {
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
}
|
||||
|
||||
render() {
|
||||
let user = this.props.user;
|
||||
return (
|
||||
<Link className="text-body font-weight-bold" to={`/u/${user.name}`}>
|
||||
{user.avatar && showAvatars() && (
|
||||
<img
|
||||
height="32"
|
||||
width="32"
|
||||
src={pictshareAvatarThumbnail(user.avatar)}
|
||||
class="rounded-circle mr-2"
|
||||
/>
|
||||
)}
|
||||
<span>{user.name}</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
116
ui/src/index.tsx
vendored
116
ui/src/index.tsx
vendored
|
@ -15,79 +15,85 @@ import { Communities } from './components/communities';
|
|||
import { User } from './components/user';
|
||||
import { Modlog } from './components/modlog';
|
||||
import { Setup } from './components/setup';
|
||||
import { AdminSettings } from './components/admin-settings';
|
||||
import { Inbox } from './components/inbox';
|
||||
import { Search } from './components/search';
|
||||
import { Sponsors } from './components/sponsors';
|
||||
import { Symbols } from './components/symbols';
|
||||
import { i18n } from './i18next';
|
||||
|
||||
import { WebSocketService, UserService } from './services';
|
||||
|
||||
const container = document.getElementById('app');
|
||||
|
||||
class Index extends Component<any, any> {
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
WebSocketService.Instance;
|
||||
UserService.Instance;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Provider i18next={i18n}>
|
||||
<BrowserRouter>
|
||||
<Navbar />
|
||||
<div class="mt-4 p-0 fl-1">
|
||||
<Switch>
|
||||
<Route exact path={`/`} component={Main} />
|
||||
<Route
|
||||
path={`/home/data_type/:data_type/listing_type/:listing_type/sort/:sort/page/:page`}
|
||||
component={Main}
|
||||
/>
|
||||
<Route path={`/login`} component={Login} />
|
||||
<Route path={`/create_post`} component={CreatePost} />
|
||||
<Route path={`/create_community`} component={CreateCommunity} />
|
||||
<Route
|
||||
path={`/create_private_message`}
|
||||
component={CreatePrivateMessage}
|
||||
/>
|
||||
<Route path={`/communities/page/:page`} component={Communities} />
|
||||
<Route path={`/communities`} component={Communities} />
|
||||
<Route path={`/post/:id/comment/:comment_id`} component={Post} />
|
||||
<Route path={`/post/:id`} component={Post} />
|
||||
<Route
|
||||
path={`/c/:name/data_type/:data_type/sort/:sort/page/:page`}
|
||||
component={Community}
|
||||
/>
|
||||
<Route path={`/community/:id`} component={Community} />
|
||||
<Route path={`/c/:name`} component={Community} />
|
||||
<Route
|
||||
path={`/u/:username/view/:view/sort/:sort/page/:page`}
|
||||
component={User}
|
||||
/>
|
||||
<Route path={`/user/:id`} component={User} />
|
||||
<Route path={`/u/:username`} component={User} />
|
||||
<Route path={`/inbox`} component={Inbox} />
|
||||
<Route
|
||||
path={`/modlog/community/:community_id`}
|
||||
component={Modlog}
|
||||
/>
|
||||
<Route path={`/modlog`} component={Modlog} />
|
||||
<Route path={`/setup`} component={Setup} />
|
||||
<Route
|
||||
path={`/search/q/:q/type/:type/sort/:sort/page/:page`}
|
||||
component={Search}
|
||||
/>
|
||||
<Route path={`/search`} component={Search} />
|
||||
<Route path={`/sponsors`} component={Sponsors} />
|
||||
<Route
|
||||
path={`/password_change/:token`}
|
||||
component={PasswordChange}
|
||||
/>
|
||||
</Switch>
|
||||
<Symbols />
|
||||
<div>
|
||||
<Navbar />
|
||||
<div class="mt-4 p-0 fl-1">
|
||||
<Switch>
|
||||
<Route exact path={`/`} component={Main} />
|
||||
<Route
|
||||
path={`/home/data_type/:data_type/listing_type/:listing_type/sort/:sort/page/:page`}
|
||||
component={Main}
|
||||
/>
|
||||
<Route path={`/login`} component={Login} />
|
||||
<Route path={`/create_post`} component={CreatePost} />
|
||||
<Route path={`/create_community`} component={CreateCommunity} />
|
||||
<Route
|
||||
path={`/create_private_message`}
|
||||
component={CreatePrivateMessage}
|
||||
/>
|
||||
<Route
|
||||
path={`/communities/page/:page`}
|
||||
component={Communities}
|
||||
/>
|
||||
<Route path={`/communities`} component={Communities} />
|
||||
<Route
|
||||
path={`/post/:id/comment/:comment_id`}
|
||||
component={Post}
|
||||
/>
|
||||
<Route path={`/post/:id`} component={Post} />
|
||||
<Route
|
||||
path={`/c/:name/data_type/:data_type/sort/:sort/page/:page`}
|
||||
component={Community}
|
||||
/>
|
||||
<Route path={`/community/:id`} component={Community} />
|
||||
<Route path={`/c/:name`} component={Community} />
|
||||
<Route
|
||||
path={`/u/:username/view/:view/sort/:sort/page/:page`}
|
||||
component={User}
|
||||
/>
|
||||
<Route path={`/user/:id`} component={User} />
|
||||
<Route path={`/u/:username`} component={User} />
|
||||
<Route path={`/inbox`} component={Inbox} />
|
||||
<Route
|
||||
path={`/modlog/community/:community_id`}
|
||||
component={Modlog}
|
||||
/>
|
||||
<Route path={`/modlog`} component={Modlog} />
|
||||
<Route path={`/setup`} component={Setup} />
|
||||
<Route path={`/admin`} component={AdminSettings} />
|
||||
<Route
|
||||
path={`/search/q/:q/type/:type/sort/:sort/page/:page`}
|
||||
component={Search}
|
||||
/>
|
||||
<Route path={`/search`} component={Search} />
|
||||
<Route path={`/sponsors`} component={Sponsors} />
|
||||
<Route
|
||||
path={`/password_change/:token`}
|
||||
component={PasswordChange}
|
||||
/>
|
||||
</Switch>
|
||||
<Symbols />
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
<Footer />
|
||||
</BrowserRouter>
|
||||
</Provider>
|
||||
);
|
||||
|
|
21
ui/src/interfaces.ts
vendored
21
ui/src/interfaces.ts
vendored
|
@ -43,6 +43,8 @@ export enum UserOperation {
|
|||
GetPrivateMessages,
|
||||
UserJoin,
|
||||
GetComments,
|
||||
GetSiteConfig,
|
||||
SaveSiteConfig,
|
||||
}
|
||||
|
||||
export enum CommentSortType {
|
||||
|
@ -724,6 +726,19 @@ export interface SiteForm {
|
|||
auth?: string;
|
||||
}
|
||||
|
||||
export interface GetSiteConfig {
|
||||
auth?: string;
|
||||
}
|
||||
|
||||
export interface GetSiteConfigResponse {
|
||||
config_hjson: string;
|
||||
}
|
||||
|
||||
export interface SiteConfigForm {
|
||||
config_hjson: string;
|
||||
auth?: string;
|
||||
}
|
||||
|
||||
export interface GetSiteResponse {
|
||||
site: Site;
|
||||
admins: Array<UserView>;
|
||||
|
@ -871,7 +886,8 @@ export type MessageType =
|
|||
| PasswordChangeForm
|
||||
| PrivateMessageForm
|
||||
| EditPrivateMessageForm
|
||||
| GetPrivateMessagesForm;
|
||||
| GetPrivateMessagesForm
|
||||
| SiteConfigForm;
|
||||
|
||||
type ResponseType =
|
||||
| SiteResponse
|
||||
|
@ -893,7 +909,8 @@ type ResponseType =
|
|||
| BanUserResponse
|
||||
| AddAdminResponse
|
||||
| PrivateMessageResponse
|
||||
| PrivateMessagesResponse;
|
||||
| PrivateMessagesResponse
|
||||
| GetSiteConfigResponse;
|
||||
|
||||
export interface WebSocketResponse {
|
||||
op: UserOperation;
|
||||
|
|
13
ui/src/services/WebSocketService.ts
vendored
13
ui/src/services/WebSocketService.ts
vendored
|
@ -40,6 +40,8 @@ import {
|
|||
GetPrivateMessagesForm,
|
||||
GetCommentsForm,
|
||||
UserJoinForm,
|
||||
GetSiteConfig,
|
||||
SiteConfigForm,
|
||||
MessageType,
|
||||
WebSocketJsonResponse,
|
||||
} from '../interfaces';
|
||||
|
@ -268,6 +270,12 @@ export class WebSocketService {
|
|||
this.ws.send(this.wsSendWrapper(UserOperation.GetSite, {}));
|
||||
}
|
||||
|
||||
public getSiteConfig() {
|
||||
let siteConfig: GetSiteConfig = {};
|
||||
this.setAuth(siteConfig);
|
||||
this.ws.send(this.wsSendWrapper(UserOperation.GetSiteConfig, siteConfig));
|
||||
}
|
||||
|
||||
public search(form: SearchForm) {
|
||||
this.setAuth(form, false);
|
||||
this.ws.send(this.wsSendWrapper(UserOperation.Search, form));
|
||||
|
@ -314,6 +322,11 @@ export class WebSocketService {
|
|||
this.ws.send(this.wsSendWrapper(UserOperation.GetPrivateMessages, form));
|
||||
}
|
||||
|
||||
public saveSiteConfig(form: SiteConfigForm) {
|
||||
this.setAuth(form);
|
||||
this.ws.send(this.wsSendWrapper(UserOperation.SaveSiteConfig, form));
|
||||
}
|
||||
|
||||
private wsSendWrapper(op: UserOperation, data: MessageType) {
|
||||
let send = { op: UserOperation[op], data: data };
|
||||
console.log(send);
|
||||
|
|
24
ui/src/utils.ts
vendored
24
ui/src/utils.ts
vendored
|
@ -43,8 +43,9 @@ import twemoji from 'twemoji';
|
|||
import emojiShortName from 'emoji-short-name';
|
||||
import Toastify from 'toastify-js';
|
||||
import tippy from 'tippy.js';
|
||||
import EmojiButton from '@joeattardi/emoji-button';
|
||||
|
||||
export const repoUrl = 'https://github.com/dessalines/lemmy';
|
||||
export const repoUrl = 'https://github.com/LemmyNet/lemmy';
|
||||
export const helpGuideUrl = '/docs/about_guide.html';
|
||||
export const markdownHelpUrl = `${helpGuideUrl}#markdown-guide`;
|
||||
export const sortingHelpUrl = `${helpGuideUrl}#sorting`;
|
||||
|
@ -88,6 +89,14 @@ export const themes = [
|
|||
'i386',
|
||||
];
|
||||
|
||||
export const emojiPicker = new EmojiButton({
|
||||
// Use the emojiShortName from native
|
||||
style: 'twemoji',
|
||||
theme: 'dark',
|
||||
position: 'auto-start',
|
||||
// TODO i18n
|
||||
});
|
||||
|
||||
export function randomStr() {
|
||||
return Math.random()
|
||||
.toString(36)
|
||||
|
@ -473,8 +482,9 @@ export function setupTribute(): Tribute {
|
|||
{
|
||||
trigger: ':',
|
||||
menuItemTemplate: (item: any) => {
|
||||
let emoji = `:${item.original.key}:`;
|
||||
return `${item.original.val} ${emoji}`;
|
||||
let shortName = `:${item.original.key}:`;
|
||||
let twemojiIcon = twemoji.parse(item.original.val);
|
||||
return `${twemojiIcon} ${shortName}`;
|
||||
},
|
||||
selectTemplate: (item: any) => {
|
||||
return `:${item.original.key}:`;
|
||||
|
@ -824,6 +834,14 @@ function randomHsl() {
|
|||
return `hsla(${Math.random() * 360}, 100%, 50%, 1)`;
|
||||
}
|
||||
|
||||
export function previewLines(text: string, lines: number = 3): string {
|
||||
// Use lines * 2 because markdown requires 2 lines
|
||||
return text
|
||||
.split('\n')
|
||||
.slice(0, lines * 2)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
export function hostname(url: string): string {
|
||||
return new URL(url).hostname;
|
||||
}
|
||||
|
|
2
ui/src/version.ts
vendored
2
ui/src/version.ts
vendored
|
@ -1 +1 @@
|
|||
export const version: string = 'v0.6.44';
|
||||
export const version: string = 'v0.6.49';
|
||||
|
|
6
ui/translations/en.json
vendored
6
ui/translations/en.json
vendored
|
@ -53,6 +53,8 @@
|
|||
"mods": "mods",
|
||||
"moderates": "Moderates",
|
||||
"settings": "Settings",
|
||||
"admin_settings": "Admin Settings",
|
||||
"site_config": "Site Configuration",
|
||||
"remove_as_mod": "remove as mod",
|
||||
"appoint_as_mod": "appoint as mod",
|
||||
"modlog": "Modlog",
|
||||
|
@ -78,6 +80,7 @@
|
|||
"unban": "unban",
|
||||
"unban_from_site": "unban from site",
|
||||
"banned": "banned",
|
||||
"banned_users": "Banned Users",
|
||||
"save": "save",
|
||||
"unsave": "unsave",
|
||||
"create": "create",
|
||||
|
@ -187,6 +190,7 @@
|
|||
"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_liberapay": "Support on Liberapay",
|
||||
"support_on_open_collective": "Support on OpenCollective",
|
||||
"donate_to_lemmy": "Donate to Lemmy",
|
||||
"donate": "Donate",
|
||||
"general_sponsors":
|
||||
|
@ -210,6 +214,7 @@
|
|||
"Lemmy is a <1>link aggregator</1> / reddit alternative, intended to work in the <2>fediverse</2>.<3></3>It's self-hostable, has live-updating comment threads, and is tiny (<4>~80kB</4>). Federation into the ActivityPub network is on the roadmap. <5></5>This is a <6>very early beta version</6>, and a lot of features are currently broken or missing. <7></7>Suggest new features or report bugs <8>here.</8><9></9>Made with <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
|
||||
"not_logged_in": "Not logged in.",
|
||||
"logged_in": "Logged in.",
|
||||
"site_saved": "Site Saved.",
|
||||
"community_ban": "You have been banned from this community.",
|
||||
"site_ban": "You have been banned from the site",
|
||||
"couldnt_create_comment": "Couldn't create comment.",
|
||||
|
@ -251,5 +256,6 @@
|
|||
"couldnt_update_private_message": "Couldn't update private message.",
|
||||
"time": "Time",
|
||||
"action": "Action",
|
||||
"emoji_picker": "Emoji Picker",
|
||||
"block_leaving": "Are you sure you want to leave?"
|
||||
}
|
||||
|
|
72
ui/translations/es.json
vendored
72
ui/translations/es.json
vendored
|
@ -4,13 +4,15 @@
|
|||
"no_posts": "Sin publicaciones.",
|
||||
"create_a_post": "Crear una publicación",
|
||||
"create_post": "Crear Publicación",
|
||||
"number_of_posts": "{{count}} Publicaciones",
|
||||
"number_of_posts": "{{count}} Publicación",
|
||||
"number_of_posts_plural": "{{count}} Publicaciónes",
|
||||
"posts": "Publicaciones",
|
||||
"related_posts": "Estas publicaciones podrían estar relacionadas",
|
||||
"cross_posts": "Este link también ha sido publicado en:",
|
||||
"cross_post": "cross-post",
|
||||
"comments": "Comentarios",
|
||||
"number_of_comments": "{{count}} Comentarios",
|
||||
"number_of_comments": "{{count}} Comentario",
|
||||
"number_of_comments_plural": "{{count}} Comentarios",
|
||||
"remove_comment": "Eliminar Comentarios",
|
||||
"communities": "Comunidades",
|
||||
"users": "Usuarios",
|
||||
|
@ -20,7 +22,8 @@
|
|||
"subscribed_to_communities": "Suscrito a <1>comunidades</1>",
|
||||
"trending_communities": "<1>Comunidades</1> en tendencia",
|
||||
"list_of_communities": "Lista de comunidades",
|
||||
"number_of_communities": "{{count}} Comunidades",
|
||||
"number_of_communities": "{{count}} Comunidad",
|
||||
"number_of_communities_plural": "{{count}} Comunidades",
|
||||
"community_reqs": "minúsculas, guión bajo, y sin espacios.",
|
||||
"create_private_message": "Crear Mensaje Privado",
|
||||
"send_secure_message": "Enviar Mensaje Seguro",
|
||||
|
@ -63,8 +66,7 @@
|
|||
"delete": "eliminar",
|
||||
"deleted": "eliminado",
|
||||
"delete_account": "Eliminar Cuenta",
|
||||
"delete_account_confirm":
|
||||
"Aviso: esta acción eliminará permanentemente tu información. Introduce tu contraseña para continuar",
|
||||
"delete_account_confirm": "Aviso: esta acción eliminará permanentemente tu información. Introduce tu contraseña para continuar",
|
||||
"restore": "restaurar",
|
||||
"ban": "expulsar",
|
||||
"ban_from_site": "expulsar del sitio",
|
||||
|
@ -77,10 +79,14 @@
|
|||
"creator": "creador",
|
||||
"username": "Nombre de Usuario",
|
||||
"email_or_username": "Correo o Usuario",
|
||||
"number_of_users": "{{count}} Usuarios",
|
||||
"number_of_subscribers": "{{count}} Suscriptores",
|
||||
"number_of_points": "{{count}} Puntos",
|
||||
"number_online": "{{count}} Usuarios En Línea",
|
||||
"number_of_users": "{{count}} Usuario",
|
||||
"number_of_users_plural": "{{count}} Usuarios",
|
||||
"number_of_subscribers": "{{count}} Suscriptor",
|
||||
"number_of_subscribers_plural": "{{count}} Suscriptores",
|
||||
"number_of_points": "{{count}} Punto",
|
||||
"number_of_points_plural": "{{count}} Puntos",
|
||||
"number_online": "{{count}} Usuario En Línea",
|
||||
"number_online_plural": "{{count}} Usuarios En Línea",
|
||||
"name": "Nombre",
|
||||
"title": "Titulo",
|
||||
"category": "Categoría",
|
||||
|
@ -120,8 +126,7 @@
|
|||
"login_sign_up": "Iniciar sesión / Crear cuenta",
|
||||
"login": "Iniciar sesión",
|
||||
"sign_up": "Crear cuenta",
|
||||
"notifications_error":
|
||||
"Notificaciones de escritorio no disponibles en tu navegador. Prueba Firefox o Chrome.",
|
||||
"notifications_error": "Notificaciones de escritorio no disponibles en tu navegador. Prueba Firefox o Chrome.",
|
||||
"unread_messages": "Mensajes no leídos",
|
||||
"messages": "Mensajes",
|
||||
"password": "Contraseña",
|
||||
|
@ -134,8 +139,7 @@
|
|||
"no_email_setup": "Este servidor no ha activado correctamente el correo.",
|
||||
"email": "Correo electrónico",
|
||||
"matrix_user_id": "Usuario Matricial",
|
||||
"private_message_disclaimer":
|
||||
"Aviso: Los mensajes privados en Lemmy no son seguros. Por favor cree una cuenta en <1>Riot.im</1> para mensajeria segura.",
|
||||
"private_message_disclaimer": "Aviso: Los mensajes privados en Lemmy no son seguros. Por favor cree una cuenta en <1>Riot.im</1> para mensajeria segura.",
|
||||
"send_notifications_to_email": "Enviar notificaciones al correo",
|
||||
"optional": "Opcional",
|
||||
"expires": "Expira",
|
||||
|
@ -165,14 +169,12 @@
|
|||
"theme": "Tema",
|
||||
"sponsors": "Patrocinadores",
|
||||
"sponsors_of_lemmy": "Patrocinadores de Lemmy",
|
||||
"sponsor_message":
|
||||
"Lemmy es software libre y de <1>código abierto</1>, lo que significa que no tendrá publicidades, monetización, ni capitales emprendedores, nunca. Tus donaciones apoyan directamente el desarrollo a tiempo completo del proyecto. Muchas gracias a las siguientes personas:",
|
||||
"sponsor_message": "Lemmy es software libre y de <1>código abierto</1>, lo que significa que no tendrá publicidades, monetización, ni capitales emprendedores, nunca. Tus donaciones apoyan directamente el desarrollo a tiempo completo del proyecto. Muchas gracias a las siguientes personas:",
|
||||
"support_on_patreon": "Apoyo en Patreon",
|
||||
"support_on_liberapay": "Apoyo en Liberapay",
|
||||
"donate_to_lemmy": "Donar a Lemmy",
|
||||
"donate": "Donar",
|
||||
"general_sponsors":
|
||||
"Los Patrocinadores Generales son aquellos que señaron entre $10 y $39 a Lemmy.",
|
||||
"general_sponsors": "Los Patrocinadores Generales son aquellos que señaron entre $10 y $39 a Lemmy.",
|
||||
"crypto": "Crypto",
|
||||
"bitcoin": "Bitcoin",
|
||||
"ethereum": "Ethereum",
|
||||
|
@ -188,8 +190,7 @@
|
|||
"yes": "sí",
|
||||
"no": "no",
|
||||
"powered_by": "Impulsado por",
|
||||
"landing_0":
|
||||
"Lemmy es un <1>agregador de links</1> / alternativa a reddit, con la intención de funcionar en el <2>fediverso</2>.<3></3>Es alojable por uno mismo (sin necesidad de grandes compañías), tiene actualización en vivo de cadenas de comentarios, y es pequeño (<4>~80kB</4>). Federar con el sistema de redes ActivityPub forma parte de los objetivos del proyecto. <5></5>Esta es una <6>version beta muy prematura</6>, y actualmente muchas de las características están rotas o faltan. <7></7>Sugiere nuevas características o reporta errores <8>aquí</8>.<9></9>Hecho con <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
|
||||
"landing_0": "Lemmy es un <1>agregador de links</1> / alternativa a reddit, con la intención de funcionar en el <2>fediverso</2>.<3></3>Es alojable por uno mismo (sin necesidad de grandes compañías), tiene actualización en vivo de cadenas de comentarios, y es pequeño (<4>~80kB</4>). Federar con el sistema de redes ActivityPub forma parte de los objetivos del proyecto. <5></5>Esta es una <6>version beta muy prematura</6>, y actualmente muchas de las características están rotas o faltan. <7></7>Sugiere nuevas características o reporta errores <8>aquí</8>.<9></9>Hecho con <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
|
||||
"not_logged_in": "No has iniciado sesión.",
|
||||
"logged_in": "Has iniciado sesión.",
|
||||
"community_ban": "Has sido expulsado de esta comunidad.",
|
||||
|
@ -204,12 +205,9 @@
|
|||
"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.",
|
||||
|
@ -220,21 +218,31 @@
|
|||
"not_an_admin": "No es un administrador.",
|
||||
"site_already_exists": "El sitio ya existe.",
|
||||
"couldnt_update_site": "No se pudo actualizar el sitio.",
|
||||
"couldnt_find_that_username_or_email":
|
||||
"No se pudo encontrar ese nombre de usuario o correo electrónico.",
|
||||
"couldnt_find_that_username_or_email": "No se pudo encontrar ese nombre de usuario o correo electrónico.",
|
||||
"password_incorrect": "Contraseña incorrecta.",
|
||||
"passwords_dont_match": "Las contraseñas no coinciden.",
|
||||
"admin_already_created": "Lo sentimos, ya hay un adminisitrador.",
|
||||
"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.",
|
||||
"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.",
|
||||
"no_private_message_edit_allowed": "Sin permisos para editar el mensaje privado.",
|
||||
"couldnt_update_private_message": "No se pudo actualizar el mensaje privado.",
|
||||
"old": "Antiguo",
|
||||
"time": "Tiempo",
|
||||
"action": "Acción"
|
||||
"action": "Acción",
|
||||
"more": "más",
|
||||
"cross_posted_to": "publicado también en:",
|
||||
"sorting_help": "ayuda del orden",
|
||||
"upvote": "Voto Positivo",
|
||||
"number_of_upvotes": "{{count}} Voto Positivo",
|
||||
"number_of_upvotes_plural": "{{count}} Votos Positivos",
|
||||
"downvote": "Voto Negativo",
|
||||
"number_of_downvotes": "{{count}} Voto Negativo",
|
||||
"number_of_downvotes_plural": "{{count}} Votos Negativos",
|
||||
"couldnt_get_comments": "No se pudo obtener los comentarios.",
|
||||
"post_title_too_long": "El título de la publicación es muy largo.",
|
||||
"block_leaving": "¿Está seguro de que desea salir?",
|
||||
"show_context": "Mostrar contexto"
|
||||
}
|
||||
|
|
3
ui/translations/fr.json
vendored
3
ui/translations/fr.json
vendored
|
@ -227,5 +227,6 @@
|
|||
"no_private_message_edit_allowed": "Pas autorisé à modifier un message privé.",
|
||||
"couldnt_update_private_message": "Impossible de modifier un message privé.",
|
||||
"time": "Temps",
|
||||
"action": "Action"
|
||||
"action": "Action",
|
||||
"more": "plus"
|
||||
}
|
||||
|
|
223
ui/translations/ka.json
vendored
223
ui/translations/ka.json
vendored
|
@ -1,2 +1,225 @@
|
|||
{
|
||||
"post": "პოსტი",
|
||||
"remove_post": "პოსტის წაშლა",
|
||||
"no_posts": "0 პოსტები",
|
||||
"create_a_post": "პოსტის შექმნა",
|
||||
"create_post": "პოსტის შექმნა",
|
||||
"number_of_posts": "თარგმნა",
|
||||
"number_of_posts_plural": "თარგმნა",
|
||||
"posts": "პოსტები",
|
||||
"cross_posts": "ეს ლინკი უკვე დადებულია აქ:",
|
||||
"comments": "კომენტარები",
|
||||
"number_of_comments": "კომენტარი",
|
||||
"number_of_comments_plural": "კომანტარები",
|
||||
"remove_comment": "კომენტარის წაშლა",
|
||||
"communities": "თემები",
|
||||
"users": "მომხმარებელი",
|
||||
"create_a_community": "ახალი თემის შექმნა",
|
||||
"create_community": "თემის შექმნა",
|
||||
"remove_community": "თემის წაშლა",
|
||||
"community_reqs": "პატარა ასო, ქვედა ტირე, და გამოტოვების გარეშე.",
|
||||
"create_private_message": "კერძო მესეჯის შექმნა",
|
||||
"send_secure_message": "ინკრიპტული მესეჯის გაგზავნა",
|
||||
"send_message": "მესეჯის გაგზავნა",
|
||||
"message": "მესეჯი",
|
||||
"edit": "რადექტირება",
|
||||
"reply": "პასუხის გაცემა",
|
||||
"more": "მეტი",
|
||||
"cancel": "გაუქება",
|
||||
"upload_image": "სურათის ატვირთვა",
|
||||
"avatar": "ავატარი",
|
||||
"upload_avatar": "ავატარის ატვირთვა",
|
||||
"show_context": "კონტექსტის ნახვა",
|
||||
"sorting_help": "სორტირების დახმარება",
|
||||
"view_source": "view source",
|
||||
"unlock": "გაღება",
|
||||
"lock": "ჩაკეტვა",
|
||||
"sticky": "sticky",
|
||||
"link": "ლინკი",
|
||||
"archive_link": "ლინკის არქივება",
|
||||
"mod": "მოდერატორი",
|
||||
"mods": "მოდერატორები",
|
||||
"moderates": "მოდერატორს",
|
||||
"settings": "პარამეტრები",
|
||||
"appoint_as_mod": "დანიშნე როგორც მოდერატორი",
|
||||
"modlog": "მოდ-ლოგი",
|
||||
"admin": "ადმინი",
|
||||
"admins": "ადმინები",
|
||||
"appoint_as_admin": "დანიშნე როგორც ადმინი",
|
||||
"remove": "მოხსნა",
|
||||
"removed": "მოხსნილია",
|
||||
"locked": "ჩაკეტილი",
|
||||
"stickied": "დაწეპებული",
|
||||
"reason": "მიზეზი",
|
||||
"mark_as_read": "მონიშნე როგორც წაკითხული",
|
||||
"mark_as_unread": "მონიშნე როგორც წაუკითხავი",
|
||||
"delete": "წაშლა",
|
||||
"deleted": "წაშლილია",
|
||||
"delete_account": "ჩემი ანგარიშის წაშლა",
|
||||
"restore": "რასტორაცია",
|
||||
"ban": "გაშავება",
|
||||
"ban_from_site": "გაშავება საიტიდან",
|
||||
"unban": "გაშავების გაუქმნება",
|
||||
"unban_from_site": "სატიდან გაშავების გაუქმნება",
|
||||
"banned": "გაშავებულია",
|
||||
"save": "დამახსოვრება",
|
||||
"unsave": "დამახსოვრების გაუქმნება",
|
||||
"create": "შექმნა",
|
||||
"preview": "წინასწარ ნახვა",
|
||||
"show_avatars": "ავატარები გამოჩენა",
|
||||
"formatting_help": "formatting help",
|
||||
"unsticky": "unsticky",
|
||||
"remove_as_mod": "მოხსენი როგორც მოდერატორი",
|
||||
"remove_as_admin": "მოხსენი როგორც ადმინი",
|
||||
"delete_account_confirm": "გაფთხილება: ეს შენს ყველაფერს წაშლის. პაროლი ჩაწერეთ რომ დაადასტუროთ.",
|
||||
"creator": "შემქნელი",
|
||||
"username": "მომხმარებლის სახელი",
|
||||
"email_or_username": "ელ-პოსტა ან მომხმარებლის სახელი",
|
||||
"number_of_users": "მომხმარებელი",
|
||||
"number_of_users_plural": "მომხმარებლები",
|
||||
"number_of_subscribers": "გამომწერი",
|
||||
"number_of_subscribers_plural": "გამომწერები",
|
||||
"number_of_points": "ქულა",
|
||||
"number_of_points_plural": "ქულა",
|
||||
"number_online": "მომხმარებელი საიტზე",
|
||||
"number_online_plural": "მომხმარებელი საიტზე",
|
||||
"name": "სახელი",
|
||||
"title": "სათაური",
|
||||
"category": "კატეგორია",
|
||||
"subscribers": "გამომწერი",
|
||||
"both": "ორივე",
|
||||
"saved": "შანახული",
|
||||
"unsubscribe": "გამოწერის გაუქმნება",
|
||||
"subscribe": "გამოწერა",
|
||||
"subscribed": "გამოწერილია",
|
||||
"prev": "წუნა",
|
||||
"next": "შემდეგი",
|
||||
"sidebar": "Sidebar",
|
||||
"sort_type": "სორტირების ტიპი",
|
||||
"inbox": "Inbox",
|
||||
"inbox_for": "<1>{{user}}</1>-s Inbox",
|
||||
"mark_all_as_read": "მონიშვნა ყველასი როგორც წაკითხული",
|
||||
"type": "ტიპი",
|
||||
"unread": "წაუკითხავია",
|
||||
"mentions": "ხსენებები",
|
||||
"reply_sent": "პასუხი გაგზავნილია",
|
||||
"message_sent": "მესეჯი",
|
||||
"search": "ძებმა",
|
||||
"overview": "გადახედვა",
|
||||
"view": "ნახვა",
|
||||
"logout": "გასვლა",
|
||||
"login_sign_up": "შესვლა ან რეგისტრაცია",
|
||||
"login": "შესვლა",
|
||||
"sign_up": "რეგისტრაცია",
|
||||
"unread_messages": "წაუკითხავი მესეჯები",
|
||||
"messages": "მესეჯები",
|
||||
"password": "პაროლი",
|
||||
"verify_password": "პაროლის დადასტურება",
|
||||
"old_password": "ძველი პაროლი",
|
||||
"forgot_password": "აღგდენა",
|
||||
"reset_password_mail_sent": "ელ-პოსტა შეამოწმეთ",
|
||||
"password_change": "პაროლის შეცვლა",
|
||||
"new_password": "ახალი პაროლი",
|
||||
"email": "ელ-პოსტა",
|
||||
"matrix_user_id": "მატრიცული მომხმარებელი",
|
||||
"private_message_disclaimer": ".",
|
||||
"send_notifications_to_email": "შეტყობინების გაგზავნა ელ-პოსტაზე",
|
||||
"optional": "არასავალდებულო",
|
||||
"expires": "ვადა გასდის",
|
||||
"language": "ენა",
|
||||
"browser_default": "Browser Default",
|
||||
"enable_downvotes": "არმოწონების ჩართვა",
|
||||
"upvote": "მოწონება",
|
||||
"downvote": "არ მოწონება",
|
||||
"open_registration": "რეგისტრაციის გახსნა",
|
||||
"registration_closed": "რეგისტრაცია დახურულია",
|
||||
"enable_nsfw": "Enable NSFW",
|
||||
"url": "მისამართი",
|
||||
"body": "ტექსტი",
|
||||
"copy_suggested_title": "დაკოპირება რეკომინდებულის სათაური: {{title}}",
|
||||
"community": "თემა",
|
||||
"expand_here": "Expand here",
|
||||
"subscribe_to_communities": "Subscribe to some <1>communities</1>.",
|
||||
"chat": "ჩეტი",
|
||||
"recent_comments": "ბოლო კომენტარები",
|
||||
"no_results": "0 შედეგი",
|
||||
"setup": "Setup",
|
||||
"lemmy_instance_setup": "Lemmy Instance Setup",
|
||||
"setup_admin": "Set Up Site Administrator",
|
||||
"your_site": "შენი გვერდი",
|
||||
"modified": "რედაკტირებული",
|
||||
"nsfw": "NSFW",
|
||||
"notifications_error": "გთხოვთ იხმაღეთ Chome ან Firefox შეტყობინებისთვის",
|
||||
"no_email_setup": "This server hasn't correctly set up email.",
|
||||
"downvotes_disabled": "არმოწონები გამორთულია",
|
||||
"number_of_upvotes": "მოწონება",
|
||||
"number_of_upvotes_plural": "მოწონება",
|
||||
"number_of_downvotes": "არ მოწონება",
|
||||
"number_of_downvotes_plural": "არ მოწონება",
|
||||
"hot": "ცხელი",
|
||||
"new": "ახალი",
|
||||
"old": "ძველი",
|
||||
"top_day": "ტოპ დღეს",
|
||||
"week": "კვირა",
|
||||
"month": "თვე",
|
||||
"year": "წელი",
|
||||
"all": "ყველა",
|
||||
"top": "ტოპ",
|
||||
"api": "API",
|
||||
"show_nsfw": "Show NSFW content",
|
||||
"related_posts": "ეს პოსტები შეიძლება ერთმანეც ეხება",
|
||||
"cross_post": "გადაკვეთა-პოსტი",
|
||||
"general_sponsors": "General Sponsors are those that pledged $10 to $39 to Lemmy.",
|
||||
"cross_posted_to": "გადაკვეთა-პოსტი გაკეთდა: ",
|
||||
"subscribed_to_communities": "მიყვები <1>communities</1>",
|
||||
"trending_communities": "ტრენდული <1>communities</1>",
|
||||
"list_of_communities": "ყველა თემა",
|
||||
"number_of_communities": "თემა",
|
||||
"number_of_communities_plural": "თემები",
|
||||
"landing": "Lemmy is a <1>link aggregator</1> / reddit alternative, intended to work in the <2>fediverse</2>.<3></3>It's self-hostable, has live-updating comment threads, and is tiny (<4>~80kB</4>). Federation into the ActivityPub network is on the roadmap. <5></5>This is a <6>very early beta version</6>, and a lot of features are currently broken or missing. <7></7>Suggest new features or report bugs <8>here.</8><9></9>Made with <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
|
||||
"docs": "დოკუმენტაცია",
|
||||
"couldnt_like_comment": "კომენტარის მოწონება ვერ მოხერხდა.",
|
||||
"couldnt_update_comment": "კომენტარის განახლება ვერ მოხერხდა.",
|
||||
"replies": "პასუხები",
|
||||
"theme": "საიტის თემა",
|
||||
"sponsors": "სპონსორები",
|
||||
"sponsors_of_lemmy": "Sponsors",
|
||||
"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_liberapay": "Support on Liberapay",
|
||||
"donate_to_lemmy": "Donate to Lemmy",
|
||||
"no_comment_edit_allowed": "კომენტარის რედაკტირება არ შეიძლება.",
|
||||
"donate": "Donate",
|
||||
"crypto": "Crypto",
|
||||
"bitcoin": "Bitcoin",
|
||||
"ethereum": "Ethereum",
|
||||
"code": "კოდი",
|
||||
"joined": "დაემატა",
|
||||
"by": "by",
|
||||
"to": "to",
|
||||
"from": "from",
|
||||
"transfer_community": "transfer community",
|
||||
"transfer_site": "transfer site",
|
||||
"are_you_sure": "დარწმუნებული ხარ?",
|
||||
"yes": "კი",
|
||||
"no": "არა",
|
||||
"powered_by": "Powered by",
|
||||
"not_logged_in": "შასული არ ხართ",
|
||||
"logged_in": "შაული ხართ.",
|
||||
"community_ban": "შენ ამ თემისგან გაშავებული ხარ.",
|
||||
"site_ban": "საიტიდან გაშავებული ხარ.",
|
||||
"couldnt_create_comment": "კომენტარის შექმნა ვერ მოხერხდა.",
|
||||
"couldnt_find_community": "ტემა არ მოიძებნა.",
|
||||
"couldnt_save_comment": "კომენტარის შენახვა ვერ მოხერხდა.",
|
||||
"couldnt_get_comments": "კომენტარების ნახვა ვერ მოხერხდა.",
|
||||
"no_post_edit_allowed": "პოსტის რედაკტირება არ შეიძლება.",
|
||||
"no_community_edit_allowed": "თემის რედაკტირება არ შეიძლება.",
|
||||
"couldnt_update_community": "თემა ვერ განახლდა.",
|
||||
"community_already_exists": "ეს თემა უკვე არსებობს.",
|
||||
"community_follower_already_exists": "თემის ფოლოვორი უკვე არსებობს.",
|
||||
"community_user_already_banned": "თემის მომხმარებელი უკვე შავ სიაშია.",
|
||||
"couldnt_like_post": "პოსტის მოწონება ვერ მოხერხდა.",
|
||||
"community_moderator_already_exists": "ამ თემის მოდერატორი უკვე არსებობს.",
|
||||
"couldnt_create_post": "პოსტი ვერ შეიქმნა.",
|
||||
"post_title_too_long": "პოსტის სათაური ძალიან გრძელია."
|
||||
}
|
||||
|
|
251
ui/yarn.lock
vendored
251
ui/yarn.lock
vendored
|
@ -126,10 +126,55 @@
|
|||
lodash "^4.17.13"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@popperjs/core@^2.1.1":
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.1.1.tgz#12c572ab88ef7345b43f21883fca26631c223085"
|
||||
integrity sha512-sLqWxCzC5/QHLhziXSCAksBxHfOnQlhPRVgPK0egEw+ktWvG75T2k+aYWVjVh9+WKeT3tlG3ZNbZQvZLmfuOIw==
|
||||
"@fortawesome/fontawesome-common-types@^0.2.28":
|
||||
version "0.2.28"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.28.tgz#1091bdfe63b3f139441e9cba27aa022bff97d8b2"
|
||||
integrity sha512-gtis2/5yLdfI6n0ia0jH7NJs5i/Z/8M/ZbQL6jXQhCthEOe5Cr5NcQPhgTvFxNOtURE03/ZqUcEskdn2M+QaBg==
|
||||
|
||||
"@fortawesome/fontawesome-svg-core@^1.2.22":
|
||||
version "1.2.28"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.28.tgz#e5b8c8814ef375f01f5d7c132d3c3a2f83a3abf9"
|
||||
integrity sha512-4LeaNHWvrneoU0i8b5RTOJHKx7E+y7jYejplR7uSVB34+mp3Veg7cbKk7NBCLiI4TyoWS1wh9ZdoyLJR8wSAdg==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.28"
|
||||
|
||||
"@fortawesome/free-regular-svg-icons@^5.10.2":
|
||||
version "5.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.13.0.tgz#925a13d8bdda0678f71551828cac80ab47b8150c"
|
||||
integrity sha512-70FAyiS5j+ANYD4dh9NGowTorNDnyvQHHpCM7FpnF7GxtDjBUCKdrFqCPzesEIpNDFNd+La3vex+jDk4nnUfpA==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.28"
|
||||
|
||||
"@fortawesome/free-solid-svg-icons@^5.10.2":
|
||||
version "5.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.13.0.tgz#44d9118668ad96b4fd5c9434a43efc5903525739"
|
||||
integrity sha512-IHUgDJdomv6YtG4p3zl1B5wWf9ffinHIvebqQOmV3U+3SLw4fC+LUCCgwfETkbTtjy5/Qws2VoVf6z/ETQpFpg==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.28"
|
||||
|
||||
"@joeattardi/emoji-button@^2.12.1":
|
||||
version "2.12.1"
|
||||
resolved "https://registry.yarnpkg.com/@joeattardi/emoji-button/-/emoji-button-2.12.1.tgz#190df7c00721e04742ed6f8852db828798a4cf98"
|
||||
integrity sha512-rUuCXIcv4mRFK2IUKarYJN6J667wtH234smb1aQILzRf3/ycOoa6yUwnnvjxZeXMsPhuTnz15ndMOP2DhO5nNw==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-svg-core" "^1.2.22"
|
||||
"@fortawesome/free-regular-svg-icons" "^5.10.2"
|
||||
"@fortawesome/free-solid-svg-icons" "^5.10.2"
|
||||
"@popperjs/core" "^2.0.0"
|
||||
focus-trap "^5.1.0"
|
||||
tiny-emitter "^2.1.0"
|
||||
tslib "^1.10.0"
|
||||
twemoji "^12.1.5"
|
||||
|
||||
"@popperjs/core@^2.0.0":
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.2.3.tgz#0ae22b5650ab0b8fe508047245b66e71fc59e983"
|
||||
integrity sha512-68EQPzEZRrpFavFX40V2+80eqzQIhgza2AGTXW+i8laxSA4It+Y13rmZInrAYoIujp8YO7YJPbvgOesDZcIulQ==
|
||||
|
||||
"@popperjs/core@^2.2.0":
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.3.2.tgz#1e56eb99bccddbda6a3e29aa4f3660f5b23edc43"
|
||||
integrity sha512-18Tz3QghwsuHUC4gTNoxcEw1ClsrJ+lRypYpm+aucQonYNnmskQYvDZZKLHMPvQ7OwthWJl715UEX+Tg2fJkJw==
|
||||
|
||||
"@samverschueren/stream-to-observable@^0.3.0":
|
||||
version "0.3.0"
|
||||
|
@ -162,10 +207,10 @@
|
|||
dependencies:
|
||||
"@types/sizzle" "*"
|
||||
|
||||
"@types/js-cookie@^2.2.5":
|
||||
version "2.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.5.tgz#38dfaacae8623b37cc0b0d27398e574e3fc28b1e"
|
||||
integrity sha512-cpmwBRcHJmmZx0OGU7aPVwGWGbs4iKwVYchk9iuMtxNCA2zorwdaTz4GkLgs2WGxiRZRFKnV1k6tRUHX7tBMxg==
|
||||
"@types/js-cookie@^2.2.6":
|
||||
version "2.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.6.tgz#f1a1cb35aff47bc5cfb05cb0c441ca91e914c26f"
|
||||
integrity sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw==
|
||||
|
||||
"@types/json-schema@^7.0.3":
|
||||
version "7.0.4"
|
||||
|
@ -196,10 +241,10 @@
|
|||
dependencies:
|
||||
"@types/linkify-it" "*"
|
||||
|
||||
"@types/node@^13.9.2":
|
||||
version "13.9.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.2.tgz#ace1880c03594cc3e80206d96847157d8e7fa349"
|
||||
integrity sha512-bnoqK579sAYrQbp73wwglccjJ4sfRdKU7WNEZ5FW4K2U6Kc0/eZ5kvXG0JKsEKFB50zrFmfFt52/cvBbZa7eXg==
|
||||
"@types/node@^13.11.1":
|
||||
version "13.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7"
|
||||
integrity sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==
|
||||
|
||||
"@types/normalize-package-data@^2.4.0":
|
||||
version "2.4.0"
|
||||
|
@ -749,6 +794,14 @@ chalk@^3.0.0:
|
|||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72"
|
||||
integrity sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chardet@^0.4.0:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
|
||||
|
@ -790,10 +843,10 @@ class-utils@^0.3.5:
|
|||
isobject "^3.0.0"
|
||||
static-extend "^0.1.1"
|
||||
|
||||
classcat@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/classcat/-/classcat-1.1.3.tgz#ec748eecd962ec195a5d8f73f01d67c3d9040912"
|
||||
integrity sha512-nuf6HJ5RlEgUUPqN/giIy1wsfA0LJwCHpo/aMGMwEIAxYypbLW/ZdPH4SNrF+OwdrkL3wxJmAs4GPyoE3ZkQ4w==
|
||||
classcat@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/classcat/-/classcat-4.0.2.tgz#bd5d51b656e01e9cdd21c1aae3d29ed035a52126"
|
||||
integrity sha512-RlMPOPp8VDu3CJOUVorPumhz/CI+t9ft6f0uexxxCguk28/M+Kf27eQXjNWeDTisEQWei/30oDfITOQqr1TNpQ==
|
||||
|
||||
clean-css@^4.1.9:
|
||||
version "4.2.3"
|
||||
|
@ -890,10 +943,10 @@ commander@^4.0.1:
|
|||
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.0.tgz#545983a0603fe425bc672d66c9e3c89c42121a83"
|
||||
integrity sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw==
|
||||
|
||||
compare-versions@^3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.5.1.tgz#26e1f5cf0d48a77eced5046b9f67b6b61075a393"
|
||||
integrity sha512-9fGPIB7C6AyM18CJJBHt5EnCZDG3oiTJYy0NjfIAGjKpzv0tkxWko7TNQHF5ymqm7IH03tqmeuBxtvD+Izh6mg==
|
||||
compare-versions@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62"
|
||||
integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==
|
||||
|
||||
component-emitter@^1.2.1:
|
||||
version "1.3.0"
|
||||
|
@ -1283,10 +1336,10 @@ eslint-plugin-inferno@^7.14.3:
|
|||
object.values "^1.1.0"
|
||||
resolve "^1.12.0"
|
||||
|
||||
eslint-plugin-jane@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jane/-/eslint-plugin-jane-7.2.0.tgz#a2454a6700c644e6c86821ca294adf303e75eddc"
|
||||
integrity sha512-/BPZrfxWX9T45gJSf4/2GHfBYgsBYTW7StAQfxL8PxWABZIQKWPWy/5ZokX7UaJlgKHAoC42rJHCQLK5hmfJNA==
|
||||
eslint-plugin-jane@^7.2.1:
|
||||
version "7.2.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jane/-/eslint-plugin-jane-7.2.1.tgz#5ffba9ce75e0a5e5dbe3918fc0c5332d2cd89c13"
|
||||
integrity sha512-hUmhEkHTDq6lQ4oLWZV5cLut9L67fcTiy0USbTsEOx658i9Jdikedt8NJhtamRqO5OUHBGSPU0JkOqBtVNUD+A==
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin" "2.24.0"
|
||||
"@typescript-eslint/parser" "2.24.0"
|
||||
|
@ -1300,7 +1353,7 @@ eslint-plugin-jane@^7.2.0:
|
|||
eslint-plugin-prettier "3.1.2"
|
||||
eslint-plugin-promise "4.2.1"
|
||||
eslint-plugin-react "7.19.0"
|
||||
eslint-plugin-react-hooks "2.5.0"
|
||||
eslint-plugin-react-hooks "2.5.1"
|
||||
eslint-plugin-unicorn "17.2.0"
|
||||
|
||||
eslint-plugin-jest@23.8.2:
|
||||
|
@ -1349,10 +1402,10 @@ eslint-plugin-promise@4.2.1:
|
|||
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
|
||||
integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
|
||||
|
||||
eslint-plugin-react-hooks@2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.5.0.tgz#c50ab7ca5945ce6d1cf8248d9e185c80b54171b6"
|
||||
integrity sha512-bzvdX47Jx847bgAYf0FPX3u1oxU+mKU8tqrpj4UX9A96SbAmj/HVEefEy6rJUog5u8QIlOPTKZcBpGn5kkKfAQ==
|
||||
eslint-plugin-react-hooks@2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.5.1.tgz#4ef5930592588ce171abeb26f400c7fbcbc23cd0"
|
||||
integrity sha512-Y2c4b55R+6ZzwtTppKwSmK/Kar8AdLiC2f9NADCuxbcTgPPg41Gyqa6b9GppgXSvCtkRw43ZE86CT5sejKC6/g==
|
||||
|
||||
eslint-plugin-react@7.19.0:
|
||||
version "7.19.0"
|
||||
|
@ -1829,6 +1882,14 @@ fliplog@^0.3.13:
|
|||
dependencies:
|
||||
chain-able "^1.0.1"
|
||||
|
||||
focus-trap@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-5.1.0.tgz#64a0bfabd95c382103397dbc96bfef3a3cf8e5ad"
|
||||
integrity sha512-CkB/nrO55069QAUjWFBpX6oc+9V90Qhgpe6fBWApzruMq5gnlh90Oo7iSSDK7pKiV5ugG6OY2AXM5mxcmL3lwQ==
|
||||
dependencies:
|
||||
tabbable "^4.0.0"
|
||||
xtend "^4.0.1"
|
||||
|
||||
for-in@^1.0.1, for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
|
@ -2182,14 +2243,14 @@ human-signals@^1.1.1:
|
|||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
|
||||
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
|
||||
|
||||
husky@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.3.tgz#3b18d2ee5febe99e27f2983500202daffbc3151e"
|
||||
integrity sha512-VxTsSTRwYveKXN4SaH1/FefRJYCtx+wx04sSVcOpD7N2zjoHxa+cEJ07Qg5NmV3HAK+IRKOyNVpi2YBIVccIfQ==
|
||||
husky@^4.2.5:
|
||||
version "4.2.5"
|
||||
resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36"
|
||||
integrity sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==
|
||||
dependencies:
|
||||
chalk "^3.0.0"
|
||||
chalk "^4.0.0"
|
||||
ci-info "^2.0.0"
|
||||
compare-versions "^3.5.1"
|
||||
compare-versions "^3.6.0"
|
||||
cosmiconfig "^6.0.0"
|
||||
find-versions "^3.2.0"
|
||||
opencollective-postinstall "^2.0.2"
|
||||
|
@ -2198,10 +2259,10 @@ husky@^4.2.3:
|
|||
slash "^3.0.0"
|
||||
which-pm-runs "^1.0.0"
|
||||
|
||||
i18next@^19.3.3:
|
||||
version "19.3.3"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.3.3.tgz#04bd79b315e5fe2c87ab8f411e5d55eda0a17bd8"
|
||||
integrity sha512-CnuPqep5/JsltkGvQqzYN4d79eCe0TreCBRF3a8qHHi8x4SON1qqZ/pvR2X7BfNkNqpA5HXIqw0E731H+VsgSg==
|
||||
i18next@^19.4.1:
|
||||
version "19.4.1"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.4.1.tgz#4929d15d3d01e4712350a368d005cefa50ff5455"
|
||||
integrity sha512-dC3ue15jkLebN2je4xEjfjVYd/fSAo+UVK9f+JxvceCJRowkI+S0lGohgKejqU+FYLfvw9IAPylIIEWwR8Djrg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
|
||||
|
@ -2814,10 +2875,10 @@ linkify-it@^2.0.0:
|
|||
dependencies:
|
||||
uc.micro "^1.0.1"
|
||||
|
||||
lint-staged@^10.0.8:
|
||||
version "10.0.8"
|
||||
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.0.8.tgz#0f7849cdc336061f25f5d4fcbcfa385701ff4739"
|
||||
integrity sha512-Oa9eS4DJqvQMVdywXfEor6F4vP+21fPHF8LUXgBbVWUSWBddjqsvO6Bv1LwMChmgQZZqwUvgJSHlu8HFHAPZmA==
|
||||
lint-staged@^10.1.3:
|
||||
version "10.1.3"
|
||||
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.1.3.tgz#da27713d3ac519da305381b4de87d5f866b1d2f1"
|
||||
integrity sha512-o2OkLxgVns5RwSC5QF7waeAjJA5nz5gnUfqL311LkZcFipKV7TztrSlhNUK5nQX9H0E5NELAdduMQ+M/JPT7RQ==
|
||||
dependencies:
|
||||
chalk "^3.0.0"
|
||||
commander "^4.0.1"
|
||||
|
@ -3612,10 +3673,10 @@ prettier-linter-helpers@^1.0.0:
|
|||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@^1.18.2:
|
||||
version "1.19.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
|
||||
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
|
||||
prettier@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.4.tgz#2d1bae173e355996ee355ec9830a7a1ee05457ef"
|
||||
integrity sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w==
|
||||
|
||||
pretty-time@^0.2.0:
|
||||
version "0.2.0"
|
||||
|
@ -4001,13 +4062,20 @@ rx-lite@*, rx-lite@^4.0.8:
|
|||
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
|
||||
integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=
|
||||
|
||||
rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.3:
|
||||
rxjs@^6.3.3, rxjs@^6.5.3:
|
||||
version "6.5.4"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
|
||||
integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
rxjs@^6.5.5:
|
||||
version "6.5.5"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec"
|
||||
integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
|
@ -4197,10 +4265,10 @@ snapdragon@^0.8.1:
|
|||
source-map-resolve "^0.5.0"
|
||||
use "^3.1.0"
|
||||
|
||||
sortpack@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/sortpack/-/sortpack-2.1.2.tgz#25bf86f2923c81f43a00a2166ff4d271fafeed11"
|
||||
integrity sha512-43fSND1vmAdyfgC38aOkVxZBV331f4blF8acjwQmx7Gba4nuL2ene/Cq5eixNmDhKA/qQHnvSeAl+jEWb31rfg==
|
||||
sortpack@^2.1.4:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/sortpack/-/sortpack-2.1.4.tgz#a2e251c5868455135cc41d3c98a53756a6de5282"
|
||||
integrity sha512-RGD0l9kGmuPelXMT8WMMiSv1MkUkaqElB39nMkboIaqVkYns1aaNx263B2EE5QzF1YVUOrBlXnQpd7RX68SSow==
|
||||
|
||||
source-map-resolve@^0.5.0:
|
||||
version "0.5.3"
|
||||
|
@ -4473,6 +4541,11 @@ symbol-observable@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
|
||||
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
|
||||
|
||||
tabbable@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-4.0.0.tgz#5bff1d1135df1482cf0f0206434f15eadbeb9261"
|
||||
integrity sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ==
|
||||
|
||||
table@^5.2.3:
|
||||
version "5.4.6"
|
||||
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
|
||||
|
@ -4483,10 +4556,10 @@ table@^5.2.3:
|
|||
slice-ansi "^2.1.0"
|
||||
string-width "^3.0.0"
|
||||
|
||||
terser@^4.6.7:
|
||||
version "4.6.7"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.7.tgz#478d7f9394ec1907f0e488c5f6a6a9a2bad55e72"
|
||||
integrity sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g==
|
||||
terser@^4.6.11:
|
||||
version "4.6.11"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.11.tgz#12ff99fdd62a26de2a82f508515407eb6ccd8a9f"
|
||||
integrity sha512-76Ynm7OXUG5xhOpblhytE7X58oeNSmC8xnNhjWVo8CksHit0U0kO4hfNbPrrYwowLWFgM2n9L176VNx2QaHmtA==
|
||||
dependencies:
|
||||
commander "^2.20.0"
|
||||
source-map "~0.6.1"
|
||||
|
@ -4502,6 +4575,11 @@ through@^2.3.6:
|
|||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||
|
||||
tiny-emitter@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
|
||||
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
|
||||
|
||||
tiny-invariant@^1.0.2:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
|
||||
|
@ -4512,12 +4590,12 @@ tiny-warning@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
|
||||
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
|
||||
|
||||
tippy.js@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.1.0.tgz#9c58b94f92f3044d5e861b9d83da3c2a6d3d4323"
|
||||
integrity sha512-cRFydlVZlvo4soQSUfVNbH2K77zDUhDAzaAjxseyn81gGIa+j72y98yDL2yB0n8gas/E+Zlr1iOyR5ckslUFqA==
|
||||
tippy.js@^6.1.1:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.1.1.tgz#9ed09aa4f9c47fb06a0e280e03055f898f5ddfff"
|
||||
integrity sha512-Sk+FPihack9XFbPOc2jRbn6iRLA9my2a8qhaGY6wwD3EeW57/xY5PAPkZOutKVYDWLyNZ/laCkJqg7QJG/gqQw==
|
||||
dependencies:
|
||||
"@popperjs/core" "^2.1.1"
|
||||
"@popperjs/core" "^2.2.0"
|
||||
|
||||
tmp@^0.0.33:
|
||||
version "0.0.33"
|
||||
|
@ -4581,15 +4659,15 @@ tough-cookie@~2.4.3:
|
|||
psl "^1.1.24"
|
||||
punycode "^1.4.1"
|
||||
|
||||
tributejs@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/tributejs/-/tributejs-5.1.2.tgz#d8492d974d3098d6016248d689fb063cda6e77f7"
|
||||
integrity sha512-R9ff/q6w4T5f3Y9+RL+qinog3X1eAj1UnR/yfZaGJ8D3wuJs4/vicrGYul9+fgS9EJ/iYgwARekTb92xwark0g==
|
||||
tributejs@^5.1.3:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/tributejs/-/tributejs-5.1.3.tgz#980600fc72865be5868893078b4bfde721129eae"
|
||||
integrity sha512-B5CXihaVzXw+1UHhNFyAwUTMDk1EfoLP5Tj1VhD9yybZ1I8DZJEv8tZ1l0RJo0t0tk9ZhR8eG5tEsaCvRigmdQ==
|
||||
|
||||
ts-node@^8.7.0:
|
||||
version "8.7.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.7.0.tgz#266186947596bef9f3a034687595b30e31b20976"
|
||||
integrity sha512-s659CsHrsxaRVDEleuOkGvbsA0rWHtszUNEt1r0CgAFN5ZZTQtDzpsluS7W5pOGJIa1xZE8R/zK4dEs+ldFezg==
|
||||
ts-node@^8.8.2:
|
||||
version "8.8.2"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.8.2.tgz#0b39e690bee39ea5111513a9d2bcdc0bc121755f"
|
||||
integrity sha512-duVj6BpSpUpD/oM4MfhO98ozgkp3Gt9qIp3jGxwU2DFvl/3IRaEAvbLa8G60uS7C77457e/m5TMowjedeRxI1Q==
|
||||
dependencies:
|
||||
arg "^4.1.0"
|
||||
diff "^4.0.1"
|
||||
|
@ -4597,17 +4675,20 @@ ts-node@^8.7.0:
|
|||
source-map-support "^0.5.6"
|
||||
yn "3.1.1"
|
||||
|
||||
ts-transform-classcat@^0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ts-transform-classcat/-/ts-transform-classcat-0.0.2.tgz#2386c9418f3a7c1f03261ff51225b70d0a7664fb"
|
||||
integrity sha512-7laOOhgVxWVqvhK10mIEfedJx2nnNOS8J4P/6a/ehXtHFvsBVRRS9/FcTifgzJweOScZsF5BRD5VOGeNidMSqQ==
|
||||
dependencies:
|
||||
typescript "^2.6.2"
|
||||
ts-transform-classcat@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-transform-classcat/-/ts-transform-classcat-1.0.0.tgz#6ae1be1b32f1f3c6b1c4232daf8a28e3ced0b62f"
|
||||
integrity sha512-LWXEYvBwHDOqBBtoDWSUmbPMsw8FI9vD4XZm98RgziN9UCIj5MRtpmXuP5YYoimCTlPU+D4TFR3IqS+5xSzWsQ==
|
||||
|
||||
ts-transform-inferno@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ts-transform-inferno/-/ts-transform-inferno-4.0.2.tgz#06b9be45edf874ba7a6ebfb6107ba782509c6afe"
|
||||
integrity sha512-CZb4+w/2l2zikPZ/c51fi3n+qnR2HCEfAS73oGQB80aqRLffkZqm25kYYTMmqUW2+oVfs4M5AZa0z14cvxlQ5w==
|
||||
ts-transform-inferno@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/ts-transform-inferno/-/ts-transform-inferno-4.0.3.tgz#2cc0eb125abdaff24b8298106a618ab7c6319edc"
|
||||
integrity sha512-Pcg0PVQwJ7Fpv4+3R9obFNsrNKQyLbmUqsjeG7T7r4/4UTgIl0MSwurexjtuGpCp2iv2X/i9ffKPAfAOyYJ9og==
|
||||
|
||||
tslib@^1.10.0:
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
|
||||
integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==
|
||||
|
||||
tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0:
|
||||
version "1.10.0"
|
||||
|
@ -4638,7 +4719,7 @@ twemoji-parser@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:
|
||||
twemoji@^12.1.2, twemoji@^12.1.5:
|
||||
version "12.1.5"
|
||||
resolved "https://registry.yarnpkg.com/twemoji/-/twemoji-12.1.5.tgz#a961fb65a1afcb1f729ad7e59391f9fe969820b9"
|
||||
integrity sha512-B0PBVy5xomwb1M/WZxf/IqPZfnoIYy1skXnlHjMwLwTNfZ9ljh8VgWQktAPcJXu8080WoEh6YwQGPVhDVqvrVQ==
|
||||
|
@ -4673,11 +4754,6 @@ type-is@~1.6.17, type-is@~1.6.18:
|
|||
media-typer "0.3.0"
|
||||
mime-types "~2.1.24"
|
||||
|
||||
typescript@^2.6.2:
|
||||
version "2.9.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
|
||||
integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==
|
||||
|
||||
typescript@^3.8.3:
|
||||
version "3.8.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061"
|
||||
|
@ -4890,6 +4966,11 @@ xregexp@^4.3.0:
|
|||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.8.3"
|
||||
|
||||
xtend@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
yaml@^1.7.2:
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.7.2.tgz#f26aabf738590ab61efaca502358e48dc9f348b2"
|
||||
|
|
Loading…
Reference in a new issue