1
0
Fork 0
hugo/docs/content/en/functions/transform/PortableText.md

5.7 KiB

title description categories keywords params
transform.PortableText Converts Portable Text to Markdown.
functions_and_methods
returnType signatures
string
transform.PortableText MAP

{{< new-in "0.145.0" />}}

Portable Text is a JSON structure that represent rich text content in the Sanity CMS. In Hugo, this function is typically used in a Content Adapter that creates pages from Sanity data.

Types supported

  • block and span
  • image. Note that the image handling is currently very simple; we link to the asset.url using asset.altText as the image alt text and asset.title as the title. For more fine grained control you may want to process the images in a image render hook.
  • code (see the code-input plugin). Code will be rendered as fenced code blocks with any file name provided passed on as a markdown attribute.

[!note] Since the Portable Text gets converted to Markdown before it gets passed to Hugo, rendering of links, headings, images and code blocks can be controlled with Render Hooks.

Example

Content Adapter

{{ $projectID := "mysanityprojectid" }}
{{ $useCached := true }}
{{ $api := "api" }}
{{ if $useCached }}
  {{/* See https://www.sanity.io/docs/api-cdn */}}
  {{ $api = "apicdn" }}
{{ end }}
{{ $url := printf "https://%s.%s.sanity.io/v2021-06-07/data/query/production"  $projectID $api }}

{{/* prettier-ignore-start */ -}}
{{ $q :=  `*[_type == 'post']{
  title, publishedAt, summary, slug, body[]{
    ...,
    _type == "image" => {
      ...,
      asset->{
        _id,
        path,
        url,
        altText,
        title,
        description,
        metadata {
          dimensions {
            aspectRatio,
            width,
            height
          }
        }
      }
    }
  },
  }`
}}
{{/* prettier-ignore-end */ -}}
{{ $body := dict "query" $q | jsonify }}
{{ $opts := dict "method" "post" "body" $body }}
{{ $r := resources.GetRemote $url $opts }}
{{ $m := $r | transform.Unmarshal }}
{{ $result := $m.result }}
{{ range $result }}
  {{ if not .slug }}
    {{ continue }}
  {{ end }}
  {{ $markdown := transform.PortableText .body }}
  {{ $content := dict
    "mediaType" "text/markdown"
    "value" $markdown
  }}
  {{ $params := dict
    "portabletext" (.body | jsonify (dict "indent" " "))
  }}
  {{ $page := dict
    "content" $content
    "kind" "page"
    "path" .slug.current
    "title" .title
    "date" (.publishedAt | time )
    "summary" .summary
    "params" $params
  }}
  {{ $.AddPage $page }}
{{ end }}

Sanity setup

Below outlines a suitable Sanity studio setup for the above example.

import {defineConfig} from 'sanity'
import {structureTool} from 'sanity/structure'
import {visionTool} from '@sanity/vision'
import {schemaTypes} from './schemaTypes'
import {media} from 'sanity-plugin-media'
import {codeInput} from '@sanity/code-input'

export default defineConfig({
  name: 'default',
  title: 'my-sanity-project',

  projectId: 'mysanityprojectid',
  dataset: 'production',

  plugins: [structureTool(), visionTool(), media(),codeInput()],

  schema: {
    types: schemaTypes,
  },
})

Type/schema definition:

import {defineField, defineType} from 'sanity'

export const postType = defineType({
  name: 'post',
  title: 'Post',
  type: 'document',
  fields: [
    defineField({
      name: 'title',
      type: 'string',
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'summary',
      type: 'string',
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'slug',
      type: 'slug',
      options: {source: 'title'},
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'publishedAt',
      type: 'datetime',
      initialValue: () => new Date().toISOString(),
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'body',
      type: 'array',
      of: [
        {
          type: 'block',
        },
        {
          type: 'image'
        },
        {
          type: 'code',
          options: {
            language: 'css',
            languageAlternatives: [
              {title: 'HTML', value: 'html'},
              {title: 'CSS', value: 'css'},
            ],
            withFilename: true,
          },
        },
      ],
    }),
  ],
})

Note that the above requires some additional plugins to be installed:

npm i sanity-plugin-media @sanity/code-input
import {postType} from './postType'

export const schemaTypes = [postType]

Server setup

Unfortunately, Sanity's API does not support RFC 7234 and their output changes even if the data has not. A recommended setup is therefore to use their cached apicdn endpoint (see above) and then set up a reasonable polling and file cache strategy in your Hugo configuration, e.g:

{{< code-toggle file=hugo >}} [HTTPCache] HTTPCache.polls disable = false low = '30s' high = '3m' [HTTPCache.polls.for] includes = ['https://..sanity.io/**']

[caches.getresource] dir = ':cacheDir/:project' maxAge = "5m" {{< /code-toggle >}}

The polling above will be used when running the server/watch mode and rebuild when you push new content in Sanity.

See Caching in resources.GetRemote for more fine grained control.