From b6f21cba0d689df2e6c7ba575b034ecc76693337 Mon Sep 17 00:00:00 2001 From: Eosdia Date: Fri, 7 Jul 2023 00:15:14 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=EC=B4=88=EA=B8=B0=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 13 +++++++++++++ src/components/App.js | 25 +++++++++++++++++++++++++ src/main.js | 5 +++++ 3 files changed, 43 insertions(+) create mode 100644 index.html create mode 100644 src/components/App.js create mode 100644 src/main.js diff --git a/index.html b/index.html new file mode 100644 index 00000000..4d355da4 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + 노션 클로닝 + + + +
+ + + diff --git a/src/components/App.js b/src/components/App.js new file mode 100644 index 00000000..26a9b139 --- /dev/null +++ b/src/components/App.js @@ -0,0 +1,25 @@ +import DocumentListPage from './Sidebar/DocumentListPage.js'; +import EditPage from './Editor/EditPage.js'; + +export default function App({ $target }) { + const $sidebar = document.createElement('aside'); + const $editor = document.createElement('section'); + + const documentListPage = new DocumentListPage({ $target: $sidebar }); + const editPage = new EditPage({ + $target: $editor, + initialState: { + documentId: 'new', + }, + }); + + this.route = () => { + $target.append($sidebar, $editor); + const { pathname } = window.location; + if (pathname === '/') { + documentListPage.render(); + editPage.render(); + } + }; + this.route(); +} diff --git a/src/main.js b/src/main.js new file mode 100644 index 00000000..f1e09721 --- /dev/null +++ b/src/main.js @@ -0,0 +1,5 @@ +import App from './components/App.js'; + +const $target = document.querySelector('#app'); + +new App({ $target }); From 6acc8d3a3ac0a92f8a25a99003a7c371cbd757da Mon Sep 17 00:00:00 2001 From: Eosdia Date: Fri, 7 Jul 2023 00:26:35 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20api.js=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20(=EC=B4=88=EA=B8=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/api.js diff --git a/src/api.js b/src/api.js new file mode 100644 index 00000000..88e16d8a --- /dev/null +++ b/src/api.js @@ -0,0 +1,19 @@ +export const API_END_POINT = 'https://kdt-frontend.programmers.co.kr'; + +export const request = async (url, options = {}) => { + try { + const res = await fetch(`${API_END_POINT}${url}`, { + ...options, + headers: { + 'x-username': 'whj', + 'Content-Type': 'application/json', + }, + }); + if (res.ok) { + return await res.json(); + } + throw new Error('API처리중 문제 발생!!'); + } catch (e) { + console.log(e.message); + } +}; From 17369a571390472746cfd4f2f62e89613d841243 Mon Sep 17 00:00:00 2001 From: Eosdia Date: Fri, 7 Jul 2023 00:27:11 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20editor=20=EC=B4=88=EA=B8=B0=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Editor/EditPage.js | 62 +++++++++++++++++++++++++++++++ src/components/Editor/Editor.js | 43 +++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 src/components/Editor/EditPage.js create mode 100644 src/components/Editor/Editor.js diff --git a/src/components/Editor/EditPage.js b/src/components/Editor/EditPage.js new file mode 100644 index 00000000..a4da48a2 --- /dev/null +++ b/src/components/Editor/EditPage.js @@ -0,0 +1,62 @@ +import { request } from '../../api.js'; +import Editor from './Editor.js'; + +export default function EditPage({ $target, initialState }) { + const $page = document.createElement('div'); + + this.state = initialState; + + const fetchDocument = async () => { + const { id } = this.state; + if (documentId !== 'new') { + const document = await request(`/documents/${id}`); //api GET + console.log(document); + this.setState({ + ...this.state, + document, + }); + return; + } + }; + + let timer = null; + const editor = new Editor({ + $target: $page, + initialState: { title: '', content: '' }, + onEditing: (document) => { + if (timer !== null) { + clearTimeout(timer); + } + timer = setTimeout(async () => { + const newDocument = await request('/documents', { + method: 'POST', + body: JSON.stringify({ + title: document.title, + parent: + this.state.documentId === 'new' ? null : this.state.documentId, + }), + }); + console.log(newDocument); + }, 1500); + }, + }); + + this.setState = async (nextState) => { + if (this.state.documentId !== nextState.documentId) { + this.state = nextState; + if (this.state.documentId === 'new') { + editor.setState(this.state); + } else { + await fetchDocument(); + } + return; + } + this.state = nextState; + this.render(); + editor.setState(this.state.document || { title: '', content: '' }); + }; + + this.render = () => { + $target.appendChild($page); + }; +} diff --git a/src/components/Editor/Editor.js b/src/components/Editor/Editor.js new file mode 100644 index 00000000..2bd7ee48 --- /dev/null +++ b/src/components/Editor/Editor.js @@ -0,0 +1,43 @@ +export default function Editor({ $target, initialState, onEditing }) { + const $editor = document.createElement('form'); + $editor.className = 'editor'; + $target.appendChild($editor); + + const $title = document.createElement('input'); + $title.className = 'title'; + $title.name = 'title'; + + const $content = document.createElement('textarea'); + $content.className = 'content'; + $content.name = 'content'; + + let isInitialize = false; + + this.state = initialState; + this.setState = (nextState) => { + this.state = nextState; + 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); + } + }); + + this.render = () => { + if (!isInitialize) { + $editor.append($title, $content); + isInitialize = true; + } + $title.value = this.state.title; + $content.value = this.state.content; + }; + this.render(); +} From 3fe3e4e2b5f4b0e085d7ee4272b5580a8debc0d3 Mon Sep 17 00:00:00 2001 From: Eosdia Date: Fri, 7 Jul 2023 00:27:39 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20sidebar=20=EC=B4=88=EA=B8=B0=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Sidebar/DocumentList.js | 46 ++++++++++++++++++++++ src/components/Sidebar/DocumentListPage.js | 23 +++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/components/Sidebar/DocumentList.js create mode 100644 src/components/Sidebar/DocumentListPage.js diff --git a/src/components/Sidebar/DocumentList.js b/src/components/Sidebar/DocumentList.js new file mode 100644 index 00000000..5a8b59ea --- /dev/null +++ b/src/components/Sidebar/DocumentList.js @@ -0,0 +1,46 @@ +export default function DocumentList({ $target, initialState }) { + const $list = document.createElement('div'); + $list.className = 'document_list'; + $target.appendChild($list); + + if (Array.isArray(initialState)) { + this.state = initialState; + } + this.setState = (nextState) => { + this.state = nextState; + this.render(); + }; + this.render = () => { + const $ul = document.createElement('ul'); + + this.state.map((documentInfo) => { + const $li = document.createElement('li'); + $li.className = 'document_li'; + + const $title = document.createElement('span'); + $title.textContent = documentInfo.title; + $title.setAttribute('data-id', documentInfo.id); + $li.appendChild($title); + + const $newBtn = document.createElement('button'); + $newBtn.textContent = '+'; + $li.appendChild($newBtn); + + if (documentInfo.documents.length > 0) { + new DocumentList({ + $target: $li, + initialState: documentInfo.documents, + }); + } + $ul.appendChild($li); + }); + $list.appendChild($ul); + $ul.addEventListener('click', (e) => { + const $title = e.target.closest('span'); + console.log($title); + const { id } = $title.dataset; + console.log(id); + }); + }; + this.render(); +} diff --git a/src/components/Sidebar/DocumentListPage.js b/src/components/Sidebar/DocumentListPage.js new file mode 100644 index 00000000..0c8fb25a --- /dev/null +++ b/src/components/Sidebar/DocumentListPage.js @@ -0,0 +1,23 @@ +import { request } from '../../api.js'; +import DocumentList from './DocumentList.js'; +export default function DocumentListPage({ $target }) { + const $page = document.createElement('div'); + + const fetchDocument = async () => { + const documents = await request('/documents'); + console.log(documents); + return documents; + }; + + const documentList = new DocumentList({ + $target: $page, + initialState: [], + }); + + this.setState = async () => {}; + this.render = async () => { + const documents = await fetchDocument(); + documentList.setState(documents); + $target.appendChild($page); + }; +} From d73d0a536109a341a42ad31a89c96e814f05cd64 Mon Sep 17 00:00:00 2001 From: Eosdia Date: Fri, 7 Jul 2023 12:34:37 +0900 Subject: [PATCH 5/8] =?UTF-8?q?style:=20editor=EC=97=90=20placeholder=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Editor/Editor.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/Editor/Editor.js b/src/components/Editor/Editor.js index 2bd7ee48..ce50cd8e 100644 --- a/src/components/Editor/Editor.js +++ b/src/components/Editor/Editor.js @@ -6,10 +6,12 @@ export default function Editor({ $target, initialState, onEditing }) { const $title = document.createElement('input'); $title.className = 'title'; $title.name = 'title'; + $title.placeholder = '제목을 작성하세요...'; const $content = document.createElement('textarea'); $content.className = 'content'; $content.name = 'content'; + $content.placeholder = '내용을 입력하세요...'; let isInitialize = false; From 74e10243aba50dfff41222be254315db1e2984b1 Mon Sep 17 00:00:00 2001 From: Eosdia Date: Fri, 7 Jul 2023 12:55:31 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20sidebar=20=EC=97=90=20header=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/App.js | 26 ++++++++++++++++++++++++-- src/components/Sidebar/ListHeader.js | 21 +++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 src/components/Sidebar/ListHeader.js diff --git a/src/components/App.js b/src/components/App.js index 26a9b139..cb433036 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -1,15 +1,37 @@ import DocumentListPage from './Sidebar/DocumentListPage.js'; import EditPage from './Editor/EditPage.js'; +import ListHeader from './Sidebar/ListHeader.js'; export default function App({ $target }) { const $sidebar = document.createElement('aside'); + $sidebar.className = 'sidebar'; const $editor = document.createElement('section'); - const documentListPage = new DocumentListPage({ $target: $sidebar }); + new ListHeader({ + $target: $sidebar, + onNewDocument: () => { + console.log('새로운 문서'); + const nextState = { + documentId: 'new', + parent: null, + }; + editPage.setState(nextState); + }, + }); + + const documentListPage = new DocumentListPage({ + $target: $sidebar, + }); + const editPage = new EditPage({ $target: $editor, initialState: { documentId: 'new', + document: { + title: '', + content: '', + }, + parent: null, }, }); @@ -17,7 +39,7 @@ export default function App({ $target }) { $target.append($sidebar, $editor); const { pathname } = window.location; if (pathname === '/') { - documentListPage.render(); + documentListPage.setState(); editPage.render(); } }; diff --git a/src/components/Sidebar/ListHeader.js b/src/components/Sidebar/ListHeader.js new file mode 100644 index 00000000..5d3f095a --- /dev/null +++ b/src/components/Sidebar/ListHeader.js @@ -0,0 +1,21 @@ +export default function ListHeader({ $target, onNewDocument }) { + const $header = document.createElement('div'); + $header.className = 'sidebar_header'; + + $target.appendChild($header); + this.render = () => { + const $userInfo = document.createElement('h3'); + $userInfo.textContent = 'Notion 과제중...'; + + const $newDocument = document.createElement('div'); + $newDocument.className = 'sidebar_newDocument'; + $newDocument.textContent = '새 페이지'; + + $header.append($userInfo, $newDocument); + + $newDocument.addEventListener('click', (e) => { + onNewDocument(); + }); + }; + this.render(); +} From 08125a5d0d3c915361092a165c57d626d1fa7116 Mon Sep 17 00:00:00 2001 From: Eosdia Date: Fri, 7 Jul 2023 13:02:38 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20document=EC=B6=94=EA=B0=80,=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C,=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20?= =?UTF-8?q?=ED=8E=B8=EC=A7=91=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/App.js | 24 +++++++++++ src/components/Editor/EditPage.js | 46 ++++++++++++--------- src/components/Sidebar/DocumentList.js | 47 +++++++++++++++++----- src/components/Sidebar/DocumentListPage.js | 23 ++++++----- 4 files changed, 101 insertions(+), 39 deletions(-) diff --git a/src/components/App.js b/src/components/App.js index cb433036..b242d940 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -1,6 +1,7 @@ import DocumentListPage from './Sidebar/DocumentListPage.js'; import EditPage from './Editor/EditPage.js'; import ListHeader from './Sidebar/ListHeader.js'; +import { request } from '../api.js'; export default function App({ $target }) { const $sidebar = document.createElement('aside'); @@ -21,6 +22,26 @@ export default function App({ $target }) { const documentListPage = new DocumentListPage({ $target: $sidebar, + onSelectDocument: (documentId) => { + const nextState = { documentId }; + console.log(nextState); + editPage.setState(nextState); + }, + onCreateDocument: (documentId) => { + const parent = documentId; + const nextState = { + documentId: 'new', + parent, + }; + editPage.setState(nextState); + }, + onRemoveDocument: async (documentId) => { + console.log('remove'); + await request(`/documents/${documentId}`, { + method: 'DELETE', + }); + documentListPage.setState(); + }, }); const editPage = new EditPage({ @@ -33,6 +54,9 @@ export default function App({ $target }) { }, parent: null, }, + onChange: () => { + documentListPage.setState(); + }, }); this.route = () => { diff --git a/src/components/Editor/EditPage.js b/src/components/Editor/EditPage.js index a4da48a2..6ccb864a 100644 --- a/src/components/Editor/EditPage.js +++ b/src/components/Editor/EditPage.js @@ -1,43 +1,53 @@ import { request } from '../../api.js'; import Editor from './Editor.js'; -export default function EditPage({ $target, initialState }) { +export default function EditPage({ $target, initialState, onChange }) { const $page = document.createElement('div'); this.state = initialState; const fetchDocument = async () => { - const { id } = this.state; + const { documentId } = this.state; if (documentId !== 'new') { - const document = await request(`/documents/${id}`); //api GET - console.log(document); + const document = await request(`/documents/${documentId}`); //api GET this.setState({ ...this.state, document, }); - return; } }; let timer = null; const editor = new Editor({ $target: $page, - initialState: { title: '', content: '' }, + initialState: this.state.document, onEditing: (document) => { if (timer !== null) { clearTimeout(timer); } timer = setTimeout(async () => { - const newDocument = await request('/documents', { - method: 'POST', - body: JSON.stringify({ - title: document.title, - parent: - this.state.documentId === 'new' ? null : this.state.documentId, - }), - }); - console.log(newDocument); - }, 1500); + const isNew = this.state.documentId === 'new'; + if (isNew) { + const createdDocument = await request('/documents', { + method: 'POST', + body: JSON.stringify({ + title: document.title, + parent: this.state.parent, + }), + }); + this.setState({ + documentId: createdDocument.id, + document, + }); + onChange(); + } else { + await request(`/documents/${this.state.documentId}`, { + method: 'PUT', + body: JSON.stringify(document), + }); + } + onChange(); + }, 1000); }, }); @@ -45,14 +55,14 @@ export default function EditPage({ $target, initialState }) { if (this.state.documentId !== nextState.documentId) { this.state = nextState; if (this.state.documentId === 'new') { - editor.setState(this.state); + this.render(); + editor.setState({ title: '', content: '' }); } else { await fetchDocument(); } return; } this.state = nextState; - this.render(); editor.setState(this.state.document || { title: '', content: '' }); }; diff --git a/src/components/Sidebar/DocumentList.js b/src/components/Sidebar/DocumentList.js index 5a8b59ea..29826fc3 100644 --- a/src/components/Sidebar/DocumentList.js +++ b/src/components/Sidebar/DocumentList.js @@ -1,4 +1,10 @@ -export default function DocumentList({ $target, initialState }) { +export default function DocumentList({ + $target, + initialState, + onSelectDocument, + onCreateDocument, + onRemoveDocument, +}) { const $list = document.createElement('div'); $list.className = 'document_list'; $target.appendChild($list); @@ -16,15 +22,22 @@ export default function DocumentList({ $target, initialState }) { this.state.map((documentInfo) => { const $li = document.createElement('li'); $li.className = 'document_li'; + $li.setAttribute('data-id', documentInfo.id); const $title = document.createElement('span'); $title.textContent = documentInfo.title; - $title.setAttribute('data-id', documentInfo.id); + $title.className = 'title'; $li.appendChild($title); - const $newBtn = document.createElement('button'); - $newBtn.textContent = '+'; - $li.appendChild($newBtn); + const $createBtn = document.createElement('button'); + $createBtn.textContent = '+'; + $createBtn.className = 'createBtn'; + $li.appendChild($createBtn); + + const $removeBtn = document.createElement('button'); + $removeBtn.textContent = '-'; + $removeBtn.className = 'removeBtn'; + $li.appendChild($removeBtn); if (documentInfo.documents.length > 0) { new DocumentList({ @@ -34,12 +47,26 @@ export default function DocumentList({ $target, initialState }) { } $ul.appendChild($li); }); - $list.appendChild($ul); + $list.replaceChildren($ul); + $ul.addEventListener('click', (e) => { - const $title = e.target.closest('span'); - console.log($title); - const { id } = $title.dataset; - console.log(id); + const $li = e.target.closest('li'); + if ($li !== null) { + const documentId = $li.dataset.id; + + // document 선택 + if (e.target.className === 'title') { + onSelectDocument(documentId); + } + //하위 document 생성 + if (e.target.className === 'createBtn') { + onCreateDocument(documentId); + } + // document 삭제 + else if (e.target.className === 'removeBtn') { + onRemoveDocument(documentId); + } + } }); }; this.render(); diff --git a/src/components/Sidebar/DocumentListPage.js b/src/components/Sidebar/DocumentListPage.js index 0c8fb25a..9b499d21 100644 --- a/src/components/Sidebar/DocumentListPage.js +++ b/src/components/Sidebar/DocumentListPage.js @@ -1,23 +1,24 @@ import { request } from '../../api.js'; import DocumentList from './DocumentList.js'; -export default function DocumentListPage({ $target }) { +export default function DocumentListPage({ + $target, + onSelectDocument, + onCreateDocument, + onRemoveDocument, +}) { const $page = document.createElement('div'); - - const fetchDocument = async () => { - const documents = await request('/documents'); - console.log(documents); - return documents; - }; + $target.appendChild($page); const documentList = new DocumentList({ $target: $page, initialState: [], + onSelectDocument, + onCreateDocument, + onRemoveDocument, }); - this.setState = async () => {}; - this.render = async () => { - const documents = await fetchDocument(); + this.setState = async () => { + const documents = await request('/documents'); documentList.setState(documents); - $target.appendChild($page); }; } From 241fa821f971ef042be2b6a2fc8b60c0d8b66a34 Mon Sep 17 00:00:00 2001 From: Eosdia Date: Fri, 7 Jul 2023 13:03:30 +0900 Subject: [PATCH 8/8] =?UTF-8?q?design:=20css=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.css | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 index.css diff --git a/index.css b/index.css new file mode 100644 index 00000000..01c6d9a9 --- /dev/null +++ b/index.css @@ -0,0 +1,58 @@ +body { + margin: 0; +} +#app { + width: 100%; + display: flex; +} +.sidebar { + width: 25%; + min-width: 250px; + background-color: rgb(247, 247, 245); + padding: 5px; +} +.sidebar_header { + padding: 8px; + border-bottom: 1px solid black; +} +.sidebar_newDocument { + cursor: pointer; + box-sizing: border-box; + padding: 3px; +} +.sidebar_newDocument:hover { + background-color: rgb(232, 232, 230); +} + +.document_list > ul { + padding-left: 8px; +} +.document_li { + list-style: none; + cursor: pointer; +} + +section { + width: 100%; + padding: 8px; +} +.editor { + display: flex; + flex-direction: column; + width: 100%; + height: 100vh; +} +.title { + flex-grow: 1; + width: 100%; + height: 50px; + margin-bottom: 10px; + padding: 5px 20px; + outline: none; +} +.content { + width: 100%; + height: 100%; + padding: 20px; + outline: none; +}