From a77986c0e69a017e0ad060dd1b6fa837b8399bde Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Mon, 2 Jun 2025 22:03:55 +0800 Subject: [PATCH] feat: add search functionality to the notebook (#1318) --- .../public/locales/ar/translation.json | 4 +- .../public/locales/de/translation.json | 4 +- .../public/locales/el/translation.json | 4 +- .../public/locales/es/translation.json | 4 +- .../public/locales/fr/translation.json | 4 +- .../public/locales/hi/translation.json | 4 +- .../public/locales/id/translation.json | 4 +- .../public/locales/it/translation.json | 4 +- .../public/locales/ja/translation.json | 4 +- .../public/locales/ko/translation.json | 4 +- .../public/locales/nl/translation.json | 4 +- .../public/locales/pl/translation.json | 4 +- .../public/locales/pt/translation.json | 4 +- .../public/locales/ru/translation.json | 4 +- .../public/locales/tr/translation.json | 4 +- .../public/locales/uk/translation.json | 4 +- .../public/locales/vi/translation.json | 4 +- .../public/locales/zh-CN/translation.json | 4 +- .../public/locales/zh-TW/translation.json | 4 +- .../app/reader/components/notebook/Header.tsx | 9 +- .../reader/components/notebook/Notebook.tsx | 113 ++++++++++--- .../reader/components/notebook/SearchBar.tsx | 153 ++++++++++++++++++ 22 files changed, 307 insertions(+), 44 deletions(-) create mode 100644 apps/readest-app/src/app/reader/components/notebook/SearchBar.tsx diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index 7c7e18ba..57ba8748 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -344,5 +344,7 @@ "Search in {{count}} Book(s)..._two": "بحث في كتابين...", "Search in {{count}} Book(s)..._few": "بحث في {{count}} كتب...", "Search in {{count}} Book(s)..._many": "بحث في {{count}} كتابًا...", - "Search in {{count}} Book(s)..._other": "بحث في {{count}} من الكتب..." + "Search in {{count}} Book(s)..._other": "بحث في {{count}} من الكتب...", + "No notes match your search": "لا توجد ملاحظات تطابق بحثك", + "Search notes and excerpts...": "بحث في الملاحظات والمقتطفات..." } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index aaed87d3..c4320321 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -336,5 +336,7 @@ "Enable only if you trust the file.": "Nur aktivieren, wenn Sie der Datei vertrauen.", "Sort TOC by Page": "TOC nach Seite sortieren", "Search in {{count}} Book(s)..._one": "Suche in {{count}} Buch...", - "Search in {{count}} Book(s)..._other": "Suche in {{count}} Büchern..." + "Search in {{count}} Book(s)..._other": "Suche in {{count}} Büchern...", + "No notes match your search": "Noch keine Notizen gefunden", + "Search notes and excerpts...": "Notizen und Auszüge suchen..." } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index 1120f222..c9e338dd 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -336,5 +336,7 @@ "Enable only if you trust the file.": "Ενεργοποιήστε μόνο εάν εμπιστεύεστε το αρχείο.", "Sort TOC by Page": "Ταξινόμηση TOC κατά σελίδα", "Search in {{count}} Book(s)..._one": "Αναζήτηση σε {{count}} Βιβλίο...", - "Search in {{count}} Book(s)..._other": "Αναζήτηση σε {{count}} Βιβλία..." + "Search in {{count}} Book(s)..._other": "Αναζήτηση σε {{count}} Βιβλία...", + "No notes match your search": "Καμία σημείωση δεν βρέθηκε", + "Search notes and excerpts...": "Αναζήτηση σημειώσεων..." } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index 08443f5b..c5ee4877 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -338,5 +338,7 @@ "Sort TOC by Page": "Ordenar TOC por página", "Search in {{count}} Book(s)..._one": "Buscar en {{count}} libro...", "Search in {{count}} Book(s)..._many": "Buscar en {{count}} libros...", - "Search in {{count}} Book(s)..._other": "Buscar en {{count}} libros..." + "Search in {{count}} Book(s)..._other": "Buscar en {{count}} libros...", + "No notes match your search": "No se encontraron notas", + "Search notes and excerpts...": "Buscar notas..." } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index 84b305f6..dca9492d 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -338,5 +338,7 @@ "Sort TOC by Page": "Tris la table des matières par page", "Search in {{count}} Book(s)..._one": "Rechercher dans {{count}} Livre...", "Search in {{count}} Book(s)..._many": "Rechercher dans {{count}} Livres...", - "Search in {{count}} Book(s)..._other": "Rechercher dans {{count}} Livres..." + "Search in {{count}} Book(s)..._other": "Rechercher dans {{count}} Livres...", + "No notes match your search": "Aucune note trouvée", + "Search notes and excerpts...": "Rechercher des notes..." } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index 781bf7b9..c866060a 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -336,5 +336,7 @@ "Enable only if you trust the file.": "केवल तभी सक्षम करें जब आप फ़ाइल पर भरोसा करते हों।", "Sort TOC by Page": "सामग्री को पृष्ठ द्वारा क्रमबद्ध करें", "Search in {{count}} Book(s)..._one": "{{count}} पुस्तक में खोजें...", - "Search in {{count}} Book(s)..._other": "{{count}} पुस्तकों में खोजें..." + "Search in {{count}} Book(s)..._other": "{{count}} पुस्तकों में खोजें...", + "No notes match your search": "कोई नोट्स नहीं मिले", + "Search notes and excerpts...": "नोट्स खोजें..." } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 351e069b..fcb881c3 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -334,5 +334,7 @@ "Allow JavaScript": "Izinkan JavaScript", "Enable only if you trust the file.": "Aktifkan hanya jika Anda mempercayai file tersebut.", "Sort TOC by Page": "Urutkan TOC berdasarkan Halaman", - "Search in {{count}} Book(s)..._other": "Cari di {{count}} Buku..." + "Search in {{count}} Book(s)..._other": "Cari di {{count}} Buku...", + "No notes match your search": "Tidak ada catatan ditemukan", + "Search notes and excerpts...": "Cari catatan..." } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index 4f7d6774..7047c4f2 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -338,5 +338,7 @@ "Sort TOC by Page": "Ordina sommario per pagina", "Search in {{count}} Book(s)..._one": "Cerca in {{count}} libro...", "Search in {{count}} Book(s)..._many": "Cerca in {{count}} libri...", - "Search in {{count}} Book(s)..._other": "Cerca in {{count}} libri..." + "Search in {{count}} Book(s)..._other": "Cerca in {{count}} libri...", + "No notes match your search": "Nessuna nota trovata", + "Search notes and excerpts...": "Cerca nelle note..." } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index a913438e..9531d9ee 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -334,5 +334,7 @@ "Allow JavaScript": "JavaScriptを許可", "Enable only if you trust the file.": "ファイルを信頼する場合にのみ有効にしてください。", "Sort TOC by Page": "目次をページで並べ替え", - "Search in {{count}} Book(s)..._other": "{{count}}冊の本を検索..." + "Search in {{count}} Book(s)..._other": "{{count}}冊の本を検索...", + "No notes match your search": "ノートが見つかりません", + "Search notes and excerpts...": "ノートを検索..." } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index ac1d531d..56318206 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -334,5 +334,7 @@ "Allow JavaScript": "JavaScript 허용", "Enable only if you trust the file.": "파일을 신뢰하는 경우에만 활성화하세요.", "Sort TOC by Page": "목차를 페이지별로 정렬", - "Search in {{count}} Book(s)..._other": "{{count}}권의 책에서 검색..." + "Search in {{count}} Book(s)..._other": "{{count}}권의 책에서 검색...", + "No notes match your search": "일치하는 노트가 없습니다", + "Search notes and excerpts...": "노트 검색..." } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index a74841b7..17700d46 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -336,5 +336,7 @@ "Enable only if you trust the file.": "Schakel alleen in als u het bestand vertrouwt.", "Sort TOC by Page": "__STRING_NOT_TRANSLATED__", "Search in {{count}} Book(s)..._one": "Zoeken in {{count}} Boek...", - "Search in {{count}} Book(s)..._other": "Zoeken in {{count}} Boeken..." + "Search in {{count}} Book(s)..._other": "Zoeken in {{count}} Boeken...", + "No notes match your search": "Geen notities gevonden", + "Search notes and excerpts...": "Zoek notities..." } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index 882be330..7cd81881 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -340,5 +340,7 @@ "Search in {{count}} Book(s)..._one": "Szukaj w {{count}} książce...", "Search in {{count}} Book(s)..._few": "Szukaj w {{count}} książkach...", "Search in {{count}} Book(s)..._many": "Szukaj w {{count}} książkach...", - "Search in {{count}} Book(s)..._other": "Szukaj w {{count}} książkach..." + "Search in {{count}} Book(s)..._other": "Szukaj w {{count}} książkach...", + "No notes match your search": "Nie znaleziono notatek", + "Search notes and excerpts...": "Szukaj notatek..." } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index 23c50cad..01998efd 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -338,5 +338,7 @@ "Sort TOC by Page": "Ordenar TOC por Página", "Search in {{count}} Book(s)..._one": "Pesquisar em {{count}} Livro...", "Search in {{count}} Book(s)..._many": "Pesquisar em {{count}} Livros...", - "Search in {{count}} Book(s)..._other": "Pesquisar em {{count}} Livros..." + "Search in {{count}} Book(s)..._other": "Pesquisar em {{count}} Livros...", + "No notes match your search": "Nenhuma nota encontrada", + "Search notes and excerpts...": "Buscar notas..." } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index 3c3e98cd..3dd01d91 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -340,5 +340,7 @@ "Search in {{count}} Book(s)..._one": "Поиск в {{count}} книге...", "Search in {{count}} Book(s)..._few": "Поиск в {{count}} книгах...", "Search in {{count}} Book(s)..._many": "Поиск в {{count}} книгах...", - "Search in {{count}} Book(s)..._other": "Поиск в {{count}} книгах..." + "Search in {{count}} Book(s)..._other": "Поиск в {{count}} книгах...", + "No notes match your search": "Записи не найдены", + "Search notes and excerpts...": "Искать записи..." } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index b88d455d..8fe0929e 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -336,5 +336,7 @@ "Enable only if you trust the file.": "Yalnızca dosyaya güveniyorsanız etkinleştirin.", "Sort TOC by Page": "İçindekileri Sayfaya Göre Sırala", "Search in {{count}} Book(s)..._one": "{{count}} kitapta ara...", - "Search in {{count}} Book(s)..._other": "{{count}} kitapta ara..." + "Search in {{count}} Book(s)..._other": "{{count}} kitapta ara...", + "No notes match your search": "Eşleşen not bulunamadı", + "Search notes and excerpts...": "Notlarda ara..." } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index 0ff984b6..90521fd2 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -340,5 +340,7 @@ "Search in {{count}} Book(s)..._one": "Шукати в {{count}} книзі...", "Search in {{count}} Book(s)..._few": "Шукати в {{count}} книгах...", "Search in {{count}} Book(s)..._many": "Шукати в {{count}} книгах...", - "Search in {{count}} Book(s)..._other": "Шукати в {{count}} книгах..." + "Search in {{count}} Book(s)..._other": "Шукати в {{count}} книгах...", + "No notes match your search": "Нотаток не знайдено", + "Search notes and excerpts...": "Шукати нотатки..." } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index 9ab750c3..0cc82895 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -334,5 +334,7 @@ "Allow JavaScript": "Cho phép JavaScript", "Enable only if you trust the file.": "Chỉ bật nếu bạn tin tưởng tệp.", "Sort TOC by Page": "Sort Mục lục theo Trang", - "Search in {{count}} Book(s)..._other": "Tìm kiếm trong {{count}} cuốn sách..." + "Search in {{count}} Book(s)..._other": "Tìm kiếm trong {{count}} cuốn sách...", + "No notes match your search": "Không tìm thấy ghi chú nào", + "Search notes and excerpts...": "Tìm ghi chú..." } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 37d8f6f1..2919a779 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -334,5 +334,7 @@ "Allow JavaScript": "允许 JavaScript", "Enable only if you trust the file.": "建议仅在您信任该文件时启用", "Sort TOC by Page": "按页码排序目录", - "Search in {{count}} Book(s)..._other": "在 {{count}} 本书籍中搜索..." + "Search in {{count}} Book(s)..._other": "在 {{count}} 本书籍中搜索...", + "No notes match your search": "未找到匹配的笔记", + "Search notes and excerpts...": "搜索笔记..." } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index e1b5b7c4..1dc54c0b 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -334,5 +334,7 @@ "Allow JavaScript": "允許 JavaScript", "Enable only if you trust the file.": "建議僅在您信任該檔案時啟用", "Sort TOC by Page": "依頁碼排序目錄", - "Search in {{count}} Book(s)..._other": "在 {{count}} 本書中搜索..." + "Search in {{count}} Book(s)..._other": "在 {{count}} 本書中搜索...", + "No notes match your search": "找不到符合的筆記", + "Search notes and excerpts...": "搜尋筆記..." } diff --git a/apps/readest-app/src/app/reader/components/notebook/Header.tsx b/apps/readest-app/src/app/reader/components/notebook/Header.tsx index faabe704..8e2fd03f 100644 --- a/apps/readest-app/src/app/reader/components/notebook/Header.tsx +++ b/apps/readest-app/src/app/reader/components/notebook/Header.tsx @@ -9,9 +9,11 @@ import { useResponsiveSize } from '@/hooks/useResponsiveSize'; const NotebookHeader: React.FC<{ isPinned: boolean; + isSearchBarVisible: boolean; handleClose: () => void; handleTogglePin: () => void; -}> = ({ isPinned, handleClose, handleTogglePin }) => { + handleToggleSearchBar: () => void; +}> = ({ isPinned, isSearchBarVisible, handleClose, handleTogglePin, handleToggleSearchBar }) => { const _ = useTranslation(); const iconSize14 = useResponsiveSize(14); return ( @@ -38,7 +40,10 @@ const NotebookHeader: React.FC<{
-
diff --git a/apps/readest-app/src/app/reader/components/notebook/Notebook.tsx b/apps/readest-app/src/app/reader/components/notebook/Notebook.tsx index 9d0741c2..6347ce40 100644 --- a/apps/readest-app/src/app/reader/components/notebook/Notebook.tsx +++ b/apps/readest-app/src/app/reader/components/notebook/Notebook.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { useSettingsStore } from '@/store/settingsStore'; import { useBookDataStore } from '@/store/bookDataStore'; @@ -18,6 +18,7 @@ import { getBookDirFromLanguage } from '@/utils/book'; import BooknoteItem from '../sidebar/BooknoteItem'; import NotebookHeader from './Header'; import NoteEditor from './NoteEditor'; +import SearchBar from './SearchBar'; const MIN_NOTEBOOK_WIDTH = 0.15; const MAX_NOTEBOOK_WIDTH = 0.45; @@ -35,6 +36,10 @@ const Notebook: React.FC = ({}) => { const { setNotebookWidth, setNotebookVisible, toggleNotebookPin } = useNotebookStore(); const { setNotebookNewAnnotation, setNotebookEditAnnotation } = useNotebookStore(); + const [isSearchBarVisible, setIsSearchBarVisible] = useState(false); + const [searchResults, setSearchResults] = useState(null); + const [searchTerm, setSearchTerm] = useState(''); + const onNavigateEvent = async () => { const pinButton = document.querySelector('.sidebar-pin-btn'); const isPinButtonHidden = !pinButton || window.getComputedStyle(pinButton).display === 'none'; @@ -135,6 +140,39 @@ const Notebook: React.FC = ({}) => { const { handleDragStart } = useDrag(onDragMove); + const config = getConfig(sideBarBookKey); + const { booknotes: allNotes = [] } = config || {}; + const annotationNotes = allNotes + .filter((note) => note.type === 'annotation' && note.note && !note.deletedAt) + .sort((a, b) => b.createdAt - a.createdAt); + const excerptNotes = allNotes + .filter((note) => note.type === 'excerpt' && note.text && !note.deletedAt) + .sort((a, b) => a.createdAt - b.createdAt); + + const handleToggleSearchBar = () => { + setIsSearchBarVisible((prev) => !prev); + if (isSearchBarVisible) { + setSearchResults(null); + setSearchTerm(''); + } + }; + + const filteredAnnotationNotes = useMemo( + () => + isSearchBarVisible && searchResults + ? searchResults.filter((note) => note.type === 'annotation' && note.note && !note.deletedAt) + : annotationNotes, + [annotationNotes, searchResults, isSearchBarVisible], + ); + + const filteredExcerptNotes = useMemo( + () => + isSearchBarVisible && searchResults + ? searchResults.filter((note) => note.type === 'excerpt' && note.text && !note.deletedAt) + : excerptNotes, + [excerptNotes, searchResults, isSearchBarVisible], + ); + if (!sideBarBookKey) return null; const bookData = getBookData(sideBarBookKey); @@ -145,14 +183,8 @@ const Notebook: React.FC = ({}) => { const { bookDoc } = bookData; const languageDir = getBookDirFromLanguage(bookDoc.metadata.language); - const config = getConfig(sideBarBookKey); - const { booknotes: allNotes = [] } = config || {}; - const annotationNotes = allNotes - .filter((note) => note.type === 'annotation' && note.note && !note.deletedAt) - .sort((a, b) => b.createdAt - a.createdAt); - const excerptNotes = allNotes - .filter((note) => note.type === 'excerpt' && note.text && !note.deletedAt) - .sort((a, b) => a.createdAt - b.createdAt); + const hasSearchResults = filteredAnnotationNotes.length > 0 || filteredExcerptNotes.length > 0; + const hasAnyNotes = annotationNotes.length > 0 || excerptNotes.length > 0; return isNotebookVisible ? ( <> @@ -161,7 +193,7 @@ const Notebook: React.FC = ({}) => { )}
{ className='drag-bar absolute left-0 top-0 h-full w-0.5 cursor-col-resize' onMouseDown={handleDragStart} /> - setNotebookVisible(false)} - handleTogglePin={handleTogglePin} - /> -
+
+ setNotebookVisible(false)} + handleTogglePin={handleTogglePin} + handleToggleSearchBar={handleToggleSearchBar} + /> +
+ +
+
+
+ {isSearchBarVisible && searchResults && !hasSearchResults && hasAnyNotes && ( +
+

{_('No notes match your search')}

+
+ )}
- {excerptNotes.length > 0 && ( -

{_('Excerpts')}

+ {filteredExcerptNotes.length > 0 && ( +

+ {_('Excerpts')} + {isSearchBarVisible && searchResults && ( + + ({filteredExcerptNotes.length}) + + )} +

)}
    - {excerptNotes.map((item, index) => ( + {filteredExcerptNotes.map((item, index) => (
  • { ))}
- {(notebookNewAnnotation || annotationNotes.length > 0) && ( -

{_('Notes')}

+ {(notebookNewAnnotation || filteredAnnotationNotes.length > 0) && ( +

+ {_('Notes')} + {isSearchBarVisible && searchResults && filteredAnnotationNotes.length > 0 && ( + + ({filteredAnnotationNotes.length}) + + )} +

)}
- {(notebookNewAnnotation || notebookEditAnnotation) && ( + {(notebookNewAnnotation || notebookEditAnnotation) && !isSearchBarVisible && ( handleEditNote(item, false)} /> )}
    - {annotationNotes.map((item, index) => ( + {filteredAnnotationNotes.map((item, index) => ( ))}
diff --git a/apps/readest-app/src/app/reader/components/notebook/SearchBar.tsx b/apps/readest-app/src/app/reader/components/notebook/SearchBar.tsx new file mode 100644 index 00000000..7e1d64ca --- /dev/null +++ b/apps/readest-app/src/app/reader/components/notebook/SearchBar.tsx @@ -0,0 +1,153 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { FaSearch, FaTimes } from 'react-icons/fa'; + +import { useBookDataStore } from '@/store/bookDataStore'; +import { useTranslation } from '@/hooks/useTranslation'; +import { BookNote } from '@/types/book'; +import { useResponsiveSize } from '@/hooks/useResponsiveSize'; + +interface SearchBarProps { + isVisible: boolean; + bookKey: string; + searchTerm: string; + onSearchResultChange: (results: BookNote[] | null) => void; +} + +const SearchBar: React.FC = ({ + isVisible, + bookKey, + searchTerm: term, + onSearchResultChange, +}) => { + const _ = useTranslation(); + const { getConfig } = useBookDataStore(); + const [searchTerm, setSearchTerm] = useState(term); + const inputRef = useRef(null); + const searchTimeout = useRef | null>(null); + + const iconSize16 = useResponsiveSize(16); + const iconSize12 = useResponsiveSize(12); + + useEffect(() => { + handleSearchTermChange(searchTerm); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [bookKey]); + + useEffect(() => { + setSearchTerm(term); + handleSearchTermChange(term); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [term]); + + useEffect(() => { + if (isVisible && inputRef.current) { + inputRef.current.focus(); + } + }, [isVisible]); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape' && inputRef.current) { + inputRef.current.blur(); + } + }; + window.addEventListener('keydown', handleKeyDown); + return () => { + window.removeEventListener('keydown', handleKeyDown); + if (searchTimeout.current) { + clearTimeout(searchTimeout.current); + } + }; + }, []); + + const handleInputChange = (e: React.ChangeEvent) => { + const value = e.target.value; + setSearchTerm(value); + + if (searchTimeout.current) { + clearTimeout(searchTimeout.current); + } + + searchTimeout.current = setTimeout(() => { + handleSearchTermChange(value); + }, 300); + }; + + const handleClearSearch = () => { + setSearchTerm(''); + handleSearchTermChange(''); + if (inputRef.current) { + inputRef.current.focus(); + } + }; + + const filterNotes = (notes: BookNote[], query: string): BookNote[] => { + if (!query.trim()) return []; + + const lowercaseQuery = query.toLowerCase(); + return notes.filter((note) => { + const textMatch = note.text?.toLowerCase().includes(lowercaseQuery) || false; + const noteMatch = note.note?.toLowerCase().includes(lowercaseQuery) || false; + return textMatch || noteMatch; + }); + }; + + const handleSearchTermChange = (term: string) => { + if (term.trim().length >= 1) { + handleSearch(term); + } else { + resetSearch(); + } + }; + + const handleSearch = (term: string) => { + const config = getConfig(bookKey); + if (!config) { + resetSearch(); + return; + } + + const { booknotes: allNotes = [] } = config; + const validNotes = allNotes.filter((note) => !note.deletedAt); + + const results = filterNotes(validNotes, term); + onSearchResultChange(results); + }; + + const resetSearch = () => { + onSearchResultChange(null); + }; + + return ( +
+
+
+ +
+ + + + {searchTerm && ( +
+ +
+ )} +
+
+ ); +}; + +export default SearchBar;