patleeman / quill-markdown-shortcuts

Quill.js module that converts markdown to rich text formatting while typing.
https://patleeman.github.io/quill-markdown-shortcuts/
139 stars 49 forks source link

Inline formats such as bold, italic, bold-italic, code does not work well with inline-embeds. #41

Open wooolfgang opened 4 years ago

wooolfgang commented 4 years ago

For my particular use-case, using quill-markdown-shortcuts with quill-mentions causes a bug. The bug will also happen if you have custom inline-embeds, or default ones such as images, etc.

Steps to reproduce:

The issue is caused by this line in particular

    const startIndex = lineStart + match.index

    if (text.match(/^([*_ \n]+)$/g)) return

     setTimeout(() => {
         this.quill.deleteText(startIndex, annotatedText.length)
         this.quill.insertText(startIndex, matchedText, {bold: true, italic: true})
         this.quill.format('bold', false)
    }, 0)

The calculations for startIndex is not correct when there are inline-embed blots before the matchedText and within the current line. Inline-embeds will only have length === 1, causing an issue with the markdown-shortcut plugin once the editor has non-text contents.

I have a solution that fixes this issue, but I'm not sure if this is best one. I would like to know anyone's thoughts, or if they came up with more elegant solutions. Thanks!

 getStartIndex (annotatedText) {
    const wholeText = this.quill.getText()
    const embedsCountBeforeText = this.getInlineEmbedsBeforeText(annotatedText)
    const startIndex = wholeText.indexOf(annotatedText) + embedsCountBeforeText
    return startIndex
  }

  /**
   * @description -- This counts the number of inline embeds (image|imageLoading|mention)
   * before a matched text within the current line
   * @param {String} annotatedText
   * @returns {Number}
   */
  getInlineEmbedsBeforeText (annotatedText) {
    const contents = this.quill.getContents()
    let count = 0
    let startCount = false
    let matchedIndex = 0

    if (contents.ops.length <= 1) {
      return count
    }

    for (let i = contents.ops.length - 1; i >= 0; i--) {
      const content = contents.ops[i]

      // If the annotatedText matches within the text, start the count
      if (typeof content.insert === 'string' && content.insert.includes(annotatedText)) {
        startCount = true
        matchedIndex = i
      }
      // If an inline embed is matched before the annotatedText and within the current line, increment
      if (startCount &&
        (content.insert.mention || content.insert.image || content.insert.imageLoading)) {
        count += 1
      }
      // If the content is not within the current line, end the count
      if (
        startCount &&
        typeof content.insert === 'string' &&
        content.insert.includes('↵') &&
        matchedIndex !== i
      ) {
        startCount = false
        break
      }
    }

    return count
  }