-
Notifications
You must be signed in to change notification settings - Fork 28
[Mission4/유지영] - Project_Notion_VanillaJS #8
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_jiyoung
Are you sure you want to change the base?
Changes from 17 commits
f691ecd
b7cbcda
11b73d7
13efc6b
c19551e
61cf50b
6198d4e
ed39799
4974646
6e5bd52
908560e
bbcbe3a
a7597c4
d041791
c196ba0
0a1721a
298f263
61f54e2
b71489c
1442d48
1b534d2
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 @@ | ||
| apiUrl.js |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| <!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="preconnect" href="https://fonts.googleapis.com" /> | ||
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | ||
| <link | ||
| href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300&display=swap" | ||
| rel="stylesheet" | ||
| /> | ||
| <script src="/src/main.js" type="module"></script> | ||
| <link rel="stylesheet" href="/style/style.css" /> | ||
| <title>노션 클로닝 프로젝트</title> | ||
| </head> | ||
| <body> | ||
| <main id="app"></main> | ||
| </body> | ||
| </html> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import PostEditPage from "./Page/editPage/PostEditPage.js"; | ||
| import StartPage from "./Page/startPage/StartPage.js"; | ||
| import PostPage from "./Page/listPage/PostPage.js"; | ||
| import { initRouter } from "./utils/router.js"; | ||
| import instanceCheck from "./utils/instanceCheck.js"; | ||
|
|
||
| export default function App({ $target }) { | ||
| instanceCheck(new.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 $postListContainer = document.createElement("div"); | ||
| const $postEditContainer = document.createElement("div"); | ||
|
|
||
| $target.appendChild($postListContainer); | ||
| $target.appendChild($postEditContainer); | ||
|
Comment on lines
+10
to
+14
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 postPage = new PostPage({ | ||
| $target: $postListContainer, | ||
| }); | ||
|
|
||
| const startPage = new StartPage({ | ||
| $target: $postEditContainer, | ||
| }); | ||
|
|
||
| const postEditPage = new PostEditPage({ | ||
| $target: $postEditContainer, | ||
| initialState: { | ||
| postId: "new", | ||
| posts: { | ||
| title: "", | ||
| content: "", | ||
| }, | ||
| }, | ||
| listUpdate: () => { | ||
|
||
| postPage.render(); | ||
| }, | ||
| }); | ||
|
|
||
| this.route = () => { | ||
| $postEditContainer.innerHTML = ""; | ||
| const { pathname } = window.location; | ||
| if (pathname.indexOf("/documents/") === 0) { | ||
| const [, , postId] = pathname.split("/"); | ||
| postEditPage.setState({ postId }); | ||
| } else { | ||
| postPage.render(); | ||
| startPage.render(); | ||
| } | ||
| }; | ||
|
|
||
| this.route(); | ||
|
|
||
| initRouter(() => this.route()); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import instanceCheck from "../../utils/instanceCheck.js"; | ||
|
|
||
| /**편집기를 그리는 컴포넌트 */ | ||
| export default function Editor({ | ||
| $target, | ||
| initialState = { | ||
| title: "", | ||
| content: "", | ||
| }, | ||
| onEditing, | ||
| }) { | ||
| instanceCheck(new.target); | ||
|
|
||
| const $editor = document.createElement("div"); | ||
| $editor.classList.add("editor"); | ||
| $target.appendChild($editor); | ||
|
|
||
| this.state = initialState; | ||
|
|
||
| this.setState = (nextState) => { | ||
| this.state = nextState; | ||
| $editor.querySelector("[name=title]").value = this.state.title; | ||
| $editor.querySelector("[name=content]").value = this.state.content; | ||
| }; | ||
|
|
||
| this.render = () => { | ||
| $editor.innerHTML = ` | ||
| <input type="text" name="title" class="title" value="${this.state.title}"/> | ||
| <textarea name="content" class="content" placeholder="내용을 입력하세요.">${this.state.content}</textarea> | ||
| `; | ||
| }; | ||
|
|
||
| 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,55 @@ | ||
| import { push } from "../../utils/router.js"; | ||
| import instanceCheck from "../../utils/instanceCheck.js"; | ||
|
|
||
| /**편집기 아래에 해당 document의 하위 documents를 그리는 컴포넌트 */ | ||
| export default function MarkUpList({ $target, initialState }) { | ||
| instanceCheck(new.target); | ||
|
|
||
| const $markUpList = document.createElement("div"); | ||
| $markUpList.classList.add("markUpList"); | ||
| $target.appendChild($markUpList); | ||
|
|
||
| this.state = initialState; | ||
|
|
||
| this.setState = (nextState) => { | ||
| this.state = [nextState]; | ||
| this.render(); | ||
| }; | ||
|
|
||
| const markUpList = (list, text) => { | ||
| text += ` | ||
| <ul> | ||
| ${list | ||
| .map( | ||
| ({ id, title, documents }) => ` | ||
| <div class='documentsTree'> | ||
| <li data-id="${id}"> | ||
| <img class="svg" src="../icon/chevron-right-solid.svg" /> | ||
| ${title} | ||
| </li> | ||
| ${documents.map((document) => markUpList([document], text)).join("")} | ||
| </div> | ||
| ` | ||
| ) | ||
| .join("")} | ||
| </ul> | ||
| `; | ||
|
||
|
|
||
| return text; | ||
| }; | ||
|
||
|
|
||
| this.render = () => { | ||
| const documentsList = markUpList(this.state, ""); | ||
| $markUpList.innerHTML = `<div class="list">${documentsList}</div>`; | ||
| }; | ||
|
|
||
| this.render(); | ||
|
|
||
| $markUpList.addEventListener("click", (e) => { | ||
| const $li = e.target.closest("li"); | ||
| const id = $li.dataset.id; | ||
| if ($li) { | ||
| push(`/documents/${id}`); | ||
| } | ||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| import { request } from "../../utils/api.js"; | ||
| import Editor from "./Editor.js"; | ||
| import MarkUpList from "./MarkUpList.js"; | ||
| import { getItem, removeItem, setItem } from "../../utils/storage.js"; | ||
| import instanceCheck from "../../utils/instanceCheck.js"; | ||
|
|
||
| /**편집기의 내용을 서버에 저장하고 불러오는 컴포넌트 */ | ||
| export default function PostEditPage({ $target, initialState, listUpdate }) { | ||
| instanceCheck(new.target); | ||
|
|
||
| const $page = document.createElement("div"); | ||
| $page.classList.add("editPage"); | ||
|
|
||
| this.state = initialState; | ||
|
|
||
| let postLocalSaveKey = `temp-post-${this.state.postId}`; | ||
|
|
||
| const editor = new Editor({ | ||
| $target: $page, | ||
| initialState: { | ||
| title: "", | ||
| content: "", | ||
| }, | ||
| onEditing: (post) => { | ||
| storageSave(post); | ||
| serverSave(post); | ||
| }, | ||
| }); | ||
|
|
||
| const markupList = new MarkUpList({ | ||
| $target: $page, | ||
| initialState: [], | ||
| }); | ||
|
|
||
| let storageTimer = null; | ||
| let serveTimer = null; | ||
|
||
|
|
||
| const storageSave = (post) => { | ||
| if (storageTimer !== null) { | ||
| clearTimeout(storageTimer); | ||
| } | ||
|
||
|
|
||
| storageTimer = setTimeout(() => { | ||
| setItem(postLocalSaveKey, { | ||
| ...post, | ||
| tempSaveDate: new Date(), | ||
| }); | ||
| }, 500); | ||
| }; | ||
|
|
||
| const serverSave = (post) => { | ||
| if (serveTimer !== null) { | ||
| clearTimeout(serveTimer); | ||
| } | ||
|
|
||
| serveTimer = setTimeout(async () => { | ||
| await request(`/documents/${post.id}`, { | ||
| method: "PUT", | ||
| body: JSON.stringify(post), | ||
| }); | ||
|
|
||
| removeItem(postLocalSaveKey); | ||
| listUpdate(); | ||
|
|
||
| const data = await request(`/documents/${post.id}`); | ||
| markupList.setState(data); | ||
|
||
| }, 1000); | ||
| }; | ||
|
|
||
| this.setState = async (nextState) => { | ||
| if (this.state.postId !== nextState.postId) { | ||
| postLocalSaveKey = `temp-post-${nextState.postId}`; | ||
| this.state = nextState; | ||
| await fetchPost(); | ||
| return; | ||
| } | ||
| this.state = nextState; | ||
|
|
||
| this.render(); | ||
|
|
||
| if (this.state.post) { | ||
| markupList.setState(this.state.post); | ||
| editor.setState(this.state.post); | ||
| } | ||
| }; | ||
|
|
||
| const fetchPost = async () => { | ||
| const { postId } = this.state; | ||
| const post = await request(`/documents/${postId}`); | ||
| const tempPost = getItem(postLocalSaveKey, { | ||
| title: "", | ||
| content: "", | ||
| }); | ||
|
|
||
| if (tempPost.tempSaveDate && tempPost.tempSaveDate > post.updatedAt) { | ||
| if (confirm("저장되지 않은 임시 데이터가 있습니다. 불러오시겠습니까?")) { | ||
|
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. confirm의 결과값이 true false로 나오기때문에 윗 조건식에 &&이나 ||르 추가하셔도 될것 같습니다! |
||
| this.setState({ | ||
| ...this.state, | ||
| post: tempPost, | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| this.setState({ | ||
| ...this.state, | ||
| post, | ||
| }); | ||
| }; | ||
|
|
||
| this.render = () => { | ||
| $target.appendChild($page); | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { push } from "../../utils/router.js"; | ||
| import instanceCheck from "../../utils/instanceCheck.js"; | ||
|
|
||
| /**user와 email을 받아서 헤더로 그리는 컴포넌트 */ | ||
| export default function Header({ $target, initialState }) { | ||
| instanceCheck(new.target); | ||
|
|
||
| const $header = document.createElement("div"); | ||
| $header.classList.add("header"); | ||
| $target.appendChild($header); | ||
|
|
||
| this.state = initialState; | ||
|
|
||
| this.render = () => { | ||
| const { user, email } = this.state; | ||
| $header.innerHTML = ` | ||
| <img class="logo" src="../icon/notion-logo.svg" /> | ||
| <div class="user"> | ||
| <h3> ${user}의 notion</h3> | ||
| <h5>${email}</h5> | ||
| </div> | ||
| `; | ||
| }; | ||
|
|
||
| this.render(); | ||
|
|
||
| $header.addEventListener("click", (e) => { | ||
| const $user = e.target.closest(".header"); | ||
|
||
|
|
||
| if ($user) { | ||
| push("/"); | ||
| } | ||
| }); | ||
| } | ||
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.
사실 지금 그렇게 신경쓸 필요는 없지만!
lang과 mata tag들을 적절하게 바꿔는것도 좋다고 하네요~!!, 저도 (mata쪽을 변경해야겠어요)
(SEO 위해서..!)