-
Notifications
You must be signed in to change notification settings - Fork 28
[Mission4/김규란] - Project_Notion_Vanilla_JS #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 3/#4_gxxrxn
Are you sure you want to change the base?
Changes from 6 commits
5c7931d
0da15c1
5e74b67
9a7f4b2
f591bf4
35bb3b3
c0f809c
5decc3d
7454bd5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| .env.js |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="ko"> | ||
|
|
||
| <head> | ||
| <meta charset="UTF-8"> | ||
| <title>Fry's Notion</title> | ||
| <link rel="stylesheet" href="/style.css" type="text/css" /> | ||
| </head> | ||
|
|
||
| <body> | ||
| <div id="app"></div> | ||
| <script type="module" src="/src/main.js"></script> | ||
| </body> | ||
|
|
||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import MainPage from "./pages/MainPage.js"; | ||
| import NotFound from "./pages/NotFound.js"; | ||
| import Router from "./Router.js"; | ||
|
|
||
| import { routes } from "./constants/routes.js"; | ||
|
|
||
| export default function App({ $target }) { | ||
| this.$target = $target; | ||
| this.state = { currentPage: null }; | ||
|
|
||
| this.init = () => { | ||
| this.currentPage = new MainPage({ $target: this.$target }); | ||
| new Router({ $target: this.$target, onRoute: this.route.bind(this) }); | ||
| }; | ||
|
|
||
| this.route = () => { | ||
| const findMatchedRoute = () => routes.find((route) => route.path.test(location.pathname)); | ||
| const Page = findMatchedRoute()?.element || NotFound; | ||
|
||
| const [, , documentId] = location.pathname.split("/"); | ||
|
|
||
| if (documentId) { | ||
| this.currentPage.setState({ documentId }); | ||
| } | ||
|
|
||
| console.log(window.location.pathname); | ||
| console.log(/^\/documents\//.test(location.pathname)); | ||
|
||
| }; | ||
|
|
||
| this.init(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import NotFound from "./pages/NotFound.js"; | ||
|
|
||
| import { HISTORY_CHANGE_EVENT_NAME, routes } from "./constants/routes.js"; | ||
|
|
||
| export default function Router({ $target, onRoute }) { | ||
|
||
| this.$target = $target; | ||
| this.state = { currentPage: null }; | ||
|
|
||
| this.init = () => { | ||
| window.addEventListener(HISTORY_CHANGE_EVENT_NAME, ({ detail }) => { | ||
| const { to, isReplace } = detail; | ||
|
|
||
| if (isReplace || to === location.pathname) { | ||
| history.replaceState(null, "", to); | ||
| } else { | ||
| history.pushState(null, "", to); | ||
| } | ||
|
|
||
| onRoute(); | ||
| }); | ||
|
|
||
| window.addEventListener("popstate", () => { | ||
| onRoute(); | ||
| }); | ||
| }; | ||
|
|
||
| this.init(); | ||
| onRoute(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| export default function Component({ $target, initialState }) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p5) 현재 사용되지 않는 파일 같네요! 아마 클래스로 짜시다가 중간에 함수형으로 바꾸셨다고 했는데 그 과정에서 남기신게 아닐까.. 추측해봅니다! |
||
| this.$target = $target; | ||
| this.state = { ...initialState }; | ||
|
|
||
| this.init = () => { | ||
| this.render(); | ||
| }; | ||
|
|
||
| this.setState = () => { | ||
| this.render(); | ||
| }; | ||
|
|
||
| this.render = () => { | ||
| this.$target.innerHTML = ` | ||
| `; | ||
| this.mounted(); | ||
| }; | ||
|
|
||
| this.mounted = () => {}; | ||
|
|
||
| this.setEvent = () => {}; | ||
|
|
||
| this.init(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| import DocumentHeader from "./DocumentHeader.js"; | ||
| import DocumentContent from "./DocumentContent.js"; | ||
|
|
||
| import API from "../../utils/api.js"; | ||
| import { USER } from "../../config.js"; | ||
| import { debounce } from "../../utils/debounce.js"; | ||
| import { getItemFromStorage, setItemToStorage } from "../../utils/storage.js"; | ||
|
||
|
|
||
| export default function Document({ | ||
| $target, | ||
| initialState = { | ||
| documentId: null, | ||
| }, | ||
| }) { | ||
| const handleDocumentEdit = debounce(async (text, type = "title") => { | ||
| const storedItem = getItemFromStorage("notion", { currentDocument: {} }); | ||
|
|
||
| storedItem.currentDocument = { | ||
| ...storedItem.currentDocument, | ||
| [type]: text, | ||
| tempSavedAt: new Date(), | ||
| }; | ||
| setItemToStorage("notion", { ...storedItem }); | ||
| await updateDocument(this.state.documentId, storedItem.currentDocument); | ||
| }, 300); | ||
|
|
||
| const fetchDocument = async (documentId) => { | ||
| const response = await API.getDocuments(documentId); | ||
|
Comment on lines
+15
to
+16
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. API를 객체로 선언하시다니...! 이렇게 작성하면 실수를 줄일 수 있겠네요!😲 |
||
|
|
||
| if (!response) { | ||
| return [{ title: "", content: "" }, {}]; | ||
| } | ||
| return [{ title: response.title, content: response.content }, response]; | ||
| }; | ||
|
|
||
| const updateDocument = async (documentId, newDocument) => { | ||
| const { title, content } = newDocument; | ||
| await API.updateDocument(documentId, { title, content }); | ||
| }; | ||
|
|
||
| const renderNoDocument = ($container) => { | ||
| $container.innerHTML = `<h1>🎉 Welcome to ${USER.NAME}'s Notion! 🎉</h1>`; | ||
| }; | ||
|
|
||
| const renderNewDocument = ($header, $body) => { | ||
| new DocumentHeader({ $target: $header, onEdit: handleDocumentEdit.bind(this) }); | ||
| new DocumentContent({ $target: $body, onEdit: handleDocumentEdit.bind(this) }); | ||
| }; | ||
|
|
||
| const renderDocumentById = async ($header, $body) => { | ||
| const { documentId } = this.state; | ||
| const [{ title, content }, response] = await fetchDocument(documentId); | ||
|
|
||
| setItemToStorage("notion", { currentDocument: response }); | ||
|
|
||
| new DocumentHeader({ | ||
| $target: $header, | ||
| initialState: { title }, | ||
| onEdit: handleDocumentEdit.bind(this), | ||
| }); | ||
| new DocumentContent({ | ||
| $target: $body, | ||
| initialState: { content }, | ||
| onEdit: handleDocumentEdit.bind(this), | ||
| }); | ||
| }; | ||
|
|
||
| this.$target = $target; | ||
| this.state = initialState; | ||
|
|
||
| this.init = () => { | ||
| this.render(); | ||
| }; | ||
|
|
||
| this.render = () => { | ||
| this.$target.innerHTML = ` | ||
| <div id="document"> | ||
| <div id="document-header"></div> | ||
| <div id="document-body"></div> | ||
| </div> | ||
| `; | ||
|
|
||
| this.mounted(); | ||
| }; | ||
|
|
||
| this.mounted = async () => { | ||
| const $container = this.$target.querySelector("#document"); | ||
| const $header = this.$target.querySelector("#document-header"); | ||
| const $body = this.$target.querySelector("#document-body"); | ||
|
|
||
| const { documentId } = this.state; | ||
|
|
||
| if (documentId === "new") { | ||
| renderNewDocument($header, $body); | ||
| } else if (!!documentId) { | ||
| console.log(documentId); | ||
|
||
| renderDocumentById($header, $body); | ||
| } else { | ||
| renderNoDocument($container); | ||
| } | ||
| }; | ||
|
|
||
| this.setEvent = () => {}; | ||
|
|
||
| this.init(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import { addEvent } from "../../utils/event.js"; | ||
|
|
||
| export default function DocumentContent({ | ||
| $target, | ||
| initialState = { | ||
| content: "", | ||
| }, | ||
| onEdit, | ||
| }) { | ||
| this.$target = $target; | ||
| this.state = initialState; | ||
|
|
||
| this.init = () => { | ||
| this.setEvent(); | ||
| this.render(); | ||
| }; | ||
|
|
||
| this.setState = (newState) => { | ||
| this.state = { ...this.state, ...newState }; | ||
| this.render(); | ||
| }; | ||
|
|
||
| this.render = () => { | ||
| const { content } = this.state; | ||
|
|
||
| this.$target.innerHTML = ` | ||
| <div id="document-editor" name="content" contenteditable="true" placeholder="Type for creating new document">${ | ||
| content ?? "" | ||
| }</div> | ||
| `; | ||
| }; | ||
|
|
||
| this.setEvent = () => { | ||
| addEvent(this.$target, "keyup", "[name=content]", (event) => { | ||
| onEdit(event.target.innerText, "content"); | ||
| }); | ||
| }; | ||
|
Comment on lines
+29
to
+33
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이벤트 리스너를 따로 함수로 만드신 건가요?! 이렇게 코드를 작성할 수도 있군요... 😮 |
||
| this.init(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import { DEFAULT } from "../../config.js"; | ||
| import { addEvent } from "../../utils/event.js"; | ||
|
|
||
| export default function DocumentHeader({ | ||
| $target, | ||
| initialState = { | ||
| title: "", | ||
| }, | ||
| onEdit, | ||
| }) { | ||
| this.$target = $target; | ||
| this.state = initialState; | ||
|
|
||
| this.init = () => { | ||
| this.setEvent(); | ||
| this.render(); | ||
| }; | ||
|
|
||
| this.setState = () => { | ||
| this.render(); | ||
| }; | ||
|
|
||
| this.render = () => { | ||
| const { title } = this.state; | ||
|
|
||
| this.$target.innerHTML = ` | ||
| <div class="title" name="title" placeholder=${DEFAULT.DOCUMENT_NAME} contenteditable="true">${ | ||
| title === DEFAULT.DOCUMENT_NAME ? "" : title | ||
| }</div>`; | ||
| }; | ||
|
|
||
| this.setEvent = () => { | ||
| addEvent(this.$target, "keyup", "[name=title]", (event) => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P5 : 저의 경우에는 event type을 keyup으로 해두면 화살표 키나 shift, caps lock 등 입력 값이 변하지 않았음에도 이벤트가 발생해서 유효하지 않은 api가 날라가더라구요. 그래서 input으로 바꿔서 입력값이 변할 때에만 api가 날라갈 수 있게 했었습니다. 그리고 이전에 React에서 겪은 적이 있는데, event type을 keyup으로 해두고 한글을 입력하면 이벤트가 중복해서 2번 발생하는 현상이 있었습니다. 실제로 검색해보니 javascript 자체의 문제더라구요. 한글 관련 에러여서 공식 자료도 찾기 어려웠었습니다. 이런 현상을 피하기 위해서 다른 event type을 고려해보시는 것도 좋을 것 같습니다! p.s 과제에서 확인해보니, 여기선 keyup으로 해도 이벤트가 중복 발생하진 않네요! |
||
| onEdit(event.target.innerText, "title"); | ||
| }); | ||
| }; | ||
|
|
||
| this.init(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| import DocumentItem from "./DocumentItem.js"; | ||
|
|
||
| import { $documentsList } from "../../utils/templates.js"; | ||
|
|
||
| export default function Details({ | ||
| $target, | ||
| initialState = { | ||
| id: null, | ||
| title: "", | ||
| documents: [], | ||
| }, | ||
| onAddButtonClick, | ||
| }) { | ||
| this.$target = $target; | ||
| this.state = initialState; | ||
|
|
||
| this.init = () => { | ||
| this.render(); | ||
| }; | ||
|
|
||
| this.setState = (nextState) => { | ||
| this.state = { ...this.state, ...nextState }; | ||
| this.render(); | ||
| }; | ||
|
|
||
| this.render = () => { | ||
| const { documents } = this.state; | ||
|
|
||
| this.$target.innerHTML = ` | ||
| <details id="document-details"> | ||
| <summary></summary> | ||
| <ul>${$documentsList(documents)}</ul> | ||
| </details> | ||
| `; | ||
|
|
||
| this.mounted(); | ||
| }; | ||
|
|
||
| this.mounted = () => { | ||
| const $rootDocument = this.$target.querySelector("summary"); | ||
| const { id, title, documents } = this.state; | ||
|
|
||
| new DocumentItem({ | ||
| $target: $rootDocument, | ||
| initialState: { | ||
| id, | ||
| title, | ||
| }, | ||
| onAddButtonClick, | ||
| }); | ||
|
|
||
| documents.forEach(({ id, title, documents }) => { | ||
| const $li = this.$target.querySelector(`[data-document-id="${id}"]`); | ||
|
||
|
|
||
| new Details({ | ||
| $target: $li, | ||
| initialState: { | ||
| title, | ||
| id, | ||
| documents, | ||
| }, | ||
| onAddButtonClick, | ||
| }); | ||
| }); | ||
| }; | ||
|
|
||
| this.init(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| export default function DocumentItem({ $target, initialState }) { | ||
| this.$target = $target; | ||
| this.state = initialState; | ||
|
|
||
| this.init = () => { | ||
| this.render(); | ||
| }; | ||
|
|
||
| this.render = () => { | ||
| const { title, id } = this.state; | ||
|
|
||
| this.$target.innerHTML = ` | ||
| <div id="document-item" data-document-id="${id}"> | ||
| <svg viewBox="0 0 12 12" class="arrow"> | ||
| <path | ||
| d="M6.02734 8.80274C6.27148 8.80274 6.47168 8.71484 6.66211 8.51465L10.2803 4.82324C10.4268 4.67676 10.5 4.49609 10.5 4.28125C10.5 3.85156 10.1484 3.5 9.72363 3.5C9.50879 3.5 9.30859 3.58789 9.15234 3.74902L6.03223 6.9668L2.90722 3.74902C2.74609 3.58789 2.55078 3.5 2.33105 3.5C1.90137 3.5 1.55469 3.85156 1.55469 4.28125C1.55469 4.49609 1.62793 4.67676 1.77441 4.82324L5.39258 8.51465C5.58789 8.71973 5.78808 8.80274 6.02734 8.80274Z"> | ||
| </path> | ||
| </svg> | ||
| <svg viewBox="0 0 30 30" class="page"> | ||
| <g><path d="M16,1H4v28h22V11L16,1z M16,3.828L23.172,11H16V3.828z M24,27H6V3h8v10h10V27z M8,17h14v-2H8V17z M8,21h14v-2H8V21z M8,25h14v-2H8V25z"></path></g> | ||
| </svg> | ||
| <span class="title">${title}</span> | ||
| <svg viewBox="0 0 16 16" class="trash hidden"> | ||
| <path d="M4.8623 15.4287H11.1445C12.1904 15.4287 12.8672 14.793 12.915 13.7402L13.3799 3.88965H14.1318C14.4736 3.88965 14.7402 3.62988 14.7402 3.28809C14.7402 2.95312 14.4736 2.69336 14.1318 2.69336H11.0898V1.66797C11.0898 0.62207 10.4268 0 9.29199 0H6.69434C5.56641 0 4.89648 0.62207 4.89648 1.66797V2.69336H1.86133C1.5332 2.69336 1.25977 2.95312 1.25977 3.28809C1.25977 3.62988 1.5332 3.88965 1.86133 3.88965H2.62012L3.08496 13.7471C3.13281 14.7998 3.80273 15.4287 4.8623 15.4287ZM6.1543 1.72949C6.1543 1.37402 6.40039 1.14844 6.7832 1.14844H9.20312C9.58594 1.14844 9.83203 1.37402 9.83203 1.72949V2.69336H6.1543V1.72949ZM4.99219 14.2188C4.61621 14.2188 4.34277 13.9453 4.32227 13.542L3.86426 3.88965H12.1152L11.6709 13.542C11.6572 13.9453 11.3838 14.2188 10.9941 14.2188H4.99219ZM5.9834 13.1182C6.27051 13.1182 6.45508 12.9336 6.44824 12.667L6.24316 5.50293C6.23633 5.22949 6.04492 5.05176 5.77148 5.05176C5.48438 5.05176 5.2998 5.23633 5.30664 5.50293L5.51172 12.667C5.51855 12.9404 5.70996 13.1182 5.9834 13.1182ZM8 13.1182C8.28711 13.1182 8.47852 12.9336 8.47852 12.667V5.50293C8.47852 5.23633 8.28711 5.05176 8 5.05176C7.71289 5.05176 7.52148 5.23633 7.52148 5.50293V12.667C7.52148 12.9336 7.71289 13.1182 8 13.1182ZM10.0166 13.1182C10.29 13.1182 10.4746 12.9404 10.4814 12.667L10.6934 5.50293C10.7002 5.23633 10.5088 5.05176 10.2285 5.05176C9.95508 5.05176 9.76367 5.22949 9.75684 5.50293L9.54492 12.667C9.53809 12.9336 9.72949 13.1182 10.0166 13.1182Z"></path> | ||
| </svg> | ||
| <span class="add-btn hidden"> + </span> | ||
| </div> | ||
| `; | ||
| }; | ||
|
|
||
| this.init(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ask) 코드를 읽어보니 모두 this.$target = $target 으로 초기 설정을 하셨는데 이유가 뭔가요????? 궁금합니다.. 뭔가 규란님이라면 이유가 있어서 하셨을 것 같아서요 !!