/* This file is part of the Notesnook project (https://notesnook.com/) Copyright (C) 2023 Streetwriters (Private) Limited This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ import { FlashList } from "@shopify/flash-list"; import React, { useEffect, useRef, useState } from "react"; import { Platform, StatusBar, Text, TextInput, TouchableOpacity, View, useWindowDimensions } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import Icon from "react-native-vector-icons/MaterialCommunityIcons"; import { db } from "../app/common/database"; import { getElevation } from "../app/utils"; import { initDatabase, useShareStore } from "./store"; const ListItem = ({ item, mode, close }) => { const colors = useShareStore((state) => state.colors); const selectedNotebooks = useShareStore((state) => state.selectedNotebooks); const selectedTags = useShareStore((state) => state.selectedTags); const isSelected = mode === "appendNote" ? false : mode === "selectTags" ? selectedTags.indexOf(item.alias) > -1 : selectedNotebooks.findIndex( (selected) => selected.id === item.id && selected.type === item.type ) > -1; const SearchSetters = React.useMemo( () => ({ appendNote: (item) => { useShareStore.getState().setAppendNote(item); close(); }, selectNotebooks: (item) => { const selectedNotebooks = [ ...useShareStore.getState().selectedNotebooks ]; const currentIndex = selectedNotebooks.findIndex( (selected) => selected.id === item.id && selected.type === item.type ); if (currentIndex === -1) { selectedNotebooks.push(item); } else { selectedNotebooks.splice(currentIndex, 1); } useShareStore.getState().setSelectedNotebooks(selectedNotebooks); }, selectTags: (item) => { const selectedTags = [...useShareStore.getState().selectedTags]; const currentIndex = selectedTags.indexOf(item.alias); if (currentIndex === -1) { selectedTags.push(item.alias); } else { selectedTags.splice(currentIndex, 1); } useShareStore.getState().setSelectedTags(selectedTags); } }), [close] ); const set = React.useMemo(() => SearchSetters[mode], [mode, SearchSetters]); const onSelectItem = async (item) => { if (item.locked) { return; } set(item); }; return ( onSelectItem(item)} style={{ flexDirection: "column", borderBottomWidth: item.topics?.length > 0 ? 0 : 1, borderBottomColor: colors.nav, justifyContent: "center", paddingVertical: 12 }} > {item.type !== "note" ? ( ) : null} {item.type === "tag" ? "#" : ""} {item.alias || item.title} {item.type === "note" ? ( {item.headline} ) : null} {item.type === "notebook" && item.topics && item.topics.length > 0 ? ( {item.topics.map((topic) => ( ))} ) : null} ); }; const SearchGetters = { appendNote: () => db.notes.all, selectNotebooks: () => db.notebooks.all, selectTags: () => { const selected = useShareStore.getState().selectedTags; const tags = []; if (selected) tags.push( ...selected.map((t) => ({ alias: t, type: "tag" })) ); for (let tag of db.tags.all) { const index = tags.findIndex((t) => t.alias === tag.alias); // Skip selected tags as they are already in the list. if (index > -1) continue; tags.push(tag); } return tags; } }; const SearchLookup = { appendNote: (items, kwd) => db.lookup.notes(items, kwd), selectNotebooks: (items, kwd) => db.lookup.notebooks(items, kwd), selectTags: (items, kwd) => db.lookup.tags(items, kwd) }; export const Search = ({ close, getKeyboardHeight, quicknote, mode }) => { const colors = useShareStore((state) => state.colors); const { height } = useWindowDimensions(); const timer = useRef(null); const inputRef = useRef(); const [searchResults, setSearchResults] = useState([]); const searchableItems = useRef(null); const searchKeyword = useRef(null); const insets = Platform.OS === "android" ? { top: StatusBar.currentHeight } : // eslint-disable-next-line react-hooks/rules-of-hooks useSafeAreaInsets(); const get = SearchGetters[mode]; const lookup = SearchLookup[mode]; const onSearch = async () => { if (!searchableItems.current) { await initDatabase(); searchableItems.current = get(); } if (timer.current) { clearTimeout(timer.current); timer.current = null; } timer.current = setTimeout(async () => { if (!searchKeyword.current) { setSearchResults([]); setSearchResults(get()); return; } setSearchResults( await lookup(searchableItems.current, searchKeyword.current) ); }, 150); }; useEffect(() => { (async () => { await initDatabase(); searchableItems.current = get(); setSearchResults(searchableItems.current); })(); }, [get]); const renderItem = ({ item }) => !item.locked ? : null; let extra = quicknote ? { marginTop: -insets.top, paddingTop: insets.top } : {}; const searchHeight = height - getKeyboardHeight(); return ( { if (mode === "selectTags") { searchKeyword.current = db.tags.sanitize(value); } else { searchKeyword.current = value; } onSearch(); }} onSubmitEditing={onSearch} /> 550 ? 550 : searchHeight, height: searchHeight > 550 ? 550 : searchHeight }} > 0 && !searchResults.find( (item) => item.title === searchKeyword.current ))) ? ( ) : null } ListFooterComponent={} ListEmptyComponent={ {searchKeyword.current ? `No results found for "${searchKeyword.current}"` : "Search for a note to append to it."} } /> ); };