Skip to content

Commit ec86f65

Browse files
committed
Doc enhancements.
1 parent 7fe5b65 commit ec86f65

File tree

3 files changed

+378
-32
lines changed

3 files changed

+378
-32
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ https://sandbox.dereuromark.de/sandbox/djot
103103
- [Converters](docs/converters.md) - Markdown/BBCode to Djot conversion
104104
- [Cookbook](docs/cookbook.md) - Common customizations and recipes
105105
- [Architecture](docs/architecture.md) - Internal design
106+
- [Enhancements](docs/enhancements.md) - Fixes beyond the current spec
106107

107108
## Security
108109

docs/cookbook.md

Lines changed: 125 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -644,70 +644,110 @@ Output:
644644

645645
### Wiki Links
646646

647-
Support `[[Page Name]]` and `[[Page Name|display text]]` wiki-style links:
647+
Support wiki-style links using a `wiki:` URL scheme. This approach uses standard
648+
djot link syntax and avoids ambiguity with nested spans (since `[[x]{.a}]{.b}` is valid djot).
648649

649650
```php
650651
use Djot\DjotConverter;
651-
use Djot\Node\Inline\Link;
652-
use Djot\Node\Inline\Text;
652+
use Djot\Event\RenderEvent;
653653

654654
$converter = new DjotConverter();
655-
$parser = $converter->getParser()->getInlineParser();
656655

657-
// Pattern matches [[target]] or [[target|display text]]
658-
$parser->addInlinePattern('/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/', function ($match, $groups, $p) {
659-
$target = trim($groups[1]);
660-
$display = isset($groups[2]) ? trim($groups[2]) : $target;
656+
// Handle wiki: scheme in links
657+
$converter->on('render.link', function (RenderEvent $event): void {
658+
$link = $event->getNode();
659+
$url = $link->getDestination() ?? '';
661660

662-
// Convert target to URL (customize as needed)
663-
$url = '/wiki/' . rawurlencode(str_replace(' ', '-', $target));
661+
if (str_starts_with($url, 'wiki:')) {
662+
$target = substr($url, 5); // Remove 'wiki:' prefix
664663

665-
$link = new Link($url);
666-
$link->appendChild(new Text($display));
667-
$link->setAttribute('class', 'wikilink');
668-
return $link;
664+
// If empty, use the link text as target
665+
if ($target === '') {
666+
$text = '';
667+
foreach ($link->getChildren() as $child) {
668+
if ($child instanceof \Djot\Node\Inline\Text) {
669+
$text .= $child->getContent();
670+
}
671+
}
672+
$target = $text;
673+
}
674+
675+
// Convert to URL slug
676+
$slug = strtolower(str_replace(' ', '-', $target));
677+
$link->setDestination('/wiki/' . rawurlencode($slug));
678+
$link->setAttribute('class', 'wikilink');
679+
}
669680
});
670681

671-
echo $converter->convert('See [[Home Page]] and [[API Reference|the API docs]].');
682+
echo $converter->convert('See [Home Page](wiki:) and [the API docs](wiki:API Reference).');
672683
```
673684

674685
Output:
675686
```html
676-
<p>See <a href="/wiki/Home-Page" class="wikilink">Home Page</a> and <a href="/wiki/API-Reference" class="wikilink">the API docs</a>.</p>
687+
<p>See <a href="/wiki/home-page" class="wikilink">Home Page</a> and <a href="/wiki/api-reference" class="wikilink">the API docs</a>.</p>
677688
```
678689

690+
The syntax:
691+
- `[Page Name](wiki:)` - link text becomes the target
692+
- `[display text](wiki:Page Name)` - explicit target with custom display text
693+
679694
#### Configurable Base URL
680695

681696
```php
682-
// Make the base URL configurable
683697
$wikiBaseUrl = '/docs/'; // or 'https://wiki.example.com/'
684698

685-
$parser->addInlinePattern('/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/', function ($match, $groups, $p) use ($wikiBaseUrl) {
686-
$target = trim($groups[1]);
687-
$display = isset($groups[2]) ? trim($groups[2]) : $target;
688-
$slug = strtolower(str_replace(' ', '-', $target));
699+
$converter->on('render.link', function (RenderEvent $event) use ($wikiBaseUrl): void {
700+
$link = $event->getNode();
701+
$url = $link->getDestination() ?? '';
689702

690-
$link = new Link($wikiBaseUrl . rawurlencode($slug));
691-
$link->appendChild(new Text($display));
692-
return $link;
703+
if (str_starts_with($url, 'wiki:')) {
704+
$target = substr($url, 5);
705+
706+
if ($target === '') {
707+
$text = '';
708+
foreach ($link->getChildren() as $child) {
709+
if ($child instanceof \Djot\Node\Inline\Text) {
710+
$text .= $child->getContent();
711+
}
712+
}
713+
$target = $text;
714+
}
715+
716+
$slug = strtolower(str_replace(' ', '-', $target));
717+
$link->setDestination($wikiBaseUrl . rawurlencode($slug));
718+
}
693719
});
694720
```
695721

696722
#### With File Extension
697723

698724
```php
725+
use Djot\Event\RenderEvent;
726+
699727
// Add .html extension for static sites
700-
$parser->addInlinePattern('/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/', function ($match, $groups, $p) {
701-
$target = trim($groups[1]);
702-
$display = isset($groups[2]) ? trim($groups[2]) : $target;
703-
$slug = strtolower(str_replace(' ', '-', $target));
728+
$converter->on('render.link', function (RenderEvent $event): void {
729+
$link = $event->getNode();
730+
$url = $link->getDestination() ?? '';
704731

705-
$link = new Link('/pages/' . $slug . '.html');
706-
$link->appendChild(new Text($display));
707-
return $link;
732+
if (str_starts_with($url, 'wiki:')) {
733+
$target = substr($url, 5);
734+
735+
if ($target === '') {
736+
$text = '';
737+
foreach ($link->getChildren() as $child) {
738+
if ($child instanceof \Djot\Node\Inline\Text) {
739+
$text .= $child->getContent();
740+
}
741+
}
742+
$target = $text;
743+
}
744+
745+
$slug = strtolower(str_replace(' ', '-', $target));
746+
$link->setDestination('/pages/' . $slug . '.html');
747+
}
708748
});
709749

710-
// [[Installation Guide]] → <a href="/pages/installation-guide.html">Installation Guide</a>
750+
// [Installation Guide](wiki:) → <a href="/pages/installation-guide.html">Installation Guide</a>
711751
```
712752

713753
### Hashtags
@@ -726,6 +766,59 @@ $parser->addInlinePattern('/#([a-zA-Z][a-zA-Z0-9_]*)/', function ($match, $group
726766
echo $converter->convert('Check out #PHP and #WebDev!');
727767
```
728768

769+
### Root-Relative Links
770+
771+
Support `<~/path>` and `<~/path|display text>` for site-root-relative links:
772+
773+
```php
774+
use Djot\DjotConverter;
775+
use Djot\Node\Inline\Link;
776+
use Djot\Node\Inline\Text;
777+
778+
$converter = new DjotConverter();
779+
$parser = $converter->getParser()->getInlineParser();
780+
781+
// Pattern matches <~/path> or <~/path|display text>
782+
$parser->addInlinePattern('/<~([^>|]+)(?:\|([^>]+))?>/​', function ($match, $groups, $p) {
783+
$path = trim($groups[1]);
784+
$display = isset($groups[2]) ? trim($groups[2]) : basename($path);
785+
786+
// Build root-relative URL
787+
$url = '/' . ltrim($path, '/');
788+
789+
$link = new Link($url);
790+
$link->appendChild(new Text($display));
791+
$link->setAttribute('class', 'internal-link');
792+
return $link;
793+
});
794+
795+
echo $converter->convert('See <~/docs/installation> and <~/api/users|the API>.');
796+
```
797+
798+
Output:
799+
```html
800+
<p>See <a href="/docs/installation" class="internal-link">installation</a> and <a href="/api/users" class="internal-link">the API</a>.</p>
801+
```
802+
803+
#### Configurable Base Path
804+
805+
```php
806+
$basePath = '/docs/v2'; // Prepend to all paths
807+
808+
$parser->addInlinePattern('/<~([^>|]+)(?:\|([^>]+))?>/​', function ($match, $groups, $p) use ($basePath) {
809+
$path = trim($groups[1]);
810+
$display = isset($groups[2]) ? trim($groups[2]) : basename($path);
811+
812+
$url = $basePath . '/' . ltrim($path, '/');
813+
814+
$link = new Link($url);
815+
$link->appendChild(new Text($display));
816+
return $link;
817+
});
818+
819+
// <~/guide> → <a href="/docs/v2/guide">guide</a>
820+
```
821+
729822
### Conditional Patterns
730823

731824
Return `null` to fall back to default parsing:

0 commit comments

Comments
 (0)