diff --git a/app/Concerns/UsesFilters.php b/app/Concerns/UsesFilters.php
index 6d5459e05..241b5251e 100644
--- a/app/Concerns/UsesFilters.php
+++ b/app/Concerns/UsesFilters.php
@@ -6,10 +6,10 @@
trait UsesFilters
{
- public function getFilter(string $default = 'recent'): string
+ public function getFilter(array $options = ['recent', 'resolved', 'unresolved'], string $default = 'recent'): string
{
$filter = (string) request('filter');
- return in_array($filter, ['recent', 'resolved', 'unresolved']) ? $filter : $default;
+ return in_array($filter, $options) ? $filter : $default;
}
}
diff --git a/app/Http/Controllers/Articles/ArticlesController.php b/app/Http/Controllers/Articles/ArticlesController.php
index a0183fdba..93a210e29 100644
--- a/app/Http/Controllers/Articles/ArticlesController.php
+++ b/app/Http/Controllers/Articles/ArticlesController.php
@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Articles;
+use App\Concerns\UsesFilters;
use App\Http\Controllers\Controller;
use App\Http\Middleware\Authenticate;
use App\Http\Requests\ArticleRequest;
@@ -19,6 +20,8 @@
class ArticlesController extends Controller
{
+ use UsesFilters;
+
public function __construct()
{
$this->middleware([Authenticate::class, EnsureEmailIsVerified::class], ['except' => ['index', 'show']]);
@@ -26,21 +29,41 @@ public function __construct()
public function index(Request $request)
{
+ $filter = $this->getFilter(['recent', 'popular', 'trending']);
+
$pinnedArticles = Article::published()
->pinned()
->latest('submitted_at')
->take(4)
->get();
+
+ $articles = Article::published()
+ ->notPinned()
+ ->{$filter}();
+
+ $tags = Tag::whereHas('articles', function ($query) {
+ $query->published();
+ })->orderBy('name')->get();
+
+ if ($activeTag = Tag::where('slug', $request->tag)->first()) {
+ $articles->forTag($activeTag->slug());
+ }
+
$moderators = Cache::remember('moderators', now()->addMinutes(30), function () {
return User::moderators()->get();
});
- $canonical = canonical('articles', $request->only('sortBy', 'tag'));
+
+ $canonical = canonical('articles', ['filter' => $filter, 'tag' => $activeTag?->slug()]);
$topAuthors = Cache::remember('topAuthors', now()->addMinutes(30), function () {
return User::mostSubmissionsInLastDays(365)->take(5)->get();
});
- return view('articles.index', [
+ return view('articles.overview', [
'pinnedArticles' => $pinnedArticles,
+ 'articles' => $articles->paginate(10),
+ 'tags' => $tags,
+ 'activeTag' => $activeTag,
+ 'filter' => $filter,
'moderators' => $moderators,
'canonical' => $canonical,
'topAuthors' => $topAuthors,
@@ -74,7 +97,7 @@ public function create()
{
return view('articles.create', [
'tags' => Tag::all(),
- 'selectedTags' => old('tags', []),
+ 'activeTags' => old('tags', []),
]);
}
@@ -94,7 +117,7 @@ public function edit(Article $article)
return view('articles.edit', [
'article' => $article,
'tags' => Tag::all(),
- 'selectedTags' => old('tags', $article->tags()->pluck('id')->toArray()),
+ 'activeTags' => old('tags', $article->tags()->pluck('id')->toArray()),
]);
}
diff --git a/app/Http/Livewire/ShowArticles.php b/app/Http/Livewire/ShowArticles.php
deleted file mode 100644
index 2a9b8f938..000000000
--- a/app/Http/Livewire/ShowArticles.php
+++ /dev/null
@@ -1,73 +0,0 @@
- ['except' => ''],
- 'sortBy' => ['except' => 'recent'],
- ];
-
- public function render(): View
- {
- $this->sortBy($this->sortBy);
- $articles = Article::published()
- ->notPinned();
-
- $tags = Tag::whereHas('articles', function ($query) {
- $query->published();
- })->orderBy('name')->get();
-
- $selectedTag = Tag::where('name', $this->tag)->first();
-
- if ($this->tag) {
- $articles->forTag($this->tag);
- }
-
- $articles->{$this->sortBy}();
-
- return view('livewire.show-articles', [
- 'articles' => $articles->paginate(10),
- 'tags' => $tags,
- 'selectedTag' => $selectedTag,
- 'selectedSortBy' => $this->sortBy,
- ]);
- }
-
- public function toggleTag($tag): void
- {
- $this->tag = $this->tag !== $tag && $this->tagExists($tag) ? $tag : null;
- }
-
- public function sortBy($sort): void
- {
- $this->sortBy = $this->validSort($sort) ? $sort : 'recent';
- }
-
- public function tagExists($tag): bool
- {
- return Tag::where('slug', $tag)->exists();
- }
-
- public function validSort($sort): bool
- {
- return in_array($sort, [
- 'recent',
- 'popular',
- 'trending',
- ]);
- }
-}
diff --git a/resources/views/articles/index.blade.php b/resources/views/articles/index.blade.php
deleted file mode 100644
index b4cd4c49f..000000000
--- a/resources/views/articles/index.blade.php
+++ /dev/null
@@ -1,89 +0,0 @@
-@title('Community Articles')
-@canonical($canonical)
-
-@extends('layouts.default', ['isTailwindUi' => true])
-
-@section('content')
-
-
-
-
-
-
-
-
- Share Your Article
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @include('layouts._ads._forum_sidebar')
-
-
-
-
- Top authors
-
-
-
- @foreach ($topAuthors as $author)
- -
-
-
-
-
-
-
- {{ $loop->iteration }}
-
-
-
-
-
-
-
- @endforeach
-
-
-
-
-
-
-
-
-
-@endsection
diff --git a/resources/views/articles/overview.blade.php b/resources/views/articles/overview.blade.php
new file mode 100644
index 000000000..f3216685b
--- /dev/null
+++ b/resources/views/articles/overview.blade.php
@@ -0,0 +1,189 @@
+@title('Community Articles')
+@canonical($canonical)
+
+@extends('layouts.default', ['isTailwindUi' => true])
+
+@section('content')
+
+
+
+
+
+
+
+
+ Share Your Article
+
+
+
+
+
+
+
+
+
+
+
+
+ Articles
+
+
+
+
+
+ {{ number_format($articles->total()) }} Articles
+
+
+
+
+
+
+
+
+ Tag filter
+
+
+
+
+
+ @if ($activeTag)
+
+ Filter applied
+
+
+ {{ $activeTag->name() }}
+
+
+
+
+
+
+ @endisset
+
+
+
+ @include('layouts._ads._forum_sidebar')
+
+
+
+
+
+
+ Tag filter
+
+
+
+
+
+
+ Create Article
+
+
+
+
+
+
+
+
+ @if ($activeTag)
+
+ Filter applied
+
+
+ {{ $activeTag->name() }}
+
+
+
+
+ @endif
+
+
+
+
+ @foreach ($articles as $article)
+
+ @endforeach
+
+
+
+ {{ $articles->appends(Request::only('filter', 'tag'))->onEachSide(1)->links() }}
+
+
+
+
+
+
+
+
+ @include('layouts._ads._forum_sidebar')
+
+
+
+
+ Top authors
+
+
+
+ @foreach ($topAuthors as $author)
+ -
+
+
+
+
+
+
+ {{ $loop->iteration }}
+
+
+
+
+
+
+
+ @endforeach
+
+
+
+
+
+
+
+
+
+@endsection
diff --git a/resources/views/components/articles/filter.blade.php b/resources/views/components/articles/filter.blade.php
index 30ffe24d7..3e652dfdc 100644
--- a/resources/views/components/articles/filter.blade.php
+++ b/resources/views/components/articles/filter.blade.php
@@ -1,26 +1,26 @@
-
+
-
+
-
+
\ No newline at end of file
diff --git a/resources/views/components/articles/tag-filter.blade.php b/resources/views/components/articles/tag-filter.blade.php
deleted file mode 100644
index ff206e243..000000000
--- a/resources/views/components/articles/tag-filter.blade.php
+++ /dev/null
@@ -1,73 +0,0 @@
-@props([
- 'selectedTag',
- 'tags',
-])
-
-
-
-
-
-
Filter tag
-
-
-
-
-
-
Select a tag below to filter the results
-
-
-
-
-
-
-
-
-
-
- Cancel
-
-
-
- Remove filter
-
-
-
\ No newline at end of file
diff --git a/resources/views/components/tag-filter.blade.php b/resources/views/components/tag-filter.blade.php
index 4d9f96975..cdf2c34b0 100644
--- a/resources/views/components/tag-filter.blade.php
+++ b/resources/views/components/tag-filter.blade.php
@@ -1,7 +1,10 @@
@props([
'activeTag',
'tags',
- 'filter'
+ 'filter',
+ 'route' => 'forum.tag',
+ 'cancelRoute' => 'forum',
+ 'jumpTo' => null
])
diff --git a/resources/views/forum/overview.blade.php b/resources/views/forum/overview.blade.php
index 0ab240e7f..f16bbc8cc 100644
--- a/resources/views/forum/overview.blade.php
+++ b/resources/views/forum/overview.blade.php
@@ -105,7 +105,7 @@
- {{ $threads->onEachSide(1)->links() }}
+ {{ $threads->appends(Request::only('filter'))->onEachSide(1)->links() }}
diff --git a/resources/views/livewire/show-articles.blade.php b/resources/views/livewire/show-articles.blade.php
deleted file mode 100644
index 0fde9356d..000000000
--- a/resources/views/livewire/show-articles.blade.php
+++ /dev/null
@@ -1,97 +0,0 @@
-
-
-
-
- Articles
-
-
-
-
-
- {{ number_format($articles->total()) }} Articles
-
-
-
-
-
-
-
-
- Tag filter
-
-
-
-
-
- @if ($selectedTag)
-
- Filter applied
-
-
- {{ $selectedTag->name() }}
-
-
-
-
- @endisset
-
-
-
- @include('layouts._ads._forum_sidebar')
-
-
-
-
-
-
- Tag filter
-
-
-
-
-
-
- Create Article
-
-
-
-
-
-
-
-
- @if ($selectedTag)
-
- Filter applied
-
-
- {{ $selectedTag->name() }}
-
-
-
-
- @endif
-
-
-
-
- @foreach ($articles as $article)
-
- @endforeach
-
-
-
- {{ $articles->onEachSide(1)->links() }}
-
-
-
-
-
diff --git a/tests/Feature/ArticleTest.php b/tests/Feature/ArticleTest.php
index c639d7d88..e61dee483 100644
--- a/tests/Feature/ArticleTest.php
+++ b/tests/Feature/ArticleTest.php
@@ -1,12 +1,10 @@
assertResponseStatus(404);
});
-test('sort parameters are set correctly', function () {
- Livewire::test(ShowArticles::class)
- ->assertSet('sortBy', 'recent')
- ->call('sortBy', 'popular')
- ->assertSet('sortBy', 'popular')
- ->call('sortBy', 'trending')
- ->assertSet('sortBy', 'trending')
- ->call('sortBy', 'recent')
- ->assertSet('sortBy', 'recent');
-});
-
-test('tags can be toggled', function () {
- $tag = Tag::factory()->create();
-
- Livewire::test(ShowArticles::class)
- ->call('toggleTag', $tag->slug)
- ->assertSet('tag', $tag->slug)
- ->call('toggleTag', $tag->slug)
- ->assertSet('tag', null);
-});
-
-test('loading page with invalid sort parameter defaults to recent', function () {
- Livewire::withQueryParams(['sortBy' => 'something-invalid'])
- ->test(ShowArticles::class)
- ->assertSet('sortBy', 'recent');
-});
-
-test('invalid sort parameter defaults to recent', function () {
- Livewire::test(ShowArticles::class)
- ->call('sortBy', 'something-invalid')
- ->assertSet('sortBy', 'recent');
-});
-
test('a user can view their articles', function () {
$user = $this->createUser();
@@ -435,3 +400,24 @@
->dontSeeLink('Twitter handle')
->dontSee('so we can link to your profile when we tweet out your article.');
});
+
+test('loading page with invalid sort parameter defaults to recent', function () {
+ Article::factory()->create(['slug' => 'my-first-article', 'submitted_at' => now(), 'approved_at' => now()]);
+
+ $this->get('/articles?filter=invalid')
+ ->see('');
+});
+
+test('can filter articles by tag', function () {
+ $articleOne = Article::factory()->create(['title' => 'My First Article', 'slug' => 'my-first-article', 'submitted_at' => now(), 'approved_at' => now()]);
+ $tagOne = Tag::factory()->create(['slug' => 'one']);
+ $articleOne->syncTags([$tagOne->id]);
+
+ $articleTwo = Article::factory()->create(['title' => 'My Second Article', 'slug' => 'my-second-article', 'submitted_at' => now(), 'approved_at' => now()]);
+ $tagTwo = Tag::factory()->create(['slug' => 'two']);
+ $articleTwo->syncTags([$tagTwo->id]);
+
+ $this->get('/articles?tag=one')
+ ->see('My First Article')
+ ->dontSee('My Second Article');
+});
diff --git a/tests/Feature/CanonicalUrlTest.php b/tests/Feature/CanonicalUrlTest.php
index 3966f19f9..9f2bc011e 100644
--- a/tests/Feature/CanonicalUrlTest.php
+++ b/tests/Feature/CanonicalUrlTest.php
@@ -41,10 +41,10 @@
});
test('query_params_are_always_in_the_same_order', function () {
- Tag::factory()->create(['name' => 'Laravel']);
+ Tag::factory()->create(['name' => 'Laravel', 'slug' => 'laravel']);
- $this->get('articles?utm_source=twitter&utm_medium=social&utm_term=abc123&sortBy=trending&page=2&tag=Laravel')
- ->see('');
+ $this->get('articles?utm_source=twitter&utm_medium=social&utm_term=abc123&filter=trending&page=2&tag=laravel')
+ ->see('');
});
test('standard pages always remove query params from canonical url', function () {