How to disable nodes by type - such as images, lists, headings?

Greetings!

I’ve been studying prosemirror for a couple of days now. I think I have a rough grasp of the conceptual entites and how they fit together.

I’m implementing a markdown editor based on the “Friendly markdown” tutorial. All has gone well so far in customising it to be exactly as I want (menu items, in particular).

But I am stuck when trying to prevent users from adding certain node types. (I believe nodes is the correct term). I’ve partly disabled the user from adding headings, images, and ordered and unordered lists by excluding them from the menu, but users can still paste these things in from their clipboard.

I believe that the place to do this is in the schema, so I’ve created a copy of the provided schema (https://github.com/ProseMirror/prosemirror-markdown/blob/master/src/schema.js), and set some properties to false like this:

export const schema = new Schema({
    nodes: {
        ordered_list: false,
        bullet_list: false,
        list_item: false,

But I can still paste the unwanted things into the editor.

Is this the correct place to blacklist element types?

I am thinking that maybe I need to join the schema into the editor view somehow?

“Setting things to false” has no meaning in a schema definition—rather, you only include the node types that you want to appear in the schema. So I suspect you’re doing something wrong in your schema definition. When nodes don’t appear in the schema, you definitely can’t paste them into the editor anymore (or, rather, the editor will parse them in terms of the nodes it does know, so it might reinterpret a heading as a paragraph, for example).

We use the basic schema and then remove nodes we want to replace or exclude entirely. Here’s a simplified version if that helps.

const {Schema} = require('prosemirror-model')
const {schema: baseSchema} = require('prosemirror-schema-basic')
const linkMark = require('./custom/link_mark')

const basicNodes =
  baseSchema.spec.nodes
  .remove('code_block')
  .remove('heading')
  .remove('blockquote')

const basicMarks =
  baseSchema.spec.marks
  .remove('link')

const basicAndCustomMarks =
  basicMarks.addToStart('link', linkMark)

const localSchema = new Schema({
  nodes: basicNodes,
  marks: basicAndCustomMarks
})

That should work, then.

Thank you very much for your replies, and greetings from a cloudy UK.

I had a feeling that the problem wasn’t the schema, but how I was hooking it into the rest of the editor pieces.

Galvinised from what you’ve said about the schema having absolute authority over how rich text is interpreted, I looked harder at how I was wiring it in.

I found that the problem was that I was erroneously leveraging your defaultMarkdownParser instance, when I needed to be leveraging a customised instance of the MarkdownParser class.

For the thread, the code below shows how I’ve linked things together to have the editor working on a whitelisted set of markdown nodes:

(The code below is intended to augment the foundational example code show here: https://prosemirror.net/examples/markdown/)

const parser = new MarkdownParser(schema, markdownit('commonmark', { html: false }), {
    paragraph: { block: 'paragraph' },
    hardbreak: { node: 'hard_break' },
    em: { mark: 'em' },
    strong: { mark: 'strong' },
    link: {
        mark: 'link',
        getAttrs: tok => ({
            href: tok.attrGet('href'),
            title: tok.attrGet('title') || null,
        }),
    },
});

class ProseMirrorView {
    view;
    constructor(target, content, { onChange = null } = {}) {
        const extraKeymap = buildKeymap(schema, null);
        this.view = new EditorView(target, {
            state: EditorState.create({
                doc: markdownParser.parse(content),
                plugins: [
                    keymap({
                        ...extraKeymap,
                        ...baseKeymap,
                    }),
                    createMenuPlugin(schema),
                ],
            }),
            dispatchTransaction(tr) {
                this.updateState(this.state.apply(tr));
                const con = markdownSerializer.serialize(this.state.doc);
                if (typeof onChange === 'function') {
                    onChange(null, con);
                }
            },
        });
    }

    get content() {
        return markdownSerializer.serialize(this.view.state.doc);
    }
    focus() {
        this.view.focus();
    }
    destroy() {
        this.view.destroy();
    }
}