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

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

Conversation

DongjaJ
Copy link
Member

@DongjaJ DongjaJ commented Jul 6, 2023

📌 과제 설명

Vanilla Javascript를 이용해 노션 클로닝 과제를 진행했습니다

HTML, CSS, JAVASCRIPT를 사용했으며
클래스 변경 네이밍을 쉽게 하기 위해 PostCSS를 추가로 사용했고 번들러로 Vite를 사용했습니다.
그리고 import를 깔끔하게 하기 위해 절대경로를 설정했고 디렉토리 별로 index.js를 만들었습니다.
배포는 Netlify로 했습니다.

배포 링크

https://brilliant-dango-09fdf9.netlify.app/

사용법

cd Notion_VanillaJS
yarn install
yarn dev

깃허브 위키

https://github.com/DongjaJ/FEDC4-5_Project_Notion_VanillaJS/wiki

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

📌 요구사항

✏️ 기본 요구사항

  • 기본적인 레이아웃은 노션과 같으며, 스타일링, 컬러값 등은 원하는대로 커스텀합니다.
  • 글 단위를 Document라고 합니다. Document는 Document 여러개를 포함할 수 있습니다.
  • 화면 좌측에 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를 불러와 편집기에 로딩합니다.

✨ 보너스 요구사항

  • 기본적으로 편집기는 textarea 기반으로 단순한 텍스트 편집기로 시작하되, 여력이 되면 div와 contentEditable을 조합해서 좀 더 Rich한 에디터를 만들어봅니다.
  • 편집기 최하단에는 현재 편집 중인 Document의 하위 Document 링크를 렌더링하도록 추가합니다.
  • 편집기 내에서 다른 Document name을 적은 경우, 자동으로 해당 Document의 편집 페이지로 이동하는 링크를 거는 기능을 추가합니다.
  • 그외 개선하거나 구현했으면 좋겠다는 부분이 있으면 적극적으로 구현해봅니다!
    • 알림창, 브레드 크럼을 구현했습니다!

구현 내용

왼쪽의 사이드 바와 오른쪽의 페이지로 구성되어 있으며 오른쪽의 페이지는 url에 따라 홈, 게시글, NotFound 이렇게 세 개의 페이지가 있습니다

홈 화면

노션 클로닝-홈 화면

홈페이지에 접속하면 가장 먼저 뜨는 페이지입니다.

사이드 바

노션-사이드바 라우팅

모든 게시글을 보여줄 수 있는 사이드바입니다. 서브 게시글이 있는 게시글은 드롭다운 버튼이 있으며
제목의 여부에 따라 아이콘을 변경했습니다.

  • 게시글 추가, 삭제 기능
    노션 클로닝-추가,삭제

게시글 페이지

노션 클로닝 - PostEdit 페이지

게시글 페이지 입니다
url의 id에 해당하는 게시글을 보여주며 만약 해당하는 id에 해당하는 게시글이 없다면 홈으로 라우팅합니다.
게시글 세부 내용 조회, 수정 및 저장이 가능하며
게시글의 컨텐츠는 스타일을 변경할 수 있고 상단에는 브레드 크럼을 추가해 상위 페이지로 이동할 수 있도록 했습니다.
그리고 하단에서 게시글의 서브 리스트들을 볼 수 있습니다.

  • 제목 및 내용 입력, 저장
    노션클로닝-게시글 입력 및 저장

  • 스타일 변경
    노션클로닝-스타일 변경

굵기, 기울임, 밑줄, 취소선으로 글자 스타일 변경이 가능하며
왼쪽, 가운데, 오른쪽 정렬 또한 가능합니다.

  • 서브리스트 및 브레드 크럼
    노션 클로닝 - 브레드 크럼, 서브 리스트

알림창

노션 클로닝- 알림창

게시글이 추가, 수정, 삭제가 될때마다 알림을 보여주면서 사용자에게 해당 기능이 잘 작동함을 알려줍니다.

NotFound 페이지
노션 클로닝- Not Found

홈이나 게시글 페이지와는 다른 uri를 입력하면 이동하는 페이지입니다.
홈에서 쿠로미가 들고 있는 큐브와 유사하게 생긴 주사위를 굴릴 수 있도록 했습니다.

디렉토리 구조
디렉토리-1

디렉토리-2

프로젝트 구조

flux

FlUX패턴으로 설계했습니다.
Redux와 유사하게 모든 비즈니스 로직은 외부로 분리했으며
모든 게시글의 정보를 다루는 PostListStore와 현재 게시글의 정보를 다루는 PostStore를 만들었습니다.
Store에서 Action이 발생한다면 Store가 해당 Store에 구독하고 있는 컴포넌트의 render함수가 실행됩니다.

컴포넌트를 생성할 때는 Component의 static함수를 이용해 생성했습니다. - 팩토리 패턴을 따라 해봤습니

✅ PR 포인트 & 궁금한 점

🔖 PR포인트

  • Store에 구독할 필요 없는데 구독한 컴포넌트가 있을 것 같습니다. 저도 리팩토링하면서 찾아보겠지만 혹시 찾으신다면 말씀 해주시면 좋을 것 같습니다!
  • document.execcommand를 대체하려고 Range와 Selection Api를 이용해서 구현해보았습니다.

❓ 궁금한점

  • PostCSS를 사용하면서 클래스 네이밍은 용이해졌지만 그와 반대로 css파일이 많아지고 흩어지면서 오히려 css을 수정하기 어려울수도 있겠다라는 생각이 들었는데 이에 대한 의견이 궁금합니다
  • content를 입력할 때 textarea가 아니라 contenteditable한 div를 사용했는데 그때문에 렌더될 때마다 콘텐츠 div의 맨앞으로 커서가 이동하는 문제가 있었습니다. 이를 해결하기 위해 렌더링될때마다 항상 포커스가 div의 마지막으로 이동하게 했는데 이렇게 하니 제목을 수정할때도 콘텐츠의 마지막으로 커서가 이동해버렸습니다.
    • 다른 좋은 해결 방법이 있나 궁금합니다.
    • 해결하기 어렵다면 둘중에 어떤 방식이 나을 지 고민됩니다.
      1. content를 수정하고 렌더링 할 때 div의 맨앞으로 커서 이동하도록 놔두기
      2. 렌더링할때마다 모든 컨텐츠의 뒤로 포커스 이동하도록 하기

Copy link
Member

@judahhh judahhh left a comment

Choose a reason for hiding this comment

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

칭찬 밖에 해줄 수 없어서 죄송할 따름이지만 항상 동환님 코드 보면 구글링 하면서 저도 많이 알아가는 것 같아요! 항상 배움을 주셔서 감사하고 과제하느라 정말 고생많으셨습니다!!😆

Comment on lines 16 to 21
return `<header>Dongja's Notion</header>
${
Array.isArray(postList)
? this.getPostListTemplate(postList)
: `<h2>로딩중</h2>`
}
Copy link
Member

Choose a reason for hiding this comment

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

p5 : 로딩 중일 때의 화면 렌더링 하신걸 보니 투두리스트 때보다 ux에 신경을 많이 쓰신게 보입니다!

payload: { id: deletedId },
});
const nowId = PostStore.getState()?.post?.id;
if (parseInt(deletedId) === nowId) push('/');
Copy link
Member

Choose a reason for hiding this comment

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

p5 : 삭제한 메모장의 상위 메모장이 있어도 무조건 홈으로 라우팅 되나요??


if (!childList) return;

dropDownButton.classList.toggle(`${styles.down}`);
Copy link
Member

Choose a reason for hiding this comment

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

p5 : toggle() 이라는 메서드도 있었군요..! 동환님 코드 보면서 저도 구글링 하면서 찾아보게 되네요

Comment on lines 88 to 93
this.addEvent({
eventType: 'click',
selector: `.${styles.title}`,
callback: this.onClickLink,
});
}
Copy link
Member

Choose a reason for hiding this comment

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

p5 : 크.. 역시 함수 분리 장인👍

Notion_VanillaJS/src/store/PostListStore.js Outdated Show resolved Hide resolved
Copy link
Member

@imb96 imb96 left a comment

Choose a reason for hiding this comment

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

기본 요구사항은 물론이고 추가로 기능도 만드시고 대단하십니다
디자인도 이쁘고 이스터에그도 재밌었습니다 👍

Notion_VanillaJS/src/App.js Show resolved Hide resolved
Notion_VanillaJS/src/App.js Show resolved Hide resolved
Copy link

@nsr1349 nsr1349 left a comment

Choose a reason for hiding this comment

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

안녕하세요 동환님 과제하느라 고생하셨습니다! 저는 좀 이런 파일 나뉘고 이런거가 익숙치 않아서 별 말씀을 못 드린 것 같습니다 ㅜㅜ 다음 과제도 화이팅입니다..

Notion_VanillaJS/src/core/Store.js Show resolved Hide resolved
Notion_VanillaJS/src/App.js Show resolved Hide resolved
Notion_VanillaJS/index.html Show resolved Hide resolved
padding: 2rem 5rem;
}

.headerTitle {
Copy link
Member

Choose a reason for hiding this comment

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

css에서는 camel case가 아닌 kebab case를 사용합니다. header-title

README.md Show resolved Hide resolved
if (parseInt(deletedId) === nowId) push('/');
}

async onClickAdd({ target }) {
Copy link
Member

Choose a reason for hiding this comment

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

이런 event handler들은 저는 하나로 묶어서 관리합니다.

const postHandler = {
  async onClickDelete({ target }) {},
  async onClickAdd({ target }) {},
  onClickToggle({ target }) {},
  onClickLink({ target }) {}
}
postHandler.onClickDelete(event);
postHandler.onClickAdd(event);

@@ -0,0 +1,83 @@
function isAppliedAlign($parent, style) {
Copy link
Member

Choose a reason for hiding this comment

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

요놈들도 쓰임새에 따라 각각 객체로 묶어서 연관된 것들을 관리해보는건 어떨까요?

@yohanpro
Copy link
Member

안녕하세요 동환님. 클래스를 사용하고 저번에 말했던 FLUX패턴을 사용해서 만드신 부분이 눈에띄네요.
실제로 다음에 프로젝트를 들어가서 Redux같은 툴을 사용하면 지금 만들었던 경험으로 더 쉽게 만드실수 있을것 같습니다.

프로젝트의 완성도도 매우 높고, 다른사람이 수정하기에도 어느정도 용이하게 잘 만드셨습니다.


PostCSS를 사용하면서 클래스 네이밍은 용이해졌지만 그와 반대로 css파일이 많아지고 흩어지면서 오히려 css을 수정하기 어려울수도 있겠다라는 생각이 들었는데 이에 대한 의견이 궁금합니다

-> 각각 css를 모듈화해서 사용하셨는데 좋은 방법입니다. 실제로도 styled component같은 것을 사용하지 않는다면 위와같은 방식으로 관리합니다. 다만 css가 은근히 용량이 크기 때문에 번들러에서 압축을 잘 해야합니다.

content를 입력할 때 textarea가 아니라 contenteditable한 div를 사용했는데 그때문에 렌더될 때마다 콘텐츠 div의 맨앞으로 커서가 이동하는 문제가 있었습니다. 이를 해결하기 위해 렌더링될때마다 항상 포커스가 div의 마지막으로 이동하게 했는데 이렇게 하니 제목을 수정할때도 콘텐츠의 마지막으로 커서가 이동해버렸습니다.

다른 좋은 해결 방법이 있나 궁금합니다.

-> React라면 ref와 useEffect를 사용해서 해결했을텐데, 순수 js로는 어렵네요. 위의 제시한 방법중에서는 둘다 사용자에게 불편하기 때문에, 지금 작성하신 그대로 둬도 될것 같네요

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.

5 participants