Skip to content

Commit c75897e

Browse files
docs: implement deep re-routing, pagination, and migration banner (#2664)
## Description **1. Version Deep Re-routing** * Upgraded the version dropdown menu with deep-routing JavaScript. * When a user switches versions, the script now dynamically strips the base URL and preserves their current page path, seamlessly dropping them onto the exact same page in the selected version (or displays 404 error is the same page doesn't exist in the destination version). **2. Pagination** * **Pagination:** Implemented pagination across the docs to improve navigation and readability for longer lists/sections. **3. Banner** * **Migration Banner:** Added a global migration banner to Github Pages docsite with a redirection URL ## PR Checklist > Thank you for opening a Pull Request! Before submitting your PR, there are a > few things you can do to make sure it goes smoothly: - [ ] Make sure you reviewed [CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md) - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [ ] Ensure the tests and linter pass - [ ] Code coverage does not decrease (if any source code was changed) - [ ] Appropriate docs were updated (if necessary) - [ ] Make sure to add `!` if this involve a breaking change 🛠️ Fixes #<issue_number_goes_here> --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent f12c4bf commit c75897e

File tree

8 files changed

+322
-5
lines changed

8 files changed

+322
-5
lines changed

.hugo/hugo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ ignoreFiles = ["quickstart/shared", "quickstart/python", "quickstart/js", "quick
3737
github_subdir = "docs"
3838
offlineSearch = true
3939
version_menu = "Releases"
40+
releases_url = "/genai-toolbox/releases.releases"
41+
global_logo_url = "/genai-toolbox/"
4042
[params.ui]
4143
ul_show = 100
4244
showLightDarkModeMenu = true

.hugo/layouts/docs/section.html

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{{ define "main" }}
2+
<div class="td-content">
3+
<h1>{{ .Title }}</h1>
4+
{{ with .Params.description }}<div class="lead">{{ . | markdownify }}</div>{{ end }}
5+
<header class="article-meta">
6+
{{ partial "taxonomy_terms_article_wrapper.html" . }}
7+
{{ if (and (not .Params.hide_readingtime) (.Site.Params.ui.readingtime.enable)) }}
8+
{{ partial "reading-time.html" . }}
9+
{{ end }}
10+
</header>
11+
{{ .Content }}
12+
{{ partial "section-index.html" . }}
13+
{{ partial "pager.html" . }}
14+
{{ if (and (not .Params.hide_feedback) (.Site.Params.ui.feedback.enable) (.Site.GoogleAnalytics)) }}
15+
{{ partial "feedback.html" .Site.Params.ui.feedback }}
16+
<br />
17+
{{ end }}
18+
{{ if (.Site.Params.DisqusShortname) }}
19+
<br />
20+
{{ partial "disqus-comment.html" . }}
21+
{{ end }}
22+
{{ partial "page-meta-lastmod.html" . }}
23+
</div>
24+
{{ end }}

.hugo/layouts/docs/single.html

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{{ define "main" }}
2+
<div class="td-content">
3+
<h1>{{ .Title }}</h1>
4+
{{ with .Params.description }}<div class="lead">{{ . | markdownify }}</div>{{ end }}
5+
<header class="article-meta">
6+
{{ partial "taxonomy_terms_article_wrapper.html" . }}
7+
{{ if (and (not .Params.hide_readingtime) (.Site.Params.ui.readingtime.enable)) }}
8+
{{ partial "reading-time.html" . }}
9+
{{ end }}
10+
</header>
11+
{{ .Content }}
12+
{{ partial "section-index.html" . }}
13+
{{ partial "pager.html" . }}
14+
{{ if (and (not .Params.hide_feedback) (.Site.Params.ui.feedback.enable) (.Site.GoogleAnalytics)) }}
15+
{{ partial "feedback.html" .Site.Params.ui.feedback }}
16+
<br />
17+
{{ end }}
18+
{{ if (.Site.Params.DisqusShortname) }}
19+
<br />
20+
{{ partial "disqus-comment.html" . }}
21+
{{ end }}
22+
{{ partial "page-meta-lastmod.html" . }}
23+
</div>
24+
{{ end }}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
document.addEventListener("DOMContentLoaded", function() {
3+
const logoLink = document.querySelector('.navbar-brand');
4+
if (logoLink) {
5+
logoLink.href = "{{ .Site.Params.global_logo_url | default "/" }}";
6+
}
7+
});
8+
</script>
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
<script src='{{ .Site.BaseURL }}js/w3.js' type="application/x-javascript"></script>
1+
<script src='{{ "js/w3.js" | relURL }}'></script>
2+
{{ if not .Site.Params.disableMigrationBanner }}
3+
<script src="{{ "js/migration-banner.js" | relURL }}"></script>
4+
{{ end }}

.hugo/layouts/partials/navbar-version-selector.html

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,66 @@
22
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
33
{{ .Site.Params.version_menu }}
44
</a>
5-
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
6-
<div w3-include-html="/genai-toolbox/releases.releases" w3-include-html-default='<a class="dropdown-item" href="/genai-toolbox/dev/">Dev</a>'></div>
5+
<div class="dropdown-menu" id="version-dropdown-menu" aria-labelledby="navbarDropdown">
6+
<div w3-include-html="{{ .Site.Params.releases_url }}" w3-include-html-default='<a class="dropdown-item" href="{{ .Site.Params.global_logo_url }}dev/">Dev</a>'></div>
7+
78
<script>
8-
// This must run after the w3.js script has loaded.
9-
w3.includeHTML();
9+
w3.includeHTML(function() {
10+
const basePath = "{{ site.BaseURL | relURL }}";
11+
12+
function stripBase(path) {
13+
if (path.startsWith(basePath)) return path.substring(basePath.length);
14+
if (basePath === "/" && path.startsWith("/")) return path.substring(1);
15+
return path.replace(/^\//, '');
16+
}
17+
18+
document.getElementById('version-dropdown-menu').addEventListener('click', function(e) {
19+
const link = e.target.closest('a.dropdown-item');
20+
if (!link) return;
21+
e.preventDefault();
22+
23+
let targetPath = link.pathname;
24+
if (!targetPath.endsWith('/')) targetPath += '/';
25+
26+
let cleanCurrentPath = stripBase(window.location.pathname);
27+
const allLinks = document.querySelectorAll('#version-dropdown-menu a.dropdown-item');
28+
let currentVersionPrefix = "";
29+
30+
allLinks.forEach(a => {
31+
let cleanVPath = stripBase(a.pathname);
32+
if (!cleanVPath.endsWith('/')) cleanVPath += '/';
33+
if (cleanVPath !== "" && cleanVPath !== "/" && cleanCurrentPath.startsWith(cleanVPath)) {
34+
if (cleanVPath.length > currentVersionPrefix.length) {
35+
currentVersionPrefix = cleanVPath;
36+
}
37+
}
38+
});
39+
40+
let deepPath = cleanCurrentPath;
41+
if (currentVersionPrefix !== "") {
42+
deepPath = cleanCurrentPath.substring(currentVersionPrefix.length);
43+
}
44+
deepPath = deepPath.replace(/^\//, '');
45+
46+
const fullTargetPath = targetPath + deepPath;
47+
48+
// Perform a HEAD request to check if the deep route exists
49+
fetch(fullTargetPath, { method: 'HEAD' })
50+
.then(response => {
51+
if (response.ok) {
52+
// Page exists! Redirect to deep path
53+
window.location.href = fullTargetPath;
54+
} else {
55+
// 404 or other error: Fallback to the version root
56+
window.location.href = targetPath;
57+
}
58+
})
59+
.catch(() => {
60+
// If the fetch fails entirely, fallback to root
61+
window.location.href = targetPath;
62+
});
63+
});
64+
});
1065
</script>
1166
</div>
1267
{{ end -}}

.hugo/layouts/partials/pager.html

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
{{ $curr := . }}
2+
{{ $next := "" }}
3+
{{ $prev := "" }}
4+
5+
{{/* 1. Calculate PREVIOUS */}}
6+
{{ if .Parent }}
7+
{{ $siblings := .Parent.Pages.ByWeight }}
8+
{{ $currIndex := -1 }}
9+
10+
{{ range $index, $page := $siblings }}
11+
{{ if eq $page.RelPermalink $curr.RelPermalink }}
12+
{{ $currIndex = $index }}
13+
{{ end }}
14+
{{ end }}
15+
16+
{{ if gt $currIndex 0 }}
17+
{{/* Preceding sibling (if it's a folder, this naturally points to its _index.md!) */}}
18+
{{ $prev = index $siblings (sub $currIndex 1) }}
19+
{{ else }}
20+
{{/* First child, so Previous points up to the Parent folder */}}
21+
{{ if ne .Parent.Type "home" }}
22+
{{ $prev = .Parent }}
23+
{{ end }}
24+
{{ end }}
25+
{{ end }}
26+
27+
28+
{{/* 2. Calculate NEXT */}}
29+
{{ if and .IsNode (gt (len .Pages) 0) }}
30+
{{/* If it's a folder with children, Next dives into the first child */}}
31+
{{ $next = index .Pages.ByWeight 0 }}
32+
{{ else }}
33+
{{/* Leaf page, or empty folder */}}
34+
{{ if .Parent }}
35+
{{ $siblings := .Parent.Pages.ByWeight }}
36+
{{ $currIndex := -1 }}
37+
38+
{{ range $index, $page := $siblings }}
39+
{{ if eq $page.RelPermalink $curr.RelPermalink }}
40+
{{ $currIndex = $index }}
41+
{{ end }}
42+
{{ end }}
43+
44+
{{ if lt $currIndex (sub (len $siblings) 1) }}
45+
{{/* Next sibling in the same folder */}}
46+
{{ $next = index $siblings (add $currIndex 1) }}
47+
{{ else }}
48+
{{/* Last item in folder, step out and find the Parent's next sibling */}}
49+
{{ $p := .Parent }}
50+
{{ $foundNext := false }}
51+
52+
{{/* Check up to 3 directory levels up to find the next section */}}
53+
{{ range seq 3 }}
54+
{{ if and (not $foundNext) $p }}
55+
{{ if $p.Parent }}
56+
{{ $pSiblings := $p.Parent.Pages.ByWeight }}
57+
{{ $pIndex := -1 }}
58+
{{ range $index, $page := $pSiblings }}
59+
{{ if eq $page.RelPermalink $p.RelPermalink }}
60+
{{ $pIndex = $index }}
61+
{{ end }}
62+
{{ end }}
63+
64+
{{ if and (ge $pIndex 0) (lt $pIndex (sub (len $pSiblings) 1)) }}
65+
{{ $next = index $pSiblings (add $pIndex 1) }}
66+
{{ $foundNext = true }}
67+
{{ else }}
68+
{{ $p = $p.Parent }}
69+
{{ end }}
70+
{{ else }}
71+
{{ $p = false }}
72+
{{ end }}
73+
{{ end }}
74+
{{ end }}
75+
76+
{{ end }}
77+
{{ end }}
78+
{{ end }}
79+
80+
<!-- 3. Render the Buttons -->
81+
{{ if or $prev $next }}
82+
<nav class="mt-5 pt-4 border-top d-flex justify-content-between" aria-label="Page navigation">
83+
84+
<div>
85+
{{ with $prev }}
86+
<a href="{{ .RelPermalink }}" class="text-decoration-none">
87+
<small class="text-muted d-block mb-1">&laquo; Previous</small>
88+
<span class="text-body">{{ .Title }}</span>
89+
</a>
90+
{{ end }}
91+
</div>
92+
93+
<div class="text-end">
94+
{{ with $next }}
95+
<a href="{{ .RelPermalink }}" class="text-decoration-none">
96+
<small class="text-muted d-block mb-1">Next &raquo;</small>
97+
<span class="text-body">{{ .Title }}</span>
98+
</a>
99+
{{ end }}
100+
</div>
101+
102+
</nav>
103+
{{ end }}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
document.addEventListener('DOMContentLoaded', function() {
2+
3+
// Setup CSS for the wrapper and the banner
4+
var styleTag = document.createElement('style');
5+
styleTag.innerHTML = `
6+
.td-navbar .dropdown-menu {
7+
z-index: 9999 !important;
8+
}
9+
10+
.theme-banner-wrapper {
11+
position: sticky;
12+
z-index: 20;
13+
padding-top: 15px; /* This is your gap! */
14+
padding-bottom: 5px; /* Breathing room below the banner */
15+
/* Uses Bootstrap's native body background variable, with white as fallback */
16+
background-color: var(--bs-body-bg, #ffffff);
17+
}
18+
19+
.theme-migration-banner {
20+
background-color: #ebf3fc;
21+
border: 1px solid #80a7e9;
22+
color: #1c3a6b;
23+
border-radius: 4px;
24+
padding: 15px;
25+
text-align: center;
26+
width: 100%;
27+
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
28+
}
29+
30+
.theme-migration-banner a {
31+
color: #4484f4;
32+
text-decoration: underline;
33+
font-weight: bold;
34+
}
35+
36+
/* DARK MODE STYLING */
37+
html[data-bs-theme="dark"] .theme-banner-wrapper,
38+
body.dark .theme-banner-wrapper,
39+
html.dark-mode .theme-banner-wrapper {
40+
/* Uses Docsy's dark mode background fallback if var fails */
41+
background-color: var(--bs-body-bg, #20252b);
42+
}
43+
44+
html[data-bs-theme="dark"] .theme-migration-banner,
45+
body.dark .theme-migration-banner,
46+
html.dark-mode .theme-migration-banner {
47+
background-color: #1a273b;
48+
color: #e6efff;
49+
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
50+
}
51+
52+
html[data-bs-theme="dark"] .theme-migration-banner a,
53+
body.dark .theme-migration-banner a,
54+
html.dark-mode .theme-migration-banner a {
55+
color: #80a7e9;
56+
}
57+
58+
/* Fallback for OS-level dark mode */
59+
@media (prefers-color-scheme: dark) {
60+
html:not([data-bs-theme="light"]):not(.light) .theme-banner-wrapper {
61+
background-color: var(--bs-body-bg, #20252b);
62+
}
63+
html:not([data-bs-theme="light"]):not(.light) .theme-migration-banner {
64+
background-color: #1a273b;
65+
color: #e6efff;
66+
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
67+
}
68+
html:not([data-bs-theme="light"]):not(.light) .theme-migration-banner a {
69+
color: #80a7e9;
70+
}
71+
}
72+
`;
73+
document.head.appendChild(styleTag);
74+
75+
// Create the Wrapper
76+
var wrapper = document.createElement('div');
77+
wrapper.id = 'migration-banner-wrapper';
78+
wrapper.className = 'theme-banner-wrapper';
79+
80+
// Create the Banner
81+
var banner = document.createElement('div');
82+
banner.className = 'theme-migration-banner';
83+
banner.innerHTML = '⚠️ <strong>Archived Docs:</strong> Visit <a href="https://mcp-toolbox.dev/">mcp-toolbox.dev</a> for the latest version.';
84+
wrapper.appendChild(banner);
85+
86+
// Inject the wrapper into the center information column
87+
var contentArea = document.querySelector('.td-content') || document.querySelector('main');
88+
if (contentArea) {
89+
contentArea.prepend(wrapper);
90+
} else {
91+
console.warn("Could not find the main content column to inject the banner.");
92+
}
93+
94+
// Calculate navbar height synchronously to correctly offset the sticky wrapper
95+
var navbar = document.querySelector('.td-navbar');
96+
var navbarHeight = navbar ? navbar.offsetHeight : 64;
97+
wrapper.style.top = navbarHeight + 'px';
98+
});

0 commit comments

Comments
 (0)