mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-26 06:11:26 +00:00
Merge branch 'master' into iav-arm-musl-dessalines
This commit is contained in:
commit
983a45e178
31 changed files with 466 additions and 165 deletions
34
RELEASES.md
vendored
34
RELEASES.md
vendored
|
@ -1,3 +1,37 @@
|
||||||
|
# Lemmy v0.7.0 Release (2020-06-2X)
|
||||||
|
|
||||||
|
## Breaking Change to our image server: Pictshare to Pict-rs migration guide
|
||||||
|
|
||||||
|
This release replaces [pictshare](https://github.com/HaschekSolutions/pictshare) with [pict-rs](https://git.asonix.dog/asonix/pict-rs), and a script must be run on your server to upgrade.
|
||||||
|
|
||||||
|
To update, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd /lemmy
|
||||||
|
wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/docker-compose.yml
|
||||||
|
wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/migrate-pictshare-to-pictrs.bash
|
||||||
|
sudo bash migrate-pictshare-to-pictrs.bash
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll also have to update your nginx config, use the [one here](https://github.com/LemmyNet/lemmy/blob/master/ansible/templates/nginx.conf).
|
||||||
|
|
||||||
|
*You'll have to log in again to pick up your avatar*
|
||||||
|
|
||||||
|
Apart from that, we've closed [~90 issues!](https://github.com/LemmyNet/lemmy/milestone/16?closed=1), including:
|
||||||
|
|
||||||
|
- Site-wide list of recent comments.
|
||||||
|
- Reconnecting websockets.
|
||||||
|
- Lots more themes, including a default light one.
|
||||||
|
- Expandable embeds for post links (and thumbnails), from iframely.
|
||||||
|
- Better icons.
|
||||||
|
- Emoji autocomplete to post and message bodies, and an Emoji Picker.
|
||||||
|
- Post body now searchable.
|
||||||
|
- Community title and description is now searchable.
|
||||||
|
- Simplified cross-posts.
|
||||||
|
- Better documentation.
|
||||||
|
- LOTS more languages.
|
||||||
|
- Lots of bugs squashed.
|
||||||
|
|
||||||
# Lemmy v0.6.0 Release (2020-01-16)
|
# Lemmy v0.6.0 Release (2020-01-16)
|
||||||
|
|
||||||
`v0.6.0` is here, and we've closed [41 issues!](https://github.com/LemmyNet/lemmy/milestone/15?closed=1)
|
`v0.6.0` is here, and we've closed [41 issues!](https://github.com/LemmyNet/lemmy/milestone/15?closed=1)
|
||||||
|
|
2
ansible/VERSION
vendored
2
ansible/VERSION
vendored
|
@ -1 +1 @@
|
||||||
v0.6.77
|
v0.6.79
|
||||||
|
|
1
ansible/ansible.cfg
vendored
1
ansible/ansible.cfg
vendored
|
@ -1,5 +1,6 @@
|
||||||
[defaults]
|
[defaults]
|
||||||
inventory=inventory
|
inventory=inventory
|
||||||
|
interpreter_python=/usr/bin/python3
|
||||||
|
|
||||||
[ssh_connection]
|
[ssh_connection]
|
||||||
pipelining = True
|
pipelining = True
|
||||||
|
|
8
ansible/lemmy.yml
vendored
8
ansible/lemmy.yml
vendored
|
@ -24,10 +24,11 @@
|
||||||
creates: '/etc/letsencrypt/live/{{domain}}/privkey.pem'
|
creates: '/etc/letsencrypt/live/{{domain}}/privkey.pem'
|
||||||
|
|
||||||
- name: create lemmy folder
|
- name: create lemmy folder
|
||||||
file: path={{item.path}} state=directory
|
file: path={{item.path}} {{item.owner}} state=directory
|
||||||
with_items:
|
with_items:
|
||||||
- { path: '/lemmy/' }
|
- { path: '/lemmy/', owner: 'root' }
|
||||||
- { path: '/lemmy/volumes/' }
|
- { path: '/lemmy/volumes/', owner: 'root' }
|
||||||
|
- { path: '/lemmy/volumes/pictrs/', owner: '991' }
|
||||||
|
|
||||||
- block:
|
- block:
|
||||||
- name: add template files
|
- name: add template files
|
||||||
|
@ -59,6 +60,7 @@
|
||||||
project_src: /lemmy/
|
project_src: /lemmy/
|
||||||
state: present
|
state: present
|
||||||
pull: yes
|
pull: yes
|
||||||
|
remove_orphans: yes
|
||||||
|
|
||||||
- name: reload nginx with new config
|
- name: reload nginx with new config
|
||||||
shell: nginx -s reload
|
shell: nginx -s reload
|
||||||
|
|
8
ansible/lemmy_dev.yml
vendored
8
ansible/lemmy_dev.yml
vendored
|
@ -26,10 +26,11 @@
|
||||||
creates: '/etc/letsencrypt/live/{{domain}}/privkey.pem'
|
creates: '/etc/letsencrypt/live/{{domain}}/privkey.pem'
|
||||||
|
|
||||||
- name: create lemmy folder
|
- name: create lemmy folder
|
||||||
file: path={{item.path}} state=directory
|
file: path={{item.path}} owner={{item.owner}} state=directory
|
||||||
with_items:
|
with_items:
|
||||||
- { path: '/lemmy/' }
|
- { path: '/lemmy/', owner: 'root' }
|
||||||
- { path: '/lemmy/volumes/' }
|
- { path: '/lemmy/volumes/', owner: 'root' }
|
||||||
|
- { path: '/lemmy/volumes/pictrs/', owner: '991' }
|
||||||
|
|
||||||
- block:
|
- block:
|
||||||
- name: add template files
|
- name: add template files
|
||||||
|
@ -88,6 +89,7 @@
|
||||||
project_src: /lemmy/
|
project_src: /lemmy/
|
||||||
state: present
|
state: present
|
||||||
recreate: always
|
recreate: always
|
||||||
|
remove_orphans: yes
|
||||||
ignore_errors: yes
|
ignore_errors: yes
|
||||||
|
|
||||||
- name: reload nginx with new config
|
- name: reload nginx with new config
|
||||||
|
|
11
ansible/templates/docker-compose.yml
vendored
11
ansible/templates/docker-compose.yml
vendored
|
@ -12,7 +12,7 @@ services:
|
||||||
- ./lemmy.hjson:/config/config.hjson:ro
|
- ./lemmy.hjson:/config/config.hjson:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- pictshare
|
- pictrs
|
||||||
- iframely
|
- iframely
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
|
@ -25,12 +25,13 @@ services:
|
||||||
- ./volumes/postgres:/var/lib/postgresql/data
|
- ./volumes/postgres:/var/lib/postgresql/data
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
pictshare:
|
pictrs:
|
||||||
image: hascheksolutions/pictshare:latest
|
image: asonix/pictrs:amd64-v0.1.0-r9
|
||||||
|
user: 991:991
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:8537:80"
|
- "127.0.0.1:8537:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- ./volumes/pictshare:/usr/share/nginx/html/data
|
- ./volumes/pictrs:/mnt
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
iframely:
|
iframely:
|
||||||
|
|
26
ansible/templates/nginx.conf
vendored
26
ansible/templates/nginx.conf
vendored
|
@ -48,8 +48,8 @@ server {
|
||||||
add_header X-Frame-Options "DENY";
|
add_header X-Frame-Options "DENY";
|
||||||
add_header X-XSS-Protection "1; mode=block";
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
|
|
||||||
# Upload limit for pictshare
|
# Upload limit for pictrs
|
||||||
client_max_body_size 50M;
|
client_max_body_size 20M;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://0.0.0.0:8536;
|
proxy_pass http://0.0.0.0:8536;
|
||||||
|
@ -70,15 +70,21 @@ server {
|
||||||
proxy_cache_min_uses 5;
|
proxy_cache_min_uses 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /pictshare/ {
|
# Redirect pictshare images to pictrs
|
||||||
proxy_pass http://0.0.0.0:8537/;
|
location ~ /pictshare/(.*)$ {
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
return 301 /pictrs/image/$1;
|
||||||
proxy_set_header Host $host;
|
}
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
|
|
||||||
if ($request_uri ~ \.(?:ico|gif|jpe?g|png|webp|bmp|mp4)$) {
|
# pict-rs images
|
||||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
location /pictrs {
|
||||||
}
|
location /pictrs/image {
|
||||||
|
proxy_pass http://0.0.0.0:8537/image;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
# Block the import
|
||||||
|
return 403;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /iframely/ {
|
location /iframely/ {
|
||||||
|
|
32
docker/dev/docker-compose.yml
vendored
32
docker/dev/docker-compose.yml
vendored
|
@ -1,15 +1,6 @@
|
||||||
version: '3.3'
|
version: '3.3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
|
||||||
image: postgres:12-alpine
|
|
||||||
environment:
|
|
||||||
- POSTGRES_USER=lemmy
|
|
||||||
- POSTGRES_PASSWORD=password
|
|
||||||
- POSTGRES_DB=lemmy
|
|
||||||
volumes:
|
|
||||||
- ./volumes/postgres:/var/lib/postgresql/data
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
lemmy:
|
lemmy:
|
||||||
build:
|
build:
|
||||||
|
@ -23,16 +14,27 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ../lemmy.hjson:/config/config.hjson
|
- ../lemmy.hjson:/config/config.hjson
|
||||||
depends_on:
|
depends_on:
|
||||||
|
- pictrs
|
||||||
- postgres
|
- postgres
|
||||||
- pictshare
|
|
||||||
- iframely
|
- iframely
|
||||||
|
|
||||||
pictshare:
|
postgres:
|
||||||
image: hascheksolutions/pictshare:latest
|
image: postgres:12-alpine
|
||||||
ports:
|
environment:
|
||||||
- "127.0.0.1:8537:80"
|
- POSTGRES_USER=lemmy
|
||||||
|
- POSTGRES_PASSWORD=password
|
||||||
|
- POSTGRES_DB=lemmy
|
||||||
volumes:
|
volumes:
|
||||||
- ./volumes/pictshare:/usr/share/nginx/html/data
|
- ./volumes/postgres:/var/lib/postgresql/data
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
pictrs:
|
||||||
|
image: asonix/pictrs:v0.1.13-r0
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8537:8080"
|
||||||
|
user: 991:991
|
||||||
|
volumes:
|
||||||
|
- ./volumes/pictrs:/mnt
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
iframely:
|
iframely:
|
||||||
|
|
16
docker/prod/docker-compose.yml
vendored
16
docker/prod/docker-compose.yml
vendored
|
@ -12,7 +12,7 @@ services:
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
lemmy:
|
lemmy:
|
||||||
image: dessalines/lemmy:v0.6.77
|
image: dessalines/lemmy:v0.6.79
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:8536:8536"
|
- "127.0.0.1:8536:8536"
|
||||||
restart: always
|
restart: always
|
||||||
|
@ -22,17 +22,17 @@ services:
|
||||||
- ./lemmy.hjson:/config/config.hjson
|
- ./lemmy.hjson:/config/config.hjson
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- pictshare
|
- pictrs
|
||||||
- iframely
|
- iframely
|
||||||
|
|
||||||
pictshare:
|
pictrs:
|
||||||
image: hascheksolutions/pictshare:latest
|
image: asonix/pictrs:v0.1.13-r0
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:8537:80"
|
- "127.0.0.1:8537:8080"
|
||||||
|
user: 991:991
|
||||||
volumes:
|
volumes:
|
||||||
- ./volumes/pictshare:/usr/share/nginx/html/data
|
- ./volumes/pictrs:/mnt
|
||||||
restart: always
|
restart: always
|
||||||
mem_limit: 100m
|
|
||||||
|
|
||||||
iframely:
|
iframely:
|
||||||
image: dogbin/iframely:latest
|
image: dogbin/iframely:latest
|
||||||
|
|
60
docker/prod/migrate-pictshare-to-pictrs.bash
vendored
Normal file
60
docker/prod/migrate-pictshare-to-pictrs.bash
vendored
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [[ $(id -u) != 0 ]]; then
|
||||||
|
echo "This migration needs to be run as root"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f docker-compose.yml ]]; then
|
||||||
|
echo "No docker-compose.yml found in current directory. Is this the right folder?"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fixing pictrs permissions
|
||||||
|
mkdir -p volumes/pictrs
|
||||||
|
sudo chown -R 991:991 volumes/pictrs
|
||||||
|
|
||||||
|
echo "Restarting docker-compose, making sure that pictrs is started and pictshare is removed"
|
||||||
|
docker-compose up -d --remove-orphans
|
||||||
|
|
||||||
|
if [[ -z $(docker-compose ps | grep pictrs) ]]; then
|
||||||
|
echo "Pict-rs is not running, make sure you update Lemmy first"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# echo "Stopping Lemmy so that users dont upload new images during the migration"
|
||||||
|
# docker-compose stop lemmy
|
||||||
|
|
||||||
|
pushd volumes/pictshare/
|
||||||
|
echo "Importing pictshare images to pict-rs..."
|
||||||
|
IMAGE_NAMES=*
|
||||||
|
for image in $IMAGE_NAMES; do
|
||||||
|
IMAGE_PATH="$(pwd)/$image/$image"
|
||||||
|
if [[ ! -f $IMAGE_PATH ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo -e "\nImporting $IMAGE_PATH"
|
||||||
|
ret=0
|
||||||
|
curl --silent --fail -F "images[]=@$IMAGE_PATH" http://127.0.0.1:8537/import || ret=$?
|
||||||
|
if [[ $ret != 0 ]]; then
|
||||||
|
echo "Error for $IMAGE_PATH : $ret"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Fixing permissions on pictshare folder"
|
||||||
|
find . -type d -exec chmod 755 {} \;
|
||||||
|
find . -type f -exec chmod 644 {} \;
|
||||||
|
|
||||||
|
popd
|
||||||
|
|
||||||
|
echo "Rewrite image links in Lemmy database"
|
||||||
|
docker-compose exec -u postgres postgres psql -U lemmy -c "UPDATE user_ SET avatar = REPLACE(avatar, 'pictshare', 'pictrs/image') WHERE avatar is not null;"
|
||||||
|
docker-compose exec -u postgres postgres psql -U lemmy -c "UPDATE post SET url = REPLACE(url, 'pictshare', 'pictrs/image') WHERE url is not null;"
|
||||||
|
|
||||||
|
echo "Moving pictshare data folder to pictshare_backup"
|
||||||
|
mv volumes/pictshare volumes/pictshare_backup
|
||||||
|
|
||||||
|
echo "Migration done, starting Lemmy again"
|
||||||
|
echo "If everything went well, you can delete ./volumes/pictshare_backup/"
|
||||||
|
docker-compose start lemmy
|
|
@ -1,4 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::is_valid_community_name;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct GetCommunity {
|
pub struct GetCommunity {
|
||||||
|
@ -220,6 +221,10 @@ impl Perform for Oper<CreateCommunity> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !is_valid_community_name(&data.name) {
|
||||||
|
return Err(APIError::err("invalid_community_name").into());
|
||||||
|
}
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
|
||||||
let conn = pool.get()?;
|
let conn = pool.get()?;
|
||||||
|
@ -306,6 +311,10 @@ impl Perform for Oper<EditCommunity> {
|
||||||
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
Err(_e) => return Err(APIError::err("not_logged_in").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !is_valid_community_name(&data.name) {
|
||||||
|
return Err(APIError::err("invalid_community_name").into());
|
||||||
|
}
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
|
||||||
let conn = pool.get()?;
|
let conn = pool.get()?;
|
||||||
|
|
|
@ -18,7 +18,7 @@ use crate::db::user_mention_view::*;
|
||||||
use crate::db::user_view::*;
|
use crate::db::user_view::*;
|
||||||
use crate::db::*;
|
use crate::db::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
extract_usernames, fetch_iframely_and_pictshare_data, generate_random_string, naive_from_unix,
|
extract_usernames, fetch_iframely_and_pictrs_data, generate_random_string, naive_from_unix,
|
||||||
naive_now, remove_slurs, send_email, slur_check, slurs_vec_to_str,
|
naive_now, remove_slurs, send_email, slur_check, slurs_vec_to_str,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -116,9 +116,9 @@ impl Perform for Oper<CreatePost> {
|
||||||
return Err(APIError::err("site_ban").into());
|
return Err(APIError::err("site_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch Iframely and Pictshare cached image
|
// Fetch Iframely and pictrs cached image
|
||||||
let (iframely_title, iframely_description, iframely_html, pictshare_thumbnail) =
|
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
|
||||||
fetch_iframely_and_pictshare_data(data.url.to_owned());
|
fetch_iframely_and_pictrs_data(data.url.to_owned());
|
||||||
|
|
||||||
let post_form = PostForm {
|
let post_form = PostForm {
|
||||||
name: data.name.to_owned(),
|
name: data.name.to_owned(),
|
||||||
|
@ -135,7 +135,7 @@ impl Perform for Oper<CreatePost> {
|
||||||
embed_title: iframely_title,
|
embed_title: iframely_title,
|
||||||
embed_description: iframely_description,
|
embed_description: iframely_description,
|
||||||
embed_html: iframely_html,
|
embed_html: iframely_html,
|
||||||
thumbnail_url: pictshare_thumbnail,
|
thumbnail_url: pictrs_thumbnail,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_post = match Post::create(&conn, &post_form) {
|
let inserted_post = match Post::create(&conn, &post_form) {
|
||||||
|
@ -450,9 +450,9 @@ impl Perform for Oper<EditPost> {
|
||||||
return Err(APIError::err("site_ban").into());
|
return Err(APIError::err("site_ban").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch Iframely and Pictshare cached image
|
// Fetch Iframely and Pictrs cached image
|
||||||
let (iframely_title, iframely_description, iframely_html, pictshare_thumbnail) =
|
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
|
||||||
fetch_iframely_and_pictshare_data(data.url.to_owned());
|
fetch_iframely_and_pictrs_data(data.url.to_owned());
|
||||||
|
|
||||||
let post_form = PostForm {
|
let post_form = PostForm {
|
||||||
name: data.name.to_owned(),
|
name: data.name.to_owned(),
|
||||||
|
@ -469,7 +469,7 @@ impl Perform for Oper<EditPost> {
|
||||||
embed_title: iframely_title,
|
embed_title: iframely_title,
|
||||||
embed_description: iframely_description,
|
embed_description: iframely_description,
|
||||||
embed_html: iframely_html,
|
embed_html: iframely_html,
|
||||||
thumbnail_url: pictshare_thumbnail,
|
thumbnail_url: pictrs_thumbnail,
|
||||||
};
|
};
|
||||||
|
|
||||||
let _updated_post = match Post::update(&conn, data.edit_id, &post_form) {
|
let _updated_post = match Post::update(&conn, data.edit_id, &post_form) {
|
||||||
|
|
|
@ -187,25 +187,35 @@ pub fn fetch_iframely(url: &str) -> Result<IframelyResponse, failure::Error> {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
pub struct PictshareResponse {
|
pub struct PictrsResponse {
|
||||||
status: String,
|
files: Vec<PictrsFile>,
|
||||||
url: String,
|
msg: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_pictshare(image_url: &str) -> Result<PictshareResponse, failure::Error> {
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
pub struct PictrsFile {
|
||||||
|
file: String,
|
||||||
|
delete_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetch_pictrs(image_url: &str) -> Result<PictrsResponse, failure::Error> {
|
||||||
is_image_content_type(image_url)?;
|
is_image_content_type(image_url)?;
|
||||||
|
|
||||||
let fetch_url = format!(
|
let fetch_url = format!(
|
||||||
"http://pictshare/api/geturl.php?url={}",
|
"http://pictrs:8080/image/download?url={}",
|
||||||
utf8_percent_encode(image_url, NON_ALPHANUMERIC)
|
utf8_percent_encode(image_url, NON_ALPHANUMERIC) // TODO this might not be needed
|
||||||
);
|
);
|
||||||
let text = attohttpc::get(&fetch_url).send()?.text()?;
|
let text = attohttpc::get(&fetch_url).send()?.text()?;
|
||||||
let res: PictshareResponse = serde_json::from_str(&text)?;
|
let res: PictrsResponse = serde_json::from_str(&text)?;
|
||||||
Ok(res)
|
if res.msg == "ok" {
|
||||||
|
Ok(res)
|
||||||
|
} else {
|
||||||
|
Err(format_err!("{}", &res.msg))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_iframely_and_pictshare_data(
|
fn fetch_iframely_and_pictrs_data(
|
||||||
url: Option<String>,
|
url: Option<String>,
|
||||||
) -> (
|
) -> (
|
||||||
Option<String>,
|
Option<String>,
|
||||||
|
@ -225,20 +235,20 @@ fn fetch_iframely_and_pictshare_data(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fetch pictshare thumbnail
|
// Fetch pictrs thumbnail
|
||||||
let pictshare_thumbnail = match iframely_thumbnail_url {
|
let pictrs_thumbnail = match iframely_thumbnail_url {
|
||||||
Some(iframely_thumbnail_url) => match fetch_pictshare(&iframely_thumbnail_url) {
|
Some(iframely_thumbnail_url) => match fetch_pictrs(&iframely_thumbnail_url) {
|
||||||
Ok(res) => Some(res.url),
|
Ok(res) => Some(res.files[0].file.to_owned()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("pictshare err: {}", e);
|
error!("pictrs err: {}", e);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Try to generate a small thumbnail if iframely is not supported
|
// Try to generate a small thumbnail if iframely is not supported
|
||||||
None => match fetch_pictshare(&url) {
|
None => match fetch_pictrs(&url) {
|
||||||
Ok(res) => Some(res.url),
|
Ok(res) => Some(res.files[0].file.to_owned()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("pictshare err: {}", e);
|
error!("pictrs err: {}", e);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -248,7 +258,7 @@ fn fetch_iframely_and_pictshare_data(
|
||||||
iframely_title,
|
iframely_title,
|
||||||
iframely_description,
|
iframely_description,
|
||||||
iframely_html,
|
iframely_html,
|
||||||
pictshare_thumbnail,
|
pictrs_thumbnail,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
None => (None, None, None, None),
|
None => (None, None, None, None),
|
||||||
|
@ -273,11 +283,15 @@ pub fn is_valid_username(name: &str) -> bool {
|
||||||
VALID_USERNAME_REGEX.is_match(name)
|
VALID_USERNAME_REGEX.is_match(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_valid_community_name(name: &str) -> bool {
|
||||||
|
VALID_COMMUNITY_NAME_REGEX.is_match(name)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
extract_usernames, is_email_regex, is_image_content_type, is_valid_username, remove_slurs,
|
extract_usernames, is_email_regex, is_image_content_type, is_valid_community_name,
|
||||||
slur_check, slurs_vec_to_str,
|
is_valid_username, remove_slurs, slur_check, slurs_vec_to_str,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -304,6 +318,15 @@ mod tests {
|
||||||
assert!(!is_valid_username(""));
|
assert!(!is_valid_username(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_valid_community_name() {
|
||||||
|
assert!(is_valid_community_name("example"));
|
||||||
|
assert!(is_valid_community_name("example_community"));
|
||||||
|
assert!(!is_valid_community_name("Example"));
|
||||||
|
assert!(!is_valid_community_name("Ex"));
|
||||||
|
assert!(!is_valid_community_name(""));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_slur_filter() {
|
fn test_slur_filter() {
|
||||||
let test =
|
let test =
|
||||||
|
@ -366,4 +389,5 @@ lazy_static! {
|
||||||
static ref SLUR_REGEX: Regex = RegexBuilder::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|nig(\b|g?(a|er)?(s|z)?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btrann?(y|ies?)|ladyboy(s?)|\b(b|re|r)tard(ed)?s?)").case_insensitive(true).build().unwrap();
|
static ref SLUR_REGEX: Regex = RegexBuilder::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|nig(\b|g?(a|er)?(s|z)?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btrann?(y|ies?)|ladyboy(s?)|\b(b|re|r)tard(ed)?s?)").case_insensitive(true).build().unwrap();
|
||||||
static ref USERNAME_MATCHES_REGEX: Regex = Regex::new(r"/u/[a-zA-Z][0-9a-zA-Z_]*").unwrap();
|
static ref USERNAME_MATCHES_REGEX: Regex = Regex::new(r"/u/[a-zA-Z][0-9a-zA-Z_]*").unwrap();
|
||||||
static ref VALID_USERNAME_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9_]{3,20}$").unwrap();
|
static ref VALID_USERNAME_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9_]{3,20}$").unwrap();
|
||||||
|
static ref VALID_COMMUNITY_NAME_REGEX: Regex = Regex::new(r"^[a-z0-9_]{3,20}$").unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
pub const VERSION: &str = "v0.6.77";
|
pub const VERSION: &str = "v0.6.79";
|
||||||
|
|
45
ui/src/components/comment-form.tsx
vendored
45
ui/src/components/comment-form.tsx
vendored
|
@ -18,6 +18,7 @@ import {
|
||||||
setupTribute,
|
setupTribute,
|
||||||
wsJsonToRes,
|
wsJsonToRes,
|
||||||
emojiPicker,
|
emojiPicker,
|
||||||
|
pictrsDeleteToast,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import autosize from 'autosize';
|
import autosize from 'autosize';
|
||||||
|
@ -162,8 +163,9 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
</button>
|
</button>
|
||||||
{this.state.commentForm.content && (
|
{this.state.commentForm.content && (
|
||||||
<button
|
<button
|
||||||
className={`btn btn-sm mr-2 btn-secondary ${this.state
|
className={`btn btn-sm mr-2 btn-secondary ${
|
||||||
.previewMode && 'active'}`}
|
this.state.previewMode && 'active'
|
||||||
|
}`}
|
||||||
onClick={linkEvent(this, this.handlePreviewToggle)}
|
onClick={linkEvent(this, this.handlePreviewToggle)}
|
||||||
>
|
>
|
||||||
{i18n.t('preview')}
|
{i18n.t('preview')}
|
||||||
|
@ -304,9 +306,9 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
file = event;
|
file = event;
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageUploadUrl = `/pictshare/api/upload.php`;
|
const imageUploadUrl = `/pictrs/image`;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('images[]', file);
|
||||||
|
|
||||||
i.state.imageLoading = true;
|
i.state.imageLoading = true;
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
|
@ -317,16 +319,31 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
let url = `${window.location.origin}/pictshare/${res.url}`;
|
console.log('pictrs upload:');
|
||||||
let imageMarkdown =
|
console.log(res);
|
||||||
res.filetype == 'mp4' ? `[vid](${url}/raw)` : `![](${url})`;
|
if (res.msg == 'ok') {
|
||||||
let content = i.state.commentForm.content;
|
let hash = res.files[0].file;
|
||||||
content = content ? `${content}\n${imageMarkdown}` : imageMarkdown;
|
let url = `${window.location.origin}/pictrs/image/${hash}`;
|
||||||
i.state.commentForm.content = content;
|
let deleteToken = res.files[0].delete_token;
|
||||||
i.state.imageLoading = false;
|
let deleteUrl = `${window.location.origin}/pictrs/image/delete/${deleteToken}/${hash}`;
|
||||||
i.setState(i.state);
|
let imageMarkdown = `![](${url})`;
|
||||||
let textarea: any = document.getElementById(i.id);
|
let content = i.state.commentForm.content;
|
||||||
autosize.update(textarea);
|
content = content ? `${content}\n${imageMarkdown}` : imageMarkdown;
|
||||||
|
i.state.commentForm.content = content;
|
||||||
|
i.state.imageLoading = false;
|
||||||
|
i.setState(i.state);
|
||||||
|
let textarea: any = document.getElementById(i.id);
|
||||||
|
autosize.update(textarea);
|
||||||
|
pictrsDeleteToast(
|
||||||
|
i18n.t('click_to_delete_picture'),
|
||||||
|
i18n.t('picture_deleted'),
|
||||||
|
deleteUrl
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
i.state.imageLoading = false;
|
||||||
|
i.setState(i.state);
|
||||||
|
toast(JSON.stringify(res), 'danger');
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
i.state.imageLoading = false;
|
i.state.imageLoading = false;
|
||||||
|
|
6
ui/src/components/navbar.tsx
vendored
6
ui/src/components/navbar.tsx
vendored
|
@ -22,7 +22,7 @@ import {
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import {
|
import {
|
||||||
wsJsonToRes,
|
wsJsonToRes,
|
||||||
pictshareAvatarThumbnail,
|
pictrsAvatarThumbnail,
|
||||||
showAvatars,
|
showAvatars,
|
||||||
fetchLimit,
|
fetchLimit,
|
||||||
isCommentType,
|
isCommentType,
|
||||||
|
@ -218,7 +218,7 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
<span>
|
<span>
|
||||||
{UserService.Instance.user.avatar && showAvatars() && (
|
{UserService.Instance.user.avatar && showAvatars() && (
|
||||||
<img
|
<img
|
||||||
src={pictshareAvatarThumbnail(
|
src={pictrsAvatarThumbnail(
|
||||||
UserService.Instance.user.avatar
|
UserService.Instance.user.avatar
|
||||||
)}
|
)}
|
||||||
height="32"
|
height="32"
|
||||||
|
@ -381,7 +381,7 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
|
|
||||||
requestNotificationPermission() {
|
requestNotificationPermission() {
|
||||||
if (UserService.Instance.user) {
|
if (UserService.Instance.user) {
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
if (!Notification) {
|
if (!Notification) {
|
||||||
toast(i18n.t('notifications_error'), 'danger');
|
toast(i18n.t('notifications_error'), 'danger');
|
||||||
return;
|
return;
|
||||||
|
|
30
ui/src/components/post-form.tsx
vendored
30
ui/src/components/post-form.tsx
vendored
|
@ -35,6 +35,7 @@ import {
|
||||||
setupTribute,
|
setupTribute,
|
||||||
setupTippy,
|
setupTippy,
|
||||||
emojiPicker,
|
emojiPicker,
|
||||||
|
pictrsDeleteToast,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import autosize from 'autosize';
|
import autosize from 'autosize';
|
||||||
import Tribute from 'tributejs/src/Tribute.js';
|
import Tribute from 'tributejs/src/Tribute.js';
|
||||||
|
@ -518,9 +519,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
file = event;
|
file = event;
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageUploadUrl = `/pictshare/api/upload.php`;
|
const imageUploadUrl = `/pictrs/image`;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('images[]', file);
|
||||||
|
|
||||||
i.state.imageLoading = true;
|
i.state.imageLoading = true;
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
|
@ -531,13 +532,26 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
let url = `${window.location.origin}/pictshare/${encodeURI(res.url)}`;
|
console.log('pictrs upload:');
|
||||||
if (res.filetype == 'mp4') {
|
console.log(res);
|
||||||
url += '/raw';
|
if (res.msg == 'ok') {
|
||||||
|
let hash = res.files[0].file;
|
||||||
|
let url = `${window.location.origin}/pictrs/image/${hash}`;
|
||||||
|
let deleteToken = res.files[0].delete_token;
|
||||||
|
let deleteUrl = `${window.location.origin}/pictrs/image/delete/${deleteToken}/${hash}`;
|
||||||
|
i.state.postForm.url = url;
|
||||||
|
i.state.imageLoading = false;
|
||||||
|
i.setState(i.state);
|
||||||
|
pictrsDeleteToast(
|
||||||
|
i18n.t('click_to_delete_picture'),
|
||||||
|
i18n.t('picture_deleted'),
|
||||||
|
deleteUrl
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
i.state.imageLoading = false;
|
||||||
|
i.setState(i.state);
|
||||||
|
toast(JSON.stringify(res), 'danger');
|
||||||
}
|
}
|
||||||
i.state.postForm.url = url;
|
|
||||||
i.state.imageLoading = false;
|
|
||||||
i.setState(i.state);
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
i.state.imageLoading = false;
|
i.state.imageLoading = false;
|
||||||
|
|
10
ui/src/components/post-listing.tsx
vendored
10
ui/src/components/post-listing.tsx
vendored
|
@ -28,7 +28,7 @@ import {
|
||||||
isImage,
|
isImage,
|
||||||
isVideo,
|
isVideo,
|
||||||
getUnixTime,
|
getUnixTime,
|
||||||
pictshareImage,
|
pictrsImage,
|
||||||
setupTippy,
|
setupTippy,
|
||||||
previewLines,
|
previewLines,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
|
@ -161,15 +161,15 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
getImage(thumbnail: boolean = false) {
|
getImage(thumbnail: boolean = false) {
|
||||||
let post = this.props.post;
|
let post = this.props.post;
|
||||||
if (isImage(post.url)) {
|
if (isImage(post.url)) {
|
||||||
if (post.url.includes('pictshare')) {
|
if (post.url.includes('pictrs')) {
|
||||||
return pictshareImage(post.url, thumbnail);
|
return pictrsImage(post.url, thumbnail);
|
||||||
} else if (post.thumbnail_url) {
|
} else if (post.thumbnail_url) {
|
||||||
return pictshareImage(post.thumbnail_url, thumbnail);
|
return pictrsImage(post.thumbnail_url, thumbnail);
|
||||||
} else {
|
} else {
|
||||||
return post.url;
|
return post.url;
|
||||||
}
|
}
|
||||||
} else if (post.thumbnail_url) {
|
} else if (post.thumbnail_url) {
|
||||||
return pictshareImage(post.thumbnail_url, thumbnail);
|
return pictrsImage(post.thumbnail_url, thumbnail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
24
ui/src/components/private-message.tsx
vendored
24
ui/src/components/private-message.tsx
vendored
|
@ -5,12 +5,7 @@ import {
|
||||||
EditPrivateMessageForm,
|
EditPrivateMessageForm,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import {
|
import { mdToHtml, pictrsAvatarThumbnail, showAvatars, toast } from '../utils';
|
||||||
mdToHtml,
|
|
||||||
pictshareAvatarThumbnail,
|
|
||||||
showAvatars,
|
|
||||||
toast,
|
|
||||||
} from '../utils';
|
|
||||||
import { MomentTime } from './moment-time';
|
import { MomentTime } from './moment-time';
|
||||||
import { PrivateMessageForm } from './private-message-form';
|
import { PrivateMessageForm } from './private-message-form';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
|
@ -78,7 +73,7 @@ export class PrivateMessage extends Component<
|
||||||
<img
|
<img
|
||||||
height="32"
|
height="32"
|
||||||
width="32"
|
width="32"
|
||||||
src={pictshareAvatarThumbnail(
|
src={pictrsAvatarThumbnail(
|
||||||
this.mine
|
this.mine
|
||||||
? message.recipient_avatar
|
? message.recipient_avatar
|
||||||
: message.creator_avatar
|
: message.creator_avatar
|
||||||
|
@ -144,8 +139,9 @@ export class PrivateMessage extends Component<
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class={`icon icon-inline ${message.read &&
|
class={`icon icon-inline ${
|
||||||
'text-success'}`}
|
message.read && 'text-success'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<use xlinkHref="#icon-check"></use>
|
<use xlinkHref="#icon-check"></use>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -188,8 +184,9 @@ export class PrivateMessage extends Component<
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class={`icon icon-inline ${message.deleted &&
|
class={`icon icon-inline ${
|
||||||
'text-danger'}`}
|
message.deleted && 'text-danger'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<use xlinkHref="#icon-trash"></use>
|
<use xlinkHref="#icon-trash"></use>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -204,8 +201,9 @@ export class PrivateMessage extends Component<
|
||||||
data-tippy-content={i18n.t('view_source')}
|
data-tippy-content={i18n.t('view_source')}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class={`icon icon-inline ${this.state.viewSource &&
|
class={`icon icon-inline ${
|
||||||
'text-success'}`}
|
this.state.viewSource && 'text-success'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<use xlinkHref="#icon-file-text"></use>
|
<use xlinkHref="#icon-file-text"></use>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
2
ui/src/components/search.tsx
vendored
2
ui/src/components/search.tsx
vendored
|
@ -22,7 +22,7 @@ import {
|
||||||
fetchLimit,
|
fetchLimit,
|
||||||
routeSearchTypeToEnum,
|
routeSearchTypeToEnum,
|
||||||
routeSortTypeToEnum,
|
routeSortTypeToEnum,
|
||||||
pictshareAvatarThumbnail,
|
pictrsAvatarThumbnail,
|
||||||
showAvatars,
|
showAvatars,
|
||||||
toast,
|
toast,
|
||||||
createCommentLikeRes,
|
createCommentLikeRes,
|
||||||
|
|
2
ui/src/components/sidebar.tsx
vendored
2
ui/src/components/sidebar.tsx
vendored
|
@ -11,7 +11,7 @@ import { WebSocketService, UserService } from '../services';
|
||||||
import {
|
import {
|
||||||
mdToHtml,
|
mdToHtml,
|
||||||
getUnixTime,
|
getUnixTime,
|
||||||
pictshareAvatarThumbnail,
|
pictrsAvatarThumbnail,
|
||||||
showAvatars,
|
showAvatars,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { CommunityForm } from './community-form';
|
import { CommunityForm } from './community-form';
|
||||||
|
|
4
ui/src/components/user-listing.tsx
vendored
4
ui/src/components/user-listing.tsx
vendored
|
@ -1,7 +1,7 @@
|
||||||
import { Component } from 'inferno';
|
import { Component } from 'inferno';
|
||||||
import { Link } from 'inferno-router';
|
import { Link } from 'inferno-router';
|
||||||
import { UserView } from '../interfaces';
|
import { UserView } from '../interfaces';
|
||||||
import { pictshareAvatarThumbnail, showAvatars } from '../utils';
|
import { pictrsAvatarThumbnail, showAvatars } from '../utils';
|
||||||
|
|
||||||
interface UserOther {
|
interface UserOther {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -25,7 +25,7 @@ export class UserListing extends Component<UserListingProps, any> {
|
||||||
<img
|
<img
|
||||||
height="32"
|
height="32"
|
||||||
width="32"
|
width="32"
|
||||||
src={pictshareAvatarThumbnail(user.avatar)}
|
src={pictrsAvatarThumbnail(user.avatar)}
|
||||||
class="rounded-circle mr-2"
|
class="rounded-circle mr-2"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
23
ui/src/components/user.tsx
vendored
23
ui/src/components/user.tsx
vendored
|
@ -988,9 +988,9 @@ export class User extends Component<any, UserState> {
|
||||||
handleImageUpload(i: User, event: any) {
|
handleImageUpload(i: User, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let file = event.target.files[0];
|
let file = event.target.files[0];
|
||||||
const imageUploadUrl = `/pictshare/api/upload.php`;
|
const imageUploadUrl = `/pictrs/image`;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('images[]', file);
|
||||||
|
|
||||||
i.state.avatarLoading = true;
|
i.state.avatarLoading = true;
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
|
@ -1001,14 +1001,19 @@ export class User extends Component<any, UserState> {
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
let url = `${window.location.origin}/pictshare/${res.url}`;
|
console.log('pictrs upload:');
|
||||||
if (res.filetype == 'mp4') {
|
console.log(res);
|
||||||
url += '/raw';
|
if (res.msg == 'ok') {
|
||||||
|
let hash = res.files[0].file;
|
||||||
|
let url = `${window.location.origin}/pictrs/image/${hash}`;
|
||||||
|
i.state.userSettingsForm.avatar = url;
|
||||||
|
i.state.avatarLoading = false;
|
||||||
|
i.setState(i.state);
|
||||||
|
} else {
|
||||||
|
i.state.avatarLoading = false;
|
||||||
|
i.setState(i.state);
|
||||||
|
toast(JSON.stringify(res), 'danger');
|
||||||
}
|
}
|
||||||
i.state.userSettingsForm.avatar = url;
|
|
||||||
console.log(url);
|
|
||||||
i.state.avatarLoading = false;
|
|
||||||
i.setState(i.state);
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
i.state.avatarLoading = false;
|
i.state.avatarLoading = false;
|
||||||
|
|
62
ui/src/utils.ts
vendored
62
ui/src/utils.ts
vendored
|
@ -414,12 +414,16 @@ export function setTheme(theme: string = 'darkly', loggedIn: boolean = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the user is not logged in, we load the default themes and let the browser decide
|
// if the user is not logged in, we load the default themes and let the browser decide
|
||||||
if(!loggedIn) {
|
if (!loggedIn) {
|
||||||
document.getElementById("default-light").removeAttribute('disabled')
|
document.getElementById('default-light').removeAttribute('disabled');
|
||||||
document.getElementById("default-dark").removeAttribute('disabled')
|
document.getElementById('default-dark').removeAttribute('disabled');
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("default-light").setAttribute('disabled', 'disabled');
|
document
|
||||||
document.getElementById("default-dark").setAttribute('disabled', 'disabled');
|
.getElementById('default-light')
|
||||||
|
.setAttribute('disabled', 'disabled');
|
||||||
|
document
|
||||||
|
.getElementById('default-dark')
|
||||||
|
.setAttribute('disabled', 'disabled');
|
||||||
|
|
||||||
// Load the theme dynamically
|
// Load the theme dynamically
|
||||||
let cssLoc = `/static/assets/css/themes/${theme}.min.css`;
|
let cssLoc = `/static/assets/css/themes/${theme}.min.css`;
|
||||||
|
@ -449,10 +453,12 @@ export function objectFlip(obj: any) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pictshareAvatarThumbnail(src: string): string {
|
export function pictrsAvatarThumbnail(src: string): string {
|
||||||
// sample url: http://localhost:8535/pictshare/gs7xuu.jpg
|
// sample url: http://localhost:8535/pictrs/image/thumbnail256/gs7xuu.jpg
|
||||||
let split = src.split('pictshare');
|
let split = src.split('/pictrs/image');
|
||||||
let out = `${split[0]}pictshare/${canUseWebP() ? 'webp/' : ''}96${split[1]}`;
|
let out = `${split[0]}/pictrs/image/${
|
||||||
|
canUseWebP() ? 'webp/' : ''
|
||||||
|
}thumbnail96${split[1]}`;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,21 +470,18 @@ export function showAvatars(): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts to image thumbnail
|
// Converts to image thumbnail
|
||||||
export function pictshareImage(
|
export function pictrsImage(hash: string, thumbnail: boolean = false): string {
|
||||||
hash: string,
|
let root = `/pictrs/image`;
|
||||||
thumbnail: boolean = false
|
|
||||||
): string {
|
|
||||||
let root = `/pictshare`;
|
|
||||||
|
|
||||||
// Necessary for other servers / domains
|
// Necessary for other servers / domains
|
||||||
if (hash.includes('pictshare')) {
|
if (hash.includes('pictrs')) {
|
||||||
let split = hash.split('/pictshare/');
|
let split = hash.split('/pictrs/image/');
|
||||||
root = `${split[0]}/pictshare`;
|
root = `${split[0]}/pictrs/image`;
|
||||||
hash = split[1];
|
hash = split[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
let out = `${root}/${canUseWebP() ? 'webp/' : ''}${
|
let out = `${root}/${canUseWebP() ? 'webp/' : ''}${
|
||||||
thumbnail ? '192/' : ''
|
thumbnail ? 'thumbnail256/' : ''
|
||||||
}${hash}`;
|
}${hash}`;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
@ -497,6 +500,29 @@ export function toast(text: string, background: string = 'success') {
|
||||||
}).showToast();
|
}).showToast();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function pictrsDeleteToast(
|
||||||
|
clickToDeleteText: string,
|
||||||
|
deletePictureText: string,
|
||||||
|
deleteUrl: string
|
||||||
|
) {
|
||||||
|
let backgroundColor = `var(--light)`;
|
||||||
|
let toast = Toastify({
|
||||||
|
text: clickToDeleteText,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
gravity: 'top',
|
||||||
|
position: 'right',
|
||||||
|
duration: 0,
|
||||||
|
onClick: () => {
|
||||||
|
if (toast) {
|
||||||
|
window.location.replace(deleteUrl);
|
||||||
|
alert(deletePictureText);
|
||||||
|
toast.hideToast();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close: true,
|
||||||
|
}).showToast();
|
||||||
|
}
|
||||||
|
|
||||||
export function messageToastify(
|
export function messageToastify(
|
||||||
creator: string,
|
creator: string,
|
||||||
avatar: string,
|
avatar: string,
|
||||||
|
|
2
ui/src/version.ts
vendored
2
ui/src/version.ts
vendored
|
@ -1 +1 @@
|
||||||
export const version: string = 'v0.6.77';
|
export const version: string = 'v0.6.79';
|
||||||
|
|
3
ui/translations/en.json
vendored
3
ui/translations/en.json
vendored
|
@ -27,6 +27,7 @@
|
||||||
"number_of_communities": "{{count}} Community",
|
"number_of_communities": "{{count}} Community",
|
||||||
"number_of_communities_plural": "{{count}} Communities",
|
"number_of_communities_plural": "{{count}} Communities",
|
||||||
"community_reqs": "lowercase, underscores, and no spaces.",
|
"community_reqs": "lowercase, underscores, and no spaces.",
|
||||||
|
"invalid_community_name": "Invalid name.",
|
||||||
"create_private_message": "Create Private Message",
|
"create_private_message": "Create Private Message",
|
||||||
"send_secure_message": "Send Secure Message",
|
"send_secure_message": "Send Secure Message",
|
||||||
"send_message": "Send Message",
|
"send_message": "Send Message",
|
||||||
|
@ -75,6 +76,8 @@
|
||||||
"delete_account": "Delete Account",
|
"delete_account": "Delete Account",
|
||||||
"delete_account_confirm":
|
"delete_account_confirm":
|
||||||
"Warning: this will permanently delete all your data. Enter your password to confirm.",
|
"Warning: this will permanently delete all your data. Enter your password to confirm.",
|
||||||
|
"click_to_delete_picture": "Click to delete picture.",
|
||||||
|
"picture_deleted": "Picture deleted.",
|
||||||
"restore": "restore",
|
"restore": "restore",
|
||||||
"ban": "ban",
|
"ban": "ban",
|
||||||
"ban_from_site": "ban from site",
|
"ban_from_site": "ban from site",
|
||||||
|
|
16
ui/translations/es.json
vendored
16
ui/translations/es.json
vendored
|
@ -5,7 +5,7 @@
|
||||||
"create_a_post": "Crear una publicación",
|
"create_a_post": "Crear una publicación",
|
||||||
"create_post": "Crear Publicación",
|
"create_post": "Crear Publicación",
|
||||||
"number_of_posts": "{{count}} Publicación",
|
"number_of_posts": "{{count}} Publicación",
|
||||||
"number_of_posts_plural": "{{count}} Publicaciónes",
|
"number_of_posts_plural": "{{count}} Publicaciones",
|
||||||
"posts": "Publicaciones",
|
"posts": "Publicaciones",
|
||||||
"related_posts": "Estas publicaciones podrían estar relacionadas",
|
"related_posts": "Estas publicaciones podrían estar relacionadas",
|
||||||
"cross_posts": "Este link también ha sido publicado en:",
|
"cross_posts": "Este link también ha sido publicado en:",
|
||||||
|
@ -57,16 +57,16 @@
|
||||||
"remove_as_admin": "eliminar como administrador",
|
"remove_as_admin": "eliminar como administrador",
|
||||||
"appoint_as_admin": "designar como administrador",
|
"appoint_as_admin": "designar como administrador",
|
||||||
"remove": "eliminar",
|
"remove": "eliminar",
|
||||||
"removed": "eliminado",
|
"removed": "eliminado por moderador",
|
||||||
"locked": "bloqueado",
|
"locked": "bloqueado",
|
||||||
"stickied": "fijado",
|
"stickied": "fijado",
|
||||||
"reason": "Razón",
|
"reason": "Razón",
|
||||||
"mark_as_read": "marcar como leído",
|
"mark_as_read": "marcar como leído",
|
||||||
"mark_as_unread": "marcar como no leído",
|
"mark_as_unread": "marcar como no leído",
|
||||||
"delete": "eliminar",
|
"delete": "eliminar",
|
||||||
"deleted": "eliminado",
|
"deleted": "eliminado por creador",
|
||||||
"delete_account": "Eliminar Cuenta",
|
"delete_account": "Eliminar Cuenta",
|
||||||
"delete_account_confirm": "Aviso: esta acción eliminará permanentemente tu información. Introduce tu contraseña para continuar",
|
"delete_account_confirm": "Advertencia: esta acción eliminará permanentemente toda tu información. Introduce tu contraseña para confirmar.",
|
||||||
"restore": "restaurar",
|
"restore": "restaurar",
|
||||||
"ban": "expulsar",
|
"ban": "expulsar",
|
||||||
"ban_from_site": "expulsar del sitio",
|
"ban_from_site": "expulsar del sitio",
|
||||||
|
@ -169,7 +169,7 @@
|
||||||
"theme": "Tema",
|
"theme": "Tema",
|
||||||
"sponsors": "Patrocinadores",
|
"sponsors": "Patrocinadores",
|
||||||
"sponsors_of_lemmy": "Patrocinadores de Lemmy",
|
"sponsors_of_lemmy": "Patrocinadores de Lemmy",
|
||||||
"sponsor_message": "Lemmy es software libre y de <1>código abierto</1>, lo que significa que no tendrá publicidades, monetización, ni capitales emprendedores, nunca. Tus donaciones apoyan directamente el desarrollo a tiempo completo del proyecto. Muchas gracias a las siguientes personas:",
|
"sponsor_message": "Lemmy es software libre y de <1>código abierto</1>, lo que significa que nunca tendrá publicidad, monetización, ni capitales emprendedores. Tus donaciones apoyan directamente el desarrollo a tiempo completo del proyecto. Muchas gracias a las siguientes personas:",
|
||||||
"support_on_patreon": "Apoyo en Patreon",
|
"support_on_patreon": "Apoyo en Patreon",
|
||||||
"support_on_liberapay": "Apoyo en Liberapay",
|
"support_on_liberapay": "Apoyo en Liberapay",
|
||||||
"donate_to_lemmy": "Donar a Lemmy",
|
"donate_to_lemmy": "Donar a Lemmy",
|
||||||
|
@ -250,6 +250,8 @@
|
||||||
"banned_users": "Usuarios Baneados",
|
"banned_users": "Usuarios Baneados",
|
||||||
"support_on_open_collective": "Dona en OpenCollective",
|
"support_on_open_collective": "Dona en OpenCollective",
|
||||||
"site_saved": "Sitio Guardado.",
|
"site_saved": "Sitio Guardado.",
|
||||||
"emoji_picker": "Emoji Picker",
|
"emoji_picker": "Lista de emojis",
|
||||||
"admin_settings": "Panel de Administración"
|
"admin_settings": "Panel de Administración",
|
||||||
|
"select_a_community": "Selecciona una comunidad",
|
||||||
|
"invalid_username": "Nombre de usuario inválido."
|
||||||
}
|
}
|
||||||
|
|
2
ui/translations/fr.json
vendored
2
ui/translations/fr.json
vendored
|
@ -36,7 +36,7 @@
|
||||||
"preview": "prévisualiser",
|
"preview": "prévisualiser",
|
||||||
"upload_image": "envoyer une image",
|
"upload_image": "envoyer une image",
|
||||||
"avatar": "Avatar",
|
"avatar": "Avatar",
|
||||||
"upload_avatar": "Télécharger une avatar",
|
"upload_avatar": "Télécharger un avatar",
|
||||||
"show_avatars": "Afficher les avatars",
|
"show_avatars": "Afficher les avatars",
|
||||||
"formatting_help": "aide au formattage",
|
"formatting_help": "aide au formattage",
|
||||||
"view_source": "voir la source",
|
"view_source": "voir la source",
|
||||||
|
|
95
ui/translations/hu.json
vendored
95
ui/translations/hu.json
vendored
|
@ -2,8 +2,8 @@
|
||||||
"post": "Elküld",
|
"post": "Elküld",
|
||||||
"remove_post": "Bejegyzés eltávolítása",
|
"remove_post": "Bejegyzés eltávolítása",
|
||||||
"no_posts": "Nincs bejegyzés.",
|
"no_posts": "Nincs bejegyzés.",
|
||||||
"create_post": "Új bejegyzés létrehozása",
|
"create_post": "Bejegyzés létrehozása",
|
||||||
"create_a_post": "Új bejegyzés létrehozása",
|
"create_a_post": "Bejegyzés létrehozása",
|
||||||
"number_of_posts": "{{count}} bejegyzés",
|
"number_of_posts": "{{count}} bejegyzés",
|
||||||
"number_of_posts_plural": "{{count}} bejegyzés",
|
"number_of_posts_plural": "{{count}} bejegyzés",
|
||||||
"posts": "Bejegyzések",
|
"posts": "Bejegyzések",
|
||||||
|
@ -14,5 +14,94 @@
|
||||||
"remove_comment": "Hozzászólások eltávolítása",
|
"remove_comment": "Hozzászólások eltávolítása",
|
||||||
"cross_posted_to": "beküldve ide is: ",
|
"cross_posted_to": "beküldve ide is: ",
|
||||||
"number_of_comments": "{{count}} hozzászólás",
|
"number_of_comments": "{{count}} hozzászólás",
|
||||||
"number_of_comments_plural": "{{count}} hozzászólás"
|
"number_of_comments_plural": "{{count}} hozzászólás",
|
||||||
|
"communities": "Közösségek",
|
||||||
|
"users": "Felhasználók",
|
||||||
|
"create_a_community": "Közösség létrehozása",
|
||||||
|
"select_a_community": "Közösség kiválasztása",
|
||||||
|
"create_community": "Közösség létrehozása",
|
||||||
|
"remove_community": "Közösség eltávolítása",
|
||||||
|
"trending_communities": "Népszerű <1>közösségek</1>",
|
||||||
|
"list_of_communities": "Közösségek listája",
|
||||||
|
"community_reqs": "Kisbetű és alsóvonás megengedett, szóköz nem.",
|
||||||
|
"create_private_message": "Privát üzenet létrehozása",
|
||||||
|
"send_secure_message": "Biztonságos üzenet küldése",
|
||||||
|
"send_message": "Üzenet küldése",
|
||||||
|
"message": "Üzenet",
|
||||||
|
"edit": "szerkesztés",
|
||||||
|
"reply": "válasz",
|
||||||
|
"more": "több",
|
||||||
|
"cancel": "Mégse",
|
||||||
|
"preview": "Előnézet",
|
||||||
|
"upload_image": "kép feltöltése",
|
||||||
|
"avatar": "Avatár",
|
||||||
|
"upload_avatar": "Avatár feltöltése",
|
||||||
|
"show_avatars": "Avatárok mutatása",
|
||||||
|
"show_context": "Összefüggés mutatása",
|
||||||
|
"sorting_help": "rendezési segítség",
|
||||||
|
"view_source": "forrás megtekintése",
|
||||||
|
"unlock": "zárolás feloldása",
|
||||||
|
"lock": "zárolás",
|
||||||
|
"sticky": "rögzítés",
|
||||||
|
"unsticky": "rögzítés feloldása",
|
||||||
|
"link": "hivatkozás",
|
||||||
|
"mod": "moderátor",
|
||||||
|
"mods": "moderátorok",
|
||||||
|
"moderates": "Moderált közösségek",
|
||||||
|
"settings": "Beállítások",
|
||||||
|
"admin_settings": "Adminisztrációs beállítások",
|
||||||
|
"remove_as_mod": "moderátori jog eltávolítása",
|
||||||
|
"appoint_as_mod": "kinevezés moderátornak",
|
||||||
|
"modlog": "Moderációs napló",
|
||||||
|
"admin": "admin",
|
||||||
|
"admins": "adminok",
|
||||||
|
"remove_as_admin": "adminjog eltávolítása",
|
||||||
|
"appoint_as_admin": "kinevezés adminnak",
|
||||||
|
"remove": "eltávolítás",
|
||||||
|
"locked": "zárolva",
|
||||||
|
"stickied": "rögzítve",
|
||||||
|
"reason": "Indok",
|
||||||
|
"mark_as_read": "megjelölés olvasottnak",
|
||||||
|
"mark_as_unread": "megjelölés olvasatlannak",
|
||||||
|
"delete": "törlés",
|
||||||
|
"deleted": "eltávolítva a szerző által",
|
||||||
|
"delete_account": "FIók törlése",
|
||||||
|
"restore": "visszaállítás",
|
||||||
|
"ban": "kitiltás",
|
||||||
|
"ban_from_site": "kitiltás az oldalról",
|
||||||
|
"unban": "kitiltás visszavonása",
|
||||||
|
"unban_from_site": "az oldalról történő kitiltás visszavonása",
|
||||||
|
"banned": "kitiltva",
|
||||||
|
"banned_users": "Kitiltott felhasználók",
|
||||||
|
"save": "mentés",
|
||||||
|
"unsave": "mentés visszavonása",
|
||||||
|
"create": "létrehozás",
|
||||||
|
"creator": "szerző",
|
||||||
|
"username": "Felhasználónév",
|
||||||
|
"number_of_points": "{{count}} pont",
|
||||||
|
"number_of_points_plural": "{{count}} pont",
|
||||||
|
"number_of_subscribers": "{{count}} feliratkozó",
|
||||||
|
"number_of_subscribers_plural": "{{count}} feliratkozó",
|
||||||
|
"name": "Név",
|
||||||
|
"title": "Cím",
|
||||||
|
"category": "Kategória",
|
||||||
|
"both": "Mindkettő",
|
||||||
|
"saved": "Mentve",
|
||||||
|
"unsubscribe": "Leiratkozás",
|
||||||
|
"subscribe": "Feliratkozás",
|
||||||
|
"subscribed": "Feliratkozva",
|
||||||
|
"subscribed_to_communities": "Követett <1>közösségek</1>",
|
||||||
|
"number_of_communities": "{{count}} közösség",
|
||||||
|
"number_of_communities_plural": "{{count}} közösség",
|
||||||
|
"formatting_help": "formázási segítség",
|
||||||
|
"archive_link": "hivatkozás archiválása",
|
||||||
|
"site_config": "Oldalbeállítások",
|
||||||
|
"removed": "eltávolítva egy mod által",
|
||||||
|
"delete_account_confirm": "Figyelmeztetés: ez véglegesen törölni fogja az összes adatodat. A megerősítéshez írd be a jelszavad!",
|
||||||
|
"email_or_username": "Email vagy felhasználónév",
|
||||||
|
"number_of_users": "{{count}} felhasználó",
|
||||||
|
"number_of_users_plural": "{{count}} felhasználó",
|
||||||
|
"number_online": "{{count}} online felhasználó",
|
||||||
|
"number_online_plural": "{{count}} online felhasználó",
|
||||||
|
"subscribers": "Feliratkozók"
|
||||||
}
|
}
|
||||||
|
|
12
ui/translations/zh.json
vendored
12
ui/translations/zh.json
vendored
|
@ -35,13 +35,13 @@
|
||||||
"remove_as_admin": "移除管理权限",
|
"remove_as_admin": "移除管理权限",
|
||||||
"appoint_as_admin": "添加管理权限",
|
"appoint_as_admin": "添加管理权限",
|
||||||
"remove": "移除",
|
"remove": "移除",
|
||||||
"removed": "已移除",
|
"removed": "已被管理员移除",
|
||||||
"locked": "已加锁",
|
"locked": "已加锁",
|
||||||
"reason": "原因",
|
"reason": "原因",
|
||||||
"mark_as_read": "标记未读",
|
"mark_as_read": "标记未读",
|
||||||
"mark_as_unread": "标记已读",
|
"mark_as_unread": "标记已读",
|
||||||
"delete": "删除",
|
"delete": "删除",
|
||||||
"deleted": "已删除",
|
"deleted": "作者已删除",
|
||||||
"restore": "恢复",
|
"restore": "恢复",
|
||||||
"ban": "禁止",
|
"ban": "禁止",
|
||||||
"ban_from_site": "禁止此站点",
|
"ban_from_site": "禁止此站点",
|
||||||
|
@ -235,5 +235,11 @@
|
||||||
"time": "时间",
|
"time": "时间",
|
||||||
"action": "行动",
|
"action": "行动",
|
||||||
"block_leaving": "确定要离开吗?",
|
"block_leaving": "确定要离开吗?",
|
||||||
"show_context": "显示上下文"
|
"show_context": "显示上下文",
|
||||||
|
"admin_settings": "管理员设置",
|
||||||
|
"site_config": "网站配置",
|
||||||
|
"banned_users": "被禁止用户",
|
||||||
|
"site_saved": "网站已保存",
|
||||||
|
"emoji_picker": "选择表情",
|
||||||
|
"invalid_username": "用户名无效"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue