WYSIWYG의 Markdown 에디터를 목표로 ContentEditable 및 execCommand와 정면 승부 해 본다 (part 2)

이 기사는 part2입니다.
part1 을 아직 보지 않은 분은 그쪽을 먼저 읽어 주시면 이야기의 흐름을 알기 쉬울까 생각합니다.

마지막으로 남겨진 수정점



이전의 구현으로 남겨진 수정점은 다음과 같이 되어 있습니다.
  • 삽입 된 요소에 caret를 맞출 수 없다
  • blockquoteEnter
    이번에는 이것들을 수정하겠습니다.

    ① 삽입한 요소에 캐럿을 맞출 수 없는 문제에 대해



    이전 구현에서는 execCommand 에서 HTML 요소를 변경한 후 innerText 를 비웠습니다.
    이 때, caret가 옮겨놓은 요소의 직전의 요소에 (왜인지) 이동해 버리는 것이 원인으로 이러한 문제가 일어나고 있습니다.
    그리고 대체한 요소에는 캐럿을 맞출 수 없습니다.



    이 문제를 해결하기 위해 요소를 대체 한 후 innerText를 비우는 대신 공백을 넣음으로써 해결했습니다.
    즉,
    // 前回のコード
    window.getSelection().getRangeAt(0).endContainer.parentNode.innerText = ''
    
    // 修正
    window.getSelection().getRangeAt(0).endContainer.parentNode.innerText = '\xA0'
    



    어떻게 든 캐럿을 맞출 수있었습니다.
    하지만 이것에 의해 새로운 문제가 나왔습니다.

    위의 Gif 이미지의 끝에 조금 비치고 있습니다만, 개행한 후에 다시 H1 가 삽입되고 있습니다.
    이 문제는 마지막 두 번째 문제인 무한 blockquote 와 비슷한 것으로 파악하고 다음 장에서 해결합니다.

    또, 브라우저의 표준 기능으로서, 리스트를 빠져 나올 때는 하늘의 행으로 Enter 를 눌러야 합니다.
    1. aaa
    2. |  ←キャレット位置
    
    ↓ Enter押下
    
    1. aaa
    |
    

    하지만, 방금전의 구현에서는 행의 말미에 공백이 들어가기 때문에, 행이 하늘이라고는 간주되지 않고 개행해도 연장 목록의 그대로입니다.
    즉, 리스트나 순서 첨부 리스트를 삽입할 때에는 스페이스를 삽입할 필요는 없을 것 같습니다.
    전회의 구현으로 마크다운 기법의 경우 나누기를 했습니다만, 리스트나 순서 첨부 리스트의 때에는 전회와 같이 innerText 를 비우도록(듯이) 합니다.

    ② 무한 blockquote 에 대해



    예에 의해 자세한 원인은 불명합니다만, 전회의 구현에서는 blockquote 속에서 Enter 를 눌러도 빠져나갈 수 없었습니다.
    원인을 모르기 때문에, 개행으로 새롭게 삽입된 blockquoteexecCommand 로 무리하게 div 로 변환하는 구현을 했습니다.
    마크다운 기법을 대체한 것과 반대의 방법입니다.
    document.execCommand('formatblock', false, 'div')
    

    또한, Shift+Enter 는 같은 요소내에서의 개행이므로, Shift 없음의 Enter 만을 포착해 상기의 커멘드를 실행할 필요가 있습니다.
    또, 리스트나 순서 첨부 리스트는 브라우저의 표준 기능으로 바람직한 움직임을 하므로, div 로 옮겨놓는 처리를 적응하지 않아야 합니다.
    Shift의 캡처는 키 이벤트의 .shiftKey 속성에서 사용할 수 있습니다.
    리스트의 판정은, caret가 있는 요소명을 취득하는 것으로 했습니다.

    상기를 정리하면 이하의 처리가 됩니다.
    if (!event.shiftKey && event.keyCode === 13) {
        if (!focusingOnOrderElement()) {
           document.execCommand('formatblock', false, 'div')
        }
    } 
    
    const focusingOnOrderElement = () => {
        const element_name = window.getSelection().getRangeAt(0).endContainer.parentNode.nodeName
        return (element_name === 'LI' || element_name === 'UL' || element_name === 'OL')
    }
    

    완성형



    이번 개량점을 정리하면 다음과 같습니다.
    
    const element = document.getElementById('markdown')
        element.focus()
    
        element.addEventListener('keyup', (event) => {
          const currentLine = window.getSelection().getRangeAt(0).endContainer.data || ''
    
          if (!event.shiftKey && event.keyCode === 13) {
             if (!focusingOnOrderElement()) {
              document.execCommand('formatblock', false, 'div')
            }
          }else{
            if (currentLine.match(/^#{1}\xA0$/)) { // 見出し
              document.execCommand('formatblock', false, 'h1')
              clearCurrentLine()
            } else if (currentLine.match(/^#{2}\xA0$/)){
              document.execCommand('formatblock', false, 'h2')
              clearCurrentLine()
            } else if (currentLine.match(/^#{3}\xA0$/)) {
              document.execCommand('formatblock', false, 'h3')
              clearCurrentLine()
            } else if (currentLine.match(/^#{4}\xA0$/)) {
              document.execCommand('formatblock', false, 'h4')
              clearCurrentLine()
            } else if (currentLine.match(/^#{5}\xA0$/)) {
              document.execCommand('formatblock', false, 'h5')
              clearCurrentLine()
            } else if (currentLine.match(/^#{6}\xA0$/)) {
              document.execCommand('formatblock', false, 'h6')
              clearCurrentLine()
            } else if (currentLine.match(/^>\xA0$/)) { // 引用
              document.execCommand('formatblock', false, 'blockquote')
              clearCurrentLine()
            } else if (currentLine.match(/^\d+\.\xA0$/)) { // 順序付きリスト
              document.execCommand('insertOrderedList')
              clearCurrentLine('')
            } else if (currentLine.match(/^[\-+*]\xA0+$/)) { // リスト
              document.execCommand('insertUnorderedList')
              clearCurrentLine('')
            }
          }
        })
    
        const clearCurrentLine = (clearCharacter = '\xA0') => {
          window.getSelection().getRangeAt(0).endContainer.parentNode.innerText = clearCharacter
        }
    
        const focusingOnOrderElement = () => {
          const element_name = window.getSelection().getRangeAt(0).endContainer.parentNode.nodeName
          return (element_name === 'LI' || element_name === 'UL' || element_name === 'OL')
        }
    

    데모는 여기입니다.

    다음 번에



    이번 구현으로 Chrome에서도 어떻게든 움직이게되었습니다 (Safari와 Chrome에서만 시도하고 있습니다)
    다음 번 이후에는 강조나 이탤릭 등의 문두가 아닌 마크다운 기법의 판정을 할 수 있으면 좋겠습니다.

    난문이었지만 끝까지 읽어 주셔서 감사합니다 m (_ _) m
    개선점 등 있으면, 교수 바랍니다.
  • 좋은 웹페이지 즐겨찾기