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

[Mission4/최별] - Project_Notion_VanillaJS #34

Open
wants to merge 9 commits into
base: 3/#4_choibyeol_working
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
10 changes: 10 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<html>
<head>
<title>Star notion</title>
<link rel="stylesheet" href="src/style/style.css" />
Copy link
Member

Choose a reason for hiding this comment

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

특정 document를 클릭하고 새로고침을 하면 css가 날라가서 찾아보니! css도 스크립트처럼 상대경로가 아닌 절대경로로 지정해주니 해결되는 것 같네요! 😊
<link rel="stylesheet" href="/src/style/style.css" />

Copy link
Author

Choose a reason for hiding this comment

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

와 그렇구나 ㅠㅠㅠ 절대 경로로 지정해주는 습관 들여야겠어요...

</head>
<body>
<main id="app"></main>
<script src="/src/main.js" type="module"></script>
</body>
</html>
42 changes: 42 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Sidebar from "./components/sidebar/Sidebar.js";
import PostEditPage from "./components/posts/PostEditPage.js";
import { initRouter, push } from "./utils/router.js";

export default function App({ $target }) {
const $sidebar = document.createElement("div");
const $postEditPage = document.createElement("div");

$target.appendChild($sidebar);
$target.appendChild($postEditPage);

Comment on lines +6 to +11

Choose a reason for hiding this comment

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

중복 로직은 묶에서 해결하는 것도 좋을것 같아요~!

const sidebar = new Sidebar({ $target: $sidebar });

const postEditPage = new PostEditPage({
$target: $postEditPage,
initialState: {
postId: "new",
post: {
title: "",
content: "",
},
},
});

// const fetchNewPost = async (postId) => {
// push(`/documents/${postId}`);
// };
Comment on lines +25 to +27
Copy link

Choose a reason for hiding this comment

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

주석은 삭제하는 습관을 들이는게 좋겠어요! git이 관리해주니까요:-)


this.route = () => {
const { pathname } = window.location;
if (pathname.indexOf("/documents/") === 0) {
const [, , postId] = pathname.split("/");
postEditPage.setState({ postId });
} else {
sidebar.setState();
}
};

this.route();

initRouter(() => this.route());
}
44 changes: 44 additions & 0 deletions src/components/posts/Editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
export default function Editor({
$target,
initialState = {
title: "",
content: "",
},
onEditing,
}) {
const $editor = document.createElement("div");
$editor.className = "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]").innerHTML = this.state.content;
Copy link
Member

Choose a reason for hiding this comment

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

페이지의 내용을 수정하고 다른 list를 누르면 content 내용이 해당 list의 content가 아니라 수정했던 이전 list의 content가 계속 떠 있더라구요! 아마 이 부분이 value가 아니라 innerHTML로 되어있어서 그런 것 같습니다 😊

Copy link
Author

Choose a reason for hiding this comment

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

헉 지영님 고마워요...!!! 역시 똑똑이 지영님🥺

};

this.render = () => {
$editor.innerHTML = `
<input class="editor-title" type="text" name="title" value="${this.state.title}" />
<textarea class="editor-content" name="content">${this.state.content}</textarea>
`;
};

this.render();

$editor.addEventListener("keyup", (e) => {

Choose a reason for hiding this comment

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

e를 받아올때 한번에 {target}으로 분리해주는것도 좋을것 같아요~

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);
}
});
}
122 changes: 122 additions & 0 deletions src/components/posts/PostEditPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { request } from "../../utils/api.js";
import { getItem, removeItem, setItem } from "../../utils/storage.js";
import Editor from "./Editor.js";

export default function PostEditPage({ $target, initialState }) {
const $postEditPage = document.createElement("div");
$postEditPage.className = "post-edit-page";

this.state = initialState;

let postLocalSaveKey = `temp-post-${this.state.postId}`;

const post = getItem(postLocalSaveKey, {
Comment on lines +5 to +13

Choose a reason for hiding this comment

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

전반적으로 new 방어코드 처리를 추가해주시면 좋을 것 같습니다 :)

title: "",
content: "",
});

let timer = null;

const editor = new Editor({
$target: $postEditPage,
initialState: post,

onEditing: (post) => {
/* 연속으로 입력을 하고 있을 때는 계속 이벤트 발생을 지연시키다가
입력을 멈췄을 때, 즉, 마지막으로 이벤트가 발생하고 일정 시간이 지났을 때
지연시켰던 이벤트를 실행시키는 것 - 디바운스
디바운스를 이용하면 이벤트 발생하는 횟수를 줄일 수 있다. -> 성능, 최적화
*/
Comment on lines +25 to +29
Copy link

Choose a reason for hiding this comment

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

이러한 주석은 readme에 작성하면 좋을 것 같습니다ㅎㅎ

if (timer !== null) {
clearTimeout(timer);
}
timer = setTimeout(async () => {
setItem(postLocalSaveKey, {
...post,
tempSaveDate: new Date(),
});

const isNew = this.state.postId === "new";
if (isNew) {
const createdPost = await request("/documents", {
method: "POST",
body: JSON.stringify(post),
});
history.replaceState(null, null, `/documents/${createdPost.id}`);
removeItem(postLocalSaveKey);

this.setState({
postId: createdPost.id,
});
Comment on lines +40 to +50
Copy link
Member

Choose a reason for hiding this comment

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

새 페이지를 추가하거나 하위문서가 추가되면 바로 postId로 주소를 넘겨서 postId가 new일 때의 로직은 사용되지 않아도 될 것 같아요! 😊

} else {
await request(`/documents/${post.id}`, {
method: "PUT",
body: JSON.stringify(post),
});
removeItem(postLocalSaveKey);
}
}, 1000);
},
});

this.setState = async (nextState) => {
if (this.state.postId !== nextState.postId) {
postLocalSaveKey = `temp-post-${nextState.postId}`;
this.state = nextState;

if (this.state.postId === "new") {
const post = getItem(postLocalSaveKey, {
title: "",
content: "",
});
this.render();
editor.setState(post);
} else {
await fetchPost();
}
return;
}

this.state = nextState;
this.render();

editor.setState(
this.state.post || {
title: "",
content: "",
}
);
Comment on lines +83 to +88
Copy link
Member

Choose a reason for hiding this comment

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

동일한 문서를 재클릭했을 때 this.state.post 값이 undefined여서 기존의 제목과 내용이 뜨지 않는 것 같아요!

if(this.state.post){
editor.setState(this.state.post)
}

이런 식으로 undefined일 땐 아예 setState가 일어나지 않도록 하는 것도 방법일 것 같아요! 👍

};

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

const fetchPost = async () => {
const { postId } = this.state;

if (postId !== "new") {
const post = await request(`/documents/${[postId]}`);
Copy link
Member

@LucyYoo LucyYoo Nov 18, 2022

Choose a reason for hiding this comment

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

postId가 배열로 들어가는 이유가 따로 있을까용?? 😊

Copy link
Author

Choose a reason for hiding this comment

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

앗 제가 강의 보면서 잘못 친거 같네요...


const tempPost = getItem(postLocalSaveKey, {
title: "",
content: "",
});

if (tempPost.tempSaveDate && tempPost.tempSaveDate > post.updated_at) {
if (confirm("저장되지 않은 임시 데이터가 있습니다. 불러올까요?")) {
this.setState({
...this.state,
post: tempPost,
});
return;
}
}

this.setState({
...this.state,
post,
});
}
};
}
39 changes: 39 additions & 0 deletions src/components/sidebar/sidebar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import SidebarHeader from "./SidebarHeader.js";
import SidebarBody from "./SidebarBody.js";
import SidebarFooter from "./SidebarFooter.js";
import { request } from "../../utils/api.js";

export default function Sidebar({ $target }) {
const $sidebar = document.createElement("div");
$sidebar.className = "sidebar";
const $sidebarHeader = document.createElement("div");
const $sidebarBody = document.createElement("div");
const $sidebarFooter = document.createElement("div");

const sidebarBody = new SidebarBody({
$target: $sidebarBody,
});

this.setState = () => {
sidebarBody.setState();
};

new SidebarHeader({
$target: $sidebarHeader,
setState: this.setState(),
});

new SidebarFooter({
$target: $sidebarFooter,
setState: this.setState(),
Comment on lines +23 to +28
Copy link

Choose a reason for hiding this comment

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

함수를 props로 전달할 때에는 호출하지 않고 넘겨주셔야 더 안전할 것 같아요! this.setState만 넘겨주고 하위 컴포넌트에서 실행이 필요한 경우에만 setState() 로 실행하는 게 맞아 보입니다.

그리고 부모 컴포넌트의 setState를 자식 컴포넌트에서 실행하게 되면 나중에 상태관리가 꼬이게 되니까 다른 방법을 고민해봐야 할 것 같아요. 강의에서처럼 onClick과 같은 트리거 함수를 정의해서 자식에게 전달하고, 그 트리거 함수를 실행하면 부모 컴포넌트에서 setState를 실행하는 게 더 좋지 않을까 의견드려봅니다!

Choose a reason for hiding this comment

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

제 생각에도 append 목적으로는 각 컴포넌트에서 해주는게 좋을것 같고
상위 setState를 통해 하위 state를 관리하는 방향이 더 좋을것 같습니다~
만약 상위의 상태값을 하위에서 바꿔야하는 로직(이벤트)이 있다면 해당 setState를 가져오는게 아닌 해당 로직을 호출하면서 내부에서 변경하는게 좋을것 같습니다~

//ex
new SidebarFooter ({
onClick : () => {
const nextState = 변경로직
this.setState(nextStatee)
}
})

});

this.render = () => {
$target.appendChild($sidebar);
$sidebar.appendChild($sidebarHeader);
$sidebar.appendChild($sidebarBody);
$sidebar.appendChild($sidebarFooter);
};

Comment on lines +31 to +37
Copy link

Choose a reason for hiding this comment

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

현재 sidebar를 컨테이너로 하신 것 같은데, 그러면 하위의 sidebarHeader, sidebarBody, sidebarFooter 에 타겟으로 sidebar 컨테이너를 넘겨주기만 해도 이러한 render 함수는 생략할 수 있지 않을까요?

new를 하는 시점에서 sidebar 컨테이너에 append가 되고, 어차피 렌더링은 innerHTML로 처리하니까요! 혹시 이후에 render 함수를 쓰는 일이 있나 봤는데 없는 것 같아서 말씀드려봅니당

this.render();
}
87 changes: 87 additions & 0 deletions src/components/sidebar/sidebarBody.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { request } from "../../utils/api.js";
import { push } from "../../utils/router.js";

export default function SidebarBody({ $target }) {
const $sidebarBody = document.createElement("div");
const $renderList = document.createElement("div");
$sidebarBody.className = "sidebar-body";
$target.appendChild($sidebarBody);

this.setState = async () => {
this.state = await request("/documents", {
method: "GET",
});
$renderList.innerHTML = "";
this.render();
Comment on lines +14 to +15

Choose a reason for hiding this comment

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

이 부분 로직은 계속 중복 되는것 같습니다!! State 랜더를 담당하는 부분까지 역할이 과중 된것 같습니다

};

const addDocumnet = async (dataId) => {
const newDocument = await request("/documents", {
method: "POST",
body: JSON.stringify({
title: "제목",
parent: dataId,
}),
});
$renderList.innerHTML = "";
this.setState();
push(`/documents/${newDocument.id}`);
};

const deleteDocument = async (dataId) => {
await request(`/documents/${dataId}`, {
method: "DELETE",
});
$renderList.innerHTML = "";
push("/");
};

const renderDocuments = (documents, $renderList) => {
documents.map((e) => {
const $ul = document.createElement("ul");
const $li = document.createElement("li");
$li.className = "document-li";
$li.setAttribute("data-id", e.id);
$li.textContent = e.title;
const $addBtn = document.createElement("button");
$addBtn.className = "add-btn";
$addBtn.innerHTML = "+";
const $deleteBtn = document.createElement("button");
$deleteBtn.className = "delete-btn";
$deleteBtn.innerHTML = "x";
Comment on lines +48 to +51
Copy link

Choose a reason for hiding this comment

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

이부분도 textContent로 처리할 수 있었을 것 같네요ㅎㅎ


$renderList.appendChild($ul);
$ul.appendChild($li);
$li.appendChild($addBtn);
$li.appendChild($deleteBtn);

if (e.documents) {
renderDocuments(e.documents, $ul);
}
});

return $renderList.innerHTML;
};

this.render = () => {
if (this.state) {
$sidebarBody.innerHTML = renderDocuments(this.state, $renderList);
} else {
$sidebarBody.innerHTML = "새 페이지를 눌러 문서를 작성해 주세요!";
}
};

this.render();

$sidebarBody.addEventListener("click", (e) => {
const target = e.target;
const dataId = target.closest("li").dataset.id;
Copy link
Member

Choose a reason for hiding this comment

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

li의 padding 부분이 클릭 됐을 때

image

이런 에러가 뜨더라구요! dataset이 없을 때 에러가 안 뜨도록 하기 위해서

const dataId = target.closest("li")?.dataset.id;

이런 식으로 하는 것도 좋을 것 같아요 😊

Copy link
Author

Choose a reason for hiding this comment

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

이부분도 고민 중이었는데 일단 실행은 되니까 넘어갔었어요 핳ㅎㅎ.. 좋은 방법 감사합니다!!

if (target.className === "add-btn") {
addDocumnet(dataId);
} else if (target.className === "delete-btn") {
deleteDocument(dataId);
} else if (dataId) {
push(`/documents/${dataId}`);
}
});
}
33 changes: 33 additions & 0 deletions src/components/sidebar/sidebarFooter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { request } from "../../utils/api.js";
import { push } from "../../utils/router.js";

export default function SidebarFooter({ $target, setState }) {
const $sidebarFooter = document.createElement("div");
$sidebarFooter.className = "sidebar-footer";
$target.appendChild($sidebarFooter);

this.render = () => {
$sidebarFooter.innerHTML = `
<div>
+ 새 페이지
</div>
`;
};

this.render();

const addNewDocument = async () => {
const newDocument = await request("/documents", {
method: "POST",
body: JSON.stringify({
title: "제목",
parent: null,
}),
});
setState;
push("/");
push(`/documents/${newDocument.id}`);
};

$sidebarFooter.addEventListener("click", addNewDocument);
}
Loading