contentful-rich-text-vue-renderer icon indicating copy to clipboard operation
contentful-rich-text-vue-renderer copied to clipboard

Render custom vue 3 components

Open danielmalmros opened this issue 4 years ago • 5 comments

Hi,

Thanks for maintaining this project :)

I'm trying to use "[email protected]" in our Vue 3 (Single Page Application) to render some custom components based on BLOCKS.EMBEDDED_ENTRY.

We have several different embedded entry types in Contentful that all uses different layout - so we need to render different custom components. But right now it seems like thats not possible?

Right now, I can't even render a router-link. When looking into the DOM, I see <router-link to="/path">Read more</router-link> - so it seems like it's not rendering correctly? I guess I would expect it to render the router-link or any other custom components.

Here is an example of what we are trying to do:

<template>
  <RichTextRenderer
    :document="component.richText"
    :nodeRenderers="renderNodes()"
  />
</template>

<script lang="ts">
import { defineComponent, h } from "vue";
import { BLOCKS } from "@contentful/rich-text-types";
import RichTextRenderer from "contentful-rich-text-vue-renderer";

export default defineComponent({
  name: "Example",
  components: {
    RichTextRenderer,
  },
  props: {
    component
  },
  setup() {

    const renderNodes = () => {
      return {
        [BLOCKS.EMBEDDED_ENTRY]: (node, key, next) =>
          h(
            "router-link",
            {
              key,
              to: 'path'
            },
            'Read more'
          ),
      };
    };

    return { renderNodes };
  },
});
</script>

danielmalmros avatar Sep 17 '21 14:09 danielmalmros

@danielmalmros thank you for the detailed issue report!

I can reproduce your issue. I just can't seem to figure out what's causing it. It's as if Vue has no idea that vue-router has it's components registered.

Have you done any further debugging in the meantime? It would be amazing if you could pinpoint the issue (and even maybe fix it with a PR 👀)

tolgap avatar Sep 23 '21 15:09 tolgap

@tolgap sorry for late response. I did a little digging but not much and did not find anything unfortunately..

If I can find the time I'll try dig more into it.

danielmalmros avatar Oct 15 '21 05:10 danielmalmros

If you are trying to sort out the router-link here is the only way we've managed to sort it out... A bit hacky but it works...

<template>
  <div class="rich-text" v-html="parsedText" />
</template>

<script>
import { INLINES } from '@contentful/rich-text-types'
import { documentToHtmlString } from '@contentful/rich-text-html-renderer'
import _each from 'lodash/each'

export default {
  name: "rich-text",

  props: ["text"],

  mounted() {
    this.links = this.$el.querySelectorAll('.nuxt-link-fake')
    _each(this.links, link => link.addEventListener('click', this.onClick))
  },

  computed: {
    richOptions() {
      return {
        renderNode: {
            [INLINES.HYPERLINK]: (node, next) => {
              let link = `<a href='${node.data.uri}' `
              link += this.isExternalUrl(node.data.uri) ? `target='_blank'` : `class='nuxt-link-fake'`
              link += `>${next(node.content)}`
              link += `</a>`
              return link
            }
        }
      } 
    },

    parsedText() {
        return documentToHtmlString(this.text, this.richOptions)
    },

    websiteUrl() {
        return process.client ? location : new URL(process.env.WEBSITE_URL)
    }
  },

  methods: {
    isExternalUrl(url) {
      return new URL(url).origin !== this.websiteUrl.origin;
    },

    onClick(e) {
      e.preventDefault()
      let url = new URL(e.currentTarget.getAttribute("href"))
      this.$router.push(url.pathname)
    }
  }
}
</script>

hjujah avatar Oct 22 '21 15:10 hjujah

I've been looking into this during my holidays. I think I've figured out what is going wrong. Because of the recent changes to Vue 3, where the createElement, aka h function has been refactored to a global function. If you want to render a component in a render function, you need to actually provide the component itself, instead of just the component template name: https://jsfiddle.net/2rbxg1q9/

So actually, there are no changes required in this library. You just need to make sure to actually use the component definition, instead of just it's name.

tolgap avatar Dec 28 '21 14:12 tolgap

@danielmalmros could you try out the solution in that fiddle, and let me know if it worked out for you? Then we can close this issue👍.

tolgap avatar Dec 28 '21 14:12 tolgap