From 0b35e54fe1ef4c5e939e0d4dca3228f3f92f6202 Mon Sep 17 00:00:00 2001 From: lizhuoran <625237490@qq.com> Date: Mon, 20 Apr 2026 13:41:27 +0800 Subject: [PATCH] Fix rich editor image preview URLs --- .../GuideResource/Pages/ManageGuidePages.php | 26 ++++++------ app/Models/GuidePage.php | 32 ++++++++++++++- resources/views/guides/page.blade.php | 2 +- tests/Unit/GuidePageContentTest.php | 40 +++++++++++++++++++ 4 files changed, 85 insertions(+), 15 deletions(-) create mode 100644 tests/Unit/GuidePageContentTest.php diff --git a/app/Filament/Resources/GuideResource/Pages/ManageGuidePages.php b/app/Filament/Resources/GuideResource/Pages/ManageGuidePages.php index 37a7520..3f29c50 100644 --- a/app/Filament/Resources/GuideResource/Pages/ManageGuidePages.php +++ b/app/Filament/Resources/GuideResource/Pages/ManageGuidePages.php @@ -30,7 +30,7 @@ class ManageGuidePages extends Page public function getTitle(): string { - return $this->getRecord()->name . ' - 页面流程'; + return $this->getRecord()->name.' - 页面流程'; } public function loadGraph(): void @@ -56,17 +56,17 @@ class ManageGuidePages extends Page $queue = []; foreach ($pages as $p) { - if (!isset($hasIncoming[$p->id])) { + if (! isset($hasIncoming[$p->id])) { $queue[] = $p->id; $levels[$p->id] = 0; $visited[$p->id] = true; } } - while (!empty($queue)) { + while (! empty($queue)) { $cur = array_shift($queue); foreach ($children[$cur] ?? [] as $child) { - if (!isset($visited[$child])) { + if (! isset($visited[$child])) { $visited[$child] = true; $levels[$child] = $levels[$cur] + 1; $queue[] = $child; @@ -77,7 +77,7 @@ class ManageGuidePages extends Page // Orphans at bottom $maxLevel = empty($levels) ? 0 : max($levels); foreach ($pages as $p) { - if (!isset($levels[$p->id])) { + if (! isset($levels[$p->id])) { $levels[$p->id] = $maxLevel + 1; } } @@ -107,17 +107,17 @@ class ManageGuidePages extends Page } } - $this->nodes = $pages->map(fn(GuidePage $p) => [ + $this->nodes = $pages->map(fn (GuidePage $p) => [ 'id' => $p->id, 'title' => $p->title, 'uri' => $p->uri, - 'is_entry' => !isset($hasIncoming[$p->id]), + 'is_entry' => ! isset($hasIncoming[$p->id]), 'options' => $p->options ?? [], 'x' => $positions[$p->id]['x'] ?? 50, 'y' => $positions[$p->id]['y'] ?? 50, ])->values()->toArray(); - $this->edges = $edgeModels->map(fn(GuidePageEdge $e) => [ + $this->edges = $edgeModels->map(fn (GuidePageEdge $e) => [ 'id' => $e->id, 'from' => $e->from_page_id, 'to' => $e->to_page_id, @@ -132,8 +132,8 @@ class ManageGuidePages extends Page $guide = $this->getRecord(); if ( - !$guide->pages()->where('id', $fromPageId)->exists() || - !$guide->pages()->where('id', $toPageId)->exists() + ! $guide->pages()->where('id', $fromPageId)->exists() || + ! $guide->pages()->where('id', $toPageId)->exists() ) { return; } @@ -156,7 +156,7 @@ class ManageGuidePages extends Page $options = $page->options ?? []; $label = null; - if (!empty($options)) { + if (! empty($options)) { $outputIndex = (int) str_replace('output_', '', $outputClass) - 1; $label = $options[$outputIndex] ?? null; } @@ -207,7 +207,7 @@ class ManageGuidePages extends Page $page = $this->getRecord()->pages()->findOrFail($arguments['id']); $form->fill([ 'title' => $page->title, - 'content' => $page->content, + 'content' => $page->normalized_content, 'options' => $page->options ?? [], ]); }) @@ -267,6 +267,8 @@ class ManageGuidePages extends Page ->fileAttachmentsDisk('public') ->fileAttachmentsDirectory('guide-pages') ->fileAttachmentsVisibility('public') + ->getUploadedAttachmentUrlUsing(fn (string $file): string => GuidePage::uploadedAttachmentUrl($file)) + ->dehydrateStateUsing(fn (?string $state): string => GuidePage::normalizeRichTextContent($state)) ->columnSpanFull(), Forms\Components\TagsInput::make('options') diff --git a/app/Models/GuidePage.php b/app/Models/GuidePage.php index a398ffc..3cea133 100644 --- a/app/Models/GuidePage.php +++ b/app/Models/GuidePage.php @@ -30,6 +30,35 @@ class GuidePage extends Model return route('guides.pages.show', $this->id); } + public function getNormalizedContentAttribute(): string + { + return static::normalizeRichTextContent($this->content); + } + + public static function normalizeRichTextContent(?string $content): string + { + if (blank($content)) { + return ''; + } + + $content = preg_replace_callback( + '~(?:https?:)?//[^"\'\s<>()]+(?/storage/guide-pages/[^"\'\s<>()]*)~i', + static fn (array $matches): string => $matches['path'], + $content, + ) ?? $content; + + return preg_replace( + '~(?<=["\'])storage/guide-pages/~i', + '/storage/guide-pages/', + $content, + ) ?? $content; + } + + public static function uploadedAttachmentUrl(string $path): string + { + return '/storage/'.ltrim($path, '/'); + } + public function guide() { return $this->belongsTo(Guide::class); @@ -60,7 +89,6 @@ class GuidePage extends Model public function isEntry(): bool { - return !$this->incomingEdges()->exists(); + return ! $this->incomingEdges()->exists(); } - } diff --git a/resources/views/guides/page.blade.php b/resources/views/guides/page.blade.php index 9d08fac..9be0f1c 100644 --- a/resources/views/guides/page.blade.php +++ b/resources/views/guides/page.blade.php @@ -65,7 +65,7 @@

{{ $page->title }}

- {!! $page->content !!} + {!! $page->normalized_content !!}
diff --git a/tests/Unit/GuidePageContentTest.php b/tests/Unit/GuidePageContentTest.php new file mode 100644 index 0000000..f0d3c7a --- /dev/null +++ b/tests/Unit/GuidePageContentTest.php @@ -0,0 +1,40 @@ + <<<'HTML' +
+ +
+HTML, + ]); + + expect($page->normalized_content) + ->toContain('/storage/guide-pages/example.png?signature=abc') + ->not->toContain('http://localhost:8000/storage/guide-pages/example.png?signature=abc'); +}); + +it('adds a leading slash to relative guide image urls', function () { + $page = new GuidePage([ + 'content' => '', + ]); + + expect($page->normalized_content) + ->toBe(''); +}); + +it('keeps external image urls unchanged', function () { + $page = new GuidePage([ + 'content' => '', + ]); + + expect($page->normalized_content) + ->toBe(''); +}); + +it('builds root relative upload urls for new attachments', function () { + expect(GuidePage::uploadedAttachmentUrl('guide-pages/example.png')) + ->toBe('/storage/guide-pages/example.png'); +});