mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-05 00:32:03 +00:00
fixes for search and character matching and the state updates around it in the searchbar
This commit is contained in:
parent
387c93fe0c
commit
4c73fc3725
@ -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"
|
||||
|
@ -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);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -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([]);
|
||||
|
Loading…
x
Reference in New Issue
Block a user