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
andmarks
. - Add new attributes to existing
nodes
andmarks
. - Remove existing
nodes
andmarks
.
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.
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.
-
Import
schema
(the default Editorschema
object) andSchema
(the ProseMirrorSchema
class) from the@progress/kendo-angular-editor
package:tsimport { schema, Schema } from '@progress/kendo-angular-editor';
-
Customize the existing nodes by using the relevant properties and methods from the
NodeSpec
andOrderedMap
APIs. For example, to add a newdir
attribute to the existingparagraph
node, follow the steps:-
Use the
OrderedMap
get
method to access theparagraph
node.tsconst paragraph = { ...schema.spec.nodes.get('paragraph') };
-
Use the
NodeSpec attrs
property to define the new attribute.tsparagraph.attrs['dir'] = { default: null };
-
Use the
OrderedMap
update
method to replace the existingparagraph
node with the customized one.tslet nodes = schema.spec.nodes.update('paragraph', paragraph);
-
-
Create new nodes and add them to the existing
nodes
collection. For example, to provide support for the<iframe>
tag, create and add aniframe
node:-
Define the
iframe
node as a configuration object which complies with theNodeSpec
interface.ts// 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 ]; } };
-
Append the new
node
to thenodes
collection by using theOrderedMap
addToEnd
method.ts// Append the new node. nodes = schema.spec.nodes.addToEnd('iframe', iframe);
-
-
Create new marks and add them to the existing
marks
collection. For example, to provide support for the<s>
tag, create and add ans
mark:-
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.ts// 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');
-
Append the new
mark
to themarks
collection by using theOrderedMap
append
method.tslet marks = schema.spec.marks.append(mark);
-
-
Use the
Schema
constructor to create the customized schema with the modified and newly addednodes
andmarks
.ts// TS Class code public mySchema = new Schema({ marks, nodes });
-
Bind the Editor
schema
input to the new schema.ts// 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.
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.
-
Define all
marks
and their respective attributes so that they comply with the ProseMirrorMarkSpec
interface. Optionally, use a helper function that generates tags by a predefined tag name, passed as an argument.ts// 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 ]) } };
-
Define all
nodes
and their respective attributes so that they comply with the ProseMirrorNodeSpec
interface.tsconst 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 }
-
Use the
Schema
constructor to create the new schema with thenodes
andmarks
defined in the previous steps.tsimport { Schema } from '@progress/kendo-angular-editor'; export const mySchema = new Schema({ nodes, marks });
-
Bind the Editor
schema
input to the newly created schema.tsimport { mySchema } from './custom-schema'; // Ttemplate <kendo-editor [schema]="mySchema" ...></kendo-editor> // TS Class code public mySchema = mySchema;