Merge pull request #1964 from pixelfed/staging

Update Timeline.vue, fix #1963 and update profile card layout
This commit is contained in:
daniel 2020-01-27 19:26:36 -07:00 committed by GitHub
commit f131105244
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 481 additions and 405 deletions

View File

@ -17,6 +17,7 @@
- Updated compose view, add deprecation notice for v3 ([57e155b9](https://github.com/pixelfed/pixelfed/commit/57e155b9)) - Updated compose view, add deprecation notice for v3 ([57e155b9](https://github.com/pixelfed/pixelfed/commit/57e155b9))
- Updated StoryController, orientate story media and strip exif ([07a13fcf](https://github.com/pixelfed/pixelfed/commit/07a13fcf)) - Updated StoryController, orientate story media and strip exif ([07a13fcf](https://github.com/pixelfed/pixelfed/commit/07a13fcf))
- Updated admin reports, fixed 404 bug ([dbd5c4cf](https://github.com/pixelfed/pixelfed/commit/dbd5c4cf)) - Updated admin reports, fixed 404 bug ([dbd5c4cf](https://github.com/pixelfed/pixelfed/commit/dbd5c4cf))
- Updated AdminController, abstracted dashboard stats to AdminStatsService ([41abe9d2](https://github.com/pixelfed/pixelfed/commit/41abe9d2))
### Changed ### Changed

View File

@ -3,405 +3,336 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\{ use App\{
Contact, Contact,
FailedJob, Hashtag,
Hashtag, Newsroom,
Instance, OauthClient,
Media, Profile,
Like, Report,
Newsroom, Status,
OauthClient, User
Profile,
Report,
Status,
User
}; };
use DB, Cache; use DB, Cache;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Admin\{ use App\Http\Controllers\Admin\{
AdminDiscoverController, AdminDiscoverController,
AdminInstanceController, AdminInstanceController,
AdminReportController, AdminReportController,
AdminMediaController, AdminMediaController,
AdminSettingsController, AdminSettingsController,
AdminSupportController AdminSupportController
}; };
use App\Util\Lexer\PrettyNumber;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use App\Services\AdminStatsService;
class AdminController extends Controller class AdminController extends Controller
{ {
use AdminReportController, use AdminReportController,
AdminDiscoverController, AdminDiscoverController,
AdminMediaController, AdminMediaController,
AdminSettingsController, AdminSettingsController,
AdminInstanceController; AdminInstanceController;
public function __construct() public function __construct()
{ {
$this->middleware('admin'); $this->middleware('admin');
$this->middleware('twofactor'); $this->middleware('twofactor');
} }
public function home() public function home()
{ {
$day = config('database.default') == 'pgsql' ? 'DATE_PART(\'day\',' : 'day('; $data = AdminStatsService::get();
return view('admin.home', compact('data'));
}
$recent = Cache::remember('admin:dashboard:home:data:15min', now()->addMinutes(15), function() use ($day) { public function users(Request $request)
return [ {
'contact' => [ $col = $request->query('col') ?? 'id';
'count' => PrettyNumber::convert(Contact::whereNull('read_at')->count()), $dir = $request->query('dir') ?? 'desc';
'graph' => Contact::selectRaw('count(*) as count, '.$day.'created_at) as d')->groupBy('d')->whereNull('read_at')->whereBetween('created_at',[now()->subDays(14), now()])->orderBy('d')->pluck('count') $users = User::select('id', 'username', 'status')
], ->withCount('statuses')
'failedjobs' => [ ->orderBy($col, $dir)
'count' => PrettyNumber::convert(FailedJob::where('failed_at', '>=', \Carbon\Carbon::now()->subDay())->count()), ->simplePaginate(10);
'graph' => FailedJob::selectRaw('count(*) as count, '.$day.'failed_at) as d')->groupBy('d')->whereBetween('failed_at',[now()->subDays(14), now()])->orderBy('d')->pluck('count')
],
'reports' => [
'count' => PrettyNumber::convert(Report::whereNull('admin_seen')->count()),
'graph' => Report::selectRaw('count(*) as count, '.$day.'created_at) as d')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('d')->orderBy('d')->pluck('count')
],
'statuses' => [
'count' => PrettyNumber::convert(Status::whereNull('in_reply_to_id')->whereNull('reblog_of_id')->count()),
'graph' => Status::selectRaw('count(*) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
'replies' => [
'count' => PrettyNumber::convert(Status::whereNotNull('in_reply_to_id')->count()),
'graph' => Status::whereNotNull('in_reply_to_id')->selectRaw('count(*) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
'shares' => [
'count' => PrettyNumber::convert(Status::whereNotNull('reblog_of_id')->count()),
'graph' => Status::whereNotNull('reblog_of_id')->selectRaw('count(*) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
'likes' => [
'count' => PrettyNumber::convert(Like::count()),
'graph' => Like::selectRaw('count(*) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
'profiles' => [
'count' => PrettyNumber::convert(Profile::count()),
'graph' => Profile::selectRaw('count(*) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
];
});
$longer = Cache::remember('admin:dashboard:home:data:24hr', now()->addHours(24), function() use ($day) { return view('admin.users.home', compact('users'));
return [ }
'users' => [
'count' => PrettyNumber::convert(User::count()),
'graph' => User::selectRaw('count(*) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
'instances' => [
'count' => PrettyNumber::convert(Instance::count()),
'graph' => Instance::selectRaw('count(*) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(28), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
'media' => [
'count' => PrettyNumber::convert(Media::count()),
'graph' => Media::selectRaw('count(*) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
'storage' => [
'count' => Media::sum('size'),
'graph' => Media::selectRaw('sum(size) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
]
];
});
$data = array_merge($recent, $longer); public function editUser(Request $request, $id)
return view('admin.home', compact('data')); {
} $user = User::findOrFail($id);
$profile = $user->profile;
return view('admin.users.edit', compact('user', 'profile'));
}
public function users(Request $request) public function statuses(Request $request)
{ {
$col = $request->query('col') ?? 'id'; $statuses = Status::orderBy('id', 'desc')->simplePaginate(10);
$dir = $request->query('dir') ?? 'desc';
$users = User::select('id', 'username', 'status')->withCount('statuses')->orderBy($col, $dir)->simplePaginate(10);
return view('admin.users.home', compact('users')); return view('admin.statuses.home', compact('statuses'));
} }
public function editUser(Request $request, $id) public function showStatus(Request $request, $id)
{ {
$user = User::findOrFail($id); $status = Status::findOrFail($id);
$profile = $user->profile;
return view('admin.users.edit', compact('user', 'profile'));
}
public function statuses(Request $request) return view('admin.statuses.show', compact('status'));
{ }
$statuses = Status::orderBy('id', 'desc')->simplePaginate(10);
return view('admin.statuses.home', compact('statuses')); public function reports(Request $request)
} {
$this->validate($request, [
'filter' => 'nullable|string|in:all,open,closed'
]);
$filter = $request->input('filter');
$reports = Report::orderBy('created_at','desc')
->when($filter, function($q, $filter) {
return $filter == 'open' ?
$q->whereNull('admin_seen') :
$q->whereNotNull('admin_seen');
})
->paginate(4);
return view('admin.reports.home', compact('reports'));
}
public function showStatus(Request $request, $id) public function showReport(Request $request, $id)
{ {
$status = Status::findOrFail($id); $report = Report::findOrFail($id);
return view('admin.reports.show', compact('report'));
}
return view('admin.statuses.show', compact('status')); public function profiles(Request $request)
} {
$this->validate($request, [
'search' => 'nullable|string|max:250',
'filter' => [
'nullable',
'string',
Rule::in(['all', 'local', 'remote'])
],
'limit' => 'nullable|integer|min:1|max:50'
]);
$search = $request->input('search');
$filter = $request->input('filter');
$limit = 12;
if($search) {
$profiles = Profile::select('id','username')
->where('username', 'like', "%$search%")
->orderBy('id','desc')
->simplePaginate($limit);
} else if($filter) {
$profiles = Profile::select('id','username')->withCount(['likes','statuses','followers'])->orderBy($filter, $order)->simplePaginate($limit);
} else {
$profiles = Profile::select('id','username')->orderBy('id','desc')->simplePaginate($limit);
}
public function reports(Request $request) return view('admin.profiles.home', compact('profiles'));
{ }
$this->validate($request, [
'filter' => 'nullable|string|in:all,open,closed'
]);
$filter = $request->input('filter');
$reports = Report::orderBy('created_at','desc')
->when($filter, function($q, $filter) {
return $filter == 'open' ?
$q->whereNull('admin_seen') :
$q->whereNotNull('admin_seen');
})
->paginate(4);
return view('admin.reports.home', compact('reports'));
}
public function showReport(Request $request, $id) public function profileShow(Request $request, $id)
{ {
$report = Report::findOrFail($id); $profile = Profile::findOrFail($id);
return view('admin.reports.show', compact('report')); $user = $profile->user;
} return view('admin.profiles.edit', compact('profile', 'user'));
}
public function profiles(Request $request) public function appsHome(Request $request)
{ {
$this->validate($request, [ $filter = $request->input('filter');
'search' => 'nullable|string|max:250', if(in_array($filter, ['revoked'])) {
'filter' => [ $apps = OauthClient::with('user')
'nullable', ->whereNotNull('user_id')
'string', ->whereRevoked(true)
Rule::in(['id','username','statuses_count','followers_count','likes_count']) ->orderByDesc('id')
], ->paginate(10);
'order' => [ } else {
'nullable', $apps = OauthClient::with('user')
'string', ->whereNotNull('user_id')
Rule::in(['asc','desc']) ->orderByDesc('id')
], ->paginate(10);
'layout' => [ }
'nullable', return view('admin.apps.home', compact('apps'));
'string', }
Rule::in(['card','list'])
],
'limit' => 'nullable|integer|min:1|max:50'
]);
$search = $request->input('search');
$filter = $request->input('filter');
$order = $request->input('order') ?? 'desc';
$limit = $request->input('limit') ?? 12;
if($search) {
$profiles = Profile::select('id','username')->where('username','like', "%$search%")->orderBy('id','desc')->simplePaginate($limit);
} else if($filter && $order) {
$profiles = Profile::select('id','username')->withCount(['likes','statuses','followers'])->orderBy($filter, $order)->simplePaginate($limit);
} else {
$profiles = Profile::select('id','username')->orderBy('id','desc')->simplePaginate($limit);
}
return view('admin.profiles.home', compact('profiles')); public function hashtagsHome(Request $request)
} {
$hashtags = Hashtag::orderByDesc('id')->paginate(10);
return view('admin.hashtags.home', compact('hashtags'));
}
public function profileShow(Request $request, $id) public function messagesHome(Request $request)
{ {
$profile = Profile::findOrFail($id); $messages = Contact::orderByDesc('id')->paginate(10);
$user = $profile->user; return view('admin.messages.home', compact('messages'));
return view('admin.profiles.edit', compact('profile', 'user')); }
}
public function appsHome(Request $request) public function messagesShow(Request $request, $id)
{ {
$filter = $request->input('filter'); $message = Contact::findOrFail($id);
if(in_array($filter, ['revoked'])) { return view('admin.messages.show', compact('message'));
$apps = OauthClient::with('user') }
->whereNotNull('user_id')
->whereRevoked(true)
->orderByDesc('id')
->paginate(10);
} else {
$apps = OauthClient::with('user')
->whereNotNull('user_id')
->orderByDesc('id')
->paginate(10);
}
return view('admin.apps.home', compact('apps'));
}
public function hashtagsHome(Request $request) public function messagesMarkRead(Request $request)
{ {
$hashtags = Hashtag::orderByDesc('id')->paginate(10); $this->validate($request, [
return view('admin.hashtags.home', compact('hashtags')); 'id' => 'required|integer|min:1'
} ]);
$id = $request->input('id');
$message = Contact::findOrFail($id);
if($message->read_at) {
return;
}
$message->read_at = now();
$message->save();
return;
}
public function messagesHome(Request $request) public function newsroomHome(Request $request)
{ {
$messages = Contact::orderByDesc('id')->paginate(10); $newsroom = Newsroom::latest()->paginate(10);
return view('admin.messages.home', compact('messages')); return view('admin.newsroom.home', compact('newsroom'));
} }
public function messagesShow(Request $request, $id) public function newsroomCreate(Request $request)
{ {
$message = Contact::findOrFail($id); return view('admin.newsroom.create');
return view('admin.messages.show', compact('message')); }
}
public function messagesMarkRead(Request $request) public function newsroomEdit(Request $request, $id)
{ {
$this->validate($request, [ $news = Newsroom::findOrFail($id);
'id' => 'required|integer|min:1' return view('admin.newsroom.edit', compact('news'));
]); }
$id = $request->input('id');
$message = Contact::findOrFail($id);
if($message->read_at) {
return;
}
$message->read_at = now();
$message->save();
return;
}
public function newsroomHome(Request $request) public function newsroomDelete(Request $request, $id)
{ {
$newsroom = Newsroom::latest()->paginate(10); $news = Newsroom::findOrFail($id);
return view('admin.newsroom.home', compact('newsroom')); $news->delete();
} return redirect('/i/admin/newsroom');
}
public function newsroomCreate(Request $request) public function newsroomUpdate(Request $request, $id)
{ {
return view('admin.newsroom.create'); $this->validate($request, [
} 'title' => 'required|string|min:1|max:100',
'summary' => 'nullable|string|max:200',
'body' => 'nullable|string'
]);
$changed = false;
$changedFields = [];
$news = Newsroom::findOrFail($id);
$fields = [
'title' => 'string',
'summary' => 'string',
'body' => 'string',
'category' => 'string',
'show_timeline' => 'boolean',
'auth_only' => 'boolean',
'show_link' => 'boolean',
'force_modal' => 'boolean',
'published' => 'published'
];
foreach($fields as $field => $type) {
switch ($type) {
case 'string':
if($request->{$field} != $news->{$field}) {
if($field == 'title') {
$news->slug = str_slug($request->{$field});
}
$news->{$field} = $request->{$field};
$changed = true;
array_push($changedFields, $field);
}
break;
public function newsroomEdit(Request $request, $id) case 'boolean':
{ $state = $request->{$field} == 'on' ? true : false;
$news = Newsroom::findOrFail($id); if($state != $news->{$field}) {
return view('admin.newsroom.edit', compact('news')); $news->{$field} = $state;
} $changed = true;
array_push($changedFields, $field);
}
break;
case 'published':
$state = $request->{$field} == 'on' ? true : false;
$published = $news->published_at != null;
if($state != $published) {
$news->published_at = $state ? now() : null;
$changed = true;
array_push($changedFields, $field);
}
break;
public function newsroomDelete(Request $request, $id) }
{ }
$news = Newsroom::findOrFail($id);
$news->delete();
return redirect('/i/admin/newsroom');
}
public function newsroomUpdate(Request $request, $id) if($changed) {
{ $news->save();
$this->validate($request, [ }
'title' => 'required|string|min:1|max:100', $redirect = $news->published_at ? $news->permalink() : $news->editUrl();
'summary' => 'nullable|string|max:200', return redirect($redirect);
'body' => 'nullable|string' }
]);
$changed = false;
$changedFields = [];
$news = Newsroom::findOrFail($id);
$fields = [
'title' => 'string',
'summary' => 'string',
'body' => 'string',
'category' => 'string',
'show_timeline' => 'boolean',
'auth_only' => 'boolean',
'show_link' => 'boolean',
'force_modal' => 'boolean',
'published' => 'published'
];
foreach($fields as $field => $type) {
switch ($type) {
case 'string':
if($request->{$field} != $news->{$field}) {
if($field == 'title') {
$news->slug = str_slug($request->{$field});
}
$news->{$field} = $request->{$field};
$changed = true;
array_push($changedFields, $field);
}
break;
case 'boolean':
$state = $request->{$field} == 'on' ? true : false;
if($state != $news->{$field}) {
$news->{$field} = $state;
$changed = true;
array_push($changedFields, $field);
}
break;
case 'published':
$state = $request->{$field} == 'on' ? true : false;
$published = $news->published_at != null;
if($state != $published) {
$news->published_at = $state ? now() : null;
$changed = true;
array_push($changedFields, $field);
}
break;
}
}
if($changed) {
$news->save();
}
$redirect = $news->published_at ? $news->permalink() : $news->editUrl();
return redirect($redirect);
}
public function newsroomStore(Request $request) public function newsroomStore(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'title' => 'required|string|min:1|max:100', 'title' => 'required|string|min:1|max:100',
'summary' => 'nullable|string|max:200', 'summary' => 'nullable|string|max:200',
'body' => 'nullable|string' 'body' => 'nullable|string'
]); ]);
$changed = false; $changed = false;
$changedFields = []; $changedFields = [];
$news = new Newsroom(); $news = new Newsroom();
$fields = [ $fields = [
'title' => 'string', 'title' => 'string',
'summary' => 'string', 'summary' => 'string',
'body' => 'string', 'body' => 'string',
'category' => 'string', 'category' => 'string',
'show_timeline' => 'boolean', 'show_timeline' => 'boolean',
'auth_only' => 'boolean', 'auth_only' => 'boolean',
'show_link' => 'boolean', 'show_link' => 'boolean',
'force_modal' => 'boolean', 'force_modal' => 'boolean',
'published' => 'published' 'published' => 'published'
]; ];
foreach($fields as $field => $type) { foreach($fields as $field => $type) {
switch ($type) { switch ($type) {
case 'string': case 'string':
if($request->{$field} != $news->{$field}) { if($request->{$field} != $news->{$field}) {
if($field == 'title') { if($field == 'title') {
$news->slug = str_slug($request->{$field}); $news->slug = str_slug($request->{$field});
} }
$news->{$field} = $request->{$field}; $news->{$field} = $request->{$field};
$changed = true; $changed = true;
array_push($changedFields, $field); array_push($changedFields, $field);
} }
break; break;
case 'boolean': case 'boolean':
$state = $request->{$field} == 'on' ? true : false; $state = $request->{$field} == 'on' ? true : false;
if($state != $news->{$field}) { if($state != $news->{$field}) {
$news->{$field} = $state; $news->{$field} = $state;
$changed = true; $changed = true;
array_push($changedFields, $field); array_push($changedFields, $field);
} }
break; break;
case 'published': case 'published':
$state = $request->{$field} == 'on' ? true : false; $state = $request->{$field} == 'on' ? true : false;
$published = $news->published_at != null; $published = $news->published_at != null;
if($state != $published) { if($state != $published) {
$news->published_at = $state ? now() : null; $news->published_at = $state ? now() : null;
$changed = true; $changed = true;
array_push($changedFields, $field); array_push($changedFields, $field);
} }
break; break;
}
}
if($changed) { }
$news->save(); }
}
$redirect = $news->published_at ? $news->permalink() : $news->editUrl(); if($changed) {
return redirect($redirect); $news->save();
} }
$redirect = $news->published_at ? $news->permalink() : $news->editUrl();
return redirect($redirect);
}
} }

View File

@ -0,0 +1,93 @@
<?php
namespace App\Services;
use Cache;
use App\Util\Lexer\PrettyNumber;
use App\{
Contact,
FailedJob,
Hashtag,
Instance,
Media,
Like,
Profile,
Report,
Status,
User
};
class AdminStatsService
{
public static function get()
{
return array_merge(self::recentData(), self::additionalData());
}
protected static function recentData()
{
$day = config('database.default') == 'pgsql' ? 'DATE_PART(\'day\',' : 'day(';
return Cache::remember('admin:dashboard:home:data:15min', now()->addMinutes(15), function() use ($day) {
return [
'contact' => [
'count' => PrettyNumber::convert(Contact::whereNull('read_at')->count()),
'graph' => Contact::selectRaw('count(*) as count, '.$day.'created_at) as d')->groupBy('d')->whereNull('read_at')->whereBetween('created_at',[now()->subDays(14), now()])->orderBy('d')->pluck('count')
],
'failedjobs' => [
'count' => PrettyNumber::convert(FailedJob::where('failed_at', '>=', \Carbon\Carbon::now()->subDay())->count()),
'graph' => FailedJob::selectRaw('count(*) as count, '.$day.'failed_at) as d')->groupBy('d')->whereBetween('failed_at',[now()->subDays(14), now()])->orderBy('d')->pluck('count')
],
'reports' => [
'count' => PrettyNumber::convert(Report::whereNull('admin_seen')->count()),
'graph' => Report::selectRaw('count(*) as count, '.$day.'created_at) as d')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('d')->orderBy('d')->pluck('count')
],
'statuses' => [
'count' => PrettyNumber::convert(Status::whereNull('in_reply_to_id')->whereNull('reblog_of_id')->count()),
'graph' => Status::selectRaw('count(*) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
'replies' => [
'count' => PrettyNumber::convert(Status::whereNotNull('in_reply_to_id')->count()),
'graph' => Status::whereNotNull('in_reply_to_id')->selectRaw('count(*) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
'shares' => [
'count' => PrettyNumber::convert(Status::whereNotNull('reblog_of_id')->count()),
'graph' => Status::whereNotNull('reblog_of_id')->selectRaw('count(*) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
'likes' => [
'count' => PrettyNumber::convert(Like::count()),
'graph' => Like::selectRaw('count(*) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
'profiles' => [
'count' => PrettyNumber::convert(Profile::count()),
'graph' => Profile::selectRaw('count(*) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
];
});
}
protected static function additionalData()
{
$day = config('database.default') == 'pgsql' ? 'DATE_PART(\'day\',' : 'day(';
return Cache::remember('admin:dashboard:home:data:24hr', now()->addHours(24), function() use ($day) {
return [
'users' => [
'count' => PrettyNumber::convert(User::count()),
'graph' => User::selectRaw('count(*) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
'instances' => [
'count' => PrettyNumber::convert(Instance::count()),
'graph' => Instance::selectRaw('count(*) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(28), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
'media' => [
'count' => PrettyNumber::convert(Media::count()),
'graph' => Media::selectRaw('count(*) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
'storage' => [
'count' => Media::sum('size'),
'graph' => Media::selectRaw('sum(size) as count, '.$day.'created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
]
];
});
}
}

File diff suppressed because one or more lines are too long

2
public/js/status.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -18,12 +18,12 @@
"/js/hashtag.js": "/js/hashtag.js?id=e6b41cab117cb03c7d2a", "/js/hashtag.js": "/js/hashtag.js?id=e6b41cab117cb03c7d2a",
"/js/loops.js": "/js/loops.js?id=ac610897b12207c829b9", "/js/loops.js": "/js/loops.js?id=ac610897b12207c829b9",
"/js/mode-dot.js": "/js/mode-dot.js?id=1225a9aac7a93d5d232f", "/js/mode-dot.js": "/js/mode-dot.js?id=1225a9aac7a93d5d232f",
"/js/profile.js": "/js/profile.js?id=c2221e6dd749d3aab260", "/js/profile.js": "/js/profile.js?id=2370d5629003b30a605f",
"/js/profile-directory.js": "/js/profile-directory.js?id=7160b00d9beda164f1bc", "/js/profile-directory.js": "/js/profile-directory.js?id=7160b00d9beda164f1bc",
"/js/quill.js": "/js/quill.js?id=9b15ab0ae830e7293390", "/js/quill.js": "/js/quill.js?id=9b15ab0ae830e7293390",
"/js/search.js": "/js/search.js?id=22e8bccee621e57963d9", "/js/search.js": "/js/search.js?id=22e8bccee621e57963d9",
"/js/status.js": "/js/status.js?id=e79505d19162a11cb404", "/js/status.js": "/js/status.js?id=c0058d6c5fecb0bc96c0",
"/js/story-compose.js": "/js/story-compose.js?id=7b00ed457af2459b916e", "/js/story-compose.js": "/js/story-compose.js?id=df582bf83d8c0d0bc3de",
"/js/theme-monokai.js": "/js/theme-monokai.js?id=39b089458f249e8717ad", "/js/theme-monokai.js": "/js/theme-monokai.js?id=39b089458f249e8717ad",
"/js/timeline.js": "/js/timeline.js?id=bad396bbfbe5fd2ad3e8" "/js/timeline.js": "/js/timeline.js?id=1db81ef37e38304aba79"
} }

View File

@ -76,7 +76,7 @@
</div> </div>
<!-- DESKTOP PROFILE PICTURE --> <!-- DESKTOP PROFILE PICTURE -->
<div class="d-none d-md-block pb-5"> <div class="d-none d-md-block pb-3">
<div v-if="hasStory" class="has-story-lg cursor-pointer shadow-sm" @click="storyRedirect()"> <div v-if="hasStory" class="has-story-lg cursor-pointer shadow-sm" @click="storyRedirect()">
<img :alt="profileUsername + '\'s profile picture'" class="rounded-circle box-shadow cursor-pointer" :src="profile.avatar" width="150px" height="150px"> <img :alt="profileUsername + '\'s profile picture'" class="rounded-circle box-shadow cursor-pointer" :src="profile.avatar" width="150px" height="150px">
</div> </div>
@ -641,6 +641,12 @@
if(document.querySelectorAll('body')[0].classList.contains('loggedIn') == true) { if(document.querySelectorAll('body')[0].classList.contains('loggedIn') == true) {
axios.get('/api/pixelfed/v1/accounts/verify_credentials').then(res => { axios.get('/api/pixelfed/v1/accounts/verify_credentials').then(res => {
this.user = res.data; this.user = res.data;
if(res.data.id == this.profileId || this.relationship.following == true) {
axios.get('/api/stories/v1/exists/' + this.profileId)
.then(res => {
this.hasStory = res.data == true;
})
}
}); });
} }
if(window.outerWidth < 576) { if(window.outerWidth < 576) {
@ -659,10 +665,7 @@
this.profile = res.data; this.profile = res.data;
}).then(res => { }).then(res => {
this.fetchPosts(); this.fetchPosts();
axios.get('/api/stories/v1/exists/' + this.profileId)
.then(res => {
this.hasStory = res.data == true;
})
}); });
}, },

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="container mt-2 mt-md-5"> <div class="container mt-2 mt-md-5">
<input type="file" id="pf-dz" name="media" class="w-100 h-100 d-none file-input" draggable="true" v-bind:accept="config.mimes"> <input type="file" id="pf-dz" name="media" class="d-none file-input" v-bind:accept="config.mimes">
<div class="row"> <div class="row">
<div class="col-12 col-md-6 offset-md-3"> <div class="col-12 col-md-6 offset-md-3">
@ -227,7 +227,8 @@
}).catch(function(e) { }).catch(function(e) {
self.uploading = false; self.uploading = false;
io.value = null; io.value = null;
swal('Oops!', e.response.data.message, 'warning'); let msg = e.response.data.message ? e.response.data.message : 'Something went wrong.'
swal('Oops!', msg, 'warning');
}); });
io.value = null; io.value = null;
self.uploadProgress = 0; self.uploadProgress = 0;

View File

@ -1,10 +1,12 @@
<template> <template>
<div class="container" style=""> <div class="container" style="">
<div v-if="layout === 'feed'" class="row"> <div v-if="layout === 'feed'" class="row">
<div :class="[modes.distractionFree ? 'col-md-8 col-lg-8 offset-md-2 px-0 my-sm-3 timeline order-2 order-md-1':'col-md-8 col-lg-8 px-0 my-sm-3 timeline order-2 order-md-1']"> <div :class="[modes.distractionFree ? 'col-md-8 col-lg-8 offset-md-2 px-0 mb-sm-3 timeline order-2 order-md-1':'col-md-8 col-lg-8 px-0 mb-sm-3 timeline order-2 order-md-1']">
<story-component v-if="config.features.stories"></story-component> <div v-if="config.features.stories">
<div style="padding-top:10px;"> <story-component v-if="config.features.stories"></story-component>
<div v-if="loading" class="text-center"> </div>
<div>
<div v-if="loading" class="text-center" style="padding-top:10px;">
<div class="spinner-border" role="status"> <div class="spinner-border" role="status">
<span class="sr-only">Loading...</span> <span class="sr-only">Loading...</span>
</div> </div>
@ -21,7 +23,7 @@
<div class="card-body text-center pt-3"> <div class="card-body text-center pt-3">
<p class="mb-0"> <p class="mb-0">
<a :href="'/'+rec.username"> <a :href="'/'+rec.username">
<img :src="rec.avatar" class="img-fluid rounded-circle cursor-pointer" width="45px" height="45px"> <img :src="rec.avatar" class="img-fluid rounded-circle cursor-pointer" width="45px" height="45px" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=2'">
</a> </a>
</p> </p>
<div class="py-3"> <div class="py-3">
@ -70,7 +72,13 @@
<div class="card mb-sm-4 status-card card-md-rounded-0 shadow-none border"> <div class="card mb-sm-4 status-card card-md-rounded-0 shadow-none border">
<div v-if="!modes.distractionFree" class="card-header d-inline-flex align-items-center bg-white"> <div v-if="!modes.distractionFree" class="card-header d-inline-flex align-items-center bg-white">
<img v-bind:src="status.account.avatar" width="32px" height="32px" class="cursor-pointer" style="border-radius: 32px;" @click="profileUrl(status)"> <img v-bind:src="status.account.avatar" width="38px" height="38px" class="cursor-pointer" style="border-radius: 38px;" @click="profileUrl(status)" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=2'">
<!-- <div v-if="hasStory" class="has-story has-story-sm cursor-pointer shadow-sm" @click="profileUrl(status)">
<img class="rounded-circle box-shadow" :src="status.account.avatar" width="32px" height="32px" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=2'">
</div>
<div v-else>
<img class="rounded-circle box-shadow" :src="status.account.avatar" width="32px" height="32px" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=2'">
</div> -->
<div class="pl-2"> <div class="pl-2">
<!-- <a class="d-block username font-weight-bold text-dark" v-bind:href="status.account.url" style="line-height:0.5;"> --> <!-- <a class="d-block username font-weight-bold text-dark" v-bind:href="status.account.url" style="line-height:0.5;"> -->
<a class="username font-weight-bold text-dark text-decoration-none" v-bind:href="profileUrl(status)" v-html="statusCardUsernameFormat(status)"> <a class="username font-weight-bold text-dark text-decoration-none" v-bind:href="profileUrl(status)" v-html="statusCardUsernameFormat(status)">
@ -207,13 +215,19 @@
</div> </div>
<div v-if="!modes.distractionFree" class="col-md-4 col-lg-4 my-3 order-1 order-md-2 d-none d-md-block"> <div v-if="!modes.distractionFree" class="col-md-4 col-lg-4 my-3 order-1 order-md-2 d-none d-md-block">
<div class="position-sticky" style="top:78px;"> <div class="position-sticky" style="top:83px;">
<div class="mb-4"> <div class="mb-4">
<div class=""> <div class="card shadow-none border">
<div class=""> <div class="card-body pb-2">
<div class="media d-flex align-items-center"> <div class="media d-flex align-items-center">
<a :href="profile.url"> <a :href="!userStory ? profile.url : '/stories/' + profile.acct" class="mr-3">
<img class="mr-3 rounded-circle box-shadow" :src="profile.avatar || '/storage/avatars/default.png'" alt="avatar" width="64px" height="64px"> <!-- <img class="mr-3 rounded-circle box-shadow" :src="profile.avatar || '/storage/avatars/default.png'" alt="avatar" width="64px" height="64px" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=2'"> -->
<div v-if="userStory" class="has-story cursor-pointer shadow-sm" @click="storyRedirect()">
<img class="rounded-circle box-shadow" :src="profile.avatar" width="64px" height="64px" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=2'">
</div>
<div v-else>
<img class="rounded-circle box-shadow" :src="profile.avatar" width="64px" height="64px" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=2'">
</div>
</a> </a>
<div class="media-body d-flex justify-content-between word-break" > <div class="media-body d-flex justify-content-between word-break" >
<div> <div>
@ -226,7 +240,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card-footer bg-transparent border-0 mt-2 py-1"> <div class="card-footer bg-transparent border-top mt-2 py-1">
<div class="d-flex justify-content-between text-center"> <div class="d-flex justify-content-between text-center">
<span class="cursor-pointer" @click="redirect(profile.url)"> <span class="cursor-pointer" @click="redirect(profile.url)">
<p class="mb-0 font-weight-bold">{{formatCount(profile.statuses_count)}}</p> <p class="mb-0 font-weight-bold">{{formatCount(profile.statuses_count)}}</p>
@ -260,7 +274,7 @@
</div> </div>
<div v-show="showSuggestions == true && suggestions.length && config.ab && config.ab.rec == true" class="mb-4"> <div v-show="showSuggestions == true && suggestions.length && config.ab && config.ab.rec == true" class="mb-4">
<div class="card"> <div class="card shadow-none border">
<div class="card-header bg-white d-flex align-items-center justify-content-between"> <div class="card-header bg-white d-flex align-items-center justify-content-between">
<a class="small text-muted cursor-pointer" href="#" @click.prevent="refreshSuggestions" ref="suggestionRefresh"><i class="fas fa-sync-alt"></i></a> <a class="small text-muted cursor-pointer" href="#" @click.prevent="refreshSuggestions" ref="suggestionRefresh"><i class="fas fa-sync-alt"></i></a>
<div class="small text-dark text-uppercase font-weight-bold">Suggestions</div> <div class="small text-dark text-uppercase font-weight-bold">Suggestions</div>
@ -269,7 +283,7 @@
<div class="card-body pt-0"> <div class="card-body pt-0">
<div v-for="(rec, index) in suggestions" class="media align-items-center mt-3"> <div v-for="(rec, index) in suggestions" class="media align-items-center mt-3">
<a :href="'/'+rec.username"> <a :href="'/'+rec.username">
<img :src="rec.avatar" width="32px" height="32px" class="rounded-circle mr-3"> <img :src="rec.avatar" width="32px" height="32px" class="rounded-circle mr-3" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=2'">
</a> </a>
<div class="media-body"> <div class="media-body">
<p class="mb-0 font-weight-bold small"> <p class="mb-0 font-weight-bold small">
@ -328,7 +342,7 @@
</div> </div>
</div> </div>
<div class="py-3 media align-items-center"> <div class="py-3 media align-items-center">
<img :src="s.account.avatar" class="mr-3 rounded-circle shadow-sm" :alt="s.account.username + ' \'s avatar'" width="30px" height="30px"> <img :src="s.account.avatar" class="mr-3 rounded-circle shadow-sm" :alt="s.account.username + ' \'s avatar'" width="30px" height="30px" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=2'">
<div class="media-body"> <div class="media-body">
<p class="mb-0 font-weight-bold small">{{s.account.username}}</p> <p class="mb-0 font-weight-bold small">{{s.account.username}}</p>
<p class="mb-0" style="line-height: 0.7;"> <p class="mb-0" style="line-height: 0.7;">
@ -472,6 +486,34 @@
opacity: .3; opacity: .3;
color: #3897f0; color: #3897f0;
} }
.has-story {
width: 64px;
height: 64px;
border-radius: 50%;
padding: 2px;
background: radial-gradient(ellipse at 70% 70%, #ee583f 8%, #d92d77 42%, #bd3381 58%);
}
.has-story img {
width: 60px;
height: 60px;
border-radius: 50%;
padding: 3px;
background: #fff;
}
.has-story.has-story-sm {
width: 32px;
height: 32px;
border-radius: 50%;
padding: 2px;
background: radial-gradient(ellipse at 70% 70%, #ee583f 8%, #d92d77 42%, #bd3381 58%);
}
.has-story.has-story-sm img {
width: 28px;
height: 28px;
border-radius: 50%;
padding: 3px;
background: #fff;
}
</style> </style>
<script type="text/javascript"> <script type="text/javascript">
@ -503,7 +545,7 @@
followingCursor: 1, followingCursor: 1,
followingMore: true, followingMore: true,
lightboxMedia: false, lightboxMedia: false,
showSuggestions: false, showSuggestions: true,
showReadMore: true, showReadMore: true,
replyStatus: {}, replyStatus: {},
replyText: '', replyText: '',
@ -516,6 +558,7 @@
ctxEmbedPayload: false, ctxEmbedPayload: false,
copiedEmbed: false, copiedEmbed: false,
showTips: true, showTips: true,
userStory: false,
} }
}, },
@ -525,8 +568,13 @@
}, },
mounted() { mounted() {
if($('link[data-stylesheet="dark"]').length != 0) { if(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches || $('link[data-stylesheet="dark"]').length != 0) {
this.modes.dark = true; this.modes.dark = true;
// todo: release after dark mode updates
/* let el = document.querySelector('link[data-stylesheet="light"]');
el.setAttribute('href', '/css/appdark.css?id=' + Date.now());
el.setAttribute('data-stylesheet', 'dark'); */
} }
if(localStorage.getItem('pf_metro_ui.exp.rec') == 'false') { if(localStorage.getItem('pf_metro_ui.exp.rec') == 'false') {
@ -573,7 +621,9 @@
if(this.profile.is_admin == true) { if(this.profile.is_admin == true) {
this.modes.mod = true; this.modes.mod = true;
} }
//this.expRec(); window._sharedData.curUser = res.data;
this.hasStory();
// this.expRec();
}).catch(err => { }).catch(err => {
swal( swal(
'Oops, something went wrong', 'Oops, something went wrong',
@ -1056,7 +1106,7 @@
}, },
expRec() { expRec() {
return; //return;
if(this.config.ab.rec == false) { if(this.config.ab.rec == false) {
return; return;
@ -1201,6 +1251,7 @@
this.ctxMenuStatus = status; this.ctxMenuStatus = status;
this.ctxEmbedPayload = window.App.util.embed.post(status.url); this.ctxEmbedPayload = window.App.util.embed.post(status.url);
if(status.account.id == this.profile.id) { if(status.account.id == this.profile.id) {
this.ctxMenuRelationship = false;
this.$refs.ctxModal.show(); this.$refs.ctxModal.show();
} else { } else {
axios.get('/api/pixelfed/v1/accounts/relationships', { axios.get('/api/pixelfed/v1/accounts/relationships', {
@ -1240,12 +1291,6 @@
axios.post('/i/follow', { axios.post('/i/follow', {
item: id item: id
}).then(res => { }).then(res => {
this.feed.forEach(s => {
if(s.account.id == id) {
s.account.relationship.following = !s.account.relationship.following;
}
});
let username = this.ctxMenuStatus.account.acct; let username = this.ctxMenuStatus.account.acct;
this.closeCtxMenu(); this.closeCtxMenu();
setTimeout(function() { setTimeout(function() {
@ -1259,11 +1304,6 @@
axios.post('/i/follow', { axios.post('/i/follow', {
item: id item: id
}).then(res => { }).then(res => {
this.feed.forEach(s => {
if(s.account.id == id) {
s.account.relationship.following = !s.account.relationship.following;
}
});
let username = this.ctxMenuStatus.account.acct; let username = this.ctxMenuStatus.account.acct;
if(this.scope == 'home') { if(this.scope == 'home') {
this.feed = this.feed.filter(s => { this.feed = this.feed.filter(s => {
@ -1383,6 +1423,13 @@
length: len length: len
}); });
}, },
hasStory() {
axios.get('/api/stories/v1/exists/'+this.profile.id)
.then(res => {
this.userStory = res.data;
})
}
} }
} }
</script> </script>