b2in/packages/flux-cms/components/resources/views/livewire/frontend/blog-post.blade.php
2025-10-20 17:50:35 +02:00

339 lines
No EOL
15 KiB
PHP

<article class="flux-cms-blog-post max-w-4xl mx-auto">
{{-- Post Header --}}
<header class="blog-post-header mb-8">
{{-- Featured Image --}}
@if($post->getFeaturedImage())
<div class="aspect-w-16 aspect-h-9 mb-8">
<img src="{{ $post->getFeaturedImageUrl('hero') }}"
alt="{{ $post->getTranslation('title', app()->getLocale()) }}"
class="w-full h-64 md:h-96 object-cover rounded-lg">
</div>
@endif
{{-- Category & Featured Badge --}}
<div class="flex items-center justify-between mb-4">
<div class="flex items-center space-x-3">
@if($post->category)
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
{{ $post->category }}
</span>
@endif
@if($post->is_featured)
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-yellow-100 text-yellow-800">
Featured
</span>
@endif
</div>
{{-- Share Buttons --}}
@if($showShareButtons)
<div class="flex items-center space-x-2">
<span class="text-sm text-gray-500">Share:</span>
<a href="https://twitter.com/intent/tweet?url={{ urlencode(request()->url()) }}&text={{ urlencode($post->getTranslation('title', app()->getLocale())) }}"
target="_blank"
class="text-gray-400 hover:text-blue-500 transition-colors">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/>
</svg>
</a>
<a href="https://www.facebook.com/sharer/sharer.php?u={{ urlencode(request()->url()) }}"
target="_blank"
class="text-gray-400 hover:text-blue-600 transition-colors">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
</svg>
</a>
<a href="https://www.linkedin.com/sharing/share-offsite/?url={{ urlencode(request()->url()) }}"
target="_blank"
class="text-gray-400 hover:text-blue-700 transition-colors">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
</svg>
</a>
</div>
@endif
</div>
{{-- Title --}}
<h1 class="text-3xl md:text-4xl lg:text-5xl font-bold text-gray-900 leading-tight mb-6">
{{ $post->getTranslation('title', app()->getLocale()) }}
</h1>
{{-- Meta Information --}}
<div class="flex flex-wrap items-center gap-4 text-sm text-gray-600 mb-6">
@if($showAuthor && $post->author)
<div class="flex items-center space-x-2">
@if($post->author->profile_photo_url ?? false)
<img src="{{ $post->author->profile_photo_url }}"
alt="{{ $post->author->name }}"
class="w-8 h-8 rounded-full">
@endif
<span>By <strong>{{ $post->author->name }}</strong></span>
</div>
@endif
@if($showDate)
<time datetime="{{ $post->published_at->toISOString() }}"
class="flex items-center space-x-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
<span>{{ $post->published_at->format('F j, Y') }}</span>
</time>
@endif
@if($showReadingTime)
<div class="flex items-center space-x-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span>{{ $post->reading_time }} min read</span>
</div>
@endif
@if($showUpdatedDate && $post->updated_at->gt($post->published_at))
<div class="flex items-center space-x-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
<span>Updated {{ $post->updated_at->format('F j, Y') }}</span>
</div>
@endif
</div>
{{-- Excerpt --}}
@if($showExcerpt && $post->excerpt)
<div class="text-lg text-gray-600 leading-relaxed border-l-4 border-blue-500 pl-6 italic">
{{ $post->getTranslation('excerpt', app()->getLocale()) }}
</div>
@endif
</header>
{{-- Post Content --}}
<div class="blog-post-content prose prose-lg max-w-none">
{!! $post->getTranslation('content', app()->getLocale()) !!}
</div>
{{-- Tags --}}
@if($showTags && $post->tags && count($post->tags) > 0)
<div class="mt-8 pt-8 border-t border-gray-200">
<h3 class="text-sm font-medium text-gray-900 mb-3">Tags</h3>
<div class="flex flex-wrap gap-2">
@foreach($post->tags as $tag)
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-gray-100 text-gray-800 hover:bg-gray-200 transition-colors">
#{{ $tag }}
</span>
@endforeach
</div>
</div>
@endif
{{-- Author Bio --}}
@if($showAuthorBio && $post->author)
<div class="mt-12 pt-8 border-t border-gray-200">
<div class="flex items-start space-x-4">
@if($post->author->profile_photo_url ?? false)
<img src="{{ $post->author->profile_photo_url }}"
alt="{{ $post->author->name }}"
class="w-16 h-16 rounded-full">
@endif
<div>
<h3 class="text-lg font-semibold text-gray-900">{{ $post->author->name }}</h3>
@if($post->author->bio ?? false)
<p class="text-gray-600 mt-1">{{ $post->author->bio }}</p>
@endif
</div>
</div>
</div>
@endif
{{-- Navigation to Other Posts --}}
@if($showNavigation && ($previousPost || $nextPost))
<nav class="mt-12 pt-8 border-t border-gray-200">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@if($previousPost)
<a href="{{ $previousPost->getUrl() }}" class="group">
<div class="flex items-center space-x-3 p-4 border border-gray-200 rounded-lg hover:border-gray-300 transition-colors">
<svg class="w-5 h-5 text-gray-400 group-hover:text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
</svg>
<div>
<div class="text-sm text-gray-500">Previous</div>
<div class="font-medium text-gray-900 group-hover:text-blue-600">
{{ str_limit($previousPost->getTranslation('title', app()->getLocale()), 50) }}
</div>
</div>
</div>
</a>
@endif
@if($nextPost)
<a href="{{ $nextPost->getUrl() }}" class="group">
<div class="flex items-center justify-end space-x-3 p-4 border border-gray-200 rounded-lg hover:border-gray-300 transition-colors">
<div class="text-right">
<div class="text-sm text-gray-500">Next</div>
<div class="font-medium text-gray-900 group-hover:text-blue-600">
{{ str_limit($nextPost->getTranslation('title', app()->getLocale()), 50) }}
</div>
</div>
<svg class="w-5 h-5 text-gray-400 group-hover:text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</div>
</a>
@endif
</div>
</nav>
@endif
{{-- SEO & Structured Data --}}
@pushOnce('meta')
<title>{{ $post->getSeoTitle() }}</title>
<meta name="description" content="{{ $post->getSeoDescription() }}">
<meta name="author" content="{{ $post->author?->name }}">
{{-- Open Graph --}}
<meta property="og:title" content="{{ $post->getSeoTitle() }}">
<meta property="og:description" content="{{ $post->getSeoDescription() }}">
<meta property="og:type" content="article">
<meta property="og:url" content="{{ $post->getUrl() }}">
@if($post->getFeaturedImage())
<meta property="og:image" content="{{ $post->getFeaturedImageUrl('hero') }}">
@endif
<meta property="article:published_time" content="{{ $post->published_at->toISOString() }}">
<meta property="article:modified_time" content="{{ $post->updated_at->toISOString() }}">
@if($post->author)
<meta property="article:author" content="{{ $post->author->name }}">
@endif
{{-- Twitter Card --}}
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ $post->getSeoTitle() }}">
<meta name="twitter:description" content="{{ $post->getSeoDescription() }}">
@if($post->getFeaturedImage())
<meta name="twitter:image" content="{{ $post->getFeaturedImageUrl('hero') }}">
@endif
@endPushOnce
@pushOnce('structured-data')
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "{{ $post->getTranslation('title', app()->getLocale()) }}",
"description": "{{ $post->getSeoDescription() }}",
"image": "{{ $post->getFeaturedImageUrl('hero') }}",
"author": {
"@type": "Person",
"name": "{{ $post->author?->name }}"
},
"publisher": {
"@type": "Organization",
"name": "{{ config('app.name') }}"
},
"datePublished": "{{ $post->published_at->toISOString() }}",
"dateModified": "{{ $post->updated_at->toISOString() }}",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "{{ $post->getUrl() }}"
}
}
</script>
@endPushOnce
</article>
{{-- Styles for content --}}
@pushOnce('styles')
<style>
.aspect-w-16 {
position: relative;
padding-bottom: 56.25%; /* 16:9 */
}
.aspect-w-16 img {
position: absolute;
height: 100%;
width: 100%;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.prose {
color: #374151;
max-width: none;
}
.prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 {
color: #111827;
font-weight: 600;
line-height: 1.25;
}
.prose h2 {
font-size: 1.875rem;
margin-top: 2rem;
margin-bottom: 1rem;
}
.prose h3 {
font-size: 1.5rem;
margin-top: 1.75rem;
margin-bottom: 0.75rem;
}
.prose p {
margin-top: 1.25rem;
margin-bottom: 1.25rem;
line-height: 1.75;
}
.prose a {
color: #2563eb;
text-decoration: underline;
}
.prose a:hover {
color: #1d4ed8;
}
.prose blockquote {
font-style: italic;
border-left: 4px solid #e5e7eb;
padding-left: 1rem;
margin: 1.5rem 0;
color: #6b7280;
}
.prose ul, .prose ol {
margin: 1.25rem 0;
padding-left: 1.25rem;
}
.prose li {
margin: 0.5rem 0;
}
.prose img {
margin: 2rem 0;
border-radius: 0.5rem;
}
.prose pre {
background-color: #f3f4f6;
border-radius: 0.5rem;
padding: 1rem;
overflow-x: auto;
margin: 1.5rem 0;
}
.prose code {
background-color: #f3f4f6;
border-radius: 0.25rem;
padding: 0.125rem 0.25rem;
font-size: 0.875em;
}
</style>
@endPushOnce