-
Notifications
You must be signed in to change notification settings - Fork 28
[Mission4/김민재] - Project_Notion_VanillaJS #16
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_minjae
Are you sure you want to change the base?
Changes from 9 commits
a3791c7
dfcb1a5
b729d29
cc8da73
58880ea
e701bc6
ab4ada1
21e6fb6
5c66953
9b976db
d5ef51f
99ba91b
44b6780
95f7373
7b9451e
e686a19
df7f49e
eeb2c72
dca573d
2949465
c86403e
e79b992
d38ffa4
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,2 @@ | ||
| constants.js | ||
| storage.js | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <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" /> | ||
| <link rel="stylesheet" href="/style.css" /> | ||
| <title>Minjae의 Notion</title> | ||
| </head> | ||
| <body> | ||
| <main id="app" style="display: flex; flex-direction: row"></main> | ||
|
||
| <script src="/src/main.js" type="module"></script> | ||
| </body> | ||
| </html> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| import EditorContainer from "./components/editpage/EditorContainer.js"; | ||
| import Sidebar from "./components/sidebar/Sidebar.js"; | ||
| import { request } from "./api.js"; | ||
| import { initRouter, push } from "./router.js"; | ||
| import { validation } from "./validation.js"; | ||
| import LandingPage from "./components/editpage/LandingPage.js"; | ||
|
|
||
| export default function App({ $target }) { | ||
| validation(new.target, "App"); | ||
|
|
||
| this.state = { | ||
| documentsList: [], | ||
| editorDocument: { | ||
| docId: null, | ||
| doc: { | ||
| title: "", | ||
| content: "", | ||
| documents: [], | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| const sidebar = new Sidebar({ | ||
| $target, | ||
| initialState: this.state.documentsList, | ||
| }); | ||
|
|
||
| new LandingPage({ $target }); | ||
|
|
||
| const editContainer = new EditorContainer({ | ||
| $target, | ||
| initialState: this.state.editorDocument, | ||
| }); | ||
|
Comment on lines
+11
to
+33
Member
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. initial state 관리 방식 좋네요 👍
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. 감사합니다!🙇🏻♂️ 필요한 state들만 사용해보려고 노력해봤습니다. |
||
|
|
||
| this.setState = (nextState) => { | ||
| this.state = nextState; | ||
| editContainer.setState(this.state.editorDocument); | ||
| }; | ||
|
|
||
| this.init = async () => { | ||
| const docs = await request("/documents"); | ||
| this.setState({ | ||
| ...this.state, | ||
| documentsList: docs, | ||
| }); | ||
| await sidebar.setState(docs); | ||
| this.route(); | ||
| }; | ||
|
|
||
| this.route = () => { | ||
| const { pathname } = window.location; | ||
| const editArea = document.querySelector(".editContainer"); | ||
| const landingPage = document.querySelector(".landingPage"); | ||
|
|
||
| if (pathname === "/") { | ||
| editArea.style.display = "none"; | ||
| landingPage.style.display = "block"; | ||
| } else if (pathname.indexOf("/documents/") === 0) { | ||
| editArea.style.display = "block"; | ||
| landingPage.style.display = "none"; | ||
| const [, , docId] = pathname.split("/"); | ||
| //같은 문서 두번 클릭시 내용 삭제 방지 | ||
| if (this.state.editorDocument.docId === docId) { | ||
| return; | ||
| } | ||
| //뒤로가기 대비 방어코드 | ||
| if (docId === null) { | ||
| push("/"); | ||
| } | ||
| this.setState({ | ||
| ...this.state, | ||
| editorDocument: { docId }, | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| this.init(); | ||
|
|
||
| window.addEventListener("popstate", () => this.route()); | ||
|
|
||
| initRouter(() => this.route()); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { API_END_POINT } from "./constants.js"; | ||
| import { X_USERNAME } from "./constants.js"; | ||
|
||
|
|
||
| export const request = async (url, options = {}) => { | ||
| try { | ||
| const res = await fetch(`${API_END_POINT}${url}`, { | ||
| ...options, | ||
| headers: { | ||
| "x-username": X_USERNAME, | ||
| "Content-Type": "application/json", | ||
| }, | ||
| }); | ||
|
|
||
| if (res.ok) { | ||
| return await res.json(); | ||
| } | ||
|
|
||
| throw new Error("API 처리중 뭔가 이상합니다!"); | ||
| } catch (e) { | ||
| alert(e.message); | ||
| history.replaceState(null, null, "/"); | ||
| location.reload(); | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,59 @@ | ||||||||||||||||||||||||
| import { validation } from "../../validation.js"; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| export default function BreadCrumb({ $target, initialState, clickPath }) { | ||||||||||||||||||||||||
| validation(new.target, "BreadCrumb"); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const $breadCrumb = document.createElement("nav"); | ||||||||||||||||||||||||
| $breadCrumb.className = "linkContainer"; | ||||||||||||||||||||||||
| $target.appendChild($breadCrumb); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| this.state = initialState; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| this.setState = (nextState) => { | ||||||||||||||||||||||||
|
Contributor
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. state도 validation을 거치면 좋을 것 같습니다.
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. 전체적으로 validation 적용해보도록 하겠습니다. 감사합니다! |
||||||||||||||||||||||||
| this.state = nextState; | ||||||||||||||||||||||||
| this.render(); | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| this.renderBreadCrumb = (id) => { | ||||||||||||||||||||||||
| const path = []; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const recursiveBC = (id) => { | ||||||||||||||||||||||||
| const $curLi = document.getElementById(id); | ||||||||||||||||||||||||
| const $ul = $curLi.closest("ul"); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if ($ul.className === "child") { | ||||||||||||||||||||||||
| path.push([$curLi.querySelector("span").innerHTML, id]); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
| const $curLi = document.getElementById(id); | |
| const $ul = $curLi.closest("ul"); | |
| if ($ul.className === "child") { | |
| path.push([$curLi.querySelector("span").innerHTML, id]); | |
| const $curLi = document.getElementById(id); | |
| if ($curLi) { | |
| const $ul = $curLi.closest("ul"); | |
| if ($ul.className === "child") { | |
| path.push([$curLi.querySelector("span").innerHTML, id]); | |
$curLi가 존재하는가에 대한 방어코드를 넣는것도 좋을듯 합니다
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.
해당 컴포넌트에서는 방어코드가 없었는데 중요한 부분을 잘 잡아주신것 같습니다. 리뷰 반영해보도록 하겠습니다! 감사합니다👍
Outdated
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.
이 함수 정의 부분을 별도의 메서드로 분리해도 좋을 것 같네요.
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.
생각해보니 함수안에 정의할 필요가 없는 것 같습니다😓 함수를 밖으로 빼서 분리하는 리뷰 반영해보겠습니다. 감사합니다!
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.
👍
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,46 @@ | ||||||||
| import { validation } from "../../validation.js"; | ||||||||
|
|
||||||||
| export default function Editor({ $target, initialState, onEdit }) { | ||||||||
| validation(new.target, "Editor"); | ||||||||
| const $editor = document.createElement("div"); | ||||||||
| $editor.className = "editor"; | ||||||||
| $target.appendChild($editor); | ||||||||
|
|
||||||||
| $editor.innerHTML = ` | ||||||||
| <div style="display: flex; flex-direction: column"> | ||||||||
| <input type="text" placeholder="제목 없음" name="title" style="height:100px" /> | ||||||||
| <textarea name="content" placeholder="내용을 입력하세요" style="height:75vh;"></textarea> | ||||||||
| </div> | ||||||||
| `; | ||||||||
|
|
||||||||
| this.state = initialState; | ||||||||
|
|
||||||||
| this.setState = (nextState) => { | ||||||||
| this.state = nextState; | ||||||||
| this.render(); | ||||||||
| }; | ||||||||
|
|
||||||||
| this.render = () => { | ||||||||
| $editor.querySelector("[name=title]").value = this.state.title; | ||||||||
| $editor.querySelector("[name=content]").value = this.state.content; | ||||||||
| }; | ||||||||
|
|
||||||||
| this.render(); | ||||||||
|
|
||||||||
| $editor.addEventListener("keyup", (e) => { | ||||||||
| const targetTag = e.target.tagName; | ||||||||
| const { value } = e.target; | ||||||||
|
||||||||
| const targetTag = e.target.tagName; | |
| const { value } = e.target; | |
| const { tagName: targetTag, value } = e.target; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import { request } from "../../api.js"; | ||
| import BreadCrumb from "./BreadCrumb.js"; | ||
| import Editor from "./Editor.js"; | ||
| import { push } from "../../router.js"; | ||
| import SubLink from "./SubLink.js"; | ||
| import { validation } from "../../validation.js"; | ||
|
|
||
| //여기에선 docId만 핸들, Editor에는 Doc만 | ||
| export default function EditorContainer({ $target, initialState }) { | ||
| validation(new.target, "EditorContainer"); | ||
|
|
||
| const $editorContainer = document.createElement("section"); | ||
| $editorContainer.className = "editContainer"; | ||
|
|
||
| this.state = initialState; | ||
|
|
||
| let timer = null; | ||
|
|
||
| const breadCrumb = new BreadCrumb({ | ||
| $target: $editorContainer, | ||
| initialState: this.state, | ||
| clickPath: (id) => { | ||
| push(`/documents/${id}`); | ||
| }, | ||
| }); | ||
|
|
||
| const editor = new Editor({ | ||
| $target: $editorContainer, | ||
| initialState: this.state.doc, | ||
| onEdit: (post) => { | ||
| document.getElementById(post.id).getElementsByTagName("span")[0].innerText = post.title; | ||
|
|
||
| if (timer !== null) { | ||
| clearTimeout(timer); | ||
| } | ||
| timer = setTimeout(async () => { | ||
| await request(`/documents/${post.id}`, { | ||
| method: "PUT", | ||
| body: JSON.stringify(post), | ||
| }); | ||
| }, 500); | ||
| }, | ||
| }); | ||
|
|
||
| const subLink = new SubLink({ | ||
| $target: $editorContainer, | ||
| initialState: this.state, | ||
| clickLink: (id) => { | ||
| push(`/documents/${id}`); | ||
| }, | ||
| }); | ||
|
|
||
| this.setState = async (nextState) => { | ||
| if (this.state.docId !== nextState.docId) { | ||
| this.state = nextState; | ||
| await fetchDoc(); | ||
| return; | ||
| } | ||
| this.state = nextState; | ||
|
|
||
| breadCrumb.setState(this.state); | ||
| subLink.setState(this.state); | ||
| editor.setState(this.state.doc || { title: "", content: "" }); | ||
| this.render(); | ||
| }; | ||
|
|
||
| this.render = () => { | ||
| $target.appendChild($editorContainer); | ||
| }; | ||
|
|
||
| const fetchDoc = async () => { | ||
| const { docId } = this.state; | ||
| const doc = await request(`/documents/${docId}`); | ||
|
|
||
| this.setState({ | ||
| ...this.state, | ||
| doc, | ||
| }); | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import { validation } from "../../validation.js"; | ||
|
|
||
| export default function LandingPage({ $target }) { | ||
| validation(new.target, "LandingPage"); | ||
|
|
||
| const $landingPage = document.createElement("section"); | ||
| $landingPage.className = "landingPage"; | ||
| $target.appendChild($landingPage); | ||
|
|
||
| this.render = () => { | ||
| $landingPage.innerHTML = ` | ||
| <h1 class="landingTitle"> | ||
| Notion에 오신 것을 환영합니다. | ||
| </h1> | ||
| <p class="landingP">페이지를 추가하여 새 문서를 작성해보세요.</p> | ||
| `; | ||
| }; | ||
|
|
||
| this.render(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import { validation } from "../../validation.js"; | ||
| export default function SubLink({ $target, initialState, clickLink }) { | ||
| validation(new.target, "SubLink"); | ||
|
|
||
| const $subLink = document.createElement("footer"); | ||
| $subLink.className = "linkContainer"; | ||
| $target.appendChild($subLink); | ||
|
|
||
| this.state = initialState; | ||
|
|
||
| this.setState = (nextState) => { | ||
| this.state = nextState; | ||
| this.render(); | ||
| }; | ||
|
|
||
| this.render = () => { | ||
| const { doc } = this.state; | ||
|
|
||
| if (doc.documents.length > 0) { | ||
| $subLink.innerHTML = doc.documents | ||
| .map((el) => `<div class="link" data-id=${el.id}>${el.title}</div>`) | ||
| .join(""); | ||
| } else { | ||
| $subLink.innerHTML = ` | ||
| `; | ||
| } | ||
|
||
| }; | ||
|
|
||
| $subLink.addEventListener("click", (e) => { | ||
| const { id } = e.target.closest("div").dataset; | ||
| const $curLi = document.getElementById(id); | ||
| const $parentUl = $curLi.closest("ul"); | ||
|
|
||
| $parentUl.closest("li").querySelector(".toggleFold").innerText = "▼"; | ||
| $parentUl.style.display = "block"; | ||
| clickLink(id); | ||
| }); | ||
| } | ||
|
Comment on lines
+29
to
+36
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
이번 프로젝트에서 storage를 사용하지 않아서 ignore했다가 지우는걸 잊었습니다.😓 리팩토링 진행하며 storage를 사용해볼 생각입니다.