Compare commits

...

6 Commits

Author SHA1 Message Date
6ce0255764 Remove ring offset 2026-01-10 15:45:52 +01:00
e136e8e1b6 Refresh on delete 2026-01-10 15:45:41 +01:00
a3b66b483b Add admin and donator badge 2026-01-10 15:34:05 +01:00
4c2a6024d7 Add dark mode 2026-01-10 15:27:37 +01:00
5f575024e2 Add Livewire comment system 2026-01-10 15:02:14 +01:00
67f5d0db8b Remove laravelista/comments 2026-01-10 14:06:00 +01:00
24 changed files with 471 additions and 636 deletions

92
app/Livewire/Comment.php Normal file
View File

@@ -0,0 +1,92 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class Comment extends Component
{
use AuthorizesRequests;
public $comment;
public $isReplying = false;
public $replyState = [
'body' => ''
];
public $isEditing = false;
public $editState = [
'body' => ''
];
protected $listeners = [
'refresh' => '$refresh'
];
protected $validationAttributes = [
'replyState.body' => 'reply'
];
public function updatedIsEditing($isEditing)
{
if (! $isEditing) {
return;
}
$this->editState = [
'body' => $this->comment->body
];
}
public function editComment()
{
$this->authorize('update', $this->comment);
$this->comment->update($this->editState);
$this->isEditing = false;
}
public function deleteComment()
{
$this->authorize('destroy', $this->comment);
$this->comment->delete();
$this->dispatch('refresh');
}
public function postReply()
{
if (! $this->comment->depth() < 2) {
return;
}
$this->validate([
'replyState.body' => 'required'
]);
$reply = $this->comment->children()->make($this->replyState);
$reply->user()->associate(auth()->user());
$reply->commentable()->associate($this->comment->commentable);
$reply->save();
$this->replyState = [
'body' => ''
];
$this->isReplying = false;
$this->dispatch('refresh')->self();
}
public function render()
{
return view('livewire.comment');
}
}

56
app/Livewire/Comments.php Normal file
View File

@@ -0,0 +1,56 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use Livewire\WithPagination;
class Comments extends Component
{
use WithPagination;
public $model;
public $newCommentState = [
'body' => ''
];
protected $validationAttributes = [
'newCommentState.body' => 'comment'
];
protected $listeners = [
'refresh' => '$refresh'
];
public function postComment()
{
$this->validate([
'newCommentState.body' => 'required'
]);
$comment = $this->model->comments()->make($this->newCommentState);
$comment->user()->associate(auth()->user());
$comment->save();
$this->newCommentState = [
'body' => ''
];
$this->resetPage();
}
public function render()
{
$comments = $this->model
->comments()
->with('user', 'children.user', 'children.children')
->parent()
->latest()
->paginate(3);
return view('livewire.comments', [
'comments' => $comments
]);
}
}

61
app/Models/Comment.php Normal file
View File

@@ -0,0 +1,61 @@
<?php
namespace App\Models;
use App\Models\Presenters\CommentPresenter;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Comment extends Model
{
use HasFactory, SoftDeletes;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'body'
];
public function presenter()
{
return new CommentPresenter($this);
}
public function user()
{
return $this->belongsTo(User::class);
}
public function scopeParent(Builder $builder)
{
$builder->whereNull('parent_id');
}
public function children()
{
return $this->hasMany(Comment::class, 'parent_id')->oldest();
}
public function commentable()
{
return $this->morphTo();
}
public function parent()
{
return $this->hasOne(Comment::class, 'id', 'parent_id');
}
// Recursevly calculates how deep the nesting is
public function depth(): int
{
return $this->parent
? $this->parent->depth() + 1
: 0;
}
}

View File

@@ -8,7 +8,6 @@ use App\Models\PopularWeekly;
use App\Models\PopularDaily; use App\Models\PopularDaily;
use Conner\Tagging\Taggable; use Conner\Tagging\Taggable;
use Laravelista\Comments\Commentable;
use Laravel\Scout\Searchable; use Laravel\Scout\Searchable;
use Maize\Markable\Markable; use Maize\Markable\Markable;
use Maize\Markable\Models\Like; use Maize\Markable\Models\Like;
@@ -26,7 +25,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
class Episode extends Model implements Sitemapable class Episode extends Model implements Sitemapable
{ {
use Commentable, Markable, Taggable; use Markable, Taggable;
use HasFactory; use HasFactory;
use Searchable; use Searchable;
@@ -161,6 +160,11 @@ class Episode extends Model implements Sitemapable
return cache()->remember('episodeComments' . $this->id, 300, fn() => $this->comments->count()); return cache()->remember('episodeComments' . $this->id, 300, fn() => $this->comments->count());
} }
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
public function getProblematicTags(): string public function getProblematicTags(): string
{ {
$problematicTags = ['Gore', 'Scat', 'Horror']; $problematicTags = ['Gore', 'Scat', 'Horror'];

View File

@@ -10,11 +10,10 @@ use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Conner\Tagging\Taggable; use Conner\Tagging\Taggable;
use Laravelista\Comments\Commentable;
class Hentai extends Model implements Sitemapable class Hentai extends Model implements Sitemapable
{ {
use Commentable, Taggable; use Taggable;
use HasFactory; use HasFactory;
/** /**

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Models\Presenters;
use App\Models\Comment;
use Illuminate\Support\Str;
class CommentPresenter
{
public $comment;
public function __construct(Comment $comment)
{
$this->comment = $comment;
}
public function markdownBody()
{
return Str::of($this->comment->body)->markdown([
'html_input' => 'strip',
]);
}
public function relativeCreatedAt()
{
return $this->comment->created_at->diffForHumans();
}
}

View File

@@ -9,13 +9,11 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Laravelista\Comments\Commenter;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class User extends Authenticatable class User extends Authenticatable
{ {
use HasFactory, Notifiable, Commenter; use HasFactory, Notifiable;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.

View File

@@ -1,56 +0,0 @@
<?php
namespace Laravelista\Comments;
use Laravelista\Comments\Comment;
class CommentPolicy
{
/**
* Can user create the comment
*
* @param $user
* @return bool
*/
public function create($user) : bool
{
return true;
}
/**
* Can user delete the comment
*
* @param $user
* @param Comment $comment
* @return bool
*/
public function delete($user, Comment $comment) : bool
{
return ($user->getKey() == $comment->commenter_id) || $user->is_admin;
}
/**
* Can user update the comment
*
* @param $user
* @param Comment $comment
* @return bool
*/
public function update($user, Comment $comment) : bool
{
return $user->getKey() == $comment->commenter_id;
}
/**
* Can user reply to the comment
*
* @param $user
* @param Comment $comment
* @return bool
*/
public function reply($user, Comment $comment) : bool
{
return $user->getKey() != $comment->commenter_id;
}
}

View File

@@ -1,138 +0,0 @@
<?php
namespace Laravelista\Comments;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Validator;
use App\Notifications\CommentNotification;
use App\Models\User;
use App\Models\Episode;
class CommentService
{
/**
* Handles creating a new comment for given model.
* @return mixed the configured comment-model
*/
public function store(Request $request)
{
// If guest commenting is turned off, authorize this action.
if (Config::get('comments.guest_commenting') == false) {
Gate::authorize('create-comment', Comment::class);
}
// Define guest rules if user is not logged in.
if (!Auth::check()) {
$guest_rules = [
'guest_name' => 'required|string|max:255',
'guest_email' => 'required|string|email|max:255',
];
}
// Merge guest rules, if any, with normal validation rules.
Validator::make($request->all(), array_merge($guest_rules ?? [], [
'commentable_type' => 'required|string',
'commentable_id' => 'required|string|min:1',
'message' => 'required|string'
]))->validate();
$model = $request->commentable_type::findOrFail($request->commentable_id);
$commentClass = Config::get('comments.model');
$comment = new $commentClass;
if (!Auth::check()) {
$comment->guest_name = $request->guest_name;
$comment->guest_email = $request->guest_email;
} else {
$comment->commenter()->associate(Auth::user());
}
$comment->commentable()->associate($model);
$comment->comment = $request->message;
$comment->approved = !Config::get('comments.approval_required');
$comment->save();
return $comment;
}
/**
* Handles updating the message of the comment.
* @return mixed the configured comment-model
*/
public function update(Request $request, Comment $comment)
{
Gate::authorize('edit-comment', $comment);
Validator::make($request->all(), [
'message' => 'required|string'
])->validate();
$comment->update([
'comment' => $request->message
]);
return $comment;
}
/**
* Handles deleting a comment.
* @return mixed the configured comment-model
*/
public function destroy(Comment $comment): void
{
Gate::authorize('delete-comment', $comment);
if (Config::get('comments.soft_deletes') == true) {
$comment->delete();
} else {
$comment->delete();
}
}
/**
* Handles creating a reply "comment" to a comment.
* @return mixed the configured comment-model
*/
public function reply(Request $request, Comment $comment)
{
Gate::authorize('reply-to-comment', $comment);
Validator::make($request->all(), [
'message' => 'required|string'
])->validate();
$commentClass = Config::get('comments.model');
$reply = new $commentClass;
$reply->commenter()->associate(Auth::user());
$reply->commentable()->associate($comment->commentable);
$reply->parent()->associate($comment);
$reply->comment = $request->message;
$reply->approved = !Config::get('comments.approval_required');
$reply->save();
// Notify
if ($comment->commentable_type == 'App\Models\Episode') {
$episode = Episode::where('id', $comment->commentable_id)->firstOrFail();
$url = '/hentai/' . $episode->slug . '#comment-' . $reply->id;
$user = Auth::user();
$parentCommentUser = User::where('id', $comment->commenter_id)->firstOrFail();
$parentCommentUser->notify(
new CommentNotification(
"{$user->name} replied to your comment.",
Str::limit($reply->comment, 50),
$url
)
);
}
return $reply;
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Policies;
use App\Models\User;
use App\Models\Comment;
use Illuminate\Auth\Access\HandlesAuthorization;
class CommentPolicy
{
use HandlesAuthorization;
public function update(User $user, Comment $comment): bool
{
return $user->id === $comment->user_id;
}
public function destroy(User $user, Comment $comment): bool
{
return $user->id === $comment->user_id;
}
}

View File

@@ -19,7 +19,6 @@
"laravel/scout": "^10.20", "laravel/scout": "^10.20",
"laravel/socialite": "^5.24", "laravel/socialite": "^5.24",
"laravel/tinker": "^2.10", "laravel/tinker": "^2.10",
"laravelista/comments": "dev-l11-compatibility",
"livewire/livewire": "^3.6.4", "livewire/livewire": "^3.6.4",
"maize-tech/laravel-markable": "^2.3.0", "maize-tech/laravel-markable": "^2.3.0",
"meilisearch/meilisearch-php": "^1.16", "meilisearch/meilisearch-php": "^1.16",
@@ -50,19 +49,13 @@
} }
], ],
"autoload": { "autoload": {
"exclude-from-classmap": [ "exclude-from-classmap": [],
"vendor/laravelista/comments/src/CommentPolicy.php",
"vendor/laravelista/comments/src/CommentService.php"
],
"psr-4": { "psr-4": {
"App\\": "app/", "App\\": "app/",
"Database\\Factories\\": "database/factories/", "Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/" "Database\\Seeders\\": "database/seeders/"
}, },
"files": [ "files": []
"app/Override/Comments/CommentPolicy.php",
"app/Override/Comments/CommentService.php"
]
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {

196
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "749225dc4ea2aca06f1639bef889cc59", "content-hash": "cf750c98603544d91cf1bdb428866a8f",
"packages": [ "packages": [
{ {
"name": "brick/math", "name": "brick/math",
@@ -581,56 +581,6 @@
], ],
"time": "2025-03-06T22:45:56+00:00" "time": "2025-03-06T22:45:56+00:00"
}, },
{
"name": "erusev/parsedown",
"version": "1.7.4",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35"
},
"type": "library",
"autoload": {
"psr-0": {
"Parsedown": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Emanuil Rusev",
"email": "hello@erusev.com",
"homepage": "http://erusev.com"
}
],
"description": "Parser for Markdown.",
"homepage": "http://parsedown.org",
"keywords": [
"markdown",
"parser"
],
"support": {
"issues": "https://github.com/erusev/parsedown/issues",
"source": "https://github.com/erusev/parsedown/tree/1.7.x"
},
"time": "2019-12-30T22:54:17+00:00"
},
{ {
"name": "firebase/php-jwt", "name": "firebase/php-jwt",
"version": "v7.0.2", "version": "v7.0.2",
@@ -2266,70 +2216,6 @@
}, },
"time": "2025-01-27T14:24:01+00:00" "time": "2025-01-27T14:24:01+00:00"
}, },
{
"name": "laravelista/comments",
"version": "dev-l11-compatibility",
"source": {
"type": "git",
"url": "https://github.com/renatokira/comments.git",
"reference": "490764a774d520a4d9e43395b472d0f9bf802ef6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/renatokira/comments/zipball/490764a774d520a4d9e43395b472d0f9bf802ef6",
"reference": "490764a774d520a4d9e43395b472d0f9bf802ef6",
"shasum": ""
},
"require": {
"erusev/parsedown": "^1.7",
"illuminate/database": "^9.0|^10.0|^11.0",
"illuminate/http": "^9.0|^10.0|^11.0",
"illuminate/pagination": "^9.0|^10.0|^11.0",
"illuminate/queue": "^9.0|^10.0|^11.0",
"illuminate/routing": "^9.0|^10.0|^11.0",
"illuminate/support": "^9.0|^10.0|^11.0",
"php": "^8.0",
"spatie/laravel-honeypot": "^4.5"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Laravelista\\Comments\\ServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Laravelista\\Comments\\": "src/"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "Mario Bašić",
"email": "mario@laravelista.hr",
"homepage": "https://laravelista.hr"
}
],
"description": "Comments for Laravel.",
"keywords": [
"comments",
"laravel"
],
"support": {
"source": "https://github.com/renatokira/comments/tree/l11-compatibility"
},
"funding": [
{
"type": "patreon",
"url": "https://www.patreon.com/laravelista"
}
],
"time": "2024-03-16T14:14:11+00:00"
},
{ {
"name": "league/commonmark", "name": "league/commonmark",
"version": "2.7.1", "version": "2.7.1",
@@ -5817,82 +5703,6 @@
], ],
"time": "2025-05-20T08:42:52+00:00" "time": "2025-05-20T08:42:52+00:00"
}, },
{
"name": "spatie/laravel-honeypot",
"version": "4.6.1",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-honeypot.git",
"reference": "38d164f14939e943b92771859fc206c74cba8397"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-honeypot/zipball/38d164f14939e943b92771859fc206c74cba8397",
"reference": "38d164f14939e943b92771859fc206c74cba8397",
"shasum": ""
},
"require": {
"illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0",
"illuminate/encryption": "^8.0|^9.0|^10.0|^11.0|^12.0",
"illuminate/http": "^8.0|^9.0|^10.0|^11.0|^12.0",
"illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0",
"illuminate/validation": "^8.0|^9.0|^10.0|^11.0|^12.0",
"nesbot/carbon": "^2.0|^3.0",
"php": "^8.0",
"spatie/laravel-package-tools": "^1.9",
"symfony/http-foundation": "^5.1.2|^6.0|^7.0"
},
"require-dev": {
"livewire/livewire": "^2.10|^3.0",
"orchestra/testbench": "^6.23|^7.0|^8.0|^9.0|^10.0",
"pestphp/pest-plugin-livewire": "^1.0|^2.1|^3.0",
"phpunit/phpunit": "^9.6|^10.5|^11.5",
"spatie/pest-plugin-snapshots": "^1.1|^2.1",
"spatie/phpunit-snapshot-assertions": "^4.2|^5.1",
"spatie/test-time": "^1.2.1"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Spatie\\Honeypot\\HoneypotServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Spatie\\Honeypot\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
}
],
"description": "Preventing spam submitted through forms",
"homepage": "https://github.com/spatie/laravel-honeypot",
"keywords": [
"laravel-honeypot",
"spatie"
],
"support": {
"source": "https://github.com/spatie/laravel-honeypot/tree/4.6.1"
},
"funding": [
{
"url": "https://spatie.be/open-source/support-us",
"type": "custom"
}
],
"time": "2025-05-05T13:50:37+00:00"
},
{ {
"name": "spatie/laravel-package-tools", "name": "spatie/laravel-package-tools",
"version": "1.92.7", "version": "1.92.7",
@@ -11900,9 +11710,7 @@
], ],
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": { "stability-flags": {},
"laravelista/comments": 20
},
"prefer-stable": true, "prefer-stable": true,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {

View File

@@ -0,0 +1,38 @@
<?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
{
// Remame old table from laravelista/comments
Schema::rename('comments', 'comments_old');
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('parent_id')->nullable()->constrained('comments')->cascadeOnDelete();
$table->morphs('commentable'); // What is being commented on
$table->text('body');
$table->softDeletes();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('comments');
// Revert to old table from laravelista/comments
Schema::rename('comments_old', 'comments');
}
};

View File

@@ -31,7 +31,7 @@
</a> </a>
</div> </div>
<div class="w-[60vw]"> <div class="w-[60vw]">
@include('partials.comment', ['comment' => $comment]) {{--@include('partials.comment', ['comment' => $comment])--}}
</div> </div>
</div> </div>
@elseif($comment->commentable_type == 'App\Models\Hentai') @elseif($comment->commentable_type == 'App\Models\Hentai')
@@ -53,7 +53,7 @@
</a> </a>
</div> </div>
<div class="w-[60vw]"> <div class="w-[60vw]">
@include('partials.comment', ['comment' => $comment]) {{--@include('partials.comment', ['comment' => $comment])--}}
</div> </div>
</div> </div>
@endif @endif

View File

@@ -0,0 +1,103 @@
<div>
<div class="flex">
<div class="flex-shrink-0 mr-4">
<img class="h-10 w-10 rounded-full" src="{{ $comment->user->getAvatar() }}" alt="{{ $comment->user->name }}">
</div>
<div class="flex-grow">
<div class="flex gap-2">
<p class="font-medium text-gray-900 dark:text-gray-100">{{ $comment->user->name }}</p>
@if($comment->user->is_admin)
<a data-te-toggle="tooltip" title="Admin"><i class="fa-solid fa-crown text-yellow-600"></i></a>
@endif
@if($comment->user->is_patreon)
<a data-te-toggle="tooltip" title="Badge of appreciation for the horny people supporting us! :3"><i class="fa-solid fa-hand-holding-heart text-rose-600"></i></a>
@endif
</div>
<div class="mt-1 flex-grow w-full">
@if ($isEditing)
<form wire:submit.prevent="editComment">
<div>
<label for="comment" class="sr-only">Comment body</label>
<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
@error('editState.body') border-red-500 @enderror"
placeholder="Write something" wire:model.defer="editState.body"></textarea>
@error('editState.body')
<p class="mt-2 text-sm text-red-500">{{ $message }}</p>
@enderror
</div>
<div class="mt-3 flex items-center justify-between">
<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">
Edit
</button>
</div>
</form>
@else
<div class="text-gray-700 dark:text-gray-200">{!! $comment->presenter()->markdownBody() !!}</div>
@endif
</div>
<div class="mt-2 space-x-2">
<span class="text-gray-500 dark:text-gray-300 font-medium">
{{ $comment->presenter()->relativeCreatedAt() }}
</span>
@auth
@if ($comment->depth() < 2)
<button wire:click="$toggle('isReplying')" type="button" class="text-gray-900 dark:text-gray-100 font-medium">
Reply
</button>
@endif
@can ('update', $comment)
<button wire:click="$toggle('isEditing')" type="button" class="text-gray-900 dark:text-gray-100 font-medium">
Edit
</button>
@endcan
@can ('destroy', $comment)
<button x-data="{
confirmCommentDeletion () {
if (window.confirm('Are you sure you want to delete this comment?')) {
@this.call('deleteComment');
}
}
}"
@click="confirmCommentDeletion"
type="button"
class="text-gray-900 dark:text-gray-100 font-medium"
>
Delete
</button>
@endcan
@endauth
</div>
</div>
</div>
<div class="ml-14 mt-6">
@if ($isReplying)
<form wire:submit.prevent="postReply" class="my-4">
<div>
<label for="comment" class="sr-only">Reply body</label>
<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
@error('replyState.body') border-red-500 @enderror"
placeholder="Write something" wire:model.defer="replyState.body"></textarea>
@error('replyState.body')
<p class="mt-2 text-sm text-red-500">{{ $message }}</p>
@enderror
</div>
<div class="mt-3 flex items-center justify-between">
<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-rose-700 focus:outline-none focus:ring-2 focus:ring-rose-500">
Comment
</button>
</div>
</form>
@endif
@foreach ($comment->children as $child)
<livewire:comment :comment="$child" :key="$child->id"/>
@endforeach
</div>
</div>

View File

@@ -0,0 +1,54 @@
<section>
<div class="bg-white dark:bg-neutral-800 shadow sm:rounded-lg sm:overflow-hidden">
<div class="divide-y divide-gray-200 dark:divide-gray-400/40">
<div class="px-4 py-5 sm:px-6">
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-200">Comments</h2>
</div>
<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() }}
@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">
@auth
<div class="flex">
<div class="flex-shrink-0 mr-4">
<img class="h-10 w-10 rounded-full" src="{{ auth()->user()->getAvatar() }}" alt="{{ auth()->user()->name }}">
</div>
<div class="min-w-0 flex-1">
<form wire:submit.prevent="postComment">
<div>
<label for="comment" class="sr-only">Comment body</label>
<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
@error('newCommentState.body') border-red-500 @enderror"
placeholder="Write something" wire:model.defer="newCommentState.body"></textarea>
@error('newCommentState.body')
<p class="mt-2 text-sm text-red-500">{{ $message }}</p>
@enderror
</div>
<div class="mt-3 flex items-center justify-between">
<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-rose-700 focus:outline-none focus:ring-2 focus:ring-rose-500">
Comment
</button>
</div>
</form>
</div>
</div>
@endauth
@guest
<p class="text-gray-900 dark:text-gray-200">Log in to comment.</p>
@endguest
</div>
</div>
</section>

View File

@@ -1,22 +0,0 @@
@inject('markdown', 'Parsedown')
@php
// TODO: There should be a better place for this.
$markdown->setSafeMode(true);
@endphp
<div id="comment-{{ $comment->id }}" class="flex rounded-lg bg-white p-1 mb-2 shadow-[0_2px_15px_-3px_rgba(0,0,0,0.07),0_10px_20px_-2px_rgba(0,0,0,0.04)] dark:bg-neutral-900">
@php $user = cache()->rememberForever('commentUser'.$comment->commenter_id, fn () => \App\Models\User::where('id', $comment->commenter_id)->first()); @endphp
<div>
<img class="w-16 h-16 rounded-full m-2" src="{{ $user->getAvatar() }}" alt="{{ $user->name }} Avatar">
</div>
<div class="text-gray-800 dark:text-gray-200">
<div>
@if($user->is_patreon)
<h5 class="text-gray-800 dark:text-gray-400">{{ $user->name }} <a data-te-toggle="tooltip" title="Badge of appreciation for the horny people supporting us! :3"><i class="fa-solid fa-hand-holding-heart text-rose-600 animate-pulse"></i></a> <small class="text-muted">- {{ \Carbon\Carbon::parse($comment->created_at)->diffForHumans() }}</small></h5>
@else
<h5 class="text-gray-800 dark:text-gray-400">{{ $user->name }} <small class="text-muted">- {{ \Carbon\Carbon::parse($comment->created_at)->diffForHumans() }}</small></h5>
@endif
</div>
<div style="white-space: pre-wrap;">{!! $markdown->line($comment->comment) !!}</div>
<br />
</div>
</div>

View File

@@ -65,7 +65,7 @@
</a> </a>
</div> </div>
<div class="md:w-[60vw]"> <div class="md:w-[60vw]">
@comments(['model' => $episode]) {{--@comments(['model' => $episode])--}}
</div> </div>
</div> </div>
@endforeach @endforeach

View File

@@ -3,6 +3,6 @@
<p class="leading-normal font-bold text-lg text-rose-600"> <p class="leading-normal font-bold text-lg text-rose-600">
{{ __('home.latest-comments') }} {{ __('home.latest-comments') }}
</p> </p>
@comments(['model' => $hentai]) {{--@comments(['model' => $hentai])--}}
</div> </div>
</div> </div>

View File

@@ -26,7 +26,7 @@
<!-- Infos --> <!-- Infos -->
@include('stream.partials.info') @include('stream.partials.info')
<!-- Comments --> <!-- Comments -->
@include('stream.partials.comments') <livewire:comments :model="$episode"/>
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
@if(! $isMobile) @if(! $isMobile)

View File

@@ -1,8 +0,0 @@
<div class="bg-transparent rounded-lg overflow-hidden bg-white dark:bg-neutral-700/40 p-5">
<div id="comments" class="grid grid-cols-1 bg-transparent rounded-lg">
<p class="leading-normal font-bold text-lg text-rose-600">
{{ __('home.latest-comments') }}
</p>
@comments(['model' => $episode])
</div>
</div>

View File

@@ -1,87 +0,0 @@
@inject('markdown', 'Parsedown')
@php
// TODO: There should be a better place for this.
$markdown->setSafeMode(true);
@endphp
<div id="comment-{{ $comment->getKey() }}" class="flex rounded-lg p-1 mb-2 ">
<div class="contents">
<img class="w-12 h-12 rounded-lg m-2" src="{{ $comment->commenter->getAvatar() }}" alt="{{ $comment->commenter->name }} Avatar">
</div>
<div class="text-gray-800 dark:text-gray-200">
<div>
@if($comment->commenter->is_patreon)
<h5 class="text-gray-800 dark:text-gray-400">{{ $comment->commenter->name }} <a data-te-toggle="tooltip" title="Badge of appreciation for the horny people supporting us! :3"><i class="fa-solid fa-hand-holding-heart text-rose-600 animate-pulse"></i></a> <small class="text-muted">- {{ $comment->created_at->diffForHumans() }}</small></h5>
@else
<h5 class="text-gray-800 dark:text-gray-400">{{ $comment->commenter->name }} <small class="text-muted">- {{ $comment->created_at->diffForHumans() }}</small></h5>
@endif
</div>
<div style="white-space: pre-wrap;">{!! $markdown->line($comment->comment) !!}</div>
@if (! Illuminate\Support\Facades\Route::is('profile.comments'))
<div>
@can('reply-to-comment', $comment)
<button data-te-toggle="modal" data-te-target="#reply-modal-{{ $comment->getKey() }}" class="inline-flex items-center px-4 py-2 mt-2 dark:focus:ring-offset-gray-800 bg-rose-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-rose-700 focus:bg-rose-700 active:bg-rose-900 focus:outline-none focus:ring-2 focus:ring-rose-500 focus:ring-offset-2 transition ease-in-out duration-150">@lang('comments::comments.reply')</button>
@endcan
@can('edit-comment', $comment)
<button data-te-toggle="modal" data-te-target="#comment-modal-{{ $comment->getKey() }}" class="inline-flex items-center px-4 py-2 mt-2 dark:focus:ring-offset-gray-800 bg-rose-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-rose-700 focus:bg-rose-700 active:bg-rose-900 focus:outline-none focus:ring-2 focus:ring-rose-500 focus:ring-offset-2 transition ease-in-out duration-150">@lang('comments::comments.edit')</button>
@endcan
@can('delete-comment', $comment)
<a href="{{ route('comments.destroy', $comment->getKey()) }}" onclick="event.preventDefault();document.getElementById('comment-delete-form-{{ $comment->getKey() }}').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>
<form id="comment-delete-form-{{ $comment->getKey() }}" action="{{ route('comments.destroy', $comment->getKey()) }}" method="POST" style="display: none;">
@method('DELETE')
@csrf
</form>
@endcan
</div>
@endif
@can('edit-comment', $comment)
@include('modals.comment-edit')
@endcan
@can('reply-to-comment', $comment)
@include('modals.comment-reply')
@endcan
<br />{{-- Margin bottom --}}
<?php
if (!isset($indentationLevel)) {
$indentationLevel = 1;
} else {
$indentationLevel++;
}
?>
{{-- Recursion for children --}}
@if($grouped_comments->has($comment->getKey()) && $indentationLevel <= $maxIndentationLevel)
{{-- TODO: Don't repeat code. Extract to a new file and include it. --}}
@foreach($grouped_comments[$comment->getKey()] as $child)
<div class="flex">
<div class="h-[100px] bg-rose-600 w-[4px] rounded-lg"></div>
@include('comments::_comment', [
'comment' => $child,
'grouped_comments' => $grouped_comments
])
</div>
@endforeach
@endif
</div>
</div>
{{-- Recursion for children --}}
@if($grouped_comments->has($comment->getKey()) && $indentationLevel > $maxIndentationLevel)
{{-- TODO: Don't repeat code. Extract to a new file and include it. --}}
@foreach($grouped_comments[$comment->getKey()] as $child)
@include('comments::_comment', [
'comment' => $child,
'grouped_comments' => $grouped_comments
])
@endforeach
@endif

View File

@@ -1,30 +0,0 @@
<div class="pt-5">
@if($errors->has('commentable_type'))
<div class="alert alert-danger" role="alert">
{{ $errors->first('commentable_type') }}
</div>
@endif
@if($errors->has('commentable_id'))
<div class="alert alert-danger" role="alert">
{{ $errors->first('commentable_id') }}
</div>
@endif
<form class="block rounded-lg p-6 dark:bg-neutral-700/40" method="POST" action="{{ route('comments.store') }}">
@csrf
@honeypot
<input type="hidden" name="commentable_type" value="\{{ get_class($model) }}" />
<input type="hidden" name="commentable_id" value="{{ $model->getKey() }}" />
<div class="pb-5">
<label class="mb-2 text-xl font-medium leading-tight text-gray-800 dark:text-gray-200 w-full" for="message">@lang('comments::comments.enter_your_message_here')</label>
<textarea 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 @if($errors->has('message')) is-invalid @endif" name="message" rows="3"></textarea>
</div>
<x-primary-button>
@lang('comments::comments.submit')
</x-primary-button>
</form>
</div>
<br />

View File

@@ -1,80 +0,0 @@
@php
if (isset($approved) and $approved == true) {
$comments = $model->approvedComments;
} else {
$comments = $model->comments;
}
@endphp
@if($comments->count() < 1)
<div class="mb-4 rounded-lg text-black px-6 py-5 text-base dark:text-neutral-50 bg-white dark:bg-neutral-700/40">@lang('comments::comments.there_are_no_comments')</div>
@endif
<div>
@php
$comments = $comments->sortByDesc('created_at');
if (isset($perPage)) {
$page = request()->query('page', 1) - 1;
$parentComments = $comments->where('child_id', '');
$slicedParentComments = $parentComments->slice($page * $perPage, $perPage);
$m = Config::get('comments.model'); // This has to be done like this, otherwise it will complain.
$modelKeyName = (new $m)->getKeyName(); // This defaults to 'id' if not changed.
$slicedParentCommentsIds = $slicedParentComments->pluck($modelKeyName)->toArray();
// Remove parent Comments from comments.
$comments = $comments->where('child_id', '!=', '');
$grouped_comments = new \Illuminate\Pagination\LengthAwarePaginator(
$slicedParentComments->merge($comments)->groupBy('child_id'),
$parentComments->count(),
$perPage
);
$grouped_comments->withPath(request()->url());
} else {
$grouped_comments = $comments->groupBy('child_id');
}
@endphp
@foreach($grouped_comments as $comment_id => $comments)
{{-- Process parent nodes --}}
@if($comment_id == '')
@foreach($comments as $comment)
@include('comments::_comment', [
'comment' => $comment,
'grouped_comments' => $grouped_comments,
'maxIndentationLevel' => $maxIndentationLevel ?? 3
])
@endforeach
@endif
@endforeach
</div>
@isset ($perPage)
{{ $grouped_comments->links() }}
@endisset
@if ((! Illuminate\Support\Facades\Route::is('profile.comments')) && (! Illuminate\Support\Facades\Route::is('home.index')))
@auth
@include('comments::_form')
@elseif(Config::get('comments.guest_commenting') == true)
@include('comments::_form', [
'guest_commenting' => true
])
@else
<div class="block rounded-lg p-6 shadow-[0_2px_15px_-3px_rgba(0,0,0,0.07),0_10px_20px_-2px_rgba(0,0,0,0.04)] bg-white dark:bg-neutral-700/40">
<div class="card-body">
<h5 class="mb-2 text-xl font-medium leading-tight text-gray-800 dark:text-gray-200 w-full">@lang('comments::comments.authentication_required')</h5>
<p class="mb-2 leading-tight text-gray-800 dark:text-gray-200 w-full">@lang('comments::comments.you_must_login_to_post_a_comment')</p>
<br>
<a href="{{ route('login') }}" class="relative bg-blue-700 hover:bg-blue-600 text-white font-bold px-6 h-10 rounded pt-2 pb-2" style="width: 12px">
<i class="fa-brands fa-discord"></i> Login
</a>
</div>
</div>
@endauth
@endif