🚨 Notice: This code isn't required anymore as we now have the official @portabletext/svelte
package
I wrote an article on how I got to this code - you should check that out if you want to help build/test/document a better Svelte + PortableText integration :)
Copy and paste all of these files into your Svelte project, import the <PortableText>
component and use it like so:
<script>
import PortableText from '../PortableText/PortableText.svelte'
// importing your components
export let article
</script>
<PortableText
blocks={article.body}
serializers={{
types: {
// block-level components
callout: Callout,
// inline-level components
userInfo: UserInfo,
},
marks: {
absUrl: AbsoluteURL,
// Overwrite default mark renderers
strong: CustomStrong,
},
blockStyles: {
normal: CustomParagraph,
blockquote: Quote,
// Re-using the same component across multiple styles
h1: CustomHeading,
h2: CustomHeading,
h3: CustomHeading,
// Custom user-defined style
textCenter: CentralizedText
},
}}
/>
Example components from above:
<!-- UserInfo (type) -->
<script lang="ts">
import { session } from '$app/stores'
export let node: { bold?: boolean } = { bold: false }
$: userName = $session?.user?.name || 'person'
</script>
{#if node.bold}
<strong>{userName}</strong>
{:else}
{userName}
{/if}
<!-- AbsoluteURL (mark) -->
<script lang="ts">
export let mark: { url?: string; newWindow?: boolean } = {}
$: newWindow = mark.newWindow || false
</script>
{#if mark.url}
<a href={mark.url} target={newWindow ? "_blank" : undefined}><slot /></a>
{:else}
<slot />
{/if}
<!-- CustomHeading (blockStyle) -->
<script>
export let index
export let blocks
export let node
const HEADING_STYLES = ["h1", "h2", "h3", "h4", "h5"]
$: style = node.style
$: precededByHeading = HEADING_STYLES.includes(blocks[index - 1]?.style)
$: anchorId = `heading-${node._key}`
</script>
<!-- If preceded by heading, have a higher margin top -->
<div class="relative {precededByHeading ? "mt-10": "mt-4"}" id={anchorId}>
<a href="#{anchorId}">
<span class="sr-only">Link to this heading</span>
🔗
</a>
{#if style === "h1"}
<h1 class="text-4xl font-black"><slot/></h1>
{:else if style === "h2"}
<h2 class="text-3xl"><slot/></h2>
{:else}
<h3 class="text-xl text-gray-600"><slot/></h3>
{/if}
</div>
The component above is also an example of how you can access blocks surounding the current one for rule-based design 😉