-
Notifications
You must be signed in to change notification settings - Fork 29
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
[Mission5/이승희] Project_Notion_VanillaJS 과제 #41
base: 4/#5_leeseunghee
Are you sure you want to change the base?
Changes from all commits
4756752
c35c1d3
6f6e40e
56d74a7
b33b96d
4c697cd
c19d25e
914096d
b4af059
b17d83e
7d67523
5b086be
168ee2b
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,15 @@ | ||
{ | ||
"env": { | ||
"browser": true, | ||
"es2021": true | ||
}, | ||
"extends": ["airbnb-base", "prettier"], | ||
"plugins": ["prettier"], | ||
"parserOptions": { | ||
"ecmaVersion": 12, | ||
"sourceType": "module" | ||
}, | ||
"rules": { | ||
"prettier/prettier": ["error"] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
node_modules | ||
.DS_Store | ||
.eslintcache | ||
.vscode |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"printWidth": 120, | ||
"tabWidth": 2, | ||
"semi": true, | ||
"singleQuote": true, | ||
"quoteProps": "as-needed", | ||
"trailingComma": "es5", | ||
"bracketSpacing": true, | ||
"endOfLine": "lf", | ||
"useTabs": false, | ||
"arrowParens": "always" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<!DOCTYPE html> | ||
<html lang="ko"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Notion</title> | ||
<link | ||
rel="stylesheet" | ||
href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css" | ||
integrity="sha512-NhSC1YmyruXifcj/KFRWoC561YpHpc5Jtzgvbuzx5VozKpWvQ+4nXhPdFgmx8xqexRcpAglTj9sIBWINXa8x5w==" | ||
crossorigin="anonymous" | ||
referrerpolicy="no-referrer" | ||
/> | ||
<link rel="preconnect" href="https://fonts.googleapis.com" /> | ||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | ||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet" /> | ||
<link rel="stylesheet" href="/src/styles/style.css" /> | ||
</head> | ||
<body> | ||
<main id="app" style="display: flex"></main> | ||
<script src="/src/main.js" type="module"></script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"name": "fedc4-5_project_notion_vanillajs", | ||
"version": "1.0.0", | ||
"main": "index.js", | ||
"repository": "https://github.com/prgrms-fe-devcourse/FEDC4-5_Project_Notion_VanillaJS.git", | ||
"author": "eeseung <[email protected]>", | ||
"license": "MIT", | ||
"devDependencies": { | ||
"eslint": "^7.32.0 || ^8.2.0", | ||
"eslint-config-airbnb-base": "^15.0.0", | ||
"eslint-config-prettier": "^8.8.0", | ||
"eslint-plugin-import": "^2.25.2", | ||
"eslint-plugin-prettier": "^4.2.1", | ||
"prettier": "2.8.8" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import NotionPage from './pages/NotionPage.js'; | ||
import { initRouter } from './utils/router.js'; | ||
import { getItem } from './utils/storage.js'; | ||
|
||
export default function App({ $target }) { | ||
const openedDocuments = getItem('opened-documents', []); | ||
|
||
const notionPage = new NotionPage({ | ||
$target, | ||
initialState: { | ||
documents: [], | ||
documentId: null, | ||
openedDocuments: openedDocuments, | ||
}, | ||
}); | ||
|
||
this.route = () => { | ||
const { pathname } = window.location; | ||
|
||
if (pathname === '/') { | ||
notionPage.setState({ | ||
...notionPage.state, | ||
documentId: null, | ||
}); | ||
} else if (pathname.indexOf('/documents/') === 0) { | ||
const [, , id] = pathname.split('/'); | ||
notionPage.setState({ | ||
...notionPage.state, | ||
documentId: parseInt(id), | ||
}); | ||
} | ||
}; | ||
|
||
this.route(); | ||
|
||
initRouter(() => this.route()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { API_END_POINT, USER_NAME } from './constants.js'; | ||
|
||
export const request = async (url, options = {}) => { | ||
try { | ||
const res = await fetch(`${API_END_POINT}${url}`, { | ||
...options, | ||
headers: { | ||
'Content-Type': 'application/json', | ||
'x-username': USER_NAME, | ||
}, | ||
}); | ||
Comment on lines
+5
to
+11
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. 만약 options에 header가 포함된다면 어떻게 될까요? 👀 |
||
|
||
if (res.ok) { | ||
return await res.json(); | ||
} | ||
throw new Error('API 호출 오류'); | ||
} catch (e) { | ||
alert(e.message); | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
export default function DocumentHeader({ $target, initialState }) { | ||
const $header = document.createElement('header'); | ||
$header.classList.add('document-header'); | ||
$target.appendChild($header); | ||
|
||
this.state = initialState; | ||
|
||
this.setState = (nextState) => { | ||
this.state = nextState; | ||
|
||
this.render(); | ||
}; | ||
|
||
this.render = () => { | ||
$header.innerHTML = ` | ||
<a href="/documents/${this.state.id}">${this.state.title}</a> | ||
`; | ||
}; | ||
|
||
this.render(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
export default function Editor({ $target, initialState, onEditing }) { | ||
const $editor = document.createElement('div'); | ||
$editor.classList.add('editor'); | ||
$target.appendChild($editor); | ||
|
||
let isInitialize = false; | ||
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. initialize의 경우 동사이기 때문에 변수명으로 잘 쓰이지는 않고, is와 should 등의 접두어로 시작되는 상태를 나타내는 변수의 경우 축약어를 사용하는 isInit, 시제가 드러나는 isInitialized, isInitializing 등의 네이밍이 주로 쓰이곤 합니다. 초기화뿐만 아니라 상태를 표현하는 다른 플래그 변수도 비슷합니다. |
||
|
||
this.state = initialState; | ||
|
||
this.setState = (nextState) => { | ||
this.state = nextState; | ||
|
||
$editor.querySelector('[name=title]').value = this.state.title; | ||
$editor.querySelector('[name=content]').value = this.state.content; | ||
|
||
this.render(); | ||
}; | ||
|
||
this.render = () => { | ||
if (!isInitialize) { | ||
$editor.innerHTML = ` | ||
<input type="text" name="title" value="${this.state.title}" placeholder="제목 없음"/> | ||
<textarea name="content" placeholder="내용을 입력해 주세요.">${this.state.content}</textarea> | ||
`; | ||
isInitialize = true; | ||
} | ||
}; | ||
|
||
this.render(); | ||
|
||
$editor.addEventListener('keyup', (e) => { | ||
const { target } = e; | ||
const name = target.getAttribute('name'); | ||
|
||
if (this.state[name] !== undefined) { | ||
const nextState = { | ||
...this.state, | ||
[name]: target.value, | ||
}; | ||
|
||
this.setState(nextState); | ||
onEditing(this.state); | ||
} | ||
}); | ||
Comment on lines
+31
to
+44
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. 불필요하다고 말할 필요까지는 없을 것 같지만 🤔, throttling 기법을 적용한다면 효율을 높일 수 있을 것 같네요. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
export default function CreateButton({ $target, initialState, onClick }) { | ||
const $button = document.createElement('button'); | ||
$button.classList.add('create-button'); | ||
$target.appendChild($button); | ||
|
||
this.state = initialState; | ||
|
||
this.render = () => { | ||
$button.textContent = this.state.text; | ||
}; | ||
|
||
$button.addEventListener('click', () => { | ||
onClick(); | ||
}); | ||
|
||
this.render(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { push } from '../../utils/router.js'; | ||
|
||
export default function DocumentList({ $target, initialState, onToggle, onCreate, onDelete }) { | ||
const $documentList = document.createElement('div'); | ||
$documentList.classList.add('document-list'); | ||
$target.appendChild($documentList); | ||
|
||
this.state = initialState; | ||
|
||
this.setState = (nextState) => { | ||
this.state = nextState; | ||
this.render(); | ||
}; | ||
|
||
const renderDocuments = (documents, depth) => { | ||
return ` | ||
${ | ||
documents.length > 0 | ||
? documents | ||
.map((document) => { | ||
const isExpanded = this.state.openedDocuments.indexOf(document.id) > -1; | ||
return ` | ||
<li data-id="${document.id}" class="document-list-item"> | ||
<div class="document-item ${document.id === this.state.documentId ? 'selected' : ''}" | ||
style="padding:2px 10px 2px ${depth * 14 + 5}px;"> | ||
<button class="toggle-button ${isExpanded ? 'expanded' : ''}"></button> | ||
<div class="document-title"> | ||
${document.title.length === 0 ? '제목 없음' : document.title} | ||
</div> | ||
<div class="document-button-wrapper"> | ||
<div> | ||
<button class="delete-button"></button> | ||
<button class="add-button"></button> | ||
</div> | ||
</div> | ||
</div> | ||
<ul class="${isExpanded ? 'expanded' : ''}"> | ||
${renderDocuments(document.documents, depth + 1)} | ||
</ul> | ||
</li>`; | ||
}) | ||
.join('') | ||
: ` | ||
<li class="document-list-none" style="padding:2px 10px 2px ${depth * 14 + 15}px;"> | ||
하위 페이지 없음 | ||
</li> | ||
` | ||
} | ||
`; | ||
}; | ||
Comment on lines
+15
to
+50
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. html 때문에 함수 라인이 길어지는데 map을 통해 실행되는 함수를 한 번 더 분리해주면 조금 더 깔끔하게 유지할 수 있지 않을까요? |
||
|
||
this.render = () => { | ||
$documentList.innerHTML = ` | ||
${this.state.documents.length > 0 ? `<ul class="expanded">${renderDocuments(this.state.documents, 0)}</ul>` : ''} | ||
`; | ||
}; | ||
|
||
$documentList.addEventListener('click', (e) => { | ||
const $li = e.target.closest('.document-list-item'); | ||
|
||
if ($li) { | ||
const { id } = $li.dataset; | ||
const [className] = e.target.classList; | ||
|
||
if (className === 'toggle-button') { | ||
onToggle(parseInt(id), $li.querySelector('ul')); | ||
} else if (className === 'add-button') { | ||
onCreate(parseInt(id)); | ||
} else if (className === 'delete-button') { | ||
onDelete(parseInt(id)); | ||
} else if (className === 'document-item-none') { | ||
return; | ||
} else { | ||
push(`/documents/${id}`); | ||
} | ||
} | ||
}); | ||
|
||
this.render(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
export default function SidebarHeader({ $target, initialState }) { | ||
const $header = document.createElement('header'); | ||
$header.classList.add('sidebar-header'); | ||
$target.appendChild($header); | ||
|
||
this.state = initialState; | ||
|
||
this.render = () => { | ||
$header.innerHTML = ` | ||
<a href="/">${this.state.heading}</a> | ||
`; | ||
}; | ||
|
||
this.render(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
export const API_END_POINT = 'https://kdt-frontend.programmers.co.kr'; | ||
export const USER_NAME = 'eeseung'; | ||
|
||
export const TEMP_DOCUMENT_KEY = (id) => `temp-document-${id}`; | ||
export const OPENED_DOCUMENTS_KEY = 'opened-documents'; | ||
|
||
export const INIT_DOCUMENT = { | ||
title: '', | ||
content: '', | ||
}; | ||
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. 반복되는 부분을 줄이기 위해서, 의미를 명시적으로 나타내기 위해서 상수로 따로 모아두면 정말 좋은거 같네요 👍 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import App from './App.js'; | ||
|
||
const $target = document.querySelector('#app'); | ||
|
||
new App({ $target }); |
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.
동일한 프로토타입을 가진 인스턴스에서 모두 활용할 수 있는 메서드는 프로토타입 객체에 선언하는 것이 좋습니다. this 속성으로 초기화한다면 인스턴스가 생성될 때마다 매번 초기화가 이루어집니다.