@@ -644,22 +644,110 @@ Output:
644644
645645### Wiki Links
646646
647- Support ` [[Page Name]] ` 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
650- $parser->addInlinePattern('/\[\[([^\]]+)\]\]/', function ($match, $groups, $p) {
651- $page = $groups[1];
652- $link = new Link('/wiki/' . rawurlencode($page));
653- $link->appendChild(new Text($page));
654- return $link;
651+ use Djot\DjotConverter;
652+ use Djot\Event\RenderEvent;
653+
654+ $converter = new DjotConverter();
655+
656+ // Handle wiki: scheme in links
657+ $converter->on('render.link', function (RenderEvent $event): void {
658+ $link = $event->getNode();
659+ $url = $link->getDestination() ?? '';
660+
661+ if (str_starts_with($url, 'wiki:')) {
662+ $target = substr($url, 5); // Remove 'wiki:' prefix
663+
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+ }
655680});
656681
657- echo $converter->convert('See [[ Home Page]] and [[Getting Started]] .');
682+ echo $converter->convert('See [Home Page](wiki:) and [the API docs](wiki:API Reference) .');
658683```
659684
660685Output:
661686``` html
662- <p >See <a href =" /wiki/Home%20Page" >Home Page</a > and <a href =" /wiki/Getting%20Started" >Getting Started</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 >
688+ ```
689+
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+
694+ #### Configurable Base URL
695+
696+ ``` php
697+ $wikiBaseUrl = '/docs/'; // or 'https://wiki.example.com/'
698+
699+ $converter->on('render.link', function (RenderEvent $event) use ($wikiBaseUrl): void {
700+ $link = $event->getNode();
701+ $url = $link->getDestination() ?? '';
702+
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+ }
719+ });
720+ ```
721+
722+ #### With File Extension
723+
724+ ``` php
725+ use Djot\Event\RenderEvent;
726+
727+ // Add .html extension for static sites
728+ $converter->on('render.link', function (RenderEvent $event): void {
729+ $link = $event->getNode();
730+ $url = $link->getDestination() ?? '';
731+
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+ }
748+ });
749+
750+ // [Installation Guide](wiki:) → <a href =" /pages/installation-guide.html" >Installation Guide</a >
663751```
664752
665753### Hashtags
@@ -678,6 +766,59 @@ $parser->addInlinePattern('/#([a-zA-Z][a-zA-Z0-9_]*)/', function ($match, $group
678766echo $converter->convert('Check out #PHP and #WebDev!');
679767```
680768
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+
681822### Conditional Patterns
682823
683824Return ` null ` to fall back to default parsing:
0 commit comments