What I came up with
I tried to recursively find all nodes in the string and them push them to an array of vNodes that gets bound in the end to one root node.
const parsedTree = (props, context) => {
let {content} = props
const div = document.createElement('div')
div.innerHTML = content
const getAttrs = (node) => {const attrs = {}; for (const attr of node.attributes) attrs[attr.name] = attr.value; return attrs;}
const htmlToNodes = (nodes) => {
return Array.from(nodes).map(element => {
// 3 is #text, not sure how else to build this one
if (element.nodeType === 3) return h('span', element.nodeValue)
if (element.childNodes.length)
return h(element.tagName, getAttrs(element), htmlToNodes(element.childNodes))
return h(element.tagName, getAttrs(element), element.innerHTML)
})
}
const hElements = htmlToNodes(div.childNodes)
return h(`div`, {...context.attrs}, hElements)
}
The whole function is inside a functional component that you can add in a template:
// parsedTree.ts
import { h } from 'vue'
const parsedTree = (props, context) => {
let {content} = props
const div = document.createElement('div')
div.innerHTML = content
const getAttrs = (node) => {const attrs = {}; for (const attr of node.attributes) attrs[attr.name] = attr.value; return attrs;}
const htmlToNodes = (nodes) => {
return Array.from(nodes).map(element => {
// 3 is #text, not sure how else to build this one
if (element.nodeType === 3) return h('span', element.nodeValue)
if (element.childNodes.length)
return h(element.tagName, getAttrs(element), htmlToNodes(element.childNodes))
return h(element.tagName, getAttrs(element), element.innerHTML)
})
}
const hElements = htmlToNodes(div.childNodes)
return h(`div`, {...context.attrs}, hElements)
}
parsedTree.props = ['content']
export default parsedTree
<template>
<parsed-tree :content="myHTML"/>
</template>
<script setup>
const myHTML = "<div><p>Hello <button>Click!</button> some more text</p><p><em>cursive <strong>and bold</strong> stuff</em></p></div>"
</script>
Key is, that in the parsedTree render, you can check nodes and transform them if needed. In my case, I wanted to transform a plain button that gets set in Tiptap to a custom VBtn from Vuetify
When putting (in my case) the following code below the #text node check, I can add a Vuetify button based on my input
...
if (element.attributes.getNamedItem('data-id')?.value === 'richTextBtn')
return h(VBtn, {onClick($event) { alert('hello!')}}, [element.innerHTML])
...
Future
I hope this helps someone else in the struggle I had, please feel free to comment or add your own solution, very happy to see them!