@@ -700,29 +700,96 @@ private function generateStaticContent(array $node, string $pageTitle, array $re
700700 return $ content ;
701701 }
702702
703+ private function processMermaidReferences (string $ content , array $ registry , array $ navPathMap , array $ navIdMap , string $ sourceOwner ): string
704+ {
705+ // Process @navid references within Mermaid code blocks
706+ // Pattern to match Mermaid code blocks: ```mermaid ... ```
707+ return preg_replace_callback (
708+ '/^```mermaid\n((?:(?!^```)[\s\S])*?)^```/m ' ,
709+ function ($ matches ) use ($ registry , $ navPathMap , $ navIdMap , $ sourceOwner ) {
710+ $ mermaidContent = $ matches [1 ];
711+
712+ // Process click events with @navid references within the Mermaid content
713+ // Pattern: click element "@navid:target" "tooltip" or click element '@navid:target' 'tooltip'
714+ // Also supports: click element "@navid:target#fragment" "tooltip"
715+ // Element names can contain letters, numbers, underscores, hyphens, and dots
716+ // Tooltip is optional
717+ $ clickPattern = '/click\s+([a-zA-Z0-9_.-]+)\s+([" \'])@(ref|navid):([^#" \']+)(?:#([^" \']+))?\2(?:\s+([" \'])([^" \']+)\6)?/ ' ;
718+
719+ $ processedMermaidContent = preg_replace_callback (
720+ $ clickPattern ,
721+ function ($ clickMatches ) use ($ registry , $ navPathMap , $ navIdMap , $ sourceOwner ) {
722+ $ element = $ clickMatches [1 ]; // The Mermaid element to click
723+ $ refType = $ clickMatches [3 ]; // 'ref' or 'navid' (adjusted index due to quote capture)
724+ $ refTarget = $ clickMatches [4 ]; // The reference target (adjusted index)
725+ $ fragment = $ clickMatches [5 ] ?? null ; // Optional fragment (adjusted index)
726+ $ tooltip = $ clickMatches [7 ] ?? null ; // The tooltip text (optional, adjusted index)
727+
728+ // Resolve the reference
729+ $ resolvedLink = $ this ->resolveReference ($ refType , $ refTarget , $ registry , $ navPathMap , $ navIdMap , $ sourceOwner );
730+
731+ if ($ resolvedLink === null ) {
732+ // Reference couldn't be resolved - throw build error with helpful context
733+ $ sourceInfo = $ sourceOwner ? " in {$ sourceOwner }" : '' ;
734+ $ fragmentInfo = $ fragment ? "# {$ fragment }" : '' ;
735+
736+ throw new \RuntimeException ("Broken Mermaid reference: @ {$ refType }: {$ refTarget }{$ fragmentInfo } in Mermaid diagram {$ sourceInfo }" );
737+ }
738+
739+ $ linkUrl = $ resolvedLink ['url ' ];
740+
741+ // Append fragment identifier if provided
742+ if ($ fragment ) {
743+ $ linkUrl .= '# ' . $ fragment ;
744+ }
745+
746+ // Return the processed click event with resolved URL
747+ // Include tooltip only if it was provided
748+ if ($ tooltip !== null ) {
749+ return "click {$ element } \"{$ linkUrl }\" \"{$ tooltip }\"" ;
750+ } else {
751+ return "click {$ element } \"{$ linkUrl }\"" ;
752+ }
753+ },
754+ $ mermaidContent
755+ );
756+
757+ // Return the processed Mermaid block
758+ return "```mermaid \n{$ processedMermaidContent }``` " ;
759+ },
760+ $ content
761+ );
762+ }
763+
703764 private function processInlineReferences (string $ content , array $ registry , array $ navPathMap , array $ navIdMap , string $ sourceOwner ): string
704765 {
705- // Process [@ref:...] and [@navid:...] syntax
766+ // First, process Mermaid code blocks to handle @navid references within them
767+ $ content = $ this ->processMermaidReferences ($ content , $ registry , $ navPathMap , $ navIdMap , $ sourceOwner );
768+
769+ // Process [@ref:...] and [@navid:...] syntax with optional fragment support
706770 // Pattern explanation:
707- // \[ - Opening bracket (always consumed)
708- // (?:([^\]]+)\]\()? - Optional custom link text in [text]( format
709- // @(ref|navid): - The @ref: or @navid: syntax
710- // ([^)\]\s]+) - The reference target (no spaces, closing parens, or brackets)
711- // [\])] - Closing bracket or paren
771+ // \[ - Opening bracket (always consumed)
772+ // (?:([^\]]+)\]\()? - Optional custom link text in [text]( format
773+ // @(ref|navid): - The @ref: or @navid: syntax
774+ // ([^#)\]\s]+) - The reference target (no #, spaces, closing parens, or brackets)
775+ // (?:#([^)\]\s]+))? - Optional fragment identifier after #
776+ // [\])] - Closing bracket or paren
712777
713- $ pattern = '/\[(?:([^\]]+)\]\()?@(ref|navid):([^)\]\s]+)[\])]/ ' ;
778+ $ pattern = '/\[(?:([^\]]+)\]\()?@(ref|navid):([^# )\]\s]+)(?:#([^)\]\s]+))? [\])]/ ' ;
714779
715780 return preg_replace_callback ($ pattern , function ($ matches ) use ($ registry , $ navPathMap , $ navIdMap , $ sourceOwner ) {
716781 $ customText = $ matches [1 ] !== '' ? $ matches [1 ] : null ; // Custom link text if provided
717782 $ refType = $ matches [2 ]; // 'ref' or 'navid'
718783 $ refTarget = $ matches [3 ]; // The actual reference target
784+ $ fragment = $ matches [4 ] ?? null ; // Optional fragment identifier
719785
720786 // Resolve the reference based on type
721787 $ resolvedLink = $ this ->resolveReference ($ refType , $ refTarget , $ registry , $ navPathMap , $ navIdMap , $ sourceOwner );
722788
723789 if ($ resolvedLink === null ) {
724790 // Reference couldn't be resolved - throw build error with helpful context
725791 $ sourceInfo = $ sourceOwner ? " in {$ sourceOwner }" : '' ;
792+ $ fragmentInfo = $ fragment ? "# {$ fragment }" : '' ;
726793 $ suggestion = '' ;
727794
728795 if ($ refType === 'ref ' ) {
@@ -737,12 +804,17 @@ private function processInlineReferences(string $content, array $registry, array
737804 "3. Or use a code block instead: ` {$ refTarget }` " ;
738805 }
739806
740- throw new \RuntimeException ("Broken reference: @ {$ refType }: {$ refTarget }{$ sourceInfo }{$ suggestion }" );
807+ throw new \RuntimeException ("Broken reference: @ {$ refType }: {$ refTarget }{$ fragmentInfo }{ $ sourceInfo }{$ suggestion }" );
741808 }
742809
743810 $ linkText = $ customText ?: $ resolvedLink ['title ' ];
744811 $ linkUrl = $ resolvedLink ['url ' ];
745812
813+ // Append fragment identifier if provided
814+ if ($ fragment ) {
815+ $ linkUrl .= '# ' . $ fragment ;
816+ }
817+
746818 return "[ {$ linkText }]( {$ linkUrl }) " ;
747819 }, $ content );
748820 }
0 commit comments