Browse Source

Merge pull request #263 from dansup/frontend-ui-refactor

Frontend ui refactor
daniel 7 years ago
parent
commit
60bbb22454

+ 8 - 0
app/Avatar.php

@@ -3,8 +3,16 @@
 namespace App;
 
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
 
 class Avatar extends Model
 {
+    use SoftDeletes;
 
+    /**
+     * The attributes that should be mutated to dates.
+     *
+     * @var array
+     */
+    protected $dates = ['deleted_at'];
 }

+ 2 - 2
app/Http/Controllers/CommentController.php

@@ -34,8 +34,8 @@ class CommentController extends Controller
 
       $reply = new Status();
       $reply->profile_id = $profile->id;
-      $reply->caption = $comment;
-      $reply->rendered = e($comment);
+      $reply->caption = e(strip_tags($comment));
+      $reply->rendered = $comment;
       $reply->in_reply_to_id = $status->id;
       $reply->in_reply_to_profile_id = $status->profile_id;
       $reply->save();

+ 29 - 17
app/Http/Controllers/StatusController.php

@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
 
 use Auth, Cache;
 use App\Jobs\StatusPipeline\{NewStatusPipeline, StatusDelete};
+use App\Jobs\ImageOptimizePipeline\ImageOptimize;
 use Illuminate\Http\Request;
 use App\{Media, Profile, Status, User};
 use Vinkla\Hashids\Facades\Hashids;
@@ -14,7 +15,7 @@ class StatusController extends Controller
     {
       $user = Profile::whereUsername($username)->firstOrFail();
       $status = Status::whereProfileId($user->id)
-              ->withCount(['likes', 'comments'])
+              ->withCount(['likes', 'comments', 'media'])
               ->findOrFail($id);
       if(!$status->media_path && $status->in_reply_to_id) {
         return redirect($status->url());
@@ -32,36 +33,47 @@ class StatusController extends Controller
       $user = Auth::user();
 
       $this->validate($request, [
-        'photo'   => 'required|mimes:jpeg,png,bmp,gif|max:' . config('pixelfed.max_photo_size'),
+        'photo.*'   => 'required|mimes:jpeg,png,bmp,gif|max:' . config('pixelfed.max_photo_size'),
         'caption' => 'string|max:' . config('pixelfed.max_caption_length'),
-        'cw'      => 'nullable|string'
+        'cw'      => 'nullable|string',
+        'filter_class' => 'nullable|string',
+        'filter_name' => 'nullable|string',
       ]);
 
       $cw = $request->filled('cw') && $request->cw == 'on' ? true : false;
       $monthHash = hash('sha1', date('Y') . date('m'));
       $userHash = hash('sha1', $user->id . (string) $user->created_at);
-      $storagePath = "public/m/{$monthHash}/{$userHash}";
-      $path = $request->photo->store($storagePath);
       $profile = $user->profile;
 
       $status = new Status;
       $status->profile_id = $profile->id;
-      $status->caption = $request->caption;
+      $status->caption = strip_tags($request->caption);
       $status->is_nsfw = $cw;
 
       $status->save();
 
-      $media = new Media;
-      $media->status_id = $status->id;
-      $media->profile_id = $profile->id;
-      $media->user_id = $user->id;
-      $media->media_path = $path;
-      $media->size = $request->file('photo')->getClientSize();
-      $media->mime = $request->file('photo')->getClientMimeType();
-      $media->save();
-      NewStatusPipeline::dispatch($status, $media);
-
-      // TODO: Parse Caption
+      $photos = $request->file('photo');
+      $order = 1;
+      foreach ($photos as $k => $v) {
+        $storagePath = "public/m/{$monthHash}/{$userHash}";
+        $path = $v->store($storagePath);
+        $media = new Media;
+        $media->status_id = $status->id;
+        $media->profile_id = $profile->id;
+        $media->user_id = $user->id;
+        $media->media_path = $path;
+        $media->size = $v->getClientSize();
+        $media->mime = $v->getClientMimeType();
+        $media->filter_class = $request->input('filter_class');
+        $media->filter_name = $request->input('filter_name');
+        $media->order = $order;
+        $media->save();
+        ImageOptimize::dispatch($media);
+        $order++;
+      }
+
+      NewStatusPipeline::dispatch($status);
+
       // TODO: Send to subscribers
       
       return redirect($status->url());

+ 3 - 8
app/Jobs/StatusPipeline/NewStatusPipeline.php

@@ -16,17 +16,15 @@ class NewStatusPipeline implements ShouldQueue
     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
     protected $status;
-    protected $media;
 
     /**
      * Create a new job instance.
      *
      * @return void
      */
-    public function __construct(Status $status, $media = false)
+    public function __construct(Status $status)
     {
         $this->status = $status;
-        $this->media = $media;
     }
 
     /**
@@ -37,13 +35,10 @@ class NewStatusPipeline implements ShouldQueue
     public function handle()
     {
         $status = $this->status;
-        $media = $this->media;
 
         StatusEntityLexer::dispatch($status);
-        StatusActivityPubDeliver::dispatch($status);
-        if($media) {
-            ImageOptimize::dispatch($media);
-        }
+        //StatusActivityPubDeliver::dispatch($status);
+
         Cache::forever('post.' . $status->id, $status);
         
         $redis = Redis::connection();

+ 4 - 1
app/Jobs/StatusPipeline/StatusDelete.php

@@ -2,7 +2,7 @@
 
 namespace App\Jobs\StatusPipeline;
 
-use App\{Media, StatusHashtag, Status};
+use App\{Media, Notification, StatusHashtag, Status};
 use Illuminate\Bus\Queueable;
 use Illuminate\Queue\SerializesModels;
 use Illuminate\Queue\InteractsWithQueue;
@@ -60,6 +60,9 @@ class StatusDelete implements ShouldQueue
         }
 
         $status->likes()->delete();
+        Notification::whereItemType('App\Status')
+            ->whereItemId($status->id)
+            ->delete();
         StatusHashtag::whereStatusId($status->id)->delete();
         $status->delete();
 

+ 61 - 15
app/Jobs/StatusPipeline/StatusEntityLexer.php

@@ -6,21 +6,28 @@ use Cache;
 use App\{
     Hashtag,
     Media,
+    Mention,
+    Profile,
     Status,
     StatusHashtag
 };
 use App\Util\Lexer\Hashtag as HashtagLexer;
+use App\Util\Lexer\{Autolink, Extractor};
 use Illuminate\Bus\Queueable;
 use Illuminate\Queue\SerializesModels;
 use Illuminate\Queue\InteractsWithQueue;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Foundation\Bus\Dispatchable;
+use App\Jobs\MentionPipeline\MentionPipeline;
 
 class StatusEntityLexer implements ShouldQueue
 {
     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
     protected $status;
+    protected $entities;
+    protected $autolink;
+
     /**
      * Create a new job instance.
      *
@@ -39,22 +46,40 @@ class StatusEntityLexer implements ShouldQueue
     public function handle()
     {
         $status = $this->status;
-        $this->parseHashtags();
+        $this->parseEntities();
+    }
+
+    public function parseEntities()
+    {
+        $this->extractEntities();
+    }
+
+    public function extractEntities()
+    {
+        $this->entities = Extractor::create()->extract($this->status->caption);
+        $this->autolinkStatus();    
     }
 
-    public function parseHashtags()
+    public function autolinkStatus()
+    {
+        $this->autolink = Autolink::create()->autolink($this->status->caption);
+        $this->storeEntities();
+    }
+
+    public function storeEntities()
     {
         $status = $this->status;
-        $text = e($status->caption);
-        $tags = HashtagLexer::getHashtags($text);
-        $rendered = $text;
-        if(count($tags) > 0) {
-            $rendered = HashtagLexer::replaceHashtagsWithLinks($text);
-        }
-        $status->rendered = $rendered;
+        $this->storeHashtags();
+        $this->storeMentions();
+        $status->rendered = $this->autolink;
+        $status->entities = json_encode($this->entities);
         $status->save();
-        
-        Cache::forever('post.' . $status->id, $status);
+    }
+
+    public function storeHashtags()
+    {
+        $tags = array_unique($this->entities['hashtags']);
+        $status = $this->status;
 
         foreach($tags as $tag) {
             $slug = str_slug($tag);
@@ -64,11 +89,32 @@ class StatusEntityLexer implements ShouldQueue
                 ['slug' => $slug]
             );
 
-            $stag = new StatusHashtag;
-            $stag->status_id = $status->id;
-            $stag->hashtag_id = $htag->id;
-            $stag->save();
+            StatusHashtag::firstOrCreate(
+                ['status_id' => $status->id],
+                ['hashtag_id' => $htag->id]
+            );
         }
+    }
+
+    public function storeMentions()
+    {
+        $mentions = array_unique($this->entities['mentions']);
+        $status = $this->status;
+
+        foreach($mentions as $mention) {
+            $mentioned = Profile::whereUsername($mention)->first();
+            
+            if(empty($mentioned) || !isset($mentioned->id)) {
+                continue;
+            }
 
+            $m = new Mention;
+            $m->status_id = $status->id;
+            $m->profile_id = $mentioned->id;
+            $m->save();
+
+            MentionPipeline::dispatch($status, $m);
+        }
     }
+
 }

+ 10 - 0
app/Like.php

@@ -3,9 +3,19 @@
 namespace App;
 
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
 
 class Like extends Model
 {
+    use SoftDeletes;
+
+    /**
+     * The attributes that should be mutated to dates.
+     *
+     * @var array
+     */
+    protected $dates = ['deleted_at'];
+
     public function actor()
     {
       return $this->belongsTo(Profile::class, 'profile_id', 'id');

+ 11 - 1
app/Media.php

@@ -2,11 +2,21 @@
 
 namespace App;
 
-use Illuminate\Database\Eloquent\Model;
 use Storage;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
 
 class Media extends Model
 {
+    use SoftDeletes;
+
+    /**
+     * The attributes that should be mutated to dates.
+     *
+     * @var array
+     */
+    protected $dates = ['deleted_at'];
+    
     public function url()
     {
       $path = $this->media_path;

+ 9 - 0
app/Mention.php

@@ -3,9 +3,18 @@
 namespace App;
 
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
 
 class Mention extends Model
 {
+    use SoftDeletes;
+
+    /**
+     * The attributes that should be mutated to dates.
+     *
+     * @var array
+     */
+    protected $dates = ['deleted_at'];
 
     public function profile()
     {

+ 29 - 20
app/Notification.php

@@ -3,28 +3,37 @@
 namespace App;
 
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
 
 class Notification extends Model
 {
-
-  public function actor()
-  {
-    return $this->belongsTo(Profile::class, 'actor_id', 'id');
-  }
-
-  public function profile()
-  {
-    return $this->belongsTo(Profile::class, 'profile_id', 'id');
-  }
-
-  public function item()
-  {
-    return $this->morphTo();
-  }
-
-  public function status()
-  {
-    return $this->belongsTo(Status::class, 'item_id', 'id');
-  }
+    use SoftDeletes;
+
+    /**
+     * The attributes that should be mutated to dates.
+     *
+     * @var array
+     */
+    protected $dates = ['deleted_at'];
+    
+    public function actor()
+    {
+      return $this->belongsTo(Profile::class, 'actor_id', 'id');
+    }
+
+    public function profile()
+    {
+      return $this->belongsTo(Profile::class, 'profile_id', 'id');
+    }
+
+    public function item()
+    {
+      return $this->morphTo();
+    }
+
+    public function status()
+    {
+      return $this->belongsTo(Status::class, 'item_id', 'id');
+    }
 
 }

+ 9 - 0
app/Profile.php

@@ -5,9 +5,18 @@ namespace App;
 use Storage;
 use App\Util\Lexer\PrettyNumber;
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
 
 class Profile extends Model
 {
+    use SoftDeletes;
+
+    /**
+     * The attributes that should be mutated to dates.
+     *
+     * @var array
+     */
+    protected $dates = ['deleted_at'];
     protected $hidden = [
         'private_key',
     ];

+ 17 - 3
app/Status.php

@@ -2,12 +2,21 @@
 
 namespace App;
 
-use Illuminate\Database\Eloquent\Model;
 use Storage;
-use Vinkla\Hashids\Facades\Hashids;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
 
 class Status extends Model
 {
+    use SoftDeletes;
+
+    /**
+     * The attributes that should be mutated to dates.
+     *
+     * @var array
+     */
+    protected $dates = ['deleted_at'];
+    
     public function profile()
     {
       return $this->belongsTo(Profile::class);
@@ -25,7 +34,7 @@ class Status extends Model
 
     public function thumb()
     {
-      if($this->media->count() == 0) {
+      if($this->media->count() == 0 || $this->is_nsfw) {
         return "data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==";
       }
       return url(Storage::url($this->firstMedia()->thumbnail_path));
@@ -43,6 +52,11 @@ class Status extends Model
       return url($path);
     }
 
+    public function editUrl()
+    {
+      return $this->url() . '/edit';
+    }
+
     public function mediaUrl()
     {
       $media = $this->firstMedia();

+ 9 - 1
app/User.php

@@ -3,11 +3,19 @@
 namespace App;
 
 use Illuminate\Notifications\Notifiable;
+use Illuminate\Database\Eloquent\SoftDeletes;
 use Illuminate\Foundation\Auth\User as Authenticatable;
 
 class User extends Authenticatable
 {
-    use Notifiable;
+    use Notifiable, SoftDeletes;
+
+    /**
+     * The attributes that should be mutated to dates.
+     *
+     * @var array
+     */
+    protected $dates = ['deleted_at'];
 
     /**
      * The attributes that are mass assignable.

+ 34 - 0
database/migrations/2018_06_11_030049_add_filters_to_media_table.php

@@ -0,0 +1,34 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class AddFiltersToMediaTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('media', function (Blueprint $table) {
+            $table->string('filter_name')->nullable()->after('orientation');
+            $table->string('filter_class')->nullable()->after('filter_name');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('media', function (Blueprint $table) {
+            $table->dropColumn('filter_name');
+            $table->dropColumn('filter_class');
+        });
+    }
+}

+ 58 - 0
database/migrations/2018_06_14_001318_add_soft_deletes_to_models.php

@@ -0,0 +1,58 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class AddSoftDeletesToModels extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('avatars', function ($table) {
+            $table->softDeletes();
+        });
+
+        Schema::table('likes', function ($table) {
+            $table->softDeletes();
+        });
+
+        Schema::table('media', function ($table) {
+            $table->softDeletes();
+        });
+
+        Schema::table('mentions', function ($table) {
+            $table->softDeletes();
+        });
+
+        Schema::table('notifications', function ($table) {
+            $table->softDeletes();
+        });
+
+        Schema::table('profiles', function ($table) {
+            $table->softDeletes();
+        });
+
+        Schema::table('statuses', function ($table) {
+            $table->softDeletes();
+        });
+
+        Schema::table('users', function ($table) {
+            $table->softDeletes();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        //
+    }
+}

BIN
public/css/app.css


BIN
public/mix-manifest.json


+ 42 - 2
resources/views/status/show.blade.php

@@ -16,7 +16,47 @@
       </div>
      </div>
       <div class="col-12 col-md-8 status-photo px-0">
-        <img src="{{$status->mediaUrl()}}" width="100%">
+        @if($status->is_nsfw && $status->media_count == 1)
+        <details class="details-animated">
+          <p>
+            <summary>NSFW / Hidden Image</summary>
+            <a class="max-hide-overflow {{$status->firstMedia()->filter_class}}" href="{{$status->url()}}">
+              <img class="card-img-top" src="{{$status->mediaUrl()}}">
+            </a>
+          </p>
+        </details>
+        @elseif(!$status->is_nsfw && $status->media_count == 1)
+        <div class="{{$status->firstMedia()->filter_class}}">
+          <img src="{{$status->mediaUrl()}}" width="100%">
+        </div>
+        @elseif($status->is_nsfw && $status->media_count > 1)
+
+        @elseif(!$status->is_nsfw && $status->media_count > 1)
+          <div id="photoCarousel" class="carousel slide carousel-fade" data-ride="carousel">
+            <ol class="carousel-indicators">
+              @for($i = 0; $i < $status->media_count; $i++)
+              <li data-target="#photoCarousel" data-slide-to="{{$i}}" class="{{$i == 0 ? 'active' : ''}}"></li>
+              @endfor
+            </ol>
+            <div class="carousel-inner">
+              @foreach($status->media()->orderBy('order')->get() as $media)
+              <div class="carousel-item {{$loop->iteration == 1 ? 'active' : ''}}">
+                <figure class="{{$media->filter_class}}">
+                  <img class="d-block w-100" src="{{$media->url()}}" alt="{{$status->caption}}">
+                </figure>
+              </div>
+              @endforeach
+            </div>
+            <a class="carousel-control-prev" href="#photoCarousel" role="button" data-slide="prev">
+              <span class="carousel-control-prev-icon" aria-hidden="true"></span>
+              <span class="sr-only">Previous</span>
+            </a>
+            <a class="carousel-control-next" href="#photoCarousel" role="button" data-slide="next">
+              <span class="carousel-control-next-icon" aria-hidden="true"></span>
+              <span class="sr-only">Next</span>
+            </a>
+          </div>
+        @endif
       </div>
       <div class="col-12 col-md-4 px-0 d-flex flex-column border-left border-md-left-0">
         <div class="d-md-flex d-none align-items-center justify-content-between card-header py-3 bg-white">
@@ -40,7 +80,7 @@
                 @foreach($status->comments->reverse()->take(10) as $item)
                 <p class="mb-0">
                   <span class="font-weight-bold pr-1"><bdi><a class="text-dark" href="{{$item->profile->url()}}">{{$item->profile->username}}</a></bdi></span>
-                  <span class="comment-text">{!!$item->rendered!!} <a href="{{$item->url()}}" class="text-dark small font-weight-bold float-right">{{$item->created_at->diffForHumans(null, true, true ,true)}}</a></span>
+                  <span class="comment-text">{!! $item->rendered ?? e($item->caption) !!} <a href="{{$item->url()}}" class="text-dark small font-weight-bold float-right">{{$item->created_at->diffForHumans(null, true, true ,true)}}</a></span>
                 </p>
                 @endforeach
               </div>

+ 6 - 5
resources/views/status/template.blade.php

@@ -15,6 +15,7 @@
                 <a class="dropdown-item" href="#">Embed</a>
               @if(Auth::check())
                 @if(Auth::user()->profile->id === $item->profile->id || Auth::user()->is_admin == true)
+                <a class="dropdown-item" href="{{$item->editUrl()}}">Edit</a>
                 <form method="post" action="/i/delete">
                   @csrf
                   <input type="hidden" name="type" value="post">
@@ -29,16 +30,16 @@
           </div>
         </div>
         @if($item->is_nsfw)
-        <details>
+        <details class="details-animated">
           <p>
             <summary>NSFW / Hidden Image</summary>
-            <a class="max-hide-overflow" href="{{$item->url()}}">
+            <a class="max-hide-overflow {{$item->firstMedia()->filter_class}}" href="{{$item->url()}}">
               <img class="card-img-top" src="{{$item->mediaUrl()}}">
             </a>
           </p>
         </details>
         @else
-        <a class="max-hide-overflow" href="{{$item->url()}}">
+        <a class="max-hide-overflow {{$item->firstMedia()->filter_class}}" href="{{$item->url()}}">
           <img class="card-img-top" src="{{$item->mediaUrl()}}">
         </a>
         @endif
@@ -84,7 +85,7 @@
                     <a class="text-dark" href="{{$status->profile->url()}}">{{$status->profile->username}}</a>
                   </bdi>
                 </span>
-                <span class="comment-text">{!!$status->rendered!!}</span>
+                <span class="comment-text">{!! $item->rendered ?? e($item->caption) !!}</span>
                 <span class="float-right">
                   <a href="{{$status->url()}}" class="text-dark small font-weight-bold">
                     {{$status->created_at->diffForHumans(null, true, true, true)}}
@@ -95,7 +96,7 @@
             @foreach($item->comments->reverse()->take(3) as $comment)
               <p class="mb-0">
                 <span class="font-weight-bold pr-1"><bdi><a class="text-dark" href="{{$comment->profile->url()}}">{{$comment->profile->username}}</a></bdi></span>
-                <span class="comment-text">{{ str_limit($comment->caption, 125) }}</span>
+                <span class="comment-text">{!! str_limit($item->rendered ?? e($item->caption), 150) !!}</span>
               </p>
             @endforeach
             @endif