New to Kendo UI for Angular? Start a free 30-day trial

ProseMirror Schema

The Kendo UI for Angular Editor component is based on the ProseMirror library. ProseMirror provides a set of tools and concepts for building rich text editors, using user interface inspired by what-you-see-is-what-you-get.

Concepts

ProseMirror defines its own data structure, the Node, to represent content documents. The ProseMirror document is a tree-like structure comprised of nodes. Therefore, a document is an instance of Node with children that are also instances of Node.

Each ProseMirror document conforms to a specific schema. Document schemas allow you to edit documents with a custom structure without writing your own editor from scratch.

The schema describes all nodes that may occur in the document, the way they are nested, and any marks applied to them. A mark is a piece of information that can be attached to a node, such as it being emphasized, in code font, or a link. It has a type and optionally a set of attributes that provide further information (such as the target of the link).

For more details, refer to the ProseMirror schema guide.

Editor Schema

The Editor uses a built-in schema with predefined nodes and marks, which describes some of the most common HTML tags and their basic attributes like id, class, and style.

To modify existing nodes and marks or define new ones, you can customize the default schema or create a new one that best fits your needs.

Creating a custom Editor schema enables you to:

  • Define new types of nodes and marks.
  • Add new attributes to existing nodes and marks.
  • Remove existing nodes and marks.

To create a custom schema object, use one of the following approaches:

To replace the default schema with the custom one, bind the Editor schema option to the new schema object.

You can set a custom schema only once—upon the initialization of the Editor component. Further changes to the object to which the schema input is bound will not be reflected, and the Editor will continue to use the initially provided schema or, if no schema is initially provided, the built-in one.

Modifying the Default Schema

The following example demonstrates how to:

  • Add a new node to the nodes object of the schema.
  • Add a new mark to the marks object of the schema.
  • Add a new attribute to an existing node.
Example
View Source
Change Theme:

The following step-by-step guide shows how to recreate the above example by enhancing the built-in schema through adding definitions for HTML tags and attributes which are not available out-of-the-box. The demonstrated approach relies on importing the default schema, and modifying its nodes and marks collections. The schema can store nodes and marks in a custom data type, OrderedMap. The OrderedMap API exposes utility methods for accessing specific items and updating the collection.

  1. Import schema (the default Editor schema object) and Schema (the ProseMirror Schema class) from the @progress/kendo-angular-editor package:

        import { schema, Schema } from '@progress/kendo-angular-editor';
  2. Customize the existing nodes by using the relevant properties and methods from the NodeSpec and OrderedMap APIs. For example, to add a new dir attribute to the existing paragraph node, follow the steps:

    1. Use the OrderedMap get method to access the paragraph node.

          const paragraph = { ...schema.spec.nodes.get('paragraph') };
    2. Use the NodeSpec attrs property to define the new attribute.

          paragraph.attrs['dir'] = { default: null };
    3. Use the OrderedMap update method to replace the existing paragraph node with the customized one.

          let nodes = schema.spec.nodes.update('paragraph', paragraph);
  3. Create new nodes and add them to the existing nodes collection. For example, to provide support for the <iframe> tag, create and add an iframe node:

    1. Define the iframe node as a configuration object which complies with the NodeSpec interface.

          // NodeSpec interface
          // http://prosemirror.net/docs/ref/#model.NodeSpec
          const iframe = {
              attrs: {
                  src: { default: null },
                  style: { default: null }
              },
              group: 'block',
              selectable: false,
              parseDOM: [ {
                  tag: 'iframe',
                  getAttrs: (dom) => ({
                      src: dom.getAttribute('src'),
                      style: dom.getAttribute('style')
                  })
              } ],
              toDOM: node => {
                  const attrs = {
                      src: node.attrs.src,
                      style: node.attrs.style,
                      frameborder: '0',
                      allowfullscreen: 'true'
                  };
                  return [ 'iframe', attrs ];
              }
          };
    2. Append the new node to the nodes collection by using the OrderedMap addToEnd method.

          // Append the new node.
          nodes = schema.spec.nodes.addToEnd('iframe', iframe);
  4. Create new marks and add them to the existing marks collection. For example, to provide support for the <s> tag, create and add an s mark:

    1. Define the mark as a configuration object, which complies with the MarkSpec interface. Optionally, use a helper function that generates tags by a predefined tag name, passed as an argument.

          // Append a new mark representing the <s> formatting tag.
          // MarkSpec interface
          // https://prosemirror.net/docs/ref/#model.MarkSpec
          const tagMark = tag => ({
              [tag]: {
                  name: tag,
                  inclusive: true,
                  parseDOM: [ { tag } ],
                  toDOM: () => [ tag, 0 ]
              }
          });
          const mark = tagMark('s');
    2. Append the new mark to the marks collection by using the OrderedMap append method.

          let marks = schema.spec.marks.append(mark);
  5. Use the Schema constructor to create the customized schema with the modified and newly added nodes and marks.

        // TS Class code
        public mySchema = new Schema({
            marks,
            nodes
        });
  6. Bind the Editor schema input to the new schema.

        // Template
        <kendo-editor [schema]="mySchema" ...></kendo-editor>
    
        // TS Class code
        public mySchema = new Schema({
            marks,
            nodes
        });

Creating a New Schema

The following example demonstrates how to create a custom Editor schema.

Example
View Source
Change Theme:

The following step-by-step guide shows how to create a new schema by configuring all nodes and marks which the document may contain. The demonstrated approach relies on creating new nodes and marks objects, and passing them to the Schema class constructor.

  1. Define all marks and their respective attributes so that they comply with the ProseMirror MarkSpec interface. Optionally, use a helper function that generates tags by a predefined tag name, passed as an argument.

        // https://prosemirror.net/docs/ref/#model.MarkSpec
        const tagMark = tag => {
            return {
                [tag]: {
                    name: tag,
                    inclusive: true,
                    parseDOM: [ { tag: tag } ],
                    toDOM: () => [ tag, 0 ]
                }
            };
        };
    
        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), 0 ]; }
            },
    
            ...tagMark('strong'),
            ...tagMark('b'),
            ...tagMark('em'),
            ...tagMark('i'),
            ...tagMark('u'),
            ...tagMark('del'),
            ...tagMark('sub'),
            ...tagMark('sup'),
            ...tagMark('code'),
    
            style: {
                attrs: {
                    ...commonAttributes()
                },
                parseDOM: [ {
                    tag: 'span',
                    getAttrs: getAttributes
                } ],
                toDOM: node => (hasAttrs(node.attrs) ?
                    [ 'span', getAttrs(node.attrs), 0 ] : [ 'span', 0 ])
            }
        };
  2. Define all nodes and their respective attributes so that they comply with the ProseMirror NodeSpec interface.

        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), 0 ] : [ 'p', 0 ]
            },
    
            div: {
                content: 'block*',
                group: 'block',
                attrs: {
                    ...commonAttributes()
                },
                parseDOM: [ {
                    tag: 'div',
                    getAttrs: getAttributes
                } ],
                toDOM: node => hasAttrs(node.attrs) ? [ 'div', getAttrs(node.attrs), 0 ] : [ 'div', 0 ]
            },
            // ...more nodes
        }
  3. Use the Schema constructor to create the new schema with the nodes and marks defined in the previous steps.

        import { Schema } from '@progress/kendo-angular-editor';
    
        export const mySchema = new Schema({ nodes, marks });
  4. Bind the Editor schema input to the newly created schema.

        import { mySchema } from './custom-schema';
    
        // Ttemplate
        <kendo-editor [schema]="mySchema" ...></kendo-editor>
    
        // TS Class code
        public mySchema = mySchema;