1// Sticky showversion: once enabled via ?showversion=true it is persisted to localStorage, so the 2// version label survives the host app's history.replace (which strips the query string) and later 3// full reloads. ?showversion=false turns it back off. (A build may still force it on by pre-setting 4// window.shipyard.showversion â that override wins via the spread below.) 5const resolveShowversion = () => { 6 const sv = new URLSearchParams(window.location.search).get('showversion'); 7 if (sv === 'true') localStorage.setItem('shipyard_showversion', 'true'); 8 else if (sv === 'false') localStorage.removeItem('shipyard_showversion'); 9 return localStorage.getItem('shipyard_showversion') === 'true'; 10}; 11 12window.shipyard = { 13 showversion: resolveShowversion(), 14 defaultVersion: 'main', 15 ...(window.shipyard || {}) 16}; 17 18const getLsKey = (key) => `shipyard_${key}` 19 20const getQueryParamKey = (key = "version") => { 21 return key.replace("shipyard_", ""); 22} 23 24const getCurrentVersion = (key = "version") => { 25 // get from query params if present 26 const queryParamVersion = getQueryParamKey(getLsKey(key)) && new URLSearchParams(window.location.search).get(getQueryParamKey(getLsKey(key))); 27 28 if (queryParamVersion) { 29 setVersion(key, queryParamVersion, true); 30 return queryParamVersion; 31 } 32 33 return localStorage.getItem(getLsKey(key)) || window.shipyard.defaultVersion; 34} 35 36window.freezeVersion = (key = "version") => { 37 setVersion(key, getCurrentVersion(key), true); 38 injectVersionLabel(); 39} 40 41// Helper function to get all shipyard keys from localStorage 42window.getAllShipyardVersions = () => { 43 const versions = {}; 44 for (let i = 0; i < localStorage.length; i++) { 45 const key = localStorage.key(i); 46 if (key && key.startsWith('shipyard_')) { 47 const displayKey = key.replace('shipyard_', ''); 48 versions[displayKey] = localStorage.getItem(key); 49 } 50 } 51 return versions; 52} 53 54window.setVersion = (key = "version", version, avoidRedirect) => { 55 // Ensure we always use the localStorage key format 56 const storageKey = key.startsWith('shipyard_') ? key : getLsKey(key); 57 localStorage.setItem(storageKey, version); 58 if (!avoidRedirect && window.shipyard.redirectUrl) { 59 // Get all shipyard versions from localStorage 60 const allVersions = window.getAllShipyardVersions(); 61 62 // Build query parameters for all versions 63 const queryParams = Object.entries(allVersions) 64 .map(([versionKey, versionValue]) => `${versionKey}=${encodeURIComponent(versionValue)}`) 65 .join('&'); 66 67 // Construct the redirect URL with all version parameters 68 const separator = window.shipyard.redirectUrl.includes('?') ? '&' : '?'; 69 const targetUrl = window.shipyard.redirectUrl + (queryParams ? `${separator}${queryParams}` : ''); 70 71 // Prevent redirect loops by checking if we're already on the target URL 72 // Extract the path and query from both current and target URLs 73 const currentPath = window.location.pathname; 74 const currentParams = new URLSearchParams(window.location.search); 75 76 const targetUrlObj = new URL(targetUrl, window.location.origin); 77 const targetPath = targetUrlObj.pathname; 78 const targetParams = new URLSearchParams(targetUrlObj.search); 79 80 // Check if we're redirecting to the same path with identical version parameters 81 let sameDestination = (currentPath === targetPath); 82 if (sameDestination) { 83 // Compare all version parameters
84 Object.keys(allVersions).forEach(versionKey => { 85 if (currentParams.get(versionKey) !== targetParams.get(versionKey)) { 86 sameDestination = false; 87 } 88 }); 89 } 90 91 if (!sameDestination) { 92 console.log('Shipyard redirecting to:', targetUrl); 93 window.location.href = targetUrl; 94 } else { 95 console.log('Shipyard redirect loop prevented - already at target destination'); 96 } 97 } 98} 99 100window.getVersion = (key = "version") => { 101 const queryParamVersion = getQueryParamKey(getLsKey(key)) && new URLSearchParams(window.location.search).get(getQueryParamKey(getLsKey(key))); 102 103 if (queryParamVersion) { 104 setVersion(key, queryParamVersion, true); 105 return queryParamVersion; 106 } 107 return localStorage.getItem(getLsKey(key)) || window.shipyard.defaultVersion; 108} 109 110let currentVersion = window.getVersion(); 111 112window.injectVersionLabel = () => { 113 // Only inject if showversion is true 114 if (!window.shipyard.showversion) { 115 return; 116 } 117 118 // Check if label already exists to avoid duplicates 119 if (document.getElementById('version-label')) { 120 // remove from DOM 121 document.getElementById('version-label').remove(); 122 return; 123 } 124 125 const versionLabel = document.createElement('div'); 126 versionLabel.id = 'version-label'; 127 128 // Show all versions in the label 129 const allVersions = window.getAllShipyardVersions(); 130 const versionText = Object.keys(allVersions).length > 0 131 ? Object.entries(allVersions).map(([key, value]) => `[${getQueryParamKey(key)}]: ${value}`).join(', ') 132 : `[version]: ${window.getVersion()}`; 133 134 versionLabel.innerHTML = `<span style="font-size: 10px;">v</span>${versionText}`; 135 136 // Styling for bottom left positioning 137 versionLabel.style.cssText = ` 138 position: fixed; 139 bottom: 0px; 140 left: 0px; 141 background-color: rgba(0, 0, 0, 0.7); 142 color: white; 143 padding: 4px 8px; 144 border-radius: 4px; 145 font-family: monospace; 146 font-size: 12px; 147 z-index: 9999; 148 pointer-events: auto; 149 user-select: none; 150 cursor: pointer; 151 transition: background-color 0.2s; 152 max-width: 80vw; 153 word-wrap: break-word; 154 `; 155 156 // Add hover effect 157 versionLabel.addEventListener('mouseenter', () => { 158 versionLabel.style.backgroundColor = 'rgba(0, 0, 0, 0.9)'; 159 }); 160 161 versionLabel.addEventListener('mouseleave', () => { 162 versionLabel.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; 163 }); 164 165 // Add click handler to open version change popup 166 versionLabel.addEventListener('click', () => { 167 const allVersions = window.getAllShipyardVersions(); 168 169 // Create custom dialog 170 const dialog = document.createElement('div'); 171 dialog.id = 'version-dialog'; 172 dialog.style.cssText = ` 173 position: fixed; 174 top: 0; 175 left: 0; 176 width: 100%; 177 height: 100%; 178 background-color: rgba(0, 0, 0, 0.5); 179 display: flex; 180 justify-content: center; 181 align-items: center; 182 z-index: 10000; 183 `; 184 185 const dialogContent = document.createElement('div'); 186 dialogContent.style.cssText = ` 187 background-color: white; 188 padding: 20px; 189 border-radius: 8px; 190 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); 191 min-width: 400px; 192 max-width: 80vw; 193 max-height: 80vh; 194 overflow-y: auto; 195 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 196 `; 197 198 // Build the dialog content with multiple version inputs 199 let dialogHTML = ` 200 <h3 style="margin: 0 0 15px 0; font-size: 16px; color: #333;">Change Versions</h3> 201 `; 202 203 // If no versions exist, create default version entry 204 if (Object.keys(allVersions).length === 0) { 205 allVersions['version'] = window.getVersion(); 206 } 207 208 let versionInputsHTML = '';
209 Object.entries(allVersions).forEach(([key, value]) => { 210 versionInputsHTML += ` 211 <div style="margin-bottom: 15px; padding: 15px; border: 1px solid #e0e0e0; border-radius: 6px; background-color: #f9f9f9;"> 212 <div style="display: flex; align-items: center; gap: 12px;"> 213 <label style="font-weight: 600; color: #333; min-width: 80px; font-size: 14px;">${getQueryParamKey(key)}:</label> 214 <input type="text" id="version-input-${key}" value="${value}" style=" 215 flex: 1; 216 padding: 8px 12px; 217 border: 1px solid #ddd; 218 border-radius: 4px; 219 font-size: 14px; 220 margin-right: 8px; 221 " placeholder="Enter version"> 222 <button class="apply-single-btn" data-key="${key}" style=" 223 background-color: #28a745; 224 color: white; 225 border: none; 226 padding: 8px 16px; 227 border-radius: 4px; 228 cursor: pointer; 229 font-size: 13px; 230 font-weight: 500; 231 margin-right: 6px; 232 transition: background-color 0.2s; 233 ">Apply</button> 234 <button class="use-latest-single-btn" data-key="${key}" style=" 235 background-color: #007bff; 236 color: white; 237 border: none; 238 padding: 8px 16px; 239 border-radius: 4px; 240 cursor: pointer; 241 font-size: 13px; 242 font-weight: 500; 243 transition: background-color 0.2s; 244 ">Use Latest</button> 245 </div> 246 </div> 247 `; 248 }); 249 250 dialogHTML += versionInputsHTML; 251 252 dialogHTML += ` 253 <div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px; padding-top: 15px; border-top: 1px solid #e0e0e0;"> 254 <button id="cancel-btn" style=" 255 background-color: #f0f0f0; 256 color: #333; 257 border: none; 258 padding: 10px 20px; 259 border-radius: 4px; 260 cursor: pointer; 261 font-size: 14px; 262 ">Cancel</button> 263 <button id="use-all-latest-btn" style=" 264 background-color: #007acc; 265 color: white; 266 border: none; 267 padding: 10px 20px; 268 border-radius: 4px; 269 cursor: pointer; 270 font-size: 14px; 271 ">Use All Latest</button> 272 </div> 273 `; 274 275 dialogContent.innerHTML = dialogHTML; 276 dialog.appendChild(dialogContent); 277 document.body.appendChild(dialog); 278 279 const cancelBtn = document.getElementById('cancel-btn'); 280 const useAllLatestBtn = document.getElementById('use-all-latest-btn'); 281 282 const closeDialog = () => { 283 document.body.removeChild(dialog); 284 }; 285 286 const updateVersionLabel = () => { 287 const updatedVersions = window.getAllShipyardVersions(); 288 const versionText = Object.keys(updatedVersions).length > 0 289 ? Object.entries(updatedVersions).map(([key, value]) => `[${key}]: ${value}`).join(', ') 290 : `[version]: ${window.getVersion()}`; 291 versionLabel.innerHTML = `<span style="font-size: 10px;">v</span>${versionText}`; 292 }; 293 294 // Handle individual apply buttons 295 document.querySelectorAll('.apply-single-btn').forEach(btn => { 296 btn.addEventListener('click', () => {
297 const key = btn.dataset.key; 298 const input = document.getElementById(`version-input-${key}`); 299 const newVersion = input.value.trim(); 300 if (newVersion !== '') { 301 window.setVersion(key, newVersion, false); 302 updateVersionLabel(); 303 } 304 }); 305 }); 306 307 // Handle individual use latest buttons 308 document.querySelectorAll('.use-latest-single-btn').forEach(btn => { 309 btn.addEventListener('click', () => { 310 const key = btn.dataset.key; 311 const input = document.getElementById(`version-input-${key}`); 312 input.value = 'main'; 313 window.setVersion(key, 'main', false); 314 updateVersionLabel(); 315 }); 316 }); 317 318 // Handle use all latest button 319 useAllLatestBtn.addEventListener('click', () => { 320 Object.keys(allVersions).forEach(key => { 321 window.setVersion(key, 'main', false); 322 }); 323 updateVersionLabel(); 324 closeDialog(); 325 }); 326 327 cancelBtn.addEventListener('click', closeDialog); 328 329 // Close on escape key 330 dialog.addEventListener('keydown', (e) => { 331 if (e.key === 'Escape') { 332 closeDialog(); 333 } 334 }); 335 336 // Close on backdrop click 337 dialog.addEventListener('click', (e) => { 338 if (e.target === dialog) { 339 closeDialog(); 340 } 341 }); 342 }); 343 344 document.body.appendChild(versionLabel); 345} 346 347// Auto-inject when DOM is ready 348if (document.readyState === 'loading') { 349 document.addEventListener('DOMContentLoaded', window.injectVersionLabel); 350} else { 351 window.injectVersionLabel(); 352}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.