From 469160dc3f33ba62d671d89d466ce8118fac4fcb Mon Sep 17 00:00:00 2001 From: gouki Date: Fri, 18 Sep 2026 08:24:16 +0000 Subject: [PATCH] feat: add admin env badge, quick post button, and harden plugin enable Show local/prod in the Filament topbar, jump to create post in one click, tighten sidebar spacing, and stop Hello World from 500ing when its tables are missing. Co-authored-by: Cursor --- CHANGELOG.md | 6 + Fetures.md | 12 + README.md | 3 +- .../PluginManagerServiceProvider.php | 6 +- app/Blog/Services/PostContentRenderer.php | 1 + app/Blog/Support/PluginManager.php | 38 +- app/Filament/Support/AdminEnvironment.php | 31 + app/Providers/AppServiceProvider.php | 22 +- app/Providers/Filament/AdminPanelProvider.php | 5 + .../database/mysql-schema-20260917-232725.sql | 582 ++++++++++++++++++ docs/journal.md | 7 + docs/plugins.md | 41 +- docs/tutorial-plugin.md | 24 +- .../src/Filament/Pages/HelloWorldSettings.php | 2 + .../src/Http/HelloWorldController.php | 4 +- .../demo.hello-world/src/ServiceProvider.php | 10 +- .../filament/hooks/topbar-tools.blade.php | 22 + tests/Feature/AdminPagesTest.php | 32 + tests/Feature/DemoPluginTest.php | 20 + 19 files changed, 825 insertions(+), 43 deletions(-) create mode 100644 app/Filament/Support/AdminEnvironment.php create mode 100644 docs/database/mysql-schema-20260917-232725.sql create mode 100644 docs/journal.md create mode 100644 resources/views/filament/hooks/topbar-tools.blade.php diff --git a/CHANGELOG.md b/CHANGELOG.md index ecfcb8a..e94476e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2026-09-17 + +- Admin top-right now shows the current environment (`local` / `prod`) and a **写文章** shortcut to create a post. +- Tightened Filament sidebar vertical spacing (correct `fi-sidebar-item-btn` selectors + smaller group gaps). +- Hello World plugin: enable runs plugin migrations; missing `hello_world_logs` no longer 500s the demo page; plugin boot/hooks are isolated. + ## 2026-09-16 - Switched new attachments/covers to `xpier/filament-media-library`. diff --git a/Fetures.md b/Fetures.md index 7a8578b..3edd8e4 100644 --- a/Fetures.md +++ b/Fetures.md @@ -12,6 +12,18 @@ Modern blog migrated from SaBlog-X: Filament admin, themes, plugins, sablog impo See `README.md` and `docs/` for install, import, plugins, and themes. +## Admin chrome + +- Top-right badge shows the current app environment (`local` / `prod` / others). +- Top-right **写文章** button jumps to `/admin/posts/create`. +- Sidebar nav is compacted (Filament default `gap-y-7` / `py-8` is too tall for this site). + +## Hello World plugin + +- Enabling a plugin now runs that plugin's `database/migrations` automatically. +- Plugin boot / hook failures are isolated so one plugin cannot take down the whole admin. +- `/hello-world` stays available even if `hello_world_logs` is missing. + ## Media library (xpier) - Package: `xpier/filament-media-library` (admin Media Library + MediaPicker). diff --git a/README.md b/README.md index e2fa48f..79484e0 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ - **脚本迁移**:`php artisan sablog:import` 一键从老库迁入全部数据(文章/分类/评论/标签/友链/用户/附件),保留原始 ID,老 URL 全部 301 兼容 - **多主题**:`themes/` 目录即皮肤,后台一键切换/上传 ZIP;内置 sablog 经典风 + modern 简约风两套 -- **插件系统**:`plugins/` 目录 + 钩子系统(action/filter),ZIP 安装 + 可选远程市场 +- **插件系统**:`plugins/` 目录 + 钩子系统(action/filter),ZIP 安装 + 可选远程市场;启用时自动跑插件迁移 +- **后台顶栏**:右上角显示当前环境(local/prod)+ 一键写文章 - **内置插件**:SEO & GEO(JSON-LD/OG/llms.txt/Markdown 导出)、AI 评论审核 + 内容润色(Workerman 异步)、支付(支付宝/微信/沙箱)、会员(套餐订阅 + 付费文章) - **附件全走 S3**:R2 / COS / OSS 兼容,新上传零落盘,`attachments:sync-s3` 一键同步老附件(大文件 Multipart) diff --git a/app/Blog/Providers/PluginManagerServiceProvider.php b/app/Blog/Providers/PluginManagerServiceProvider.php index df5b7ea..fcd71e2 100644 --- a/app/Blog/Providers/PluginManagerServiceProvider.php +++ b/app/Blog/Providers/PluginManagerServiceProvider.php @@ -45,7 +45,11 @@ class PluginManagerServiceProvider extends ServiceProvider $provider = $manager->resolveProvider($key); if ($provider && class_exists($provider)) { - $provider::bootPlugin($manager); + try { + $provider::bootPlugin($manager); + } catch (\Throwable $e) { + report($e); + } } } } diff --git a/app/Blog/Services/PostContentRenderer.php b/app/Blog/Services/PostContentRenderer.php index d37a393..5ab8f8e 100644 --- a/app/Blog/Services/PostContentRenderer.php +++ b/app/Blog/Services/PostContentRenderer.php @@ -49,6 +49,7 @@ class PostContentRenderer // 插件钩子:付费内容过滤、AI 处理等 $html = app(\App\Blog\Support\PluginManager::class)->applyFilters('post.rendered', $html, $post); + $html = is_string($html) ? $html : ''; // 兜底:任何情况下不把裸 [paid] 标签输出到页面(如会员插件被停用时) return preg_replace('/\[(\/?paid)\]/i', '', $html); diff --git a/app/Blog/Support/PluginManager.php b/app/Blog/Support/PluginManager.php index fc1296d..9a88f16 100644 --- a/app/Blog/Support/PluginManager.php +++ b/app/Blog/Support/PluginManager.php @@ -6,6 +6,7 @@ declare(strict_types=1); namespace App\Blog\Support; use App\Models\PluginRecord; +use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\File; use Illuminate\Support\Str; use RuntimeException; @@ -155,6 +156,8 @@ class PluginManager throw new RuntimeException('无法启用 '.$plugin.':'.implode(';', $problems)); } + $this->migratePlugin($plugin); + [$vendor, $name] = array_pad(explode('.', $plugin), 2, $plugin); PluginRecord::query()->updateOrCreate( ['vendor' => $vendor, 'name' => $name], @@ -162,6 +165,29 @@ class PluginManager ); } + /** + * Run the plugin's own migrations so enabling a demo/third-party plugin + * does not 500 on missing tables. + */ + public function migratePlugin(string $plugin): void + { + $relativePath = 'plugins/'.$plugin.'/database/migrations'; + + if (! is_dir(base_path($relativePath))) { + return; + } + + $exitCode = Artisan::call('migrate', [ + '--path' => $relativePath, + '--force' => true, + '--no-interaction' => true, + ]); + + if ($exitCode !== 0) { + throw new RuntimeException('插件迁移失败:'.$plugin.'。'.trim(Artisan::output())); + } + } + public function disable(string $plugin): void { $dependents = $this->dependents($plugin); @@ -243,7 +269,11 @@ class PluginManager { foreach ($this->actions[$hook] ?? [] as $callbacks) { foreach ($callbacks as $callback) { - $callback(...$args); + try { + $callback(...$args); + } catch (\Throwable $e) { + report($e); + } } } } @@ -252,7 +282,11 @@ class PluginManager { foreach ($this->filters[$hook] ?? [] as $callbacks) { foreach ($callbacks as $callback) { - $value = $callback($value, ...$args); + try { + $value = $callback($value, ...$args); + } catch (\Throwable $e) { + report($e); + } } } diff --git a/app/Filament/Support/AdminEnvironment.php b/app/Filament/Support/AdminEnvironment.php new file mode 100644 index 0000000..cebd301 --- /dev/null +++ b/app/Filament/Support/AdminEnvironment.php @@ -0,0 +1,31 @@ +environment(); + + return match ($environment) { + 'production', 'prod' => 'prod', + default => $environment, + }; + } + + public static function color(): string + { + return match (app()->environment()) { + 'production', 'prod' => 'danger', + 'local' => 'gray', + 'staging' => 'warning', + default => 'info', + }; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 931e539..046a8ae 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -62,13 +62,21 @@ class AppServiceProvider extends ServiceProvider PanelsRenderHook::STYLES_AFTER, fn () => Blade::render(<<<'HTML'