]> Untitled Git - lemmy.git/commitdiff
Merge branch 'master' of https://github.com/makigi-io/makigi into makigi-io-master
authorDessalines <tyhou13@gmx.com>
Mon, 22 Jun 2020 18:52:46 +0000 (14:52 -0400)
committerDessalines <tyhou13@gmx.com>
Mon, 22 Jun 2020 18:52:46 +0000 (14:52 -0400)
30 files changed:
RELEASES.md
ansible/VERSION
ansible/ansible.cfg
ansible/lemmy.yml
ansible/lemmy_dev.yml
ansible/templates/docker-compose.yml
ansible/templates/nginx.conf
docker/dev/docker-compose.yml
docker/prod/docker-compose.yml
docker/prod/migrate-pictshare-to-pictrs.bash [new file with mode: 0644]
server/src/api/mod.rs
server/src/api/post.rs
server/src/lib.rs
server/src/version.rs
ui/src/components/comment-form.tsx
ui/src/components/navbar.tsx
ui/src/components/post-form.tsx
ui/src/components/post-listing.tsx
ui/src/components/private-message.tsx
ui/src/components/search.tsx
ui/src/components/sidebar.tsx
ui/src/components/user-listing.tsx
ui/src/components/user.tsx
ui/src/utils.ts
ui/src/version.ts
ui/translations/en.json
ui/translations/es.json
ui/translations/fr.json
ui/translations/hu.json
ui/translations/zh.json

index 5a4c7645ee18f56bc9673d9c42f69a6c4c9d4aa1..25c30861a7dfd9dd010b03348bae22ef88218a4e 100644 (file)
@@ -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)
 
 `v0.6.0` is here, and we've closed [41 issues!](https://github.com/LemmyNet/lemmy/milestone/15?closed=1) 
index fe18d878e78ea20a2f2b07e6497f5ef745260931..ed2321280fe8d9a027d1bee9c12d7536274e7805 100644 (file)
@@ -1 +1 @@
-v0.6.74
+v0.6.79
index 960a7c40fd58f72b093e88b620a40b401affae52..74b6ab2f1955975f7c50530ebc43eb802f2d3278 100644 (file)
@@ -1,5 +1,6 @@
 [defaults]
 inventory=inventory
+interpreter_python=/usr/bin/python3
 
 [ssh_connection]
 pipelining = True
index bc01623fc55cea14e7a37f3b7bbb4196cd7a170b..7b78ab8d35fbd6ae4be115d99d6ac66d93a6401c 100644 (file)
       creates: '/etc/letsencrypt/live/{{domain}}/privkey.pem'
 
   - name: create lemmy folder
-    file: path={{item.path}} state=directory
+    file: path={{item.path}} {{item.owner}} state=directory
     with_items:
-      - { path: '/lemmy/' }
-      - { path: '/lemmy/volumes/' }
+      - { path: '/lemmy/', owner: 'root' }
+      - { path: '/lemmy/volumes/', owner: 'root' }
+      - { path: '/lemmy/volumes/pictrs/', owner: '991' }
 
   - block:
     - name:  add template files
@@ -59,6 +60,7 @@
       project_src: /lemmy/
       state: present
       pull: yes
+      remove_orphans: yes
 
   - name: reload nginx with new config
     shell: nginx -s reload
index e9b8364f386a85357ca89d16de83f15ec650bfcd..7a3683610ec04a3098cb045b3ef52aabcf7be694 100644 (file)
       creates: '/etc/letsencrypt/live/{{domain}}/privkey.pem'
 
   - name: create lemmy folder
-    file: path={{item.path}} state=directory
+    file: path={{item.path}} owner={{item.owner}} state=directory
     with_items:
-      - { path: '/lemmy/' }
-      - { path: '/lemmy/volumes/' }
+      - { path: '/lemmy/', owner: 'root' }
+      - { path: '/lemmy/volumes/', owner: 'root' }
+      - { path: '/lemmy/volumes/pictrs/', owner: '991' }
 
   - block:
     - name:  add template files
@@ -88,6 +89,7 @@
       project_src: /lemmy/
       state: present
       recreate: always
+      remove_orphans: yes
     ignore_errors: yes
 
   - name: reload nginx with new config
index 9ec1bfbc22d701849a6bacf04670e69ab4d31925..f4c94fd71dba5536bc4a1a74d28027f540239ee9 100644 (file)
@@ -12,7 +12,7 @@ services:
       - ./lemmy.hjson:/config/config.hjson:ro
     depends_on:
       - postgres
-      - pictshare
+      - pictrs
       - iframely
 
   postgres:
@@ -25,12 +25,13 @@ services:
       - ./volumes/postgres:/var/lib/postgresql/data
     restart: always
 
-  pictshare:
-    image: hascheksolutions/pictshare:latest
+  pictrs:
+    image: asonix/pictrs:amd64-v0.1.0-r9
+    user: 991:991
     ports:
-      - "127.0.0.1:8537:80"
+      - "127.0.0.1:8537:8080"
     volumes:
-      - ./volumes/pictshare:/usr/share/nginx/html/data
+      - ./volumes/pictrs:/mnt
     restart: always
 
   iframely:
index a978c18999a755efd31663d56b59bf82b9ef1c66..b710fdb30bde9bf543afd572ccf44d1c70c10c9c 100644 (file)
@@ -48,8 +48,8 @@ server {
     add_header X-Frame-Options "DENY";
     add_header X-XSS-Protection "1; mode=block";
 
-    # Upload limit for pictshare
-    client_max_body_size 50M;
+    # Upload limit for pictrs
+    client_max_body_size 20M;
 
     location / {
         proxy_pass http://0.0.0.0:8536;
@@ -70,15 +70,21 @@ server {
         proxy_cache_min_uses    5;
     }    
 
-    location /pictshare/ {
-      proxy_pass http://0.0.0.0:8537/;
-      proxy_set_header X-Real-IP $remote_addr;
-      proxy_set_header Host $host;
-      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    # Redirect pictshare images to pictrs
+    location ~ /pictshare/(.*)$ {
+      return 301 /pictrs/image/$1;
+    }
 
-      if ($request_uri ~ \.(?:ico|gif|jpe?g|png|webp|bmp|mp4)$) {
-        add_header Cache-Control "public, max-age=31536000, immutable";
-      }   
+    # pict-rs images
+    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/ {
index 1702f66d3ff58bce14ba6371f44e364b4239d216..bdcb4308ab4bdbf80491d2ed7e60a7f6ff46e886 100644 (file)
@@ -1,15 +1,6 @@
 version: '3.3'
 
 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:
     build: 
@@ -23,16 +14,27 @@ services:
     volumes:
       - ../lemmy.hjson:/config/config.hjson
     depends_on: 
+      - pictrs
       - postgres
-      - pictshare
       - iframely
 
-  pictshare:
-    image: hascheksolutions/pictshare:latest
-    ports:
-      - "127.0.0.1:8537:80"
+  postgres:
+    image: postgres:12-alpine
+    environment:
+      - POSTGRES_USER=lemmy
+      - POSTGRES_PASSWORD=password
+      - POSTGRES_DB=lemmy
+    volumes:
+      - ./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/pictshare:/usr/share/nginx/html/data
+      - ./volumes/pictrs:/mnt
     restart: always
 
   iframely:
index 246a95860fb2101f008f2a640b133ce31440ddf6..863ff593b544106130515acaeb3080fc4091fbc4 100644 (file)
@@ -12,7 +12,7 @@ services:
     restart: always
 
   lemmy:
-    image: dessalines/lemmy:v0.6.74
+    image: dessalines/lemmy:v0.6.79
     ports:
       - "127.0.0.1:8536:8536"
     restart: always
@@ -22,17 +22,17 @@ services:
       - ./lemmy.hjson:/config/config.hjson
     depends_on:
       - postgres
-      - pictshare
+      - pictrs
       - iframely
 
-  pictshare:
-    image: hascheksolutions/pictshare:latest
-    ports:
-      - "127.0.0.1:8537:80"
+  pictrs:
+    image: asonix/pictrs:v0.1.13-r0
+    ports: 
+      - "127.0.0.1:8537:8080"
+    user: 991:991
     volumes:
-      - ./volumes/pictshare:/usr/share/nginx/html/data
+      - ./volumes/pictrs:/mnt
     restart: always
-    mem_limit: 100m
 
   iframely:
     image: dogbin/iframely:latest
diff --git a/docker/prod/migrate-pictshare-to-pictrs.bash b/docker/prod/migrate-pictshare-to-pictrs.bash
new file mode 100644 (file)
index 0000000..8229eb2
--- /dev/null
@@ -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
index 3488a8c42def23ed1c568c36a5e210ae0e3e4cbe..4f11b3278e72bbbc8294b8bff49a9fc9710d557e 100644 (file)
@@ -18,7 +18,7 @@ use crate::db::user_mention_view::*;
 use crate::db::user_view::*;
 use crate::db::*;
 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,
 };
 
index 84ef89f16fcf92b0a2bc8e86fb2ea50efaf6e5a1..9eeb5158085a842b6a6901967eec66e91322052f 100644 (file)
@@ -116,9 +116,9 @@ impl Perform for Oper<CreatePost> {
       return Err(APIError::err("site_ban").into());
     }
 
-    // Fetch Iframely and Pictshare cached image
-    let (iframely_title, iframely_description, iframely_html, pictshare_thumbnail) =
-      fetch_iframely_and_pictshare_data(data.url.to_owned());
+    // Fetch Iframely and pictrs cached image
+    let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
+      fetch_iframely_and_pictrs_data(data.url.to_owned());
 
     let post_form = PostForm {
       name: data.name.to_owned(),
@@ -135,7 +135,7 @@ impl Perform for Oper<CreatePost> {
       embed_title: iframely_title,
       embed_description: iframely_description,
       embed_html: iframely_html,
-      thumbnail_url: pictshare_thumbnail,
+      thumbnail_url: pictrs_thumbnail,
     };
 
     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());
     }
 
-    // Fetch Iframely and Pictshare cached image
-    let (iframely_title, iframely_description, iframely_html, pictshare_thumbnail) =
-      fetch_iframely_and_pictshare_data(data.url.to_owned());
+    // Fetch Iframely and Pictrs cached image
+    let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
+      fetch_iframely_and_pictrs_data(data.url.to_owned());
 
     let post_form = PostForm {
       name: data.name.to_owned(),
@@ -469,7 +469,7 @@ impl Perform for Oper<EditPost> {
       embed_title: iframely_title,
       embed_description: iframely_description,
       embed_html: iframely_html,
-      thumbnail_url: pictshare_thumbnail,
+      thumbnail_url: pictrs_thumbnail,
     };
 
     let _updated_post = match Post::update(&conn, data.edit_id, &post_form) {
index e3ce5d71629136ef6a615ded7773c8501595ce44..ebfe17d9c9ef803c3863a2ab52ca83ca4e45f937 100644 (file)
@@ -187,25 +187,35 @@ pub fn fetch_iframely(url: &str) -> Result<IframelyResponse, failure::Error> {
   Ok(res)
 }
 
-#[derive(Deserialize, Debug)]
-pub struct PictshareResponse {
-  status: String,
-  url: String,
+#[derive(Deserialize, Debug, Clone)]
+pub struct PictrsResponse {
+  files: Vec<PictrsFile>,
+  msg: String,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct PictrsFile {
+  file: String,
+  delete_token: String,
 }
 
-pub fn fetch_pictshare(image_url: &str) -> Result<PictshareResponse, failure::Error> {
+pub fn fetch_pictrs(image_url: &str) -> Result<PictrsResponse, failure::Error> {
   is_image_content_type(image_url)?;
 
   let fetch_url = format!(
-    "http://pictshare/api/geturl.php?url={}",
-    utf8_percent_encode(image_url, NON_ALPHANUMERIC)
+    "http://pictrs:8080/image/download?url={}",
+    utf8_percent_encode(image_url, NON_ALPHANUMERIC) // TODO this might not be needed
   );
   let text = attohttpc::get(&fetch_url).send()?.text()?;
-  let res: PictshareResponse = serde_json::from_str(&text)?;
-  Ok(res)
+  let res: PictrsResponse = serde_json::from_str(&text)?;
+  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>,
 ) -> (
   Option<String>,
@@ -225,20 +235,20 @@ fn fetch_iframely_and_pictshare_data(
           }
         };
 
-      // Fetch pictshare thumbnail
-      let pictshare_thumbnail = match iframely_thumbnail_url {
-        Some(iframely_thumbnail_url) => match fetch_pictshare(&iframely_thumbnail_url) {
-          Ok(res) => Some(res.url),
+      // Fetch pictrs thumbnail
+      let pictrs_thumbnail = match iframely_thumbnail_url {
+        Some(iframely_thumbnail_url) => match fetch_pictrs(&iframely_thumbnail_url) {
+          Ok(res) => Some(res.files[0].file.to_owned()),
           Err(e) => {
-            error!("pictshare err: {}", e);
+            error!("pictrs err: {}", e);
             None
           }
         },
         // Try to generate a small thumbnail if iframely is not supported
-        None => match fetch_pictshare(&url) {
-          Ok(res) => Some(res.url),
+        None => match fetch_pictrs(&url) {
+          Ok(res) => Some(res.files[0].file.to_owned()),
           Err(e) => {
-            error!("pictshare err: {}", e);
+            error!("pictrs err: {}", e);
             None
           }
         },
@@ -248,7 +258,7 @@ fn fetch_iframely_and_pictshare_data(
         iframely_title,
         iframely_description,
         iframely_html,
-        pictshare_thumbnail,
+        pictrs_thumbnail,
       )
     }
     None => (None, None, None, None),
index 529e3bff94b14830c72eaff72a5a75a59539ba0c..a27f3acb9f0ec2663dad6dccd9b8f4e829b04c78 100644 (file)
@@ -1 +1 @@
-pub const VERSION: &str = "v0.6.74";
+pub const VERSION: &str = "v0.6.79";
index 5239eb2c7a10660182154a1dd7f91b305ca367b7..79aa91bdd061720a2095da203e6dd2ce8a6e8788 100644 (file)
@@ -18,6 +18,7 @@ import {
   setupTribute,
   wsJsonToRes,
   emojiPicker,
+  pictrsDeleteToast,
 } from '../utils';
 import { WebSocketService, UserService } from '../services';
 import autosize from 'autosize';
@@ -162,8 +163,9 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
               </button>
               {this.state.commentForm.content && (
                 <button
-                  className={`btn btn-sm mr-2 btn-secondary ${this.state
-                    .previewMode && 'active'}`}
+                  className={`btn btn-sm mr-2 btn-secondary ${
+                    this.state.previewMode && 'active'
+                  }`}
                   onClick={linkEvent(this, this.handlePreviewToggle)}
                 >
                   {i18n.t('preview')}
@@ -304,9 +306,9 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
       file = event;
     }
 
-    const imageUploadUrl = `/pictshare/api/upload.php`;
+    const imageUploadUrl = `/pictrs/image`;
     const formData = new FormData();
-    formData.append('file', file);
+    formData.append('images[]', file);
 
     i.state.imageLoading = true;
     i.setState(i.state);
@@ -317,16 +319,31 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
     })
       .then(res => res.json())
       .then(res => {
-        let url = `${window.location.origin}/pictshare/${res.url}`;
-        let imageMarkdown =
-          res.filetype == 'mp4' ? `[vid](${url}/raw)` : `![](${url})`;
-        let content = i.state.commentForm.content;
-        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);
+        console.log('pictrs upload:');
+        console.log(res);
+        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}`;
+          let imageMarkdown = `![](${url})`;
+          let content = i.state.commentForm.content;
+          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 => {
         i.state.imageLoading = false;
index e0d8aff50ad98789073adc97bad7a170638787d9..4cb74391b7361bdc6e4edba8a04265a1db67b4de 100644 (file)
@@ -22,7 +22,7 @@ import {
 } from '../interfaces';
 import {
   wsJsonToRes,
-  pictshareAvatarThumbnail,
+  pictrsAvatarThumbnail,
   showAvatars,
   fetchLimit,
   isCommentType,
@@ -218,7 +218,7 @@ export class Navbar extends Component<any, NavbarState> {
                     <span>
                       {UserService.Instance.user.avatar && showAvatars() && (
                         <img
-                          src={pictshareAvatarThumbnail(
+                          src={pictrsAvatarThumbnail(
                             UserService.Instance.user.avatar
                           )}
                           height="32"
@@ -381,7 +381,7 @@ export class Navbar extends Component<any, NavbarState> {
 
   requestNotificationPermission() {
     if (UserService.Instance.user) {
-      document.addEventListener('DOMContentLoaded', function() {
+      document.addEventListener('DOMContentLoaded', function () {
         if (!Notification) {
           toast(i18n.t('notifications_error'), 'danger');
           return;
index 6f1e34e01417e0e2b1a25dff00d6b2baf0e2df42..d424538bdf7e451a98af85b4b1d35c2ef395895f 100644 (file)
@@ -35,6 +35,7 @@ import {
   setupTribute,
   setupTippy,
   emojiPicker,
+  pictrsDeleteToast,
 } from '../utils';
 import autosize from 'autosize';
 import Tribute from 'tributejs/src/Tribute.js';
@@ -518,9 +519,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
       file = event;
     }
 
-    const imageUploadUrl = `/pictshare/api/upload.php`;
+    const imageUploadUrl = `/pictrs/image`;
     const formData = new FormData();
-    formData.append('file', file);
+    formData.append('images[]', file);
 
     i.state.imageLoading = true;
     i.setState(i.state);
@@ -531,13 +532,26 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
     })
       .then(res => res.json())
       .then(res => {
-        let url = `${window.location.origin}/pictshare/${encodeURI(res.url)}`;
-        if (res.filetype == 'mp4') {
-          url += '/raw';
+        console.log('pictrs upload:');
+        console.log(res);
+        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 => {
         i.state.imageLoading = false;
index 36a1e2828dc2dd3aab20182b6fb390ede30ebd4d..7d10acf72139bd41586fa5e3299fa3b548498175 100644 (file)
@@ -28,7 +28,7 @@ import {
   isImage,
   isVideo,
   getUnixTime,
-  pictshareImage,
+  pictrsImage,
   setupTippy,
   previewLines,
 } from '../utils';
@@ -161,15 +161,15 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
   getImage(thumbnail: boolean = false) {
     let post = this.props.post;
     if (isImage(post.url)) {
-      if (post.url.includes('pictshare')) {
-        return pictshareImage(post.url, thumbnail);
+      if (post.url.includes('pictrs')) {
+        return pictrsImage(post.url, thumbnail);
       } else if (post.thumbnail_url) {
-        return pictshareImage(post.thumbnail_url, thumbnail);
+        return pictrsImage(post.thumbnail_url, thumbnail);
       } else {
         return post.url;
       }
     } else if (post.thumbnail_url) {
-      return pictshareImage(post.thumbnail_url, thumbnail);
+      return pictrsImage(post.thumbnail_url, thumbnail);
     }
   }
 
index 337b165012000f5598dcdd6ff4d670703aec765f..71924f0cbabb882c84a24b2718fe97afa62aec02 100644 (file)
@@ -5,12 +5,7 @@ import {
   EditPrivateMessageForm,
 } from '../interfaces';
 import { WebSocketService, UserService } from '../services';
-import {
-  mdToHtml,
-  pictshareAvatarThumbnail,
-  showAvatars,
-  toast,
-} from '../utils';
+import { mdToHtml, pictrsAvatarThumbnail, showAvatars, toast } from '../utils';
 import { MomentTime } from './moment-time';
 import { PrivateMessageForm } from './private-message-form';
 import { i18n } from '../i18next';
@@ -78,7 +73,7 @@ export class PrivateMessage extends Component<
                     <img
                       height="32"
                       width="32"
-                      src={pictshareAvatarThumbnail(
+                      src={pictrsAvatarThumbnail(
                         this.mine
                           ? message.recipient_avatar
                           : message.creator_avatar
@@ -144,8 +139,9 @@ export class PrivateMessage extends Component<
                         }
                       >
                         <svg
-                          class={`icon icon-inline ${message.read &&
-                            'text-success'}`}
+                          class={`icon icon-inline ${
+                            message.read && 'text-success'
+                          }`}
                         >
                           <use xlinkHref="#icon-check"></use>
                         </svg>
@@ -188,8 +184,9 @@ export class PrivateMessage extends Component<
                         }
                       >
                         <svg
-                          class={`icon icon-inline ${message.deleted &&
-                            'text-danger'}`}
+                          class={`icon icon-inline ${
+                            message.deleted && 'text-danger'
+                          }`}
                         >
                           <use xlinkHref="#icon-trash"></use>
                         </svg>
@@ -204,8 +201,9 @@ export class PrivateMessage extends Component<
                     data-tippy-content={i18n.t('view_source')}
                   >
                     <svg
-                      class={`icon icon-inline ${this.state.viewSource &&
-                        'text-success'}`}
+                      class={`icon icon-inline ${
+                        this.state.viewSource && 'text-success'
+                      }`}
                     >
                       <use xlinkHref="#icon-file-text"></use>
                     </svg>
index b9662fae3abcb9f62ec25b6ee94511f5306db485..c32f4f2fbb7d99f05aadc87cf27f4fa6f65e7e3d 100644 (file)
@@ -22,7 +22,7 @@ import {
   fetchLimit,
   routeSearchTypeToEnum,
   routeSortTypeToEnum,
-  pictshareAvatarThumbnail,
+  pictrsAvatarThumbnail,
   showAvatars,
   toast,
   createCommentLikeRes,
index fce315610006627d63328f297cabca7049dd79d5..b280ef4fbfabc64f7efc247c461b172dbd770cb0 100644 (file)
@@ -11,7 +11,7 @@ import { WebSocketService, UserService } from '../services';
 import {
   mdToHtml,
   getUnixTime,
-  pictshareAvatarThumbnail,
+  pictrsAvatarThumbnail,
   showAvatars,
 } from '../utils';
 import { CommunityForm } from './community-form';
index 1f136e006c8fec137d0103b2087e5876fa014a16..ea87fd3aae778d8313853abe0ffbccc8d053fcc6 100644 (file)
@@ -1,7 +1,7 @@
 import { Component } from 'inferno';
 import { Link } from 'inferno-router';
 import { UserView } from '../interfaces';
-import { pictshareAvatarThumbnail, showAvatars } from '../utils';
+import { pictrsAvatarThumbnail, showAvatars } from '../utils';
 
 interface UserOther {
   name: string;
@@ -25,7 +25,7 @@ export class UserListing extends Component<UserListingProps, any> {
           <img
             height="32"
             width="32"
-            src={pictshareAvatarThumbnail(user.avatar)}
+            src={pictrsAvatarThumbnail(user.avatar)}
             class="rounded-circle mr-2"
           />
         )}
index 7cd460a17b9ff2110891467949b0b497a1c47b9f..a791f0c8d6db79ff7d4af7786a01d440d06419fb 100644 (file)
@@ -988,9 +988,9 @@ export class User extends Component<any, UserState> {
   handleImageUpload(i: User, event: any) {
     event.preventDefault();
     let file = event.target.files[0];
-    const imageUploadUrl = `/pictshare/api/upload.php`;
+    const imageUploadUrl = `/pictrs/image`;
     const formData = new FormData();
-    formData.append('file', file);
+    formData.append('images[]', file);
 
     i.state.avatarLoading = true;
     i.setState(i.state);
@@ -1001,14 +1001,19 @@ export class User extends Component<any, UserState> {
     })
       .then(res => res.json())
       .then(res => {
-        let url = `${window.location.origin}/pictshare/${res.url}`;
-        if (res.filetype == 'mp4') {
-          url += '/raw';
+        console.log('pictrs upload:');
+        console.log(res);
+        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 => {
         i.state.avatarLoading = false;
index 93b9cab08bdae41b3c0caa57bc6e55a9301a9251..39c371a11523154f6c8ccea858175d9a746b673a 100644 (file)
@@ -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(!loggedIn) {
-    document.getElementById("default-light").removeAttribute('disabled')
-    document.getElementById("default-dark").removeAttribute('disabled')
+  if (!loggedIn) {
+    document.getElementById('default-light').removeAttribute('disabled');
+    document.getElementById('default-dark').removeAttribute('disabled');
   } else {
-    document.getElementById("default-light").setAttribute('disabled', 'disabled');
-    document.getElementById("default-dark").setAttribute('disabled', 'disabled');
+    document
+      .getElementById('default-light')
+      .setAttribute('disabled', 'disabled');
+    document
+      .getElementById('default-dark')
+      .setAttribute('disabled', 'disabled');
 
     // Load the theme dynamically
     let cssLoc = `/static/assets/css/themes/${theme}.min.css`;
@@ -449,10 +453,12 @@ export function objectFlip(obj: any) {
   return ret;
 }
 
-export function pictshareAvatarThumbnail(src: string): string {
-  // sample url: http://localhost:8535/pictshare/gs7xuu.jpg
-  let split = src.split('pictshare');
-  let out = `${split[0]}pictshare/${canUseWebP() ? 'webp/' : ''}96${split[1]}`;
+export function pictrsAvatarThumbnail(src: string): string {
+  // sample url: http://localhost:8535/pictrs/image/thumbnail256/gs7xuu.jpg
+  let split = src.split('/pictrs/image');
+  let out = `${split[0]}/pictrs/image/${
+    canUseWebP() ? 'webp/' : ''
+  }thumbnail96${split[1]}`;
   return out;
 }
 
@@ -464,21 +470,18 @@ export function showAvatars(): boolean {
 }
 
 // Converts to image thumbnail
-export function pictshareImage(
-  hash: string,
-  thumbnail: boolean = false
-): string {
-  let root = `/pictshare`;
+export function pictrsImage(hash: string, thumbnail: boolean = false): string {
+  let root = `/pictrs/image`;
 
   // Necessary for other servers / domains
-  if (hash.includes('pictshare')) {
-    let split = hash.split('/pictshare/');
-    root = `${split[0]}/pictshare`;
+  if (hash.includes('pictrs')) {
+    let split = hash.split('/pictrs/image/');
+    root = `${split[0]}/pictrs/image`;
     hash = split[1];
   }
 
   let out = `${root}/${canUseWebP() ? 'webp/' : ''}${
-    thumbnail ? '192/' : ''
+    thumbnail ? 'thumbnail256/' : ''
   }${hash}`;
   return out;
 }
@@ -497,6 +500,29 @@ export function toast(text: string, background: string = 'success') {
   }).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(
   creator: string,
   avatar: string,
index e6e1137889014648ab6a8d43a6cee81785160a85..50df125c533f6f8d138d0816f5f64a5cf6b4eb37 100644 (file)
@@ -1 +1 @@
-export const version: string = 'v0.6.74';
+export const version: string = 'v0.6.79';
index a07da6216f3aafd2b31efe9ebe37e90bc8ed35b7..6d2d1c5dce66acaa84dbe90be2dcad2157d80393 100644 (file)
@@ -76,6 +76,8 @@
     "delete_account": "Delete Account",
     "delete_account_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",
     "ban": "ban",
     "ban_from_site": "ban from site",
index 796aebde3dd7f5a677bfcc6d9c902740c8ea5cd7..0b25e08c0cfe662327270c3a4c5db567f9a547f0 100644 (file)
@@ -5,7 +5,7 @@
     "create_a_post": "Crear una publicación",
     "create_post": "Crear Publicación",
     "number_of_posts": "{{count}} Publicación",
-    "number_of_posts_plural": "{{count}} Publicaciónes",
+    "number_of_posts_plural": "{{count}} Publicaciones",
     "posts": "Publicaciones",
     "related_posts": "Estas publicaciones podrían estar relacionadas",
     "cross_posts": "Este link también ha sido publicado en:",
     "remove_as_admin": "eliminar como administrador",
     "appoint_as_admin": "designar como administrador",
     "remove": "eliminar",
-    "removed": "eliminado",
+    "removed": "eliminado por moderador",
     "locked": "bloqueado",
     "stickied": "fijado",
     "reason": "Razón",
     "mark_as_read": "marcar como leído",
     "mark_as_unread": "marcar como no leído",
     "delete": "eliminar",
-    "deleted": "eliminado",
+    "deleted": "eliminado por creador",
     "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",
     "ban": "expulsar",
     "ban_from_site": "expulsar del sitio",
     "theme": "Tema",
     "sponsors": "Patrocinadores",
     "sponsors_of_lemmy": "Patrocinadores de Lemmy",
-    "sponsor_message": "Lemmy es software libre y de <1>código abierto</1>, lo que significa que no tendrá publicidades, monetización, ni capitales emprendedores, nunca. Tus donaciones apoyan directamente el desarrollo a tiempo completo del proyecto. Muchas gracias a las siguientes personas:",
+    "sponsor_message": "Lemmy es software libre y de <1>código abierto</1>, lo que significa que 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_liberapay": "Apoyo en Liberapay",
     "donate_to_lemmy": "Donar a Lemmy",
     "banned_users": "Usuarios Baneados",
     "support_on_open_collective": "Dona en OpenCollective",
     "site_saved": "Sitio Guardado.",
-    "emoji_picker": "Emoji Picker",
-    "admin_settings": "Panel de Administración"
+    "emoji_picker": "Lista de emojis",
+    "admin_settings": "Panel de Administración",
+    "select_a_community": "Selecciona una comunidad",
+    "invalid_username": "Nombre de usuario inválido."
 }
index f7457b2e17fde433b00b43b045d28cee2bd63d7f..a2dfcfdb305c50717517bdf102cecbca38756de6 100644 (file)
@@ -36,7 +36,7 @@
     "preview": "prévisualiser",
     "upload_image": "envoyer une image",
     "avatar": "Avatar",
-    "upload_avatar": "Télécharger une avatar",
+    "upload_avatar": "Télécharger un avatar",
     "show_avatars": "Afficher les avatars",
     "formatting_help": "aide au formattage",
     "view_source": "voir la source",
index 35f826750a759ed71bb403f9e1c819125d7287ca..6ad7e0de07b779f7042a7b0b531b70d4d0bc5e18 100644 (file)
@@ -2,8 +2,8 @@
     "post": "Elküld",
     "remove_post": "Bejegyzés eltávolítása",
     "no_posts": "Nincs bejegyzés.",
-    "create_post": "Új bejegyzés létrehozása",
-    "create_a_post": "Új bejegyzés létrehozása",
+    "create_post": "Bejegyzés létrehozása",
+    "create_a_post": "Bejegyzés létrehozása",
     "number_of_posts": "{{count}} bejegyzés",
     "number_of_posts_plural": "{{count}} bejegyzés",
     "posts": "Bejegyzések",
     "remove_comment": "Hozzászólások eltávolítása",
     "cross_posted_to": "beküldve ide is: ",
     "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"
 }
index 872d0b51fbf0b1438fe3322243577ce010d899fd..def3b05cb8ae2025abbf08c0fe03822b5369bb55 100644 (file)
     "remove_as_admin": "移除管理权限",
     "appoint_as_admin": "添加管理权限",
     "remove": "移除",
-    "removed": "已移除",
+    "removed": "已被管理员移除",
     "locked": "已加锁",
     "reason": "原因",
     "mark_as_read": "标记未读",
     "mark_as_unread": "标记已读",
     "delete": "删除",
-    "deleted": "已删除",
+    "deleted": "作者已删除",
     "restore": "恢复",
     "ban": "禁止",
     "ban_from_site": "禁止此站点",
     "time": "时间",
     "action": "行动",
     "block_leaving": "确定要离开吗?",
-    "show_context": "显示上下文"
+    "show_context": "显示上下文",
+    "admin_settings": "管理员设置",
+    "site_config": "网站配置",
+    "banned_users": "被禁止用户",
+    "site_saved": "网站已保存",
+    "emoji_picker": "选择表情",
+    "invalid_username": "用户名无效"
 }