import { useTranslation } from "react-i18next";
import { useEffect, useRef, useState } from "react";
import "./searchBox.css";
import {
ConfigService,
CollectionUtil,
} from "../../assets/lib/kookit-extra-browser.min";
import ConfigUtil from "../../utils/file/configUtil";
import BookUtil from "../../utils/file/bookUtil";
import { useAppStore } from "../../store/useAppStore";
export interface SearchBoxProps {
isNavSearch?: boolean;
mode?: string;
width?: string;
height?: string;
handleNavSearchState?: (state: string) => void;
handleSearchList?: (searchList: any) => void;
}
const collectionUtil = new CollectionUtil(ConfigService);
const SearchBox = (props: SearchBoxProps) => {
const htmlBook = useAppStore((s) => s.reader.htmlBook);
const isNavLocked = useAppStore((s) => s.reader.isNavLocked);
const isSearch = useAppStore((s) => s.manager.isSearch);
const isReading = useAppStore((s) => s.book.isReading);
const tabMode = useAppStore((s) => s.sidebar.mode);
const handleSearchResults = useAppStore((s) => s.handleSearchResults);
const handleSearch = useAppStore((s) => s.handleSearch);
const { t } = useTranslation();
const searchBoxRef = useRef<HTMLInputElement>(null);
const [isFocused, setIsFocused] = useState(false);
useEffect(() => {
if (props.isNavSearch) {
let searchBox: any = document.querySelector(".header-search-box");
searchBox && searchBox.focus();
}
}, []);
const handleGetSearchResults = async (keyword: string) => {
let results =
tabMode === "note"
? await ConfigUtil.searchNotesByKeyword(keyword, "", "note")
: tabMode === "highlight"
? await ConfigUtil.searchNotesByKeyword(keyword, "", "highlight")
: await BookUtil.searchBooksByKeyword(keyword);
if (tabMode !== "note" && tabMode !== "highlight") {
let lowerKeyword = keyword.toLowerCase();
collectionUtil.getCollectionList().forEach((collection) => {
if (collectionUtil.isCollectionDeleted(collection.key)) return;
if (collection.name.toLowerCase().includes(lowerKeyword)) {
results.push({ key: collection.key, name: collection.name });
}
});
}
let deletedBookKeys = ConfigService.getAllListConfig("deletedBooks");
results = results.filter((result: any) => {
return !deletedBookKeys.includes(
result[
tabMode === "note" || tabMode === "highlight"
? "bookKey"
: "key"
]
);
});
return results;
};
const search = async (q: string) => {
props.handleNavSearchState?.("searching");
let searchList = await htmlBook.rendition.doSearch(q);
props.handleNavSearchState?.("pending");
props.handleSearchList?.(
searchList.map((item: any) => {
const regex = new RegExp(
q.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
"gi"
);
item.excerpt = item.excerpt.replace(
regex,
`<span class="content-search-text">$&</span>`
);
return item;
})
);
props.handleNavSearchState?.("done");
};
const handleMouse = async () => {
let value = searchBoxRef.current?.value || "";
if (props.isNavSearch) {
value && search(value);
}
setIsFocused(false);
if (props.mode === "nav") {
props.handleNavSearchState?.("searching");
}
let keyword = (
document.querySelector(".header-search-box") as HTMLInputElement
).value.toLowerCase();
let results = await handleGetSearchResults(keyword);
if (results) {
handleSearchResults(results);
handleSearch(true);
if (props.mode === "nav") {
props.handleNavSearchState?.("done");
}
}
};
const handleKey = async (event: any) => {
if (event.keyCode !== 13) {
return;
}
let value = searchBoxRef.current?.value || "";
if (props.isNavSearch || isReading) {
value && search(value);
}
setIsFocused(false);
if (event && event.keyCode === 13) {
let keyword = event.target.value.toLowerCase();
let results = await handleGetSearchResults(keyword);
if (results) {
handleSearchResults(results);
handleSearch(true);
if (props.mode === "nav") {
props.handleNavSearchState?.("done");
}
}
}
};
const handleCancel = () => {
if (props.isNavSearch) {
props.handleSearchList?.(null);
props.handleNavSearchState?.("done");
}
handleSearch(false);
(document.querySelector(".header-search-box") as HTMLInputElement).value =
"";
};
return (
<div style={{ position: "relative" }}>
<input
type="text"
ref={searchBoxRef}
className="header-search-box"
onKeyDown={(event) => {
handleKey(event);
}}
onFocus={() => {
setIsFocused(true);
if (props.mode === "nav") {
props.handleNavSearchState?.("focused");
}
}}
placeholder={
props.isNavSearch || props.mode === "nav"
? t("Search in the Book")
: tabMode === "note"
? t("Search my notes")
: tabMode === "highlight"
? t("Search my highlights")
: t("Search my library")
}
style={
props.mode === "nav"
? {
width: props.width,
height: props.height,
paddingRight: "30px",
}
: {}
}
onCompositionStart={() => {
if (props.mode === "nav") {
props.handleNavSearchState?.("focused");
}
if (isNavLocked) {
return;
} else {
ConfigService.setReaderConfig("isTempLocked", "yes");
ConfigService.setReaderConfig("isNavLocked", "yes");
}
}}
onCompositionEnd={() => {
if (ConfigService.getReaderConfig("isTempLocked") === "yes") {
ConfigService.setReaderConfig("isNavLocked", "");
ConfigService.setReaderConfig("isTempLocked", "");
}
}}
/>
{isSearch && !isFocused ? (
<span
className="header-search-text"
onClick={() => {
handleCancel();
}}
style={props.mode === "nav" ? { right: "-9px", top: "14px" } : {}}
>
<span className="icon-close theme-color-delete"></span>
</span>
) : (
<span
className="header-search-text"
onClick={() => {
handleMouse();
}}
>
<span
className="icon-search header-search-icon"
style={props.mode === "nav" ? { right: "5px" } : {}}
></span>
</span>
)}
</div>
);
};
export default SearchBox;