From 1986ccfd52dce8c03ff5e7ec912f8dabd382c373 Mon Sep 17 00:00:00 2001 From: MinHK4 Date: Thu, 8 Dec 2022 14:06:51 +0900 Subject: [PATCH 01/44] =?UTF-8?q?feat:=20=EB=B7=B0=EC=96=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B8=80=20=EC=82=AD=EC=A0=9C=20useFetch?= =?UTF-8?q?=20=ED=9B=85=20=EB=B0=98=EC=98=81=20-=20#246?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/apis/articleApi.ts | 2 +- .../viewer/ArticleContent/index.tsx | 21 ++++++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/frontend/apis/articleApi.ts b/frontend/apis/articleApi.ts index d817a6ba..88400f61 100644 --- a/frontend/apis/articleApi.ts +++ b/frontend/apis/articleApi.ts @@ -52,7 +52,7 @@ export const modifyArticleApi = async (articleId: number, data: CreateArticleApi return response.data; }; -export const deleteArticleApi = async (articleId: string) => { +export const deleteArticleApi = async (articleId: number) => { const url = `/api/articles/${articleId}`; const response = await api({ url, method: 'DELETE' }); diff --git a/frontend/components/viewer/ArticleContent/index.tsx b/frontend/components/viewer/ArticleContent/index.tsx index 9883e081..148be10a 100644 --- a/frontend/components/viewer/ArticleContent/index.tsx +++ b/frontend/components/viewer/ArticleContent/index.tsx @@ -6,14 +6,17 @@ import { useEffect } from 'react'; import axios from 'axios'; import { useRecoilValue } from 'recoil'; +import { deleteArticleApi } from '@apis/articleApi'; import LeftBtnIcon from '@assets/ico_leftBtn.svg'; import Original from '@assets/ico_original.svg'; import RightBtnIcon from '@assets/ico_rightBtn.svg'; import Scrap from '@assets/ico_scrap.svg'; import signInStatusState from '@atoms/signInStatus'; import Content from '@components/common/Content'; +import useFetch from '@hooks/useFetch'; import { IArticleBook, IScrap } from '@interfaces'; import { TextLarge } from '@styles/common'; +import { toastSuccess } from '@utils/toast'; import ArticleButton from './Button'; import { @@ -40,6 +43,8 @@ export default function Article({ bookAuthor, handleScrapBtnClick, }: ArticleProps) { + const { data: deleteArticleData, execute: deleteArticle } = useFetch(deleteArticleApi); + const user = useRecoilValue(signInStatusState); const router = useRouter(); @@ -62,14 +67,7 @@ export default function Article({ const handleDeleteBtnOnClick = () => { if (window.confirm('해당 글을 삭제하시겠습니까?')) { - axios - .delete(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/articles/${article.id}`) - .catch((err) => { - // 추후 에러 핸들링 추가 예정 - console.log(err); - }); - - router.push('/'); + deleteArticle(article.id); } }; @@ -98,6 +96,13 @@ export default function Article({ checkArticleAuthority(article.id); }, []); + useEffect(() => { + if (!deleteArticleData) return; + + toastSuccess(`<${article.title}> 글이 삭제되었습니다`); + router.push('/'); + }, [deleteArticleData]); + return ( {article.id === scraps.at(0)?.article.id ? null : ( From 099b028575cfe829c60ebde3a6b53445d7171112 Mon Sep 17 00:00:00 2001 From: MinHK4 Date: Thu, 8 Dec 2022 16:39:50 +0900 Subject: [PATCH 02/44] =?UTF-8?q?feat:=20delete=20scrap,=20article=20API?= =?UTF-8?q?=20return=20=EA=B0=92=20=EB=84=A3=EC=96=B4=EC=A3=BC=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/apis/articles/articles.controller.ts | 4 +- backend/src/apis/articles/articles.service.ts | 4 +- backend/src/apis/scraps/scraps.controller.ts | 5 +- backend/src/apis/scraps/scraps.service.ts | 4 +- frontend/components/study/FAB/index.tsx | 51 +++++++++++++++---- 5 files changed, 52 insertions(+), 16 deletions(-) diff --git a/backend/src/apis/articles/articles.controller.ts b/backend/src/apis/articles/articles.controller.ts index b38b5ca6..8d3dedab 100644 --- a/backend/src/apis/articles/articles.controller.ts +++ b/backend/src/apis/articles/articles.controller.ts @@ -73,9 +73,9 @@ const updateArticle = async (req: Request, res: Response) => { const deleteArticle = async (req: Request, res: Response) => { const articleId = Number(req.params.articleId); - await articlesService.deleteArticle(articleId); + const article = await articlesService.deleteArticle(articleId); - return res.status(204).send(); + return res.status(200).send(article); }; const getTemporaryArticle = async (req: Request, res: Response) => { diff --git a/backend/src/apis/articles/articles.service.ts b/backend/src/apis/articles/articles.service.ts index 89899603..fee5d219 100644 --- a/backend/src/apis/articles/articles.service.ts +++ b/backend/src/apis/articles/articles.service.ts @@ -112,7 +112,7 @@ const createArticle = async (dto: CreateArticle) => { }; const deleteArticle = async (articleId: number) => { - await prisma.article.update({ + const article = await prisma.article.update({ where: { id: articleId, }, @@ -120,6 +120,8 @@ const deleteArticle = async (articleId: number) => { deleted_at: new Date(), }, }); + + return article; }; const getTemporaryArticle = async (userId: number) => { diff --git a/backend/src/apis/scraps/scraps.controller.ts b/backend/src/apis/scraps/scraps.controller.ts index 80e765e4..dde76f1d 100644 --- a/backend/src/apis/scraps/scraps.controller.ts +++ b/backend/src/apis/scraps/scraps.controller.ts @@ -26,10 +26,11 @@ const createScrap = async (req: Request, res: Response) => { const deleteScrap = async (req: Request, res: Response) => { const scrapId = Number(req.params.scrapId); - await scrapsService.deleteScrap(scrapId); + const scrap = await scrapsService.deleteScrap(scrapId); - return res.status(200).send(); + return res.status(200).send(scrap); }; + const getScraps = async (req: Request, res: Response) => { const scraps = await scrapsService.getScraps(); diff --git a/backend/src/apis/scraps/scraps.service.ts b/backend/src/apis/scraps/scraps.service.ts index 14cbfb6b..3d6e54a3 100644 --- a/backend/src/apis/scraps/scraps.service.ts +++ b/backend/src/apis/scraps/scraps.service.ts @@ -53,11 +53,13 @@ const updateScrapOrder = async (scraps: IScrap) => { }; const deleteScrap = async (scrapId: number) => { - await prisma.scrap.delete({ + const scrap = await prisma.scrap.delete({ where: { id: scrapId, }, }); + + return scrap; }; const getScraps = async () => { diff --git a/frontend/components/study/FAB/index.tsx b/frontend/components/study/FAB/index.tsx index c89a46c5..f0968ef7 100644 --- a/frontend/components/study/FAB/index.tsx +++ b/frontend/components/study/FAB/index.tsx @@ -24,10 +24,14 @@ interface FabProps { } export default function FAB({ isEditing, setIsEditing }: FabProps) { - const { data: deletedBook, execute: deleteBook } = useFetch(deleteBookApi); - const { data: editBookData, execute: editBook } = useFetch(editBookApi); - const { data: deleteArticleData, execute: deleteArticle } = useFetch(deleteArticleApi); - const { data: deleteScrapData, execute: deleteScrap } = useFetch(deleteScrapApi); + const { isLoading: aaa, data: deletedBook, execute: deleteBook } = useFetch(deleteBookApi); + const { isLoading: bbb, data: editBookData, execute: editBook } = useFetch(editBookApi); + const { + isLoading: ccc, + data: deleteArticleData, + execute: deleteArticle, + } = useFetch(deleteArticleApi); + const { isLoading: ddd, data: deleteScrapData, execute: deleteScrap } = useFetch(deleteScrapApi); const [editInfo, setEditInfo] = useRecoilState(editInfoState); @@ -48,11 +52,9 @@ export default function FAB({ isEditing, setIsEditing }: FabProps) { editInfo.editted.forEach((edit) => { editBook(edit); }); - // 원본글 삭제 editInfo.deletedArticle.forEach((articleId) => { deleteArticle(articleId); }); - // 스크랩 삭제 editInfo.deletedScraps.forEach((scrapId) => { deleteScrap(scrapId); }); @@ -68,19 +70,48 @@ export default function FAB({ isEditing, setIsEditing }: FabProps) { }, [deletedBook]); useEffect(() => { - if (!editBookData) return; + console.log('editBookData', editBookData); + if (!editBookData || !deleteScrapData) return; + + if (aaa || bbb || ccc || ddd) return; setEditInfo({ ...editInfo, editted: editInfo.editted.filter((edit) => edit.id !== editBookData.id), + deletedScraps: editInfo.deletedScraps.filter((scrapId) => scrapId !== deleteScrapData.id), }); - }, [editBookData]); + }, [editBookData, deleteScrapData]); + + useEffect(() => { + if (!deleteArticleData) return; + console.log('deleteArticleData', deleteArticleData); + + setEditInfo({ + ...editInfo, + deletedArticle: editInfo.deletedArticle.filter( + (articleId) => articleId !== deleteArticleData.id + ), + }); + }, [deleteArticleData]); + + // useEffect(() => { + // if (!deleteScrapData) return; + // console.log('deleteScrapData', deleteScrapData); + + // setEditInfo({ + // ...editInfo, + // deletedScraps: editInfo.deletedScraps.filter((scrapId) => scrapId !== deleteScrapData.id), + // }); + // }, [deleteScrapData]); useEffect(() => { + console.log(deletedBook, editBookData, deleteArticleData, deleteScrapData, editInfo); if ( - (deletedBook || editBookData) && + (deletedBook || editBookData || deleteArticleData || deleteScrapData) && editInfo.deleted.length === 0 && - editInfo.editted.length === 0 + editInfo.editted.length === 0 && + editInfo.deletedArticle.length === 0 && + editInfo.deletedScraps.length === 0 ) { toastSuccess(`수정 완료되었습니다`); } From d0f71ff112f7f2b006a6204b5d245d4dedecaf2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=84?= Date: Mon, 12 Dec 2022 17:51:08 +0900 Subject: [PATCH 03/44] =?UTF-8?q?feat:=20Guard=20=EB=AF=B8=EB=93=A4?= =?UTF-8?q?=EC=9B=A8=EC=96=B4=20=EC=97=90=EB=9F=AC=20=ED=95=B8=EB=93=A4?= =?UTF-8?q?=EB=9F=AC=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20Guard=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/apis/index.ts | 21 ++++++++++---------- backend/src/middlewares/tokenErrorHandler.ts | 7 +++++++ 2 files changed, 18 insertions(+), 10 deletions(-) create mode 100644 backend/src/middlewares/tokenErrorHandler.ts diff --git a/backend/src/apis/index.ts b/backend/src/apis/index.ts index 172ca29a..15226aac 100644 --- a/backend/src/apis/index.ts +++ b/backend/src/apis/index.ts @@ -10,6 +10,7 @@ import imagesController from '@apis/images/images.controller'; import scrapsController from '@apis/scraps/scraps.controller'; import usersController from '@apis/users/users.controller'; import decoder from '@middlewares/tokenDecoder'; +import { tokenErrorHandler } from '@middlewares/tokenErrorHandler'; import guard from '@middlewares/tokenValidator'; import { catchAsync } from '@utils/catch-async'; @@ -19,19 +20,19 @@ router.post('/auth/signin/local', catchAsync(authController.signIn)); router.post('/auth/signin/github', catchAsync(authController.signInGithub)); router.post('/auth/signup', catchAsync(authController.signUp)); router.get('/auth/signout', catchAsync(authController.signOut)); -router.get('/auth', decoder, catchAsync(authController.checkSignInStatus)); +router.get('/auth', guard, tokenErrorHandler, catchAsync(authController.checkSignInStatus)); router.get('/articles/temporary', guard, catchAsync(articlesController.getTemporaryArticle)); router.post('/articles/temporary', guard, catchAsync(articlesController.createTemporaryArticle)); router.get('/articles/search', catchAsync(articlesController.searchArticles)); router.get('/articles/:articleId', catchAsync(articlesController.getArticle)); -router.post('/articles', catchAsync(articlesController.createArticle)); -router.patch('/articles/:articleId', catchAsync(articlesController.updateArticle)); -router.delete('/articles/:articleId', catchAsync(articlesController.deleteArticle)); +router.post('/articles', guard, catchAsync(articlesController.createArticle)); +router.patch('/articles/:articleId', guard, catchAsync(articlesController.updateArticle)); +router.delete('/articles/:articleId', guard, catchAsync(articlesController.deleteArticle)); -router.post('/image', multer().single('image'), catchAsync(imagesController.createImage)); +router.post('/image', guard, multer().single('image'), catchAsync(imagesController.createImage)); -router.get('/books/search', catchAsync(booksController.searchBooks)); +router.get('/books/search', decoder, catchAsync(booksController.searchBooks)); router.get('/books/:bookId', decoder, catchAsync(booksController.getBook)); router.get('/books', decoder, catchAsync(booksController.getBooks)); router.post('/books', guard, catchAsync(booksController.createBook)); @@ -39,14 +40,14 @@ router.patch('/books', guard, catchAsync(booksController.updateBook)); router.delete('/books/:bookId', guard, catchAsync(booksController.deleteBook)); router.post('/bookmarks', guard, catchAsync(bookmarksController.createBookmark)); -router.delete('/bookmarks/:bookmarkId', catchAsync(bookmarksController.deleteBookmark)); +router.delete('/bookmarks/:bookmarkId', guard, catchAsync(bookmarksController.deleteBookmark)); router.get('/scraps', catchAsync(scrapsController.getScraps)); -router.patch('/scraps', catchAsync(guard), catchAsync(scrapsController.updateScrapsOrder)); -router.post('/scraps', catchAsync(scrapsController.createScrap)); +router.patch('/scraps', guard, catchAsync(scrapsController.updateScrapsOrder)); +router.post('/scraps', guard, catchAsync(scrapsController.createScrap)); router.delete('/scraps/:scrapId', guard, catchAsync(scrapsController.deleteScrap)); router.get('/users', catchAsync(usersController.getUserProfile)); -router.patch('/users/:userId', catchAsync(usersController.editUserProfile)); +router.patch('/users/:userId', guard, catchAsync(usersController.editUserProfile)); export default router; diff --git a/backend/src/middlewares/tokenErrorHandler.ts b/backend/src/middlewares/tokenErrorHandler.ts new file mode 100644 index 00000000..1e21c5dc --- /dev/null +++ b/backend/src/middlewares/tokenErrorHandler.ts @@ -0,0 +1,7 @@ +import { ErrorRequestHandler } from 'express'; + +export const tokenErrorHandler: ErrorRequestHandler = (err, req, res, next) => { + if (!err) return next(); + + return res.status(200).send({ id: 0 }); +}; From 223d7fbc1fc9f35668b0baa295f1b1006401561d Mon Sep 17 00:00:00 2001 From: parkhyeonki Date: Mon, 12 Dec 2022 18:15:26 +0900 Subject: [PATCH 04/44] =?UTF-8?q?feat:=20=EC=88=98=EC=A0=95=EB=AA=A8?= =?UTF-8?q?=EB=8B=AC=20UI=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/study/EditBookModal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/study/EditBookModal/index.tsx b/frontend/components/study/EditBookModal/index.tsx index e3033ccd..cf34e2c3 100644 --- a/frontend/components/study/EditBookModal/index.tsx +++ b/frontend/components/study/EditBookModal/index.tsx @@ -151,7 +151,7 @@ export default function EditBookModal({ book, handleModalClose }: BookProps) { Contents - {isContentsShown ? '완료' : '수정'} + {isContentsShown ? '저장' : '수정'} From ce6da3e2e9a479178923e898177dbfa1acbead16 Mon Sep 17 00:00:00 2001 From: parkhyeonki Date: Mon, 12 Dec 2022 18:27:25 +0900 Subject: [PATCH 05/44] =?UTF-8?q?feat:=20=EA=B8=80=20Toc=20UI=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/viewer/TOC/index.tsx | 5 +++-- frontend/components/viewer/TOC/styled.ts | 14 +++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/frontend/components/viewer/TOC/index.tsx b/frontend/components/viewer/TOC/index.tsx index 8390b2dc..a9516bee 100644 --- a/frontend/components/viewer/TOC/index.tsx +++ b/frontend/components/viewer/TOC/index.tsx @@ -25,6 +25,7 @@ import { TocArticleTitle, TocCurrentArticle, TocOpenButton, + TocCurrentText, } from './styled'; interface TocProps { @@ -86,9 +87,9 @@ export default function TOC({ ) : ( - + {v.order}.{v.article.title} - + {isArticleShown && articleToc.map((article) => ( ` font-size: 14px; line-height: 20px; text-decoration: none; - color: #c29880; display: block; margin-bottom: 5px; padding-left: ${(props) => `${props.count}px`}; + + &:hover { + color: #c29880; + } `; export const TocProfile = styled(Link)` From a26491c41c6c1135f2e2c812cd253295ec307dd3 Mon Sep 17 00:00:00 2001 From: parkhyeonki Date: Mon, 12 Dec 2022 18:30:11 +0900 Subject: [PATCH 06/44] =?UTF-8?q?chore:=20=EC=83=89=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/viewer/TOC/styled.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/viewer/TOC/styled.ts b/frontend/components/viewer/TOC/styled.ts index 62a0325d..0cbc7c0f 100644 --- a/frontend/components/viewer/TOC/styled.ts +++ b/frontend/components/viewer/TOC/styled.ts @@ -112,7 +112,7 @@ export const TocArticleTitle = styled(Link)<{ count: number | undefined }>` padding-left: ${(props) => `${props.count}px`}; &:hover { - color: #c29880; + color: var(--primary-color); } `; From 5ea20fde9dddcc0e3266362163de1523f8f4bebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=84?= Date: Mon, 12 Dec 2022 18:38:48 +0900 Subject: [PATCH 07/44] =?UTF-8?q?feat:=20TOC=20=EA=B0=80=EB=A1=9C=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/viewer/TOC/styled.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/components/viewer/TOC/styled.ts b/frontend/components/viewer/TOC/styled.ts index 6bdc3846..0a94f6d7 100644 --- a/frontend/components/viewer/TOC/styled.ts +++ b/frontend/components/viewer/TOC/styled.ts @@ -63,11 +63,13 @@ export const TocContainer = styled.div` color: var(--grey-01-color); background-color: var(--white-color); border-radius: 16px; - overflow: auto; + overflow-x: hidden; + overflow-y: scroll; ::-webkit-scrollbar { width: 10px; } + ::-webkit-scrollbar-thumb { background-color: var(--grey-02-color); border-radius: 10px; From a0a5eb50ce0d9cff3a5a5f1afa05c275ae8cf6bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=84?= Date: Mon, 12 Dec 2022 18:39:07 +0900 Subject: [PATCH 08/44] =?UTF-8?q?fix:=20=ED=97=A4=EB=94=A9=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EB=82=B4=EB=B6=80=20=ED=85=8D=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=97=90=20=EA=B3=B5=EB=B0=B1=EC=9D=B4=20=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20TOC=EA=B0=80=20=EB=8F=99=EC=9E=91=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=98=A4=EB=A5=98=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/utils/articleConversion.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/utils/articleConversion.ts b/frontend/utils/articleConversion.ts index 94e01841..27dfc7f7 100644 --- a/frontend/utils/articleConversion.ts +++ b/frontend/utils/articleConversion.ts @@ -29,7 +29,7 @@ export const articleConversion = (content: string) => { if (v.includes('h1') || v.includes('h2') || v.includes('h3')) { const title = v.replace(/<[^>]*>?/g, ''); const result = v.split(''); - result.splice(3, 0, ' ', `id=${title}`); + result.splice(3, 0, ' ', `id="${title}"`); return result.join(''); } return v; From cfd8a93330196f9aab492d963ce9e3547f55711c Mon Sep 17 00:00:00 2001 From: parkhyeonki Date: Mon, 12 Dec 2022 18:46:31 +0900 Subject: [PATCH 09/44] =?UTF-8?q?feat:=20toast=EB=A9=94=EC=84=B8=EC=A7=80?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80,=20=EB=A7=81=ED=81=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/edit/ModifyModal/index.tsx | 7 ++++++- frontend/components/edit/PublishModal/index.tsx | 7 ++++++- frontend/components/viewer/ScrapModal/index.tsx | 2 ++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/frontend/components/edit/ModifyModal/index.tsx b/frontend/components/edit/ModifyModal/index.tsx index 8c56b093..3bd91976 100644 --- a/frontend/components/edit/ModifyModal/index.tsx +++ b/frontend/components/edit/ModifyModal/index.tsx @@ -12,6 +12,7 @@ import Dropdown from '@components/common/Dropdown'; import ModalButton from '@components/common/Modal/ModalButton'; import useFetch from '@hooks/useFetch'; import { IArticle, IBook, IBookScraps, IScrap } from '@interfaces'; +import { toastSuccess } from '@utils/toast'; import { ArticleWrapper, Label, ModifyModalWrapper, WarningLabel } from './styled'; @@ -95,7 +96,11 @@ export default function ModifyModal({ books, originalArticle }: ModifyModalProps }; useEffect(() => { - if (modifiedArticle) router.push('/'); + if (modifiedArticle) { + const { id, title } = modifiedArticle.modifiedArticle; + router.push(`/viewer/${selectedBookIndex}/${id}`); + toastSuccess(`${title}글이 수정되었습니다.`); + } }, [modifiedArticle]); return ( diff --git a/frontend/components/edit/PublishModal/index.tsx b/frontend/components/edit/PublishModal/index.tsx index bb7de33c..3df67518 100644 --- a/frontend/components/edit/PublishModal/index.tsx +++ b/frontend/components/edit/PublishModal/index.tsx @@ -12,6 +12,7 @@ import Dropdown from '@components/common/Dropdown'; import ModalButton from '@components/common/Modal/ModalButton'; import useFetch from '@hooks/useFetch'; import { IBook, IBookScraps, IScrap } from '@interfaces'; +import { toastSuccess } from '@utils/toast'; import { ArticleWrapper, Label, PublishModalWrapper } from './styled'; @@ -73,7 +74,11 @@ export default function PublishModal({ books }: PublishModalProps) { }; useEffect(() => { - if (createdArticle) router.push('/'); + if (createdArticle) { + const { id, title } = createdArticle.createdArticle; + router.push(`/viewer/${selectedBookIndex}/${id}`); + toastSuccess(`${title}글이 발행되었습니다.`); + } }, [createdArticle]); return ( diff --git a/frontend/components/viewer/ScrapModal/index.tsx b/frontend/components/viewer/ScrapModal/index.tsx index 3d307bc9..a453fd74 100644 --- a/frontend/components/viewer/ScrapModal/index.tsx +++ b/frontend/components/viewer/ScrapModal/index.tsx @@ -13,6 +13,7 @@ import Dropdown from '@components/common/Dropdown'; import ModalButton from '@components/common/Modal/ModalButton'; import useFetch from '@hooks/useFetch'; import { IBook, IArticle, IScrap, IBookScraps } from '@interfaces'; +import { toastSuccess } from '@utils/toast'; import { ArticleWrapper, Label, ScrapModalWrapper, WarningLabel } from './styled'; @@ -97,6 +98,7 @@ export default function ScrapModal({ bookId, handleModalClose, article }: ScrapM useEffect(() => { if (createScrapData === undefined) return; router.push(`/viewer/${bookId}/${article.id}`); + toastSuccess(`${article.title}글이 스크랩되었습니다.`); handleModalClose(); }, [createScrapData]); From de13a38ac19134c23385419e410bf3c51ed9c499 Mon Sep 17 00:00:00 2001 From: MinHK4 Date: Mon, 12 Dec 2022 18:48:41 +0900 Subject: [PATCH 10/44] =?UTF-8?q?feat:=20sessionStorage=20=ED=9B=85=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20-=20#303?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/hooks/useSeesionStorage.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 frontend/hooks/useSeesionStorage.ts diff --git a/frontend/hooks/useSeesionStorage.ts b/frontend/hooks/useSeesionStorage.ts new file mode 100644 index 00000000..2151e819 --- /dev/null +++ b/frontend/hooks/useSeesionStorage.ts @@ -0,0 +1,23 @@ +import { useEffect, useState } from 'react'; + +const useSessionStorage = (key: string, initialValue: T) => { + const [value, setStateValue] = useState(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; From 535f58519183196c62b4ea5ffbc65a55a3ade7dc Mon Sep 17 00:00:00 2001 From: parkhyeonki Date: Mon, 12 Dec 2022 19:05:50 +0900 Subject: [PATCH 11/44] =?UTF-8?q?feat:=20=EC=84=9C=EC=9E=AC=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=B1=85=20=EC=82=AC=EC=9D=B4=EC=A6=88=20?= =?UTF-8?q?=EA=B3=A0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/common/Book/styled.ts | 22 +++++++++---------- .../components/study/EditBookModal/index.tsx | 2 +- .../components/study/UserProfile/styled.ts | 2 +- frontend/utils/toast.ts | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/frontend/components/common/Book/styled.ts b/frontend/components/common/Book/styled.ts index e82fa156..27a88bff 100644 --- a/frontend/components/common/Book/styled.ts +++ b/frontend/components/common/Book/styled.ts @@ -18,24 +18,24 @@ export const BookWrapper = styled(FlexColumn)` overflow: hidden; color: var(--grey-01-color); - aspect-ratio: 280/480; - @media ${(props) => props.theme.tablet} { - width: 100%; - height: auto; - overflow: none; - } + // aspect-ratio: 280/480; + // @media ${(props) => props.theme.tablet} { + // width: 100%; + // height: auto; + // overflow: none; + // } `; export const BookThumbnail = styled(Image)` width: 280px; min-height: 200px; object-fit: cover; - aspect-ratio: 280/200; + // aspect-ratio: 280/200; - @media ${(props) => props.theme.tablet} { - width: 100%; - min-height: auto; - } + // @media ${(props) => props.theme.tablet} { + // width: 100%; + // min-height: auto; + // } `; export const BookInfoContainer = styled(FlexColumn)` diff --git a/frontend/components/study/EditBookModal/index.tsx b/frontend/components/study/EditBookModal/index.tsx index cf34e2c3..0fc9381f 100644 --- a/frontend/components/study/EditBookModal/index.tsx +++ b/frontend/components/study/EditBookModal/index.tsx @@ -151,7 +151,7 @@ export default function EditBookModal({ book, handleModalClose }: BookProps) { Contents - {isContentsShown ? '저장' : '수정'} + {isContentsShown ? '저장' : 'ㅎ수정'} diff --git a/frontend/components/study/UserProfile/styled.ts b/frontend/components/study/UserProfile/styled.ts index 2b89c6bf..6cb558a4 100644 --- a/frontend/components/study/UserProfile/styled.ts +++ b/frontend/components/study/UserProfile/styled.ts @@ -40,7 +40,7 @@ export const Username = styled(TextLarge)` export const UserDescription = styled(TextSmall)` width: 400px; @media ${(props) => props.theme.tablet} { - width: 300px; + width: 250px; } `; diff --git a/frontend/utils/toast.ts b/frontend/utils/toast.ts index 9b2b20b2..d1172285 100644 --- a/frontend/utils/toast.ts +++ b/frontend/utils/toast.ts @@ -3,7 +3,7 @@ import { toast } from 'react-toastify'; export const toastError = (message: string) => { toast.error(message, { position: 'top-right', - autoClose: 3000, + autoClose: 1000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, @@ -16,7 +16,7 @@ export const toastError = (message: string) => { export const toastSuccess = (message: string) => { toast.success(message, { position: 'top-right', - autoClose: 3000, + autoClose: 1000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, From e247b1c91b53f96b2d589509ec3f3e23fe184530 Mon Sep 17 00:00:00 2001 From: MinHK4 Date: Mon, 12 Dec 2022 21:40:26 +0900 Subject: [PATCH 12/44] =?UTF-8?q?chore:=20=EC=9D=B4=EB=A6=84=20=EC=98=A4?= =?UTF-8?q?=ED=83=80=20=EC=88=98=EC=A0=95=20-=20#303?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/hooks/{useSeesionStorage.ts => useSessionStorage.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename frontend/hooks/{useSeesionStorage.ts => useSessionStorage.ts} (100%) diff --git a/frontend/hooks/useSeesionStorage.ts b/frontend/hooks/useSessionStorage.ts similarity index 100% rename from frontend/hooks/useSeesionStorage.ts rename to frontend/hooks/useSessionStorage.ts From 294ac38800b88a434e6f9e15ddd37302de2fb858 Mon Sep 17 00:00:00 2001 From: parkhyeonki Date: Mon, 12 Dec 2022 21:48:36 +0900 Subject: [PATCH 13/44] =?UTF-8?q?chore:=20=EB=AC=B8=EA=B5=AC=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/study/EditBookModal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/study/EditBookModal/index.tsx b/frontend/components/study/EditBookModal/index.tsx index 0fc9381f..8710b299 100644 --- a/frontend/components/study/EditBookModal/index.tsx +++ b/frontend/components/study/EditBookModal/index.tsx @@ -151,7 +151,7 @@ export default function EditBookModal({ book, handleModalClose }: BookProps) { Contents - {isContentsShown ? '저장' : 'ㅎ수정'} + {isContentsShown ? '순서저장' : '순서수정'} From c12704000f5132f9d242920eb79862a37ddf261d Mon Sep 17 00:00:00 2001 From: MinHK4 Date: Mon, 12 Dec 2022 21:58:10 +0900 Subject: [PATCH 14/44] =?UTF-8?q?feat:=20slider=20useSessionStorage=20?= =?UTF-8?q?=ED=9B=85=20=EC=A0=81=EC=9A=A9=20-=20#303?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/home/Slider/index.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/frontend/components/home/Slider/index.tsx b/frontend/components/home/Slider/index.tsx index 8f17cd51..3eb5de3a 100644 --- a/frontend/components/home/Slider/index.tsx +++ b/frontend/components/home/Slider/index.tsx @@ -7,6 +7,7 @@ import RightArrowIcon from '@assets/ico_arrow_right.svg'; import ListIcon from '@assets/ico_flower.svg'; import Book from '@components/common/Book'; import SkeletonBook from '@components/common/SkeletonBook'; +import useSessionStorage from '@hooks/useSessionStorage'; import { IBookScraps } from '@interfaces'; import { @@ -37,8 +38,20 @@ const setNumBetween = (val: number, min: number, max: number) => { }; function Slider({ bookList, title, isLoading, numberPerPage }: SliderProps) { - const [curBookIndex, setCurBookIndex] = useState(0); - const [sliderNumber, setSliderNumber] = useState(1); + // const [curBookIndex, setCurBookIndex] = useState(0); + // const [sliderNumber, setSliderNumber] = useState(1); + + const { + value: curBookIndex, + setValue: setCurBookIndex, + isValueSet: isCurBookIndexSet, + } = useSessionStorage(`${title}_curBookIndex`, 0); + + const { + value: sliderNumber, + setValue: setSliderNumber, + isValueSet: isSliderNumberSet, + } = useSessionStorage(`${title}sliderNumber`, 1); const SkeletonList = Array.from({ length: numberPerPage }, (_, i) => i + 1); From 2abf4e1406f195532ecb511e77bc223788a0a9bf Mon Sep 17 00:00:00 2001 From: MinHK4 Date: Mon, 12 Dec 2022 22:22:22 +0900 Subject: [PATCH 15/44] =?UTF-8?q?feat:=20useSessionStorage=20=ED=9B=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20#303?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/home/Slider/index.tsx | 23 +++++++++-------------- frontend/hooks/useSessionStorage.ts | 17 ++++++----------- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/frontend/components/home/Slider/index.tsx b/frontend/components/home/Slider/index.tsx index 3eb5de3a..0c69a6a3 100644 --- a/frontend/components/home/Slider/index.tsx +++ b/frontend/components/home/Slider/index.tsx @@ -38,20 +38,15 @@ const setNumBetween = (val: number, min: number, max: number) => { }; function Slider({ bookList, title, isLoading, numberPerPage }: SliderProps) { - // const [curBookIndex, setCurBookIndex] = useState(0); - // const [sliderNumber, setSliderNumber] = useState(1); - - const { - value: curBookIndex, - setValue: setCurBookIndex, - isValueSet: isCurBookIndexSet, - } = useSessionStorage(`${title}_curBookIndex`, 0); - - const { - value: sliderNumber, - setValue: setSliderNumber, - isValueSet: isSliderNumberSet, - } = useSessionStorage(`${title}sliderNumber`, 1); + const { value: curBookIndex, setValue: setCurBookIndex } = useSessionStorage( + `${title}_curBookIndex`, + 0 + ); + + const { value: sliderNumber, setValue: setSliderNumber } = useSessionStorage( + `${title}_sliderNumber`, + 1 + ); const SkeletonList = Array.from({ length: numberPerPage }, (_, i) => i + 1); diff --git a/frontend/hooks/useSessionStorage.ts b/frontend/hooks/useSessionStorage.ts index 2151e819..4aa19399 100644 --- a/frontend/hooks/useSessionStorage.ts +++ b/frontend/hooks/useSessionStorage.ts @@ -1,23 +1,18 @@ -import { useEffect, useState } from 'react'; +import { useState } from 'react'; const useSessionStorage = (key: string, initialValue: T) => { - const [value, setStateValue] = useState(initialValue); - const [isValueSet, setIsValueSet] = useState(false); + const savedValue = sessionStorage.getItem(key); - useEffect(() => { - if (typeof window !== 'undefined') { - setIsValueSet(true); - const savedValue = sessionStorage.getItem(key); - if (savedValue) setStateValue(JSON.parse(savedValue)); - } - }, [typeof window]); + const [value, setStateValue] = useState( + savedValue === null ? initialValue : JSON.parse(savedValue) + ); const setValue = (newValue: T) => { setStateValue(newValue); sessionStorage.setItem(key, JSON.stringify(newValue)); }; - return { value, isValueSet, setValue }; + return { value, setValue }; }; export default useSessionStorage; From 984afbe0b47489ebaee3bc293464e5a86f6463a5 Mon Sep 17 00:00:00 2001 From: MinHK4 Date: Mon, 12 Dec 2022 22:24:05 +0900 Subject: [PATCH 16/44] =?UTF-8?q?chore:=20import=20=EC=A0=95=EB=A6=AC=20-?= =?UTF-8?q?=20#303?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/home/Slider/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/home/Slider/index.tsx b/frontend/components/home/Slider/index.tsx index 0c69a6a3..0088618a 100644 --- a/frontend/components/home/Slider/index.tsx +++ b/frontend/components/home/Slider/index.tsx @@ -1,6 +1,6 @@ import Image from 'next/image'; -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import LeftArrowIcon from '@assets/ico_arrow_left.svg'; import RightArrowIcon from '@assets/ico_arrow_right.svg'; From 1a5591e2bdc3c558b8a2bf0375392d8e3e95208c Mon Sep 17 00:00:00 2001 From: MinHK4 Date: Mon, 12 Dec 2022 22:50:24 +0900 Subject: [PATCH 17/44] =?UTF-8?q?feat:=20Slider=20=EC=86=8D=EB=8F=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20-=20#303?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/home/Slider/styled.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/home/Slider/styled.ts b/frontend/components/home/Slider/styled.ts index eee8827c..40fa5bf7 100644 --- a/frontend/components/home/Slider/styled.ts +++ b/frontend/components/home/Slider/styled.ts @@ -51,7 +51,7 @@ export const SliderBookContainer = styled.div` export const SliderTrack = styled.div<{ curBookIndex: number }>` display: flex; ${(props) => `transform: translateX(-${300 * props.curBookIndex}px);`} - transition: transform 700ms ease 0ms; + transition: transform 500ms ease 0ms; `; export const SliderIndicatorContainer = styled.div` From 143a714fbea0296258aa8c61a2bf4b6143cff834 Mon Sep 17 00:00:00 2001 From: MinHK4 Date: Mon, 12 Dec 2022 22:50:43 +0900 Subject: [PATCH 18/44] =?UTF-8?q?feat:=20Slider=20touch=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80=20-=20#303?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/home/Slider/index.tsx | 24 +++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/frontend/components/home/Slider/index.tsx b/frontend/components/home/Slider/index.tsx index 0088618a..5d2658a4 100644 --- a/frontend/components/home/Slider/index.tsx +++ b/frontend/components/home/Slider/index.tsx @@ -1,6 +1,6 @@ import Image from 'next/image'; -import { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import LeftArrowIcon from '@assets/ico_arrow_left.svg'; import RightArrowIcon from '@assets/ico_arrow_right.svg'; @@ -48,6 +48,8 @@ function Slider({ bookList, title, isLoading, numberPerPage }: SliderProps) { 1 ); + const [touchPositionX, setTouchPositionX] = useState(0); + const SkeletonList = Array.from({ length: numberPerPage }, (_, i) => i + 1); const sliderIndicatorCount = bookList ? Math.ceil(bookList.length / numberPerPage) : 0; @@ -63,6 +65,20 @@ function Slider({ bookList, title, isLoading, numberPerPage }: SliderProps) { setSliderNumber(sliderNumber + 1); }; + const handleSliderTrackTouchStart = (e: React.TouchEvent) => { + setTouchPositionX(e.changedTouches[0].pageX); + }; + + const handleSliderTrackTouchEnd = (e: React.TouchEvent) => { + const distanceX = touchPositionX - e.changedTouches[0].pageX; + if (distanceX > 0 && sliderNumber !== sliderIndicatorCount) { + handleRightArrowClick(); + } + if (distanceX < 0 && sliderNumber !== 1) { + handleLeftArrowClick(); + } + }; + useEffect(() => { if (!bookList) return; @@ -101,7 +117,11 @@ function Slider({ bookList, title, isLoading, numberPerPage }: SliderProps) { - + {isLoading ? SkeletonList.map((key) => ) : bookList.map((book) => ( From bfa81ab2147c92381f88b11458eff0e20aa6932c Mon Sep 17 00:00:00 2001 From: dahyeon405 Date: Mon, 12 Dec 2022 23:30:54 +0900 Subject: [PATCH 19/44] =?UTF-8?q?feat:=20favicon=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/public/favicon.ico | Bin 25931 -> 2626 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico index 718d6fea4835ec2d246af9800eddb7ffb276240c..8923f4ab07051a6cbbf6ffa2f1cfc502062224a1 100644 GIT binary patch literal 2626 zcmZuz2~<+s8r}zia6qAOC{4|&_sRjUtBYkSm6qm^mKI%0A)iB8X+A3}jbjd&LpE5N zT2gt-mSg2jEmMT1TWY43V=7lO%@iEE0V2GEWq0-N>%wMSx|xaAxon;IC#d&ZLsd6p+^QCP4aDo< zh{+DgY$vj2C~4Wk#8X85h_i0siCejcv6e1dyKA47Pu<1R8w1l`Ly^OsC(8P=q`i`e z2O%Z$%a?8L{n>oh`l(J}+TVWCK37;?xorQqUcEz(W@`_u zGX(E3{j+?EmJ$kqm^(zL6Je044PW8DVLU>n?kmR+%Uv1O&U0ui*UAE**)^b`Y{Gx= zwj;uNzfZP6a={=|7oIlg?XPF8M>XFUf6>S|-hZtD#F!ONa5fx+ToxGgd|Jo)NlAD$ z2Ao=;zgd~u-74S9*VI^(xfp)w_r~tI&FosH#+E_Ka`_)3YczeDB2N(BNZBnltzA!dep*V*Ug%%pMTF~7XZ?57?TS;qdWIp3 zqG;EQ?c~^Ntw51!AozVF<*l|faeEeJPw1>*$b>>G#Bu153&oM<9j7XL7U4wEemE9V z_jJ88z^ZblQ)b{-s_-&pg_q{TA@B+X{+bF5=At4V02tr^Ne8w;E4C{Leq;*`1;HvHb1AHe0zue*_j$s( ze4KZ2m~RQc?_yg+wsys&`P^uBCde}ehF~r?G9dY2By3dYnE$}VP6l}^=hh`+=@-As z(t@YH%i65M+5i6=8kCDL^RmCzt&jl^Vd1s_&h427En7On#Fu6h<==+*{E-JBU2pp0*bt2-Vt^I zXt?bK&LF3T-2kMk_1-jMUde-Fs+y4VmP3St_<{Cl`kt!BuVFjSH2g??_sQ{it_^a4 zo6~?vovPr#OlR+Ki2jQ^9ii8ZK%+8eNLq2S?@VLw$Tr3u=MsWhGX{9y^TO3cD}EOWpl33 zVUV{RDNjF&fBPHX&yBstuMB1aV1_ucKVFy-Q`vEpJ zmG=y}Ycj{TOdNsW$$}YAz4qhrc0Q{tzy}5sQc z>SOuw+R8WFpvZO5bPv%68lm=GS7c@Wu@Z|9#HKjw^Zv4{d6ZcOBWt7Mm_a-!@L&eo zn(Afu`E=f%9ntjhAWms5Fq~C>@5e7Jzu}zd5wH=oj7m;$Gn>-G)Ak+#+gZBYhpA;n z)~i%Foi_;Z^udoixK6pf*>_Rp%j)hq{vIA1_(GMln_;EfmpzF(+dC#tZVY#iRf^b7 zd;M&vPp5Q=tS!^}W-(Z+sV?m9Yl|M6F>D5qD0rZb%*d$~_i6pvAYgcyuRtkb``>Rg zxJ1vXoj9s-D@nj&ad~_eKTR*;+WA_wwk@E=xqB0E$5Z4p+@<39uq&g-4~X`Pr~}4i zIyk6o*Dbvn9A+Q8`E$tbVu5oylNkns{V(z*Q%KgmCAYp5SvHPV<_aoe9=-m6QX{~J z#WPAM`p1g5U)1Zy7*`w@t6MDt_fFJM=xZ#GJ!lNycmHh${^beUhmity>pMLk;BNC) zvV8L9_>n8OC}=CoWuSE`|MPM2P@kR^p+`Q@{nDO{_k#)}cpBJu-}J3_jPmj5H^tP6m%JN9FS3`H#u#aL7WzPU@Ob zK^!E|tXgYOWqY`Qz|j!~u`QLzQhYr#jk`2HE2*2;6VtqBP5R;Xn5+G^sprRr6ND)s zO8eAwHC(hH`rqW186~%=m+?_XqaNah-&Qj0s*HNN2J)*@)j3|Wd%dT;RY04^WoS5= zDk+wiSzr~912u!2qD)L2$kdI?NV?=aHwm1ysQ0h5{g(-t^81yU^s6!I!c--;eWxLb z0H^vnJihOXKB5|+!o2GeS{{`ka))$QqXwqjPGMquXrzz*ox*ic#}QmF(NnqZ`BAmI z5uqtzECFl*y3bJW?r`ujiT!lcwSx|M2acFsXGx6w1j{;1`$A0~9lJ}eA-om6gjStv z;?hVrJ4Caa=p6-8SMCK~5dW#v9-f)1#9!Myt~S+`QMm|yjpquFDS7!em68APrAo~~ Z4($eK%hXqh^pMv-;Ogw@RJxvi>VF$I10DbX literal 25931 zcmeHv30#a{`}aL_*G&7qml|y<+KVaDM2m#dVr!KsA!#An?kSQM(q<_dDNCpjEux83 zLb9Z^XxbDl(w>%i@8hT6>)&Gu{h#Oeyszu?xtw#Zb1mO{pgX9699l+Qppw7jXaYf~-84xW z)w4x8?=youko|}Vr~(D$UXIbiXABHh`p1?nn8Po~fxRJv}|0e(BPs|G`(TT%kKVJAdg5*Z|x0leQq0 zkdUBvb#>9F()jo|T~kx@OM8$9wzs~t2l;K=woNssA3l6|sx2r3+kdfVW@e^8e*E}v zA1y5{bRi+3Z`uD3{F7LgFJDdvm;nJilkzDku>BwXH(8ItVCXk*-lSJnR?-2UN%hJ){&rlvg`CDTj z)Bzo!3v7Ou#83zEDEFcKt(f1E0~=rqeEbTnMvWR#{+9pg%7G8y>u1OVRUSoox-ovF z2Ydma(;=YuBY(eI|04{hXzZD6_f(v~H;C~y5=DhAC{MMS>2fm~1H_t2$56pc$NH8( z5bH|<)71dV-_oCHIrzrT`2s-5w_+2CM0$95I6X8p^r!gHp+j_gd;9O<1~CEQQGS8) zS9Qh3#p&JM-G8rHekNmKVewU;pJRcTAog68KYo^dRo}(M>36U4Us zfgYWSiHZL3;lpWT=zNAW>Dh#mB!_@Lg%$ms8N-;aPqMn+C2HqZgz&9~Eu z4|Kp<`$q)Uw1R?y(~S>ePdonHxpV1#eSP1B;Ogo+-Pk}6#0GsZZ5!||ev2MGdh}_m z{DeR7?0-1^zVs&`AV6Vt;r3`I`OI_wgs*w=eO%_#7Kepl{B@xiyCANc(l zzIyd4y|c6PXWq9-|KM8(zIk8LPk(>a)zyFWjhT!$HJ$qX1vo@d25W<fvZQ2zUz5WRc(UnFMKHwe1| zWmlB1qdbiA(C0jmnV<}GfbKtmcu^2*P^O?MBLZKt|As~ge8&AAO~2K@zbXelK|4T<{|y4`raF{=72kC2Kn(L4YyenWgrPiv z@^mr$t{#X5VuIMeL!7Ab6_kG$&#&5p*Z{+?5U|TZ`B!7llpVmp@skYz&n^8QfPJzL z0G6K_OJM9x+Wu2gfN45phANGt{7=C>i34CV{Xqlx(fWpeAoj^N0Biu`w+MVcCUyU* zDZuzO0>4Z6fbu^T_arWW5n!E45vX8N=bxTVeFoep_G#VmNlQzAI_KTIc{6>c+04vr zx@W}zE5JNSU>!THJ{J=cqjz+4{L4A{Ob9$ZJ*S1?Ggg3klFp!+Y1@K+pK1DqI|_gq z5ZDXVpge8-cs!o|;K73#YXZ3AShj50wBvuq3NTOZ`M&qtjj#GOFfgExjg8Gn8>Vq5 z`85n+9|!iLCZF5$HJ$Iu($dm?8~-ofu}tEc+-pyke=3!im#6pk_Wo8IA|fJwD&~~F zc16osQ)EBo58U7XDuMexaPRjU@h8tXe%S{fA0NH3vGJFhuyyO!Uyl2^&EOpX{9As0 zWj+P>{@}jxH)8|r;2HdupP!vie{sJ28b&bo!8`D^x}TE$%zXNb^X1p@0PJ86`dZyj z%ce7*{^oo+6%&~I!8hQy-vQ7E)0t0ybH4l%KltWOo~8cO`T=157JqL(oq_rC%ea&4 z2NcTJe-HgFjNg-gZ$6!Y`SMHrlj}Etf7?r!zQTPPSv}{so2e>Fjs1{gzk~LGeesX%r(Lh6rbhSo_n)@@G-FTQy93;l#E)hgP@d_SGvyCp0~o(Y;Ee8{ zdVUDbHm5`2taPUOY^MAGOw*>=s7=Gst=D+p+2yON!0%Hk` zz5mAhyT4lS*T3LS^WSxUy86q&GnoHxzQ6vm8)VS}_zuqG?+3td68_x;etQAdu@sc6 zQJ&5|4(I?~3d-QOAODHpZ=hlSg(lBZ!JZWCtHHSj`0Wh93-Uk)_S%zsJ~aD>{`A0~ z9{AG(e|q3g5B%wYKRxiL2Y$8(4w6bzchKuloQW#e&S3n+P- z8!ds-%f;TJ1>)v)##>gd{PdS2Oc3VaR`fr=`O8QIO(6(N!A?pr5C#6fc~Ge@N%Vvu zaoAX2&(a6eWy_q&UwOhU)|P3J0Qc%OdhzW=F4D|pt0E4osw;%<%Dn58hAWD^XnZD= z>9~H(3bmLtxpF?a7su6J7M*x1By7YSUbxGi)Ot0P77`}P3{)&5Un{KD?`-e?r21!4vTTnN(4Y6Lin?UkSM z`MXCTC1@4A4~mvz%Rh2&EwY))LeoT=*`tMoqcEXI>TZU9WTP#l?uFv+@Dn~b(>xh2 z;>B?;Tz2SR&KVb>vGiBSB`@U7VIWFSo=LDSb9F{GF^DbmWAfpms8Sx9OX4CnBJca3 zlj9(x!dIjN?OG1X4l*imJNvRCk}F%!?SOfiOq5y^mZW)jFL@a|r-@d#f7 z2gmU8L3IZq0ynIws=}~m^#@&C%J6QFo~Mo4V`>v7MI-_!EBMMtb%_M&kvAaN)@ZVw z+`toz&WG#HkWDjnZE!6nk{e-oFdL^$YnbOCN}JC&{$#$O27@|Tn-skXr)2ml2~O!5 zX+gYoxhoc7qoU?C^3~&!U?kRFtnSEecWuH0B0OvLodgUAi}8p1 zrO6RSXHH}DMc$&|?D004DiOVMHV8kXCP@7NKB zgaZq^^O<7PoKEp72kby@W0Z!Y*Ay{&vfg#C&gG@YVR9g?FEocMUi1gSN$+V+ayF45{a zuDZDTN}mS|;BO%gEf}pjBfN2-gIrU#G5~cucA;dokXW89%>AyXJJI z9X4UlIWA|ZYHgbI z5?oFk@A=Ik7lrEQPDH!H+b`7_Y~aDb_qa=B2^Y&Ow41cU=4WDd40dp5(QS-WMN-=Y z9g;6_-JdNU;|6cPwf$ak*aJIcwL@1n$#l~zi{c{EW?T;DaW*E8DYq?Umtz{nJ&w-M zEMyTDrC&9K$d|kZe2#ws6)L=7K+{ zQw{XnV6UC$6-rW0emqm8wJoeZK)wJIcV?dST}Z;G0Arq{dVDu0&4kd%N!3F1*;*pW zR&qUiFzK=@44#QGw7k1`3t_d8&*kBV->O##t|tonFc2YWrL7_eqg+=+k;!F-`^b8> z#KWCE8%u4k@EprxqiV$VmmtiWxDLgnGu$Vs<8rppV5EajBXL4nyyZM$SWVm!wnCj-B!Wjqj5-5dNXukI2$$|Bu3Lrw}z65Lc=1G z^-#WuQOj$hwNGG?*CM_TO8Bg-1+qc>J7k5c51U8g?ZU5n?HYor;~JIjoWH-G>AoUP ztrWWLbRNqIjW#RT*WqZgPJXU7C)VaW5}MiijYbABmzoru6EmQ*N8cVK7a3|aOB#O& zBl8JY2WKfmj;h#Q!pN%9o@VNLv{OUL?rixHwOZuvX7{IJ{(EdPpuVFoQqIOa7giLVkBOKL@^smUA!tZ1CKRK}#SSM)iQHk)*R~?M!qkCruaS!#oIL1c z?J;U~&FfH#*98^G?i}pA{ z9Jg36t4=%6mhY(quYq*vSxptes9qy|7xSlH?G=S@>u>Ebe;|LVhs~@+06N<4CViBk zUiY$thvX;>Tby6z9Y1edAMQaiH zm^r3v#$Q#2T=X>bsY#D%s!bhs^M9PMAcHbCc0FMHV{u-dwlL;a1eJ63v5U*?Q_8JO zT#50!RD619#j_Uf))0ooADz~*9&lN!bBDRUgE>Vud-i5ck%vT=r^yD*^?Mp@Q^v+V zG#-?gKlr}Eeqifb{|So?HM&g91P8|av8hQoCmQXkd?7wIJwb z_^v8bbg`SAn{I*4bH$u(RZ6*xUhuA~hc=8czK8SHEKTzSxgbwi~9(OqJB&gwb^l4+m`k*Q;_?>Y-APi1{k zAHQ)P)G)f|AyjSgcCFps)Fh6Bca*Xznq36!pV6Az&m{O8$wGFD? zY&O*3*J0;_EqM#jh6^gMQKpXV?#1?>$ml1xvh8nSN>-?H=V;nJIwB07YX$e6vLxH( zqYwQ>qxwR(i4f)DLd)-$P>T-no_c!LsN@)8`e;W@)-Hj0>nJ-}Kla4-ZdPJzI&Mce zv)V_j;(3ERN3_@I$N<^|4Lf`B;8n+bX@bHbcZTopEmDI*Jfl)-pFDvo6svPRoo@(x z);_{lY<;);XzT`dBFpRmGrr}z5u1=pC^S-{ce6iXQlLGcItwJ^mZx{m$&DA_oEZ)B{_bYPq-HA zcH8WGoBG(aBU_j)vEy+_71T34@4dmSg!|M8Vf92Zj6WH7Q7t#OHQqWgFE3ARt+%!T z?oLovLVlnf?2c7pTc)~cc^($_8nyKwsN`RA-23ed3sdj(ys%pjjM+9JrctL;dy8a( z@en&CQmnV(()bu|Y%G1-4a(6x{aLytn$T-;(&{QIJB9vMox11U-1HpD@d(QkaJdEb zG{)+6Dos_L+O3NpWo^=gR?evp|CqEG?L&Ut#D*KLaRFOgOEK(Kq1@!EGcTfo+%A&I z=dLbB+d$u{sh?u)xP{PF8L%;YPPW53+@{>5W=Jt#wQpN;0_HYdw1{ksf_XhO4#2F= zyPx6Lx2<92L-;L5PD`zn6zwIH`Jk($?Qw({erA$^bC;q33hv!d!>%wRhj# zal^hk+WGNg;rJtb-EB(?czvOM=H7dl=vblBwAv>}%1@{}mnpUznfq1cE^sgsL0*4I zJ##!*B?=vI_OEVis5o+_IwMIRrpQyT_Sq~ZU%oY7c5JMIADzpD!Upz9h@iWg_>>~j zOLS;wp^i$-E?4<_cp?RiS%Rd?i;f*mOz=~(&3lo<=@(nR!_Rqiprh@weZlL!t#NCc zO!QTcInq|%#>OVgobj{~ixEUec`E25zJ~*DofsQdzIa@5^nOXj2T;8O`l--(QyU^$t?TGY^7#&FQ+2SS3B#qK*k3`ye?8jUYSajE5iBbJls75CCc(m3dk{t?- zopcER9{Z?TC)mk~gpi^kbbu>b-+a{m#8-y2^p$ka4n60w;Sc2}HMf<8JUvhCL0B&Btk)T`ctE$*qNW8L$`7!r^9T+>=<=2qaq-;ll2{`{Rg zc5a0ZUI$oG&j-qVOuKa=*v4aY#IsoM+1|c4Z)<}lEDvy;5huB@1RJPquU2U*U-;gu z=En2m+qjBzR#DEJDO`WU)hdd{Vj%^0V*KoyZ|5lzV87&g_j~NCjwv0uQVqXOb*QrQ zy|Qn`hxx(58c70$E;L(X0uZZ72M1!6oeg)(cdKO ze0gDaTz+ohR-#d)NbAH4x{I(21yjwvBQfmpLu$)|m{XolbgF!pmsqJ#D}(ylp6uC> z{bqtcI#hT#HW=wl7>p!38sKsJ`r8}lt-q%Keqy%u(xk=yiIJiUw6|5IvkS+#?JTBl z8H5(Q?l#wzazujH!8o>1xtn8#_w+397*_cy8!pQGP%K(Ga3pAjsaTbbXJlQF_+m+-UpUUent@xM zg%jqLUExj~o^vQ3Gl*>wh=_gOr2*|U64_iXb+-111aH}$TjeajM+I20xw(((>fej-@CIz4S1pi$(#}P7`4({6QS2CaQS4NPENDp>sAqD z$bH4KGzXGffkJ7R>V>)>tC)uax{UsN*dbeNC*v}#8Y#OWYwL4t$ePR?VTyIs!wea+ z5Urmc)X|^`MG~*dS6pGSbU+gPJoq*^a=_>$n4|P^w$sMBBy@f*Z^Jg6?n5?oId6f{ z$LW4M|4m502z0t7g<#Bx%X;9<=)smFolV&(V^(7Cv2-sxbxopQ!)*#ZRhTBpx1)Fc zNm1T%bONzv6@#|dz(w02AH8OXe>kQ#1FMCzO}2J_mST)+ExmBr9cva-@?;wnmWMOk z{3_~EX_xadgJGv&H@zK_8{(x84`}+c?oSBX*Ge3VdfTt&F}yCpFP?CpW+BE^cWY0^ zb&uBN!Ja3UzYHK-CTyA5=L zEMW{l3Usky#ly=7px648W31UNV@K)&Ub&zP1c7%)`{);I4b0Q<)B}3;NMG2JH=X$U zfIW4)4n9ZM`-yRj67I)YSLDK)qfUJ_ij}a#aZN~9EXrh8eZY2&=uY%2N0UFF7<~%M zsB8=erOWZ>Ct_#^tHZ|*q`H;A)5;ycw*IcmVxi8_0Xk}aJA^ath+E;xg!x+As(M#0=)3!NJR6H&9+zd#iP(m0PIW8$ z1Y^VX`>jm`W!=WpF*{ioM?C9`yOR>@0q=u7o>BP-eSHqCgMDj!2anwH?s%i2p+Q7D zzszIf5XJpE)IG4;d_(La-xenmF(tgAxK`Y4sQ}BSJEPs6N_U2vI{8=0C_F?@7<(G; zo$~G=8p+076G;`}>{MQ>t>7cm=zGtfbdDXm6||jUU|?X?CaE?(<6bKDYKeHlz}DA8 zXT={X=yp_R;HfJ9h%?eWvQ!dRgz&Su*JfNt!Wu>|XfU&68iRikRrHRW|ZxzRR^`eIGt zIeiDgVS>IeExKVRWW8-=A=yA`}`)ZkWBrZD`hpWIxBGkh&f#ijr449~m`j6{4jiJ*C!oVA8ZC?$1RM#K(_b zL9TW)kN*Y4%^-qPpMP7d4)o?Nk#>aoYHT(*g)qmRUb?**F@pnNiy6Fv9rEiUqD(^O zzyS?nBrX63BTRYduaG(0VVG2yJRe%o&rVrLjbxTaAFTd8s;<<@Qs>u(<193R8>}2_ zuwp{7;H2a*X7_jryzriZXMg?bTuegABb^87@SsKkr2)0Gyiax8KQWstw^v#ix45EVrcEhr>!NMhprl$InQMzjSFH54x5k9qHc`@9uKQzvL4ihcq{^B zPrVR=o_ic%Y>6&rMN)hTZsI7I<3&`#(nl+3y3ys9A~&^=4?PL&nd8)`OfG#n zwAMN$1&>K++c{^|7<4P=2y(B{jJsQ0a#U;HTo4ZmWZYvI{+s;Td{Yzem%0*k#)vjpB zia;J&>}ICate44SFYY3vEelqStQWFihx%^vQ@Do(sOy7yR2@WNv7Y9I^yL=nZr3mb zXKV5t@=?-Sk|b{XMhA7ZGB@2hqsx}4xwCW!in#C zI@}scZlr3-NFJ@NFaJlhyfcw{k^vvtGl`N9xSo**rDW4S}i zM9{fMPWo%4wYDG~BZ18BD+}h|GQKc-g^{++3MY>}W_uq7jGHx{mwE9fZiPCoxN$+7 zrODGGJrOkcPQUB(FD5aoS4g~7#6NR^ma7-!>mHuJfY5kTe6PpNNKC9GGRiu^L31uG z$7v`*JknQHsYB!Tm_W{a32TM099djW%5e+j0Ve_ct}IM>XLF1Ap+YvcrLV=|CKo6S zb+9Nl3_YdKP6%Cxy@6TxZ>;4&nTneadr z_ES90ydCev)LV!dN=#(*f}|ZORFdvkYBni^aLbUk>BajeWIOcmHP#8S)*2U~QKI%S zyrLmtPqb&TphJ;>yAxri#;{uyk`JJqODDw%(Z=2`1uc}br^V%>j!gS)D*q*f_-qf8&D;W1dJgQMlaH5er zN2U<%Smb7==vE}dDI8K7cKz!vs^73o9f>2sgiTzWcwY|BMYHH5%Vn7#kiw&eItCqa zIkR2~Q}>X=Ar8W|^Ms41Fm8o6IB2_j60eOeBB1Br!boW7JnoeX6Gs)?7rW0^5psc- zjS16yb>dFn>KPOF;imD}e!enuIniFzv}n$m2#gCCv4jM#ArwlzZ$7@9&XkFxZ4n!V zj3dyiwW4Ki2QG{@i>yuZXQizw_OkZI^-3otXC{!(lUpJF33gI60ak;Uqitp74|B6I zgg{b=Iz}WkhCGj1M=hu4#Aw173YxIVbISaoc z-nLZC*6Tgivd5V`K%GxhBsp@SUU60-rfc$=wb>zdJzXS&-5(NRRodFk;Kxk!S(O(a0e7oY=E( zAyS;Ow?6Q&XA+cnkCb{28_1N8H#?J!*$MmIwLq^*T_9-z^&UE@A(z9oGYtFy6EZef LrJugUA?W`A8`#=m From 8ff7cc067530105b3cc311f14132daa0e762cb15 Mon Sep 17 00:00:00 2001 From: dahyeon405 Date: Mon, 12 Dec 2022 23:47:50 +0900 Subject: [PATCH 20/44] =?UTF-8?q?feat:=20=EB=B7=B0=EC=96=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B8=80=20=EC=9D=B4=EB=8F=99=20=EC=8B=9C?= =?UTF-8?q?=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EB=A7=A8=20=EC=9C=84=EB=A1=9C?= =?UTF-8?q?=20=EC=9D=B4=EB=8F=99=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/viewer/ArticleContent/index.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/components/viewer/ArticleContent/index.tsx b/frontend/components/viewer/ArticleContent/index.tsx index c33e9602..f3fcf265 100644 --- a/frontend/components/viewer/ArticleContent/index.tsx +++ b/frontend/components/viewer/ArticleContent/index.tsx @@ -117,6 +117,12 @@ export default function Article({ setIsScrollDown(isScrollDown ? 'true' : 'false'); }, [isScrollDown]); + useEffect(() => { + if (scrollTarget.current) { + scrollTarget.current.scrollTop = 0; + } + }, [router.query.data]); + return ( From 374440b6a00640492741d37213cbe3a2a4796dd0 Mon Sep 17 00:00:00 2001 From: dahyeon405 Date: Mon, 12 Dec 2022 23:57:29 +0900 Subject: [PATCH 21/44] =?UTF-8?q?feat:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=EC=97=90=EC=84=9C=20=EB=AA=A8=EB=8B=AC=20pla?= =?UTF-8?q?ceholder=20=ED=8F=B0=ED=8A=B8=20=EC=82=AC=EC=9D=B4=EC=A6=88=20?= =?UTF-8?q?=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/common/LabeledInput/styled.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/components/common/LabeledInput/styled.ts b/frontend/components/common/LabeledInput/styled.ts index 4cc7f700..da04e79f 100644 --- a/frontend/components/common/LabeledInput/styled.ts +++ b/frontend/components/common/LabeledInput/styled.ts @@ -13,4 +13,10 @@ export const Input = styled.input` font-size: 16px; border: 1px solid var(--grey-02-color); border-radius: 10px; + + @media ${(props) => props.theme.mobile} { + ::placeholder { + font-size: 12px; + } + } `; From 946d9b5cda0af94f40bac9cd88efe565847a8654 Mon Sep 17 00:00:00 2001 From: dahyeon405 Date: Tue, 13 Dec 2022 00:40:33 +0900 Subject: [PATCH 22/44] =?UTF-8?q?fix:=20schema=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/prisma/schema.prisma | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 326bf0cc..31b68e75 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -14,7 +14,7 @@ model User { password String @db.VarChar(100) nickname String @unique @db.VarChar(20) description String @db.VarChar(100) - profile_image String @default("https://kr.object.ncloudstorage.com/j027/522da4f3-c9d7-403d-a98f-2b09cabefc47.png") @db.VarChar(255) + profile_image String @default("https://kr.object.ncloudstorage.com/j027/20e92b3e-af66-4eab-8272-a40ea9212930.png") @db.VarChar(255) provider String @db.VarChar(20) created_at DateTime @default(now()) deleted_at DateTime? From 14c7798a09e22449a15bdc00f721c1eccd9f6697 Mon Sep 17 00:00:00 2001 From: dahyeon405 Date: Tue, 13 Dec 2022 00:42:06 +0900 Subject: [PATCH 23/44] =?UTF-8?q?feat:=20=EB=AA=A8=EB=8B=AC=20placeholder?= =?UTF-8?q?=20=ED=8F=B0=ED=8A=B8=20=EC=82=AC=EC=9D=B4=EC=A6=88=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/common/LabeledInput/styled.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/components/common/LabeledInput/styled.ts b/frontend/components/common/LabeledInput/styled.ts index da04e79f..7c297653 100644 --- a/frontend/components/common/LabeledInput/styled.ts +++ b/frontend/components/common/LabeledInput/styled.ts @@ -14,6 +14,10 @@ export const Input = styled.input` border: 1px solid var(--grey-02-color); border-radius: 10px; + ::placeholder { + font-size: 14px; + } + @media ${(props) => props.theme.mobile} { ::placeholder { font-size: 12px; From dd6c72ba102e8488606f6d9dda5380cae12240b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=84?= Date: Tue, 13 Dec 2022 01:00:45 +0900 Subject: [PATCH 24/44] =?UTF-8?q?feat:=20=EC=97=90=EB=94=94=ED=84=B0=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=B2=84=ED=8A=BC=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EC=88=98=EC=A0=95=20-=20#307?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/edit/EditBar/index.tsx | 8 +++++--- frontend/components/edit/EditBar/styled.ts | 14 ++++++++------ frontend/public/assets/ico_exit.svg | 1 + 3 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 frontend/public/assets/ico_exit.svg diff --git a/frontend/components/edit/EditBar/index.tsx b/frontend/components/edit/EditBar/index.tsx index 18127792..e03611f5 100644 --- a/frontend/components/edit/EditBar/index.tsx +++ b/frontend/components/edit/EditBar/index.tsx @@ -1,3 +1,4 @@ +import Image from 'next/image'; import { useRouter } from 'next/router'; import { useEffect } from 'react'; @@ -5,6 +6,7 @@ import { useEffect } from 'react'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { createTemporaryArticleApi, getTemporaryArticleApi } from '@apis/articleApi'; +import ExitIcon from '@assets/ico_exit.svg'; import articleState from '@atoms/article'; import articleBuffer from '@atoms/articleBuffer'; import useFetch from '@hooks/useFetch'; @@ -51,14 +53,14 @@ export default function EditBar({ handleModalOpen, isModifyMode }: EditBarProps) handleExitButton()}> - 나가기 + Exit Icon handleLoadButton()}>불러오기 - handleSaveButton()}>저장 + handleSaveButton()}>저장하기 - {isModifyMode ? '수정하기' : '발행'} + {isModifyMode ? '수정하기' : '발행하기'} diff --git a/frontend/components/edit/EditBar/styled.ts b/frontend/components/edit/EditBar/styled.ts index d4d5dc70..6e90e5c6 100644 --- a/frontend/components/edit/EditBar/styled.ts +++ b/frontend/components/edit/EditBar/styled.ts @@ -20,15 +20,17 @@ export const ButtonGroup = styled.div` `; const Button = styled.button` - padding: 8px 16px; - border-radius: 10px; + padding: 4px; + border-radius: 8px; + line-height: 16px; font-family: 'Noto Sans KR'; `; -export const ExitButton = styled(Button)` - color: var(--title-active-color); - background-color: var(--light-orange-color); - border: 1px solid var(--grey-01-color); +export const ExitButton = styled.button` + > img { + width: 32px; + height: 32px; + } `; export const TemporaryButton = styled(Button)` diff --git a/frontend/public/assets/ico_exit.svg b/frontend/public/assets/ico_exit.svg new file mode 100644 index 00000000..e7efdf8e --- /dev/null +++ b/frontend/public/assets/ico_exit.svg @@ -0,0 +1 @@ + \ No newline at end of file From 5f2761dfa5982a9436e4600628339b96f96abfb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=84?= Date: Tue, 13 Dec 2022 01:04:22 +0900 Subject: [PATCH 25/44] =?UTF-8?q?feat:=20=EC=97=90=EB=94=94=ED=84=B0=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A0=80=EC=9E=A5=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EB=B0=8F=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=20?= =?UTF-8?q?=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20-=20#307?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/edit/EditBar/index.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/components/edit/EditBar/index.tsx b/frontend/components/edit/EditBar/index.tsx index e03611f5..0e137a50 100644 --- a/frontend/components/edit/EditBar/index.tsx +++ b/frontend/components/edit/EditBar/index.tsx @@ -10,6 +10,7 @@ import ExitIcon from '@assets/ico_exit.svg'; import articleState from '@atoms/article'; import articleBuffer from '@atoms/articleBuffer'; import useFetch from '@hooks/useFetch'; +import { toastSuccess } from '@utils/toast'; import { Bar, ButtonGroup, ExitButton, PublishButton, TemporaryButton } from './styled'; @@ -27,11 +28,15 @@ export default function EditBar({ handleModalOpen, isModifyMode }: EditBarProps) const { execute: createTemporaryArticle } = useFetch(createTemporaryArticleApi); const handleLoadButton = () => { - getTemporaryArticle(); + const confirm = window.confirm('작성하신 글이 사라집니다.\n정말 불러오시겠습니까?'); + + if (confirm) getTemporaryArticle(); }; const handleSaveButton = () => { createTemporaryArticle({ title: article.title, content: article.content }); + + toastSuccess('글을 임시 저장했습니다.'); }; const handleExitButton = () => { @@ -47,6 +52,8 @@ export default function EditBar({ handleModalOpen, isModifyMode }: EditBarProps) title: temporaryArticle.title, content: temporaryArticle.content, }); + + toastSuccess('임시 저장된 글을 불러왔습니다.'); }, [temporaryArticle]); return ( From 24c73440ed0596fc6d942c2b296b8b27fb071216 Mon Sep 17 00:00:00 2001 From: dahyeon405 Date: Tue, 13 Dec 2022 01:43:29 +0900 Subject: [PATCH 26/44] =?UTF-8?q?fix:=20search=20api=EC=97=90=20decoder=20?= =?UTF-8?q?=EB=AF=B8=EB=93=A4=EC=9B=A8=EC=96=B4=EB=A1=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80(BE)=20-=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/apis/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/apis/index.ts b/backend/src/apis/index.ts index 172ca29a..b98c033a 100644 --- a/backend/src/apis/index.ts +++ b/backend/src/apis/index.ts @@ -23,7 +23,7 @@ router.get('/auth', decoder, catchAsync(authController.checkSignInStatus)); router.get('/articles/temporary', guard, catchAsync(articlesController.getTemporaryArticle)); router.post('/articles/temporary', guard, catchAsync(articlesController.createTemporaryArticle)); -router.get('/articles/search', catchAsync(articlesController.searchArticles)); +router.get('/articles/search', decoder, catchAsync(articlesController.searchArticles)); router.get('/articles/:articleId', catchAsync(articlesController.getArticle)); router.post('/articles', catchAsync(articlesController.createArticle)); router.patch('/articles/:articleId', catchAsync(articlesController.updateArticle)); @@ -31,7 +31,7 @@ router.delete('/articles/:articleId', catchAsync(articlesController.deleteArticl router.post('/image', multer().single('image'), catchAsync(imagesController.createImage)); -router.get('/books/search', catchAsync(booksController.searchBooks)); +router.get('/books/search', decoder, catchAsync(booksController.searchBooks)); router.get('/books/:bookId', decoder, catchAsync(booksController.getBook)); router.get('/books', decoder, catchAsync(booksController.getBooks)); router.post('/books', guard, catchAsync(booksController.createBook)); From bd492914f147c1b17336c916c08ae85649b643bb Mon Sep 17 00:00:00 2001 From: dahyeon405 Date: Tue, 13 Dec 2022 01:46:51 +0900 Subject: [PATCH 27/44] =?UTF-8?q?fix:=20=EB=82=B4=20=EC=B1=85=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EA=B2=80=EC=83=89=20=EC=84=A0=ED=83=9D=EC=97=AC?= =?UTF-8?q?=EB=B6=80=EB=A5=BC=20query=EC=97=90=20=EB=8B=B4=EB=8A=94=20?= =?UTF-8?q?=EA=B2=83=EC=9C=BC=EB=A1=9C=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95(FE)=20-=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/apis/articleApi.ts | 4 ++-- frontend/apis/bookApi.ts | 4 ++-- frontend/components/search/SearchFilter/index.tsx | 2 +- frontend/pages/search.tsx | 12 ++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/frontend/apis/articleApi.ts b/frontend/apis/articleApi.ts index d817a6ba..5c50c454 100644 --- a/frontend/apis/articleApi.ts +++ b/frontend/apis/articleApi.ts @@ -2,7 +2,7 @@ import api from '@utils/api'; interface SearchArticlesApi { query: string; - userId: number; + isUsers: 0 | 1; page: number; take: number; } @@ -11,7 +11,7 @@ export const searchArticlesApi = async (data: SearchArticlesApi) => { const url = `/api/articles/search`; const params = { query: data.query, - userId: data.userId, + isUsers: data.isUsers, page: data.page, take: data.take, }; diff --git a/frontend/apis/bookApi.ts b/frontend/apis/bookApi.ts index 1ccf0e09..ecc5530d 100644 --- a/frontend/apis/bookApi.ts +++ b/frontend/apis/bookApi.ts @@ -3,7 +3,7 @@ import api from '@utils/api'; interface SearchBooksApi { query: string; - userId: number; + isUsers: 0 | 1; page: number; take: number; } @@ -12,7 +12,7 @@ export const searchBooksApi = async (data: SearchBooksApi) => { const url = `/api/books/search`; const params = { query: data.query, - userId: data.userId, + isUsers: data.isUsers, page: data.page, take: data.take, }; diff --git a/frontend/components/search/SearchFilter/index.tsx b/frontend/components/search/SearchFilter/index.tsx index 28a7cf13..7b222b04 100644 --- a/frontend/components/search/SearchFilter/index.tsx +++ b/frontend/components/search/SearchFilter/index.tsx @@ -32,7 +32,7 @@ export default function SearchFilter({ handleFilter }: SearchFilterProps) { handleFilter({ userId: e.target.checked ? signInStatus.id : 0 })} + onChange={(e) => handleFilter({ isUsers: e.target.checked ? 1 : 0 })} /> 내 책에서 검색 diff --git a/frontend/pages/search.tsx b/frontend/pages/search.tsx index 137e8e64..6804543c 100644 --- a/frontend/pages/search.tsx +++ b/frontend/pages/search.tsx @@ -32,7 +32,7 @@ export default function Search() { const [articlePage, setArticlePage] = useState({ hasNextPage: true, pageNumber: 2 }); const [bookPage, setBookPage] = useState({ hasNextPage: true, pageNumber: 2 }); - const [filter, setFilter] = useState({ type: 'article', userId: 0 }); + const [filter, setFilter] = useState({ type: 'article', isUsers: 0 }); const [isArticleNoResult, setIsArticleNoResult] = useState(false); const [isBookNoResult, setIsBookNoResult] = useState(false); @@ -79,7 +79,7 @@ export default function Search() { searchArticles({ query: debouncedKeyword, - userId: filter.userId, + isUsers: filter.isUsers, page: 1, take: 12, }); @@ -90,7 +90,7 @@ export default function Search() { searchBooks({ query: debouncedKeyword, - userId: filter.userId, + isUsers: filter.isUsers, page: 1, take: 12, }); @@ -98,7 +98,7 @@ export default function Search() { hasNextPage: true, pageNumber: 2, }); - }, [debouncedKeyword, filter.userId]); + }, [debouncedKeyword, filter.isUsers]); useEffect(() => { if (!isIntersecting || !debouncedKeyword) return; @@ -107,7 +107,7 @@ export default function Search() { if (!articlePage.hasNextPage) return; searchArticles({ query: debouncedKeyword, - userId: filter.userId, + isUsers: filter.isUsers, page: articlePage.pageNumber, take: 12, }); @@ -119,7 +119,7 @@ export default function Search() { if (!bookPage.hasNextPage) return; searchBooks({ query: debouncedKeyword, - userId: filter.userId, + isUsers: filter.isUsers, page: bookPage.pageNumber, take: 12, }); From 6524fb1e5642c2b3eb32078e6dfd0fb546c8471c Mon Sep 17 00:00:00 2001 From: dahyeon405 Date: Tue, 13 Dec 2022 01:51:20 +0900 Subject: [PATCH 28/44] =?UTF-8?q?fix:=20search=20=EC=8B=9C=20query?= =?UTF-8?q?=EC=9D=98=20userId=EB=A5=BC=20isUsers=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=ED=95=A8=EC=97=90=20=EB=94=B0=EB=A5=B8=20BE=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20-=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/apis/articles/articles.controller.ts | 14 ++++++++++++-- .../src/apis/articles/articles.interface.ts | 3 ++- backend/src/apis/articles/articles.service.ts | 19 ++++++++++--------- backend/src/apis/books/books.controller.ts | 14 ++++++++++++-- backend/src/apis/books/books.interface.ts | 1 + backend/src/apis/books/books.service.ts | 4 ++-- 6 files changed, 39 insertions(+), 16 deletions(-) diff --git a/backend/src/apis/articles/articles.controller.ts b/backend/src/apis/articles/articles.controller.ts index c213bd21..495da0b9 100644 --- a/backend/src/apis/articles/articles.controller.ts +++ b/backend/src/apis/articles/articles.controller.ts @@ -7,9 +7,19 @@ import scrapsService from '@apis/scraps/scraps.service'; import { Forbidden, Message } from '@errors'; const searchArticles = async (req: Request, res: Response) => { - const { query, page, take, userId } = req.query as unknown as SearchArticles; + const { query, page, take, isUsers } = req.query as unknown as SearchArticles; - const searchResult = await articlesService.searchArticles({ query, page, take: +take, userId }); + let userId = res.locals.user?.id; + + if (!userId) userId = 0; + + const searchResult = await articlesService.searchArticles({ + query, + page, + take: +take, + isUsers, + userId, + }); return res.status(200).send(searchResult); }; diff --git a/backend/src/apis/articles/articles.interface.ts b/backend/src/apis/articles/articles.interface.ts index 5abd0f1b..e8195302 100644 --- a/backend/src/apis/articles/articles.interface.ts +++ b/backend/src/apis/articles/articles.interface.ts @@ -2,7 +2,8 @@ export interface SearchArticles { query: string; page: number; take: number; - userId: number; + userId?: number; + isUsers?: string; } export interface CreateArticle { diff --git a/backend/src/apis/articles/articles.service.ts b/backend/src/apis/articles/articles.service.ts index 89899603..b8490d9e 100644 --- a/backend/src/apis/articles/articles.service.ts +++ b/backend/src/apis/articles/articles.service.ts @@ -6,19 +6,20 @@ import { import { prisma } from '@config/orm.config'; const searchArticles = async (searchArticles: SearchArticles) => { - const { query, page, take, userId } = searchArticles; + const { query, page, take, userId, isUsers } = searchArticles; const skip = (page - 1) * take; - const matchUserCondition = Number(userId) - ? { - book: { - user: { - id: Number(userId), + const matchUserCondition = + isUsers === '1' + ? { + book: { + user: { + id: Number(userId), + }, }, - }, - } - : {}; + } + : {}; const articles = await prisma.article.findMany({ select: { diff --git a/backend/src/apis/books/books.controller.ts b/backend/src/apis/books/books.controller.ts index dc94588a..3b28a0f1 100644 --- a/backend/src/apis/books/books.controller.ts +++ b/backend/src/apis/books/books.controller.ts @@ -31,9 +31,19 @@ const getBooks = async (req: Request, res: Response) => { }; const searchBooks = async (req: Request, res: Response) => { - const { query, page, take, userId } = req.query as unknown as SearchBooks; + const { query, page, take, isUsers } = req.query as unknown as SearchBooks; - const searchResult = await booksService.searchBooks({ query, userId, take: +take, page }); + let userId = res.locals.user?.id; + + if (!userId) userId = 0; + + const searchResult = await booksService.searchBooks({ + query, + isUsers, + userId, + take: +take, + page, + }); return res.status(200).send(searchResult); }; diff --git a/backend/src/apis/books/books.interface.ts b/backend/src/apis/books/books.interface.ts index 6d58090a..979b6daf 100644 --- a/backend/src/apis/books/books.interface.ts +++ b/backend/src/apis/books/books.interface.ts @@ -3,6 +3,7 @@ export interface SearchBooks { page: number; take: number; userId?: number; + isUsers?: string; } export interface FindBooks { diff --git a/backend/src/apis/books/books.service.ts b/backend/src/apis/books/books.service.ts index 15965734..736ebf1d 100644 --- a/backend/src/apis/books/books.service.ts +++ b/backend/src/apis/books/books.service.ts @@ -2,7 +2,7 @@ import { FindBooks, SearchBooks, CreateBook } from '@apis/books/books.interface' import { prisma } from '@config/orm.config'; import { Message, NotFound } from '@errors'; -const searchBooks = async ({ query, userId, take, page }: SearchBooks) => { +const searchBooks = async ({ query, userId, isUsers, take, page }: SearchBooks) => { const skip = (page - 1) * take; const books = await prisma.book.findMany({ @@ -40,7 +40,7 @@ const searchBooks = async ({ query, userId, take, page }: SearchBooks) => { }, where: { deleted_at: null, - user_id: Number(userId) ? Number(userId) : undefined, + user_id: isUsers === '1' ? Number(userId) : undefined, title: { search: `${query}*`, }, From 98e9a9f1cca2f7c6f13f71a3f6af989ca26caaa9 Mon Sep 17 00:00:00 2001 From: dahyeon405 Date: Tue, 13 Dec 2022 02:05:14 +0900 Subject: [PATCH 29/44] =?UTF-8?q?fix:=20isUsers=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20-=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/apis/articles/articles.service.ts | 2 +- backend/src/apis/books/books.service.ts | 2 +- frontend/apis/articleApi.ts | 2 +- frontend/apis/bookApi.ts | 2 +- frontend/components/search/SearchFilter/index.tsx | 6 +++--- frontend/pages/search.tsx | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/src/apis/articles/articles.service.ts b/backend/src/apis/articles/articles.service.ts index b8490d9e..518ee976 100644 --- a/backend/src/apis/articles/articles.service.ts +++ b/backend/src/apis/articles/articles.service.ts @@ -11,7 +11,7 @@ const searchArticles = async (searchArticles: SearchArticles) => { const skip = (page - 1) * take; const matchUserCondition = - isUsers === '1' + isUsers === 'true' ? { book: { user: { diff --git a/backend/src/apis/books/books.service.ts b/backend/src/apis/books/books.service.ts index 736ebf1d..8652ce5c 100644 --- a/backend/src/apis/books/books.service.ts +++ b/backend/src/apis/books/books.service.ts @@ -40,7 +40,7 @@ const searchBooks = async ({ query, userId, isUsers, take, page }: SearchBooks) }, where: { deleted_at: null, - user_id: isUsers === '1' ? Number(userId) : undefined, + user_id: isUsers === 'true' ? Number(userId) : undefined, title: { search: `${query}*`, }, diff --git a/frontend/apis/articleApi.ts b/frontend/apis/articleApi.ts index 5c50c454..19cf1013 100644 --- a/frontend/apis/articleApi.ts +++ b/frontend/apis/articleApi.ts @@ -2,7 +2,7 @@ import api from '@utils/api'; interface SearchArticlesApi { query: string; - isUsers: 0 | 1; + isUsers: boolean; page: number; take: number; } diff --git a/frontend/apis/bookApi.ts b/frontend/apis/bookApi.ts index ecc5530d..988fd8b3 100644 --- a/frontend/apis/bookApi.ts +++ b/frontend/apis/bookApi.ts @@ -3,7 +3,7 @@ import api from '@utils/api'; interface SearchBooksApi { query: string; - isUsers: 0 | 1; + isUsers: boolean; page: number; take: number; } diff --git a/frontend/components/search/SearchFilter/index.tsx b/frontend/components/search/SearchFilter/index.tsx index 22fae631..a4fd4919 100644 --- a/frontend/components/search/SearchFilter/index.tsx +++ b/frontend/components/search/SearchFilter/index.tsx @@ -6,11 +6,11 @@ import { FilterButton, FilterGroup, FilterLabel, FilterWrapper } from './styled' interface Filter { type: string; - isUsers: number; + isUsers: boolean; } interface SearchFilterProps { - handleFilter: (value: { [value: string]: string | number }) => void; + handleFilter: (value: { [value: string]: string | boolean }) => void; filter: Filter; } @@ -43,7 +43,7 @@ export default function SearchFilter({ handleFilter, filter }: SearchFilterProps handleFilter({ isUsers: e.target.checked ? 1 : 0 })} + onChange={(e) => handleFilter({ isUsers: e.target.checked })} /> 내 책에서 검색 diff --git a/frontend/pages/search.tsx b/frontend/pages/search.tsx index 7e00eb81..8e856b06 100644 --- a/frontend/pages/search.tsx +++ b/frontend/pages/search.tsx @@ -37,7 +37,7 @@ export default function Search() { setValue: setFilter, } = useSessionStorage('filter', { type: 'article', - isUsers: 0, + isUsers: false, }); const { @@ -200,7 +200,7 @@ export default function Search() { }; }, []); - const handleFilter = (value: { [value: string]: string | number }) => { + const handleFilter = (value: { [value: string]: string | boolean }) => { setFilter({ ...filter, ...value, From 55b9f713bb13a3eef14d1adc69fae041c422cb06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=84?= Date: Tue, 13 Dec 2022 02:36:36 +0900 Subject: [PATCH 30/44] =?UTF-8?q?feat:=20Content=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/common/Content/styled.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/common/Content/styled.ts b/frontend/components/common/Content/styled.ts index 14e8e97d..07ec3296 100644 --- a/frontend/components/common/Content/styled.ts +++ b/frontend/components/common/Content/styled.ts @@ -56,7 +56,7 @@ export const ContentBody = styled.div` p { img { - width: 100%; + max-width: 360px; } } From 6ad2816a8aeadeed26e6958e3d9815e39ab3debf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=84?= Date: Tue, 13 Dec 2022 13:54:59 +0900 Subject: [PATCH 31/44] =?UTF-8?q?feat:=20Content=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20=EB=8C=80?= =?UTF-8?q?=EC=9D=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/common/Content/styled.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/components/common/Content/styled.ts b/frontend/components/common/Content/styled.ts index 07ec3296..e9f55c85 100644 --- a/frontend/components/common/Content/styled.ts +++ b/frontend/components/common/Content/styled.ts @@ -56,7 +56,11 @@ export const ContentBody = styled.div` p { img { - max-width: 360px; + max-width: 720px; + + @media ${(props) => props.theme.mobile} { + width: 100%; + } } } From 6d7a8579bcaba68b2e38e7b9e5fa276e56fd6724 Mon Sep 17 00:00:00 2001 From: MinHK4 Date: Tue, 13 Dec 2022 14:03:02 +0900 Subject: [PATCH 32/44] =?UTF-8?q?chore:=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/.eslintrc.json | 6 + frontend/components/common/Content/styled.ts | 10 +- frontend/components/edit/Editor/core/theme.ts | 45 +++-- .../edit/Editor/core/useCodeMirror.ts | 162 +++++++++++++++--- frontend/components/edit/Editor/index.tsx | 74 +++++++- frontend/components/edit/Editor/styled.ts | 64 ++++++- .../components/search/ArticleItem/index.tsx | 4 +- .../components/search/ArticleList/index.tsx | 38 +++- frontend/components/search/BookList/index.tsx | 55 +++++- .../components/search/SearchFilter/index.tsx | 18 +- frontend/package-lock.json | 26 +++ frontend/package.json | 2 + frontend/pages/editor.tsx | 24 +-- frontend/public/assets/ico_bold.svg | 1 + frontend/public/assets/ico_code.svg | 1 + frontend/public/assets/ico_h1.svg | 1 + frontend/public/assets/ico_h2.svg | 1 + frontend/public/assets/ico_h3.svg | 1 + frontend/public/assets/ico_image.svg | 1 + frontend/public/assets/ico_italic.svg | 1 + frontend/public/assets/ico_link.svg | 1 + frontend/public/assets/ico_quote.svg | 1 + frontend/public/assets/ico_strikethrough.svg | 1 + frontend/styles/layout.ts | 7 + frontend/utils/parser.ts | 2 + 25 files changed, 472 insertions(+), 75 deletions(-) create mode 100644 frontend/public/assets/ico_bold.svg create mode 100644 frontend/public/assets/ico_code.svg create mode 100644 frontend/public/assets/ico_h1.svg create mode 100644 frontend/public/assets/ico_h2.svg create mode 100644 frontend/public/assets/ico_h3.svg create mode 100644 frontend/public/assets/ico_image.svg create mode 100644 frontend/public/assets/ico_italic.svg create mode 100644 frontend/public/assets/ico_link.svg create mode 100644 frontend/public/assets/ico_quote.svg create mode 100644 frontend/public/assets/ico_strikethrough.svg diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index 96f0c8e7..534aa081 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -47,6 +47,12 @@ } } ], + "jsx-a11y/label-has-associated-control": [ + "error", + { + "labelAttributes": ["htmlFor"] + } + ], "prettier/prettier": ["error", { "endOfLine": "auto" }], "react/react-in-jsx-scope": "off", "react/jsx-filename-extension": ["error", { "extensions": [".ts", ".tsx"] }], diff --git a/frontend/components/common/Content/styled.ts b/frontend/components/common/Content/styled.ts index 5f4a2a29..14e8e97d 100644 --- a/frontend/components/common/Content/styled.ts +++ b/frontend/components/common/Content/styled.ts @@ -8,11 +8,12 @@ export const ContentTitle = styled.h1` margin-bottom: 16px; font-size: 24px; font-weight: 700; + display: flex; + align-items: center; + height: 35px; `; export const ContentBody = styled.div` - padding-top: 10px; - > * { line-height: 2; font-weight: 400; @@ -68,6 +69,10 @@ export const ContentBody = styled.div` } } + em { + font-style: italic; + } + blockquote { margin: 24px 0; padding: 24px 16px; @@ -86,7 +91,6 @@ export const ContentBody = styled.div` border-radius: 4px; font-family: 'consolas'; font-size: 16px; - font-weight: 700; line-height: 1.4; code { diff --git a/frontend/components/edit/Editor/core/theme.ts b/frontend/components/edit/Editor/core/theme.ts index 78182252..f6e50d48 100644 --- a/frontend/components/edit/Editor/core/theme.ts +++ b/frontend/components/edit/Editor/core/theme.ts @@ -1,25 +1,34 @@ import { HighlightStyle, syntaxHighlighting } from '@codemirror/language'; -import { tags } from '@lezer/highlight'; +import { tags as t } from '@lezer/highlight'; export default function theme() { const highlightStyle = HighlightStyle.define([ - { tag: tags.heading1, fontSize: '24px', fontWeight: '700' }, - { tag: tags.heading2, fontSize: '20px', fontWeight: '700' }, - { tag: tags.heading3, fontSize: '18px', fontWeight: '700' }, - { tag: tags.link, textDecoration: 'underline' }, - { tag: tags.strikethrough, textDecoration: 'line-through' }, - { tag: tags.invalid, color: '#cb2431' }, - { tag: [tags.string, tags.meta, tags.regexp], color: '#222222', fontWeight: 700 }, - { tag: [tags.heading, tags.strong], color: '#222222', fontWeight: '700' }, - { tag: [tags.emphasis], color: '#24292e', fontStyle: 'italic' }, - { tag: [tags.comment, tags.bracket], color: '#6a737d' }, - { tag: [tags.className, tags.propertyName], color: '#6f42c1' }, - { tag: [tags.variableName, tags.attributeName, tags.number, tags.operator], color: '#005cc5' }, - { tag: [tags.keyword, tags.typeName, tags.typeOperator, tags.typeName], color: '#d73a49' }, - { tag: [tags.name, tags.quote], color: '#22863a' }, - { tag: [tags.deleted], color: '#b31d28', backgroundColor: 'ffeef0' }, - { tag: [tags.atom, tags.bool, tags.special(tags.variableName)], color: '#e36209' }, - { tag: [tags.url, tags.escape, tags.regexp, tags.link], color: '#222222' }, + { tag: t.heading1, fontSize: '24px', fontWeight: '700' }, + { tag: t.heading2, fontSize: '20px', fontWeight: '700' }, + { tag: t.heading3, fontSize: '18px', fontWeight: '700' }, + { tag: t.link, textDecoration: 'underline' }, + { tag: t.strikethrough, textDecoration: 'line-through' }, + { tag: t.invalid, color: '#cb2431' }, + { tag: t.name, color: '#22863a' }, + { tag: t.emphasis, color: '#24292e', fontStyle: 'italic' }, + { tag: t.deleted, color: '#b31d28', backgroundColor: '#ffeef0' }, + { tag: [t.heading, t.strong, t.meta], color: '#222222', fontWeight: '700' }, + { tag: [t.url, t.escape, t.regexp, t.link, t.quote], color: '#222222' }, + { tag: t.comment, color: '#6a737d', fontFamily: 'consolas' }, + { + tag: [t.attributeName, t.className, t.propertyName, t.function(t.definition(t.variableName))], + color: '#6f42c1', + fontFamily: 'consolas', + }, + { tag: [t.operator, t.variableName, t.bracket], color: '#222222', fontFamily: 'consolas' }, + { tag: [t.string], color: '#032f62', fontFamily: 'consolas' }, + { tag: [t.number], color: '#005cc5', fontFamily: 'consolas' }, + { + tag: [t.keyword, t.typeName, t.typeOperator, t.typeName, t.atom, t.moduleKeyword], + color: '#d73a49', + fontFamily: 'consolas', + }, + { tag: [t.bool, t.special(t.variableName)], color: '#005cc5', fontFamily: 'consolas' }, ]); return [syntaxHighlighting(highlightStyle)]; diff --git a/frontend/components/edit/Editor/core/useCodeMirror.ts b/frontend/components/edit/Editor/core/useCodeMirror.ts index b2ab2cb4..a7a517e5 100644 --- a/frontend/components/edit/Editor/core/useCodeMirror.ts +++ b/frontend/components/edit/Editor/core/useCodeMirror.ts @@ -1,9 +1,10 @@ import { useCallback, useEffect, useState } from 'react'; +import { indentWithTab } from '@codemirror/commands'; import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; import { languages } from '@codemirror/language-data'; import { EditorState } from '@codemirror/state'; -import { placeholder } from '@codemirror/view'; +import { placeholder, keymap } from '@codemirror/view'; import { EditorView } from 'codemirror'; import { createImageApi } from '@apis/imageApi'; @@ -37,13 +38,113 @@ export default function useCodeMirror() { }); }; + const handleImage = (imageFile: File) => { + if (!/image\/[png,jpg,jpeg,gif]/.test(imageFile.type)) return; + + const formData = new FormData(); + + formData.append('image', imageFile); + + createImage(formData); + }; + const onChange = () => { return EditorView.updateListener.of(({ view, docChanged }) => { if (docChanged) setDocument(view.state.doc.toString()); }); }; - const onPaste = () => { + const insertStartToggle = (symbol: string) => { + if (!editorView) return; + + editorView.focus(); + + const { head } = editorView.state.selection.main; + const { from, to, text } = editorView.state.doc.lineAt(head); + + const hasExist = text.startsWith(symbol); + + if (!hasExist) { + editorView.dispatch({ + changes: { + from, + to, + insert: `${symbol}${text}`, + }, + selection: { + anchor: from + text.length + symbol.length, + }, + }); + + return; + } + + editorView.dispatch({ + changes: { + from, + to, + insert: `${text.slice(symbol.length, text.length)}`, + }, + }); + }; + + const insertBetweenToggle = (symbol: string, defaultText = '텍스트') => { + if (!editorView) return; + + editorView.focus(); + + const { from, to } = editorView.state.selection.ranges[0]; + + const text = editorView.state.sliceDoc(from, to); + + const prefixText = editorView.state.sliceDoc(from - symbol.length, from); + const affixText = editorView.state.sliceDoc(to, to + symbol.length); + + const hasExist = symbol === prefixText && symbol === affixText; + + if (!hasExist) { + editorView.dispatch({ + changes: { + from, + to, + insert: `${symbol}${text || defaultText}${symbol}`, + }, + selection: { + head: from + symbol.length, + anchor: text ? to + symbol.length : to + symbol.length + defaultText.length, + }, + }); + + return; + } + + editorView.dispatch({ + changes: { + from: from - symbol.length, + to: to + symbol.length, + insert: text, + }, + selection: { + head: from - symbol.length, + anchor: to - symbol.length, + }, + }); + }; + + const insertCursor = (text: string) => { + if (!editorView) return; + + editorView.focus(); + + const cursor = editorView.state.selection.main.head; + + editorView.dispatch({ + changes: { from: cursor, to: cursor, insert: text }, + selection: { anchor: cursor + text.length }, + }); + }; + + const eventHandler = () => { return EditorView.domEventHandlers({ paste(event) { if (!event.clipboardData) return; @@ -52,16 +153,20 @@ export default function useCodeMirror() { // eslint-disable-next-line no-restricted-syntax for (const item of items) { - if (item.kind === 'file' && /image\/[png,jpg,jpeg,gif]/.test(item.type)) { - const blob = item.getAsFile() as Blob; + if (!(item.kind === 'file')) return; + handleImage(item.getAsFile() as File); + } + }, + drop(event, view) { + if (!event.dataTransfer) return; - const formData = new FormData(); + const cursorPos = view.posAtCoords({ x: event.clientX, y: event.clientY }); + if (cursorPos) view.dispatch({ selection: { anchor: cursorPos, head: cursorPos } }); - formData.append('image', blob); + const { files } = event.dataTransfer; - createImage(formData); - } - } + // eslint-disable-next-line no-restricted-syntax + for (const file of files) handleImage(file); }, }); }; @@ -69,20 +174,10 @@ export default function useCodeMirror() { useEffect(() => { if (!editorView) return; - const cursor = editorView.state.selection.main.head; - - const markdownImage = (path: string) => `![image](${path})\n`; - - const insert = markdownImage(image?.imagePath); + const markdownImage = (path: string) => `![image](${path})`; + const text = markdownImage(image?.imagePath); - editorView.dispatch({ - changes: { - from: cursor, - to: cursor, - insert, - }, - selection: { anchor: cursor + insert.length }, - }); + insertCursor(text); }, [image]); useEffect(() => { @@ -97,8 +192,19 @@ export default function useCodeMirror() { placeholder('내용을 입력해주세요.'), theme(), onChange(), - onPaste(), + eventHandler(), EditorView.lineWrapping, + EditorView.theme({ + '.cm-content': { + padding: 0, + lineHeight: 2, + fontFamily: 'Noto Sans KR', + }, + '.cm-line': { + padding: 0, + }, + }), + keymap.of([indentWithTab]), ], }); @@ -113,5 +219,13 @@ export default function useCodeMirror() { return () => view?.destroy(); }, [element]); - return { ref, document, replaceDocument }; + return { + ref, + document, + replaceDocument, + insertStartToggle, + insertBetweenToggle, + insertCursor, + handleImage, + }; } diff --git a/frontend/components/edit/Editor/index.tsx b/frontend/components/edit/Editor/index.tsx index 357b92e5..90b3d8cd 100644 --- a/frontend/components/edit/Editor/index.tsx +++ b/frontend/components/edit/Editor/index.tsx @@ -1,7 +1,18 @@ +import Image from 'next/image'; + import { useEffect, useState } from 'react'; import { useRecoilState } from 'recoil'; +import BoldIcon from '@assets/ico_bold.svg'; +import CodeIcon from '@assets/ico_code.svg'; +import H1Icon from '@assets/ico_h1.svg'; +import H2Icon from '@assets/ico_h2.svg'; +import H3Icon from '@assets/ico_h3.svg'; +import ImageIcon from '@assets/ico_image.svg'; +import ItalicIcon from '@assets/ico_italic.svg'; +import LinkIcon from '@assets/ico_link.svg'; +import QuoteIcon from '@assets/ico_quote.svg'; import articleState from '@atoms/article'; import articleBuffer from '@atoms/articleBuffer'; import Content from '@components/common/Content'; @@ -11,7 +22,16 @@ import useInput from '@hooks/useInput'; import { IArticle } from '@interfaces'; import { html2markdown, markdown2html } from '@utils/parser'; -import { CodeMirrorWrapper, EditorInner, EditorWrapper, TitleInput } from './styled'; +import { + EditorButtonWrapper, + CodeMirrorWrapper, + EditorInner, + EditorWrapper, + TitleInput, + EditorButton, + EditorButtonSplit, + EditorImageInput, +} from './styled'; interface EditorProps { handleModalOpen: () => void; @@ -19,7 +39,15 @@ interface EditorProps { } export default function Editor({ handleModalOpen, originalArticle }: EditorProps) { - const { ref, document, replaceDocument } = useCodeMirror(); + const { + ref, + document, + replaceDocument, + insertStartToggle, + insertBetweenToggle, + insertCursor, + handleImage, + } = useCodeMirror(); const [buffer, setBuffer] = useRecoilState(articleBuffer); const [isModifyMode, setIsModifyMode] = useState(false); @@ -57,6 +85,48 @@ export default function Editor({ handleModalOpen, originalArticle }: EditorProps + + insertStartToggle('# ')}> + Heading1 Icon + + insertStartToggle('## ')}> + Heading2 Icon + + insertStartToggle('### ')}> + Heading3 Icon + + + insertBetweenToggle('**')}> + Bold Icon + + insertBetweenToggle('_')}> + Italic Icon + + + insertStartToggle('> ')}> + Quote Icon + + insertBetweenToggle('\n```\n', '코드')}> + Code Icon + + + insertCursor('[텍스트](주소)')}> + Link Icon + + + + { + if (event.target.files) handleImage(event.target.files[0]); + }} + /> + +
diff --git a/frontend/components/edit/Editor/styled.ts b/frontend/components/edit/Editor/styled.ts index b6395662..a1780044 100644 --- a/frontend/components/edit/Editor/styled.ts +++ b/frontend/components/edit/Editor/styled.ts @@ -1,5 +1,7 @@ import styled from 'styled-components'; +import { Flex } from '@styles/layout'; + export const EditorWrapper = styled.div` width: 100%; height: calc(var(--window-inner-height)); @@ -23,22 +25,70 @@ export const EditorInner = styled.div` } `; -export const CodeMirrorWrapper = styled.div` - font-size: 16px; - height: calc(var(--window-inner-height) - 160px); - overflow: auto; +export const EditorButtonWrapper = styled(Flex)` + margin-bottom: 16px; + padding-bottom: 8px; + border-bottom: 1px solid var(--grey-02-color); + align-items: center; + gap: 8px; + overflow-x: auto; + -ms-overflow-style: none; + &::-webkit-scrollbar { + display: none; + } +`; - .cm-editor.cm-focused { - outline: none; +export const EditorButton = styled.button` + padding: 4px; + width: 36px; + height: 36px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + box-sizing: border-box; + + &:hover { + background-color: var(--light-orange-color); + } + + img, + label { + width: 24px; + height: 24px; + } + + > div { + color: var(--title-active-color); } `; +export const EditorImageInput = styled.input` + display: none; +`; + +export const EditorButtonSplit = styled.div` + border-left: 2px solid var(--grey-02-color); + height: 16px; +`; + export const TitleInput = styled.input` - padding-left: 6px; + padding: 0; width: 100%; border: none; outline: none; + font-family: 'Noto Sans KR'; font-size: 24px; font-weight: 700; margin-bottom: 16px; `; + +export const CodeMirrorWrapper = styled.div` + font-size: 16px; + height: calc(var(--window-inner-height) - 220px); + overflow: auto; + + .cm-editor.cm-focused { + outline: none; + } +`; diff --git a/frontend/components/search/ArticleItem/index.tsx b/frontend/components/search/ArticleItem/index.tsx index 7e0020bb..cdab2082 100644 --- a/frontend/components/search/ArticleItem/index.tsx +++ b/frontend/components/search/ArticleItem/index.tsx @@ -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; diff --git a/frontend/components/search/ArticleList/index.tsx b/frontend/components/search/ArticleList/index.tsx index 88d60459..e59f4919 100644 --- a/frontend/components/search/ArticleList/index.tsx +++ b/frontend/components/search/ArticleList/index.tsx @@ -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)} + {text.slice(startIndex, endIndex)} + {highlightWord(text.slice(endIndex).replace(/(<([^>]+)>)/gi, ''), words)} + + ); + }; + return ( <> {articles.map((article) => ( { + title: string | React.ReactNode; } -export default function BookList({ books }: BookListProps) { +export default function BookList({ books, keywords }: BookListProps) { + const [highlightedBooks, setHighlightedBooks] = useState([]); + + 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)} + {text.slice(startIndex, endIndex)} + {highlightWord(text.slice(endIndex).replace(/(<([^>]+)>)/gi, ''), words)} + + ); + }; + + useEffect(() => { + setHighlightedBooks( + books.map((book) => { + return { + ...book, + title: highlightWord(book.title, keywords), + }; + }) + ); + }, [books, keywords]); + return ( - {books.map((book) => ( + {highlightedBooks.map((book: any) => ( diff --git a/frontend/components/search/SearchFilter/index.tsx b/frontend/components/search/SearchFilter/index.tsx index 28a7cf13..66f60173 100644 --- a/frontend/components/search/SearchFilter/index.tsx +++ b/frontend/components/search/SearchFilter/index.tsx @@ -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 ( @@ -19,12 +25,17 @@ export default function SearchFilter({ handleFilter }: SearchFilterProps) { type="radio" name="type" onChange={() => handleFilter({ type: 'article' })} - defaultChecked + checked={filter.type !== 'book'} /> 글 - handleFilter({ type: 'book' })} /> + handleFilter({ type: 'book' })} + checked={filter.type === 'book'} + /> 책 @@ -33,6 +44,7 @@ export default function SearchFilter({ handleFilter }: SearchFilterProps) { handleFilter({ userId: e.target.checked ? signInStatus.id : 0 })} + checked={filter.userId !== 0} /> 내 책에서 검색 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 92adead7..08e0cf5b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "knoticle-client", "version": "0.1.0", "dependencies": { + "@codemirror/commands": "^6.1.2", "@codemirror/lang-markdown": "^6.0.5", "@codemirror/language": "^6.3.1", "@codemirror/language-data": "^6.1.0", @@ -38,6 +39,7 @@ "rehype-parse": "^8.0.4", "rehype-remark": "^9.1.2", "rehype-stringify": "^9.0.3", + "remark-breaks": "^3.0.2", "remark-parse": "^10.0.1", "remark-rehype": "^10.1.0", "remark-stringify": "^10.0.2", @@ -5394,6 +5396,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-breaks": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-3.0.2.tgz", + "integrity": "sha512-x96YDJ9X+Ry0/JNZFKfr1hpcAKvGYWfUTszxY9RbxKEqq6uzPPoLCuHdZsLPZZUdAv3nCROyc7FPrQLWr2rxyw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-parse": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", @@ -10283,6 +10299,16 @@ "unified": "^10.0.0" } }, + "remark-breaks": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-3.0.2.tgz", + "integrity": "sha512-x96YDJ9X+Ry0/JNZFKfr1hpcAKvGYWfUTszxY9RbxKEqq6uzPPoLCuHdZsLPZZUdAv3nCROyc7FPrQLWr2rxyw==", + "requires": { + "@types/mdast": "^3.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" + } + }, "remark-parse": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 0b7c13c2..7622954b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "@codemirror/commands": "^6.1.2", "@codemirror/lang-markdown": "^6.0.5", "@codemirror/language": "^6.3.1", "@codemirror/language-data": "^6.1.0", @@ -39,6 +40,7 @@ "rehype-parse": "^8.0.4", "rehype-remark": "^9.1.2", "rehype-stringify": "^9.0.3", + "remark-breaks": "^3.0.2", "remark-parse": "^10.0.1", "remark-rehype": "^10.1.0", "remark-stringify": "^10.0.2", diff --git a/frontend/pages/editor.tsx b/frontend/pages/editor.tsx index a26c6595..e90f5a09 100644 --- a/frontend/pages/editor.tsx +++ b/frontend/pages/editor.tsx @@ -20,6 +20,8 @@ export default function EditorPage() { const PublishModal = dynamic(() => import('@components/edit/PublishModal')); const ModifyModal = dynamic(() => import('@components/edit/ModifyModal')); + const router = useRouter(); + const [isModalShown, setModalShown] = useState(false); const [originalArticle, setOriginalArticle] = useState(undefined); @@ -31,7 +33,17 @@ export default function EditorPage() { const user = useRecoilValue(signInStatusState); - const router = useRouter(); + const syncHeight = () => { + document.documentElement.style.setProperty('--window-inner-height', `${window.innerHeight}px`); + }; + + useEffect(() => { + syncHeight(); + + window.addEventListener('resize', syncHeight); + + return () => window.removeEventListener('resize', syncHeight); + }, []); useEffect(() => { getUserKnottedBooks(user.nickname); @@ -52,16 +64,6 @@ export default function EditorPage() { setOriginalArticle(article); }, [article]); - const syncHeight = () => { - document.documentElement.style.setProperty('--window-inner-height', `${window.innerHeight}px`); - }; - - useEffect(() => { - syncHeight(); - window.addEventListener('resize', syncHeight); - return () => window.removeEventListener('resize', syncHeight); - }, []); - return ( diff --git a/frontend/public/assets/ico_bold.svg b/frontend/public/assets/ico_bold.svg new file mode 100644 index 00000000..d9ac0a88 --- /dev/null +++ b/frontend/public/assets/ico_bold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/ico_code.svg b/frontend/public/assets/ico_code.svg new file mode 100644 index 00000000..3a27fb19 --- /dev/null +++ b/frontend/public/assets/ico_code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/ico_h1.svg b/frontend/public/assets/ico_h1.svg new file mode 100644 index 00000000..11eec217 --- /dev/null +++ b/frontend/public/assets/ico_h1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/ico_h2.svg b/frontend/public/assets/ico_h2.svg new file mode 100644 index 00000000..ba8b5b5b --- /dev/null +++ b/frontend/public/assets/ico_h2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/ico_h3.svg b/frontend/public/assets/ico_h3.svg new file mode 100644 index 00000000..85d26a48 --- /dev/null +++ b/frontend/public/assets/ico_h3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/ico_image.svg b/frontend/public/assets/ico_image.svg new file mode 100644 index 00000000..f6b7f4e8 --- /dev/null +++ b/frontend/public/assets/ico_image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/ico_italic.svg b/frontend/public/assets/ico_italic.svg new file mode 100644 index 00000000..85ec7506 --- /dev/null +++ b/frontend/public/assets/ico_italic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/ico_link.svg b/frontend/public/assets/ico_link.svg new file mode 100644 index 00000000..1a97c9a2 --- /dev/null +++ b/frontend/public/assets/ico_link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/ico_quote.svg b/frontend/public/assets/ico_quote.svg new file mode 100644 index 00000000..fa4d4a8f --- /dev/null +++ b/frontend/public/assets/ico_quote.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/assets/ico_strikethrough.svg b/frontend/public/assets/ico_strikethrough.svg new file mode 100644 index 00000000..7738a0ef --- /dev/null +++ b/frontend/public/assets/ico_strikethrough.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/styles/layout.ts b/frontend/styles/layout.ts index ba503763..75ecdafc 100644 --- a/frontend/styles/layout.ts +++ b/frontend/styles/layout.ts @@ -72,3 +72,10 @@ export const PageGNBHide = styled.div<{ isscrolldown: 'true' | 'false' }>` width: 100%; } `; + +export const PageWrapperWithHeight = styled.div<{ initialHeight: number }>` + padding-top: 64px; + background-color: var(--light-yellow-color); + min-height: ${(props) => + props.initialHeight !== 0 ? `${props.initialHeight + 600}px` : 'calc(100vh - 131px)'}; +`; diff --git a/frontend/utils/parser.ts b/frontend/utils/parser.ts index 93de0109..42fea91b 100644 --- a/frontend/utils/parser.ts +++ b/frontend/utils/parser.ts @@ -2,6 +2,7 @@ import rehypeHighlight from 'rehype-highlight'; import rehypeParse from 'rehype-parse'; import rehypeRemark from 'rehype-remark'; import rehypeStringify from 'rehype-stringify'; +import remarkBreaks from 'remark-breaks'; import remarkParse from 'remark-parse'; import remarkRehype from 'remark-rehype'; import remarkStringify from 'remark-stringify'; @@ -10,6 +11,7 @@ import { unified } from 'unified'; export const markdown2html = (markdown: string) => { const html = unified() .use(remarkParse) + .use(remarkBreaks) .use(remarkRehype) .use(rehypeStringify) .processSync(markdown) From 405c1bc54c6a4f363db54954bd30f6364a58bed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=84?= Date: Tue, 13 Dec 2022 14:08:03 +0900 Subject: [PATCH 33/44] =?UTF-8?q?feat:=20=EC=97=90=EB=94=94=ED=84=B0=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A0=80=EC=9E=A5=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=ED=99=95=EC=9D=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20-=20#307?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/edit/EditBar/index.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/components/edit/EditBar/index.tsx b/frontend/components/edit/EditBar/index.tsx index 0e137a50..04fa80c2 100644 --- a/frontend/components/edit/EditBar/index.tsx +++ b/frontend/components/edit/EditBar/index.tsx @@ -28,15 +28,19 @@ export default function EditBar({ handleModalOpen, isModifyMode }: EditBarProps) const { execute: createTemporaryArticle } = useFetch(createTemporaryArticleApi); const handleLoadButton = () => { - const confirm = window.confirm('작성하신 글이 사라집니다.\n정말 불러오시겠습니까?'); + const confirm = window.confirm('현재 작성하신 글이 사라집니다.\n정말 불러오시겠습니까?'); if (confirm) getTemporaryArticle(); }; const handleSaveButton = () => { - createTemporaryArticle({ title: article.title, content: article.content }); + const confirm = window.confirm('기존에 임시 저장한 글이 사라집니다.\n정말 저장하시겠습니까?'); - toastSuccess('글을 임시 저장했습니다.'); + if (confirm) { + createTemporaryArticle({ title: article.title, content: article.content }); + + toastSuccess('글을 임시 저장했습니다.'); + } }; const handleExitButton = () => { From e12a649acbc6928779d404c8f6405e9b19cda2f3 Mon Sep 17 00:00:00 2001 From: dahyeon405 Date: Tue, 13 Dec 2022 14:15:00 +0900 Subject: [PATCH 34/44] =?UTF-8?q?fix:=20=EA=B2=80=EC=83=89=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=A7=84=EC=9E=85=20=EC=A7=81=ED=9B=84=20?= =?UTF-8?q?=20=EB=82=B4=20=EC=B1=85=EC=97=90=EC=84=9C=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=EC=9E=91=EB=8F=99=20?= =?UTF-8?q?=EC=95=88=20=EB=90=98=EB=8A=94=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/search/SearchFilter/index.tsx | 3 ++- frontend/pages/search.tsx | 15 ++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/frontend/components/search/SearchFilter/index.tsx b/frontend/components/search/SearchFilter/index.tsx index a4fd4919..b6152bc4 100644 --- a/frontend/components/search/SearchFilter/index.tsx +++ b/frontend/components/search/SearchFilter/index.tsx @@ -43,7 +43,8 @@ export default function SearchFilter({ handleFilter, filter }: SearchFilterProps handleFilter({ isUsers: e.target.checked })} + onChange={() => handleFilter({ isUsers: !filter.isUsers })} + checked={filter.isUsers} /> 내 책에서 검색 diff --git a/frontend/pages/search.tsx b/frontend/pages/search.tsx index 8e856b06..05981657 100644 --- a/frontend/pages/search.tsx +++ b/frontend/pages/search.tsx @@ -31,20 +31,12 @@ export default function Search() { pageNumber: 2, }); - const { - value: filter, - isValueSet: isFilterSet, - setValue: setFilter, - } = useSessionStorage('filter', { + const { value: filter, setValue: setFilter } = useSessionStorage('filter', { type: 'article', isUsers: false, }); - const { - value: keyword, - isValueSet: isKeywordSet, - setValue: setKeyword, - } = useSessionStorage('keyword', ''); + const { value: keyword, setValue: setKeyword } = useSessionStorage('keyword', ''); const debouncedKeyword = useDebounce(keyword, 300); const [keywords, setKeywords] = useState([]); @@ -70,7 +62,7 @@ export default function Search() { }, [debouncedKeyword]); useEffect(() => { - if (!isKeywordSet || !isFilterSet || isInitialRendering) return; + if (isInitialRendering) return; if (debouncedKeyword === '') { setArticles([]); @@ -201,6 +193,7 @@ export default function Search() { }, []); const handleFilter = (value: { [value: string]: string | boolean }) => { + setIsInitialRendering(false); setFilter({ ...filter, ...value, From 920e394dca711cb39f9898f7c0f9ba61fa309712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=84?= Date: Tue, 13 Dec 2022 14:32:33 +0900 Subject: [PATCH 35/44] =?UTF-8?q?feat:=20Markdown=20=EB=B0=8F=20Html=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=97=90=EB=94=94=ED=84=B0=20=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EB=B3=80=EA=B2=BD=20-=20#312?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/common/Content/index.tsx | 4 +++- frontend/components/edit/Editor/index.tsx | 5 ++--- frontend/utils/parser.ts | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/components/common/Content/index.tsx b/frontend/components/common/Content/index.tsx index c62932a2..f1569cee 100644 --- a/frontend/components/common/Content/index.tsx +++ b/frontend/components/common/Content/index.tsx @@ -1,3 +1,5 @@ +import { markdown2html } from '@utils/parser'; + import { ContentBody, ContentTitle, ContentWrapper } from './styled'; import 'highlight.js/styles/github.css'; @@ -13,7 +15,7 @@ export default function Content({ title, content }: ContentProps) { {title && {title}} diff --git a/frontend/components/edit/Editor/index.tsx b/frontend/components/edit/Editor/index.tsx index 90b3d8cd..99fab747 100644 --- a/frontend/components/edit/Editor/index.tsx +++ b/frontend/components/edit/Editor/index.tsx @@ -20,7 +20,6 @@ import EditBar from '@components/edit/EditBar'; import useCodeMirror from '@components/edit/Editor/core/useCodeMirror'; import useInput from '@hooks/useInput'; import { IArticle } from '@interfaces'; -import { html2markdown, markdown2html } from '@utils/parser'; import { EditorButtonWrapper, @@ -68,7 +67,7 @@ export default function Editor({ handleModalOpen, originalArticle }: EditorProps if (!buffer.title && !buffer.content) return; title.setValue(buffer.title); - replaceDocument(html2markdown(buffer.content)); + replaceDocument(buffer.content); setBuffer({ title: '', content: '' }); }, [buffer]); @@ -77,7 +76,7 @@ export default function Editor({ handleModalOpen, originalArticle }: EditorProps setArticle({ ...article, title: title.value, - content: markdown2html(document), + content: document, }); }, [title.value, document]); diff --git a/frontend/utils/parser.ts b/frontend/utils/parser.ts index 42fea91b..77c5de88 100644 --- a/frontend/utils/parser.ts +++ b/frontend/utils/parser.ts @@ -18,7 +18,7 @@ export const markdown2html = (markdown: string) => { .toString(); return unified() - .use(rehypeParse) + .use(rehypeParse, { fragment: true }) .use(rehypeHighlight, { ignoreMissing: true }) .use(rehypeStringify) .processSync(html) From 62e64ff679d1eb2982c2877441828e0c0582c3e1 Mon Sep 17 00:00:00 2001 From: parkhyeonki Date: Tue, 13 Dec 2022 14:56:37 +0900 Subject: [PATCH 36/44] =?UTF-8?q?fix:=20=EC=88=98=EC=A0=95=EB=AA=A8?= =?UTF-8?q?=EB=8B=AC=20=EC=8A=A4=ED=81=AC=EB=9E=A9=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/common/DragDrop/Container/index.tsx | 10 +--------- frontend/components/common/DragDrop/ListItem/index.tsx | 2 -- frontend/components/common/DragDrop/index.tsx | 9 ++------- frontend/components/edit/PublishModal/index.tsx | 6 +----- frontend/components/study/BookListTab/index.tsx | 4 ++++ frontend/components/study/EditBookModal/index.tsx | 5 ++--- frontend/components/viewer/ScrapModal/index.tsx | 6 +----- 7 files changed, 11 insertions(+), 31 deletions(-) diff --git a/frontend/components/common/DragDrop/Container/index.tsx b/frontend/components/common/DragDrop/Container/index.tsx index e1c50ef8..11849e36 100644 --- a/frontend/components/common/DragDrop/Container/index.tsx +++ b/frontend/components/common/DragDrop/Container/index.tsx @@ -1,11 +1,10 @@ -import { useEffect, memo, useCallback } from 'react'; +import { memo, useCallback } from 'react'; import { useDrop } from 'react-dnd'; import update from 'immutability-helper'; import { useRecoilState } from 'recoil'; import scrapState from '@atoms/scrap'; -import { IScrap } from '@interfaces'; import { ListItem } from '../ListItem'; import ContainerWapper from './styled'; @@ -15,22 +14,15 @@ const ItemTypes = { }; export interface ContainerState { - data: IScrap[]; isContentsShown: boolean; isDeleteBtnShown: boolean; } const DragContainer = memo(function Container({ - data, isContentsShown, isDeleteBtnShown, }: ContainerState) { const [scraps, setScraps] = useRecoilState(scrapState); - useEffect(() => { - if (!data) return; - setScraps(data); - }, []); - const findScrap = useCallback( (id: number) => { const scrap = scraps.filter((c) => c.article.id === id)[0]; diff --git a/frontend/components/common/DragDrop/ListItem/index.tsx b/frontend/components/common/DragDrop/ListItem/index.tsx index 08d0e279..143c7ecd 100644 --- a/frontend/components/common/DragDrop/ListItem/index.tsx +++ b/frontend/components/common/DragDrop/ListItem/index.tsx @@ -82,8 +82,6 @@ export const ListItem = memo(function Scrap({ ); const handleMinusBtnClick = () => { - // 원본글이 아니면 스크랩에서만 삭제 - // 원본글이면 실제로 삭제 if (isOriginal) { if (window.confirm('이 글은 원본 글입니다. 정말로 삭제하시겠습니까?')) { setEditInfo({ diff --git a/frontend/components/common/DragDrop/index.tsx b/frontend/components/common/DragDrop/index.tsx index ed4d57d7..73be9ce4 100644 --- a/frontend/components/common/DragDrop/index.tsx +++ b/frontend/components/common/DragDrop/index.tsx @@ -13,19 +13,14 @@ export interface EditScrap { }; } export interface ContainerState { - data: EditScrap[]; isContentsShown: boolean; isDeleteBtnShown: boolean; } -export default function DragArticle({ data, isContentsShown, isDeleteBtnShown }: ContainerState) { +export default function DragArticle({ isContentsShown, isDeleteBtnShown }: ContainerState) { return ( - + ); } diff --git a/frontend/components/edit/PublishModal/index.tsx b/frontend/components/edit/PublishModal/index.tsx index 3df67518..95460e5d 100644 --- a/frontend/components/edit/PublishModal/index.tsx +++ b/frontend/components/edit/PublishModal/index.tsx @@ -94,11 +94,7 @@ export default function PublishModal({ books }: PublishModalProps) { {filteredScraps.length !== 0 && ( - + )} diff --git a/frontend/components/study/BookListTab/index.tsx b/frontend/components/study/BookListTab/index.tsx index 76cd6fd4..d9d0e2f2 100644 --- a/frontend/components/study/BookListTab/index.tsx +++ b/frontend/components/study/BookListTab/index.tsx @@ -7,6 +7,7 @@ import { useRecoilState } from 'recoil'; import MinusWhite from '@assets/ico_minus_white.svg'; import curKnottedBookListState from '@atoms/curKnottedBookList'; import editInfoState from '@atoms/editInfo'; +import scrapState from '@atoms/scrap'; import Book from '@components/common/Book'; import FAB from '@components/study/FAB'; import { IBookScraps } from '@interfaces'; @@ -39,6 +40,7 @@ export default function BookListTab({ const [curKnottedBookList, setCurKnottedBookList] = useRecoilState(curKnottedBookListState); const [editInfo, setEditInfo] = useRecoilState(editInfoState); + const [_, setScraps] = useRecoilState(scrapState); const [isModalShown, setModalShown] = useState(false); const [curEditBook, setCurEditBook] = useState(null); @@ -51,10 +53,12 @@ export default function BookListTab({ setModalShown(true); setCurEditBook(curBook); + setScraps(curBook.scraps); }; const handleModalClose = () => { setModalShown(false); + setScraps([]); }; const handleMinusBtnClick = (e: React.MouseEvent, id: number) => { diff --git a/frontend/components/study/EditBookModal/index.tsx b/frontend/components/study/EditBookModal/index.tsx index 8710b299..803e208d 100644 --- a/frontend/components/study/EditBookModal/index.tsx +++ b/frontend/components/study/EditBookModal/index.tsx @@ -41,7 +41,7 @@ interface BookProps { } export default function EditBookModal({ book, handleModalClose }: BookProps) { - const { id, title, user, scraps } = book; + const { id, title, user } = book; const { data: imgFile, execute: createImage } = useFetch(createImageApi); const { value: titleData, onChange: onTitleChange } = useInput(title); @@ -106,7 +106,6 @@ export default function EditBookModal({ book, handleModalClose }: BookProps) { }, ], }); - handleModalClose(); }; @@ -155,7 +154,7 @@ export default function EditBookModal({ book, handleModalClose }: BookProps) { - + {isContentsShown && ( 드래그앤드롭으로 글의 순서를 변경할 수 있습니다. diff --git a/frontend/components/viewer/ScrapModal/index.tsx b/frontend/components/viewer/ScrapModal/index.tsx index a453fd74..1c7a4c54 100644 --- a/frontend/components/viewer/ScrapModal/index.tsx +++ b/frontend/components/viewer/ScrapModal/index.tsx @@ -119,11 +119,7 @@ export default function ScrapModal({ bookId, handleModalClose, article }: ScrapM {filteredScraps.length !== 0 && ( - + )} From 33a56676d168937c562835ef8f31f0ab36ce3306 Mon Sep 17 00:00:00 2001 From: parkhyeonki Date: Tue, 13 Dec 2022 15:04:49 +0900 Subject: [PATCH 37/44] =?UTF-8?q?chore:=20lint=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/edit/ModifyModal/index.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/frontend/components/edit/ModifyModal/index.tsx b/frontend/components/edit/ModifyModal/index.tsx index 3bd91976..0ae32357 100644 --- a/frontend/components/edit/ModifyModal/index.tsx +++ b/frontend/components/edit/ModifyModal/index.tsx @@ -118,11 +118,7 @@ export default function ModifyModal({ books, originalArticle }: ModifyModalProps {filteredScraps.length !== 0 && ( - + )} From ff961c9e4fde979113f6e3a7c848e80d708611cf Mon Sep 17 00:00:00 2001 From: MinHK4 Date: Tue, 13 Dec 2022 15:07:04 +0900 Subject: [PATCH 38/44] =?UTF-8?q?fix:=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=AF=BC=EA=B0=90=EC=84=B1=20?= =?UTF-8?q?=EB=86=92=EC=97=AC=EC=A3=BC=EA=B8=B0=20-=20#303?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/home/Slider/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/components/home/Slider/index.tsx b/frontend/components/home/Slider/index.tsx index 5bc5107c..f5dc184e 100644 --- a/frontend/components/home/Slider/index.tsx +++ b/frontend/components/home/Slider/index.tsx @@ -74,10 +74,10 @@ function Slider({ bookList, title, isLoading, numberPerPage }: SliderProps) { const handleSliderTrackTouchEnd = (e: React.TouchEvent) => { const distanceX = touchPositionX - e.changedTouches[0].pageX; - if (distanceX > 0 && sliderNumber !== sliderIndicatorCount) { + if (distanceX > 30 && sliderNumber !== sliderIndicatorCount) { handleRightArrowClick(); } - if (distanceX < 0 && sliderNumber !== 1) { + if (distanceX < -30 && sliderNumber !== 1) { handleLeftArrowClick(); } }; From e6ecbb7bfe806c23f8950bbe8a89d521bc8352a4 Mon Sep 17 00:00:00 2001 From: MinHK4 Date: Tue, 13 Dec 2022 15:27:51 +0900 Subject: [PATCH 39/44] =?UTF-8?q?fix:=20=EC=88=98=EC=A0=95=20=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=20=EA=B0=84=EC=86=8C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/study/FAB/index.tsx | 90 ++++++++++++------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/frontend/components/study/FAB/index.tsx b/frontend/components/study/FAB/index.tsx index 79bb789c..6a8ba134 100644 --- a/frontend/components/study/FAB/index.tsx +++ b/frontend/components/study/FAB/index.tsx @@ -24,14 +24,10 @@ interface FabProps { } export default function FAB({ isEditing, setIsEditing }: FabProps) { - const { isLoading: aaa, data: deletedBook, execute: deleteBook } = useFetch(deleteBookApi); - const { isLoading: bbb, data: editBookData, execute: editBook } = useFetch(editBookApi); - const { - isLoading: ccc, - data: deleteArticleData, - execute: deleteArticle, - } = useFetch(deleteArticleApi); - const { isLoading: ddd, data: deleteScrapData, execute: deleteScrap } = useFetch(deleteScrapApi); + const { data: deletedBook, execute: deleteBook } = useFetch(deleteBookApi); + const { data: editBookData, execute: editBook } = useFetch(editBookApi); + const { data: deleteArticleData, execute: deleteArticle } = useFetch(deleteArticleApi); + const { data: deleteScrapData, execute: deleteScrap } = useFetch(deleteScrapApi); const [editInfo, setEditInfo] = useRecoilState(editInfoState); @@ -58,41 +54,46 @@ export default function FAB({ isEditing, setIsEditing }: FabProps) { editInfo.deletedScraps.forEach((scrapId) => { deleteScrap(scrapId); }); - }; - - useEffect(() => { - if (!deletedBook) return; - setEditInfo({ - ...editInfo, - deleted: editInfo.deleted.filter((id) => id !== deletedBook.id), + deleted: [], + editted: [], + deletedArticle: [], + deletedScraps: [], }); - }, [deletedBook]); + toastSuccess(`수정 완료되었습니다`); + }; - useEffect(() => { - console.log('editBookData', editBookData); - if (!editBookData || !deleteScrapData) return; + // useEffect(() => { + // if (!deletedBook) return; - if (aaa || bbb || ccc || ddd) return; + // setEditInfo({ + // ...editInfo, + // deleted: editInfo.deleted.filter((id) => id !== deletedBook.id), + // }); + // }, [deletedBook]); - setEditInfo({ - ...editInfo, - editted: editInfo.editted.filter((edit) => edit.id !== editBookData.id), - deletedScraps: editInfo.deletedScraps.filter((scrapId) => scrapId !== deleteScrapData.id), - }); - }, [editBookData, deleteScrapData]); + // useEffect(() => { + // console.log('editBookData', editBookData); + // if (!editBookData) return; - useEffect(() => { - if (!deleteArticleData) return; - console.log('deleteArticleData', deleteArticleData); + // setEditInfo({ + // ...editInfo, + // editted: editInfo.editted.filter((edit) => edit.id !== editBookData.id), + // deletedScraps: editInfo.deletedScraps.filter((scrapId) => scrapId !== deleteScrapData.id), + // }); + // }, [editBookData, deleteScrapData]); - setEditInfo({ - ...editInfo, - deletedArticle: editInfo.deletedArticle.filter( - (articleId) => articleId !== deleteArticleData.id - ), - }); - }, [deleteArticleData]); + // useEffect(() => { + // if (!deleteArticleData) return; + // console.log('deleteArticleData', deleteArticleData); + + // setEditInfo({ + // ...editInfo, + // deletedArticle: editInfo.deletedArticle.filter( + // (articleId) => articleId !== deleteArticleData.id + // ), + // }); + // }, [deleteArticleData]); // useEffect(() => { // if (!deleteScrapData) return; @@ -106,15 +107,14 @@ export default function FAB({ isEditing, setIsEditing }: FabProps) { useEffect(() => { console.log(deletedBook, editBookData, deleteArticleData, deleteScrapData, editInfo); - if ( - (deletedBook || editBookData || deleteArticleData || deleteScrapData) && - editInfo.deleted.length === 0 && - editInfo.editted.length === 0 && - editInfo.deletedArticle.length === 0 && - editInfo.deletedScraps.length === 0 - ) { - toastSuccess(`수정 완료되었습니다`); - } + // if ( + // (deletedBook || editBookData || deleteArticleData || deleteScrapData) && + // editInfo.deleted.length === 0 && + // editInfo.editted.length === 0 && + // editInfo.deletedArticle.length === 0 && + // editInfo.deletedScraps.length === 0 + // ) { + // } }, [editInfo]); return ( From afbdb0b8e3bfea394f06d544c8421067da128b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=84?= Date: Tue, 13 Dec 2022 15:35:48 +0900 Subject: [PATCH 40/44] =?UTF-8?q?feat:=20=EA=B8=80=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=EC=97=90=EC=84=9C=20=EB=A7=88=ED=81=AC?= =?UTF-8?q?=EB=8B=A4=EC=9A=B4=20=EB=AC=B8=EB=B2=95=EC=9D=B4=20=EB=B3=B4?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20#312?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/search/ArticleList/index.tsx | 7 +- frontend/package-lock.json | 79 +++++++++++++++++++ frontend/package.json | 3 + frontend/utils/parser.ts | 19 ++++- 4 files changed, 101 insertions(+), 7 deletions(-) diff --git a/frontend/components/search/ArticleList/index.tsx b/frontend/components/search/ArticleList/index.tsx index e59f4919..55779c4b 100644 --- a/frontend/components/search/ArticleList/index.tsx +++ b/frontend/components/search/ArticleList/index.tsx @@ -1,5 +1,6 @@ import ArticleItem from '@components/search/ArticleItem'; import { IArticleBook } from '@interfaces'; +import { markdown2text } from '@utils/parser'; interface ArticleListProps { articles: IArticleBook[]; @@ -24,7 +25,7 @@ export default function ArticleList({ articles, keywords }: ArticleListProps) { let paddingIndex = 0; if (isFirst) { - const regex = /(<([^>]+)>)/g; + const regex = /\n/g; while (regex.test(text.slice(0, startIndex))) paddingIndex = regex.lastIndex; } @@ -33,7 +34,7 @@ export default function ArticleList({ articles, keywords }: ArticleListProps) { <> {text.slice(paddingIndex, startIndex)} {text.slice(startIndex, endIndex)} - {highlightWord(text.slice(endIndex).replace(/(<([^>]+)>)/gi, ''), words)} + {highlightWord(text.slice(endIndex), words)} ); }; @@ -44,7 +45,7 @@ export default function ArticleList({ articles, keywords }: ArticleListProps) { { @@ -17,19 +20,27 @@ export const markdown2html = (markdown: string) => { .processSync(markdown) .toString(); - return unified() - .use(rehypeParse, { fragment: true }) + const htmlWithSyntaxHighlight = rehype() .use(rehypeHighlight, { ignoreMissing: true }) - .use(rehypeStringify) .processSync(html) .toString(); + + return htmlWithSyntaxHighlight; }; export const html2markdown = (html: string) => { - return unified() + const markdown = unified() .use(rehypeParse) .use(rehypeRemark) .use(remarkStringify) .processSync(html) .toString(); + + return markdown; +}; + +export const markdown2text = (markdown: string) => { + const text = remark().use(stripMarkdown).processSync(markdown).toString(); + + return text; }; From 6768656ee872d5ddb7a17d2291431ef555941c71 Mon Sep 17 00:00:00 2001 From: MinHK4 Date: Tue, 13 Dec 2022 15:38:10 +0900 Subject: [PATCH 41/44] =?UTF-8?q?fix:=20=EC=88=98=EC=A0=95=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/common/DragDrop/ListItem/styled.ts | 1 + frontend/components/study/BookListTab/index.tsx | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/components/common/DragDrop/ListItem/styled.ts b/frontend/components/common/DragDrop/ListItem/styled.ts index d6be6f76..dbee668e 100644 --- a/frontend/components/common/DragDrop/ListItem/styled.ts +++ b/frontend/components/common/DragDrop/ListItem/styled.ts @@ -16,6 +16,7 @@ export const Article = styled.div<{ isShown: true | false }>` align-items: center; border-bottom: 1px solid var(--grey-02-color); padding: 5px; + cursor: pointer; `; export const MinusButton = styled.div` diff --git a/frontend/components/study/BookListTab/index.tsx b/frontend/components/study/BookListTab/index.tsx index d9d0e2f2..5c5cbe60 100644 --- a/frontend/components/study/BookListTab/index.tsx +++ b/frontend/components/study/BookListTab/index.tsx @@ -1,5 +1,3 @@ -import dynamic from 'next/dynamic'; - import React, { useState } from 'react'; import { useRecoilState } from 'recoil'; @@ -9,9 +7,11 @@ import curKnottedBookListState from '@atoms/curKnottedBookList'; import editInfoState from '@atoms/editInfo'; import scrapState from '@atoms/scrap'; import Book from '@components/common/Book'; +import Modal from '@components/common/Modal'; import FAB from '@components/study/FAB'; import { IBookScraps } from '@interfaces'; +import EditBookModal from '../EditBookModal'; import { BookGrid, BookListTabWrapper, @@ -35,9 +35,6 @@ export default function BookListTab({ bookmarkedBookList, isUserMatched, }: BookListTabProps) { - const Modal = dynamic(() => import('@components/common/Modal')); - const EditBookModal = dynamic(() => import('@components/study/EditBookModal')); - const [curKnottedBookList, setCurKnottedBookList] = useRecoilState(curKnottedBookListState); const [editInfo, setEditInfo] = useRecoilState(editInfoState); const [_, setScraps] = useRecoilState(scrapState); From f557fe32a8cfa65d1cafa29fd9089b5d4d98f85e Mon Sep 17 00:00:00 2001 From: MinHK4 Date: Tue, 13 Dec 2022 15:38:33 +0900 Subject: [PATCH 42/44] =?UTF-8?q?chore:=20Lint=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/study/BookListTab/index.tsx | 4 +- frontend/components/study/FAB/index.tsx | 37 +++++++++++-------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/frontend/components/study/BookListTab/index.tsx b/frontend/components/study/BookListTab/index.tsx index 5c5cbe60..ba16c4df 100644 --- a/frontend/components/study/BookListTab/index.tsx +++ b/frontend/components/study/BookListTab/index.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import { useRecoilState } from 'recoil'; +import { useRecoilState, useSetRecoilState } from 'recoil'; import MinusWhite from '@assets/ico_minus_white.svg'; import curKnottedBookListState from '@atoms/curKnottedBookList'; @@ -37,7 +37,7 @@ export default function BookListTab({ }: BookListTabProps) { const [curKnottedBookList, setCurKnottedBookList] = useRecoilState(curKnottedBookListState); const [editInfo, setEditInfo] = useRecoilState(editInfoState); - const [_, setScraps] = useRecoilState(scrapState); + const setScraps = useSetRecoilState(scrapState); const [isModalShown, setModalShown] = useState(false); const [curEditBook, setCurEditBook] = useState(null); diff --git a/frontend/components/study/FAB/index.tsx b/frontend/components/study/FAB/index.tsx index 6a8ba134..cc3f1bf0 100644 --- a/frontend/components/study/FAB/index.tsx +++ b/frontend/components/study/FAB/index.tsx @@ -1,6 +1,6 @@ import Image from 'next/image'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { useRecoilState } from 'recoil'; @@ -24,10 +24,15 @@ interface FabProps { } export default function FAB({ isEditing, setIsEditing }: FabProps) { - const { data: deletedBook, execute: deleteBook } = useFetch(deleteBookApi); - const { data: editBookData, execute: editBook } = useFetch(editBookApi); - const { data: deleteArticleData, execute: deleteArticle } = useFetch(deleteArticleApi); - const { data: deleteScrapData, execute: deleteScrap } = useFetch(deleteScrapApi); + const { execute: deleteBook } = useFetch(deleteBookApi); + const { execute: editBook } = useFetch(editBookApi); + const { execute: deleteArticle } = useFetch(deleteArticleApi); + const { execute: deleteScrap } = useFetch(deleteScrapApi); + + // const { data: deletedBook, execute: deleteBook } = useFetch(deleteBookApi); + // const { data: editBookData, execute: editBook } = useFetch(editBookApi); + // const { data: deleteArticleData, execute: deleteArticle } = useFetch(deleteArticleApi); + // const { data: deleteScrapData, execute: deleteScrap } = useFetch(deleteScrapApi); const [editInfo, setEditInfo] = useRecoilState(editInfoState); @@ -105,17 +110,17 @@ export default function FAB({ isEditing, setIsEditing }: FabProps) { // }); // }, [deleteScrapData]); - useEffect(() => { - console.log(deletedBook, editBookData, deleteArticleData, deleteScrapData, editInfo); - // if ( - // (deletedBook || editBookData || deleteArticleData || deleteScrapData) && - // editInfo.deleted.length === 0 && - // editInfo.editted.length === 0 && - // editInfo.deletedArticle.length === 0 && - // editInfo.deletedScraps.length === 0 - // ) { - // } - }, [editInfo]); + // useEffect(() => { + // console.log(deletedBook, editBookData, deleteArticleData, deleteScrapData, editInfo); + // if ( + // (deletedBook || editBookData || deleteArticleData || deleteScrapData) && + // editInfo.deleted.length === 0 && + // editInfo.editted.length === 0 && + // editInfo.deletedArticle.length === 0 && + // editInfo.deletedScraps.length === 0 + // ) { + // } + // }, [editInfo]); return ( From 82654dcad2d1694c0fbf60b82c43fc392b6429ae Mon Sep 17 00:00:00 2001 From: parkhyeonki Date: Tue, 13 Dec 2022 16:05:37 +0900 Subject: [PATCH 43/44] =?UTF-8?q?fix:=20=EC=82=AD=EC=A0=9C=EB=90=9C=20?= =?UTF-8?q?=EA=B8=80=20=EC=A0=91=EA=B7=BC=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/pages/viewer/[...data].tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/pages/viewer/[...data].tsx b/frontend/pages/viewer/[...data].tsx index c6a7e488..347f098e 100644 --- a/frontend/pages/viewer/[...data].tsx +++ b/frontend/pages/viewer/[...data].tsx @@ -38,6 +38,7 @@ export default function Viewer({ article }: ViewerProps) { }; const checkArticleAuthority = (targetBook: IBookScraps, id: number) => { + if (!targetBook) return false; if (targetBook.scraps.find((scrap) => scrap.article.id === id)) return true; return false; }; @@ -64,7 +65,7 @@ export default function Viewer({ article }: ViewerProps) { }, [router.query.data]); useEffect(() => { - if (!book) return; + if (book === undefined) return; if (!checkArticleAuthority(book, article.id)) router.push('/404'); }, [book]); From bc6164c1d3b8f63360a55859bcd2e59b5db86e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=84?= Date: Tue, 13 Dec 2022 16:09:47 +0900 Subject: [PATCH 44/44] =?UTF-8?q?refactor:=20TOC=20=EA=B0=9C=EC=84=A0=20?= =?UTF-8?q?=EB=B0=8F=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20#312?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/viewer/TOC/index.tsx | 3 +- frontend/package-lock.json | 83 ++++++++++++++++++++++++ frontend/package.json | 1 + frontend/pages/viewer/[...data].tsx | 6 +- frontend/utils/articleConversion.ts | 39 ----------- frontend/utils/parser.ts | 2 + frontend/utils/toc.ts | 27 ++++++++ 7 files changed, 118 insertions(+), 43 deletions(-) delete mode 100644 frontend/utils/articleConversion.ts create mode 100644 frontend/utils/toc.ts diff --git a/frontend/components/viewer/TOC/index.tsx b/frontend/components/viewer/TOC/index.tsx index 8390b2dc..2d2e4f08 100644 --- a/frontend/components/viewer/TOC/index.tsx +++ b/frontend/components/viewer/TOC/index.tsx @@ -10,6 +10,7 @@ import useBookmark from '@hooks/useBookmark'; import { IBookScraps } from '@interfaces'; import { TextMedium, TextSmall } from '@styles/common'; import { FlexCenter, FlexSpaceBetween } from '@styles/layout'; +import { text2link } from '@utils/toc'; import { TocWrapper, @@ -92,7 +93,7 @@ export default function TOC({ {isArticleShown && articleToc.map((article) => ( diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 06ac8029..eec9f602 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -39,6 +39,7 @@ "rehype-highlight": "^6.0.0", "rehype-parse": "^8.0.4", "rehype-remark": "^9.1.2", + "rehype-slug": "^5.1.0", "rehype-stringify": "^9.0.3", "remark": "^14.0.2", "remark-breaks": "^3.0.2", @@ -3221,6 +3222,11 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" + }, "node_modules/glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -3420,6 +3426,18 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-heading-rank": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-2.1.0.tgz", + "integrity": "sha512-w+Rw20Q/iWp2Bcnr6uTrYU6/ftZLbHKhvc8nM26VIWpDqDMlku2iXUVTeOlsdoih/UKQhY7PHQ+vZ0Aqq8bxtQ==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-is-body-ok-link": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-2.0.0.tgz", @@ -3521,6 +3539,18 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz", + "integrity": "sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-to-text": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-3.1.1.tgz", @@ -5400,6 +5430,24 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-slug": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-5.1.0.tgz", + "integrity": "sha512-Gf91dJoXneiorNEnn+Phx97CO7oRMrpi+6r155tTxzGuLtm+QrI4cTwCa9e1rtePdL4i9tSO58PeSS6HWfgsiw==", + "dependencies": { + "@types/hast": "^2.0.0", + "github-slugger": "^2.0.0", + "hast-util-has-property": "^2.0.0", + "hast-util-heading-rank": "^2.0.0", + "hast-util-to-string": "^2.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/rehype-stringify": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-9.0.3.tgz", @@ -8938,6 +8986,11 @@ "integrity": "sha512-X8u8fREiYOE6S8hLbq99PeykTDoLVnxvF4DjWKJmz9xy2nNRdUcV8ZN9tniJFeKyTU3qnC9lL8n4Chd6LmVKHg==", "dev": true }, + "github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" + }, "glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -9080,6 +9133,14 @@ "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-2.0.0.tgz", "integrity": "sha512-4Qf++8o5v14us4Muv3HRj+Er6wTNGA/N9uCaZMty4JWvyFKLdhULrv4KE1b65AthsSO9TXSZnjuxS8ecIyhb0w==" }, + "hast-util-heading-rank": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-2.1.0.tgz", + "integrity": "sha512-w+Rw20Q/iWp2Bcnr6uTrYU6/ftZLbHKhvc8nM26VIWpDqDMlku2iXUVTeOlsdoih/UKQhY7PHQ+vZ0Aqq8bxtQ==", + "requires": { + "@types/hast": "^2.0.0" + } + }, "hast-util-is-body-ok-link": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-2.0.0.tgz", @@ -9157,6 +9218,14 @@ "unist-util-visit": "^4.0.0" } }, + "hast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz", + "integrity": "sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==", + "requires": { + "@types/hast": "^2.0.0" + } + }, "hast-util-to-text": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-3.1.1.tgz", @@ -10347,6 +10416,20 @@ "unified": "^10.0.0" } }, + "rehype-slug": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-5.1.0.tgz", + "integrity": "sha512-Gf91dJoXneiorNEnn+Phx97CO7oRMrpi+6r155tTxzGuLtm+QrI4cTwCa9e1rtePdL4i9tSO58PeSS6HWfgsiw==", + "requires": { + "@types/hast": "^2.0.0", + "github-slugger": "^2.0.0", + "hast-util-has-property": "^2.0.0", + "hast-util-heading-rank": "^2.0.0", + "hast-util-to-string": "^2.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" + } + }, "rehype-stringify": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-9.0.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9bdb2b85..cdcfaf54 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -40,6 +40,7 @@ "rehype-highlight": "^6.0.0", "rehype-parse": "^8.0.4", "rehype-remark": "^9.1.2", + "rehype-slug": "^5.1.0", "rehype-stringify": "^9.0.3", "remark": "^14.0.2", "remark-breaks": "^3.0.2", diff --git a/frontend/pages/viewer/[...data].tsx b/frontend/pages/viewer/[...data].tsx index c6a7e488..ccc6478d 100644 --- a/frontend/pages/viewer/[...data].tsx +++ b/frontend/pages/viewer/[...data].tsx @@ -13,7 +13,7 @@ import ViewerHead from '@components/viewer/ViewerHead'; import useFetch from '@hooks/useFetch'; import { IArticleBook, IBookScraps } from '@interfaces'; import { Flex, PageGNBHide, PageNoScrollWrapper } from '@styles/layout'; -import { articleToc, articleConversion } from '@utils/articleConversion'; +import { parseHeadings } from '@utils/toc'; interface ViewerProps { article: IArticleBook; @@ -81,7 +81,7 @@ export default function Viewer({ article }: ViewerProps) { diff --git a/frontend/utils/articleConversion.ts b/frontend/utils/articleConversion.ts deleted file mode 100644 index 94e01841..00000000 --- a/frontend/utils/articleConversion.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { html2markdown } from './parser'; - -export const articleToc = (content: string) => { - // 게시물 본문을 줄바꿈 기준으로 나누고, 제목 요소인 것만 저장 - const titles = html2markdown(content) - .split(`\n`) - .filter((t) => t.includes('# ')); - - // 예외처리 - 제목은 문자열 시작부터 #을 써야함 - const result = titles - .filter((str) => str[0] === '#') - .map((item) => { - // #의 갯수에 따라 제목의 크기가 달라지므로 갯수를 센다. - let count = item.match(/#/g)?.length; - if (count) { - // 갯수에 따라 목차에 그릴때 들여쓰기 하기위해 *10을 함. - count *= 10; - } - - // 제목의 내용물만 꺼내기 위해 '# '을 기준으로 나누고, 백틱과 공백을 없애주고 count와 묶어서 리턴 - return { title: item.split('# ')[1].replace(/`/g, '').trim(), count }; - }); - - return result; -}; - -export const articleConversion = (content: string) => { - const newArticle = content.split('\n').map((v, idx) => { - if (v.includes('h1') || v.includes('h2') || v.includes('h3')) { - const title = v.replace(/<[^>]*>?/g, ''); - const result = v.split(''); - result.splice(3, 0, ' ', `id=${title}`); - return result.join(''); - } - return v; - }); - - return newArticle.join('\n'); -}; diff --git a/frontend/utils/parser.ts b/frontend/utils/parser.ts index a572759b..6803d3a0 100644 --- a/frontend/utils/parser.ts +++ b/frontend/utils/parser.ts @@ -2,6 +2,7 @@ import { rehype } from 'rehype'; import rehypeHighlight from 'rehype-highlight'; import rehypeParse from 'rehype-parse'; import rehypeRemark from 'rehype-remark'; +import rehypeSlug from 'rehype-slug'; import rehypeStringify from 'rehype-stringify'; import { remark } from 'remark'; import remarkBreaks from 'remark-breaks'; @@ -21,6 +22,7 @@ export const markdown2html = (markdown: string) => { .toString(); const htmlWithSyntaxHighlight = rehype() + .use(rehypeSlug) .use(rehypeHighlight, { ignoreMissing: true }) .processSync(html) .toString(); diff --git a/frontend/utils/toc.ts b/frontend/utils/toc.ts new file mode 100644 index 00000000..505550f8 --- /dev/null +++ b/frontend/utils/toc.ts @@ -0,0 +1,27 @@ +export const parseHeadings = (content: string) => { + // 게시물 본문을 줄바꿈 기준으로 나누고, 제목 요소인 것만 저장 + const headings = content.split('\n').filter((line) => line.includes('# ')); + + // 예외처리 - 제목은 문자열 시작부터 #을 써야함 + const parsedHeadings = headings + .filter((heading) => heading.startsWith('#')) + .map((heading) => { + // #의 갯수에 따라 제목의 크기가 달라지므로 갯수를 센다. + let count = heading.match(/#/g)?.length; + + // 갯수에 따라 목차에 그릴때 들여쓰기 하기위해 *10을 함. + if (count) count *= 16; + + // 제목의 내용물만 꺼내기 위해 '# '을 기준으로 나누고, 백틱과 공백을 없애주고 count와 묶어서 리턴 + return { + title: heading.split('# ')[1].trim(), + count, + }; + }); + + return parsedHeadings; +}; + +export const text2link = (text: string) => { + return `#${text.replace(/ /g, '-').replace(/[^\uAC00-\uD7A30-9a-zA-Z_-]/g, '')}`; +};