Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When using Nuxt, how to use NuxtLink component inside a rich text field, instead of the standard a element? #289

Open
stefanobartoletti opened this issue Jan 26, 2023 · 35 comments
Assignees
Labels
investigation [Issue] The Storyblok team is investigating question [Issue] Further information is requested

Comments

@stefanobartoletti
Copy link

stefanobartoletti commented Jan 26, 2023

Context: website built with Storyblok as CMS and Nuxt 3 for the static frontend.

When editing Rich Text content within Storyblok, the user (not necessarily the developer) can also insert links.

From what I see, these are rendered on the frontend as standard a elements. This is not optimal, since Nuxt provides the NuxtLink component that applies various optimizations, like prefetching, to links where it is used.

Especially for internal links, NuxtLink navigates to the specified route without forcing a full refresh of the page, resulting in a smooth transition between the pages and a SPA feel. While the default a anchor fully refreshes the page, like in plain HTML websites.

Is there a way to render links inside a Rich Text as NuxtLink components, or make them have the same behaviour?

Thanks! Let me know if you need more context/info.

@PTiCA1
Copy link

PTiCA1 commented Mar 3, 2023

I am dealing with the exact same problem. Does anyone have any advice on the best way to solve routing using ?

@SebbeJohansson
Copy link
Contributor

I think the options are to try to get it to work with the schema system that is built in (https://github.com/storyblok/storyblok-js#rendering-rich-text) or do a manual loop of all the nodes and do your own handling (https://gist.github.com/SebbeJohansson/99aa4f1f622552d29927c208c528b489 and https://gist.github.com/SebbeJohansson/31b64c5c4f914abfec2660210ec7795a).

@alvarosabu
Copy link
Contributor

I agree with @SebbeJohansson

Did you try it @stefanobartoletti ?

@alvarosabu alvarosabu self-assigned this May 9, 2023
@alvarosabu alvarosabu added pending-author [Issue] Awaiting further information or action from the issue author question [Issue] Further information is requested labels May 9, 2023
@stefanobartoletti
Copy link
Author

Hey @alvarosabu thanks for checking out, I had a try some time ago but at the moment I'm working on a couple of projects that use another tech stack, so I will check this as soon as possible :)

@madebyfabian
Copy link

So strange that this is not easily archivable, since this is a Nuxt Specific module. Has anyone figured out how to do it with the schema system? https://github.com/storyblok/storyblok-js#rendering-rich-text

@alvarosabu
Copy link
Contributor

Hi @madebyfabian maybe I can throw some light about the richtext.

What the js client of storyblok offer is a way to parse the richtext into semantic html.

NuxtLink is not semantic, is a vue component. For that you would need an specific package that allow you to overwrite tags to vue components.

https://github.com/m4rvr/storyblok-rich-text-renderer is the most solid option for this.

@madebyfabian
Copy link

madebyfabian commented Sep 7, 2023

@alvarosabu Sure, but this is the nuxt/vue package, so I would expect the richtext renderer to support vue components.
I already implemented the workaround with the vue3-runtime-template package to render components, but this does not support nodes and marks like links or images. I need to also render a responsive picture + their source description with a badge for example – I can't use the native renderer, because it places everything inside a p tag, which would be invalid html (div inside p).

The package you suggested is not maintained, outdated, has nearly 0 docs and has various issues. I cannot even get it to run at all in Nuxt 3.7.0 with SSR. (nothing against @m4vrv here, I think it's not on the community to build this).

I would offer my help in creating a proper renderer, but currently don't have any resources available to focus on this.

@alvarosabu
Copy link
Contributor

alvarosabu commented Sep 7, 2023

Yes, @madebyfabian we are aware of the package being no longer maintained. All the rich text renderers of the ecosystem are done by the community:

The reason is that we don't have enough capacity to maintain SDKs + richtext renderers for every framework. We have discussed it recently to see how we could approach it. For now, max we can do is support the community in building one I'm afraid.

I would offer my help in creating a proper renderer, but currently don't have any resources available to focus on this.

Thanks for offering 🙏🏻. If you ever find time for it or any other dev checking this ticket, I personally will support you to get this build.

@alvarosabu
Copy link
Contributor

I know it's not the answer you were looking for, but here is an example of how I used @m4vr package to overwrite the node types with custom resolvers https://github.com/alvarosabu/portfolio-monorepo/blob/main/apps/portfolio/plugins/storyblok.ts

@alvarosabu
Copy link
Contributor

@stefanobartoletti a solution for NuxtLink and NuxtImage would be:

// /plugins/storyblok.ts

import { plugin, defaultResolvers } from '@marvr/storyblok-rich-text-vue-renderer'
import { NodeTypes } from '@marvr/storyblok-rich-text-types'

export default defineNuxtPlugin(nuxtApp => {
  nuxtApp.vueApp.use(
    plugin({
      resolvers: {
        ...defaultResolvers,
        [NodeTypes.IMAGE]: NuxtImage,
        [NodeTypes.LINK]: NuxtLink
        },
    }),
  )
})

@Dawntraoz Dawntraoz added investigation [Issue] The Storyblok team is investigating and removed pending-author [Issue] Awaiting further information or action from the issue author labels Sep 7, 2023
@madebyfabian
Copy link

@alvarosabu Thanks for your answers. I managed to get it up and running with those. I understand that this is time consuming, I just think SDKs should be priorized higher, since this is what customers mostly will interact with. Same for the Content Delivery APIs.

@menschner
Copy link

is there a solution to handle this elemantary problem for nuxt-users without integrating thirt party libaries/ projects (doesn't work in my case anyway)?

@stefanobartoletti
Copy link
Author

@alvarosabu thanks for your help and for the suggestion, I will test this as soon as possible.

@SebbeJohansson
Copy link
Contributor

@alvarosabu I could potentially be interested in building something. At least for vue/nuxt3. What exactly would be needed?

@madebyfabian
Copy link

FYI: I got maintainer access to the suggested library, and added all of the currently missing marks/nodes. Again, don't have much capacity to go into detail but maybe if there is something urgent to fix, you can add an issue/PR.

@SebbeJohansson
Copy link
Contributor

@madebyfabian that is brilliant. Maybe we could open it up a bit more?

@madebyfabian
Copy link

@SebbeJohansson Do you mean functionality-wise? or Docs?

@SebbeJohansson
Copy link
Contributor

@madebyfabian I specifically meant adding more developers and more of a collaboration :D

@madebyfabian
Copy link

@SebbeJohansson Sure! You can ask @m4rvr. Not sure if I just should invite other devs.

@alvarosabu
Copy link
Contributor

Hi @madebyfabian and @SebbeJohansson thanks for jumping in! Would it make things easier if we created a repo ourselves?

@SebbeJohansson
Copy link
Contributor

@alvarosabu I think it would make sense if it was storyblok that had control over it so to speak and allow for easy administration by the community. A solution could be a fork at like storyblok/rich-text-renderer, but i do think that it would require @m4rvr's okay first.

@SebbeJohansson
Copy link
Contributor

@madebyfabian if you have a way to contact him, feel free to ask about this!

@madebyfabian
Copy link

@SebbeJohansson sounds great, yeah. I asked him, will let you know the response.

@madebyfabian
Copy link

madebyfabian commented Sep 13, 2023

@SebbeJohansson
So the todos would be:

So that in the end people can use it as npm i @storyblok/vue-rich-text-vue-renderer

Feel free to suggest a different solution :)

@m4rvr
Copy link

m4rvr commented Sep 13, 2023

@alvarosabu

Hey Alvaro, sorry to say that, but IMO it has a bad taste to just copy a repo from someone. The richtext is an important part of Storyblok and I can't believe there are no capacities and money left for creating an own version, but instead relying on the free outdated solution of the community. It's open source, yes, and I'm fine with it, but it feels weird to just copy the work. Without @madebyfabian's work, it would be still a pain for the Storyblok users. They pay for it and get an outdated, non-working solution. That's not what you expect from a business that gets $47 million Series B funding TBH.

I hope you can do better in the future and consider spending more time and money on creating a great solution. Feel free to take the repo as inspiration, but not just copy it and sell it. Especially not if the work is so important and stated like it's Storyblok's made solution, without me having any benefit from it.

Thanks

@alvarosabu
Copy link
Contributor

alvarosabu commented Sep 13, 2023

Hi @m4rvr

First of all, we are really grateful for all your work on the rich text renderer because it has enabled a lot of users to use it with Vue. Let me clarify that my message never states to copy your work by any means.

Would it make things easier if we created a repo ourselves?

Create a new repo in Storyblok org so we can support in terms of administration of the repo and implement a renderer (not copying yours) in case you don't have the capacity or desire to continue supporting the original repository.

So I clarify again "we are not going to use your code" if we develop a renderer, The original idea we had was supporting the open source repos (you) in case you wanted to continue with it.

I'm really sorry if you felt that way, but to be strictly frank, your code can be easily forked and used by anyone, If you really want to control how your codebase is used, I suggest you add a LICENSE file to your projects.

Storyblok is a SaaS product, not an open-source product, The SDKs exist entirely because the DevRel team of Storyblok wanted to improve the developer experience of the users. Any product could provide you with just a single SDK JS client and that's it, We don't, we offer SDKS for all the major technologies and we sponsor open source which most companies don't.

but not just copy it and sell it

Could you elaborate on how we are selling open-source code? Are you paying for using this Nuxt module? Are you paying for any of the SDKs? Are you aware that we suggest your solution everywhere on our content?

Please reflect on your message because it's at all unfair.

I'm here to support administrate, help, and even code any solution that the community wants to create and the credit will be always for the people who build it.

Thanks

@SebbeJohansson
Copy link
Contributor

I won't respond to specific points, but I just wanted to chime in and say that this is exactly why I wanted @m4rvr to be properly included in the discussion. Because taking over an abandoned open source project is always sensitive.

I do agree to a certain extent that rich text rendering should be something that storyblok solves on their end, but if the community gotta do it then I think it makes sense if we try to do it together to the best of our ability.

Not sure I agree with just using some parts in a different repo. I think it makes more sense to just continue with the actual package repo, no matter where it is.

@m4rvr maybe a better solution would be to allow storyblok to administrate the devrel stuff for the package?

@alvarosabu
Copy link
Contributor

@SebbeJohansson thanks for jumping in. Please, could you both elaborate on how the following question

Would it make things easier if we created a repo ourselves?

Means copy/forking or using some parts in a different repo ? Let's be responsible for our statements please, all that was offered was an official repo on the org for all of you to be able to develop the solution you would like to have with the support of the Storyblok devrel team.

@alvarosabu
Copy link
Contributor

To clarify and avoid further misunderstandings:

  • @m4rvr please let me know if you would like to continue maintaining your solution https://github.com/m4rvr/storyblok-rich-text-renderer with the help of the community and the support of the Storyblok devrel team.
  • @SebbeJohansson @madebyfabian let me know how can we support, from administration, code, release management, etc to contribute to the solution.

Let's move forward constructively, we want the same goal at the end.

@SebbeJohansson
Copy link
Contributor

SebbeJohansson commented Sep 13, 2023

@alvarosabu I don't think reinventing the wheel makes sense if Marvin is on board, no matter if we keep going in his repo or make a new one.

  • If a new repository is made I guess the options are either to redo it from scratch, or do it with importing parts of @m4rvr's module - I don't think this makes sense.
  • if a fork is done from Marvin's code, it would mean that the legacy is still included and all the "glory" is intact, but the upside would be that it's now more in storybloks control to help with admin stuff.
  • If we keep going with the original repository I think it would make sense if @m4rvr allowed storyblok to control the admin stuff completely - this only makes sense if Marvin isn't interested in being active.

I hope that is more clear and I hope we can come to an agreement on what to do! I would love it if @m4rvr could clarify what exactly he is afraid of could potentially happen and what he wants to get out of this. Marvin, if you wanna chat more easily with me, feel free to reach out on discord!

@alvarosabu
Copy link
Contributor

Hi @m4rvr have you considered the proposals stated above?

To clarify and avoid further misunderstandings:

  • @m4rvr please let me know if you would like to continue maintaining your solution https://github.com/m4rvr/storyblok-rich-text-renderer with the help of the community and the support of the Storyblok devrel team.
  • @SebbeJohansson @madebyfabian let me know how can we support, from administration, code, release management, etc to contribute to the solution.

Let's move forward constructively, we want the same goal at the end.

@mirko-kienle
Copy link

mirko-kienle commented Nov 19, 2023

@stefanobartoletti a solution for NuxtLink and NuxtImage would be:

// /plugins/storyblok.ts

 

I don't necessarily want to butt into the ongoing discussion here, but for others who (like me) find this thread from Google in the future, I needed to make the following changes to this snippet to make it all work as expected:

// /plugins/storyblok.ts

import {
  plugin,
  defaultResolvers,
} from "@marvr/storyblok-rich-text-vue-renderer";
import { NodeTypes} from "@marvr/storyblok-rich-text-types";
import { AppLink } from "#components";

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(
    plugin({
      resolvers: {
        ...defaultResolvers,
        [NodeTypes.LINK]: AppLink,
        components: {},
      },
    }),
  );
});
<!-- /components/global/AppLink.vue --> 

<template>
  <NuxtLink
    v-if="linktype === 'story'"
    :to="`${href}${anchor ? `#${anchor}` : ''}`"
  >
    <slot />
  </NuxtLink>
  <a
    v-else
    :href="linktype === 'email' ? `mailto:${href}` : href"
    :target="target"
    :rel="target === '_blank' ? 'noopener noreferrer' : ''"
  >
    <slot />
  </a>
</template>

<script setup lang="ts">
defineProps({ href: String, linktype: String, anchor: String, target: String });
</script>

I also needed to install @marvr/storyblok-rich-text-vue-renderer@next to get the latest version.

Thanks to everyone in this thread for raising the issue and working on it!

@stefanobartoletti
Copy link
Author

Some news about this? Are there still some plans to bring this functionality inside the module, or a user-made custom solution is to be implemented? Thanks :)

@menschner
Copy link

the solution from marvr doesn't work for us (pflege.de).
we set up a custom solution for the issues, that is easy to maintain and doesn't need any further dependencies. we have a component for the richtext rendering.

the component gets the richtext-json and loop trough the root level and rearranged it into 3 categories:

  • components (will be used as component)
  • images (will be used as NuxtImage)
  • text (v-html with listeners for internal links)

.. the template renders the new array with "categorization" and renders each category individually.
after the rendering we setup a watcher for the links, that checks, if it is a internal link and catches the events and handle clicks to internal links like it's done by nuxt with a history-push .. here's the code:

<template>
  <div>
    <template v-for="node in renderedContent">
      <component
        :is="node.blok.component"
        v-if="node.blok"
        :key="`c_${node.index}`"
        :blok="node.blok"
      />
      <NuxtImg
        v-else-if="node.image?.src"
        :key="`i_${node.index}`"
        format="webp"
        placeholder
        :src="node.image.src"
        :alt="node.image.alt"
        :title="node.image.title"
        :data-copyright="node.image.copyright"
        loading="lazy"
      />
      <div v-else :key="`t_${node.index}`" ref="textNodes" class="text" v-html="node.html" />
    </template>
  </div>
</template>

<script setup>
const props = defineProps({
  blok: {
    type: Object,
    required: false,
    default: () => {}
  }
})

const renderedContent = computed(() => {
  const nodes = []
  props.blok.content.forEach((textSectionObject, index) => {
    if (textSectionObject.type === 'blok') {
      textSectionObject.attrs?.body.forEach((blok) => {
        nodes.push({
          index,
          blok
        })
      })
      return
    }

    if (textSectionObject.content && textSectionObject.content[0].type === 'image') {
      nodes.push({
        index,
        image: textSectionObject.content[0].attrs
      })
      return
    }

    const html = useStoryblokApi()
      .richTextResolver.render({
        type: 'doc',
        content: [textSectionObject]
      })
      .replace('//a.storyblok.com', '//assets.pflege.de')
      .replace('href="//', 'href="/')
    nodes.push({
      index,
      html
    })
  })
  return nodes
})

const useRichtextResolverInternalLinkTransformer = (textNodes) => {
  const router = useRouter()
  const onAnchorClick = (e) => {
    e.stopPropagation()
    if (e.currentTarget.hasAttribute('uuid') && e.currentTarget.hasAttribute('href')) {
      e.preventDefault()
      router.push({ path: e.currentTarget.getAttribute('href') })
    }
  }
  const setEventListener = (isOnMount) => {
    textNodes.forEach((textNode) => {
      const listOfAnchorNodes = textNode.getElementsByTagName('a')
      for (const anchorNode of listOfAnchorNodes) {
        if (isOnMount) {
          anchorNode.addEventListener('click', onAnchorClick)
        } else {
          anchorNode.removeEventListener('click', onAnchorClick)
        }
      }
    })
  }
  onMounted(() => {
    setEventListener(true)
  })
  onBeforeUnmount(() => {
    setEventListener(false)
  })
}
const textNodes = ref([])
useRichtextResolverInternalLinkTransformer(textNodes.value)
</script>

i hope this will help others :)

@stefanobartoletti
Copy link
Author

Thanks for your reply and your solution @menschner !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
investigation [Issue] The Storyblok team is investigating question [Issue] Further information is requested
Projects
None yet
Development

No branches or pull requests

9 participants