diff --git a/_layouts/default.html b/_layouts/default.html index 0e6c726..7e1ec98 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -4,23 +4,8 @@ - {{ page.title }} - + @@ -38,7 +23,7 @@
{% for item in section.items %} - + {{ item.title }} diff --git a/assets/js/nav.js b/assets/js/nav.js index d13b783..a84a93e 100644 --- a/assets/js/nav.js +++ b/assets/js/nav.js @@ -47,211 +47,6 @@ }); // Ensure search results stay on developers.procore.com under /documentation var basePath = "/documentation"; - - // Helper function to check if a hostname matches a domain (handles subdomains correctly) - function isDomainMatch(hostname, domain) { - if (!hostname || !domain) return false; - // Exact match - if (hostname === domain) return true; - // Subdomain match: hostname ends with '.' + domain - // This prevents matching malicious domains like 'malicious-developers.procore.com' - return hostname.endsWith('.' + domain); - } - - // Helper function to normalize origin (remove trailing slash, ensure proper format) - function normalizeOrigin(origin) { - if (!origin) return null; - // Remove trailing slash if present - origin = origin.replace(/\/+$/, ''); - // Ensure it's a valid origin format (protocol://host) - try { - var url = new URL(origin); - var hostname = url.hostname; - - // Check hostname against known domains (parse URL first, then check hostname) - if (isDomainMatch(hostname, 'developers.procore.com')) { - return 'https://developers.procore.com'; - } - if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('127.0.0.1') || hostname.startsWith('localhost')) { - return 'http://' + hostname; - } - - return url.origin; - } catch (e) { - // If it's not a full URL, try to construct it - if (origin.startsWith('http://') || origin.startsWith('https://')) { - // Try parsing again with the protocol - try { - var url = new URL(origin); - var hostname = url.hostname; - - if (isDomainMatch(hostname, 'developers.procore.com')) { - return 'https://developers.procore.com'; - } - if (hostname === 'localhost' || hostname === '127.0.0.1') { - return 'http://' + hostname; - } - - return url.origin; - } catch (e2) { - return origin; - } - } - // If it's just a hostname string, check it directly - if (isDomainMatch(origin, 'developers.procore.com')) { - return 'https://developers.procore.com'; - } - if (origin === 'localhost' || origin === '127.0.0.1' || origin.startsWith('localhost') || origin.startsWith('127.0.0.1')) { - return 'http://' + origin.replace(/^https?:\/\//, ''); - } - return origin; - } - } - - // Function to get parent origin (works for both same-origin and cross-origin) - // Note: Production is cross-origin (procore.github.io -> developers.procore.com) - function getParentOrigin() { - if (window.self === window.top) { - return null; // Not in an iframe - } - - var parentOrigin = null; - - try { - // For same-origin iframes, we can access window.top.location.origin - // This will fail in production (cross-origin) and fall through to catch block - if (window.top.location.origin) { - parentOrigin = window.top.location.origin; - console.log("getParentOrigin: Using window.top.location.origin:", parentOrigin); - return parentOrigin; - } - } catch (e) { - // Cross-origin: can't access window.top.location (production scenario) - // Use other methods to determine parent origin - console.log("getParentOrigin: Cross-origin detected, trying alternative methods"); - - // Try ancestorOrigins API (modern browsers) - if (window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0) { - parentOrigin = normalizeOrigin(window.location.ancestorOrigins[0]); - console.log("getParentOrigin: Using ancestorOrigins[0] (normalized):", parentOrigin); - if (parentOrigin) return parentOrigin; - } - - // Try document.referrer - if (document.referrer) { - try { - parentOrigin = normalizeOrigin(new URL(document.referrer).origin); - console.log("getParentOrigin: Using document.referrer origin (normalized):", parentOrigin); - if (parentOrigin) return parentOrigin; - } catch (err) { - console.error("getParentOrigin: Error parsing referrer origin:", err, "referrer:", document.referrer); - } - } - - // Fallback: Use Jekyll config value (from _config.yml, exposed via window.JEKYLL_CONFIG) - // This is the production parent origin: developers.procore.com - if (typeof window.JEKYLL_CONFIG !== 'undefined' && window.JEKYLL_CONFIG.url) { - try { - parentOrigin = normalizeOrigin(new URL(window.JEKYLL_CONFIG.url).origin); - console.log("getParentOrigin: Using JEKYLL_CONFIG.url as fallback (normalized):", parentOrigin); - if (parentOrigin) return parentOrigin; - } catch (err) { - console.error("getParentOrigin: Error parsing JEKYLL_CONFIG.url:", err); - } - } - - // Hardcoded fallback for production - // If we're on procore.github.io, the parent is likely developers.procore.com - if (window.location.hostname === 'procore.github.io' || - window.location.hostname.endsWith('.github.io')) { - parentOrigin = normalizeOrigin('https://developers.procore.com'); - console.log("getParentOrigin: Using hardcoded production fallback (normalized):", parentOrigin); - if (parentOrigin) return parentOrigin; - } - - console.warn("getParentOrigin: Could not determine parent origin. Available info:", { - ancestorOrigins: window.location.ancestorOrigins, - referrer: document.referrer, - hostname: window.location.hostname, - jekyllConfig: typeof window.JEKYLL_CONFIG !== 'undefined' ? window.JEKYLL_CONFIG : 'undefined' - }); - } - - return parentOrigin; - } - - // Function to update parent window URL (works for both same-origin and cross-origin) - // Note: Production is cross-origin, so postMessage is required - function updateParentUrl(path, parentOrigin) { - if (!parentOrigin) { - console.warn("Cannot update parent URL: parentOrigin is null"); - return; - } - - var fullUrl = parentOrigin + path; - console.log("Updating parent URL:", { path: path, parentOrigin: parentOrigin, fullUrl: fullUrl }); - - // Try direct access first (same-origin scenarios only, e.g., reverse proxy testing) - // This will fail in production (cross-origin: procore.github.io -> developers.procore.com) - try { - if (window.top.location.origin === parentOrigin) { - // Same-origin: can directly update parent URL (testing scenarios only) - console.log("Using direct access (same-origin) to update parent URL"); - window.top.history.pushState({}, '', fullUrl); - return; - } - } catch (e) { - // Cross-origin: can't access window.top.location (production scenario) - // Must use postMessage for cross-origin communication - console.log("Cannot use direct access (cross-origin), using postMessage"); - } - - // Fallback: use postMessage (required for production cross-origin scenario) - try { - console.log("Sending postMessage to parent:", { type: 'documentationNavigation', path: path, fullUrl: fullUrl }); - window.parent.postMessage({ - type: 'documentationNavigation', - path: path, - fullUrl: fullUrl - }, parentOrigin); - } catch (err) { - console.error("Could not send navigation message to parent:", err); - } - } - - // Function to rewrite search result links for iframe display - function rewriteSearchResultLinks() { - if (window.self !== window.top) { - try { - var parentOrigin = getParentOrigin(); - - if (parentOrigin) { - $("#results-container a").each(function() { - var $link = $(this); - var originalHref = $link.attr("href"); - - // Skip if already processed or special links - if ($link.data("original-href") || !originalHref || originalHref.startsWith("#") || originalHref.startsWith("mailto:") || originalHref.startsWith("javascript:") || originalHref.startsWith("data:") || originalHref.startsWith("vbscript:")) { - return; - } - - // Get the path from the href - var path = originalHref; - if (!path.startsWith("/")) { - path = "/" + path; - } - - // Store original and set to parent origin for hover display - $link.data("original-href", originalHref); - $link.attr("href", parentOrigin + path); - }); - } - } catch (err) { - console.warn("Could not rewrite search result links:", err); - } - } - } - var sjs = SimpleJekyllSearch({ searchInput: document.getElementById("search-input"), resultsContainer: document.getElementById("results-container"), @@ -270,189 +65,32 @@ return path + (u.search || "") + (u.hash || ""); } catch (e) { // Fallback for odd values (e.g., "page.html" or "#anchor") - console.error("Error parsing URL in templateMiddleware:", e, "value:", value); if (value.charAt(0) === "#") return value; return basePath.replace(/\/$/, "") + "/" + value.replace(/^\//, ""); } } return value; }, - noResultsText: "No results found", - limit: 10, - fuzzy: false }); - - // Use MutationObserver to rewrite search result links when they're added - if (window.self !== window.top) { - var resultsContainer = document.getElementById("results-container"); - if (resultsContainer) { - var observer = new MutationObserver(function(mutations) { - rewriteSearchResultLinks(); - }); - observer.observe(resultsContainer, { - childList: true, - subtree: true - }); - } - } - // Handle clicks on search result links - navigate within iframe if in one + // Defensive: if a result link is absolute and points off-site, rewrite it to this host $("#results-container").on("click", "a", function (e) { - var $link = $(this); - var currentHref = $link.attr("href"); - var originalHref = $link.data("original-href"); - - if (!currentHref) return; - - // If in iframe and link points to parent origin, navigate within iframe - if (window.self !== window.top) { + var href = $(this).attr("href"); + if (!href) return; + // Only act on absolute URLs + if (/^https?:\/\//i.test(href)) { try { - var parentOrigin = getParentOrigin(); - - if (parentOrigin && currentHref.startsWith(parentOrigin)) { - e.preventDefault(); - var path = new URL(currentHref).pathname + new URL(currentHref).search + new URL(currentHref).hash; - // Update iframe location - window.location.href = path; - // Update parent window URL - updateParentUrl(path, parentOrigin); - return false; - } - } catch (err) { - console.error("Error handling search result link click:", err); - } - } - - // Fallback: handle absolute URLs pointing off-site - if (/^https?:\/\//i.test(currentHref)) { - try { - var u = new URL(currentHref); + var u = new URL(href); if (u.origin !== window.location.origin) { e.preventDefault(); var path = u.pathname; if (!path.startsWith(basePath)) { path = basePath.replace(/\/$/, "") + "/" + path.replace(/^\//, ""); } - var newUrl = path + (u.search || "") + (u.hash || ""); - window.location.href = newUrl; - return false; + window.location.href = path + (u.search || "") + (u.hash || ""); } } catch (err) { - console.error("Error handling absolute URL in search result click:", err, "href:", currentHref); + // ignore malformed URLs } } }); - - // If in an iframe, rewrite link hrefs for hover display but intercept clicks to navigate within iframe - if (window.self !== window.top) { - try { - var parentOrigin = getParentOrigin(); - - if (parentOrigin) { - // Function to get the actual path from a href - function getPathFromHref(href) { - if (!href || href.startsWith("#") || href.startsWith("mailto:") || href.startsWith("javascript:") || href.startsWith("data:") || href.startsWith("vbscript:")) { - return null; - } - try { - if (href.match(/^https?:\/\//)) { - return new URL(href).pathname + new URL(href).search + new URL(href).hash; - } else { - // Relative URL - return href.startsWith("/") ? href : "/" + href; - } - } catch (e) { - console.error("Error parsing href in getPathFromHref:", e, "href:", href); - return href.startsWith("/") ? href : "/" + href; - } - } - - // Rewrite navigation links for hover display - $("nav a").each(function() { - var $link = $(this); - var originalHref = $link.attr("href"); - var path = getPathFromHref(originalHref); - - if (path && !path.startsWith("#") && !path.startsWith("mailto:") && !path.startsWith("javascript:") && !path.startsWith("data:") && !path.startsWith("vbscript:")) { - // Store original href in data attribute - $link.data("original-href", originalHref); - // Set href to parent origin for hover display - $link.attr("href", parentOrigin + path); - } - }); - - // Rewrite links in main content area for hover display - $("main a").each(function() { - var $link = $(this); - var originalHref = $link.attr("href"); - var path = getPathFromHref(originalHref); - - if (path && !path.startsWith("#") && !path.startsWith("mailto:") && !path.startsWith("javascript:") && !path.startsWith("data:") && !path.startsWith("vbscript:")) { - // Store original href in data attribute - $link.data("original-href", originalHref); - // Set href to parent origin for hover display - $link.attr("href", parentOrigin + path); - } - }); - - // Intercept clicks on navigation and content links to navigate within iframe - $(document).on("click", "nav a, main a", function(e) { - var $link = $(this); - var originalHref = $link.data("original-href"); - var currentHref = $link.attr("href"); - - // If we have a stored original href, use it; otherwise check current href - var hrefToUse = originalHref || currentHref; - - // Skip external links, anchors, and special protocols - if (!hrefToUse || hrefToUse.startsWith("#") || hrefToUse.startsWith("mailto:") || hrefToUse.startsWith("javascript:") || hrefToUse.startsWith("data:") || hrefToUse.startsWith("vbscript:")) { - return; // Let default behavior handle these - } - - // If current href points to parent origin (for display), navigate using the path within iframe - if (currentHref && currentHref.startsWith(parentOrigin)) { - e.preventDefault(); - var path = new URL(currentHref).pathname + new URL(currentHref).search + new URL(currentHref).hash; - // Update iframe location - window.location.href = path; - // Update parent window URL - updateParentUrl(path, parentOrigin); - return false; - } else if (hrefToUse.match(/^https?:\/\//) && !hrefToUse.startsWith(window.location.origin) && !hrefToUse.startsWith(parentOrigin)) { - // External link (not parent origin, not current origin) - let it open normally - return; - } else if (originalHref && originalHref.startsWith(parentOrigin)) { - // Original href was parent origin - navigate within iframe - e.preventDefault(); - var path = new URL(originalHref).pathname + new URL(originalHref).search + new URL(originalHref).hash; - // Update iframe location - window.location.href = path; - // Update parent window URL via postMessage (cross-origin safe) - try { - window.parent.postMessage({ - type: 'documentationNavigation', - path: path, - fullUrl: parentOrigin + path - }, parentOrigin); - } catch (e) { - console.warn("Could not send navigation message to parent:", e); - } - return false; - } else if (hrefToUse && !hrefToUse.match(/^https?:\/\//)) { - // Relative link - navigate within iframe and update parent URL - e.preventDefault(); - var path = hrefToUse.startsWith("/") ? hrefToUse : "/" + hrefToUse; - // Update iframe location - window.location.href = path; - // Update parent window URL - updateParentUrl(path, parentOrigin); - return false; - } - // For other cases, let default behavior work - }); - } - } catch (err) { - console.warn("Could not rewrite links for parent origin:", err); - } - } })(); -