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_VanilaJs 과제 #58

Open
wants to merge 8 commits into
base: 4/#5_woohyunji
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
body {
margin: 0;
}
#app {
width: 100%;
display: flex;
}
.sidebar {
width: 25%;
min-width: 250px;
background-color: rgb(247, 247, 245);
padding: 5px;
}
.sidebar_header {
padding: 8px;
border-bottom: 1px solid black;
}
.sidebar_newDocument {
Copy link

Choose a reason for hiding this comment

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

p3;
네이밍 컨벤션을 하나로 통일하면 좋을 듯 합니다.
현재는 snake케이스와 camel케이스가 혼용되어있네요!

cursor: pointer;
box-sizing: border-box;
padding: 3px;
}
.sidebar_newDocument:hover {
background-color: rgb(232, 232, 230);
}

.document_list > ul {
padding-left: 8px;
}
.document_li {
list-style: none;
cursor: pointer;
}

section {
width: 100%;
padding: 8px;
}
.editor {
display: flex;
flex-direction: column;
width: 100%;
height: 100vh;
}
.title {
flex-grow: 1;
width: 100%;
height: 50px;
margin-bottom: 10px;
padding: 5px 20px;
outline: none;
}
.content {
width: 100%;
height: 100%;
padding: 20px;
outline: none;
}
13 changes: 13 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>노션 클로닝</title>
<link rel="stylesheet" href="/index.css" />
</head>
<body>
<div id="app"></div>
<script src="/src/main.js" type="module"></script>
</body>
</html>
19 changes: 19 additions & 0 deletions src/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const API_END_POINT = 'https://kdt-frontend.programmers.co.kr';

export const request = async (url, options = {}) => {
try {
const res = await fetch(`${API_END_POINT}${url}`, {
...options,
headers: {
'x-username': 'whj',
'Content-Type': 'application/json',
},
});
if (res.ok) {
return await res.json();
}
throw new Error('API처리중 문제 발생!!');
} catch (e) {
console.log(e.message);
}
};
71 changes: 71 additions & 0 deletions src/components/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import DocumentListPage from './Sidebar/DocumentListPage.js';
import EditPage from './Editor/EditPage.js';
import ListHeader from './Sidebar/ListHeader.js';
import { request } from '../api.js';

export default function App({ $target }) {
const $sidebar = document.createElement('aside');
$sidebar.className = 'sidebar';
const $editor = document.createElement('section');

new ListHeader({
$target: $sidebar,
onNewDocument: () => {
console.log('새로운 문서');
Copy link

Choose a reason for hiding this comment

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

p3;
PR업로드시에는 콘솔 제거해주세요~

const nextState = {
documentId: 'new',
parent: null,
};
editPage.setState(nextState);
},
});

const documentListPage = new DocumentListPage({
$target: $sidebar,
Comment on lines +11 to +24
Copy link
Member

Choose a reason for hiding this comment

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

p3;
SideBar에 해당되는 컴포넌트들은 SideBar 폴더의 DocumentListPage에서 컴포넌트를 생성하는 방향도 좋을 것 같아요!

onSelectDocument: (documentId) => {
const nextState = { documentId };
console.log(nextState);
editPage.setState(nextState);
},
onCreateDocument: (documentId) => {
const parent = documentId;
const nextState = {
documentId: 'new',
parent,
};
editPage.setState(nextState);
},
onRemoveDocument: async (documentId) => {
console.log('remove');
await request(`/documents/${documentId}`, {
method: 'DELETE',
});
documentListPage.setState();
},
Comment on lines +25 to +44

Choose a reason for hiding this comment

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

함수명 설정이 너무 좋은 것 같습니다! 저도 좀 더 구체적으로 정해야겠네요ㅎㅎ 배워가요!

Copy link
Member

Choose a reason for hiding this comment

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

👍👍👍👍👍 함수명 덕분에 코드를 이해하기 좋았습니당!!

Copy link

Choose a reason for hiding this comment

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

👍 👍 👍 👍 👍 👍 👍 👍

});

const editPage = new EditPage({
$target: $editor,
initialState: {
documentId: 'new',
document: {
title: '',
content: '',
},
parent: null,
},
onChange: () => {
documentListPage.setState();
},
});

this.route = () => {
$target.append($sidebar, $editor);
const { pathname } = window.location;
if (pathname === '/') {
Copy link

Choose a reason for hiding this comment

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

p4;
isNew로 처리하셨던 것처럼 변수명으로 표현하면 읽기 편할 것 같습니다!

documentListPage.setState();
editPage.render();
}
};
this.route();
}
72 changes: 72 additions & 0 deletions src/components/Editor/EditPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { request } from '../../api.js';
import Editor from './Editor.js';

export default function EditPage({ $target, initialState, onChange }) {
const $page = document.createElement('div');

this.state = initialState;

const fetchDocument = async () => {
const { documentId } = this.state;
if (documentId !== 'new') {
const document = await request(`/documents/${documentId}`); //api GET
this.setState({
...this.state,
document,
});
}
};

let timer = null;
const editor = new Editor({
$target: $page,
initialState: this.state.document,
onEditing: (document) => {
if (timer !== null) {
clearTimeout(timer);
}
timer = setTimeout(async () => {
const isNew = this.state.documentId === 'new';
Copy link

Choose a reason for hiding this comment

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

변수명 좋습니다 👍

if (isNew) {
const createdDocument = await request('/documents', {
method: 'POST',
body: JSON.stringify({
title: document.title,
parent: this.state.parent,
}),
});
this.setState({
documentId: createdDocument.id,
document,
});
onChange();
} else {
await request(`/documents/${this.state.documentId}`, {
method: 'PUT',
body: JSON.stringify(document),
});
}
onChange();
}, 1000);
},
});

this.setState = async (nextState) => {
if (this.state.documentId !== nextState.documentId) {
this.state = nextState;
if (this.state.documentId === 'new') {
this.render();
editor.setState({ title: '', content: '' });
} else {
await fetchDocument();
}
return;
}
Comment on lines +55 to +64
Copy link

Choose a reason for hiding this comment

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

if가 이중중첩이네요 🤔
nested 구조가 가독성을 낮출 수 있는데, 리팩토링해보면 어떨까요~?

this.state = nextState;
editor.setState(this.state.document || { title: '', content: '' });
Copy link

Choose a reason for hiding this comment

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

OR연산자 사용하신 거 좋습니당!!! 👍

};

this.render = () => {
$target.appendChild($page);
};
}
45 changes: 45 additions & 0 deletions src/components/Editor/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('form');
$editor.className = 'editor';
$target.appendChild($editor);

const $title = document.createElement('input');
$title.className = 'title';
$title.name = 'title';
$title.placeholder = '제목을 작성하세요...';

const $content = document.createElement('textarea');
$content.className = 'content';
$content.name = 'content';
$content.placeholder = '내용을 입력하세요...';

let isInitialize = false;

this.state = initialState;
this.setState = (nextState) => {
this.state = nextState;
this.render();
};

$editor.addEventListener('keyup', (e) => {
const name = e.target.getAttribute('name');
Copy link

Choose a reason for hiding this comment

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

이벤트 위임을 잘해주신 것 같아 좋습니다 👍
변수명이 조금더 직관적이면 좋을 것 같네요!

if (this.state[name] !== undefined) {
Copy link

Choose a reason for hiding this comment

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

p3;

Suggested change
if (this.state[name] !== undefined) {
if (this.state[name]) {

truthy로 처리 가능할 것 같네요

const nextState = {
...this.state,
Copy link

Choose a reason for hiding this comment

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

👍

[name]: e.target.value,
};
this.setState(nextState);
onEditing(this.state);
}
});

this.render = () => {
if (!isInitialize) {
$editor.append($title, $content);
isInitialize = true;
}
$title.value = this.state.title;
$content.value = this.state.content;
};
this.render();
}
73 changes: 73 additions & 0 deletions src/components/Sidebar/DocumentList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
export default function DocumentList({
$target,
initialState,
onSelectDocument,
onCreateDocument,
onRemoveDocument,
}) {
const $list = document.createElement('div');
$list.className = 'document_list';
$target.appendChild($list);

if (Array.isArray(initialState)) {
this.state = initialState;
}
Comment on lines +12 to +14
Copy link
Member

Choose a reason for hiding this comment

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

initialState validation 과정 잊었는데 저도 추가해야겠네요! 👍👍👍

this.setState = (nextState) => {
this.state = nextState;
this.render();
};
this.render = () => {
const $ul = document.createElement('ul');

this.state.map((documentInfo) => {
const $li = document.createElement('li');
$li.className = 'document_li';
$li.setAttribute('data-id', documentInfo.id);

const $title = document.createElement('span');
$title.textContent = documentInfo.title;
$title.className = 'title';
$li.appendChild($title);

const $createBtn = document.createElement('button');
$createBtn.textContent = '+';
$createBtn.className = 'createBtn';
$li.appendChild($createBtn);

const $removeBtn = document.createElement('button');
$removeBtn.textContent = '-';
$removeBtn.className = 'removeBtn';
$li.appendChild($removeBtn);

if (documentInfo.documents.length > 0) {
new DocumentList({
$target: $li,
initialState: documentInfo.documents,
});
}
$ul.appendChild($li);
Comment on lines +19 to +48

Choose a reason for hiding this comment

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

innerHTML 대신에 appendChild 너무 잘 쓰시는 것 같아요... 저는 피드백 받아도 아직 익숙하지 않네요ㅠㅠ 나중에 기회가 된다면 배워보고 싶네요!

});
$list.replaceChildren($ul);

$ul.addEventListener('click', (e) => {
const $li = e.target.closest('li');
if ($li !== null) {
const documentId = $li.dataset.id;

// document 선택
if (e.target.className === 'title') {
onSelectDocument(documentId);
}
//하위 document 생성
if (e.target.className === 'createBtn') {
onCreateDocument(documentId);
}
// document 삭제
else if (e.target.className === 'removeBtn') {
onRemoveDocument(documentId);
}
}
});
Comment on lines +52 to +70
Copy link
Member

Choose a reason for hiding this comment

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

이벤트 위임 깔끔하게 잘 된것 같습니다!!

Comment on lines +53 to +70
Copy link

Choose a reason for hiding this comment

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

👍
early return으로 작성되면 좀더 깔끔할 것 같군요!

Suggested change
const $li = e.target.closest('li');
if ($li !== null) {
const documentId = $li.dataset.id;
// document 선택
if (e.target.className === 'title') {
onSelectDocument(documentId);
}
//하위 document 생성
if (e.target.className === 'createBtn') {
onCreateDocument(documentId);
}
// document 삭제
else if (e.target.className === 'removeBtn') {
onRemoveDocument(documentId);
}
}
});
const $li = e.target.closest('li');
if (!$li) return;
const documentId = $li.dataset.id;
// document 선택
if (e.target.className === 'title') {
onSelectDocument(documentId);
}
//하위 document 생성
if (e.target.className === 'createBtn') {
onCreateDocument(documentId);
}
// document 삭제
if (e.target.className === 'removeBtn') {
onRemoveDocument(documentId);
}
});

};
this.render();
}
24 changes: 24 additions & 0 deletions src/components/Sidebar/DocumentListPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { request } from '../../api.js';
import DocumentList from './DocumentList.js';
export default function DocumentListPage({
$target,
onSelectDocument,
onCreateDocument,
onRemoveDocument,
}) {
const $page = document.createElement('div');
$target.appendChild($page);

const documentList = new DocumentList({
$target: $page,
initialState: [],
onSelectDocument,
onCreateDocument,
onRemoveDocument,
});

this.setState = async () => {
const documents = await request('/documents');
documentList.setState(documents);
};
}
Loading