R4.3: Karten-Metadaten-Logik als PressRelease-Praesentations-Helper

localizedCategoryName/cardImageUrl/teaserText/wordCount/readTimeMinutes/
compactPublishedLabel ersetzen die 5x kopierte @php-Logik der
Kartenkomponenten; Lesezeit-Formel vereinheitlicht (ceil -> round in
HomePageData-Spotlight). Tests: Web/PressReleaseCardHelpersTest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-10 11:57:26 +00:00
parent 459b339a9b
commit 01e175e138
4 changed files with 137 additions and 3 deletions

View file

@ -26,6 +26,7 @@ use Illuminate\Pagination\Paginator;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
class PressRelease extends Model
{
@ -148,6 +149,77 @@ class PressRelease extends Model
: null;
}
/**
* Kategorie-Name in der Anzeigesprache, Fallback auf die erste
* vorhandene Übersetzung. Zentrale Ablöse der zuvor in jeder
* Kartenkomponente kopierten Auflösung (R4.3).
*/
public function localizedCategoryName(?string $locale = null): ?string
{
$locale ??= app()->getLocale();
$translation = $this->category?->translations?->firstWhere('locale', $locale)
?? $this->category?->translations?->first();
return $translation?->name;
}
/**
* Bild-URL für Kartenansichten: card-Variante des ersten Bildes,
* Fallback Original. Null bei bildlosen PMs (dann greift
* chosenPlaceholderUrl() bzw. der generische Fallback der Komponente).
*/
public function cardImageUrl(): ?string
{
$image = $this->images?->first();
return $image?->variantUrl('card') ?? $image?->url();
}
/**
* Tag-freier, whitespace-normalisierter Anriss des PM-Textes.
*/
public function teaserText(int $limit = 180): string
{
return (string) Str::of(strip_tags((string) $this->text))->squish()->limit($limit);
}
public function wordCount(): int
{
return str_word_count(strip_tags((string) $this->text));
}
/**
* Lesezeit in Minuten (200 Wörter/Minute, mindestens 1).
*/
public function readTimeMinutes(): int
{
return max(1, (int) round($this->wordCount() / 200));
}
/**
* Kompakter Zeitstempel der Kartenkomponenten: heute „HH:MM",
* gestern lokalisiert, sonst Kurzdatum. Null ohne published_at.
*/
public function compactPublishedLabel(): ?string
{
$publishedAt = $this->published_at;
if (! $publishedAt) {
return null;
}
if ($publishedAt->isToday()) {
return $publishedAt->format('H:i');
}
if ($publishedAt->isYesterday()) {
return __('web.shared.yesterday');
}
return $publishedAt->isoFormat('D. MMM');
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);

View file

@ -206,7 +206,6 @@ class HomePageData
->limit(3)
->get()
->map(static function (PressRelease $release) use ($spotlightTranslation): array {
$words = str_word_count(strip_tags((string) $release->text));
$publishedAt = $release->published_at;
return [
@ -216,7 +215,7 @@ class HomePageData
'title' => $release->title,
'company' => $release->company?->name ?? '',
'city' => null,
'minutes' => max(1, (int) ceil($words / 200)),
'minutes' => $release->readTimeMinutes(),
'href' => $release->slug ? route('release.detail', ['slug' => $release->slug]) : '#',
];
});