Compare commits

...

19 Commits

Author SHA1 Message Date
823a284fbc Fix PHP 8.4 deprecation warning 2026-01-11 16:39:14 +01:00
67e601d0c4 Add ability to properly set locale (session/account) 2026-01-11 16:33:13 +01:00
7e4ebd91ad Remove vluzrmos/language-detector package 2026-01-11 16:31:16 +01:00
4dc5dee2b9 Update dependencies 2026-01-11 15:46:33 +01:00
5310908b0c Update login button on mobile 2026-01-11 00:21:05 +01:00
4b05b3db6d Fix cache not flushed after comment delete by admin 2026-01-10 23:24:31 +01:00
df47a926e4 Fix comment mass delete 2026-01-10 23:21:29 +01:00
1e9e95f35f Fix admin comment moderation 2026-01-10 22:35:35 +01:00
2aa76baafd Fix account deletion anonymizing comments 2026-01-10 22:35:21 +01:00
aa50bb1f72 Merge pull request 'Replace Comment System' (#4) from comment-system into main
Reviewed-on: #4
2026-01-10 21:16:55 +00:00
dfedf4058e Fix rate limit and make it more strict (1 message in 5 minutes) 2026-01-10 22:15:50 +01:00
268e3eb4c2 Add Notification for comments 2026-01-10 22:00:09 +01:00
ab61574956 Fix comment depth chain check 2026-01-10 21:59:53 +01:00
81038b6c26 Add id to comment, so it can autoscroll to that notification 2026-01-10 21:59:08 +01:00
e949ba955a Add rate limiter to comment system 2026-01-10 21:04:26 +01:00
819e2fde27 Misc changes 2026-01-10 20:33:35 +01:00
3259e2197b Update design comments home page 2026-01-10 19:45:19 +01:00
b133db0573 Add likes to comments 2026-01-10 19:41:23 +01:00
41c34e6d89 Fix style 2026-01-10 19:15:32 +01:00
22 changed files with 1641 additions and 1869 deletions

View File

@@ -111,11 +111,13 @@ class HomeController extends Controller
*/ */
public function updateLanguage(Request $request): \Illuminate\Http\RedirectResponse public function updateLanguage(Request $request): \Illuminate\Http\RedirectResponse
{ {
if(! in_array($request->language, config('lang-detector.languages'))) { abort_unless(in_array($request->language, config('app.supported_locales'), true), 404);
return redirect()->back();
}
Cookie::queue(Cookie::forever('locale', $request->language)); session(['locale' => $request->language]);
if (Auth::check()) {
Auth::user()->update(['locale' => $request->language]);
}
return redirect()->back(); return redirect()->back();
} }

View File

@@ -151,7 +151,7 @@ class ProfileController extends Controller
} }
// Update comments to deleted user // Update comments to deleted user
DB::table('comments')->where('commenter_id', '=', $user->id)->update(['commenter_id' => 1]); DB::table('comments')->where('user_id', '=', $user->id)->update(['user_id' => 1]);
// Delete Profile Picture // Delete Profile Picture
if ($user->avatar) { if ($user->avatar) {

View File

@@ -37,6 +37,7 @@ class Kernel extends HttpKernel
\App\Http\Middleware\VerifyCsrfToken::class, \App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\IsBanned::class, \App\Http\Middleware\IsBanned::class,
\App\Http\Middleware\SetLocale::class,
], ],
'api' => [ 'api' => [

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class SetLocale
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
// 1. Logged-in user preference
if (Auth::check() && Auth::user()->locale) {
App::setLocale(Auth::user()->locale);
return $next($request);
}
// 2. Session (guest or user override)
if (session()->has('locale') && in_array($request->language, config('app.supported_locales'), true)) {
App::setLocale(session('locale'));
return $next($request);
}
// 3. Browser language
$locale = $request->getPreferredLanguage(config('app.supported_locales'));
if ($locale) {
App::setLocale($locale);
}
return $next($request);
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Livewire; namespace App\Livewire;
use App\Models\Comment;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@@ -24,13 +25,19 @@ class AdminCommentSearch extends Component
$this->resetPage(); $this->resetPage();
} }
public function deleteComment($commentId)
{
$comment = Comment::where('id', (int) $commentId)->firstOrFail();
$comment->delete();
cache()->flush();
}
public function render() public function render()
{ {
$comments = DB::table('comments') $comments = Comment::when($this->search !== '', fn ($query) => $query->where('body', 'LIKE', "%$this->search%"))
->join('users', 'comments.commenter_id', '=', 'users.id') ->when($this->userSearch !== '', fn ($query) => $query->whereHas('user', fn ($query) => $query->where('name', 'LIKE', "%{$this->userSearch}%")))
->select('comments.*', 'users.name') ->orderBy('created_at', 'DESC')
->when($this->search !== '', fn ($query) => $query->where('comment', 'LIKE', "%$this->search%"))
->when($this->userSearch !== '', fn ($query) => $query->where('name', 'LIKE', "%$this->userSearch%"))
->paginate(12); ->paginate(12);
return view('livewire.admin-comment-search', [ return view('livewire.admin-comment-search', [

View File

@@ -2,14 +2,13 @@
namespace App\Livewire; namespace App\Livewire;
use App\Models\Comment;
use App\Models\User; use App\Models\User;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
use Illuminate\Support\Facades\DB;
class AdminUserSearch extends Component class AdminUserSearch extends Component
{ {
use WithPagination; use WithPagination;
@@ -31,8 +30,7 @@ class AdminUserSearch extends Component
$user = User::where('id', $userID) $user = User::where('id', $userID)
->firstOrFail(); ->firstOrFail();
DB::table('comments') Comment::where('user_id', $user->id)
->where('commenter_id', '=', $user->id)
->delete(); ->delete();
cache()->flush(); cache()->flush();

View File

@@ -1,10 +1,22 @@
<?php <?php
namespace App\Livewire; namespace App\Livewire;
use App\Models\User;
use App\Models\Episode;
use App\Notifications\CommentNotification;
use Livewire\Component; use Livewire\Component;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Maize\Markable\Models\Like;
class Comment extends Component class Comment extends Component
{ {
use AuthorizesRequests; use AuthorizesRequests;
@@ -13,6 +25,10 @@ class Comment extends Component
public $isReplying = false; public $isReplying = false;
public $likeCount = 0;
public $liked = false;
public $replyState = [ public $replyState = [
'body' => '' 'body' => ''
]; ];
@@ -62,20 +78,50 @@ class Comment extends Component
public function postReply() public function postReply()
{ {
if (! $this->comment->depth() < 2) { if (!($this->comment->depth() < 2)) {
$this->addError('replyState.body', "Too many sub comments.");
return; return;
} }
$user = auth()->user();
$rateLimitKey = "send-comment:{$user->id}";
$rateLimitMinutes = 60 * 5; // 5 minutes
if (RateLimiter::tooManyAttempts($rateLimitKey, 1)) {
$seconds = RateLimiter::availableIn($rateLimitKey);
$this->addError('replyState.body', "Too many comments. Try again in {$seconds} seconds.");
return;
}
RateLimiter::hit($rateLimitKey, $rateLimitMinutes);
$this->validate([ $this->validate([
'replyState.body' => 'required' 'replyState.body' => 'required'
]); ]);
$reply = $this->comment->children()->make($this->replyState); $reply = $this->comment->children()->make($this->replyState);
$reply->user()->associate(auth()->user()); $reply->user()->associate($user);
$reply->commentable()->associate($this->comment->commentable); $reply->commentable()->associate($this->comment->commentable);
$reply->save(); $reply->save();
// Notify if Episode and if not the same user
if ($reply->commentable_type == Episode::class && $user->id !== $reply->parent->user->id) {
$episode = Episode::where('id', $reply->commentable_id)
->firstOrFail();
$url = route('hentai.index', ['title' => $episode->slug]);
$reply->parent->user->notify(
new CommentNotification(
"{$user->name} replied to your comment.",
Str::limit($reply->body, 50),
"{$url}#comment-{$reply->id}"
)
);
}
$this->replyState = [ $this->replyState = [
'body' => '' 'body' => ''
]; ];
@@ -85,6 +131,34 @@ class Comment extends Component
$this->dispatch('refresh')->self(); $this->dispatch('refresh')->self();
} }
public function like()
{
if (! Auth::check()) {
return;
}
Like::toggle($this->comment, User::where('id', Auth::user()->id)->firstOrFail());
Cache::forget('commentLikes'.$this->comment->id);
if ($this->liked) {
$this->liked = false;
$this->likeCount--;
return;
}
$this->liked = true;
$this->likeCount++;
}
public function mount()
{
if (Auth::check()) {
$this->likeCount = $this->comment->likeCount();
$this->liked = Like::has($this->comment, User::where('id', Auth::user()->id)->firstOrFail());
}
}
public function render() public function render()
{ {
return view('livewire.comment'); return view('livewire.comment');

View File

@@ -5,6 +5,8 @@ namespace App\Livewire;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
use Illuminate\Support\Facades\RateLimiter;
class Comments extends Component class Comments extends Component
{ {
use WithPagination; use WithPagination;
@@ -29,8 +31,21 @@ class Comments extends Component
'newCommentState.body' => 'required' 'newCommentState.body' => 'required'
]); ]);
$user = auth()->user();
$rateLimitKey = "send-comment:{$user->id}";
$rateLimitMinutes = 60 * 5; // 5 minutes
if (RateLimiter::tooManyAttempts($rateLimitKey, 1)) {
$seconds = RateLimiter::availableIn($rateLimitKey);
$this->addError('newCommentState.body', "Too many comments. Try again in {$seconds} seconds.");
return;
}
RateLimiter::hit($rateLimitKey, $rateLimitMinutes);
$comment = $this->model->comments()->make($this->newCommentState); $comment = $this->model->comments()->make($this->newCommentState);
$comment->user()->associate(auth()->user()); $comment->user()->associate($user);
$comment->save(); $comment->save();
$this->newCommentState = [ $this->newCommentState = [

View File

@@ -8,9 +8,16 @@ use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Maize\Markable\Markable;
use Maize\Markable\Models\Like;
class Comment extends Model class Comment extends Model
{ {
use HasFactory, SoftDeletes; use HasFactory, SoftDeletes, Markable;
protected static $marks = [
Like::class
];
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
@@ -58,4 +65,12 @@ class Comment extends Model
? $this->parent->depth() + 1 ? $this->parent->depth() + 1
: 0; : 0;
} }
/**
* Get cached like count
*/
public function likeCount(): int
{
return cache()->remember('commentLikes' . $this->id, 300, fn() => $this->likes->count());
}
} }

View File

@@ -34,8 +34,8 @@ class EpisodeService
Request $request, Request $request,
Hentai $hentai, Hentai $hentai,
int $episodeNumber, int $episodeNumber,
Studios $studio = null, ?Studios $studio = null,
Episode $referenceEpisode = null ?Episode $referenceEpisode = null
): Episode ): Episode
{ {
$episode = new Episode(); $episode = new Episode();

View File

@@ -12,27 +12,26 @@
"guzzlehttp/guzzle": "^7.8.1", "guzzlehttp/guzzle": "^7.8.1",
"hisorange/browser-detect": "^5.0", "hisorange/browser-detect": "^5.0",
"http-interop/http-factory-guzzle": "^1.2", "http-interop/http-factory-guzzle": "^1.2",
"intervention/image": "^3.9", "intervention/image": "^3.11",
"intervention/image-laravel": "^1.3", "intervention/image-laravel": "^1.5",
"laravel/framework": "^11.0", "laravel/framework": "^12.0",
"laravel/sanctum": "^4.0", "laravel/sanctum": "^4.2",
"laravel/scout": "^10.20", "laravel/scout": "^10.20",
"laravel/socialite": "^5.24", "laravel/socialite": "^5.24",
"laravel/tinker": "^2.10", "laravel/tinker": "^2.10",
"livewire/livewire": "^3.6.4", "livewire/livewire": "^3.7.0",
"maize-tech/laravel-markable": "^2.3.0", "maize-tech/laravel-markable": "^2.3.0",
"meilisearch/meilisearch-php": "^1.16", "meilisearch/meilisearch-php": "^1.16",
"mews/captcha": "3.4.4", "mews/captcha": "^3.4.4",
"predis/predis": "^2.2", "predis/predis": "^2.2",
"realrashid/sweet-alert": "^7.2", "realrashid/sweet-alert": "^7.2",
"rtconner/laravel-tagging": "^4.1", "rtconner/laravel-tagging": "^5.0",
"socialiteproviders/discord": "^4.2", "socialiteproviders/discord": "^4.2",
"spatie/laravel-discord-alerts": "^1.5", "spatie/laravel-discord-alerts": "^1.8",
"spatie/laravel-sitemap": "^7.3", "spatie/laravel-sitemap": "^7.3"
"vluzrmos/language-detector": "^2.3"
}, },
"require-dev": { "require-dev": {
"barryvdh/laravel-debugbar": "^3.14.7", "barryvdh/laravel-debugbar": "^3.16",
"fakerphp/faker": "^1.24.0", "fakerphp/faker": "^1.24.0",
"laravel/breeze": "^2.3", "laravel/breeze": "^2.3",
"laravel/pint": "^1.18", "laravel/pint": "^1.18",

1752
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -111,6 +111,18 @@ return [
'faker_locale' => 'en_US', 'faker_locale' => 'en_US',
/*
|--------------------------------------------------------------------------
| Supported Locales
|--------------------------------------------------------------------------
|
| This is used to display the supported locales by this app, it also is
| used to verify session data and requests in the SetLocale Middleware
|
*/
'supported_locales' => ['en', 'de', 'fr'],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Encryption Key | Encryption Key

View File

@@ -1,46 +0,0 @@
<?php
return [
/*
* Indicates whenever should autodetect and apply the language of the request.
*/
'autodetect' => env('LANG_DETECTOR_AUTODETECT', true),
/*
* Default driver to use to detect the request language.
*
* Available: browser, subdomain, uri.
*/
'driver' => env('LANG_DETECTOR_DRIVER', 'browser'),
/*
* Used on subdomain and uri drivers. That indicates which segment should be used
* to verify the language.
*/
'segment' => env('LANG_DETECTOR_SEGMENT', 0),
/*
* Languages available on the application.
*
* You could use parse_langs_to_array to use the string syntax
* or just use the array of languages with its aliases.
*/
'languages' => parse_langs_to_array(
env('LANG_DETECTOR_LANGUAGES', ['en', 'de', 'fr'])
),
/*
* Indicates if should store detected locale on cookies
*/
'cookie' => (bool) env('LANG_DETECTOR_COOKIE', true),
/*
* Indicates if should encrypt cookie
*/
'cookie_encrypt' => (bool) env('LANG_DETECTOR_COOKIE_ENCRYPT', false),
/*
* Cookie name
*/
'cookie_name' => env('LANG_DETECTOR_COOKIE', 'locale'),
];

View File

@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('locale', 10)
->nullable()
->after('discord_avatar');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('locale');
});
}
};

1301
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,7 @@
src="{{ $episode->cover_url }}"></img> src="{{ $episode->cover_url }}"></img>
</a> </a>
</div> </div>
<div class="w-[60vw] pt-4"> <div class="w-[60vw] pt-4 bg-neutral-100 dark:bg-neutral-800 rounded-lg pl-4">
@include('partials.comment', ['comment' => $comment]) @include('partials.comment', ['comment' => $comment])
</div> </div>
</div> </div>
@@ -59,7 +59,7 @@
src="{{ $hentai->episodes->first()->cover_url }}"></img> src="{{ $hentai->episodes->first()->cover_url }}"></img>
</a> </a>
</div> </div>
<div class="w-[60vw]"> <div class="w-[60vw] pt-4 bg-neutral-100 dark:bg-neutral-800 rounded-lg pl-4">
@include('partials.comment', ['comment' => $comment]) @include('partials.comment', ['comment' => $comment])
</div> </div>
</div> </div>

View File

@@ -296,8 +296,8 @@
<div class="pb-1 text-center w-full"> <div class="pb-1 text-center w-full">
<x-responsive-nav-link :href="route('login')"> <x-responsive-nav-link :href="route('login')">
<div <div
class="relative bg-blue-700 hover:bg-blue-600 text-white font-bold px-4 h-10 rounded text-center p-[10px]"> class="relative bg-rose-700 hover:bg-rose-600 text-white font-bold px-4 h-10 rounded text-center p-[10px]">
<i class="fa-brands fa-discord"></i> {{ __('nav.login') }} <i class="fa-solid fa-arrow-right-to-bracket"></i> {{ __('nav.login') }}
</div> </div>
</x-responsive-nav-link> </x-responsive-nav-link>
</div> </div>

View File

@@ -25,6 +25,8 @@
placeholder="Search..." placeholder="Search..."
> >
</th> </th>
<th scope="col" class="px-6 py-3">
</th>
<th scope="col" class="px-6 py-3"> <th scope="col" class="px-6 py-3">
Actions Actions
</th> </th>
@@ -34,17 +36,18 @@
@foreach($comments as $comment) @foreach($comments as $comment)
<tr wire:key="comment-{{ $comment->id }}" class="bg-white border-t dark:bg-neutral-800 dark:border-pink-700"> <tr wire:key="comment-{{ $comment->id }}" class="bg-white border-t dark:bg-neutral-800 dark:border-pink-700">
<td class="px-6 py-4"> <td class="px-6 py-4">
{{ $comment->name }} {{ $comment->user->name }}
</td> </td>
<th scope="row" class="px-6 py-4 font-medium text-gray-900 dark:text-white max-w-lg"> <th scope="row" class="px-6 py-4 font-medium text-gray-900 dark:text-white max-w-lg">
{{ $comment->comment }} {{ $comment->body }}
</th>
<th scope="row" class="px-6 py-4 font-medium text-gray-900 dark:text-white max-w-lg">
{{ $comment->created_at }}
</th> </th>
<td class="px-6 py-4"> <td class="px-6 py-4">
<a href="{{ route('comments.destroy', $comment->id) }}" onclick="event.preventDefault();document.getElementById('comment-delete-form-{{ $comment->id }}').submit();" class="inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150 mt-2">@lang('comments::comments.delete')</a> <button wire:click="deleteComment({{$comment->id}})" type="button" class="inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150 mt-2">
<form id="comment-delete-form-{{ $comment->id }}" action="{{ route('comments.destroy', $comment->id) }}" method="POST" style="display: none;"> Delete
@method('DELETE') </button>
@csrf
</form>
</td> </td>
</tr> </tr>
@endforeach @endforeach

View File

@@ -1,5 +1,5 @@
<div> <div>
<div class="flex"> <div class="flex" id="comment-{{ $comment->id }}">
<div class="flex-shrink-0 mr-4"> <div class="flex-shrink-0 mr-4">
<img class="h-10 w-10 rounded-full" src="{{ $comment->user->getAvatar() }}" alt="{{ $comment->user->name }}"> <img class="h-10 w-10 rounded-full" src="{{ $comment->user->getAvatar() }}" alt="{{ $comment->user->name }}">
</div> </div>
@@ -28,7 +28,7 @@
</div> </div>
<div class="mt-3 flex items-center justify-between"> <div class="mt-3 flex items-center justify-between">
<button type="submit" <button type="submit"
class="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md shadow-sm text-white bg-rose-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-rose-500"> class="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md shadow-sm text-white bg-rose-600 hover:bg-rose-700 focus:outline-none focus:ring-2 focus:ring-rose-500">
Edit Edit
</button> </button>
</div> </div>
@@ -37,10 +37,28 @@
<div class="text-gray-700 dark:text-gray-200">{!! $comment->presenter()->markdownBody() !!}</div> <div class="text-gray-700 dark:text-gray-200">{!! $comment->presenter()->markdownBody() !!}</div>
@endif @endif
</div> </div>
<div class="mt-2 space-x-2"> <div class="mt-2 space-x-2 flex flex-row">
<span class="text-gray-500 dark:text-gray-300 font-medium"> <span class="text-gray-500 dark:text-gray-300">
{{ $comment->presenter()->relativeCreatedAt() }} {{ $comment->presenter()->relativeCreatedAt() }}
</span> </span>
@guest
<span data-te-toggle="tooltip" title="Please login to like the episode" class="text-gray-800 cursor-pointer dark:text-gray-200">
<i class="fa-regular fa-heart"></i> {{ $comment->likeCount() }}
</span>
@endguest
@auth
<!-- Like Button -->
<button class="text-gray-800 dark:text-gray-200 leading-tight cursor-pointer whitespace-nowrap" wire:click="like">
@if ($liked)
<i class="fa-solid fa-heart text-rose-600"></i> {{ $likeCount }}
@else
<i class="fa-solid fa-heart"></i> {{ $likeCount }}
@endif
</button>
@endauth
@auth @auth
@if ($comment->depth() < 2) @if ($comment->depth() < 2)
<button wire:click="$toggle('isReplying')" type="button" class="text-gray-900 dark:text-gray-100 font-medium"> <button wire:click="$toggle('isReplying')" type="button" class="text-gray-900 dark:text-gray-100 font-medium">

View File

@@ -4,19 +4,8 @@
<div class="px-4 py-5 sm:px-6"> <div class="px-4 py-5 sm:px-6">
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-200">Comments</h2> <h2 class="text-lg font-medium text-gray-900 dark:text-gray-200">Comments</h2>
</div> </div>
<div class="px-4 py-6 sm:px-6"> <div>
<div class="space-y-8"> <!-- Comment Input -->
@if ($comments->isNotEmpty())
@foreach($comments as $comment)
<livewire:comment :comment="$comment" :key="$comment->id"/>
@endforeach
{{ $comments->links('pagination::tailwind') }}
@else
<p class="text-gray-900 dark:text-gray-200">No comments yet.</p>
@endif
</div>
</div>
</div>
<div class="bg-gray-50 dark:bg-neutral-800 px-4 py-6 sm:px-6"> <div class="bg-gray-50 dark:bg-neutral-800 px-4 py-6 sm:px-6">
@auth @auth
<div class="flex"> <div class="flex">
@@ -28,7 +17,7 @@
<div> <div>
<label for="comment" class="sr-only">Comment body</label> <label for="comment" class="sr-only">Comment body</label>
<textarea id="comment" name="comment" rows="3" <textarea id="comment" name="comment" rows="3"
class="bg-white dark:bg-neutral-700 shadow-sm block w-full focus:ring-rose-500 focus:border-rose-500 border-gray-300 dark:border-gray-400/40 text-gray-900 dark:text-gray-200 placeholder:text-gray-400 rounded-md class="peer block min-h-[auto] w-full border-1 bg-transparent px-3 py-[0.32rem] leading-[1.6] outline-none transition-all duration-200 ease-linear dark:placeholder:text-neutral-200 border-gray-300 dark:border-neutral-950 dark:bg-neutral-900 dark:text-gray-300 focus:border-rose-500 dark:focus:border-rose-600 focus:ring-rose-500 dark:focus:ring-rose-600 rounded-md shadow-sm
@error('newCommentState.body') border-red-500 @enderror" @error('newCommentState.body') border-red-500 @enderror"
placeholder="Write something" wire:model.defer="newCommentState.body"></textarea> placeholder="Write something" wire:model.defer="newCommentState.body"></textarea>
@error('newCommentState.body') @error('newCommentState.body')
@@ -50,5 +39,22 @@
<p class="text-gray-900 dark:text-gray-200">Log in to comment.</p> <p class="text-gray-900 dark:text-gray-200">Log in to comment.</p>
@endguest @endguest
</div> </div>
<!-- Comments -->
<div class="px-4 py-6 sm:px-6">
<div class="space-y-8">
@if ($comments->isNotEmpty())
@foreach($comments as $comment)
<livewire:comment :comment="$comment" :key="$comment->id"/>
@endforeach
{{ $comments->links('pagination::tailwind') }}
@else
<p class="text-gray-900 dark:text-gray-200">No comments yet.</p>
@endif
</div>
</div>
</div>
</div>
</div> </div>
</section> </section>

View File

@@ -88,6 +88,20 @@
<i class="fa-regular fa-heart"></i> {{ $episode->likeCount() }} <i class="fa-regular fa-heart"></i> {{ $episode->likeCount() }}
<i class="fa-regular fa-comment"></i> {{ $episode->commentCount() }} <i class="fa-regular fa-comment"></i> {{ $episode->commentCount() }}
</p> </p>
</a>
</div>
@elseif($comment->commentable_type == \App\Models\Hentai::class)
@php
$hentai = \App\Models\Hentai::find($comment->commentable_id);
$episode = $hentai->episodes->first();
@endphp
<div class="relative p-1 w-full transition duration-300 ease-in-out md:p-2 md:hover:-translate-y-1 md:hover:scale-110">
<a href="{{ route('hentai.index', ['title' => $hentai->slug]) }}">
<img alt="{{ $episode->title }}" loading="lazy" width="1000"
class="block object-cover object-center relative z-20 rounded-lg aspect-video"
src="{{ $episode->gallery->first()->thumbnail_url }}" />
</a> </a>
</div> </div>
@endif @endif