- Infinite loop carousels for Similar Artworks & Trending rails
- Mouse wheel horizontal scrolling on both carousels
- Author avatar shown on hover in RailCard (similar + trending)
- Removed "View" badge from RailCard hover overlay
- Added `id` to Meilisearch filterable attributes
- Auto-prepend Scout prefix in meilisearch:configure-index command
- Added author name + avatar to Similar Artworks API response
- Added avatar_url to ArtworkListResource author object
- Added direct /art/{id}/{slug} URL to ArtworkListResource
- Fixed race condition: Similar Artworks no longer briefly shows trending items
- Fixed user_profiles eager load (user_id primary key, not id)
- Bumped /api/art/{id}/similar rate limit to 300/min
- Removed decorative heart icons from tag pills
- Moved ReactionBar under artwork description
96 lines
2.3 KiB
PHP
96 lines
2.3 KiB
PHP
<?php
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
/**
|
|
* App\Models\ArtworkComment
|
|
*
|
|
* @property int $id
|
|
* @property int $artwork_id
|
|
* @property int $user_id
|
|
* @property string|null $content Legacy plain-text column
|
|
* @property string|null $raw_content User-submitted Markdown
|
|
* @property string|null $rendered_content Cached sanitized HTML
|
|
* @property bool $is_approved
|
|
* @property-read Artwork $artwork
|
|
* @property-read User $user
|
|
* @property-read \Illuminate\Database\Eloquent\Collection|CommentReaction[] $reactions
|
|
*/
|
|
class ArtworkComment extends Model
|
|
{
|
|
use HasFactory, SoftDeletes;
|
|
|
|
protected $table = 'artwork_comments';
|
|
|
|
protected $fillable = [
|
|
'legacy_id',
|
|
'artwork_id',
|
|
'user_id',
|
|
'parent_id',
|
|
'content',
|
|
'raw_content',
|
|
'rendered_content',
|
|
'is_approved',
|
|
];
|
|
|
|
protected $casts = [
|
|
'is_approved' => 'boolean',
|
|
];
|
|
|
|
public function artwork(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Artwork::class);
|
|
}
|
|
|
|
public function user(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class);
|
|
}
|
|
|
|
public function parent(): BelongsTo
|
|
{
|
|
return $this->belongsTo(self::class, 'parent_id');
|
|
}
|
|
|
|
public function replies(): HasMany
|
|
{
|
|
return $this->hasMany(self::class, 'parent_id')->orderBy('created_at');
|
|
}
|
|
|
|
/**
|
|
* Recursively eager-load approved replies (tree structure).
|
|
*/
|
|
public function approvedReplies(): HasMany
|
|
{
|
|
return $this->hasMany(self::class, 'parent_id')
|
|
->where('is_approved', true)
|
|
->orderBy('created_at')
|
|
->with(['user.profile', 'approvedReplies']);
|
|
}
|
|
|
|
public function reactions(): HasMany
|
|
{
|
|
return $this->hasMany(CommentReaction::class, 'comment_id');
|
|
}
|
|
|
|
/**
|
|
* Return the best available rendered content for display.
|
|
* Falls back to escaping raw legacy content if rendering isn't done yet.
|
|
*/
|
|
public function getDisplayHtml(): string
|
|
{
|
|
if ($this->rendered_content !== null) {
|
|
return $this->rendered_content;
|
|
}
|
|
|
|
// Lazy render: raw_content takes priority over legacy content
|
|
$raw = $this->raw_content ?? $this->content ?? '';
|
|
return \App\Services\ContentSanitizer::render($raw);
|
|
}
|
|
}
|