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 과제 #48

Open
wants to merge 18 commits into
base: 4/#5_kimjuha
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
108 changes: 108 additions & 0 deletions index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
html,
body {
width: 100vw;
height: 100vh;
}

header {
text-align: center;
margin: 7%;
padding: 2%;
background-color: #eef3ed;
cursor: pointer;
}

ul {
padding-left: 20px;
}
li {
font-weight: 600;
margin: 15px;
list-style-type: none;
margin-bottom: 7%;
}

.postContainer {
width: 700px;
height: 92%;
margin: 2% 0;
background-color: #eef3ed;
padding: 2%;
border: 3px dashed #a48bbe;
border-radius: 10px;
}
.editor {
display: flex;
flex-direction: column;
height: 100%;
}

.title {
height: 70px;
font-size: x-large;
margin-bottom: 1%;
}

.content {
height: 100%;
}

main {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
margin: 0 auto;
justify-content: center;
}

div {
box-sizing: border-box;
}

.sidebarContainer {
width: 400px;
height: 92%;
margin: 2% 0;
background-color: #faf3dd;
border: 3px dashed #a48bbe;
border-radius: 10px;
}

.addDocumentBtn {
background-color: #faf3dd;
border: none;
width: 100%;
height: 3%;
margin-top: 2%;
font-size: medium;
cursor: pointer;
}
.addDocumentBtn:hover {
background-color: #c29243;
}

.documentTitle {
margin: 0 5%;
padding: 2% 5%;
border-radius: 10px;
cursor: pointer;
}
.documentTitle:hover {
background-color: #c29243;
user-select: none;
}

.toggleBtn {
font-size: medium;
background-color: #faf3dd;
border: none;
cursor: pointer;
}
.add,
.delete {
cursor: pointer;
margin-left: 10px;
font-size: medium;
border: none;
}
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>JUHA's Notion Cloning</title>
<link rel="stylesheet" href="index.css?v" />
</head>
<body>
<main id="app"></main>
<script src="/src/main.js" type="module"></script>
</body>
</html>
39 changes: 39 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import PostEditPage from "./page/PostEditPage.js";
import SidebarPage from "./page/SidebarPage.js";
import { initRouter } from "./utils/router.js";

export default function App({ $target, username }) {
const $sidebarContainer = document.createElement("div");
const $postContainer = document.createElement("div");
$sidebarContainer.className = "sidebarContainer";
$postContainer.className = "postContainer";

$target.appendChild($sidebarContainer);
$target.appendChild($postContainer);

const sidebarPage = new SidebarPage({ $target: $sidebarContainer, username });

const postEditPage = new PostEditPage({
$target: $postContainer,
initialState: {
id: "root",
},
username,
});

this.route = () => {
const { pathname } = window.location;
sidebarPage.setState();

if (pathname === "/") {
postEditPage.setState({ id: "root" });
} else {
const [, , id] = pathname.split("/");
postEditPage.setState({ id: id });
}
};

this.route();

initRouter(() => this.route());
}
6 changes: 6 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import App from "./App.js";

const username = "Haya";
const $app = document.querySelector("#app");

new App({ $target: $app, username });
73 changes: 73 additions & 0 deletions src/page/PostEditPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Editor from "./editor/Editor.js";
import { setItem, removeItem } from "../utils/storage.js";
import { getApi, postApi, putApi } from "../utils/api.js";
import { makeNewPost } from "../utils/btnCustomEvent.js";

export default function PostEditPage({ $target, initialState, username }) {
this.state = initialState;

let postLocalSaveKey = `temp-document-${this.state.id}`;

let timer = null;

const editor = new Editor({
$target,
initialState: this.state.post,
onEditing: (post) => {
if (timer !== null) {
clearTimeout(timer);
}
timer = setTimeout(async () => {
setItem(postLocalSaveKey, {
...post,
saveDate: new Date(),
});
const { id, title, content } = post;
await putApi(username, id, title, content);
removeItem(postLocalSaveKey);
}, 200);
},
});

this.setState = (nextState) => {
const { id } = nextState;
if (nextState.id === "root") {
rootState();
} else if (this.state.id !== id) {
documentChangeState(id);
} else {
this.state = {
...this.state,
...nextState,
};
}
};

const rootState = () => {
this.state = { id: "root" };
editor.setState({
title: `${username}의 노션 클로닝 페이지`,
content: `${username}의 노션에 오신 것을 환영합니다!\n\n당연히 노션 작성법은 알고 계시겠죠??\n\nmarkdown 규정에 맞춰 작성해주세요!`,
});
};

const documentChangeState = async (id) => {
postLocalSaveKey = `temp-post-${id}`;
const post = await getApi(username, id);
this.state = {
id: id,
post,
};
editor.setState(this.state.post);
};

const newPost = async (id) => {
const createdPost = await postApi(username, id);
history.replaceState(null, null, `/documents/${createdPost.id}`); // url을 new에서 생성된 id로 바꾸기
removeItem(postLocalSaveKey); // local storage에서 temp-document-new 지우기
this.setState({
id: createdPost.id,
});
};
makeNewPost((id) => newPost(id));
}
18 changes: 18 additions & 0 deletions src/page/SidebarPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import DocumentList from "./sidebar/DocumentList.js";
import { getApi } from "../utils/api.js";

export default function SidebarPage({ $target, username }) {
const $page = document.createElement("div");
$target.appendChild($page);

const documentList = new DocumentList({
$target: $page,
initialState: [],
username,
});

this.setState = async () => {
const document = await getApi(username);
documentList.setState(document);
};
}
52 changes: 52 additions & 0 deletions src/page/editor/Editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
export default function Editor({
$target,
initialState = {
id: "",
title: "",
content: "",
createdAt: "",
updatedAt: "",
documents: [],
},
onEditing,
}) {
const $editor = document.createElement("div");
$editor.className = "editor";
$target.appendChild($editor);

let isInitialize = false;
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();
};

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

this.render();

$editor.addEventListener("keyup", (e) => {
const name = e.target.getAttribute("name");

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

this.setState(nextState);
onEditing(this.state);
}
});
}
45 changes: 45 additions & 0 deletions src/page/sidebar/DocumentList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { DocumentListRenderer } from "./DocumentListRenderer.js";
import { onClickBtn, onClickDocument, onClickHeader } from "./handlerEvent.js";

export default function DocumentList({ $target, initialState, username }) {
const $header = document.createElement("header");
const $documentList = document.createElement("div");
const $addDocumentBtn = document.createElement("button");
const $ul = document.createElement("ul");
$header.innerHTML = `<h3><span style="font-size: 30px;">${username}</span>의 노션 페이지</h3>`;
$header.className = "header";
$addDocumentBtn.textContent = "+ Add a Page";
$addDocumentBtn.className = "addDocumentBtn";

$target.appendChild($header);
$target.appendChild($documentList);
$target.appendChild($addDocumentBtn);

this.state = initialState;

this.setState = (nextState) => {
if (JSON.stringify(this.state) !== JSON.stringify(nextState)) {
this.state = nextState;
this.render();
}
};

this.render = () => {
$ul.innerHTML = "";
this.state.map((item) => $ul.appendChild(DocumentListRenderer(item)));
$documentList.appendChild($ul);
};

this.render();

$target.addEventListener("click", (e) => {
const { target } = e;

// 이동할 document 클릭 시
onClickDocument(target);
// header 클릭 시, root로 이동
onClickHeader(target);
// 각 button에 해당하는 이벤트
onClickBtn(target, this.state, username);
});
}
29 changes: 29 additions & 0 deletions src/page/sidebar/DocumentListRenderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export const DocumentListRenderer = (item, depth = 1) => {
const $div = document.createElement("div");
$div.className = "dropdown";
$div.innerHTML = listRendererTemplate(item, depth);

// 현재 document가 자식 document를 가지고 있으면
const childItems = item.documents;
if (childItems.length > 0) {
const $ul = document.createElement("ul");
childItems.forEach((child) =>
$ul.appendChild(DocumentListRenderer(child, depth + 1))
);
$div.appendChild($ul);
}

return $div;
};

const listRendererTemplate = (item, depth) => {
const { id, title } = item;
return `
<li data-id="${id}" style="padding-left: ${depth}px;">
<button class="toggleBtn">></button>
<span class="documentTitle">${title}</span>
<button data-id="${id}" class="addChild">+</button>
<button data-id="${id}" class="delete">-</button>
</li>
`;
};
Loading