From 0e0319c6ada3764c5f17128f488bb74c1cffbd78 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Thu, 22 Oct 2026 09:28:47 -0300 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20converte=20markdown=20colado=20em?= =?UTF-8?q?=20blocos=20de=20conte=C3=BAdo=20do=20post?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/Support/MarkdownBlocksConverter.php | 120 ++++++++++++++++++ .../Support/MarkdownBlocksConverterTest.php | 82 ++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 site_novo/app/Support/MarkdownBlocksConverter.php create mode 100644 site_novo/tests/Feature/Support/MarkdownBlocksConverterTest.php diff --git a/site_novo/app/Support/MarkdownBlocksConverter.php b/site_novo/app/Support/MarkdownBlocksConverter.php new file mode 100644 index 0000000..ba14b93 --- /dev/null +++ b/site_novo/app/Support/MarkdownBlocksConverter.php @@ -0,0 +1,120 @@ +}> + */ + public static function toBuilderItems(string $markdown): array + { + $items = []; + + foreach (static::toBlocks($markdown) as $block) { + $items[(string) Str::uuid()] = $block; + } + + return $items; + } + + /** + * @return list}> + */ + public static function toBlocks(string $markdown): array + { + $html = (new GithubFlavoredMarkdownConverter(['html_input' => 'allow']))->convert($markdown)->getContent(); + + $dom = new DOMDocument; + libxml_use_internal_errors(true); + $dom->loadHTML('
'.$html.'
', LIBXML_NOERROR | LIBXML_NOWARNING); + libxml_clear_errors(); + + $container = $dom->getElementsByTagName('div')->item(0); + + if (! $container) { + return []; + } + + $blocks = []; + + foreach ($container->childNodes as $node) { + if (! $node instanceof DOMElement) { + continue; + } + + if (preg_match('/^h[1-6]$/', $node->tagName)) { + $blocks[] = [ + 'type' => 'title', + 'data' => [ + 'text' => trim($node->textContent), + 'level' => self::mapHeadingLevel($node->tagName), + ], + ]; + + continue; + } + + if ($image = self::extractStandaloneImage($node)) { + $blocks[] = [ + 'type' => 'image', + 'data' => $image, + ]; + + continue; + } + + $blocks[] = [ + 'type' => 'paragraph', + 'data' => [ + 'text' => $dom->saveHTML($node) ?: '', + ], + ]; + } + + return $blocks; + } + + private static function mapHeadingLevel(string $tagName): string + { + return match ($tagName) { + 'h1', 'h2' => 'h2', + 'h3' => 'h3', + default => 'h4', + }; + } + + /** + * @return array{url: string, alt: string, ratio: string}|null + */ + private static function extractStandaloneImage(DOMElement $node): ?array + { + if ($node->tagName !== 'p') { + return null; + } + + $images = $node->getElementsByTagName('img'); + + if ($images->length !== 1 || trim($node->textContent) !== '') { + return null; + } + + $img = $images->item(0); + + return [ + 'url' => $img->getAttribute('src'), + 'alt' => $img->getAttribute('alt'), + 'ratio' => '16-9', + ]; + } +} diff --git a/site_novo/tests/Feature/Support/MarkdownBlocksConverterTest.php b/site_novo/tests/Feature/Support/MarkdownBlocksConverterTest.php new file mode 100644 index 0000000..30f93a9 --- /dev/null +++ b/site_novo/tests/Feature/Support/MarkdownBlocksConverterTest.php @@ -0,0 +1,82 @@ +toHaveCount(4) + ->and($blocks[0])->toBe(['type' => 'title', 'data' => ['text' => 'Nível 1', 'level' => 'h2']]) + ->and($blocks[1])->toBe(['type' => 'title', 'data' => ['text' => 'Nível 2', 'level' => 'h2']]) + ->and($blocks[2])->toBe(['type' => 'title', 'data' => ['text' => 'Nível 3', 'level' => 'h3']]) + ->and($blocks[3])->toBe(['type' => 'title', 'data' => ['text' => 'Nível 5', 'level' => 'h4']]); +}); + +test('it converts a plain paragraph into a paragraph block with its html', function () { + $blocks = MarkdownBlocksConverter::toBlocks('Um **parágrafo** com `código`.'); + + expect($blocks)->toHaveCount(1) + ->and($blocks[0]['type'])->toBe('paragraph') + ->and($blocks[0]['data']['text'])->toBe('

Um parágrafo com código.

'); +}); + +test('it converts a list into a paragraph block preserving the list markup', function () { + $blocks = MarkdownBlocksConverter::toBlocks("- um\n- dois"); + + expect($blocks)->toHaveCount(1) + ->and($blocks[0]['type'])->toBe('paragraph') + ->and($blocks[0]['data']['text'])->toContain('