conversationId = $conversationId; $this->authorizeConversation(); $this->markAsRead(); } public function loadEarlier(): void { $this->messageLimit += 25; } #[On('conversation-search-toggled')] public function toggleMessageSearch(int $conversationId): void { if ($conversationId !== $this->conversationId) { return; } $this->messageSearchOpen = ! $this->messageSearchOpen; if (! $this->messageSearchOpen) { $this->clearMessageSearch(); } } public function updatedMessageSearch(): void { $this->messageLimit = 40; unset($this->messages, $this->hasMoreMessages, $this->searchResultsCount); } public function clearMessageSearch(): void { $this->messageSearch = ''; $this->messageLimit = 40; unset($this->messages, $this->hasMoreMessages, $this->searchResultsCount); } public function closeMessageSearch(): void { $this->messageSearchOpen = false; $this->clearMessageSearch(); } #[On('message-created')] public function refreshMessages(int $conversationId): void { if ($conversationId !== $this->conversationId) { return; } unset($this->messages, $this->hasMoreMessages); $this->markAsRead(); } #[On('conversation-updated')] public function refreshConversation(int $conversationId): void { if ($conversationId === $this->conversationId) { unset($this->conversation); } } #[Computed] public function conversation(): Conversation { $conversation = Conversation::query() ->forUser(Auth::user()) ->with([ 'participants' => fn ($query) => $query ->select(['id', 'conversation_id', 'user_id', 'role', 'last_read_at']) ->with('user:id,name,email'), ]) ->findOrFail($this->conversationId); Gate::authorize('view', $conversation); return $conversation; } /** * @return Collection */ #[Computed] public function messages(): Collection { return $this->messageQuery() ->with('sender:id,name,email') ->latest() ->limit($this->messageLimit) ->get() ->reverse() ->values(); } #[Computed] public function hasMoreMessages(): bool { return $this->messageQuery() ->count() > $this->messageLimit; } #[Computed] public function searchResultsCount(): int { if (! $this->messageSearchIsActive()) { return 0; } return $this->messageQuery()->count(); } public function dateLabel(CarbonInterface $timestamp): string { if ($timestamp->isToday()) { return __('Today'); } if ($timestamp->isYesterday()) { return __('Yesterday'); } return $timestamp->format('F j, Y'); } public function messageSearchIsActive(): bool { return $this->messageSearchOpen && trim($this->messageSearch) !== ''; } public function highlightedText(string $text): HtmlString { $escapedText = e($text); $search = trim($this->messageSearch); if (! $this->messageSearchIsActive() || $search === '') { return new HtmlString($escapedText); } $escapedSearch = e($search); $highlighted = preg_replace( '/('.preg_quote($escapedSearch, '/').')/iu', '$1', $escapedText, ); return new HtmlString($highlighted ?? $escapedText); } private function authorizeConversation(): void { $conversation = Conversation::query() ->forUser(Auth::user()) ->findOrFail($this->conversationId); Gate::authorize('view', $conversation); } private function markAsRead(): void { ConversationParticipant::query() ->where('conversation_id', $this->conversationId) ->where('user_id', Auth::id()) ->update(['last_read_at' => now()]); } /** * @return Builder */ private function messageQuery(): Builder { $search = trim($this->messageSearch); return Message::query() ->where('conversation_id', $this->conversationId) ->when($this->messageSearchIsActive(), fn (Builder $query) => $query->where(function (Builder $query) use ($search): void { $query ->where('body', 'like', "%{$search}%") ->orWhereHas('sender', fn (Builder $sender) => $sender ->where('name', 'like', "%{$search}%") ->orWhere('email', 'like', "%{$search}%")); })); } public function render(): View { return view('livewire.chat.conversation-view'); } }