From 9e2957a0e351d34355959fa5655da2ed6d76d08d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Sun, 5 Jul 2026 02:00:06 +0200 Subject: [PATCH] docs: normalise extension docs --- docs/CodeBlockTitle.md | 229 ++++------------------- docs/ContentSlicer.md | 359 +++++------------------------------- docs/HeadingLevel.md | 324 ++++---------------------------- docs/Import.md | 10 +- docs/Include.md | 10 +- docs/LinkRewriter.md | 398 ++++------------------------------------ docs/README.md | 2 +- docs/Source.md | 8 +- docs/TableOfContents.md | 12 +- docs/Tabs.md | 6 +- 10 files changed, 174 insertions(+), 1184 deletions(-) diff --git a/docs/CodeBlockTitle.md b/docs/CodeBlockTitle.md index 6b3aca5..599063c 100644 --- a/docs/CodeBlockTitle.md +++ b/docs/CodeBlockTitle.md @@ -1,7 +1,7 @@ # CodeBlockTitle Extension -Parses `title="..."` attributes from fenced code block info strings and renders -them as `
` elements inside `
` tags. +Reads a `title="..."` attribute from a fenced code block's info string and wraps +the block in a `
` with a `
`. ## Basic Usage @@ -14,223 +14,72 @@ $environment = new Environment(); $environment->addExtension(new CodeBlockTitleExtension()); $converter = new MarkdownConverter($environment); - -$markdown = <<<'MD' - ```php title="example.php" - echo "Hello, World!"; - ``` -MD; - -echo $converter->convert($markdown); -``` - -## Features - -- Parses `title="..."` from fenced code block info strings -- Wraps code blocks in `
` elements with `
` -- Preserves the language specifier alongside the title -- Works with all code block languages -- Integrates seamlessly with other extensions - -## Syntax - -Add a `title="..."` attribute to your code block's info string: - -```markdown - ```javascript title="app.js" - function greet(name) { - console.log(`Hello, ${name}!`); - } - ``` ``` -The `title` attribute can contain any text and supports special characters. -Quotes within the title should be escaped: - -```markdown - ```bash title="script with \"quotes\"" - echo "test" - ``` -``` - -## Output - -The extension transforms: - -```markdown - ```php title="example.php" - echo "Hello, World!"; - ``` +````markdown +```php title="example.php" +echo "Hello, World!"; ``` - -Into: +```` ```html -
-
example.php
-
echo "Hello, World!";
+
+
example.php
+
echo "Hello, World!";
+
``` -## Configuration - -The extension requires no configuration and can be registered without -parameters: +A block without a title renders as the usual `
` with no `
`. -```php -$environment->addExtension(new CodeBlockTitleExtension()); -``` +## Syntax -Optionally, you can provide a custom base renderer: +Add the attribute to the info string, right after the language: -```php -use League\CommonMark\Extension\CommonMark\Renderer\Block\FencedCodeRenderer; - -$baseRenderer = new FencedCodeRenderer(); -$environment->addExtension(new CodeBlockTitleExtension($baseRenderer)); +````markdown +```javascript title="app.js" +console.log("hi"); ``` +```` -## Advanced Usage - -### With Multiple Extensions +- `filename="..."` is accepted as an alias for `title`. +- Escape quotes inside the title with a backslash: `title="a \"quoted\" name"`. -Combine with other extensions for enhanced functionality: +## Constructor ```php -$environment = new Environment(); -$environment->addExtension(new CommonMarkCoreExtension()); -$environment->addExtension(new CodeBlockTitleExtension()); -$environment->addExtension(new ContentSlicerExtension()); - -$converter = new MarkdownConverter($environment); +new CodeBlockTitleExtension(?NodeRendererInterface $baseRenderer = null) ``` -### Styling the Figure +| Parameter | Type | Default | Description | +|----------------|-------------------------------|------------------------|----------------------------------------------------| +| `baseRenderer` | `?NodeRendererInterface` | `new FencedCodeRenderer()` | Renderer used to build the inner `
`. Override to compose with another code renderer. |
 
-The extension generates semantic HTML that can be styled with CSS:
+## Styling
 
-```css
-figure {
-  border: 1px solid #E0E0E0;
-  border-radius: 4px;
-  padding: 12px;
-  margin: 16px 0;
-}
+The output uses stable hooks: `.code-block.has-title`, `.code-title`, and a
+`data-title` attribute.
 
-figcaption {
-  font-size: 12px;
-  font-weight: 600;
-  color: #666666;
-  margin-bottom: 8px;
-  font-family: 'Monaco', 'Menlo', monospace;
+```css
+.code-block.has-title {
+    border: 1px solid #e0e0e0;
+    border-radius: 4px;
 }
 
-figure code {
-  display: block;
-  overflow-x: auto;
+.code-title {
+    font: 600 12px/1 ui-monospace, monospace;
+    color: #666;
+    padding: 8px 12px;
 }
 ```
 
-## Implementation Details
-
-- **Pattern**: Renderer-based decorator
-- **Priority**: 10 (runs before default renderers)
-- **Custom Nodes**: None (uses standard `FencedCode` node)
-- **Event Listeners**: None
-
-The extension decorates the default `FencedCodeRenderer` and extracts the title
-attribute during rendering. This approach ensures compatibility with other
-rendering extensions.
-
-## Examples
-
-### Multiple Code Blocks with Titles
-
-```markdown
-    ```javascript title="index.js"
-    import express from 'express';
-    const app = express();
-    app.listen(3000);
-    ```
-    
-    ```html title="index.html"
-    
-    
-      App
-      Hello World
-    
-    ```
-```
-
-### Without Title
-
-Blocks without a title attribute work normally:
-
-```markdown
-    ```python
-    def hello():
-        print("Hello, World!")
-    ```
-```
-
-Renders as:
-
-```html
-
-
def hello():
-    print("Hello, World!")
-``` - -### With Complex Titles - -Titles can include file paths and descriptions: - -```markdown - ```bash title="src/scripts/deploy.sh" - #!/bin/bash - ./build.sh && ./deploy.sh - ``` -``` - -## Troubleshooting - -### Title not appearing - -Ensure the `title="..."` attribute is in the info string (the part immediately -after the opening ````): - -```markdown - - ```php title="file.php" - code here - ``` - - - ```php - // title="file.php" - code here - ``` -``` - -### Quotes in titles - -Use backslash escaping for quotes within titles: - -```markdown - ```bash title="script with \"quotes\"" - echo "test" - ``` -``` - ## See Also -- [league/commonmark documentation](https://commonmark.thephpleague.com/) +- [Source](Source.md): display a source file with line numbers and highlighting +- [Import](Import.md): embed raw file content into a code block --- -> **This package is part of -the [alto/commonmark](https://github.com/PhpAlto/commonmark) monorepo.** -> This repository is a read-only split — to file issues, open pull requests, or -> contribute, please use the main repository: * -*https://github.com/PhpAlto/commonmark** +> **This package is part of the [alto/commonmark](https://github.com/PhpAlto/commonmark) monorepo.** +> This repository is a read-only split. To file issues, open pull requests, or contribute, use the main repository: **https://github.com/PhpAlto/commonmark** diff --git a/docs/ContentSlicer.md b/docs/ContentSlicer.md index f9853d0..2a9de6f 100644 --- a/docs/ContentSlicer.md +++ b/docs/ContentSlicer.md @@ -1,7 +1,8 @@ # ContentSlicer Extension -Automatically divides a document into nested `
` elements based on -heading hierarchy, creating a semantic document structure. +Wraps heading sections in nested `
` elements to give the document a +semantic outline. By default the top-level heading stays at the document root and +every deeper heading opens a section. ## Basic Usage @@ -14,74 +15,44 @@ $environment = new Environment(); $environment->addExtension(new ContentSlicerExtension()); $converter = new MarkdownConverter($environment); - -$markdown = <<<'MD' -# Main Topic - -Content here. - -## Subtopic 1 - -More content. - -## Subtopic 2 - -Final content. -MD; - -echo $converter->convert($markdown); ``` -## Features - -- Automatically wraps heading-based sections in `
` elements -- Respects heading hierarchy (h1, h2, h3, etc.) -- Creates proper nesting based on heading levels -- Works with any heading level -- No configuration required -- Integrates with semantic HTML practices - -## How It Works - -The extension processes the document after parsing and restructures it based on -heading levels: - -1. **Detection**: Identifies all headings in the document -2. **Grouping**: Groups content following each heading -3. **Nesting**: Creates nested sections matching the heading hierarchy -4. **Wrapping**: Wraps each group in a `
` element - -## Output Examples - -### Simple Structure - -Input: - ```markdown # Main Topic -Content here. +Intro. ## Subtopic -More content. +Details. ``` -Output: - ```html +

Main Topic

+

Intro.

-

Main Topic

-

Content here.

-
-

Subtopic

-

More content.

-
+

Subtopic

+

Details.

``` -### Complex Hierarchy +The `

` is not wrapped: at the default threshold only headings deeper than +level 1 open a section. + +## Constructor + +```php +new ContentSlicerExtension(int $minSectionLevel = 1) +``` + +| Parameter | Type | Default | Description | +|-------------------|------|---------|----------------------------------------------------------------------------------------------------------| +| `minSectionLevel` | int | `1` | Only headings deeper than this level open a section. `1` wraps h2+ (h1 stays at root); `0` wraps h1 too; `2` wraps only h3+. | + +## Output Examples + +### Nested hierarchy Input: @@ -107,278 +78,46 @@ Output: ```html +

Main

+

Content 1

-

Main

-

Content 1

-
-

Sub 1

-

Content 2

-
-

Sub 1.1

-

Content 3

-
-
+

Sub 1

+

Content 2

-

Sub 2

-

Content 4

+

Sub 1.1

+

Content 3

-``` - -## Configuration - -No configuration is required. Simply register the extension: - -```php -$environment->addExtension(new ContentSlicerExtension()); -``` - -## Advanced Usage - -### Combining with HeadingLevel Extension - -Adjust heading levels before creating sections: - -```php -use Alto\CommonMark\Extension\HeadingLevel\HeadingLevelExtension; - -$environment->addExtension( - new HeadingLevelExtension(['down' => 1]) // Shift headings down by 1 -); -$environment->addExtension(new ContentSlicerExtension()); -``` - -### Styling Sections - -Generate CSS to style the section structure: - -```css -section { - margin: 20px 0; - padding: 16px; - border-left: 4px solid #007BFF; -} - -section section { - margin-left: 20px; - border-left-color: #28A745; -} - -section section section { - border-left-color: #FFC107; -} - -h1, h2, h3, h4, h5, h6 { - margin-top: 0; -} -``` - -### With Table of Contents - -Generate a table of contents alongside sections: - -```php -$html = $converter->convert($markdown); - -// Extract heading levels and create TOC -preg_match_all('/(.*?)<\/h\1>/s', $html, $matches); - -$toc = '
    '; -foreach ($matches[2] as $heading) { - $toc .= '
  • ' . strip_tags($heading) . '
  • '; -} -$toc .= '
'; - -echo $toc; -echo $html; -``` - -### Handling Content Before First Heading - -Content that appears before the first heading is preserved at the document root -level: - -Input: - -```markdown -Introduction paragraph. - -# Section 1 - -Content here. -``` - -Output: - -```html -

Introduction paragraph.

-

Section 1

-

Content here.

+

Sub 2

+

Content 4

``` -## Use Cases - -### Documentation Sites - -Structure documentation with automatic section wrapping for better semantics and -styling. - -### Blog Posts - -Automatically organize blog content with proper heading hierarchy. - -### HTML Export - -Create valid, nested section structures for better accessibility and semantic -meaning. - -### API Documentation - -Generate properly nested sections for endpoint documentation organized by -resource or category. - -## Implementation Details - -- **Pattern**: Event-based listener with custom node rendering -- **Event**: `DocumentParsedEvent` -- **Custom Nodes**: `Section` (custom block node) -- **Renderers**: `SectionRenderer` (renders `
` tags) - -The extension listens to the document parsed event and creates custom `Section` -nodes that are then rendered as `
` HTML elements. - -## Examples - -### Book-like Structure - -```markdown -# Part 1: Getting Started - -## Chapter 1: Introduction - -Content here. - -## Chapter 2: Setup - -More content. - -# Part 2: Advanced Topics - -## Chapter 3: Patterns - -Advanced content. -``` - -### API Documentation - -```markdown -# Users API - -## User Objects - -Description of user structure. - -### Creating Users - -POST endpoint details. - -### Updating Users - -PUT endpoint details. - -## Authentication - -Security information. -``` - -### Knowledge Base - -```markdown -# HTML & CSS - -## Formatting - -### Text - -Content about text formatting. - -### Lists - -Content about lists. - -## Images - -Image handling documentation. - -# JavaScript - -## Basics - -JavaScript fundamentals. - -## DOM - -DOM manipulation guide. -``` - -## Accessibility Notes - -The extension improves document accessibility by: - -- Creating proper document outlines with nested sections -- Ensuring logical heading hierarchy -- Supporting assistive technologies with semantic HTML -- Enabling better document navigation for screen readers - -## Troubleshooting - -### Sections not appearing - -Ensure you're registering the extension before creating the converter: - -```php -$environment = new Environment(); -$environment->addExtension(new ContentSlicerExtension()); -$converter = new MarkdownConverter($environment); -``` - -### Unexpected nesting - -Verify your heading hierarchy is logical. The extension respects heading levels -strictly. If you have an h1 followed by an h3 (skipping h2), the h3 will nest -under the h1. - -To fix: +### Wrapping every heading -```markdown -# Main +With `new ContentSlicerExtension(0)`, level-1 headings open a section as well: -## Sub +```html -### Sub-sub +
+

Main

+

Content 1

+
+

Sub 1

+

Content 2

+
+
``` -### Combining with other extensions - -Register ContentSlicer after parsing-related extensions: - -```php -$environment->addExtension(new CommonMarkCoreExtension()); -$environment->addExtension(new HeadingLevelExtension([...])); -$environment->addExtension(new ContentSlicerExtension()); -``` +Content that appears before the first wrapped heading stays at the document root. ## See Also -- [HeadingLevel Extension](HeadingLevel.md) - Adjust heading levels -- [league/commonmark documentation](https://commonmark.thephpleague.com/) +- [HeadingLevel](HeadingLevel.md): adjust heading levels before sectioning +- [TableOfContents](TableOfContents.md): generate a TOC from the same headings --- -> **This package is part of -the [alto/commonmark](https://github.com/PhpAlto/commonmark) monorepo.** -> This repository is a read-only split — to file issues, open pull requests, or -> contribute, please use the main repository: * -*https://github.com/PhpAlto/commonmark** +> **This package is part of the [alto/commonmark](https://github.com/PhpAlto/commonmark) monorepo.** +> This repository is a read-only split. To file issues, open pull requests, or contribute, use the main repository: **https://github.com/PhpAlto/commonmark** diff --git a/docs/HeadingLevel.md b/docs/HeadingLevel.md index eca65b6..cbf4626 100644 --- a/docs/HeadingLevel.md +++ b/docs/HeadingLevel.md @@ -1,7 +1,7 @@ # HeadingLevel Extension -Adjusts heading levels in a document through mapping, shifts, or custom -callbacks. +Rewrites heading levels across a document with one of three strategies: an +explicit map, a uniform shift, or a callback. ## Basic Usage @@ -11,87 +11,42 @@ use League\CommonMark\Environment\Environment; use League\CommonMark\MarkdownConverter; $environment = new Environment(); -// Shift all headings down by 1 level (h1 → h2, h2 → h3, etc.) +// Shift every heading down by one level (h1 becomes h2, h2 becomes h3, ...) $environment->addExtension(new HeadingLevelExtension(['down' => 1])); $converter = new MarkdownConverter($environment); - -$markdown = "# Main Title\n## Subtitle"; -echo $converter->convert($markdown); ``` -## Features - -- **Shift headings**: Uniformly increase or decrease heading levels -- **Map headings**: Explicitly define h1→h3, h2→h4, etc. -- **Custom logic**: Use callbacks for complex transformation -- **Flexible configuration**: Multiple strategies in one config -- **Validation**: Prevents invalid heading levels (h0, h7+) - -## Configuration Options - -### 1. Simple Shift (down/up) - -Shift all headings by a specific amount: - -```php -// Shift down by 1 level -new HeadingLevelExtension(['down' => 1]) -// h1 → h2, h2 → h3, ... - -// Shift down by 2 levels -new HeadingLevelExtension(['down' => 2]) -// h1 → h3, h2 → h4, ... - -// Shift up by 1 level (negative down) -new HeadingLevelExtension(['down' => -1]) -// h2 → h1, h3 → h2, ... -``` +## Configuration -### 2. Level Mapping +Exactly **one** strategy applies, chosen by this priority: `map`, then `down`, +then `callback`. If more than one key is set, the first present in that order +wins; they are not combined. -Explicitly map heading levels: +| Key | Type | Description | +|------------|------------------------|-----------------------------------------------------------------------------| +| `map` | `array` | Explicit level-to-level mapping. Levels absent from the map are left unchanged. | +| `down` | `int` | Uniform shift. Positive moves deeper (h1 to h2); negative moves up (h2 to h1). | +| `callback` | `callable(int): ?int` | Receives the current level, returns the new level, or `null` to leave it unchanged. | ```php -new HeadingLevelExtension([ - 'map' => [ - 1 => 2, // h1 → h2 - 2 => 3, // h2 → h3 - 3 => 3, // h3 stays h3 - ] -]) -``` - -### 3. Custom Callback +// Map +new HeadingLevelExtension(['map' => [1 => 2, 2 => 3]]); -Use a function for complex logic: +// Shift up by 2 (negative down) +new HeadingLevelExtension(['down' => -2]); -```php -new HeadingLevelExtension([ - 'callback' => function(int $level): int { - // Custom logic - return min($level + 1, 6); // Shift down but cap at h6 - } -]) +// Callback +new HeadingLevelExtension(['callback' => fn (int $level): int => min($level + 1, 6)]); ``` -### 4. Combined Configuration - -Mix multiple strategies (applied in order): - -```php -new HeadingLevelExtension([ - 'down' => 1, // First: shift down by 1 - 'map' => [1 => 2], // Then: map h1 to h2 (now effective h2 → h2) - 'callback' => function($level) { - return min($level, 6); // Ensure max h6 - } -]) -``` +> Levels are written as-is. Nothing clamps the result, so a shift can produce +> `h0` or `h7`+. Guard the range yourself with a callback if that matters: +> `fn (int $l): int => max(1, min($l + 1, 6))`. ## Output Examples -### Shift Down +### Shift down Input: @@ -103,9 +58,7 @@ Input: ### Details ``` -Config: `['down' => 1]` - -Output: +Config `['down' => 1]`: ```html

Main Title

@@ -113,7 +66,7 @@ Output:

Details

``` -### Shift Up +### Shift up Input: @@ -123,16 +76,14 @@ Input: #### Sub-section ``` -Config: `['down' => -2]` - -Output: +Config `['down' => -2]`: ```html

Nested Heading

Sub-section

``` -### Custom Mapping +### Map Input: @@ -144,9 +95,7 @@ Input: ### Subsection ``` -Config: `['map' => [1 => 2, 2 => 3, 3 => 4]]` - -Output: +Config `['map' => [1 => 2, 2 => 3, 3 => 4]]`: ```html

Title

@@ -154,223 +103,14 @@ Output:

Subsection

``` -## Advanced Usage - -### Normalizing Imported Content - -When embedding content from external sources: - -```php -// External content starts at h1, but you need h2 -$environment->addExtension( - new HeadingLevelExtension(['down' => 1]) -); -``` - -### Preventing Invalid Levels - -Ensure headings don't exceed h6: - -```php -new HeadingLevelExtension([ - 'callback' => function(int $level): int { - return min($level + 2, 6); // Max h6 - } -]) -``` - -### Document Composition - -Shift content based on context: - -```php -function convertFragment(string $markdown, int $baseLevel): string { - $env = new Environment(); - $env->addExtension( - new HeadingLevelExtension([ - 'down' => $baseLevel - 1 - ]) - ); - - $converter = new MarkdownConverter($env); - return $converter->convert($markdown); -} - -// Use different base levels -echo convertFragment($intro, 1); // Starts at h1 -echo convertFragment($section1, 2); // Starts at h2 -echo convertFragment($section2, 2); // Starts at h2 -``` - -### With ContentSlicer - -Adjust levels before creating sections: - -```php -$environment->addExtension( - new HeadingLevelExtension(['down' => 1]) -); -$environment->addExtension( - new ContentSlicerExtension() -); -``` - -The heading adjustment happens before sectioning, so sections reflect the -adjusted levels. - -## Common Patterns - -### Include Fragment in Document - -```php -// Main document starts with h1 -$main = "# Main Document\n## Introduction"; - -// Fragment to include at h3 level -$fragment = "# Fragment Title\n## Subsection"; - -$env = new Environment(); -$env->addExtension( - new HeadingLevelExtension(['down' => 2]) // Shift fragment to h3, h4 -); - -$converter = new MarkdownConverter($env); - -echo "# Document\n" . $fragment; // Fragment adjusted to fit -``` - -### Multi-level Document Structure - -```php -$config = [ - 'level1' => ['down' => 0], // h1, h2, h3... - 'level2' => ['down' => 1], // h2, h3, h4... - 'level3' => ['down' => 2], // h3, h4, h5... -]; - -function includeMarkdown(string $file, int $level): string { - $env = new Environment(); - $env->addExtension( - new HeadingLevelExtension($config["level$level"]) - ); - $converter = new MarkdownConverter($env); - return $converter->convert(file_get_contents($file)); -} -``` - -### Limiting Maximum Heading Level - -Ensure no heading exceeds h6: - -```php -new HeadingLevelExtension([ - 'callback' => function(int $level): int { - // Shift but cap at h6 - return min($level + 1, 6); - } -]) -``` - -## Implementation Details - -- **Pattern**: Event-based processor -- **Event**: `DocumentParsedEvent` -- **Custom Nodes**: None (modifies existing `Heading` nodes) -- **Tree Traversal**: Walks AST and modifies heading levels - -The extension processes the document after parsing and directly modifies heading -levels in the AST. - -## Examples - -### Book Structure with Included Chapters - -```php -// chapters/intro.md starts with # Introduction -// chapters/chapter1.md starts with # Chapter 1 -// chapters/chapter2.md starts with # Chapter 2 - -$env = new Environment(); -$env->addExtension(new ContentSlicerExtension()); - -$book = "# My Book\n\n"; -$book .= $convertFragment('chapters/intro.md', 2); -$book .= $convertFragment('chapters/chapter1.md', 2); -$book .= $convertFragment('chapters/chapter2.md', 2); - -$converter = new MarkdownConverter($env); -echo $converter->convert($book); -``` - -### API Documentation - -```php -// Each endpoint documentation starts with # Title -// Adjust to h3 under # API section - -$env = new Environment(); -$env->addExtension( - new HeadingLevelExtension(['down' => 2]) -); - -$markdown = "# API\n\n" . file_get_contents('endpoints.md'); -$converter = new MarkdownConverter($env); -echo $converter->convert($markdown); -``` - -## Troubleshooting - -### Headings not changing - -Ensure the extension is registered before creating the converter: - -```php -$environment = new Environment(); -$environment->addExtension( - new HeadingLevelExtension(['down' => 1]) -); -$converter = new MarkdownConverter($environment); // Must be after -``` - -### Invalid heading levels created - -Check your configuration doesn't create h0 or h7+: - -```php -// Bad: creates h0 -new HeadingLevelExtension(['down' => -1]) // h1 → h0 - -// Good: validate levels -new HeadingLevelExtension([ - 'callback' => function(int $level): int { - return max(1, min($level - 1, 6)); // Keep within h1-h6 - } -]) -``` - -### Mapping not applied as expected - -Remember that multiple config options apply in sequence: - -```php -[ - 'down' => 1, - 'map' => [2 => 3] -] -// First applies down, then map -// So h1 → h2, then h2 → h3 via map -``` +Levels missing from the map keep their original value. ## See Also -- [ContentSlicer Extension](ContentSlicer.md) - Create sections based on heading - levels -- [league/commonmark documentation](https://commonmark.thephpleague.com/) +- [ContentSlicer](ContentSlicer.md): wrap heading sections in `
` tags +- [Include](Include.md): compose documents from Markdown fragments --- -> **This package is part of -the [alto/commonmark](https://github.com/PhpAlto/commonmark) monorepo.** -> This repository is a read-only split — to file issues, open pull requests, or -> contribute, please use the main repository: * -*https://github.com/PhpAlto/commonmark** +> **This package is part of the [alto/commonmark](https://github.com/PhpAlto/commonmark) monorepo.** +> This repository is a read-only split. To file issues, open pull requests, or contribute, use the main repository: **https://github.com/PhpAlto/commonmark** diff --git a/docs/Import.md b/docs/Import.md index d76abeb..f211f2f 100644 --- a/docs/Import.md +++ b/docs/Import.md @@ -69,7 +69,7 @@ new ImportExtension(string $basePath = '.', int $maxDepth = 10)

Welcome to the guide.

``` -The file content is embedded verbatim — it is **not** re-parsed as Markdown. +The file content is embedded verbatim; it is **not** re-parsed as Markdown. ### Code import with options @@ -108,13 +108,11 @@ Errors render as an inline `
` so the rest of the page remains intact: ## See Also -- [Include](Include.md) — re-parses the included file as Markdown -- [Source](Source.md) — displays source code with syntax highlighting +- [Include](Include.md): re-parses the included file as Markdown +- [Source](Source.md): displays source code with syntax highlighting --- > **This package is part of the [alto/commonmark](https://github.com/PhpAlto/commonmark) monorepo.** -> This repository is a read-only split — to file issues, open pull requests, or -> contribute, please use the main repository: * -*https://github.com/PhpAlto/commonmark** +> This repository is a read-only split. To file issues, open pull requests, or contribute, use the main repository: **https://github.com/PhpAlto/commonmark** diff --git a/docs/Include.md b/docs/Include.md index 51a7ce2..c141093 100644 --- a/docs/Include.md +++ b/docs/Include.md @@ -95,7 +95,7 @@ Only the first five lines of `changelog.md` are included and parsed. | Feature | `@import` | `@include` | |----------------------------|---------------|------------| -| Re-parses as Markdown | No — raw text | **Yes** | +| Re-parses as Markdown | No, raw text | **Yes** | | `lang` option (code block) | Yes | No | | `indent` option | Yes | No | | `allowedExtensions` filter | No | Yes | @@ -128,13 +128,11 @@ All paths are resolved with `realpath()` and validated against `basePath`: ## See Also -- [Import](Import.md) — embeds raw file content (not re-parsed) -- [Source](Source.md) — displays source code with syntax highlighting +- [Import](Import.md): embeds raw file content (not re-parsed) +- [Source](Source.md): displays source code with syntax highlighting --- > **This package is part of the [alto/commonmark](https://github.com/PhpAlto/commonmark) monorepo.** -> This repository is a read-only split — to file issues, open pull requests, or -> contribute, please use the main repository: * -*https://github.com/PhpAlto/commonmark** +> This repository is a read-only split. To file issues, open pull requests, or contribute, use the main repository: **https://github.com/PhpAlto/commonmark** diff --git a/docs/LinkRewriter.md b/docs/LinkRewriter.md index a2a4f40..d007cc3 100644 --- a/docs/LinkRewriter.md +++ b/docs/LinkRewriter.md @@ -1,7 +1,7 @@ # LinkRewriter Extension -Rewrites URLs in links and images through various strategies: base URI -prepending, simple mapping, regex patterns, or custom callbacks. +Rewrites the URLs of links and images after parsing, using any combination of +four strategies: a base URI, a lookup map, a regex pattern, or a callback. ## Basic Usage @@ -11,123 +11,47 @@ use League\CommonMark\Environment\Environment; use League\CommonMark\MarkdownConverter; $environment = new Environment(); - -$config = [ - 'link_rewriter' => [ - 'base_uri' => 'https://docs.example.com' - ] -]; - -$environment->addExtension(new LinkRewriterExtension($config['link_rewriter'])); +$environment->addExtension(new LinkRewriterExtension([ + 'base_uri' => 'https://docs.example.com', +])); $converter = new MarkdownConverter($environment); -$markdown = "[Guide](/guide)"; -echo $converter->convert($markdown); // Guide -``` - -## Features - -- **Base URI**: Prepend a base URL to relative links -- **URL Mapping**: Simple find-and-replace URL transformations -- **Regex Patterns**: Complex URL rewriting with regex and capture groups -- **Callbacks**: Custom PHP functions for dynamic rewriting -- **Composable**: Chain multiple rewriters in sequence -- **Link & Image Support**: Works on both `` href and `` src attributes - -## Configuration Options - -### 1. Base URI - -Prepend a base URL to relative links: - -```php -new LinkRewriterExtension([ - 'base_uri' => 'https://docs.example.com' -]) -``` - -Input: `[Link](/api/users)` -Output: `Link` - -Absolute URLs are not modified: -Input: `[External](https://example.org)` -Output: `External` - -### 2. URL Mapping - -Simple find-and-replace mapping: - -```php -new LinkRewriterExtension([ - 'map' => [ - '/old-path' => '/new-path', - 'example.com' => 'example.org', - ] -]) -``` - -### 3. Regex Patterns - -Use regex patterns with replacements: - -```php -new LinkRewriterExtension([ - 'pattern' => [ - 'pattern' => '/^\/docs\/(.+)$/', - 'replacement' => 'https://docs.example.com/$1' - ] -]) +echo $converter->convert('[Guide](/guide)'); +// Guide ``` -Input: `/docs/api/users` -Output: `https://docs.example.com/api/users` - -### 4. Custom Callback +## Configuration -Use a function for complex logic: - -```php -new LinkRewriterExtension([ - 'callback' => function(string $url, \League\CommonMark\Node\Node $node): string { - // Custom logic - if (str_starts_with($url, '/')) { - return 'https://mysite.com' . $url; - } - return $url; - } -]) -``` +Any subset of these keys may be set. When several are present they are applied +**in sequence** in the order below, each receiving the previous result. -### 5. Combined Configuration +| Key | Type | Description | +|------------|-----------------------------------|--------------------------------------------------------------------| +| `base_uri` | `string` | Prepend a base URL to relative links. Absolute URLs are untouched. | +| `map` | `array` | Literal find-and-replace on the URL. | +| `pattern` | `array{pattern: string, replacement: string}` | `preg_replace`-style rewrite with capture groups. | +| `callback` | `callable(string $url, Node $node): string` | Return the new URL. Receives the link/image node too. | -Rewriters are applied in order: +Invalid config throws a `TypeError` at construction (for example a non-string +`base_uri`, or a `pattern` without both `pattern` and `replacement`). ```php new LinkRewriterExtension([ 'base_uri' => 'https://docs.example.com', - 'map' => [ - '/legacy' => '/current' - ], - 'pattern' => [ - 'pattern' => '/^https:\/\/docs\.old\.com\/(.+)$/', - 'replacement' => 'https://docs.new.com/$1' - ], - 'callback' => function(string $url): string { - // Final transformations - return $url; - } -]) + 'map' => ['/legacy' => '/current'], + 'pattern' => ['pattern' => '#^/docs/(.+)$#', 'replacement' => 'https://docs.new.com/$1'], + 'callback' => fn (string $url): string => rtrim($url, '/'), +]); ``` -Rewriters are applied sequentially, so the output of one becomes the input to -the next. +Both `` and `` are rewritten. ## Output Examples ### Base URI -Input markdown: +Config `['base_uri' => 'https://mysite.com']`: ```markdown [Home](/) @@ -135,306 +59,54 @@ Input markdown: [External](https://example.com) ``` -Config: `['base_uri' => 'https://mysite.com']` - -Output: - ```html Home About External ``` -### URL Mapping +### Map -Input markdown: +Config `['map' => ['/old-api' => '/api/v2']]`: ```markdown [Old link](/old-api) -[File](readme.txt) -``` - -Config: - -```php -'map' => [ - '/old-api' => '/api/v2', - 'readme.txt' => 'README.md' -] ``` -Output: - ```html Old link -File ``` -### Regex Pattern +### Regex pattern -Input markdown: +Config `['pattern' => ['pattern' => '#^/docs/(v\d+)/(.+)$#', 'replacement' => 'https://versioned-docs.com/$1/$2']]`: ```markdown [Version 1](/docs/v1/intro) -[Version 2](/docs/v2/intro) -``` - -Config: - -```php -'pattern' => [ - 'pattern' => '/^\/docs\/(v\d+)\/(.+)$/', - 'replacement' => 'https://versioned-docs.com/$1/$2' -] ``` -Output: - ```html Version 1 -Version 2 -``` - -## Advanced Usage - -### Content Distribution - -Rewrite links when distributing content across domains: - -```php -// Main site -$mainEnv = new Environment(); -$mainEnv->addExtension(new LinkRewriterExtension([ - 'base_uri' => 'https://main.com' -])); - -// CDN mirror -$cdnEnv = new Environment(); -$cdnEnv->addExtension(new LinkRewriterExtension([ - 'base_uri' => 'https://cdn.main.com' -])); - -// Each uses the same markdown but different rewriting -$markdown = file_get_contents('article.md'); -$mainHtml = new MarkdownConverter($mainEnv)->convert($markdown); -$cdnHtml = new MarkdownConverter($cdnEnv)->convert($markdown); -``` - -### Version-Aware Links - -Create version-specific documentation links: - -```php -function createVersionConverter(string $version): MarkdownConverter { - $env = new Environment(); - $env->addExtension(new LinkRewriterExtension([ - 'pattern' => [ - 'pattern' => '/^(\/docs\/)/', - 'replacement' => "/docs/$version/" - ] - ])); - return new MarkdownConverter($env); -} - -$v1Converter = createVersionConverter('1.0'); -$v2Converter = createVersionConverter('2.0'); ``` -### Link Validation & Logging - -Use callbacks for tracking: +### Images -```php -$links = []; - -new LinkRewriterExtension([ - 'callback' => function(string $url): string { - global $links; - $links[] = $url; - return $url; - } -]) - -// Later: analyze collected links -foreach ($links as $url) { - // Validate, log, etc. -} -``` - -### Anchor Resolution - -Resolve local anchors to full URLs: - -```php -new LinkRewriterExtension([ - 'callback' => function(string $url): string { - if (str_starts_with($url, '#')) { - // Local anchor - return 'page.html' . $url; - } - return $url; - } -]) -``` - -Input: `[See below](#section)` -Output: `See below` - -### Proxy URLs - -Route all external links through a proxy: - -```php -new LinkRewriterExtension([ - 'pattern' => [ - 'pattern' => '/^https?:\/\/(?!mysite\.com)(.+)$/', - 'replacement' => 'https://mysite.com/proxy?url=$0' - ] -]) -``` - -### Multi-Domain Migration - -Migrate links from old to new domain: - -```php -new LinkRewriterExtension([ - 'map' => [ - 'old-domain.com' => 'new-domain.com', - 'old-cdn.com' => 'new-cdn.com', - ] -]) -``` - -## Common Patterns - -### Static Site with Assets - -```php -$env = new Environment(); -$env->addExtension(new LinkRewriterExtension([ - 'base_uri' => '/generated', // Relative base - 'map' => [ - '/images/' => '/assets/img/', - '/styles/' => '/assets/css/', - ] -])); -``` - -### API Documentation Link Prefix - -```php -new LinkRewriterExtension([ - 'callback' => function(string $url): string { - if (preg_match('/^\/api/', $url)) { - return 'https://api.example.com' . $url; - } - return $url; - } -]) -``` - -### Relative to Absolute URLs - -```php -new LinkRewriterExtension([ - 'callback' => function(string $url): string { - if (!filter_var($url, FILTER_VALIDATE_URL)) { - // Relative URL - return 'https://mysite.com' . (str_starts_with($url, '/') ? '' : '/') . $url; - } - return $url; - } -]) -``` - -## Working with Images - -The extension also rewrites image URLs: +Config `['base_uri' => 'https://mysite.com']`: ```markdown ![Alt text](/images/photo.jpg) ``` -With config `['base_uri' => 'https://mysite.com']`: - ```html -Alt text -``` - -## Implementation Details - -- **Pattern**: Event-based processor -- **Event**: `DocumentParsedEvent` -- **Nodes Modified**: Link and Image nodes -- **Rewriter Interface**: Composable functions taking URL and Node - -The extension walks the AST after parsing, finds all Link and Image nodes, and -applies configured rewriters in sequence. - -## Troubleshooting - -### Rewriter not applied - -Ensure the extension is registered before creating the converter: - -```php -$env = new Environment(); -$env->addExtension( - new LinkRewriterExtension(['base_uri' => 'https://example.com']) -); -$converter = new MarkdownConverter($env); // Must be after -``` - -### Regex not matching - -Test your pattern separately: - -```php -$pattern = '/^\/docs\/(.+)$/'; -$url = '/docs/api/users'; - -if (preg_match($pattern, $url, $matches)) { - // Pattern matches -} -``` - -### Multiple rewriters conflict - -Remember rewriters apply in order. Later ones can override earlier ones: - -```php -[ - 'base_uri' => 'https://example.com', // Applied first - 'map' => ['https://example.com/old' => 'https://new.com'], // Overrides base_uri for this URL - 'callback' => function($url) { ... } // Applied last -] -``` - -### Absolute URLs getting modified - -Check your conditions: - -```php -'callback' => function(string $url): string { - // Skip already absolute URLs - if (filter_var($url, FILTER_VALIDATE_URL)) { - return $url; - } - // Only rewrite relative URLs - return 'https://mysite.com' . $url; -} +Alt text ``` ## See Also -- [HeadingLevel Extension](HeadingLevel.md) - Adjust heading levels -- [league/commonmark documentation](https://commonmark.thephpleague.com/) +- [Include](Include.md): compose documents from Markdown fragments +- [ContentSlicer](ContentSlicer.md): wrap heading sections in `
` tags --- -> **This package is part of -the [alto/commonmark](https://github.com/PhpAlto/commonmark) monorepo.** -> This repository is a read-only split — to file issues, open pull requests, or -> contribute, please use the main repository: * -*https://github.com/PhpAlto/commonmark** +> **This package is part of the [alto/commonmark](https://github.com/PhpAlto/commonmark) monorepo.** +> This repository is a read-only split. To file issues, open pull requests, or contribute, use the main repository: **https://github.com/PhpAlto/commonmark** diff --git a/docs/README.md b/docs/README.md index 3ed8b65..39b6d7d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ -# Alto CommonMark — Documentation +# Alto CommonMark Documentation Nine `league/commonmark` extensions, each solving a specific documentation problem cleanly. diff --git a/docs/Source.md b/docs/Source.md index 5ec6599..8879cc6 100644 --- a/docs/Source.md +++ b/docs/Source.md @@ -144,13 +144,11 @@ All paths are resolved with `realpath()` and validated against `basePath`: ## See Also -- [Import](Import.md) — embeds raw file content (plain text or code block) -- [Include](Include.md) — includes and re-parses a Markdown file +- [Import](Import.md): embeds raw file content (plain text or code block) +- [Include](Include.md): includes and re-parses a Markdown file --- > **This package is part of the [alto/commonmark](https://github.com/PhpAlto/commonmark) monorepo.** -> This repository is a read-only split — to file issues, open pull requests, or -> contribute, please use the main repository: * -*https://github.com/PhpAlto/commonmark** +> This repository is a read-only split. To file issues, open pull requests, or contribute, use the main repository: **https://github.com/PhpAlto/commonmark** diff --git a/docs/TableOfContents.md b/docs/TableOfContents.md index d6b9013..1c26318 100644 --- a/docs/TableOfContents.md +++ b/docs/TableOfContents.md @@ -66,8 +66,8 @@ in the same document each generate a separate TOC. | Option | Type | Example | Description | |-----------|------|-------------------|----------------------------------------| -| `min` | int | `{min: 2}` | Minimum heading level to include (1–6) | -| `max` | int | `{max: 3}` | Maximum heading level to include (1–6) | +| `min` | int | `{min: 2}` | Minimum heading level to include (1-6) | +| `max` | int | `{max: 3}` | Maximum heading level to include (1-6) | | `ordered` | bool | `{ordered: true}` | Use `
    ` instead of `
      ` | ## Constructor @@ -136,13 +136,11 @@ Heading IDs are generated by slugifying the heading text: ## See Also -- [ContentSlicer](ContentSlicer.md) — wraps heading sections in `
      ` tags -- [HeadingLevel](HeadingLevel.md) — adjust heading levels before TOC generation +- [ContentSlicer](ContentSlicer.md): wraps heading sections in `
      ` tags +- [HeadingLevel](HeadingLevel.md): adjust heading levels before TOC generation --- > **This package is part of the [alto/commonmark](https://github.com/PhpAlto/commonmark) monorepo.** -> This repository is a read-only split — to file issues, open pull requests, or -> contribute, please use the main repository: * -*https://github.com/PhpAlto/commonmark** +> This repository is a read-only split. To file issues, open pull requests, or contribute, use the main repository: **https://github.com/PhpAlto/commonmark** diff --git a/docs/Tabs.md b/docs/Tabs.md index 6d9f8ab..98d9837 100644 --- a/docs/Tabs.md +++ b/docs/Tabs.md @@ -145,13 +145,11 @@ The extension generates full WAI-ARIA markup: ## See Also -- [ContentSlicer](ContentSlicer.md) — wrap heading sections in semantic +- [ContentSlicer](ContentSlicer.md): wrap heading sections in semantic `
      ` tags --- > **This package is part of the [alto/commonmark](https://github.com/PhpAlto/commonmark) monorepo.** -> This repository is a read-only split — to file issues, open pull requests, or -> contribute, please use the main repository: * -*https://github.com/PhpAlto/commonmark** +> This repository is a read-only split. To file issues, open pull requests, or contribute, use the main repository: **https://github.com/PhpAlto/commonmark**