parseDom Content for Inline Node (footnotes)

I appear to have run into the infamous footnote problem. I am trying to add footnotes (annotations) to the document. I’ve taken a nodeView approach to solving this.

My Schema (abbreviated)

nodes: {
	doc: { content: 'block+' },

    paragraph: {
		group: 'block',
		content: 'inline<_>*',
		parseDOM: [{tag: 'p'}],
		toDOM: function toDOM () { return ['p', 0]; }
	annotation: {
		group: 'inline',
		inline: true,
		attrs: {
		parseDOM: [{
			tag: 'annotation',
			getAttrs: function (node) {
				var attrs = {};
				_.each(node.attributes, function (attr) {
					if ('annotation') !== -1) {
						attrs[] = attr.value;

				return attrs;
			getContent: function (node) {
				return node.innerText;
		toDOM: function (node) {
			return ['span', {class: 'annotation'}];

My NodeView

nodeViews: {
    annotation: function (node, view, getPos) {
		var $dom = $('<span>').addClass('annotation').attr(node.attrs);

		$('<div>').addClass('annotation-wrapper').html('<div class="annotation-body"><div class="annotation-title">Annotation</div><p class="annotation-comment"></p></div>').appendTo($dom);

		return {
			dom: $dom[0]

With some CSS the annotation-wrapper is floated into a gutter and the annotation-target stays within the document editor. That all looks fine but editing is a little weird. The problem that I am looking at now though has to do with parseDom on the annotation. The dom it parses is one annotation tag with no children. Just some attributes and text content. The content of the annotation tag is never retrieved. I think this might be because the annotation node is considered to be a leaf node and when parsing its contents is never set:

ParseContext.prototype.addElementByRule if (nodeType.isLeaf) { this.insertNode(nodeType.create(rule.attrs, null, this.marks)) }

When the addElementByRule function is called it considers the node to be a leaf and sets the contents to null. It never tries to retrieve the content even though I have getContent defined on the annotation node spec. Which seems to be the case for inline nodes (or nodes without content). Because of this I am not able to parse the annotation target’s text content to put into the doc.

Is there a way for me to parse and set the content? Is this a good method for adding annotations to ProseMirror?

Even if I solve this problem of parsing the dom I am concerned that I will run into other issues like editing the annotation target text and selecting and moving the cursor around it.

Your node doesn’t allow content, so indeed, as you noticed, the parser won’t it won’t look for content. Did you mean for to have content: 'text*' or similar?

Still, to be able to properly edit these, your node view is going to have to do a bunch more work (at the very least, it’ll have to set the content editable – or create a textarea with the content). Embedding an extra ProseMirror to edit a node like this will also get easier in the next release.

1 Like

Adding content: 'text*' was the key to getting it to work. I had to make a couple of other changes once I did that.

  1. I got rid of my getContent function in annotation.parseDOM and just let ProseMirror handle that for me.

  2. Serializing was not working when going from the nodeView dom -> my dom. My dom stores the annotation as one element. To get that working I added an onContent function to the options passed when calling serializeFragment

myDOMSerializer.serializeFragment(textEditorView.state.doc.content, {
	onContent: function (node, contentDOM, options) {
		if ( === 'annotation') {
		else {
			myDOMSerializer.serializeFragment(node.content, options, contentDOM);

I couldn’t figure out a way to write the toDOM function or the DOMOutputSpec in such a way that it would serialize the content correctly. But using onContent is working.

I plan on adding a textarea to the nodeView’s dom for editing the annotation comment. And will be storing the value of the comment in an attribute when serialized into my dom structure.

Thanks for the help @marijn!