mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-06 18:31:00 +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 { useRouter } from 'next/router';
|
||||||
import useWindowWidth from '@/hooks/useWindowWidth';
|
import useWindowWidth from '@/hooks/useWindowWidth';
|
||||||
import { useNDKContext } from '@/context/NDKContext';
|
import { useNDKContext } from '@/context/NDKContext';
|
||||||
|
import { ProgressSpinner } from 'primereact/progressspinner';
|
||||||
|
|
||||||
const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
|
const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
|
||||||
const { searchContent, searchResults: contentResults } = useContentSearch();
|
const { searchContent, searchResults: contentResults } = useContentSearch();
|
||||||
@ -26,8 +27,10 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
|
|||||||
];
|
];
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [searchResults, setSearchResults] = useState([]);
|
const [searchResults, setSearchResults] = useState([]);
|
||||||
|
const [isSearching, setIsSearching] = useState(false);
|
||||||
const op = useRef(null);
|
const op = useRef(null);
|
||||||
const { ndk, reInitializeNDK } = useNDKContext();
|
const { ndk, reInitializeNDK } = useNDKContext();
|
||||||
|
const searchTimeout = useRef(null);
|
||||||
|
|
||||||
const selectedOptionTemplate = (option, props) => {
|
const selectedOptionTemplate = (option, props) => {
|
||||||
if (isDesktopNav) {
|
if (isDesktopNav) {
|
||||||
@ -54,13 +57,28 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
|
|||||||
const term = e.target.value;
|
const term = e.target.value;
|
||||||
setSearchTerm(term);
|
setSearchTerm(term);
|
||||||
|
|
||||||
|
// 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') {
|
if (selectedSearchOption.code === 'content') {
|
||||||
searchContent(term);
|
searchContent(term);
|
||||||
setSearchResults(contentResults);
|
|
||||||
} else if (selectedSearchOption.code === 'community' && ndk) {
|
} else if (selectedSearchOption.code === 'community' && ndk) {
|
||||||
searchCommunity(term);
|
searchCommunity(term);
|
||||||
setSearchResults(communityResults);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
setIsSearching(false);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
if (!isMobileSearch && term.length > 2) {
|
if (!isMobileSearch && term.length > 2) {
|
||||||
op.current.show(e);
|
op.current.show(e);
|
||||||
@ -75,7 +93,21 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
|
|||||||
} else if (selectedSearchOption.code === 'community') {
|
} else if (selectedSearchOption.code === 'community') {
|
||||||
setSearchResults(communityResults);
|
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(() => {
|
useEffect(() => {
|
||||||
const handleError = event => {
|
const handleError = event => {
|
||||||
@ -115,6 +147,7 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
|
|||||||
searchContent('');
|
searchContent('');
|
||||||
searchCommunity('');
|
searchCommunity('');
|
||||||
setSearchResults([]);
|
setSearchResults([]);
|
||||||
|
setIsSearching(false);
|
||||||
|
|
||||||
if (op.current) {
|
if (op.current) {
|
||||||
op.current.hide();
|
op.current.hide();
|
||||||
@ -134,6 +167,16 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderSearchResults = () => {
|
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) {
|
if (searchResults.length === 0 && searchTerm.length > 2) {
|
||||||
return <div className="p-4 text-center text-gray-400">No results found</div>;
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`${isDesktopNav ? 'w-full max-w-md' : 'w-full'}`}>
|
<div className={`${isDesktopNav ? 'w-full max-w-md' : 'w-full'}`}>
|
||||||
@ -185,7 +243,7 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
value={selectedSearchOption}
|
value={selectedSearchOption}
|
||||||
onChange={e => setSelectedSearchOption(e.value)}
|
onChange={handleSearchOptionChange}
|
||||||
options={searchOptions}
|
options={searchOptions}
|
||||||
optionLabel="name"
|
optionLabel="name"
|
||||||
dropdownIcon={<i className="pi pi-chevron-down text-gray-400 ml-1" />}
|
dropdownIcon={<i className="pi pi-chevron-down text-gray-400 ml-1" />}
|
||||||
@ -227,7 +285,7 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
|
|||||||
{searchOptions.map(option => (
|
{searchOptions.map(option => (
|
||||||
<button
|
<button
|
||||||
key={option.code}
|
key={option.code}
|
||||||
onClick={() => setSelectedSearchOption(option)}
|
onClick={() => handleSearchOptionChange({ value: option })}
|
||||||
className={`flex items-center gap-2 px-4 py-2 rounded-full ${
|
className={`flex items-center gap-2 px-4 py-2 rounded-full ${
|
||||||
selectedSearchOption.code === option.code
|
selectedSearchOption.code === option.code
|
||||||
? 'bg-gray-700 text-white'
|
? 'bg-gray-700 text-white'
|
||||||
@ -253,7 +311,7 @@ const SearchBar = ({ isMobileSearch, isDesktopNav, onCloseSearch }) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
value={selectedSearchOption}
|
value={selectedSearchOption}
|
||||||
onChange={e => setSelectedSearchOption(e.value)}
|
onChange={handleSearchOptionChange}
|
||||||
options={searchOptions}
|
options={searchOptions}
|
||||||
optionLabel="name"
|
optionLabel="name"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
|
@ -26,25 +26,46 @@ export const useCommunitySearch = () => {
|
|||||||
|
|
||||||
const lowercaseTerm = term.toLowerCase();
|
const lowercaseTerm = term.toLowerCase();
|
||||||
|
|
||||||
|
// Discord search
|
||||||
const filteredDiscord = (discordData || [])
|
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' }));
|
.map(message => ({ ...message, type: 'discord' }));
|
||||||
|
|
||||||
|
// Nostr search
|
||||||
const filteredNostr = (nostrData || [])
|
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' }));
|
.map(message => ({ ...message, type: 'nostr' }));
|
||||||
|
|
||||||
|
// StackerNews search
|
||||||
const filteredStackerNews = (stackerNewsData || [])
|
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' }));
|
.map(item => ({ ...item, type: 'stackernews' }));
|
||||||
|
|
||||||
|
// Combine and sort the results
|
||||||
const combinedResults = [...filteredDiscord, ...filteredNostr, ...filteredStackerNews].sort(
|
const combinedResults = [...filteredDiscord, ...filteredNostr, ...filteredStackerNews].sort(
|
||||||
(a, b) => {
|
(a, b) => {
|
||||||
const dateA =
|
// Get timestamps in a consistent format (milliseconds)
|
||||||
a.type === 'nostr' ? a.created_at * 1000 : new Date(a.timestamp || a.createdAt);
|
const getTimestamp = item => {
|
||||||
const dateB =
|
if (item.type === 'nostr') {
|
||||||
b.type === 'nostr' ? b.created_at * 1000 : new Date(b.timestamp || b.createdAt);
|
return item.created_at * 1000;
|
||||||
return dateB - dateA;
|
} 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 events = await ndk.fetchEvents(filter);
|
||||||
|
|
||||||
const parsedEvents = new Set();
|
const parsedEvents = [];
|
||||||
events.forEach(event => {
|
events.forEach(event => {
|
||||||
let parsed;
|
let parsed;
|
||||||
if (event.kind === 30004) {
|
if (event.kind === 30004) {
|
||||||
@ -28,7 +28,7 @@ export const useContentSearch = () => {
|
|||||||
} else {
|
} else {
|
||||||
parsed = parseEvent(event);
|
parsed = parseEvent(event);
|
||||||
}
|
}
|
||||||
parsedEvents.add(parsed);
|
parsedEvents.push(parsed);
|
||||||
});
|
});
|
||||||
setAllContent(parsedEvents);
|
setAllContent(parsedEvents);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -44,17 +44,25 @@ export const useContentSearch = () => {
|
|||||||
|
|
||||||
const searchContent = term => {
|
const searchContent = term => {
|
||||||
if (term.length > 2) {
|
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();
|
const searchableTitle = (content?.title || content?.name || '').toLowerCase();
|
||||||
|
if (searchableTitle.includes(searchTerm)) return true;
|
||||||
|
|
||||||
|
// Search in summary/description
|
||||||
const searchableDescription = (
|
const searchableDescription = (
|
||||||
content?.summary ||
|
content?.summary ||
|
||||||
content?.description ||
|
content?.description ||
|
||||||
''
|
''
|
||||||
).toLowerCase();
|
).toLowerCase();
|
||||||
const searchTerm = term.toLowerCase();
|
if (searchableDescription.includes(searchTerm)) return true;
|
||||||
|
|
||||||
return searchableTitle.includes(searchTerm) || searchableDescription.includes(searchTerm);
|
// Search in topics/tags
|
||||||
|
const topics = content?.topics || [];
|
||||||
|
return topics.some(topic => topic.toLowerCase().includes(searchTerm));
|
||||||
});
|
});
|
||||||
|
|
||||||
setSearchResults(filtered);
|
setSearchResults(filtered);
|
||||||
} else {
|
} else {
|
||||||
setSearchResults([]);
|
setSearchResults([]);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user