New to Telerik UI for Blazor? Download free 30-day trial

Create a New Schema

This article describes how you can create a new ProseMirror schema for the Editor to use. Creating a new schema is useful if you want to change the majority of the default schema.

Prerequisites

To work with ProseMirror, make sure you are familiar with:

  • JavaScript—ProseMirror is a JavaScript library and the schema uses JavaScript syntax.
  • ProseMirror Schema—The schema structure and its children (nodes and marks).

Modifying the ProseMirror Schema is outside of the Editor scope and we do not provide support for such customizations.

Basics

The Editor accepts a custom ProseMirror schema through its Schema parameter. Set this string parameter to the name of a JavaScript function that:

  • Is declared in the global scope (the window object).
  • Returns an instance of the ProseMirror Schema class(the updated schema). You can access this class from the ProseMirror object of the event arguments.
  • Accepts a single argument.

The Editor will call this function and will pass an argument object that contains the following properties:

Property Description
getSchema A function that returns the current Schema object. Before the Editor is initialized, the returned Schema object is the default schema of the Editor. After the Editor is initialized, the returned Schema object is the updated schema. If you don't provide a custom schema, this function always returns the default schema.
getView A function that returns the currently used instance of the EditorView object. Before the Editor is initialized, the view (the result of the function) is null.
ProseMirror An object that contains various ProseMirror classes and functions.

You can set a custom schema only once during initialization of the Editor component. Further changes to the schema will not take effect and the component will continue using the initial custom or built-in schema.

Plugin Dependencies

Some of the ProseMirror plugins that the Editor uses by design depend on specific nodes in the default ProseMirror schema of the Editor. To get a collection of the default plugins, use the getPlugins function.

When creating a new schema from scratch, it is possible to get an exception if you do not include the needed nodes in your custom schema.

You have several options in this case:

Note that with the last two options you will lose the functionality that comes with the plugin(s) you remove.

Creating a New Schema

The below example shows how to:

  • Create a new instance of the Schema object and include several nodes and marks in it. The new schema supports only a couple of HTML elements such as <p>, <ul>, <ol> and <a>.
  • Remove a plugin that requires a node which is not part of your schema. The new Schema in this example does not include <ol> or <ul> elements, so we are removing the plugin that requires these nodes.
  • Return the updated schema, so the Editor can use it.

Create New ProseMirror Schema

@using Telerik.Blazor.Components.Editor
@* Avoid ambiguous reference with SVG icons *@
@using EditorNS = Telerik.Blazor.Components.Editor;

<TelerikEditor @bind-Value="@EditorValue"
               Schema="schemaProvider"
               Plugins="pluginsProvider"
               Tools="@EditorTools"
               Width="650px"
               Height="400px">
</TelerikEditor>

@code {
    private string EditorValue { get; set; } = string.Empty;

    protected override Task OnInitializedAsync()
    {
        EditorValue = @"
        <p>This Telerik Blazor Editor uses a ProseMirror Schema that supports only several nodes and marks - Paragraph, Hyperlinks, Bold text, Emphasized text, Underlined text.</p>

        <p>The new Schema does not include &lt;ol&gt or &lt;ul&gt elements, so we are removing the plugin that requires these nodes.</p>

        <p> Try editing the HTML to inserted non-supported tags such as &lt;ol&gt, &lt;h1&gt; or &lt;img&gt; - see how it is stripped and converted to &lt;p&gt;.";

        return base.OnInitializedAsync();
    }

    private List<IEditorTool> EditorTools { get; set; }

    protected override void OnInitialized()
    {
        EditorTools = new List<IEditorTool>();

        EditorButtonGroup firstGroup = new EditorButtonGroup(
            new EditorNS.Bold(),
            new EditorNS.Italic(),
            new EditorNS.Underline()
        );
        EditorTools.Add(firstGroup);

        EditorTools.Add(new CreateLink());

        EditorTools.Add(new ViewHtml());

        base.OnInitialized();
    }
}

@* Move JavaScript code to a separate JS file in production *@
<script suppress-error="BL9992">
    window.pluginsProvider = (args) => {
        const defaultSchema = args.getSchema();

        var plugins = args.getPlugins(defaultSchema);

        plugins.shift();

        console.log(plugins);

        return plugins;
    }

    var getAttributes = (dom) => {
        const result = {};
        const attributes = dom.attributes;
        let attr;

        for (let i = 0; i < attributes.length; i++) {
            attr = attributes[i];
            result[attr.name] = attr.value;
        }

        return result;
    };

    var commonAttributes = () => {
        return {
            style: { default: null },
            class: { default: null },
            id: { default: null },
        };
    };

    var hasAttrs = (
        attrs,
        exclude
    ) => {
        for (const attr in attrs) {
            if (attr && attrs[attr] !== null && attr !== exclude) {
                return true;
            }
        }
        return false;
    };

    var getAttrs = (
        attrs,
        exclude
    ) => {
        const result = {};
        for (const attr in attrs) {
            if (attr && attrs[attr] !== null && attr !== exclude) {
                result[attr] = attrs[attr];
            }
        }
        return result;
    };

    var tagMark = (tag) => {
        // https://prosemirror.net/docs/ref/#model.MarkSpec
        return {
            [tag]: {
                name: tag,
                inclusive: true,
                parseDOM: [{ tag: tag }],
                toDOM: () => [tag, hole],
            },
        };
    };

    var hole = 0;

    window.schemaProvider = (args) => {
        const nodes = {
            // The top level document node.
            doc: {
                content: "block+",
            },

            paragraph: {
                content: "inline*",
                group: "block",
                attrs: {
                    ...commonAttributes(),
                },
                parseDOM: [
                    {
                        tag: "p",
                        getAttrs: getAttributes,
                    },
                ],
                toDOM: (node) =>
                    hasAttrs(node.attrs) ? ["p", getAttrs(node.attrs), hole] : ["p", hole],
            },

            // The text node.
            text: {
                inline: true,
                group: "inline",
            },

            // default ProseMirror table nodes
            ...args.ProseMirror.tableNodes({
                tableGroup: "block",
                cellContent: "block+",
                cellAttributes: {},
            }),
        };

        const marks = {
            link: {
                attrs: {
                    ...commonAttributes(),
                    href: { default: null },
                    target: { default: null },
                    title: { default: null },
                },
                inclusive: false,
                parseDOM: [{ tag: "a", getAttrs: getAttributes }],
                toDOM(node) {
                    return ["a", getAttrs(node.attrs), hole];
                },
            },

            ...tagMark("strong"),
            ...tagMark("b"),
            ...tagMark("em"),
            ...tagMark("i"),
            ...tagMark("u")
        };

        const mySchema = new args.ProseMirror.Schema({ nodes, marks });

        return mySchema;
    }
</script>

See Also

In this article