diff --git a/src/components/NotePage/NoteList/NoteItem.tsx b/src/components/NotePage/NoteList/NoteItem.tsx index 52963ac49a..b2057c591c 100644 --- a/src/components/NotePage/NoteList/NoteItem.tsx +++ b/src/components/NotePage/NoteList/NoteItem.tsx @@ -9,6 +9,7 @@ import { } from '../../../lib/styled/styleFunctions' import cc from 'classcat' import { setTransferrableNoteData } from '../../../lib/dnd' +import HighlightText from '../../atoms/HighlightText' export const StyledNoteListItem = styled.div` margin: 0; @@ -76,21 +77,41 @@ type NoteItemProps = { note: NoteDoc active: boolean storageId: string + search: string basePathname: string focusList: () => void } -export default ({ storageId, note, active, basePathname }: NoteItemProps) => { +export default ({ + storageId, + note, + active, + basePathname, + search +}: NoteItemProps) => { const href = `${basePathname}/${note._id}` const contentPreview = useMemo(() => { - return ( - note.content - .trim() + const trimmedContent = note.content.trim() + const searchFirstIndex = trimmedContent + .toLowerCase() + .indexOf(search.toLowerCase()) + + if (search !== '' && searchFirstIndex !== -1) { + const contentToHighlight = trimmedContent + .substring(searchFirstIndex) .split('\n') - .shift() || 'Empty note' - ) - }, [note.content]) + .shift() + + return contentToHighlight == null ? ( + 'Empty note' + ) : ( + + ) + } + + return trimmedContent.split('\n').shift() || 'Empty note' + }, [note.content, search]) const handleDragStart = useCallback( (event: React.DragEvent) => { @@ -107,16 +128,16 @@ export default ({ storageId, note, active, basePathname }: NoteItemProps) => { >
-
{note.title}
+
+ +
{note.title.length === 0 &&
No title
}
DD days ago
{contentPreview}
{note.tags.length > 0 && (
{note.tags.map(tag => ( - - {tag} - + ))}
)} diff --git a/src/components/NotePage/NoteList/NoteList.tsx b/src/components/NotePage/NoteList/NoteList.tsx index d61dff50bb..7e2f560ae4 100644 --- a/src/components/NotePage/NoteList/NoteList.tsx +++ b/src/components/NotePage/NoteList/NoteList.tsx @@ -1,4 +1,9 @@ -import React, { useCallback, KeyboardEvent, useRef } from 'react' +import React, { + useCallback, + KeyboardEvent, + useRef, + ChangeEventHandler +} from 'react' import NoteItem from './NoteItem' import { NoteDoc } from '../../../lib/db/types' import styled from '../../../lib/styled' @@ -65,23 +70,34 @@ export const StyledNoteListContainer = styled.div` type NoteListProps = { storageId: string + currentNoteIndex: number + search: string notes: NoteDoc[] createNote: () => Promise - basePathname: string - currentNoteIndex: number - navigateUp: () => void + setSearchInput: (input: string) => void navigateDown: () => void + navigateUp: () => void + basePathname: string } const NoteList = ({ notes, - currentNoteIndex, createNote, storageId, basePathname, - navigateUp, - navigateDown + search, + currentNoteIndex, + setSearchInput, + navigateDown, + navigateUp }: NoteListProps) => { + const updateSearchInput: ChangeEventHandler = useCallback( + event => { + setSearchInput(event.target.value) + }, + [setSearchInput] + ) + const handleListKeyDown = useCallback( (event: KeyboardEvent) => { switch (event.key) { @@ -101,15 +117,14 @@ const NoteList = ({ const focusList = useCallback(() => { listRef.current!.focus() }, []) - return (
{}} + value={search} + onChange={updateSearchInput} placeholder='Search Notes' /> @@ -129,11 +144,18 @@ const NoteList = ({ storageId={storageId} basePathname={basePathname} focusList={focusList} + search={search} /> ) })} - {notes.length === 0 && ''} + {notes.length === 0 ? ( + search.trim() === '' ? ( +
  • No notes
  • + ) : ( +
  • No notes could be found.
  • + ) + ) : null} ) diff --git a/src/components/NotePage/NotePage.tsx b/src/components/NotePage/NotePage.tsx index 82e562a079..ba7a4feef7 100644 --- a/src/components/NotePage/NotePage.tsx +++ b/src/components/NotePage/NotePage.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useCallback } from 'react' +import React, { useMemo, useCallback, useState } from 'react' import NoteList from './NoteList' import styled from '../../lib/styled' import NoteDetail from './NoteDetail' @@ -41,7 +41,8 @@ export default () => { if (storageId == null) return undefined return db.storageMap[storageId] }, [db.storageMap, storageId]) - + const router = useRouter() + const [search, setSearchInput] = useState('') const currentPathnameWithoutNoteId = usePathnameWithoutNoteId() const notes = useMemo((): NoteDoc[] => { @@ -78,20 +79,29 @@ export default () => { return [] }, [currentStorage, routeParams]) - const router = useRouter() + const filteredNotes = useMemo(() => { + if (search.trim() === '') return notes + const regex = new RegExp(search, 'i') + return notes.filter( + note => + note.tags.join().match(regex) || + note.title.match(regex) || + note.content.match(regex) + ) + }, [search, notes]) const currentNoteIndex = useMemo(() => { - for (let i = 0; i < notes.length; i++) { - if (notes[i]._id === noteId) { + for (let i = 0; i < filteredNotes.length; i++) { + if (filteredNotes[i]._id === noteId) { return i } } return 0 - }, [notes, noteId]) + }, [filteredNotes, noteId]) const currentNote = useMemo(() => { - return notes[currentNoteIndex] - }, [notes, currentNoteIndex]) + return filteredNotes[currentNoteIndex] + }, [filteredNotes, currentNoteIndex]) const createNote = useCallback(async () => { if (storageId == null || routeParams.name === 'storages.trashCan') { @@ -108,22 +118,6 @@ export default () => { }) }, [db, routeParams, storageId]) - const naviagateUp = useCallback(() => { - if (currentNoteIndex > 0) { - router.push( - currentPathnameWithoutNoteId + `/${notes[currentNoteIndex - 1]._id}` - ) - } - }, [notes, currentNoteIndex, router, currentPathnameWithoutNoteId]) - - const naviagateDown = useCallback(() => { - if (currentNoteIndex < notes.length - 1) { - router.push( - currentPathnameWithoutNoteId + `/${notes[currentNoteIndex + 1]._id}` - ) - } - }, [notes, currentNoteIndex, router, currentPathnameWithoutNoteId]) - const { generalStatus, setGeneralStatus } = useGeneralStatus() const updateNoteListWidth = useCallback( (leftWidth: number) => { @@ -182,6 +176,24 @@ export default () => { [storageId, db] ) + const navigateUp = useCallback(() => { + if (currentNoteIndex > 0) { + router.push( + currentPathnameWithoutNoteId + + `/${filteredNotes[currentNoteIndex - 1]._id}` + ) + } + }, [filteredNotes, currentNoteIndex, router, currentPathnameWithoutNoteId]) + + const navigateDown = useCallback(() => { + if (currentNoteIndex < filteredNotes.length - 1) { + router.push( + currentPathnameWithoutNoteId + + `/${filteredNotes[currentNoteIndex + 1]._id}` + ) + } + }, [filteredNotes, currentNoteIndex, router, currentPathnameWithoutNoteId]) + return storageId != null ? ( { left={ } diff --git a/src/components/atoms/HighlightText.tsx b/src/components/atoms/HighlightText.tsx new file mode 100644 index 0000000000..ca891b9e99 --- /dev/null +++ b/src/components/atoms/HighlightText.tsx @@ -0,0 +1,38 @@ +import React, { useMemo } from 'react' +import styled from '../../lib/styled' + +const HighlightContainer = styled.span` + .highlighted { + background: rgba(3, 197, 136, 0.6); + } +` + +interface HighlightTextProps { + text: string + search: string +} + +function HighlightText(props: HighlightTextProps) { + const { text, search } = props + return useMemo(() => { + if (search.trim() === '') return <>{text} + const searchRegex = new RegExp(`(${search})`, 'gi') + const parts = text.split(searchRegex) + + return ( + + {parts.map((part, i) => + part.toLowerCase() === search.toLowerCase() ? ( + + {part} + + ) : ( + {part} + ) + )} + + ) + }, [text, search]) +} + +export default HighlightText