Using extended schema with prosemirror-inputrules

Hi all,

I try to use prosemirror-inputrules with a custom schema that extends prosemirror-markdown’s with an additional mark. It works just fine for marks, but my nodes (headings, blockquotes, etc.) aren’t replaced anymore. From what I see, the findWrapping() function doesn’t return anything in prosemirror-inputrules’s wrappingInputRule()… Any idea why? Here’s how I created my schema:

import { Schema } from 'prosemirror-model'
import { schema, defaultMarkdownParser, defaultMarkdownSerializer } from 'prosemirror-markdown'

const newSchema = new Schema({
  nodes: schema.nodeSpec,
  marks: schema.markSpec.addToEnd('del', {
    parseDOM: [{ tag: 'del' }],
    toDOM() { return ['del'] }
  })
})

Many thanks in advance! :wink:

Are you passing a node type from your schema when calling wrappingInputRule?

@marijn Yes, I set up the prosemirror-inputrules plugin like this:

plugins: [
  inputRules({ rules: allInputRules.concat(buildInputRules(newSchema)) })
]

const buildInputRules = (schema) => {
  let result = [], type
  if (type = schema.nodes.heading) result.push(headingRule(type, 6))
  return result
}

newSchema being my custom schema that extends the one of prosemirror-markdown

When I try the code you gave, the heading input rule works fine for me. Could you set up a full example?

Hey @marijn, thank you very much for replying on a Christmas day! Here is my full code:

import { Schema } from 'prosemirror-model'
import { EditorState } from 'prosemirror-state'
import { EditorView } from 'prosemirror-view'
import { schema, defaultMarkdownParser, defaultMarkdownSerializer } from 'prosemirror-markdown'
import { InputRule, inputRules, allInputRules, headingRule, bulletListRule, orderedListRule, blockQuoteRule, codeBlockRule } from 'prosemirror-inputrules'
import { keymap } from 'prosemirror-keymap'
import { baseKeymap } from 'prosemirror-commands'

const newSchema = new Schema({
  nodes: schema.nodeSpec,
  marks: schema.markSpec.addToEnd('del', {
    parseDOM: [{ tag: 'del' }],
    toDOM() { return ['del'] }
  })
})

const markInputRule = (regexp, markType, getAttrs) => {
  const newRegexp = new RegExp(regexp.source.replace(/\$$/, '') + '(.)' + '$')

  return new InputRule(newRegexp, (state, match, start, end) => {
    const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs
    const textStart = start + match[0].indexOf(match[1])
    const textEnd = textStart + match[1].length
    const tr = state.tr

    start = (match[0].match(/^\s/)) ? start + 1 : start

    if (textEnd < end) tr.delete(textEnd, end)
    if (textStart > start) tr.delete(start, textStart)

    end = start + match[1].length

    return tr
      .addMark(start, end, markType.create(attrs))
      .insert(end, schema.text(match[2]))
  })
}

const buildInputRules = (schema) => {
  let result = [], type

  if (type = schema.nodes.heading) result.push(headingRule(type, 6))
  if (type = schema.nodes.bullet_list) result.push(bulletListRule(type))
  if (type = schema.nodes.ordered_list) result.push(orderedListRule(type))
  if (type = schema.nodes.blockquote) result.push(blockQuoteRule(type))
  if (type = schema.nodes.code_block) result.push(codeBlockRule(type))
  if (type = schema.marks.strong) result.push(markInputRule(/(?:\*\*|__)([^\*_]+)(?:\*\*|__)$/, type))
  if (type = schema.marks.em) result.push(markInputRule(/(?:^|[^\*_])(?:\*|_)([^\*_]+)(?:\*|_)$/, type))
  if (type = schema.marks.code) result.push(markInputRule(/(?:`)([^`]+)(?:`)$/, type))
  if (type = schema.marks.del) result.push(markInputRule(/(?:~~)([^~]+)(?:~~)$/, type))

  return result
}

const view = new EditorView(document.querySelector('#editor'), {
  state: EditorState.create({
    doc: defaultMarkdownParser.parse(document.querySelector('#content').textContent),
    plugins: [
      inputRules({ rules: allInputRules.concat(buildInputRules(newSchema)) }),
      keymap(baseKeymap)
    ]
  }),
  onAction: action => view.updateState(view.state.applyAction(action))
})

Thank you again and Merry Christmas! :slight_smile:

I absolutely have my priorities wrong :worried:

Your problem is that defaultMarkdownParser will return documents in the original markdown schema. You can do something like this to create a new parser for your schema:

const parser = new MarkdownParser(newSchema,
                                  defaultMarkdownParser.tokenizer,
                                  defaultMarkdownParser.tokens)

Many thanks @marijn, I’d never have thought about that myself! It works almost perfectly… The only problem is that this line (inside the markInputRule() function) doesn’t insert the character anymore:

return tr
      .addMark(start, end, markType.create(attrs))
      .insert(end, schema.text(match[2]))

I don’t get why because schema.text(match[2]) seems to return the same object as before using the new parser…

Are you sure you’re referring to your own schema with the schema variable?

You’re absolutely right, sorry for taking your time for that! And thank you again for the help… :wink:

@davidgeilfus That markInputRule function you wrote is pretty neat! Have you considered publishing it as a standalone module to NPM. Or maybe a PR for including it in the core prosemirror-inputrules module, if @marijn is interested to merge it?