diff --git a/app/Http/Controllers/Api/BaseApiController.php b/app/Http/Controllers/Api/BaseApiController.php index 61c8b9e8b..ad717dfe6 100644 --- a/app/Http/Controllers/Api/BaseApiController.php +++ b/app/Http/Controllers/Api/BaseApiController.php @@ -8,6 +8,7 @@ use App\Http\Controllers\{ AvatarController }; use Auth, Cache, URL; +use Carbon\Carbon; use App\{ Avatar, Notification, @@ -47,7 +48,22 @@ class BaseApiController extends Controller $resource = new Fractal\Resource\Item($notification, new NotificationTransformer()); $res = $this->fractal->createData($resource)->toArray(); - return response()->json($res, 200, [], JSON_PRETTY_PRINT); + return response()->json($res); + } + + public function notifications(Request $request) + { + $pid = Auth::user()->profile->id; + $timeago = Carbon::now()->subMonths(6); + $notifications = Notification::with('actor') + ->whereProfileId($pid) + ->whereDate('created_at', '>', $timeago) + ->orderBy('created_at','desc') + ->paginate(10); + $resource = new Fractal\Resource\Collection($notifications, new NotificationTransformer()); + $res = $this->fractal->createData($resource)->toArray(); + + return response()->json($res); } public function accounts(Request $request, $id) @@ -56,7 +72,7 @@ class BaseApiController extends Controller $resource = new Fractal\Resource\Item($profile, new AccountTransformer()); $res = $this->fractal->createData($resource)->toArray(); - return response()->json($res, 200, [], JSON_PRETTY_PRINT); + return response()->json($res); } public function accountFollowers(Request $request, $id) @@ -66,7 +82,7 @@ class BaseApiController extends Controller $resource = new Fractal\Resource\Collection($followers, new AccountTransformer()); $res = $this->fractal->createData($resource)->toArray(); - return response()->json($res, 200, [], JSON_PRETTY_PRINT); + return response()->json($res); } public function accountFollowing(Request $request, $id) @@ -76,7 +92,7 @@ class BaseApiController extends Controller $resource = new Fractal\Resource\Collection($following, new AccountTransformer()); $res = $this->fractal->createData($resource)->toArray(); - return response()->json($res, 200, [], JSON_PRETTY_PRINT); + return response()->json($res); } public function accountStatuses(Request $request, $id) @@ -92,7 +108,7 @@ class BaseApiController extends Controller $resource = new Fractal\Resource\Collection($statuses, new StatusTransformer()); $res = $this->fractal->createData($resource)->toArray(); - return response()->json($res, 200, [], JSON_PRETTY_PRINT); + return response()->json($res); } public function followSuggestions(Request $request) @@ -140,13 +156,13 @@ class BaseApiController extends Controller ]); } - public function showTempMedia(Request $request, $profileId, $mediaId) + public function showTempMedia(Request $request, int $profileId, $mediaId) { if (!$request->hasValidSignature()) { abort(401); } $profile = Auth::user()->profile; - if($profile->id !== (int) $profileId) { + if($profile->id !== $profileId) { abort(403); } $media = Media::whereProfileId($profile->id)->findOrFail($mediaId); @@ -240,4 +256,13 @@ class BaseApiController extends Controller return response()->json($res); } + + public function verifyCredentials(Request $request) + { + $profile = Auth::user()->profile; + $resource = new Fractal\Resource\Item($profile, new AccountTransformer()); + $res = $this->fractal->createData($resource)->toArray(); + + return response()->json($res); + } } diff --git a/app/Http/Controllers/InternalApiController.php b/app/Http/Controllers/InternalApiController.php index d21b8eaba..fb4cdcde9 100644 --- a/app/Http/Controllers/InternalApiController.php +++ b/app/Http/Controllers/InternalApiController.php @@ -94,37 +94,6 @@ class InternalApiController extends Controller return $status->url(); } - public function notifications(Request $request) - { - $this->validate($request, [ - 'page' => 'nullable|min:1|max:3', - ]); - - $profile = Auth::user()->profile; - $timeago = Carbon::now()->subMonths(6); - $notifications = Notification::with('actor') - ->whereProfileId($profile->id) - ->whereDate('created_at', '>', $timeago) - ->orderBy('id', 'desc') - ->simplePaginate(30); - $notifications = $notifications->map(function($k, $v) { - return [ - 'id' => $k->id, - 'action' => $k->action, - 'message' => $k->message, - 'rendered' => $k->rendered, - 'actor' => [ - 'avatar' => $k->actor->avatarUrl(), - 'username' => $k->actor->username, - 'url' => $k->actor->url(), - ], - 'url' => $k->item->url(), - 'read_at' => $k->read_at, - ]; - }); - return response()->json($notifications, 200, [], JSON_PRETTY_PRINT); - } - // deprecated public function discover(Request $request) { @@ -288,4 +257,19 @@ class InternalApiController extends Controller return; } + + public function statusReplies(Request $request, int $id) + { + $parent = Status::findOrFail($id); + + $children = Status::whereInReplyToId($parent->id) + ->orderBy('created_at', 'desc') + ->take(3) + ->get(); + + $resource = new Fractal\Resource\Collection($children, new StatusTransformer()); + $res = $this->fractal->createData($resource)->toArray(); + + return response()->json($res); + } } diff --git a/app/Http/Controllers/PublicApiController.php b/app/Http/Controllers/PublicApiController.php index 440effd86..637dde844 100644 --- a/app/Http/Controllers/PublicApiController.php +++ b/app/Http/Controllers/PublicApiController.php @@ -12,6 +12,7 @@ use App\{ Profile, StatusHashtag, Status, + UserFilter }; use Auth,Cache; use Carbon\Carbon; @@ -194,4 +195,127 @@ class PublicApiController extends Controller break; } } + + public function publicTimelineApi(Request $request) + { + if(!Auth::check()) { + return abort(403); + } + + $this->validate($request,[ + 'page' => 'nullable|integer|max:40', + 'min_id' => 'nullable|integer', + 'max_id' => 'nullable|integer', + 'limit' => 'nullable|integer|max:20' + ]); + + $page = $request->input('page'); + $min = $request->input('min_id'); + $max = $request->input('max_id'); + $limit = $request->input('limit') ?? 10; + + // TODO: Use redis for timelines + // $timeline = Timeline::build()->local(); + $pid = Auth::user()->profile->id; + + $private = Profile::whereIsPrivate(true)->where('id', '!=', $pid)->pluck('id'); + $filters = UserFilter::whereUserId($pid) + ->whereFilterableType('App\Profile') + ->whereIn('filter_type', ['mute', 'block']) + ->pluck('filterable_id')->toArray(); + $filtered = array_merge($private->toArray(), $filters); + + if($min || $max) { + $dir = $min ? '>' : '<'; + $id = $min ?? $max; + $timeline = Status::whereHas('media') + ->where('id', $dir, $id) + ->whereNotIn('profile_id', $filtered) + ->whereNull('in_reply_to_id') + ->whereNull('reblog_of_id') + ->whereVisibility('public') + ->withCount(['comments', 'likes']) + ->orderBy('created_at', 'desc') + ->limit($limit) + ->get(); + } else { + $timeline = Status::whereHas('media') + ->whereNotIn('profile_id', $filtered) + ->whereNull('in_reply_to_id') + ->whereNull('reblog_of_id') + ->whereVisibility('public') + ->withCount(['comments', 'likes']) + ->orderBy('created_at', 'desc') + ->simplePaginate($limit); + } + + $fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer()); + $res = $this->fractal->createData($fractal)->toArray(); + return response()->json($res); + + } + + public function homeTimelineApi(Request $request) + { + if(!Auth::check()) { + return abort(403); + } + + $this->validate($request,[ + 'page' => 'nullable|integer|max:40', + 'min_id' => 'nullable|integer', + 'max_id' => 'nullable|integer', + 'limit' => 'nullable|integer|max:20' + ]); + + $page = $request->input('page'); + $min = $request->input('min_id'); + $max = $request->input('max_id'); + $limit = $request->input('limit') ?? 10; + + // TODO: Use redis for timelines + // $timeline = Timeline::build()->local(); + $pid = Auth::user()->profile->id; + + $following = Follower::whereProfileId($pid)->pluck('following_id'); + $following->push($pid)->toArray(); + + $private = Profile::whereIsPrivate(true)->where('id', '!=', $pid)->pluck('id'); + $filters = UserFilter::whereUserId($pid) + ->whereFilterableType('App\Profile') + ->whereIn('filter_type', ['mute', 'block']) + ->pluck('filterable_id')->toArray(); + $filtered = array_merge($private->toArray(), $filters); + + if($min || $max) { + $dir = $min ? '>' : '<'; + $id = $min ?? $max; + $timeline = Status::whereHas('media') + ->where('id', $dir, $id) + ->whereIn('profile_id', $following) + ->whereNotIn('profile_id', $filtered) + ->whereNull('in_reply_to_id') + ->whereNull('reblog_of_id') + ->whereVisibility('public') + ->withCount(['comments', 'likes']) + ->orderBy('created_at', 'desc') + ->limit($limit) + ->get(); + } else { + $timeline = Status::whereHas('media') + ->whereIn('profile_id', $following) + ->whereNotIn('profile_id', $filtered) + ->whereNull('in_reply_to_id') + ->whereNull('reblog_of_id') + ->whereVisibility('public') + ->withCount(['comments', 'likes']) + ->orderBy('created_at', 'desc') + ->simplePaginate($limit); + } + + $fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer()); + $res = $this->fractal->createData($fractal)->toArray(); + return response()->json($res); + + } } diff --git a/app/Http/Controllers/SiteController.php b/app/Http/Controllers/SiteController.php index d7ab96480..fa12c9392 100644 --- a/app/Http/Controllers/SiteController.php +++ b/app/Http/Controllers/SiteController.php @@ -31,28 +31,7 @@ class SiteController extends Controller public function homeTimeline() { - $pid = Auth::user()->profile->id; - // TODO: Use redis for timelines - - $following = Follower::whereProfileId($pid)->pluck('following_id'); - $following->push($pid)->toArray(); - - $filtered = UserFilter::whereUserId($pid) - ->whereFilterableType('App\Profile') - ->whereIn('filter_type', ['mute', 'block']) - ->pluck('filterable_id')->toArray(); - - $timeline = Status::whereIn('profile_id', $following) - ->whereNotIn('profile_id', $filtered) - ->whereHas('media') - ->whereVisibility('public') - ->orderBy('created_at', 'desc') - ->withCount(['comments', 'likes', 'shares']) - ->simplePaginate(20); - - $type = 'personal'; - - return view('timeline.template', compact('timeline', 'type')); + return view('timeline.home'); } public function changeLocale(Request $request, $locale) diff --git a/app/Http/Controllers/TimelineController.php b/app/Http/Controllers/TimelineController.php index 5ce51eb89..417692855 100644 --- a/app/Http/Controllers/TimelineController.php +++ b/app/Http/Controllers/TimelineController.php @@ -20,30 +20,6 @@ class TimelineController extends Controller public function local(Request $request) { - $this->validate($request,[ - 'page' => 'nullable|integer|max:20' - ]); - // TODO: Use redis for timelines - // $timeline = Timeline::build()->local(); - $pid = Auth::user()->profile->id; - - $private = Profile::whereIsPrivate(true)->where('id', '!=', $pid)->pluck('id'); - $filters = UserFilter::whereUserId($pid) - ->whereFilterableType('App\Profile') - ->whereIn('filter_type', ['mute', 'block']) - ->pluck('filterable_id')->toArray(); - $filtered = array_merge($private->toArray(), $filters); - - $timeline = Status::whereHas('media') - ->whereNotIn('profile_id', $filtered) - ->whereNull('in_reply_to_id') - ->whereNull('reblog_of_id') - ->whereVisibility('public') - ->withCount(['comments', 'likes']) - ->orderBy('created_at', 'desc') - ->simplePaginate(10); - $type = 'local'; - - return view('timeline.template', compact('timeline', 'type')); + return view('timeline.local'); } } diff --git a/app/Profile.php b/app/Profile.php index 5dc6599ee..4176a3101 100644 --- a/app/Profile.php +++ b/app/Profile.php @@ -156,6 +156,7 @@ class Profile extends Model public function statusCount() { return $this->statuses() + ->getQuery() ->whereHas('media') ->whereNull('in_reply_to_id') ->whereNull('reblog_of_id') diff --git a/app/Transformer/Api/MediaTransformer.php b/app/Transformer/Api/MediaTransformer.php index 4ac5c6be2..8ab38fc6f 100644 --- a/app/Transformer/Api/MediaTransformer.php +++ b/app/Transformer/Api/MediaTransformer.php @@ -23,6 +23,7 @@ class MediaTransformer extends Fractal\TransformerAbstract 'orientation' => $media->orientation, 'filter_name' => $media->filter_name, 'filter_class' => $media->filter_class, + 'mime' => $media->mime, ]; } } diff --git a/app/Transformer/Api/NotificationTransformer.php b/app/Transformer/Api/NotificationTransformer.php index d5afa1b60..16d537c97 100644 --- a/app/Transformer/Api/NotificationTransformer.php +++ b/app/Transformer/Api/NotificationTransformer.php @@ -45,6 +45,7 @@ class NotificationTransformer extends Fractal\TransformerAbstract 'mention' => 'mention', 'reblog' => 'share', 'like' => 'favourite', + 'comment' => 'comment', ]; return $verbs[$verb]; } diff --git a/config/pixelfed.php b/config/pixelfed.php index 131a1959c..ca1b4be2d 100644 --- a/config/pixelfed.php +++ b/config/pixelfed.php @@ -23,7 +23,7 @@ return [ | This value is the version of your PixelFed instance. | */ - 'version' => '0.4.3', + 'version' => '0.5.0', /* |-------------------------------------------------------------------------- diff --git a/package-lock.json b/package-lock.json index a77f1ee82..8545e1add 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2571,6 +2571,11 @@ "assert-plus": "^1.0.0" } }, + "date-fns": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz", + "integrity": "sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==" + }, "date-now": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", @@ -11372,6 +11377,14 @@ "integrity": "sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg==", "dev": true }, + "vue-timeago": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/vue-timeago/-/vue-timeago-5.0.0.tgz", + "integrity": "sha512-C+EqTlfHE9nO6FOQIS6q5trAZ0WIgNz/eydTvsanPRsLVV1xqNiZirTG71d9nl/LjfNETwaktnBlgP8adCc37A==", + "requires": { + "date-fns": "^1.29.0" + } + }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", diff --git a/package.json b/package.json index 7f21d6449..6c173a69e 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "filesize": "^3.6.1", "infinite-scroll": "^3.0.4", "laravel-echo": "^1.4.0", + "opencollective": "^1.0.3", "opencollective-postinstall": "^2.0.1", "plyr": "^3.4.7", "pusher-js": "^4.2.2", @@ -34,10 +35,10 @@ "twitter-text": "^2.0.5", "vue-infinite-loading": "^2.4.3", "vue-loading-overlay": "^3.1.0", - "opencollective": "^1.0.3" + "vue-timeago": "^5.0.0" }, "collective": { "type": "opencollective", "url": "https://opencollective.com/pixelfed-528" } -} \ No newline at end of file +} diff --git a/public/js/components.js b/public/js/components.js index 3998e2e55..28802d7b0 100644 Binary files a/public/js/components.js and b/public/js/components.js differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 6eaeb181a..6a8b80f4d 100644 Binary files a/public/mix-manifest.json and b/public/mix-manifest.json differ diff --git a/resources/assets/js/components.js b/resources/assets/js/components.js index edb50ceab..48ad4e922 100644 --- a/resources/assets/js/components.js +++ b/resources/assets/js/components.js @@ -2,10 +2,12 @@ window.Vue = require('vue'); import BootstrapVue from 'bootstrap-vue' import InfiniteLoading from 'vue-infinite-loading'; import Loading from 'vue-loading-overlay'; +import VueTimeago from 'vue-timeago' Vue.use(BootstrapVue); Vue.use(InfiniteLoading); Vue.use(Loading); +Vue.use(VueTimeago); pixelfed.readmore = () => { $('.read-more').each(function(k,v) { @@ -81,6 +83,11 @@ Vue.component( require('./components/PostComments.vue') ); +Vue.component( + 'timeline', + require('./components/Timeline.vue') +); + Vue.component( 'passport-clients', require('./components/passport/Clients.vue') diff --git a/resources/assets/js/components/PostComponent.vue b/resources/assets/js/components/PostComponent.vue index 41cade86f..7a507a678 100644 --- a/resources/assets/js/components/PostComponent.vue +++ b/resources/assets/js/components/PostComponent.vue @@ -4,12 +4,23 @@ max-height: 70vh; overflow-y: scroll; } + +.status-comments, +.reactions, +.col-md-4 { + background: #fff; +} + +.postPresenterContainer { + background: #000; + min-height: 600px; +}