paramander / contentful-rich-text-vue-renderer

Render Contentful Rich Text field using Vue
https://www.npmjs.com/package/contentful-rich-text-vue-renderer
MIT License
38 stars 12 forks source link

Render custom vue 3 components #36

Open danielmalmros opened 3 years ago

danielmalmros commented 3 years ago

Hi,

Thanks for maintaining this project :)

I'm trying to use "contentful-rich-text-vue-renderer@3.0.0-rc.3" 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>
tolgap commented 3 years ago

@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 👀)

danielmalmros commented 3 years ago

@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.

hjujah commented 2 years ago

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>
tolgap commented 2 years ago

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 commented 2 years ago

@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👍.