import './search.css';
import { forwardRef } from 'preact/compat';
import {
useEffect,
useImperativeHandle,
useLayoutEffect,
useRef,
useState,
} from 'preact/hooks';
import { InView } from 'react-intersection-observer';
import { useParams, useSearchParams } from 'react-router-dom';
import AccountBlock from '../components/account-block';
import Icon from '../components/icon';
import Link from '../components/link';
import Loader from '../components/loader';
import NavMenu from '../components/nav-menu';
import Status from '../components/status';
import { api } from '../utils/api';
import useTitle from '../utils/useTitle';
const SHORT_LIMIT = 5;
const LIMIT = 40;
function Search(props) {
const params = useParams();
const { masto, instance, authenticated } = api({
instance: params.instance,
});
const [uiState, setUiState] = useState('default');
const [searchParams] = useSearchParams();
const searchFormRef = useRef();
const q = props?.query || searchParams.get('q');
const type = props?.type || searchParams.get('type');
useTitle(
q
? `Search: ${q}${
type
? ` (${
{
statuses: 'Posts',
accounts: 'Accounts',
hashtags: 'Hashtags',
}[type]
})`
: ''
}`
: 'Search',
`/search`,
);
const [showMore, setShowMore] = useState(false);
const offsetRef = useRef(0);
useEffect(() => {
offsetRef.current = 0;
}, [type]);
const scrollableRef = useRef();
useLayoutEffect(() => {
scrollableRef.current?.scrollTo?.(0, 0);
}, [q, type]);
const [statusResults, setStatusResults] = useState([]);
const [accountResults, setAccountResults] = useState([]);
const [hashtagResults, setHashtagResults] = useState([]);
function loadResults(firstLoad) {
setUiState('loading');
if (firstLoad && !type) {
setStatusResults(statusResults.slice(0, SHORT_LIMIT));
setAccountResults(accountResults.slice(0, SHORT_LIMIT));
setHashtagResults(hashtagResults.slice(0, SHORT_LIMIT));
}
(async () => {
const params = {
q,
resolve: authenticated,
limit: SHORT_LIMIT,
};
if (type) {
params.limit = LIMIT;
params.type = type;
params.offset = offsetRef.current;
}
try {
const results = await masto.v2.search(params);
console.log(results);
if (type && !firstLoad) {
if (type === 'statuses') {
setStatusResults((prev) => [...prev, ...results.statuses]);
} else if (type === 'accounts') {
setAccountResults((prev) => [...prev, ...results.accounts]);
} else if (type === 'hashtags') {
setHashtagResults((prev) => [...prev, ...results.hashtags]);
}
offsetRef.current = offsetRef.current + LIMIT;
setShowMore(!!results[type]?.length);
} else {
setStatusResults(results.statuses);
setAccountResults(results.accounts);
setHashtagResults(results.hashtags);
}
setUiState('default');
} catch (err) {
console.error(err);
setUiState('error');
}
})();
}
useEffect(() => {
// searchFieldRef.current?.focus?.();
// searchFormRef.current?.focus?.();
if (q) {
// searchFieldRef.current.value = q;
searchFormRef.current?.setValue?.(q);
loadResults(true);
}
}, [q, type, instance]);
return (
{!!q && (
{!!type && ‹ All}
{[
{
label: 'Accounts',
type: 'accounts',
to: `/search?q=${q}&type=accounts`,
},
{
label: 'Hashtags',
type: 'hashtags',
to: `/search?q=${q}&type=hashtags`,
},
{
label: 'Posts',
type: 'statuses',
to: `/search?q=${q}&type=statuses`,
},
]
.sort((a, b) => {
if (a.type === type) return -1;
if (b.type === type) return 1;
return 0;
})
.map((link) => (
{link.label}
))}
)}
{!!q ? (
<>
{(!type || type === 'accounts') && (
<>
{type !== 'accounts' && (
)}
{accountResults.length > 0 ? (
<>
{accountResults.map((account) => (
-
))}
{type !== 'accounts' && (
See more accounts
)}
>
) : (
!type &&
(uiState === 'loading' ? (
) : (
No accounts found.
))
)}
>
)}
{(!type || type === 'hashtags') && (
<>
{type !== 'hashtags' && (
)}
{hashtagResults.length > 0 ? (
<>
{hashtagResults.map((hashtag) => (
-
{hashtag.name}
))}
{type !== 'hashtags' && (
See more hashtags
)}
>
) : (
!type &&
(uiState === 'loading' ? (
) : (
No hashtags found.
))
)}
>
)}
{(!type || type === 'statuses') && (
<>
{type !== 'statuses' && (
)}
{statusResults.length > 0 ? (
<>
{statusResults.map((status) => (
-
))}
{type !== 'statuses' && (
See more posts
)}
>
) : (
!type &&
(uiState === 'loading' ? (
) : (
No posts found.
))
)}
>
)}
{!!type &&
(uiState === 'default' ? (
showMore ? (
{
if (inView) {
loadResults();
}
}}
>
) : (
The end.
)
) : (
!!(
hashtagResults.length ||
accountResults.length ||
statusResults.length
) && (
)
))}
>
) : uiState === 'loading' ? (
) : (
Enter your search term or paste a URL above to get started.
)}
);
}
export default Search;
const SearchForm = forwardRef((props, ref) => {
const { instance } = api();
const [searchParams, setSearchParams] = useSearchParams();
const [searchMenuOpen, setSearchMenuOpen] = useState(false);
const [query, setQuery] = useState(searchParams.q || '');
const formRef = useRef(null);
const searchFieldRef = useRef(null);
useImperativeHandle(ref, () => ({
setValue: (value) => {
setQuery(value);
},
focus: () => {
searchFieldRef.current.focus();
},
}));
return (
);
});