rdunk / sanity-blocks-vue-component Goto Github PK
View Code? Open in Web Editor NEW[DEPRECATED] Vue component for rendering block content from Sanity
License: MIT License
[DEPRECATED] Vue component for rendering block content from Sanity
License: MIT License
Hi, thank you for the amazing plugin. Sanity + Gridsome is the best.
I am trying to modify the default block serializer with the following code:
serializers: {
types: {
block: ({ node }) => {
switch (node.style) {
case 'h2':
return <h2 class="blub-h2">{node.children}</h2>
case 'h3':
return <h3 class="blub-h3 >{node.children}</h3>
case 'h4':
return <h4 class="blub-h4">{node.children}</h4>
default:
return <p>{node.children}</p>
}
},..........
And this does work as long as there is text inside the heading block. However, when I use a custom inline block inside a heading, it keeps returning undefined.
With the default block serializer it processes the custom component inside fine. Any idea how I can make that work?
Thanks!
With version 0.1.0
, I’m finding that styles are only applied to a set of styles that use predefined HTML tags e.g. blockquote
, h1
, h2
, etc.
When I try to create my own styles, these are ignored.
Mark serialization and custom annotations have always worked perfectly so I’m not sure where I am going wrong.
Any insight would be greatly appreciated!
Thanks so much,
Simon
PortableText.vue:
<template>
<BlockContent
v-if="blocks"
:blocks="blocks"
:className="className ? className :'text'"
:renderContainerOnSingleChild="true"
:serializers="serializers"
/>
</template>
<script>
import PortableTextCenter from '~/components/PortableTextCenter.vue';
import PortableTextUnderline from '~/components/PortableTextUnderline.vue';
import PortableTextExternalLink from '~/components/PortableTextExternalLink.vue';
export default {
props: ['blocks', 'className'],
data() {
return {
serializers: {
styles: {
center: PortableTextCenter,
center: (props,children) => {
return h("blockquote", {}, children.slots.default()[0].children);
},
},
marks: {
underline: PortableTextUnderline,
link: PortableTextExternalLink
}
}
}
}
}
</script>
PortableTextCenter.vue:
<template>
<span class="center"><slot /></span>
</template>
<style lang="scss" scoped>
.center {
display: block;
width: 100%;
text-align: center;
}
</style>
If you are using sanity-blocks-vue-component
with Nuxt, this earlier version won't work out of the box with any kind of server-side fetching or asyncData. It will work in development mode, but when you build, it will have lots of casting problems with custom components attempting to be stringified.
Hi, I'm having trouble using a custom component as a serializer. I've tried looking for examples, but am also struggling to find those.
I'm trying to create a serializer for a code
block, and my component looks like this:
<script>
export default {
name: 'Code',
functional: true,
render(h, { props }) {
return h(
'pre',
{
props,
},
props.code
)
},
}
</script>
and I'm importing it into my view:
<template>
<div>
<SanityContent
:blocks="fetchedBlocks" // Assignment removed for clarity
:serializers="serializers"
/>
</div>
</template>
<script>
import Code from '~/components/Code.vue'
export default {
name: 'Page',
data() {
return {
serializers: {
types: {
code: Code,
},
},
}
},
}
</script>
I'm using Nuxt.js with the SanityContent component, and am able to load these changes client-side when it hot reloads, but after refreshing the page I get a Maximum call stack size exceeded
in regards to @nuxt/devalue
getting stuck in a loop.
Is there something I'm missing in regards to creating a component serializer or importing it?
Thanks!
At the moment this package depends on @sanity/client
through a number of other dependencies. Would it be possible to refactor so this package could be a bit lighter?
Hey! :)
Would it make sense to avoid an error when retrieving an empty or undefined value for :blocks?
I have the situation that the speaker.description
could be accidentally empty because sanity doesn't have a default or empty value for empty fields:
<BlockContent
:blocks="speaker.description"
/>
Which would lead in nuxt.js to a server error.
I could of course check like:
<BlockContent
v-if="speaker.description"
:blocks="speaker.description"
/>
But I would like to avoid doing so everywhere and everytime I use the component. Looking forward to your feedback!
Hello there,<em>
marks are not rendering in SanityContent. Is this intentional? In src/sanity-module.ts
there is code that sets it as default.. but on the front end you have to explicitly set em
in the marks object. strong
is working as intended..
Current work around is:
data() {
return {
serializers: {
types: ...,
marks: {
em: 'em',
},
},
}
},
Am I doing something wrong?
I use this serializer to render external link:
<template functional>
<a
v-if="props.href && children[0] && children[0].text"
:href="props.href"
target="_blank"
rel="noopener"
class="underline"
>
<slot />
</a>
</template>
When marking the link bold, the marked text is not rendered:
This component handles this case correctly:
https://github.com/nuxt-community/sanity-module/blob/main/src/components/sanity-content.ts
Edit: Issue created in error.
Using e.g. vue-tsc to get typechecking in Vue templates the example in the readme doesn't actually typecheck. Specifically you'll get an error that the key block
is missing from the types
object nested in serializers
. This seems to be because block
is defined as a required key here: https://github.com/rdunk/sanity-blocks-vue-component/blob/master/src/types.ts#L77. Though the serializers
prop is a Partial<Serializers>
, Partial
doesn't extend to nested objects, so it doesn't stop block
from being required.
As of version 1.0.1
, all marks inside blocks defined as list items (bullet or numbered):
{
"_type": "block",
"children": [...],
"level": 1,
"listItem": "bullet",
"style": "normal"
"markDefs": [
{
"_type": "link",
"href": "https://test.com"
}
]
}
will be ignored and rendered as <span>
rather than <a>
. Custom marks passed in the serializers
prop will also be ignored.
Hi
I am only working development from time to time so this might be a novice question, sorry.
I am trying to use it with Gridsome but I get "TypeError: Cannot read property 'call' of undefined"
My block content is basic rich text from an about page. Paragraphs with links.
I registered the component in the main.js
file for global use:
import DefaultLayout from '~/layouts/Default.vue'
import { SanityBlocks } from 'sanity-blocks-vue-component';
export default function (Vue, { router, head, isClient }) {
Vue.component('Layout', DefaultLayout)
Vue.component('sanity-blocks', SanityBlocks)
}
And my component looks like this:
<template>
<div>
<sanity-blocks :blocks="$static.about._rawColumn_1" />
</div>
</template>
<static-query>
query {
about: sanityAbout (id: "about") {
title
_rawColumn_1
}
}
</static-query>
I am not using any custom serializers as my block content is basic. In case I interpretted the description correctly "This package comes with default serializers for rendering basic block content"
I tried everything I could find online but I end up at the same error.
Anyone who can point me in the right direction? Thank you.
Reading here (https://sanity.nuxtjs.org/helpers/portable-text) I see:
marks: {
// You can also just pass a string for a custom serializer if it's an HTML element
internalLink: 'a'
}
Is anyone able to provide a simple example of opening links in a new window for Nuxt 3 please?
I'm imagining something like:
marks: {
// You can also just pass a string for a custom serializer if it's an HTML element
link: 'a', target:_blank'
}
We have articles that use different serializer components, right now it's loading all the serializer components regardless if it's on the page or not but we want to lazy load it instead so it get loaded only if it's on the page. We're using nuxt for our site.
More a question than an issue. Is there a way of determining the position of a component within the blocks array?
Use case would be to apply a class to a component's outer element if it's the first item in the parent blocks array.
I guess I could use:
this.$parent.blocks.findIndex(block => block._key == $attrs._key)
Is there a better option?
Current all the blocks are wrapped in a div
.
It would be nice to be able to specify the containing element either as a simple string or a component.
For example if you know all the blocks will be list-elements, one should be able to wrap them in anul
-element.
The React package does this through the serializers.container
prop.
I have created 2 custom serializer types, when develop using ssr: false, all images and video iframe shows up correctly, but when using ssr: true, there's and error in nuxt generate,
when in browser, all custom types is showing at first load then disappearing.
this is my nuxtJs code:
<template v-if="$fetchState.pending && !contents.length"> </template>
<template v-else>
<article>
<client-only>
<PortableText
:blocks="contents.slugData.body"
:serializers="serializers"
/>
</client-only>
</article>
<template>
import PortableText from 'sanity-blocks-vue-component'
export default {
name: 'NewsSlug',
components: {
PortableText,
LazyHydrate,
},
async fetch() {
if (this.$route.query.preview === 'true') {
this.contents = await sanityClient.fetch(previewQuery, this.$route.params)
}
this.contents = await sanityClient.fetch(prodQuery, this.$route.params)
},
data() {
return {
heroImageSize: {
width: 672,
height: 488,
},
serializers: {
marks: {
internalLink: InternalLink,
link: ExternalLink,
},
types: {
fullWidthImage: ({ node, children }) => (
<client-only>
<SanityImageCaption image={node} />
</client-only>
),
videoEmbed: ({ node, children }) => (
<client-only>
<VideoEmbed node={node} />
</client-only>
),
},
},
contents: {},
shareList: ['facebook', 'twitter', 'linkedin'],
}
},
}
sanityImageCaption components
<template>
<figure>
<img
:key="image._key"
v-lazy-load
:src="imageUrl"
:alt="image.alt"
class="object-center object-cover h-full w-full"
/>
<figcaption>{{ image.caption }}</figcaption>
</figure>
</template>
<script>
import imageUrlBuilder from '@sanity/image-url'
import sanityClient from '~/sanityClient'
const builder = imageUrlBuilder(sanityClient)
export default {
props: {
image: {
type: Object,
required: true,
},
auto: {
default: 'format',
type: String,
},
fit: {
default: 'max',
type: String,
},
},
computed: {
imageUrl() {
return builder.image(this.image).auto(this.auto).fit(this.fit)
},
},
}
</script>
error message from nuxt generate
WARN Cannot stringify a function renderWithStyleInjection 11:42:31
WARN Cannot stringify a function hook 11:42:31
WARN Cannot stringify a function hook 11:42:31
WARN Cannot stringify a function fullWidthImage 11:42:31
WARN Cannot stringify a function videoEmbed
ERROR /news/contact-us 11:42:35
TypeError: Cannot read property '_key' of undefined
at /Users/Baydiwo/Projects/dark-horse-nuxtjs/node_modules/@sanity/block-content-to-hyperscript/lib/generateKeys.js:7:15
at Array.map (<anonymous>)
at module.exports (/Users/Baydiwo/Projects/dark-horse-nuxtjs/node_modules/@sanity/block-content-to-hyperscript/lib/generateKeys.js:6:17)
at blocksToNodes (/Users/Baydiwo/Projects/dark-horse-nuxtjs/node_modules/@sanity/block-content-to-hyperscript/lib/blocksToNodes.js:27:21)
at blocksToNodes (/Users/Baydiwo/Projects/dark-horse-nuxtjs/node_modules/@sanity/block-content-to-hyperscript/lib/internals.js:14:14)
at blocksToVue (/Users/Baydiwo/Projects/dark-horse-nuxtjs/node_modules/sanity-blocks-vue-component/lib/blocksToVue.js:51:10)
at render (/Users/Baydiwo/Projects/dark-horse-nuxtjs/node_modules/sanity-blocks-vue-component/lib/BlockContent.js:46:12)
at /Users/Baydiwo/Projects/dark-horse-nuxtjs/node_modules/vue/dist/vue.runtime.common.prod.js:6:22507
at Ie (/Users/Baydiwo/Projects/dark-horse-nuxtjs/node_modules/vue/dist/vue.runtime.common.prod.js:6:22689)
at /Users/Baydiwo/Projects/dark-horse-nuxtjs/node_modules/vue/dist/vue.runtime.common.prod.js:6:23819
at Le (/Users/Baydiwo/Projects/dark-horse-nuxtjs/node_modules/vue/dist/vue.runtime.common.prod.js:6:24170)
at e._c (/Users/Baydiwo/Projects/dark-horse-nuxtjs/node_modules/vue/dist/vue.runtime.common.prod.js:6:32380)
at a.render (pages/news/_slug.vue?d307:1:0)
at a.t._render (/Users/Baydiwo/Projects/dark-horse-nuxtjs/node_modules/vue/dist/vue.runtime.common.prod.js:6:35273)
at /Users/Baydiwo/Projects/dark-horse-nuxtjs/node_modules/vue-server-renderer/build.prod.js:1:70637
at runMicrotasks (<anonymous>)
I've tried using client-only but it seems not solving the problem
I have photos in PortableText in Sanity. I have created a component that handles lazy load and creates a placeholder that is displayed while the image loads.
To make the placeholder have the same size as the image, I need metadata from the image. I can do this if I create my own image field. But when the image is inserted into PortableText and via the sanity-blocks-vue component, I do not get sent with parameters so I can get this metadata.
Is it possible to get the sanity-blocks-vue component to retrieve metadata about the image? If so, how do I do it?
Hi,
and thanks for making and sharing this! It works brilliantly in most cases, however I have the following scenario where it does not (a bit hard to explain but I'll try):
I have a page with two languages, and that I use the same component for. I use
<block-content :blocks="blocks" :serializers="serializers" />
to render the content on this page. I use watch $route
to catch language change and to update the block
variable, so the page component does not get recreated, only the data updated. I also do some computed styles based on the content of the blocks. The number of blocks in the two languages is not the same, e.g.: { en: {blocks: Array[6]}, nb: {blocks: Array[8]} }
So what happens when I change language is that all the content in the various blocks get updated correctly, but the computed styling gets mixed up and apparently sticks to the order the element had in the previous language. So if the third block element was pink in the first instance, it will stay pink in also when changing languages, even though it should be red.
My hunch is that this is related to the way the blocks are keyed, and I see you use index
in some cases, to that might be the sinner? I'd be willing to help out with anything if I can, just let me know.
Thanks again!
I'm creating a blog for myself using Sanity and Vue with TypeScript. Since I encountered some bumps on the road regarding this library, I'd like to let you know and maybe there could be something to improve. I can make a pull request later too if it's necessary. So here's the beef.
I'm not sure if the default block image serializer is supposed to work? Because without making a custom image serializer (as you will see below) the default serializer simply detected that there's an image among my blocks array and emitted an empty image tag into the DOM without any props or content.
But since I did make a custom serializer, the types of sanity-blocks-vue-component doesn't seem to like it very much. See the following simple example:
<template>
<div>
<div v-if="post" class="content">
<h1>{{ post.title }}</h1>
<SanityBlocks :blocks="blocks" :serializers="serializers" />
</div>
</div>
</template>
<script setup lang="ts">
import imageUrlBuilder from "@sanity/image-url";
import { SanityBlocks } from "sanity-blocks-vue-component";
import { defineComponent, h, ref } from "vue";
import type { Ref } from "vue";
import { useRoute } from "vue-router";
import sanity from "../client";
const imageBuilder = imageUrlBuilder(sanity);
const loading = ref(false);
const post: Ref<Record<string, any> | null> = ref(null);
const query = `*[slug.current == $slug] {
_id,
title,
slug,
body
}[0]
`;
function imageUrlFor(source: any) {
return imageBuilder.image(source);
}
const serializers = {
types: {
image: defineComponent({
props: ["asset"],
setup(props) {
return () =>
h("img", {
src: imageUrlFor(props.asset).width(480).url(),
});
},
}),
},
};
function fetchData() {
const route = useRoute();
loading.value = true;
sanity.fetch(query, { slug: route.params.slug }).then(
(data) => {
loading.value = false;
post.value = data;
},
(err) => {
console.error(error);
loading.value = false;
}
);
}
fetchData();
</script>
The issue comes from the SanityBlocks component's serializer prop. As I pass it my custom serializer it does work, but it gives the following type error:
Type '{ types: { image: DefineComponent<Readonly<{ asset?: any; }>, () => VNode<RendererNode, RendererElement, { [key: string]: any; }>, unknown, {}, {}, ComponentOptionsMixin, ... 5 more ..., { ...; }>; }; }' is not assignable to type 'Partial<Serializers>'.
Types of property 'types' are incompatible.ts(2322)
types.d.ts(43, 9): 'block' is declared here.
index.d.ts(19, 5): The expected type comes from property 'serializers' which is declared here on type 'IntrinsicAttributes & Partial<{ blocks: Block[]; serializers: Partial<Serializers>; }> & Omit<Readonly<{ blocks: Block[]; serializers: Partial<...>; }> & VNodeProps & AllowedComponentProps & ComponentCustomProps, "blocks" | "serializers">'
I can supress this by setting the const serializer = { ... } as any
, but it's not really a long term solution.
One major feature from other Portable Text tools is the ability to pass a custom serializer for
block
types. The default block serializer works "out of the box" only if the styles correspond to certain HTML elements, which doesn't lend itself to more abstract definitions e.g.headline
instead ofh1
.
This is obviously possible through slots! Though perhaps an example could be added to the readme.
I think that documentation regarding marks
needs to be updated or an example of the correct marks serializer usage should be provided. Right now it's not clear how exactly you should define and access marks
props. It's really hard to find a correct answer anywhere in the web.
Current readme.md
section:
When using custom Vue components as mark serializers, all properties of the block object will be passed via v-bind. To access the data, define the corresponding props in your component. You can use slots (e.g. this.$slots.default) to access the mark text or content.
Hi Rupert,
This is probably a dumb question... sorry! I’ve been really wanting to use this for a while but on a couple of projects now I just get stumped at the classic mismatching nodes warning...
Mismatching childNodes vs. VNodes: NodeList(11) [comment, text, div.header, text, blockcontent, text, div.section-images.margin-bottom-double.clickable, text, div.section-text, text, comment] (11) [VNode, VNode, VNode, VNode, VNode, VNode, VNode, VNode, VNode, VNode, VNode]
vue.common.dev.js?4650:630 [Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content. This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>. Bailing hydration and performing full client-side render.
I’m sure I must be, but don’t think I’m doing anything untoward – just a standard async fetch where I check if my Vuex store is already populated, and if not, dispatch an API call from an action in the store.
async fetch ({ store, params }) {
if (store.state.homepage === '') {
await store.dispatch('HOMEPAGE_CALL');
}
},
In my pages I am just doing the following:
<div
v-if="homepage"
v-for="(section, index) in homepage.sections"
:key="section._id"
:id="makeId(section.title)"
class="section">
[...]
<BlockContent
v-if="section.text"
:blocks="section.text"
className="section-text"
:renderContainerOnSingleChild="true" />
[...]
</div>
Sections is simply returned as a computed property:
homepage() {
return this.$store.state.homepage
}
The issue stops if I comment out the BlockContent component, so I really don’t think I’m doing anything strange here – but would hugely appreciate any thoughts!
Hi there... I can't seem to get this feature to work. has this been removed since v3 released? I am using serializer to apply custom components to custom block types. These are all wrapped in a section tag. But I also want the Generic block text to be wrapped in sections too. How do I do this?
const serializers = {
container: 'section',
types: {
gallery:Gallery,
annotatedVisual:AnnotatedVisual,
testimonial:Testimonial,
videos:Videos,
},
}
Originally posted by @toddpadwick in #3 (comment)
Hi!
By accident and without realizing it, one of my client added a mark (internal link) to an empty space, resulting in a empty and, in my case, invisible <a href="/about"></a>
on the front-end. However it is still focusable when tabbing.
Ideally, I don't want that link to be rendered at all. I was able to solve the problem with functional template components by checking for children[0] && children[0].text
. Example:
<template functional>
<nuxt-link
v-if="props.slug && children[0] && children[0].text"
:to="props.slug + '/'"
class="underline"
>
<slot />
</nuxt-link>
</template>
However, for normal vue components, I only found convoluted ways and not clean and easy ways to check for empty slot. From what I understood "this.$slots.default[0]" is "false" at first and then "true", but it can't be track in a computed property because it's not reactive.
I was thinking, maybe we could add an option to this library for automatically hiding marks with empty text property?
Is it possible to define a 'catch all' serializer?
Use case is to have a 'Fallback' vue component that does not break the build but simply renders a warning explaining the serializer hasn't been set
I realize I might just be braindead right now but I just can't figure out how to pass the block array I get from sanity into
...
setup() {
const blocks = [...];
...
Total vue noob, just figured I'd give it a try coming from react/gatsby so I'm most likely overlooking something obvious here. I've tried looking through all the vue/gridsome + sanity starters and repos I could find but they all use v0.1.0 of sanity-blocks-vue-component which seems to handle things a little differently.
Basically I'd just like to see a complete example or an updated starter if anyone has the time and patience for a vue noob ;)
I'm able to assign a vue component as a serializer for a list just fine, but which props is available to know if it the list type is bullet or numbers?
I'm thinking of having a global configration regarding serializers in Nuxt.js projects.
As we are going to use the block component on so many pages I would like to make the component globally available so we don't have to import it in hundreds of pages.
In addition I would like to define "global" serializers which should be available everywhere and we wouldn't need to add a serializers object in all of these pages.
Do you have an idea or suggestion how I could achieve that?
Hi there,
The issue i'm having is exactly the same as this one from block-content-to-react, so I'll just quote it.
sanity-io/block-content-to-react#23
I am using block-content-to-react for some basic rich text. Somewhere in the pipeline it is turning what should be:
<ol> <li>Alpha<li> <li>Bravo<li> <li>Charlie<li> </ol>
Into
<ol> <li>Alpha<li> </ol> <ol> <li>Bravo<li> </ol> <ol> <li>Charlie<li> </ol>
Which is affecting the numberd list and intended spacing for the elements.
Here is what I have in sanity and what I expect:
...but here in staging it breaks what should be a list into multiple lists:
Any help would be much appreciated, thank you!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.