Rebuild the site on the stack the other converted sites share: Vite 8, React 19, TypeScript 7, react-router 8, Biome and sanitize.css, with the site data moved out of the bundle into static-contents/. - base the database and pico code on the other converted sites, keeping this site's layout, pages and item fields - read file timestamps as Unix seconds in the file and license views - serve all module data from modules/, redirecting the old download and /database/file URLs - keep form controls on white - track page views with GA4
70 lines
2.6 KiB
TypeScript
70 lines
2.6 KiB
TypeScript
import { type ChangeEvent, type FormEvent, useEffect, useState } from 'react';
|
|
import { Link, useLocation, useNavigate } from 'react-router';
|
|
import Config, { type MultiLang } from '../../config';
|
|
import Functions from '../../functions';
|
|
import ItemUtil, { type KeywordSearchType } from '../lib/ItemUtil';
|
|
|
|
interface Props {
|
|
lang: MultiLang;
|
|
}
|
|
|
|
const SEARCH_PATH = '/database/search';
|
|
|
|
const Search = (props: Props) => {
|
|
const { lang } = props;
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
const [type, setType] = useState<KeywordSearchType>('all');
|
|
const [keyword, setKeyword] = useState('');
|
|
|
|
// Landing on the results page adopts the query it was called with; moving
|
|
// anywhere else leaves whatever the visitor has typed alone.
|
|
useEffect(() => {
|
|
if (location.pathname === SEARCH_PATH) {
|
|
const parsed = ItemUtil.getSearchKeywordByQuery(location.search);
|
|
setType(parsed.type);
|
|
setKeyword(parsed.keyword);
|
|
}
|
|
}, [location.pathname, location.search]);
|
|
|
|
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault();
|
|
void navigate(ItemUtil.getSearchByKeywordUrl(type, keyword));
|
|
};
|
|
|
|
const options = [
|
|
{ value: 'all', label: '[en]All[/en][ja]全て[/ja]' },
|
|
{ value: 'basic', label: '[en]Title & Keyword[/en][ja]タイトル&キーワード[/ja]' },
|
|
];
|
|
Config.XOONIPS_ITEMTYPES.forEach((itemType) => {
|
|
options.push({ value: itemType, label: Functions.pascalCase(itemType) });
|
|
});
|
|
return (
|
|
<form onSubmit={handleSubmit}>
|
|
<input
|
|
style={{ width: '170px' }}
|
|
type="text"
|
|
value={keyword}
|
|
onChange={(event: ChangeEvent<HTMLInputElement>) => setKeyword(event.target.value.trim())}
|
|
/>
|
|
|
|
<select
|
|
value={type}
|
|
onChange={(event: ChangeEvent<HTMLSelectElement>) => setType(event.target.value as KeywordSearchType)}
|
|
>
|
|
{options.map((option) => (
|
|
<option key={option.value} value={option.value}>
|
|
{Functions.mlang(option.label, lang)}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<br />
|
|
<input className="formButton" type="submit" value="Search" />
|
|
|
|
<Link to="/database/advanced">{Functions.mlang('[en]Advanced[/en][ja]詳細検索[/ja]', lang)}</Link>
|
|
</form>
|
|
);
|
|
};
|
|
|
|
export default Search;
|