diff --git a/app/Jobs/HomeFeedPipeline/FeedInsertRemotePipeline.php b/app/Jobs/HomeFeedPipeline/FeedInsertRemotePipeline.php index 97ca56e94..006dfaced 100644 --- a/app/Jobs/HomeFeedPipeline/FeedInsertRemotePipeline.php +++ b/app/Jobs/HomeFeedPipeline/FeedInsertRemotePipeline.php @@ -16,11 +16,23 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Log; +use Throwable; class FeedInsertRemotePipeline implements ShouldBeUniqueUntilProcessing, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + /** + * Remote statuses published more than this many days ago are not + * inserted into home feeds. + * + * The home timeline is scored by status id, and a remote status gets + * its id when we first store it, not when it was published. Without + * this guard an old post that is fetched for the first time (a boost, + * an edit, a reply to it) lands at the top of every follower's feed. + */ + public const MAX_AGE_DAYS = 7; + protected $sid; protected $pid; @@ -101,6 +113,10 @@ class FeedInsertRemotePipeline implements ShouldBeUniqueUntilProcessing, ShouldQ return; } + if (self::isTooOld($status)) { + return; + } + $ids = FollowerService::localFollowerIds($pid); if (! $ids || ! count($ids)) { @@ -132,4 +148,19 @@ class FeedInsertRemotePipeline implements ShouldBeUniqueUntilProcessing, ShouldQ } } } + + public static function isTooOld(string|\DateTimeInterface|null $createdAt): bool + { + if ($createdAt === null || $createdAt === '') { + return true; + } + + try { + $published = now()->parse($createdAt); + } catch (Throwable) { + return true; + } + + return $published->lt(now()->subDays(self::MAX_AGE_DAYS)); + } } diff --git a/app/Util/ActivityPub/Helpers.php b/app/Util/ActivityPub/Helpers.php index 1c87aa75c..6c91d2eab 100644 --- a/app/Util/ActivityPub/Helpers.php +++ b/app/Util/ActivityPub/Helpers.php @@ -989,7 +989,8 @@ class Helpers { if ( config('instance.timeline.network.cached') && - self::isEligibleForNetwork($status) + self::isEligibleForNetwork($status) && + ! FeedInsertRemotePipeline::isTooOld($status->created_at) ) { $urlDomain = parse_url($url, PHP_URL_HOST); $filteredDomains = self::getFilteredDomains(); diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 1fd0d01ac..1e04dd406 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -168,64 +168,87 @@
@csrf -
+
- - + + has('email')) aria-invalid="true" aria-describedby="emailError" @endif + required + autofocus> @if ($errors->has('email')) - + {{ $errors->first('email') }} @endif - -
-
+
- +
- + has('password')) aria-invalid="true" aria-describedby="passwordError" @endif + required>
@if ($errors->has('password')) - + {{ $errors->first('password') }} @endif - -

- - {{ __('auth.forgot') }} - -

+ +
-
-
@@ -340,6 +363,7 @@ const reveal = function() { password.type = 'text'; + toggle.setAttribute('aria-pressed', 'true'); if (icon) { icon.classList.remove('fa-eye'); icon.classList.add('fa-eye-slash'); @@ -348,13 +372,13 @@ const conceal = function() { password.type = 'password'; + toggle.setAttribute('aria-pressed', 'false'); if (icon) { icon.classList.remove('fa-eye-slash'); icon.classList.add('fa-eye'); } }; - // Press and hold to reveal; release (or leave/blur) to hide. toggle.addEventListener('mousedown', function(e) { e.preventDefault(); reveal(); @@ -366,11 +390,12 @@ toggle.addEventListener('touchstart', function(e) { e.preventDefault(); reveal(); - }, { passive: false }); + }, { + passive: false + }); toggle.addEventListener('touchend', conceal); toggle.addEventListener('touchcancel', conceal); - // Keyboard: reveal while Space/Enter is held, hide on release/blur. toggle.addEventListener('keydown', function(e) { if (e.key === ' ' || e.key === 'Enter' || e.key === 'Spacebar') { e.preventDefault(); @@ -384,7 +409,6 @@ }); toggle.addEventListener('blur', conceal); - // Safety net: never leave the password visible on tab-away. document.addEventListener('visibilitychange', function() { if (document.hidden) { conceal();