Skip to content

Commit e49651c

Browse files
committed
feat: suppot alert blocks
1 parent b6c4a19 commit e49651c

File tree

7 files changed

+202
-0
lines changed

7 files changed

+202
-0
lines changed

src/Markdown/Alerts/AlertBlock.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace App\Markdown\Alerts;
4+
5+
use League\CommonMark\Node\Block\AbstractBlock;
6+
7+
final class AlertBlock extends AbstractBlock
8+
{
9+
public function __construct(
10+
public readonly string $alertType,
11+
public readonly ?string $title,
12+
) {
13+
parent::__construct();
14+
}
15+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
namespace App\Markdown\Alerts;
4+
5+
use League\CommonMark\Node\Block\AbstractBlock;
6+
use League\CommonMark\Parser\Block\BlockContinue;
7+
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
8+
use League\CommonMark\Parser\Cursor;
9+
use League\CommonMark\Util\RegexHelper;
10+
11+
final class AlertBlockParser implements BlockContinueParserInterface
12+
{
13+
protected $block;
14+
protected $finished = false;
15+
16+
public function __construct(
17+
protected string $alertType,
18+
protected string $title,
19+
) {
20+
$this->block = new AlertBlock($alertType, $title);
21+
}
22+
23+
public function addLine(string $line): void
24+
{
25+
}
26+
27+
public function getBlock(): AbstractBlock
28+
{
29+
return $this->block;
30+
}
31+
32+
public function isContainer(): bool
33+
{
34+
return true;
35+
}
36+
37+
public function canContain(AbstractBlock $childBlock): bool
38+
{
39+
return true;
40+
}
41+
42+
public function canHaveLazyContinuationLines(): bool
43+
{
44+
return false;
45+
}
46+
47+
public function parseInlines(): bool
48+
{
49+
return true;
50+
}
51+
52+
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
53+
{
54+
if ($cursor->isIndented()) {
55+
return BlockContinue::at($cursor);
56+
}
57+
58+
$match = RegexHelper::matchFirst('/^:::$/', $cursor->getLine());
59+
if ($match !== null) {
60+
$this->finished = true;
61+
return BlockContinue::finished();
62+
}
63+
64+
return BlockContinue::at($cursor);
65+
}
66+
67+
public function closeBlock(): void
68+
{
69+
// Nothing to do here
70+
}
71+
72+
public function isFinished(): bool
73+
{
74+
return $this->finished;
75+
}
76+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace App\Markdown\Alerts;
4+
5+
use League\CommonMark\Node\Node;
6+
use League\CommonMark\Renderer\ChildNodeRendererInterface;
7+
use League\CommonMark\Renderer\HtmlRenderer;
8+
use League\CommonMark\Renderer\NodeRendererInterface;
9+
use League\CommonMark\Util\HtmlElement;
10+
11+
final class AlertBlockRenderer implements NodeRendererInterface
12+
{
13+
public function render(Node $node, ChildNodeRendererInterface $htmlRenderer)
14+
{
15+
if (! ($node instanceof AlertBlock)) {
16+
throw new \InvalidArgumentException('Incompatible node type: ' . get_class($node));
17+
}
18+
19+
if (! ($htmlRenderer instanceof HtmlRenderer)) {
20+
throw new \InvalidArgumentException('Incompatible renderer type: ' . get_class($htmlRenderer));
21+
}
22+
23+
$cssClass = 'alert alert-' . $node->alertType;
24+
$content = new HtmlElement(
25+
tagName: 'div',
26+
attributes: ['class' => 'alert-wrapper'],
27+
contents: [
28+
$node->title ? new HtmlElement('span', attributes: ['class' => 'alert-title'], contents: $node->title) : null,
29+
$htmlRenderer->renderNodes($node->children()),
30+
],
31+
);
32+
33+
return new HtmlElement(
34+
'div',
35+
['class' => $cssClass],
36+
$content,
37+
);
38+
}
39+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace App\Markdown\Alerts;
4+
5+
use League\CommonMark\Parser\Block\BlockStart;
6+
use League\CommonMark\Parser\Block\BlockStartParserInterface;
7+
use League\CommonMark\Parser\Cursor;
8+
use League\CommonMark\Parser\MarkdownParserStateInterface;
9+
use League\CommonMark\Util\RegexHelper;
10+
11+
final class AlertBlockStartParser implements BlockStartParserInterface
12+
{
13+
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
14+
{
15+
if ($cursor->isIndented()) {
16+
return BlockStart::none();
17+
}
18+
19+
$match = RegexHelper::matchFirst('/^:::([a-z]+) ?(.*?)$/i', $cursor->getLine());
20+
if ($match === null) {
21+
return BlockStart::none();
22+
}
23+
24+
$cursor->advanceToEnd();
25+
26+
$alertType = $match[1];
27+
$title = $match[2];
28+
29+
return BlockStart::of(new AlertBlockParser($alertType, $title))->at($cursor);
30+
}
31+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace App\Markdown\Alerts;
4+
5+
use League\CommonMark\Environment\EnvironmentBuilderInterface;
6+
use League\CommonMark\Extension\ExtensionInterface;
7+
8+
final class AlertExtension implements ExtensionInterface
9+
{
10+
public function register(EnvironmentBuilderInterface $environment): void
11+
{
12+
$environment->addBlockStartParser(new AlertBlockStartParser());
13+
$environment->addRenderer(AlertBlock::class, new AlertBlockRenderer());
14+
}
15+
}

src/Markdown/MarkdownInitializer.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace App\Markdown;
66

7+
use App\Markdown\Alerts\AlertExtension;
78
use App\Markdown\CodeBlockRenderer;
89
use League\CommonMark\Environment\Environment;
910
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
@@ -30,6 +31,7 @@ public function initialize(Container $container): MarkdownConverter
3031
$environment
3132
->addExtension(new CommonMarkCoreExtension())
3233
->addExtension(new FrontMatterExtension())
34+
->addExtension(new AlertExtension())
3335
->addInlineParser(new TempestPackageParser())
3436
->addInlineParser(new FqcnParser())
3537
->addInlineParser(new HandleParser())

src/Web/assets/typography.css

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,30 @@
77
@apply !mt-0;
88
}
99

10+
.alert {
11+
@apply my-4 relative overflow-hidden w-full rounded-md p-4 flex gap-2.5 items-start text-(--ui-text-highlighted) bg-(--ui-bg-elevated)/50 ring ring-inset ring-(--ui-border-accented);
12+
13+
&.alert-warning {
14+
@apply bg-(--ui-warning)/10 text-(--ui-warning) ring-(--ui-warning)/25;
15+
}
16+
17+
&.alert-success {
18+
@apply bg-(--ui-success)/10 text-(--ui-success) ring-(--ui-success)/25;
19+
}
20+
21+
div.alert-wrapper {
22+
@apply flex flex-col gap-y-2;
23+
24+
span.alert-title {
25+
@apply text-sm font-semibold inline-block;
26+
}
27+
28+
p {
29+
@apply my-0 text-sm opacity-90;
30+
}
31+
}
32+
}
33+
1034
.has-pre {
1135
@apply my-0;
1236
}

0 commit comments

Comments
 (0)