From 7163b3f8665a661deccc2befd3299bd2f166e6eb Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Sun, 13 Nov 2022 19:42:14 +0900 Subject: [PATCH 01/24] =?UTF-8?q?docs:=20:memo:=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=A0=9D=ED=8A=B8=20=EC=A0=95=EC=9D=98=EC=84=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1(README.md)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.assets/yenotion_wireframe (1).png | Bin 0 -> 126157 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 README.assets/yenotion_wireframe (1).png diff --git a/README.assets/yenotion_wireframe (1).png b/README.assets/yenotion_wireframe (1).png new file mode 100644 index 0000000000000000000000000000000000000000..000b4978c36245d7f79e43aa71b180fd88a93947 GIT binary patch literal 126157 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 eY#BtqmFqAf+AaH(Hi@ literal 0 HcmV?d00001 From 41ec3f5a57fa19ee5d2be52f200d2cff288c812c Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Mon, 14 Nov 2022 12:06:31 +0900 Subject: [PATCH 02/24] =?UTF-8?q?chore:=20:package:=20package.json=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 00000000..9f3132db --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "yenotion", + "version": "1.0.0", + "description": "automatic text editor", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "yezyvibe", + "license": "ISC" +} From d01fb474fe4594de50473906f179d837335bdedf Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Mon, 14 Nov 2022 16:03:21 +0900 Subject: [PATCH 03/24] =?UTF-8?q?feat:=20:tada:=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=A0=9D=ED=8A=B8=20=EA=B5=AC=EC=A1=B0=20=EB=B0=8F=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + README.md | 105 ++++++++++++++++++++++++++++--- index.html | 12 ++++ package.json | 3 +- src/App.js | 11 ++++ src/components/DocumentEditor.js | 13 ++++ src/components/DocumentInfo.js | 0 src/components/DocumentNode.js | 56 +++++++++++++++++ src/components/DocumentTree.js | 53 ++++++++++++++++ src/components/Header.js | 13 ++++ src/index.js | 7 +++ src/pages/DocumentDetailPage.js | 0 src/pages/DocumentEditPage.js | 13 ++++ src/styles/style.css | 16 +++++ src/utils/api.js | 45 +++++++++++++ src/utils/router.js | 0 yarn.lock | 8 +++ 17 files changed, 347 insertions(+), 10 deletions(-) create mode 100644 .gitignore create mode 100644 index.html create mode 100644 src/App.js create mode 100644 src/components/DocumentEditor.js create mode 100644 src/components/DocumentInfo.js create mode 100644 src/components/DocumentNode.js create mode 100644 src/components/DocumentTree.js create mode 100644 src/components/Header.js create mode 100644 src/index.js create mode 100644 src/pages/DocumentDetailPage.js create mode 100644 src/pages/DocumentEditPage.js create mode 100644 src/styles/style.css create mode 100644 src/utils/api.js create mode 100644 src/utils/router.js create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a56a7ef4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules + diff --git a/README.md b/README.md index 82b85e81..1043854b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,98 @@ -# 📌 4주차 프로젝트[Project1] +# 📝YENOTION -## 필수 프로젝트 -- 프로젝트 기한 - - 프로젝트 수행 기간 : 2022년 11월 8일(화) ~ 2022년 11월 16일(수) - - 멘티 코드 리뷰 기간 : 2022년 11월 17일(목) ~ 2022년 11월 20일(일) - - 멘토 코드 리뷰 기간 : 2022년 11월 17일(목) ~ 2022년 11월 22일(화) - - 코드 리뷰 반영 기간 : 2022년 11월 23일(수) ~ 2022년 11월 25일(금) -- 내용 - - **Day 17 [프로젝트] 노션 클로닝 요구사항** 확인 부탁드립니다. + +## 🔸프로젝트 수행 과정 + +1. 과제 명세 재정의, 리드미 수정 +2. 와이어프레임, 컴포넌트 구조 설계 +3. 프로젝트 초기 셋팅 // +4. 공통 컴포넌트 및 페이지 제작 +5. 기능별 컴포넌트 생성 및 구현 +6. 기능 테스트 +7. 추가 기능 구현 +8. 기능 테스트 +9. 배포 +10. 개발 문서 수정 +11. 리팩토링 + + + +## 🔸프로그래밍 규칙 + +* 클래스형 컴포넌트 구현 + + + +## 🔸 프로젝트 소개 및 수행 기간 + +* API를 활용하여 문서 자동 저장 편집기를 구현합니다. 필수 기능을 구현하고 추가 기능을 자유롭게 구현할 수 있습니다. + +> - 프로젝트 수행 기간 : 2022년 11월 8일(화) ~ 2022년 11월 16일(수) +> - 멘티 코드 리뷰 기간 : 2022년 11월 17일(목) ~ 2022년 11월 20일(일) +> - 멘토 코드 리뷰 기간 : 2022년 11월 17일(목) ~ 2022년 11월 22일(화) +> - 코드 리뷰 반영 기간 : 2022년 11월 23일(수) ~ 2022년 11월 25일(금) + +## 🔸 컴포넌트 구조 설계 + +### 와이어프레임 + +![yenotion_wireframe (1)](README.assets/yenotion_wireframe (1).png) + +* Document +* DocumentList +* DocumentTree +* DocumentEditor +* 페이지 + +## 🔸 기능 명세 + +### 기본 기능 + +* 메인 페이지 왼쪽에 전체 문서 트리를 출력합니다. + + * 루트 문서를 클릭하면 오른쪽 편집기 영역에 문서 내용을 렌더링합니다. + * 문서 이름이 특정 길이를 넘으면 특정 길이까지 자르고 ... 생략으로 표시한다. + +* 새로운 문서를 생성할 수 있습니다. + + * 각 문서의 우측에 + 버튼이 있고, 해당 버튼을 클릭하면 하위에 새로운 문서 생성되고 편집 화면으로 넘어갑니다. + * 루트 문서 또는 특정 문서 하위에 새로운 문서를 추가할 수 있습니다. + * 추가할 수 있는 문서의 개수는 `몇개`로 제한한다. + +* 문서를 수정하고 저장할 수 있습니다. + + * 문서 저장 버튼을 따로 만들지 않습니다. + * document save api를 이용, 서버에 지속적인 요청을 통해 저장되도록 설정합니다. + +* 문서를 삭제할 수 있습니다. + +* History API를 활용하여 SPA로 제작합니다. + + * 루트 URL: 문서 선택이 아직 안 된 상태를 보여줍니다. + * 편집기에는 기본 페이지 출력 + * /documents/{documentId}: 해당 문서의 내용을 불러와 편집기에 출력 + + + +### 추가 기능 + +> 보너스 요구사항 +> +> - 기본적으로 편집기는 textarea 기반으로 단순한 텍스트 편집기로 시작하되, 여력이 되면 div와 contentEditable을 조합해서 좀 더 Rich한 에디터를 만들어봅니다. +> - 편집기 최하단에는 현재 편집 중인 Document의 하위 Document 링크를 렌더링하도록 추가합니다. +> - 편집기 내에서 다른 Document name을 적은 경우, 자동으로 해당 Document의 편집 페이지로 이동하는 링크를 거는 기능을 추가합니다. +> - 그외 개선하거나 구현했으면 좋겠다는 부분이 있으면 적극적으로 구현해봅니다! + +* 마크다운 편집기 만들기 +* 검색 기능 +* 다크모드 +* 임베드 +* 북마크 +* 하이라이팅 +* 이미지 임베딩 +* 공유하기 +* undo, redo +* 수정이력 출력 + * 과거 상태로 돌아가기 + diff --git a/index.html b/index.html new file mode 100644 index 00000000..7ca636e0 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + YENOTION + + + +
+ + + diff --git a/package.json b/package.json index 9f3132db..605e0bbb 100644 --- a/package.json +++ b/package.json @@ -7,5 +7,6 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "author": "yezyvibe", - "license": "ISC" + "license": "ISC", + "type": "module" } diff --git a/src/App.js b/src/App.js new file mode 100644 index 00000000..eaec39fb --- /dev/null +++ b/src/App.js @@ -0,0 +1,11 @@ +import Header from "./components/Header.js"; +import DocumentEditPage from "./pages/DocumentEditPage.js"; + +console.log("app is running~"); + +function App({ $target }) { + new Header({ $target }); + new DocumentEditPage({ $target }); +} + +export default App; diff --git a/src/components/DocumentEditor.js b/src/components/DocumentEditor.js new file mode 100644 index 00000000..278ae1ba --- /dev/null +++ b/src/components/DocumentEditor.js @@ -0,0 +1,13 @@ +function DocumentEditor({ $target }) { + const $editor = document.createElement("section"); + $editor.className = "document-editor"; + $target.appendChild($editor); + + this.render = () => { + $editor.innerHTML = `

에디터

`; + }; + + this.render(); +} + +export default DocumentEditor; diff --git a/src/components/DocumentInfo.js b/src/components/DocumentInfo.js new file mode 100644 index 00000000..e69de29b diff --git a/src/components/DocumentNode.js b/src/components/DocumentNode.js new file mode 100644 index 00000000..6f0cb850 --- /dev/null +++ b/src/components/DocumentNode.js @@ -0,0 +1,56 @@ +function DocumentNode({ $target, initialState, onClick }) { + const $node = document.createElement("section"); + $node.className = "node"; + $target.appendChild($node); + + this.state = initialState; + + this.setState = (nextState) => { + this.state = nextState; + this.render(); + }; + + this.render = () => { + const { data } = this.state; + console.log(data, "노드"); + + $node.innerHTML = ` +
    + ${data + .map(({ id, title, documents }) => { + new DocumentNode({ + $target, + initialState: { data: documents }, + }); + return ` +
  • + ${title} + +
  • + `; + }) + .join("")} +
+ `; + }; + + this.init = () => { + this.render(); + }; + + this.init(); + + $node.addEventListener("click", (e) => { + const $button = e.target.closest("button"); + + if ($button?.className === "add-button") { + const { parentId } = $button.dataset; + + if (parentId) { + onClick(parseInt(parentId)); + } + } + }); +} + +export default DocumentNode; diff --git a/src/components/DocumentTree.js b/src/components/DocumentTree.js new file mode 100644 index 00000000..cfdc5978 --- /dev/null +++ b/src/components/DocumentTree.js @@ -0,0 +1,53 @@ +import { fetchData } from "../utils/api.js"; +import DocumentNode from "./DocumentNode.js"; + +function DocumentTree({ $target, initialState }) { + const $tree = document.createElement("section"); + $tree.className = "document-tree"; + $target.appendChild($tree); + + this.state = initialState; + + this.setState = (nextState) => { + this.state = nextState; + this.render(); + }; + + this.render = () => { + $tree.innerHTML = ` +

트리

+ `; + + new DocumentNode({ + $target: $tree, + initialState: { data: this.state.data }, + onClick: (parentId) => { + // parent id, 새로운 문서의 title 입력 받기 + const title = prompt("새로운 문서의 제목을 입력해주세요"); + // 새로운 문서 생성 하기 + }, + }); + }; + + const getRootDocuments = async () => { + const data = await fetchData("/documents"); + // const data = await fetchData("/documents", "POST", { + // title: "첫 번째 문서", + // parent: null, + // }); + + this.setState({ + ...this.state, + data, + }); + console.log(this.state.data); + }; + + this.init = () => { + getRootDocuments(); + }; + + this.init(); +} + +export default DocumentTree; diff --git a/src/components/Header.js b/src/components/Header.js new file mode 100644 index 00000000..79e1477b --- /dev/null +++ b/src/components/Header.js @@ -0,0 +1,13 @@ +function Header({ $target }) { + const $title = document.createElement("div"); + $title.className = "header-title"; + $target.appendChild($title); + + this.render = () => { + $title.innerHTML = "

YENOTION

"; + }; + + this.render(); +} + +export default Header; diff --git a/src/index.js b/src/index.js new file mode 100644 index 00000000..ab1e6ea9 --- /dev/null +++ b/src/index.js @@ -0,0 +1,7 @@ +// import * as dotenv from "dotenv"; +import App from "./App.js"; + +new App({ $target: document.querySelector("#App") }); + +// console.log(dotenv); +// console.log(process.env.API_END_POINT); diff --git a/src/pages/DocumentDetailPage.js b/src/pages/DocumentDetailPage.js new file mode 100644 index 00000000..e69de29b diff --git a/src/pages/DocumentEditPage.js b/src/pages/DocumentEditPage.js new file mode 100644 index 00000000..247e5faf --- /dev/null +++ b/src/pages/DocumentEditPage.js @@ -0,0 +1,13 @@ +import DocumentEditor from "../components/DocumentEditor.js"; +import DocumentTree from "../components/DocumentTree.js"; + +function DocumentEditPage({ $target }) { + const $editPage = document.createElement("div"); + $editPage.className = "main-content"; + $target.appendChild($editPage); + + new DocumentTree({ $target: $editPage }); + new DocumentEditor({ $target: $editPage }); +} + +export default DocumentEditPage; diff --git a/src/styles/style.css b/src/styles/style.css new file mode 100644 index 00000000..425fee01 --- /dev/null +++ b/src/styles/style.css @@ -0,0 +1,16 @@ +.main-content { + display: flex; + width: 100vw; + height: 100vh; +} + +.document-editor { + background-color: antiquewhite; + width: 100vw; +} + +.document-tree { + background-color: rgb(227, 250, 215); + width: 30vw; + max-width: 300px; +} diff --git a/src/utils/api.js b/src/utils/api.js new file mode 100644 index 00000000..cc0b9d42 --- /dev/null +++ b/src/utils/api.js @@ -0,0 +1,45 @@ +import { API_END_POINT, HEADERS } from "./constants.js"; + +export const fetchData = async ( + url, + method = "GET", + data, + defaultValue = [] +) => { + try { + const response = await fetch(API_END_POINT + url, { + method, + headers: HEADERS, + data, + }); + + if (response.ok) { + const json = await response.json(); + console.log(json, "데이터 패치 완료"); + return json; + } + + return defaultValue; + } catch (e) { + console.log(e.message); + } +}; + +// export const postData = async (data) => { +// try { +// const response = await fetch(API_END_POINT + url, { +// method: "POST", +// headers: HEADERS, +// data, +// }); + +// if (response.ok) { +// const json = await response.json(); +// return json; +// } + +// return defaultValue; +// } catch (e) { +// console.log(e.message); +// } +// }; diff --git a/src/utils/router.js b/src/utils/router.js new file mode 100644 index 00000000..e69de29b diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..c0b2f6aa --- /dev/null +++ b/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +dotenv@^16.0.3: + version "16.0.3" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" + integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== From d5af6e490043e0f5e8f067e11f9c10a24556ec4d Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Mon, 14 Nov 2022 18:06:46 +0900 Subject: [PATCH 04/24] =?UTF-8?q?feat:=20:sparkles:=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=20=EB=AA=A9=EB=A1=9D=20=ED=8A=B8=EB=A6=AC=20=ED=98=95=ED=83=9C?= =?UTF-8?q?=EB=A1=9C=20=EC=B6=9C=EB=A0=A5,=20=EC=83=88=EB=A1=9C=EC=9A=B4?= =?UTF-8?q?=20=EB=AC=B8=EC=84=9C=20=EC=83=9D=EC=84=B1=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DocumentNode.js | 32 ++++++++++++++++++-------------- src/components/DocumentTree.js | 17 +++++++++-------- src/utils/api.js | 21 +-------------------- 3 files changed, 28 insertions(+), 42 deletions(-) diff --git a/src/components/DocumentNode.js b/src/components/DocumentNode.js index 6f0cb850..53927dbf 100644 --- a/src/components/DocumentNode.js +++ b/src/components/DocumentNode.js @@ -1,6 +1,5 @@ function DocumentNode({ $target, initialState, onClick }) { - const $node = document.createElement("section"); - $node.className = "node"; + const $node = document.createElement("article"); $target.appendChild($node); this.state = initialState; @@ -12,22 +11,27 @@ function DocumentNode({ $target, initialState, onClick }) { this.render = () => { const { data } = this.state; - console.log(data, "노드"); $node.innerHTML = ` -
    +
      ${data .map(({ id, title, documents }) => { - new DocumentNode({ - $target, - initialState: { data: documents }, - }); - return ` -
    • - ${title} - -
    • - `; + const $li = document.createElement("li"); + + $li.className = "node-item"; + $li.innerHTML = ` + ${title}${id} + + `; + + if (documents.length > 0) { + new DocumentNode({ + $target: $li, + initialState: { data: documents }, + }); + } + + return $li.outerHTML; }) .join("")}
    diff --git a/src/components/DocumentTree.js b/src/components/DocumentTree.js index cfdc5978..e9244276 100644 --- a/src/components/DocumentTree.js +++ b/src/components/DocumentTree.js @@ -17,30 +17,31 @@ function DocumentTree({ $target, initialState }) { $tree.innerHTML = `

    트리

    `; - new DocumentNode({ $target: $tree, initialState: { data: this.state.data }, onClick: (parentId) => { - // parent id, 새로운 문서의 title 입력 받기 const title = prompt("새로운 문서의 제목을 입력해주세요"); - // 새로운 문서 생성 하기 + const data = { + title, + parent: parentId, + }; + createNewDocument(data); }, }); }; const getRootDocuments = async () => { const data = await fetchData("/documents"); - // const data = await fetchData("/documents", "POST", { - // title: "첫 번째 문서", - // parent: null, - // }); this.setState({ ...this.state, data, }); - console.log(this.state.data); + }; + + const createNewDocument = async (data) => { + await fetchData("/documents", "POST", data); }; this.init = () => { diff --git a/src/utils/api.js b/src/utils/api.js index cc0b9d42..02b45f3e 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -10,7 +10,7 @@ export const fetchData = async ( const response = await fetch(API_END_POINT + url, { method, headers: HEADERS, - data, + body: JSON.stringify(data), }); if (response.ok) { @@ -24,22 +24,3 @@ export const fetchData = async ( console.log(e.message); } }; - -// export const postData = async (data) => { -// try { -// const response = await fetch(API_END_POINT + url, { -// method: "POST", -// headers: HEADERS, -// data, -// }); - -// if (response.ok) { -// const json = await response.json(); -// return json; -// } - -// return defaultValue; -// } catch (e) { -// console.log(e.message); -// } -// }; From d183a5015c5359ada033178c5fcd7da401f12b90 Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Tue, 15 Nov 2022 00:23:58 +0900 Subject: [PATCH 05/24] =?UTF-8?q?feat:=20:sparkles:=20=EC=84=A0=ED=83=9D?= =?UTF-8?q?=EB=90=9C=20=EB=AC=B8=EC=84=9C=EC=9D=98=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5,=20=ED=8E=B8=EC=A7=91=EA=B8=B0=20=EC=97=B4?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + src/components/DocumentEditor.js | 18 ++++++++-- src/components/DocumentInfo.js | 0 src/components/DocumentNode.js | 18 ++++++++-- src/components/DocumentTree.js | 22 ++++++------ src/pages/DocumentDetailPage.js | 0 src/pages/DocumentEditPage.js | 58 ++++++++++++++++++++++++++++++-- src/styles/style.css | 20 +++++++++++ src/utils/api.js | 8 +---- src/utils/fetchData.js | 15 +++++++++ src/utils/router.js | 19 +++++++++++ 11 files changed, 154 insertions(+), 25 deletions(-) delete mode 100644 src/components/DocumentInfo.js delete mode 100644 src/pages/DocumentDetailPage.js create mode 100644 src/utils/fetchData.js diff --git a/.gitignore b/.gitignore index a56a7ef4..e6d39074 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules +constants.js \ No newline at end of file diff --git a/src/components/DocumentEditor.js b/src/components/DocumentEditor.js index 278ae1ba..3ab1d8fd 100644 --- a/src/components/DocumentEditor.js +++ b/src/components/DocumentEditor.js @@ -1,10 +1,24 @@ -function DocumentEditor({ $target }) { +function DocumentEditor({ $target, initialState }) { const $editor = document.createElement("section"); $editor.className = "document-editor"; $target.appendChild($editor); + this.state = initialState; + + this.setState = (nextState) => { + this.state = nextState; + this.render(); + }; + this.render = () => { - $editor.innerHTML = `

    에디터

    `; + const { title, content, documents } = this.state.data; + + if (!title) return; + + $editor.innerHTML = ` +

    ${title}

    + + `; }; this.render(); diff --git a/src/components/DocumentInfo.js b/src/components/DocumentInfo.js deleted file mode 100644 index e69de29b..00000000 diff --git a/src/components/DocumentNode.js b/src/components/DocumentNode.js index 53927dbf..3b02fe3a 100644 --- a/src/components/DocumentNode.js +++ b/src/components/DocumentNode.js @@ -1,4 +1,6 @@ -function DocumentNode({ $target, initialState, onClick }) { +import { navigate } from "../utils/router.js"; + +function DocumentNode({ $target, initialState, onClick, onSelect }) { const $node = document.createElement("article"); $target.appendChild($node); @@ -17,7 +19,7 @@ function DocumentNode({ $target, initialState, onClick }) { ${data .map(({ id, title, documents }) => { const $li = document.createElement("li"); - + $li.dataset.documentId = id; $li.className = "node-item"; $li.innerHTML = ` ${title}${id} @@ -53,6 +55,18 @@ function DocumentNode({ $target, initialState, onClick }) { if (parentId) { onClick(parseInt(parentId)); } + return; + } + + const $li = e.target.closest("li"); + + if ($li) { + const { documentId } = $li.dataset; + // 여기서 라우터 변경을 한 후 이동한 페이지에서 api 요청해서 문서 정보 불러오기 + if (documentId) { + onSelect(parseInt(documentId)); + navigate(`/document/${documentId}`); + } } }); } diff --git a/src/components/DocumentTree.js b/src/components/DocumentTree.js index e9244276..82ff6dc4 100644 --- a/src/components/DocumentTree.js +++ b/src/components/DocumentTree.js @@ -1,4 +1,8 @@ -import { fetchData } from "../utils/api.js"; +import { + createNewDocument, + getDocumentDetail, + getRootDocuments, +} from "../utils/fetchData.js"; import DocumentNode from "./DocumentNode.js"; function DocumentTree({ $target, initialState }) { @@ -28,26 +32,20 @@ function DocumentTree({ $target, initialState }) { }; createNewDocument(data); }, + onSelect: (documentId) => { + getDocumentDetail(documentId); + }, }); }; - const getRootDocuments = async () => { - const data = await fetchData("/documents"); - + this.init = async () => { + const data = await getRootDocuments(); this.setState({ ...this.state, data, }); }; - const createNewDocument = async (data) => { - await fetchData("/documents", "POST", data); - }; - - this.init = () => { - getRootDocuments(); - }; - this.init(); } diff --git a/src/pages/DocumentDetailPage.js b/src/pages/DocumentDetailPage.js deleted file mode 100644 index e69de29b..00000000 diff --git a/src/pages/DocumentEditPage.js b/src/pages/DocumentEditPage.js index 247e5faf..f77b7998 100644 --- a/src/pages/DocumentEditPage.js +++ b/src/pages/DocumentEditPage.js @@ -1,13 +1,67 @@ import DocumentEditor from "../components/DocumentEditor.js"; import DocumentTree from "../components/DocumentTree.js"; +import { getDocumentDetail } from "../utils/fetchData.js"; +import { handleLocationChange } from "../utils/router.js"; function DocumentEditPage({ $target }) { const $editPage = document.createElement("div"); $editPage.className = "main-content"; $target.appendChild($editPage); - new DocumentTree({ $target: $editPage }); - new DocumentEditor({ $target: $editPage }); + const $tree = document.createElement("section"); + $tree.className = "document-tree"; + $editPage.appendChild($tree); + new DocumentTree({ $target: $tree }); + + const $editor = document.createElement("section"); + $editor.className = "document-editor"; + $editPage.appendChild($editor); + + // this.state = { + // data: {}, + // }; + + // this.setState = (nextState) => { + // this.state = nextState; + // }; + + this.route = () => { + const { pathname } = window.location; + const [, , documentId] = pathname.split("/"); + + if (documentId) { + $editor.innerHTML = ""; + + this.getData(documentId); + + this.documentEditor = new DocumentEditor({ + $target: $editor, + initialState: { + data: {}, + }, + }); + } + }; + + this.getData = async (documentId) => { + const data = await getDocumentDetail(documentId); + this.documentEditor.setState({ + data, + }); + }; + + this.init = () => { + window.addEventListener("onChangeLocation", (e) => + handleLocationChange(e, this.route) + ); + window.addEventListener("popstate", () => { + route(); + }); + + this.route(); + }; + + this.init(); } export default DocumentEditPage; diff --git a/src/styles/style.css b/src/styles/style.css index 425fee01..8180be3d 100644 --- a/src/styles/style.css +++ b/src/styles/style.css @@ -14,3 +14,23 @@ width: 30vw; max-width: 300px; } + +.tree-node { + padding-left: 12px; + border-left: 1px solid lightgray; +} + +.node-item { + list-style: none; + padding-top: 6px; + cursor: pointer; +} + +.add-button { + all: unset; +} + +.text-editor { + width: 500px; + height: 1200px; +} diff --git a/src/utils/api.js b/src/utils/api.js index 02b45f3e..e0b18494 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -1,11 +1,6 @@ import { API_END_POINT, HEADERS } from "./constants.js"; -export const fetchData = async ( - url, - method = "GET", - data, - defaultValue = [] -) => { +export const request = async (url, method = "GET", data, defaultValue = []) => { try { const response = await fetch(API_END_POINT + url, { method, @@ -15,7 +10,6 @@ export const fetchData = async ( if (response.ok) { const json = await response.json(); - console.log(json, "데이터 패치 완료"); return json; } diff --git a/src/utils/fetchData.js b/src/utils/fetchData.js new file mode 100644 index 00000000..715dff09 --- /dev/null +++ b/src/utils/fetchData.js @@ -0,0 +1,15 @@ +import { request } from "./api.js"; + +export const getRootDocuments = async () => { + const data = await request("/documents"); + return data; +}; + +export const createNewDocument = async (data) => { + await request("/documents", "POST", data); +}; + +export const getDocumentDetail = async (documentId) => { + const data = await request(`/documents/${documentId}`); + return data; +}; diff --git a/src/utils/router.js b/src/utils/router.js index e69de29b..c57133e5 100644 --- a/src/utils/router.js +++ b/src/utils/router.js @@ -0,0 +1,19 @@ +export const handleLocationChange = (e, route) => { + const { href } = e.detail; + const { pathname } = window.location; + + if (pathname === href) return; + + window.history.pushState(null, null, href); + route(); +}; + +export const navigate = (url) => { + const locationChangeEvent = new CustomEvent("onChangeLocation", { + composed: true, + detail: { + href: url, + }, + }); + window.dispatchEvent(locationChangeEvent); +}; From 87b736521a1dc7d9604e8e49222ea7110c0563e1 Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Tue, 15 Nov 2022 13:50:20 +0900 Subject: [PATCH 06/24] =?UTF-8?q?feat:=20:sparkles:=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=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/DocumentEditor.js | 33 ++++++++++++++++++++++++++++---- src/components/DocumentNode.js | 2 +- src/components/DocumentTree.js | 10 +++++++++- src/pages/DocumentEditPage.js | 22 ++++++++++++--------- src/styles/style.css | 26 +++++++++++++++++++++---- src/utils/fetchData.js | 22 ++++++++++++++++----- 6 files changed, 91 insertions(+), 24 deletions(-) diff --git a/src/components/DocumentEditor.js b/src/components/DocumentEditor.js index 3ab1d8fd..b9af1eab 100644 --- a/src/components/DocumentEditor.js +++ b/src/components/DocumentEditor.js @@ -1,4 +1,4 @@ -function DocumentEditor({ $target, initialState }) { +function DocumentEditor({ $target, initialState, onChange }) { const $editor = document.createElement("section"); $editor.className = "document-editor"; $target.appendChild($editor); @@ -6,6 +6,7 @@ function DocumentEditor({ $target, initialState }) { this.state = initialState; this.setState = (nextState) => { + console.log(nextState); this.state = nextState; this.render(); }; @@ -13,15 +14,39 @@ function DocumentEditor({ $target, initialState }) { this.render = () => { const { title, content, documents } = this.state.data; - if (!title) return; + // 로딩 처리 해주기 + console.log(content, "내용 수정됨"); $editor.innerHTML = ` -

    ${title}

    - + + `; }; this.render(); + + $editor.addEventListener("keyup", (e) => { + const $textarea = e.target.closest("textarea"); + + if ($textarea) { + const $title = document.querySelector(".editor-document-title"); + + onChange($title.value, $textarea.value); + return; + } + + const $input = e.target.closest("input"); + + if ($input) { + const $content = document.querySelector(".editor-text"); + + onChange($input.value, $content.value); + } + }); } export default DocumentEditor; diff --git a/src/components/DocumentNode.js b/src/components/DocumentNode.js index 3b02fe3a..d0b03793 100644 --- a/src/components/DocumentNode.js +++ b/src/components/DocumentNode.js @@ -23,7 +23,7 @@ function DocumentNode({ $target, initialState, onClick, onSelect }) { $li.className = "node-item"; $li.innerHTML = ` ${title}${id} - + `; if (documents.length > 0) { diff --git a/src/components/DocumentTree.js b/src/components/DocumentTree.js index 82ff6dc4..37946166 100644 --- a/src/components/DocumentTree.js +++ b/src/components/DocumentTree.js @@ -26,10 +26,18 @@ function DocumentTree({ $target, initialState }) { initialState: { data: this.state.data }, onClick: (parentId) => { const title = prompt("새로운 문서의 제목을 입력해주세요"); + + if (title === null) return; + if (!title.trim()) { + alert("올바른 제목을 입력해주세요."); + return; + } + const data = { - title, + title: title.trim(), parent: parentId, }; + createNewDocument(data); }, onSelect: (documentId) => { diff --git a/src/pages/DocumentEditPage.js b/src/pages/DocumentEditPage.js index f77b7998..6232d096 100644 --- a/src/pages/DocumentEditPage.js +++ b/src/pages/DocumentEditPage.js @@ -1,6 +1,6 @@ import DocumentEditor from "../components/DocumentEditor.js"; import DocumentTree from "../components/DocumentTree.js"; -import { getDocumentDetail } from "../utils/fetchData.js"; +import { getDocumentDetail, updateDocument } from "../utils/fetchData.js"; import { handleLocationChange } from "../utils/router.js"; function DocumentEditPage({ $target }) { @@ -17,14 +17,6 @@ function DocumentEditPage({ $target }) { $editor.className = "document-editor"; $editPage.appendChild($editor); - // this.state = { - // data: {}, - // }; - - // this.setState = (nextState) => { - // this.state = nextState; - // }; - this.route = () => { const { pathname } = window.location; const [, , documentId] = pathname.split("/"); @@ -39,10 +31,22 @@ function DocumentEditPage({ $target }) { initialState: { data: {}, }, + onChange: (newTitle, newContent) => { + updateDocument(documentId, { + title: newTitle, + content: newContent, + }); + + this.getData(documentId); + }, }); } }; + // this.updateData = async (documentId, newData) => { + // const data = await updateDocument(documentId, newData); + // }; + this.getData = async (documentId) => { const data = await getDocumentDetail(documentId); this.documentEditor.setState({ diff --git a/src/styles/style.css b/src/styles/style.css index 8180be3d..2e81da61 100644 --- a/src/styles/style.css +++ b/src/styles/style.css @@ -22,15 +22,33 @@ .node-item { list-style: none; - padding-top: 6px; + margin-top: 6px; cursor: pointer; } .add-button { all: unset; + color: gray; + background-color: lightblue; + width: 12px; + padding: 0 3px; + border-radius: 4px; } -.text-editor { - width: 500px; - height: 1200px; +.editor-text { + width: 100vw; + height: 100vh; +} + +.document-editor { + display: flex; + flex-direction: column; +} + +.editor-document-title { + height: 50px; + font-size: x-large; + font-weight: bold; + padding-left: 10px; + border: none; } diff --git a/src/utils/fetchData.js b/src/utils/fetchData.js index 715dff09..d525bb09 100644 --- a/src/utils/fetchData.js +++ b/src/utils/fetchData.js @@ -1,15 +1,27 @@ import { request } from "./api.js"; export const getRootDocuments = async () => { - const data = await request("/documents"); - return data; + const response = await request("/documents"); + return response; }; export const createNewDocument = async (data) => { - await request("/documents", "POST", data); + const response = await request("/documents", "POST", data); + return response; }; export const getDocumentDetail = async (documentId) => { - const data = await request(`/documents/${documentId}`); - return data; + const response = await request(`/documents/${documentId}`); + return response; +}; + +export const updateDocument = async (documentId, data) => { + const response = await request(`/documents/${documentId}`, "PUT", data); + console.log(response); + return response; +}; + +export const deleteDocumet = async (documentId) => { + const response = await request(`/documents/${documentId}`, "DELETE"); + console.log(response); }; From d5ac93e70553fd702734ba782b70e368314d6880 Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Tue, 15 Nov 2022 14:28:57 +0900 Subject: [PATCH 07/24] =?UTF-8?q?feat:=20:sparkles:=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=94=94?= =?UTF-8?q?=EB=B0=94=EC=9A=B4=EC=8B=B1=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DocumentEditor.js | 20 ++++++++++++++++---- src/pages/DocumentEditPage.js | 4 ---- src/styles/style.css | 1 + 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/components/DocumentEditor.js b/src/components/DocumentEditor.js index b9af1eab..f67170ec 100644 --- a/src/components/DocumentEditor.js +++ b/src/components/DocumentEditor.js @@ -3,10 +3,11 @@ function DocumentEditor({ $target, initialState, onChange }) { $editor.className = "document-editor"; $target.appendChild($editor); + this.debounce = null; + this.state = initialState; this.setState = (nextState) => { - console.log(nextState); this.state = nextState; this.render(); }; @@ -16,7 +17,6 @@ function DocumentEditor({ $target, initialState, onChange }) { // 로딩 처리 해주기 - console.log(content, "내용 수정됨"); $editor.innerHTML = ` { + onChange($title.value, $textarea.value); + }, 5000); + return; } @@ -43,8 +50,13 @@ function DocumentEditor({ $target, initialState, onChange }) { if ($input) { const $content = document.querySelector(".editor-text"); + if (this.debounce) { + clearTimeout(this.debounce); + } - onChange($input.value, $content.value); + this.debounce = setTimeout(() => { + onChange($input.value, $content.value); + }, 5000); } }); } diff --git a/src/pages/DocumentEditPage.js b/src/pages/DocumentEditPage.js index 6232d096..f8378542 100644 --- a/src/pages/DocumentEditPage.js +++ b/src/pages/DocumentEditPage.js @@ -43,10 +43,6 @@ function DocumentEditPage({ $target }) { } }; - // this.updateData = async (documentId, newData) => { - // const data = await updateDocument(documentId, newData); - // }; - this.getData = async (documentId) => { const data = await getDocumentDetail(documentId); this.documentEditor.setState({ diff --git a/src/styles/style.css b/src/styles/style.css index 2e81da61..803d1a8f 100644 --- a/src/styles/style.css +++ b/src/styles/style.css @@ -38,6 +38,7 @@ .editor-text { width: 100vw; height: 100vh; + font-size: 20px; } .document-editor { From fd0c4c4a045fc7d571e55b5dbc74ac81676255fd Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Tue, 15 Nov 2022 15:29:37 +0900 Subject: [PATCH 08/24] =?UTF-8?q?feat:=20:sparkles:=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5,=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EB=A1=9C=EB=94=A9=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DocumentEditor.js | 7 +++- src/components/DocumentNode.js | 14 ++++++-- src/components/DocumentTree.js | 56 +++++++++++++++++++++++--------- src/styles/style.css | 13 +++++--- 4 files changed, 66 insertions(+), 24 deletions(-) diff --git a/src/components/DocumentEditor.js b/src/components/DocumentEditor.js index f67170ec..936cde5d 100644 --- a/src/components/DocumentEditor.js +++ b/src/components/DocumentEditor.js @@ -15,7 +15,12 @@ function DocumentEditor({ $target, initialState, onChange }) { this.render = () => { const { title, content, documents } = this.state.data; - // 로딩 처리 해주기 + if (!title && !content) { + $editor.innerHTML = ` +

    loading..

    + `; + return; + } $editor.innerHTML = ` - `; @@ -58,6 +59,15 @@ function DocumentNode({ $target, initialState, onClick, onSelect }) { return; } + if ($button?.className === "delete-button") { + const { parentId } = $button.dataset; + + if (parentId) { + onDelete(parseInt(parentId)); + } + return; + } + const $li = e.target.closest("li"); if ($li) { diff --git a/src/components/DocumentTree.js b/src/components/DocumentTree.js index 37946166..8e6c748e 100644 --- a/src/components/DocumentTree.js +++ b/src/components/DocumentTree.js @@ -1,5 +1,6 @@ import { createNewDocument, + deleteDocumet, getDocumentDetail, getRootDocuments, } from "../utils/fetchData.js"; @@ -19,34 +20,31 @@ function DocumentTree({ $target, initialState }) { this.render = () => { $tree.innerHTML = ` -

    트리

    +
    +

    문서 목록

    + +
    `; new DocumentNode({ $target: $tree, initialState: { data: this.state.data }, - onClick: (parentId) => { - const title = prompt("새로운 문서의 제목을 입력해주세요"); + onClick: (parentId) => this.onClick(parentId), + onDelete: (documentId) => { + const answer = confirm("문서를 정말 삭제하시겠습니까?"); - if (title === null) return; - if (!title.trim()) { - alert("올바른 제목을 입력해주세요."); - return; + if (answer) { + deleteDocumet(documentId); + this.init(); } - - const data = { - title: title.trim(), - parent: parentId, - }; - - createNewDocument(data); }, + onSelect: (documentId) => { getDocumentDetail(documentId); }, }); }; - this.init = async () => { + this.getData = async () => { const data = await getRootDocuments(); this.setState({ ...this.state, @@ -54,7 +52,33 @@ function DocumentTree({ $target, initialState }) { }); }; - this.init(); + $tree.addEventListener("click", (e) => { + const $button = e.target.closest("button"); + + if ($button?.className === "add-root-button") { + this.onClick(null); + } + }); + + this.onClick = (parentId) => { + const title = prompt("새로운 문서의 제목을 입력해주세요"); + + if (title === null) return; + if (!title.trim()) { + alert("올바른 제목을 입력해주세요."); + return; + } + + const data = { + title: title.trim(), + parent: parentId, + }; + + createNewDocument(data); + this.getData(); + }; + + this.getData(); } export default DocumentTree; diff --git a/src/styles/style.css b/src/styles/style.css index 803d1a8f..9d2b217e 100644 --- a/src/styles/style.css +++ b/src/styles/style.css @@ -26,13 +26,16 @@ cursor: pointer; } -.add-button { - all: unset; - color: gray; +.tree-subtitle-wrapper { + display: flex; +} + +.add-root-button, +.add-button, +.delete-button { background-color: lightblue; - width: 12px; - padding: 0 3px; border-radius: 4px; + border: none; } .editor-text { From b0ce8ebc435c826993e652bac7d763fb70124731 Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Tue, 15 Nov 2022 16:55:01 +0900 Subject: [PATCH 09/24] =?UTF-8?q?feat:=20:sparkles:=20=ED=98=84=EC=9E=AC?= =?UTF-8?q?=20=ED=8E=B8=EC=A7=91=EC=A4=91=EC=9D=B8=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=EC=9D=98=20=ED=95=98=EC=9C=84=20=EB=AC=B8=EC=84=9C=EB=93=A4?= =?UTF-8?q?=EC=9D=98=20=EB=A7=81=ED=81=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 49 ++++++++++++++++---------------- src/components/DocumentEditor.js | 8 +++++- src/components/DocumentTree.js | 3 +- src/pages/DocumentEditPage.js | 14 ++++++--- src/styles/style.css | 16 +++++++++-- src/utils/fetchData.js | 1 - 6 files changed, 57 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 1043854b..737e03d5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ 1. 과제 명세 재정의, 리드미 수정 2. 와이어프레임, 컴포넌트 구조 설계 -3. 프로젝트 초기 셋팅 // +3. 프로젝트 초기 셋팅 4. 공통 컴포넌트 및 페이지 제작 5. 기능별 컴포넌트 생성 및 구현 6. 기능 테스트 @@ -49,40 +49,39 @@ ### 기본 기능 -* 메인 페이지 왼쪽에 전체 문서 트리를 출력합니다. +- [x] 메인 페이지 왼쪽에 전체 문서 트리를 출력합니다. + - 루트 문서를 클릭하면 오른쪽 편집기 영역에 문서 내용을 렌더링합니다. + - **문서 이름이 특정 길이를 넘으면 특정 길이까지 자르고 ... 생략으로 표시한다.** - * 루트 문서를 클릭하면 오른쪽 편집기 영역에 문서 내용을 렌더링합니다. - * 문서 이름이 특정 길이를 넘으면 특정 길이까지 자르고 ... 생략으로 표시한다. +- [x] 새로운 문서를 생성할 수 있습니다. + - 각 문서의 우측에 + 버튼이 있고, 해당 버튼을 클릭하면 하위에 새로운 문서 생성되고 편집 화면으로 넘어갑니다. + - 루트 문서 또는 특정 문서 하위에 새로운 문서를 추가할 수 있습니다. + - **추가할 수 있는 문서의 개수는 `몇개`로 제한한다.** -* 새로운 문서를 생성할 수 있습니다. +- [x] 문서를 수정하고 저장할 수 있습니다. + - 문서 저장 버튼을 따로 만들지 않습니다. + - document save api를 이용, 서버에 지속적인 요청을 통해 저장되도록 설정합니다. - * 각 문서의 우측에 + 버튼이 있고, 해당 버튼을 클릭하면 하위에 새로운 문서 생성되고 편집 화면으로 넘어갑니다. - * 루트 문서 또는 특정 문서 하위에 새로운 문서를 추가할 수 있습니다. - * 추가할 수 있는 문서의 개수는 `몇개`로 제한한다. +- [x] 문서를 삭제할 수 있습니다. -* 문서를 수정하고 저장할 수 있습니다. +- [x] History API를 활용하여 SPA로 제작합니다. + - 루트 URL: 문서 선택이 아직 안 된 상태를 보여줍니다. + * 편집기에는 기본 페이지 출력 + - /documents/{documentId}: 해당 문서의 내용을 불러와 편집기에 출력 - * 문서 저장 버튼을 따로 만들지 않습니다. - * document save api를 이용, 서버에 지속적인 요청을 통해 저장되도록 설정합니다. + -* 문서를 삭제할 수 있습니다. +### 추가 기능 -* History API를 활용하여 SPA로 제작합니다. +[과제 제안] - * 루트 URL: 문서 선택이 아직 안 된 상태를 보여줍니다. - * 편집기에는 기본 페이지 출력 - * /documents/{documentId}: 해당 문서의 내용을 불러와 편집기에 출력 +- [ ] 기본적으로 편집기는 textarea 기반으로 단순한 텍스트 편집기로 시작하되, 여력이 되면 div와 contentEditable을 조합해서 좀 더 Rich한 에디터를 만들어봅니다. - +- [x] 편집기 최하단에는 현재 편집 중인 Document의 하위 Document 링크를 렌더링하도록 추가합니다. -### 추가 기능 +- [ ] 편집기 내에서 다른 Document name을 적은 경우, 자동으로 해당 Document의 편집 페이지로 이동하는 링크를 거는 기능을 추가합니다. -> 보너스 요구사항 -> -> - 기본적으로 편집기는 textarea 기반으로 단순한 텍스트 편집기로 시작하되, 여력이 되면 div와 contentEditable을 조합해서 좀 더 Rich한 에디터를 만들어봅니다. -> - 편집기 최하단에는 현재 편집 중인 Document의 하위 Document 링크를 렌더링하도록 추가합니다. -> - 편집기 내에서 다른 Document name을 적은 경우, 자동으로 해당 Document의 편집 페이지로 이동하는 링크를 거는 기능을 추가합니다. -> - 그외 개선하거나 구현했으면 좋겠다는 부분이 있으면 적극적으로 구현해봅니다! +[기타] * 마크다운 편집기 만들기 * 검색 기능 @@ -94,5 +93,5 @@ * 공유하기 * undo, redo * 수정이력 출력 - * 과거 상태로 돌아가기 +* 과거 상태로 돌아가기 diff --git a/src/components/DocumentEditor.js b/src/components/DocumentEditor.js index 936cde5d..07b5ec07 100644 --- a/src/components/DocumentEditor.js +++ b/src/components/DocumentEditor.js @@ -14,7 +14,7 @@ function DocumentEditor({ $target, initialState, onChange }) { this.render = () => { const { title, content, documents } = this.state.data; - + console.log(documents); if (!title && !content) { $editor.innerHTML = `

    loading..

    @@ -29,6 +29,12 @@ function DocumentEditor({ $target, initialState, onChange }) { +
    +

    📌하위 문서 목록

    + ${documents + .map(({ id, title }) => `${title}`) + .join("")} +
    `; }; diff --git a/src/components/DocumentTree.js b/src/components/DocumentTree.js index 8e6c748e..3bc08cfb 100644 --- a/src/components/DocumentTree.js +++ b/src/components/DocumentTree.js @@ -34,7 +34,8 @@ function DocumentTree({ $target, initialState }) { if (answer) { deleteDocumet(documentId); - this.init(); + // 비동기 처리 문제, 다시 확인하기 + this.getData(); } }, diff --git a/src/pages/DocumentEditPage.js b/src/pages/DocumentEditPage.js index f8378542..8af15bad 100644 --- a/src/pages/DocumentEditPage.js +++ b/src/pages/DocumentEditPage.js @@ -11,7 +11,8 @@ function DocumentEditPage({ $target }) { const $tree = document.createElement("section"); $tree.className = "document-tree"; $editPage.appendChild($tree); - new DocumentTree({ $target: $tree }); + + const documentTree = new DocumentTree({ $target: $tree }); const $editor = document.createElement("section"); $editor.className = "document-editor"; @@ -32,12 +33,10 @@ function DocumentEditPage({ $target }) { data: {}, }, onChange: (newTitle, newContent) => { - updateDocument(documentId, { + this.updateData(documentId, { title: newTitle, content: newContent, }); - - this.getData(documentId); }, }); } @@ -50,6 +49,13 @@ function DocumentEditPage({ $target }) { }); }; + this.updateData = async (documentId, data) => { + await updateDocument(documentId, data); + this.getData(documentId); + documentTree.getData(); + return; + }; + this.init = () => { window.addEventListener("onChangeLocation", (e) => handleLocationChange(e, this.route) diff --git a/src/styles/style.css b/src/styles/style.css index 9d2b217e..b9ba0488 100644 --- a/src/styles/style.css +++ b/src/styles/style.css @@ -28,19 +28,26 @@ .tree-subtitle-wrapper { display: flex; + justify-content: space-between; + padding: 8px; +} + +.add-root-button { + all: unset; + cursor: pointer; } -.add-root-button, .add-button, .delete-button { background-color: lightblue; border-radius: 4px; border: none; + cursor: pointer; } .editor-text { width: 100vw; - height: 100vh; + height: 80vh; font-size: 20px; } @@ -56,3 +63,8 @@ padding-left: 10px; border: none; } + +.child-documents-wrapper { + display: flex; + flex-direction: column; +} diff --git a/src/utils/fetchData.js b/src/utils/fetchData.js index d525bb09..df72e3fa 100644 --- a/src/utils/fetchData.js +++ b/src/utils/fetchData.js @@ -17,7 +17,6 @@ export const getDocumentDetail = async (documentId) => { export const updateDocument = async (documentId, data) => { const response = await request(`/documents/${documentId}`, "PUT", data); - console.log(response); return response; }; From 64ca28a34bbdaf43b48cc9505875b676a1a73956 Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Tue, 15 Nov 2022 18:30:55 +0900 Subject: [PATCH 10/24] =?UTF-8?q?feat:=20:sparkles:=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=20=EB=82=B4=EC=9A=A9=20=EB=A7=88=ED=81=AC=EB=8B=A4=EC=9A=B4?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DocumentEditor.js | 48 +++++++++++++++++++----- src/styles/style.css | 21 ++++++++++- src/utils/parseMarkdown.js | 63 ++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 10 deletions(-) create mode 100644 src/utils/parseMarkdown.js diff --git a/src/components/DocumentEditor.js b/src/components/DocumentEditor.js index 07b5ec07..946d56ac 100644 --- a/src/components/DocumentEditor.js +++ b/src/components/DocumentEditor.js @@ -1,3 +1,5 @@ +import { parseMarkdown } from "../utils/parseMarkdown.js"; + function DocumentEditor({ $target, initialState, onChange }) { const $editor = document.createElement("section"); $editor.className = "document-editor"; @@ -14,7 +16,7 @@ function DocumentEditor({ $target, initialState, onChange }) { this.render = () => { const { title, content, documents } = this.state.data; - console.log(documents); + if (!title && !content) { $editor.innerHTML = `

    loading..

    @@ -26,9 +28,15 @@ function DocumentEditor({ $target, initialState, onChange }) { - +
    +
    ${ + content ? content : "아직 내용이 없습니다." + }
    +
    + ${parseMarkdown(content)} +
    +
    +

    📌하위 문서 목록

    ${documents @@ -40,10 +48,31 @@ function DocumentEditor({ $target, initialState, onChange }) { this.render(); + //
    + // 모달 + // + //
    + + // const mentionPage = () => { + // console.log("페이지 멘션"); + // const $modal = document.querySelector(".mention-modal"); + // const $input = document.querySelector(".mention-modal-input"); + // $modal.classList.add("activate"); + // $input.autofocus = "true"; + + // $input.addEventListener("keyup", (e) => { + + // }) + // }; + // if (e.key === "@") { + // mentionPage(); + // return; + // } + $editor.addEventListener("keyup", (e) => { - const $textarea = e.target.closest("textarea"); + const $div = e.target.closest("div"); - if ($textarea) { + if ($div) { const $title = document.querySelector(".editor-document-title"); if (this.debounce) { @@ -51,8 +80,8 @@ function DocumentEditor({ $target, initialState, onChange }) { } this.debounce = setTimeout(() => { - onChange($title.value, $textarea.value); - }, 5000); + onChange($title.value, $div.innerText); + }, 2000); return; } @@ -61,13 +90,14 @@ function DocumentEditor({ $target, initialState, onChange }) { if ($input) { const $content = document.querySelector(".editor-text"); + if (this.debounce) { clearTimeout(this.debounce); } this.debounce = setTimeout(() => { onChange($input.value, $content.value); - }, 5000); + }, 2000); } }); } diff --git a/src/styles/style.css b/src/styles/style.css index b9ba0488..3a40fdcf 100644 --- a/src/styles/style.css +++ b/src/styles/style.css @@ -44,11 +44,16 @@ border: none; cursor: pointer; } +.eidtor-wrapper { + display: flex; + width: 100vw; +} .editor-text { - width: 100vw; + width: 50vw; height: 80vh; font-size: 20px; + padding: 16px; } .document-editor { @@ -68,3 +73,17 @@ display: flex; flex-direction: column; } + +.modal { + display: none; +} + +.activate { + display: block; + width: 200px; + height: 50px; + position: fixed; + top: 50%; + left: 50%; + background-color: lightblue; +} diff --git a/src/utils/parseMarkdown.js b/src/utils/parseMarkdown.js new file mode 100644 index 00000000..0c6cd0a9 --- /dev/null +++ b/src/utils/parseMarkdown.js @@ -0,0 +1,63 @@ +export const parseMarkdown = (text) => { + //ul + console.log(text); + text = text.replace(/^\s*\n\*/gm, "
      \n*"); + text = text.replace(/^(\*.+)\s*\n([^\*])/gm, "$1\n
    \n\n$2"); + text = text.replace(/^\*(.+)/gm, "
  • $1
  • "); + + //ol + text = text.replace(/^\s*\n\d\./gm, "
      \n1."); + text = text.replace(/^(\d\..+)\s*\n([^\d\.])/gm, "$1\n
    \n\n$2"); + text = text.replace(/^\d\.(.+)/gm, "
  • $1
  • "); + + //blockquote + text = text.replace(/^\>(.+)/gm, "
    $1
    "); + + //h + text = text.replace(/[\#]{6}(.+)/g, "
    $1
    "); + text = text.replace(/[\#]{5}(.+)/g, "
    $1
    "); + text = text.replace(/[\#]{4}(.+)/g, "

    $1

    "); + text = text.replace(/[\#]{3}(.+)/g, "

    $1

    "); + text = text.replace(/[\#]{2}(.+)/g, "

    $1

    "); + text = text.replace(/[\#]{1}(.+)/g, "

    $1

    "); + + //alt h + text = text.replace(/^(.+)\n\=+/gm, "

    $1

    "); + text = text.replace(/^(.+)\n\-+/gm, "

    $1

    "); + + //images + text = text.replace( + /\!\[([^\]]+)\]\(([^\)]+)\)/g, + '$1' + ); + + //links + text = text.replace( + /[\[]{1}([^\]]+)[\]]{1}[\(]{1}([^\)\"]+)(\"(.+)\")?[\)]{1}/g, + '$1' + ); + + //font styles + text = text.replace(/[\*\_]{2}([^\*\_]+)[\*\_]{2}/g, "$1"); + text = text.replace(/[\*\_]{1}([^\*\_]+)[\*\_]{1}/g, "$1"); + text = text.replace(/[\~]{2}([^\~]+)[\~]{2}/g, "$1"); + + //pre + text = text.replace(/^\s*\n\`\`\`(([^\s]+))?/gm, '
    ');
    +  text = text.replace(/^\`\`\`\s*\n/gm, "
    \n\n"); + + //code + text = text.replace(/[\`]{1}([^\`]+)[\`]{1}/g, "$1"); + + //p + text = text.replace(/^\s*(\n)?(.+)/gm, function (m) { + return /\<(\/)?(h\d|ul|ol|li|blockquote|pre|img)/.test(m) + ? m + : "

    " + m + "

    "; + }); + + //strip p from pre + text = text.replace(/(\)\s*\n\(.+)\<\/p\>/gm, "$1$2"); + + return text; +}; From b12dcc94f1f60fc62cd8066d1da46972018ecb47 Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Tue, 15 Nov 2022 22:11:19 +0900 Subject: [PATCH 11/24] =?UTF-8?q?feat:=20:lipstick:=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=20=ED=8A=B8=EB=A6=AC=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=8F=20css=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 4 +++ src/components/DocumentEditor.js | 32 ++++++-------------- src/components/DocumentNode.js | 29 +++++++++++++----- src/components/DocumentTree.js | 8 ++--- src/styles/style.css | 52 ++++++++++++++++++++++++-------- src/utils/parseMarkdown.js | 1 - 6 files changed, 79 insertions(+), 47 deletions(-) diff --git a/index.html b/index.html index 7ca636e0..c22506a9 100644 --- a/index.html +++ b/index.html @@ -4,6 +4,10 @@ YENOTION +
    diff --git a/src/components/DocumentEditor.js b/src/components/DocumentEditor.js index 946d56ac..9fc981eb 100644 --- a/src/components/DocumentEditor.js +++ b/src/components/DocumentEditor.js @@ -14,8 +14,16 @@ function DocumentEditor({ $target, initialState, onChange }) { this.render(); }; + const parseContent = (text) => { + console.log(text); + const a = text.replace("/n", "
    "); + console.log(text, "변경후"); + return a; + }; + this.render = () => { const { title, content, documents } = this.state.data; + console.log(content); if (!title && !content) { $editor.innerHTML = ` @@ -30,7 +38,7 @@ function DocumentEditor({ $target, initialState, onChange }) { }"/>
    ${ - content ? content : "아직 내용이 없습니다." + content ? parseContent(content) : "아직 내용이 없습니다." }
    ${parseMarkdown(content)} @@ -48,27 +56,6 @@ function DocumentEditor({ $target, initialState, onChange }) { this.render(); - //
    - // 모달 - // - //
    - - // const mentionPage = () => { - // console.log("페이지 멘션"); - // const $modal = document.querySelector(".mention-modal"); - // const $input = document.querySelector(".mention-modal-input"); - // $modal.classList.add("activate"); - // $input.autofocus = "true"; - - // $input.addEventListener("keyup", (e) => { - - // }) - // }; - // if (e.key === "@") { - // mentionPage(); - // return; - // } - $editor.addEventListener("keyup", (e) => { const $div = e.target.closest("div"); @@ -80,6 +67,7 @@ function DocumentEditor({ $target, initialState, onChange }) { } this.debounce = setTimeout(() => { + console.log($div.innerText); onChange($title.value, $div.innerText); }, 2000); diff --git a/src/components/DocumentNode.js b/src/components/DocumentNode.js index 95487865..cec75c3d 100644 --- a/src/components/DocumentNode.js +++ b/src/components/DocumentNode.js @@ -13,7 +13,6 @@ function DocumentNode({ $target, initialState, onClick, onDelete, onSelect }) { this.render = () => { const { data } = this.state; - $node.innerHTML = `
      ${data @@ -21,16 +20,32 @@ function DocumentNode({ $target, initialState, onClick, onDelete, onSelect }) { const $li = document.createElement("li"); $li.dataset.documentId = id; $li.className = "node-item"; + $li.innerHTML = ` - ${title} - - +
      +
      + keyboard_arrow_right + ${title} +
      +
      + + +
      +
      `; if (documents.length > 0) { - new DocumentNode({ + this.documentNode = new DocumentNode({ $target: $li, - initialState: { data: documents }, + initialState: { data: documents, isDisplay: false }, }); } @@ -74,7 +89,7 @@ function DocumentNode({ $target, initialState, onClick, onDelete, onSelect }) { const { documentId } = $li.dataset; // 여기서 라우터 변경을 한 후 이동한 페이지에서 api 요청해서 문서 정보 불러오기 if (documentId) { - onSelect(parseInt(documentId)); + // onSelect(parseInt(documentId)); navigate(`/document/${documentId}`); } } diff --git a/src/components/DocumentTree.js b/src/components/DocumentTree.js index 3bc08cfb..69f29716 100644 --- a/src/components/DocumentTree.js +++ b/src/components/DocumentTree.js @@ -27,7 +27,7 @@ function DocumentTree({ $target, initialState }) { `; new DocumentNode({ $target: $tree, - initialState: { data: this.state.data }, + initialState: { data: this.state.data, isDisplay: false }, onClick: (parentId) => this.onClick(parentId), onDelete: (documentId) => { const answer = confirm("문서를 정말 삭제하시겠습니까?"); @@ -39,9 +39,9 @@ function DocumentTree({ $target, initialState }) { } }, - onSelect: (documentId) => { - getDocumentDetail(documentId); - }, + // onSelect: (documentId) => { + // getDocumentDetail(documentId); + // }, }); }; diff --git a/src/styles/style.css b/src/styles/style.css index 3a40fdcf..6965c1bb 100644 --- a/src/styles/style.css +++ b/src/styles/style.css @@ -26,6 +26,13 @@ cursor: pointer; } +.tree-node-li { + display: flex; + align-items: center; + justify-content: space-between; + max-width: 300px; +} + .tree-subtitle-wrapper { display: flex; justify-content: space-between; @@ -37,13 +44,42 @@ cursor: pointer; } +.node-title { + display: flex; +} + +.node-title span { + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.node-button-group { + display: flex; +} + .add-button, .delete-button { - background-color: lightblue; - border-radius: 4px; - border: none; + all: unset; cursor: pointer; + width: 20px; + height: 20px; + text-align: center; + border-radius: 4px; +} + +.add-button:hover, +.delete-button:hover { + background-color: lightgray; } + +.add-button span, +.delete-button span { + font-size: small; + color: black; +} + .eidtor-wrapper { display: flex; width: 100vw; @@ -77,13 +113,3 @@ .modal { display: none; } - -.activate { - display: block; - width: 200px; - height: 50px; - position: fixed; - top: 50%; - left: 50%; - background-color: lightblue; -} diff --git a/src/utils/parseMarkdown.js b/src/utils/parseMarkdown.js index 0c6cd0a9..0e2a93bd 100644 --- a/src/utils/parseMarkdown.js +++ b/src/utils/parseMarkdown.js @@ -1,6 +1,5 @@ export const parseMarkdown = (text) => { //ul - console.log(text); text = text.replace(/^\s*\n\*/gm, "
        \n*"); text = text.replace(/^(\*.+)\s*\n([^\*])/gm, "$1\n
      \n\n$2"); text = text.replace(/^\*(.+)/gm, "
    • $1
    • "); From 854774c429f6fd08dac10089382bb0bf8f0a8f5b Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Wed, 16 Nov 2022 17:15:36 +0900 Subject: [PATCH 12/24] =?UTF-8?q?refactor:=20:recycle:=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=ED=95=A8=EC=88=98=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=ED=95=A8=EC=88=98=20=EB=B6=84=EB=A6=AC?= =?UTF-8?q?,=20=EB=AC=B8=EC=84=9C=20=EC=82=AD=EC=A0=9C=20=EC=8B=9C=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EA=B0=92=20=EB=B3=80=EA=B2=BD=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++-- src/components/DocumentEditor.js | 15 ++------- src/components/DocumentNode.js | 58 +++++++++++++++++++------------- src/components/DocumentTree.js | 39 +++++++++++---------- src/index.js | 4 --- src/pages/DocumentEditPage.js | 28 ++++++++------- 6 files changed, 76 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 737e03d5..ce87739d 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ - [x] 문서를 삭제할 수 있습니다. -- [x] History API를 활용하여 SPA로 제작합니다. +- [ ] History API를 활용하여 SPA로 제작합니다. - 루트 URL: 문서 선택이 아직 안 된 상태를 보여줍니다. * 편집기에는 기본 페이지 출력 - /documents/{documentId}: 해당 문서의 내용을 불러와 편집기에 출력 @@ -77,9 +77,9 @@ - [ ] 기본적으로 편집기는 textarea 기반으로 단순한 텍스트 편집기로 시작하되, 여력이 되면 div와 contentEditable을 조합해서 좀 더 Rich한 에디터를 만들어봅니다. -- [x] 편집기 최하단에는 현재 편집 중인 Document의 하위 Document 링크를 렌더링하도록 추가합니다. +- [x] 편집기 최하단에는 현재 편집 중인 문서의 하위 문서들의 링크를 추가. -- [ ] 편집기 내에서 다른 Document name을 적은 경우, 자동으로 해당 Document의 편집 페이지로 이동하는 링크를 거는 기능을 추가합니다. +- [ ] `페이지 멘션`: 편집기 내에서 @시작해서 다른 문서의 title을 적은 경우, 자동으로 해당 Document의 편집 페이지로 이동하는 링크를 거는 기능을 추가합니다. [기타] diff --git a/src/components/DocumentEditor.js b/src/components/DocumentEditor.js index 9fc981eb..1081c00f 100644 --- a/src/components/DocumentEditor.js +++ b/src/components/DocumentEditor.js @@ -14,16 +14,8 @@ function DocumentEditor({ $target, initialState, onChange }) { this.render(); }; - const parseContent = (text) => { - console.log(text); - const a = text.replace("/n", "
      "); - console.log(text, "변경후"); - return a; - }; - this.render = () => { const { title, content, documents } = this.state.data; - console.log(content); if (!title && !content) { $editor.innerHTML = ` @@ -38,10 +30,10 @@ function DocumentEditor({ $target, initialState, onChange }) { }"/>
      ${ - content ? parseContent(content) : "아직 내용이 없습니다." + content ? content : "아직 내용이 없습니다." }
      -
      - ${parseMarkdown(content)} +
      + ${content ? parseMarkdown(content) : "아직 내용이 없습니다."}
      @@ -67,7 +59,6 @@ function DocumentEditor({ $target, initialState, onChange }) { } this.debounce = setTimeout(() => { - console.log($div.innerText); onChange($title.value, $div.innerText); }, 2000); diff --git a/src/components/DocumentNode.js b/src/components/DocumentNode.js index cec75c3d..e75ac5b8 100644 --- a/src/components/DocumentNode.js +++ b/src/components/DocumentNode.js @@ -1,6 +1,6 @@ import { navigate } from "../utils/router.js"; -function DocumentNode({ $target, initialState, onClick, onDelete, onSelect }) { +function DocumentNode({ $target, initialState, onClick, onDelete }) { const $node = document.createElement("article"); $target.appendChild($node); @@ -13,11 +13,14 @@ function DocumentNode({ $target, initialState, onClick, onDelete, onSelect }) { this.render = () => { const { data } = this.state; + $node.innerHTML = `
        + ${data .map(({ id, title, documents }) => { const $li = document.createElement("li"); + $li.dataset.documentId = id; $li.className = "node-item"; @@ -45,7 +48,9 @@ function DocumentNode({ $target, initialState, onClick, onDelete, onSelect }) { if (documents.length > 0) { this.documentNode = new DocumentNode({ $target: $li, - initialState: { data: documents, isDisplay: false }, + initialState: { + data: documents, + }, }); } @@ -56,42 +61,47 @@ function DocumentNode({ $target, initialState, onClick, onDelete, onSelect }) { `; }; - this.init = () => { - this.render(); - }; + this.render(); - this.init(); + const onClickButton = ($button) => { + const { className } = $button; + const { parentId } = $button.dataset; - $node.addEventListener("click", (e) => { - const $button = e.target.closest("button"); + if (!parentId) { + return; + } - if ($button?.className === "add-button") { - const { parentId } = $button.dataset; + if (className === "add-button") { + onClick(parseInt(parentId)); + return; + } - if (parentId) { - onClick(parseInt(parentId)); - } + if (className === "delete-button") { + onDelete(parseInt(parentId)); return; } + }; - if ($button?.className === "delete-button") { - const { parentId } = $button.dataset; + const onSelectDocument = ($li) => { + const { documentId } = $li.dataset; + if (documentId) { + navigate(`/document/${documentId}`); + } + }; - if (parentId) { - onDelete(parseInt(parentId)); - } + $node.addEventListener("click", (e) => { + const $button = e.target.closest("button"); + + if ($button) { + onClickButton($button); return; } const $li = e.target.closest("li"); if ($li) { - const { documentId } = $li.dataset; - // 여기서 라우터 변경을 한 후 이동한 페이지에서 api 요청해서 문서 정보 불러오기 - if (documentId) { - // onSelect(parseInt(documentId)); - navigate(`/document/${documentId}`); - } + onSelectDocument($li); + return; } }); } diff --git a/src/components/DocumentTree.js b/src/components/DocumentTree.js index 69f29716..3a8f6f77 100644 --- a/src/components/DocumentTree.js +++ b/src/components/DocumentTree.js @@ -1,7 +1,6 @@ import { createNewDocument, deleteDocumet, - getDocumentDetail, getRootDocuments, } from "../utils/fetchData.js"; import DocumentNode from "./DocumentNode.js"; @@ -25,26 +24,26 @@ function DocumentTree({ $target, initialState }) {
    `; + new DocumentNode({ $target: $tree, - initialState: { data: this.state.data, isDisplay: false }, + initialState: { data: this.state.data }, onClick: (parentId) => this.onClick(parentId), onDelete: (documentId) => { const answer = confirm("문서를 정말 삭제하시겠습니까?"); if (answer) { - deleteDocumet(documentId); - // 비동기 처리 문제, 다시 확인하기 - this.getData(); + this.delete(documentId); } }, - - // onSelect: (documentId) => { - // getDocumentDetail(documentId); - // }, }); }; + this.delete = async (documentId) => { + await deleteDocumet(documentId); + this.getData(); + }; + this.getData = async () => { const data = await getRootDocuments(); this.setState({ @@ -53,14 +52,6 @@ function DocumentTree({ $target, initialState }) { }); }; - $tree.addEventListener("click", (e) => { - const $button = e.target.closest("button"); - - if ($button?.className === "add-root-button") { - this.onClick(null); - } - }); - this.onClick = (parentId) => { const title = prompt("새로운 문서의 제목을 입력해주세요"); @@ -79,7 +70,19 @@ function DocumentTree({ $target, initialState }) { this.getData(); }; - this.getData(); + this.init = () => { + this.getData(); + }; + + this.init(); + + $tree.addEventListener("click", (e) => { + const $button = e.target.closest("button"); + + if ($button?.className === "add-root-button") { + this.onClick(null); + } + }); } export default DocumentTree; diff --git a/src/index.js b/src/index.js index ab1e6ea9..595e10ef 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,3 @@ -// import * as dotenv from "dotenv"; import App from "./App.js"; new App({ $target: document.querySelector("#App") }); - -// console.log(dotenv); -// console.log(process.env.API_END_POINT); diff --git a/src/pages/DocumentEditPage.js b/src/pages/DocumentEditPage.js index 8af15bad..bcfa9b55 100644 --- a/src/pages/DocumentEditPage.js +++ b/src/pages/DocumentEditPage.js @@ -18,19 +18,30 @@ function DocumentEditPage({ $target }) { $editor.className = "document-editor"; $editPage.appendChild($editor); - this.route = () => { + this.state = { + data: {}, + }; + + this.setState = (nextState) => { + this.state = nextState; + }; + + this.route = async () => { const { pathname } = window.location; const [, , documentId] = pathname.split("/"); if (documentId) { $editor.innerHTML = ""; - this.getData(documentId); + const data = await getDocumentDetail(documentId); + this.setState({ + data, + }); - this.documentEditor = new DocumentEditor({ + new DocumentEditor({ $target: $editor, initialState: { - data: {}, + data: this.state.data, }, onChange: (newTitle, newContent) => { this.updateData(documentId, { @@ -42,13 +53,6 @@ function DocumentEditPage({ $target }) { } }; - this.getData = async (documentId) => { - const data = await getDocumentDetail(documentId); - this.documentEditor.setState({ - data, - }); - }; - this.updateData = async (documentId, data) => { await updateDocument(documentId, data); this.getData(documentId); @@ -63,8 +67,6 @@ function DocumentEditPage({ $target }) { window.addEventListener("popstate", () => { route(); }); - - this.route(); }; this.init(); From baae2369ecb2ef73651c441c40e449389b08e7f1 Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Wed, 16 Nov 2022 20:29:20 +0900 Subject: [PATCH 13/24] =?UTF-8?q?feat:=20:sparkles:=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=20=ED=8E=B8=EC=A7=91=EA=B8=B0=20=EB=AF=B8=EB=A6=AC=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DocumentEditor.js | 20 ++++++++++++++------ src/components/EditorPreview.js | 30 ++++++++++++++++++++++++++++++ src/pages/DocumentEditPage.js | 12 +++++++++--- src/styles/style.css | 8 ++++++-- 4 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 src/components/EditorPreview.js diff --git a/src/components/DocumentEditor.js b/src/components/DocumentEditor.js index 1081c00f..e48e81ed 100644 --- a/src/components/DocumentEditor.js +++ b/src/components/DocumentEditor.js @@ -1,4 +1,5 @@ import { parseMarkdown } from "../utils/parseMarkdown.js"; +import EditorPreview from "./EditorPreview.js"; function DocumentEditor({ $target, initialState, onChange }) { const $editor = document.createElement("section"); @@ -11,9 +12,19 @@ function DocumentEditor({ $target, initialState, onChange }) { this.setState = (nextState) => { this.state = nextState; - this.render(); + editorPreview.setState({ + data: this.state.data.content, + }); + // this.render(); }; + const editorPreview = new EditorPreview({ + $target, + initialState: { + data: this.state.data.content, + }, + }); + this.render = () => { const { title, content, documents } = this.state.data; @@ -32,9 +43,6 @@ function DocumentEditor({ $target, initialState, onChange }) {
    ${ content ? content : "아직 내용이 없습니다." }
    -
    - ${content ? parseMarkdown(content) : "아직 내용이 없습니다."} -
    @@ -60,7 +68,7 @@ function DocumentEditor({ $target, initialState, onChange }) { this.debounce = setTimeout(() => { onChange($title.value, $div.innerText); - }, 2000); + }, 1000); return; } @@ -76,7 +84,7 @@ function DocumentEditor({ $target, initialState, onChange }) { this.debounce = setTimeout(() => { onChange($input.value, $content.value); - }, 2000); + }, 1000); } }); } diff --git a/src/components/EditorPreview.js b/src/components/EditorPreview.js new file mode 100644 index 00000000..ff9525b6 --- /dev/null +++ b/src/components/EditorPreview.js @@ -0,0 +1,30 @@ +import { parseMarkdown } from "../utils/parseMarkdown.js"; + +function EditorPreview({ $target, initialState }) { + const $preview = document.createElement("section"); + $preview.className = "editor-preview"; + $target.appendChild($preview); + + this.state = initialState; + + this.setState = (nextState) => { + this.state = nextState; + console.log(this.state); + this.render(); + }; + + this.render = () => { + const { data } = this.state; + + if (!data) return; + + $preview.innerHTML = ` +

    preview

    +
    ${parseMarkdown(data)}
    + `; + }; + + this.render(); +} + +export default EditorPreview; diff --git a/src/pages/DocumentEditPage.js b/src/pages/DocumentEditPage.js index bcfa9b55..b1cec83d 100644 --- a/src/pages/DocumentEditPage.js +++ b/src/pages/DocumentEditPage.js @@ -15,7 +15,7 @@ function DocumentEditPage({ $target }) { const documentTree = new DocumentTree({ $target: $tree }); const $editor = document.createElement("section"); - $editor.className = "document-editor"; + $editor.className = "document-editor-wrapper"; $editPage.appendChild($editor); this.state = { @@ -38,7 +38,7 @@ function DocumentEditPage({ $target }) { data, }); - new DocumentEditor({ + this.documentEditor = new DocumentEditor({ $target: $editor, initialState: { data: this.state.data, @@ -55,7 +55,13 @@ function DocumentEditPage({ $target }) { this.updateData = async (documentId, data) => { await updateDocument(documentId, data); - this.getData(documentId); + + const newData = await getDocumentDetail(documentId); + + this.documentEditor.setState({ + data: newData, + }); + documentTree.getData(); return; }; diff --git a/src/styles/style.css b/src/styles/style.css index 6965c1bb..04f9af96 100644 --- a/src/styles/style.css +++ b/src/styles/style.css @@ -4,9 +4,13 @@ height: 100vh; } +.document-editor-wrapper { + display: flex; + width: 100vw; +} + .document-editor { background-color: antiquewhite; - width: 100vw; } .document-tree { @@ -82,7 +86,7 @@ .eidtor-wrapper { display: flex; - width: 100vw; + /* width: 100vw; */ } .editor-text { From b6ccbf0e52e94a363e68028767d8d76d902f9e96 Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Thu, 17 Nov 2022 01:43:41 +0900 Subject: [PATCH 14/24] =?UTF-8?q?feat:=20:lipstick:=20=ED=95=98=EC=9C=84?= =?UTF-8?q?=20=EB=AC=B8=EC=84=9C=20=EC=B6=9C=EB=A0=A5=20=EB=B0=8F=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20css=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DocumentEditor.js | 49 +++++++++++++++--------------- src/components/DocumentNode.js | 3 +- src/components/DocumentTree.js | 4 +-- src/components/EditorPreview.js | 10 ++++--- src/pages/DocumentEditPage.js | 1 + src/styles/style.css | 51 ++++++++++++++++++++++++++------ 6 files changed, 78 insertions(+), 40 deletions(-) diff --git a/src/components/DocumentEditor.js b/src/components/DocumentEditor.js index e48e81ed..73ff3c11 100644 --- a/src/components/DocumentEditor.js +++ b/src/components/DocumentEditor.js @@ -1,4 +1,4 @@ -import { parseMarkdown } from "../utils/parseMarkdown.js"; +import { navigate } from "../utils/router.js"; import EditorPreview from "./EditorPreview.js"; function DocumentEditor({ $target, initialState, onChange }) { @@ -13,15 +13,14 @@ function DocumentEditor({ $target, initialState, onChange }) { this.setState = (nextState) => { this.state = nextState; editorPreview.setState({ - data: this.state.data.content, + data: this.state.data, }); - // this.render(); }; const editorPreview = new EditorPreview({ $target, initialState: { - data: this.state.data.content, + data: this.state.data, }, }); @@ -41,16 +40,19 @@ function DocumentEditor({ $target, initialState, onChange }) { }"/>
    ${ - content ? content : "아직 내용이 없습니다." + content ? content : "" }
    - -
    -

    📌하위 문서 목록

    - ${documents - .map(({ id, title }) => `${title}`) - .join("")} -
    + ${ + documents.length > 0 + ? `
    +

    📌하위 문서 목록

    + ${documents + .map(({ id, title }) => `${title}`) + .join("")} +
    ` + : "" + } `; }; @@ -61,30 +63,29 @@ function DocumentEditor({ $target, initialState, onChange }) { if ($div) { const $title = document.querySelector(".editor-document-title"); + const $content = document.querySelector(".editor-text"); if (this.debounce) { clearTimeout(this.debounce); } this.debounce = setTimeout(() => { - onChange($title.value, $div.innerText); - }, 1000); + onChange($title.value, $content.innerText); + }, 500); return; } + }); - const $input = e.target.closest("input"); - - if ($input) { - const $content = document.querySelector(".editor-text"); + $editor.addEventListener("click", (e) => { + e.preventDefault(); - if (this.debounce) { - clearTimeout(this.debounce); - } + const $a = e.target.closest("a"); - this.debounce = setTimeout(() => { - onChange($input.value, $content.value); - }, 1000); + if ($a) { + console.log($a.href); + const [, , , , documentId] = $a.href.split("/"); + navigate(`/document/${documentId}`); } }); } diff --git a/src/components/DocumentNode.js b/src/components/DocumentNode.js index e75ac5b8..9a69e317 100644 --- a/src/components/DocumentNode.js +++ b/src/components/DocumentNode.js @@ -45,11 +45,12 @@ function DocumentNode({ $target, initialState, onClick, onDelete }) { `; - if (documents.length > 0) { + if (this.state.isDisplay && documents.length > 0) { this.documentNode = new DocumentNode({ $target: $li, initialState: { data: documents, + isDisplay: true, }, }); } diff --git a/src/components/DocumentTree.js b/src/components/DocumentTree.js index 3a8f6f77..3c6c5ed0 100644 --- a/src/components/DocumentTree.js +++ b/src/components/DocumentTree.js @@ -7,7 +7,7 @@ import DocumentNode from "./DocumentNode.js"; function DocumentTree({ $target, initialState }) { const $tree = document.createElement("section"); - $tree.className = "document-tree"; + $target.appendChild($tree); this.state = initialState; @@ -27,7 +27,7 @@ function DocumentTree({ $target, initialState }) { new DocumentNode({ $target: $tree, - initialState: { data: this.state.data }, + initialState: { data: this.state.data, isDisplay: true }, onClick: (parentId) => this.onClick(parentId), onDelete: (documentId) => { const answer = confirm("문서를 정말 삭제하시겠습니까?"); diff --git a/src/components/EditorPreview.js b/src/components/EditorPreview.js index ff9525b6..ef63c4e4 100644 --- a/src/components/EditorPreview.js +++ b/src/components/EditorPreview.js @@ -14,13 +14,15 @@ function EditorPreview({ $target, initialState }) { }; this.render = () => { - const { data } = this.state; + if (!this.state.data) return; - if (!data) return; + const { content } = this.state.data; $preview.innerHTML = ` -

    preview

    -
    ${parseMarkdown(data)}
    +

    미리보기

    +
    ${ + content ? parseMarkdown(content) : "내용 없음" + }
    `; }; diff --git a/src/pages/DocumentEditPage.js b/src/pages/DocumentEditPage.js index b1cec83d..71dd5090 100644 --- a/src/pages/DocumentEditPage.js +++ b/src/pages/DocumentEditPage.js @@ -34,6 +34,7 @@ function DocumentEditPage({ $target }) { $editor.innerHTML = ""; const data = await getDocumentDetail(documentId); + console.log(data); this.setState({ data, }); diff --git a/src/styles/style.css b/src/styles/style.css index 04f9af96..ed615e40 100644 --- a/src/styles/style.css +++ b/src/styles/style.css @@ -1,22 +1,35 @@ +body { + margin: 0; + padding: 0; +} + .main-content { display: flex; width: 100vw; height: 100vh; } +.header-title { + border-bottom: 1px solid lightgray; +} + .document-editor-wrapper { display: flex; width: 100vw; } .document-editor { - background-color: antiquewhite; + /* background-color: antiquewhite; */ + min-width: 40vw; + max-width: 40vw; } .document-tree { - background-color: rgb(227, 250, 215); - width: 30vw; + /* background-color: rgb(227, 250, 215); */ + background-color: rgb(251, 251, 250); + min-width: 250px; max-width: 300px; + overflow: scroll; } .tree-node { @@ -86,26 +99,31 @@ .eidtor-wrapper { display: flex; - /* width: 100vw; */ } .editor-text { - width: 50vw; - height: 80vh; + height: 70vh; + width: 100%; font-size: 20px; - padding: 16px; + padding: 32px; + white-space: pre-wrap; + overflow-x: auto; } +.editor-text::-webkit-scrollbar { + display: none; /* Chrome, Safari, Opera*/ +} .document-editor { + padding: 16px; display: flex; flex-direction: column; } .editor-document-title { height: 50px; - font-size: x-large; + font-size: 32px; font-weight: bold; - padding-left: 10px; + padding: 10px; border: none; } @@ -117,3 +135,18 @@ .modal { display: none; } + +.editor-preview { + font-size: 20px; + padding: 16px; + border-left: 1px solid lightgray; +} + +.editor-preview h1 { + font-size: 32px; +} + +.preview-title { + margin: 0; + padding: 10px; +} From 43fc1db084958d89ba671fc8c8b2d16569769030 Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Thu, 17 Nov 2022 11:47:17 +0900 Subject: [PATCH 15/24] =?UTF-8?q?refactor:=20:recycle:=20=EC=97=90?= =?UTF-8?q?=EB=94=94=ED=84=B0=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EB=82=B4=20=ED=95=A8=EC=88=98=20=EB=B6=84=EB=A6=AC,=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EB=AA=85=20=EC=83=81=EC=88=98?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- src/components/DocumentEditor.js | 43 ++++++++++++++++++-------------- src/components/DocumentTree.js | 8 +++--- src/styles/style.css | 17 +++++-------- src/utils/api.js | 2 +- src/utils/constants.js | 4 +++ yarn.lock | 8 ------ 7 files changed, 40 insertions(+), 44 deletions(-) create mode 100644 src/utils/constants.js delete mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore index e6d39074..57ac0b46 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ node_modules -constants.js \ No newline at end of file +baseUrl.js \ No newline at end of file diff --git a/src/components/DocumentEditor.js b/src/components/DocumentEditor.js index 73ff3c11..80a820ae 100644 --- a/src/components/DocumentEditor.js +++ b/src/components/DocumentEditor.js @@ -1,7 +1,10 @@ import { navigate } from "../utils/router.js"; import EditorPreview from "./EditorPreview.js"; +import { className } from "../utils/constants.js"; function DocumentEditor({ $target, initialState, onChange }) { + const { editorDocumentTitle, editorDocumentContent } = className; + const $editor = document.createElement("section"); $editor.className = "document-editor"; $target.appendChild($editor); @@ -27,7 +30,7 @@ function DocumentEditor({ $target, initialState, onChange }) { this.render = () => { const { title, content, documents } = this.state.data; - if (!title && !content) { + if (!title) { $editor.innerHTML = `

    loading..

    `; @@ -35,13 +38,13 @@ function DocumentEditor({ $target, initialState, onChange }) { } $editor.innerHTML = ` - +
    -
    ${ - content ? content : "" - }
    +
    ${ + content ? content : "" + }
    ${ documents.length > 0 @@ -58,21 +61,24 @@ function DocumentEditor({ $target, initialState, onChange }) { this.render(); - $editor.addEventListener("keyup", (e) => { - const $div = e.target.closest("div"); + const updateDocument = () => { + const $title = document.querySelector(`.${editorDocumentTitle}`); + const $content = document.querySelector(`.${editorDocumentContent}`); - if ($div) { - const $title = document.querySelector(".editor-document-title"); - const $content = document.querySelector(".editor-text"); + if (this.debounce) { + clearTimeout(this.debounce); + } - if (this.debounce) { - clearTimeout(this.debounce); - } + this.debounce = setTimeout(() => { + onChange($title.value, $content.innerText); + }, 500); + }; - this.debounce = setTimeout(() => { - onChange($title.value, $content.innerText); - }, 500); + $editor.addEventListener("keyup", (e) => { + const $div = e.target.closest("div"); + if ($div) { + updateDocument(); return; } }); @@ -83,7 +89,6 @@ function DocumentEditor({ $target, initialState, onChange }) { const $a = e.target.closest("a"); if ($a) { - console.log($a.href); const [, , , , documentId] = $a.href.split("/"); navigate(`/document/${documentId}`); } diff --git a/src/components/DocumentTree.js b/src/components/DocumentTree.js index 3c6c5ed0..14316239 100644 --- a/src/components/DocumentTree.js +++ b/src/components/DocumentTree.js @@ -20,8 +20,8 @@ function DocumentTree({ $target, initialState }) { this.render = () => { $tree.innerHTML = `
    -

    문서 목록

    - + add + 새로운 문서
    `; @@ -77,9 +77,9 @@ function DocumentTree({ $target, initialState }) { this.init(); $tree.addEventListener("click", (e) => { - const $button = e.target.closest("button"); + const $div = e.target.closest("div"); - if ($button?.className === "add-root-button") { + if ($div?.className === "tree-subtitle-wrapper") { this.onClick(null); } }); diff --git a/src/styles/style.css b/src/styles/style.css index ed615e40..ec56399e 100644 --- a/src/styles/style.css +++ b/src/styles/style.css @@ -11,6 +11,7 @@ body { .header-title { border-bottom: 1px solid lightgray; + padding-left: 16px; } .document-editor-wrapper { @@ -19,13 +20,11 @@ body { } .document-editor { - /* background-color: antiquewhite; */ min-width: 40vw; max-width: 40vw; } .document-tree { - /* background-color: rgb(227, 250, 215); */ background-color: rgb(251, 251, 250); min-width: 250px; max-width: 300px; @@ -52,12 +51,7 @@ body { .tree-subtitle-wrapper { display: flex; - justify-content: space-between; - padding: 8px; -} - -.add-root-button { - all: unset; + padding: 16px 10px; cursor: pointer; } @@ -101,7 +95,7 @@ body { display: flex; } -.editor-text { +.editor-document-content { height: 70vh; width: 100%; font-size: 20px; @@ -110,9 +104,10 @@ body { overflow-x: auto; } -.editor-text::-webkit-scrollbar { - display: none; /* Chrome, Safari, Opera*/ +.editor-document-content::-webkit-scrollbar { + display: none; } + .document-editor { padding: 16px; display: flex; diff --git a/src/utils/api.js b/src/utils/api.js index e0b18494..153b606d 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -1,4 +1,4 @@ -import { API_END_POINT, HEADERS } from "./constants.js"; +import { API_END_POINT, HEADERS } from "./baseUrl.js"; export const request = async (url, method = "GET", data, defaultValue = []) => { try { diff --git a/src/utils/constants.js b/src/utils/constants.js new file mode 100644 index 00000000..07029627 --- /dev/null +++ b/src/utils/constants.js @@ -0,0 +1,4 @@ +export const className = { + editorDocumentTitle: "editor-document-title", + editorDocumentContent: "editor-document-content", +}; diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index c0b2f6aa..00000000 --- a/yarn.lock +++ /dev/null @@ -1,8 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -dotenv@^16.0.3: - version "16.0.3" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" - integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== From 2936ef19a67e02cf03bcbe352d9e2ecbbfb258f3 Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Thu, 17 Nov 2022 12:13:30 +0900 Subject: [PATCH 16/24] =?UTF-8?q?refactor:=20:recycle:=20DocumentTree=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81,=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EB=AA=85=EA=B3=BC=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DocumentEditor.js | 3 +-- src/components/DocumentNode.js | 4 +--- src/components/DocumentTree.js | 34 +++++++++++++++++++++----------- src/styles/style.css | 2 +- src/utils/constants.js | 1 + src/utils/fetchData.js | 1 - src/utils/messages.js | 5 +++++ 7 files changed, 32 insertions(+), 18 deletions(-) create mode 100644 src/utils/messages.js diff --git a/src/components/DocumentEditor.js b/src/components/DocumentEditor.js index 80a820ae..79b121c6 100644 --- a/src/components/DocumentEditor.js +++ b/src/components/DocumentEditor.js @@ -39,7 +39,7 @@ function DocumentEditor({ $target, initialState, onChange }) { $editor.innerHTML = `
    ${ @@ -79,7 +79,6 @@ function DocumentEditor({ $target, initialState, onChange }) { if ($div) { updateDocument(); - return; } }); diff --git a/src/components/DocumentNode.js b/src/components/DocumentNode.js index 9a69e317..f6b10d3c 100644 --- a/src/components/DocumentNode.js +++ b/src/components/DocumentNode.js @@ -16,7 +16,6 @@ function DocumentNode({ $target, initialState, onClick, onDelete }) { $node.innerHTML = `
      - ${data .map(({ id, title, documents }) => { const $li = document.createElement("li"); @@ -45,12 +44,11 @@ function DocumentNode({ $target, initialState, onClick, onDelete }) { `; - if (this.state.isDisplay && documents.length > 0) { + if (documents.length > 0) { this.documentNode = new DocumentNode({ $target: $li, initialState: { data: documents, - isDisplay: true, }, }); } diff --git a/src/components/DocumentTree.js b/src/components/DocumentTree.js index 14316239..8cf3b1ea 100644 --- a/src/components/DocumentTree.js +++ b/src/components/DocumentTree.js @@ -4,12 +4,17 @@ import { getRootDocuments, } from "../utils/fetchData.js"; import DocumentNode from "./DocumentNode.js"; +import { className } from "../utils/constants.js"; +import { messages } from "../utils/messages.js"; function DocumentTree({ $target, initialState }) { const $tree = document.createElement("section"); - $target.appendChild($tree); + const { treeRootAddButton } = className; + const { messageForDelete, messageForCreate, errorMessageInvalidTitle } = + messages; + this.state = initialState; this.setState = (nextState) => { @@ -19,7 +24,7 @@ function DocumentTree({ $target, initialState }) { this.render = () => { $tree.innerHTML = ` -
      +
      add 새로운 문서
      @@ -27,10 +32,14 @@ function DocumentTree({ $target, initialState }) { new DocumentNode({ $target: $tree, - initialState: { data: this.state.data, isDisplay: true }, - onClick: (parentId) => this.onClick(parentId), + initialState: { + data: this.state.data, + }, + onClick: (parentId) => { + this.onClick(parentId); + }, onDelete: (documentId) => { - const answer = confirm("문서를 정말 삭제하시겠습니까?"); + const answer = confirm(messageForDelete); if (answer) { this.delete(documentId); @@ -53,20 +62,23 @@ function DocumentTree({ $target, initialState }) { }; this.onClick = (parentId) => { - const title = prompt("새로운 문서의 제목을 입력해주세요"); + const title = prompt(messageForCreate); + + if (title === null) { + return; + } - if (title === null) return; if (!title.trim()) { - alert("올바른 제목을 입력해주세요."); + alert(errorMessageInvalidTitle); return; } - const data = { + const newData = { title: title.trim(), parent: parentId, }; - createNewDocument(data); + createNewDocument(newData); this.getData(); }; @@ -79,7 +91,7 @@ function DocumentTree({ $target, initialState }) { $tree.addEventListener("click", (e) => { const $div = e.target.closest("div"); - if ($div?.className === "tree-subtitle-wrapper") { + if ($div?.className === treeRootAddButton) { this.onClick(null); } }); diff --git a/src/styles/style.css b/src/styles/style.css index ec56399e..f43d3d2e 100644 --- a/src/styles/style.css +++ b/src/styles/style.css @@ -49,7 +49,7 @@ body { max-width: 300px; } -.tree-subtitle-wrapper { +.tree-add-button-wrapper { display: flex; padding: 16px 10px; cursor: pointer; diff --git a/src/utils/constants.js b/src/utils/constants.js index 07029627..71a330a7 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -1,4 +1,5 @@ export const className = { editorDocumentTitle: "editor-document-title", editorDocumentContent: "editor-document-content", + treeRootAddButton: "tree-add-button-wrapper", }; diff --git a/src/utils/fetchData.js b/src/utils/fetchData.js index df72e3fa..ebef0284 100644 --- a/src/utils/fetchData.js +++ b/src/utils/fetchData.js @@ -22,5 +22,4 @@ export const updateDocument = async (documentId, data) => { export const deleteDocumet = async (documentId) => { const response = await request(`/documents/${documentId}`, "DELETE"); - console.log(response); }; diff --git a/src/utils/messages.js b/src/utils/messages.js new file mode 100644 index 00000000..8b1f35bd --- /dev/null +++ b/src/utils/messages.js @@ -0,0 +1,5 @@ +export const messages = { + messageForDelete: "문서를 정말 삭제하시겠습니까?", + messageForCreate: "새로운 문서의 제목을 입력해주세요.", + errorMessageInvalidTitle: "올바른 제목을 입력해주세요.", +}; From ead265d9e4e9e91453de170d414d9f7729473adf Mon Sep 17 00:00:00 2001 From: yezyvibe Date: Thu, 17 Nov 2022 14:34:04 +0900 Subject: [PATCH 17/24] =?UTF-8?q?refactor:=20:recycle:=20DocumentNode=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81,=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EB=AA=85=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DocumentNode.js | 12 ++++++++---- src/components/Header.js | 5 ++++- src/styles/style.css | 20 +++++++++++++++++++- src/utils/constants.js | 3 +++ 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/components/DocumentNode.js b/src/components/DocumentNode.js index f6b10d3c..73bd52a1 100644 --- a/src/components/DocumentNode.js +++ b/src/components/DocumentNode.js @@ -1,9 +1,12 @@ import { navigate } from "../utils/router.js"; +import { className } from "../utils/constants.js"; function DocumentNode({ $target, initialState, onClick, onDelete }) { const $node = document.createElement("article"); $target.appendChild($node); + const { addButton, deleteButton } = className; + this.state = initialState; this.setState = (nextState) => { @@ -30,12 +33,12 @@ function DocumentNode({ $target, initialState, onClick, onDelete }) { ${title}
      - -