Add Network Timeline

This commit is contained in:
Daniel Supernault 2021-04-06 21:17:42 -06:00
parent b9221ffc27
commit af7face4da
No known key found for this signature in database
GPG key ID: 0DEF1C662C9033F7
10 changed files with 2897 additions and 557 deletions

View file

@ -247,7 +247,7 @@ class AccountController extends Controller
switch ($type) { switch ($type) {
case 'user': case 'user':
$profile = Profile::findOrFail($item); $profile = Profile::findOrFail($item);
if ($profile->id == $user->id || $profile->user->is_admin == true) { if ($profile->id == $user->id || ($profile->user && $profile->user->is_admin == true)) {
return abort(403); return abort(403);
} }
$class = get_class($profile); $class = get_class($profile);

View file

@ -351,7 +351,6 @@ class PublicApiController extends Controller
$fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer()); $fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
$res = $this->fractal->createData($fractal)->toArray(); $res = $this->fractal->createData($fractal)->toArray();
return response()->json($res); return response()->json($res);
} }
public function homeTimelineApi(Request $request) public function homeTimelineApi(Request $request)
@ -470,12 +469,96 @@ class PublicApiController extends Controller
$fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer()); $fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
$res = $this->fractal->createData($fractal)->toArray(); $res = $this->fractal->createData($fractal)->toArray();
return response()->json($res); return response()->json($res);
} }
public function networkTimelineApi(Request $request) public function networkTimelineApi(Request $request)
{ {
return response()->json([]); abort_if(!Auth::check(), 403);
abort_if(config('federation.network_timeline') == false, 404);
$this->validate($request,[
'page' => 'nullable|integer|max:40',
'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
'limit' => 'nullable|integer|max:30'
]);
$page = $request->input('page');
$min = $request->input('min_id');
$max = $request->input('max_id');
$limit = $request->input('limit') ?? 3;
$user = $request->user();
$key = 'user:last_active_at:id:'.$user->id;
$ttl = now()->addMinutes(5);
Cache::remember($key, $ttl, function() use($user) {
$user->last_active_at = now();
$user->save();
return;
});
if($min || $max) {
$dir = $min ? '>' : '<';
$id = $min ?? $max;
$timeline = Status::select(
'id',
'uri',
'caption',
'rendered',
'profile_id',
'type',
'in_reply_to_id',
'reblog_of_id',
'is_nsfw',
'scope',
'local',
'reply_count',
'comments_disabled',
'place_id',
'likes_count',
'reblogs_count',
'created_at',
'updated_at'
)->where('id', $dir, $id)
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNotNull('uri')
->whereScope('public')
// ->where('created_at', '>', now()->subMonths(3))
->orderBy('created_at', 'desc')
->limit($limit)
->get();
} else {
$timeline = Status::select(
'id',
'uri',
'caption',
'rendered',
'profile_id',
'type',
'in_reply_to_id',
'reblog_of_id',
'is_nsfw',
'scope',
'local',
'reply_count',
'comments_disabled',
'created_at',
'place_id',
'likes_count',
'reblogs_count',
'updated_at'
)->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->with('profile', 'hashtags', 'mentions')
->whereNotNull('uri')
->whereScope('public')
->where('created_at', '>', now()->subMonths(3))
->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 relationships(Request $request) public function relationships(Request $request)

View file

@ -2,12 +2,6 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Auth, Cache;
use App\Follower;
use App\Profile;
use App\Status;
use App\User;
use App\UserFilter;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class TimelineController extends Controller class TimelineController extends Controller
@ -29,6 +23,7 @@ class TimelineController extends Controller
public function network(Request $request) public function network(Request $request)
{ {
abort_if(config('federation.network_timeline') == false, 404);
$this->validate($request, [ $this->validate($request, [
'layout' => 'nullable|string|in:grid,feed' 'layout' => 'nullable|string|in:grid,feed'
]); ]);

View file

@ -10,7 +10,6 @@ return [
| ActivityPub configuration | ActivityPub configuration
| |
*/ */
'activitypub' => [ 'activitypub' => [
'enabled' => env('ACTIVITY_PUB', false), 'enabled' => env('ACTIVITY_PUB', false),
'outbox' => env('AP_OUTBOX', true), 'outbox' => env('AP_OUTBOX', true),
@ -41,4 +40,6 @@ return [
'enabled' => env('WEBFINGER', true) 'enabled' => env('WEBFINGER', true)
], ],
'network_timeline' => env('PF_NETWORK_TIMELINE', false)
]; ];

File diff suppressed because it is too large Load diff

49
resources/assets/js/network-timeline.js vendored Normal file
View file

@ -0,0 +1,49 @@
Vue.component(
'notification-card',
require('./components/NotificationCard.vue').default
);
Vue.component(
'photo-presenter',
require('./components/presenter/PhotoPresenter.vue').default
);
Vue.component(
'video-presenter',
require('./components/presenter/VideoPresenter.vue').default
);
Vue.component(
'photo-album-presenter',
require('./components/presenter/PhotoAlbumPresenter.vue').default
);
Vue.component(
'video-album-presenter',
require('./components/presenter/VideoAlbumPresenter.vue').default
);
Vue.component(
'mixed-album-presenter',
require('./components/presenter/MixedAlbumPresenter.vue').default
);
Vue.component(
'post-menu',
require('./components/PostMenu.vue').default
);
Vue.component(
'network-timeline',
require('./components/NetworkTimeline.vue').default
);
Vue.component(
'announcements-card',
require('./components/AnnouncementsCard.vue').default
);
Vue.component(
'story-component',
require('./components/StoryTimelineComponent.vue').default
);

View file

@ -64,14 +64,30 @@
</a> </a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown"> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item font-weight-bold" href="{{route('discover')}}"> @if(config('federation.network_timeline'))
<span class="far fa-compass pr-2 text-lighter"></span> <a class="dropdown-item font-weight-bold" href="{{route('timeline.public')}}">
{{__('navmenu.discover')}} <span class="fas fa-stream pr-2 text-lighter"></span>
Public
</a>
<a class="dropdown-item font-weight-bold" href="{{route('timeline.network')}}">
<span class="fas fa-globe pr-2 text-lighter"></span>
Network
</a>
@else
<a class="dropdown-item font-weight-bold" href="/">
<span class="fas fa-home pr-2 text-lighter"></span>
Home
</a> </a>
<a class="dropdown-item font-weight-bold" href="{{route('timeline.public')}}"> <a class="dropdown-item font-weight-bold" href="{{route('timeline.public')}}">
<span class="fas fa-stream pr-2 text-lighter"></span> <span class="fas fa-stream pr-2 text-lighter"></span>
Public Public
</a> </a>
@endif
<div class="dropdown-divider"></div>
<a class="dropdown-item font-weight-bold" href="{{route('discover')}}">
<span class="far fa-compass pr-2 text-lighter"></span>
{{__('navmenu.discover')}}
</a>
<a class="dropdown-item font-weight-bold" href="/i/stories/new"> <a class="dropdown-item font-weight-bold" href="/i/stories/new">
<span class="fas fa-history text-lighter pr-2"></span> <span class="fas fa-history text-lighter pr-2"></span>
Stories Stories

View file

@ -7,31 +7,31 @@
</div> </div>
<hr> <hr>
<p class="lead">Timelines are chronological feeds of posts.</p> <p class="lead">Timelines are chronological feeds of posts.</p>
<p class="font-weight-bold h5 py-3">Pixelfed has 2 different timelines:</p> {{-- <p class="font-weight-bold h5 py-3">Pixelfed has 3 different timelines:</p> --}}
<ul> <ul class="list-unstyled">
<li class="lead"> <li class="lead mb-2">
<span class="font-weight-bold"><i class="fas fa-home text-muted mr-2"></i> Personal</span> <span class="font-weight-bold"><i class="fas fa-home mr-2"></i> Home</span>
<span class="px-2">&mdash;</span> <span class="px-2">&mdash;</span>
<span class="font-weight-light">Timeline with posts from accounts you follow</span> <span class="font-weight-light">Timeline with content from accounts you follow</span>
</li>
<li class="lead mb-2">
<span class="font-weight-bold"><i class="fas fa-stream mr-2"></i> Public</span>
<span class="px-2">&mdash;</span>
<span class="font-weight-light">Timeline with content from other users on this server</span>
</li> </li>
<li class="lead"> <li class="lead">
<span class="font-weight-bold"><i class="far fa-map text-muted mr-2"></i> Public</span> <span class="font-weight-bold"><i class="fas fa-globe mr-2"></i> Network</span>
<span class="px-2">&mdash;</span> <span class="px-2">&mdash;</span>
<span class="font-weight-light">Timeline with posts from other users on the same instance</span> <span class="font-weight-light">Timeline with unmoderated content from other servers</span>
</li> </li>
{{-- <li class="lead text-muted">
<span class="font-weight-bold"><i class="fas fa-globe text-muted mr-2"></i> Network</span>
<span class="px-2">&mdash;</span>
<span class="font-weight-light text-muted">Timeline with posts from local and remote accounts - coming soon!</span>
</li> --}}
</ul> </ul>
<div class="py-3"></div> <div class="py-3"></div>
<div class="card bg-primary border-primary" style="box-shadow: none !important;border: 3px solid #08d!important;"> <div class="card bg-primary border-primary" style="box-shadow: none !important;border: 3px solid #08d!important;">
<div class="card-header text-light font-weight-bold h4 p-4 bg-primary">Timeline Tips</div> <div class="card-header text-light font-weight-bold h4 p-4 bg-primary">Timeline Tips</div>
<div class="card-body bg-white p-3"> <div class="card-body bg-white p-3">
<ul class="pt-3"> <ul class="pt-3">
<li class="lead mb-4">You can mute or block accounts to prevent them from appearing in timelines.</li> <li class="lead mb-4">You can mute or block accounts to prevent them from appearing in home and public timelines.</li>
<li class="lead mb-4">You can create <span class="font-weight-bold">Unlisted</span> posts that don't appear in public timelines.</li> <li class="lead mb-4">You can create <span class="font-weight-bold">Unlisted</span> posts that don't appear in public timelines.</li>
</ul> </ul>

View file

@ -167,6 +167,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('notifications', 'ApiController@notifications'); Route::get('notifications', 'ApiController@notifications');
Route::get('timelines/public', 'PublicApiController@publicTimelineApi'); Route::get('timelines/public', 'PublicApiController@publicTimelineApi');
Route::get('timelines/home', 'PublicApiController@homeTimelineApi'); Route::get('timelines/home', 'PublicApiController@homeTimelineApi');
Route::get('timelines/network', 'PublicApiController@networkTimelineApi');
Route::get('newsroom/timeline', 'NewsroomController@timelineApi'); Route::get('newsroom/timeline', 'NewsroomController@timelineApi');
Route::post('newsroom/markasread', 'NewsroomController@markAsRead'); Route::post('newsroom/markasread', 'NewsroomController@markAsRead');
Route::get('favourites', 'Api\BaseApiController@accountLikes'); Route::get('favourites', 'Api\BaseApiController@accountLikes');
@ -468,6 +469,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::group(['prefix' => 'timeline'], function () { Route::group(['prefix' => 'timeline'], function () {
Route::redirect('/', '/'); Route::redirect('/', '/');
Route::get('public', 'TimelineController@local')->name('timeline.public'); Route::get('public', 'TimelineController@local')->name('timeline.public');
Route::get('network', 'TimelineController@network')->name('timeline.network');
}); });
Route::group(['prefix' => 'users'], function () { Route::group(['prefix' => 'users'], function () {

21
webpack.mix.js vendored
View file

@ -1,17 +1,9 @@
let mix = require('laravel-mix'); let mix = require('laravel-mix');
mix.sass('resources/assets/sass/app.scss', 'public/css', { mix.sass('resources/assets/sass/app.scss', 'public/css')
implementation: require('node-sass') .sass('resources/assets/sass/appdark.scss', 'public/css')
}) .sass('resources/assets/sass/landing.scss', 'public/css')
.sass('resources/assets/sass/appdark.scss', 'public/css', { .sass('resources/assets/sass/quill.scss', 'public/css').version();
implementation: require('node-sass')
})
.sass('resources/assets/sass/landing.scss', 'public/css', {
implementation: require('node-sass')
})
.sass('resources/assets/sass/quill.scss', 'public/css', {
implementation: require('node-sass')
}).version();
mix.js('resources/assets/js/app.js', 'public/js') mix.js('resources/assets/js/app.js', 'public/js')
.js('resources/assets/js/activity.js', 'public/js') .js('resources/assets/js/activity.js', 'public/js')
@ -41,6 +33,11 @@ mix.js('resources/assets/js/app.js', 'public/js')
.js('resources/assets/js/rempro.js', 'public/js') .js('resources/assets/js/rempro.js', 'public/js')
.js('resources/assets/js/rempos.js', 'public/js') .js('resources/assets/js/rempos.js', 'public/js')
//.js('resources/assets/js/timeline_next.js', 'public/js') //.js('resources/assets/js/timeline_next.js', 'public/js')
// .js('resources/assets/js/memoryprofile.js', 'public/js')
// .js('resources/assets/js/my2020.js', 'public/js')
.js('resources/assets/js/network-timeline.js', 'public/js')
// .js('resources/assets/js/drive.js', 'public/js')
// .js('resources/assets/js/register.js', 'public/js')
.extract([ .extract([
'lodash', 'lodash',