Skip to content

Commit 23eac61

Browse files
authored
Upgrade to Statamic v4 & Laravel 10 (statamic#1082)
* Upgrade Custom Hint Extension to CommonMark 2 I started with the [Uberdosis CommonMark Hint Extension](https://github.com/ueberdosis/commonmark-hint-extension) (which our own Hint extension was based on — painstakingly back-ported to CommonMark v1) and then modified it until it the output was the same. * Use CommonMark 2's Description List syntax * Upgrade to Laravel 9 and Statamic 4 * Upgrade to Laravel 10 * remove debug comment * Remove superfluous triple colon
1 parent 0aeb0f5 commit 23eac61

15 files changed

+2151
-1290
lines changed

app/Http/Middleware/TrustProxies.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace App\Http\Middleware;
44

55
use Illuminate\Http\Request;
6-
use Fideloper\Proxy\TrustProxies as Middleware;
6+
use Illuminate\Http\Middleware\TrustProxies as Middleware;
77

88
class TrustProxies extends Middleware
99
{
@@ -19,5 +19,10 @@ class TrustProxies extends Middleware
1919
*
2020
* @var int
2121
*/
22-
protected $headers = Request::HEADER_X_FORWARDED_ALL;
22+
protected $headers =
23+
Request::HEADER_X_FORWARDED_FOR |
24+
Request::HEADER_X_FORWARDED_HOST |
25+
Request::HEADER_X_FORWARDED_PORT |
26+
Request::HEADER_X_FORWARDED_PROTO |
27+
Request::HEADER_X_FORWARDED_AWS_ELB;
2328
}

app/Markdown/Hint/Hint.php

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,68 @@
22

33
namespace App\Markdown\Hint;
44

5-
use League\CommonMark\Block\Element\AbstractBlock;
6-
use League\CommonMark\Cursor;
5+
use League\CommonMark\Node\Block\AbstractBlock;
6+
use League\CommonMark\Node\StringContainerInterface;
77

8-
class Hint extends AbstractBlock
8+
class Hint extends AbstractBlock implements StringContainerInterface
99
{
10-
protected $type;
11-
protected $title;
10+
private ?string $header = 'Hot Tip!';
1211

13-
public function __construct($type, $title)
14-
{
15-
$this->type = $type;
16-
$this->title = $title;
17-
}
12+
protected string $literal;
1813

19-
public function canContain(AbstractBlock $block): bool
14+
public function getTitle(): ?string
2015
{
21-
return true;
22-
}
16+
$words = $this->getHeaderWords();
2317

24-
public function isCode(): bool
25-
{
26-
return false;
27-
}
18+
if (count($words) > 1) {
2819

29-
public function matchesNextLine(Cursor $cursor): bool
30-
{
31-
if ($cursor->match('/^:::$/')) {
32-
return false;
20+
array_shift($words);
21+
return join(' ', $words);
3322
}
3423

35-
return true;
36-
}
37-
38-
public function getTitle(): ?string
39-
{
40-
if ($this->title) {
41-
return $this->title;
24+
if ($words[0] === 'tip') {
25+
return 'Hot Tip!';
4226
}
4327

44-
if ($this->type === 'warning') {
28+
if ($words[0] === 'warning') {
4529
return 'Warning!';
4630
}
4731

48-
if ($this->type === 'best-practice') {
32+
if ($words[0] === 'best-practice') {
4933
return 'Best Practice';
5034
}
5135

52-
return 'Hot Tip!';
36+
return null;
5337
}
5438

5539
public function getType(): ?string
5640
{
57-
return $this->type;
41+
$words = $this->getHeaderWords();
42+
43+
if (count($words) > 0) {
44+
return $words[0];
45+
}
46+
47+
return null;
48+
}
49+
50+
public function getHeaderWords(): array
51+
{
52+
return \preg_split('/\s+/', $this->header ?? '') ?: [];
53+
}
54+
55+
public function setHeader($header)
56+
{
57+
$this->header = $header;
58+
}
59+
60+
public function setLiteral(string $literal): void
61+
{
62+
$this->literal = $literal;
63+
}
64+
65+
public function getLiteral(): string
66+
{
67+
return $this->literal;
5868
}
5969
}

app/Markdown/Hint/HintExtension.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
namespace App\Markdown\Hint;
44

5-
use League\CommonMark\ConfigurableEnvironmentInterface;
5+
use League\CommonMark\Environment\EnvironmentBuilderInterface;
66
use League\CommonMark\Extension\ExtensionInterface;
77

88
class HintExtension implements ExtensionInterface
99
{
10-
public function register(ConfigurableEnvironmentInterface $environment): void
10+
public function register(EnvironmentBuilderInterface $environment): void
1111
{
12-
$environment->addBlockParser(new HintParser());
13-
$environment->addBlockRenderer(Hint::class, new HintRenderer());
12+
$environment->addBlockStartParser(new HintStartParser());
13+
$environment->addRenderer(Hint::class, new HintRenderer());
1414
}
1515
}

app/Markdown/Hint/HintParser.php

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,86 @@
22

33
namespace App\Markdown\Hint;
44

5-
use League\CommonMark\Block\Parser\BlockParserInterface;
6-
use League\CommonMark\ContextInterface;
7-
use League\CommonMark\Cursor;
5+
use League\CommonMark\Node\Block\AbstractBlock;
6+
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
7+
use League\CommonMark\Parser\Block\BlockContinue;
8+
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
9+
use League\CommonMark\Parser\Block\BlockContinueParserWithInlinesInterface;
10+
use League\CommonMark\Parser\Cursor;
11+
use League\CommonMark\Parser\InlineParserEngineInterface;
12+
use League\CommonMark\Util\ArrayCollection;
13+
use League\CommonMark\Util\RegexHelper;
814

9-
class HintParser implements BlockParserInterface
15+
class HintParser extends AbstractBlockContinueParser implements BlockContinueParserWithInlinesInterface
1016
{
11-
public function parse(ContextInterface $context, Cursor $cursor): bool
12-
{
13-
if ($cursor->isIndented()) {
14-
return false;
17+
/** @psalm-readonly */
18+
private Hint $block;
19+
20+
/** @var ArrayCollection<string> */
21+
private ArrayCollection $strings;
22+
23+
public function __construct()
24+
{
25+
$this->block = new Hint();
26+
$this->strings = new ArrayCollection();
1527
}
1628

17-
$fence = $cursor->match('/^(:::)(?:.+)$/');
29+
public function getBlock(): Hint
30+
{
31+
return $this->block;
32+
}
1833

19-
if ($fence === null) {
34+
public function isContainer(): bool
35+
{
2036
return false;
2137
}
2238

23-
[$type, $heading] = array_pad(explode(' ', substr($fence, 3), 2), 2, '');
39+
public function canContain(AbstractBlock $childBlock): bool
40+
{
41+
return false;
42+
}
43+
44+
public function canHaveLazyContinuationLines(): bool
45+
{
46+
return true;
47+
}
48+
49+
public function parseInlines(InlineParserEngineInterface $inlineParser): void
50+
{
51+
$inlineParser->parse($this->block->getLiteral(), $this->block);
52+
}
53+
54+
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
55+
{
56+
if ($cursor->getLine() === ':::') {
57+
return BlockContinue::finished();
58+
}
2459

25-
$context->addBlock(new Hint($type, $heading));
60+
$cursor->advanceToNextNonSpaceOrTab();
61+
$cursor->advanceBySpaceOrTab();
2662

27-
return true;
28-
}
63+
return BlockContinue::at($cursor);
64+
}
65+
66+
public function addLine(string $line): void
67+
{
68+
$this->strings[] = $line;
69+
}
70+
71+
public function closeBlock(): void
72+
{
73+
// first line becomes info string
74+
$firstLine = $this->strings->first();
75+
if ($firstLine === false) {
76+
$firstLine = '';
77+
}
78+
79+
$this->block->setHeader(RegexHelper::unescape(\trim($firstLine)));
80+
81+
if ($this->strings->count() === 1) {
82+
$this->block->setLiteral('');
83+
} else {
84+
$this->block->setLiteral(\implode("\n", $this->strings->slice(1)) . "\n");
85+
}
86+
}
2987
}

app/Markdown/Hint/HintRenderer.php

Lines changed: 20 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,36 @@
22

33
namespace App\Markdown\Hint;
44

5-
use League\CommonMark\Block\Element\AbstractBlock;
6-
use League\CommonMark\Block\Renderer\BlockRendererInterface;
7-
use League\CommonMark\ElementRendererInterface;
8-
use League\CommonMark\HtmlElement;
5+
use League\CommonMark\Node\Node;
6+
use League\CommonMark\Renderer\ChildNodeRendererInterface;
7+
use League\CommonMark\Renderer\NodeRendererInterface;
8+
use League\CommonMark\Util\HtmlElement;
99

10-
final class HintRenderer implements BlockRendererInterface
10+
final class HintRenderer implements NodeRendererInterface
1111
{
12-
public function render(AbstractBlock $node, ElementRendererInterface $childRenderer, bool $inTightList = false)
12+
/**
13+
* @param Hint $node
14+
*
15+
* {@inheritDoc}
16+
*
17+
* @psalm-suppress MoreSpecificImplementedParamType
18+
*/
19+
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
1320
{
14-
if (!($node instanceof Hint)) {
15-
throw new \InvalidArgumentException('Incompatible block type: ' . \get_class($node));
16-
}
21+
Hint::assertInstanceOf($node);
1722

18-
$attrs = $node->getData('attributes');
23+
$attrs = $node->data->get('attributes');
1924
isset($attrs['class']) ? $attrs['class'] .= ' hint' : $attrs['class'] = 'hint';
2025

2126
if ($type = $node->getType()) {
2227
$attrs['class'] = isset($attrs['class']) ? $attrs['class'] . ' ' : '';
2328
$attrs['class'] .= $type;
2429
}
2530

26-
if ($type === 'watch') {
31+
if ($type === "watch") {
2732
return $this->renderWatch($node, $childRenderer, $attrs);
2833
}
2934

30-
if ($type === 'callout') {
31-
return $this->renderCallout($node, $childRenderer, $attrs);
32-
}
33-
3435
$title = $node->getTitle();
3536
$title = $title
3637
? new HtmlElement(
@@ -41,9 +42,9 @@ public function render(AbstractBlock $node, ElementRendererInterface $childRende
4142
: '';
4243

4344
$content = new HtmlElement(
44-
'div',
45+
'p',
4546
['class' => 'hint-content'],
46-
$childRenderer->renderBlocks($node->children())
47+
$childRenderer->renderNodes($node->children())
4748
);
4849

4950
return new HtmlElement(
@@ -56,32 +57,12 @@ public function render(AbstractBlock $node, ElementRendererInterface $childRende
5657
);
5758
}
5859

59-
private function renderCallout(Hint $node, ElementRendererInterface $childRenderer, array $attrs)
60-
{
61-
$content = new HtmlElement(
62-
'div',
63-
['class' => 'hint-content'],
64-
$childRenderer->renderBlocks($node->children())
65-
);
66-
67-
return new HtmlElement(
68-
'div',
69-
$attrs,
70-
'<a href="'.$node->getTitle().'">' .
71-
$childRenderer->renderBlocks($node->children()).
72-
'</a>'
73-
);
74-
}
75-
76-
private function renderWatch(Hint $node, ElementRendererInterface $childRenderer, array $attrs)
60+
private function renderWatch(Hint $node, ChildNodeRendererInterface $childRenderer, array $attrs)
7761
{
78-
// Grab the first paragraph. There should only be one anyway.
79-
$content = $node->children()[0];
80-
8162
$caption = new HtmlElement(
8263
'p',
8364
['class' => 'caption'],
84-
$childRenderer->renderInlines($content->children())
65+
$childRenderer->renderNodes($node->children())
8566
);
8667

8768
return new HtmlElement(

app/Markdown/Hint/HintStartParser.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace App\Markdown\Hint;
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+
10+
class HintStartParser implements BlockStartParserInterface
11+
{
12+
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
13+
{
14+
if ($cursor->isIndented()) {
15+
return BlockStart::none();
16+
}
17+
18+
$fence = $cursor->match('/^(?:\:{3,}(?!.*`))/');
19+
if ($fence === null) {
20+
return BlockStart::none();
21+
}
22+
23+
return BlockStart::of(new HintParser())->at($cursor);
24+
}
25+
}

app/Providers/AppServiceProvider.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22

33
namespace App\Providers;
44

5+
use Statamic\Facades\Markdown;
56
use App\Markdown\Hint\HintExtension;
67
use Illuminate\Support\Facades\View;
78
use Illuminate\Support\ServiceProvider;
8-
use Statamic\Facades\Markdown;
9-
use Torchlight\Commonmark\TorchlightExtension;
9+
use League\CommonMark\MarkdownConverter;
10+
use App\Http\View\Composers\SideNavComposer;
11+
use Torchlight\Commonmark\V2\TorchlightExtension;
1012
use League\CommonMark\Extension\Attributes\AttributesExtension;
13+
use League\CommonMark\Extension\DescriptionList\DescriptionListExtension;
14+
15+
1116

1217
class AppServiceProvider extends ServiceProvider
1318
{
@@ -21,7 +26,7 @@ public function boot()
2126
// View::composer('partials.side-nav', SideNavComposer::class);
2227

2328
Markdown::addExtensions(function () {
24-
return [new HintExtension, new AttributesExtension];
29+
return [new DescriptionListExtension, new HintExtension, new AttributesExtension];
2530
});
2631

2732
if (config('torchlight.token')) {

0 commit comments

Comments
 (0)