mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-18 20:41:27 +00:00
Merge pull request #613 from pixelfed/frontend-ui-refactor
Frontend ui refactor
This commit is contained in:
commit
f7c4b45098
16 changed files with 389 additions and 47 deletions
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,16 +111,25 @@ 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);
|
||||
|
||||
// TODO: Send to subscribers
|
||||
|
@ -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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ return [
|
|||
| This value is the version of your PixelFed instance.
|
||||
|
|
||||
*/
|
||||
'version' => '0.3.0',
|
||||
'version' => '0.4.0',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
10
package-lock.json
generated
10
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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
vendored
BIN
public/css/app.css
vendored
Binary file not shown.
BIN
public/js/components.js
vendored
BIN
public/js/components.js
vendored
Binary file not shown.
Binary file not shown.
5
resources/assets/js/components.js
vendored
5
resources/assets/js/components.js
vendored
|
@ -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) {
|
||||
|
|
|
@ -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
resources/assets/sass/app.scss
vendored
2
resources/assets/sass/app.scss
vendored
|
@ -22,3 +22,5 @@
|
|||
@import '~bootstrap-vue/dist/bootstrap-vue.css';
|
||||
|
||||
@import '~plyr/dist/plyr.css';
|
||||
|
||||
@import '~vue-loading-overlay/dist/vue-loading.css';
|
||||
|
|
|
@ -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');
|
||||
|
|
Loading…
Reference in a new issue