Fix various issues
This commit is contained in:
		@@ -22,21 +22,4 @@
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
    </script>
 | 
					    </script>
 | 
				
			||||||
  </body>
 | 
					  </body>
 | 
				
			||||||
 | 
					 | 
				
			||||||
  <style>
 | 
					 | 
				
			||||||
    .bes-online-editor {
 | 
					 | 
				
			||||||
      width: 80%;
 | 
					 | 
				
			||||||
      height: 300px;
 | 
					 | 
				
			||||||
      margin: 0 auto;
 | 
					 | 
				
			||||||
      padding: 20px;
 | 
					 | 
				
			||||||
      border-radius: 10px;
 | 
					 | 
				
			||||||
      background-color: #f5f5f5;
 | 
					 | 
				
			||||||
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
 | 
					 | 
				
			||||||
      line-height: 1.6;
 | 
					 | 
				
			||||||
      white-space: pre-wrap;
 | 
					 | 
				
			||||||
      overflow-y: auto;
 | 
					 | 
				
			||||||
      font-family: Arial, Helvetica, sans-serif;
 | 
					 | 
				
			||||||
      z-index: 2;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  </style>
 | 
					 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										185
									
								
								online-editor.js
									
									
									
									
									
								
							
							
						
						
									
										185
									
								
								online-editor.js
									
									
									
									
									
								
							@@ -2,9 +2,11 @@ const besUrl = 'http://localhost:225/api/v2/check'
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
let besEditors = [] // Collection of all grammar checking services in the document
 | 
					let besEditors = [] // Collection of all grammar checking services in the document
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: Add support for <textarea>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BesEditor {
 | 
					class BesEditor {
 | 
				
			||||||
  constructor(edit, CKEditorInstance) {
 | 
					  constructor(edit, CKEditorInstance) {
 | 
				
			||||||
    this.el = edit
 | 
					    this.editorElement = edit
 | 
				
			||||||
    this.timer = null
 | 
					    this.timer = null
 | 
				
			||||||
    this.children = []
 | 
					    this.children = []
 | 
				
			||||||
    const { correctionPanel, scrollPanel } = this.createCorrectionPanel(edit)
 | 
					    const { correctionPanel, scrollPanel } = this.createCorrectionPanel(edit)
 | 
				
			||||||
@@ -12,9 +14,9 @@ class BesEditor {
 | 
				
			|||||||
    this.scrollPanel = scrollPanel
 | 
					    this.scrollPanel = scrollPanel
 | 
				
			||||||
    this.offsetTop = null
 | 
					    this.offsetTop = null
 | 
				
			||||||
    this.CKEditorInstance = CKEditorInstance
 | 
					    this.CKEditorInstance = CKEditorInstance
 | 
				
			||||||
    if (!this.CKEditorInstance) edit.classList.add('bes-online-editor')
 | 
					 | 
				
			||||||
    this.originalSpellcheck = edit.spellcheck
 | 
					    this.originalSpellcheck = edit.spellcheck
 | 
				
			||||||
    edit.spellcheck = false
 | 
					    edit.spellcheck = false
 | 
				
			||||||
 | 
					    this.abortController = new AbortController()
 | 
				
			||||||
    this.proof(edit)
 | 
					    this.proof(edit)
 | 
				
			||||||
    edit.addEventListener('beforeinput', BesEditor.handleBeforeInput, false)
 | 
					    edit.addEventListener('beforeinput', BesEditor.handleBeforeInput, false)
 | 
				
			||||||
    edit.addEventListener('click', BesEditor.handleClick)
 | 
					    edit.addEventListener('click', BesEditor.handleClick)
 | 
				
			||||||
@@ -37,60 +39,42 @@ class BesEditor {
 | 
				
			|||||||
   * Unregisters grammar checking service
 | 
					   * Unregisters grammar checking service
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  unregister() {
 | 
					  unregister() {
 | 
				
			||||||
    this.el.removeEventListener('scroll', BesEditor.handleScroll)
 | 
					    this.editorElement.removeEventListener('scroll', BesEditor.handleScroll)
 | 
				
			||||||
    this.el.removeEventListener('click', BesEditor.handleClick)
 | 
					    this.editorElement.removeEventListener('click', BesEditor.handleClick)
 | 
				
			||||||
    this.el.removeEventListener(
 | 
					    this.editorElement.removeEventListener(
 | 
				
			||||||
      'beforeinput',
 | 
					      'beforeinput',
 | 
				
			||||||
      BesEditor.handleBeforeInput,
 | 
					      BesEditor.handleBeforeInput,
 | 
				
			||||||
      false
 | 
					      false
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    if (this.timer) clearTimeout(this.timer)
 | 
					    if (this.timer) clearTimeout(this.timer)
 | 
				
			||||||
    besEditors = besEditors.filter(item => item !== this)
 | 
					    besEditors = besEditors.filter(item => item !== this)
 | 
				
			||||||
    this.el.spellcheck = this.originalSpellcheck
 | 
					    this.editorElement.spellcheck = this.originalSpellcheck
 | 
				
			||||||
    this.el.classList.remove('bes-online-editor')
 | 
					 | 
				
			||||||
    this.correctionPanel.remove()
 | 
					    this.correctionPanel.remove()
 | 
				
			||||||
    this.scrollPanel.remove()
 | 
					    this.scrollPanel.remove()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // TODO: add support for textarea elements
 | 
					 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Recursively grammar-proofs a DOM tree.
 | 
					   * Recursively grammar-proofs a DOM tree.
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
   * @param {Node} el DOM root node to proof
 | 
					   * @param {Node} node DOM root node to proof
 | 
				
			||||||
   * @returns {Array} Markup of text to proof using BesStr
 | 
					   * @returns {Array} Markup of text to proof using BesStr
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async proof(el) {
 | 
					  async proof(node) {
 | 
				
			||||||
    // If first child is not a block element, add a dummy <div>...</div> around it.
 | 
					    switch (node.nodeType) {
 | 
				
			||||||
    // This solution is still not fully tested and might need some improvements.
 | 
					 | 
				
			||||||
    if (el.classList?.contains('bes-online-editor')) {
 | 
					 | 
				
			||||||
      const firstChild = el.firstChild
 | 
					 | 
				
			||||||
      if (
 | 
					 | 
				
			||||||
        firstChild &&
 | 
					 | 
				
			||||||
        (firstChild.nodeType === Node.TEXT_NODE ||
 | 
					 | 
				
			||||||
          !BesEditor.isBlockElement(firstChild))
 | 
					 | 
				
			||||||
      ) {
 | 
					 | 
				
			||||||
        const divEl = document.createElement('div')
 | 
					 | 
				
			||||||
        if (firstChild.nodeType === Node.TEXT_NODE) {
 | 
					 | 
				
			||||||
          divEl.textContent = firstChild.textContent
 | 
					 | 
				
			||||||
        } else divEl.appendChild(firstChild.cloneNode(true))
 | 
					 | 
				
			||||||
        el.insertBefore(divEl, firstChild)
 | 
					 | 
				
			||||||
        el.removeChild(firstChild)
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    switch (el.nodeType) {
 | 
					 | 
				
			||||||
      case Node.TEXT_NODE:
 | 
					      case Node.TEXT_NODE:
 | 
				
			||||||
        return [{ text: el.textContent, el: el, markup: false }]
 | 
					        return [{ text: node.textContent, node: node, markup: false }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case Node.ELEMENT_NODE:
 | 
					      case Node.ELEMENT_NODE:
 | 
				
			||||||
        if (BesEditor.isBlockElement(el)) {
 | 
					        if (BesEditor.isBlockElement(node)) {
 | 
				
			||||||
          // Block elements are grammar-proofed independently.
 | 
					          // Block elements are grammar-proofed independently.
 | 
				
			||||||
          if (this.isProofed(el)) {
 | 
					          if (this.isProofed(node)) {
 | 
				
			||||||
            return [{ text: '<' + el.tagName + '/>', el: el, markup: true }]
 | 
					            return [
 | 
				
			||||||
 | 
					              { text: '<' + node.tagName + '/>', node: node, markup: true }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          this.clearMistakeMarkup(el)
 | 
					          this.clearMistakeMarkup(node)
 | 
				
			||||||
          let data = []
 | 
					          let data = []
 | 
				
			||||||
          for (const el2 of el.childNodes) {
 | 
					          for (const el2 of node.childNodes) {
 | 
				
			||||||
            data = data.concat(await this.proof(el2))
 | 
					            data = data.concat(await this.proof(el2))
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          if (data.some(x => !x.markup && !/^\s*$/.test(x.text))) {
 | 
					          if (data.some(x => !x.markup && !/^\s*$/.test(x.text))) {
 | 
				
			||||||
@@ -101,7 +85,7 @@ class BesEditor {
 | 
				
			|||||||
                  x.markup ? { markup: x.text } : { text: x.text }
 | 
					                  x.markup ? { markup: x.text } : { text: x.text }
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
              }),
 | 
					              }),
 | 
				
			||||||
              language: el.lang ? el.lang : 'sl',
 | 
					              language: node.lang ? node.lang : 'sl',
 | 
				
			||||||
              level: 'picky'
 | 
					              level: 'picky'
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            const request = new Request(besUrl, {
 | 
					            const request = new Request(besUrl, {
 | 
				
			||||||
@@ -109,7 +93,8 @@ class BesEditor {
 | 
				
			|||||||
              headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
 | 
					              headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
 | 
				
			||||||
              body: new URLSearchParams(requestData)
 | 
					              body: new URLSearchParams(requestData)
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            fetch(request)
 | 
					            const signal = this.abortController.signal
 | 
				
			||||||
 | 
					            fetch(request, { signal })
 | 
				
			||||||
              .then(response => {
 | 
					              .then(response => {
 | 
				
			||||||
                if (!response.ok) {
 | 
					                if (!response.ok) {
 | 
				
			||||||
                  // TODO: Make connectivity and BesStr issues non-fatal. But show an error sign somewhere in the UI.
 | 
					                  // TODO: Make connectivity and BesStr issues non-fatal. But show an error sign somewhere in the UI.
 | 
				
			||||||
@@ -134,7 +119,7 @@ class BesEditor {
 | 
				
			|||||||
                        startingOffset + data[idx].text.length
 | 
					                        startingOffset + data[idx].text.length
 | 
				
			||||||
                    ) {
 | 
					                    ) {
 | 
				
			||||||
                      range.setStart(
 | 
					                      range.setStart(
 | 
				
			||||||
                        data[idx].el,
 | 
					                        data[idx].node,
 | 
				
			||||||
                        match.offset - startingOffset
 | 
					                        match.offset - startingOffset
 | 
				
			||||||
                      )
 | 
					                      )
 | 
				
			||||||
                      break
 | 
					                      break
 | 
				
			||||||
@@ -153,7 +138,7 @@ class BesEditor {
 | 
				
			|||||||
                      /*startingOffset <= endOffset &&*/ endOffset <=
 | 
					                      /*startingOffset <= endOffset &&*/ endOffset <=
 | 
				
			||||||
                        startingOffset + data[idx].text.length
 | 
					                        startingOffset + data[idx].text.length
 | 
				
			||||||
                    ) {
 | 
					                    ) {
 | 
				
			||||||
                      range.setEnd(data[idx].el, endOffset - startingOffset)
 | 
					                      range.setEnd(data[idx].node, endOffset - startingOffset)
 | 
				
			||||||
                      break
 | 
					                      break
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                  }
 | 
					                  }
 | 
				
			||||||
@@ -167,37 +152,34 @@ class BesEditor {
 | 
				
			|||||||
                    match: match
 | 
					                    match: match
 | 
				
			||||||
                  })
 | 
					                  })
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
                this.markProofed(el, matches)
 | 
					                this.markProofed(node, matches)
 | 
				
			||||||
 | 
					 | 
				
			||||||
                // This is a solution for displaying mistakes in CKEditor. It is not the best solution, but it works for now.
 | 
					 | 
				
			||||||
                if (this.CKEditorInstance) {
 | 
					 | 
				
			||||||
                  const resizeEvent = new Event('resize')
 | 
					 | 
				
			||||||
                  window.dispatchEvent(resizeEvent)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              })
 | 
					              })
 | 
				
			||||||
              .catch(error => {
 | 
					              .catch(error => {
 | 
				
			||||||
 | 
					                if (error.name === 'AbortError') return
 | 
				
			||||||
                // TODO: Make parsing issues non-fatal. But show an error sign somewhere in the UI.
 | 
					                // TODO: Make parsing issues non-fatal. But show an error sign somewhere in the UI.
 | 
				
			||||||
                throw new Error(
 | 
					                throw new Error(
 | 
				
			||||||
                  'Parsing backend server response failed: ' + error
 | 
					                  'Parsing backend server response failed: ' + error
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
              })
 | 
					              })
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          return [{ text: '<' + el.tagName + '/>', el: el, markup: true }]
 | 
					          return [{ text: '<' + node.tagName + '/>', node: node, markup: true }]
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          // Surround inline element with dummy <tagName>...</tagName>.
 | 
					          // Surround inline element with dummy <tagName>...</tagName>.
 | 
				
			||||||
          let data = [{ text: '<' + el.tagName + '>', el: el, markup: true }]
 | 
					          let data = [
 | 
				
			||||||
          for (const el2 of el.childNodes) {
 | 
					            { text: '<' + node.tagName + '>', node: node, markup: true }
 | 
				
			||||||
 | 
					          ]
 | 
				
			||||||
 | 
					          for (const el2 of node.childNodes) {
 | 
				
			||||||
            data = data.concat(await this.proof(el2))
 | 
					            data = data.concat(await this.proof(el2))
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          data.splice(data.length, 0, {
 | 
					          data.splice(data.length, 0, {
 | 
				
			||||||
            text: '</' + el.tagName + '>',
 | 
					            text: '</' + node.tagName + '>',
 | 
				
			||||||
            markup: true
 | 
					            markup: true
 | 
				
			||||||
          })
 | 
					          })
 | 
				
			||||||
          return data
 | 
					          return data
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      default:
 | 
					      default:
 | 
				
			||||||
        return [{ text: '<?' + el.nodeType + '>', el: el, markup: true }]
 | 
					        return [{ text: '<?' + node.nodeType + '>', node: node, markup: true }]
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -226,9 +208,10 @@ class BesEditor {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  static handleBeforeInput(event) {
 | 
					  static handleBeforeInput(event) {
 | 
				
			||||||
    const edit = event.target
 | 
					    const edit = event.target
 | 
				
			||||||
    let editor = besEditors.find(e => e.el === edit)
 | 
					    let editor = besEditors.find(e => e.editorElement === edit)
 | 
				
			||||||
    if (!editor) return
 | 
					    if (!editor) return
 | 
				
			||||||
    if (editor.timer) clearTimeout(editor.timer)
 | 
					    if (editor.timer) clearTimeout(editor.timer)
 | 
				
			||||||
 | 
					    editor.abortController.abort()
 | 
				
			||||||
    let blockElements = new Set()
 | 
					    let blockElements = new Set()
 | 
				
			||||||
    event.getTargetRanges().forEach(range => {
 | 
					    event.getTargetRanges().forEach(range => {
 | 
				
			||||||
      BesEditor.getNodesInRange(range).forEach(el =>
 | 
					      BesEditor.getNodesInRange(range).forEach(el =>
 | 
				
			||||||
@@ -244,6 +227,7 @@ class BesEditor {
 | 
				
			|||||||
      editor.repositionMistakes()
 | 
					      editor.repositionMistakes()
 | 
				
			||||||
    }, 0)
 | 
					    }, 0)
 | 
				
			||||||
    editor.timer = setTimeout(function () {
 | 
					    editor.timer = setTimeout(function () {
 | 
				
			||||||
 | 
					      editor.abortController = new AbortController()
 | 
				
			||||||
      editor.proof(edit)
 | 
					      editor.proof(edit)
 | 
				
			||||||
    }, 1000)
 | 
					    }, 1000)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -255,7 +239,7 @@ class BesEditor {
 | 
				
			|||||||
   * @returns {Boolean} true if the element has already been grammar-proofed; false otherwise.
 | 
					   * @returns {Boolean} true if the element has already been grammar-proofed; false otherwise.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  isProofed(el) {
 | 
					  isProofed(el) {
 | 
				
			||||||
    return this.children.find(child => child.elements === el)?.isProofed
 | 
					    return this.children.find(child => child.element === el)?.isProofed
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -268,9 +252,12 @@ class BesEditor {
 | 
				
			|||||||
    this.removeChild(el)
 | 
					    this.removeChild(el)
 | 
				
			||||||
    this.children.push({
 | 
					    this.children.push({
 | 
				
			||||||
      isProofed: true,
 | 
					      isProofed: true,
 | 
				
			||||||
      elements: el, // TODO: Rename "elements" to "el" - 1. It contains only single element (plural elements is misleading), 2. BesEditor also uses "el" named field for DOM matching.
 | 
					      element: el,
 | 
				
			||||||
      matches: matches
 | 
					      matches: matches
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // This is a solution for displaying mistakes in CKEditor. It is not the best solution, but it works for now.
 | 
				
			||||||
 | 
					    if (this.CKEditorInstance) window.dispatchEvent(new Event('resize'))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -279,12 +266,14 @@ class BesEditor {
 | 
				
			|||||||
   * @param {Element} el DOM element that we should re-grammar-proof
 | 
					   * @param {Element} el DOM element that we should re-grammar-proof
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  clearMistakeMarkup(el) {
 | 
					  clearMistakeMarkup(el) {
 | 
				
			||||||
    let child = this.children.find(child => child.elements === el)
 | 
					    let child = this.children.find(child => child.element === el)
 | 
				
			||||||
    if (!child) return
 | 
					    if (!child) return
 | 
				
			||||||
    child.isProofed = false
 | 
					    child.isProofed = false
 | 
				
			||||||
    child.matches.forEach(match => {
 | 
					    child.matches.forEach(match => {
 | 
				
			||||||
      match?.highlights.forEach(h => h.remove())
 | 
					      if (match?.highlights) {
 | 
				
			||||||
 | 
					        match.highlights.forEach(h => h.remove())
 | 
				
			||||||
        delete match.highlights
 | 
					        delete match.highlights
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -294,7 +283,7 @@ class BesEditor {
 | 
				
			|||||||
   * @param {Element} el DOM element for removal
 | 
					   * @param {Element} el DOM element for removal
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  removeChild(el) {
 | 
					  removeChild(el) {
 | 
				
			||||||
    this.children = this.children.filter(child => child.elements !== el)
 | 
					    this.children = this.children.filter(child => child.element !== el)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -302,7 +291,7 @@ class BesEditor {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  repositionMistakes() {
 | 
					  repositionMistakes() {
 | 
				
			||||||
    this.children.forEach(child => {
 | 
					    this.children.forEach(child => {
 | 
				
			||||||
      this.clearMistakeMarkup(child.elements)
 | 
					      this.clearMistakeMarkup(child.element)
 | 
				
			||||||
      child.matches.forEach(match => {
 | 
					      child.matches.forEach(match => {
 | 
				
			||||||
        const { clientRects, highlights } = this.addMistakeMarkup(match.range)
 | 
					        const { clientRects, highlights } = this.addMistakeMarkup(match.range)
 | 
				
			||||||
        match.rects = clientRects
 | 
					        match.rects = clientRects
 | 
				
			||||||
@@ -318,7 +307,6 @@ class BesEditor {
 | 
				
			|||||||
   * @returns {Object} Client rectangles and grammar mistake highlight elements
 | 
					   * @returns {Object} Client rectangles and grammar mistake highlight elements
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  addMistakeMarkup(range) {
 | 
					  addMistakeMarkup(range) {
 | 
				
			||||||
    // TODO: Consider using range.getClientRects() instead of range.getBoundingClientRect()
 | 
					 | 
				
			||||||
    // In CKEditor case, the highlight element is not shown for some reason. But after resizing the window it is shown.
 | 
					    // In CKEditor case, the highlight element is not shown for some reason. But after resizing the window it is shown.
 | 
				
			||||||
    const clientRects = range.getClientRects()
 | 
					    const clientRects = range.getClientRects()
 | 
				
			||||||
    const scrollPanelRect = this.scrollPanel.getBoundingClientRect()
 | 
					    const scrollPanelRect = this.scrollPanel.getBoundingClientRect()
 | 
				
			||||||
@@ -343,7 +331,7 @@ class BesEditor {
 | 
				
			|||||||
   * Tests if given element is block element.
 | 
					   * Tests if given element is block element.
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
   * @param {Element} el DOM element
 | 
					   * @param {Element} el DOM element
 | 
				
			||||||
   * @returns false if CSS display property is inline or inline-block; true otherwise.
 | 
					   * @returns false if CSS display property is inline; true otherwise.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  static isBlockElement(el) {
 | 
					  static isBlockElement(el) {
 | 
				
			||||||
    switch (
 | 
					    switch (
 | 
				
			||||||
@@ -353,7 +341,6 @@ class BesEditor {
 | 
				
			|||||||
        .toLowerCase()
 | 
					        .toLowerCase()
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
      case 'inline':
 | 
					      case 'inline':
 | 
				
			||||||
      case 'inline-block':
 | 
					 | 
				
			||||||
        return false
 | 
					        return false
 | 
				
			||||||
      default:
 | 
					      default:
 | 
				
			||||||
        return true
 | 
					        return true
 | 
				
			||||||
@@ -367,7 +354,7 @@ class BesEditor {
 | 
				
			|||||||
   * @returns {Element} Innermost block element containing given node
 | 
					   * @returns {Element} Innermost block element containing given node
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  getBlockParent(el) {
 | 
					  getBlockParent(el) {
 | 
				
			||||||
    for (; el && el !== this.el; el = el.parentNode) {
 | 
					    for (; el && el !== this.editorElement; el = el.parentNode) {
 | 
				
			||||||
      if (el.nodeType === Node.ELEMENT_NODE && BesEditor.isBlockElement(el))
 | 
					      if (el.nodeType === Node.ELEMENT_NODE && BesEditor.isBlockElement(el))
 | 
				
			||||||
        return el
 | 
					        return el
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -454,7 +441,7 @@ class BesEditor {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  static handleClick(event) {
 | 
					  static handleClick(event) {
 | 
				
			||||||
    const edit = BesEditor.findParent(event.target)
 | 
					    const edit = BesEditor.findParent(event.target)
 | 
				
			||||||
    let editor = besEditors.find(e => e.el === edit)
 | 
					    let editor = besEditors.find(e => e.editorElement === edit)
 | 
				
			||||||
    if (!editor) return
 | 
					    if (!editor) return
 | 
				
			||||||
    const target = editor.getBlockParent(event.target)
 | 
					    const target = editor.getBlockParent(event.target)
 | 
				
			||||||
    editor.renderPopup(target, event.clientX, event.clientY)
 | 
					    editor.renderPopup(target, event.clientX, event.clientY)
 | 
				
			||||||
@@ -469,13 +456,13 @@ class BesEditor {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  static handleScroll(event) {
 | 
					  static handleScroll(event) {
 | 
				
			||||||
    const edit = event.target
 | 
					    const edit = event.target
 | 
				
			||||||
    let editor = besEditors.find(e => e.el === edit)
 | 
					    let editor = besEditors.find(e => e.editorElement === edit)
 | 
				
			||||||
    if (!editor) return
 | 
					    if (!editor) return
 | 
				
			||||||
    editor.scrollPanel.style.top = -edit.scrollTop + 'px'
 | 
					    editor.scrollPanel.style.top = -edit.scrollTop + 'px'
 | 
				
			||||||
    editor.offsetTop = edit.scrollTop
 | 
					    editor.offsetTop = edit.scrollTop
 | 
				
			||||||
    setTimeout(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
      editor.repositionMistakes()
 | 
					      editor.repositionMistakes()
 | 
				
			||||||
    }, 300)
 | 
					    }, 100)
 | 
				
			||||||
    // TODO: Move popup (if open) too.
 | 
					    // TODO: Move popup (if open) too.
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -487,10 +474,7 @@ class BesEditor {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  static findParent(el) {
 | 
					  static findParent(el) {
 | 
				
			||||||
    for (; el; el = el.parentNode) {
 | 
					    for (; el; el = el.parentNode) {
 | 
				
			||||||
      if (
 | 
					      if (besEditors.find(editor => editor.editorElement === el)) {
 | 
				
			||||||
        el.classList?.contains('bes-online-editor') ||
 | 
					 | 
				
			||||||
        el.classList?.contains('ck-editor__editable') // Find a better way to handle CKEditor
 | 
					 | 
				
			||||||
      ) {
 | 
					 | 
				
			||||||
        return el
 | 
					        return el
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -506,7 +490,7 @@ class BesEditor {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  renderPopup(el, clientX, clientY) {
 | 
					  renderPopup(el, clientX, clientY) {
 | 
				
			||||||
    const popup = document.querySelector('bes-popup-el')
 | 
					    const popup = document.querySelector('bes-popup-el')
 | 
				
			||||||
    const matches = this.children.find(child => child.elements === el)?.matches
 | 
					    const matches = this.children.find(child => child.element === el)?.matches
 | 
				
			||||||
    if (matches) {
 | 
					    if (matches) {
 | 
				
			||||||
      for (let m of matches) {
 | 
					      for (let m of matches) {
 | 
				
			||||||
        if (m.rects) {
 | 
					        if (m.rects) {
 | 
				
			||||||
@@ -516,8 +500,7 @@ class BesEditor {
 | 
				
			|||||||
              m.match.replacements.forEach(replacement => {
 | 
					              m.match.replacements.forEach(replacement => {
 | 
				
			||||||
                popup.appendReplacements(
 | 
					                popup.appendReplacements(
 | 
				
			||||||
                  el,
 | 
					                  el,
 | 
				
			||||||
                  r,
 | 
					                  m,
 | 
				
			||||||
                  m.match,
 | 
					 | 
				
			||||||
                  replacement.value,
 | 
					                  replacement.value,
 | 
				
			||||||
                  this
 | 
					                  this
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
@@ -534,17 +517,15 @@ class BesEditor {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // TODO: In rich HTML texts, match.offset has different value than in plain text.
 | 
					  // TODO: In rich HTML texts, match.offset has different value than in plain text.
 | 
				
			||||||
  // This function should be able to handle both cases or find a way that works for both.
 | 
					  // This function should be able to handle both cases or find a way that works for both.
 | 
				
			||||||
  static replaceText(el, rect, match, replacement, editor) {
 | 
					  replaceText(el, match, replacement) {
 | 
				
			||||||
 | 
					    if (this.timer) clearTimeout(this.timer)
 | 
				
			||||||
 | 
					    this.abortController.abort()
 | 
				
			||||||
    // const tags = this.getTagsAndText(el)
 | 
					    // const tags = this.getTagsAndText(el)
 | 
				
			||||||
    if (!editor.CKEditorInstance) {
 | 
					    if (!this.CKEditorInstance) {
 | 
				
			||||||
      const text = el.textContent
 | 
					      match.range.deleteContents()
 | 
				
			||||||
      const newText =
 | 
					      match.range.insertNode(document.createTextNode(replacement))
 | 
				
			||||||
        text.substring(0, match.offset) +
 | 
					 | 
				
			||||||
        replacement +
 | 
					 | 
				
			||||||
        text.substring(match.offset + match.length)
 | 
					 | 
				
			||||||
      el.textContent = newText
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      const { CKEditorInstance } = editor
 | 
					      const { CKEditorInstance } = this
 | 
				
			||||||
      CKEditorInstance.model.change(writer => {
 | 
					      CKEditorInstance.model.change(writer => {
 | 
				
			||||||
        const viewElement =
 | 
					        const viewElement =
 | 
				
			||||||
          CKEditorInstance.editing.view.domConverter.mapDomToView(el)
 | 
					          CKEditorInstance.editing.view.domConverter.mapDomToView(el)
 | 
				
			||||||
@@ -552,15 +533,15 @@ class BesEditor {
 | 
				
			|||||||
          CKEditorInstance.editing.mapper.toModelElement(viewElement)
 | 
					          CKEditorInstance.editing.mapper.toModelElement(viewElement)
 | 
				
			||||||
        if (modelElement) {
 | 
					        if (modelElement) {
 | 
				
			||||||
          const elementRange = writer.createRangeIn(modelElement)
 | 
					          const elementRange = writer.createRangeIn(modelElement)
 | 
				
			||||||
          // TODO: This logic should work once the HTML tags are removed from match.offset and match.length if is possible.
 | 
					          // TODO: This logic should work once the HTML tags are removed from match.match.offset and match.match.length if is possible.
 | 
				
			||||||
          if (
 | 
					          if (
 | 
				
			||||||
            elementRange.start.offset <= match.offset &&
 | 
					            elementRange.start.offset <= match.match.offset &&
 | 
				
			||||||
            elementRange.end.offset >= match.offset + match.length
 | 
					            elementRange.end.offset >= match.match.offset + match.match.length
 | 
				
			||||||
          ) {
 | 
					          ) {
 | 
				
			||||||
            const start = writer.createPositionAt(modelElement, match.offset)
 | 
					            const start = writer.createPositionAt(modelElement, match.match.offset)
 | 
				
			||||||
            const end = writer.createPositionAt(
 | 
					            const end = writer.createPositionAt(
 | 
				
			||||||
              modelElement,
 | 
					              modelElement,
 | 
				
			||||||
              match.offset + match.length
 | 
					              match.match.offset + match.match.length
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            const range = writer.createRange(start, end)
 | 
					            const range = writer.createRange(start, end)
 | 
				
			||||||
            writer.remove(range)
 | 
					            writer.remove(range)
 | 
				
			||||||
@@ -569,11 +550,12 @@ class BesEditor {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    BesEditor.clearSingleMistake(editor, el, rect)
 | 
					    this.clearMistakeMarkup(el)
 | 
				
			||||||
    // In my opinion, this approach provides the most straightforward solution for repositioning mistakes after a change.
 | 
					    // In my opinion, this approach provides the most straightforward solution for repositioning mistakes after a change.
 | 
				
			||||||
    // It maintains reasonable performance as it only checks the block element that has been modified,
 | 
					    // It maintains reasonable performance as it only checks the block element that has been modified,
 | 
				
			||||||
    // rather than re-evaluating the entire document or a larger set of elements.
 | 
					    // rather than re-evaluating the entire document or a larger set of elements.
 | 
				
			||||||
    editor.proof(el)
 | 
					    this.abortController = new AbortController()
 | 
				
			||||||
 | 
					    this.proof(el)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // static getTagsAndText(node) {
 | 
					  // static getTagsAndText(node) {
 | 
				
			||||||
@@ -590,25 +572,6 @@ class BesEditor {
 | 
				
			|||||||
  //   }
 | 
					  //   }
 | 
				
			||||||
  // }
 | 
					  // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // This function clears a single mistake
 | 
					 | 
				
			||||||
  static clearSingleMistake(editor, el, rect) {
 | 
					 | 
				
			||||||
    const childToDelete = editor.children.filter(
 | 
					 | 
				
			||||||
      child => child.elements === el
 | 
					 | 
				
			||||||
    )[0]
 | 
					 | 
				
			||||||
    childToDelete.isProofed = false
 | 
					 | 
				
			||||||
    childToDelete.matches = childToDelete.matches.filter(
 | 
					 | 
				
			||||||
      match => !BesEditor.isPointInRect(rect.left, rect.top, match.rects[0])
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // TODO: find a better way to remove elements from the DOM
 | 
					 | 
				
			||||||
    Array.from(editor.scrollPanel.children)
 | 
					 | 
				
			||||||
      .filter(child => {
 | 
					 | 
				
			||||||
        const childRect = child.getBoundingClientRect()
 | 
					 | 
				
			||||||
        return BesEditor.isPointInRect(childRect.left, childRect.top, rect)
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .forEach(child => child.remove())
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  setCorrectionPanelSize(editor, correctionPanel, scrollPanel) {
 | 
					  setCorrectionPanelSize(editor, correctionPanel, scrollPanel) {
 | 
				
			||||||
    const styles = window.getComputedStyle(editor)
 | 
					    const styles = window.getComputedStyle(editor)
 | 
				
			||||||
    const totalWidth =
 | 
					    const totalWidth =
 | 
				
			||||||
@@ -648,12 +611,12 @@ window.onload = () => {
 | 
				
			|||||||
window.onresize = () => {
 | 
					window.onresize = () => {
 | 
				
			||||||
  besEditors.forEach(editor => {
 | 
					  besEditors.forEach(editor => {
 | 
				
			||||||
    editor.setCorrectionPanelSize(
 | 
					    editor.setCorrectionPanelSize(
 | 
				
			||||||
      editor.el,
 | 
					      editor.editorElement,
 | 
				
			||||||
      editor.correctionPanel,
 | 
					      editor.correctionPanel,
 | 
				
			||||||
      editor.scrollPanel
 | 
					      editor.scrollPanel
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    editor.children.forEach(child => {
 | 
					    editor.children.forEach(child => {
 | 
				
			||||||
      editor.clearMistakeMarkup(child.elements)
 | 
					      editor.clearMistakeMarkup(child.element)
 | 
				
			||||||
      child.matches.forEach(match => {
 | 
					      child.matches.forEach(match => {
 | 
				
			||||||
        const { clientRects, highlights } = editor.addMistakeMarkup(match.range)
 | 
					        const { clientRects, highlights } = editor.addMistakeMarkup(match.range)
 | 
				
			||||||
        match.rects = clientRects
 | 
					        match.rects = clientRects
 | 
				
			||||||
@@ -775,14 +738,14 @@ class BesPopupEl extends HTMLElement {
 | 
				
			|||||||
    this.shadowRoot.querySelector('.popup-text').textContent = text
 | 
					    this.shadowRoot.querySelector('.popup-text').textContent = text
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  appendReplacements(el, rect, match, replacement, editor) {
 | 
					  appendReplacements(el, match, replacement, editor) {
 | 
				
			||||||
    const replacementDiv = this.shadowRoot.querySelector('.bes-replacement-div')
 | 
					    const replacementDiv = this.shadowRoot.querySelector('.bes-replacement-div')
 | 
				
			||||||
    const replacementBtn = document.createElement('button')
 | 
					    const replacementBtn = document.createElement('button')
 | 
				
			||||||
    replacementBtn.classList.add('bes-replacement-btn')
 | 
					    replacementBtn.classList.add('bes-replacement-btn')
 | 
				
			||||||
    replacementBtn.textContent = replacement
 | 
					    replacementBtn.textContent = replacement
 | 
				
			||||||
    replacementBtn.classList.add('bes-replacement')
 | 
					    replacementBtn.classList.add('bes-replacement')
 | 
				
			||||||
    replacementBtn.addEventListener('click', () => {
 | 
					    replacementBtn.addEventListener('click', () => {
 | 
				
			||||||
      BesEditor.replaceText(el, rect, match, replacement, editor)
 | 
					      editor.replaceText(el, match, replacement)
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    replacementDiv.appendChild(replacementBtn)
 | 
					    replacementDiv.appendChild(replacementBtn)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user