diff --git a/CHANGELOG.md b/CHANGELOG.md index 586193a53..81e36c387 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,13 @@ - Updated MediaStorageService, improve head checks to fix failed jobs. ([1769cdfd](https://github.com/pixelfed/pixelfed/commit/1769cdfd)) - Updated user admin, remove expensive db query and add search. ([8feeadbf](https://github.com/pixelfed/pixelfed/commit/8feeadbf)) - Updated Compose apis, prevent private accounts from posting public or unlisted scopes. ([f53bfa6f](https://github.com/pixelfed/pixelfed/commit/f53bfa6f)) +- Updated font icons, use font-display:swap. ([77d4353a](https://github.com/pixelfed/pixelfed/commit/77d4353a)) +- Updated ComposeModal, limit visibility scope for private accounts. ([001d4105](https://github.com/pixelfed/pixelfed/commit/001d4105)) +- Updated ComposeController, add autocomplete apis for hashtags and mentions. ([f0e48a09](https://github.com/pixelfed/pixelfed/commit/f0e48a09)) +- Updated StatusController, invalidate profile embed cache on status delete. ([9c8a87c3](https://github.com/pixelfed/pixelfed/commit/9c8a87c3)) +- Updated moderation api, invalidate profile embed. ([b2501bfc](https://github.com/pixelfed/pixelfed/commit/b2501bfc)) +- Updated Nodeinfo util, use last_active_at for monthly active user count. ([d200c12c](https://github.com/pixelfed/pixelfed/commit/d200c12c)) +- Updated PhotoPresenter, add width and height to images. ([3f8202e2](https://github.com/pixelfed/pixelfed/commit/3f8202e2)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.10.10 (2021-01-28)](https://github.com/pixelfed/pixelfed/compare/v0.10.9...v0.10.10) diff --git a/app/Http/Controllers/ComposeController.php b/app/Http/Controllers/ComposeController.php index a51e2ab04..001fc66c1 100644 --- a/app/Http/Controllers/ComposeController.php +++ b/app/Http/Controllers/ComposeController.php @@ -7,6 +7,7 @@ use Auth, Cache, Storage, URL; use Carbon\Carbon; use App\{ Avatar, + Hashtag, Like, Media, MediaTag, @@ -304,6 +305,72 @@ class ComposeController extends Controller return $places; } + public function searchMentionAutocomplete(Request $request) + { + abort_if(!$request->user(), 403); + + $this->validate($request, [ + 'q' => 'required|string|min:2|max:50' + ]); + + $q = $request->input('q'); + + if(Str::of($q)->startsWith('@')) { + if(strlen($q) < 3) { + return []; + } + } + + $blocked = UserFilter::whereFilterableType('App\Profile') + ->whereFilterType('block') + ->whereFilterableId($request->user()->profile_id) + ->pluck('user_id'); + + $blocked->push($request->user()->profile_id); + + $results = Profile::select('id','domain','username') + ->whereNotIn('id', $blocked) + ->where('username','like','%'.$q.'%') + ->groupBy('domain') + ->limit(15) + ->get() + ->map(function($profile) { + $username = $profile->domain ? substr($profile->username, 1) : $profile->username; + return [ + 'key' => '@' . str_limit($username, 30), + 'value' => $username, + ]; + }); + + return $results; + } + + public function searchHashtagAutocomplete(Request $request) + { + abort_if(!$request->user(), 403); + + $this->validate($request, [ + 'q' => 'required|string|min:2|max:50' + ]); + + $q = $request->input('q'); + + $results = Hashtag::select('slug') + ->where('slug', 'like', '%'.$q.'%') + ->whereIsNsfw(false) + ->whereIsBanned(false) + ->limit(5) + ->get() + ->map(function($tag) { + return [ + 'key' => '#' . $tag->slug, + 'value' => $tag->slug + ]; + }); + + return $results; + } + public function store(Request $request) { $this->validate($request, [ diff --git a/app/Http/Controllers/InternalApiController.php b/app/Http/Controllers/InternalApiController.php index 1a0af7d1a..2df78dee3 100644 --- a/app/Http/Controllers/InternalApiController.php +++ b/app/Http/Controllers/InternalApiController.php @@ -132,13 +132,15 @@ class InternalApiController extends Controller public function statusReplies(Request $request, int $id) { + $this->validate($request, [ + 'limit' => 'nullable|int|min:1|max:6' + ]); $parent = Status::whereScope('public')->findOrFail($id); - + $limit = $request->input('limit') ?? 3; $children = Status::whereInReplyToId($parent->id) ->orderBy('created_at', 'desc') - ->take(3) + ->take($limit) ->get(); - $resource = new Fractal\Resource\Collection($children, new StatusTransformer()); $res = $this->fractal->createData($resource)->toArray(); @@ -310,6 +312,10 @@ class InternalApiController extends Controller } break; } + + Cache::forget('_api:statuses:recent_9:' . $status->profile_id); + Cache::forget('profile:embed:' . $status->profile_id); + return ['msg' => 200]; } diff --git a/app/Http/Controllers/PublicApiController.php b/app/Http/Controllers/PublicApiController.php index 1402940ef..c1e8ef9f0 100644 --- a/app/Http/Controllers/PublicApiController.php +++ b/app/Http/Controllers/PublicApiController.php @@ -166,7 +166,7 @@ class PublicApiController extends Controller ->whereNull('reblog_of_id') ->whereIn('scope', $scope) ->whereNotIn('profile_id', $filtered) - ->select('id', 'caption', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') + ->select('id', 'caption', 'local', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') ->where('id', '>=', $request->min_id) ->orderBy('id', 'desc') ->paginate($limit); @@ -176,7 +176,7 @@ class PublicApiController extends Controller ->whereNull('reblog_of_id') ->whereIn('scope', $scope) ->whereNotIn('profile_id', $filtered) - ->select('id', 'caption', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') + ->select('id', 'caption', 'local', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') ->where('id', '<=', $request->max_id) ->orderBy('id', 'desc') ->paginate($limit); @@ -186,7 +186,7 @@ class PublicApiController extends Controller ->whereNull('reblog_of_id') ->whereIn('scope', $scope) ->whereNotIn('profile_id', $filtered) - ->select('id', 'caption', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') + ->select('id', 'caption', 'local', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') ->orderBy('id', 'desc') ->paginate($limit); } diff --git a/app/Http/Controllers/StatusController.php b/app/Http/Controllers/StatusController.php index 17ce59fb2..cda8c77ee 100644 --- a/app/Http/Controllers/StatusController.php +++ b/app/Http/Controllers/StatusController.php @@ -74,6 +74,12 @@ class StatusController extends Controller } $template = $status->in_reply_to_id ? 'status.reply' : 'status.show'; + // $template = $status->type === 'video' && + // $request->has('video_beta') && + // $request->video_beta == 1 && + // $request->user() ? + // 'status.show_video' : 'status.show'; + return view($template, compact('user', 'status')); } @@ -212,6 +218,7 @@ class StatusController extends Controller Cache::forget('_api:statuses:recent_9:' . $status->profile_id); Cache::forget('profile:status_count:' . $status->profile_id); + Cache::forget('profile:embed:' . $status->profile_id); StatusService::del($status->id); if ($status->profile_id == $user->profile->id || $user->is_admin == true) { Cache::forget('profile:status_count:'.$status->profile_id); diff --git a/app/Util/Site/Nodeinfo.php b/app/Util/Site/Nodeinfo.php index 1c9920441..022615e37 100644 --- a/app/Util/Site/Nodeinfo.php +++ b/app/Util/Site/Nodeinfo.php @@ -12,24 +12,31 @@ class Nodeinfo { { $res = Cache::remember('api:nodeinfo', now()->addMinutes(15), function () { $activeHalfYear = Cache::remember('api:nodeinfo:ahy', now()->addHours(12), function() { + // todo: replace with last_active_at after July 9, 2021 (96afc3e781) $count = collect([]); $likes = Like::select('profile_id')->with('actor')->where('created_at', '>', now()->subMonths(6)->toDateTimeString())->groupBy('profile_id')->get()->filter(function($like) {return $like->actor && $like->actor->domain == null;})->pluck('profile_id')->toArray(); $count = $count->merge($likes); $statuses = Status::select('profile_id')->whereLocal(true)->where('created_at', '>', now()->subMonths(6)->toDateTimeString())->groupBy('profile_id')->pluck('profile_id')->toArray(); $count = $count->merge($statuses); - $profiles = Profile::select('id')->whereNull('domain')->where('created_at', '>', now()->subMonths(6)->toDateTimeString())->groupBy('id')->pluck('id')->toArray(); + $profiles = User::select('profile_id', 'last_active_at') + ->whereNotNull('last_active_at') + ->where('last_active_at', '>', now()->subMonths(6)) + ->pluck('profile_id') + ->toArray(); + $newProfiles = User::select('profile_id', 'last_active_at', 'created_at') + ->whereNull('last_active_at') + ->where('created_at', '>', now()->subMonths(6)) + ->pluck('profile_id') + ->toArray(); + $count = $count->merge($newProfiles); $count = $count->merge($profiles); return $count->unique()->count(); }); - $activeMonth = Cache::remember('api:nodeinfo:am', now()->addHours(12), function() { - $count = collect([]); - $likes = Like::select('profile_id')->where('created_at', '>', now()->subMonths(1)->toDateTimeString())->groupBy('profile_id')->get()->filter(function($like) {return $like->actor && $like->actor->domain == null;})->pluck('profile_id')->toArray(); - $count = $count->merge($likes); - $statuses = Status::select('profile_id')->whereLocal(true)->where('created_at', '>', now()->subMonths(1)->toDateTimeString())->groupBy('profile_id')->pluck('profile_id')->toArray(); - $count = $count->merge($statuses); - $profiles = Profile::select('id')->whereNull('domain')->where('created_at', '>', now()->subMonths(1)->toDateTimeString())->groupBy('id')->pluck('id')->toArray(); - $count = $count->merge($profiles); - return $count->unique()->count(); + $activeMonth = Cache::remember('api:nodeinfo:am', now()->addHours(2), function() { + return User::select('last_active_at') + ->where('last_active_at', '>', now()->subMonths(1)) + ->orWhere('created_at', '>', now()->subMonths(1)) + ->count(); }); return [ 'metadata' => [ diff --git a/public/css/app.css b/public/css/app.css index 9bde4d09c..4d055ba6e 100644 Binary files a/public/css/app.css and b/public/css/app.css differ diff --git a/public/css/appdark.css b/public/css/appdark.css index b05ad48a6..4316e2ca4 100644 Binary files a/public/css/appdark.css and b/public/css/appdark.css differ diff --git a/public/css/landing.css b/public/css/landing.css index 8403b1bde..39b77f2c9 100644 Binary files a/public/css/landing.css and b/public/css/landing.css differ diff --git a/public/css/quill.css b/public/css/quill.css index f11350023..a1b638f27 100644 Binary files a/public/css/quill.css and b/public/css/quill.css differ diff --git a/public/js/compose.js b/public/js/compose.js index eaf9b1b50..2eea9e6e7 100644 Binary files a/public/js/compose.js and b/public/js/compose.js differ diff --git a/public/js/profile.js b/public/js/profile.js index e7a1361d3..e74c11967 100644 Binary files a/public/js/profile.js and b/public/js/profile.js differ diff --git a/public/js/rempos.js b/public/js/rempos.js index a5cd7c1ba..8c3861383 100644 Binary files a/public/js/rempos.js and b/public/js/rempos.js differ diff --git a/public/js/status.js b/public/js/status.js index 78d2c8efe..78bfd4b41 100644 Binary files a/public/js/status.js and b/public/js/status.js differ diff --git a/public/js/timeline.js b/public/js/timeline.js index f6c7c2080..e9adcae1d 100644 Binary files a/public/js/timeline.js and b/public/js/timeline.js differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 167da845a..ef93cf5dd 100644 Binary files a/public/mix-manifest.json and b/public/mix-manifest.json differ diff --git a/resources/assets/js/components/ComposeModal.vue b/resources/assets/js/components/ComposeModal.vue index 729cc03aa..f700f0373 100644 --- a/resources/assets/js/components/ComposeModal.vue +++ b/resources/assets/js/components/ComposeModal.vue @@ -479,9 +479,26 @@
-
Public
-
Unlisted
-
Followers Only
+
+ Public +
+
+ Unlisted +
+
+ Followers Only +
@@ -641,7 +658,7 @@ export default { return { config: window.App.config, pageLoading: false, - profile: {}, + profile: window._sharedData.curUser, composeText: '', composeTextLength: 0, nsfw: false, @@ -708,20 +725,19 @@ export default { methods: { fetchProfile() { - let self = this; - if(window._sharedData.curUser) { - self.profile = window._sharedData.curUser; - if(self.profile.locked == true) { - self.visibility = 'private'; - self.visibilityTag = 'Followers Only'; + if(window._sharedData.curUser.id) { + this.profile = window._sharedData.curUser; + if(this.profile.locked == true) { + this.visibility = 'private'; + this.visibilityTag = 'Followers Only'; } } else { axios.get('/api/pixelfed/v1/accounts/verify_credentials').then(res => { - self.profile = res.data; - window.pixelfed.currentUser = res.data; - if(res.data.locked == true) { - self.visibility = 'private'; - self.visibilityTag = 'Followers Only'; + window._sharedData.currentUser = res.data; + this.profile = res.data; + if(this.profile.locked == true) { + this.visibility = 'private'; + this.visibilityTag = 'Followers Only'; } }).catch(err => { }); diff --git a/resources/assets/js/components/Timeline.vue b/resources/assets/js/components/Timeline.vue index c3dc423d0..1b13867df 100644 --- a/resources/assets/js/components/Timeline.vue +++ b/resources/assets/js/components/Timeline.vue @@ -1,416 +1,14 @@ - - + + + diff --git a/resources/assets/js/components/presenter/PhotoPresenter.vue b/resources/assets/js/components/presenter/PhotoPresenter.vue index 0ac4ef2e2..85f23e8a1 100644 --- a/resources/assets/js/components/presenter/PhotoPresenter.vue +++ b/resources/assets/js/components/presenter/PhotoPresenter.vue @@ -23,7 +23,13 @@
- +
@@ -67,6 +73,24 @@ toggleContentWarning(status) { this.$emit('togglecw'); + }, + + width() { + if( !this.status.media_attachments[0].meta || + !this.status.media_attachments[0].meta.original || + !this.status.media_attachments[0].meta.original.width ) { + return; + } + return this.status.media_attachments[0].meta.original.width; + }, + + height() { + if( !this.status.media_attachments[0].meta || + !this.status.media_attachments[0].meta.original || + !this.status.media_attachments[0].meta.original.height ) { + return; + } + return this.status.media_attachments[0].meta.original.height; } } } diff --git a/resources/assets/sass/lib/fontawesome.scss b/resources/assets/sass/lib/fontawesome.scss index c710c14b1..2a70ab85e 100644 --- a/resources/assets/sass/lib/fontawesome.scss +++ b/resources/assets/sass/lib/fontawesome.scss @@ -4255,7 +4255,7 @@ readers do not read off random characters that represent icons */ font-family: 'Font Awesome 5 Brands'; font-style: normal; font-weight: normal; - font-display: auto; + font-display: swap; src: url("/fonts/fa-brands-400.eot"); src: url("/fonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("/fonts/fa-brands-400.woff2") format("woff2"), url("/fonts/fa-brands-400.woff") format("woff"), url("/fonts/fa-brands-400.ttf") format("truetype"), url("/fonts/fa-brands-400.svg#fontawesome") format("svg"); } @@ -4265,7 +4265,7 @@ readers do not read off random characters that represent icons */ font-family: 'Font Awesome 5 Free'; font-style: normal; font-weight: 400; - font-display: auto; + font-display: swap; src: url("/fonts/fa-regular-400.eot"); src: url("/fonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), url("/fonts/fa-regular-400.woff2") format("woff2"), url("/fonts/fa-regular-400.woff") format("woff"), url("/fonts/fa-regular-400.ttf") format("truetype"), url("/fonts/fa-regular-400.svg#fontawesome") format("svg"); } @@ -4276,7 +4276,7 @@ readers do not read off random characters that represent icons */ font-family: 'Font Awesome 5 Free'; font-style: normal; font-weight: 900; - font-display: auto; + font-display: swap; src: url("/fonts/fa-solid-900.eot"); src: url("/fonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("/fonts/fa-solid-900.woff2") format("woff2"), url("/fonts/fa-solid-900.woff") format("woff"), url("/fonts/fa-solid-900.ttf") format("truetype"), url("/fonts/fa-solid-900.svg#fontawesome") format("svg"); } diff --git a/routes/web.php b/routes/web.php index 3606ae93d..91b088a90 100644 --- a/routes/web.php +++ b/routes/web.php @@ -113,6 +113,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::delete('/media/delete', 'ComposeController@mediaDelete'); Route::get('/search/tag', 'ComposeController@searchTag'); Route::get('/search/location', 'ComposeController@searchLocation'); + Route::get('/search/mention', 'ComposeController@searchMentionAutocomplete'); + Route::get('/search/hashtag', 'ComposeController@searchHashtagAutocomplete'); Route::post('/publish', 'ComposeController@store') ->middleware('throttle:maxPostsPerHour,60')