body = Str::sanitizeHtml($comment->body); }); static::deleting(function (self $comment): void { $comment->replies()->each(fn ($reply) => $reply->delete()); }); static::forceDeleting(function (self $comment): void { $comment->attachments()->delete(); $comment->reactions()->delete(); $comment->mentions()->detach(); }); } protected $fillable = [ 'body', 'parent_id', 'commenter_id', 'commenter_type', 'edited_at', ]; public function getTable(): string { return CommentsConfig::getCommentTable(); } /** @return array */ protected function casts(): array { return [ 'edited_at' => 'datetime', ]; } protected static function newFactory(): CommentFactory { return CommentFactory::new(); } public function commentable(): MorphTo { return $this->morphTo(); } public function commenter(): MorphTo { return $this->morphTo(); } public function parent(): BelongsTo { return $this->belongsTo(CommentsConfig::getCommentModel(), 'parent_id'); } public function replies(): HasMany { return $this->hasMany(CommentsConfig::getCommentModel(), 'parent_id'); } public function reactions(): HasMany { return $this->hasMany(Reaction::class); } public function attachments(): HasMany { return $this->hasMany(Attachment::class); } public function mentions(): MorphToMany { return $this->morphedByMany( CommentsConfig::getCommenterModel(), 'commenter', CommentsConfig::getTableName('mentions'), 'comment_id', 'commenter_id', ); } public function isReply(): bool { return $this->parent_id !== null; } public function isTopLevel(): bool { return $this->parent_id === null; } public function hasReplies(): bool { return $this->replies()->exists(); } public function isEdited(): bool { return $this->edited_at !== null; } public function canReply(): bool { return $this->depth() < CommentsConfig::getMaxDepth(); } public function depth(): int { $depth = 0; $maxDepth = CommentsConfig::getMaxDepth(); $parentId = $this->parent_id; while ($parentId !== null && $depth < $maxDepth) { $depth++; $parentId = static::where('id', $parentId)->value('parent_id'); } return $depth; } public function renderBodyWithMentions(): string { $body = $this->body; if ($this->hasRichEditorMentions($body)) { return RichContentRenderer::make($body) ->mentions([ MentionProvider::make('@') ->getLabelsUsing(fn (array $ids): array => CommentsConfig::getCommenterModel()::query() ->whereIn('id', $ids) ->pluck('name', 'id') ->all()), ]) ->toHtml(); } $mentionNames = $this->mentions->pluck('name')->filter()->unique(); foreach ($mentionNames as $name) { $escapedName = e($name); $styledSpan = '@'.$escapedName.''; $body = str_replace("@{$name}", $styledSpan, $body); $body = str_replace("@{$name}", $styledSpan, $body); } return $body; } protected function hasRichEditorMentions(string $body): bool { return str_contains($body, 'data-type="mention"') || str_contains($body, '

') || str_contains($body, '