-
-
Notifications
You must be signed in to change notification settings - Fork 13
[2.x] Custom Markdown heading renderer #2047
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
emmadesilva
merged 100 commits into
new-asset-system
from
custom-markdown-heading-renderer
Dec 5, 2024
Merged
Changes from all commits
Commits
Show all changes
100 commits
Select commit
Hold shift + click to select a range
aff4388
Create HeadingRenderer.php
emmadesilva ed7f281
Create markdown-heading.blade.php
emmadesilva 7e45297
Render slot literally
emmadesilva 5607c6d
Indent code
emmadesilva ec6b2c6
Add an attribute for controlling permalink state
emmadesilva 98ddab1
Set state from class
emmadesilva e1ccda8
Fix recursion issue
emmadesilva 24e459f
Support extra attributes
emmadesilva 2c3e07b
Forward node attributes
emmadesilva 6631d86
Format props
emmadesilva 07d5e97
Update added config setting
emmadesilva c430b9b
Apply fixes from StyleCI
StyleCIBot b0a4ef9
Register the custom heading renderer
emmadesilva 87f03f7
Extract helper method
emmadesilva 585764d
Create heading permalinks configuration
emmadesilva bfc93e3
Better option name
emmadesilva cb6c70e
Construct with page class
emmadesilva 94cada0
Add permalinks only for configured pages
emmadesilva bcf5cdd
Format long line
emmadesilva 22d30c5
Nullable class string
emmadesilva 6fbb656
Remove auto-configuration for HeadingPermalinkExtension
emmadesilva b7f0509
Change component to match markup made by extension
emmadesilva 539a8eb
Use more explicit assertions
emmadesilva 53b158f
Make the permalink headings level configurable
emmadesilva bb9fd0a
Default to minimum level of 2
emmadesilva d8896ad
Introduce local variable
emmadesilva 2c53963
Post process to normalize result to CommonMark implementation
emmadesilva 358c799
Replace multiple replacements with single Regex
emmadesilva cc8cc6d
Add a test for when heading permalinks are disabled
emmadesilva 2475010
Sync configuration files
emmadesilva 30469f4
Mock the View factory
emmadesilva a6bcaa1
Fix mock bindings
emmadesilva 567b505
Granular mocks
emmadesilva 22a7745
Update test to test the automatic permalinks feature
emmadesilva 1100719
Remove the `MarkdownService::withPermalinks` method
emmadesilva d03dbf3
Add todo
emmadesilva 9311bd1
Create HeadingRendererUnitTest.php
emmadesilva 80ed9d4
Create MarkdownHeadingRendererTest.php
emmadesilva 5e243e7
Create a minimal view environment
emmadesilva c7eb6bb
Register finder path
emmadesilva bad9eb3
Create and configure the engine resolver
emmadesilva fd6df97
Extract helper method
emmadesilva 4995016
Basic constructor tests
emmadesilva a13f1e8
Cleanup helper method code
emmadesilva 0d08cc5
Helper to mock child node renderer
emmadesilva 6dc0095
Improve formatting
emmadesilva a6b26be
Set default value
emmadesilva 598d9c9
Implement initial unit test
emmadesilva 5b00e79
Test can add permalink based on configuration
emmadesilva bc876e8
Refactor to data provider attribute
emmadesilva 10e905a
Clean up after test
emmadesilva 703e70c
Automatically reset to cached config default
emmadesilva 039c11a
More extensive range testing
emmadesilva 3a709dd
Revert "More extensive range testing"
emmadesilva 496260c
Make protected helper methods public internal
emmadesilva 90ff929
Remove tests for later refactor
emmadesilva 0382947
Cleaner and extended testing
emmadesilva 240e3dd
Test more code paths
emmadesilva bbd5a0b
Clarify test name
emmadesilva 1195704
Apply fixes from StyleCI
StyleCIBot 65cc1ff
Unit test the post processing
emmadesilva 2dd5c32
Clarify test name to specify the reason behind it
emmadesilva b1b6266
Add extra test case
emmadesilva 2f31b9b
Apply fixes from StyleCI
StyleCIBot 3c3661a
Clean up test code to remove focus from implementation details
emmadesilva b00ea06
Implement the high level feature test
emmadesilva a7b52d6
Add some more assertions
emmadesilva e67a640
Assert on the full output when relevant
emmadesilva 6fe5cb8
Expand feature testing
emmadesilva 3c285a7
Add todo
emmadesilva 43d82a4
Test escaping
emmadesilva 4e80297
Document custom Markdown heading renderer
emmadesilva f53eb4d
Remove the enabled key from the permalinks configuration
emmadesilva ce21d66
Remove the unused `canEnablePermalinks` from `MarkdownService`
emmadesilva 3d3ed48
Improve the test
emmadesilva 7f79bee
Update RELEASE_NOTES.md
emmadesilva 0710e95
More semantic Markdown heading permalinks
emmadesilva 8737a52
Conditionally add element identifier
emmadesilva 04aa325
Remove support for custom identifier when it breaks permalinks
emmadesilva e7be152
Expect semantic heading identifiers
emmadesilva 3c66d44
Refactor to use Tailwind permalink styles
emmadesilva 5f90cbc
Tweak styles to match original behaviour
emmadesilva af9f4fa
Update tests for semantic Markdown permalink headers
emmadesilva 1caa631
Add scroll margin to header
emmadesilva 9c49d46
Format long line
emmadesilva 7ebd17f
Merge pull request #2052 from hydephp/semantic-markdown-heading-perma…
emmadesilva 418044e
Merge attributes dynamically
emmadesilva 4fb8e57
Unwrap unnecessary unwrapping
emmadesilva f9f9755
Trim empty class directives
emmadesilva a36760b
Allow side effect of extra space for edge case
emmadesilva c78ddeb
Inline local variable override
emmadesilva 3feddd3
Revert "Inline local variable override"
emmadesilva 0d9f3e8
Remove todo as it can be handled in Blade
emmadesilva 99e2d1e
Add internal heading registry
emmadesilva 7f40d01
Start duplicate suffixes at two
emmadesilva c4e67ff
Add suffix to heading identifiers of the same name
emmadesilva f0adee1
Update RELEASE_NOTES.md
emmadesilva 155c456
Add more test values to ensure proper count
emmadesilva b959d2d
Cleanup and refactor code
emmadesilva 3df7aa2
Add array types
emmadesilva File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
packages/framework/resources/views/components/markdown-heading.blade.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| @props([ | ||
| 'level' => 1, | ||
| 'id' => null, | ||
| 'extraAttributes' => [], | ||
| 'addPermalink' => config('markdown.permalinks.enabled', true), | ||
| ]) | ||
|
|
||
| @php | ||
| $tag = 'h' . $level; | ||
| $id = $id ?? \Illuminate\Support\Str::slug($slot); | ||
|
|
||
| $extraAttributes = array_merge($extraAttributes, [ | ||
| 'id' => $addPermalink ? $id : ($extraAttributes['id'] ?? null), | ||
| 'class' => trim(($extraAttributes['class'] ?? '') . ($addPermalink ? ' group w-fit scroll-mt-2' : '')), | ||
| ]); | ||
| @endphp | ||
|
|
||
| <{{ $tag }} {{ $attributes->merge($extraAttributes) }}> | ||
| {!! $slot !!} | ||
| @if($addPermalink === true) | ||
| <a href="#{{ $id }}" class="heading-permalink opacity-0 ml-1 transition-opacity duration-300 ease-linear px-1 group-hover:opacity-100 focus:opacity-100 group-hover:grayscale-0 focus:grayscale-0" title="Permalink"> | ||
| # | ||
| </a> | ||
| @endif | ||
| </{{ $tag }}> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
packages/framework/src/Markdown/Processing/HeadingRenderer.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Hyde\Markdown\Processing; | ||
|
|
||
| use Hyde\Pages\DocumentationPage; | ||
| use Illuminate\Support\Str; | ||
| use League\CommonMark\Extension\CommonMark\Node\Block\Heading; | ||
| use League\CommonMark\Node\Node; | ||
| use League\CommonMark\Renderer\ChildNodeRendererInterface; | ||
| use League\CommonMark\Renderer\NodeRendererInterface; | ||
|
|
||
| /** | ||
| * Renders a heading node, and supports built-in permalink generation. | ||
| * | ||
| * @see \League\CommonMark\Extension\CommonMark\Renderer\Block\HeadingRenderer | ||
| */ | ||
| class HeadingRenderer implements NodeRendererInterface | ||
| { | ||
| /** @var ?class-string<\Hyde\Pages\Concerns\HydePage> */ | ||
| protected ?string $pageClass = null; | ||
|
|
||
| /** @var array<string> */ | ||
| protected array $headingRegistry = []; | ||
|
|
||
| /** @param ?class-string<\Hyde\Pages\Concerns\HydePage> $pageClass */ | ||
| public function __construct(string $pageClass = null, array &$headingRegistry = []) | ||
| { | ||
| $this->pageClass = $pageClass; | ||
| $this->headingRegistry = &$headingRegistry; | ||
| } | ||
|
|
||
| public function render(Node $node, ChildNodeRendererInterface $childRenderer): string | ||
| { | ||
| if (! ($node instanceof Heading)) { | ||
| throw new \InvalidArgumentException('Incompatible node type: '.get_class($node)); | ||
| } | ||
|
|
||
| $content = $childRenderer->renderNodes($node->children()); | ||
|
|
||
| $rendered = view('hyde::components.markdown-heading', [ | ||
| 'level' => $node->getLevel(), | ||
| 'slot' => $content, | ||
| 'id' => $this->makeHeadingId($content), | ||
| 'addPermalink' => $this->canAddPermalink($content, $node->getLevel()), | ||
| 'extraAttributes' => $node->data->get('attributes'), | ||
| ])->render(); | ||
|
|
||
| return $this->postProcess($rendered); | ||
| } | ||
|
|
||
| /** @internal */ | ||
| public function canAddPermalink(string $content, int $level): bool | ||
| { | ||
| return config('markdown.permalinks.enabled', true) | ||
| && $level >= config('markdown.permalinks.min_level', 2) | ||
| && $level <= config('markdown.permalinks.max_level', 6) | ||
| && ! str_contains($content, 'class="heading-permalink"') | ||
| && in_array($this->pageClass, config('markdown.permalinks.pages', [DocumentationPage::class])); | ||
| } | ||
|
|
||
| /** @internal */ | ||
| public function postProcess(string $html): string | ||
| { | ||
| $html = str_replace('class=""', '', $html); | ||
| $html = preg_replace('/<h([1-6]) >/', '<h$1>', $html); | ||
|
|
||
| return implode('', array_map('trim', explode("\n", $html))); | ||
| } | ||
|
|
||
| protected function makeHeadingId(string $contents): string | ||
| { | ||
| $identifier = $this->ensureIdentifierIsUnique(Str::slug($contents)); | ||
|
|
||
| $this->headingRegistry[] = $identifier; | ||
|
|
||
| return $identifier; | ||
| } | ||
|
|
||
| protected function ensureIdentifierIsUnique(string $slug): string | ||
| { | ||
| $identifier = $slug; | ||
| $suffix = 2; | ||
|
|
||
| while (in_array($identifier, $this->headingRegistry)) { | ||
| $identifier = $slug.'-'.$suffix++; | ||
| } | ||
|
|
||
| return $identifier; | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.