Fix rich editor image preview URLs
This commit is contained in:
@@ -30,7 +30,7 @@ class ManageGuidePages extends Page
|
|||||||
|
|
||||||
public function getTitle(): string
|
public function getTitle(): string
|
||||||
{
|
{
|
||||||
return $this->getRecord()->name . ' - 页面流程';
|
return $this->getRecord()->name.' - 页面流程';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadGraph(): void
|
public function loadGraph(): void
|
||||||
@@ -56,17 +56,17 @@ class ManageGuidePages extends Page
|
|||||||
$queue = [];
|
$queue = [];
|
||||||
|
|
||||||
foreach ($pages as $p) {
|
foreach ($pages as $p) {
|
||||||
if (!isset($hasIncoming[$p->id])) {
|
if (! isset($hasIncoming[$p->id])) {
|
||||||
$queue[] = $p->id;
|
$queue[] = $p->id;
|
||||||
$levels[$p->id] = 0;
|
$levels[$p->id] = 0;
|
||||||
$visited[$p->id] = true;
|
$visited[$p->id] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!empty($queue)) {
|
while (! empty($queue)) {
|
||||||
$cur = array_shift($queue);
|
$cur = array_shift($queue);
|
||||||
foreach ($children[$cur] ?? [] as $child) {
|
foreach ($children[$cur] ?? [] as $child) {
|
||||||
if (!isset($visited[$child])) {
|
if (! isset($visited[$child])) {
|
||||||
$visited[$child] = true;
|
$visited[$child] = true;
|
||||||
$levels[$child] = $levels[$cur] + 1;
|
$levels[$child] = $levels[$cur] + 1;
|
||||||
$queue[] = $child;
|
$queue[] = $child;
|
||||||
@@ -77,7 +77,7 @@ class ManageGuidePages extends Page
|
|||||||
// Orphans at bottom
|
// Orphans at bottom
|
||||||
$maxLevel = empty($levels) ? 0 : max($levels);
|
$maxLevel = empty($levels) ? 0 : max($levels);
|
||||||
foreach ($pages as $p) {
|
foreach ($pages as $p) {
|
||||||
if (!isset($levels[$p->id])) {
|
if (! isset($levels[$p->id])) {
|
||||||
$levels[$p->id] = $maxLevel + 1;
|
$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,
|
'id' => $p->id,
|
||||||
'title' => $p->title,
|
'title' => $p->title,
|
||||||
'uri' => $p->uri,
|
'uri' => $p->uri,
|
||||||
'is_entry' => !isset($hasIncoming[$p->id]),
|
'is_entry' => ! isset($hasIncoming[$p->id]),
|
||||||
'options' => $p->options ?? [],
|
'options' => $p->options ?? [],
|
||||||
'x' => $positions[$p->id]['x'] ?? 50,
|
'x' => $positions[$p->id]['x'] ?? 50,
|
||||||
'y' => $positions[$p->id]['y'] ?? 50,
|
'y' => $positions[$p->id]['y'] ?? 50,
|
||||||
])->values()->toArray();
|
])->values()->toArray();
|
||||||
|
|
||||||
$this->edges = $edgeModels->map(fn(GuidePageEdge $e) => [
|
$this->edges = $edgeModels->map(fn (GuidePageEdge $e) => [
|
||||||
'id' => $e->id,
|
'id' => $e->id,
|
||||||
'from' => $e->from_page_id,
|
'from' => $e->from_page_id,
|
||||||
'to' => $e->to_page_id,
|
'to' => $e->to_page_id,
|
||||||
@@ -132,8 +132,8 @@ class ManageGuidePages extends Page
|
|||||||
$guide = $this->getRecord();
|
$guide = $this->getRecord();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!$guide->pages()->where('id', $fromPageId)->exists() ||
|
! $guide->pages()->where('id', $fromPageId)->exists() ||
|
||||||
!$guide->pages()->where('id', $toPageId)->exists()
|
! $guide->pages()->where('id', $toPageId)->exists()
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,7 @@ class ManageGuidePages extends Page
|
|||||||
$options = $page->options ?? [];
|
$options = $page->options ?? [];
|
||||||
$label = null;
|
$label = null;
|
||||||
|
|
||||||
if (!empty($options)) {
|
if (! empty($options)) {
|
||||||
$outputIndex = (int) str_replace('output_', '', $outputClass) - 1;
|
$outputIndex = (int) str_replace('output_', '', $outputClass) - 1;
|
||||||
$label = $options[$outputIndex] ?? null;
|
$label = $options[$outputIndex] ?? null;
|
||||||
}
|
}
|
||||||
@@ -207,7 +207,7 @@ class ManageGuidePages extends Page
|
|||||||
$page = $this->getRecord()->pages()->findOrFail($arguments['id']);
|
$page = $this->getRecord()->pages()->findOrFail($arguments['id']);
|
||||||
$form->fill([
|
$form->fill([
|
||||||
'title' => $page->title,
|
'title' => $page->title,
|
||||||
'content' => $page->content,
|
'content' => $page->normalized_content,
|
||||||
'options' => $page->options ?? [],
|
'options' => $page->options ?? [],
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
@@ -267,6 +267,8 @@ class ManageGuidePages extends Page
|
|||||||
->fileAttachmentsDisk('public')
|
->fileAttachmentsDisk('public')
|
||||||
->fileAttachmentsDirectory('guide-pages')
|
->fileAttachmentsDirectory('guide-pages')
|
||||||
->fileAttachmentsVisibility('public')
|
->fileAttachmentsVisibility('public')
|
||||||
|
->getUploadedAttachmentUrlUsing(fn (string $file): string => GuidePage::uploadedAttachmentUrl($file))
|
||||||
|
->dehydrateStateUsing(fn (?string $state): string => GuidePage::normalizeRichTextContent($state))
|
||||||
->columnSpanFull(),
|
->columnSpanFull(),
|
||||||
|
|
||||||
Forms\Components\TagsInput::make('options')
|
Forms\Components\TagsInput::make('options')
|
||||||
|
|||||||
@@ -30,6 +30,35 @@ class GuidePage extends Model
|
|||||||
return route('guides.pages.show', $this->id);
|
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<>()]+(?<path>/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()
|
public function guide()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Guide::class);
|
return $this->belongsTo(Guide::class);
|
||||||
@@ -60,7 +89,6 @@ class GuidePage extends Model
|
|||||||
|
|
||||||
public function isEntry(): bool
|
public function isEntry(): bool
|
||||||
{
|
{
|
||||||
return !$this->incomingEdges()->exists();
|
return ! $this->incomingEdges()->exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<article>
|
<article>
|
||||||
<h1>{{ $page->title }}</h1>
|
<h1>{{ $page->title }}</h1>
|
||||||
{!! $page->content !!}
|
{!! $page->normalized_content !!}
|
||||||
</article>
|
</article>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
40
tests/Unit/GuidePageContentTest.php
Normal file
40
tests/Unit/GuidePageContentTest.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\GuidePage;
|
||||||
|
|
||||||
|
it('normalizes uploaded guide image urls for rendering', function () {
|
||||||
|
$page = new GuidePage([
|
||||||
|
'content' => <<<'HTML'
|
||||||
|
<figure data-trix-attachment='{"url":"http://localhost:8000/storage/guide-pages/example.png?signature=abc"}'>
|
||||||
|
<img src="http://localhost:8000/storage/guide-pages/example.png?signature=abc">
|
||||||
|
</figure>
|
||||||
|
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' => '<img src="storage/guide-pages/example.png">',
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect($page->normalized_content)
|
||||||
|
->toBe('<img src="/storage/guide-pages/example.png">');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps external image urls unchanged', function () {
|
||||||
|
$page = new GuidePage([
|
||||||
|
'content' => '<img src="https://example.com/images/example.png">',
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect($page->normalized_content)
|
||||||
|
->toBe('<img src="https://example.com/images/example.png">');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('builds root relative upload urls for new attachments', function () {
|
||||||
|
expect(GuidePage::uploadedAttachmentUrl('guide-pages/example.png'))
|
||||||
|
->toBe('/storage/guide-pages/example.png');
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user