feat: restore sablog browse modes and clean imported HTML
Keep the public site readable after import (nbsp, highlighter, wrapping code, compact pager) and finish the admin extras already in this tree: sticky levels, preview, page cache, and live llms.txt. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,6 +6,7 @@ namespace Plugins\Demo\HelloWorld;
|
||||
|
||||
use App\Blog\Support\PluginManager;
|
||||
use App\Blog\Support\PluginServiceProvider;
|
||||
use App\Filament\Resources\Posts\Schemas\PostForm;
|
||||
use App\Models\Comment;
|
||||
use App\Models\Post;
|
||||
use App\Models\Setting;
|
||||
@@ -53,8 +54,7 @@ class ServiceProvider extends PluginServiceProvider
|
||||
|
||||
// 5. 后台表单注入示例:给文章编辑页加一个演示字段(meta.demo_note)
|
||||
$manager->addAction('filament.post_form', function (Schema $schema) {
|
||||
$schema->components([
|
||||
...$schema->getComponents(),
|
||||
PostForm::appendToSidebar($schema, [
|
||||
Section::make('Hello World 演示')
|
||||
->collapsible()
|
||||
->schema([
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"title": "SEO & GEO",
|
||||
"version": "1.0.0",
|
||||
"description": "完善 SEO(结构化数据/OG 标签)+ GEO(llms.txt / Markdown 导出,面向 AI 搜索引擎优化)",
|
||||
"version": "1.0.1",
|
||||
"description": "完善 SEO(结构化数据/OG 标签)。llms.txt 和 Markdown 导出由核心路由提供。",
|
||||
"author": "LaraLog",
|
||||
"type": "core",
|
||||
"provider": "Plugins\\Neatstudio\\BlogSeo\\ServiceProvider",
|
||||
"usage": "无需额外配置,启用即生效:\n- SEO:页面 title/description/keywords、OG/Twitter 标签、JSON-LD(BlogPosting/WebSite)自动输出\n- GEO:/llms.txt 站点摘要、/posts/{slug}.md 文章 Markdown 导出\n- /sitemap.xml、/robots.txt 由核心路由提供\n站点名/描述等在「博客设置 → 站点」配置,社交账号在「博客设置 → 社交账号」配置"
|
||||
"usage": "无需额外配置,启用即生效:\n- SEO:页面 title/description/keywords、OG/Twitter 标签、JSON-LD(BlogPosting/WebSite)自动输出\n- GEO:核心路由 /llms.txt、/posts/{slug}.md(停用本插件后仍可用)\n- /sitemap.xml、/robots.txt 由核心路由提供\n站点名/描述等在「博客设置 → 站点」配置,社交账号在「博客设置 → 社交账号」配置"
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Plugins\Neatstudio\BlogSeo\Http\SeoController;
|
||||
|
||||
// GEO:面向 AI 搜索引擎(LLM 爬虫)
|
||||
Route::get('/llms.txt', [SeoController::class, 'llmsTxt'])->name('geo.llms');
|
||||
Route::get('/posts/{slug}.md', [SeoController::class, 'postMarkdown'])->where('slug', '[A-Za-z0-9\-]+')->name('geo.post.markdown');
|
||||
@@ -1,71 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace Plugins\Neatstudio\BlogSeo\Http;
|
||||
|
||||
use App\Models\Post;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SeoController
|
||||
{
|
||||
/**
|
||||
* GEO:/llms.txt — 面向 LLM 的站点索引
|
||||
*/
|
||||
public function llmsTxt()
|
||||
{
|
||||
$siteName = blog_setting('site_name', config('blog.name'));
|
||||
$siteDescription = blog_setting('site_description', config('blog.description'));
|
||||
|
||||
$posts = Post::published()->latest('published_at')->limit(200)->get();
|
||||
|
||||
$lines = [];
|
||||
$lines[] = '# '.$siteName;
|
||||
$lines[] = '';
|
||||
$lines[] = '> '.$siteDescription;
|
||||
$lines[] = '';
|
||||
$lines[] = '## 文章(Markdown 全文可访问 /posts/{slug}.md)';
|
||||
$lines[] = '';
|
||||
|
||||
foreach ($posts as $post) {
|
||||
$lines[] = '- ['.$post->title.']('.route('posts.show', $post->id).') — '.Str::limit(strip_tags($post->excerpt_or_fallback), 120);
|
||||
}
|
||||
|
||||
return response(implode("\n", $lines)."\n")
|
||||
->header('Content-Type', 'text/plain; charset=utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* GEO:/posts/{slug}.md — 面向 AI 爬虫的纯 Markdown 导出
|
||||
*/
|
||||
public function postMarkdown(Request $request, string $slug)
|
||||
{
|
||||
$post = Post::query()->where('slug', $slug)->firstOrFail();
|
||||
|
||||
if ($post->status !== 'published') {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$body = $post->content_format === 'markdown' ? $post->content : $this->htmlToMarkdown($post->content);
|
||||
|
||||
$out = "# {$post->title}\n\n";
|
||||
if ($post->excerpt) {
|
||||
$out .= '> '.$post->excerpt."\n\n";
|
||||
}
|
||||
$out .= $body."\n";
|
||||
|
||||
return response($out)->header('Content-Type', 'text/markdown; charset=utf-8');
|
||||
}
|
||||
|
||||
private function htmlToMarkdown(string $html): string
|
||||
{
|
||||
$converter = new \League\HTMLToMarkdown\HtmlConverter([
|
||||
'strip_tags' => false,
|
||||
'hard_break' => true,
|
||||
]);
|
||||
|
||||
return $converter->convert($html);
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,16 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace Plugins\Neatstudio\BlogSeo;
|
||||
|
||||
use App\Blog\Support\PluginManager;
|
||||
use App\Blog\Support\PluginServiceProvider;
|
||||
use Plugins\Neatstudio\BlogSeo\Http\Middleware\SeoMiddleware;
|
||||
use Plugins\Neatstudio\BlogSeo\Http\SeoController;
|
||||
|
||||
class ServiceProvider extends PluginServiceProvider
|
||||
{
|
||||
protected function boot(PluginManager $manager): void
|
||||
{
|
||||
$this->loadRoutes(__DIR__.'/../routes/web.php');
|
||||
|
||||
app('router')->pushMiddlewareToGroup('web', SeoMiddleware::class);
|
||||
|
||||
$manager->addFilter('seo.structured_data', function (array $data) {
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace Plugins\Neatstudio\Membership;
|
||||
|
||||
use App\Blog\Support\PluginManager;
|
||||
use App\Blog\Support\PluginServiceProvider;
|
||||
use App\Filament\Resources\Posts\Schemas\PostForm;
|
||||
use App\Models\Post;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
@@ -53,7 +52,7 @@ class ServiceProvider extends PluginServiceProvider
|
||||
}, 10);
|
||||
|
||||
// 付费文章:[paid]...[/paid] —— 无权限访客替换为 teaser;有权限替换为付费内容块(避免原始标签裸露在页面)
|
||||
$manager->addFilter('post.rendered', function (string $html, \App\Models\Post $post) {
|
||||
$manager->addFilter('post.rendered', function (string $html, Post $post) {
|
||||
$service = app(Services\MembershipService::class);
|
||||
$user = auth()->user();
|
||||
|
||||
@@ -81,25 +80,18 @@ class ServiceProvider extends PluginServiceProvider
|
||||
return $html;
|
||||
}, 10);
|
||||
|
||||
// 后台文章表单注入:付费设置(meta.price 单篇价格 + meta.members_only 会员专享)
|
||||
// 后台文章表单注入:右侧「访问设置」追加会员专享 / 价格
|
||||
$manager->addAction('filament.post_form', function (Schema $schema) {
|
||||
$schema->components([
|
||||
...$schema->getComponents(),
|
||||
Section::make('付费设置')
|
||||
->description('需启用支付插件并配置支付渠道;0 = 免费')
|
||||
->columns(3)
|
||||
->collapsible()
|
||||
->schema([
|
||||
TextInput::make('meta.price')
|
||||
->label('单篇价格(分)')
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->default(0)
|
||||
->helperText('例:9900 = ¥99;填 0 表示单篇免费'),
|
||||
Toggle::make('meta.members_only')
|
||||
->label('会员专享')
|
||||
->helperText('整篇文章仅会员可读'),
|
||||
]),
|
||||
PostForm::appendToAccessSection($schema, [
|
||||
Toggle::make('meta.members_only')
|
||||
->label('会员专享')
|
||||
->helperText('整篇文章仅会员可读'),
|
||||
TextInput::make('meta.price')
|
||||
->label('价格')
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->default(0)
|
||||
->helperText('单位:分,例 9900 = ¥99;0 表示单篇免费'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user