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 ` + + `; +}; 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, + }); +}