Merge pull request #2844 from pixelfed/staging

Staging
This commit is contained in:
daniel 2021-07-07 01:12:10 -06:00 committed by GitHub
commit 941a5d0d2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 356 additions and 401 deletions

View file

@ -45,6 +45,11 @@
- Updated ApiV1Controller, use PublicTimelineService. ([f67c67bc](https://github.com/pixelfed/pixelfed/commit/f67c67bc)) - Updated ApiV1Controller, use PublicTimelineService. ([f67c67bc](https://github.com/pixelfed/pixelfed/commit/f67c67bc))
- Updated ApiV1Controller, use ProfileService for verify_credentials. ([352aa573](https://github.com/pixelfed/pixelfed/commit/352aa573)) - Updated ApiV1Controller, use ProfileService for verify_credentials. ([352aa573](https://github.com/pixelfed/pixelfed/commit/352aa573))
- Updated RemotePost.vue, fix content warning button. ([7647e724](https://github.com/pixelfed/pixelfed/commit/7647e724)) - Updated RemotePost.vue, fix content warning button. ([7647e724](https://github.com/pixelfed/pixelfed/commit/7647e724))
- Updated AdminMediaController, improve perf and use simple pagination. ([f2686cac](https://github.com/pixelfed/pixelfed/commit/f2686cac))
- Updated PostComponent, fix MomentUI like counter. ([42c6121a](https://github.com/pixelfed/pixelfed/commit/42c6121a))
- Updated status views, remove like counts from status embed. ([1a2e41b1](https://github.com/pixelfed/pixelfed/commit/1a2e41b1))
- Updated Profile, fix unauthenticated private profiles. ([9017f7c4](https://github.com/pixelfed/pixelfed/commit/9017f7c4))
- Updated PublicApiController, impr home timeline perf. ([4fe42e5b](https://github.com/pixelfed/pixelfed/commit/4fe42e5b))
- ([](https://github.com/pixelfed/pixelfed/commit/)) - ([](https://github.com/pixelfed/pixelfed/commit/))
## [v0.11.0 (2021-06-01)](https://github.com/pixelfed/pixelfed/compare/v0.10.10...v0.11.0) ## [v0.11.0 (2021-06-01)](https://github.com/pixelfed/pixelfed/compare/v0.10.10...v0.11.0)

View file

@ -27,6 +27,7 @@ trait AdminMediaController
], ],
'search' => 'nullable|string|min:1|max:20' 'search' => 'nullable|string|min:1|max:20'
]); ]);
if($request->filled('search')) { if($request->filled('search')) {
$profiles = Profile::where('username', 'like', '%'.$request->input('search').'%')->pluck('id')->toArray(); $profiles = Profile::where('username', 'like', '%'.$request->input('search').'%')->pluck('id')->toArray();
$media = Media::whereHas('status') $media = Media::whereHas('status')
@ -42,7 +43,8 @@ trait AdminMediaController
$media = MediaBlocklist::latest()->paginate(12); $media = MediaBlocklist::latest()->paginate(12);
return view('admin.media.home', compact('media')); return view('admin.media.home', compact('media'));
} }
$media = Media::whereHas('status')->with('status')->orderby('id', 'desc')->paginate(12);
$media = Media::whereNull('remote_url')->orderby('id', 'desc')->simplePaginate(12);
return view('admin.media.home', compact('media')); return view('admin.media.home', compact('media'));
} }
@ -51,4 +53,4 @@ trait AdminMediaController
$media = Media::findOrFail($id); $media = Media::findOrFail($id);
return view('admin.media.show', compact('media')); return view('admin.media.show', compact('media'));
} }
} }

View file

@ -47,7 +47,8 @@ class ProfileController extends Controller
}); });
if ($user->is_private == true) { if ($user->is_private == true) {
abort(404); $profile = null;
return view('profile.private', compact('user'));
} }
$owner = false; $owner = false;

View file

@ -290,69 +290,32 @@ class PublicApiController extends Controller
$max = $request->input('max_id'); $max = $request->input('max_id');
$limit = $request->input('limit') ?? 3; $limit = $request->input('limit') ?? 3;
$user = $request->user(); $user = $request->user();
$amin = SnowflakeService::byDate(now()->subDays(90));
$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;
});
$filtered = UserFilter::whereUserId($user->profile_id) $filtered = UserFilter::whereUserId($user->profile_id)
->whereFilterableType('App\Profile') ->whereFilterableType('App\Profile')
->whereIn('filter_type', ['mute', 'block']) ->whereIn('filter_type', ['mute', 'block'])
->pluck('filterable_id')->toArray(); ->pluck('filterable_id')->toArray();
if($min || $max) { if(PublicTimelineService::count() == 0) {
$dir = $min ? '>' : '<'; PublicTimelineService::warmCache(true, 400);
$id = $min ?? $max;
$timeline = Status::select(
'id',
'profile_id',
'type',
'scope',
'local'
)->where('id', $dir, $id)
->where('id', '>', $amin)
->whereIn('type', ['text', 'photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNotIn('profile_id', $filtered)
->whereLocal(true)
->whereScope('public')
->orderBy('id', 'desc')
->limit($limit)
->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
return $status;
});
$res = $timeline->toArray();
} else {
$timeline = Status::select(
'id',
'profile_id',
'type',
'scope',
'local'
)->whereIn('type', ['text', 'photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->where('id', '>', $amin)
->whereNotIn('profile_id', $filtered)
->with('profile', 'hashtags', 'mentions')
->whereLocal(true)
->whereScope('public')
->orderBy('id', 'desc')
->limit($limit)
->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
return $status;
});
$res = $timeline->toArray();
} }
if ($max) {
$feed = PublicTimelineService::getRankedMaxId($max, $limit);
} else if ($min) {
$feed = PublicTimelineService::getRankedMinId($min, $limit);
} else {
$feed = PublicTimelineService::get(0, $limit);
}
$res = collect($feed)
->map(function($k) use($user) {
$status = StatusService::get($k);
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $k);
return $status;
})
->toArray();
return response()->json($res); return response()->json($res);
} }

BIN
public/js/profile.js vendored

Binary file not shown.

BIN
public/js/status.js vendored

Binary file not shown.

Binary file not shown.

View file

@ -323,7 +323,7 @@
</div> </div>
<div class="like-count font-weight-bold mt-2 rounded border" style="cursor:pointer;" v-on:click="likesModal"> <div class="like-count font-weight-bold mt-2 rounded border" style="cursor:pointer;" v-on:click="likesModal">
{{ownerOrAdmin() ? (status.liked_by.total_count + 1) : 0}} {{ownerOrAdmin == true ? (status.liked_by.total_count + 1) : 0}}
</div> </div>
</div> </div>
<div class="text-center"> <div class="text-center">
@ -898,6 +898,10 @@ export default {
return; return;
} }
if(u.has('ui') && u.get('ui') == 'moment' && this.layout != 'moment') {
this.layout = 'moment';
}
if(forceMetro == true || u.has('ui') && u.get('ui') == 'metro' && this.layout != 'metro') { if(forceMetro == true || u.has('ui') && u.get('ui') == 'metro' && this.layout != 'metro') {
this.layout = 'metro'; this.layout = 'metro';
} }

View file

@ -703,6 +703,11 @@
this.fetchProfile(); this.fetchProfile();
let u = new URLSearchParams(window.location.search); let u = new URLSearchParams(window.location.search);
let forceMetro = localStorage.getItem('pf_metro_ui.exp.forceMetro') == 'true'; let forceMetro = localStorage.getItem('pf_metro_ui.exp.forceMetro') == 'true';
if(u.has('ui') && u.get('ui') == 'moment' && this.layout != 'moment') {
this.layout = 'moment';
}
if(forceMetro == true || u.has('ui') && u.get('ui') == 'metro' && this.layout != 'metro') { if(forceMetro == true || u.has('ui') && u.get('ui') == 'metro' && this.layout != 'metro') {
this.layout = 'metro'; this.layout = 'metro';
} }
@ -739,10 +744,6 @@
} }
}); });
} }
if(window.outerWidth < 576) {
$('nav.navbar').hide();
this.isMobile = true;
}
}, },
updated() { updated() {

View file

@ -15,8 +15,8 @@ return [
'timelines' => 'خط زمانی', 'timelines' => 'خط زمانی',
'embed' => 'توکار', 'embed' => 'توکار',
'communityGuidelines' => 'دستورالعمل‌های انجمن', 'communityGuidelines' => 'دستورالعمل‌های اجتماع',
'whatIsTheFediverse' => 'نامتمرکز یعنی چی؟', 'whatIsTheFediverse' => 'فدیورس چیست؟',
'controllingVisibility' => 'کنترل نمایش', 'controllingVisibility' => 'کنترل نمایش',
'blockingAccounts' => 'حساب‌های مسدودشده', 'blockingAccounts' => 'حساب‌های مسدودشده',
'safetyTips' => 'نکات امنیتی', 'safetyTips' => 'نکات امنیتی',

View file

@ -5,15 +5,15 @@ return [
'home' => 'خانه', 'home' => 'خانه',
'local' => 'محلی', 'local' => 'محلی',
'network' => 'شبکه', 'network' => 'شبکه',
'discover' => 'کشف‌کردن', 'discover' => 'کشف ‌کردن',
'viewMyProfile' => 'مشاهده نمایه من', 'viewMyProfile' => 'مشاهده نمایه من',
'myProfile' => 'نمایه من', 'myProfile' => 'نمایه من',
'myTimeline' => 'جدول زمانی من', 'myTimeline' => 'خط‌زمانی من',
'publicTimeline' => 'جدول زمانی عمومی', 'publicTimeline' => 'خط‌زمانی عمومی',
'remoteFollow' => 'از راه دور دنبال کنید', 'remoteFollow' => 'پیگیری راه دور',
'settings' => 'تنظیمات', 'settings' => 'تنظیمات',
'admin' => 'مدیر', 'admin' => 'مدیر',
'logout' => 'خروج', 'logout' => 'خروج',
'directMessages' => 'پیام خصوصی', 'directMessages' => 'پیام مستقیم',
'composePost' => 'نوشتن پست', 'composePost' => 'ایجاد فرسته',
]; ];

View file

@ -2,11 +2,11 @@
return [ return [
'likedPhoto' => 'پست شما را پسندید.', 'likedPhoto' => 'فرستهٔ شما را پسندید.',
'likedComment' => 'دیدگاه شما را پسندید.', 'likedComment' => 'دیدگاه شما را پسندید.',
'startedFollowingYou' => 'شما را دنبال می‌کند.', 'startedFollowingYou' => 'شما را دنبال می‌کند.',
'commented' => 'دیدگاهی روی پست شما نوشت.', 'commented' => 'دیدگاهی روی فرستهٔ شما نوشت.',
'mentionedYou' => 'شما را صدا کرد.', 'mentionedYou' => 'شما را صدا کرد.',
'shared' => 'پستی منتشر کرد.', 'shared' => 'فرسته‌ای منتشر کرد.',
]; ];

View file

@ -13,10 +13,10 @@ return [
| |
*/ */
'password' => 'رمز عبور شما باید حداقل شامل ۶ کاراکتر باشد و همچنین با تکرار آن مطابق باشد.', 'password' => 'رمز عبور شما باید حداقل شامل ۶ حرف بوده و همچنین با تکرار آن مطابق باشد.',
'reset' => 'رمز عبور شما بازنشانی شد!', 'reset' => 'رمز عبور شما بازنشانی شد!',
'sent' => 'اگر ایمیل شما در پایگاه‌داده ما موجود باشد، شما ایمیل شامل یک لینک بازنشانی رمز عبور در چند دقیقه آینده دریافت خواهید کرد. در صورتی که ایمیلی دریافت نکردید، لطفا صندوق هرزنامه خود را نیز بررسی کنید.', 'sent' => 'اگر ایمیل‌تان در پایگاه‌داده ما موجود باشد، در چند دقیقه آینده ایمیلی حاوی یک پیوند بازنشانی رمز عبور دریافت خواهید کرد. در صورتی که ایمیلی دریافت نکردید، لطفا صندوق هرزنامه خود را نیز بررسی کنید.',
'token' => 'این لینک نامعتبر است.', 'token' => 'این پیوند نامعتبر است.',
'user' => 'اگر ایمیل شما در پایگاه‌داده ما موجود باشد، شما ایمیل شامل یک لینک بازنشانی رمز عبور در چند دقیقه آینده دریافت خواهید کرد. در صورتی که ایمیلی دریافت نکردید، لطفا صندوق هرزنامه خود را نیز بررسی کنید.', 'user' => 'اگر ایمیل‌تان در پایگاه‌داده ما موجود باشد، در چند دقیقه آینده ایمیلی حاوی یک پیوند بازنشانی رمز عبور دریافت خواهید کرد. در صورتی که ایمیلی دریافت نکردید، لطفا صندوق هرزنامه خود را نیز بررسی کنید.',
]; ];

View file

@ -1,10 +1,10 @@
<?php <?php
return [ return [
'emptyTimeline' => 'این کاربر هنوز پستی ارسال نکرده!', 'emptyTimeline' => 'این کاربر هنوز چیزی منتشر نکرده است!',
'emptyFollowers' => 'این کاربر هنوز دنبال‌کننده‌ای ندارد!', 'emptyFollowers' => 'این کاربر هنوز دنبال‌کننده‌ای ندارد!',
'emptyFollowing' => 'این کاربر هنوز کسی را دنبال نمی‌کند!', 'emptyFollowing' => 'این کاربر هنوز کسی را دنبال نمی‌کند!',
'emptySaved' => 'شما هنوز هیچ پستی ذخیره نکرده‌اید!', 'emptySaved' => 'شما هنوز هیچ فرسته‌ای را ذخیره نکرده‌اید!',
'savedWarning' => 'فقط شما می‌توانید آنچه که ذخیره کرده‌اید را ببینید.', 'savedWarning' => 'فقط شما می‌توانید آنچه که ذخیره کرده‌اید را ببینید.',
'privateProfileWarning' => 'این حساب خصوصی است.', 'privateProfileWarning' => 'این حساب خصوصی است.',
'alreadyFollow' => 'الان :username را دنبال می‌کنید؟', 'alreadyFollow' => 'الان :username را دنبال می‌کنید؟',

View file

@ -5,8 +5,8 @@ return [
'about' => 'درباره', 'about' => 'درباره',
'help' => 'راهنما', 'help' => 'راهنما',
'language' => 'زبان‌ها', 'language' => 'زبان‌ها',
'fediverse' => 'نامتمرکز', 'fediverse' => 'فدیورس',
'opensource' => 'متن باز', 'opensource' => 'آزاد/متن‌باز',
'terms' => 'قوانین', 'terms' => 'قوانین',
'privacy' => 'حریم خصوصی', 'privacy' => 'حریم خصوصی',
'l10nWip' => 'ما هنوز روی محلی‌سازی کار می‌کنیم.', 'l10nWip' => 'ما هنوز روی محلی‌سازی کار می‌کنیم.',

View file

@ -2,6 +2,6 @@
return [ return [
'emptyPersonalTimeline' => 'جدول زمانی شما خالی است.', 'emptyPersonalTimeline' => 'خط‌زمانی شما خالی است.',
]; ];

View file

@ -14,7 +14,7 @@ return [
*/ */
'accepted' => ':attribute باید پذیرفته شده باشد.', 'accepted' => ':attribute باید پذیرفته شده باشد.',
'active_url' => 'آدرس :attribute معتبر نیست', 'active_url' => 'نشانی :attribute معتبر نیست',
'after' => ':attribute باید تاریخی بعد از :date باشد.', 'after' => ':attribute باید تاریخی بعد از :date باشد.',
'after_or_equal' => ':attribute باید تاریخی بعد از :date، یا مطابق با آن باشد.', 'after_or_equal' => ':attribute باید تاریخی بعد از :date، یا مطابق با آن باشد.',
'alpha' => ':attribute باید فقط حروف الفبا باشد.', 'alpha' => ':attribute باید فقط حروف الفبا باشد.',
@ -26,8 +26,8 @@ return [
'between' => [ 'between' => [
'numeric' => ':attribute باید بین :min و :max باشد.', 'numeric' => ':attribute باید بین :min و :max باشد.',
'file' => ':attribute باید بین :min و :max کیلوبایت باشد.', 'file' => ':attribute باید بین :min و :max کیلوبایت باشد.',
'string' => ':attribute باید بین :min و :max کاراکتر باشد.', 'string' => ':attribute باید بین :min و :max حرف باشد.',
'array' => ':attribute باید بین :min و :max آیتم باشد.', 'array' => ':attribute باید بین :min و :max مورد باشد.',
], ],
'boolean' => 'فیلد :attribute فقط می‌تواند صحیح و یا غلط باشد', 'boolean' => 'فیلد :attribute فقط می‌تواند صحیح و یا غلط باشد',
'confirmed' => ':attribute با فیلد تکرار مطابقت ندارد.', 'confirmed' => ':attribute با فیلد تکرار مطابقت ندارد.',
@ -38,39 +38,39 @@ return [
'digits_between' => ':attribute باید بین :min و :max رقم باشد.', 'digits_between' => ':attribute باید بین :min و :max رقم باشد.',
'dimensions' => 'ابعاد تصویر :attribute قابل قبول نیست.', 'dimensions' => 'ابعاد تصویر :attribute قابل قبول نیست.',
'distinct' => 'فیلد :attribute تکراری است.', 'distinct' => 'فیلد :attribute تکراری است.',
'email' => ':attribute باید یک ایمیل معتبر باشد', 'email' => ':attribute باید ایمیلی معتبر باشد',
'exists' => ':attribute انتخاب شده، معتبر نیست.', 'exists' => ':attribute انتخاب شده معتبر نیست.',
'file' => ':attribute باید یک فایل باشد', 'file' => ':attribute باید یک پرونده باشد',
'filled' => 'فیلد :attribute الزامی است', 'filled' => 'فیلد :attribute الزامی است',
'image' => ':attribute باید تصویر باشد.', 'image' => ':attribute باید تصویر باشد.',
'in' => ':attribute انتخاب شده، معتبر نیست.', 'in' => ':attribute انتخاب شده معتبر نیست.',
'in_array' => 'فیلد :attribute در :other وجود ندارد.', 'in_array' => 'فیلد :attribute در :other وجود ندارد.',
'integer' => ':attribute باید عدد صحیح باشد.', 'integer' => ':attribute باید عددی صحیح باشد.',
'ip' => ':attribute باید IP معتبر باشد.', 'ip' => ':attribute باید IP معتبر باشد.',
'ipv4' => ':attribute باید یک آدرس معتبر از نوع IPv4 باشد.', 'ipv4' => ':attribute باید یک نشانی معتبر از نوع IPv4 باشد.',
'ipv6' => ':attribute باید یک آدرس معتبر از نوع IPv6 باشد.', 'ipv6' => ':attribute باید یک نشانی معتبر از نوع IPv6 باشد.',
'json' => 'فیلد :attribute باید یک رشته از نوع JSON باشد.', 'json' => 'فیلد :attribute باید یک رشته از نوع JSON باشد.',
'max' => [ 'max' => [
'numeric' => ':attribute نباید بزرگتر از :max باشد.', 'numeric' => ':attribute نباید بزرگتر از :max باشد.',
'file' => ':attribute نباید بزرگتر از :max کیلوبایت باشد.', 'file' => ':attribute نباید بزرگتر از :max کیلوبایت باشد.',
'string' => ':attribute نباید بیشتر از :max کاراکتر باشد.', 'string' => ':attribute نباید بیشتر از :max حرف باشد.',
'array' => ':attribute نباید بیشتر از :max آیتم باشد.', 'array' => ':attribute نباید بیشتر از :max مورد باشد.',
], ],
'mimes' => ':attribute باید یکی از فرمت های :values باشد.', 'mimes' => ':attribute باید یکی از قالب‌های :values باشد.',
'mimetypes' => ':attribute باید یکی از فرمت های :values باشد.', 'mimetypes' => ':attribute باید یکی از قالب‌های :values باشد.',
'min' => [ 'min' => [
'numeric' => ':attribute نباید کوچکتر از :min باشد.', 'numeric' => ':attribute نباید کوچکتر از :min باشد.',
'file' => ':attribute نباید کوچکتر از :min کیلوبایت باشد.', 'file' => ':attribute نباید کوچکتر از :min کیلوبایت باشد.',
'string' => ':attribute نباید کمتر از :min کاراکتر باشد.', 'string' => ':attribute نباید کمتر از :min حرف باشد.',
'array' => ':attribute نباید کمتر از :min آیتم باشد.', 'array' => ':attribute نباید کمتر از :min مورد باشد.',
], ],
'not_in' => ':attribute انتخاب شده، معتبر نیست.', 'not_in' => ':attribute انتخاب شده معتبر نیست.',
'not_regex' => ':attribute نامعتبر است.', 'not_regex' => ':attribute نامعتبر است.',
'numeric' => ':attribute باید عدد باشد.', 'numeric' => ':attribute باید عدد باشد.',
'present' => 'فیلد :attribute باید در پارامترهای ارسالی وجود داشته باشد.', 'present' => 'فیلد :attribute باید در پارامترهای ارسالی وجود داشته باشد.',
'regex' => 'فرمت :attribute معتبر نیست', 'regex' => 'قالب :attribute معتبر نیست',
'required' => 'فیلد :attribute الزامی است', 'required' => 'فیلد :attribute الزامی است',
'required_if' => 'هنگامی که :other برابر با :value است، فیلد :attribute الزامی است.', 'required_if' => 'هنگامی که :other برابر با :value باشد، فیلد :attribute الزامی است.',
'required_unless' => 'فیلد :attribute ضروری است، مگر آنکه :other در :values موجود باشد.', 'required_unless' => 'فیلد :attribute ضروری است، مگر آنکه :other در :values موجود باشد.',
'required_with' => 'در صورت وجود فیلد :values، فیلد :attribute الزامی است.', 'required_with' => 'در صورت وجود فیلد :values، فیلد :attribute الزامی است.',
'required_with_all' => 'در صورت وجود فیلدهای :values، فیلد :attribute الزامی است.', 'required_with_all' => 'در صورت وجود فیلدهای :values، فیلد :attribute الزامی است.',
@ -80,14 +80,14 @@ return [
'size' => [ 'size' => [
'numeric' => ':attribute باید برابر با :size باشد.', 'numeric' => ':attribute باید برابر با :size باشد.',
'file' => ':attribute باید برابر با :size کیلوبایت باشد.', 'file' => ':attribute باید برابر با :size کیلوبایت باشد.',
'string' => ':attribute باید برابر با :size کاراکتر باشد.', 'string' => ':attribute باید برابر با :size حرف باشد.',
'array' => ':attribute باسد شامل :size آیتم باشد.', 'array' => ':attribute باید شامل :size مورد باشد.',
], ],
'string' => 'فیلد :attribute باید متن باشد.', 'string' => 'فیلد :attribute باید متن باشد.',
'timezone' => 'فیلد :attribute باید یک منطقه زمانی قابل قبول باشد.', 'timezone' => 'فیلد :attribute باید یک منطقه زمانی قابل قبول باشد.',
'unique' => ':attribute قبلا انتخاب شده است.', 'unique' => ':attribute قبلا انتخاب شده است.',
'uploaded' => 'آپلود فایل :attribute موفقیت آمیز نبود.', 'uploaded' => 'بارگذاری پرونده :attribute موفقیت آمیز نبود.',
'url' => 'فرمت آدرس :attribute اشتباه است.', 'url' => 'قالب نشانی :attribute اشتباه است.',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -131,7 +131,7 @@ return [
'phone' => 'تلفن', 'phone' => 'تلفن',
'mobile' => 'تلفن همراه', 'mobile' => 'تلفن همراه',
'age' => 'سن', 'age' => 'سن',
'sex' => 'جنسیت', 'sex' => 'جنس',
'gender' => 'جنسیت', 'gender' => 'جنسیت',
'day' => 'روز', 'day' => 'روز',
'month' => 'ماه', 'month' => 'ماه',

View file

@ -1,107 +1,97 @@
<div class="bg-white py-5 border-bottom"> <div class="bg-white py-5 border-bottom">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12 col-md-4 d-flex"> <div class="col-12 col-md-4 d-flex">
<div class="profile-avatar mx-auto"> <div class="profile-avatar mx-auto">
<img class="rounded-circle box-shadow" src="{{$user->avatarUrl()}}" width="172px" height="172px"> <img class="rounded-circle box-shadow" src="{{$user->avatarUrl()}}" width="172px" height="172px">
</div> </div>
</div> </div>
<div class="col-12 col-md-8 d-flex align-items-center"> <div class="col-12 col-md-8 d-flex align-items-center">
<div class="profile-details"> <div class="profile-details">
<div class="username-bar pb-2 d-flex align-items-center"> <div class="username-bar pb-2 d-flex align-items-center">
<span class="font-weight-ultralight h3">{{$user->username}}</span> <span class="font-weight-ultralight h3">{{$user->username}}</span>
@if(Auth::check() && $is_following == true) @if(Auth::check() && $is_following == true)
<span class="pl-4"> <span class="pl-4">
<form class="follow-form" method="post" action="/i/follow" style="display: inline;" data-id="{{$user->id}}" data-action="unfollow"> <form class="follow-form" method="post" action="/i/follow" style="display: inline;" data-id="{{$user->id}}" data-action="unfollow">
@csrf @csrf
<input type="hidden" name="item" value="{{$user->id}}"> <input type="hidden" name="item" value="{{$user->id}}">
<button class="btn btn-outline-secondary font-weight-bold px-4 py-0" type="submit">Unfollow</button> <button class="btn btn-outline-secondary font-weight-bold px-4 py-0" type="submit">Unfollow</button>
</form> </form>
</span> </span>
@elseif(Auth::check() && $requested == true) @elseif(Auth::check() && $requested == true)
<span class="pl-4"> <span class="pl-4">
<button class="btn btn-outline-secondary font-weight-bold px-4 py-0 disabled" disabled type="button">Follow Requested</button> <button class="btn btn-outline-secondary font-weight-bold px-4 py-0 disabled" disabled type="button">Follow Requested</button>
</span> </span>
@elseif(Auth::check() && $is_following == false) @elseif(Auth::check() && $is_following == false)
<span class="pl-4"> <span class="pl-4">
<form class="follow-form" method="post" action="/i/follow" style="display: inline;" data-id="{{$user->id}}" data-action="follow"> <form class="follow-form" method="post" action="/i/follow" style="display: inline;" data-id="{{$user->id}}" data-action="follow">
@csrf @csrf
<input type="hidden" name="item" value="{{$user->id}}"> <input type="hidden" name="item" value="{{$user->id}}">
<button class="btn btn-primary font-weight-bold px-4 py-0" type="submit">Follow</button> <button class="btn btn-primary font-weight-bold px-4 py-0" type="submit">Follow</button>
</form> </form>
</span> </span>
@endif @endif
<span class="pl-4"> @auth
<i class="fas fa-cog fa-lg text-muted cursor-pointer" data-toggle="modal" data-target="#ctxProfileMenu"></i> <span class="pl-4">
<div class="modal" tabindex="-1" role="dialog" id="ctxProfileMenu"> <i class="fas fa-cog fa-lg text-muted cursor-pointer" data-toggle="modal" data-target="#ctxProfileMenu"></i>
<div class="modal-dialog modal-dialog-centered modal-sm"> <div class="modal" tabindex="-1" role="dialog" id="ctxProfileMenu">
<div class="modal-content"> <div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-body p-0"> <div class="modal-content">
<div class="list-group-item cursor-pointer text-center rounded text-dark" onclick="window.App.util.clipboard('{{$user->url()}}');$('#ctxProfileMenu').modal('hide')"> <div class="modal-body p-0">
Copy Link <div class="list-group-item cursor-pointer text-center rounded text-dark" onclick="window.App.util.clipboard('{{$user->url()}}');$('#ctxProfileMenu').modal('hide')">
</div> Copy Link
@auth </div>
<div class="list-group-item cursor-pointer text-center rounded text-dark" onclick="muteProfile()"> @auth
Mute <div class="list-group-item cursor-pointer text-center rounded text-dark" onclick="muteProfile()">
</div> Mute
<a class="list-group-item cursor-pointer text-center rounded text-dark text-decoration-none" href="i/report?type=user&id={{$user->id}}"> </div>
Report User <a class="list-group-item cursor-pointer text-center rounded text-dark text-decoration-none" href="i/report?type=user&id={{$user->id}}">
</a> Report User
<div class="list-group-item cursor-pointer text-center rounded text-dark" onclick="blockProfile()"> </a>
Block <div class="list-group-item cursor-pointer text-center rounded text-dark" onclick="blockProfile()">
</div> Block
@endauth </div>
<div class="list-group-item cursor-pointer text-center rounded text-muted" onclick="$('#ctxProfileMenu').modal('hide')"> @endauth
Close <div class="list-group-item cursor-pointer text-center rounded text-muted" onclick="$('#ctxProfileMenu').modal('hide')">
</div> Close
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</span> </div>
</div> </span>
<div class="profile-stats pb-3 d-inline-flex lead"> @endauth
<div class="font-weight-light pr-5"> </div>
<span class="font-weight-bold">{{$user->statuses()->whereNull('reblog_of_id')->whereNull('in_reply_to_id')->count()}}</span> </div>
Posts </div>
</div> </div>
</div> </div>
<p class="lead mb-0">
<span class="font-weight-bold">{{$user->name}}</span>
@if($user->remote_url)
<span class="badge badge-info">REMOTE PROFILE</span>
@endif
</p>
<p class="mb-0 lead">{{$user->bio}}</p>
<p class="mb-0"><a href="{{$user->website}}" class="font-weight-bold" rel="me external nofollow noopener" target="_blank">{{str_limit($user->website, 30)}}</a></p>
</div>
</div>
</div>
</div>
</div> </div>
@push('scripts') @push('scripts')
@auth
<script type="text/javascript"> <script type="text/javascript">
function muteProfile() { function muteProfile() {
axios.post('/i/mute', { axios.post('/i/mute', {
type: 'user', type: 'user',
item: '{{$user->id}}' item: '{{$user->id}}'
}).then(res => { }).then(res => {
$('#ctxProfileMenu').modal('hide'); $('#ctxProfileMenu').modal('hide');
$('#ctxProfileMenu').hide(); $('#ctxProfileMenu').hide();
swal('Muted Profile', 'You have successfully muted this profile.', 'success'); swal('Muted Profile', 'You have successfully muted this profile.', 'success');
}); });
} }
function blockProfile() { function blockProfile() {
axios.post('/i/block', { axios.post('/i/block', {
type: 'user', type: 'user',
item: '{{$user->id}}' item: '{{$user->id}}'
}).then(res => { }).then(res => {
$('#ctxProfileMenu').modal('hide'); $('#ctxProfileMenu').modal('hide');
$('#ctxProfileMenu').hide(); $('#ctxProfileMenu').hide();
swal('Blocked Profile', 'You have successfully blocked this profile.', 'success'); swal('Blocked Profile', 'You have successfully blocked this profile.', 'success');
}); });
} }
</script> </script>
@endpush @endauth
@endpush

View file

@ -2,28 +2,28 @@
@section('content') @section('content')
@if (session('error')) @if (session('error'))
<div class="alert alert-danger text-center font-weight-bold mb-0"> <div class="alert alert-danger text-center font-weight-bold mb-0">
{{ session('error') }} {{ session('error') }}
</div> </div>
@endif @endif
@include('profile.partial.private-info') @include('profile.partial.private-info')
<div class="container"> <div class="container">
<div class="profile-timeline mt-2 mt-md-4"> <div class="profile-timeline mt-2 mt-md-4">
<div class="card"> <div class="">
<div class="card-body py-5"> <div class="py-5">
<p class="text-center lead font-weight-bold mb-0"> <p class="text-center lead font-weight-bold">
{{__('profile.privateProfileWarning')}} {{__('profile.privateProfileWarning')}}
</p> </p>
@if(!Auth::check()) @if(!Auth::check())
<p class="text-center mb-0">{{ __('profile.alreadyFollow', ['username'=>$user->username])}}</p> <p class="text-center mb-0">{{ __('profile.alreadyFollow', ['username'=>$user->username])}}</p>
<p class="text-center mb-0"><a href="{{route('login')}}">{{__('Log in')}}</a></p> <p class="text-center mb-0"><a href="{{route('login')}}">{{__('Log in')}}</a></p>
<p class="text-center mb-0">{{__('profile.loginToSeeProfile')}}</p> <p class="text-center mb-0">{{__('profile.loginToSeeProfile')}}</p>
@endif @endif
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@endsection @endsection
@ -34,4 +34,4 @@
@if($user->remote_url) @if($user->remote_url)
<meta name="robots" content="noindex, nofollow"> <meta name="robots" content="noindex, nofollow">
@endif @endif
@endpush @endpush

View file

@ -1,177 +1,172 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{ app()->getLocale() }}"> <html lang="{{ app()->getLocale() }}">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
<title>{{ $title ?? config('app.name', 'Pixelfed') }}</title> <title>{{ $title ?? config('app.name', 'Pixelfed') }}</title>
<meta property="og:site_name" content="{{ config('app.name', 'pixelfed') }}"> <meta property="og:site_name" content="{{ config('app.name', 'pixelfed') }}">
<meta property="og:title" content="{{ $title ?? config('app.name', 'pixelfed') }}"> <meta property="og:title" content="{{ $title ?? config('app.name', 'pixelfed') }}">
<meta property="og:type" content="article"> <meta property="og:type" content="article">
<meta property="og:url" content="{{$status->url()}}"> <meta property="og:url" content="{{$status->url()}}">
<meta name="medium" content="image"> <meta name="medium" content="image">
<meta name="theme-color" content="#10c5f8"> <meta name="theme-color" content="#10c5f8">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<link rel="shortcut icon" type="image/png" href="/img/favicon.png?v=2"> <link rel="shortcut icon" type="image/png" href="/img/favicon.png?v=2">
<link rel="apple-touch-icon" type="image/png" href="/img/favicon.png?v=2"> <link rel="apple-touch-icon" type="image/png" href="/img/favicon.png?v=2">
<link href="{{ mix('css/app.css') }}" rel="stylesheet"> <link href="{{ mix('css/app.css') }}" rel="stylesheet">
<style type="text/css"> <style type="text/css">
body.embed-card { body.embed-card {
background: #fff !important; background: #fff !important;
margin: 0; margin: 0;
padding-bottom: 0; padding-bottom: 0;
} }
.status-card-embed { .status-card-embed {
box-shadow: none; box-shadow: none;
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
} }
</style> </style>
</head> </head>
<body class="bg-white"> <body class="bg-white">
<div class="embed-card"> <div class="embed-card">
@php($item = $status) @php($item = $status)
<div class="card status-card-embed card-md-rounded-0 border"> <div class="card status-card-embed card-md-rounded-0 border">
<div class="card-header d-inline-flex align-items-center bg-white"> <div class="card-header d-inline-flex align-items-center bg-white">
<img src="{{$item->profile->avatarUrl()}}" width="32px" height="32px" target="_blank" style="border-radius: 32px;"> <img src="{{$item->profile->avatarUrl()}}" width="32px" height="32px" target="_blank" style="border-radius: 32px;">
<a class="username font-weight-bold pl-2 text-dark" target="_blank" href="{{$item->profile->url()}}"> <a class="username font-weight-bold pl-2 text-dark" target="_blank" href="{{$item->profile->url()}}">
{{$item->profile->username}} {{$item->profile->username}}
</a> </a>
</div> </div>
<a href="{{$status->url()}}" target="_blank"> <a href="{{$status->url()}}" target="_blank">
@php($status = $item) @php($status = $item)
@switch($status->viewType()) @switch($status->viewType())
@case('photo') @case('photo')
@case('image') @case('image')
@if($status->is_nsfw) @if($status->is_nsfw)
<details class="details-animated"> <details class="details-animated">
<summary> <summary>
<p class="mb-0 lead font-weight-bold">CW / NSFW / Hidden Media</p> <p class="mb-0 lead font-weight-bold">CW / NSFW / Hidden Media</p>
<p class="font-weight-light">(click to show)</p> <p class="font-weight-light">(click to show)</p>
</summary> </summary>
<a class="max-hide-overflow {{$status->firstMedia()->filter_class}}" href="{{$status->url()}}" target="_blank"> <a class="max-hide-overflow {{$status->firstMedia()->filter_class}}" href="{{$status->url()}}" target="_blank">
<img class="card-img-top" src="{{$status->mediaUrl()}}"> <img class="card-img-top" src="{{$status->mediaUrl()}}">
</a> </a>
</details> </details>
@else @else
<div class="{{$status->firstMedia()->filter_class}}"> <div class="{{$status->firstMedia()->filter_class}}">
<img src="{{$status->mediaUrl()}}" width="100%"> <img src="{{$status->mediaUrl()}}" width="100%">
</div> </div>
@endif @endif
@break @break
@case('photo:album') @case('photo:album')
<div id="photo-carousel-wrapper-{{$status->id}}" class="carousel slide carousel-fade mb-n3 " data-ride="carousel"> <div id="photo-carousel-wrapper-{{$status->id}}" class="carousel slide carousel-fade mb-n3 " data-ride="carousel">
<ol class="carousel-indicators"> <ol class="carousel-indicators">
@for($i = 0; $i < $status->media_count; $i++) @for($i = 0; $i < $status->media_count; $i++)
<li data-target="#photo-carousel-wrapper-{{$status->id}}" data-slide-to="{{$i}}" class="{{$i == 0 ? 'active' : ''}}"></li> <li data-target="#photo-carousel-wrapper-{{$status->id}}" data-slide-to="{{$i}}" class="{{$i == 0 ? 'active' : ''}}"></li>
@endfor @endfor
</ol> </ol>
<div class="carousel-inner"> <div class="carousel-inner">
@foreach($status->media()->orderBy('order')->get() as $media) @foreach($status->media()->orderBy('order')->get() as $media)
<div class="carousel-item {{$loop->iteration == 1 ? 'active' : ''}}"> <div class="carousel-item {{$loop->iteration == 1 ? 'active' : ''}}">
<figure class="{{$media->filter_class}}"> <figure class="{{$media->filter_class}}">
<div class="float-right mr-3 badge badge-dark border border-secondary rounded-pill p-2" style="position:absolute;top:8px;right:0;margin-bottom:-20px;">{{$loop->iteration}}/{{$loop->count}}</div> <div class="float-right mr-3 badge badge-dark border border-secondary rounded-pill p-2" style="position:absolute;top:8px;right:0;margin-bottom:-20px;">{{$loop->iteration}}/{{$loop->count}}</div>
<img class="d-block w-100" src="{{$media->url()}}" alt="{{$status->caption}}"> <img class="d-block w-100" src="{{$media->url()}}" alt="{{$status->caption}}">
</figure> </figure>
</div> </div>
@endforeach @endforeach
</div> </div>
<a class="carousel-control-prev" href="#photo-carousel-wrapper-{{$status->id}}" role="button" data-slide="prev"> <a class="carousel-control-prev" href="#photo-carousel-wrapper-{{$status->id}}" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span> <span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span> <span class="sr-only">Previous</span>
</a> </a>
<a class="carousel-control-next" href="#photo-carousel-wrapper-{{$status->id}}" role="button" data-slide="next"> <a class="carousel-control-next" href="#photo-carousel-wrapper-{{$status->id}}" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span> <span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span> <span class="sr-only">Next</span>
</a> </a>
</div> </div>
@break @break
@case('video') @case('video')
@if($status->is_nsfw) @if($status->is_nsfw)
<details class="details-animated"> <details class="details-animated">
<summary> <summary>
<p class="mb-0 lead font-weight-bold">CW / NSFW / Hidden Media</p> <p class="mb-0 lead font-weight-bold">CW / NSFW / Hidden Media</p>
<p class="font-weight-light">(click to show)</p> <p class="font-weight-light">(click to show)</p>
</summary> </summary>
<div class="embed-responsive embed-responsive-16by9"> <div class="embed-responsive embed-responsive-16by9">
<video class="video" preload="none" controls loop> <video class="video" preload="none" controls loop>
<source src="{{$status->firstMedia()->url()}}" type="{{$status->firstMedia()->mime}}"> <source src="{{$status->firstMedia()->url()}}" type="{{$status->firstMedia()->mime}}">
</video> </video>
</div> </div>
</details> </details>
@else @else
<div class="embed-responsive embed-responsive-16by9"> <div class="embed-responsive embed-responsive-16by9">
<video class="video" preload="none" controls loop> <video class="video" preload="none" controls loop>
<source src="{{$status->firstMedia()->url()}}" type="{{$status->firstMedia()->mime}}"> <source src="{{$status->firstMedia()->url()}}" type="{{$status->firstMedia()->mime}}">
</video> </video>
</div> </div>
@endif @endif
@break @break
@case('video-album') @case('video-album')
@if($status->is_nsfw) @if($status->is_nsfw)
<details class="details-animated"> <details class="details-animated">
<summary> <summary>
<p class="mb-0 lead font-weight-bold">CW / NSFW / Hidden Media</p> <p class="mb-0 lead font-weight-bold">CW / NSFW / Hidden Media</p>
<p class="font-weight-light">(click to show)</p> <p class="font-weight-light">(click to show)</p>
</summary> </summary>
<div class="embed-responsive embed-responsive-16by9"> <div class="embed-responsive embed-responsive-16by9">
<video class="video" preload="none" controls loop> <video class="video" preload="none" controls loop>
<source src="{{$status->firstMedia()->url()}}" type="{{$status->firstMedia()->mime}}"> <source src="{{$status->firstMedia()->url()}}" type="{{$status->firstMedia()->mime}}">
</video> </video>
</div> </div>
</details> </details>
@else @else
<div class="embed-responsive embed-responsive-16by9"> <div class="embed-responsive embed-responsive-16by9">
<video class="video" preload="none" controls loop> <video class="video" preload="none" controls loop>
<source src="{{$status->firstMedia()->url()}}" type="{{$status->firstMedia()->mime}}"> <source src="{{$status->firstMedia()->url()}}" type="{{$status->firstMedia()->mime}}">
</video> </video>
</div> </div>
@endif @endif
@break @break
@endswitch @endswitch
</a> </a>
@if($layout != 'compact') @if($layout != 'compact')
<div class="card-body"> <div class="card-body">
<div class="view-more mb-2"> <div class="view-more mb-2">
<a class="font-weight-bold" href="{{$status->url()}}" target="_blank">View More on Pixelfed</a> <a class="font-weight-bold" href="{{$status->url()}}" target="_blank">View More on Pixelfed</a>
</div> </div>
<hr> <hr>
@if($showLikes) <div class="caption">
<div class="likes font-weight-bold pb-2"> <p class="my-0">
<span class="like-count">{{$item->likes_count}}</span> likes <span class="username font-weight-bold">
</div> <bdi><a class="text-dark" href="{{$item->profile->url()}}" target="_blank">{{$item->profile->username}}</a></bdi>
@endif </span>
<div class="caption"> @if($showCaption)
<p class="my-0"> <span class="caption-container">{!! $item->rendered ?? e($item->caption) !!}</span>
<span class="username font-weight-bold"> @endif
<bdi><a class="text-dark" href="{{$item->profile->url()}}" target="_blank">{{$item->profile->username}}</a></bdi> </p>
</span> </div>
@if($showCaption) </div>
<span class="caption-container">{!! $item->rendered ?? e($item->caption) !!}</span> @endif
@endif <div class="card-footer bg-white d-inline-flex justify-content-between align-items-center">
</p> <div class="timestamp">
</div> <p class="small text-uppercase mb-0"><a href="{{$item->url()}}" class="text-muted" target="_blank">{{$item->created_at->diffForHumans()}}</a></p>
</div> </div>
@endif <div>
<div class="card-footer bg-white d-inline-flex justify-content-between align-items-center"> <a class="small font-weight-bold text-muted pr-1" href="{{config('app.url')}}" target="_blank">{{config('pixelfed.domain.app')}}</a>
<div class="timestamp"> <img src="/img/pixelfed-icon-color.svg" width="26px">
<p class="small text-uppercase mb-0"><a href="{{$item->url()}}" class="text-muted" target="_blank">{{$item->created_at->diffForHumans()}}</a></p> </div>
</div> </div>
<div> </div>
<a class="small font-weight-bold text-muted pr-1" href="{{config('app.url')}}" target="_blank">{{config('pixelfed.domain.app')}}</a> </div>
<img src="/img/pixelfed-icon-color.svg" width="26px"> <script type="text/javascript">window.addEventListener("message",e=>{const t=e.data||{};window.parent&&"setHeight"===t.type&&window.parent.postMessage({type:"setHeight",id:t.id,height:document.getElementsByTagName("html")[0].scrollHeight},"*")});</script>
</div> <script type="text/javascript">document.querySelectorAll('.caption-container a').forEach(function(i) {i.setAttribute('target', '_blank');});</script>
</div> <script type="text/javascript" src="{{ mix('js/manifest.js') }}"></script>
</div> <script type="text/javascript" src="{{ mix('js/vendor.js') }}"></script>
</div> <script type="text/javascript" src="{{ mix('js/app.js') }}"></script>
<script type="text/javascript">window.addEventListener("message",e=>{const t=e.data||{};window.parent&&"setHeight"===t.type&&window.parent.postMessage({type:"setHeight",id:t.id,height:document.getElementsByTagName("html")[0].scrollHeight},"*")});</script>
<script type="text/javascript">document.querySelectorAll('.caption-container a').forEach(function(i) {i.setAttribute('target', '_blank');});</script>
<script type="text/javascript" src="{{ mix('js/manifest.js') }}"></script>
<script type="text/javascript" src="{{ mix('js/vendor.js') }}"></script>
<script type="text/javascript" src="{{ mix('js/app.js') }}"></script>
</body> </body>
</html> </html>

View file

@ -25,11 +25,5 @@
@push('scripts') @push('scripts')
<script type="text/javascript" src="{{ mix('js/status.js') }}"></script> <script type="text/javascript" src="{{ mix('js/status.js') }}"></script>
<script type="text/javascript"> <script type="text/javascript">App.boot();</script>
$(document).ready(function() {
new Vue({
el: '#content'
});
});
</script>
@endpush @endpush

View file

@ -72,7 +72,7 @@ Route::group(['prefix' => 'api'], function() use($middleware) {
Route::get('timelines/home', 'Api\ApiV1Controller@timelineHome')->middleware($middleware); Route::get('timelines/home', 'Api\ApiV1Controller@timelineHome')->middleware($middleware);
Route::get('timelines/public', 'Api\ApiV1Controller@timelinePublic'); Route::get('timelines/public', 'Api\ApiV1Controller@timelinePublic')->middleware($middleware);
Route::get('timelines/tag/{hashtag}', 'Api\ApiV1Controller@timelineHashtag'); Route::get('timelines/tag/{hashtag}', 'Api\ApiV1Controller@timelineHashtag');
}); });
Route::group(['prefix' => 'stories'], function () use($middleware) { Route::group(['prefix' => 'stories'], function () use($middleware) {