Skip to content

Commit

Permalink
v3 - customer case rich text block (#788)
Browse files Browse the repository at this point in the history
* richtextblock for customer cases

* prepare for richtext

* feat(richTextBlock): initial preview

* feat(richTextBlock): improve type check for rich text preview

* feat(customerCase): include block sections in customer case query

---------

Co-authored-by: Ane <[email protected]>
  • Loading branch information
mathiazom and anemne authored Oct 17, 2024
1 parent 06680fe commit abcb145
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 19 deletions.
42 changes: 30 additions & 12 deletions studio/lib/interfaces/global.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { PortableTextBlock } from "sanity";

import { isRichText } from "./richText";

export interface Slug {
_type: string;
current: string;
}

export interface DocumentWithSlug {
slug: Slug;
_updatedAt: string;
}

export function isInternationalizedString(
export function isInternationalizedValue(
value: unknown,
): value is InternationalizedString {
): value is InternationalizedValue<unknown> {
return (
Array.isArray(value) &&
value.every(
Expand All @@ -19,15 +18,34 @@ export function isInternationalizedString(
item !== null &&
"_key" in item &&
typeof item._key === "string" &&
"value" in item &&
typeof item.value === "string",
"value" in item,
)
);
}

export type InternationalizedString = InternationalizedStringRecord[];
export function isInternationalizedString(
value: unknown,
): value is InternationalizedString {
return (
isInternationalizedValue(value) &&
value.every((item) => typeof item.value === "string")
);
}

export interface InternationalizedStringRecord {
export function isInternationalizedRichText(
value: unknown,
): value is InternationalizedValue<PortableTextBlock[]> {
return (
isInternationalizedValue(value) &&
value.every((record) => isRichText(record.value))
);
}

export type InternationalizedValue<T> = InternationalizedValueRecord<T>[];

export interface InternationalizedValueRecord<T> {
_key: string;
value: string;
value: T;
}

export type InternationalizedString = InternationalizedValueRecord<string>[];
37 changes: 37 additions & 0 deletions studio/lib/interfaces/richText.ts
Original file line number Diff line number Diff line change
@@ -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"
);
}
12 changes: 6 additions & 6 deletions studio/utils/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { InternationalizedString } from "studio/lib/interfaces/global";
import { InternationalizedValue } from "studio/lib/interfaces/global";

export function firstTranslation(
translatedString: InternationalizedString,
): string | null {
if (translatedString.length === 0) {
export function firstTranslation<T>(
internationalizedValue: InternationalizedValue<T>,
): T | null {
if (internationalizedValue.length === 0) {
return null;
}
return translatedString[0].value;
return internationalizedValue[0].value;
}
20 changes: 20 additions & 0 deletions studio/utils/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {
PortableTextBlock,
isPortableTextSpan,
isPortableTextTextBlock,
} from "sanity";

// Convert rich text content to plaintext preview string
// (inspired by https://www.sanity.io/docs/previewing-block-content)
export function richTextPreview(
richText: PortableTextBlock[],
): string | undefined {
const block = richText.find((block) => block._type === "block");
if (!isPortableTextTextBlock(block)) {
return undefined;
}
return block.children
.filter((child) => isPortableTextSpan(child))
.map((span) => span.text)
.join("");
}
7 changes: 7 additions & 0 deletions studioShared/lib/queries/customerCases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ export const CUSTOMER_CASE_QUERY = groq`
"sector": ${translatedFieldFragment("sector")},
"delivery": ${translatedFieldFragment("delivery")},
consultants
},
"sections": sections[] {
_type,
_type == "richTextBlock" => {
...,
"richText": ${translatedFieldFragment("richText")},
}
}
}
`;
Expand Down
9 changes: 8 additions & 1 deletion studioShared/schemas/documents/customerCase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { richTextID, titleID } from "studio/schemas/fields/text";
import { titleSlug } from "studio/schemas/schemaTypes/slug";
import { firstTranslation } from "studio/utils/i18n";
import { customerCaseProjectInfo } from "studioShared/schemas/fields/customerCaseProjectInfo";
import richTextBlock from "studioShared/schemas/objects/richTextBlock";

export const customerCaseID = "customerCase";

Expand All @@ -30,7 +31,13 @@ const customerCase = defineType({
"Short paragraph displayed at the top of the customer case page",
}),
customerCaseProjectInfo,
//TODO: Block section
defineField({
name: "sections",
title: "Sections",
description: "Add sections here",
type: "array",
of: [richTextBlock],
}),
defineField({
name: richTextID,
title: "Body",
Expand Down
39 changes: 39 additions & 0 deletions studioShared/schemas/objects/richTextBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { defineField } from "sanity";

import { isInternationalizedRichText } from "studio/lib/interfaces/global";
import { firstTranslation } from "studio/utils/i18n";
import { richTextPreview } from "studio/utils/preview";

const richTextBlock = defineField({
name: "richTextBlock",
title: "Rich Text Block",
type: "object",
fields: [
{
name: "richText",
title: "Rich Text",
type: "internationalizedArrayRichText",
},
],
preview: {
select: {
richText: "richText",
},
prepare({ richText }) {
if (!isInternationalizedRichText(richText)) {
throw new TypeError(
`Expected 'richText' to be InternationalizedRichText, was ${typeof richText}`,
);
}
const translatedRichText = firstTranslation(richText);
return {
title:
translatedRichText !== null
? richTextPreview(translatedRichText)
: undefined,
};
},
},
});

export default richTextBlock;

0 comments on commit abcb145

Please sign in to comment.