wip: article AI polish, category SEO fields, cover generator, membership plan seeder
CI / PHPUnit (PHP 8.3) (push) Failing after 4s
CI / PHPUnit (PHP 8.2) (push) Failing after 1m9s
CI / Deploy (manual gate) (push) Skipped

This commit is contained in:
2026-09-07 18:48:37 +00:00
parent 263b98b218
commit 3cec4c5e18
121 changed files with 4701 additions and 313 deletions
+46
View File
@@ -129,6 +129,32 @@ a:hover { color: var(--teal); }
}
.post-list { display: grid; gap: 0; }
.post-item__cover {
display: block;
margin: 0 0 0.75rem;
border-radius: 4px;
overflow: hidden;
aspect-ratio: 16 / 9;
background: #e8eef2;
}
.post-item__cover img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.post__cover {
margin: 0 0 1.25rem;
border-radius: 4px;
overflow: hidden;
background: #e8eef2;
}
.post__cover img {
width: 100%;
max-height: 420px;
object-fit: cover;
display: block;
}
.post-item {
padding: 1.35rem 0;
border-bottom: 1px solid var(--line);
@@ -148,6 +174,24 @@ a:hover { color: var(--teal); }
.meta { color: var(--muted); font-size: 0.92rem; margin: 0 0 0.55rem; }
.excerpt { margin: 0; color: #243442; }
.category-hero { margin-bottom: 1.25rem; }
.category-hero__cover {
margin: 0 0 1rem;
border-radius: calc(var(--radius) - 0.35rem);
overflow: hidden;
}
.category-hero__cover img {
width: 100%;
max-height: 240px;
object-fit: cover;
display: block;
}
.category-hero__intro {
margin: 0 0 0.75rem;
color: #243442;
line-height: 1.7;
}
.page-title {
font-family: var(--serif);
font-size: clamp(1.8rem, 3vw, 2.4rem);
@@ -292,6 +336,8 @@ a:hover { color: var(--teal); }
margin-bottom: 0.35rem;
}
.comment__author { color: var(--ink); }
a.comment__author { text-decoration: none; }
a.comment__author:hover { color: var(--teal); }
.comment__body { color: #243442; }
.article-body .body { font-size: 1.05rem; }
+7 -1
View File
@@ -34,6 +34,12 @@
</p>
</header>
@if($article->hasCover() && ($coverUrl = $article->coverUrl()))
<figure class="post__cover">
<img src="{{ $coverUrl }}" alt="{{ $article->title }}" itemprop="image">
</figure>
@endif
@if(count($toc) >= 2)
<nav class="post__toc" aria-labelledby="post-toc-heading">
<h2 id="post-toc-heading" class="post__toc-title">{{ __('frontend.article.toc') }}</h2>
@@ -70,7 +76,7 @@
@forelse($article->comments as $comment)
<article class="comment">
<header class="comment__meta">
<strong class="comment__author">{{ $comment->author }}</strong>
@include('theme::partials.comment-author')
@if($comment->published_at)
<time datetime="{{ $comment->published_at->toIso8601String() }}">
{{ $comment->published_at->format('Y-m-d H:i') }}
+41
View File
@@ -0,0 +1,41 @@
@extends('theme::layout')
@section('content')
<header class="category-hero panel">
@if(filled($category->coverUrl()))
<figure class="category-hero__cover">
<img src="{{ $category->coverUrl() }}" alt="{{ $category->name }}">
</figure>
@endif
<h1 class="page-title">{{ $category->name }}</h1>
@if(filled($category->intro))
<div class="category-hero__intro">{!! nl2br(e($category->intro)) !!}</div>
@elseif(filled($category->description))
<p class="excerpt">{{ $category->description }}</p>
@endif
<p class="meta">{{ __('frontend.category.post_count', ['count' => $articles->total()]) }}</p>
</header>
<div class="post-list">
@forelse($articles as $article)
<article class="post-item">
@if($article->hasCover() && ($coverUrl = $article->coverUrl()))
<a class="post-item__cover" href="{{ url('/show-'.$article->id.'.shtml') }}">
<img src="{{ $coverUrl }}" alt="{{ $article->title }}" loading="lazy">
</a>
@endif
<h2><a href="{{ url('/show-'.$article->id.'.shtml') }}">{{ $article->title }}</a></h2>
<p class="meta">
{{ optional($article->published_at)->format('Y-m-d') }}
· {{ strtoupper($article->content_format) }}
@if($article->stick) · {{ __('frontend.common.pinned') }} @endif
</p>
<p class="excerpt">{{ app(\App\Domain\Blog\ArticleExcerpt::class)->forList($article) }}</p>
</article>
@empty
<div class="panel">
<p class="meta">{{ __('frontend.common.no_posts') }}</p>
</div>
@endforelse
</div>
<div class="pagination">{{ $articles->links() }}</div>
@endsection
+1 -1
View File
@@ -5,7 +5,7 @@
<div class="panel">
@forelse($comments as $comment)
<div class="comment">
<strong>{{ $comment->author }}</strong>
@include('theme::partials.comment-author')
<span class="meta"> · <a href="{{ url('/show-'.$comment->article_id.'.shtml') }}">{{ $comment->article?->title }}</a></span>
<div>{!! $comment->content !!}</div>
</div>
+5
View File
@@ -4,6 +4,11 @@
<div class="post-list">
@forelse($articles as $article)
<article class="post-item">
@if($article->hasCover() && ($coverUrl = $article->coverUrl()))
<a class="post-item__cover" href="{{ url('/show-'.$article->id.'.shtml') }}">
<img src="{{ $coverUrl }}" alt="{{ $article->title }}" loading="lazy">
</a>
@endif
<h2><a href="{{ url('/show-'.$article->id.'.shtml') }}">{{ $article->title }}</a></h2>
<p class="meta">
{{ optional($article->published_at)->format('Y-m-d') }}
+6 -1
View File
@@ -73,7 +73,12 @@
<h3>{{ __('frontend.sidebar.categories') }}</h3>
<ul>
@forelse(($categories ?? []) as $category)
<li><a href="{{ url('/category-'.$category->id.'.shtml') }}">{{ $category->name }}</a></li>
<li>
<a href="{{ $category->publicUrl() }}">{{ $category->name }}</a>
@if((int) $category->articles_count > 0)
<span class="meta">({{ $category->articles_count }})</span>
@endif
</li>
@empty
<li class="note">{{ __('frontend.sidebar.empty_category') }}</li>
@endforelse
@@ -0,0 +1,8 @@
@php
$websiteHref = $comment->websiteHref();
@endphp
@if($websiteHref)
<a class="comment__author" href="{{ $websiteHref }}" rel="nofollow noopener noreferrer" target="_blank">{{ $comment->author }}</a>
@else
<strong class="comment__author">{{ $comment->author }}</strong>
@endif
+15
View File
@@ -11,6 +11,21 @@
</ul>
@endif
<p class="meta">用户名:{{ $user->username ?: $user->name }}</p>
@if(!empty($membership))
<div class="panel" style="margin: 1rem 0; padding: 0.75rem 1rem; background: #eef7f6;">
@if($membership['active'] ?? false)
<p>{{ __('frontend.membership.active', ['plan' => $membership['plan_name'] ?? '—']) }}</p>
@if(!empty($membership['expires_at']))
<p class="meta">{{ __('frontend.membership.expires', ['date' => $membership['expires_at']]) }}</p>
@else
<p class="meta">{{ __('frontend.membership.lifetime') }}</p>
@endif
@else
<p>{{ __('frontend.membership.inactive') }}</p>
@endif
<p class="meta"><a href="{{ url('/plugins/membership') }}">{{ __('frontend.membership.manage') }}</a></p>
</div>
@endif
<form class="stack" method="post" action="{{ url('/post.php') }}">
@csrf
<input type="hidden" name="action" value="modpro">
+3 -1
View File
@@ -68,7 +68,9 @@
<h3>{{ __('frontend.sidebar.categories') }}</h3>
<ul>
@foreach(($categories ?? []) as $category)
<li><a href="{{ url('/category-'.$category->id.'.shtml') }}">{{ $category->name }}</a></li>
<li>
<a href="{{ $category->publicUrl() }}">{{ $category->name }}</a>
</li>
@endforeach
</ul>
</div>