TypeCellOS / BlockNote

A React Rich Text Editor that's block-based (Notion style) and extensible. Built on top of Prosemirror and Tiptap.

Home Page:https://www.blocknotejs.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How can I preserve the text appearance when I replace the selected text using HTML or Markdown?

jhonattan-inner opened this issue · comments

How can I preserve the text appearance when I replace the selected text using HTML or Markdown doing a merge?

I tried three different approaches, but I could not preserve the same selected structure.

Flow the link with the example:
https://codesandbox.io/p/devbox/kind-oskar-yn7c4y?file=%2Fapp%2FEditor.tsx&embed=1

Will be good if has a way to change the selected text between different blocks doing a merge with these blocks and the new content, by Markdown or HTML.

image

image

Hmm in this case imo it makes more sense to replace the content in each block separately, since BlockNote's API doesn't handle selections partially spanning multiple blocks atm. Seems like you did manage to figure it out using the TipTap API, so I would keep using that for now, but we are planning to make working with selections partially spanning multiple blocks easier in the future.

Thanks for the response @matthewlipski, I changed the way to replace the selection here:

function replaceTheSelectedTextWithTheNewTextUsingTipTapAPI() {
    if (sourceSelectedText.length === 0) return

    const { from } = props.editor._tiptapEditor.state.selection
    props.editor.tryParseMarkdownToBlocks(sourceSelectedText).then((blocks) => {
      props.editor.blocksToHTMLLossy(blocks).then((finalHtml) => {
        const div = document.createElement('div')
        div.innerHTML = finalHtml
        const p = div.querySelector('p:not(li > p)')
        if (p) {
          const span = document.createElement('span')
          span.innerHTML = p.innerHTML
          p.replaceWith(span)
        }

        const list = div.querySelector('ul')
        if (list) {
          const allListItems = div.querySelectorAll('li')
          allListItems.forEach((li) => {
            const newLi = document.createElement('li')
            newLi.innerHTML =
              li.querySelector('p')?.textContent || li.textContent || ''
            li.replaceWith(newLi)
          })
        }

        props.editor.prosemirrorView.pasteHTML(div.innerHTML)

        props.editor._tiptapEditor.commands.setTextSelection({
          from,
          to: props.editor._tiptapEditor.state.selection.to
        })
        props.editor._tiptapEditor.commands.focus()

        // if the previous block is empty remove it
        const currentBlock = props.editor.getTextCursorPosition().block as any
        if (
          (currentBlock &&
            currentBlock.type === 'paragraph' &&
            currentBlock.content.length === 0) ||
          (currentBlock &&
            currentBlock.type === 'heading' &&
            currentBlock.content.length === 0) ||
          (currentBlock &&
            currentBlock.type === 'bulletListItem' &&
            currentBlock.content.length === 0) ||
          currentBlock.content === undefined
        ) {
          props.editor.removeBlocks([currentBlock.id])
        }

        props.editor._tiptapEditor.commands.setTextSelection({
          from,
          to: props.editor._tiptapEditor.state.selection.to
        })
        props.editor._tiptapEditor.commands.focus()
      })
    })
  }