Skip to content

Commit 4443f11

Browse files
committed
Merge branch 'production' into max/dlp/prompt-logs-fast-follow
2 parents 732e3b9 + 9798e52 commit 4443f11

File tree

49 files changed

+1465
-76
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1465
-76
lines changed

public/__redirects

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1633,6 +1633,7 @@
16331633
/waf/reference/migration-guides/old-rate-limiting-deprecation/ /waf/reference/legacy/old-rate-limiting/upgrade/ 301
16341634
/waf/reference/migration-guides/waf-managed-rules-migration/ /waf/reference/legacy/old-waf-managed-rules/upgrade/ 301
16351635
/waf/reference/migration-guides/firewall-rules-to-custom-rules/ /waf/reference/legacy/firewall-rules-upgrade/ 301
1636+
/waf/managed-rules/handle-false-positives/ /waf/managed-rules/troubleshooting/ 301
16361637

16371638
# waiting-room
16381639
/waiting-room/how-to/mobile-traffic/ /waiting-room/how-to/json-response/ 301
118 KB
Loading
88.5 KB
Loading
75.1 KB
Loading
43.2 KB
Loading
252 KB
Loading

src/components/homepage/FeaturedContentSection.astro

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { z } from "astro:schema";
44
import { marked } from "marked";
55
66
import { CardGrid, Card, LinkCard } from "@astrojs/starlight/components";
7-
import Stream from "../Stream.astro";
7+
import YouTube from "../YouTube.astro";
88
99
const props = z.object({
1010
title: z.string(),
@@ -54,12 +54,7 @@ const { title, text, image, cards, imagePosition } = props.parse(Astro.props);
5454
title === "Developer Platform" ? (
5555
<div class="[&>article]:gap-0">
5656
<Card title="">
57-
<Stream
58-
id="d89f290431f98e551f2b1467f85d6561"
59-
title="foo"
60-
thumbnail="https://pub-d9bf66e086fb4b639107aa52105b49dd.r2.dev/cloudflare-stack.jpg"
61-
showMoreVideos={false}
62-
/>
57+
<YouTube id="FH5-m0aiO5g" />
6358

6459
<LinkCard
6560
title={"Explore our Developer Platform"}

src/components/overrides/Sidebar.astro

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,256 @@ const [product, module] = Astro.url.pathname.split("/").filter(Boolean);
1616
</span>
1717
</a>
1818

19+
<!-- Search Input -->
20+
<div class="relative">
21+
<input
22+
type="text"
23+
id="sidebar-search"
24+
placeholder="Search sidebar..."
25+
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-orange-500 dark:focus:border-orange-500 transition-colors duration-200"
26+
/>
27+
</div>
28+
29+
<!-- No Results Message -->
30+
<div
31+
id="sidebar-no-results"
32+
class="text-sm text-gray-500 dark:text-gray-400 text-center p-4 hidden"
33+
>
34+
No results found. Try a different search term, or use our <button id="global-search-link" class="text-blue-500 dark:text-orange-500 underline hover:no-underline cursor-pointer bg-transparent border-none p-0 font-inherit">global search</button>.
35+
</div>
36+
1937
<Default><slot /></Default>
2038

39+
<script>
40+
function initSidebarSearch() {
41+
const searchInput = document.getElementById('sidebar-search') as HTMLInputElement;
42+
const noResultsMessage = document.getElementById('sidebar-no-results') as HTMLElement;
43+
const globalSearchLink = document.getElementById('global-search-link') as HTMLButtonElement;
44+
const sidebarContent = document.querySelector('.sidebar-content');
45+
46+
if (!searchInput || !sidebarContent || !noResultsMessage || !globalSearchLink) return;
47+
48+
const originalState: Map<Element, boolean> = new Map();
49+
50+
// Store original state of details elements
51+
function storeOriginalState() {
52+
if (originalState.size === 0) {
53+
const detailsElements = sidebarContent!.querySelectorAll('details');
54+
detailsElements.forEach(details => {
55+
originalState.set(details, details.open);
56+
});
57+
}
58+
}
59+
60+
61+
// Check if text matches all search terms (for multi-word searches)
62+
function matchesAllTerms(text: string, searchTerms: string[]): boolean {
63+
const lowerText = text.toLowerCase();
64+
return searchTerms.every(term => lowerText.includes(term));
65+
}
66+
67+
// Show only direct children of a folder (not recursive)
68+
function showDirectChildren(details: HTMLDetailsElement) {
69+
details.open = true;
70+
const directList = details.querySelector(':scope > ul');
71+
if (directList) {
72+
const directChildren = directList.querySelectorAll(':scope > li');
73+
directChildren.forEach(child => {
74+
(child as HTMLElement).style.display = '';
75+
});
76+
}
77+
}
78+
79+
// Show parent chain for a specific item
80+
function showParentChain(element: Element) {
81+
let parent = element.parentElement;
82+
while (parent && parent !== sidebarContent) {
83+
if (parent.tagName === 'LI') {
84+
(parent as HTMLElement).style.display = '';
85+
}
86+
if (parent.tagName === 'DETAILS') {
87+
(parent as HTMLDetailsElement).open = true;
88+
}
89+
parent = parent.parentElement;
90+
}
91+
}
92+
93+
// Filter sidebar items based on search query
94+
function filterSidebarItems(query: string) {
95+
const items = sidebarContent!.querySelectorAll('li');
96+
const detailsElements = sidebarContent!.querySelectorAll('details');
97+
98+
if (!query.trim()) {
99+
// Reset to original state
100+
items.forEach(item => {
101+
(item as HTMLElement).style.display = '';
102+
});
103+
104+
// Restore original details state
105+
detailsElements.forEach(details => {
106+
const originalOpen = originalState.get(details);
107+
if (originalOpen !== undefined) {
108+
(details as HTMLDetailsElement).open = originalOpen;
109+
}
110+
});
111+
112+
// Hide no results message
113+
noResultsMessage.classList.add('hidden');
114+
115+
return;
116+
}
117+
118+
// Split search query into terms for more precise matching
119+
const searchTerms = query.toLowerCase().split(/\s+/).filter(term => term.length > 0);
120+
121+
// First, hide all items and close all details
122+
items.forEach(item => {
123+
(item as HTMLElement).style.display = 'none';
124+
});
125+
detailsElements.forEach(details => {
126+
(details as HTMLDetailsElement).open = false;
127+
});
128+
129+
// Track what we've matched to avoid duplicates
130+
const matchedItems = new Set<Element>();
131+
132+
// 1. Check for folder/subfolder matches first (highest priority)
133+
detailsElements.forEach(details => {
134+
const summary = details.querySelector('summary');
135+
if (summary) {
136+
const summaryText = summary.textContent || '';
137+
138+
if (matchesAllTerms(summaryText, searchTerms)) {
139+
// This is a folder match - show the folder and its direct children
140+
const parentLi = details.closest('li');
141+
if (parentLi && !matchedItems.has(parentLi)) {
142+
(parentLi as HTMLElement).style.display = '';
143+
showDirectChildren(details);
144+
showParentChain(parentLi);
145+
matchedItems.add(parentLi);
146+
}
147+
}
148+
}
149+
});
150+
151+
// 2. Check for specific page matches (show page + parent chain)
152+
items.forEach(item => {
153+
if (matchedItems.has(item)) return; // Skip if already matched as folder
154+
155+
const link = item.querySelector('a');
156+
const summary = item.querySelector('summary');
157+
158+
// Skip if this is a folder (has summary) - those are handled above
159+
if (summary) return;
160+
161+
if (link) {
162+
const linkText = link.textContent || '';
163+
164+
if (matchesAllTerms(linkText, searchTerms)) {
165+
// This is a specific page match - show page + parent chain
166+
(item as HTMLElement).style.display = '';
167+
showParentChain(item);
168+
matchedItems.add(item);
169+
}
170+
}
171+
});
172+
173+
// 3. Fallback: if no exact matches, show partial matches (less specific)
174+
if (matchedItems.size === 0) {
175+
items.forEach(item => {
176+
const textContent = item.textContent?.toLowerCase() || '';
177+
const link = item.querySelector('a');
178+
const linkText = link?.textContent?.toLowerCase() || '';
179+
const summary = item.querySelector('summary');
180+
const summaryText = summary?.textContent?.toLowerCase() || '';
181+
182+
// Check if any search term is found (partial matching)
183+
const hasPartialMatch = searchTerms.some(term =>
184+
textContent.includes(term) || linkText.includes(term) || summaryText.includes(term)
185+
);
186+
187+
if (hasPartialMatch) {
188+
(item as HTMLElement).style.display = '';
189+
190+
// If it's a folder, show direct children only
191+
if (summary) {
192+
const details = item.querySelector('details');
193+
if (details) {
194+
showDirectChildren(details);
195+
}
196+
}
197+
198+
showParentChain(item);
199+
matchedItems.add(item);
200+
}
201+
});
202+
}
203+
204+
// Show/hide no results message based on matches
205+
if (matchedItems.size === 0) {
206+
noResultsMessage.classList.remove('hidden');
207+
} else {
208+
noResultsMessage.classList.add('hidden');
209+
}
210+
}
211+
212+
// Event listeners
213+
searchInput.addEventListener('input', (e) => {
214+
storeOriginalState();
215+
const query = (e.target as HTMLInputElement).value;
216+
filterSidebarItems(query);
217+
});
218+
219+
// Clear search on Escape key
220+
searchInput.addEventListener('keydown', (e) => {
221+
if (e.key === 'Escape') {
222+
searchInput.value = '';
223+
filterSidebarItems('');
224+
}
225+
});
226+
227+
// Global search link click handler
228+
globalSearchLink.addEventListener('click', () => {
229+
const currentQuery = searchInput.value.trim();
230+
if (currentQuery) {
231+
// Try multiple selectors for DocSearch
232+
const docSearchButton = document.querySelector('#docsearch button') as HTMLButtonElement ||
233+
document.querySelector('.DocSearch-Button') as HTMLButtonElement ||
234+
document.querySelector('[data-docsearch-button]') as HTMLButtonElement;
235+
236+
if (docSearchButton) {
237+
// Click the DocSearch button to open the modal
238+
docSearchButton.click();
239+
240+
// Wait for modal to open and set the search term
241+
setTimeout(() => {
242+
const searchInput = document.querySelector('.DocSearch-Input') as HTMLInputElement ||
243+
document.querySelector('#docsearch-input') as HTMLInputElement ||
244+
document.querySelector('[data-docsearch-input]') as HTMLInputElement;
245+
246+
if (searchInput) {
247+
searchInput.value = currentQuery;
248+
searchInput.focus();
249+
// Trigger search
250+
searchInput.dispatchEvent(new Event('input', { bubbles: true }));
251+
}
252+
}, 100);
253+
}
254+
}
255+
});
256+
}
257+
258+
// Initialize when DOM is loaded
259+
if (document.readyState === 'loading') {
260+
document.addEventListener('DOMContentLoaded', initSidebarSearch);
261+
} else {
262+
initSidebarSearch();
263+
}
264+
265+
// Re-initialize on navigation (for SPA-like behavior)
266+
initSidebarSearch();
267+
</script>
268+
21269
<style is:global>
22270
:root {
23271
.sidebar-content {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
pcx_content_type: navigation
3+
title: MCP server portals
4+
tags:
5+
- MCP
6+
sidebar:
7+
order: 101
8+
external_link: /cloudflare-one/applications/configure-apps/mcp-servers/mcp-portals/
9+
description: Centralize multiple MCP servers onto a single endpoint and customize the tools, prompts, and resources available to users.
10+
11+
---

0 commit comments

Comments
 (0)