fixes for search and character matching and the state updates around it in the searchbar

This commit is contained in:
austinkelsay 2025-04-27 12:10:08 -05:00
parent 387c93fe0c
commit 4c73fc3725
No known key found for this signature in database
GPG Key ID: 5A763922E5BA08EE
3 changed files with 111 additions and 24 deletions

View File

@ -9,6 +9,7 @@ import { useCommunitySearch } from '@/hooks/useCommunitySearch';
import { useRouter } from 'next/router';
import useWindowWidth from '@/hooks/useWindowWidth';
import { useNDKContext } from '@/context/NDKContext';
import { ProgressSpinner } from 'primereact/progressspinner';
const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
const { searchContent, searchResults: contentResults } = useContentSearch();
@ -26,8 +27,10 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
];
const [searchTerm, setSearchTerm] = useState('');
const [searchResults, setSearchResults] = useState([]);
const [isSearching, setIsSearching] = useState(false);
const op = useRef(null);
const { ndk, reInitializeNDK } = useNDKContext();
const searchTimeout = useRef(null);
const selectedOptionTemplate = (option, props) => {
if (isDesktopNav) {
@ -54,14 +57,29 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
const term = e.target.value;
setSearchTerm(term);
if (selectedSearchOption.code === 'content') {
searchContent(term);
setSearchResults(contentResults);
} else if (selectedSearchOption.code === 'community' && ndk) {
searchCommunity(term);
setSearchResults(communityResults);
// Clear any existing timeout to avoid unnecessary API calls
if (searchTimeout.current) {
clearTimeout(searchTimeout.current);
}
// Set loading state if term length is sufficient
if (term.length > 2) {
setIsSearching(true);
}
// Set a timeout to avoid searching on each keystroke
searchTimeout.current = setTimeout(() => {
if (term.length > 2) {
if (selectedSearchOption.code === 'content') {
searchContent(term);
} else if (selectedSearchOption.code === 'community' && ndk) {
searchCommunity(term);
}
} else {
setIsSearching(false);
}
}, 300);
if (!isMobileSearch && term.length > 2) {
op.current.show(e);
} else if (!isMobileSearch) {
@ -75,7 +93,21 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
} else if (selectedSearchOption.code === 'community') {
setSearchResults(communityResults);
}
}, [selectedSearchOption, contentResults, communityResults]);
// Once we have results, set isSearching to false
if (searchTerm.length > 2) {
setIsSearching(false);
}
}, [selectedSearchOption, contentResults, communityResults, searchTerm]);
// Cleanup the timeout on component unmount
useEffect(() => {
return () => {
if (searchTimeout.current) {
clearTimeout(searchTimeout.current);
}
};
}, []);
useEffect(() => {
const handleError = event => {
@ -115,6 +147,7 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
searchContent('');
searchCommunity('');
setSearchResults([]);
setIsSearching(false);
if (op.current) {
op.current.hide();
@ -134,6 +167,16 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
};
const renderSearchResults = () => {
// Show loading spinner while searching
if (isSearching) {
return (
<div className="flex items-center justify-center p-6">
<ProgressSpinner style={{ width: '50px', height: '50px' }} strokeWidth="4" />
</div>
);
}
// Show no results message
if (searchResults.length === 0 && searchTerm.length > 2) {
return <div className="p-4 text-center text-gray-400">No results found</div>;
}
@ -147,6 +190,21 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
);
};
// When search option changes, trigger search with current term
const handleSearchOptionChange = e => {
setSelectedSearchOption(e.value);
// If there's a search term, run the search again with the new option
if (searchTerm.length > 2) {
setIsSearching(true);
if (e.value.code === 'content') {
searchContent(searchTerm);
} else if (e.value.code === 'community' && ndk) {
searchCommunity(searchTerm);
}
}
};
return (
<>
<div className={`${isDesktopNav ? 'w-full max-w-md' : 'w-full'}`}>
@ -185,7 +243,7 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
},
}}
value={selectedSearchOption}
onChange={e => setSelectedSearchOption(e.value)}
onChange={handleSearchOptionChange}
options={searchOptions}
optionLabel="name"
dropdownIcon={<i className="pi pi-chevron-down text-gray-400 ml-1" />}
@ -227,7 +285,7 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
{searchOptions.map(option => (
<button
key={option.code}
onClick={() => setSelectedSearchOption(option)}
onClick={() => handleSearchOptionChange({ value: option })}
className={`flex items-center gap-2 px-4 py-2 rounded-full ${
selectedSearchOption.code === option.code
? 'bg-gray-700 text-white'
@ -253,7 +311,7 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
},
}}
value={selectedSearchOption}
onChange={e => setSelectedSearchOption(e.value)}
onChange={handleSearchOptionChange}
options={searchOptions}
optionLabel="name"
placeholder="Search"

View File

@ -26,25 +26,46 @@ export const useCommunitySearch = () => {
const lowercaseTerm = term.toLowerCase();
// Discord search
const filteredDiscord = (discordData || [])
.filter(message => message.content.toLowerCase().includes(lowercaseTerm))
.filter(message => {
if (!message.content) return false;
return message.content.toLowerCase().includes(lowercaseTerm);
})
.map(message => ({ ...message, type: 'discord' }));
// Nostr search
const filteredNostr = (nostrData || [])
.filter(message => message.content.toLowerCase().includes(lowercaseTerm))
.filter(message => {
if (!message.content) return false;
return message.content.toLowerCase().includes(lowercaseTerm);
})
.map(message => ({ ...message, type: 'nostr' }));
// StackerNews search
const filteredStackerNews = (stackerNewsData || [])
.filter(item => item.title.toLowerCase().includes(lowercaseTerm))
.filter(item => {
if (!item.title) return false;
return item.title.toLowerCase().includes(lowercaseTerm);
})
.map(item => ({ ...item, type: 'stackernews' }));
// Combine and sort the results
const combinedResults = [...filteredDiscord, ...filteredNostr, ...filteredStackerNews].sort(
(a, b) => {
const dateA =
a.type === 'nostr' ? a.created_at * 1000 : new Date(a.timestamp || a.createdAt);
const dateB =
b.type === 'nostr' ? b.created_at * 1000 : new Date(b.timestamp || b.createdAt);
return dateB - dateA;
// Get timestamps in a consistent format (milliseconds)
const getTimestamp = item => {
if (item.type === 'nostr') {
return item.created_at * 1000;
} else if (item.type === 'discord') {
return new Date(item.timestamp).getTime();
} else if (item.type === 'stackernews') {
return new Date(item.createdAt).getTime();
}
return 0;
};
return getTimestamp(b) - getTimestamp(a);
}
);

View File

@ -20,7 +20,7 @@ export const useContentSearch = () => {
};
const events = await ndk.fetchEvents(filter);
const parsedEvents = new Set();
const parsedEvents = [];
events.forEach(event => {
let parsed;
if (event.kind === 30004) {
@ -28,7 +28,7 @@ export const useContentSearch = () => {
} else {
parsed = parseEvent(event);
}
parsedEvents.add(parsed);
parsedEvents.push(parsed);
});
setAllContent(parsedEvents);
} catch (error) {
@ -44,17 +44,25 @@ export const useContentSearch = () => {
const searchContent = term => {
if (term.length > 2) {
const filtered = Array.from(allContent).filter(content => {
const searchTerm = term.toLowerCase();
const filtered = allContent.filter(content => {
// Search in title/name
const searchableTitle = (content?.title || content?.name || '').toLowerCase();
if (searchableTitle.includes(searchTerm)) return true;
// Search in summary/description
const searchableDescription = (
content?.summary ||
content?.description ||
''
).toLowerCase();
const searchTerm = term.toLowerCase();
return searchableTitle.includes(searchTerm) || searchableDescription.includes(searchTerm);
if (searchableDescription.includes(searchTerm)) return true;
// Search in topics/tags
const topics = content?.topics || [];
return topics.some(topic => topic.toLowerCase().includes(searchTerm));
});
setSearchResults(filtered);
} else {
setSearchResults([]);