166 lines
7.3 KiB
PHP
166 lines
7.3 KiB
PHP
@extends('layouts.nova')
|
|
|
|
@php
|
|
use App\Support\ForumPostContent;
|
|
use App\Support\AvatarUrl;
|
|
use Illuminate\Support\Facades\Gate;
|
|
use Illuminate\Support\Str;
|
|
|
|
$filesBaseUrl = rtrim((string) config('cdn.files_url', ''), '/');
|
|
|
|
$serializePost = function ($post) use ($filesBaseUrl) {
|
|
$user = $post->user ?? null;
|
|
return [
|
|
'id' => $post->id,
|
|
'user_id' => $post->user_id,
|
|
'content' => $post->content,
|
|
'rendered_content' => ForumPostContent::render($post->content),
|
|
'created_at' => $post->created_at?->toIso8601String(),
|
|
'edited_at' => $post->edited_at?->toIso8601String(),
|
|
'is_edited' => (bool) $post->is_edited,
|
|
'can_edit' => auth()->check() && (
|
|
(int) $post->user_id === (int) auth()->id() || Gate::allows('moderate-forum')
|
|
),
|
|
'current_user_id' => auth()->id(),
|
|
'user' => $user ? [
|
|
'id' => $user->id,
|
|
'name' => $user->name,
|
|
'avatar_url' => AvatarUrl::forUser((int) $user->id, $user->profile?->avatar_hash ?? null),
|
|
'role' => $user->role ?? 'member',
|
|
'level' => (int) ($user->level ?? 1),
|
|
'rank' => (string) ($user->rank ?? 'Newbie'),
|
|
] : null,
|
|
'attachments' => collect($post->attachments ?? [])->map(fn ($a) => [
|
|
'id' => $a->id,
|
|
'mime_type' => $a->mime_type,
|
|
'url' => $filesBaseUrl !== '' ? $filesBaseUrl . '/' . ltrim($a->file_path, '/') : '/' . ltrim($a->file_path, '/'),
|
|
'file_size' => $a->file_size,
|
|
'width' => $a->width,
|
|
'height' => $a->height,
|
|
])->values()->all(),
|
|
];
|
|
};
|
|
|
|
$serializedOp = isset($opPost) && $opPost ? $serializePost($opPost) : null;
|
|
$serializedPosts = collect($posts->items())->map($serializePost)->values()->all();
|
|
$threadDescription = null;
|
|
$threadDescriptionSource = (string) ($serializedOp['rendered_content'] ?? $serializedOp['content'] ?? '');
|
|
|
|
if ($threadDescriptionSource !== '') {
|
|
$threadDescriptionSource = html_entity_decode($threadDescriptionSource, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
|
$threadDescriptionSource = html_entity_decode($threadDescriptionSource, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
|
$threadDescription = trim((string) preg_replace('/\s+/u', ' ', strip_tags($threadDescriptionSource)));
|
|
$threadDescription = Str::limit($threadDescription, 220);
|
|
}
|
|
|
|
$paginationData = [
|
|
'current_page' => $posts->currentPage(),
|
|
'last_page' => $posts->lastPage(),
|
|
'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([
|
|
'thread' => [
|
|
'id' => $thread->id,
|
|
'title' => $thread->title,
|
|
'slug' => $thread->slug,
|
|
'description'=> $threadDescription,
|
|
'views' => (int) ($thread->views ?? 0),
|
|
'is_pinned' => (bool) $thread->is_pinned,
|
|
'is_locked' => (bool) $thread->is_locked,
|
|
'created_at' => $thread->created_at?->toIso8601String(),
|
|
],
|
|
'category' => ['id' => $category->id ?? null, 'name' => $category->name ?? '', 'slug' => $category->slug ?? ''],
|
|
'author' => ['name' => $author->name ?? 'Skinbase'],
|
|
'opPost' => $serializedOp,
|
|
'posts' => $serializedPosts,
|
|
'pagination' => $paginationData,
|
|
'replyCount' => (int) ($reply_count ?? 0),
|
|
'sort' => $sort ?? 'asc',
|
|
'quotedPost' => $quoted_post ? ['user' => ['name' => data_get($quoted_post, 'user.name', 'Anonymous')]] : null,
|
|
'replyPrefill' => $reply_prefill ?? '',
|
|
'isAuthenticated' => auth()->check(),
|
|
'canModerate' => Gate::allows('moderate-forum'),
|
|
'csrfToken' => csrf_token(),
|
|
'status' => session('status'),
|
|
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
|
|
@endphp
|
|
<script type="application/json" id="forum-thread-props">{!! $forumThreadProps !!}</script>
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
@vite(['resources/js/entry-forum.jsx'])
|
|
@endpush
|