-
-
Save SebbeJohansson/31b64c5c4f914abfec2660210ec7795a to your computer and use it in GitHub Desktop.
<script setup lang="ts"> | |
import { Richtext } from 'storyblok-js-client'; | |
const props = defineProps({ blok: Object }); | |
const nuxtApp = useNuxtApp(); | |
const textObject = { ...props.blok.text }; | |
const nodes = []; | |
// Proof of concept for custom handling of inline blok nodes. | |
Object.entries(textObject.content).forEach(([key, node]) => { | |
if (node.type === 'blok') { | |
const blok = { | |
content: node.attrs?.body?.[0], | |
}; | |
nodes.push({ | |
key, | |
type: 'blok', | |
content: { | |
blok, | |
}, | |
}); | |
} else { | |
nodes.push({ | |
key, | |
type: 'html', | |
content: nuxtApp.$formatRichText(useStoryblokApi().richTextResolver.render({ | |
type: 'doc', | |
content: [ | |
node, | |
], | |
} as Richtext)), | |
}); | |
} | |
}); | |
</script> | |
<template> | |
<div v-editable="blok" class="text"> | |
<div v-for="node in nodes" :key="node.key"> | |
<component | |
:is="$resolveStoryBlokComponent(node.content.blok)" | |
v-if="node.type === 'blok'" | |
:blok="node.content.blok.content" | |
/> | |
<div v-else v-html="node.content" /> | |
</div> | |
</div> | |
</template> | |
<style> | |
.text img { | |
max-width: 100%; | |
} | |
</style> |
Sorry yea that might have been a bit confusing. formatRichText is a plugin specific for my solution. It mainly converts the normal image tag into an image tag with lazy loading. You can find that plugin here: https://github.com/SebbeJohansson/sebbejohansson-front/blob/main/plugins/media-handler.ts#L57-L64
Here is the Text.vue component as it is in my current version of that project: https://github.com/SebbeJohansson/sebbejohansson-front/blob/main/storyblok/Text.vue
Glad it could help!
It helped a lot. I got another solution from Storyblok support, I find this one more simple.
Hmm interesting solution you got from them. Yes I agree that this solution is more easy to understand.
Do these solutions work with components embedded deeper? For example I have a Feature blok inside a bullet item:
{
"type": "doc",
"content": [
{
"type": "bullet_list",
"content": [
{
"type": "list_item",
"content": [
{
"type": "paragraph",
"content": [
{
"text": "a bullet item",
"type": "text"
}
]
},
{
"type": "blok",
"attrs": {
"id": "44d8f268-e6ad-403a-8287-bb62f7b3da5e",
"body": [
{
"_uid": "i-3858d62b-1db5-4648-95af-94f968b5ec5f",
"name": "Inside the feature",
"component": "Feature",
"_editable": "<!--#storyblok#{\"name\": \"Feature\", \"space\": \"1001749\", \"uid\": \"i-3858d62b-1db5-4648-95af-94f968b5ec5f\", \"id\": \"4874\"}-->"
}
]
}
}
]
},
{
"type": "list_item",
"content": [
{
"type": "paragraph",
"content": [
{
"text": "another bullet",
"type": "text"
}
]
}
]
}
]
},
{
"type": "horizontal_rule"
},
{
"type": "blok",
"attrs": {
"id": "020364a3-b998-49f8-9cd6-5f4008ce4072",
"body": [
{
"_uid": "i-d016076c-7869-429e-acf7-22d7337766c8",
"name": "Internal Feature Block",
"component": "Feature",
"_editable": "<!--#storyblok#{\"name\": \"Feature\", \"space\": \"1001749\", \"uid\": \"i-d016076c-7869-429e-acf7-22d7337766c8\", \"id\": \"4874\"}-->"
}
]
}
}
]
}
It looks to me like the solutions you two have proposed only look at the first level of content.
@jelmerdemaat I havent tested, but i think renderRichText
is going as deep as it can.
@SebbeJohansson I think you mean @joezimjs :)
@SebbeJohansson I think you mean @joezimjs :)
100%! :D God damn autocomplete :P
@SebbeJohansson Yes, renderRichText
does render deeply, but if it comes across a blok deep in there, it won't load this Vue component in order to render it as a component.
@joezimjs aaah i understand.
Should just be a case of infinite loop until it find the end no?
@SebbeJohansson No, it means you can't use renderRichText
. You have to be able to render every kind of node yourself via a Vue component that is also able to traverse the rich text data structure and display each of its child nodes in the same way.
@marvr/storyblok-rich-text-vue-renderer Does this, but it hasn't been touched in a while and I'm not sure if there's a good way to set it up for Nuxt. I used it with iles but I had to register each component that could be used in it manually like this:
import { defineApp } from 'iles'
import { StoryblokVue, apiPlugin } from '@storyblok/vue';
import { plugin as VueRichTextRenderer, RichTextRenderer, defaultResolvers } from '@marvr/storyblok-rich-text-vue-renderer';
import { h, VNode } from 'vue';
import Grid from "@/components/Grid.vue";
import Page from "@/components/Page.vue";
import Teaser from "@/components/Teaser.vue";
import Feature from "@/components/Feature.vue";
export default defineApp({
enhanceApp({app}) {
/*
* Register components for automatic import for use in the rich text renderer and StoryblokComponent
* since they can't just use unplugin-vue-components
*/
// LIST ALL COMPONENTS HERE 👇
const components: Record<string, any> = {
Grid, Page, Teaser, Feature, RichTextRenderer
}
// List of components for use by rich text renderer. (generated later)
const componentRenderers: Record<string, (data: any) => VNode> = {}
// The render function used by the rich text renderer for our components
function componentRenderer ({id, component, _uid, fields} : {id:string, component: string, _uid: string, fields: Record<string, any>}) {
return h(components[component], {blok:{id, component, _uid, ...fields}})
}
// Iterate through all the component we listed so we can register them
Object.entries(components).forEach(([name, component]) => {
// Register component for use inside other components without needing to import
app.component(name, component)
// Register component for use by rich text renderer
componentRenderers[name] = componentRenderer
})
app.use(VueRichTextRenderer({
resolvers: {
...defaultResolvers,
// 👇 list of component renderers we just generated
components: componentRenderers
}
}))
},
})
I'm honestly REALLY surprised there hasn't been an official library/plugin that does this at this point. It's an extremely common use case and no one has an amazing solution.
@joezimjs Have you tried raising a feature request specifically for this?
Thanks!
I had to simplify it little bit to make it work on my case (RichText.vue):
Usage:
<RichText :blok="blok.richTextPropertyName" />