Select and replace last entered character

I’m writing a command that when the character ` is pressed, that character will be selected and replaced with a node I’ve defined (a span element). I’ve tried to hook this behavior in my insert command for the span node as so but it doesn’t seem to work

function insertMyNode(fromKeyMap=false) {
    return function (state, dispatch) {
        let {$from} = state.selection, index = $from.index();
        if (!$from.parent.canReplaceWith(index, index, scratchSchema.nodes.mynode)) {
            return false;
        }
        if (dispatch) {
            if (fromKeyMap) {
                // select the ` character. (hardwritten as position (0,1) for testing.)
                let sel = TextSelection.create(state.doc, 0, 1);
                state.tr.setSelection(sel);
            }
            let myNode = scratchSchema.nodes.mynode.create();
            dispatch(state.tr.replaceSelectionWith(myNode));
        }
        return true;
    }
}

Am I using TextSelection.create() correctly or is it perhaps how I’m using dispatch()? Ideally I would set my selection relative to the cursor position after hitting the ` key but I’m just starting some testing and am having no luck.

You’re creating a transaction and then directly throwing it away again. You'll probably get better results if you first create a transaction (with state.tr), store it in a variable, and use that same transaction throughout the function.

Thank you, I had no idea I was throwing away my transaction this whole time. Do I need to create a transaction as well if I want to transform the resulting span element with Javascript? After the span is created I am attempting to call a javascript function that fills the span element with content and makes some of it interactive (such as a button). However, I’m noticing that ProseMirror is immediately removing this content

Don’t do that. Either change the document, define a node view, or use a decoration. Directly messing with the DOM is not going to work.

1 Like

Perfect! exactly what I needed. I’m noticing that if I interact with the inner content, ProseMirror ignores all changes as expected. Now it’s time to save the state of the custom content so it can be loaded next time the user opens the page. I’m piecing together through the documentation and other discussions that to do that I need to define a parseDom property in my nodeSpec. I’ve tried something like so:

const mySpec : NodeSpec = {
  inline: true,
  group: "inline",
  content: "text*",
  selectable: true,
  toDOM: () => {
    return ["span", {"my-node": true}, 0]
  },
  parseDOM: [{
    tag: "span[my-node=true]",
    getContent: (node: HTMLElement) => {
      return 'my custom content settings';
    }
  }]
}

though I’m not seeing the returned string in the ProseMirror structure no matter what I do. I’m guessing I might have to call parseDom manually on every update? My Node View looks rather complex but the only information it needs to render is a string no greater then 256 characters long. I’m thinking that storing that string as the node’s content should be easy but I can’t seem to put this together given the examples I’ve seen. Inspecting the node, I see that it’s title is set but it has no content attribute unlike the other text nodes

getContent should return a ProseMirror Fragment, not a string.

Thank you. I’ve been scratching my head at this for the last couple days… I’ve tried calling debugger or even console.log inside getContent just so I can at least get a sense of the flow of execution within ProseMirror but it never reaches that method. I imagine it’s because I have my Node View set to

stopEvent = true

and

ignoreMutations = true

It makes sense that since my Node View handles it’s own interactions that ProseMirror never recognizes an update but is it possible that I can trigger getContent manually? Also seeing as how getContent returns a Fragment, does that mean I have to add a child node within my node just to hold the string I’m trying to store?

Parse rules are used for external content, not node views. In a node view, you can fire your own transactions (probably using the extra parameters passed to the node view’s constructor) in response to user interaction if you need to. It sounds like that’s what you’re looking for.

I’m using a custom event listener that whenever the node view is interacted with, I explicitly call the update() method on the node view.

Then, in the update() method I try to set/update one of the Node’s attrs with the setNodeMarkup() method but as soon as the method runs, my node view resets and all interacted elements revert back as if they were just created. Here is my update() method within my node view:

update(node, view, getPos) {
    console.log('edditing');
    let thisOptions = this.interface.toString();
    let tr =this.view.state.tr;
    console.log(tr);
    this.view.dispatch(
        tr.setNodeMarkup(this.getPos(), this.node.type, {'options': thisOptions})
    );
}

is this incorrect to do?

Don’t. That’s a method that the editor view will call to update the node, not something that you can meaningfully call yourself. It should definitely not create transactions—it should, as the docs explain, update the node view’s DOM to reflect a new instance of the node.