기본페이지 틀 만들기
이젠 페이지를 만들어보자.
노션에서 처음 페이지를 만들면 이렇게 생겨먹었다.
일단은 아이콘이 있는 빈 페이지와, 빈 페이지, 아이콘 추가 버튼정도만 구현해보자.
일단 body에 붙일 mainUI 를 만들어서 body에 붙여주자.
this.mainUI = new Dom('div', 'main');
document.body.appendChild(this.mainUI.element);
기본 틀
그리고 iconBar, newPage(페이지의 각 블록, 본문을 가질 부분), newTitle을 만들어서 newPage에 붙이고, newPage를 mainUI에 붙여준다.이후 페이지 전환시에 mainUI는 그대로 있고, newPage만 교체될 것이다.
this.mainUI = new Dom('div', 'main');
document.body.appendChild(this.mainUI.element);
this.newPage = new Dom('div', 'newPage');
this.newTitle = new Dom('input', 'title', '제목없음');
this.iconBar = new Dom('div', 'iconBar', '☺ 아이콘 추가');
this.newPage.append(this.iconBar);
this.newPage.append(this.newTitle);
this.mainUI.append(this.newPage);
아래의 코드로, constructor 실행시에 mainUI에 Dom child가 있는 경우, 그것을 제거해준다.
페이지전환 될때 기존의 페이지를 없애주는 것이다.
if (this.mainUI.hasChildNodes()) {
this.mainUI.removeChild(this.mainUI.firstChild);
}
그다음은 템플릿 버튼들을 만들어주자.
템플릿 버튼 추가
이 템플릿 버튼들은 버튼과 상호작용하거나, 제목에서 enter를 입력하는 경우 없어진다.
그래서 호출하기 쉽게 makeTemplate과 destroyTemplate함수로 분리해주었다.
makeTemplate() {
this.introduce = new Dom(
'div',
'introduce',
'Enter 키를 눌러 빈 페이지로 시작하거나, ↕키로 원하는 템플릿을 선택하세요.'
);
this.noIconTemplate = new Dom('div', 'template', '📄 빈 페이지');
this.iconTemplate = new Dom(
'div',
'template',
'📑 아이콘이 있는 빈 페이지'
);
this.newPage.append(this.iconTemplate);
this.newPage.append(this.noIconTemplate);
this.newPage.append(this.introduce);
}
destroyTemplate() {
this.newPage.remove(this.introduce);
this.newPage.remove(this.iconTemplate);
this.newPage.remove(this.noIconTemplate);
}
본문추가하기(Block)
노션의 한줄은 블록형태로 관리된다.
나는 한 블록(한 줄)을 input태그 하나로 관리하기로 하였다.
현재 Page의 본문영역의 최하단 문맥을 받아서 거기에 Block형태의 Dom을 추가해주었다.
참고로 상수 EMPTY_MESSAGE 는 '명령어 사용 시 "/"를 입력하세요' 이다.
class Block {
constructor(main) {
const newBlock = new Dom('input', 'block', EMPTY_MESSAGE);
newBlock.focus();
main.append(newBlock);
...
}
...
}
본문 keydown 이벤트 추가
본문의 키를 눌렀을 때 일어날 이벤트들을 추가했는데, if else로 관리하다보니 너무 많아져서 객체 형태로 관리하였다.
const keyDownEvent = {
Enter() {
newBlock.placeholder = '';
newBlock.onblur();
new Block(main);
},
Backspace() {
if (newBlock.value !== '') return;
main.remove(newBlock);
if (main.childElementCount != 2)
main.lastChild.placeholder = EMPTY_MESSAGE;
setTimeout(() => main.lastChild.focus(), 0);
},
ArrowUp() {
newBlock.placeholder = '';
setTimeout(() => {
newBlock.previousSibling.focus();
newBlock.previousSibling.placeholder = EMPTY_MESSAGE;
}, 0);
},
ArrowDown() {
if (newBlock.nextSibling) {
newBlock.placeholder = '';
setTimeout(() => {
newBlock.nextSibling.focus();
newBlock.nextSibling.placeholder = EMPTY_MESSAGE;
}, 0);
}
},
};
Enter시에는 새로운 블럭을 만들고, Backspace시에는 블록을 지운다. ArrowUp과 ArrowDown을 통해 문맥을 관리한다.
이벤트 리스너만 달아주면 끝
keyDownEvent객체에 event.key에 해당하는 키값이 없으면 바로 return 한다.
아니면 해당 객체 키값에 맞는 메서드를 호출한다.
newBlock.addEventListener('keydown', (event) => {
if (!Object.keys(keyDownEvent).includes(event.key)) return;
keyDownEvent[event.key]();
});
h1, h2, h3 기능 추가
각 블럭에 공백이 들어온 경우, value값을 검사해서 수행하도록 했다.
newBlock.addEventListener('keyup', (event) => {
if (event.key === ' ' && newBlock.value === '# ') {
newBlock.addClass('h1');
newBlock.value = '';
newBlock.placeholder = '제목1';
}
if (event.key === ' ' && newBlock.value === '## ') {
newBlock.addClass('h2');
newBlock.value = '';
newBlock.placeholder = '제목2';
}
if (event.key === ' ' && newBlock.value === '### ') {
newBlock.addClass('h3');
newBlock.value = '';
newBlock.placeholder = '제목3';
}
});
Page 버튼 기능추가
이제 각종 이벤트 리스너를 달아주자.
1. 제목에서 Enter키 입력하는 경우 템플릿 삭제 후 본문 블록 생성 후 focus 이동
this.newTitle.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
console.log(this.newPage.childElementCount);
if (this.newPage.childElementCount != 2) {
this.destroyTemplate();
}
this.newTitle.onblur();
new Block(this.newPage);
}
});
2. Icon 추가 버튼 클릭시 아이콘 생성
this.iconBar.addEventListener('click', () => this.iconAdd());
...
iconAdd() {
this.iconBar.innerHTML = '😚';
this.iconBar.element.style.fontSize = '70px';
this.iconBar.element.style.width = '80px';
this.iconBar.element.style.height = '80px';
}
3. 아이콘이 있는 빈 페이지 클릭시 아이콘을 추가하고, 템플릿을 제거한후, 본문 내용 생성
this.iconTemplate.addEventListener('click', () => {
this.iconAdd();
this.destroyTemplate();
new Block(this.newPage);
});
4. 빈 페이지 클릭시, 템플릿을 제거한 후 본문내용 생성
this.noIconTemplate.addEventListener('click', () => {
this.destroyTemplate();
new Block(this.newPage);
});
Page 전체코드
import Block from './Block';
import Dom from './Dom';
class Page {
constructor() {
this.mainUI = new Dom('div', 'main');
document.body.appendChild(this.mainUI.element);
this.newPage = new Dom('div', 'newPage');
this.newTitle = new Dom('input', 'title', '제목없음');
this.iconBar = new Dom('div', 'iconBar', '☺ 아이콘 추가');
this.introduce = new Dom(
'div',
'introduce',
'Enter 키를 눌러 빈 페이지로 시작하거나, ↕키로 원하는 템플릿을 선택하세요.'
);
this.newPage.append(this.iconBar);
this.newPage.append(this.newTitle);
this.mainUI.append(this.newPage);
this.newPage.append(this.introduce);
this.makeTemplate();
this.newTitle.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
console.log(this.newPage.childElementCount);
if (this.newPage.childElementCount != 2) {
this.destroyTemplate();
}
this.newTitle.onblur();
new Block(this.newPage);
}
});
this.iconBar.addEventListener('click', () => this.iconAdd());
this.noIconTemplate.addEventListener('click', () => {
this.destroyTemplate();
});
this.iconTemplate.addEventListener('click', () => {
this.iconAdd();
this.destroyTemplate();
new Block(this.newPage);
});
}
makeTemplate(){
this.noIconTemplate = new Dom('div', 'template', '📄 빈 페이지');
this.iconTemplate = new Dom(
'div',
'template',
'📑 아이콘이 있는 빈 페이지'
);
this.newPage.append(this.iconTemplate);
this.newPage.append(this.noIconTemplate);
}
destroyTemplate() {
this.newPage.remove(this.introduce);
this.newPage.remove(this.iconTemplate);
this.newPage.remove(this.noIconTemplate);
}
addIcon(){
this.iconBar.element.innerHTML = '😚';
this.iconBar.element.style.fontSize = '70px';
this.iconBar.element.style.width = '80px';
this.iconBar.element.style.height = '80px';
}
}
export default Page;
완성 페이지 및 기능 모습