Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app/Concerns/HasMentions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace App\Concerns;

use App\Models\ReplyAble;
use App\Models\User;
use Illuminate\Support\Collection;

trait HasMentions
{
public function mentionedIn(): ReplyAble
{
if ($this instanceof ReplyAble) {
return $this;
}

return $this->replyAble();
}

public function getMentionedUsers(): Collection
{
preg_match_all('/@([a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w))/', $this->body(), $matches);

return User::whereIn('username', $matches[1])->get();
}
}
11 changes: 11 additions & 0 deletions app/Concerns/ProvidesSubscriptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Models\Subscription;
use App\Models\User;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Ramsey\Uuid\Uuid;

trait ProvidesSubscriptions
{
Expand Down Expand Up @@ -38,4 +39,14 @@ public function hasSubscriber(User $user): bool
->where('user_id', $user->id())
->exists();
}

public function subscribe(User $user): Subscription
{
$subscription = new Subscription();
$subscription->uuid = Uuid::uuid4()->toString();
$subscription->userRelation()->associate($user);
$subscription->subscriptionAbleRelation()->associate($this);

return $this->subscriptionsRelation()->save($subscription);
}
}
18 changes: 18 additions & 0 deletions app/Concerns/ReceivesReplies.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
namespace App\Concerns;

use App\Models\Reply;
use App\Models\User;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\Relation;

trait ReceivesReplies
{
Expand All @@ -15,6 +18,21 @@ public function replies()
return $this->repliesRelation;
}

public function replyAuthors(): HasManyThrough
{
return $this->hasManyThrough(
User::class,
Reply::class,
'replyable_id',
'id',
'id',
'author_id',
)->where(
'replyable_type',
array_search(static::class, Relation::morphMap()),
);
}

/**
* @return \Illuminate\Database\Eloquent\Collection
*/
Expand Down
16 changes: 16 additions & 0 deletions app/Events/ThreadWasCreated.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace App\Events;

use App\Models\Thread;
use Illuminate\Queue\SerializesModels;

final class ThreadWasCreated
{
use SerializesModels;

public function __construct(
public Thread $thread
) {
}
}
43 changes: 41 additions & 2 deletions app/Http/Livewire/Editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace App\Http\Livewire;

use App\Models\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Livewire\Component;

class Editor extends Component
Expand All @@ -20,19 +23,55 @@ class Editor extends Component

public $buttonIcon;

public $hasMentions = false;

public $users;

public $participants;

public function mount($participants = null)
{
$this->users = collect();
$this->participants = $participants ?: collect();
}

public function render()
{
$this->body = old('body', $this->body);

return view('livewire.editor');
}

public function getPreviewProperty()
public function getUsers($query): Collection
{
if (! $this->hasMentions) {
return $this->users;
}

if (! $query) {
return $this->users = $this->participants;
}

$query = Str::after($query, '@');
$users = User::where('username', 'like', "{$query}%")->take(5)->get();

if ($this->participants->isNotEmpty()) {
$users = $this->participants->filter(function ($participant) use ($query) {
return Str::startsWith($participant['username'], $query);
})
->merge($users)
->unique('id');
}

return $this->users = $users;
}

public function getPreviewProperty(): string
{
return replace_links(md_to_html($this->body ?: ''));
}

public function preview()
public function preview(): void
{
$this->emit('previewRequested');
}
Expand Down
3 changes: 3 additions & 0 deletions app/Jobs/CreateThread.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Jobs;

use App\Events\ThreadWasCreated;
use App\Http\Requests\ThreadRequest;
use App\Models\Subscription;
use App\Models\Thread;
Expand Down Expand Up @@ -48,6 +49,8 @@ public function handle(): Thread

$thread->subscriptionsRelation()->save($subscription);

event(new ThreadWasCreated($thread));

return $thread;
}
}
18 changes: 18 additions & 0 deletions app/Listeners/NotifyUsersMentionedInReply.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace App\Listeners;

use App\Events\ReplyWasCreated;
use App\Notifications\MentionNotification;

final class NotifyUsersMentionedInReply
{
public function handle(ReplyWasCreated $event): void
{
$event->reply->getMentionedUsers()->each(function ($user) use ($event) {
$user->notify(new MentionNotification($event->reply));
});
}
}
18 changes: 18 additions & 0 deletions app/Listeners/NotifyUsersMentionedInThread.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace App\Listeners;

use App\Events\ThreadWasCreated;
use App\Notifications\MentionNotification;

final class NotifyUsersMentionedInThread
{
public function handle(ThreadWasCreated $event): void
{
$event->thread->getMentionedUsers()->each(function ($user) use ($event) {
$user->notify(new MentionNotification($event->thread));
});
}
}
22 changes: 22 additions & 0 deletions app/Listeners/SubscribeUsersMentionedInReply.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace App\Listeners;

use App\Events\ReplyWasCreated;
use App\Models\Thread;

final class SubscribeUsersMentionedInReply
{
public function handle(ReplyWasCreated $event): void
{
$event->reply->getMentionedUsers()->each(function ($user) use ($event) {
$replyAble = $event->reply->mentionedIn();

if ($replyAble instanceof Thread && ! $replyAble->hasSubscriber($user)) {
$replyAble->subscribe($user);
}
});
}
}
19 changes: 19 additions & 0 deletions app/Listeners/SubscribeUsersMentionedInThread.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace App\Listeners;

use App\Events\ThreadWasCreated;

final class SubscribeUsersMentionedInThread
{
public function handle(ThreadWasCreated $event): void
{
$event->thread->getMentionedUsers()->each(function ($user) use ($event) {
if (! $event->thread->hasSubscriber($user)) {
$event->thread->subscribe($user);
}
});
}
}
22 changes: 22 additions & 0 deletions app/Mail/MentionEmail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace App\Mail;

use App\Models\MentionAble;
use App\Models\User;
use Illuminate\Mail\Mailable;

final class MentionEmail extends Mailable
{
public function __construct(
public MentionAble $mentionAble,
public User $receiver
) {
}

public function build()
{
return $this->subject("Mentioned: {$this->mentionAble->mentionedIn()->subject()}")
->markdown('emails.mention');
}
}
18 changes: 18 additions & 0 deletions app/Models/MentionAble.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace App\Models;

use Illuminate\Support\Collection;

interface MentionAble
{
public function body(): string;

public function excerpt(): string;

public function author(): User;

public function mentionedIn(): ReplyAble;

public function getMentionedUsers(): Collection;
}
6 changes: 4 additions & 2 deletions app/Models/Reply.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Concerns\HasAuthor;
use App\Concerns\HasLikes;
use App\Concerns\HasMentions;
use App\Concerns\HasTimestamps;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
Expand All @@ -13,11 +14,12 @@
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Str;

final class Reply extends Model
final class Reply extends Model implements MentionAble
{
use HasFactory;
use HasAuthor;
use HasFactory;
use HasLikes;
use HasMentions;
use HasTimestamps;

const TABLE = 'replies';
Expand Down
14 changes: 12 additions & 2 deletions app/Models/Thread.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Concerns\HasAuthor;
use App\Concerns\HasLikes;
use App\Concerns\HasMentions;
use App\Concerns\HasSlug;
use App\Concerns\HasTags;
use App\Concerns\HasTimestamps;
Expand All @@ -26,11 +27,12 @@
use Spatie\Feed\Feedable;
use Spatie\Feed\FeedItem;

final class Thread extends Model implements Feedable, ReplyAble, SubscriptionAble
final class Thread extends Model implements Feedable, ReplyAble, SubscriptionAble, MentionAble
{
use HasFactory;
use HasAuthor;
use HasFactory;
use HasLikes;
use HasMentions;
use HasSlug;
use HasTags;
use HasTimestamps;
Expand Down Expand Up @@ -340,4 +342,12 @@ public function scopeUnlocked(Builder $query): Builder
{
return $query->whereNull('locked_at');
}

public function participants(): SupportCollection
{
return $this->replyAuthors()
->get()
->prepend($this->author())
->unique();
}
}
Loading