Skip to content

Commit 595cfea

Browse files
committed
Add generate raw content and catalog content json
(cherry picked from commit f0e5fab) Add in the ability to dynamically tune the host pointer for dev-portal
1 parent 22de2d5 commit 595cfea

6 files changed

Lines changed: 754 additions & 27 deletions

File tree

_config.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ description: >- # this means to ignore newlines until "baseurl:"
2525
baseurl: "/documentation" # the subpath of your site, e.g. /blog
2626
url: "https://developers.procore.com" # the base hostname & protocol for your site, e.g. http://example.com
2727

28+
# Base URL of the dev-portal API that powers documentation search.
29+
# Override at build/serve time with the DEV_PORTAL_BASE_URL environment variable:
30+
# DEV_PORTAL_BASE_URL=http://localhost:3001 bundle exec jekyll serve --port 4005
31+
# In production (GitHub Pages / CI) this default is used automatically.
32+
dev_portal_base_url: "https://developers.procore.com"
33+
2834
# Build settings
2935
theme: minima
3036
plugins:

_layouts/default.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,11 @@ <h1>
6464
</main>
6565
</section>
6666
</article>
67-
<script src="https://unpkg.com/simple-jekyll-search@latest/dest/simple-jekyll-search.min.js"></script>
67+
<script>
68+
// Injected at Jekyll build/serve time from site.dev_portal_base_url
69+
// (set in _config.yml or overridden via the DEV_PORTAL_BASE_URL env var).
70+
window.DEV_PORTAL_BASE_URL = "{{ site.dev_portal_base_url | escape }}";
71+
</script>
6872
<script src="{{ 'assets/js/nav.js' | relative_url }}"></script>
6973
</body>
7074
</html>

_plugins/env_config.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# frozen_string_literal: true
2+
3+
# env_config.rb
4+
#
5+
# Reads select environment variables at Jekyll bootstrap time and merges them
6+
# into the site configuration, allowing build-time overrides without touching
7+
# _config.yml.
8+
#
9+
# Supported variables
10+
# -------------------
11+
# DEV_PORTAL_BASE_URL – Base URL of the dev-portal API used for search.
12+
# Defaults to the value in _config.yml
13+
# (https://developers.procore.com).
14+
#
15+
# Usage (local development)
16+
# DEV_PORTAL_BASE_URL=http://localhost:3001 bundle exec jekyll serve --port 4005
17+
#
18+
# Usage (CI / GitHub Actions)
19+
# Add `DEV_PORTAL_BASE_URL: ${{ vars.DEV_PORTAL_BASE_URL }}` to the env block
20+
# of the build step and Jekyll will pick it up automatically.
21+
22+
Jekyll::Hooks.register :site, :after_init do |site|
23+
if (base_url = ENV['DEV_PORTAL_BASE_URL'].to_s.strip) && !base_url.empty?
24+
Jekyll.logger.info "env_config:", "DEV_PORTAL_BASE_URL=#{base_url}"
25+
site.config['dev_portal_base_url'] = base_url
26+
end
27+
end

assets/js/nav.js

Lines changed: 102 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -45,33 +45,109 @@
4545
$(this).closest(".collapsible").find(".col_content").slideToggle("350");
4646
$(this).find(".icon").toggleClass(["fa-square-minus", "fa-square-plus"]);
4747
});
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];
7156
}
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+
}
75151

76152
// Helper function to check if a link should be skipped during rewriting
77153
function shouldSkipLink($link, originalHref) {

0 commit comments

Comments
 (0)