Merge branch 'main' into federation_tests_reorg
This commit is contained in:
commit
1a5a28adc4
63 changed files with 448 additions and 247 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -16,5 +16,6 @@ ui/src/translations
|
||||||
|
|
||||||
# ide config
|
# ide config
|
||||||
.idea/
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
target
|
target
|
||||||
|
|
2
ansible/VERSION
vendored
2
ansible/VERSION
vendored
|
@ -1 +1 @@
|
||||||
v0.7.33
|
v0.7.39
|
||||||
|
|
3
ansible/templates/nginx.conf
vendored
3
ansible/templates/nginx.conf
vendored
|
@ -60,6 +60,9 @@ server {
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
# Cuts off the trailing slash on URLs to make them valid
|
||||||
|
rewrite ^(.+)/+$ $1 permanent;
|
||||||
|
|
||||||
# WebSocket support
|
# WebSocket support
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
|
9
docker/federation/nginx.conf
vendored
9
docker/federation/nginx.conf
vendored
|
@ -17,6 +17,9 @@ http {
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
# Cuts off the trailing slash on URLs to make them valid
|
||||||
|
rewrite ^(.+)/+$ $1 permanent;
|
||||||
|
|
||||||
# WebSocket support
|
# WebSocket support
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
@ -57,6 +60,9 @@ http {
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
# Cuts off the trailing slash on URLs to make them valid
|
||||||
|
rewrite ^(.+)/+$ $1 permanent;
|
||||||
|
|
||||||
# WebSocket support
|
# WebSocket support
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
@ -97,6 +103,9 @@ http {
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
# Cuts off the trailing slash on URLs to make them valid
|
||||||
|
rewrite ^(.+)/+$ $1 permanent;
|
||||||
|
|
||||||
# WebSocket support
|
# WebSocket support
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
|
2
docker/prod/docker-compose.yml
vendored
2
docker/prod/docker-compose.yml
vendored
|
@ -12,7 +12,7 @@ services:
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
lemmy:
|
lemmy:
|
||||||
image: dessalines/lemmy:v0.7.33
|
image: dessalines/lemmy:v0.7.39
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:8536:8536"
|
- "127.0.0.1:8536:8536"
|
||||||
restart: always
|
restart: always
|
||||||
|
|
4
docker/travis/docker_push.sh
vendored
4
docker/travis/docker_push.sh
vendored
|
@ -1,5 +1,5 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||||
docker tag dessalines/lemmy:travis \
|
docker tag dessalines/lemmy:travis \
|
||||||
dessalines/lemmy:v0.7.33
|
dessalines/lemmy:v0.7.39
|
||||||
docker push dessalines/lemmy:v0.7.33
|
docker push dessalines/lemmy:v0.7.39
|
||||||
|
|
43
docs/src/administration_backup_and_restore.md
vendored
43
docs/src/administration_backup_and_restore.md
vendored
|
@ -9,14 +9,14 @@ When using docker or ansible, there should be a `volumes` folder, which contains
|
||||||
To incrementally backup the DB to an `.sql` file, you can run:
|
To incrementally backup the DB to an `.sql` file, you can run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker exec -t FOLDERNAME_postgres_1 pg_dumpall -c -U lemmy > lemmy_dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql
|
docker-compose exec postgres pg_dumpall -c -U lemmy > lemmy_dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql
|
||||||
```
|
```
|
||||||
### A Sample backup script
|
### A Sample backup script
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# DB Backup
|
# DB Backup
|
||||||
ssh MY_USER@MY_IP "docker exec -t FOLDERNAME_postgres_1 pg_dumpall -c -U lemmy" > ~/BACKUP_LOCATION/INSTANCE_NAME_dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql
|
ssh MY_USER@MY_IP "docker-compose exec postgres pg_dumpall -c -U lemmy" > ~/BACKUP_LOCATION/INSTANCE_NAME_dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql
|
||||||
|
|
||||||
# Volumes folder Backup
|
# Volumes folder Backup
|
||||||
rsync -avP -zz --rsync-path="sudo rsync" MY_USER@MY_IP:/LEMMY_LOCATION/volumes ~/BACKUP_LOCATION/FOLDERNAME
|
rsync -avP -zz --rsync-path="sudo rsync" MY_USER@MY_IP:/LEMMY_LOCATION/volumes ~/BACKUP_LOCATION/FOLDERNAME
|
||||||
|
@ -37,6 +37,45 @@ cat db_dump.sql | docker exec -i FOLDERNAME_postgres_1 psql -U lemmy # restore
|
||||||
docker exec -i FOLDERNAME_postgres_1 psql -U lemmy -c "alter user lemmy with password 'bleh'"
|
docker exec -i FOLDERNAME_postgres_1 psql -U lemmy -c "alter user lemmy with password 'bleh'"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Changing your domain name
|
||||||
|
|
||||||
|
If you haven't federated yet, you can change your domain name in the DB. **Warning: do not do this after you've federated, or it will break federation.**
|
||||||
|
|
||||||
|
Get into `psql` for your docker:
|
||||||
|
|
||||||
|
`docker-compose exec postgres psql -U lemmy`
|
||||||
|
|
||||||
|
```
|
||||||
|
-- Post
|
||||||
|
update post set ap_id = replace (ap_id, 'old_domain', 'new_domain');
|
||||||
|
update post set url = replace (url, 'old_domain', 'new_domain');
|
||||||
|
update post set body = replace (body, 'old_domain', 'new_domain');
|
||||||
|
update post set thumbnail_url = replace (thumbnail_url, 'old_domain', 'new_domain');
|
||||||
|
|
||||||
|
delete from post_aggregates_fast;
|
||||||
|
insert into post_aggregates_fast select * from post_aggregates_view;
|
||||||
|
|
||||||
|
-- Comments
|
||||||
|
update comment set ap_id = replace (ap_id, 'old_domain', 'new_domain');
|
||||||
|
update comment set content = replace (content, 'old_domain', 'new_domain');
|
||||||
|
|
||||||
|
delete from comment_aggregates_fast;
|
||||||
|
insert into comment_aggregates_fast select * from comment_aggregates_view;
|
||||||
|
|
||||||
|
-- User
|
||||||
|
update user_ set actor_id = replace (actor_id, 'old_domain', 'new_domain');
|
||||||
|
update user_ set avatar = replace (avatar, 'old_domain', 'new_domain');
|
||||||
|
|
||||||
|
delete from user_fast;
|
||||||
|
insert into user_fast select * from user_view;
|
||||||
|
|
||||||
|
-- Community
|
||||||
|
update community set actor_id = replace (actor_id, 'old_domain', 'new_domain');
|
||||||
|
|
||||||
|
delete from community_aggregates_fast;
|
||||||
|
insert into community_aggregates_fast select * from community_aggregates_view;
|
||||||
|
```
|
||||||
|
|
||||||
## More resources
|
## More resources
|
||||||
|
|
||||||
- https://stackoverflow.com/questions/24718706/backup-restore-a-dockerized-postgresql-database
|
- https://stackoverflow.com/questions/24718706/backup-restore-a-dockerized-postgresql-database
|
||||||
|
|
2
docs/src/contributing_local_development.md
vendored
2
docs/src/contributing_local_development.md
vendored
|
@ -1,7 +1,7 @@
|
||||||
### Install build requirements
|
### Install build requirements
|
||||||
#### Ubuntu
|
#### Ubuntu
|
||||||
```
|
```
|
||||||
sudo apt install git cargo libssl-dev pkg-config libpq-dev yarn curl gnupg2
|
sudo apt install git cargo libssl-dev pkg-config libpq-dev yarn curl gnupg2 espeak
|
||||||
# install yarn
|
# install yarn
|
||||||
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
||||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
||||||
|
|
73
server/Cargo.lock
generated
vendored
73
server/Cargo.lock
generated
vendored
|
@ -1,19 +1,10 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "activitystreams-ext"
|
name = "activitystreams"
|
||||||
version = "0.1.0"
|
version = "0.7.0-alpha.3"
|
||||||
source = "git+https://yerbamate.dev/asonix/activitystreams-ext?branch=main#2799a4c606467a2f577e1f45f93c6828ec83cfdf"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
checksum = "e3490e8e9d7744aada19fb2fb4e2564f8c22fd080a3561093ac91ed7d10bfe78"
|
||||||
"activitystreams-new",
|
|
||||||
"serde 1.0.114",
|
|
||||||
"serde_json",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "activitystreams-new"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "git+https://yerbamate.dev/asonix/activitystreams-new?branch=main#857d5167dfa13054dd0d21d3d54f8147eea0d546"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"mime",
|
"mime",
|
||||||
|
@ -23,6 +14,17 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "activitystreams-ext"
|
||||||
|
version = "0.1.0-alpha.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb8e19a0810cc25df3535061a08b7d8f8a734d309ea4411c57a9767e4a2ffa0e"
|
||||||
|
dependencies = [
|
||||||
|
"activitystreams",
|
||||||
|
"serde 1.0.114",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix"
|
name = "actix"
|
||||||
version = "0.10.0-alpha.3"
|
version = "0.10.0-alpha.3"
|
||||||
|
@ -406,6 +408,12 @@ dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
version = "0.4.7"
|
version = "0.4.7"
|
||||||
|
@ -1194,28 +1202,6 @@ dependencies = [
|
||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "failure"
|
|
||||||
version = "0.1.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
|
|
||||||
dependencies = [
|
|
||||||
"backtrace",
|
|
||||||
"failure_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "failure_derive"
|
|
||||||
version = "0.1.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"synstructure",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fake-simd"
|
name = "fake-simd"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
@ -1726,13 +1712,14 @@ dependencies = [
|
||||||
name = "lemmy_server"
|
name = "lemmy_server"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"activitystreams",
|
||||||
"activitystreams-ext",
|
"activitystreams-ext",
|
||||||
"activitystreams-new",
|
|
||||||
"actix",
|
"actix",
|
||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"actix-web-actors",
|
"actix-web-actors",
|
||||||
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"awc",
|
"awc",
|
||||||
"base64 0.12.3",
|
"base64 0.12.3",
|
||||||
|
@ -1743,7 +1730,6 @@ dependencies = [
|
||||||
"diesel_migrations",
|
"diesel_migrations",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"failure",
|
|
||||||
"futures",
|
"futures",
|
||||||
"http",
|
"http",
|
||||||
"http-signature-normalization-actix",
|
"http-signature-normalization-actix",
|
||||||
|
@ -1762,6 +1748,7 @@ dependencies = [
|
||||||
"sha2",
|
"sha2",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
"uuid 0.8.1",
|
"uuid 0.8.1",
|
||||||
|
@ -3116,18 +3103,6 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "synstructure"
|
|
||||||
version = "0.12.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"unicode-xid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.1.0"
|
version = "3.1.0"
|
||||||
|
|
7
server/Cargo.toml
vendored
7
server/Cargo.toml
vendored
|
@ -18,12 +18,11 @@ lemmy_db = { path = "./lemmy_db" }
|
||||||
diesel = "1.4.4"
|
diesel = "1.4.4"
|
||||||
diesel_migrations = "1.4.0"
|
diesel_migrations = "1.4.0"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
activitystreams-new = { git = "https://yerbamate.dev/asonix/activitystreams-new", branch = "main" }
|
activitystreams = "0.7.0-alpha.3"
|
||||||
activitystreams-ext = { git = "https://yerbamate.dev/asonix/activitystreams-ext", branch = "main" }
|
activitystreams-ext = "0.1.0-alpha.2"
|
||||||
bcrypt = "0.8.0"
|
bcrypt = "0.8.0"
|
||||||
chrono = { version = "0.4.7", features = ["serde"] }
|
chrono = { version = "0.4.7", features = ["serde"] }
|
||||||
serde_json = { version = "1.0.52", features = ["preserve_order"]}
|
serde_json = { version = "1.0.52", features = ["preserve_order"]}
|
||||||
failure = "0.1.8"
|
|
||||||
serde = { version = "1.0.105", features = ["derive"] }
|
serde = { version = "1.0.105", features = ["derive"] }
|
||||||
actix = "0.10.0-alpha.2"
|
actix = "0.10.0-alpha.2"
|
||||||
actix-web = { version = "3.0.0-alpha.3", features = ["rustls"] }
|
actix-web = { version = "3.0.0-alpha.3", features = ["rustls"] }
|
||||||
|
@ -52,3 +51,5 @@ uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||||
sha2 = "0.9"
|
sha2 = "0.9"
|
||||||
async-trait = "0.1.36"
|
async-trait = "0.1.36"
|
||||||
captcha = "0.0.7"
|
captcha = "0.0.7"
|
||||||
|
anyhow = "1.0.32"
|
||||||
|
thiserror = "1.0.20"
|
||||||
|
|
|
@ -76,6 +76,9 @@ impl Post {
|
||||||
use crate::schema::post::dsl::*;
|
use crate::schema::post::dsl::*;
|
||||||
post
|
post
|
||||||
.filter(community_id.eq(the_community_id))
|
.filter(community_id.eq(the_community_id))
|
||||||
|
.then_order_by(published.desc())
|
||||||
|
.then_order_by(stickied.desc())
|
||||||
|
.limit(20)
|
||||||
.load::<Self>(conn)
|
.load::<Self>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ use lemmy_db::{
|
||||||
user_view::*,
|
user_view::*,
|
||||||
Crud,
|
Crud,
|
||||||
};
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
pub mod claims;
|
pub mod claims;
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
|
@ -17,8 +18,8 @@ pub mod post;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
||||||
#[derive(Fail, Debug)]
|
#[derive(Debug, Error)]
|
||||||
#[fail(display = "{{\"error\":\"{}\"}}", message)]
|
#[error("{{\"error\":\"{message}\"}}")]
|
||||||
pub struct APIError {
|
pub struct APIError {
|
||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,6 +97,7 @@ pub struct SaveUserSettings {
|
||||||
lang: String,
|
lang: String,
|
||||||
avatar: Option<String>,
|
avatar: Option<String>,
|
||||||
email: Option<String>,
|
email: Option<String>,
|
||||||
|
bio: Option<String>,
|
||||||
matrix_user_id: Option<String>,
|
matrix_user_id: Option<String>,
|
||||||
new_password: Option<String>,
|
new_password: Option<String>,
|
||||||
new_password_verify: Option<String>,
|
new_password_verify: Option<String>,
|
||||||
|
@ -557,6 +558,17 @@ impl Perform for Oper<SaveUserSettings> {
|
||||||
None => read_user.email,
|
None => read_user.email,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let bio = match &data.bio {
|
||||||
|
Some(bio) => {
|
||||||
|
if bio.chars().count() <= 300 {
|
||||||
|
Some(bio.to_owned())
|
||||||
|
} else {
|
||||||
|
return Err(APIError::err("bio_length_overflow").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => read_user.bio,
|
||||||
|
};
|
||||||
|
|
||||||
let avatar = match &data.avatar {
|
let avatar = match &data.avatar {
|
||||||
Some(avatar) => Some(avatar.to_owned()),
|
Some(avatar) => Some(avatar.to_owned()),
|
||||||
None => read_user.avatar,
|
None => read_user.avatar,
|
||||||
|
@ -613,7 +625,7 @@ impl Perform for Oper<SaveUserSettings> {
|
||||||
show_avatars: data.show_avatars,
|
show_avatars: data.show_avatars,
|
||||||
send_notifications_to_email: data.send_notifications_to_email,
|
send_notifications_to_email: data.send_notifications_to_email,
|
||||||
actor_id: read_user.actor_id,
|
actor_id: read_user.actor_id,
|
||||||
bio: read_user.bio,
|
bio,
|
||||||
local: read_user.local,
|
local: read_user.local,
|
||||||
private_key: read_user.private_key,
|
private_key: read_user.private_key,
|
||||||
public_key: read_user.public_key,
|
public_key: read_user.public_key,
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_new::base::AnyBase;
|
use activitystreams::base::AnyBase;
|
||||||
use actix_web::client::Client;
|
use actix_web::client::Client;
|
||||||
use lemmy_db::{community::Community, user::User_};
|
use lemmy_db::{community::Community, user::User_};
|
||||||
use lemmy_utils::{get_apub_protocol_string, settings::Settings};
|
use lemmy_utils::{get_apub_protocol_string, settings::Settings};
|
||||||
|
|
|
@ -21,7 +21,7 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_new::{
|
use activitystreams::{
|
||||||
activity::{
|
activity::{
|
||||||
kind::{CreateType, DeleteType, DislikeType, LikeType, RemoveType, UndoType, UpdateType},
|
kind::{CreateType, DeleteType, DislikeType, LikeType, RemoveType, UndoType, UpdateType},
|
||||||
Create,
|
Create,
|
||||||
|
|
|
@ -18,8 +18,7 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_ext::Ext2;
|
use activitystreams::{
|
||||||
use activitystreams_new::{
|
|
||||||
activity::{
|
activity::{
|
||||||
kind::{AcceptType, AnnounceType, DeleteType, LikeType, RemoveType, UndoType},
|
kind::{AcceptType, AnnounceType, DeleteType, LikeType, RemoveType, UndoType},
|
||||||
Accept,
|
Accept,
|
||||||
|
@ -31,18 +30,20 @@ use activitystreams_new::{
|
||||||
},
|
},
|
||||||
actor::{kind::GroupType, ApActor, Endpoints, Group},
|
actor::{kind::GroupType, ApActor, Endpoints, Group},
|
||||||
base::{AnyBase, BaseExt},
|
base::{AnyBase, BaseExt},
|
||||||
collection::UnorderedCollection,
|
collection::{OrderedCollection, UnorderedCollection},
|
||||||
context,
|
context,
|
||||||
object::Tombstone,
|
object::Tombstone,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
public,
|
public,
|
||||||
};
|
};
|
||||||
|
use activitystreams_ext::Ext2;
|
||||||
use actix_web::{body::Body, client::Client, web, HttpResponse};
|
use actix_web::{body::Body, client::Client, web, HttpResponse};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
community::{Community, CommunityForm},
|
community::{Community, CommunityForm},
|
||||||
community_view::{CommunityFollowerView, CommunityModeratorView},
|
community_view::{CommunityFollowerView, CommunityModeratorView},
|
||||||
naive_now,
|
naive_now,
|
||||||
|
post::Post,
|
||||||
user::User_,
|
user::User_,
|
||||||
};
|
};
|
||||||
use lemmy_utils::convert_datetime;
|
use lemmy_utils::convert_datetime;
|
||||||
|
@ -88,10 +89,10 @@ impl ToApub for Community {
|
||||||
group.set_content(d);
|
group.set_content(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ap_actor = ApActor::new(self.get_inbox_url().parse()?, group);
|
let mut ap_actor = ApActor::new(self.get_inbox_url()?, group);
|
||||||
ap_actor
|
ap_actor
|
||||||
.set_preferred_username(self.title.to_owned())
|
.set_preferred_username(self.title.to_owned())
|
||||||
.set_outbox(self.get_outbox_url().parse()?)
|
.set_outbox(self.get_outbox_url()?)
|
||||||
.set_followers(self.get_followers_url().parse()?)
|
.set_followers(self.get_followers_url().parse()?)
|
||||||
.set_following(self.get_following_url().parse()?)
|
.set_following(self.get_following_url().parse()?)
|
||||||
.set_liked(self.get_liked_url().parse()?)
|
.set_liked(self.get_liked_url().parse()?)
|
||||||
|
@ -402,7 +403,7 @@ pub async fn get_apub_community_followers(
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
let mut collection = UnorderedCollection::new(vec![]);
|
let mut collection = UnorderedCollection::new();
|
||||||
collection
|
collection
|
||||||
.set_context(context())
|
.set_context(context())
|
||||||
// TODO: this needs its own ID
|
// TODO: this needs its own ID
|
||||||
|
@ -411,6 +412,36 @@ pub async fn get_apub_community_followers(
|
||||||
Ok(create_apub_response(&collection))
|
Ok(create_apub_response(&collection))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_apub_community_outbox(
|
||||||
|
info: web::Path<CommunityQuery>,
|
||||||
|
db: DbPoolParam,
|
||||||
|
) -> Result<HttpResponse<Body>, LemmyError> {
|
||||||
|
let community = blocking(&db, move |conn| {
|
||||||
|
Community::read_from_name(&conn, &info.community_name)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let community_id = community.id;
|
||||||
|
let posts = blocking(&db, move |conn| {
|
||||||
|
Post::list_for_community(conn, community_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let mut pages: Vec<AnyBase> = vec![];
|
||||||
|
for p in posts {
|
||||||
|
pages.push(p.to_apub(&db).await?.into_any_base()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = pages.len();
|
||||||
|
let mut collection = OrderedCollection::new();
|
||||||
|
collection
|
||||||
|
.set_many_items(pages)
|
||||||
|
.set_context(context())
|
||||||
|
.set_id(community.get_outbox_url()?)
|
||||||
|
.set_total_items(len as u64);
|
||||||
|
Ok(create_apub_response(&collection))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn do_announce(
|
pub async fn do_announce(
|
||||||
activity: AnyBase,
|
activity: AnyBase,
|
||||||
community: &Community,
|
community: &Community,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::LemmyError;
|
use crate::LemmyError;
|
||||||
|
use activitystreams::unparsed::UnparsedMutExt;
|
||||||
use activitystreams_ext::UnparsedExtension;
|
use activitystreams_ext::UnparsedExtension;
|
||||||
use activitystreams_new::unparsed::UnparsedMutExt;
|
|
||||||
use diesel::PgConnection;
|
use diesel::PgConnection;
|
||||||
use lemmy_db::{category::Category, Crud};
|
use lemmy_db::{category::Category, Crud};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
use activitystreams::unparsed::UnparsedMutExt;
|
||||||
use activitystreams_ext::UnparsedExtension;
|
use activitystreams_ext::UnparsedExtension;
|
||||||
use activitystreams_new::unparsed::UnparsedMutExt;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::{apub::ActorType, LemmyError};
|
use crate::{apub::ActorType, LemmyError};
|
||||||
|
use activitystreams::unparsed::UnparsedMutExt;
|
||||||
use activitystreams_ext::UnparsedExtension;
|
use activitystreams_ext::UnparsedExtension;
|
||||||
use activitystreams_new::unparsed::UnparsedMutExt;
|
|
||||||
use actix_web::{client::ClientRequest, HttpRequest};
|
use actix_web::{client::ClientRequest, HttpRequest};
|
||||||
|
use anyhow::anyhow;
|
||||||
use http_signature_normalization_actix::{
|
use http_signature_normalization_actix::{
|
||||||
digest::{DigestClient, SignExt},
|
digest::{DigestClient, SignExt},
|
||||||
Config,
|
Config,
|
||||||
|
@ -70,7 +71,7 @@ pub fn verify(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), LemmyE
|
||||||
debug!("verified signature for {}", &request.uri());
|
debug!("verified signature for {}", &request.uri());
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(format_err!("Invalid signature on request: {}", &request.uri()).into())
|
Err(anyhow!("Invalid signature on request: {}", &request.uri()).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,9 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_new::{base::BaseExt, object::Note, prelude::*};
|
use activitystreams::{base::BaseExt, collection::OrderedCollection, object::Note, prelude::*};
|
||||||
use actix_web::client::Client;
|
use actix_web::client::Client;
|
||||||
|
use anyhow::anyhow;
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{result::Error::NotFound, PgConnection};
|
use diesel::{result::Error::NotFound, PgConnection};
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
|
@ -40,6 +41,7 @@ use std::{fmt::Debug, time::Duration};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
static ACTOR_REFETCH_INTERVAL_SECONDS: i64 = 24 * 60 * 60;
|
static ACTOR_REFETCH_INTERVAL_SECONDS: i64 = 24 * 60 * 60;
|
||||||
|
static ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG: i64 = 10;
|
||||||
|
|
||||||
// Fetch nodeinfo metadata from a remote instance.
|
// Fetch nodeinfo metadata from a remote instance.
|
||||||
async fn _fetch_node_info(client: &Client, domain: &str) -> Result<NodeInfo, LemmyError> {
|
async fn _fetch_node_info(client: &Client, domain: &str) -> Result<NodeInfo, LemmyError> {
|
||||||
|
@ -65,7 +67,7 @@ where
|
||||||
Response: for<'de> Deserialize<'de>,
|
Response: for<'de> Deserialize<'de>,
|
||||||
{
|
{
|
||||||
if !is_apub_id_valid(&url) {
|
if !is_apub_id_valid(&url) {
|
||||||
return Err(format_err!("Activitypub uri invalid or blocked: {}", url).into());
|
return Err(anyhow!("Activitypub uri invalid or blocked: {}", url).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeout = Duration::from_secs(60);
|
let timeout = Duration::from_secs(60);
|
||||||
|
@ -124,10 +126,10 @@ pub async fn search_by_apub_id(
|
||||||
let split2 = split[0].split('!').collect::<Vec<&str>>();
|
let split2 = split[0].split('!').collect::<Vec<&str>>();
|
||||||
(format!("/c/{}", split2[1]), split[1])
|
(format!("/c/{}", split2[1]), split[1])
|
||||||
} else {
|
} else {
|
||||||
return Err(format_err!("Invalid search query: {}", query).into());
|
return Err(anyhow!("Invalid search query: {}", query).into());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(format_err!("Invalid search query: {}", query).into());
|
return Err(anyhow!("Invalid search query: {}", query).into());
|
||||||
};
|
};
|
||||||
|
|
||||||
let url = format!("{}://{}{}", get_apub_protocol_string(), instance, name);
|
let url = format!("{}://{}{}", get_apub_protocol_string(), instance, name);
|
||||||
|
@ -257,12 +259,13 @@ pub async fn get_or_fetch_and_upsert_user(
|
||||||
/// TODO it won't pick up new avatars, summaries etc until a day after.
|
/// TODO it won't pick up new avatars, summaries etc until a day after.
|
||||||
/// Actors need an "update" activity pushed to other servers to fix this.
|
/// Actors need an "update" activity pushed to other servers to fix this.
|
||||||
fn should_refetch_actor(last_refreshed: NaiveDateTime) -> bool {
|
fn should_refetch_actor(last_refreshed: NaiveDateTime) -> bool {
|
||||||
if cfg!(debug_assertions) {
|
let update_interval = if cfg!(debug_assertions) {
|
||||||
true
|
// avoid infinite loop when fetching community outbox
|
||||||
|
chrono::Duration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS_DEBUG)
|
||||||
} else {
|
} else {
|
||||||
let update_interval = chrono::Duration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS);
|
chrono::Duration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS)
|
||||||
|
};
|
||||||
last_refreshed.lt(&(naive_now() - update_interval))
|
last_refreshed.lt(&(naive_now() - update_interval))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a remote community exists, create if not found, if its too old update it.Fetch a community, insert/update it in the database and return the community.
|
/// Check if a remote community exists, create if not found, if its too old update it.Fetch a community, insert/update it in the database and return the community.
|
||||||
|
@ -280,21 +283,34 @@ pub async fn get_or_fetch_and_upsert_community(
|
||||||
match community {
|
match community {
|
||||||
Ok(c) if !c.local && should_refetch_actor(c.last_refreshed_at) => {
|
Ok(c) if !c.local && should_refetch_actor(c.last_refreshed_at) => {
|
||||||
debug!("Fetching and updating from remote community: {}", apub_id);
|
debug!("Fetching and updating from remote community: {}", apub_id);
|
||||||
let group = fetch_remote_object::<GroupExt>(client, apub_id).await?;
|
fetch_remote_community(apub_id, client, pool, Some(c.id)).await
|
||||||
|
|
||||||
let mut cf = CommunityForm::from_apub(&group, client, pool).await?;
|
|
||||||
cf.last_refreshed_at = Some(naive_now());
|
|
||||||
let community = blocking(pool, move |conn| Community::update(conn, c.id, &cf)).await??;
|
|
||||||
|
|
||||||
Ok(community)
|
|
||||||
}
|
}
|
||||||
Ok(c) => Ok(c),
|
Ok(c) => Ok(c),
|
||||||
Err(NotFound {}) => {
|
Err(NotFound {}) => {
|
||||||
debug!("Fetching and creating remote community: {}", apub_id);
|
debug!("Fetching and creating remote community: {}", apub_id);
|
||||||
|
fetch_remote_community(apub_id, client, pool, None).await
|
||||||
|
}
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_remote_community(
|
||||||
|
apub_id: &Url,
|
||||||
|
client: &Client,
|
||||||
|
pool: &DbPool,
|
||||||
|
community_id: Option<i32>,
|
||||||
|
) -> Result<Community, LemmyError> {
|
||||||
let group = fetch_remote_object::<GroupExt>(client, apub_id).await?;
|
let group = fetch_remote_object::<GroupExt>(client, apub_id).await?;
|
||||||
|
|
||||||
let cf = CommunityForm::from_apub(&group, client, pool).await?;
|
let cf = CommunityForm::from_apub(&group, client, pool).await?;
|
||||||
let community = blocking(pool, move |conn| Community::create(conn, &cf)).await??;
|
let community = blocking(pool, move |conn| {
|
||||||
|
if let Some(cid) = community_id {
|
||||||
|
Community::update(conn, cid, &cf)
|
||||||
|
} else {
|
||||||
|
Community::create(conn, &cf)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
// Also add the community moderators too
|
// Also add the community moderators too
|
||||||
let attributed_to = group.inner.attributed_to().unwrap();
|
let attributed_to = group.inner.attributed_to().unwrap();
|
||||||
|
@ -313,6 +329,8 @@ pub async fn get_or_fetch_and_upsert_community(
|
||||||
creator_and_moderators.push(c_or_m);
|
creator_and_moderators.push(c_or_m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: need to make this work to update mods of existing communities
|
||||||
|
if community_id.is_none() {
|
||||||
let community_id = community.id;
|
let community_id = community.id;
|
||||||
blocking(pool, move |conn| {
|
blocking(pool, move |conn| {
|
||||||
for mod_ in creator_and_moderators {
|
for mod_ in creator_and_moderators {
|
||||||
|
@ -326,11 +344,25 @@ pub async fn get_or_fetch_and_upsert_community(
|
||||||
Ok(()) as Result<(), LemmyError>
|
Ok(()) as Result<(), LemmyError>
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch outbox (maybe make this conditional)
|
||||||
|
let outbox =
|
||||||
|
fetch_remote_object::<OrderedCollection>(client, &community.get_outbox_url()?).await?;
|
||||||
|
let outbox_items = outbox.items().unwrap().clone();
|
||||||
|
for o in outbox_items.many().unwrap() {
|
||||||
|
let page = PageExt::from_any_base(o)?.unwrap();
|
||||||
|
let post = PostForm::from_apub(&page, client, pool).await?;
|
||||||
|
let post_ap_id = post.ap_id.clone();
|
||||||
|
// Check whether the post already exists in the local db
|
||||||
|
let existing = blocking(pool, move |conn| Post::read_from_apub_id(conn, &post_ap_id)).await?;
|
||||||
|
match existing {
|
||||||
|
Ok(e) => blocking(pool, move |conn| Post::update(conn, e.id, &post)).await??,
|
||||||
|
Err(_) => blocking(pool, move |conn| Post::create(conn, &post)).await??,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Ok(community)
|
Ok(community)
|
||||||
}
|
|
||||||
Err(e) => Err(e.into()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result<Post, LemmyError> {
|
fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result<Post, LemmyError> {
|
||||||
|
|
|
@ -15,7 +15,7 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_new::{activity::*, base::AnyBase, prelude::ExtendsExt};
|
use activitystreams::{activity::*, base::AnyBase, prelude::ExtendsExt};
|
||||||
use actix_web::{client::Client, HttpResponse};
|
use actix_web::{client::Client, HttpResponse};
|
||||||
|
|
||||||
pub async fn receive_announce(
|
pub async fn receive_announce(
|
||||||
|
|
|
@ -21,7 +21,7 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_new::{activity::Create, base::AnyBase, object::Note, prelude::*};
|
use activitystreams::{activity::Create, base::AnyBase, object::Note, prelude::*};
|
||||||
use actix_web::{client::Client, HttpResponse};
|
use actix_web::{client::Client, HttpResponse};
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
comment::{Comment, CommentForm},
|
comment::{Comment, CommentForm},
|
||||||
|
|
|
@ -20,7 +20,7 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_new::{activity::Delete, base::AnyBase, object::Note, prelude::*};
|
use activitystreams::{activity::Delete, base::AnyBase, object::Note, prelude::*};
|
||||||
use actix_web::{client::Client, HttpResponse};
|
use actix_web::{client::Client, HttpResponse};
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
comment::{Comment, CommentForm},
|
comment::{Comment, CommentForm},
|
||||||
|
|
|
@ -19,7 +19,7 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_new::{activity::Dislike, base::AnyBase, object::Note, prelude::*};
|
use activitystreams::{activity::Dislike, base::AnyBase, object::Note, prelude::*};
|
||||||
use actix_web::{client::Client, HttpResponse};
|
use actix_web::{client::Client, HttpResponse};
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
comment::{CommentForm, CommentLike, CommentLikeForm},
|
comment::{CommentForm, CommentLike, CommentLikeForm},
|
||||||
|
|
|
@ -19,7 +19,7 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_new::{activity::Like, base::AnyBase, object::Note, prelude::*};
|
use activitystreams::{activity::Like, base::AnyBase, object::Note, prelude::*};
|
||||||
use actix_web::{client::Client, HttpResponse};
|
use actix_web::{client::Client, HttpResponse};
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
comment::{CommentForm, CommentLike, CommentLikeForm},
|
comment::{CommentForm, CommentLike, CommentLikeForm},
|
||||||
|
|
|
@ -20,7 +20,7 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_new::{activity::Remove, base::AnyBase, object::Note, prelude::*};
|
use activitystreams::{activity::Remove, base::AnyBase, object::Note, prelude::*};
|
||||||
use actix_web::{client::Client, HttpResponse};
|
use actix_web::{client::Client, HttpResponse};
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
comment::{Comment, CommentForm},
|
comment::{Comment, CommentForm},
|
||||||
|
|
|
@ -20,8 +20,9 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_new::{activity::*, base::AnyBase, object::Note, prelude::*};
|
use activitystreams::{activity::*, base::AnyBase, object::Note, prelude::*};
|
||||||
use actix_web::{client::Client, HttpResponse};
|
use actix_web::{client::Client, HttpResponse};
|
||||||
|
use anyhow::anyhow;
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
|
comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
|
||||||
comment_view::CommentView,
|
comment_view::CommentView,
|
||||||
|
@ -63,7 +64,7 @@ async fn receive_undo_delete(
|
||||||
"Note" => receive_undo_delete_comment(undo, &delete, client, pool, chat_server).await,
|
"Note" => receive_undo_delete_comment(undo, &delete, client, pool, chat_server).await,
|
||||||
"Page" => receive_undo_delete_post(undo, &delete, client, pool, chat_server).await,
|
"Page" => receive_undo_delete_post(undo, &delete, client, pool, chat_server).await,
|
||||||
"Group" => receive_undo_delete_community(undo, &delete, client, pool, chat_server).await,
|
"Group" => receive_undo_delete_community(undo, &delete, client, pool, chat_server).await,
|
||||||
d => Err(format_err!("Undo Delete type {} not supported", d).into()),
|
d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +81,7 @@ async fn receive_undo_remove(
|
||||||
"Note" => receive_undo_remove_comment(undo, &remove, client, pool, chat_server).await,
|
"Note" => receive_undo_remove_comment(undo, &remove, client, pool, chat_server).await,
|
||||||
"Page" => receive_undo_remove_post(undo, &remove, client, pool, chat_server).await,
|
"Page" => receive_undo_remove_post(undo, &remove, client, pool, chat_server).await,
|
||||||
"Group" => receive_undo_remove_community(undo, &remove, client, pool, chat_server).await,
|
"Group" => receive_undo_remove_community(undo, &remove, client, pool, chat_server).await,
|
||||||
d => Err(format_err!("Undo Delete type {} not supported", d).into()),
|
d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +97,7 @@ async fn receive_undo_like(
|
||||||
match type_ {
|
match type_ {
|
||||||
"Note" => receive_undo_like_comment(undo, &like, client, pool, chat_server).await,
|
"Note" => receive_undo_like_comment(undo, &like, client, pool, chat_server).await,
|
||||||
"Page" => receive_undo_like_post(undo, &like, client, pool, chat_server).await,
|
"Page" => receive_undo_like_post(undo, &like, client, pool, chat_server).await,
|
||||||
d => Err(format_err!("Undo Delete type {} not supported", d).into()),
|
d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +110,7 @@ async fn receive_undo_dislike(
|
||||||
let dislike = Dislike::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap();
|
let dislike = Dislike::from_any_base(undo.object().to_owned().one().unwrap())?.unwrap();
|
||||||
|
|
||||||
let type_ = dislike.object().as_single_kind_str().unwrap();
|
let type_ = dislike.object().as_single_kind_str().unwrap();
|
||||||
Err(format_err!("Undo Delete type {} not supported", type_).into())
|
Err(anyhow!("Undo Delete type {} not supported", type_).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive_undo_delete_comment(
|
async fn receive_undo_delete_comment(
|
||||||
|
|
|
@ -22,7 +22,7 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_new::{activity::Update, base::AnyBase, object::Note, prelude::*};
|
use activitystreams::{activity::Update, base::AnyBase, object::Note, prelude::*};
|
||||||
use actix_web::{client::Client, HttpResponse};
|
use actix_web::{client::Client, HttpResponse};
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
comment::{Comment, CommentForm},
|
comment::{Comment, CommentForm},
|
||||||
|
|
|
@ -9,11 +9,12 @@ use crate::{
|
||||||
routes::{ChatServerParam, DbPoolParam},
|
routes::{ChatServerParam, DbPoolParam},
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_new::{
|
use activitystreams::{
|
||||||
activity::{Follow, Undo},
|
activity::{Follow, Undo},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use actix_web::{client::Client, web, HttpRequest, HttpResponse};
|
use actix_web::{client::Client, web, HttpRequest, HttpResponse};
|
||||||
|
use anyhow::anyhow;
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
community::{Community, CommunityFollower, CommunityFollowerForm},
|
community::{Community, CommunityFollower, CommunityFollowerForm},
|
||||||
user::User_,
|
user::User_,
|
||||||
|
@ -57,7 +58,7 @@ pub async fn community_inbox(
|
||||||
|
|
||||||
if !community.local {
|
if !community.local {
|
||||||
return Err(
|
return Err(
|
||||||
format_err!(
|
anyhow!(
|
||||||
"Received activity is addressed to remote community {}",
|
"Received activity is addressed to remote community {}",
|
||||||
&community.actor_id
|
&community.actor_id
|
||||||
)
|
)
|
||||||
|
|
|
@ -23,7 +23,7 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_new::{
|
use activitystreams::{
|
||||||
activity::{ActorAndObject, ActorAndObjectRef},
|
activity::{ActorAndObject, ActorAndObjectRef},
|
||||||
base::{AsBase, Extends},
|
base::{AsBase, Extends},
|
||||||
object::AsObject,
|
object::AsObject,
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_new::{
|
use activitystreams::{
|
||||||
activity::{Accept, Create, Delete, Undo, Update},
|
activity::{Accept, Create, Delete, Undo, Update},
|
||||||
object::Note,
|
object::Note,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
|
|
@ -20,16 +20,16 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_ext::{Ext1, Ext2};
|
use activitystreams::{
|
||||||
use activitystreams_new::{
|
|
||||||
activity::Follow,
|
activity::Follow,
|
||||||
actor::{ApActor, Group, Person},
|
actor::{ApActor, Group, Person},
|
||||||
object::{Page, Tombstone},
|
object::{Page, Tombstone},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
use activitystreams_ext::{Ext1, Ext2};
|
||||||
use actix_web::{body::Body, client::Client, HttpResponse};
|
use actix_web::{body::Body, client::Client, HttpResponse};
|
||||||
|
use anyhow::anyhow;
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use failure::_core::fmt::Debug;
|
|
||||||
use lemmy_db::{activity::do_insert_activity, user::User_};
|
use lemmy_db::{activity::do_insert_activity, user::User_};
|
||||||
use lemmy_utils::{convert_datetime, get_apub_protocol_string, settings::Settings, MentionData};
|
use lemmy_utils::{convert_datetime, get_apub_protocol_string, settings::Settings, MentionData};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
@ -118,10 +118,10 @@ where
|
||||||
tombstone.set_deleted(convert_datetime(updated));
|
tombstone.set_deleted(convert_datetime(updated));
|
||||||
Ok(tombstone)
|
Ok(tombstone)
|
||||||
} else {
|
} else {
|
||||||
Err(format_err!("Cant convert to tombstone because updated time was None.").into())
|
Err(anyhow!("Cant convert to tombstone because updated time was None.").into())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(format_err!("Cant convert object to tombstone if it wasnt deleted").into())
|
Err(anyhow!("Cant convert object to tombstone if it wasnt deleted").into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,8 +280,8 @@ pub trait ActorType {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO move these to the db rows
|
// TODO move these to the db rows
|
||||||
fn get_inbox_url(&self) -> String {
|
fn get_inbox_url(&self) -> Result<Url, ParseError> {
|
||||||
format!("{}/inbox", &self.actor_id_str())
|
Url::parse(&format!("{}/inbox", &self.actor_id_str()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make this return `Result<Url, ParseError>
|
// TODO: make this return `Result<Url, ParseError>
|
||||||
|
@ -289,8 +289,8 @@ pub trait ActorType {
|
||||||
get_shared_inbox(&self.actor_id().unwrap())
|
get_shared_inbox(&self.actor_id().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_outbox_url(&self) -> String {
|
fn get_outbox_url(&self) -> Result<Url, ParseError> {
|
||||||
format!("{}/outbox", &self.actor_id_str())
|
Url::parse(&format!("{}/outbox", &self.actor_id_str()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_followers_url(&self) -> String {
|
fn get_followers_url(&self) -> String {
|
||||||
|
@ -339,13 +339,13 @@ pub async fn fetch_webfinger_url(
|
||||||
.links
|
.links
|
||||||
.iter()
|
.iter()
|
||||||
.find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
|
.find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
|
||||||
.ok_or_else(|| format_err!("No application/activity+json link found."))?;
|
.ok_or_else(|| anyhow!("No application/activity+json link found."))?;
|
||||||
link
|
link
|
||||||
.href
|
.href
|
||||||
.to_owned()
|
.to_owned()
|
||||||
.map(|u| Url::parse(&u))
|
.map(|u| Url::parse(&u))
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.ok_or_else(|| format_err!("No href found.").into())
|
.ok_or_else(|| anyhow!("No href found.").into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn insert_activity<T>(
|
pub async fn insert_activity<T>(
|
||||||
|
@ -355,7 +355,7 @@ pub async fn insert_activity<T>(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
) -> Result<(), LemmyError>
|
) -> Result<(), LemmyError>
|
||||||
where
|
where
|
||||||
T: Serialize + Debug + Send + 'static,
|
T: Serialize + std::fmt::Debug + Send + 'static,
|
||||||
{
|
{
|
||||||
blocking(pool, move |conn| {
|
blocking(pool, move |conn| {
|
||||||
do_insert_activity(conn, user_id, &data, local)
|
do_insert_activity(conn, user_id, &data, local)
|
||||||
|
|
|
@ -18,8 +18,7 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_ext::Ext1;
|
use activitystreams::{
|
||||||
use activitystreams_new::{
|
|
||||||
activity::{
|
activity::{
|
||||||
kind::{CreateType, DeleteType, DislikeType, LikeType, RemoveType, UndoType, UpdateType},
|
kind::{CreateType, DeleteType, DislikeType, LikeType, RemoveType, UndoType, UpdateType},
|
||||||
Create,
|
Create,
|
||||||
|
@ -35,6 +34,7 @@ use activitystreams_new::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
public,
|
public,
|
||||||
};
|
};
|
||||||
|
use activitystreams_ext::Ext1;
|
||||||
use actix_web::{body::Body, client::Client, web, HttpResponse};
|
use actix_web::{body::Body, client::Client, web, HttpResponse};
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
community::Community,
|
community::Community,
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_new::{
|
use activitystreams::{
|
||||||
activity::{
|
activity::{
|
||||||
kind::{CreateType, DeleteType, UndoType, UpdateType},
|
kind::{CreateType, DeleteType, UndoType, UpdateType},
|
||||||
Create,
|
Create,
|
||||||
|
|
|
@ -13,8 +13,7 @@ use crate::{
|
||||||
DbPool,
|
DbPool,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use activitystreams_ext::Ext1;
|
use activitystreams::{
|
||||||
use activitystreams_new::{
|
|
||||||
activity::{
|
activity::{
|
||||||
kind::{FollowType, UndoType},
|
kind::{FollowType, UndoType},
|
||||||
Follow,
|
Follow,
|
||||||
|
@ -25,6 +24,7 @@ use activitystreams_new::{
|
||||||
object::{Image, Tombstone},
|
object::{Image, Tombstone},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
use activitystreams_ext::Ext1;
|
||||||
use actix_web::{body::Body, client::Client, web, HttpResponse};
|
use actix_web::{body::Body, client::Client, web, HttpResponse};
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
naive_now,
|
naive_now,
|
||||||
|
@ -63,9 +63,9 @@ impl ToApub for User_ {
|
||||||
person.set_icon(image.into_any_base()?);
|
person.set_icon(image.into_any_base()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ap_actor = ApActor::new(self.get_inbox_url().parse()?, person);
|
let mut ap_actor = ApActor::new(self.get_inbox_url()?, person);
|
||||||
ap_actor
|
ap_actor
|
||||||
.set_outbox(self.get_outbox_url().parse()?)
|
.set_outbox(self.get_outbox_url()?)
|
||||||
.set_followers(self.get_followers_url().parse()?)
|
.set_followers(self.get_followers_url().parse()?)
|
||||||
.set_following(self.get_following_url().parse()?)
|
.set_following(self.get_following_url().parse()?)
|
||||||
.set_liked(self.get_liked_url().parse()?)
|
.set_liked(self.get_liked_url().parse()?)
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
pub extern crate strum_macros;
|
pub extern crate strum_macros;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub extern crate lazy_static;
|
pub extern crate lazy_static;
|
||||||
#[macro_use]
|
|
||||||
pub extern crate failure;
|
|
||||||
pub extern crate actix;
|
pub extern crate actix;
|
||||||
pub extern crate actix_web;
|
pub extern crate actix_web;
|
||||||
pub extern crate base64;
|
pub extern crate base64;
|
||||||
|
@ -33,6 +31,7 @@ pub mod websocket;
|
||||||
|
|
||||||
use crate::request::{retry, RecvError};
|
use crate::request::{retry, RecvError};
|
||||||
use actix_web::{client::Client, dev::ConnectionInfo};
|
use actix_web::{client::Client, dev::ConnectionInfo};
|
||||||
|
use anyhow::anyhow;
|
||||||
use lemmy_utils::{get_apub_protocol_string, settings::Settings};
|
use lemmy_utils::{get_apub_protocol_string, settings::Settings};
|
||||||
use log::error;
|
use log::error;
|
||||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||||
|
@ -48,12 +47,12 @@ pub type IPAddr = String;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LemmyError {
|
pub struct LemmyError {
|
||||||
inner: failure::Error,
|
inner: anyhow::Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<T> for LemmyError
|
impl<T> From<T> for LemmyError
|
||||||
where
|
where
|
||||||
T: Into<failure::Error>,
|
T: Into<anyhow::Error>,
|
||||||
{
|
{
|
||||||
fn from(t: T) -> Self {
|
fn from(t: T) -> Self {
|
||||||
LemmyError { inner: t.into() }
|
LemmyError { inner: t.into() }
|
||||||
|
@ -118,7 +117,7 @@ pub async fn fetch_pictrs(client: &Client, image_url: &str) -> Result<PictrsResp
|
||||||
if response.msg == "ok" {
|
if response.msg == "ok" {
|
||||||
Ok(response)
|
Ok(response)
|
||||||
} else {
|
} else {
|
||||||
Err(format_err!("{}", &response.msg).into())
|
Err(anyhow!("{}", &response.msg).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,13 +190,13 @@ pub async fn is_image_content_type(client: &Client, test: &str) -> Result<(), Le
|
||||||
if response
|
if response
|
||||||
.headers()
|
.headers()
|
||||||
.get("Content-Type")
|
.get("Content-Type")
|
||||||
.ok_or_else(|| format_err!("No Content-Type header"))?
|
.ok_or_else(|| anyhow!("No Content-Type header"))?
|
||||||
.to_str()?
|
.to_str()?
|
||||||
.starts_with("image/")
|
.starts_with("image/")
|
||||||
{
|
{
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(format_err!("Not an image type.").into())
|
Err(anyhow!("Not an image type.").into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
use crate::LemmyError;
|
use crate::LemmyError;
|
||||||
|
use anyhow::anyhow;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Fail)]
|
#[derive(Clone, Debug, Error)]
|
||||||
#[fail(display = "Error sending request, {}", _0)]
|
#[error("Error sending request, {0}")]
|
||||||
struct SendError(pub String);
|
struct SendError(pub String);
|
||||||
|
|
||||||
#[derive(Clone, Debug, Fail)]
|
#[derive(Clone, Debug, Error)]
|
||||||
#[fail(display = "Error receiving response, {}", _0)]
|
#[error("Error receiving response, {0}")]
|
||||||
pub struct RecvError(pub String);
|
pub struct RecvError(pub String);
|
||||||
|
|
||||||
pub async fn retry<F, Fut, T>(f: F) -> Result<T, LemmyError>
|
pub async fn retry<F, Fut, T>(f: F) -> Result<T, LemmyError>
|
||||||
|
@ -22,7 +24,7 @@ where
|
||||||
F: Fn() -> Fut,
|
F: Fn() -> Fut,
|
||||||
Fut: Future<Output = Result<Result<T, actix_web::client::SendRequestError>, LemmyError>>,
|
Fut: Future<Output = Result<Result<T, actix_web::client::SendRequestError>, LemmyError>>,
|
||||||
{
|
{
|
||||||
let mut response = Err(format_err!("connect timeout").into());
|
let mut response = Err(anyhow!("connect timeout").into());
|
||||||
|
|
||||||
for _ in 0u8..3 {
|
for _ in 0u8..3 {
|
||||||
match (f)().await? {
|
match (f)().await? {
|
||||||
|
|
|
@ -28,11 +28,10 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
"/c/{community_name}/followers",
|
"/c/{community_name}/followers",
|
||||||
web::get().to(get_apub_community_followers),
|
web::get().to(get_apub_community_followers),
|
||||||
)
|
)
|
||||||
// TODO This is only useful for history which we aren't doing right now
|
.route(
|
||||||
// .route(
|
"/c/{community_name}/outbox",
|
||||||
// "/c/{community_name}/outbox",
|
web::get().to(get_apub_community_outbox),
|
||||||
// web::get().to(get_apub_community_outbox),
|
)
|
||||||
// )
|
|
||||||
.route("/u/{user_name}", web::get().to(get_apub_user_http))
|
.route("/u/{user_name}", web::get().to(get_apub_user_http))
|
||||||
.route("/post/{post_id}", web::get().to(get_apub_post))
|
.route("/post/{post_id}", web::get().to(get_apub_post))
|
||||||
.route("/comment/{comment_id}", web::get().to(get_apub_comment)),
|
.route("/comment/{comment_id}", web::get().to(get_apub_comment)),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{api::claims::Claims, blocking, routes::DbPoolParam, LemmyError};
|
use crate::{api::claims::Claims, blocking, routes::DbPoolParam, LemmyError};
|
||||||
use actix_web::{error::ErrorBadRequest, *};
|
use actix_web::{error::ErrorBadRequest, *};
|
||||||
|
use anyhow::anyhow;
|
||||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use diesel::{
|
use diesel::{
|
||||||
r2d2::{ConnectionManager, Pool},
|
r2d2::{ConnectionManager, Pool},
|
||||||
|
@ -88,7 +89,7 @@ async fn get_feed(
|
||||||
"c" => RequestType::Community,
|
"c" => RequestType::Community,
|
||||||
"front" => RequestType::Front,
|
"front" => RequestType::Front,
|
||||||
"inbox" => RequestType::Inbox,
|
"inbox" => RequestType::Inbox,
|
||||||
_ => return Err(ErrorBadRequest(LemmyError::from(format_err!("wrong_type")))),
|
_ => return Err(ErrorBadRequest(LemmyError::from(anyhow!("wrong_type")))),
|
||||||
};
|
};
|
||||||
|
|
||||||
let param = path.1.to_owned();
|
let param = path.1.to_owned();
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{blocking, routes::DbPoolParam, version, LemmyError};
|
use crate::{blocking, routes::DbPoolParam, version, LemmyError};
|
||||||
use actix_web::{body::Body, error::ErrorBadRequest, *};
|
use actix_web::{body::Body, error::ErrorBadRequest, *};
|
||||||
|
use anyhow::anyhow;
|
||||||
use lemmy_db::site_view::SiteView;
|
use lemmy_db::site_view::SiteView;
|
||||||
use lemmy_utils::{get_apub_protocol_string, settings::Settings};
|
use lemmy_utils::{get_apub_protocol_string, settings::Settings};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -28,7 +29,7 @@ async fn node_info_well_known() -> Result<HttpResponse<Body>, LemmyError> {
|
||||||
async fn node_info(db: DbPoolParam) -> Result<HttpResponse, Error> {
|
async fn node_info(db: DbPoolParam) -> Result<HttpResponse, Error> {
|
||||||
let site_view = blocking(&db, SiteView::read)
|
let site_view = blocking(&db, SiteView::read)
|
||||||
.await?
|
.await?
|
||||||
.map_err(|_| ErrorBadRequest(LemmyError::from(format_err!("not_found"))))?;
|
.map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?;
|
||||||
|
|
||||||
let protocols = if Settings::get().federation.enabled {
|
let protocols = if Settings::get().federation.enabled {
|
||||||
vec!["activitypub".to_string()]
|
vec!["activitypub".to_string()]
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{blocking, routes::DbPoolParam, LemmyError};
|
use crate::{blocking, routes::DbPoolParam, LemmyError};
|
||||||
use actix_web::{error::ErrorBadRequest, web::Query, *};
|
use actix_web::{error::ErrorBadRequest, web::Query, *};
|
||||||
|
use anyhow::anyhow;
|
||||||
use lemmy_db::{community::Community, user::User_};
|
use lemmy_db::{community::Community, user::User_};
|
||||||
use lemmy_utils::{settings::Settings, WEBFINGER_COMMUNITY_REGEX, WEBFINGER_USER_REGEX};
|
use lemmy_utils::{settings::Settings, WEBFINGER_COMMUNITY_REGEX, WEBFINGER_USER_REGEX};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -62,17 +63,17 @@ async fn get_webfinger_response(
|
||||||
Community::read_from_name(conn, &community_name)
|
Community::read_from_name(conn, &community_name)
|
||||||
})
|
})
|
||||||
.await?
|
.await?
|
||||||
.map_err(|_| ErrorBadRequest(LemmyError::from(format_err!("not_found"))))?
|
.map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?
|
||||||
.actor_id
|
.actor_id
|
||||||
} else if let Some(user_name) = user_regex_parsed {
|
} else if let Some(user_name) = user_regex_parsed {
|
||||||
let user_name = user_name.as_str().to_owned();
|
let user_name = user_name.as_str().to_owned();
|
||||||
// Make sure the requested user exists.
|
// Make sure the requested user exists.
|
||||||
blocking(&db, move |conn| User_::read_from_name(conn, &user_name))
|
blocking(&db, move |conn| User_::read_from_name(conn, &user_name))
|
||||||
.await?
|
.await?
|
||||||
.map_err(|_| ErrorBadRequest(LemmyError::from(format_err!("not_found"))))?
|
.map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?
|
||||||
.actor_id
|
.actor_id
|
||||||
} else {
|
} else {
|
||||||
return Err(ErrorBadRequest(LemmyError::from(format_err!("not_found"))));
|
return Err(ErrorBadRequest(LemmyError::from(anyhow!("not_found"))));
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = WebFingerResponse {
|
let json = WebFingerResponse {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
pub const VERSION: &str = "v0.7.33";
|
pub const VERSION: &str = "v0.7.39";
|
||||||
|
|
4
ui/assets/css/main.css
vendored
4
ui/assets/css/main.css
vendored
|
@ -87,6 +87,10 @@
|
||||||
line-height: 1.0;
|
line-height: 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.post-title a:visited {
|
||||||
|
color: var(--gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
width: 1em;
|
width: 1em;
|
||||||
|
|
2
ui/package.json
vendored
2
ui/package.json
vendored
|
@ -33,7 +33,7 @@
|
||||||
"i18next": "^19.4.1",
|
"i18next": "^19.4.1",
|
||||||
"inferno": "^7.4.2",
|
"inferno": "^7.4.2",
|
||||||
"inferno-helmet": "^5.2.1",
|
"inferno-helmet": "^5.2.1",
|
||||||
"inferno-i18next": "nimbusec-oss/inferno-i18next",
|
"inferno-i18next": "github:nimbusec-oss/inferno-i18next#semver:^7.4.2",
|
||||||
"inferno-router": "^7.4.2",
|
"inferno-router": "^7.4.2",
|
||||||
"js-cookie": "^2.2.0",
|
"js-cookie": "^2.2.0",
|
||||||
"jwt-decode": "^2.2.0",
|
"jwt-decode": "^2.2.0",
|
||||||
|
|
3
ui/src/components/comment-form.tsx
vendored
3
ui/src/components/comment-form.tsx
vendored
|
@ -144,6 +144,9 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
// This only finishes this form, if the randomly generated form_id matches the one received
|
// This only finishes this form, if the randomly generated form_id matches the one received
|
||||||
if (this.state.commentForm.form_id == data.form_id) {
|
if (this.state.commentForm.form_id == data.form_id) {
|
||||||
this.setState({ finished: true });
|
this.setState({ finished: true });
|
||||||
|
|
||||||
|
// Necessary because it broke tribute for some reaso
|
||||||
|
this.setState({ finished: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
31
ui/src/components/inbox.tsx
vendored
31
ui/src/components/inbox.tsx
vendored
|
@ -275,21 +275,21 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
combined(): Array<ReplyType> {
|
||||||
|
return [
|
||||||
|
...this.state.replies,
|
||||||
|
...this.state.mentions,
|
||||||
|
...this.state.messages,
|
||||||
|
].sort((a, b) => b.published.localeCompare(a.published));
|
||||||
|
}
|
||||||
|
|
||||||
all() {
|
all() {
|
||||||
let combined: Array<ReplyType> = [];
|
|
||||||
|
|
||||||
combined.push(...this.state.replies);
|
|
||||||
combined.push(...this.state.mentions);
|
|
||||||
combined.push(...this.state.messages);
|
|
||||||
|
|
||||||
// Sort it
|
|
||||||
combined.sort((a, b) => b.published.localeCompare(a.published));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{combined.map(i =>
|
{this.combined().map(i =>
|
||||||
isCommentType(i) ? (
|
isCommentType(i) ? (
|
||||||
<CommentNodes
|
<CommentNodes
|
||||||
|
key={i.id}
|
||||||
nodes={[{ comment: i }]}
|
nodes={[{ comment: i }]}
|
||||||
noIndent
|
noIndent
|
||||||
markable
|
markable
|
||||||
|
@ -298,7 +298,7 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
enableDownvotes={this.state.site.enable_downvotes}
|
enableDownvotes={this.state.site.enable_downvotes}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<PrivateMessage privateMessage={i} />
|
<PrivateMessage key={i.id} privateMessage={i} />
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -325,6 +325,7 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
<div>
|
<div>
|
||||||
{this.state.mentions.map(mention => (
|
{this.state.mentions.map(mention => (
|
||||||
<CommentNodes
|
<CommentNodes
|
||||||
|
key={mention.id}
|
||||||
nodes={[{ comment: mention }]}
|
nodes={[{ comment: mention }]}
|
||||||
noIndent
|
noIndent
|
||||||
markable
|
markable
|
||||||
|
@ -341,7 +342,7 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{this.state.messages.map(message => (
|
{this.state.messages.map(message => (
|
||||||
<PrivateMessage privateMessage={message} />
|
<PrivateMessage key={message.id} privateMessage={message} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -565,7 +566,6 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
} else if (data.comment.creator_id == UserService.Instance.user.id) {
|
} else if (data.comment.creator_id == UserService.Instance.user.id) {
|
||||||
toast(i18n.t('reply_sent'));
|
toast(i18n.t('reply_sent'));
|
||||||
}
|
}
|
||||||
this.setState(this.state);
|
|
||||||
} else if (res.op == UserOperation.CreatePrivateMessage) {
|
} else if (res.op == UserOperation.CreatePrivateMessage) {
|
||||||
let data = res.data as PrivateMessageResponse;
|
let data = res.data as PrivateMessageResponse;
|
||||||
if (data.message.recipient_id == UserService.Instance.user.id) {
|
if (data.message.recipient_id == UserService.Instance.user.id) {
|
||||||
|
@ -597,7 +597,10 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
this.state.replies.filter(r => !r.read).length +
|
this.state.replies.filter(r => !r.read).length +
|
||||||
this.state.mentions.filter(r => !r.read).length +
|
this.state.mentions.filter(r => !r.read).length +
|
||||||
this.state.messages.filter(
|
this.state.messages.filter(
|
||||||
r => !r.read && r.creator_id !== UserService.Instance.user.id
|
r =>
|
||||||
|
UserService.Instance.user &&
|
||||||
|
!r.read &&
|
||||||
|
r.creator_id !== UserService.Instance.user.id
|
||||||
).length
|
).length
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
11
ui/src/components/markdown-textarea.tsx
vendored
11
ui/src/components/markdown-textarea.tsx
vendored
|
@ -21,9 +21,11 @@ interface MarkdownTextAreaProps {
|
||||||
replyType?: boolean;
|
replyType?: boolean;
|
||||||
focus?: boolean;
|
focus?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
maxLength?: number;
|
||||||
onSubmit?(msg: { val: string; formId: string }): any;
|
onSubmit?(msg: { val: string; formId: string }): any;
|
||||||
onContentChange?(val: string): any;
|
onContentChange?(val: string): any;
|
||||||
onReplyCancel?(): any;
|
onReplyCancel?(): any;
|
||||||
|
hideNavigationWarnings?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MarkdownTextAreaState {
|
interface MarkdownTextAreaState {
|
||||||
|
@ -77,7 +79,7 @@ export class MarkdownTextArea extends Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
if (this.state.content) {
|
if (!this.props.hideNavigationWarnings && this.state.content) {
|
||||||
window.onbeforeunload = () => true;
|
window.onbeforeunload = () => true;
|
||||||
} else {
|
} else {
|
||||||
window.onbeforeunload = undefined;
|
window.onbeforeunload = undefined;
|
||||||
|
@ -109,7 +111,10 @@ export class MarkdownTextArea extends Component<
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<form id={this.formId} onSubmit={linkEvent(this, this.handleSubmit)}>
|
<form id={this.formId} onSubmit={linkEvent(this, this.handleSubmit)}>
|
||||||
<Prompt when={this.state.content} message={i18n.t('block_leaving')} />
|
<Prompt
|
||||||
|
when={!this.props.hideNavigationWarnings && this.state.content}
|
||||||
|
message={i18n.t('block_leaving')}
|
||||||
|
/>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div className={`col-sm-12`}>
|
<div className={`col-sm-12`}>
|
||||||
<textarea
|
<textarea
|
||||||
|
@ -121,7 +126,7 @@ export class MarkdownTextArea extends Component<
|
||||||
required
|
required
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled}
|
||||||
rows={2}
|
rows={2}
|
||||||
maxLength={10000}
|
maxLength={this.props.maxLength || 10000}
|
||||||
/>
|
/>
|
||||||
{this.state.previewMode && (
|
{this.state.previewMode && (
|
||||||
<div
|
<div
|
||||||
|
|
4
ui/src/components/navbar.tsx
vendored
4
ui/src/components/navbar.tsx
vendored
|
@ -30,6 +30,7 @@ import {
|
||||||
messageToastify,
|
messageToastify,
|
||||||
md,
|
md,
|
||||||
setTheme,
|
setTheme,
|
||||||
|
getLanguage,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
|
|
||||||
|
@ -430,12 +431,13 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
// The login
|
// The login
|
||||||
if (data.my_user) {
|
if (data.my_user) {
|
||||||
UserService.Instance.user = data.my_user;
|
UserService.Instance.user = data.my_user;
|
||||||
|
WebSocketService.Instance.userJoin();
|
||||||
// On the first load, check the unreads
|
// On the first load, check the unreads
|
||||||
if (this.state.isLoggedIn == false) {
|
if (this.state.isLoggedIn == false) {
|
||||||
this.requestNotificationPermission();
|
this.requestNotificationPermission();
|
||||||
this.fetchUnreads();
|
this.fetchUnreads();
|
||||||
setTheme(data.my_user.theme, true);
|
setTheme(data.my_user.theme, true);
|
||||||
i18n.changeLanguage(data.my_user.lang);
|
i18n.changeLanguage(getLanguage());
|
||||||
}
|
}
|
||||||
this.state.isLoggedIn = true;
|
this.state.isLoggedIn = true;
|
||||||
}
|
}
|
||||||
|
|
6
ui/src/components/post-listing.tsx
vendored
6
ui/src/components/post-listing.tsx
vendored
|
@ -315,7 +315,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
<h5 className="mb-1 d-inline-block">
|
<h5 className="mb-1 d-inline-block">
|
||||||
{this.props.showBody && post.url ? (
|
{this.props.showBody && post.url ? (
|
||||||
<a
|
<a
|
||||||
className="text-body"
|
className={!post.stickied ? 'text-body' : 'text-primary'}
|
||||||
href={post.url}
|
href={post.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
title={post.url}
|
title={post.url}
|
||||||
|
@ -325,7 +325,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<Link
|
<Link
|
||||||
className="text-body"
|
className={!post.stickied ? 'text-body' : 'text-primary'}
|
||||||
to={`/post/${post.id}`}
|
to={`/post/${post.id}`}
|
||||||
title={i18n.t('comments')}
|
title={i18n.t('comments')}
|
||||||
>
|
>
|
||||||
|
@ -419,7 +419,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
className="unselectable pointer ml-2 text-muted font-italic"
|
className="unselectable pointer ml-2 text-muted font-italic"
|
||||||
data-tippy-content={i18n.t('stickied')}
|
data-tippy-content={i18n.t('stickied')}
|
||||||
>
|
>
|
||||||
<svg class={`icon icon-inline text-success`}>
|
<svg class={`icon icon-inline text-primary`}>
|
||||||
<use xlinkHref="#icon-pin"></use>
|
<use xlinkHref="#icon-pin"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</small>
|
</small>
|
||||||
|
|
13
ui/src/components/private-message.tsx
vendored
13
ui/src/components/private-message.tsx
vendored
|
@ -45,7 +45,10 @@ export class PrivateMessage extends Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
get mine(): boolean {
|
get mine(): boolean {
|
||||||
return UserService.Instance.user.id == this.props.privateMessage.creator_id;
|
return (
|
||||||
|
UserService.Instance.user &&
|
||||||
|
UserService.Instance.user.id == this.props.privateMessage.creator_id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -113,6 +116,7 @@ export class PrivateMessage extends Component<
|
||||||
<PrivateMessageForm
|
<PrivateMessageForm
|
||||||
privateMessage={message}
|
privateMessage={message}
|
||||||
onEdit={this.handlePrivateMessageEdit}
|
onEdit={this.handlePrivateMessageEdit}
|
||||||
|
onCreate={this.handlePrivateMessageCreate}
|
||||||
onCancel={this.handleReplyCancel}
|
onCancel={this.handleReplyCancel}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -280,9 +284,14 @@ export class PrivateMessage extends Component<
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePrivateMessageCreate() {
|
handlePrivateMessageCreate(message: PrivateMessageI) {
|
||||||
|
if (
|
||||||
|
UserService.Instance.user &&
|
||||||
|
message.creator_id == UserService.Instance.user.id
|
||||||
|
) {
|
||||||
this.state.showReply = false;
|
this.state.showReply = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
toast(i18n.t('message_sent'));
|
toast(i18n.t('message_sent'));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
2
ui/src/components/search.tsx
vendored
2
ui/src/components/search.tsx
vendored
|
@ -289,6 +289,7 @@ export class Search extends Component<any, SearchState> {
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
{i.type_ == 'posts' && (
|
{i.type_ == 'posts' && (
|
||||||
<PostListing
|
<PostListing
|
||||||
|
key={(i.data as Post).id}
|
||||||
post={i.data as Post}
|
post={i.data as Post}
|
||||||
showCommunity
|
showCommunity
|
||||||
enableDownvotes={this.state.site.enable_downvotes}
|
enableDownvotes={this.state.site.enable_downvotes}
|
||||||
|
@ -297,6 +298,7 @@ export class Search extends Component<any, SearchState> {
|
||||||
)}
|
)}
|
||||||
{i.type_ == 'comments' && (
|
{i.type_ == 'comments' && (
|
||||||
<CommentNodes
|
<CommentNodes
|
||||||
|
key={(i.data as Comment).id}
|
||||||
nodes={[{ comment: i.data as Comment }]}
|
nodes={[{ comment: i.data as Comment }]}
|
||||||
locked
|
locked
|
||||||
noIndent
|
noIndent
|
||||||
|
|
1
ui/src/components/site-form.tsx
vendored
1
ui/src/components/site-form.tsx
vendored
|
@ -111,6 +111,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
<MarkdownTextArea
|
<MarkdownTextArea
|
||||||
initialContent={this.state.siteForm.description}
|
initialContent={this.state.siteForm.description}
|
||||||
onContentChange={this.handleSiteDescriptionChange}
|
onContentChange={this.handleSiteDescriptionChange}
|
||||||
|
hideNavigationWarnings
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
2
ui/src/components/sort-select.tsx
vendored
2
ui/src/components/sort-select.tsx
vendored
|
@ -65,6 +65,6 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSortChange(i: SortSelect, event: any) {
|
handleSortChange(i: SortSelect, event: any) {
|
||||||
i.props.onChange(event.target.value);
|
i.props.onChange(Number(event.target.value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
ui/src/components/user-details.tsx
vendored
2
ui/src/components/user-details.tsx
vendored
|
@ -150,6 +150,7 @@ export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
|
||||||
<div>
|
<div>
|
||||||
{i.type === 'posts' ? (
|
{i.type === 'posts' ? (
|
||||||
<PostListing
|
<PostListing
|
||||||
|
key={(i.data as Post).id}
|
||||||
post={i.data as Post}
|
post={i.data as Post}
|
||||||
admins={this.props.admins}
|
admins={this.props.admins}
|
||||||
showCommunity
|
showCommunity
|
||||||
|
@ -158,6 +159,7 @@ export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<CommentNodes
|
<CommentNodes
|
||||||
|
key={(i.data as Comment).id}
|
||||||
nodes={[{ comment: i.data as Comment }]}
|
nodes={[{ comment: i.data as Comment }]}
|
||||||
admins={this.props.admins}
|
admins={this.props.admins}
|
||||||
noBorder
|
noBorder
|
||||||
|
|
43
ui/src/components/user.tsx
vendored
43
ui/src/components/user.tsx
vendored
|
@ -30,6 +30,8 @@ import {
|
||||||
showAvatars,
|
showAvatars,
|
||||||
toast,
|
toast,
|
||||||
setupTippy,
|
setupTippy,
|
||||||
|
getLanguage,
|
||||||
|
mdToHtml,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { UserListing } from './user-listing';
|
import { UserListing } from './user-listing';
|
||||||
import { SortSelect } from './sort-select';
|
import { SortSelect } from './sort-select';
|
||||||
|
@ -38,6 +40,7 @@ import { MomentTime } from './moment-time';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { UserDetails } from './user-details';
|
import { UserDetails } from './user-details';
|
||||||
|
import { MarkdownTextArea } from './markdown-textarea';
|
||||||
|
|
||||||
interface UserState {
|
interface UserState {
|
||||||
user: UserView;
|
user: UserView;
|
||||||
|
@ -108,6 +111,7 @@ export class User extends Component<any, UserState> {
|
||||||
show_avatars: null,
|
show_avatars: null,
|
||||||
send_notifications_to_email: null,
|
send_notifications_to_email: null,
|
||||||
auth: null,
|
auth: null,
|
||||||
|
bio: null,
|
||||||
},
|
},
|
||||||
userSettingsLoading: null,
|
userSettingsLoading: null,
|
||||||
deleteAccountLoading: null,
|
deleteAccountLoading: null,
|
||||||
|
@ -149,6 +153,9 @@ export class User extends Component<any, UserState> {
|
||||||
this
|
this
|
||||||
);
|
);
|
||||||
this.handlePageChange = this.handlePageChange.bind(this);
|
this.handlePageChange = this.handlePageChange.bind(this);
|
||||||
|
this.handleUserSettingsBioChange = this.handleUserSettingsBioChange.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
this.state.user_id = Number(this.props.match.params.id) || null;
|
this.state.user_id = Number(this.props.match.params.id) || null;
|
||||||
this.state.username = this.props.match.params.username;
|
this.state.username = this.props.match.params.username;
|
||||||
|
@ -374,6 +381,14 @@ export class User extends Component<any, UserState> {
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</h5>
|
</h5>
|
||||||
|
{user.bio && (
|
||||||
|
<div className="d-flex align-items-center mb-2">
|
||||||
|
<div
|
||||||
|
className="md-div"
|
||||||
|
dangerouslySetInnerHTML={mdToHtml(user.bio)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="d-flex align-items-center mb-2">
|
<div className="d-flex align-items-center mb-2">
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<use xlinkHref="#icon-cake"></use>
|
<use xlinkHref="#icon-cake"></use>
|
||||||
|
@ -569,6 +584,19 @@ export class User extends Component<any, UserState> {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-lg-3 col-form-label" htmlFor="user-bio">
|
||||||
|
{i18n.t('bio')}
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-9">
|
||||||
|
<MarkdownTextArea
|
||||||
|
initialContent={this.state.userSettingsForm.bio}
|
||||||
|
onContentChange={this.handleUserSettingsBioChange}
|
||||||
|
maxLength={300}
|
||||||
|
hideNavigationWarnings
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-lg-5 col-form-label">
|
<label class="col-lg-5 col-form-label">
|
||||||
<a
|
<a
|
||||||
|
@ -877,7 +905,7 @@ export class User extends Component<any, UserState> {
|
||||||
|
|
||||||
handleUserSettingsLangChange(i: User, event: any) {
|
handleUserSettingsLangChange(i: User, event: any) {
|
||||||
i.state.userSettingsForm.lang = event.target.value;
|
i.state.userSettingsForm.lang = event.target.value;
|
||||||
i18n.changeLanguage(i.state.userSettingsForm.lang);
|
i18n.changeLanguage(getLanguage(i.state.userSettingsForm.lang));
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -899,6 +927,11 @@ export class User extends Component<any, UserState> {
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleUserSettingsBioChange(val: string) {
|
||||||
|
this.state.userSettingsForm.bio = val;
|
||||||
|
this.setState(this.state);
|
||||||
|
}
|
||||||
|
|
||||||
handleUserSettingsMatrixUserIdChange(i: User, event: any) {
|
handleUserSettingsMatrixUserIdChange(i: User, event: any) {
|
||||||
i.state.userSettingsForm.matrix_user_id = event.target.value;
|
i.state.userSettingsForm.matrix_user_id = event.target.value;
|
||||||
if (
|
if (
|
||||||
|
@ -1056,6 +1089,7 @@ export class User extends Component<any, UserState> {
|
||||||
this.state.userSettingsForm.lang = UserService.Instance.user.lang;
|
this.state.userSettingsForm.lang = UserService.Instance.user.lang;
|
||||||
this.state.userSettingsForm.avatar = UserService.Instance.user.avatar;
|
this.state.userSettingsForm.avatar = UserService.Instance.user.avatar;
|
||||||
this.state.userSettingsForm.email = this.state.user.email;
|
this.state.userSettingsForm.email = this.state.user.email;
|
||||||
|
this.state.userSettingsForm.bio = this.state.user.bio;
|
||||||
this.state.userSettingsForm.send_notifications_to_email = this.state.user.send_notifications_to_email;
|
this.state.userSettingsForm.send_notifications_to_email = this.state.user.send_notifications_to_email;
|
||||||
this.state.userSettingsForm.show_avatars =
|
this.state.userSettingsForm.show_avatars =
|
||||||
UserService.Instance.user.show_avatars;
|
UserService.Instance.user.show_avatars;
|
||||||
|
@ -1067,9 +1101,10 @@ export class User extends Component<any, UserState> {
|
||||||
} else if (res.op == UserOperation.SaveUserSettings) {
|
} else if (res.op == UserOperation.SaveUserSettings) {
|
||||||
const data = res.data as LoginResponse;
|
const data = res.data as LoginResponse;
|
||||||
UserService.Instance.login(data);
|
UserService.Instance.login(data);
|
||||||
this.setState({
|
this.state.user.bio = this.state.userSettingsForm.bio;
|
||||||
userSettingsLoading: false,
|
this.state.userSettingsLoading = false;
|
||||||
});
|
this.setState(this.state);
|
||||||
|
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
} else if (res.op == UserOperation.DeleteAccount) {
|
} else if (res.op == UserOperation.DeleteAccount) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|
1
ui/src/interfaces.ts
vendored
1
ui/src/interfaces.ts
vendored
|
@ -597,6 +597,7 @@ export interface UserSettingsForm {
|
||||||
lang: string;
|
lang: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
|
bio?: string;
|
||||||
matrix_user_id?: string;
|
matrix_user_id?: string;
|
||||||
new_password?: string;
|
new_password?: string;
|
||||||
new_password_verify?: string;
|
new_password_verify?: string;
|
||||||
|
|
4
ui/src/services/WebSocketService.ts
vendored
4
ui/src/services/WebSocketService.ts
vendored
|
@ -82,10 +82,6 @@ export class WebSocketService {
|
||||||
this.ws.onopen = () => {
|
this.ws.onopen = () => {
|
||||||
console.log(`Connected to ${wsUri}`);
|
console.log(`Connected to ${wsUri}`);
|
||||||
|
|
||||||
if (UserService.Instance.user) {
|
|
||||||
this.userJoin();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!firstConnect) {
|
if (!firstConnect) {
|
||||||
let res: WebSocketJsonResponse = {
|
let res: WebSocketJsonResponse = {
|
||||||
reconnect: true,
|
reconnect: true,
|
||||||
|
|
15
ui/src/utils.ts
vendored
15
ui/src/utils.ts
vendored
|
@ -350,9 +350,9 @@ export function debounce(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLanguage(): string {
|
export function getLanguage(override?: string): string {
|
||||||
let user = UserService.Instance.user;
|
let user = UserService.Instance.user;
|
||||||
let lang = user && user.lang ? user.lang : 'browser';
|
let lang = override || (user && user.lang ? user.lang : 'browser');
|
||||||
|
|
||||||
if (lang == 'browser') {
|
if (lang == 'browser') {
|
||||||
return getBrowserLanguage();
|
return getBrowserLanguage();
|
||||||
|
@ -588,6 +588,9 @@ export function messageToastify(
|
||||||
|
|
||||||
export function setupTribute(): Tribute {
|
export function setupTribute(): Tribute {
|
||||||
return new Tribute({
|
return new Tribute({
|
||||||
|
noMatchTemplate: function () {
|
||||||
|
return '';
|
||||||
|
},
|
||||||
collection: [
|
collection: [
|
||||||
// Emojis
|
// Emojis
|
||||||
{
|
{
|
||||||
|
@ -669,7 +672,7 @@ function userSearch(text: string, cb: any) {
|
||||||
|
|
||||||
WebSocketService.Instance.search(form);
|
WebSocketService.Instance.search(form);
|
||||||
|
|
||||||
this.userSub = WebSocketService.Instance.subject.subscribe(
|
let userSub = WebSocketService.Instance.subject.subscribe(
|
||||||
msg => {
|
msg => {
|
||||||
let res = wsJsonToRes(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (res.op == UserOperation.Search) {
|
if (res.op == UserOperation.Search) {
|
||||||
|
@ -683,7 +686,7 @@ function userSearch(text: string, cb: any) {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
cb(users);
|
cb(users);
|
||||||
this.userSub.unsubscribe();
|
userSub.unsubscribe();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
err => console.error(err),
|
err => console.error(err),
|
||||||
|
@ -706,7 +709,7 @@ function communitySearch(text: string, cb: any) {
|
||||||
|
|
||||||
WebSocketService.Instance.search(form);
|
WebSocketService.Instance.search(form);
|
||||||
|
|
||||||
this.communitySub = WebSocketService.Instance.subject.subscribe(
|
let communitySub = WebSocketService.Instance.subject.subscribe(
|
||||||
msg => {
|
msg => {
|
||||||
let res = wsJsonToRes(msg);
|
let res = wsJsonToRes(msg);
|
||||||
if (res.op == UserOperation.Search) {
|
if (res.op == UserOperation.Search) {
|
||||||
|
@ -720,7 +723,7 @@ function communitySearch(text: string, cb: any) {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
cb(communities);
|
cb(communities);
|
||||||
this.communitySub.unsubscribe();
|
communitySub.unsubscribe();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
err => console.error(err),
|
err => console.error(err),
|
||||||
|
|
25
ui/translations/de.json
vendored
25
ui/translations/de.json
vendored
|
@ -14,7 +14,7 @@
|
||||||
"number_of_comments": "{{count}} Kommentar",
|
"number_of_comments": "{{count}} Kommentar",
|
||||||
"number_of_comments_plural": "{{count}} Kommentare",
|
"number_of_comments_plural": "{{count}} Kommentare",
|
||||||
"remove_comment": "Kommentar löschen",
|
"remove_comment": "Kommentar löschen",
|
||||||
"communities": "Communitys",
|
"communities": "Communities",
|
||||||
"users": "Benutzer",
|
"users": "Benutzer",
|
||||||
"create_a_community": "Eine Community anlegen",
|
"create_a_community": "Eine Community anlegen",
|
||||||
"create_community": "Community erstellen",
|
"create_community": "Community erstellen",
|
||||||
|
@ -168,7 +168,7 @@
|
||||||
"yes": "Ja",
|
"yes": "Ja",
|
||||||
"no": "Nein",
|
"no": "Nein",
|
||||||
"powered_by": "Bereitgestellt durch",
|
"powered_by": "Bereitgestellt durch",
|
||||||
"landing_0": "Lemmy ist ein <1>Link-Aggregator</1> / Reddit Alternative im <2>Fediverse</2>.<3></3>Es ist selbst-hostbar, hat live-updates von Kommentar-threads und ist winzig (<4>~80kB</4>). Federation in das ActivityPub Netzwerk ist geplant. <5></5>Dies ist eine <6>sehr frühe Beta Version</6>, und viele Features funktionieren zurzeit nicht richtig oder fehlen. <7></7>Schlage neue Features vor oder melde Bugs <8>hier.</8><9></9>Gebaut mit <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
|
"landing": "Lemmy ist ein <1>Link-Aggregator</1> / Reddit Alternative im <2>Fediverse</2>.<3></3>Es ist selbst-hostbar, hat live-updates von Kommentar-threads und ist winzig (<4>~80kB</4>). Federation in das ActivityPub Netzwerk ist geplant. <5></5>Dies ist eine <6>sehr frühe Beta Version</6>, und viele Features funktionieren zurzeit nicht richtig oder fehlen. <7></7>Schlage neue Features vor oder melde Bugs <8>hier.</8><9></9>Gebaut mit <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>. <14></14> <15>Vielen Dank an unsere Mitwirkenden: </15> dessalines, Nutomic, asonix, zacanger, and iav.",
|
||||||
"not_logged_in": "Nicht eingeloggt.",
|
"not_logged_in": "Nicht eingeloggt.",
|
||||||
"community_ban": "Du wurdest von dieser Community gebannt.",
|
"community_ban": "Du wurdest von dieser Community gebannt.",
|
||||||
"site_ban": "Du wurdest von dieser Seite gebannt",
|
"site_ban": "Du wurdest von dieser Seite gebannt",
|
||||||
|
@ -216,7 +216,7 @@
|
||||||
"messages": "Nachrichten",
|
"messages": "Nachrichten",
|
||||||
"old_password": "Letztes Passwort",
|
"old_password": "Letztes Passwort",
|
||||||
"matrix_user_id": "Matrix Benutzer",
|
"matrix_user_id": "Matrix Benutzer",
|
||||||
"private_message_disclaimer": "Achtung: Private Nachrichten sind in Lemmy nicht sicher. Bitte erstelle einen <1>Riot.im</1> Account für sicheren Nachrichtenverkehr.",
|
"private_message_disclaimer": "Achtung: Private Nachrichten sind in Lemmy nicht verschlüsselt. Bitte erstelle einen<1>Element.io</1> Account für sicheren Nachrichtenverkehr.",
|
||||||
"send_notifications_to_email": "Sende Benachrichtigungen per Email",
|
"send_notifications_to_email": "Sende Benachrichtigungen per Email",
|
||||||
"downvotes_disabled": "Downvotes deaktiviert",
|
"downvotes_disabled": "Downvotes deaktiviert",
|
||||||
"enable_downvotes": "Aktiviere Downvotes",
|
"enable_downvotes": "Aktiviere Downvotes",
|
||||||
|
@ -256,5 +256,22 @@
|
||||||
"click_to_delete_picture": "Klicke, um das Bild zu löschen.",
|
"click_to_delete_picture": "Klicke, um das Bild zu löschen.",
|
||||||
"picture_deleted": "Bild gelöscht.",
|
"picture_deleted": "Bild gelöscht.",
|
||||||
"select_a_community": "Wähle eine Community aus",
|
"select_a_community": "Wähle eine Community aus",
|
||||||
"invalid_username": "Ungültiger Benutzername."
|
"invalid_username": "Ungültiger Benutzername.",
|
||||||
|
"bold": "fett",
|
||||||
|
"italic": "kursiv",
|
||||||
|
"subscript": "Tiefzeichen",
|
||||||
|
"superscript": "Hochzeichen",
|
||||||
|
"header": "Header",
|
||||||
|
"strikethrough": "durchgestrichen",
|
||||||
|
"quote": "Zitat",
|
||||||
|
"spoiler": "Spoiler",
|
||||||
|
"list": "Liste",
|
||||||
|
"not_a_moderator": "Kein Moderator.",
|
||||||
|
"invalid_url": "Ungültige URL.",
|
||||||
|
"must_login": "Du musst <1>eingeloggt oder registriert</1> sein um zu Kommentieren.",
|
||||||
|
"no_password_reset": "Du kannst dein Passwort ohne E-Mail nicht zurücksetzen.",
|
||||||
|
"cake_day_info": "Heute ist {{ creator_name }}'s cake day!",
|
||||||
|
"invalid_post_title": "Ungültiger Post Titel",
|
||||||
|
"cake_day_title": "Cake day:",
|
||||||
|
"what_is": "Was ist"
|
||||||
}
|
}
|
||||||
|
|
4
ui/translations/en.json
vendored
4
ui/translations/en.json
vendored
|
@ -228,6 +228,7 @@
|
||||||
"landing_0":
|
"landing_0":
|
||||||
"Lemmy is a <1>link aggregator</1> / reddit alternative, intended to work in the <2>fediverse</2>.<3></3>It's self-hostable, has live-updating comment threads, and is tiny (<4>~80kB</4>). Federation into the ActivityPub network is on the roadmap. <5></5>This is a <6>very early beta version</6>, and a lot of features are currently broken or missing. <7></7>Suggest new features or report bugs <8>here.</8><9></9>Made with <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>. <14></14> <15>Thank you to our contributors: </15> dessalines, Nutomic, asonix, zacanger, and iav.",
|
"Lemmy is a <1>link aggregator</1> / reddit alternative, intended to work in the <2>fediverse</2>.<3></3>It's self-hostable, has live-updating comment threads, and is tiny (<4>~80kB</4>). Federation into the ActivityPub network is on the roadmap. <5></5>This is a <6>very early beta version</6>, and a lot of features are currently broken or missing. <7></7>Suggest new features or report bugs <8>here.</8><9></9>Made with <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>. <14></14> <15>Thank you to our contributors: </15> dessalines, Nutomic, asonix, zacanger, and iav.",
|
||||||
"not_logged_in": "Not logged in.",
|
"not_logged_in": "Not logged in.",
|
||||||
|
"bio_length_overflow": "User bio cannot exceed 300 characters!",
|
||||||
"logged_in": "Logged in.",
|
"logged_in": "Logged in.",
|
||||||
"must_login": "You must <1>log in or register</1> to comment.",
|
"must_login": "You must <1>log in or register</1> to comment.",
|
||||||
"site_saved": "Site Saved.",
|
"site_saved": "Site Saved.",
|
||||||
|
@ -284,5 +285,6 @@
|
||||||
"cake_day_info": "It's {{ creator_name }}'s cake day today!",
|
"cake_day_info": "It's {{ creator_name }}'s cake day today!",
|
||||||
"invalid_post_title": "Invalid post title",
|
"invalid_post_title": "Invalid post title",
|
||||||
"invalid_url": "Invalid URL.",
|
"invalid_url": "Invalid URL.",
|
||||||
"play_captcha_audio": "Play Captcha Audio"
|
"play_captcha_audio": "Play Captcha Audio",
|
||||||
|
"bio": "Bio"
|
||||||
}
|
}
|
||||||
|
|
4
ui/translations/ga.json
vendored
4
ui/translations/ga.json
vendored
|
@ -298,5 +298,7 @@
|
||||||
"user_already_exists": "Úsáideoir ann cheana.",
|
"user_already_exists": "Úsáideoir ann cheana.",
|
||||||
"email_already_exists": "Tá ríomhphost ann cheana féin.",
|
"email_already_exists": "Tá ríomhphost ann cheana féin.",
|
||||||
"couldnt_update_user": "Níorbh fhéidir an t-úsáideoir a nuashonrú.",
|
"couldnt_update_user": "Níorbh fhéidir an t-úsáideoir a nuashonrú.",
|
||||||
"time": "Am"
|
"time": "Am",
|
||||||
|
"subscript": "fo-script",
|
||||||
|
"superscript": "sár-script"
|
||||||
}
|
}
|
||||||
|
|
3
ui/translations/pl.json
vendored
3
ui/translations/pl.json
vendored
|
@ -262,5 +262,6 @@
|
||||||
"emoji_picker": "Wybór Emoji",
|
"emoji_picker": "Wybór Emoji",
|
||||||
"silver_sponsors": "Srebrni Sponsorzy to ci, którzy wpłacili co najmniej $40 na Lemmiego.",
|
"silver_sponsors": "Srebrni Sponsorzy to ci, którzy wpłacili co najmniej $40 na Lemmiego.",
|
||||||
"select_a_community": "Wybierz społeczność",
|
"select_a_community": "Wybierz społeczność",
|
||||||
"invalid_username": "Nieprawidłowa nazwa użytkownika."
|
"invalid_username": "Nieprawidłowa nazwa użytkownika.",
|
||||||
|
"invalid_community_name": "Niepoprawna nazwa."
|
||||||
}
|
}
|
||||||
|
|
26
ui/yarn.lock
vendored
26
ui/yarn.lock
vendored
|
@ -3813,14 +3813,14 @@ infer-owner@^1.0.4:
|
||||||
resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
|
resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
|
||||||
integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==
|
integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==
|
||||||
|
|
||||||
inferno-clone-vnode@^7.1.12:
|
inferno-clone-vnode@^7.4.2:
|
||||||
version "7.4.2"
|
version "7.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/inferno-clone-vnode/-/inferno-clone-vnode-7.4.2.tgz#071577098fd8ffdffd41cf81819207effa520bc1"
|
resolved "https://registry.yarnpkg.com/inferno-clone-vnode/-/inferno-clone-vnode-7.4.2.tgz#071577098fd8ffdffd41cf81819207effa520bc1"
|
||||||
integrity sha512-pX5agEWfU+w6vYaVyKtzgFT4jlMK+eOEKL/LkyWu2dtOy0DXx174AFm6GSgOt66W6xmxn58sbWI7ngsWmp4f2w==
|
integrity sha512-pX5agEWfU+w6vYaVyKtzgFT4jlMK+eOEKL/LkyWu2dtOy0DXx174AFm6GSgOt66W6xmxn58sbWI7ngsWmp4f2w==
|
||||||
dependencies:
|
dependencies:
|
||||||
inferno "7.4.2"
|
inferno "7.4.2"
|
||||||
|
|
||||||
inferno-create-element@^7.1.12:
|
inferno-create-element@^7.4.2:
|
||||||
version "7.4.2"
|
version "7.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/inferno-create-element/-/inferno-create-element-7.4.2.tgz#d3ac6c64c792f8d3c4784279825418ebd73cfe1c"
|
resolved "https://registry.yarnpkg.com/inferno-create-element/-/inferno-create-element-7.4.2.tgz#d3ac6c64c792f8d3c4784279825418ebd73cfe1c"
|
||||||
integrity sha512-FHNca1NR/9SRtaifr4DdAuA6dKBeWkCrYVVkTazMadJyjMtpPN3d+sKlkmdvorAjhUvdMMNcRQStrgRTpLcZyg==
|
integrity sha512-FHNca1NR/9SRtaifr4DdAuA6dKBeWkCrYVVkTazMadJyjMtpPN3d+sKlkmdvorAjhUvdMMNcRQStrgRTpLcZyg==
|
||||||
|
@ -3836,16 +3836,16 @@ inferno-helmet@^5.2.1:
|
||||||
inferno-side-effect "^1.1.5"
|
inferno-side-effect "^1.1.5"
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
inferno-i18next@nimbusec-oss/inferno-i18next:
|
"inferno-i18next@github:nimbusec-oss/inferno-i18next#semver:^7.4.2":
|
||||||
version "7.1.12"
|
version "7.4.2"
|
||||||
resolved "https://codeload.github.com/nimbusec-oss/inferno-i18next/tar.gz/f8c1403e60be70141c558e36f12f22c106cb7463"
|
resolved "https://codeload.github.com/nimbusec-oss/inferno-i18next/tar.gz/54b9be591ccd62c53799ad23e35f17144a62f909"
|
||||||
dependencies:
|
dependencies:
|
||||||
html-parse-stringify2 "^2.0.1"
|
html-parse-stringify2 "^2.0.1"
|
||||||
inferno "^7.1.12"
|
inferno "^7.4.2"
|
||||||
inferno-clone-vnode "^7.1.12"
|
inferno-clone-vnode "^7.4.2"
|
||||||
inferno-create-element "^7.1.12"
|
inferno-create-element "^7.4.2"
|
||||||
inferno-shared "^7.1.12"
|
inferno-shared "^7.4.2"
|
||||||
inferno-vnode-flags "^7.1.12"
|
inferno-vnode-flags "^7.4.2"
|
||||||
|
|
||||||
inferno-router@^7.4.2:
|
inferno-router@^7.4.2:
|
||||||
version "7.4.2"
|
version "7.4.2"
|
||||||
|
@ -3857,7 +3857,7 @@ inferno-router@^7.4.2:
|
||||||
inferno "7.4.2"
|
inferno "7.4.2"
|
||||||
path-to-regexp-es6 "1.7.0"
|
path-to-regexp-es6 "1.7.0"
|
||||||
|
|
||||||
inferno-shared@7.4.2, inferno-shared@^7.1.12:
|
inferno-shared@7.4.2, inferno-shared@^7.4.2:
|
||||||
version "7.4.2"
|
version "7.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/inferno-shared/-/inferno-shared-7.4.2.tgz#400cf6d19a077af9e5e852e1f189726391efa273"
|
resolved "https://registry.yarnpkg.com/inferno-shared/-/inferno-shared-7.4.2.tgz#400cf6d19a077af9e5e852e1f189726391efa273"
|
||||||
integrity sha512-SULfgzj/PuyMd3rHThEXkeEdZorjcr/q+kaqbwr7C2XhIPCk4e5jcRKZujI6YCSahGA9WFwP2Mft1v5PsaaNeg==
|
integrity sha512-SULfgzj/PuyMd3rHThEXkeEdZorjcr/q+kaqbwr7C2XhIPCk4e5jcRKZujI6YCSahGA9WFwP2Mft1v5PsaaNeg==
|
||||||
|
@ -3871,12 +3871,12 @@ inferno-side-effect@^1.1.5:
|
||||||
npm "^5.8.0"
|
npm "^5.8.0"
|
||||||
shallowequal "^1.0.1"
|
shallowequal "^1.0.1"
|
||||||
|
|
||||||
inferno-vnode-flags@7.4.2, inferno-vnode-flags@^7.1.12:
|
inferno-vnode-flags@7.4.2, inferno-vnode-flags@^7.4.2:
|
||||||
version "7.4.2"
|
version "7.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/inferno-vnode-flags/-/inferno-vnode-flags-7.4.2.tgz#54982dabe34f308853ba17de7de4241e23769135"
|
resolved "https://registry.yarnpkg.com/inferno-vnode-flags/-/inferno-vnode-flags-7.4.2.tgz#54982dabe34f308853ba17de7de4241e23769135"
|
||||||
integrity sha512-sV4KqqvZH4MW9/dNbC9blHInnpQSqMWouU5VlanbJ+NhJ8ufPwsDy0/+jiA2aODpg2HFHwVMJFF1fsewPqNtLQ==
|
integrity sha512-sV4KqqvZH4MW9/dNbC9blHInnpQSqMWouU5VlanbJ+NhJ8ufPwsDy0/+jiA2aODpg2HFHwVMJFF1fsewPqNtLQ==
|
||||||
|
|
||||||
inferno@7.4.2, inferno@^7.1.12, inferno@^7.4.2:
|
inferno@7.4.2, inferno@^7.4.2:
|
||||||
version "7.4.2"
|
version "7.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/inferno/-/inferno-7.4.2.tgz#833cc423ee7b939fad705c59ea41924f31c92453"
|
resolved "https://registry.yarnpkg.com/inferno/-/inferno-7.4.2.tgz#833cc423ee7b939fad705c59ea41924f31c92453"
|
||||||
integrity sha512-RsmuN8F7lAWTTuy2juf3Tqn/BihkRNdy0WZN+vuyryuEySKHBA1fruyq6K0covF3Ja8mAdha5NNISZz9ltgcug==
|
integrity sha512-RsmuN8F7lAWTTuy2juf3Tqn/BihkRNdy0WZN+vuyryuEySKHBA1fruyq6K0covF3Ja8mAdha5NNISZz9ltgcug==
|
||||||
|
|
Loading…
Reference in a new issue