Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-17 07:46:50

0001 /**
0002  * ACTS Version Manager
0003  *
0004  * Handles dynamic version selection and PR build detection for ACTS documentation.
0005  *
0006  * Features:
0007  * - Detects PR builds via pr.json file
0008  * - Displays PR banner for PR builds
0009  * - Loads external version selector from deploy repo
0010  * - Falls back to minimal selector if external JS unavailable
0011  */
0012 
0013 class ActsVersionManager {
0014   constructor() {
0015     this.siteRoot = null; // Will be set during discovery
0016     this.prMetadata = null;
0017     console.log('[ACTS] Version manager initialized');
0018   }
0019 
0020   /**
0021    * Calculate relative path to site root based on current URL depth
0022    * @returns {string} Relative path (e.g., '.', '..', '../..')
0023    */
0024   calculateSiteRoot() {
0025     const pathname = window.location.pathname;
0026 
0027     // Remove leading/trailing slashes
0028     const cleanPath = pathname.replace(/^\/+|\/+$/g, '');
0029 
0030     if (!cleanPath) {
0031       return '.'; // Root path
0032     }
0033 
0034     // Count directory levels (excluding filename)
0035     const parts = cleanPath.split('/');
0036     const depth = parts.length - 1; // Subtract 1 for filename
0037 
0038     if (depth === 0) {
0039       return '.'; // At root (index.html)
0040     } else {
0041       return '../'.repeat(depth).slice(0, -1); // Remove trailing slash
0042     }
0043   }
0044 
0045   /**
0046    * Discover the actual docs root by trying to find index.html at different levels
0047    * This handles cases where docs are deployed at nested paths (e.g., PR previews)
0048    * @returns {Promise<string>} Path to docs root
0049    */
0050   async discoverDocsRoot() {
0051     const pathname = window.location.pathname;
0052 
0053     // Special case: if we're already on index.html, we're at the root
0054     if (pathname.endsWith('/index.html') || pathname.endsWith('/')) {
0055       console.log('[ACTS] Already at docs root (index.html)');
0056       return '.';
0057     }
0058 
0059     const cleanPath = pathname.replace(/^\/+|\/+$/g, '');
0060     const parts = cleanPath.split('/');
0061     const maxDepth = parts.length - 1; // Don't include the filename
0062 
0063     // Try each level from current directory up to domain root
0064     for (let depth = 0; depth <= maxDepth; depth++) {
0065       const path = depth === 0 ? '.' : '../'.repeat(depth).slice(0, -1);
0066 
0067       try {
0068         const controller = new AbortController();
0069         const timeout = setTimeout(() => controller.abort(), 500);
0070 
0071         const response = await fetch(`${path}/index.html`, {
0072           method: 'HEAD', // Just check existence, don't download
0073           signal: controller.signal
0074         });
0075 
0076         clearTimeout(timeout);
0077 
0078         if (response.ok) {
0079           console.log(`[ACTS] Discovered docs root at: ${path}`);
0080           return path;
0081         }
0082       } catch (error) {
0083         // Continue searching
0084       }
0085     }
0086 
0087     // Fallback to calculated site root if discovery fails
0088     console.warn('[ACTS] Could not discover docs root, using calculated path');
0089     return this.calculateSiteRoot();
0090   }
0091 
0092   /**
0093    * Check for PR build by attempting to fetch pr.json
0094    * @returns {Promise<object|null>} PR metadata or null
0095    */
0096   async checkForPRBuild() {
0097     try {
0098       const controller = new AbortController();
0099       const timeout = setTimeout(() => controller.abort(), 2000);
0100 
0101       const response = await fetch(`${this.siteRoot}/pr.json`, {
0102         cache: 'no-store',
0103         signal: controller.signal
0104       });
0105 
0106       clearTimeout(timeout);
0107 
0108       if (!response.ok) {
0109         console.log('[ACTS] No pr.json found - not a PR build');
0110         return null;
0111       }
0112 
0113       const data = await response.json();
0114 
0115       // Validate structure
0116       if (!data.pr || !data.url) {
0117         console.warn('[ACTS] Invalid pr.json structure - missing required fields');
0118         return null;
0119       }
0120 
0121       console.log('[ACTS] PR build detected:', data);
0122       return data;
0123     } catch (error) {
0124       if (error.name === 'AbortError') {
0125         console.warn('[ACTS] pr.json fetch timeout');
0126       } else {
0127         console.log('[ACTS] No pr.json found (expected for non-PR builds)');
0128       }
0129       return null;
0130     }
0131   }
0132 
0133   /**
0134    * Render PR build banner at the top of #doc-content
0135    * @param {object} metadata - PR metadata from pr.json
0136    */
0137   renderPRBanner(metadata) {
0138     const docContent = document.querySelector('#doc-content');
0139     if (!docContent) {
0140       console.warn('[ACTS] Cannot render PR banner - #doc-content not found');
0141       return;
0142     }
0143 
0144     const banner = document.createElement('div');
0145     banner.id = 'acts-pr-banner';
0146 
0147     // Build banner content
0148     let content = `<strong>PR Build #${metadata.pr}</strong>`;
0149 
0150     if (metadata.branch) {
0151       content += ` from branch <code>${metadata.branch}</code>`;
0152     }
0153 
0154     if (metadata.title) {
0155       content += ` - ${metadata.title}`;
0156     }
0157 
0158     if (metadata.url) {
0159       content = `${content} (<a href="${metadata.url}" target="_blank" rel="noopener">View PR</a>)`;
0160     }
0161 
0162     banner.innerHTML = content;
0163 
0164     // Insert at the top of #doc-content
0165     docContent.prepend(banner);
0166 
0167     console.log('[ACTS] PR banner rendered at top of #doc-content');
0168   }
0169 
0170   /**
0171    * Load external version-selector.js from site root
0172    * @returns {Promise<boolean>} True if loaded successfully
0173    */
0174   async loadExternalVersionSelector() {
0175     return new Promise((resolve) => {
0176       const script = document.createElement('script');
0177       script.src = `${this.siteRoot}/version-selector.js`;
0178 
0179       const timeout = setTimeout(() => {
0180         console.warn('[ACTS] External version-selector.js timeout after 5s');
0181         script.remove();
0182         this.renderMinimalSelector();
0183         resolve(false);
0184       }, 5000);
0185 
0186       script.onload = () => {
0187         clearTimeout(timeout);
0188 
0189         if (typeof window.initVersionSelector === 'function') {
0190           try {
0191             console.log('[ACTS] External version-selector.js loaded, initializing...');
0192             window.initVersionSelector({
0193               container: '#acts-version-selector',
0194               currentPath: window.location.pathname,
0195               siteRoot: this.siteRoot
0196             });
0197             console.log('[ACTS] Version selector initialized successfully');
0198             resolve(true);
0199           } catch (error) {
0200             console.error('[ACTS] version-selector.js init failed:', error);
0201             this.renderMinimalSelector();
0202             resolve(false);
0203           }
0204         } else {
0205           console.warn('[ACTS] version-selector.js missing initVersionSelector function');
0206           this.renderMinimalSelector();
0207           resolve(false);
0208         }
0209       };
0210 
0211       script.onerror = () => {
0212         clearTimeout(timeout);
0213         console.log('[ACTS] External version-selector.js not available (expected for local builds)');
0214         this.renderMinimalSelector();
0215         resolve(false);
0216       };
0217 
0218       document.head.appendChild(script);
0219     });
0220   }
0221 
0222   /**
0223    * Render minimal fallback version selector
0224    */
0225   renderMinimalSelector() {
0226     // Disabled for now - remove the empty container instead
0227     const container = document.querySelector('#acts-version-selector');
0228     if (container) {
0229       container.remove();
0230       console.log('[ACTS] Minimal selector disabled - removed empty container');
0231     }
0232   }
0233 
0234   /**
0235    * Main initialization
0236    */
0237   async init() {
0238     // Discover the actual docs root (handles nested deployments)
0239     this.siteRoot = await this.discoverDocsRoot();
0240     console.log('[ACTS] Using docs root:', this.siteRoot);
0241 
0242     // Check for PR build
0243     this.prMetadata = await this.checkForPRBuild();
0244 
0245     if (this.prMetadata) {
0246       // This is a PR build - show banner only
0247       this.renderPRBanner(this.prMetadata);
0248     } else {
0249       // Regular build - show version selector
0250       // Create container first to avoid layout shift
0251       const sideNav = document.querySelector('#side-nav');
0252       if (sideNav) {
0253         const container = document.createElement('div');
0254         container.id = 'acts-version-selector';
0255         sideNav.prepend(container);
0256 
0257         // Load external version selector
0258         await this.loadExternalVersionSelector();
0259       } else {
0260         console.warn('[ACTS] Cannot create version selector - #side-nav not found');
0261       }
0262     }
0263   }
0264 }
0265 
0266 // Initialize when DOM ready (jQuery is already loaded by Doxygen)
0267 $(document).ready(function() {
0268   const versionManager = new ActsVersionManager();
0269   versionManager.init();
0270 });