chore: commit remaining workspace changes
This commit is contained in:
@@ -5,5 +5,6 @@
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
@include('partials.seo.forum-microdata', ['forumMicrodata' => $forum_microdata ?? null])
|
||||
@inertia
|
||||
@endsection
|
||||
|
||||
@@ -59,9 +59,76 @@
|
||||
'per_page' => $posts->perPage(),
|
||||
'total' => $posts->total(),
|
||||
];
|
||||
|
||||
$makeForumAuthor = function ($user) {
|
||||
if (! $user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = trim((string) ($user->name ?? $user->username ?? ''));
|
||||
|
||||
if ($name === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$username = trim((string) ($user->username ?? ''));
|
||||
|
||||
return array_filter([
|
||||
'name' => $name,
|
||||
'url' => $username !== '' ? url('/@' . ltrim($username, '@')) : null,
|
||||
]);
|
||||
};
|
||||
|
||||
// Ensure we always provide a top-level author object for structured data.
|
||||
$topAuthor = $makeForumAuthor($author ?? $opPost?->user ?? null);
|
||||
if (! $topAuthor) {
|
||||
$topAuthor = ['name' => (string) ($opPost?->user?->name ?? $thread->user?->name ?? 'Skinbase')];
|
||||
}
|
||||
|
||||
$forumMicrodata = [
|
||||
'kind' => 'topic',
|
||||
'canonical' => route('forum.thread.show', ['thread' => $thread->id, 'slug' => $thread->slug]),
|
||||
'title' => (string) $thread->title,
|
||||
'text' => $threadDescription,
|
||||
'date_published' => $thread->created_at?->toIso8601String(),
|
||||
'date_modified' => ($thread->last_post_at ?? $thread->updated_at)?->toIso8601String(),
|
||||
'comment_count' => (int) ($reply_count ?? 0),
|
||||
'author' => $topAuthor,
|
||||
'comments' => collect([$opPost])
|
||||
->filter()
|
||||
->merge($posts->getCollection())
|
||||
->map(function ($post) use ($makeForumAuthor, $thread) {
|
||||
$rendered = (string) ForumPostContent::render((string) ($post->content ?? ''));
|
||||
$text = trim((string) preg_replace('/\s+/u', ' ', strip_tags(html_entity_decode($rendered, ENT_QUOTES | ENT_HTML5, 'UTF-8'))));
|
||||
|
||||
// If a post has no textual content, provide a short fallback so
|
||||
// the Comment structured data contains one of the required
|
||||
// representations (text/image/video). Prefer a simple label for
|
||||
// attachments or a generic reply placeholder.
|
||||
if ($text === '') {
|
||||
$hasAttachments = ! empty($post->attachments ?? null) && count($post->attachments ?? []) > 0;
|
||||
if ($hasAttachments) {
|
||||
$text = 'Attachment';
|
||||
} else {
|
||||
$text = 'Reply';
|
||||
}
|
||||
}
|
||||
|
||||
return array_filter([
|
||||
'url' => route('forum.thread.show', ['thread' => $post->thread_id, 'slug' => $thread->slug]) . '#post-' . $post->id,
|
||||
'text' => Str::limit($text, 300),
|
||||
'date_published' => $post->created_at?->toIso8601String(),
|
||||
'date_modified' => ($post->edited_at ?? $post->created_at)?->toIso8601String(),
|
||||
'author' => $makeForumAuthor($post->user ?? null) ?: ['name' => (string) ($post->user?->name ?? 'Skinbase')],
|
||||
], fn ($value) => $value !== null && $value !== '');
|
||||
})
|
||||
->values()
|
||||
->all(),
|
||||
];
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
@include('partials.seo.forum-microdata', ['forumMicrodata' => $forumMicrodata])
|
||||
<div id="forum-thread-root"></div>
|
||||
@php
|
||||
$forumThreadProps = json_encode([
|
||||
@@ -76,7 +143,7 @@
|
||||
'created_at' => $thread->created_at?->toIso8601String(),
|
||||
],
|
||||
'category' => ['id' => $category->id ?? null, 'name' => $category->name ?? '', 'slug' => $category->slug ?? ''],
|
||||
'author' => ['name' => $author->name ?? 'Unknown'],
|
||||
'author' => ['name' => $author->name ?? 'Skinbase'],
|
||||
'opPost' => $serializedOp,
|
||||
'posts' => $serializedPosts,
|
||||
'pagination' => $paginationData,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<title>{{ $page_title ?? 'Similar Artworks — Skinbase' }}</title>
|
||||
<meta name="description" content="{{ $page_meta_description ?? '' }}">
|
||||
<link rel="canonical" href="{{ $page_canonical ?? url()->current() }}">
|
||||
<meta name="robots" content="{{ $page_robots ?? 'noindex,follow' }}">
|
||||
<meta name="robots" content="{{ $page_robots ?? 'index,follow' }}">
|
||||
|
||||
{{-- OpenGraph --}}
|
||||
<meta property="og:type" content="website" />
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
@php
|
||||
$isPreview = (bool) ($previewMode ?? false);
|
||||
$articleUrl = $isPreview ? ($previewCanonical ?? url()->current()) : route('news.show', $article->slug);
|
||||
$articleImageLicenseUrl = route('terms-of-service');
|
||||
$articleImageCreditText = (string) config('seo.site_name', 'Skinbase');
|
||||
$articleSchemaImage = $article->effective_og_image
|
||||
? url($article->effective_og_image)
|
||||
: url((string) config('seo.fallback_image_path', '/gfx/skinbase_back_001.webp'));
|
||||
@@ -24,6 +26,30 @@
|
||||
: null,
|
||||
])->filter()->values(),
|
||||
])
|
||||
->addJsonLd($articleSchemaImage
|
||||
? array_filter([
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $articleSchemaImage,
|
||||
'contentUrl' => $articleSchemaImage,
|
||||
'thumbnailUrl' => $article->cover_mobile_url,
|
||||
'caption' => $article->title,
|
||||
'creditText' => $articleImageCreditText,
|
||||
'license' => $articleImageLicenseUrl,
|
||||
'acquireLicensePage' => $articleImageLicenseUrl,
|
||||
'creator' => [
|
||||
'@type' => 'Organization',
|
||||
'name' => $articleImageCreditText,
|
||||
'url' => url('/'),
|
||||
],
|
||||
'publisher' => [
|
||||
'@type' => 'Organization',
|
||||
'name' => $articleImageCreditText,
|
||||
'url' => url('/'),
|
||||
],
|
||||
'representativeOfPage' => true,
|
||||
], fn (mixed $value): bool => $value !== null && $value !== '')
|
||||
: null)
|
||||
->addJsonLd(array_filter([
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'NewsArticle',
|
||||
@@ -36,6 +62,9 @@
|
||||
'contentUrl' => $articleSchemaImage,
|
||||
'thumbnailUrl' => $article->cover_mobile_url,
|
||||
'caption' => $article->title,
|
||||
'creditText' => $articleImageCreditText,
|
||||
'license' => $articleImageLicenseUrl,
|
||||
'acquireLicensePage' => $articleImageLicenseUrl,
|
||||
], fn (mixed $value): bool => $value !== null && $value !== '')
|
||||
: null,
|
||||
'datePublished' => $article->published_at?->toIso8601String(),
|
||||
@@ -133,7 +162,14 @@
|
||||
<article class="min-w-0">
|
||||
@if($article->cover_url)
|
||||
<div class="overflow-hidden rounded-[32px] border border-white/[0.06] bg-black/20 shadow-[0_24px_60px_rgba(0,0,0,0.24)]">
|
||||
<a href="{{ $articleCoverPreloadHref }}" class="group block focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-300/70 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950" aria-label="Open full cover image">
|
||||
<a
|
||||
href="{{ $articleCoverPreloadHref }}"
|
||||
class="group block focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-300/70 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950"
|
||||
aria-label="Open full cover image"
|
||||
data-news-image-preview
|
||||
data-news-image-src="{{ $articleCoverPreloadHref }}"
|
||||
data-news-image-alt="{{ $article->title }}"
|
||||
>
|
||||
<div class="relative">
|
||||
<img src="{{ $article->cover_url }}" @if($article->cover_srcset) srcset="{{ $article->cover_srcset }}" sizes="{{ $articleCoverSizes }}" @endif alt="{{ $article->title }}" fetchpriority="high" loading="eager" decoding="async" class="h-auto max-h-[520px] w-full object-cover transition duration-300 group-hover:scale-[1.01]">
|
||||
<div class="pointer-events-none absolute inset-x-4 bottom-4 flex items-center justify-between gap-3 rounded-full border border-white/10 bg-slate-950/72 px-4 py-2 text-xs font-semibold uppercase tracking-[0.18em] text-white/82 backdrop-blur-sm">
|
||||
@@ -263,6 +299,8 @@
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
@vite(['resources/js/Pages/News/NewsImagePreview.jsx'])
|
||||
|
||||
@if($needsFacebookEmbeds)
|
||||
<div id="fb-root"></div>
|
||||
<script async defer crossorigin="anonymous" src="https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v19.0"></script>
|
||||
|
||||
100
resources/views/partials/seo/forum-microdata.blade.php
Normal file
100
resources/views/partials/seo/forum-microdata.blade.php
Normal file
@@ -0,0 +1,100 @@
|
||||
@php
|
||||
$forumMicrodata = is_array($forumMicrodata ?? null) ? $forumMicrodata : null;
|
||||
@endphp
|
||||
|
||||
@if($forumMicrodata)
|
||||
<div class="hidden" aria-hidden="true">
|
||||
@if(($forumMicrodata['kind'] ?? null) === 'topic')
|
||||
<div itemscope itemtype="https://schema.org/DiscussionForumPosting">
|
||||
@if(!empty($forumMicrodata['canonical']))<meta itemprop="mainEntityOfPage" content="{{ $forumMicrodata['canonical'] }}" />@endif
|
||||
@if(!empty($forumMicrodata['canonical']))<meta itemprop="url" content="{{ $forumMicrodata['canonical'] }}" />@endif
|
||||
@if(!empty($forumMicrodata['title']))<meta itemprop="headline" content="{{ $forumMicrodata['title'] }}" />@endif
|
||||
@if(!empty($forumMicrodata['text']))<meta itemprop="text" content="{{ $forumMicrodata['text'] }}" />@endif
|
||||
@if(!empty($forumMicrodata['date_published']))<meta itemprop="datePublished" content="{{ $forumMicrodata['date_published'] }}" />@endif
|
||||
@if(!empty($forumMicrodata['date_modified']))<meta itemprop="dateModified" content="{{ $forumMicrodata['date_modified'] }}" />@endif
|
||||
@if(isset($forumMicrodata['comment_count']))<meta itemprop="commentCount" content="{{ (int) $forumMicrodata['comment_count'] }}" />@endif
|
||||
|
||||
@if(!empty($forumMicrodata['author']))
|
||||
<div itemprop="author" itemscope itemtype="https://schema.org/Person">
|
||||
@if(!empty($forumMicrodata['author']['url']))<meta itemprop="url" content="{{ $forumMicrodata['author']['url'] }}" />@endif
|
||||
@if(!empty($forumMicrodata['author']['name']))<meta itemprop="name" content="{{ $forumMicrodata['author']['name'] }}" />@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(!empty($forumMicrodata['board']))
|
||||
<div itemprop="isPartOf" itemscope itemtype="https://schema.org/WebPage">
|
||||
@if(!empty($forumMicrodata['board']['url']))<meta itemprop="url" content="{{ $forumMicrodata['board']['url'] }}" />@endif
|
||||
@if(!empty($forumMicrodata['board']['name']))<meta itemprop="name" content="{{ $forumMicrodata['board']['name'] }}" />@endif
|
||||
@if(!empty($forumMicrodata['board']['category']))
|
||||
<div itemprop="isPartOf" itemscope itemtype="https://schema.org/WebPage">
|
||||
@if(!empty($forumMicrodata['board']['category']['url']))<meta itemprop="url" content="{{ $forumMicrodata['board']['category']['url'] }}" />@endif
|
||||
@if(!empty($forumMicrodata['board']['category']['name']))<meta itemprop="name" content="{{ $forumMicrodata['board']['category']['name'] }}" />@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@foreach(($forumMicrodata['interactions'] ?? []) as $interaction)
|
||||
<div itemprop="interactionStatistic" itemscope itemtype="https://schema.org/InteractionCounter">
|
||||
@if(!empty($interaction['type']))<meta itemprop="interactionType" content="{{ $interaction['type'] }}" />@endif
|
||||
@if(isset($interaction['count']))<meta itemprop="userInteractionCount" content="{{ (int) $interaction['count'] }}" />@endif
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
@foreach(($forumMicrodata['comments'] ?? []) as $comment)
|
||||
<div itemprop="comment" itemscope itemtype="https://schema.org/Comment">
|
||||
@if(!empty($comment['url']))<meta itemprop="url" content="{{ $comment['url'] }}" />@endif
|
||||
@if(!empty($comment['text']))<meta itemprop="text" content="{{ $comment['text'] }}" />@endif
|
||||
@if(!empty($comment['date_published']))<meta itemprop="datePublished" content="{{ $comment['date_published'] }}" />@endif
|
||||
@if(!empty($comment['date_modified']))<meta itemprop="dateModified" content="{{ $comment['date_modified'] }}" />@endif
|
||||
|
||||
@if(!empty($comment['author']))
|
||||
<div itemprop="author" itemscope itemtype="https://schema.org/Person">
|
||||
@if(!empty($comment['author']['url']))<meta itemprop="url" content="{{ $comment['author']['url'] }}" />@endif
|
||||
@if(!empty($comment['author']['name']))<meta itemprop="name" content="{{ $comment['author']['name'] }}" />@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@foreach(($comment['interactions'] ?? []) as $interaction)
|
||||
<div itemprop="interactionStatistic" itemscope itemtype="https://schema.org/InteractionCounter">
|
||||
@if(!empty($interaction['type']))<meta itemprop="interactionType" content="{{ $interaction['type'] }}" />@endif
|
||||
@if(isset($interaction['count']))<meta itemprop="userInteractionCount" content="{{ (int) $interaction['count'] }}" />@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@elseif(($forumMicrodata['kind'] ?? null) === 'collection')
|
||||
<div itemscope itemtype="https://schema.org/CollectionPage">
|
||||
@if(!empty($forumMicrodata['canonical']))<meta itemprop="url" content="{{ $forumMicrodata['canonical'] }}" />@endif
|
||||
@if(!empty($forumMicrodata['name']))<meta itemprop="name" content="{{ $forumMicrodata['name'] }}" />@endif
|
||||
@if(!empty($forumMicrodata['description']))<meta itemprop="description" content="{{ $forumMicrodata['description'] }}" />@endif
|
||||
|
||||
<div itemprop="mainEntity" itemscope itemtype="https://schema.org/ItemList">
|
||||
@if(!empty($forumMicrodata['list_name']))<meta itemprop="name" content="{{ $forumMicrodata['list_name'] }}" />@endif
|
||||
|
||||
@foreach(($forumMicrodata['items'] ?? []) as $index => $item)
|
||||
<div itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
|
||||
<meta itemprop="position" content="{{ $index + 1 }}" />
|
||||
<div itemprop="item" itemscope itemtype="https://schema.org/{{ $item['type'] ?? 'WebPage' }}">
|
||||
@if(!empty($item['url']))<meta itemprop="url" content="{{ $item['url'] }}" />@endif
|
||||
@if(!empty($item['title']))<meta itemprop="{{ ($item['type'] ?? null) === 'DiscussionForumPosting' ? 'headline' : 'name' }}" content="{{ $item['title'] }}" />@endif
|
||||
@if(!empty($item['description']))<meta itemprop="description" content="{{ $item['description'] }}" />@endif
|
||||
@if(!empty($item['text']))<meta itemprop="text" content="{{ $item['text'] }}" />@endif
|
||||
@if(!empty($item['date_published']))<meta itemprop="datePublished" content="{{ $item['date_published'] }}" />@endif
|
||||
@if(isset($item['comment_count']))<meta itemprop="commentCount" content="{{ (int) $item['comment_count'] }}" />@endif
|
||||
@if(!empty($item['date_modified']))<meta itemprop="dateModified" content="{{ $item['date_modified'] }}" />@endif
|
||||
@if(!empty($item['author']))
|
||||
<div itemprop="author" itemscope itemtype="https://schema.org/Person">
|
||||
@if(!empty($item['author']['url']))<meta itemprop="url" content="{{ $item['author']['url'] }}" />@endif
|
||||
@if(!empty($item['author']['name']))<meta itemprop="name" content="{{ $item['author']['name'] }}" />@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
Reference in New Issue
Block a user