getPos() is undefined in NodeView constructor

I would like to cache the getPos function for later use. it is undefined when my custom NodeViews are created. Am I misreading the docs on this, quoted below:

You’ll often want interaction with the node to have some effect on the actual node in the document. But to create a transaction that changes a node, you first need to know where that node is. To help with that, node views get passed a getter function that can be used to query their current position in the document. Let’s modify the example so that clicking on the node queries you to enter an alt text for the image:

That’s the intended use, yes. Are you saying the third argument to your node view creator function is undefined, or are you actually calling it right away to cache its result? In the first case, that’s a bug, but it seems unlikely. In the second case, that’s a bad idea, since you can’t cache that value anyway (it might change at any time).

I set a breakpoint in my constructor. the getPos function parameter is undefined. I would like to cache the function, not the return value of the function.

In my custom view, I would like to query the view for my current position. how do I do that? is that getPos() or something else?

getPos is the way to find your position, though it currently doesn’t work when you call it right away when constructing the node view.

Can you create a minimal example where a node view is created without passing a getPos value?

I want to add custom behavior for marked text blocks. Here’s how I have approached this thus far:

I create a MarkSpec for the <mark> element and add a few data attributes

const mark= {
  atom:true,
  attrs: {
    "data-id":      {default: 1},
    "data-comment": {default:"comment"}
  },
  parseDOM: [{tag: "mark", getAttrs(dom) {
    return {
      "data-id":      dom.getAttribute("data-id"),
      "data-comment": dom.getAttribute("data-comment")
    }
  }}],
  toDOM(node) { return ["mark", node.attrs] }
}

I create my EditorView with a custom NodeView for <mark> elements

    editor.view = new EditorView( document.querySelector(".prosemirror"), {
      state:editor.state,
      dispatchTransaction: this.dispatchTransaction.bind(this),
      nodeViews: {
        mark(node, view, getPos) { return new HighlightView(node, view, getPos) }
      }
    });

I create a custom NodeView class for <mark> elements

  constructor(node, view, getPos) {
    this.dom = document.createElement("mark")
    this.dom.className = 'highlight-view';
    this.dom.setAttribute('data-id',node.attrs['data-id']);
    this.dom.setAttribute('data-comment',node.attrs['data-comment']);
    this.dom.setAttribute('title',node.attrs['data-comment']);
    this.node = node;
    this.view = view;
    this.getPos = getPos;
...

When I set a breakpoint in the constructor, the getPos parameter is undefined.

If it’s undefined in the constructor, when it is actually defined so that I can call it?

You’re saying you create a MarkSpec (though you’re using the atom property which only exists for NodeSpec), which suggests that this is a mark, not a node. Mark views don’t get a getPos callback and, in general aren’t very powerful.

Ok. I will convert my MarkSpec to a NodeSpec and see what happens.

…which is easier said than done. I’m struggling to get this conceptually right. I need some pointers on how to implement this.

Here’s the behavior that I want:

  1. user enters text
  2. user selects text
  3. menu options are enabled that let user mark the selected text with one of several types of annotations (think of tags or comments here)
  4. selected text is wrapped in a node with the specific annotation type cached in a node attribute (let’s use data-type=“tag” in this first example)
  5. single clicking the wrapped text selects the node
  6. double clicking the wrapped text selects the node and shows a context menu for the node with editing options

I converted my MarkSpec into a NodeSpec which looks like this:

export const nodeSpec= {
  content:          "inline*",    // allow inline content
  marks:            "_",          // allow any marks
  group:            "inline",
  inline:           true,         // want the text to stay inline with wrapping node
  atom:             false,        // should this be true?
  selectable:       true,         // want to be able to select the node
  defining:         true,         // need to keep the node around if contents change
  isolating:        false,        // use explicit menu options to edit this node
  attrs: {
    "data-id":      {default: 1},
    "data-type":    {default: "tag"}
  },
  toDOM(node)       { return ["mark", node.attrs] },
  parseDOM:         [{tag: `mark[data-type="tag"]`, getAttrs(dom) {
    return {
      "data-id":      dom.getAttribute("data-id"),
      "data-type":    dom.getAttribute("data-type")
    }
  }}],
}

I’m having trouble creating the menu options to convert selected text to one of these nodes. Conceptually this is like marking, but I need a node, so do I use wrapItem() to do this? I know that toggleMark() does not work.

If the text that is ‘in’ the mark doesn’t have to be editable, you can just create a leaf node and store the text in an attribute. That makes it easier to represent these—nested inline nodes are difficult. Your ‘create mark’ command could just replace the selected text with a mark node.

I thought from earlier we decided that using a mark was the wrong approach.

I was just going by the fact that you called your mark “mark” initially, and continued to use that name. Hell, even you are called Mark, to complete the confusion.

1 Like

lol. sounds like a hair-lipped dog - “mark mark”

Ok, I will report on my progress.

  • I have an editor with a custom node type for highlights (FKA mark).
  • I can transform selected text into a HighlightNode from a menu option.
  • In my Highlight NodeSpec, I set the following:
    • atom = true
    • selectable = true
    • isolating = true
    • defining = true (don’t see any effects from this)
  • I can select a Highlight node with a mouse click as expected.
  • When I cursor into a Highlight node from the left, all is ok.
  • When I cursor into a Highlight node from the right, the text contents are editable, which I believe is a bug. At this stage of design, I would prefer to use a dialog for editing, so this behavior is undesirable.
  • I have experimented with a custom NodeView, but am having trouble with selecting the node, and have disabled that NodeView for now.

I need to show a custom dialog when the node is clicked and would like to hear suggestions on how to add this capability. I looked at a bunch of prose mirror projects on github and did not detect a consistent pattern. Is the Footnote example an appropriate approach?

I also want to create a private index of the Highlight Nodes in my Highlight Plugin. Where is the appropriate place to add Highlight Nodes to an index as they are created? I was gonna do this in my custom NodeView…

thanks for your help.

(and my compliments for creating such a powerful editor)