Tiptap: Suggestions Bug - Can't write a second mention after adding a first

Created on 26 Oct 2020  ·  19Comments  ·  Source: ueberdosis/tiptap

I can write @hans, then select a person, then when i try to write @ someone else and it won't work until i create a new paragraph. I believe this used to work, any ideas?

Illustrated on the example page here:
https://tiptap.dev/suggestions

Even if you add a few paragraphs, When you write a second mention the entire dropdown goes buggy and moves to the top left of corner of the browser.

bug help wanted

Most helpful comment

@hanspagel regarding the first line of this issue, I'm not sure this is an issue with tippy.js. The onEnter/onChange callbacks are not called regardless of whether tippy is used or not.

I don't have an issue with positioning when adding a second mention in a future paragraph. I'm just completely unable to add two mentions in the same paragraph using @.

All 19 comments

Thanks for reporting! This is an issue with tippy.js, not with tiptap. So I’m not sure we can do something about that.

@hanspagel what a pain. Funnily enough, with some mucking around, i duplicated the Suggestions Plugin and Mention extension myself and was able to get it working. But i'm not sure exactly what i did or how.

Thanks for an awesome editor

@hanspagel regarding the first line of this issue, I'm not sure this is an issue with tippy.js. The onEnter/onChange callbacks are not called regardless of whether tippy is used or not.

I don't have an issue with positioning when adding a second mention in a future paragraph. I'm just completely unable to add two mentions in the same paragraph using @.

@hanspagel I agree with @axwalker. I'm not using tippy.js in my implementation at all and handle dropdowns with Vuetify. For me onEnter event doesn't fire too when I already have another mention in the line.

I'm sure it was working before and problem arrived after updating tiptap-extensions from 1.33.0 to 1.33.2

Okay, I don’t have time to look at it now, but I’ll reopen the issue.

As a current workaround fixating tiptap-extensions to 1.33.0 works 👌

@hanspagel I can confirm that the bug doesn't appear in 1.33.0. Deleting content: 'inline*' from schema in Mention.js fixes the issue, but I can't say for sure whether that breaks anything else

@greghub If current content: 'inline*' in schema is correct, then must be some bugs in SuggestionsPlugin.
It can not find a valid match, because the from, to can not pass the condition below, so not tigger the suggestions popup.

// line 59
// If the $position is located within the matched substring, return that range
        if (from < $position.pos && to >= $position.pos) {
          position = {
            range: {
              from,
              to,
            },
            query: match[0].slice(char.length),
            text: match[0],
          }
        }

I think the problems maybe the mention is a document node, when use regex match the text content, the matched index is not the same index in prosemirror.

The truth is, you can write a second mention, just make sure your new mention come first.
image

@Priestch is exactly right. All mentions that use tiptao-extensions/src/plugins/Suggestions.js fail to trigger in a paragraph that contains one or more inline nodes before the cursor position.

Package versions:

    "tiptap": "^1.30.0",
    "tiptap-commands": "^1.15.0",
    "tiptap-extensions": "^1.33.2",

Editor contents:

<div class="ProseMirror" contenteditable="true" tabindex="0">
  <p><a href="https://example.com" rel="noopener noreferrer nofollow">https://example.com</a> is an example.</p>
</div>

(Trying to start a suggestion using matcher char / at the end, like example. /)

Schema of my inline link node:

  get schema() {
    return {
      content: "text*",

      group: "inline",

      inline: true,

      attrs: {
        href: {
          default: "",
        },
      },

      parseDOM: [
        {
          tag: "a[href]",
          getAttrs: (dom) => ({
            href: dom.getAttribute("href"),
          }),
        },
      ],

      toDOM: (node) => [
        "a",
        {
          ...node.attrs,
          rel: "noopener noreferrer nofollow",
        },
        0,
      ],
    };
  }

Use of SuggestionsPlugin

SuggestionsPlugin({
  matcher: '/',

  items: [],

  // event handlers ...

  suggestionClass: "slash-command-suggestion",
})

Where it fails

At If the $position is located within the matched substring, return that range. See https://github.com/ueberdosis/tiptap/blob/94d7a480d8d5630a0e0f7942b5e7565fa8700386/packages/tiptap-extensions/src/plugins/Suggestions.js#L51

The position is out by two. I'm assuming this has to do with the edges of the inline node each counting as 1 extra position in Prosemirror. See https://prosemirror.net/docs/guide/#doc.indexing

It'd make sense to remove the content: 'inline*' expression, since that seems to allow child nodes, but I can't imagine a scenario where a Mention could have a child node 🤔

https://prosemirror.net/docs/guide/#schema.content_expressions

@andreasvirkus Content in schema has nothing to do with issue, it was added to fix copy issue in #860

I feel the progress here has stagnated. @Priestch can you think of another way to solve the copy issue perhaps? Or help us discover another way to make multiple Mention nodes work in the same paragraph?

There are other fixes in the newer versions of tiptap-extensions, like the HardBreak command (#846), that we'd like to get back, but we can't update because of this bug. Same goes for the fix https://github.com/ueberdosis/tiptap/pull/860, since it was also released in 1.33.2

@andreasvirkus I think maybe you can custom clipboardSerializer or clipboardTextSerializer to make copy work.

But I do not think this is the right way to solve the problem. @rudolfbyker give the logic reason why second mention do not work now, Because content: 'inline*' makes the mention not a leaf node any more, so code below
https://github.com/ueberdosis/tiptap/blob/b2394ffa99ae7ca5213f3f5c85f1700e56fd63c6/packages/tiptap-extensions/src/plugins/Suggestions.js#L29
which finally call function below
https://github.com/ProseMirror/prosemirror-model/blob/5564b0e966bf49648bc98b28b0055996447dafaa/src/fragment.js#L43

  // : (number, number, ?string, ?string) → string
  textBetween(from, to, blockSeparator, leafText) {
    let text = "", separated = true
    this.nodesBetween(from, to, (node, pos) => {
      if (node.isText) {
        text += node.text.slice(Math.max(from, pos) - pos, to - pos)
        separated = !blockSeparator
      } else if (node.isLeaf && leafText) {
        text += leafText
        separated = !blockSeparator
      } else if (!separated && node.isBlock) {
        text += blockSeparator
        separated = true
      }
    }, 0)
    return text
  }

Whether node is leaf or not will make the text result different. I think the mention node does has the content, maybe content: 'inline*' is not as precise as content: 'text*', but is the right way to make copy work.

@andreasvirkus I think maybe you can custom clipboardSerializer or clipboardTextSerializer to make copy work.

But I do not think this is the right way to solve the problem. @rudolfbyker give the logic reason why second mention do not work now, Because content: 'inline*' makes the mention not a leaf node any more, so code below
https://github.com/ueberdosis/tiptap/blob/b2394ffa99ae7ca5213f3f5c85f1700e56fd63c6/packages/tiptap-extensions/src/plugins/Suggestions.js#L29

which finally call function below
https://github.com/ProseMirror/prosemirror-model/blob/5564b0e966bf49648bc98b28b0055996447dafaa/src/fragment.js#L43

  // : (number, number, ?string, ?string) → string
  textBetween(from, to, blockSeparator, leafText) {
    let text = "", separated = true
    this.nodesBetween(from, to, (node, pos) => {
      if (node.isText) {
        text += node.text.slice(Math.max(from, pos) - pos, to - pos)
        separated = !blockSeparator
      } else if (node.isLeaf && leafText) {
        text += leafText
        separated = !blockSeparator
      } else if (!separated && node.isBlock) {
        text += blockSeparator
        separated = true
      }
    }, 0)
    return text
  }

Whether node is leaf or not will make the text result different. I think the mention node does has the content, maybe content: 'inline*' is not as precise as content: 'text*', but is the right way to make copy work.

If I remove the content: 'inline*', will it have any side effect?

If I remove the content: 'inline*', will it have any side effect?

I checked the old codebase from version 1.33.0 the only difference is the property content, and after i removed content: 'inline*' it works like a charm

The only currently known side effect is that it will break copy-paste (Suggestions are not included into the clipboard), so

This text mentions @John Smith and his children @Lisa and @Jason

gets transformed into

This text mentions and his children and

It was added with https://github.com/ueberdosis/tiptap/pull/860

Is there any update on this problem? Currently, I am using tiptap for my product but for client requirements, I need the mentions feature. Any help would be appreciated please @hanspagel

@Hyperblaster @andreasvirkus @Draccano @joseederangojr @USasikumar
I submitted a new commit in #861 to fix this issue, seems work!

Thank you all for your work on this! 👏 Thanks @Priestch @andreasvirkus @Hyperblaster and all others!

The PR has been merged and we plan to release a version in the first two weeks of January, maybe earlier - but we want to make sure not to introduce new bugs (like we did with the recent versions).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

agentq15 picture agentq15  ·  3Comments

nekooee picture nekooee  ·  3Comments

jameswragg picture jameswragg  ·  3Comments

afwn90cj93201nixr2e1re picture afwn90cj93201nixr2e1re  ·  3Comments

Auxxxxlx picture Auxxxxlx  ·  3Comments