Skip to content

Commit

Permalink
검색페이지 검색 결과 및 스크롤 보존 작업 (#301)
Browse files Browse the repository at this point in the history
  • Loading branch information
dahyeon405 authored Dec 12, 2022
2 parents d7990a3 + bebd21d commit a7c59e6
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 85 deletions.
4 changes: 2 additions & 2 deletions frontend/components/search/ArticleItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
} from './styled';

interface ArticleItemProps {
title: string;
content: string;
title: React.ReactNode;
content: React.ReactNode;
nickname: string;
profileImage: string;
articleUrl: string;
Expand Down
38 changes: 35 additions & 3 deletions frontend/components/search/ArticleList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,48 @@ import { IArticleBook } from '@interfaces';

interface ArticleListProps {
articles: IArticleBook[];
keywords: string[];
}

export default function ArticleList({ articles }: ArticleListProps) {
export default function ArticleList({ articles, keywords }: ArticleListProps) {
const highlightWord = (text: string, words: string[], isFirst = false): React.ReactNode => {
let wordIndexList = words.map((word) => text.toLowerCase().indexOf(word.toLowerCase()));

const filteredWords = words.filter((_, index) => wordIndexList[index] !== -1);
wordIndexList = wordIndexList.filter((wordIndex) => wordIndex !== -1);

if (wordIndexList.length === 0) return text;

const startIndex = Math.min(...wordIndexList);

const targetWord = filteredWords[wordIndexList.indexOf(startIndex)];

const endIndex = startIndex + targetWord.length;

let paddingIndex = 0;

if (isFirst) {
const regex = /(<([^>]+)>)/g;

while (regex.test(text.slice(0, startIndex))) paddingIndex = regex.lastIndex;
}

return (
<>
{text.slice(paddingIndex, startIndex)}
<b>{text.slice(startIndex, endIndex)}</b>
{highlightWord(text.slice(endIndex).replace(/(<([^>]+)>)/gi, ''), words)}
</>
);
};

return (
<>
{articles.map((article) => (
<ArticleItem
key={article.id}
title={article.title}
content={article.content}
title={highlightWord(article.title, keywords)}
content={highlightWord(article.content, keywords, true)}
nickname={article.book.user.nickname}
profileImage={article.book.user.profile_image}
articleUrl={`/viewer/${article.book.id}/${article.id}`}
Expand Down
55 changes: 53 additions & 2 deletions frontend/components/search/BookList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,67 @@
import { useEffect, useState } from 'react';

import Book from '@components/common/Book';
import { IBookScraps } from '@interfaces';

import { BookContainer, BookListWrapper } from './styled';

interface BookListProps {
books: IBookScraps[];
keywords: string[];
}

interface HighlightedBooks extends Omit<IBookScraps, 'title'> {
title: string | React.ReactNode;
}

export default function BookList({ books }: BookListProps) {
export default function BookList({ books, keywords }: BookListProps) {
const [highlightedBooks, setHighlightedBooks] = useState<HighlightedBooks[]>([]);

const highlightWord = (text: string, words: string[], isFirst = false): React.ReactNode => {
let wordIndexList = words.map((word) => text.toLowerCase().indexOf(word.toLowerCase()));

const filteredWords = words.filter((_, index) => wordIndexList[index] !== -1);
wordIndexList = wordIndexList.filter((wordIndex) => wordIndex !== -1);

if (wordIndexList.length === 0) return text;

const startIndex = Math.min(...wordIndexList);

const targetWord = filteredWords[wordIndexList.indexOf(startIndex)];

const endIndex = startIndex + targetWord.length;

let paddingIndex = 0;

if (isFirst) {
const regex = /(<([^>]+)>)/g;

while (regex.test(text.slice(0, startIndex))) paddingIndex = regex.lastIndex;
}

return (
<>
{text.slice(paddingIndex, startIndex)}
<b>{text.slice(startIndex, endIndex)}</b>
{highlightWord(text.slice(endIndex).replace(/(<([^>]+)>)/gi, ''), words)}
</>
);
};

useEffect(() => {
setHighlightedBooks(
books.map((book) => {
return {
...book,
title: highlightWord(book.title, keywords),
};
})
);
}, [books, keywords]);

return (
<BookListWrapper>
{books.map((book) => (
{highlightedBooks.map((book: any) => (
<BookContainer key={book.id}>
<Book key={book.id} book={book} />
</BookContainer>
Expand Down
18 changes: 15 additions & 3 deletions frontend/components/search/SearchFilter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ import signInStatusState from '@atoms/signInStatus';

import { FilterButton, FilterGroup, FilterLabel, FilterWrapper } from './styled';

interface Filter {
type: string;
userId: number;
}

interface SearchFilterProps {
handleFilter: (value: { [value: string]: string | number }) => void;
filter: Filter;
}

export default function SearchFilter({ handleFilter }: SearchFilterProps) {
export default function SearchFilter({ handleFilter, filter }: SearchFilterProps) {
const signInStatus = useRecoilValue(signInStatusState);

return (
Expand All @@ -19,12 +25,17 @@ export default function SearchFilter({ handleFilter }: SearchFilterProps) {
type="radio"
name="type"
onChange={() => handleFilter({ type: 'article' })}
defaultChecked
checked={filter.type !== 'book'}
/>
</FilterLabel>
<FilterLabel>
<FilterButton type="radio" name="type" onChange={() => handleFilter({ type: 'book' })} />
<FilterButton
type="radio"
name="type"
onChange={() => handleFilter({ type: 'book' })}
checked={filter.type === 'book'}
/>
</FilterLabel>
</FilterGroup>
Expand All @@ -33,6 +44,7 @@ export default function SearchFilter({ handleFilter }: SearchFilterProps) {
<FilterButton
type="checkbox"
onChange={(e) => handleFilter({ userId: e.target.checked ? signInStatus.id : 0 })}
checked={filter.userId !== 0}
/>
내 책에서 검색
</FilterLabel>
Expand Down
23 changes: 23 additions & 0 deletions frontend/hooks/useSessionStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useEffect, useState } from 'react';

const useSessionStorage = <T>(key: string, initialValue: T) => {
const [value, setStateValue] = useState<T>(initialValue);
const [isValueSet, setIsValueSet] = useState(false);

useEffect(() => {
if (typeof window !== 'undefined') {
setIsValueSet(true);
const savedValue = sessionStorage.getItem(key);
if (savedValue) setStateValue(JSON.parse(savedValue));
}
}, [typeof window]);

const setValue = (newValue: T) => {
setStateValue(newValue);
sessionStorage.setItem(key, JSON.stringify(newValue));
};

return { value, isValueSet, setValue };
};

export default useSessionStorage;
Loading

0 comments on commit a7c59e6

Please sign in to comment.