|
45 | 45 | $(this).closest(".collapsible").find(".col_content").slideToggle("350"); |
46 | 46 | $(this).find(".icon").toggleClass(["fa-square-minus", "fa-square-plus"]); |
47 | 47 | }); |
48 | | - // Ensure search results stay on developers.procore.com under /documentation |
49 | | - var basePath = "/documentation"; |
50 | | - var sjs = SimpleJekyllSearch({ |
51 | | - searchInput: document.getElementById("search-input"), |
52 | | - resultsContainer: document.getElementById("results-container"), |
53 | | - json: basePath + "/search.json", |
54 | | - searchResultTemplate: '<li><a href="{url}">{title}</a></li>', |
55 | | - // Force URLs from the index to resolve to this site's origin and base path |
56 | | - templateMiddleware: function (prop, value) { |
57 | | - if (prop === "url" && value) { |
58 | | - try { |
59 | | - var u = new URL(value, window.location.origin); |
60 | | - var path = u.pathname; |
61 | | - if (!path.startsWith(basePath)) { |
62 | | - path = basePath.replace(/\/$/, "") + "/" + path.replace(/^\//, ""); |
63 | | - } |
64 | | - // Return a same-origin, site-relative URL (preserves query/hash) |
65 | | - return path + (u.search || "") + (u.hash || ""); |
66 | | - } catch (e) { |
67 | | - // Fallback for odd values (e.g., "page.html" or "#anchor") |
68 | | - if (value.charAt(0) === "#") return value; |
69 | | - return basePath.replace(/\/$/, "") + "/" + value.replace(/^\//, ""); |
70 | | - } |
| 48 | + var searchInputEl = document.getElementById("search-input"); |
| 49 | + var resultsContainerEl = document.getElementById("results-container"); |
| 50 | + var searchDebounceTimer = null; |
| 51 | + |
| 52 | + function parentOrigin() { |
| 53 | + try { |
| 54 | + if (window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0) { |
| 55 | + return window.location.ancestorOrigins[0]; |
71 | 56 | } |
72 | | - return value; |
73 | | - }, |
74 | | - }); |
| 57 | + if (document.referrer) { |
| 58 | + return new URL(document.referrer).origin; |
| 59 | + } |
| 60 | + } catch (e) { |
| 61 | + console.warn("Could not determine parent origin:", e); |
| 62 | + } |
| 63 | + return null; |
| 64 | + } |
| 65 | + |
| 66 | + function searchApiBase() { |
| 67 | + // 1. When embedded in an iframe the parent app IS the dev-portal; use its |
| 68 | + // origin so the request is always same-origin from the parent's point |
| 69 | + // of view (avoids CORS entirely). |
| 70 | + var parent = parentOrigin(); |
| 71 | + if (parent) return parent; |
| 72 | + |
| 73 | + // 2. Value baked in at Jekyll build/serve time via the DEV_PORTAL_BASE_URL |
| 74 | + // environment variable (see _plugins/env_config.rb). This is the |
| 75 | + // authoritative config-time answer and is preferred over runtime |
| 76 | + // hostname sniffing. |
| 77 | + if (window.DEV_PORTAL_BASE_URL && window.DEV_PORTAL_BASE_URL !== "") { |
| 78 | + return window.DEV_PORTAL_BASE_URL; |
| 79 | + } |
| 80 | + |
| 81 | + // 3. Runtime fallback — keeps things working even if the global is somehow |
| 82 | + // absent (e.g. an older cached build). |
| 83 | + if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") { |
| 84 | + return "http://localhost:3001"; |
| 85 | + } |
| 86 | + if (window.location.hostname.indexOf("github.io") !== -1) { |
| 87 | + return "https://developers.procore.com"; |
| 88 | + } |
| 89 | + return window.location.origin; |
| 90 | + } |
| 91 | + |
| 92 | + function buildSearchUrl(query) { |
| 93 | + return ( |
| 94 | + searchApiBase() + |
| 95 | + "/api/v1/documentations?q=" + |
| 96 | + encodeURIComponent(query) + |
| 97 | + "&page=1&per_page=10" |
| 98 | + ); |
| 99 | + } |
| 100 | + |
| 101 | + function clearSearchResults() { |
| 102 | + resultsContainerEl.innerHTML = ""; |
| 103 | + } |
| 104 | + |
| 105 | + function renderSearchResults(results) { |
| 106 | + clearSearchResults(); |
| 107 | + if (!results || results.length === 0) return; |
| 108 | + |
| 109 | + results.forEach(function(result) { |
| 110 | + var li = document.createElement("li"); |
| 111 | + var link = document.createElement("a"); |
| 112 | + link.textContent = result.title || result.file_path || "Untitled"; |
| 113 | + link.href = result.url || ("/documentation/" + (result.file_path || "").replace(/\.md$/, "")); |
| 114 | + li.appendChild(link); |
| 115 | + resultsContainerEl.appendChild(li); |
| 116 | + }); |
| 117 | + } |
| 118 | + |
| 119 | + function performSearch(query) { |
| 120 | + if (!query || query.length < 2) { |
| 121 | + clearSearchResults(); |
| 122 | + return; |
| 123 | + } |
| 124 | + |
| 125 | + fetch(buildSearchUrl(query)) |
| 126 | + .then(function(response) { |
| 127 | + if (!response.ok) { |
| 128 | + throw new Error("Search request failed with status " + response.status); |
| 129 | + } |
| 130 | + return response.json(); |
| 131 | + }) |
| 132 | + .then(function(payload) { |
| 133 | + renderSearchResults(payload.results || []); |
| 134 | + rewriteSearchResultLinks(); |
| 135 | + }) |
| 136 | + .catch(function(error) { |
| 137 | + console.warn("Documentation search request failed:", error); |
| 138 | + clearSearchResults(); |
| 139 | + }); |
| 140 | + } |
| 141 | + |
| 142 | + if (searchInputEl && resultsContainerEl) { |
| 143 | + searchInputEl.addEventListener("input", function(event) { |
| 144 | + var query = event.target.value.trim(); |
| 145 | + clearTimeout(searchDebounceTimer); |
| 146 | + searchDebounceTimer = setTimeout(function() { |
| 147 | + performSearch(query); |
| 148 | + }, 200); |
| 149 | + }); |
| 150 | + } |
75 | 151 |
|
76 | 152 | // Helper function to check if a link should be skipped during rewriting |
77 | 153 | function shouldSkipLink($link, originalHref) { |
|
0 commit comments