diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..f7e9b972
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+Api
diff --git a/css/reset.css b/css/reset.css
new file mode 100644
index 00000000..3fbcb7b5
--- /dev/null
+++ b/css/reset.css
@@ -0,0 +1,148 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+ v2.0 | 20110126
+ License: none (public domain)
+*/
+
+html,
+body,
+div,
+span,
+applet,
+object,
+iframe,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+blockquote,
+pre,
+a,
+abbr,
+acronym,
+address,
+big,
+cite,
+code,
+del,
+dfn,
+em,
+img,
+ins,
+kbd,
+q,
+s,
+samp,
+small,
+strike,
+strong,
+sub,
+sup,
+tt,
+var,
+b,
+u,
+i,
+center,
+dl,
+dt,
+dd,
+ol,
+ul,
+li,
+fieldset,
+form,
+label,
+legend,
+table,
+caption,
+tbody,
+tfoot,
+thead,
+tr,
+th,
+td,
+article,
+aside,
+canvas,
+details,
+embed,
+figure,
+figcaption,
+footer,
+header,
+hgroup,
+menu,
+nav,
+output,
+ruby,
+section,
+summary,
+time,
+mark,
+audio,
+video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol,
+ul {
+ list-style: none;
+}
+blockquote,
+q {
+ quotes: none;
+}
+blockquote:before,
+blockquote:after,
+q:before,
+q:after {
+ content: "";
+ content: none;
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+button {
+ border: none;
+ background-color: transparent;
+ padding: 0;
+ margin: 0;
+}
+input {
+ border: none;
+ outline: none;
+}
+textarea {
+ border: none;
+ resize: none;
+ outline: none;
+}
+input::placeholder,
+textarea::placeholder {
+ color: #d7d7d7;
+}
diff --git a/css/style.css b/css/style.css
new file mode 100644
index 00000000..7391716a
--- /dev/null
+++ b/css/style.css
@@ -0,0 +1,126 @@
+main {
+ display: flex;
+ width: 100%;
+ height: 100vh;
+}
+
+div.left {
+ width: 20%;
+ background-color: #f7f5f0;
+ color: #696969;
+}
+
+.left .header {
+ width: 100%;
+ height: 10%;
+ display: flex;
+ align-items: center;
+}
+
+.left .header h2 {
+ font-size: 20px;
+ font-weight: 500;
+ margin-left: 15px;
+}
+
+.left .postList {
+ width: 100%;
+ height: 90%;
+ overflow-y: auto;
+}
+
+.postList ul {
+ padding-left: 1.5vh;
+ cursor: pointer;
+}
+
+.document {
+ width: 100%;
+ height: fit-content;
+ padding: 1.5vh 0;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+}
+
+.document:hover {
+ background-color: #e8e6e0de;
+}
+
+.document span {
+ width: 75%;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ font-size: 15px;
+ margin-right: 5px;
+}
+
+.document button {
+ width: 30px;
+ height: 30px;
+ font-size: 20px;
+ border-radius: 5px;
+ margin-right: 5px;
+ color: #565656;
+ cursor: pointer;
+}
+
+.document button:hover {
+ background-color: #cac8c5cc;
+}
+
+.new-post {
+ width: 100%;
+ height: fit-content;
+ padding: 15px 0;
+ font-size: 20px;
+ text-align: left;
+ padding-left: 15px;
+ color: #565656;
+ cursor: pointer;
+}
+
+.new-post:hover {
+ background-color: #e8e6e0de;
+}
+
+div.right {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 80%;
+}
+
+.editor {
+ width: 80%;
+ height: 80%;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+}
+
+.editor input {
+ width: 100%;
+ padding: 10px 0;
+ font-size: 45px;
+ font-weight: 700;
+}
+
+.editor div:focus {
+ outline: none;
+}
+
+.content-container:empty:before {
+ content: attr(placeholder);
+ display: block;
+ color: rgb(174, 174, 174);
+}
+
+.content-container {
+ width: 100%;
+ height: 84%;
+ font-size: 25px;
+ padding: 10px 0;
+ overflow-y: auto;
+}
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..f7f35b5a
--- /dev/null
+++ b/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+ Notion cloning
+
+
+
+
+
+
diff --git a/src/Editor/editor.js b/src/Editor/editor.js
new file mode 100644
index 00000000..be01f44c
--- /dev/null
+++ b/src/Editor/editor.js
@@ -0,0 +1,57 @@
+import { request } from "../../Api/api.js";
+
+export default function Editor({ $target, initialState }) {
+ const $editor = document.createElement("div");
+ const $div = document.createElement("div");
+ $editor.setAttribute("class", "right");
+ $div.setAttribute("class", "editor");
+
+ $target.appendChild($editor);
+ $editor.appendChild($div);
+
+ this.state = initialState;
+
+ this.handleRevise = () => {
+ const plusEvent = new CustomEvent("@ReviseCompleted");
+ $editor.dispatchEvent(plusEvent);
+ };
+
+ this.render = () => {
+ $div.innerHTML = `
+
+
+ `;
+ };
+ this.render();
+
+ this.setState = (nextState) => {
+ this.state = nextState;
+ $div.querySelector("[name=title]").value = this.state.title;
+ $div.querySelector("[name=content]").innerHTML = this.state.content;
+ };
+
+ $div.querySelector("[name=title]").addEventListener("keyup", async (e) => {
+ e.preventDefault();
+ const $input = $div.querySelector("input");
+ await request(`/documents/${this.state.id}`, {
+ method: "PUT",
+ body: JSON.stringify({
+ title: $input.value,
+ }),
+ });
+ this.handleRevise();
+ });
+
+ $editor
+ .querySelector("[name=content]")
+ .addEventListener("input", async (e) => {
+ e.preventDefault();
+ await request(`/documents/${this.state.id}`, {
+ method: "PUT",
+ body: JSON.stringify({
+ content: e.target.innerHTML,
+ }),
+ });
+ this.handleRevise();
+ });
+}
diff --git a/src/app.js b/src/app.js
new file mode 100644
index 00000000..ea851033
--- /dev/null
+++ b/src/app.js
@@ -0,0 +1,69 @@
+import { request } from "../Api/api.js";
+import Editor from "./Editor/editor.js";
+import { createPost, getPost, removePost } from "./postListUtil.js";
+import Sidebar from "./sidebar.js";
+
+export default function App({ $target }) {
+ this.setState = (nextState) => {
+ this.state = nextState;
+ sideBar.setState(nextState);
+ };
+
+ const sideBar = new Sidebar({
+ $target,
+ initialState: this.state,
+ });
+
+ const $postList = document.querySelector(".postList");
+
+ const editor = new Editor({
+ $target,
+ });
+
+ const $editor = document.querySelector(".right");
+
+ $postList.addEventListener("@CreateChildDoc", async (e) => {
+ const res = await createPost(e.detail.id);
+ editor.setState({
+ title: res.title,
+ content: "",
+ id: parseInt(res.id),
+ });
+ this.init();
+ });
+
+ $postList.addEventListener("@EditDoc", async (e) => {
+ const res = await getPost(e.detail.id);
+ editor.setState({
+ title: res.title,
+ content: res.content,
+ id: parseInt(e.detail.id),
+ });
+ });
+
+ $postList.addEventListener("@RemoveDoc", async (e) => {
+ await removePost(e.detail.id);
+ this.init();
+ });
+
+ $postList.addEventListener("@CreateNewDoc", async (e) => {
+ const res = await createPost();
+ editor.setState({
+ title: res.title,
+ content: "",
+ id: parseInt(res.id),
+ });
+ this.init();
+ });
+
+ $editor.addEventListener("@ReviseCompleted", (e) => {
+ this.init();
+ });
+
+ this.init = async () => {
+ const result = await request("/documents");
+ this.setState(result);
+ };
+
+ this.init();
+}
diff --git a/src/header.js b/src/header.js
new file mode 100644
index 00000000..396b1f4d
--- /dev/null
+++ b/src/header.js
@@ -0,0 +1,14 @@
+export default function Header({ $target }) {
+ const $div = document.createElement("div");
+ $div.setAttribute("class", "header");
+ const $header = document.createElement("h2");
+
+ $target.appendChild($div);
+ $div.appendChild($header);
+
+ this.render = () => {
+ $header.innerHTML = `
+ Notion`;
+ };
+ this.render();
+}
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 00000000..7780211c
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,7 @@
+import App from "./app.js";
+
+const $target = document.querySelector("#app");
+
+new App({
+ $target,
+});
diff --git a/src/postListUtil.js b/src/postListUtil.js
new file mode 100644
index 00000000..57fd3b28
--- /dev/null
+++ b/src/postListUtil.js
@@ -0,0 +1,48 @@
+import { request } from "../Api/api.js";
+
+export const createPost = async (parent = null) => {
+ return await request("/documents", {
+ method: "POST",
+ body: JSON.stringify({
+ title: "",
+ parent: parent,
+ }),
+ });
+};
+
+export const removePost = async (id) => {
+ await request(`/documents/${id}`, {
+ method: "DELETE",
+ });
+};
+export const getPost = async (id) => {
+ return await request(`/documents/${id}`, {
+ method: "GET",
+ });
+};
+
+export const renderLogic = (arr) => {
+ return `
+
+ ${arr
+ .map(({ id, title, documents }) => {
+ return documents
+ ? `-
+
+ > ${title}
+
+
+
+ ` + renderLogic(documents)
+ : `-
+
+ > ${title}
+
+
+
+ `;
+ })
+ .join("")}
+
+ `;
+};
diff --git a/src/postlists.js b/src/postlists.js
new file mode 100644
index 00000000..31bb3051
--- /dev/null
+++ b/src/postlists.js
@@ -0,0 +1,69 @@
+import Editor from "./Editor/editor.js";
+import { createPost, removePost, renderLogic } from "./postListUtil.js";
+
+export default function PostLists({ $target, initialState = [] }) {
+ const $postslist = document.createElement("div");
+ $postslist.setAttribute("class", "postList");
+
+ $target.appendChild($postslist);
+
+ this.state = [
+ {
+ id: 1,
+ title: "아이우에요",
+ },
+ ];
+
+ this.setState = (nextState) => {
+ this.state = nextState;
+ this.render();
+ };
+
+ this.handlePlus = (e) => {
+ const targetId = e.target.dataset.id;
+ const plusEvent = new CustomEvent("@CreateChildDoc", {
+ detail: { id: targetId },
+ });
+ $postslist.dispatchEvent(plusEvent);
+ };
+
+ this.handleRemove = (e) => {
+ const targetId = e.target.dataset.id;
+ const minusEvent = new CustomEvent("@RemoveDoc", {
+ detail: { id: targetId },
+ });
+ $postslist.dispatchEvent(minusEvent);
+ };
+
+ this.handleNewPost = () => {
+ const createNewPostEvent = new CustomEvent("@CreateNewDoc");
+ $postslist.dispatchEvent(createNewPostEvent);
+ };
+
+ this.handleEditPost = (e) => {
+ const targetId = e.target.dataset.id;
+ const editEvent = new CustomEvent("@EditDoc", {
+ detail: { id: targetId },
+ });
+ $postslist.dispatchEvent(editEvent);
+ };
+
+ this.render = () => {
+ $postslist.innerHTML =
+ renderLogic(this.state) +
+ ``;
+ };
+
+ $postslist.addEventListener("click", (e) => {
+ const $li = e.target.closest("li");
+ const id = parseInt($postslist.dataset.id);
+ const name = e.target.dataset.name;
+
+ if (name === "plus") this.handlePlus(e);
+ if (name === "minus") this.handleRemove(e);
+ if (name === "createNewPostBtn") this.handleNewPost(e);
+ if (name === "list" || name === "span-list") this.handleEditPost(e);
+ });
+
+ this.render();
+}
diff --git a/src/sidebar.js b/src/sidebar.js
new file mode 100644
index 00000000..07a0bdd4
--- /dev/null
+++ b/src/sidebar.js
@@ -0,0 +1,25 @@
+import PostLists from "./postlists.js";
+import Header from "./header.js";
+
+export default function Sidebar({ $target, initialState }) {
+ const $sidebar = document.createElement("div");
+ $sidebar.setAttribute("class", "left");
+
+ $target.appendChild($sidebar);
+
+ this.state = initialState;
+
+ this.setState = (nextState) => {
+ this.state = nextState;
+ this.postLists.setState(nextState);
+ };
+
+ this.header = new Header({
+ $target: $sidebar,
+ });
+
+ this.postLists = new PostLists({
+ $target: $sidebar,
+ initialState: this.state,
+ });
+}