diff --git a/app/Actions/Article/CreateArticleAction.php b/app/Actions/Article/CreateArticleAction.php
index 5c6104f7..0ef9d5b1 100644
--- a/app/Actions/Article/CreateArticleAction.php
+++ b/app/Actions/Article/CreateArticleAction.php
@@ -5,9 +5,8 @@
namespace App\Actions\Article;
use App\Data\CreateArticleData;
-use App\Gamify\Points\ArticleCreated;
use App\Models\Article;
-use App\Notifications\PostArticleToTelegram;
+use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
@@ -15,46 +14,27 @@ final class CreateArticleAction
{
public function execute(CreateArticleData $articleData): Article
{
- if ($articleData->publishedAt) {
- $articleData->publishedAt = new Carbon(
- time: $articleData->publishedAt,
+ if ($articleData->published_at) {
+ $articleData->published_at = new Carbon(
+ time: $articleData->published_at,
tz: config('app.timezone')
);
}
+ /** @var User $author */
+ $author = Auth::user();
+
/** @var Article $article */
$article = Article::query()->create([
'title' => $articleData->title,
- 'slug' => $articleData->title,
+ 'slug' => $articleData->slug,
'body' => $articleData->body,
- 'published_at' => $articleData->publishedAt,
- 'submitted_at' => $articleData->submittedAt,
- 'approved_at' => $articleData->approvedAt,
- 'show_toc' => $articleData->showToc,
- 'canonical_url' => $articleData->canonicalUrl,
- 'user_id' => Auth::id(),
+ 'published_at' => $articleData->published_at,
+ 'submitted_at' => $articleData->is_draft ? null : now(),
+ 'canonical_url' => $articleData->canonical_url,
+ 'user_id' => $author->id,
]);
- if (collect($articleData->tags)->isNotEmpty()) {
- $article->syncTags(tags: $articleData->tags);
- }
-
- if ($articleData->file) {
- $article->addMedia($articleData->file->getRealPath())
- ->toMediaCollection('media');
- }
-
- if ($article->isAwaitingApproval()) {
- // Envoi de la notification sur le channel Telegram pour la validation de l'article.
- Auth::user()?->notify(new PostArticleToTelegram($article));
-
- session()->flash('status', __('notifications.article.created'));
- }
-
- if (Auth::user()?->hasAnyRole(['admin', 'moderator'])) {
- givePoint(new ArticleCreated($article));
- }
-
return $article;
}
}
diff --git a/app/Data/CreateArticleData.php b/app/Data/CreateArticleData.php
index 5afceb09..1bcc8658 100644
--- a/app/Data/CreateArticleData.php
+++ b/app/Data/CreateArticleData.php
@@ -5,7 +5,6 @@
namespace App\Data;
use Carbon\Carbon;
-use Illuminate\Http\UploadedFile;
use Spatie\LaravelData\Data;
final class CreateArticleData extends Data
@@ -14,12 +13,8 @@ public function __construct(
public string $title,
public string $slug,
public string $body,
- public string $showToc,
- public string $canonicalUrl,
- public ?Carbon $publishedAt,
- public ?Carbon $submittedAt,
- public ?Carbon $approvedAt,
- public ?UploadedFile $file,
- public array $tags = [],
+ public string $canonical_url,
+ public ?Carbon $published_at,
+ public bool $is_draft = false,
) {}
}
diff --git a/app/Filament/Resources/ArticleResource.php b/app/Filament/Resources/ArticleResource.php
index 86027d11..2b3c6604 100644
--- a/app/Filament/Resources/ArticleResource.php
+++ b/app/Filament/Resources/ArticleResource.php
@@ -30,31 +30,12 @@ public static function table(Table $table): Table
TextColumn::make('title')
->label('Titre')
->sortable(),
- TextColumn::make('status')
- ->getStateUsing(function ($record) {
- if ($record->approved_at) {
- return 'Approuver';
- } elseif ($record->declined_at) {
- return 'Décliner';
- } elseif ($record->submitted_at) {
- return 'Soumis';
- } else {
- return 'Brouillon';
- }
- })
- ->colors([
- 'success' => 'Approuver',
- 'danger' => 'Décliner',
- 'warning' => 'Soumis',
- 'default' => 'Brouillon',
- ])
- ->badge(),
- TextColumn::make('submitted_at')
- ->label('Date de soumission')
- ->dateTime(),
TextColumn::make('user.name')
->label('Auteur')
->sortable(),
+ TextColumn::make('submitted_at')
+ ->label('Date de soumission')
+ ->dateTime(),
])
->filters([
Filter::make('submitted_at')->query(fn (Builder $query) => $query->whereNotNull('submitted_at'))->label('Soumis'),
@@ -90,9 +71,8 @@ public static function table(Table $table): Table
$record->declined_at = now();
$record->save();
}),
- Tables\Actions\DeleteAction::make('delete'),
+ Tables\Actions\DeleteAction::make(),
]),
-
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
@@ -105,8 +85,8 @@ public static function table(Table $table): Table
->requiresConfirmation()
->modalIcon('heroicon-s-check')
->modalHeading('Approuver')
- ->modalSubheading('Voulez-vous vraiment approuver ces articles ?')
- ->modalButton('Confirmer'),
+ ->modalDescription('Voulez-vous vraiment approuver ces articles ?')
+ ->modalSubmitActionLabel('Confirmer'),
BulkAction::make('declined')
->label('Décliner la sélection')
->icon('heroicon-s-x-mark')
@@ -116,8 +96,8 @@ public static function table(Table $table): Table
->requiresConfirmation()
->modalIcon('heroicon-s-x-mark')
->modalHeading('Décliner')
- ->modalSubheading('Voulez-vous vraiment décliner ces articles ?')
- ->modalButton('Confirmer'),
+ ->modalDescription('Voulez-vous vraiment décliner ces articles ?')
+ ->modalSubmitActionLabel('Confirmer'),
Tables\Actions\DeleteBulkAction::make(),
]),
diff --git a/app/Gamify/Points/ArticleCreated.php b/app/Gamify/Points/ArticlePublished.php
similarity index 85%
rename from app/Gamify/Points/ArticleCreated.php
rename to app/Gamify/Points/ArticlePublished.php
index b37c5777..05089e67 100644
--- a/app/Gamify/Points/ArticleCreated.php
+++ b/app/Gamify/Points/ArticlePublished.php
@@ -7,7 +7,7 @@
use App\Models\Article;
use QCod\Gamify\PointType;
-final class ArticleCreated extends PointType
+final class ArticlePublished extends PointType
{
public int $points = 50;
diff --git a/app/Http/Controllers/ArticlesController.php b/app/Http/Controllers/ArticlesController.php
deleted file mode 100644
index 26fc0172..00000000
--- a/app/Http/Controllers/ArticlesController.php
+++ /dev/null
@@ -1,29 +0,0 @@
-middleware(['auth', 'verified'], ['except' => ['index', 'show']]);
- }
-
- public function create(): View
- {
- return view('articles.new');
- }
-
- public function edit(Article $article): View
- {
- $this->authorize(ArticlePolicy::UPDATE, $article);
-
- return view('articles.edit', compact('article'));
- }
-}
diff --git a/app/Livewire/Articles/Create.php b/app/Livewire/Articles/Create.php
deleted file mode 100644
index 0d566bdf..00000000
--- a/app/Livewire/Articles/Create.php
+++ /dev/null
@@ -1,74 +0,0 @@
- 'onMarkdownUpdate'];
-
- public function mount(): void
- {
- /** @var User $user */
- $user = Auth::user();
-
- $this->published_at = now();
- $this->submitted_at = $user->hasAnyRole(['admin', 'moderator']) ? now() : null;
- $this->approved_at = $user->hasAnyRole(['admin', 'moderator']) ? now() : null;
- }
-
- public function submit(): void
- {
- $this->submitted_at = now();
- $this->store();
- }
-
- public function store(): void
- {
- $this->validate();
-
- /** @var User $user */
- $user = Auth::user();
-
- $article = app(CreateArticleAction::class)->execute(CreateArticleData::from([
- 'title' => $this->title,
- 'slug' => $this->slug,
- 'body' => $this->body,
- 'publishedAt' => $this->published_at,
- 'submittedAt' => $this->submitted_at,
- 'approvedAt' => $this->approved_at,
- 'showToc' => $this->show_toc,
- 'canonicalUrl' => $this->canonical_url,
- ]));
-
- $user->hasRole('user') ?
- $this->redirectRoute('dashboard') :
- $this->redirectRoute('articles.show', $article);
- }
-
- public function render(): View
- {
- return view('livewire.articles.create', [
- 'tags' => Tag::whereJsonContains('concerns', ['post'])->get(),
- ]);
- }
-}
diff --git a/app/Livewire/Articles/Edit.php b/app/Livewire/Articles/Edit.php
deleted file mode 100644
index 3f69ce48..00000000
--- a/app/Livewire/Articles/Edit.php
+++ /dev/null
@@ -1,97 +0,0 @@
- 'onMarkdownUpdate'];
-
- public function mount(Article $article): void
- {
- $this->article = $article;
- $this->title = $article->title;
- $this->body = $article->body;
- $this->slug = $article->slug;
- $this->show_toc = $article->show_toc;
- $this->submitted_at = $article->submitted_at;
- $this->published_at = $article->published_at ? $article->publishedAt()?->format('Y-m-d') : null;
- $this->canonical_url = $article->originalUrl();
- $this->preview = $article->getFirstMediaUrl('media');
- $this->associateTags = $this->tags_selected = old('tags', $article->tags()->pluck('id')->toArray());
- }
-
- public function submit(): void
- {
- $this->alreadySubmitted = $this->article->submitted_at !== null;
- $this->submitted_at = $this->article->submitted_at ?? now();
- $this->store();
- }
-
- public function store(): void
- {
- $this->save();
- }
-
- public function save(): void
- {
- $this->validate();
-
- /** @var User $user */
- $user = Auth::user();
-
- $this->article->update([
- 'title' => $this->title,
- 'slug' => $this->slug,
- 'body' => $this->body,
- 'show_toc' => $this->show_toc,
- 'canonical_url' => $this->canonical_url,
- 'submitted_at' => $this->submitted_at,
- 'published_at' => $this->published_at,
- ]);
-
- $this->article->syncTags($this->associateTags);
-
- if ($this->file) {
- $this->article->addMedia($this->file->getRealPath())->toMediaCollection('media');
- }
-
- Cache::forget('post-'.$this->article->id);
-
- $user->hasRole('user') ?
- $this->redirectRoute('dashboard') :
- $this->redirectRoute('articles.show', $this->article);
- }
-
- public function render(): View
- {
- return view('livewire.articles.edit', [
- 'tags' => Tag::whereJsonContains('concerns', ['post'])->get(),
- ]);
- }
-}
diff --git a/app/Livewire/Components/Slideovers/ArticleForm.php b/app/Livewire/Components/Slideovers/ArticleForm.php
new file mode 100644
index 00000000..8a4a232d
--- /dev/null
+++ b/app/Livewire/Components/Slideovers/ArticleForm.php
@@ -0,0 +1,190 @@
+article = $articleId
+ ? Article::query()->findOrFail($articleId)
+ : new Article;
+
+ $this->form->fill(array_merge($this->article->toArray(), [
+ 'is_draft' => ! $this->article->published_at,
+ 'published_at' => $this->article->published_at,
+ ]));
+ }
+
+ public static function panelMaxWidth(): string
+ {
+ return '6xl';
+ }
+
+ public static function closePanelOnEscape(): bool
+ {
+ return false;
+ }
+
+ public static function closePanelOnClickAway(): bool
+ {
+ return false;
+ }
+
+ public function form(Form $form): Form
+ {
+ return $form
+ ->schema([
+ Forms\Components\Group::make()
+ ->schema([
+ Forms\Components\TextInput::make('title')
+ ->label(__('validation.attributes.title'))
+ ->minLength(10)
+ ->required()
+ ->live(onBlur: true)
+ ->afterStateUpdated(fn ($state, Forms\Set $set) => $set('slug', Str::slug($state))),
+ Forms\Components\Hidden::make('slug'),
+ Forms\Components\Select::make('tags')
+ ->label(__('Tags'))
+ ->relationship(
+ titleAttribute: 'name',
+ modifyQueryUsing: fn (Builder $query): Builder => $query->whereJsonContains('concerns', ['post'])
+ )
+ ->searchable()
+ ->preload()
+ ->multiple()
+ ->required()
+ ->minItems(1)
+ ->maxItems(3),
+ Forms\Components\MarkdownEditor::make('body')
+ ->label(__('validation.attributes.content'))
+ ->fileAttachmentsDisk('public')
+ ->minLength(10)
+ ->minHeight('20.25rem')
+ ->maxHeight('32.75rem')
+ ->required(),
+ Forms\Components\Placeholder::make('')
+ ->content(fn () => new HtmlString(Blade::render(<<<'Blade'
+
+ Blade))),
+ ])
+ ->columnSpan(2),
+ Forms\Components\Group::make()
+ ->schema([
+ Forms\Components\SpatieMediaLibraryFileUpload::make('media')
+ ->collection('media')
+ ->label(__('pages/article.form.cover'))
+ ->maxSize(1024),
+ Forms\Components\Toggle::make('is_draft')
+ ->label(__('pages/article.form.draft'))
+ ->live()
+ ->offIcon('untitledui-check')
+ ->onColor('success')
+ ->onIcon('untitledui-pencil-line')
+ ->helperText(__('pages/article.draft_help')),
+ Forms\Components\DatePicker::make('published_at')
+ ->label(__('pages/article.form.published_at'))
+ ->minDate(now())
+ ->native(false)
+ ->visible(fn (Forms\Get $get): bool => $get('is_draft') === false)
+ ->required(fn (Forms\Get $get): bool => $get('is_draft') === false),
+ Forms\Components\TextInput::make('canonical_url')
+ ->label(__('pages/article.form.canonical_url'))
+ ->helperText(__('pages/article.canonical_help')),
+ ])
+ ->columnSpan(1),
+ ])
+ ->columns(3)
+ ->statePath('data')
+ ->model($this->article);
+ }
+
+ public function save(): void
+ {
+ // @phpstan-ignore-next-line
+ if (! Auth::user()->hasVerifiedEmail()) {
+ throw new UnverifiedUserException(
+ message: __('notifications.exceptions.unverified_user')
+ );
+ }
+
+ if ($this->article?->id) {
+ $this->authorize('update', $this->article);
+ }
+
+ $this->validate();
+
+ $validated = $this->form->getState();
+
+ if ($this->article?->id) {
+ $this->article->update(array_merge($validated, [
+ 'submitted_at' => $validated['is_draft'] ? null : now(),
+ ]));
+ $this->form->model($this->article)->saveRelationships();
+ $this->article->fresh();
+
+ Notification::make()
+ ->title(
+ $this->article->submitted_at
+ ? __('notifications.article.submitted')
+ : __('notifications.article.updated'),
+ )
+ ->success()
+ ->send();
+ } else {
+ $article = app(CreateArticleAction::class)->execute(CreateArticleData::from(array_merge($validated, [
+ 'published_at' => array_key_exists('published_at', $validated)
+ ? new Carbon($validated['published_at'])
+ : null,
+ ])));
+ $this->form->model($article)->saveRelationships();
+
+ Notification::make()
+ ->title(
+ $validated['is_draft'] === false
+ ? __('notifications.article.submitted')
+ : __('notifications.article.created'),
+ )
+ ->success()
+ ->send();
+ }
+
+ $this->redirect(route('articles.show', ['article' => $article ?? $this->article]), navigate: true);
+ }
+
+ public function render(): View
+ {
+ return view('livewire.components.slideovers.article-form');
+ }
+}
diff --git a/app/Livewire/Components/Slideovers/ThreadForm.php b/app/Livewire/Components/Slideovers/ThreadForm.php
index 8bf899b1..9b1073bb 100644
--- a/app/Livewire/Components/Slideovers/ThreadForm.php
+++ b/app/Livewire/Components/Slideovers/ThreadForm.php
@@ -51,6 +51,16 @@ public static function panelMaxWidth(): string
return '2xl';
}
+ public static function closePanelOnEscape(): bool
+ {
+ return false;
+ }
+
+ public static function closePanelOnClickAway(): bool
+ {
+ return false;
+ }
+
public function form(Form $form): Form
{
return $form
@@ -58,13 +68,13 @@ public function form(Form $form): Form
Forms\Components\Hidden::make('user_id'),
Forms\Components\TextInput::make('title')
->label(__('validation.attributes.title'))
- ->helperText(__('pages/forum.max_thread_length'))
+ ->helperText(__('pages/forum.min_thread_length'))
->required()
->live(onBlur: true)
->afterStateUpdated(function (string $operation, $state, Forms\Set $set): void {
$set('slug', Str::slug($state));
})
- ->maxLength(100),
+ ->minLength(10),
Forms\Components\Hidden::make('slug'),
Forms\Components\Select::make('channels')
->multiple()
@@ -88,13 +98,8 @@ public function form(Form $form): Form
->minLength(20),
Forms\Components\Placeholder::make('')
->content(fn () => new HtmlString(Blade::render(<<<'Blade'
-
- {{ __('pages/forum.torchlight') }}
-
- Torchlight
-
-
- Blade))),
+
+ Blade))),
])
->statePath('data')
->model($this->thread);
@@ -109,17 +114,19 @@ public function save(): void
);
}
- $this->validate();
-
if ($this->thread?->id) {
$this->authorize('update', $this->thread);
}
+ $this->validate();
+
+ $validated = $this->form->getState();
+
if ($this->thread?->id) {
- $this->thread->update($this->form->getState());
+ $this->thread->update($validated);
$this->form->model($this->thread)->saveRelationships();
} else {
- $thread = Thread::query()->create($this->form->getState());
+ $thread = Thread::query()->create($validated);
$this->form->model($thread)->saveRelationships();
app(SubscribeToThreadAction::class)->execute($thread);
@@ -137,8 +144,6 @@ public function save(): void
->success()
->send();
- $this->dispatch('thread.save.{$thread->id}');
-
$this->redirect(route('forum.show', ['thread' => $thread ?? $this->thread]), navigate: true);
}
diff --git a/app/Livewire/Modals/DeleteReply.php b/app/Livewire/Modals/DeleteReply.php
deleted file mode 100644
index b7ed9568..00000000
--- a/app/Livewire/Modals/DeleteReply.php
+++ /dev/null
@@ -1,41 +0,0 @@
-reply = Reply::query()->find($id);
- $this->slug = $slug;
- }
-
- public function delete(): void
- {
- $this->authorize('delete', $this->reply);
-
- $this->reply?->delete();
-
- session()->flash('status', __('La réponse a ete supprimée avec succès.'));
-
- $this->redirect('/forum/'.$this->slug);
- }
-
- public function render(): View
- {
- return view('livewire.modals.delete-reply');
- }
-}
diff --git a/app/Livewire/Modals/DeleteThread.php b/app/Livewire/Modals/DeleteThread.php
deleted file mode 100644
index 38b2e390..00000000
--- a/app/Livewire/Modals/DeleteThread.php
+++ /dev/null
@@ -1,43 +0,0 @@
-thread = Thread::query()->find($id);
- }
-
- public static function modalMaxWidth(): string
- {
- return 'xl';
- }
-
- public function delete(): void
- {
- $this->authorize('delete', $this->thread);
-
- $this->thread?->delete();
-
- session()->flash('status', __('Le sujet a été supprimé avec toutes ses réponses.'));
-
- $this->redirectRoute('forum.index');
- }
-
- public function render(): View
- {
- return view('livewire.modals.delete-thread');
- }
-}
diff --git a/app/Livewire/Pages/Articles/Index.php b/app/Livewire/Pages/Articles/Index.php
index 41b956a4..9a3d00e4 100644
--- a/app/Livewire/Pages/Articles/Index.php
+++ b/app/Livewire/Pages/Articles/Index.php
@@ -19,9 +19,10 @@ public function render(): View
return view('livewire.pages.articles.index', [
'articles' => Article::with(['tags', 'user', 'user.transactions'])
->withCount(['views', 'reactions'])
- ->scopes(['published', 'notPinned'])
->orderByDesc('sponsored_at')
->orderByDesc('published_at')
+ ->published()
+ ->notPinned()
->paginate($this->perPage),
'tags' => Tag::query()->whereHas('articles', function ($query): void {
$query->published();
diff --git a/app/Livewire/Pages/Articles/SinglePost.php b/app/Livewire/Pages/Articles/SinglePost.php
index 1e89295a..8dbbfe77 100644
--- a/app/Livewire/Pages/Articles/SinglePost.php
+++ b/app/Livewire/Pages/Articles/SinglePost.php
@@ -40,7 +40,7 @@ public function mount(Article $article): void
->twitterDescription($article->excerpt(150))
->twitterImage($image)
->twitterSite('laravelcm')
- ->withUrl();
+ ->url($article->canonicalUrl());
$this->article = $article;
}
diff --git a/app/Models/Article.php b/app/Models/Article.php
index 6253c8a1..2fa94065 100644
--- a/app/Models/Article.php
+++ b/app/Models/Article.php
@@ -5,6 +5,7 @@
namespace App\Models;
use App\Contracts\ReactableInterface;
+use App\Models\Builders\ArticleQueryBuilder;
use App\Traits\HasAuthor;
use App\Traits\HasSlug;
use App\Traits\HasTags;
@@ -12,7 +13,6 @@
use App\Traits\RecordsActivity;
use CyrildeWit\EloquentViewable\Contracts\Viewable;
use CyrildeWit\EloquentViewable\InteractsWithViews;
-use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
@@ -24,74 +24,21 @@
* @property string $title
* @property string $slug
* @property string $body
- * @property string | null $canonical_url
* @property bool $show_toc
* @property bool $is_pinned
* @property int $is_sponsored
+ * @property string | null $canonical_url
* @property int | null $tweet_id
* @property int $user_id
- * @property \Illuminate\Support\Carbon|null $published_at
- * @property \Illuminate\Support\Carbon|null $submitted_at
- * @property \Illuminate\Support\Carbon|null $approved_at
- * @property \Illuminate\Support\Carbon|null $shared_at
- * @property \Illuminate\Support\Carbon|null $declined_at
- * @property \Illuminate\Support\Carbon|null $sponsored_at
+ * @property-read User $user
+ * @property \Illuminate\Support\Carbon | null $published_at
+ * @property \Illuminate\Support\Carbon | null $submitted_at
+ * @property \Illuminate\Support\Carbon | null $approved_at
+ * @property \Illuminate\Support\Carbon | null $shared_at
+ * @property \Illuminate\Support\Carbon | null $declined_at
+ * @property \Illuminate\Support\Carbon | null $sponsored_at
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
- * @property-read User $user
- * @property-read \Illuminate\Database\Eloquent\Collection $activity
- * @property-read int|null $activity_count
- * @property-read \Spatie\MediaLibrary\MediaCollections\Models\Collections\MediaCollection $media
- * @property-read int|null $media_count
- * @property-read \Illuminate\Database\Eloquent\Collection $reactions
- * @property-read int|null $reactions_count
- * @property-read \Illuminate\Database\Eloquent\Collection $tags
- * @property-read int|null $tags_count
- * @property-read \Illuminate\Database\Eloquent\Collection $views
- * @property-read int|null $views_count
- *
- * @method static \Illuminate\Database\Eloquent\Builder|Article approved()
- * @method static \Illuminate\Database\Eloquent\Builder|Article awaitingApproval()
- * @method static \Illuminate\Database\Eloquent\Builder|Article declined()
- * @method static \Database\Factories\ArticleFactory factory($count = null, $state = [])
- * @method static \Illuminate\Database\Eloquent\Builder|Article forTag(string $tag)
- * @method static \Illuminate\Database\Eloquent\Builder|Article newModelQuery()
- * @method static \Illuminate\Database\Eloquent\Builder|Article newQuery()
- * @method static \Illuminate\Database\Eloquent\Builder|Article notApproved()
- * @method static \Illuminate\Database\Eloquent\Builder|Article notDeclined()
- * @method static \Illuminate\Database\Eloquent\Builder|Article notPinned()
- * @method static \Illuminate\Database\Eloquent\Builder|Article notPublished()
- * @method static \Illuminate\Database\Eloquent\Builder|Article notShared()
- * @method static \Illuminate\Database\Eloquent\Builder|Article orderByUniqueViews(string $direction = 'desc', $period = null, ?string $collection = null, string $as = 'unique_views_count')
- * @method static \Illuminate\Database\Eloquent\Builder|Article orderByViews(string $direction = 'desc', ?\CyrildeWit\EloquentViewable\Support\Period $period = null, ?string $collection = null, bool $unique = false, string $as = 'views_count')
- * @method static \Illuminate\Database\Eloquent\Builder|Article pinned()
- * @method static \Illuminate\Database\Eloquent\Builder|Article popular()
- * @method static \Illuminate\Database\Eloquent\Builder|Article published()
- * @method static \Illuminate\Database\Eloquent\Builder|Article query()
- * @method static \Illuminate\Database\Eloquent\Builder|Article recent()
- * @method static \Illuminate\Database\Eloquent\Builder|Article shared()
- * @method static \Illuminate\Database\Eloquent\Builder|Article sponsored()
- * @method static \Illuminate\Database\Eloquent\Builder|Article submitted()
- * @method static \Illuminate\Database\Eloquent\Builder|Article trending()
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereApprovedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereBody($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereCanonicalUrl($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereCreatedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereDeclinedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereId($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereIsPinned($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereIsSponsored($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article wherePublishedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereSharedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereShowToc($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereSlug($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereSponsoredAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereSubmittedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereTitle($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereTweetId($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereUpdatedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article whereUserId($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Article withViewsCount(?\CyrildeWit\EloquentViewable\Support\Period $period = null, ?string $collection = null, bool $unique = false, string $as = 'views_count')
*/
final class Article extends Model implements HasMedia, ReactableInterface, Viewable
{
@@ -139,29 +86,29 @@ public function getRouteKeyName(): string
return 'slug';
}
- public function excerpt(int $limit = 110): string
+ public function newEloquentBuilder($query): ArticleQueryBuilder
{
- return Str::limit(strip_tags((string) md_to_html($this->body)), $limit);
+ return new ArticleQueryBuilder($query);
}
- public function originalUrl(): ?string
+ public function excerpt(int $limit = 110): string
{
- return $this->canonical_url;
+ return Str::limit(strip_tags((string) md_to_html($this->body)), $limit);
}
- public function canonicalUrl(): ?string
+ public function canonicalUrl(): string
{
- return $this->originalUrl() ?: route('articles.show', $this->slug);
+ return $this->canonical_url ?: route('articles.show', $this->slug);
}
public function nextArticle(): ?Article
{
- return self::published()->where('id', '>', $this->id)->orderBy('id')->first();
+ return self::published()->where('id', '>', $this->id)->orderBy('id')->first(); // @phpstan-ignore-line
}
public function previousArticle(): ?Article
{
- return self::published()->where('id', '<', $this->id)->orderByDesc('id')->first();
+ return self::published()->where('id', '<', $this->id)->orderByDesc('id')->first(); // @phpstan-ignore-line
}
public function readTime(): int
@@ -169,13 +116,6 @@ public function readTime(): int
return Str::readDuration($this->body);
}
- public function registerMediaCollections(): void
- {
- $this->addMediaCollection('media')
- ->singleFile()
- ->acceptsMimeTypes(['image/jpg', 'image/jpeg', 'image/png']);
- }
-
public function isSubmitted(): bool
{
return ! $this->isNotSubmitted();
@@ -251,196 +191,11 @@ public function isNotAwaitingApproval(): bool
return ! $this->isAwaitingApproval();
}
- /**
- * Scope a query to return submitted posts.
- *
- * @param Builder $query
- * @return Builder
- */
- public function scopeSubmitted(Builder $query): Builder
- {
- return $query->whereNotNull('submitted_at');
- }
-
- /**
- * Scope a query to return approved posts.
- *
- * @param Builder $query
- * @return Builder
- */
- public function scopeApproved(Builder $query): Builder
- {
- return $query->whereNotNull('approved_at');
- }
-
- /**
- * Scope a query to return not approved posts.
- *
- * @param Builder $query
- * @return Builder
- */
- public function scopeNotApproved(Builder $query): Builder
- {
- return $query->whereNull('approved_at');
- }
-
- /**
- * Scope a query to return only posts on awaiting approval.
- *
- * @param Builder $query
- * @return Builder
- */
- public function scopeAwaitingApproval(Builder $query): Builder
- {
- return $query->submitted()
- ->notApproved()
- ->notDeclined();
- }
-
- /**
- * Scope a query to return only published posts.
- *
- * @param Builder $query
- * @return Builder
- */
- public function scopePublished(Builder $query): Builder
- {
- return $query->whereDate('published_at', '<=', now())
- ->submitted()
- ->approved();
- }
-
- /**
- * Scope a query to return unpublished posts.
- *
- * @param Builder $query
- * @return Builder
- */
- public function scopeNotPublished(Builder $query): Builder
- {
- return $query->where(function ($query): void {
- $query->whereNull('submitted_at')
- ->orWhereNull('approved_at')
- ->orWhereNull('published_at')
- ->orWhereNotNull('declined_at');
- });
- }
-
- /**
- * Scope a query to return only pinned posts.
- *
- * @param Builder $query
- * @return Builder
- */
- public function scopePinned(Builder $query): Builder
- {
- return $query->where('is_pinned', true);
- }
-
- /**
- * Scope a query to return unpinned posts.
- *
- * @param Builder $query
- * @return Builder
- */
- public function scopeNotPinned(Builder $query): Builder
- {
- return $query->where('is_pinned', false);
- }
-
- /**
- * Scope a query to return shared posts.
- *
- * @param Builder $query
- * @return Builder
- */
- public function scopeShared(Builder $query): Builder
- {
- return $query->whereNotNull('shared_at');
- }
-
- /**
- * Scope a query to return not shared posts.
- *
- * @param Builder $query
- * @return Builder
- */
- public function scopeNotShared(Builder $query): Builder
- {
- return $query->whereNull('shared_at');
- }
-
- /**
- * Scope a query to return only declined posts.
- *
- * @param Builder $query
- * @return Builder
- */
- public function scopeDeclined(Builder $query): Builder
- {
- return $query->whereNotNull('declined_at');
- }
-
- /**
- * Scope a query to return sponsored posts.
- *
- * @param Builder $query
- * @return Builder
- */
- public function scopeSponsored(Builder $query): Builder
- {
- return $query->whereNotNull('sponsored_at');
- }
-
- /**
- * Scope a query to return not declined posts.
- *
- * @param Builder $query
- * @return Builder
- */
- public function scopeNotDeclined(Builder $query): Builder
- {
- return $query->whereNull('declined_at');
- }
-
- /**
- * Scope a query to return recent posts.
- *
- * @param Builder $query
- * @return Builder
- */
- public function scopeRecent(Builder $query): Builder
- {
- return $query->orderByDesc('published_at')
- ->orderByDesc('created_at');
- }
-
- /**
- * Scope a query to return popular posts.
- *
- * @param Builder $query
- * @return Builder
- */
- public function scopePopular(Builder $query): Builder
- {
- return $query->withCount('reactions')
- ->orderBy('reactions_count', 'desc')
- ->orderBy('published_at', 'desc');
- }
-
- /**
- * Scope a query to return trending posts.
- *
- * @param Builder $query
- * @return Builder
- */
- public function scopeTrending(Builder $query): Builder
+ public function registerMediaCollections(): void
{
- return $query->withCount(['reactions' => function ($query): void {
- $query->where('created_at', '>=', now()->subWeek());
- }])
- ->orderBy('reactions_count', 'desc')
- ->orderBy('published_at', 'desc');
+ $this->addMediaCollection('media')
+ ->singleFile()
+ ->acceptsMimeTypes(['image/jpg', 'image/jpeg', 'image/png']);
}
public function markAsShared(): void
@@ -450,6 +205,7 @@ public function markAsShared(): void
public static function nextForSharing(): ?self
{
+ // @phpstan-ignore-next-line
return self::notShared()
->published()
->orderBy('published_at')
@@ -458,6 +214,7 @@ public static function nextForSharing(): ?self
public static function nexForSharingToTelegram(): ?self
{
+ // @phpstan-ignore-next-line
return self::published()
->whereNull('tweet_id')
->orderBy('published_at', 'asc')
diff --git a/app/Models/Builders/ArticleQueryBuilder.php b/app/Models/Builders/ArticleQueryBuilder.php
new file mode 100644
index 00000000..4ebd2d4b
--- /dev/null
+++ b/app/Models/Builders/ArticleQueryBuilder.php
@@ -0,0 +1,110 @@
+whereNotNull('submitted_at');
+ }
+
+ public function approved(): self
+ {
+ return $this->whereNotNull('approved_at');
+ }
+
+ public function notApproved(): self
+ {
+ return $this->whereNull('approved_at');
+ }
+
+ public function published(): self
+ {
+ return $this->whereDate('published_at', '<=', now())
+ ->submitted()
+ ->approved();
+ }
+
+ public function notPublished(): self
+ {
+ return $this->where(function (Builder $query): void {
+ $query->whereNull('submitted_at')
+ ->orWhereNull('approved_at')
+ ->orWhereNull('published_at')
+ ->orWhereNotNull('declined_at');
+ });
+ }
+
+ public function pinned(): self
+ {
+ return $this->where('is_pinned', true);
+ }
+
+ public function notPinned(): self
+ {
+ return $this->where('is_pinned', false);
+ }
+
+ public function shared(): self
+ {
+ return $this->whereNotNull('shared_at');
+ }
+
+ public function notShared(): self
+ {
+ return $this->whereNull('shared_at');
+ }
+
+ public function declined(): self
+ {
+ return $this->whereNotNull('declined_at');
+ }
+
+ public function sponsored(): self
+ {
+ return $this->whereNotNull('sponsored_at');
+ }
+
+ public function notDeclined(): self
+ {
+ return $this->whereNull('declined_at');
+ }
+
+ public function awaitingApproval(): self
+ {
+ return $this->submitted()
+ ->notApproved()
+ ->notDeclined();
+ }
+
+ public function recent(): self
+ {
+ return $this->orderByDesc('published_at')
+ ->orderByDesc('created_at');
+ }
+
+ public function popular(): self
+ {
+ return $this->withCount('reactions')
+ ->orderBy('reactions_count', 'desc')
+ ->orderBy('published_at', 'desc');
+ }
+
+ public function trending(): self
+ {
+ return $this->withCount(['reactions' => function (Builder $query): void {
+ $query->where('created_at', '>=', now()->subWeek());
+ }])
+ ->orderBy('reactions_count', 'desc')
+ ->orderBy('published_at', 'desc');
+ }
+}
diff --git a/app/Policies/ArticlePolicy.php b/app/Policies/ArticlePolicy.php
index c5a1efb0..9a934aeb 100644
--- a/app/Policies/ArticlePolicy.php
+++ b/app/Policies/ArticlePolicy.php
@@ -9,19 +9,14 @@
final class ArticlePolicy
{
- public const UPDATE = 'update';
-
- public const DELETE = 'delete';
-
- public const APPROVE = 'approve';
-
- public const DISAPPROVE = 'disapprove';
-
- public const PINNED = 'togglePinnedStatus';
+ public function create(User $user): bool
+ {
+ return $user->hasVerifiedEmail();
+ }
public function update(User $user, Article $article): bool
{
- return $article->isAuthoredBy($user) || $user->isModerator() || $user->isAdmin();
+ return $article->isAuthoredBy($user);
}
public function delete(User $user, Article $article): bool
diff --git a/app/Policies/DiscussionPolicy.php b/app/Policies/DiscussionPolicy.php
index 4d120452..13052df6 100644
--- a/app/Policies/DiscussionPolicy.php
+++ b/app/Policies/DiscussionPolicy.php
@@ -19,6 +19,11 @@ final class DiscussionPolicy
public const UNSUBSCRIBE = 'unsubscribe';
+ public function create(User $user): bool
+ {
+ return $user->hasVerifiedEmail();
+ }
+
public function update(User $user, Discussion $discussion): bool
{
return $discussion->isAuthoredBy($user) || $user->isModerator() || $user->isAdmin();
diff --git a/app/Policies/ThreadPolicy.php b/app/Policies/ThreadPolicy.php
index 2e104ca9..3a0abb7b 100644
--- a/app/Policies/ThreadPolicy.php
+++ b/app/Policies/ThreadPolicy.php
@@ -9,6 +9,11 @@
final class ThreadPolicy
{
+ public function create(User $user): bool
+ {
+ return $user->hasVerifiedEmail();
+ }
+
public function manage(User $user, Thread $thread): bool
{
return $thread->isAuthoredBy($user) || $user->isModerator() || $user->isAdmin();
diff --git a/composer.json b/composer.json
index e4f8bd15..0c0bda11 100644
--- a/composer.json
+++ b/composer.json
@@ -15,6 +15,7 @@
"doctrine/dbal": "^3.6.4",
"filament/filament": "^3.0",
"filament/notifications": "^3.0",
+ "filament/spatie-laravel-media-library-plugin": "^3.2",
"francescomalatesta/laravel-feature": "dev-l10-compatibility",
"gehrisandro/tailwind-merge-laravel": "^1.1",
"graham-campbell/markdown": "^15.2",
diff --git a/composer.lock b/composer.lock
index 66576d8f..e209f8df 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "9763c31086c27f0736296e14ff9534c1",
+ "content-hash": "ca2ffa41f9aa4275f7c8d699d014be6d",
"packages": [
{
"name": "abraham/twitteroauth",
@@ -2763,6 +2763,43 @@
},
"time": "2024-10-23T07:36:14+00:00"
},
+ {
+ "name": "filament/spatie-laravel-media-library-plugin",
+ "version": "v3.2.123",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/filamentphp/spatie-laravel-media-library-plugin.git",
+ "reference": "35004964449944b98c15a698a1be842f23f6f7e6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/filamentphp/spatie-laravel-media-library-plugin/zipball/35004964449944b98c15a698a1be842f23f6f7e6",
+ "reference": "35004964449944b98c15a698a1be842f23f6f7e6",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/support": "^10.45|^11.0",
+ "php": "^8.1",
+ "spatie/laravel-medialibrary": "^10.0|^11.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Filament\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Filament support for `spatie/laravel-medialibrary`.",
+ "homepage": "https://github.com/filamentphp/filament",
+ "support": {
+ "issues": "https://github.com/filamentphp/filament/issues",
+ "source": "https://github.com/filamentphp/filament"
+ },
+ "time": "2024-10-23T07:36:33+00:00"
+ },
{
"name": "filament/support",
"version": "v3.2.123",
diff --git a/lang/en/global.php b/lang/en/global.php
index 80611895..2ba037da 100644
--- a/lang/en/global.php
+++ b/lang/en/global.php
@@ -81,5 +81,13 @@
'ads' => [
'ln_ui' => 'A fun collection of small, well-coded components to streamline your development process.',
],
+ 'launch_modal' => [
+ 'forum_action' => 'Create thread',
+ 'forum_description' => 'Do you have a question? Ask it in the forum',
+ 'post_action' => 'Writing an article',
+ 'post_description' => 'Share your discoveries with thousands of developers',
+ 'discussion_action' => 'Start a discussion',
+ 'discussion_description' => 'Discuss and debate different themes and ideas',
+ ],
];
diff --git a/lang/en/notifications.php b/lang/en/notifications.php
index 0bba272b..bae0342a 100644
--- a/lang/en/notifications.php
+++ b/lang/en/notifications.php
@@ -5,7 +5,9 @@
return [
'article' => [
- 'created' => 'Thank you for submitting your article. We will only contact you once we have accepted your article.',
+ 'created' => 'Your article has been created.',
+ 'submitted' => 'Thank you for submitting your article. We will only contact you once we have accepted your article.',
+ 'updated' => 'Your article has been updated.',
],
'thread' => [
diff --git a/lang/en/pages/article.php b/lang/en/pages/article.php
index bf5b5691..044645cb 100644
--- a/lang/en/pages/article.php
+++ b/lang/en/pages/article.php
@@ -11,5 +11,21 @@
'next_article' => 'Next article',
'prev_article' => 'Previous article',
'share_article' => 'Share',
+ 'new_article' => 'Write a new article',
+ 'advice' => [
+ 'title' => 'Important advices for articles',
+ 'content' => 'Submit your article to the Laravel.cm portal. We\'re looking for high quality articles revolving around Laravel, PHP, JavaScript, CSS, and related topics. Articles can\'t be promotional in nature and should be educational and informative. We reserve the right to decline articles that don\'t meet our quality standards.',
+ 'twitter' => 'Each approved article will be shared with our users and broadcast on our Twitter account. Feel free to submit as many articles as you like. You can even reference an article on your blog with the original url.',
+ 'submission' => 'After submission for approval, articles are reviewed before being published. No notification of declined articles will be provided. After being published, you cannot edit your article anymore so please review it thoroughly before submitting for approval. Submitting the same article twice or posting spam will result in the banning of your account.',
+ ],
+ 'form' => [
+ 'cover' => 'Cover Image',
+ 'draft' => 'Draft',
+ 'published_at' => 'Published date',
+ 'canonical_url' => 'Canonical URL',
+ ],
+ 'canonical_help' => 'If you have already posted this article on your own site, enter the URL here and the content will be attributed to you.',
+ 'draft_help' => 'Putting an article in draft allows you to update it later.',
+ 'unpublished' => 'This article has not yet been published.',
];
diff --git a/lang/en/pages/forum.php b/lang/en/pages/forum.php
index 4efa3b0d..e4a7385d 100644
--- a/lang/en/pages/forum.php
+++ b/lang/en/pages/forum.php
@@ -37,7 +37,7 @@
'best_answer' => 'Best answer',
'mark_answer' => 'Mark as solution',
'report_spam' => 'Report spam',
- 'max_thread_length' => '100 characters maximum',
+ 'min_thread_length' => '10 characters minimum',
'answer_reply' => 'Answer to',
'threads_count' => ':count messages',
diff --git a/lang/fr/global.php b/lang/fr/global.php
index ff22f176..6c99a412 100644
--- a/lang/fr/global.php
+++ b/lang/fr/global.php
@@ -81,5 +81,13 @@
'ads' => [
'ln_ui' => 'Une collection interactives de petits composants bien codés pour optimiser votre temps de développement.',
],
+ 'launch_modal' => [
+ 'forum_action' => 'Créer un sujet',
+ 'forum_description' => 'Vous avez une question? Posez là dans le forum',
+ 'article_action' => 'Rédiger un article',
+ 'article_description' => 'Partagez vos découvertes à des milliers de développeurs',
+ 'discussion_action' => 'Démarrer une discussion',
+ 'discussion_description' => 'Échangez, débattez sur différentes thématiques et idées.',
+ ],
];
diff --git a/lang/fr/notifications.php b/lang/fr/notifications.php
index 9b93162f..22d562f4 100644
--- a/lang/fr/notifications.php
+++ b/lang/fr/notifications.php
@@ -5,7 +5,9 @@
return [
'article' => [
- 'created' => 'Merci d\'avoir soumis votre article. Vous aurez des nouvelles que lorsque nous accepterons votre article.',
+ 'created' => 'Votre article a été crée.',
+ 'submitted' => 'Merci d\'avoir soumis votre article. Vous aurez des nouvelles que lorsque nous accepterons votre article.',
+ 'updated' => 'Votre article a été mis à jour.',
],
'thread' => [
diff --git a/lang/fr/pages/article.php b/lang/fr/pages/article.php
index df82bfd9..0780602d 100644
--- a/lang/fr/pages/article.php
+++ b/lang/fr/pages/article.php
@@ -11,5 +11,21 @@
'next_article' => 'Article suivant',
'prev_article' => 'Article précédent',
'share_article' => 'Partager',
+ 'new_article' => 'Rédiger un article',
+ 'advice' => [
+ 'title' => 'Conseils importants concernant les articles',
+ 'content' => 'Soumettez votre article au site Laravel.cm. Nous recherchons des articles de haute qualité autour de Laravel, PHP, JavaScript, CSS et autres sujets connexes. Les articles ne peuvent pas être de nature promotionnelle et doivent être éducatifs et informatifs. Nous nous réservons le droit de refuser les articles qui ne répondent pas à nos critères de qualité.',
+ 'twitter' => 'Chaque article approuvé sera partagé avec nos utilisateurs et sera diffusé sur notre compte Twitter. N\'hésitez pas à soumettre autant d\'articles que vous le souhaitez. Vous pouvez même faire référence à un article sur votre blog avec l\'url d\'origine.',
+ 'submission' => "Après avoir été soumis pour approbation, les articles sont examinés avant d'être publiés. Une fois l'article publié, vous ne pouvez plus le modifier. Veuillez donc le relire attentivement avant de le soumettre pour approbation. Si vous soumettez deux fois le même article ou si vous publiez des spams, votre compte sera banni.",
+ ],
+ 'form' => [
+ 'cover' => 'Image de couverture',
+ 'draft' => 'Brouillon',
+ 'published_at' => 'Date de publication',
+ 'canonical_url' => 'URL Canonique',
+ ],
+ 'canonical_help' => 'Modifiez si l\'article a été publié pour la première fois ailleurs (comme sur votre propre blog).',
+ 'draft_help' => 'Mettre en article en brouillon vous donne la possibilité de le modifier plus tard',
+ 'unpublished' => 'Cet article n\'a pas encore été publié.',
];
diff --git a/lang/fr/pages/forum.php b/lang/fr/pages/forum.php
index fff63c38..17baef0a 100644
--- a/lang/fr/pages/forum.php
+++ b/lang/fr/pages/forum.php
@@ -37,7 +37,7 @@
'best_answer' => 'Meilleure réponse',
'mark_answer' => 'Marquer comme réponse',
'report_spam' => 'Signaler un spam',
- 'max_thread_length' => 'Maximum de 100 caractères.',
+ 'min_thread_length' => 'Minimum de 10 caractères.',
'answer_reply' => 'Répondre au sujet',
'threads_count' => ':count messages',
diff --git a/package.json b/package.json
index 346d6891..1dc02356 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"prettier": "npx prettier --write ./resources"
},
"devDependencies": {
+ "@alpinejs/collapse": "^3.14.3",
"@alpinejs/intersect": "^3.6.1",
"@awcodes/alpine-floating-ui": "^3.5.0",
"@ryangjchandler/alpine-tooltip": "^2.0.1",
diff --git a/resources/js/app.js b/resources/js/app.js
index c5a9bfb2..0feeb03c 100644
--- a/resources/js/app.js
+++ b/resources/js/app.js
@@ -3,6 +3,7 @@ import '../../vendor/laravelcm/livewire-slide-overs/resources/js/slide-over';
import intersect from '@alpinejs/intersect'
import Tooltip from '@ryangjchandler/alpine-tooltip'
+import collapse from '@alpinejs/collapse'
import './elements'
import { registerHeader } from './utils/header'
@@ -14,6 +15,7 @@ registerHeader()
Alpine.plugin(intersect)
Alpine.plugin(Tooltip)
+Alpine.plugin(collapse)
window.Alpine = Alpine
diff --git a/resources/views/articles/edit.blade.php b/resources/views/articles/edit.blade.php
deleted file mode 100644
index 285cbf3a..00000000
--- a/resources/views/articles/edit.blade.php
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/resources/views/articles/new.blade.php b/resources/views/articles/new.blade.php
deleted file mode 100644
index 519b3442..00000000
--- a/resources/views/articles/new.blade.php
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/resources/views/components/articles/card-author.blade.php b/resources/views/components/articles/card-author.blade.php
index bfdeb9dc..6ceffc29 100644
--- a/resources/views/components/articles/card-author.blade.php
+++ b/resources/views/components/articles/card-author.blade.php
@@ -52,8 +52,8 @@ class="size-6 ring-1 ring-offset-2 ring-gray-200 ring-offset-gray-50 dark:ring-w
{{ $article->user->name }}
-