Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 53 additions & 53 deletions src/theme/SearchBar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import {
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import { createPortal } from 'react-dom';
import translations from '@theme/SearchTranslations';
import {useAskAI} from '@site/src/hooks/useAskAI'
import { useAskAI } from '@site/src/hooks/useAskAI'
import { shouldPreventSearchAction, handleSearchKeyboardConflict } from './utils/aiConflictHandler';
import { initializeSearchAnalytics, createEnhancedSearchClient } from './utils/searchAnalytics';
import { useDocSearchModal } from './utils/useDocSearchModal';
import {
createSearchParameters,
createSearchNavigator,
transformSearchItems
import {
createSearchParameters,
createSearchNavigator,
transformSearchItems
} from './utils/searchConfig';
import { SearchHit } from './searchHit';
import { SearchResultsFooter } from './searchResultsFooter';
Expand All @@ -31,10 +31,10 @@ function DocSearch({ contextualSearch, externalUrlRegex, ...props }) {
const { isAskAIOpen } = useAskAI();
const history = useHistory();
const searchButtonRef = useRef(null);

const [selectedDocTypes, setSelectedDocTypes] = useState(null);
const searchParametersRef = useRef(null);

const {
isOpen,
initialQuery,
Expand All @@ -49,12 +49,12 @@ function DocSearch({ contextualSearch, externalUrlRegex, ...props }) {
// Update searchParameters ref instead of creating new object
useEffect(() => {
const newParams = createSearchParameters(
props,
contextualSearch,
props,
contextualSearch,
contextualSearchFacetFilters,
selectedDocTypes
);

if (!searchParametersRef.current) {
searchParametersRef.current = newParams;
} else {
Expand All @@ -67,8 +67,8 @@ function DocSearch({ contextualSearch, externalUrlRegex, ...props }) {
// Initialize on mount
if (!searchParametersRef.current) {
searchParametersRef.current = createSearchParameters(
props,
contextualSearch,
props,
contextualSearch,
contextualSearchFacetFilters,
selectedDocTypes
);
Expand All @@ -77,14 +77,14 @@ function DocSearch({ contextualSearch, externalUrlRegex, ...props }) {
// Track input changes to capture the query
useEffect(() => {
if (!isOpen) return;

const handleInput = (e) => {
const input = e.target;
if (input.classList.contains('DocSearch-Input')) {
lastQueryRef.current = input.value;
}
};

document.addEventListener('input', handleInput, true);
return () => document.removeEventListener('input', handleInput, true);
}, [isOpen]);
Expand All @@ -94,15 +94,15 @@ function DocSearch({ contextualSearch, externalUrlRegex, ...props }) {
}, [props.appId, props.apiKey]);

const navigator = useMemo(
() => createSearchNavigator(history, externalUrlRegex),
[history, externalUrlRegex]
() => createSearchNavigator(history, externalUrlRegex, currentLocale),
[history, externalUrlRegex, currentLocale]
);

const transformItems = useCallback((items, state) => {
if (state?.query) {
lastQueryRef.current = state.query;
}

return transformSearchItems(items, {
transformItems: props.transformItems,
processSearchResultUrl,
Expand All @@ -111,34 +111,34 @@ function DocSearch({ contextualSearch, externalUrlRegex, ...props }) {
});
}, [props.transformItems, processSearchResultUrl, currentLocale]);

const handleDocTypeChange = useCallback((docTypes) => {
setSelectedDocTypes(docTypes);

// Re-trigger search with updated filters after state update completes
setTimeout(() => {
const input = document.querySelector('.DocSearch-Input');
const query = lastQueryRef.current;

if (input && query) {
// Access React's internal value setter to bypass readonly property
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
'value'
).set;

// Clear input to trigger change detection
nativeInputValueSetter.call(input, '');
input.dispatchEvent(new Event('input', { bubbles: true }));

// Restore original query to execute search with new filters
setTimeout(() => {
nativeInputValueSetter.call(input, query);
const handleDocTypeChange = useCallback((docTypes) => {
setSelectedDocTypes(docTypes);

// Re-trigger search with updated filters after state update completes
setTimeout(() => {
const input = document.querySelector('.DocSearch-Input');
const query = lastQueryRef.current;

if (input && query) {
// Access React's internal value setter to bypass readonly property
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
'value'
).set;

// Clear input to trigger change detection
nativeInputValueSetter.call(input, '');
input.dispatchEvent(new Event('input', { bubbles: true }));
input.focus();
}, 0);
}
}, 100);
}, []);

// Restore original query to execute search with new filters
setTimeout(() => {
nativeInputValueSetter.call(input, query);
input.dispatchEvent(new Event('input', { bubbles: true }));
input.focus();
}, 0);
}
}, 100);
}, []);

const resultsFooterComponent = useMemo(
() => (footerProps) => <SearchResultsFooter {...footerProps} onClose={onClose} />,
Expand All @@ -147,13 +147,13 @@ const handleDocTypeChange = useCallback((docTypes) => {

const transformSearchClient = useCallback((searchClient) => {
const enhancedClient = createEnhancedSearchClient(
searchClient,
siteMetadata.docusaurusVersion,
searchClient,
siteMetadata.docusaurusVersion,
queryIDRef
);

const originalSearch = enhancedClient.search.bind(enhancedClient);

let debounceTimeout;
enhancedClient.search = (...args) => {
return new Promise((resolve, reject) => {
Expand All @@ -165,7 +165,7 @@ const handleDocTypeChange = useCallback((docTypes) => {
}, 200);
});
};

return enhancedClient;
}, [siteMetadata.docusaurusVersion]);

Expand All @@ -190,7 +190,7 @@ const handleDocTypeChange = useCallback((docTypes) => {
onInput: handleOnInput,
searchButtonRef,
});

return (
<>
<Head>
Expand All @@ -214,7 +214,7 @@ const handleDocTypeChange = useCallback((docTypes) => {
DocSearchModal &&
searchContainer &&
createPortal(
<>
<>
<DocSearchModal
onClose={onClose}
initialScrollY={window.scrollY}
Expand All @@ -232,7 +232,7 @@ const handleDocTypeChange = useCallback((docTypes) => {
placeholder={translations.placeholder}
translations={translations.modal}
/>

<div style={{
position: 'fixed',
top: window.innerWidth < 768 ? '55px' : '120px',
Expand All @@ -241,7 +241,7 @@ const handleDocTypeChange = useCallback((docTypes) => {
backgroundColor: 'var(--docsearch-modal-background)',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
}}>
<DocTypeSelector
<DocTypeSelector
selectedDocTypes={selectedDocTypes}
onSelectionChange={handleDocTypeChange}
/>
Expand Down
66 changes: 57 additions & 9 deletions src/theme/SearchBar/searchHit.jsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,85 @@
import Link from '@docusaurus/Link';
import { trackSearchResultClick } from './utils/searchAnalytics';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';

export function SearchHit({ hit, children }) {
const { i18n: { currentLocale } } = useDocusaurusContext();
const handleClick = () => trackSearchResultClick(hit);


// Transform the URL to ensure it's correct
// This is a safety measure in case transformSearchItems doesn't work
let transformedUrl = hit.url;

try {
let pathname, hash;

// If it's an absolute URL, extract pathname and hash
if (hit.url.startsWith('http://') || hit.url.startsWith('https://')) {
const urlObj = new URL(hit.url);
pathname = urlObj.pathname;
hash = urlObj.hash;
} else {
// It's already a relative URL, split pathname and hash
const hashIndex = hit.url.indexOf('#');
if (hashIndex !== -1) {
pathname = hit.url.substring(0, hashIndex);
hash = hit.url.substring(hashIndex);
} else {
pathname = hit.url;
hash = '';
}
}

// Now transform the pathname
if (currentLocale !== 'en') {
const prefix = `/docs/${currentLocale}`;
if (pathname.startsWith(prefix)) {
transformedUrl = pathname.substring(prefix.length) || '/';
} else {
transformedUrl = pathname;
}
} else {
const prefix = '/docs';
if (pathname.startsWith(prefix)) {
transformedUrl = pathname.substring(prefix.length) || '/';
} else {
transformedUrl = pathname;
}
}

transformedUrl += hash;
} catch (e) {
// If transformation fails, use original URL
}

// Extract multiple URL segments after /docs/ and clean them up
const segments = hit.url.split('/docs/')[1]?.split('/').filter(Boolean) || [];
const breadcrumbs = segments
.slice(0, 3) // Take first 3 segments max
.map(segment => segment.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()));

// Format doc_type for display, stripping quotes and formatting
const formatDocType = (docType) => {
if (!docType) return null;
// Remove surrounding quotes and format
const cleaned = docType.replace(/^'|'$/g, '');
return cleaned.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
};

const docTypeDisplay = formatDocType(hit.doc_type);

return (
<Link onClick={handleClick} to={hit.url}>
<Link onClick={handleClick} to={transformedUrl}>
{children}
<div style={{
fontSize: '10px',
<div style={{
fontSize: '10px',
color: '#888',
lineHeight: '1',
marginBottom: '12px'
}}>
{/* Doc type badge */}
{docTypeDisplay && (
<span style={{
<span style={{
backgroundColor: '#f3f4f6',
color: '#374151',
padding: '2px 6px',
Expand All @@ -42,7 +90,7 @@ export function SearchHit({ hit, children }) {
{docTypeDisplay}
</span>
)}

{/* Breadcrumbs */}
{breadcrumbs.length > 0 && (
<span>{breadcrumbs.join(' › ')}</span>
Expand Down
Loading