1

Research

So I've found this answer on how to create a vnode list from a simple SVG with one path layer and how to transform that in Vue2.

I could not find any good solutions for Vue 3, so I scaffolded something together based on my knowledge of Vue and some docs on MDN

What I want

A way to transform a HTML string that contains a tree of nodes to Vue Nodes.

1 Answer 1

0

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!

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.