File indexing completed on 2026-04-17 07:46:50
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013 class ActsVersionManager {
0014 constructor() {
0015 this.siteRoot = null;
0016 this.prMetadata = null;
0017 console.log('[ACTS] Version manager initialized');
0018 }
0019
0020
0021
0022
0023
0024 calculateSiteRoot() {
0025 const pathname = window.location.pathname;
0026
0027
0028 const cleanPath = pathname.replace(/^\/+|\/+$/g, '');
0029
0030 if (!cleanPath) {
0031 return '.';
0032 }
0033
0034
0035 const parts = cleanPath.split('/');
0036 const depth = parts.length - 1;
0037
0038 if (depth === 0) {
0039 return '.';
0040 } else {
0041 return '../'.repeat(depth).slice(0, -1);
0042 }
0043 }
0044
0045
0046
0047
0048
0049
0050 async discoverDocsRoot() {
0051 const pathname = window.location.pathname;
0052
0053
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;
0062
0063
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',
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
0084 }
0085 }
0086
0087
0088 console.warn('[ACTS] Could not discover docs root, using calculated path');
0089 return this.calculateSiteRoot();
0090 }
0091
0092
0093
0094
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
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
0135
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
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
0165 docContent.prepend(banner);
0166
0167 console.log('[ACTS] PR banner rendered at top of #doc-content');
0168 }
0169
0170
0171
0172
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
0224
0225 renderMinimalSelector() {
0226
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
0236
0237 async init() {
0238
0239 this.siteRoot = await this.discoverDocsRoot();
0240 console.log('[ACTS] Using docs root:', this.siteRoot);
0241
0242
0243 this.prMetadata = await this.checkForPRBuild();
0244
0245 if (this.prMetadata) {
0246
0247 this.renderPRBanner(this.prMetadata);
0248 } else {
0249
0250
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
0258 await this.loadExternalVersionSelector();
0259 } else {
0260 console.warn('[ACTS] Cannot create version selector - #side-nav not found');
0261 }
0262 }
0263 }
0264 }
0265
0266
0267 $(document).ready(function() {
0268 const versionManager = new ActsVersionManager();
0269 versionManager.init();
0270 });