Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[배포] Production v1.0.2 #147

Merged
merged 3 commits into from
Dec 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 41 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,45 @@

### 서비스 링크 : https://paperef.com

### 기능 목록

#### 인기 검색어

![1](https://user-images.githubusercontent.com/25934842/207813681-bade76c7-49f5-47dc-a712-4cb9dfb87cd9.gif)

- 검색량이 많은 검색어를 1~10위까지 보여줍니다.
- 검색어를 클릭하면 해당 키워드로 검색된 리스트 페이지로 이동합니다.

#### 논문 검색

![2](https://user-images.githubusercontent.com/25934842/207813670-5dc3ed09-8d44-44ef-853e-20362fab92d1.gif)

- 검색창에 포커스하면 최근 검색어 목록을 5개까지 보여줍니다.
- 키워드를 2자이상 입력하면 자동완성 검색어 목록을 보여줍니다.
- 저자, 제목, 키워드를 입력하여 검색버튼을 누르면 검색 리스트로 이동합니다.
- DOI로 검색하면 바로 해당 논문의 시각화 페이지로 이동합니다.
- 최근 검색어 목록이나 자동완성 검색어는 mouse-over, 방향키 이벤트로 커서를 이동시킬 수 있습니다.

#### 논문 리스트

![3](https://user-images.githubusercontent.com/25934842/207813649-d23bc237-71da-48d7-98f1-6cf10b6139da.gif)

- 키워드와 유사성이 높은 논문 목록을 보여줍니다.
- 리스트는 20개 단위로 페이지네이션 됩니다.

#### 논문 시각화 페이지

![4](https://user-images.githubusercontent.com/25934842/207815534-0b2cc38b-88cb-4ff6-af14-6ff48b50dee8.gif)

- 좌측에서는 선택한 논문의 정보(제목, 저자, DOI, 인용논문 목록)을 보여줍니다.
- 인용 논문 목록에 포함된 논문제목을 hovering하면 오른쪽 그래프에서 해당하는 논문node를 강조합니다.
- 우측에서는 선택한 논문의 데이터로 시각화된 네트워크 차트를 보여줍니다.
- 논문은 node, 논문간 인용관계는 line으로 표현됩니다.
- 주위 node를 클릭하면 해당 논문node의 인용관계가 추가로 시각화 됩니다.
- node에 호버링하면 해당 논문과 해당 논문이 인용한 논문들의 nodes, lines가 강조됩니다.
- 마우스 드래그로 그래프 위치를 옮길 수 있습니다.
- 스크롤로 그래프를 zoom-in, zoom-out 할 수 있습니다.

### 팀원

<table>
Expand All @@ -40,6 +79,8 @@

### 개발 환경 세팅

> 환경변수는 `/frontend`, `/backend` 폴더에 있는 `.env.sample` 파일을 참고해주시기 바랍니다.

#### Front-end

```bash
Expand All @@ -54,33 +95,6 @@ npm start
cd backend
npm install
npm start
# 환경변수 주입 필요(예: npm run start:mac)
```

### 환경변수

#### Front-end

```
REACT_APP_BASE_URL=
```

#### Back-end

```
PORT=
REDIS_POPULAR_KEY=
REDIS_PREVRANKING=
REDIS_HOST=
REDIS_PORT=
REDIS_PASSWORD=
ELASTIC_INDEX=
ELASTIC_HOST=
ELASTIC_USER=
ELASTIC_PASSWORD=
ALLOW_UPDATE=
MAIL_TO=
SHOULD_RUN_BATCH=
```

## 기술스택
Expand Down
13 changes: 13 additions & 0 deletions backend/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
PORT=
REDIS_POPULAR_KEY=
REDIS_PREVRANKING=
REDIS_HOST=
REDIS_PORT=
REDIS_PASSWORD=
ELASTIC_INDEX=
ELASTIC_HOST=
ELASTIC_USER=
ELASTIC_PASSWORD=
MAIL_TO=
ALLOW_UPDATE=
SHOULD_RUN_BATCH=
1 change: 1 addition & 0 deletions frontend/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
REACT_APP_BASE_URL=
5 changes: 3 additions & 2 deletions frontend/src/components/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ interface IProps {
icon: JSX.Element;
onClick?: React.MouseEventHandler;
onMouseDown?: React.MouseEventHandler;
type?: 'button' | 'submit' | 'reset' | undefined;
'aria-label': string;
}

const IconButton = ({ icon, ...rest }: IProps) => {
const IconButton = ({ icon, type = 'button', ...rest }: IProps) => {
return (
<Button type="button" {...rest}>
<Button type={type} {...rest}>
{icon}
</Button>
);
Expand Down
32 changes: 13 additions & 19 deletions frontend/src/components/search/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useAutoCompleteQuery } from '@/queries/queries';
import { createDetailQuery } from '@/utils/createQueryString';
import { getDoiKey, isDoiFormat } from '@/utils/format';
import { getLocalStorage, setLocalStorage } from '@/utils/storage';
import { ChangeEvent, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { ChangeEvent, FormEvent, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { createSearchParams, useNavigate } from 'react-router-dom';
import styled from 'styled-components';
import AutoCompletedList from './AutoCompletedList';
Expand Down Expand Up @@ -83,7 +83,6 @@ const Search = ({ initialKeyword = '' }: SearchProps) => {
setKeyword(target.value);
};

// localStorage에서 가져온 recent keywords를 최근에 검색한 순서대로 set
const handleInputFocus = () => {
setIsFocused(true);
};
Expand All @@ -98,7 +97,7 @@ const Search = ({ initialKeyword = '' }: SearchProps) => {
}, []);

// localStorage에 최근 검색어를 중복없이 최대 5개까지 저장 후 search-list로 이동
const handleSearchButtonClick = async (newKeyword: string) => {
const onKeywordSearch = async (newKeyword: string) => {
if (!newKeyword) return;
setKeyword(newKeyword);
setLocalStorage(
Expand All @@ -114,12 +113,14 @@ const Search = ({ initialKeyword = '' }: SearchProps) => {
goToSearchList(newKeyword);
};

const handleEnterKeyDown = () => {
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
if (!inputRef.current) return;
inputRef?.current?.blur();
inputRef.current.blur();

// hover된 항목이 없는경우
if (hoverdIndex < 0) {
handleSearchButtonClick(keyword);
onKeywordSearch(keyword);
return;
}
// hover된 항목으로 검색
Expand All @@ -128,12 +129,12 @@ const Search = ({ initialKeyword = '' }: SearchProps) => {
autoCompletedItems && goToDetailPage(autoCompletedItems?.[hoverdIndex].doi);
break;
case DROPDOWN_TYPE.RECENT_KEYWORDS:
handleSearchButtonClick(recentKeywords[hoverdIndex]);
onKeywordSearch(recentKeywords[hoverdIndex]);
break;
}
};

// 방향키, enter키 입력 이벤트 핸들러
// 방향키 입력 이벤트 핸들러
const handleInputKeyDown = (e: KeyboardEvent) => {
const length = dropdownType === DROPDOWN_TYPE.AUTO_COMPLETE ? autoCompletedItems?.length : recentKeywords.length;

Expand All @@ -146,9 +147,6 @@ const Search = ({ initialKeyword = '' }: SearchProps) => {
case 'ArrowUp':
setHoveredIndex((prev) => (prev - 1 < -1 ? length - 1 : prev - 1));
break;
case 'Enter':
handleEnterKeyDown();
break;
}
};

Expand All @@ -167,7 +165,7 @@ const Search = ({ initialKeyword = '' }: SearchProps) => {
<RecentKeywordsList
recentKeywords={recentKeywords}
hoverdIndex={hoverdIndex}
handleMouseDown={handleSearchButtonClick}
handleMouseDown={onKeywordSearch}
setHoveredIndex={setHoveredIndex}
initializeRecentKeywords={initializeRecentKeywords}
/>
Expand All @@ -188,7 +186,7 @@ const Search = ({ initialKeyword = '' }: SearchProps) => {
return (
<Container>
<SearchBox>
<SearchBar>
<SearchBar onSubmit={handleSubmit}>
<SearchInput
ref={inputRef}
placeholder="저자, 제목, 키워드, DOI"
Expand All @@ -198,11 +196,7 @@ const Search = ({ initialKeyword = '' }: SearchProps) => {
onBlur={handleInputBlur}
onKeyDown={handleInputKeyDown}
/>
<IconButton
icon={<MagnifyingGlassIcon />}
onClick={() => handleSearchButtonClick(keyword)}
aria-label="검색"
/>
<IconButton icon={<MagnifyingGlassIcon />} aria-label="검색" type="submit" />
</SearchBar>
<DropdownContainer>{renderDropdownContent(dropdownType)}</DropdownContainer>
</SearchBox>
Expand Down Expand Up @@ -233,7 +227,7 @@ const SearchBox = styled.div`
box-shadow: 0px 4px 11px -1px rgba(0, 0, 0, 0.15);
`;

const SearchBar = styled.div`
const SearchBar = styled.form`
width: 100%;
height: 50px;
min-height: 50px;
Expand Down