From 622188e3d21316b6b21e2e77af9e3860c5b4392c Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Mon, 26 Aug 2024 15:31:24 +1000 Subject: [PATCH] Render heading links on the server --- .../GithubFlavoredMarkdownConverter.php | 66 +++++++++++++++---- resources/js/docs.js | 12 ---- 2 files changed, 53 insertions(+), 25 deletions(-) diff --git a/app/Markdown/GithubFlavoredMarkdownConverter.php b/app/Markdown/GithubFlavoredMarkdownConverter.php index 77aafca7..20b4464a 100644 --- a/app/Markdown/GithubFlavoredMarkdownConverter.php +++ b/app/Markdown/GithubFlavoredMarkdownConverter.php @@ -2,25 +2,22 @@ namespace App\Markdown; -use Illuminate\Support\Str; +use Laravel\Unfenced\UnfencedExtension; +use League\CommonMark\Environment\Environment; +use League\CommonMark\Environment\EnvironmentInterface; +use League\CommonMark\Event\DocumentParsedEvent; +use League\CommonMark\Extension\Attributes\AttributesExtension; use League\CommonMark\Extension\Autolink\AutolinkExtension; +use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension; +use League\CommonMark\Extension\CommonMark\Node\Block\BlockQuote; +use League\CommonMark\Extension\CommonMark\Node\Block\Heading; +use League\CommonMark\Extension\CommonMark\Node\Inline\HtmlInline; use League\CommonMark\Extension\Strikethrough\StrikethroughExtension; use League\CommonMark\Extension\Table\TableExtension; use League\CommonMark\Extension\TaskList\TaskListExtension; use League\CommonMark\MarkdownConverter; -use League\CommonMark\Environment\Environment; -use App\Markdown\GithubFlavoredMarkdownExtension; -use Illuminate\Support\Facades\Vite; -use League\CommonMark\Node\Node; -use League\CommonMark\Renderer\ChildNodeRendererInterface; -use League\CommonMark\Util\HtmlElement; +use League\CommonMark\Node\Block\Paragraph; use Torchlight\Commonmark\V2\TorchlightExtension; -use Laravel\Unfenced\UnfencedExtension; -use League\CommonMark\Environment\EnvironmentInterface; -use League\CommonMark\Extension\Attributes\AttributesExtension; -use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension; -use League\CommonMark\Extension\CommonMark\Node\Block\BlockQuote; -use League\CommonMark\Renderer\NodeRendererInterface; /** * Converts GitHub Flavored Markdown to HTML. @@ -46,6 +43,49 @@ public function __construct(array $config = []) $environment->addRenderer(BlockQuote::class, new BlockQuoteRenderer()); + $environment->addEventListener(DocumentParsedEvent::class, function (DocumentParsedEvent $event) { + $walker = $event->getDocument()->walker(); + + while ($event = $walker->next()) { + [ + $paragraph, + $heading, + $linkOpener, + $linkCloser, + ] = [ + $event->getNode(), + $event->getNode()->next(), + $event->getNode()->firstChild(), + $event->getNode()->lastChild(), + ]; + + if ( + ! $heading instanceof Heading || + ! $paragraph instanceof Paragraph || + count($paragraph->children()) !== 2 || + ! $linkOpener instanceof HtmlInline || + ! $linkCloser instanceof HtmlInline || + ! str_starts_with($linkOpener->getLiteral(), '') || + substr_count($linkOpener->getLiteral(), '"') !== 2 || + $paragraph->firstChild()->next() !== $paragraph->lastChild() || + ! str_starts_with($linkCloser->getLiteral(), '') + ) { + continue; + } + + $link = str($linkOpener->getLiteral())->after('')->toString(); + + $heading->data->set('attributes.id', $link); + $heading->prependChild(new HtmlInline('')); + $heading->appendChild(new HtmlInline('')); + + $walker->resumeAt($heading->next()); + + $paragraph->detach(); + } + }); + parent::__construct($environment); } diff --git a/resources/js/docs.js b/resources/js/docs.js index 1ecc2fdc..0da42dc7 100644 --- a/resources/js/docs.js +++ b/resources/js/docs.js @@ -2,7 +2,6 @@ import './clipboard'; import './theme' document.addEventListener('DOMContentLoaded', () => { - wrapHeadingsInAnchors(); setupNavCurrentLinkHandling(); highlightSupportPolicyTable(); @@ -20,17 +19,6 @@ document.addEventListener('DOMContentLoaded', () => { }); }) -function wrapHeadingsInAnchors() { - [...document.querySelector('.docs_main').querySelectorAll('a[name]')].forEach(anchor => { - const heading = anchor.parentNode.nextElementSibling; - heading.id = anchor.name; - anchor.href = `#${anchor.name}`; - anchor.removeAttribute('name'); - [...heading.childNodes].forEach(node => anchor.appendChild(node)); - heading.appendChild(anchor); - }); -} - function setupNavCurrentLinkHandling() { [...document.querySelectorAll('.docs_sidebar h2')].forEach(el => { el.addEventListener('click', (e) => {