Skip to content
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

[2.x] Custom Markdown heading renderer #2047

Open
wants to merge 71 commits into
base: new-asset-system
Choose a base branch
from

Conversation

caendesilva
Copy link
Member

@caendesilva caendesilva commented Dec 1, 2024

Abstract

Changes

  • Removed auto-configuration for HeadingPermalinkExtension, meaning user added custom options provided to that extension will be ignored. If you for some reason require that extension's custom features, either implement those to the Blade component after publishing it, or disable the Hyde permalink feature from the configuration and add the extension yourself.
  • Removes the MarkdownService::withPermalinks method, see comment

@caendesilva
Copy link
Member Author

Cursor (Claude) analysis of the three approaches for implementing heading permalinks:

1. Original CommonMark Extension Implementation

    protected function configurePermalinksExtension(): void
    {
        $this->addExtension(HeadingPermalinkExtension::class);
                'fragment_prefix' => '',
        $this->config = array_merge([
            'heading_permalink' => [
                'id_prefix' => '',
                'fragment_prefix' => '',
                'symbol' => '',
                'insert' => 'after',
                'min_heading_level' => 2,
                'aria_hidden' => false,
            ],
        ], $this->config);
    }

Benefits:

  • Simple to implement and maintain
  • Follows CommonMark's standard extension pattern
  • Performant since it's handled during Markdown parsing
  • Consistent behavior across all Markdown content

Drawbacks:

  • Limited customization options
  • Requires modifying CommonMark configuration
  • Can't easily change permalink position/style per heading
  • Tightly coupled to CommonMark implementation

2. Full Blade Component Replacement

@props(['level' => 1, 'id' => null])

@php
$tag = 'h' . $level;
$id = $id ?? \Illuminate\Support\Str::slug($slot);
@endphp

<{{ $tag }} {{ $attributes->merge(['id' => $id]) }}>
    {{ $slot }}
    @if(config('markdown.features.permalinks', true))
        <a href="#{{ $id }}" class="heading-permalink" aria-label="Permalink for this section"></a>
    @endif
</{{ $tag }}> 

Benefits:

  • Full control over heading HTML structure
  • Can customize behavior per heading instance
  • Easy to modify styling and structure
  • Can add additional features like custom icons
  • More maintainable Blade templating

Drawbacks:

  • More complex implementation
  • Requires replacing CommonMark's heading renderer
  • Slightly higher performance overhead
  • Need to maintain heading level logic
  • Must handle ID generation manually

3. Permalink-Only Component Injection

Example implementation:

// components/heading-permalink.blade.php
@props(['id'])

<a href="#{{ $id }}" 
   class="heading-permalink" 
   aria-label="Permalink for this section">
</a>

Benefits:

  • Minimal implementation
  • Easy to inject before/after existing headings
  • Keeps CommonMark's heading rendering intact
  • Very focused responsibility
  • Lightweight performance impact

Drawbacks:

  • Less control over heading structure
  • Need to coordinate with existing heading HTML
  • May require additional DOM manipulation
  • Could have positioning challenges
  • Limited contextual awareness

Recommendation

I recommend using the full Blade component replacement approach (#2) because:

  1. It provides the most flexibility for future customization
  2. Keeps the implementation in pure Blade/PHP without CommonMark dependencies
  3. Allows per-heading customization when needed
  4. Easier to maintain and modify
  5. Better integration with Laravel/Blade ecosystem
  6. Can still use existing CSS without changes:
.prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 {
    @apply w-fit;
}

.prose :is(h1,h2,h3,h4,h5,h6):hover .heading-permalink,
.prose :is(h1,h2,h3,h4,h5,h6):focus .heading-permalink {
    @apply opacity-75 grayscale transition-opacity duration-100 ease-out;
}

.heading-permalink {
    @apply opacity-0 ml-1 transition-opacity duration-300 ease-linear px-1 scroll-m-4;
}

.heading-permalink::before {
    @apply content-['#'];
}

.heading-permalink:hover, .heading-permalink:focus {
    @apply opacity-100 grayscale-0;
}

The slight performance overhead is negligible for most sites, and the benefits of having full control over the heading structure outweigh the drawbacks. It also makes it easier to add features like custom icons, positioning options, and accessibility improvements in the future.

The permalink-only component approach might seem simpler at first, but it introduces complexity in positioning and coordination with existing heading elements. The full component replacement gives us a clean slate to work with while maintaining all the functionality we need.

@caendesilva caendesilva force-pushed the custom-markdown-heading-renderer branch from 61386bd to e1ccda8 Compare December 1, 2024 18:04
Copy link

codecov bot commented Dec 1, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 99.98%. Comparing base (166a551) to head (43d82a4).

Additional details and impacted files
@@                  Coverage Diff                   @@
##             new-asset-system    #2047      +/-   ##
======================================================
- Coverage              100.00%   99.98%   -0.02%     
- Complexity               1925     1932       +7     
======================================================
  Files                     195      196       +1     
  Lines                    5122     5131       +9     
======================================================
+ Hits                     5122     5130       +8     
- Misses                      0        1       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@caendesilva caendesilva changed the title [2.x] Custom markdown heading renderer [2.x] Custom Markdown heading renderer Dec 1, 2024
@caendesilva caendesilva mentioned this pull request Dec 1, 2024
15 tasks
Apply fixes from StyleCI

Co-Authored-By: StyleCI Bot <[email protected]>
@caendesilva caendesilva force-pushed the custom-markdown-heading-renderer branch from abc3b21 to 6dc0095 Compare December 1, 2024 21:56
@caendesilva caendesilva force-pushed the custom-markdown-heading-renderer branch from 8b78b4d to 598d9c9 Compare December 1, 2024 22:06
@caendesilva
Copy link
Member Author

Not sure which is best:

Which method of heading permalinks is best for a markdown renderer?

        <h2 id="documentation-heading">Documentation Heading <a href="#documentation-heading" class="heading-permalink" title="Permalink">#</a></h2>
        <h2>Documentation Heading<a id="documentation-heading" href="#documentation-heading" class="heading-permalink" title="Permalink">#</a></h2>

GPT-4o answer:

Both methods can work for generating heading permalinks in a Markdown renderer, but each has its advantages and trade-offs depending on the goals of your renderer. Here's a breakdown of the two approaches:


1st Method: Using id in the <h2> tag

<h2 id="documentation-heading">Documentation Heading <a href="#documentation-heading" class="heading-permalink" title="Permalink">#</a></h2>

Advantages:

  • Cleaner semantics: The id directly ties to the heading itself, which aligns well with HTML5 standards.
  • Improved accessibility: Assistive technologies can better associate the id with the heading content.
  • CSS styling: Easier to target headings via their id for custom styles.

Disadvantages:

  • Duplicated text: The permalink anchor (<a> with #) is part of the heading text, potentially causing confusion if copy-pasting.
  • Requires extra spacing: Styling is needed to visually separate the # permalink from the heading text.

2nd Method: Using id in the <a> tag

<h2>Documentation Heading<a id="documentation-heading" href="#documentation-heading" class="heading-permalink" title="Permalink">#</a></h2>

Advantages:

  • Copy-paste clarity: The heading text can be copied without including the permalink character.
  • Simplified DOM structure: Keeps the heading content separate from metadata like the id.

Disadvantages:

  • Less accessible: The id is associated with the <a> rather than the heading, which might be less intuitive for assistive technologies.
  • Potential styling complexity: Aligning the anchor visually and semantically with the heading may require extra CSS.

Recommendation:

The 1st method is generally better for most Markdown renderers due to its semantic correctness and accessibility. While the permalink being part of the text might be a minor annoyance for some users, this can be mitigated with good CSS (e.g., hiding the permalink on copy or adjusting its appearance).

However, if your primary goal is a cleaner user experience for copy-pasting (e.g., documentation sites where users frequently copy headings), the 2nd method might be preferable.

@caendesilva caendesilva force-pushed the custom-markdown-heading-renderer branch from 3b444a4 to 3c285a7 Compare December 2, 2024 11:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants