diff --git a/studio/lib/interfaces/global.ts b/studio/lib/interfaces/global.ts index 4550feba3..dbd856aaa 100644 --- a/studio/lib/interfaces/global.ts +++ b/studio/lib/interfaces/global.ts @@ -1,3 +1,7 @@ +import { PortableTextBlock } from "sanity"; + +import { isRichText } from "./richText"; + export interface Slug { _type: string; current: string; @@ -28,6 +32,15 @@ export function isInternationalizedString( ); } +export function isInternationalizedRichText( + value: unknown, +): value is InternationalizedValue { + return ( + isInternationalizedValue(value) && + value.every((record) => isRichText(record.value)) + ); +} + export type InternationalizedValue = InternationalizedValueRecord[]; export interface InternationalizedValueRecord { diff --git a/studio/lib/interfaces/richText.ts b/studio/lib/interfaces/richText.ts new file mode 100644 index 000000000..29ed1ad8f --- /dev/null +++ b/studio/lib/interfaces/richText.ts @@ -0,0 +1,37 @@ +import { + PortableTextBlock, + PortableTextObject, + isPortableTextTextBlock, +} from "sanity"; + +export function isRichText(value: unknown): value is PortableTextBlock[] { + return ( + Array.isArray(value) && value.every((item) => isPortableTextBlock(item)) + ); +} + +export function isPortableTextBlock( + value: unknown, +): value is PortableTextBlock { + return isPortableTextTextBlock(value) || isPortableTextObject(value); +} + +export function isPortableTextObject( + value: unknown, +): value is PortableTextObject { + return isSanityKeyTypeObject(value); +} + +function isSanityKeyTypeObject(value: unknown): value is { + _key: string; + _type: string; +} { + return ( + typeof value === "object" && + value !== null && + "_key" in value && + typeof value._key === "string" && + "_type" in value && + typeof value._type === "string" + ); +} diff --git a/studio/utils/preview.ts b/studio/utils/preview.ts index 423aa9f6f..ae701d939 100644 --- a/studio/utils/preview.ts +++ b/studio/utils/preview.ts @@ -1,27 +1,20 @@ -import { PortableTextBlock } from "sanity"; +import { + PortableTextBlock, + isPortableTextSpan, + isPortableTextTextBlock, +} from "sanity"; // Convert rich text content to plaintext preview string -// (see https://www.sanity.io/docs/previewing-block-content) +// (inspired by https://www.sanity.io/docs/previewing-block-content) export function richTextPreview( richText: PortableTextBlock[], ): string | undefined { - console.log(richText); const block = richText.find((block) => block._type === "block"); - if ( - block === undefined || - !("children" in block) || - !Array.isArray(block.children) - ) { + if (!isPortableTextTextBlock(block)) { return undefined; } return block.children - .filter( - (child: unknown) => - typeof child === "object" && - child !== null && - "_type" in child && - child._type === "span", - ) + .filter((child) => isPortableTextSpan(child)) .map((span) => span.text) .join(""); } diff --git a/studioShared/schemas/objects/richTextBlock.ts b/studioShared/schemas/objects/richTextBlock.ts index f1e2c1986..74ce0164a 100644 --- a/studioShared/schemas/objects/richTextBlock.ts +++ b/studioShared/schemas/objects/richTextBlock.ts @@ -1,6 +1,6 @@ import { defineField } from "sanity"; -import { isInternationalizedValue } from "studio/lib/interfaces/global"; +import { isInternationalizedRichText } from "studio/lib/interfaces/global"; import { firstTranslation } from "studio/utils/i18n"; import { richTextPreview } from "studio/utils/preview"; @@ -20,15 +20,17 @@ const richTextBlock = defineField({ richText: "richText", }, prepare({ richText }) { - // TODO: better type guard for rich text - if (!isInternationalizedValue(richText)) { + if (!isInternationalizedRichText(richText)) { throw new TypeError( - `Expected 'richText' to be InternationalizedValue, was ${typeof richText}`, + `Expected 'richText' to be InternationalizedRichText, was ${typeof richText}`, ); } const translatedRichText = firstTranslation(richText); return { - title: richTextPreview(translatedRichText), + title: + translatedRichText !== null + ? richTextPreview(translatedRichText) + : undefined, }; }, },