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 과제 #56

Open
wants to merge 15 commits into
base: 4/#5_leehyeonjun
Choose a base branch
from

Conversation

hyeonjun-L
Copy link

@hyeonjun-L hyeonjun-L commented Jul 6, 2023

📌 노션 클로닝 프로젝트

프로젝트 링크
노션 클로닝 이지만 노션과 다른 점이 많습니다....

👩‍💻 요구 사항과 구현 내용

공통

  • 화면 좌측에 Root Documents를 불러오는 API를 통해 루트 Documents를 렌더링
  • Root Document를 클릭하면 오른쪽 편집기 영역에 해당 Document의 Content를 렌더링
  • 해당 Root Document에 하위 Document가 있는 경우, 해당 Document 아래에 트리 형태로 렌더링
  • Document Tree에서 각 Document 우측에는 + 버튼이 있습니다. 해당 버튼을 클릭하면, 클릭한 Document의 하위 Document로 새 Document를 생성하고 편집화면으로 이동
  • 편집기에는 기본적으로 저장 버튼이 없습니다. Document Save API를 이용해 지속적으로 서버에 저장
  • History API를 이용해 SPA 형태로 제작
  • 루트 URL 접속 시엔 별다른 편집기 선택이 안 된 상태
  • /documents/{documentId} 로 접속시, 해당 Document 의 content를 불러와 편집기에 로딩

보너스 요구사항

  • div와 contentEditable을 조합해서 좀 더 Rich한 에디터
  • 편집기 최하단에는 현재 편집 중인 Document의 하위 Document 링크를 렌더링
  • 로컬스토리지 임시저장 기능 구현

✅ PR 포인트 & 궁금한 점

store를 이용해서 싱글톤 패턴으로 진행했습니다. 모든 state가 store에 있으며, 관리되게 진행했습니다.
기능에 욕심을 둬서 가독성이 크게 떨어지는 코드를 작성한거 같아 아쉬움이 많습니다.
시간이 있었더라면 리팩토링과 각 컴포넌트의 의존성을 없앨 수 있었을 것 같습니다. 또한, XSS처리하지 못한 것과 editor Tool의 기능적 버그가 많아 아쉬움이 있습니다.

싱글톤 패턴이 잘 사용된 것인지 그리고 코드에 아쉬운 부분이 있다면 의견 주시길 바랍니다.
또한 라우터 관리가 잘된 것인지 궁금합니다.

@Eunseo-jo
Copy link

싱글톤 패턴이란 걸 처음 알아갑니당 과제하느라 고생하셨습니당👍👍

Copy link
Member

@bbearcookie bbearcookie left a comment

Choose a reason for hiding this comment

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

노션 클로닝 고생하셨습니다~
Range 객체의 여러 메소드의 활용이나 싱글톤, 옵저버 패턴을 적절하게 적용해보신 부분이 좋았습니다!
특히 순서 기호나 텍스트 정렬 기능도 최고입니다


async documentTitlePut({ id, title }) {
const { content } = this.state.documentContent;
await request(`/${id}`, {
Copy link
Member

Choose a reason for hiding this comment

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

request 함수의 인자로 들어간 URL이 /documents/${id} 가 아니라 ${id} 인 이유가 궁금합니다!

또한 documentTitlePut 메소드 에서는 content를 구조 분해 할당해서 사용하고,
documentContentPut 메소드에서는 title을 구조 분해 할당해서 사용하셨는데
이름과 동작이 반대인 것 같아서 궁금합니다!

Copy link
Author

@hyeonjun-L hyeonjun-L Jul 10, 2023

Choose a reason for hiding this comment

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

우선 좋은 질문 감사합니다!!

첫번째 질문인 request 함수의 인자로 들어간 URL이 /documents/${id} 가 아니라 ${id} 인 이유는

/documents/${id}로 진행하면 https://kdt-frontend.programmers.co.kr/documents/documents/89594 해당 url로 요청이 가게 되더군요... 저도 왜 저렇게 되는지 정말 궁금합니다... 그래서 일단 /${id} 해당 방식으로 진행 했습니다.
혹시 이유를 알 수 있을까요 ❔ ❔

두번째 질문은 title의 변경이 진행되면 현재 content는 유지가 되어야 하기 때문에 현재 state에서 content값을 구조 분해 할당해서 (현재 content, 변경 된 title) api PUT을 진행 했습니다. 반대로 content도 마찬가지 입니다!

// <button class="action-style-btn" data-action="h3">h3</button>
// <button class="action-style-btn" data-action="h4">h4</button>
// <button class="action-style-btn" data-action="h5">h5</button>
// <button class="action-style-btn" data-action="h6">h6</button>
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
Author

Choose a reason for hiding this comment

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

시간이 부족해서 추후에 추가하려고 주석 처리만 진행해 놨습니다..!

range.deleteContents();
range.insertNode(wrapper);
range.setStartBefore(wrapper);
range.setEndAfter(wrapper);
Copy link
Member

Choose a reason for hiding this comment

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

오.. Range 객체에 이런 다양한 메소드들이 존재했었군요. 유용한 정보 배워갑니다!

Choose a reason for hiding this comment

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

저도 배워갑니다!


notifyEditor() {
this.editorSubscribers.forEach((subscriber) => subscriber());
}
Copy link
Member

Choose a reason for hiding this comment

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

우와.. 이게 그 옵저버 패턴이라는 것인가요? 배워갑니다

@suehdn
Copy link
Member

suehdn commented Jul 10, 2023

고생많으셨어요 현준님!!! 잘 보고 배워갑니다.
결과 페이지도 깔끔하고 보기 좋네요 😊

@suehdn suehdn requested a review from bbearcookie July 10, 2023 13:04
Copy link

@cloud0406 cloud0406 left a comment

Choose a reason for hiding this comment

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

저도 현준님 덕분에 이번에 싱글톤 패턴이란걸 처음 알았네요! 나중에 궁금한게 생기면 여쭤봐도 될까요??
코드가 복잡해서 많이 걱정하셨다고 말씀하셨는데 제 난장판 코드만 보다가 현준님 코드를 보니 오히려 걱정은 제가 했어야 했네요 ㅠㅠ 관심사에 따라 코드가 잘 분리되어있다고 느꼈습니다!
과제하시느라 고생 많으셨습니다 현준님! 항상 좋은 코드보고 배우고있어요👍👍👍

range.deleteContents();
range.insertNode(wrapper);
range.setStartBefore(wrapper);
range.setEndAfter(wrapper);

Choose a reason for hiding this comment

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

저도 배워갑니다!

const action = target.dataset.action;

if (target.tagName === "BUTTON") {
switch (target.classList.value) {

Choose a reason for hiding this comment

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

저도 이번에 어느정도 케이스가 정해져있는 유형은 if문 대신 switch문을 사용해봤는데 더 깔끔해서 좋은 것 같아요! 현준님도 저랑 같은 생각을 하신 것 같아서 따봉 누르고 가겠습니다..👍👍

@GBAJS754
Copy link

싱글톤 패턴 인상적이었습니다! 항상 다양한 방식으로 구현하려고 노력하시는 모습 멋있으세요!!👍 과제하느라 고생하셨습니다😆

const title = event.target.value;
const id = $title.dataset.id;
store.documentTitlePut({ id, title });
}, delay);
Copy link

Choose a reason for hiding this comment

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

timeout 처리하는 부분의 공통화가 가능하겠네요.


applyStyle(action) {
const textStyle = this.getSelectedTextStyle();
const formattedElement = document.createElement("span");
Copy link

Choose a reason for hiding this comment

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

동작을 보면 계속 span이 추가되서 잘 작동하지 않는 것 같은데 테스트가 필요할 것 같습니다.
Screenshot 2023-07-14 at 8 21 59 AM

string = this.createCustomListString(documents, string);
}
string += "</ul>";
return string;
Copy link

Choose a reason for hiding this comment

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

조금 가독성을 높이도록 리팩토링 해보면 좋을 것 같은 부분입니다.
크게 style을 지정하는 곳, document 엘리먼트를 추가하는 곳, 재귀 순회하는 곳으로 나뉘는 것 같은데 이 부분들의 역할이 분명히 보이게 함수를 분리할 수도 있을 것 같네요.

Comment on lines +18 to +20
const style = store.state.selectedStyles;
document.execCommand(style);
store.state.selectedStyles = "";

Choose a reason for hiding this comment

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

스토어에서 선택된 스타일도 관리하시는 군요-!! store 상태를 직접 변경하는 거랑 setState 함수로 변경하는 거랑 어떤 방법이 더 좋은지 궁금하네요 👀👀

export default class DocumentAddBtn {
constructor({ $target }) {
this.$target = $target;
this.$button = document.createElement("form");

Choose a reason for hiding this comment

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

createElement를 button말고 form으로 하신 이유가 궁금합니당-!

targetDocument.style.transform = "rotate(-90deg)";
} else if (folded === "false") {
const foldedList = getItem("folded", []);
foldedList.splice(foldedList.indexOf($targetParent.id), 1);

Choose a reason for hiding this comment

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

filter로 쓰면 코드가 좀 더 간결해질꺼 같아요 :))

Comment on lines +96 to +103
string += `<span id=${currentDocumentId} class="select-document" ${selectStyle}>
${
nextDocument.length
? `<img class="toggle-folder" ${imgStyle} src=${arrowImg} alt="arrow.svg"/>`
: ""
}
<img src=${documentImg} alt="document.svg"/>
<span id=${currentDocumentId} class="document-list-title">

Choose a reason for hiding this comment

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

id는 unique한 값이라 data-id로 하면 좋을꺼 같습니당-!

Comment on lines +119 to +121
const htmlString = this.state.reduce((acc, doc) => {
return this.createCustomListString(doc, acc);
}, "");

Choose a reason for hiding this comment

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

reduce로 문자열 합치는 방법,, 배워갑니당 👍

Comment on lines +28 to +31
const currentTime = new Date().toISOString();

clearTimeout(timer);
setItem(this.documentId, { content, saveTime: currentTime });
Copy link
Member

Choose a reason for hiding this comment

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

제가 기억하기론 API에 createdAt과 updatedAt으로 생성시간과 수정시간이 기록되어 있기때문에 저장 시간을 설정하고 싶으셨다면 없어도 되지 않을까요?🤗

Comment on lines +60 to +76
this.$list.addEventListener("mouseover", (event) => {
const targetDocument = event.target;

if (targetDocument.documentListEditor) {
targetDocument.documentListEditor.show();
return;
}

if (targetDocument.matches(".select-document")) {
const documentListEditor = new DocumentListEditor({
$target: targetDocument,
});

targetDocument.documentListEditor = documentListEditor;

targetDocument.addEventListener("mouseleave", () => {
targetDocument.documentListEditor.hide();
Copy link
Member

Choose a reason for hiding this comment

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

저는 토글버튼을 이용했는데 클릭이벤트를 이용해 hide, show를 하는 방법이 있었군요. 배워갑니다!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants