Skip to content
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

Open
wants to merge 13 commits into
base: 4/#5_leeseunghee
Choose a base branch
from
Open
15 changes: 15 additions & 0 deletions .eslintrc.json
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"]
}
}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
.DS_Store
.eslintcache
.vscode
12 changes: 12 additions & 0 deletions .prettierrc
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"
}
23 changes: 23 additions & 0 deletions index.html
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>
16 changes: 16 additions & 0 deletions package.json
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"
}
}
37 changes: 37 additions & 0 deletions src/App.js
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),
});
}
};
Comment on lines +17 to +32
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

동일한 프로토타입을 가진 인스턴스에서 모두 활용할 수 있는 메서드는 프로토타입 객체에 선언하는 것이 좋습니다. this 속성으로 초기화한다면 인스턴스가 생성될 때마다 매번 초기화가 이루어집니다.


this.route();

initRouter(() => this.route());
}
20 changes: 20 additions & 0 deletions src/api.js
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
Copy link
Member

Choose a reason for hiding this comment

The 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);
}
};
3 changes: 3 additions & 0 deletions src/assets/arrow-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/plus-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/assets/trash-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions src/components/Document/DocumentHeader.js
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();
}
45 changes: 45 additions & 0 deletions src/components/Document/Editor.js
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;
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

불필요하다고 말할 필요까지는 없을 것 같지만 🤔, throttling 기법을 적용한다면 효율을 높일 수 있을 것 같네요.

}
17 changes: 17 additions & 0 deletions src/components/Sidebar/CreateButton.js
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();
}
80 changes: 80 additions & 0 deletions src/components/Sidebar/DocumentList.js
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
Copy link
Member

Choose a reason for hiding this comment

The 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();
}
15 changes: 15 additions & 0 deletions src/components/Sidebar/SidebarHeader.js
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();
}
10 changes: 10 additions & 0 deletions src/constants.js
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: '',
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반복되는 부분을 줄이기 위해서, 의미를 명시적으로 나타내기 위해서 상수로 따로 모아두면 정말 좋은거 같네요 👍

5 changes: 5 additions & 0 deletions src/main.js
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 });
Loading