[TIL 2021.11.06] 태그가 포함된 텍스트 하이라이트 (6)

Kyu·2021년 11월 6일
0

TIL

목록 보기
299/322

Today I Learned

구현이 막혀서 구현 방법을 찾아봤으나 찾질 못해 다른 프로젝트를 참고하기 위해 그 프로젝트에서 사용하는 언어를 배우려는 상황.

그 전에 내 코드를 리뷰해보자.

어제 TIL대로 원하는 구현에 대한 방법에 대해서 찾아봤으나 내가 원하는 조건에 대한 구현 방법은 검색해도 나오지 않아서 내가 타입스크립트를 제대로 배워서 기존에 있는 코드를 해석해서 아이디어를 얻는게 가장 빠른 방법일거라고 생각했다.

그 전에, 현재 작성한 코드를 스스로 리뷰하면서 문제점이 뭔지 다시 한번 짚고 집중해야할 부분이 어디인지 리마인드 하는 시간을 가졌다.


1.

document.addEventListener("mouseup", () => {
    if (document.getSelection()?.toString().length) {
        const selection = document.getSelection() as Selection;
        const selectedText = selection.toString();

마우스를 클릭하고 뗀 순간에 document.getSelection을 통해 Selection 인터페이스를 가져온다.
이 때 selectedText는 드래그 한 text를 string으로 가져온 것이다.
selectedText는 html이 포함되어있으면 html태그는 지우고 가져온다.

2.

insertSpanToSelectedText(selection, selectedText);

function insertSpanToSelectedText(selection: Selection, selectedText: string): void {
    const span = document.createElement("SPAN");
    span.textContent = selectedText;
    span.setAttribute("class", "yellow-highlight")

    const range = selection.getRangeAt(0);
    range.deleteContents();
    range.insertNode(span);
}

사실 여기까지 하면 html이 포함되지 않은 태그에 대한 하이라이트 처리는 이것으로 끝이다.
document.createElement()를 통해서 Element를 생성하고 텍스트도 집어넣고 css처리를 위한 클래스도 설정한다.
selection.getRangeAt()을 통해 Range 를 가져오고 내용을 삭제하고 거기에 새로 만든 Element를 집어넣는다.
결과를 보면 여기서 deleteContents()는 추측으로는 안의 Text 만 삭제하고 span을 넣기 때문에
html 태그가 포함되어있었다면 그 태그는 지워지지않고 뒤로 밀리는데 어떤 원리인지 모르겠다. 이건 확인 해봐야함.

3.

const parentElement = selection.anchorNode?.parentElement as HTMLElement;
const elements = document.getElementsByTagName(parentElement.tagName);
const indexOfTags = getIndexOfTags(parentElement, elements);
const textContent = elements.item(indexOfTags)?.textContent as string;
const startOffset = textContent.indexOf(selectedText) as number;

const selectedData = new Meta(
    parentElement.tagName,
    indexOfTags,
    selectedText,
    startOffset,
    textContent
);

여긴 백엔드에 저장할 selectedText의 메타정보이다.
이 중에 2개는 실제로 백엔드에서 꺼내올 때 쓰이진 않는데, 일단은 나중을 위해 코드를 남겨둠.

4.

setItemToLocalStorage(selectedData);

function setItemToLocalStorage(selectedData: Meta) {
    let metaData: Meta[];
    if (window.localStorage.getItem("meta")) {
        const s = window.localStorage.getItem("meta") as string;
        metaData = JSON.parse(s);
    } else {
        metaData = [];
    }

    metaData.push(selectedData);

    const strLocationInfo = JSON.stringify(metaData);
    window.localStorage.setItem("meta", strLocationInfo);
}

메타정보를 백엔드에 넣어준다. 현재는 로컬스토리지이다.
참고로 로컬스토리지는 키밸류값인데 밸류값이 무조건 string 여야함.

5.

if (window.localStorage.getItem("meta")) {
    let metaData: Meta[];
    const s = window.localStorage.getItem("meta") as string;
    metaData = JSON.parse(s);

    window.addEventListener("load", function () {
        metaData.forEach((data) => {
            let elements = document.getElementsByTagName(
                data.tagName
            ) as HTMLCollection;
            let item = elements.item(data.indexOfTags) as Element;

            let innerHTML = item.innerHTML as string;

            const index = innerHTML.indexOf(data.selectedText);

            if (index >= 0) {
                innerHTML =
                    innerHTML.substring(0, index) +
                    "<span class='yellow-highlight'>" +
                    innerHTML.substring(index, index + data.selectedText.length) +
                    "</span>" +
                    innerHTML.substring(index + data.selectedText.length);
                item.innerHTML = innerHTML;
            }
        });
    });
}

마지막으로 백엔드에 저장한걸 들고온다. 딱히 특별한건 없음


해결해야할 문제

html 태그가 포함되어있는 텍스트를 어떻게 하이라이트 하냐는건데,
기존에 있는 코드에서 1,2번 과정: 들고와서 span삽입하는 과정을
새로 짜야함.

아래 이미지와 같은 결과가 나오려면 html태그 안에 span을 추가하고 그 태그 바깥족에 span을 추가하는 형식으로 해야한다. 즉 사용자입장에서 드래그한번하는거지만 태그가 있는거 없는거 구분해서 따로 span을 삽입해줘야한다

const span = document.createElement("SPAN");
span.textContent = selectedText;
span.setAttribute("class", "yellow-highlight")

const range = selection.getRangeAt(0);
range.deleteContents();
range.insertNode(span);

기존 코드는 그냥 태그가 있으나 없으나 쌩 텍스트로 변환해서 그걸 가져와
새로만든 Element에 그 태그있는거 무시한 텍스트를 집어넣어 하이라이트하고
완성된 Element로 대체하는 형식이기 때문이다.

앞으로 해줘야할건
텍스트에 태그가 있는지 체크해야할 조건이 필요할거고
아래처럼 태그 안쪽에 있는 텍스트와 바깥쪽에 있는 텍스트를 따로 구분해서 가져와야한다.

const 하이라이트된_태그안쪽텍스트 = document.createElement('span');
span.textContent = 태그안쪽텍스트;
span.setAttribute("class", "yellow-highlight");

const 하이라이트된_태그바깥쪽텍스트 = document.createElement('span');
span.textContent = 태그바깥쪽텍스트;
span.setAttribute("class", "yellow-highlight");

기존에 Range에서 content를 삭제하고 span을 삽입하는 형식은
selection이 태그있는거 없는거 다 선택한채로 하나이기때문에
이건 사용 못할거다.

앞에 이미지처럼 구현했던건 대충 코드로 보면

 selection.focusNode?.parentElement?.innerHTML = 하이라이트된_태그안쪽텍스트
 selection.anchorNode?.parentElement?.innerHTML = 하이라이트된_태그바깥쪽텍스트

대충 이런식으로 했는데 이게 맘처럼 저렇게 작동하면 좋으련만 동작하지않는 여러가지 변수가 많다. 뭔가 focusNode, anchorNode 작동방식이 잘 이해가 안가서 그런거 같다. 그걸 파악하지 못하니 로직을 짤수도 없는거같고.
느낌상으로는 저걸로 안될듯 싶다.
참고하고있는 web-highliter를 보면 Range를 활용하는거 같은데 코드가 어떻게 흘러가는지를 이해를 못하겠어서 아이디어를 얻는것조차 못하고 있다.
그래서 타입스크립트를 배우려고 함.


profile
TIL 남기는 공간입니다

0개의 댓글