-
Notifications
You must be signed in to change notification settings - Fork 28
[Mission4/최은지] - Project_Notion_VanillaJS #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 3/#4_choieunji
Are you sure you want to change the base?
Changes from all commits
9832d02
b15a0c5
8bab2c6
5e7817f
f82c1b4
f8e2b62
5f2857f
d537924
272d1e9
bdaf1ad
9c5673b
2c5333a
ce0ea52
889f7bd
95990c7
1fea468
408e099
064c959
4c87e69
3b6572d
662f7df
9572f27
b69f3e2
270f2d2
ddd6679
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api-endpoint.js |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,38 @@ | ||
| # 📌 4주차 프로젝트[Project1] | ||
|
|
||
| ## 필수 프로젝트 | ||
|
|
||
| - 프로젝트 기한 | ||
| - 프로젝트 수행 기간 : 2022년 11월 8일(화) ~ 2022년 11월 16일(수) | ||
| - 멘티 코드 리뷰 기간 : 2022년 11월 17일(목) ~ 2022년 11월 20일(일) | ||
| - 멘토 코드 리뷰 기간 : 2022년 11월 17일(목) ~ 2022년 11월 22일(화) | ||
| - 코드 리뷰 반영 기간 : 2022년 11월 23일(수) ~ 2022년 11월 25일(금) | ||
| - 내용 | ||
| - **Day 17 [프로젝트] 노션 클로닝 요구사항** 확인 부탁드립니다. | ||
|  | ||
|
|
||
| ## 배포 주소 | ||
|
|
||
| https://dmswl98-notion-clone-project.netlify.app/ | ||
|
|
||
| ## 기본 요구사항 | ||
|
|
||
| - [x] 글 단위를 Document라고 합니다. Document는 Document 여러개를 포함할 수 있습니다. | ||
| - [x] 화면 좌측에 Root Documents를 불러오는 API를 통해 루트 Documents를 렌더링합니다. | ||
| - [x] Root Document를 클릭하면 오른쪽 편집기 영역에 해당 Document의 Content를 렌더링합니다. | ||
| - [x] 해당 Root Document에 하위 Document가 있는 경우, 해당 Document 아래에 트리 형태로 렌더링 합니다. | ||
| - [x] Document Tree에서 각 Document 우측에는 + 버튼이 있습니다. 해당 버튼을 클릭하면, 클릭한 Document의 하위 Document로 새 Document를 생성하고 편집화면으로 넘깁니다. | ||
| - [x] 편집기에는 기본적으로 저장 버튼이 없습니다. Document Save API를 이용해 지속적으로 서버에 저장되도록 합니다. | ||
| - [x] History API를 이용해 SPA 형태로 만듭니다. | ||
|
|
||
| ## 추가 요구사항 | ||
|
|
||
| - [x] 기본적으로 편집기는 textarea 기반으로 단순한 텍스트 편집기로 시작하되, 여력이 되면 div와 contentEditable을 조합해서 좀 더 Rich한 에디터를 만들어봅니다. | ||
| - [x] 편집기 최하단에는 현재 편집 중인 Document의 하위 Document 링크를 렌더링하도록 추가합니다. | ||
| - [ ] 편집기 내에서 다른 Document name을 적은 경우, 자동으로 해당 Document의 편집 페이지로 이동하는 링크를 거는 기능을 추가합니다. | ||
|
|
||
| ## 추가 구현사항 | ||
|
|
||
| - [x] Sidebar 숨기기, 펼치기 기능 | ||
| - [x] 텍스트 스타일(굵게, 기울기, 밑줄, 삭선, 색깔) 모달 구현 | ||
| - [x] 상위 문서를 삭제한 경우, 내부 하단 문서까지 모두 삭제하도록 구현 | ||
|
|
||
| ## 개선 사항 | ||
|
|
||
| - [ ] 첫 화면일 때 사이드 바의 숨기기 버튼 없애기 | ||
| - [ ] 사이드 바가 펼쳐져 있을 때, 사이드 바 숨기기 버튼 없애기 | ||
| - [ ] 스크롤 시 텍스트 스타일 모달이 제 위치에 뜨지 않는 문제 해결하기 | ||
| - [ ] XSS 취약점 개선 | ||
| - [ ] 문서의 제목을 변경할 경우 Sidebar에 변경된 제목을 낙관적 업데이트로 반영하기 | ||
| - [ ] 문서를 생성했을 때 새로 생성된 문서로 이동 및 랜더링하기 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| /* /index.html 200 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="ko"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>최은지님의 Notion</title> | ||
| <link rel="stylesheet" href="/style.css" /> | ||
| </head> | ||
| <body> | ||
| <div id="app"></div> | ||
| <script type="module" src="/src/index.js"></script> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import Sidebar from "./components/Sidebar/Sidebar.js"; | ||
| import DocumentPage from "./components/Document/Document.js"; | ||
|
|
||
| import { getDocumentContent } from "./api/api.js"; | ||
|
|
||
| import { validateInstance } from "./utils/validation.js"; | ||
| import { initRouter } from "./utils/router.js"; | ||
|
|
||
| export default function App({ $target }) { | ||
| validateInstance(new.target); | ||
|
|
||
| const sidebar = new Sidebar({ $target }); | ||
|
|
||
| const documentPage = new DocumentPage({ | ||
| $target, | ||
| initialState: { | ||
| documentId: 0, | ||
| title: "", | ||
| content: "", | ||
| }, | ||
| onUpdateTitle: () => { | ||
| sidebar.setState(); | ||
| }, | ||
| }); | ||
|
|
||
| this.route = async () => { | ||
| const { pathname } = window.location; | ||
|
|
||
| if (pathname === "/") { | ||
| sidebar.setState(); | ||
| } else if (pathname.indexOf("/documents/") === 0) { | ||
| const [, , documentId] = pathname.split("/"); | ||
|
|
||
| const documentData = await getDocumentContent(documentId); | ||
|
|
||
| const { title, content, documents } = documentData; | ||
| documentPage.setState({ | ||
| documentId, | ||
| title, | ||
| content, | ||
| documents, | ||
| }); | ||
|
|
||
| sidebar.setState(); | ||
| } | ||
| }; | ||
|
|
||
| const init = () => { | ||
| this.route(); | ||
|
|
||
| initRouter(this.route); | ||
|
|
||
| window.addEventListener("popstate", () => { | ||
| this.route(); | ||
| }); | ||
| }; | ||
|
|
||
| init(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import { request } from "./index.js"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 api함수들을 따로 정리하니까 보기 좋네요! ask) request가 정의된 파일 이름이 왜 index.js인지 물어봐도 될까요:)? |
||
|
|
||
| export const getRootDouments = async () => { | ||
| return await request("/documents"); | ||
| }; | ||
|
|
||
| export const getDocumentContent = async (documentId) => { | ||
| return await request(`/documents/${documentId}`); | ||
| }; | ||
|
|
||
| export const createDocument = async (documentData) => { | ||
| return await request(`/documents`, { | ||
| method: "POST", | ||
| body: JSON.stringify(documentData), | ||
| }); | ||
| }; | ||
|
|
||
| export const modifyDocuments = async (documentId, documentData) => { | ||
| return await request(`/documents/${documentId}`, { | ||
| method: "PUT", | ||
| body: JSON.stringify(documentData), | ||
| }); | ||
| }; | ||
|
|
||
| export const deleteDocument = async (documentId) => { | ||
| return await request(`/documents/${documentId}`, { | ||
| method: "DELETE", | ||
| }); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { API_END_POINT } from "../../api-endpoint.js"; | ||
|
|
||
| export const request = async (url, options = {}) => { | ||
| try { | ||
| const res = await fetch(`${API_END_POINT}${url}`, { | ||
| ...options, | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| "x-username": "dmswl12345", | ||
| }, | ||
| }); | ||
|
|
||
| if (!res.ok) { | ||
| throw new Error("API 처리 중"); | ||
| } | ||
|
|
||
| return res.json(); | ||
| } catch (err) { | ||
| console.error(err); | ||
| history.go(-1); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P5 : 오호 뒤로가기 처리를 하는 군요! 만약에 제가 구글로 들어간 후, 잘못된 주소로 접속, 예를 들면 새로운 탭을 키고 https://dmswl98-notion-clone-project.netlify.app/documents/00101과 같이 잘못된 documentId로 들어가면 구글로 튕기네요! 사용자 입장에선 not found가 떠서 잘못된 주소임을 가르켜 주는 것이 아니라 뒤로 튕겨버리니 익숙치 않은 경험이 될 수 있을 것 같습니다!
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 그 점을 인지하고 있었는데 까먹고 고치지 못했네요ㅎㅎ 꼭 반영해야겠군요 🤯 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p5) 에러가 발생되었을 때, history.go(-1)을 통해서 한 단계전으로 돌아가는 군요! 사실 SidebarNav에서 onDeleteDocument()를 통해서 문서를 삭제했을 때, 삭제한 문서의 id를 통해서 push()를 호출하는 것으로 보입니다. 고민이 되는 부분이네요 어떻게 생각하시나요:)?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. history.go(-1)는 딱 이전 페이지를 보여주는데 만약, 문서들을 조회하고 조회한 문서들이 삭제되면 이전 페이지에 해당하는 문서가 삭제된 경우라 계속 에러가 발생하는 것 같아요. 저장된 데이터에는 삭제된 문서가 없어 fetch할 수 없기에... 이 부분은 승준님 말씀처럼 루트로 되돌리는 것이 나을 것 같고 꼭 반영해야겠어요! |
||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import DocumentHeader from "./DocumentHeader.js"; | ||
| import Editor from "./DocumentEditor.js"; | ||
|
|
||
| import { modifyDocuments } from "../../api/api.js"; | ||
|
|
||
| import { validateInstance } from "../../utils/validation.js"; | ||
| import { debounce } from "../../utils/debounce.js"; | ||
|
|
||
| export default function Document({ $target, initialState, onUpdateTitle }) { | ||
| validateInstance(new.target); | ||
|
|
||
| const $document = document.createElement("div"); | ||
| $document.classList.add("document"); | ||
|
|
||
| this.state = initialState; | ||
|
|
||
| new DocumentHeader({ $target: $document }); | ||
|
|
||
| const editor = new Editor({ | ||
| $target: $document, | ||
| initialState, | ||
| onEditTitle: (id, data) => { | ||
| debounce(async () => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. debounce를 따로 모듈로 빼서 동작하게 하는군요 저도 바로 적용해야겠습니다:) |
||
| await modifyDocuments(id, data); | ||
| onUpdateTitle(); | ||
| }, 100); | ||
| }, | ||
| onEditContent: (id, data) => { | ||
| debounce(async () => { | ||
| await modifyDocuments(id, data); | ||
| }, 300); | ||
| }, | ||
| }); | ||
|
|
||
| this.setState = (nextState) => { | ||
| this.state = nextState; | ||
| editor.setState(this.state); | ||
|
|
||
| this.render(); | ||
| }; | ||
|
|
||
| this.render = () => { | ||
| $target.appendChild($document); | ||
| }; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p6) document의 내용을 가져오는 함수 getDocumentContent의 반환값을 저장하는 변수이니 documentContent라는 변수명이 더 적당하지 않을까라는 의견을 남깁니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오호 생각치 못했던 부분..! documentContent 네이밍이 더 좋은 것 같아요! 꼭 반영하겠습니당