@@ -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
650651use 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
674685Output:
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
726766echo $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
731824Return ` null ` to fall back to default parsing:
0 commit comments