feat: restore sablog browse modes and clean imported HTML
Build and Deploy / verify (push) Successful in 40s
Build and Deploy / deploy (push) Successful in 36s

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:
gouki
2026-09-18 21:33:02 +00:00
co-authored by Cursor
parent 469160dc3f
commit ae47447062
98 changed files with 8618 additions and 901 deletions
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Blog\Support\SablogHtmlNormalizer;
use App\Models\Post;
use Illuminate\Console\Command;
class FixImportedContent extends Command
{
protected $signature = 'sablog:fix-content
{--all : Repair every post}
{id? : Single post ID}
{--dry-run : Preview only}';
protected $description = 'Clean leftover SaBlog HTML: literal &nbsp;, highlighter blocks, stacked docker divs';
public function handle(SablogHtmlNormalizer $normalizer): int
{
$query = Post::query();
if ($id = $this->argument('id')) {
$query->where('id', $id);
} elseif (! $this->option('all')) {
$this->error('Pass a post ID or use --all');
return self::FAILURE;
}
$posts = $query->get();
if ($posts->isEmpty()) {
$this->info('No posts to repair');
return self::SUCCESS;
}
$changed = 0;
foreach ($posts as $post) {
$cleaned = $normalizer->normalize((string) $post->content);
if ($cleaned === $post->content) {
continue;
}
if ($this->option('dry-run')) {
$this->line("[{$post->id}] {$post->title}");
continue;
}
$post->content = $cleaned;
$post->save();
$changed++;
}
$this->info($this->option('dry-run')
? 'Preview only, nothing written'
: "Repaired {$changed} posts");
return self::SUCCESS;
}
}