Studying/React

[React-Quill] inline style 적용하기

Kim Da Ham 2023. 7. 22. 14:52

개요

react-quill 에디터에서 비디오를 임베드할 때 항상 동일한 크기로 출력하고 싶었습니다. 그런데 react-quill의 html content 크기를 멋대로 조정할 수 있는 방법을 찾지 못했습니다.

 

 

 

문제 상황

따라서 이미지 핸들러처럼 커스텀 비디오 핸들러를 만드는 시도를 했습니다.

 

ql-hidden 클래스가 사라지면 video url을 입력하는 tooltip이 나오는 방식을 활용하여 다음과 같은 핸들러를 작성했습니다.

  const videoHandler = useCallback((editor: any) => {
    const tooltip = document.querySelector('.ql-tooltip');
    
    if (tooltip) {
      tooltip.className += ' ql-editing'
      tooltip.setAttribute('data-mode', 'video');
      tooltip.setAttribute('style', 'left:-138.5px;top:28.5px;');
      tooltip.classList.remove('ql-hidden');
    }
    
    const input = tooltip?.childNodes[1] as HTMLInputElement;
    const saveButton = tooltip?.childNodes[2] as HTMLLinkElement;
    
    saveButton.onclick = () => {
      console.log('saveButtonClick')
      const videoUrl = input.value;
      const range = editor.getSelection();
      editor?.clipboard.dangerouslyPasteHTML(
        range,
        makeIframeElement(videoUrl),
      );
    }
  }, []);

 

하지만 별도의 inline style이 적용된 요소는 react-quill의 content 요소로 인식이 안되는 문제가 발생했습니다.

 

원인은 다음과 같았습니다.

 

 

How to add inline style in element with Quill Text Editor?

Jsfiddle When inserting content using dangerouslyPasteHTML, inline styles are not inserted? For example, margin-right is not applied to p element. How to set inline styles on p element?

stackoverflow.com

 

 

Pasting html content (e-mail) partially remove css ( on images ) · Issue #1896 · quilljs/quill

Lastest version of quill, Im using quill to answers to mails Pasting my mail content [html] with editor.clipboard.dangerouslyPasteHTML(); My html content is fine, but when pasted, all inline css on...

github.com

 

dangerouslyPasteHTML을 사용하려고 하면, Quill 에디터는 quill 자체 표현에 맞지 않는 요소(인라인 스타일 포함)를 제거합니다.

 

따라서 비디오 핸들러를 별도로 만든다 한들 제가 원하는 비디오 스타일인 width=100%도 적용할 수 없었습니다.

 

 

 

 

해결 방법

그래서 결국 useChangeHtmlContent 훅을 만들었습니다.

 

import dompurify from "dompurify";

const headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
const media = ['iframe', 'img'];
const inlineElements = ['span', 'u', 'a', 'strong', 's', 'em'];
const blockElements = ['p', 'br'];

const ALLOWED_TAGS = [...headings, ...media, ...inlineElements, ...blockElements];

export default function useChangeHtmlContent() {
  const sanitizer = dompurify.sanitize;

  const sanitize = (htmlContent: string) => {
    return sanitizer(htmlContent,
      {
        ALLOWED_TAGS: ALLOWED_TAGS,
        ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder', 'scrolling', 'href']
      })
  };

  const setElementInlineStyle = (htmlContent: string) => {
    let copiedHtmlContent = htmlContent;
    
    copiedHtmlContent = copiedHtmlContent.replace(/<img/g, '<img style="width:100%; height:auto;"');
    copiedHtmlContent = copiedHtmlContent.replace(/<iframe/g, '<iframe style="width:100%;" height="696"');
    return copiedHtmlContent;
  }

  return [sanitize, setElementInlineStyle] as const;
};

 

해당 커스텀 훅은 dompurify의 sanitize 객체와 setElementInlineStyle 함수를 반환합니다.

 

setElementInlineStyle 함수는 <img>, <iframe> 태그일 경우 inline style 문자열을 추가하는 동작을 수행합니다. 따라서 portfolioDetail.tsx에서 <img>와 <iframe>을 불러올 때 제가 원하는 width: 100%; 인라인 스타일이 적용됩니다.