수정 위치에 표식용 태그를 삽입 후 해당 태그 위치를 커서 위치로 정하는 방식을 택했다.
const getPosition = (parentElement, offset) => {
let currentNode = parentElement;
const indexStack = [];
let count = 0;
while (currentNode === $content) {
let text = currentNode.outerHTML;
count += 1;
indexStack.push(getIndex(currentNode));
currentNode = currentNode.parentElement;
}
for (let i = 0; i < indexStack.length-1; i++) {
currentNode = currentNode.children[indexStack[i]];
}
return currentNode;
}
const getIndex = (element) => {
let count = 0;
while ((element = element.previousSibling) != null) {
count += 1;
}
return count;
};
$content.addEventListener("compositionend", (e) => {
const selection = window.getSelection();
const node = selection.focusNode;
const offset = selection.focusOffset;
const elementPosition = getPosition(node.parentElement, offset);
const $empty = document.createElement('span');
$empty.setAttribute('class', 'empty');
if(!elementPosition.querySelector('.empty')) {
elementPosition.appendChild($empty)
}
...
}
$content.addEventListener("compositionend", (e) => {
let selection = window.getSelection();
const node = selection.focusNode;
let offset = selection.focusOffset;
// 첫 입력시 div가 없어서 div를 씌어주기 위한 판별
const isDiv = e.target.innerHTML.includes("<div>");
// span을 삽입할 node를 찾는다.
const elementPosition = getPosition(node.parentElement, offset);
const $empty = document.createElement("span");
$empty.setAttribute("class", "empty");
// 변환이 되면서 줄어든 문자열을 반영
const text = transformText(node.data);
if (text !== node.data) {
offset = deleteText(node.data).length;
if (offset === text.length) {
offset = text.length;
}
} else {
if (offset === node.data.length) {
offset = node.data.length;
}
}
if (!elementPosition.querySelector(".empty")) {
elementPosition.appendChild($empty);
}
this.setState({
...this.state,
content: isDiv ? e.target.innerHTML : `<div>${e.target.innerHTML}</div>`,
});
// 원래 있던 range를 모두 제거
selection.removeAllRanges();
// 새로운 range생성
const range = document.createRange();
const temp = document.querySelector(".empty");
// range의 처음부터 offset까지 범위 설정
range.setStart(temp.previousSibling, 0);
range.setEnd(temp.previousSibling, offset);
range.collapse(false);
// 위치 표시해주는 span태그를 range에 더해준다.
selection.addRange(range);
// 위치를 찾기 위해 임시로 삽입한 span태그 제거
temp.remove();
onEditing(this.state);
});
const transformTag = (text) => {
let h1Pattern = /<div>#\s+(.*?)<\/div>/g;
let h2Pattern = /<div>##\s+(.*?)<\/div>/g;
let h3Pattern = /<div>###\s+(.*?)<\/div>/g;
let h4Pattern = /<div>####\s+(.*?)<\/div>/g;
let boldPattern = />(.*?)\*\*(.*?)\*\*(.*?)</g
let italicPattern = />(.*?)_(.*?)_(.*?)</g;
let strikePattern = />(.*?)~~(.*?)~~(.*?)</g;
return text
.replace(h1Pattern, "<div><h1>$1</h1></div>")
.replace(h2Pattern, "<div><h2>$1</h2></div>")
.replace(h3Pattern, "<div><h3>$1</h3></div>")
.replace(h4Pattern, "<div><h4>$1</h4></div>")
.replace(boldPattern, ">$1<b>$2</b>$3<")
.replace(italicPattern, ">$1<i>$2</i>$3<")
.replace(strikePattern, ">$1<s>$2</s>$3<")
.replace(/ /g, " ")
.replace(/\n/g, "<br>")
.replace(/<h1><br><\/h1>/g, "<br>")
.replace(/<h2><br><\/h2>/g, "<br>")
.replace(/<h3><br><\/h3>/g, "<br>")
.replace(/<h4><br><\/h4>/g, "<br>")
.replace(/<i><br><\/i>/g, "<br>")
.replace(/<b><br><\/b>/g, "<br>")
.replace(/<s><br><\/s>/g, "<br>");
};
const transformText = (text) => {
return text
.replace("#### ", "")
.replace("### ", "")
.replace("## ", "")
.replace("# ", "")
.replace(/\*\*(.*?)\*\*/g, "$1")
.replace(/_(.*?)_/g, "$1")
.replace(/~~(.*?)~~/g, "$1");
};
const deleteText = (text) => {
if (text.indexOf("#") === 0) {
text = text
.replace("#### ", "")
.replace("### ", "")
.replace("## ", "")
.replace("# ", "");
}
return text
.replace(/(.*?)\*\*(.*?)\*\*/g, "")
.replace(/(.*?)_(.*?)_/g, "")
.replace(/(.*?)~~(.*?)~~/g, "");
};