Add rich Markdown normalization and paste handling.

- 🛠 Introduce `RichTextMarkdownNormalizer` to convert Markdown and mixed input to cleaner HTML.
- 🗂 Include a new Blade partial to enable Markdown-on-paste behavior in rich-text editors.
- 📋 Enhance `create` and `edit` forms to normalize descriptions and support Markdown conversion.
- 🧪 Add test coverage for Markdown normalization scenarios.
- 🛠 Add CLI command to normalize project proposal descriptions in bulk.
- 🔧 Update `vite.config.js` for improved development setup (e.g., ignored paths).
This commit is contained in:
HolgerHatGarKeineNode
2026-04-08 17:34:55 +01:00
parent 0e0738ff23
commit 04abf231bd
8 changed files with 579 additions and 8 deletions

View File

@@ -2,6 +2,7 @@
use App\Models\ProjectProposal;
use App\Support\NostrAuth;
use App\Support\RichTextMarkdownNormalizer;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\RateLimiter;
use Livewire\Attributes\Layout;
@@ -58,6 +59,11 @@ class extends Component
}
}
public function convertMarkdownToHtml(string $markdown): string
{
return (new RichTextMarkdownNormalizer)->toHtml($markdown);
}
public function save(): void
{
Gate::forUser(NostrAuth::user())->authorize('create', ProjectProposal::class);
@@ -82,7 +88,7 @@ class extends Component
$projectProposal = new ProjectProposal;
$projectProposal->name = $this->form['name'];
$projectProposal->description = $this->form['description'];
$projectProposal->description = (new RichTextMarkdownNormalizer)->normalize($this->form['description']);
$projectProposal->support_in_sats = (int) $this->form['support_in_sats'];
$projectProposal->website = $this->form['website'];
$projectProposal->accepted = $this->isAdmin ? $this->form['accepted'] : false;
@@ -196,6 +202,8 @@ class extends Component
</div>
</div>
</div>
@include('partials.markdown-paste-listener')
@else
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
<flux:callout variant="warning" icon="exclamation-circle">

View File

@@ -2,6 +2,7 @@
use App\Models\ProjectProposal;
use App\Support\NostrAuth;
use App\Support\RichTextMarkdownNormalizer;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\RateLimiter;
use Livewire\Attributes\Layout;
@@ -77,6 +78,11 @@ class extends Component
}
}
public function convertMarkdownToHtml(string $markdown): string
{
return (new RichTextMarkdownNormalizer)->toHtml($markdown);
}
public function update(): void
{
Gate::forUser(NostrAuth::user())->authorize('update', $this->project);
@@ -104,7 +110,7 @@ class extends Component
$this->project->update([
'name' => $this->form['name'],
'description' => $this->form['description'],
'description' => (new RichTextMarkdownNormalizer)->normalize($this->form['description']),
'support_in_sats' => (int) $this->form['support_in_sats'],
'website' => $this->form['website'],
]);
@@ -247,6 +253,8 @@ class extends Component
</div>
</div>
</div>
@include('partials.markdown-paste-listener')
@else
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
<flux:callout variant="warning" icon="exclamation-circle">

View File

@@ -137,10 +137,8 @@ new class extends Component {
<h1 class="text-2xl md:text-3xl text-zinc-800 dark:text-zinc-100 font-bold mb-2">
{{ $projectProposal->name }}
</h1>
<div class="prose">
<x-markdown>
{!! $projectProposal->description !!}
</x-markdown>
<div class="prose dark:prose-invert max-w-none break-words [&_code]:break-all [&_a]:break-all">
{!! $projectProposal->description !!}
</div>
</header>