InputRule replaceWith custom node type

I’d like to replace the triple-` code block shortcut with our CodeMirror widget. Here is our widget schema and autoInput rule:

import {InputRule} from 'prosemirror/src/inputrules'
import {Media} from '../schema/media'

import uuid from 'uuid'

Media.register('autoInput', 'replaceWithCodeWidget',
  new InputRule(/^```$/, '`', function (pm, _, pos) {
    pm.tr.replaceWith(pos, pos, pm.schema.node(this, {
      id: uuid.v4(),
      type: 'code'
    })).apply()
  })
)

(updated for 0.2.0 changes)

On typing the shortcut, the code runs without errors, but the document doesn’t change, and p isn’t replaced with the widget placeholder. Any clues?

Something changes, if I hit delete after the 3 `s the whole paragraph is deleted.

Also tried the setBlockType method used in the PM code autoinput, but it doesn’t work. Maybe something is wrong with the div schema def?

Media.register('autoInput', 'replaceWithCodeWidget',
  new InputRule(/^```$/, '`', function (pm, _, pos) {
    setAs(pm, pos, this, {
      id: uuid.v4(),
      type: 'code'
    })
  })
)

function setAs (pm, pos, type, attrs) {
  pm.tr.setBlockType(pos, pos, type, attrs)
       .delete(new Pos(pos.path, 0), pos)
       .apply()
}

You can’t pass the cursor position to setBlockType. It takes the position before the block as argument. Calling pos.shorten() might help.

CodeBlock and Heading autoinput use pos the same way that I’m trying to here.

If I change Media.register to Heading.register it works to convert to h1.

I’ve tried all combinations of replaceWith, setBlockType, and setNodeType with pos and pos.shorten(). I’m suspecting something in the Media (div) schema is silently blocking the conversion.

Ah, my bad, I was thinking of setNodeType not setBlockType.

That does indeed look like it should work. Could it be that your Media type isn’t allowed in the place where the old block is? What’s its kind?

I want it to only be a top-level block:

export class Media extends Block {
  static get kinds () { return 'doc media' }
  ...

That probably doesn’t do what you intend for it to do. The strings in kinds will be compared to the contains fields in parent nodes. I don’t think you have a parent node that contains "media" or "doc".

You could give your doc node a contains of "toplevel" and add everything that may appear at the top level to that. And for nestable nodes that may also appear on the toplevel, give them kinds "toplevel block", for example.

Changing to static get kinds () { return 'block' } didn’t help.

Could you put up a (minimal) script that shows the problem somewhere? I can’t really guess what is going on from the hints you’re providing.

Current version that works with our custom code editor (CodeMirror iframe):

import {Media} from '../schema/media'
import uuid from 'uuid'
import {InputRule} from 'prosemirror/src/inputrules'


Media.register('autoInput', 'ed_start_code',
  new InputRule(/^```$/, '`', function (pm, _, pos) {
    insertBlock(pm, pos, this, {id: uuid.v4(), type: 'code'})
  })
)

function insertBlock (pm, pos, type, attrs) {
  const $pos = pm.doc.resolve(pos)
  const start = pos - $pos.parentOffset
  const nodePos = start - 1
  const codeNode = type.create(attrs)
  pm.tr
    // delete the ```
    .delete(start, pos)
    // insert the code block above the current block
    .insert(nodePos, codeNode)
    .apply()
}

1 Like