From adf99cf8060028f49f060aad95a23ec79d0180da Mon Sep 17 00:00:00 2001 From: hanyugeon Date: Thu, 17 Nov 2022 00:02:31 +0900 Subject: [PATCH 1/2] =?UTF-8?q?Feat:=20=ED=95=84=EC=88=98=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=20=EC=82=AC=ED=95=AD=20=EC=9D=BC=EB=B6=80=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(=20child=20node=20=ED=91=9C=EC=8B=9C=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=EB=88=84=EB=9D=BD.=20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/api.js | 21 ++++++ components/Content/Content.js | 99 +++++++++++++++++++++++++++++ components/Content/ContentEditor.js | 51 +++++++++++++++ components/SideBar/SideBar.js | 25 ++++++++ components/SideBar/SideBarItem.js | 11 ++++ components/SideBar/SideBarList.js | 63 ++++++++++++++++++ index.html | 10 +++ index.js | 5 ++ pages/App.js | 40 ++++++++++++ style/style.css | 37 +++++++++++ utils/localStorage.js | 19 ++++++ utils/router.js | 22 +++++++ 12 files changed, 403 insertions(+) create mode 100644 api/api.js create mode 100644 components/Content/Content.js create mode 100644 components/Content/ContentEditor.js create mode 100644 components/SideBar/SideBar.js create mode 100644 components/SideBar/SideBarItem.js create mode 100644 components/SideBar/SideBarList.js create mode 100644 index.html create mode 100644 index.js create mode 100644 pages/App.js create mode 100644 style/style.css create mode 100644 utils/localStorage.js create mode 100644 utils/router.js diff --git a/api/api.js b/api/api.js new file mode 100644 index 00000000..f56fd2d7 --- /dev/null +++ b/api/api.js @@ -0,0 +1,21 @@ +import { API_END_POINT, HEADER } from "./apiConstans.js"; + +export const request = async (url, options = {}) => { + try { + const res = await fetch(`${API_END_POINT}${url}`, { + ...options, + headers: { + "Content-Type": "application/json", + "x-username": HEADER, + }, + }); + + if (res.ok) { + return await res.json(); + } + + throw new Error("API 처리중 뭔가 이상합니다!"); + } catch (error) { + alert(error.message); + } +}; diff --git a/components/Content/Content.js b/components/Content/Content.js new file mode 100644 index 00000000..30a4c947 --- /dev/null +++ b/components/Content/Content.js @@ -0,0 +1,99 @@ +import ContentEditor from "./ContentEditor.js"; +import { request } from "../../api/api.js"; +import { push } from "../../utils/router.js"; + +export default function Content({ $target, initialState }) { + const $content = document.createElement("div"); + $content.classList.add("layout-content"); + + this.state = initialState; + + const post = { + title: "", + content: "", + }; + + let timer = null; + + const contentEditor = new ContentEditor({ + $target: $content, + initialState: post, + onEditing: (post) => { + if (timer !== null) { + clearTimeout(timer); + } + + timer = setTimeout(async () => { + const isNew = this.state.documentId === "new"; + + if (isNew) { + const createdPost = await request("/documents", { + method: "post", + body: JSON.stringify(post), + }); + + this.setState({ + documentId: createdPost.id, + }); + + push(`/documents/${createdPost.id}`); + } else { + await request(`/documents/${post.id}`, { + method: "put", + body: JSON.stringify(post), + }); + } + }, 500); + }, + }); + + this.setState = async (nextState) => { + if (this.state.documentId !== nextState.documentId) { + this.state = nextState; + + if (this.state.documentId === "new") { + const document = { + title: "", + content: "", + }; + + contentEditor.setState(document); + contentEditor.render(); + this.render(); + } else { + await fetchPost(); + } + + return; + } + + this.state = nextState; + this.render(); + contentEditor.setState( + this.state.document || { + title: "", + content: "", + } + ); + contentEditor.render(); + }; + + this.render = () => { + $target.appendChild($content); + }; + + const fetchPost = async () => { + const { documentId } = this.state; + + if (documentId !== "new") { + const document = await request(`/documents/${documentId}`, { + method: "get", + }); + + this.setState({ + ...this.state, + document, + }); + } + }; +} diff --git a/components/Content/ContentEditor.js b/components/Content/ContentEditor.js new file mode 100644 index 00000000..de3d925b --- /dev/null +++ b/components/Content/ContentEditor.js @@ -0,0 +1,51 @@ +export default function ContentEditor({ + $target, + initialState = { + title: "", + content: "", + }, + onEditing, +}) { + const $contentEditor = document.createElement("div"); + + this.state = initialState; + + this.setState = (nextState) => { + this.state = nextState; + }; + + $contentEditor.innerHTML = ` + + + `; + + $target.appendChild($contentEditor); + + this.render = () => { + $contentEditor.querySelector("[name=title]").value = this.state.title; + $contentEditor.querySelector("[name=content]").value = this.state.content; + }; + + this.render(); + + $contentEditor.addEventListener("keyup", (e) => { + const { target } = e; + const name = target.getAttribute("name"); + const nextState = { + ...this.state, + [name]: target.value, + }; + + this.setState(nextState); + onEditing(this.state); + }); +} diff --git a/components/SideBar/SideBar.js b/components/SideBar/SideBar.js new file mode 100644 index 00000000..c7b08ddb --- /dev/null +++ b/components/SideBar/SideBar.js @@ -0,0 +1,25 @@ +import SideBarList from "./SideBarList.js"; +import { request } from "../../api/api.js"; + +export default function SideBar({ $target }) { + const $sideBar = document.createElement("div"); + $sideBar.classList.add("layout-sidebar"); + + const sideBarList = new SideBarList({ + $target: $sideBar, + initialState: [], + }); + + this.setState = async () => { + const documents = await request("/documents", { + method: "get", + }); + + sideBarList.setState(documents); + this.render(); + }; + + this.render = async () => { + $target.appendChild($sideBar); + }; +} diff --git a/components/SideBar/SideBarItem.js b/components/SideBar/SideBarItem.js new file mode 100644 index 00000000..c8311d85 --- /dev/null +++ b/components/SideBar/SideBarItem.js @@ -0,0 +1,11 @@ +export default function SideBarItem(item) { + return ` +
  • + + ${item.title} + + + +
  • + `; +} diff --git a/components/SideBar/SideBarList.js b/components/SideBar/SideBarList.js new file mode 100644 index 00000000..290e403f --- /dev/null +++ b/components/SideBar/SideBarList.js @@ -0,0 +1,63 @@ +import SideBarItem from "./SideBarItem.js"; +import { request } from "../../api/api.js"; +import { push } from "../../utils/router.js"; + +export default function SideBarList({ $target, initialState }) { + const $sideBarList = document.createElement("div"); + + this.state = initialState; + + this.setState = (nextState) => { + this.state = nextState; + this.render(); + }; + + this.render = () => { + if (!this.state) return; + + $sideBarList.innerHTML = ` + + `; + + $target.appendChild($sideBarList); + }; + + this.render(); + + $sideBarList.addEventListener("click", async (e) => { + const $item = e.target; + const { id } = $item.parentElement.dataset; + + if ($item.className === "item-load") { + push(`/documents/${id}`); + + return; + } + + if ($item.className === "item-remove") { + await request(`/documents/${id}`, { + method: "delete", + }); + + push(`/`); + + return; + } + + if ($item.className === "item-add") { + const tempPost = { + title: "", + parent: id, + }; + const createdPost = await request("/documents", { + method: "post", + body: JSON.stringify(tempPost), + }); + + // this.setState() + push(`/documents/${createdPost.id}`); + } + }); +} diff --git a/index.html b/index.html new file mode 100644 index 00000000..4d714f06 --- /dev/null +++ b/index.html @@ -0,0 +1,10 @@ + + + Notion + + + +
    + + + diff --git a/index.js b/index.js new file mode 100644 index 00000000..acf90b87 --- /dev/null +++ b/index.js @@ -0,0 +1,5 @@ +import App from "./pages/App.js"; + +const $target = document.querySelector("#app"); + +new App({ $target }); diff --git a/pages/App.js b/pages/App.js new file mode 100644 index 00000000..d4cd5eef --- /dev/null +++ b/pages/App.js @@ -0,0 +1,40 @@ +import SideBar from "../components/SideBar/SideBar.js"; +import Content from "../components/Content/Content.js"; +import { initRouter } from "../utils/router.js"; + +export default function App({ $target }) { + const $layout = document.createElement("div"); + $layout.classList.add("layout-main"); + + const sideBar = new SideBar({ + $target: $layout, + }); + const content = new Content({ + $target: $layout, + initialState: { + documentId: "new", + }, + }); + + this.route = async () => { + $target.innerHTML = ``; + + const { pathname } = window.location; + + if (pathname === "/") { + await sideBar.setState(); + await content.setState({ documentId: "new" }); + } else if (pathname.indexOf("/documents/" === 0)) { + const [, , documentId] = pathname.split("/"); + + await sideBar.setState(); + await content.setState({ documentId }); + } + + $target.appendChild($layout); + }; + + this.route(); + + initRouter(() => this.route()); +} diff --git a/style/style.css b/style/style.css new file mode 100644 index 00000000..013fc1da --- /dev/null +++ b/style/style.css @@ -0,0 +1,37 @@ +.layout-main { + display: flex; + width: 100%; + height: 100%; + color: #37352f; + background: #ffffff; +} + +.layout-sidebar { + width: 20%; + max-width: 280px; + min-width: 200px; + height: 100%; + font-size: 14px; + font-weight: bold; + color: #37352f; + background: #fbfbfa; + overflow: auto; + float: left; +} + +.layout-content { + width: 80%; + height: 100%; + float: left; +} + +.editor-title { + width: 100%; + height: 5%; + max-height: 60px; +} + +.editor-content { + width: 100%; + height: 95%; +} diff --git a/utils/localStorage.js b/utils/localStorage.js new file mode 100644 index 00000000..ace8af8b --- /dev/null +++ b/utils/localStorage.js @@ -0,0 +1,19 @@ +const localStorage = window.localStorage; + +export const getItem = (key, defaultValue) => { + try { + const storedValue = localStorage.getItem(key); + + return storedValue ? JSON.parse(storedValue) : defaultValue; + } catch (error) { + return defaultValue; + } +}; + +export const setItem = (key, value) => { + localStorage.setItem(key, JSON.stringify(value)); +}; + +export const removeItem = (key) => { + localStorage.removeItem(key); +}; diff --git a/utils/router.js b/utils/router.js new file mode 100644 index 00000000..67482796 --- /dev/null +++ b/utils/router.js @@ -0,0 +1,22 @@ +const ROUTE_CHANGE_EVENT_NAME = "route-change"; + +export const initRouter = (onRoute) => { + window.addEventListener(ROUTE_CHANGE_EVENT_NAME, (e) => { + const { nextUrl } = e.detail; + + if (nextUrl) { + history.pushState(null, null, nextUrl); + onRoute(); + } + }); +}; + +export const push = (nextUrl) => { + window.dispatchEvent( + new CustomEvent(ROUTE_CHANGE_EVENT_NAME, { + detail: { + nextUrl, + }, + }) + ); +}; From 02c60ec0c497ed43a8806b2ae805be801e506bb1 Mon Sep 17 00:00:00 2001 From: hanyugeon Date: Thu, 17 Nov 2022 01:34:33 +0900 Subject: [PATCH 2/2] =?UTF-8?q?Feat:=20=ED=95=84=EC=88=98=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=20=EC=82=AC=ED=95=AD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/SideBar/SideBarItem.js | 3 +++ style/style.css | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/components/SideBar/SideBarItem.js b/components/SideBar/SideBarItem.js index c8311d85..31274c6e 100644 --- a/components/SideBar/SideBarItem.js +++ b/components/SideBar/SideBarItem.js @@ -6,6 +6,9 @@ export default function SideBarItem(item) { + `; } diff --git a/style/style.css b/style/style.css index 013fc1da..0bc79744 100644 --- a/style/style.css +++ b/style/style.css @@ -25,6 +25,10 @@ float: left; } +.sidebar-list-ul { + list-style: none; +} + .editor-title { width: 100%; height: 5%;