1// /star-trek/components/page-footer/page-footer.js 2 3// Start of the IIFE 4(function () { 5 "use strict"; 6 7 const TEMPLATE_URL = new URL( 8 "./page-footer.html", 9 import.meta.url 10 ).pathname; 11 12 const templatePromise = fetch(TEMPLATE_URL) 13 .then(response => { 14 if (!response.ok) { 15 throw new Error(`Failed to load template: ${response.status}`); 16 } 17 return response.text(); 18 }) 19 .then(htmlString => { 20 const template = document.createElement("template"); 21 template.innerHTML = htmlString.trim(); 22 // Remove all dev-only elements 23 template.content.querySelectorAll('[data-dev-only="true"]') 24 .forEach(el => el.remove()); 25 return template; 26 }) 27 .catch(err => { 28 console.error("Error loading page-footer template:", err); 29 }); 30 31 class PageFooter extends HTMLElement { 32 constructor() { 33 super(); 34 this.attachShadow({ mode: "open" }); 35 this.bannerElement = null; 36 this.majorLinks = []; 37 38 templatePromise.then(template => { 39 this.shadowRoot.appendChild(template.content.cloneNode(true)); 40 41 // Set element references once template is loaded 42 this.bannerElement = this.shadowRoot.querySelector("#bottom-banner"); 43 this.majorLinks = this.shadowRoot.querySelectorAll(".major-link"); 44 45 // Apply initial state 46 this._applyColorStyle(); 47 this._highlightCurrentPage(); 48 this._applyRootPath(); 49 this._applyStarTrekPath(); 50 }); 51 } 52 53 // Observe both required attributes 54 static get observedAttributes() { 55 return ["color-style", "current-page", "root-path", "star-trek-path"]; 56 } 57 58 attributeChangedCallback(name, oldValue, newValue) { 59 // Only run logic if the template is loaded 60 if (this.bannerElement) { 61 if (name === "color-style") { 62 this._applyColorStyle(); 63 } else if (name === "current-page") { 64 this._highlightCurrentPage(); 65 } else if (name === "root-path") { 66 this._applyRootPath(); 67 this._applyStarTrekPath(); 68 } else if (name === "star-trek-path") { 69 this._applyStarTrekPath(); 70 } 71 } 72 } 73 74 // Logic to set the color style (Replaces original JS for colorStyle) 75 _applyColorStyle() { 76 const newStyle = this.getAttribute("color-style") || "blue"; // Default to blue 77 const newStyleClass = `color-style-${newStyle}`; 78 79 // Remove all known style classes before adding the new one 80 this.bannerElement.classList.remove("color-style-blue", "color-style-yellow"); 81 this.bannerElement.classList.add(newStyleClass); 82 } 83 84 // Logic to highlight the current major link (Replaces original JS for currentPage) 85 _highlightCurrentPage() { 86 const currentPageId = this.getAttribute("current-page"); 87 88 this.majorLinks.forEach(link => { 89 link.classList.remove("current-page-link"); 90 91 // Check the data-link-id attribute against the current-page attribute 92 if (link.getAttribute("data-link-id") === currentPageId) { 93 link.classList.add("current-page-link"); 94 } 95 }); 96 } 97 98 _getRootUrlPath(rootPath) { 99 const ensureTrailingSlash = (str) => str.endsWith('/') ? str : `${str}/`; 100 try { 101 // 1. Check if rootPath is already a complete URL 102 const url = new URL(rootPath); 103 return ensureTrailingSlash(url.toString()); 104 } catch { 105 // 2. Not a complete URL, treat it as a relative path 106 try { 107 // This handles cases like "/testing" or "testing" 108 const baseUrl = new URL(rootPath, window.location.origin); 109 return ensureTrailingSlash(baseUrl.toString()); 110 } catch (error) { 111 console.error(`Failed to parse root path: ${rootPath}`, error); 112 // Fallback to origin + / 113 return new URL("/", window.location.origin).toString(); 114 } 115 } 116 } 117 118 /** 119 * Joins a base URL/path with a sub-path, ensuring exactly one slash between them. 120 * @param {string} base - The base URL (e.g., "http://site.com/root" or "/root") 121 * @param {string} extraPath - The path to append (e.g., "/news" or "news") 122 * @returns {string} The combined URL string. 123 */ 124 _joinUrlPaths(base, extraPath) { 125 // 1. Remove trailing slashes from base 126 const cleanBase = base.replace(/\/+$/, ""); 127 // 2. Remove leading slashes from extraPath 128 const cleanPath = extraPath.replace(/^\/+/, ""); 129 // 3. Join with a single slash 130 // If cleanPath is empty (e.g. extraPath was "/"), it just returns the base 131 return cleanPath ? `${cleanBase}/${cleanPath}` : cleanBase; 132 } 133 134 _applyRootPath() { 135 const rootPath = this.getAttribute("root-path") || "/"; 136 const rootUrlPath = this._getRootUrlPath(rootPath); 137 138 this.shadowRoot 139 .querySelectorAll("a[data-path-from-root]") 140 .forEach(anchor => { 141 const path = anchor.dataset.pathFromRoot || ""; 142 anchor.href = this._joinUrlPaths(rootUrlPath, path); 143 }); 144 } 145 146 _applyStarTrekPath() { 147 const rootPath = this.getAttribute("root-path") || "/"; 148 // Note: starTrekPath should be relative to rootPath 149 const starTrekPath = this.getAttribute("star-trek-path") || "star-trek/"; 150 const rootUrlPath = this._getRootUrlPath(rootPath); 151 // Combine rootPath + starTrekPath first, then add path 152 const fullStarTrekPath = this._joinUrlPaths(rootUrlPath, starTrekPath); 153 154 this.shadowRoot 155 .querySelectorAll("a[data-path-from-star-trek]") 156 .forEach(anchor => { 157 const path = anchor.dataset.pathFromStarTrek || ""; 158 anchor.href = new URL(path, fullStarTrekPath).toString(); 159 }); 160 } 161 } 162 163 // Register the custom tag 164 customElements.define("page-footer", PageFooter); 165})(); // End of the IIFE
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.