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') -
-
- -
-
- -
-
-

- Got some knowledge to share? - - Share your article with our 45.000 Twitter followers. - -

-
-
- - Share Your Article - -
-
-
-
- - -
-
-
- -
- -
- - -
-

- Top authors -

- -
    - @foreach ($topAuthors as $author) -
  • -
    -
    - - - - - - {{ $author->username() }} - - - - - {{ $author->articles_count }} {{ Str::plural('Article', $author->articles_count) }} - - -
    - -
    - - - {{ $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') +
+
+ +
+
+ +
+
+

+ Got some knowledge to share? + + Share your article with our 45.000 Twitter followers. + +

+
+
+ + Share Your Article + +
+
+
+
+ +
+
+
+
+
+

+ Articles +

+
+ +
+

+ {{ number_format($articles->total()) }} Articles +

+ + +
+ + @if ($activeTag) + + @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() }} +
+
+ + +
+ +
+ + +
+

+ Top authors +

+ +
    + @foreach ($topAuthors as $author) +
  • +
    +
    + + + + + + {{ $author->username() }} + + + + + {{ $author->articles_count }} {{ Str::plural('Article', $author->articles_count) }} + + +
    + +
    + + + {{ $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

-
- -
-
- -
- - -
-
-
- -
-
- @foreach ($tags as $tag) - - @endforeach -
-
- -
- - 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 ])
@foreach ($tags as $tag) - + Remove filter
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 -

- - -
- - @if ($selectedTag) - - @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 () {