فهرست منبع

Merge pull request #613 from pixelfed/frontend-ui-refactor

Frontend ui refactor
daniel 6 سال پیش
والد
کامیت
f7c4b45098

+ 23 - 3
app/Http/Controllers/Api/BaseApiController.php

@@ -8,9 +8,15 @@ use App\Http\Controllers\{
     AvatarController
 };
 use Auth, Cache, URL;
-use App\{Avatar,Media,Profile};
+use App\{
+    Avatar,
+    Notification,
+    Media,
+    Profile
+};
 use App\Transformer\Api\{
     AccountTransformer,
+    NotificationTransformer,
     MediaTransformer,
     StatusTransformer
 };
@@ -35,6 +41,15 @@ class BaseApiController extends Controller
         $this->fractal->setSerializer(new ArraySerializer());
     }
 
+    public function notification(Request $request, $id)
+    {
+        $notification = Notification::findOrFail($id);
+        $resource = new Fractal\Resource\Item($notification, new NotificationTransformer());
+        $res = $this->fractal->createData($resource)->toArray();
+
+        return response()->json($res, 200, [], JSON_PRETTY_PRINT);
+    }
+
     public function accounts(Request $request, $id)
     {
         $profile = Profile::findOrFail($id);
@@ -173,6 +188,11 @@ class BaseApiController extends Controller
 
         $photo = $request->file('file');
 
+        $mimes = explode(',', config('pixelfed.media_types'));
+        if(in_array($photo->getMimeType(), $mimes) == false) {
+            return;
+        }
+
         $storagePath = "public/m/{$monthHash}/{$userHash}";
         $path = $photo->store($storagePath);
         $hash = \hash_file('sha256', $photo);
@@ -183,8 +203,8 @@ class BaseApiController extends Controller
         $media->user_id = $user->id;
         $media->media_path = $path;
         $media->original_sha256 = $hash;
-        $media->size = $photo->getClientSize();
-        $media->mime = $photo->getClientMimeType();
+        $media->size = $photo->getSize();
+        $media->mime = $photo->getMimeType();
         $media->filter_class = null;
         $media->filter_name = null;
         $media->save();

+ 6 - 4
app/Http/Controllers/InternalApiController.php

@@ -53,6 +53,7 @@ class InternalApiController extends Controller
         $medias = $request->input('media');
         $attachments = [];
         $status = new Status;
+        $mimes = [];
 
         foreach($medias as $k => $media) {
             $m = Media::findOrFail($media['id']);
@@ -69,6 +70,7 @@ class InternalApiController extends Controller
             }
             $m->save();
             $attachments[] = $m;
+            array_push($mimes, $m->mime);
         }
 
         $status->caption = strip_tags($request->caption);
@@ -84,6 +86,7 @@ class InternalApiController extends Controller
 
         $status->visibility = $visibility;
         $status->scope = $visibility;
+        $status->type = StatusController::mimeTypeCheck($mimes);
         $status->save();
 
         NewStatusPipeline::dispatch($status);
@@ -96,6 +99,7 @@ class InternalApiController extends Controller
         $this->validate($request, [
           'page' => 'nullable|min:1|max:3',
         ]);
+        
         $profile = Auth::user()->profile;
         $timeago = Carbon::now()->subMonths(6);
         $notifications = Notification::with('actor')
@@ -148,8 +152,7 @@ class InternalApiController extends Controller
             ->get();
 
         $posts = Status::select('id', 'caption', 'profile_id')
-              ->whereNull('in_reply_to_id')
-              ->whereNull('reblog_of_id')
+              ->whereHas('media')
               ->whereIsNsfw(false)
               ->whereVisibility('public')
               ->whereNotIn('profile_id', $following)
@@ -233,8 +236,7 @@ class InternalApiController extends Controller
         $following = array_merge($following, $filters);
 
         $posts = Status::select('id', 'caption', 'profile_id')
-              ->whereNull('in_reply_to_id')
-              ->whereNull('reblog_of_id')
+              ->whereHas('media')
               ->whereIsNsfw(false)
               ->whereVisibility('public')
               ->whereNotIn('profile_id', $following)

+ 49 - 1
app/Http/Controllers/PublicApiController.php

@@ -31,7 +31,7 @@ class PublicApiController extends Controller
 
     public function __construct()
     {
-        $this->middleware('throttle:200, 30');
+        $this->middleware('throttle:3000, 30');
         $this->fractal = new Fractal\Manager();
         $this->fractal->setSerializer(new ArraySerializer());
     }
@@ -47,6 +47,30 @@ class PublicApiController extends Controller
     	}
     }
 
+    protected function getLikes($status)
+    {
+        if(false == Auth::check()) {
+            return [];
+        } else {
+            $profile = Auth::user()->profile;
+            $likes = $status->likedBy()->orderBy('created_at','desc')->paginate(10);
+            $collection = new Fractal\Resource\Collection($likes, new AccountTransformer());
+            return $this->fractal->createData($collection)->toArray();
+        }
+    }
+
+    protected function getShares($status)
+    {
+        if(false == Auth::check()) {
+            return [];
+        } else {
+            $profile = Auth::user()->profile;
+            $shares = $status->sharedBy()->orderBy('created_at','desc')->paginate(10);
+            $collection = new Fractal\Resource\Collection($shares, new AccountTransformer());
+            return $this->fractal->createData($collection)->toArray();
+        }
+    }
+
     public function status(Request $request, $username, int $postid)
     {
         $profile = Profile::whereUsername($username)->first();
@@ -56,6 +80,8 @@ class PublicApiController extends Controller
         $res = [
         	'status' => $this->fractal->createData($item)->toArray(),
         	'user' => $this->getUserData(),
+            'likes' => $this->getLikes($status),
+            'shares' => $this->getShares($status),
             'reactions' => [
                 'liked' => $status->liked(),
                 'shared' => $status->shared(),
@@ -104,6 +130,28 @@ class PublicApiController extends Controller
         return response()->json($res, 200, [], JSON_PRETTY_PRINT);
     }
 
+    public function statusLikes(Request $request, $username, $id)
+    {
+        $profile = Profile::whereUsername($username)->first();
+        $status = Status::whereProfileId($profile->id)->find($id);
+        $this->scopeCheck($profile, $status);
+        $likes = $this->getLikes($status);
+        return response()->json([
+            'data' => $likes
+        ]);
+    }
+
+    public function statusShares(Request $request, $username, $id)
+    {
+        $profile = Profile::whereUsername($username)->first();
+        $status = Status::whereProfileId($profile->id)->find($id);
+        $this->scopeCheck($profile, $status);
+        $shares = $this->getShares($status);
+        return response()->json([
+            'data' => $shares
+        ]);
+    }
+
     protected function scopeCheck(Profile $profile, Status $status)
     {
         if($profile->is_private == true && Auth::check() == false) {

+ 54 - 2
app/Http/Controllers/StatusController.php

@@ -92,7 +92,16 @@ class StatusController extends Controller
 
         $photos = $request->file('photo');
         $order = 1;
+        $mimes = [];
+        $medias = 0;
+
         foreach ($photos as $k => $v) {
+
+            $allowedMimes = explode(',', config('pixelfed.media_types'));
+            if(in_array($v->getMimeType(), $allowedMimes) == false) {
+                continue;
+            }
+
             $storagePath = "public/m/{$monthHash}/{$userHash}";
             $path = $v->store($storagePath);
             $hash = \hash_file('sha256', $v);
@@ -102,15 +111,24 @@ class StatusController extends Controller
             $media->user_id = $user->id;
             $media->media_path = $path;
             $media->original_sha256 = $hash;
-            $media->size = $v->getClientSize();
-            $media->mime = $v->getClientMimeType();
+            $media->size = $v->getSize();
+            $media->mime = $v->getMimeType();
             $media->filter_class = $request->input('filter_class');
             $media->filter_name = $request->input('filter_name');
             $media->order = $order;
             $media->save();
+            array_push($mimes, $media->mime);
             ImageOptimize::dispatch($media);
             $order++;
+            $medias++;
+        }
+
+        if($medias == 0) {
+            $status->delete();
+            return;
         }
+        $status->type = (new self)::mimeTypeCheck($mimes);
+        $status->save();
 
         NewStatusPipeline::dispatch($status);
 
@@ -254,4 +272,38 @@ class StatusController extends Controller
         $allowed = ['public', 'unlisted', 'private'];
         return in_array($visibility, $allowed) ? $visibility : 'public';
     }
+
+    public static function mimeTypeCheck($mimes)
+    {
+        $allowed = explode(',', config('pixelfed.media_types'));
+        $count = count($mimes);
+        $photos = 0;
+        $videos = 0;
+        foreach($mimes as $mime) {
+            if(in_array($mime, $allowed) == false) {
+                continue;
+            }
+            if(str_contains($mime, 'image/')) {
+                $photos++;
+            }
+            if(str_contains($mime, 'video/')) {
+                $videos++;
+            }
+        }
+        if($photos == 1 && $videos == 0) {
+            return 'photo';
+        }
+        if($videos == 1 && $photos == 0) {
+            return 'video';
+        }
+        if($photos > 1 && $videos == 0) {
+            return 'photo:album';
+        }
+        if($videos > 1 && $photos == 0) {
+            return 'video:album';
+        }
+        if($photos >= 1 && $videos >= 1) {
+            return 'photo:video:album';
+        }
+    }
 }

+ 57 - 2
app/Status.php

@@ -3,6 +3,7 @@
 namespace App;
 
 use Auth, Cache;
+use App\Http\Controllers\StatusController;
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\SoftDeletes;
 use Storage;
@@ -18,7 +19,21 @@ class Status extends Model
      */
     protected $dates = ['deleted_at'];
 
-    protected $fillable = ['profile_id', 'visibility', 'in_reply_to_id'];
+    protected $fillable = ['profile_id', 'visibility', 'in_reply_to_id', 'reblog_of_id'];
+
+    const STATUS_TYPES = [
+        'photo',
+        'photo:album',
+        'video',
+        'video:album',
+        'photo:video:album',
+        'share',
+        'reply',
+        'story',
+        'story:reply',
+        'story:reaction',
+        'story:live'
+    ];
 
     public function profile()
     {
@@ -35,9 +50,11 @@ class Status extends Model
         return $this->hasMany(Media::class)->orderBy('order', 'asc')->first();
     }
 
+    // todo: deprecate after 0.6.0
     public function viewType()
     {
-        return Cache::remember('status:view-type:'.$this->id, 40320, function() {
+        return Cache::remember('status:view-type:'.$this->id, 10080, function() {
+            $this->setType();
             $media = $this->firstMedia();
             $mime = explode('/', $media->mime)[0];
             $count = $this->media()->count();
@@ -49,6 +66,20 @@ class Status extends Model
         });
     }
 
+    // todo: deprecate after 0.6.0
+    public function setType()
+    {
+        if(in_array($this->type, self::STATUS_TYPES)) {
+            return;
+        }
+        $mimes = $this->media->pluck('mime')->toArray();
+        $type = StatusController::mimeTypeCheck($mimes);
+        if($type) {
+            $this->type = $type;
+            $this->save();
+        }
+    }
+
     public function thumb($showNsfw = false)
     {
         return Cache::remember('status:thumb:'.$this->id, 40320, function() use ($showNsfw) {
@@ -108,6 +139,18 @@ class Status extends Model
         return Like::whereProfileId($profile->id)->whereStatusId($this->id)->count();
     }
 
+    public function likedBy()
+    {
+        return $this->hasManyThrough(
+            Profile::class,
+            Like::class,
+            'status_id',
+            'id',
+            'id',
+            'profile_id'
+        );
+    }
+
     public function comments()
     {
         return $this->hasMany(self::class, 'in_reply_to_id');
@@ -138,6 +181,18 @@ class Status extends Model
         return self::whereProfileId($profile->id)->whereReblogOfId($this->id)->count();
     }
 
+    public function sharedBy()
+    {
+        return $this->hasManyThrough(
+            Profile::class,
+            Status::class,
+            'reblog_of_id',
+            'id',
+            'id',
+            'profile_id'
+        );
+    }
+
     public function parent()
     {
         $parent = $this->in_reply_to_id ?? $this->reblog_of_id;

+ 24 - 22
app/Transformer/Api/StatusTransformer.php

@@ -17,29 +17,31 @@ class StatusTransformer extends Fractal\TransformerAbstract
     public function transform(Status $status)
     {
         return [
-            'id'                     => $status->id,
-            'uri'                    => $status->url(),
-            'url'                    => $status->url(),
-            'in_reply_to_id'         => $status->in_reply_to_id,
-            'in_reply_to_account_id' => $status->in_reply_to_profile_id,
+            'id'                        => $status->id,
+            'uri'                       => $status->url(),
+            'url'                       => $status->url(),
+            'in_reply_to_id'            => $status->in_reply_to_id,
+            'in_reply_to_account_id'    => $status->in_reply_to_profile_id,
+            'reblog'                    => $status->reblog_of_id || $status->in_reply_to_id ? $this->transform($status->parent()) : null,
+            'content'                   => "$status->rendered",
+            'created_at'                => $status->created_at->format('c'),
+            'emojis'                    => [],
+            'reblogs_count'             => $status->shares()->count(),
+            'favourites_count'          => $status->likes()->count(),
+            'reblogged'                 => $status->shared(),
+            'favourited'                => $status->liked(),
+            'muted'                     => null,
+            'sensitive'                 => (bool) $status->is_nsfw,
+            'spoiler_text'              => $status->cw_summary,
+            'visibility'                => $status->visibility,
+            'application'               => [
+                'name'      => 'web',
+                'website'   => null
+             ],
+            'language'                  => null,
+            'pinned'                    => null,
 
-            // TODO: fixme
-            'reblog' => null,
-
-            'content'          => "$status->rendered",
-            'created_at'       => $status->created_at->format('c'),
-            'emojis'           => [],
-            'reblogs_count'    => $status->shares()->count(),
-            'favourites_count' => $status->likes()->count(),
-            'reblogged'        => $status->shared(),
-            'favourited'       => $status->liked(),
-            'muted'            => null,
-            'sensitive'        => (bool) $status->is_nsfw,
-            'spoiler_text'     => '',
-            'visibility'       => $status->visibility,
-            'application'      => null,
-            'language'         => null,
-            'pinned'           => null,
+            'pf_type'          => $status->type,
         ];
     }
 

+ 1 - 1
config/pixelfed.php

@@ -23,7 +23,7 @@ return [
     | This value is the version of your PixelFed instance.
     |
     */
-    'version' => '0.3.0',
+    'version' => '0.4.0',
 
     /*
     |--------------------------------------------------------------------------

+ 10 - 0
package-lock.json

@@ -11274,6 +11274,11 @@
             "integrity": "sha512-2j/t+wIbyVMP5NvctQoSUvLkYKoWAAk2QlQiilrM2a6/ulzFgdcLUJfTvs4XQ/3eZhHiBmmEojbjmM4AzZj8JA==",
             "dev": true
         },
+        "vue-infinite-loading": {
+            "version": "2.4.3",
+            "resolved": "https://registry.npmjs.org/vue-infinite-loading/-/vue-infinite-loading-2.4.3.tgz",
+            "integrity": "sha512-CKITl7I1cb3X4zIHbVSyrupPTs9XxZGVV/N+P5lSxSrGW+D92gq6zuTy/XnvJOwMRkjJuiotJAQrgv+gOwSx3g=="
+        },
         "vue-loader": {
             "version": "13.7.3",
             "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-13.7.3.tgz",
@@ -11336,6 +11341,11 @@
                 }
             }
         },
+        "vue-loading-overlay": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/vue-loading-overlay/-/vue-loading-overlay-3.1.0.tgz",
+            "integrity": "sha512-EJOaqxfkSwt6LRoKYnWWPch6fLRRzHWFxLBnRHjXHIK/fP0MSmbBLh9ZRpxarXJeDBiyykQevDXa7h7809JaAA=="
+        },
         "vue-style-loader": {
             "version": "3.1.2",
             "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-3.1.2.tgz",

+ 3 - 1
package.json

@@ -29,6 +29,8 @@
         "readmore-js": "^2.2.1",
         "socket.io-client": "^2.1.1",
         "sweetalert": "^2.1.0",
-        "twitter-text": "^2.0.5"
+        "twitter-text": "^2.0.5",
+        "vue-infinite-loading": "^2.4.3",
+        "vue-loading-overlay": "^3.1.0"
     }
 }

BIN
public/css/app.css


BIN
public/js/components.js


BIN
public/mix-manifest.json


+ 5 - 0
resources/assets/js/components.js

@@ -1,6 +1,11 @@
 window.Vue = require('vue');
 import BootstrapVue from 'bootstrap-vue'
+import InfiniteLoading from 'vue-infinite-loading';
+import Loading from 'vue-loading-overlay';
+
 Vue.use(BootstrapVue);
+Vue.use(InfiniteLoading);
+Vue.use(Loading);
 
 pixelfed.readmore = () => {
   $('.read-more').each(function(k,v) {

+ 153 - 11
resources/assets/js/components/PostComponent.vue

@@ -1,8 +1,12 @@
 <style>
-
+#l-modal .modal-body,
+#s-modal .modal-body {
+  max-height: 70vh;
+  overflow-y: scroll;
+}
 </style>
 <template>
-<div class="postComponent">
+<div class="postComponent d-none">
   <div class="container px-0 mt-md-4">
     <div class="card card-md-rounded-0 status-container orientation-unknown">
       <div class="row mx-0">
@@ -91,7 +95,7 @@
               <div class="status-comment">
                 <p class="mb-1 read-more" style="overflow: hidden;">
                   <span class="font-weight-bold pr-1">{{statusUsername}}</span>
-                  <span class="comment-text" :id="status.id + '-status-readmore'"></span>
+                  <span class="comment-text" :id="status.id + '-status-readmore'" v-html="status.content"></span>
                 </p>
                 <post-comments :user="this.user" :post-id="statusId" :post-username="statusUsername"></post-comments>
               </div>
@@ -124,8 +128,13 @@
                   </form>
                 </span>
               </div>
-              <div class="likes font-weight-bold mb-0">
-                <span class="like-count">{{status.favourites_count || 0}}</span> likes
+              <div class="reaction-counts font-weight-bold mb-0">
+                <span style="cursor:pointer;" v-on:click="likesModal">
+                  <span class="like-count">{{status.favourites_count || 0}}</span> likes
+                </span>
+                <span class="float-right" style="cursor:pointer;" v-on:click="sharesModal">
+                  <span class="share-count pl-4">{{status.reblogs_count || 0}}</span> shares
+                </span>
               </div>
               <div class="timestamp">
                 <a v-bind:href="statusUrl" class="small text-muted">
@@ -150,6 +159,69 @@
       </div>
     </div>
   </div>
+  
+  <b-modal ref="likesModal" 
+    id="l-modal"
+    hide-footer 
+    centered 
+    title="Likes"
+    body-class="list-group-flush p-0">
+    <div class="list-group">
+      <div class="list-group-item border-0" v-for="user in likes">
+        <div class="media">
+          <a :href="user.url">
+            <img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '\'s avatar'" width="30px">
+          </a>
+          <div class="media-body">
+            <p class="mb-0" style="font-size: 14px">
+              <a :href="user.url" class="font-weight-bold text-dark">
+                {{user.username}}
+              </a>
+            </p>
+            <p class="text-muted mb-0" style="font-size: 14px">
+                {{user.display_name}}
+              </a>
+            </p>
+          </div>
+        </div>
+      </div>
+      <infinite-loading @infinite="infiniteLikesHandler" spinner="spiral">
+        <div slot="no-more"></div>
+        <div slot="no-results"></div>
+      </infinite-loading>
+    </div>
+  </b-modal>
+  <b-modal ref="sharesModal" 
+    id="s-modal"
+    hide-footer 
+    centered 
+    title="Shares"
+    body-class="list-group-flush p-0">
+    <div class="list-group">
+      <div class="list-group-item border-0" v-for="user in shares">
+        <div class="media">
+          <a :href="user.url">
+            <img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '\'s avatar'" width="30px">
+          </a>
+          <div class="media-body">
+            <p class="mb-0" style="font-size: 14px">
+              <a :href="user.url" class="font-weight-bold text-dark">
+                {{user.username}}
+              </a>
+            </p>
+            <p class="text-muted mb-0" style="font-size: 14px">
+                {{user.display_name}}
+              </a>
+            </p>
+          </div>
+        </div>
+      </div>
+      <infinite-loading @infinite="infiniteSharesHandler" spinner="spiral">
+        <div slot="no-more"></div>
+        <div slot="no-results"></div>
+      </infinite-loading>
+    </div>
+  </b-modal>
 </div>
 </template>
 
@@ -272,9 +344,14 @@ export default {
             status: {},
             media: {},
             user: {},
-            reactions: {}
+            reactions: {},
+            likes: {},
+            likesPage: 1,
+            shares: {},
+            sharesPage: 1,
           }
     },
+
     mounted() {
       let token = $('meta[name="csrf-token"]').attr('content');
       $('input[name="_token"]').each(function(k, v) {
@@ -282,11 +359,12 @@ export default {
           el.val(token);
       });
       this.fetchData();
-      //pixelfed.hydrateLikes();
       this.authCheck();
     },
+
     updated() {
       $('.carousel').carousel();
+
       if(this.reactions) {
         if(this.reactions.bookmarked == true) {
           $('.far.fa-bookmark').removeClass('far').addClass('fas text-warning');
@@ -298,6 +376,11 @@ export default {
           $('.far.fa-heart ').removeClass('far text-dark').addClass('fas text-danger');
         }
       }
+
+      if(this.status) {
+        let title = this.status.account.username + ' posted a photo: ' + this.status.favourites_count + ' likes';
+        $('head title').text(title);
+      }
     },
     methods: {
       authCheck() {
@@ -314,24 +397,36 @@ export default {
           $('.post-actions').removeClass('d-none');
         }
       },
+
       reportUrl() {
         return '/i/report?type=post&id=' + this.status.id;
       },
+
       timestampFormat() {
           let ts = new Date(this.status.created_at);
           return ts.toDateString() + ' ' + ts.toLocaleTimeString();
       },
+
       fetchData() {
-          let url = '/api/v2/profile/'+this.statusUsername+'/status/'+this.statusId;
-          axios.get(url)
+          let loader = this.$loading.show({
+            'opacity': 0,
+            'background-color': '#f5f8fa'
+          });
+          axios.get('/api/v2/profile/'+this.statusUsername+'/status/'+this.statusId)
             .then(response => {
                 let self = this;
                 self.status = response.data.status;
                 self.user = response.data.user;
                 self.media = self.status.media_attachments;
                 self.reactions = response.data.reactions;
+                self.likes = response.data.likes;
+                self.shares = response.data.shares;
+                self.likesPage = 2;
+                self.sharesPage = 2;
                 this.buildPresenter();
                 this.showMuteBlock();
+                loader.hide();
+                $('.postComponent').removeClass('d-none');
             }).catch(error => {
               if(!error.response) {
                 $('.postPresenterLoader .lds-ring').attr('style','width:100%').addClass('pt-4 font-weight-bold text-muted').text('An error occured, cannot fetch media. Please try again later.');
@@ -351,9 +446,58 @@ export default {
               }
             });
       },
+
       commentFocus() {
         $('.comment-form input[name="comment"]').focus();
       },
+
+      likesModal() {
+        if(this.status.favourites_count == 0 || $('body').hasClass('loggedIn') == false) {
+          return;
+        }
+        this.$refs.likesModal.show();
+      },
+
+      sharesModal() {
+        if(this.status.reblogs_count == 0 || $('body').hasClass('loggedIn') == false) {
+          return;
+        }
+        this.$refs.sharesModal.show();
+      },
+
+      infiniteLikesHandler($state) {
+        let api = '/api/v2/likes/profile/'+this.statusUsername+'/status/'+this.statusId;
+        axios.get(api, {
+          params: {
+            page: this.likesPage,
+          },
+        }).then(({ data }) => {
+          if (data.data.length) {
+            this.likesPage += 1;
+            this.likes.push(...data.data);
+            $state.loaded();
+          } else {
+            $state.complete();
+          }
+        });
+      },
+
+      infiniteSharesHandler($state) {
+        axios.get('/api/v2/shares/profile/'+this.statusUsername+'/status/'+this.statusId, {
+          params: {
+            page: this.sharesPage,
+          },
+        }).then(({ data }) => {
+          if (data.data.length) {
+            this.sharesPage += 1;
+            this.shares.push(...data.data);
+            $state.loaded();
+          } else {
+            $state.complete();
+          }
+        });
+      },
+
       buildPresenter() {
         let container = $('.postPresenterContainer');
         let status = this.status;
@@ -364,8 +508,6 @@ export default {
             el.val(status.account.id);
         });
 
-        $('.status-comment .comment-text').html(status.content);
-
         if(container.children().length != 0) {
           return;
         }

+ 2 - 0
resources/assets/sass/app.scss

@@ -22,3 +22,5 @@
 @import '~bootstrap-vue/dist/bootstrap-vue.css';
 
 @import '~plyr/dist/plyr.css';
+
+@import '~vue-loading-overlay/dist/vue-loading.css';

+ 2 - 0
routes/web.php

@@ -50,6 +50,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
             Route::get('discover/posts', 'InternalApiController@discoverPosts');
             Route::get('profile/{username}/status/{postid}', 'PublicApiController@status');
             Route::get('comments/{username}/status/{postId}', 'PublicApiController@statusComments');
+            Route::get('likes/profile/{username}/status/{id}', 'PublicApiController@statusLikes');
+            Route::get('shares/profile/{username}/status/{id}', 'PublicApiController@statusShares');
         });
         Route::group(['prefix' => 'local'], function () {
             Route::get('i/follow-suggestions', 'ApiController@followSuggestions');