Skip to content

Commit

Permalink
New proof of concept
Browse files Browse the repository at this point in the history
  • Loading branch information
caendesilva committed Jul 24, 2024
1 parent 9589bc1 commit d4d8762
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 161 deletions.
38 changes: 26 additions & 12 deletions docs/digging-deeper/advanced-markdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,37 +163,51 @@ The filepaths are hidden on mobile devices using CSS to prevent them from overla

## Dynamic Markdown Links

HydePHP provides a powerful feature for automatically resolving dynamic links within your pages and posts using a special Hyde Markdown link syntax, designed to match the syntax of the `Hyde` facade.
HydePHP provides a powerful feature for automatically resolving dynamic links within your pages and posts using source file paths, which are then converted to the appropriate routes in the built site.

### Usage

You can use the following syntax options in your Markdown files:

```markdown
<!-- Resolving a page route -->
[Home](hyde::route('home'))
[Home](/_pages/index.blade.php)

<!-- Resolving a media asset -->
![Logo](hyde::asset('logo.png'))
![Logo](/_media/logo.svg)
```

By using these dynamic Markdown links, you can create more maintainable and flexible content, allowing your site structure to evolve without breaking internal links. The feature is always enabled in the Markdown converter.

### How It Works

### Breakdown
When you use a source file path in your Markdown links, HydePHP will automatically convert it to the appropriate route or asset path in the built site. For example:

The example above is equivalent to the following Blade syntax:
- `/_pages/index.blade.php` will be converted to `index.html`
- `/_pages/blog/post.blade.php` will be converted to `blog/post.html`
- `/_media/logo.svg` will be converted to `media/logo.svg`

```blade
<!-- Resolving a page route -->
{{ Hyde::route('route.name') ?? throw new RouteNotFoundException() }}
This conversion happens during the build process, ensuring that your links are always up-to-date with your current site structure.

<!-- Resolving a media asset -->
{{ Hyde::asset('path') }}
```
### Benefits

1. **IDE Support**: By using actual file paths, you get better IDE support, including autocompletion and error checking.
2. **Syntax Highlighting**: Your Markdown editor can provide proper syntax highlighting for these links, as they use standard Markdown syntax.
3. **Easy Navigation**: You can easily navigate to the source files by clicking on the links in your IDE.

### Limitations

- This syntax doesn't support linking to dynamic routes. For such cases, you may need to use a different approach.
- If you move or rename source files, make sure to update any Markdown links referencing them to avoid broken links.
- While this approach provides better IDE support and syntax highlighting, it does couple your content to your project structure. Consider this trade-off when deciding to use this feature.

### Best Practices

As you can see, we throw an exception if the route doesn't exist, to provide immediate feedback during development.
1. Always use the leading slash (`/`) in your links to ensure consistency and avoid potential issues with relative paths.
2. Keep your file structure organized and consistent to make it easier to manage links.
3. Regularly check for broken links in your content, especially after moving or renaming files.

By following these guidelines and understanding the limitations, you can effectively use dynamic Markdown links to create more maintainable and flexible content in your HydePHP projects.

## Configuration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,49 @@
namespace Hyde\Markdown\Processing;

use Hyde\Hyde;
use Hyde\Framework\Exceptions\RouteNotFoundException;
use Illuminate\Support\Str;
use Hyde\Support\Filesystem\MediaFile;
use Hyde\Markdown\Contracts\MarkdownPostProcessorContract;

class DynamicMarkdownLinkProcessor implements MarkdownPostProcessorContract
{
public static function postprocess(string $html): string
{
foreach (static::patterns() as $pattern => $replacement) {
$html = preg_replace_callback($pattern, $replacement, $html);
// Todo cache in static property bound to kernel version (but evaluation is tied to rendering page)

$map = [];

/** @var \Hyde\Support\Models\Route $route */
foreach (Hyde::routes() as $route) {
$map[$route->getSourcePath()] = $route;
}

return $html;
}
foreach ($map as $sourcePath => $route) {
$patterns = [
'<a href="'.$sourcePath.'">',
'<a href="/'.$sourcePath.'">',
];

/** @return array<string, callable(array<int, string>): string> */
protected static function patterns(): array
{
return [
'/<a href="hyde::route\(([\'"]?)([^\'"]+)\1\)"/' => function (array $matches): string {
$route = Hyde::route($matches[2]);
if ($route === null) {
// While the other patterns work regardless of if input is valid,
// this method returns null, which silently fails to an empty string.
// So we instead throw an exception to alert the developer of the issue.
throw new RouteNotFoundException($matches[2]);
}

return '<a href="'.$route.'"';
},
'/<img src="hyde::asset\(([\'"]?)([^\'"]+)\1\)"/' => function (array $matches): string {
return '<img src="'.Hyde::asset($matches[2]).'"';
},
];
$html = str_replace($patterns, '<a href="'.$route->getLink().'">', $html);
}

// maybe we just need to cache this, as the kernel is already a singleton, but this is not
$assetMap = [];

foreach (MediaFile::all() as $mediaFile) {
$assetMap[$mediaFile->getPath()] = $mediaFile;
}

foreach ($assetMap as $path => $mediaFile) {
$patterns = [
'<img src="'.$path.'"',
'<img src="/'.$path.'"',
];

$localPath = Str::after($mediaFile->getPath(), '_media/');
$html = str_replace($patterns, '<img src="'.Hyde::asset($localPath).'"', $html);
}

return $html;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,58 +8,41 @@

use Hyde\Hyde;
use Hyde\Testing\TestCase;
use Hyde\Pages\InMemoryPage;
use Hyde\Pages\MarkdownPage;
use Hyde\Support\Models\Route;
use Hyde\Foundation\Facades\Routes;

/**
* @covers \Hyde\Markdown\Processing\DynamicMarkdownLinkProcessor
* @covers \Hyde\Framework\Concerns\Internal\SetsUpMarkdownConverter
*
* @see \Hyde\Framework\Testing\Feature\Services\Markdown\DynamicMarkdownLinkProcessorTest
*/
class DynamicMarkdownLinksFeatureTest extends TestCase
{
protected function setUp(): void
public static function setUpBeforeClass(): void
{
parent::setUp();
parent::setUpBeforeClass();

Routes::addRoute(new Route(new InMemoryPage('home')));
Routes::addRoute(new Route(new InMemoryPage('blog/post')));
touch('_media/logo.png');
touch('_media/image.jpg');
}

public function testBasicDynamicMarkdownLinks()
public static function tearDownAfterClass(): void
{
$input = <<<'MARKDOWN'
[Home](hyde::route('home'))
![Logo](hyde::asset('logo.png'))
[Home](hyde::route(home))
![Logo](hyde::asset(logo.png))
MARKDOWN;

$expected = <<<'HTML'
<p><a href="home.html">Home</a>
<img src="media/logo.png" alt="Logo" /></p>
<p><a href="home.html">Home</a>
<img src="media/logo.png" alt="Logo" /></p>

HTML;
unlink('_media/logo.png');
unlink('_media/image.jpg');

$this->assertSame($expected, Hyde::markdown($input)->toHtml());
parent::tearDownAfterClass();
}

public function testDynamicMarkdownLinksWithDoubleQuotes()
public function testBasicDynamicMarkdownLinks()
{
$this->markTestSkipped('https://github.com/hydephp/develop/pull/1590#discussion_r1690082732');

$input = <<<'MARKDOWN'
[Home](hyde::route("home"))
![Logo](hyde::asset("logo.png"))
[Home](/_pages/index.blade.php)
![Logo](/_media/logo.png)
MARKDOWN;

$expected = <<<'HTML'
<p><a href="home.html">Home</a>
<p><a href="index.html">Home</a>
<img src="media/logo.png" alt="Logo" /></p>

HTML;
Expand All @@ -70,13 +53,13 @@ public function testDynamicMarkdownLinksWithDoubleQuotes()
public function testDynamicMarkdownLinksInParagraphs()
{
$input = <<<'MARKDOWN'
This is a paragraph with a [link to home](hyde::route('home')).
This is a paragraph with a [link to home](/_pages/index.blade.php).
Another paragraph with an ![image](hyde::asset('image.jpg')).
Another paragraph with an ![image](/_media/image.jpg).
MARKDOWN;

$expected = <<<'HTML'
<p>This is a paragraph with a <a href="home.html">link to home</a>.</p>
<p>This is a paragraph with a <a href="index.html">link to home</a>.</p>
<p>Another paragraph with an <img src="media/image.jpg" alt="image" />.</p>

HTML;
Expand All @@ -87,14 +70,14 @@ public function testDynamicMarkdownLinksInParagraphs()
public function testDynamicMarkdownLinksInLists()
{
$input = <<<'MARKDOWN'
- [Home](hyde::route('home'))
- ![Logo](hyde::asset('logo.png'))
- [Home](/_pages/index.blade.php)
- ![Logo](/_media/logo.png)
MARKDOWN;

$expected = <<<'HTML'
<ul>
<li>
<a href="home.html">Home</a>
<a href="index.html">Home</a>
</li>
<li>
<img src="media/logo.png" alt="Logo" />
Expand All @@ -108,12 +91,14 @@ public function testDynamicMarkdownLinksInLists()

public function testDynamicMarkdownLinksWithNestedRoutes()
{
Routes::addRoute(new Route(new MarkdownPage('about/contact')));

$input = <<<'MARKDOWN'
[Blog Post](hyde::route('blog/post'))
[Contact](/_pages/about/contact.md)
MARKDOWN;

$expected = <<<'HTML'
<p><a href="blog/post.html">Blog Post</a></p>
<p><a href="about/contact.html">Contact</a></p>

HTML;

Expand All @@ -123,15 +108,15 @@ public function testDynamicMarkdownLinksWithNestedRoutes()
public function testMixOfDynamicAndRegularMarkdownLinks()
{
$input = <<<'MARKDOWN'
[Home](hyde::route('home'))
[Home](/_pages/index.blade.php)
[External](https://example.com)
[Regular](regular-link.html)
![Logo](hyde::asset('logo.png'))
![Logo](/_media/logo.png)
![External Image](https://example.com/image.jpg)
MARKDOWN;

$expected = <<<'HTML'
<p><a href="home.html">Home</a>
<p><a href="index.html">Home</a>
<a href="https://example.com">External</a>
<a href="regular-link.html">Regular</a>
<img src="media/logo.png" alt="Logo" />
Expand All @@ -141,4 +126,20 @@ public function testMixOfDynamicAndRegularMarkdownLinks()

$this->assertSame($expected, Hyde::markdown($input)->toHtml());
}

public function testLinksWithLeadingSlash()
{
$input = <<<'MARKDOWN'
[Home with slash](/_pages/index.blade.php)
![Logo with slash](/_media/logo.png)
MARKDOWN;

$expected = <<<'HTML'
<p><a href="index.html">Home with slash</a>
<img src="media/logo.png" alt="Logo with slash" /></p>

HTML;

$this->assertSame($expected, Hyde::markdown($input)->toHtml());
}
}
Loading

0 comments on commit d4d8762

Please sign in to comment.