My Solution
I implemented a recursive algorithm to traverse through the string, extract all nodes, and then add them to an array of vNodes that eventually bind together to form a single root node.
const parseHTML = (props, context) => {
let {content} = props
const div = document.createElement('div')
div.innerHTML = content
const getAttributes = (node) => {const attrs = {}; for (const attr of node.attributes) attrs[attr.name] = attr.value; return attrs;}
const convertToNodes = (nodes) => {
return Array.from(nodes).map(element => {
if (element.nodeType === 3) return h('span', element.nodeValue)
if (element.childNodes.length)
return h(element.tagName, getAttributes(element), convertToNodes(element.childNodes))
return h(element.tagName, getAttributes(element), element.innerHTML)
})
}
const vNodes = convertToNodes(div.childNodes)
return h(`div`, {...context.attrs}, vNodes)
}
This function is enclosed within a functional component that can be included in a template:
// parseHTML.ts
import { h } from 'vue'
const parseHTML = (props, context) => {
let {content} = props
const div = document.createElement('div')
div.innerHTML = content
const getAttributes = (node) => {const attrs = {}; for (const attr of node.attributes) attrs[attr.name] = attr.value; return attrs;}
const convertToNodes = (nodes) => {
return Array.from(nodes).map(element => {
if (element.nodeType === 3) return h('span', element.nodeValue)
if (element.childNodes.length)
return h(element.tagName, getAttributes(element), convertToNodes(element.childNodes))
return h(element.tagName, getAttributes(element), element.innerHTML)
})
}
const vNodes = convertToNodes(div.childNodes)
return h(`div`, {...context.attrs}, vNodes)
}
parseHTML.props = ['content']
export default parseHTML
<template>
<parseHTML :content="myContent"/>
</template>
<script setup>
const myContent = "<div><p>Hello <button>Click!</button> some more text</p><p><em>cursive <strong>and bold</strong> stuff</em></p></div>"
</script>
The parseHTML
render function allows you to manipulate nodes as needed. In my scenario, I transformed a basic button generated by Tiptap into a customized Vuetify VBtn.
By inserting the following code after checking for a #text
node, I successfully added a Vuetify button based on specific criteria:
...
if (element.attributes.getNamedItem('data-id')?.value === 'richTextBtn')
return h(VBtn, {onClick($event) { alert('hello!')}}, [element.innerHTML])
...
Looking Ahead
I trust this information will aid others facing similar challenges. Your feedback and alternative solutions are welcomed and appreciated!