Skip to content

Commit 9e613da

Browse files
committed
Improve proof of concept
1 parent d4d8762 commit 9e613da

File tree

4 files changed

+176
-51
lines changed

4 files changed

+176
-51
lines changed

docs/digging-deeper/advanced-markdown.md

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -163,51 +163,40 @@ The filepaths are hidden on mobile devices using CSS to prevent them from overla
163163

164164
## Dynamic Markdown Links
165165

166-
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.
166+
## Overview
167+
HydePHP automatically resolves links in your Markdown files using source file paths, converting them to appropriate routes in the built site.
167168

168-
### Usage
169-
170-
You can use the following syntax options in your Markdown files:
169+
## Usage
170+
In your Markdown files, use source file paths for links:
171171

172172
```markdown
173-
<!-- Resolving a page route -->
174173
[Home](/_pages/index.blade.php)
175-
176-
<!-- Resolving a media asset -->
177174
![Logo](/_media/logo.svg)
178175
```
179176

180-
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.
181-
182-
### How It Works
183-
184-
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:
177+
## How It Works
178+
During the build process, HydePHP converts source paths to their corresponding routes:
185179

186-
- `/_pages/index.blade.php` will be converted to `index.html`
187-
- `/_pages/blog/post.blade.php` will be converted to `blog/post.html`
188-
- `/_media/logo.svg` will be converted to `media/logo.svg`
189-
190-
This conversion happens during the build process, ensuring that your links are always up-to-date with your current site structure.
191-
192-
### Benefits
193-
194-
1. **IDE Support**: By using actual file paths, you get better IDE support, including autocompletion and error checking.
195-
2. **Syntax Highlighting**: Your Markdown editor can provide proper syntax highlighting for these links, as they use standard Markdown syntax.
196-
3. **Easy Navigation**: You can easily navigate to the source files by clicking on the links in your IDE.
197-
198-
### Limitations
180+
- `/_pages/index.blade.php` becomes `index.html`
181+
- `/_pages/blog/post.blade.php` becomes `blog/post.html`
182+
- `/_media/logo.svg` becomes `media/logo.svg`
199183

200-
- This syntax doesn't support linking to dynamic routes. For such cases, you may need to use a different approach.
201-
- If you move or rename source files, make sure to update any Markdown links referencing them to avoid broken links.
202-
- 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.
184+
## Benefits
185+
- Enhanced IDE support (autocompletion, error checking)
186+
- Proper syntax highlighting in Markdown editors
187+
- Easy navigation to source files
203188

204-
### Best Practices
189+
## Limitations
190+
- Doesn't support dynamic routes
191+
- Content becomes coupled to project structure
192+
- Requires manual updates if files are moved or renamed
205193

206-
1. Always use the leading slash (`/`) in your links to ensure consistency and avoid potential issues with relative paths.
207-
2. Keep your file structure organized and consistent to make it easier to manage links.
208-
3. Regularly check for broken links in your content, especially after moving or renaming files.
194+
## Best Practices
195+
1. Use leading slashes in links for consistency
196+
2. Maintain an organized file structure
197+
3. Regularly check for broken links, especially after file operations
209198

210-
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.
199+
By leveraging this feature, you can create more maintainable content while enjoying improved developer tooling support.
211200

212201
## Configuration
213202

packages/framework/src/Markdown/Processing/DynamicMarkdownLinkProcessor.php

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,32 @@
1212
class DynamicMarkdownLinkProcessor implements MarkdownPostProcessorContract
1313
{
1414
public static function postprocess(string $html): string
15+
{
16+
foreach (static::routeMap() as $sourcePath => $route) {
17+
$patterns = [
18+
sprintf('<a href="%s">', $sourcePath),
19+
sprintf('<a href="/%s">', $sourcePath),
20+
];
21+
22+
$html = str_replace($patterns, sprintf('<a href="%s">', $route->getLink()), $html);
23+
}
24+
25+
foreach (static::assetMap() as $path => $mediaFile) {
26+
$patterns = [
27+
sprintf('<img src="%s"', $path),
28+
sprintf('<img src="/%s"', $path),
29+
];
30+
31+
$html = str_replace($patterns, sprintf('<img src="%s"', static::assetPath($mediaFile)), $html);
32+
}
33+
34+
return $html;
35+
}
36+
37+
/**
38+
* @return array<string, \Hyde\Support\Models\Route>
39+
*/
40+
protected static function routeMap(): array
1541
{
1642
// Todo cache in static property bound to kernel version (but evaluation is tied to rendering page)
1743

@@ -22,32 +48,26 @@ public static function postprocess(string $html): string
2248
$map[$route->getSourcePath()] = $route;
2349
}
2450

25-
foreach ($map as $sourcePath => $route) {
26-
$patterns = [
27-
'<a href="'.$sourcePath.'">',
28-
'<a href="/'.$sourcePath.'">',
29-
];
30-
31-
$html = str_replace($patterns, '<a href="'.$route->getLink().'">', $html);
32-
}
51+
return $map;
52+
}
3353

54+
/**
55+
* @return array<string, \Hyde\Support\Filesystem\MediaFile>
56+
*/
57+
protected static function assetMap(): array
58+
{
3459
// maybe we just need to cache this, as the kernel is already a singleton, but this is not
3560
$assetMap = [];
3661

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

41-
foreach ($assetMap as $path => $mediaFile) {
42-
$patterns = [
43-
'<img src="'.$path.'"',
44-
'<img src="/'.$path.'"',
45-
];
46-
47-
$localPath = Str::after($mediaFile->getPath(), '_media/');
48-
$html = str_replace($patterns, '<img src="'.Hyde::asset($localPath).'"', $html);
49-
}
66+
return $assetMap;
67+
}
5068

51-
return $html;
69+
protected static function assetPath(MediaFile $mediaFile): string
70+
{
71+
return Hyde::asset(Str::after($mediaFile->getPath(), '_media/'));
5272
}
5373
}

packages/framework/tests/Feature/DynamicMarkdownLinksFeatureTest.php

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88

99
use Hyde\Hyde;
1010
use Hyde\Testing\TestCase;
11+
use Hyde\Support\Includes;
1112
use Hyde\Pages\MarkdownPage;
1213
use Hyde\Support\Models\Route;
1314
use Hyde\Foundation\Facades\Routes;
1415

1516
/**
1617
* @covers \Hyde\Markdown\Processing\DynamicMarkdownLinkProcessor
1718
* @covers \Hyde\Framework\Concerns\Internal\SetsUpMarkdownConverter
19+
*
20+
* @see \Hyde\Framework\Testing\Feature\Services\Markdown\DynamicMarkdownLinkProcessorTest
1821
*/
1922
class DynamicMarkdownLinksFeatureTest extends TestCase
2023
{
@@ -142,4 +145,108 @@ public function testLinksWithLeadingSlash()
142145

143146
$this->assertSame($expected, Hyde::markdown($input)->toHtml());
144147
}
148+
149+
public function testCanRenderPageWithDynamicMarkdownLinks()
150+
{
151+
$this->file('_pages/test.md', <<<'MARKDOWN'
152+
[Home](/_pages/index.blade.php)
153+
![Logo](/_media/logo.png)
154+
155+
[non-existent](/_pages/non-existent.md)
156+
![non-existent](/_media/non-existent.png)
157+
MARKDOWN);
158+
159+
$page = MarkdownPage::get('test');
160+
Hyde::shareViewData($page);
161+
$html = $page->compile();
162+
163+
$expected = [
164+
'<a href="index.html">Home</a>',
165+
'<img src="media/logo.png" alt="Logo" />',
166+
'<a href="/_pages/non-existent.md">non-existent</a>',
167+
'<img src="/_media/non-existent.png" alt="non-existent" />',
168+
];
169+
170+
foreach ($expected as $expectation) {
171+
$this->assertStringContainsString($expectation, $html);
172+
}
173+
}
174+
175+
public function testCanRenderNestedPageWithDynamicMarkdownLinks()
176+
{
177+
$this->file('_pages/nested/test.md', <<<'MARKDOWN'
178+
[Home](/_pages/index.blade.php)
179+
![Logo](/_media/logo.png)
180+
181+
[non-existent](/_pages/non-existent.md)
182+
![non-existent](/_media/non-existent.png)
183+
MARKDOWN);
184+
185+
$page = MarkdownPage::get('nested/test');
186+
Hyde::shareViewData($page);
187+
$html = $page->compile();
188+
189+
$expected = [
190+
'<a href="../index.html">Home</a>',
191+
'<img src="../media/logo.png" alt="Logo" />',
192+
'<a href="/_pages/non-existent.md">non-existent</a>',
193+
'<img src="/_media/non-existent.png" alt="non-existent" />',
194+
];
195+
196+
foreach ($expected as $expectation) {
197+
$this->assertStringContainsString($expectation, $html);
198+
}
199+
}
200+
201+
public function testCanRenderIncludeWithDynamicMarkdownLinks()
202+
{
203+
// if there is no current path data, we assume the file is included from the root
204+
205+
$this->file('resources/includes/test.md', <<<'MARKDOWN'
206+
[Home](/_pages/index.blade.php)
207+
![Logo](/_media/logo.png)
208+
209+
[non-existent](/_pages/non-existent.md)
210+
![non-existent](/_media/non-existent.png)
211+
MARKDOWN);
212+
213+
$html = Includes::markdown('test')->toHtml();
214+
215+
$expected = [
216+
'<a href="index.html">Home</a>',
217+
'<img src="media/logo.png" alt="Logo" />',
218+
'<a href="/_pages/non-existent.md">non-existent</a>',
219+
'<img src="/_media/non-existent.png" alt="non-existent" />',
220+
];
221+
222+
foreach ($expected as $expectation) {
223+
$this->assertStringContainsString($expectation, $html);
224+
}
225+
}
226+
227+
public function testCanRenderIncludeFromNestedPageWithDynamicMarkdownLinks()
228+
{
229+
$this->mockCurrentPage('nested/test');
230+
231+
$this->file('resources/includes/test.md', <<<'MARKDOWN'
232+
[Home](/_pages/index.blade.php)
233+
![Logo](/_media/logo.png)
234+
235+
[non-existent](/_pages/non-existent.md)
236+
![non-existent](/_media/non-existent.png)
237+
MARKDOWN);
238+
239+
$html = Includes::markdown('test')->toHtml();
240+
241+
$expected = [
242+
'<a href="../index.html">Home</a>',
243+
'<img src="../media/logo.png" alt="Logo" />',
244+
'<a href="/_pages/non-existent.md">non-existent</a>',
245+
'<img src="/_media/non-existent.png" alt="non-existent" />',
246+
];
247+
248+
foreach ($expected as $expectation) {
249+
$this->assertStringContainsString($expectation, $html);
250+
}
251+
}
145252
}

packages/framework/tests/Unit/IncludesFacadeUnitTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@
99
use Hyde\Hyde;
1010
use Hyde\Support\Includes;
1111
use Hyde\Testing\UnitTestCase;
12+
use Hyde\Support\Facades\Render;
1213
use Illuminate\Support\HtmlString;
14+
use Hyde\Support\Models\RenderData;
1315
use Illuminate\Support\Facades\Blade;
1416
use Illuminate\Filesystem\Filesystem;
17+
use Hyde\Testing\MocksKernelFeatures;
1518

1619
/**
1720
* @covers \Hyde\Support\Includes
@@ -20,6 +23,8 @@
2023
*/
2124
class IncludesFacadeUnitTest extends UnitTestCase
2225
{
26+
use MocksKernelFeatures;
27+
2328
protected static bool $needsKernel = true;
2429
protected static bool $needsConfig = true;
2530

@@ -28,6 +33,10 @@ protected function setUp(): void
2833
parent::setUp();
2934

3035
Blade::swap(Mockery::mock());
36+
37+
$this->setupTestKernel();
38+
$this->kernel->setRoutes(collect());
39+
Render::swap(new RenderData());
3140
}
3241

3342
protected function tearDown(): void

0 commit comments

Comments
 (0)