import './search.css'; import { forwardRef } from 'preact/compat'; import { useEffect, useImperativeHandle, useRef, useState } from 'preact/hooks'; 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'; 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 [statusResults, setStatusResults] = useState([]); const [accountResults, setAccountResults] = useState([]); const [hashtagResults, setHashtagResults] = useState([]); useEffect(() => { // searchFieldRef.current?.focus?.(); // searchFormRef.current?.focus?.(); if (q) { // searchFieldRef.current.value = q; searchFormRef.current?.setValue?.(q); setUiState('loading'); (async () => { const results = await masto.v2.search({ q, limit: type ? 40 : 5, resolve: authenticated, type, }); console.log(results); setStatusResults(results.statuses); setAccountResults(results.accounts); setHashtagResults(results.hashtags); setUiState('default'); })(); } }, [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 && uiState !== 'loading' ? ( <> {(!type || type === 'accounts') && ( <> {type !== 'accounts' && (

Accounts

)} {accountResults.length > 0 ? ( <>
    {accountResults.map((account) => (
  • ))}
{type !== 'accounts' && (
See more accounts
)} ) : (

No accounts found.

)} )} {(!type || type === 'hashtags') && ( <> {type !== 'hashtags' && (

Hashtags

)} {hashtagResults.length > 0 ? ( <> {type !== 'hashtags' && (
See more hashtags
)} ) : (

No hashtags found.

)} )} {(!type || type === 'statuses') && ( <> {type !== 'statuses' && (

Posts

)} {statusResults.length > 0 ? ( <>
    {statusResults.map((status) => (
  • ))}
{type !== 'statuses' && (
See more posts
)} ) : (

No posts found.

)} )} ) : 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 (
{ e.preventDefault(); if (query) { setSearchParams({ q: query, }); } else { setSearchParams({}); } }} > { if (!e.target.value) { setSearchParams({}); } }} onInput={(e) => { setQuery(e.target.value); setSearchMenuOpen(true); }} onFocus={() => { setSearchMenuOpen(true); }} onBlur={() => { setTimeout(() => { setSearchMenuOpen(false); }, 100); formRef.current ?.querySelector('.search-popover-item.focus') ?.classList.remove('focus'); }} onKeyDown={(e) => { const { key } = e; switch (key) { case 'Escape': setSearchMenuOpen(false); break; case 'Down': case 'ArrowDown': e.preventDefault(); if (searchMenuOpen) { const focusItem = formRef.current.querySelector( '.search-popover-item.focus', ); if (focusItem) { let nextItem = focusItem.nextElementSibling; while (nextItem && nextItem.hidden) { nextItem = nextItem.nextElementSibling; } if (nextItem) { nextItem.classList.add('focus'); const siblings = Array.from( nextItem.parentElement.children, ).filter((el) => el !== nextItem); siblings.forEach((el) => { el.classList.remove('focus'); }); } } else { const firstItem = formRef.current.querySelector( '.search-popover-item', ); if (firstItem) { firstItem.classList.add('focus'); } } } break; case 'Up': case 'ArrowUp': e.preventDefault(); if (searchMenuOpen) { const focusItem = document.querySelector( '.search-popover-item.focus', ); if (focusItem) { let prevItem = focusItem.previousElementSibling; while (prevItem && prevItem.hidden) { prevItem = prevItem.previousElementSibling; } if (prevItem) { prevItem.classList.add('focus'); const siblings = Array.from( prevItem.parentElement.children, ).filter((el) => el !== prevItem); siblings.forEach((el) => { el.classList.remove('focus'); }); } } else { const lastItem = document.querySelector( '.search-popover-item:last-child', ); if (lastItem) { lastItem.classList.add('focus'); } } } break; case 'Enter': if (searchMenuOpen) { const focusItem = document.querySelector( '.search-popover-item.focus', ); if (focusItem) { e.preventDefault(); focusItem.click(); } setSearchMenuOpen(false); } break; } }} />
); });