Compare commits

...
Author SHA1 Message Date
9c75e66cbb Merge pull request '[MTM] Hide draft articles from the front page and mark drafts when viewed directly' (#141) from develop into main
Reviewed-on: #141
2026-09-26 17:31:02 +02:00
a31c218784
Hide draft articles from the front page and mark drafts when viewed directly
Add an Article::published() scope and use it on the front page (which
previously showed drafts), the /blog listing and the feeds. Draft
articles are still reachable at their URL, but now show a draft notice,
a "[Draft]" title prefix and a noindex robots meta tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 16:20:50 +01:00
13 changed files with 84 additions and 7 deletions

View file

@ -17,7 +17,7 @@ class ArticlesController extends Controller
*/
public function index(?int $year = null, ?int $month = null): View
{
$articles = Article::where('published', '1')
$articles = Article::published()
->date($year, $month)
->orderBy('updated_at', 'desc')
->simplePaginate(5);

View file

@ -15,7 +15,7 @@ class FeedsController extends Controller
*/
public function blogJson(): JsonResponse
{
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get();
$articles = Article::published()->latest('updated_at')->take(20)->get();
$data = [
'version' => 'https://jsonfeed.org/version/1.1',
'title' => 'The JSON Feed for '.config('user.display_name').'’s blog',
@ -89,7 +89,7 @@ class FeedsController extends Controller
*/
public function blogJf2(): JsonResponse
{
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get();
$articles = Article::published()->latest('updated_at')->take(20)->get();
$items = [];
foreach ($articles as $article) {
$items[] = [

View file

@ -26,7 +26,7 @@ class FrontPageController extends Controller
->withCount(['webmentions AS reposts' => function ($query) {
$query->where('type', 'repost-of');
}])->get();
$articles = Article::latest()->get();
$articles = Article::published()->latest()->get();
$bookmarks = Bookmark::latest()->with('tags')->get();
$likes = Like::latest()->get();

View file

@ -101,6 +101,14 @@ class Article extends Model
);
}
/**
* Scope a query to only include published (non-draft) articles.
*/
public function scopePublished(Builder $query): Builder
{
return $query->where('published', 1);
}
/**
* Scope a query to only include articles from a particular year/month.
*/

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -63,3 +63,16 @@
}
}
@layer components {
.draft-notice {
margin-block-end: 2ex;
padding: 1ex 1em;
border: 2px dashed var(--clr-border);
border-radius: 8px;
background: light-dark(
oklch(95% 0.08 85deg),
oklch(30% 0.08 85deg)
);
}
}

View file

@ -1,7 +1,18 @@
@extends('master')
@section('title'){{ strip_tags($article->title) }} « @stop
@section('title'){{ $article->published ? '' : '[Draft] ' }}{{ strip_tags($article->title) }} « @stop
@if (! $article->published)
@push('head')
<meta name="robots" content="noindex, nofollow">
@endpush
@endif
@section('content')
@unless ($article->published)
<aside class="draft-notice" role="note">
<strong>Draft</strong> — this post hasn’t been published yet. It isn’t listed on the site or in feeds, so only people with this link can see it.
</aside>
@endunless
@include('templates.article', ['article' => $article])
@stop

View file

@ -22,6 +22,7 @@
@if (File::exists(public_path('gpg.key')))
<link rel="pgpkey" href="{{ config('app.url')}}/gpg.key">
@endif
@stack('head')
</head>
<body>
<header>

View file

@ -29,6 +29,39 @@ class ArticlesTest extends TestCase
$response->assertViewIs('articles.show');
}
#[Test]
public function articles_page_does_not_list_drafts(): void
{
Article::factory()->create(['title' => 'Published Article']);
Article::factory()->create(['title' => 'Draft Article', 'published' => 0]);
$this->get('/blog')
->assertSee('Published Article')
->assertDontSee('Draft Article');
}
#[Test]
public function draft_article_is_viewable_and_marked_as_draft(): void
{
$article = Article::factory()->create(['published' => 0]);
$this->get($article->link)
->assertOk()
->assertSee('draft-notice', false)
->assertSee('<meta name="robots" content="noindex, nofollow">', false)
->assertSee('<title>[Draft] ', false);
}
#[Test]
public function published_article_is_not_marked_as_draft(): void
{
$article = Article::factory()->create();
$this->get($article->link)
->assertDontSee('draft-notice', false)
->assertDontSee('noindex', false);
}
#[Test]
public function wrong_date_in_url_redirects_to_correct_date()
{

View file

@ -31,4 +31,15 @@ class FrontPageTest extends TestCase
->assertSee('https://example.com')
->assertSee('Like 1');
}
#[Test]
public function front_page_does_not_show_draft_articles(): void
{
Article::factory()->create(['title' => 'Published Article']);
Article::factory()->create(['title' => 'Draft Article', 'published' => 0]);
$this->get('/')
->assertSee('Published Article')
->assertDontSee('Draft Article');
}
}