Skip to content
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 과제 #54

Open
wants to merge 6 commits into
base: 4/#5_kimdaeun
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/style.css">
<title>Document</title>
</head>
<body>
<div class="app"></div>
<script type="module" src="/main.js"></script>
</body>
</html>
5 changes: 5 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import App from "./src/App.js"

const $target = document.querySelector(".app")

new App({$target})
36 changes: 36 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import DocumentPage from "./pages/DocumentPage.js";
import EditorPage from "./pages/EditorPage.js";
import { initRouter } from "./utils/router.js";

export default function App({$target}){
const documentPage = new DocumentPage({
$target,
})

const editorpage = new EditorPage({
$target,
initialState :{}
})

this.route = () =>{
const {pathname} = window.location
$target.innerHTML = ""
if(pathname === "/"){
documentPage.render()
}else if(pathname.indexOf("/documents/") === 0){
const [,,documentId] = pathname.split("/")
documentPage.render()
editorpage.setState({documentId})
}else{
$target.innerHTML = ""
documentPage.render()
}
Comment on lines +17 to +27
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$target.innerHTML = ""
if(pathname === "/"){
documentPage.render()
}else if(pathname.indexOf("/documents/") === 0){
const [,,documentId] = pathname.split("/")
documentPage.render()
editorpage.setState({documentId})
}else{
$target.innerHTML = ""
documentPage.render()
}
$target.innerHTML = ""
documentPage.render()
if(pathname.indexOf("/documents/") === 0){
const [,,documentId] = pathname.split("/")
editorpage.setState({documentId})
}

공통된 부분이 많아 이렇게 줄여볼 수 있겠네요~

}
documentPage.render()
this.route()

initRouter(()=>{
this.route()
})

}
24 changes: 24 additions & 0 deletions src/components/CreateLists.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {getItem, OPENED_DOCUMENTS} from "../utils/storage.js"

export const createList = (currentList) => {
const openedDocuments = getItem(OPENED_DOCUMENTS,[])
return currentList.map(({title,documents,id})=>{
const isOpen = openedDocuments.includes(String(id));
return`<ul data-id="${id}">
<div class="document__title">
<span>
<button class="toggle__button">></button>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<button class="toggle__button">></button>
<button class="toggle__button">&gt;</button>

HTML에서는 마크업 문제가 있을 수 있으니 &gt; &lt; 를 이용하길 권장합니다.

${title === "" ? "제목없음" : title}
</span>
<div>
<button class="create">+</button>
<button class="remove">x</button>
</div>
</div>
<div class="${isOpen ? "" : "hide"}">
<li>${documents.length > 0 ? createList(documents) : ""}</li>
</div>
</ul>`
}
).join("")
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DocumentLists 컴포넌트의 렌더링 로직에서 그려야 할 HTML 템플릿을 따로 함수로 구현해주신 점 좋네요!
다만 파일이 src/components 디렉토리에 존재하고 있어서 이것이 컴포넌트라는 생각이 들 수도 있을 것 같아요!

사실 컴포넌트라기보다는 들어온 인자를 가지고 가공해서 문자열을 반환하는 순수 함수의 형태인 것 같아서
이 함수를 DocumentLists.js 파일 내부에 존재하도록 하거나 혹은 template/createList.js 같은 느낌으로 이사하는건 어떨까요?

45 changes: 45 additions & 0 deletions src/components/DocumentLists.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { push } from "../utils/router.js"
import { updateStorage } from "../utils/storage.js"
import {createList} from "./CreateLists.js"

export default function DocumentLists({$target, initialState, onRemove, onCreate}){

const $documentLists = document.createElement("div")
$target.appendChild($documentLists)
$documentLists.className = "document__lists"

this.state =initialState
this.setState = nextState =>{
this.state = nextState
this.render()
}

this.render = () =>{
$documentLists.innerHTML = `${createList(this.state)}`
}

$documentLists.addEventListener("click", (e)=>{
const {target} = e
const $ul = target.closest("ul")

if($ul){
const {id} = $ul.dataset
if(target.className === "remove"){
onRemove(id)
updateStorage.delete(id)
}else if(target.className === "create"){
onCreate(id)
updateStorage.add(id)
}else if(target.className === "toggle__button"){
updateStorage.toggle(id)
this.render()
}else{
push(`/documents/${id}`)
}
}

})


this.render()
}
41 changes: 41 additions & 0 deletions src/components/Editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export default function Editor({$target, initialState, onEditing}){
const $editor = document.createElement("div")
$target.appendChild($editor)

this.state = initialState;
this.setState = nextState =>{
this.state = nextState
$editor.querySelector(".title").value = this.state.title
$editor.querySelector(".content").value = this.state.content
this.render()
}

let init = false
this.render = () =>{
if(!init){
$editor.innerHTML=`
<input class="title" style="width:800px;height:70px;" value="${this.state.title}">
<textarea class="content" style="width:800px;height:500px;">${this.state.content}</textarea>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳이 인라인 스타일을 쓰지 않아도 될 것 같습니다.

`
}
init =true

}

$editor.addEventListener("keyup", (e)=>{
const {target} = e
const name = target.getAttribute("class")
const isTitle = name === "title"

if(this.state[name] !== undefined){
const nextState = {
...this.state,
[name] : target.value
}
this.setState(nextState)
onEditing(this.state,isTitle)
}
})

this.render()
}
10 changes: 10 additions & 0 deletions src/components/Footer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default function Footer({$target, onClick}){
const $button = document.createElement("button")
$target.appendChild($button)
$button.textContent = "페이지 추가"
$button.className = "footer"

document.querySelector(".footer").addEventListener("click", (e)=>{
onClick()
})
}
Comment on lines +1 to +10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Header와 같은 의미로 <footer> 태그를 이용하시는 편이 위치를 이해하기 좋을 것 같다는 생각이 듭니다.☺️

5 changes: 5 additions & 0 deletions src/components/Header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default function Header({$target}){
const $header = document.createElement("h2")
$header.innerHTML = "김다은의 Notion"
$target.appendChild($header)
}
Comment on lines +1 to +5
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

처음에 Header 컴포넌트를 보고 <header>태그를 사용하신 컴포넌트로 이해했습니다.
위치는 <header> 태그를 이용했을때와 같겠지만 태그를 이용하는 편이 이해하기 좀 더 수월할 수도 있지 않을까요? ☺️

54 changes: 54 additions & 0 deletions src/pages/DocumentPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import DocumentLists from "../components/DocumentLists.js"
import Footer from "../components/Footer.js"
import Header from "../components/Header.js"
import { deleteDocument, postDocument, fetchDocumentLists } from "../utils/requset.js"
import { updateDocumentTitle, push } from "../utils/router.js"


export default function DocumentPage({$target}){
const $page = document.createElement("div")
$target.appendChild($page)
$page.className = "document__page"

this.render = ()=>{
$target.appendChild($page)
}

new Header({
$target : $page
})

const documentLists = new DocumentLists({
$target : $page,
initialState :[],
onRemove: async(id)=>{
await deleteDocument(id)
push("/")
updateDocumentList()
},
onCreate : async(id)=>{
const newDocument = await postDocument(id)
push(`/documents/${newDocument.id}`)
updateDocumentList()
},
})

new Footer({
$target : $page,
onClick : async()=>{
const newDocument = await postDocument()
push(`/documents/${newDocument.id}`)
updateDocumentList()
}
})

const updateDocumentList = async()=>{
documentLists.setState(await fetchDocumentLists())
}

updateDocumentTitle(()=>{
updateDocumentList()
})
updateDocumentList()
}

40 changes: 40 additions & 0 deletions src/pages/EditorPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Editor from "../components/Editor.js"
import {putDocument ,fetchEditor} from "../utils/requset.js"
import { push, update} from "../utils/router.js"

export default function EditorPage({$target, initialState}){
const $page = document.createElement("div")
$page.className = "editor__page"

this.render = ()=>{
$target.appendChild($page)
}

this.state = initialState
this.setState = async nextState =>{
this.state = nextState
const document = await fetchEditor(this.state.documentId)
editor.setState(document)
this.render()
}

let timer = null;
const editor = new Editor({
$target : $page,
initialState :{
title : "",
content :""},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initialState가 인자로 특별히 필요한지 잘 모르겠습니다.

onEditing : (document,isTitle)=>{
if(timer !== null){
clearTimeout(timer)
}
timer = setTimeout(async ()=>{
await putDocument(document)
if(isTitle){
update()
}
}, 800)
}
})

}
22 changes: 22 additions & 0 deletions src/utils/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {push} from "./router.js"

export const API_END_POINT = "https://kdt-frontend.programmers.co.kr"

export const request = async (url, options = {}) => {
try{
const res = await fetch(`${API_END_POINT}${url}`,{
...options,
headers :{
"Content-Type" : "application/json", //데이터 형식을 json으로 명시합니다.
"x-username" : "kimdaeun"
}
})
if(res.ok){
return res.json()
}
throw new Error("API 호출 오류")
}catch(e){
console.log(e.message)
push("/")
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서버에 요청을 보내는 일반화된 함수 request 는 api.js 에 존재하고,
실제로 구체화된 요청을 보내는 함수는 request.js 에 존재하는 형태인 것 같습니다!

request 함수는 request.js 에 작성하고, 구체화된 요청을 하는 함수는 api.js 에 작성하는 형태가 더 좋지 않을지 궁금합니다!

55 changes: 55 additions & 0 deletions src/utils/requset.js
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

파일 이름이 requset.js 로 오타가 있었던 것 같습니다!

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { request } from "./api.js"

//document를 삭제합니다
export const deleteDocument = async(id)=>{
await request(`/documents/${id}`, {
method : "DELETE"
})

}

//하위document 또는 상위document를 생성합니다
export const postDocument = async (id)=>{
if(id){
return await request("/documents", {
method : "POST",
body : JSON.stringify({
title : "제목 없음",
parent : `${id}`
})
})
}else{
return await request("/documents", {
method : "POST",
body : JSON.stringify({
title : "",
parent : ""
})
})
}
}

//document를 수정합니다.
export const putDocument = async(document)=>{
return await request(`/documents/${document.id}`, {
method : "PUT",
body : JSON.stringify({
title : document.title,
content : document.content
})
})
}

//특정 document를 불러옵니다.
export const fetchEditor = async(documentId)=>{
const document = await request(`/documents/${documentId}`)
return document
}


//전체 document를 불러옵니다.
export const fetchDocumentLists = async() =>{
const lists = await request("/documents")
return lists
}

Loading