-
Notifications
You must be signed in to change notification settings - Fork 28
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
[Mission5/임지선] Project_Notion_VanillaJs 과제 #50
base: 4/#5_limjiseon
Are you sure you want to change the base?
Changes from 23 commits
67775ff
53e14ec
e741067
03f954a
21ab7c4
32d75bc
7668c85
a4ff1d7
b14c73e
d3f3b9e
037630a
612fa32
722e43a
80933e5
631e92f
59b36a6
35b68fd
6853d66
09e3956
e7b3dff
bded9fe
9c3557f
0e0fec1
b6a613a
ae82433
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,10 @@ | ||
<html> | ||
<head> | ||
<title>Notion Project</title> | ||
</head> | ||
<body> | ||
<main id="app"></main> | ||
<link rel="stylesheet" href="style.css" /> | ||
<script src="/src/main.js" type="module"></script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import PostEditPage from "./PostEditPage.js"; | ||
import PostNavBar from "./PostNavBar.js"; | ||
import { initRouter } from "./router.js"; | ||
|
||
export default function App({ $target }) { | ||
const postNavBar = new PostNavBar({ | ||
$target, | ||
}); | ||
|
||
const postEditPage = new PostEditPage({ | ||
$target, | ||
initialState: { | ||
postId: "new", | ||
post: { | ||
title: "", | ||
content: "", | ||
}, | ||
}, | ||
}); | ||
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. 지석 멘토님께서 말씀해 주셨듯이 사이드 바와 편집기 두개를 각각 새로운 컴포넌트로 만들어서 상위 |
||
|
||
this.route = () => { | ||
$target.innerHTML = ""; | ||
const { pathname } = window.location; | ||
|
||
if (pathname !== "/" && pathname.indexOf("/") === 0) { | ||
const [, postId] = pathname.split("/"); | ||
postEditPage.setState({ postId }); | ||
} | ||
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.
요구사항과 살짝 다르게 개발이 되었습니다. |
||
postNavBar.setState(); | ||
}; | ||
|
||
this.route(); | ||
|
||
initRouter(() => this.route()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
export default function Editor({ | ||
$target, | ||
initialState = { | ||
title: "", | ||
content: "", | ||
}, | ||
onEditing, | ||
}) { | ||
const $editor = document.createElement("div"); | ||
$editor.id = "editor"; | ||
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. id 를 사용할 명확한 이유가 없다면, className 을 사용하능 것이 더 좋다고 알고 있습니다! https://dev.to/clairecodes/reasons-not-to-use-ids-in-css-4ni4 |
||
|
||
let isinitialize = false; | ||
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. 카멜케이스로 작성 해주면 좋습니다! |
||
|
||
this.state = initialState; | ||
|
||
$target.appendChild($editor); | ||
|
||
this.setState = (nextState) => { | ||
this.state = nextState; | ||
$editor.querySelector("[name=title]").value = this.state.title; | ||
$editor.querySelector("[name=content]").value = this.state.content; | ||
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. this.state 가 여러번 반복되는 것 같아서... 구조분해 할당을 사용해주시는 것은 어떨까요? const { title, content } = this.state; |
||
this.render(); | ||
}; | ||
|
||
this.render = () => { | ||
if (!isinitialize) { | ||
$editor.innerHTML = ` | ||
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. 새로운 글을 클릭할 때마다 Editor가 렌더링 되다보니, input과 textarea가 매번 생성되서 화면 깜빡임(Flickering) 현상이 발생하는 것 같습니다. 맨 처음 Editor를 생성할 때 외에는 value 값만 변경하는 게 어떨지 추천드립니다. 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. 저도 창욱님의 말씀에 동의합니다! 아무래도 동적으로 추가되고 삭제될 일이 없는 DOM element 는 컴포넌트 초기화 시점애 미리 만들어두는 것이 좋을것 같다고 생각합니다! |
||
<input type="text" name="title" value="${this.state.title}" /> | ||
<textarea name="content">${this.state.content}</textarea> | ||
`; | ||
isinitialize = true; | ||
} | ||
}; | ||
this.render(); | ||
|
||
$editor.addEventListener("keyup", (e) => { | ||
const { target } = e; | ||
|
||
const name = target.getAttribute("name"); | ||
|
||
if (this.state[name] !== undefined) { | ||
const nextState = { | ||
...this.state, | ||
[name]: target.value, | ||
}; | ||
this.setState(nextState); | ||
onEditing(this.state); | ||
} | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { request } from "./api.js"; | ||
import Editor from "./Editor.js"; | ||
import { pushRouter } from "./router.js"; | ||
|
||
export default function PostEditPage({ $target, initialState }) { | ||
const $page = document.createElement("div"); | ||
$page.id = "editPage"; | ||
$page.className = "2"; | ||
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. 클래스 이름은...조금 더 의미있게 지어주세요!!! :) |
||
|
||
this.state = initialState; | ||
|
||
let timer = null; | ||
|
||
const editor = new Editor({ | ||
$target: $page, | ||
initialState: { | ||
title: "", | ||
content: "", | ||
}, | ||
onEditing: (post) => { | ||
if (timer !== null) { | ||
clearTimeout(timer); | ||
} | ||
|
||
timer = setTimeout(async () => { | ||
if (this.state.postId) { | ||
await request(`/documents/${this.state.postId}`, { | ||
method: "PUT", | ||
body: JSON.stringify(post), | ||
}); | ||
pushRouter(`/${this.state.postId}`); | ||
await getDocument(); | ||
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. 이 부분도 postid 를 구조분해하여 꺼내 사용하면 가독성이 더 좋아질 것 같습니다! |
||
} | ||
}, 2000); | ||
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. 오우 갑자기 멘션당해서 깜짝 놀랐습니다 ㅎㅎㅎ 맞아요 매직 넘버와 매직 스트링 쓰는 습관을 들이는건 좋은 것 같습니다! 상수의 경우 여러 곳에서 동일한 의미와 용도로 사용된다면 매직 넘버로 할당하시는게 좋고, 오탈자가 발생하면 에러가 일어날 확률이 높은 문자열은 매직 스트링으로 할당해서 사용하시는게 좋습니다! (IDE의 자동 완성의 힘을 믿어보자구요!) |
||
}, | ||
}); | ||
|
||
this.setState = async (nextState) => { | ||
if (this.state.postId !== nextState.postId) { | ||
this.state = nextState; | ||
await getDocument(); | ||
return; | ||
} else { | ||
this.state = nextState; | ||
this.render(); | ||
} | ||
|
||
editor.setState( | ||
this.state.post || { | ||
title: "", | ||
content: "", | ||
} | ||
); | ||
}; | ||
|
||
this.render = () => { | ||
$target.appendChild($page); | ||
console.log($target) | ||
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. 아마 테스트 해보실 때 |
||
}; | ||
|
||
const getDocument = async () => { | ||
const { postId } = this.state; | ||
|
||
if (postId) { | ||
const post = await request(`/documents/${postId}`); | ||
|
||
this.setState({ | ||
...this.state, | ||
post, | ||
}); | ||
} | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { request } from "./api.js"; | ||
import { pushRouter } from "./router.js"; | ||
|
||
export default function PostItem(title, id) { | ||
const $postItemBox = document.createElement("div"); | ||
$postItemBox.className = id; | ||
|
||
const $li = document.createElement("li"); | ||
$li.className = id; | ||
|
||
const $title = document.createElement("span"); | ||
$title.className = id; | ||
$title.textContent = title; | ||
$li.appendChild($title); | ||
|
||
const $addButton = document.createElement("button"); | ||
$addButton.textContent = "+"; | ||
$addButton.className = id; | ||
$li.appendChild($addButton); | ||
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. button 을 생성하는 로직이 반복되는 것 같습니다! 이 부분은 따로 함수화하거나, Button 컴포넌트를 만들어 사용해주시면 좋을 것 같습니다! |
||
|
||
const $removeButton = document.createElement("button"); | ||
$removeButton.textContent = "-"; | ||
$removeButton.className = id; | ||
$li.appendChild($removeButton); | ||
|
||
const $postSubItemBox = document.createElement("ul"); | ||
|
||
$postItemBox.appendChild($li); | ||
$postItemBox.append($postSubItemBox); | ||
|
||
$title.addEventListener("click", async () => { | ||
request(`/documents/${$title.className}`); | ||
pushRouter(`/${$title.className}`); | ||
}); | ||
|
||
$addButton.addEventListener("click", async () => { | ||
const createdPost = await request("/documents", { | ||
method: "POST", | ||
body: JSON.stringify({ | ||
title: "제목 없음", | ||
parent: $addButton.className, | ||
}), | ||
}); | ||
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 통신은 여러번 쓰일 수 있으니 따로 분리해두는 것이 편리할 수 있을 것 같습니다! |
||
pushRouter(`/${createdPost.id}`); | ||
}); | ||
|
||
$removeButton.addEventListener("click", async () => { | ||
await request(`/documents/${$removeButton.className}`, { | ||
method: "DELETE", | ||
}); | ||
pushRouter(`/`); | ||
}); | ||
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. 지선님!! 코드는 정말 깔끔하게 잘 짜셨어요 👍 이제 조금 더 팁을 드려보겠습니다! 아래 링크를 읽어보시면 좋을 것 같습니다! :) |
||
|
||
return { $postItemBox, $postSubItemBox }; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import PostItem from "./PostItem.js"; | ||
|
||
export default function PostList({ $target, initialState }) { | ||
const $postList = document.createElement("ul"); | ||
$target.appendChild($postList); | ||
|
||
this.state = initialState; | ||
|
||
this.setState = (nextState) => { | ||
this.state = nextState; | ||
this.render(); | ||
}; | ||
|
||
this.makeList = ($wrap, data) => { | ||
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. $wrap말고 조금 더 의미있는 파라미터 명을 작성해주세요!!
|
||
data.forEach(({ title, documents, id }) => { | ||
const { $postItemBox, $postSubItemBox } = PostItem(title, id); | ||
|
||
$wrap.appendChild($postItemBox); | ||
|
||
if (documents.length > 0) { | ||
this.makeList($postSubItemBox, documents); | ||
} | ||
}); | ||
}; | ||
|
||
this.render = () => { | ||
$postList.innerHTML = ""; | ||
this.makeList($postList, this.state); | ||
}; | ||
|
||
this.render(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { request } from "./api.js"; | ||
import PostList from "./PostList.js"; | ||
import { pushRouter } from "./router.js"; | ||
|
||
export default function PostNavBar({ $target }) { | ||
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.
|
||
const $nav = document.createElement("div"); | ||
const $createButton = document.createElement("button"); | ||
const $title = document.createElement("h1"); | ||
$nav.id = "nav"; | ||
$nav.className = "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. 위에서 지석멘토님께서 피드백 주셨듯이, 의미있는 className을 정해주는 것이 좋을 것 같습니다! |
||
$createButton.id = "createButton"; | ||
$title.id = "title"; | ||
|
||
const postList = new PostList({ | ||
$target: $nav, | ||
initialState: [], | ||
}); | ||
|
||
this.setState = async () => { | ||
const documents = await request("/documents"); | ||
postList.setState(documents); | ||
this.render(); | ||
}; | ||
|
||
this.render = async () => { | ||
$createButton.textContent = "문서 생성하기"; | ||
$title.textContent = "Notion Project"; | ||
$nav.appendChild($title); | ||
$nav.appendChild($createButton); | ||
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. $nav 에다가 PostList를 생성하고나서 |
||
$target.appendChild($nav); | ||
}; | ||
|
||
$createButton.addEventListener("click", async () => { | ||
const createdPost = await request("/documents", { | ||
method: "POST", | ||
body: JSON.stringify({ | ||
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. 저도 이렇게 request 내부에서 stringify를 했는데, 오프 멘토님 리뷰를 보니 직렬화 과정을 별도로 분리하는 게 좋을 것 같다는 피드백을 주셨더라구요. 다르게 구현하는 방법을 생각해보는거도 좋을 것 같습니다! |
||
title: "제목 없음", | ||
parent: null, | ||
}), | ||
}); | ||
pushRouter(`/${createdPost.id}`); | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
export const API_END_POINT = "https://kdt-frontend.programmers.co.kr"; | ||
export const API_X_USERNAME = "API_X_USERNAME_LIMJISEON"; | ||
|
||
export const request = async (url, options = {}) => { | ||
try { | ||
const response = await fetch(`${API_END_POINT}${url}`, { | ||
...options, | ||
headers: { | ||
"Content-Type": "application/json", | ||
"x-username": API_X_USERNAME, | ||
}, | ||
}); | ||
|
||
if (response.ok) { | ||
return await response.json(); | ||
} | ||
|
||
throw new Error("API 처리중 에러가 발생했습니다."); | ||
} catch (e) { | ||
alert(e.message); | ||
console.error(); | ||
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. console.error(e); |
||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import App from "./App.js"; | ||
|
||
const $target = document.querySelector("#app"); | ||
$target.id = "contentWrap"; | ||
|
||
new App({ $target }); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
const ROUTE_CHANGE_EVENT_NAME = "route-change"; | ||
|
||
export const initRouter = (onRoute) => { | ||
window.addEventListener(ROUTE_CHANGE_EVENT_NAME, (e) => { | ||
const { nextUrl } = e.detail; | ||
|
||
if (nextUrl) { | ||
history.pushState(null, null, nextUrl); | ||
onRoute(); | ||
} | ||
}); | ||
}; | ||
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. 지선님도 뒤로가기/앞으로가기 때 발생하는 |
||
|
||
export const pushRouter = (nextUrl) => { | ||
window.dispatchEvent( | ||
new CustomEvent("route-change", { | ||
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. 위에 ROUTE_CHANGE_EVENT_NAME을 상수로 선언해주셨으니 상수를 사용하시는 게 좋을 것 같습니다. |
||
detail: { | ||
nextUrl, | ||
}, | ||
}) | ||
); | ||
}; |
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.
지선님 추측대로, appendChild하면서 타이밍 이슈로(아직 왜 그런진 파악 중입니다) 실제로 아래 사진과 같이
contentWrap
하위에 순서가 바뀌어서 렌더링 됩니다.제일 쉬운 방법은, 의도적으로
contentWrap
하위에 2개의 div를 만들고 하나는 sidebar, 하나는 postContainer로 정의하여각각의 $target을, postNavBar, postEditPage 생성시에 다른 타겟으로 넘겨주면 문제가 없을 것 같습니다!
관련해서 주하님의 PR 일부를 공유드릴테니 참고해서 바꿔봐주세요! 아주 조금만 수정하면 해결 됩니다!
https://github.com/prgrms-fe-devcourse/FEDC4-5_Project_Notion_VanillaJS/pull/48/files#diff-3d74dddefb6e35fbffe3c76ec0712d5c416352d9449e2fcc8210a9dee57dff67R6-R22