Adding div with foreign html content

Hi,

I’d like to insert some html content into a [div] tag using the node schema. Inserting the [div] tag itself was rather straight forward. How should I code the parseDOM and toDOM so that the html content can be placed within the [div] tags?

Thank you

You could put the HTML in an attribute of the node, and have parseDOM and toDOM use innerHTML to read the attribute and put it back into the <div>. You’ll probably at some point want to define a node view for such a node to prevent it from re-rendering on every update (which is the default behavior when attributes change).

2 Likes

Hi marijn,

Thanks a lot for replying. I understand what you mean. That seems like a long shot though.

Thank you.

We are using the approach Marijn described for foreign HTML content.

We have a special node which has an attribute that contains an arbitrary HTML string. This node type does not allow children in the PM document. We then have a node view which makes a few adjustments to the HTML string before rendering it inside the nodeview dom element. The nodeview has logic to avoid re-parsing and re-rendering the HTML content unless strictly necessary.

There’s a bunch more you can do with it too, but that approach has worked well for us so far and allows for further extension. For example, we have a command which converts the node and its HTML content into a simplified form that maps nicely to nodes in our ProseMirror document schema.

1 Like

Could be feasible adding a property for a node spec (eg ‘opaque’) to manage such cases?

Sorry to revive this topic but I’ve been misusing ProseMirror a bit (using a single Node to show a lot of content fetched from the database) and I needed to inject external HTML into a Node, so I wrote a HTML string to DOMOutputSpecArray converter.

I didn’t want to spin up a new ProseMirror instance each time because I might need to do this hundreds of times in a single document, also I’m using a NodeView for creating the document with lots of controls and consistently different markup, and simple HTML/toDOM-style view when printing to a PDF so I couldn’t just use NodeView.

Once I will have some time, I will refactor my code so it will work as intended within the ProseMirror philosophy but right now I can contribute this hack to others that are stuck in a position like I am.

Here’s the code as TypeScript: Convert an HTML string to ProseMirror's DOMOutputSpecArray · GitHub

Here is the code as JavaScript

const defaultSpec = ['p', {}, '']

const domParser = new window.DOMParser()

function namedNodeMapToRecord(attributes) {
  const output = {}
  for (let i = 0; i < attributes.length; i++) {
    const attribute = attributes[i]
    output[attribute.name] = attribute.value
  }
  return output
}

/**
 * Convert an HTML string into ProseMirror's DOMOutputSpec,
 * but always wrap result in an Array for destructuring.
 */
export function htmlToDOMOutputSpec(html) {
  if (html == null || html === '') {
    return [defaultSpec]
  }

  const collection = domParser
    .parseFromString(html, 'text/html')
    .querySelectorAll('body')[0].childNodes

  const convert = (nodes) => {
    const output = []

    if (nodes.length === 0) {
      return output
    }

    for (let i = 0; i < nodes.length; i++) {
      const node = nodes[i]
      node.normalize()

      if (node.nodeType === Node.TEXT_NODE) {
        output.push(node.textContent)
      } else if (node.nodeType === Node.ELEMENT_NODE) {
        const { attributes } = node
        output.push([
          node.nodeName.toLowerCase(),
          attributes.length > 0 ? namedNodeMapToRecord(attributes) : {},
          ...convert(node.childNodes),
        ])
      }
    }
    return output
  }

  return convert(collection)
}

Example usage would be something similar to this:

toDOM(node) {
  return [
    'div',
    { class: 'user-generated-note' },
    ...htmlToDOMOutputSpec(node.attrs.note)
  ]
}

Shouldn’t that do the same?

toDOM(node) {
  const dom = document.createElement('div')

  dom.classList.add('user-generated-note')
  dom.innerHTML = node.attrs.note

  return dom
}

is it possible to mix DOMOutputSpec and normal DOM?

everything else I have inside toDOM is using the former

another question: if I would use this Node method inside toDOM, how could I use the 0(hole)? I can’t find anything in the docs about this!

you can’t mix it but you can define a content hole via contentDOM similar to node views.

toDOM(node) {
  const dom = document.createElement('div')
  const contentDOM = document.createElement('div')

  dom.appendChild(contentDOM)

  return {
    dom,
    contentDOM,
  }
}

thanks!

so my solution is not completely useless after all because of the lack of mixing =)

do you know if there’s a reason to have support for these two formats? is one going to be deprecated at one point?