How to render SVG (#something)

You can notice that the <use> does not work… But when I copy the same code on another page that is not rendered by ProseMirror, the <use> tag is rendered properly.

Code:

const todo_item = {
  attrs: {
    done: { default: false },
  },
  content: 'paragraph block*',
  toDOM(node) {
    const { done } = node.attrs;

    return ['label', {
      'data-type': 'todo_item',
      class: 'todo',
      'data-done': done.toString(),
    },
    // <input class="todo__state" type="checkbox" />
    ['input', { class: 'todo__state', type: 'checkbox', checked: 'true' }],
    ['svg', {
      xmlns: 'http://www.w3.org/2000/svg',
      'xmlns:xlink': 'http://www.w3.org/1999/xlink',
      viewBox: '0 0 200 25',
      class: 'todo__icon',
    },
    ['use', { 'xlink:href': '#todo__line', class: 'todo__line' }],
    ['use', { 'xlink:href': '#todo__box', class: 'todo__box' }],
    ['use', { 'xlink:href': '#todo__check', class: 'todo__check' }],
    ['use', { 'xlink:href': '#todo__circle', class: 'todo__circle' }],
    ],
    ['div', { class: 'todo__text' }, 0],
    ];
  },
  parseDOM: [{
    priority: 51, // Needs higher priority than other nodes that use a "li" tag
    tag: '[data-type="todo_item"]',
    getAttrs(dom) {
      return {
        done: dom.getAttribute('data-done') === 'true',
      };
    },
  }],
};

const todo_list = {
  group: 'block',
  content: 'todo_item+ | list_item+',
  toDOM(node) {
    return ['div', {
      'data-type': 'todo_list',
      class: 'todo-list',
    }, 0];
  },
  parseDOM: [{
    priority: 51, // Needs higher priority than other nodes that use a "ul" tag
    tag: '[data-type="todo_list"]',
  }],
};

I have the same problem.

The cause is HTML namespace. svg tag must have http://www.w3.org/2000/svg namespace, and ‘xlink:href’ attribute must have http://www.w3.org/1999/xlink.
document.createElementNS and Element.setAttributeNS APIs are necessary to set the namespace, but ProseMirror uses document.createElement and Element.setAttribute. So the nodes and attributes do not have any namespace, then thay do not work.

We can solve the problem with the following patch for prosemirror-model.

This patch is based on Add support for xmlns elements in DOMOutputSpec · Issue #898 · ProseMirror/prosemirror · GitHub.
The patch made by steveccable, who is author of the issue, does not treat attribute. So I modify to enable to work attribute handling.

We can use the patch with the following schema definition.

toDOM(node) {
  return [
    'svg',
    { xmlns: 'http://www.w3.org/2000.svg' },
    ['use', { "xlink:href": { value: "#YOUR_SVG_ID", xmlns: "http://www.w3.org/1999/xlink" } }]
  ]
}

It applies the namespace to SVG and USE tags, and it displays the image.

However, I found another pull request for the pull request, and it was closed. Support creation of elements with a namespace by vandenoever · Pull Request #10 · ProseMirror/prosemirror-model · GitHub

marijnh said:

I don’t like this approach, but I’d be okay with a PR that makes renderSpec recognize "foo:bar" type strings to mean bar in namespace foo , if you want to submit such a thing.

But I guess the approach, "foo:bar" is difficult to use. Because actually the namespace is a URI, such as http://www.w3.org/2000/svg. It is not a simple string like foo.
I tried to use svg and xlink instead of the URIs, but it did not work.

So I think we need to reconsider the style of the format. My patch works, but I think it is not the best format.
If you have any good ideas, please tell me.

I still think something string-based would be nice, because it doesn’t require any weirdness with attributes. Since URLs can’t contain spaces, maybe "https://namespace.something tagname" works?

Thank you for your suggestion. It makes sense. I opened a pull request for this problem based your idea.

Thanks.

@pocke. Thank you for writing that PR. Now that ProseMirror has a doc.createElementNS method, could you write up an example of how to use it? Perhaps an example schema and an HTML snippet that makes use of it?

2 Likes