Skip to content

Commit 4ababfe

Browse files
cdervclaude
andcommitted
Activate tabs with search matches when page loads with ?q= parameter
After highlight() adds <mark> tags to matching text, the new activateTabsWithMatches() function detects marks inside inactive Bootstrap tab panes and activates the first one. If the already-active tab in a tabset contains a match, no switch occurs. Includes test fixture for manual and future Playwright testing. Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent 440b654 commit 4ababfe

4 files changed

Lines changed: 105 additions & 0 deletions

File tree

src/resources/projects/website/search/quarto-search.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ window.document.addEventListener("DOMContentLoaded", function (_event) {
4747
// perform any highlighting
4848
highlight(escapeRegExp(query), mainEl);
4949

50+
// activate tabs that contain highlighted matches
51+
activateTabsWithMatches(mainEl);
52+
5053
// fix up the URL to remove the q query param
5154
const replacementUrl = new URL(window.location);
5255
replacementUrl.searchParams.delete(kQueryArg);
@@ -1112,6 +1115,47 @@ function escapeRegExp(string) {
11121115
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
11131116
}
11141117

1118+
// After search highlighting, activate any tabs whose panes contain <mark> matches.
1119+
// This ensures that search results inside inactive Bootstrap tabs become visible.
1120+
// Only switches tabs when no match is already visible in the active tab of that tabset.
1121+
function activateTabsWithMatches(mainEl) {
1122+
if (typeof bootstrap === "undefined") return;
1123+
1124+
const marks = mainEl.querySelectorAll("mark");
1125+
if (marks.length === 0) return;
1126+
1127+
// Group marks by their parent tabset (.tab-content container)
1128+
const tabsetMatches = new Map();
1129+
for (const mark of marks) {
1130+
const pane = mark.closest(".tab-pane");
1131+
if (!pane) continue;
1132+
const tabContent = pane.parentElement;
1133+
if (!tabContent || !tabContent.classList.contains("tab-content")) continue;
1134+
1135+
if (!tabsetMatches.has(tabContent)) {
1136+
tabsetMatches.set(tabContent, { activeHasMatch: false, firstInactivePane: null });
1137+
}
1138+
const info = tabsetMatches.get(tabContent);
1139+
if (pane.classList.contains("active")) {
1140+
info.activeHasMatch = true;
1141+
} else if (!info.firstInactivePane) {
1142+
info.firstInactivePane = pane;
1143+
}
1144+
}
1145+
1146+
// For each tabset, only activate if the active tab has no match
1147+
for (const [, info] of tabsetMatches) {
1148+
if (info.activeHasMatch || !info.firstInactivePane) continue;
1149+
1150+
const tabButton = mainEl.querySelector(
1151+
`[data-bs-toggle="tab"][data-bs-target="#${info.firstInactivePane.id}"]`
1152+
);
1153+
if (tabButton) {
1154+
new bootstrap.Tab(tabButton).show();
1155+
}
1156+
}
1157+
}
1158+
11151159
// highlight matches
11161160
function highlight(term, el) {
11171161
const termRegex = new RegExp(term, "ig");
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/.quarto/
2+
/_site/
3+
**/*.quarto_ipynb
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
project:
2+
type: website
3+
website:
4+
title: "Search Tab Test"
5+
search: true
6+
navbar:
7+
left:
8+
- href: index.qmd
9+
text: Home
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
title: "Search Tab Test"
3+
---
4+
5+
## Plain Section
6+
7+
This section contains epsilon-no-tabs content outside of any tabset.
8+
9+
## Ungrouped Tabset
10+
11+
::: {.panel-tabset}
12+
13+
### Tab Alpha
14+
15+
This tab contains alpha-visible-content that is in the default active tab.
16+
17+
### Tab Beta
18+
19+
This tab contains beta-unique-search-term that is only in this inactive tab.
20+
21+
:::
22+
23+
## Both Tabs Match
24+
25+
::: {.panel-tabset}
26+
27+
### R
28+
29+
This tab contains gamma-both-tabs content in the active R tab.
30+
31+
### Python
32+
33+
This tab also contains gamma-both-tabs content in the inactive Python tab.
34+
35+
:::
36+
37+
## Grouped Tabset
38+
39+
::: {.panel-tabset group="language"}
40+
41+
### R
42+
43+
This tab contains delta-r-only-term that is only in the R tab.
44+
45+
### Python
46+
47+
This tab contains python-only-content in the Python tab.
48+
49+
:::

0 commit comments

Comments
 (0)