Blocks rich text editor

Just went down a bit of a rabbit hole on this myself after not understanding how to save content into a Rich text “Blocks” field from the API :thinking:

Populated an entry with all the existing blocks and converted resulting output into a typescript type as well as a JSON Schema.

Typescript Type

interface TextNode {
    text: string;
    type: 'text';
    bold?: boolean;
    underline?: boolean;
    italic?: boolean;
    strikethrough?: boolean;
    code?: boolean;
}

interface LinkNode {
    url: string;
    type: 'link';
    children: TextNode[];
}

interface ListItemNode {
    type: 'list-item';
    children: (TextNode | LinkNode)[];
}

interface ImageFormat {
    ext: string;
    url: string;
    hash: string;
    mime: string;
    name: string;
    path: null | string;
    size: number;
    width: number;
    height: number;
}

interface ImageNode {
    type: 'image';
    image: {
        ext: string;
        url: string;
        hash: string;
        mime: string;
        name: string;
        size: number;
        width: number;
        height: number;
        caption: string;
        formats: {
            large?: ImageFormat;
            small?: ImageFormat;
            medium?: ImageFormat;
            thumbnail?: ImageFormat;
        };
        provider: string;
        createdAt: string;
        updatedAt: string;
        previewUrl: null | string;
        alternativeText: string;
        provider_metadata: null | any;
    };
    children: TextNode[];
}

interface BlockNode {
    type: 'heading' | 'paragraph' | 'list' | 'quote';
    level?: number;
    format?: 'unordered' | 'ordered';
    children: (TextNode | LinkNode | ListItemNode | ImageNode)[];
}

type RichTextInput = BlockNode[];

JSON Schema

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "type": {
                "type": "string"
            },
            "level": {
                "type": "integer"
            },
            "format": {
                "type": "string"
            },
            "children": {
                "type": "array",
                "items": {
                    "oneOf": [
                        {
                            "$ref": "#/definitions/textNode"
                        },
                        {
                            "$ref": "#/definitions/linkNode"
                        },
                        {
                            "$ref": "#/definitions/listItemNode"
                        },
                        {
                            "$ref": "#/definitions/imageNode"
                        }
                    ]
                }
            }
        },
        "required": ["type", "children"]
    },
    "definitions": {
        "textNode": {
            "type": "object",
            "properties": {
                "text": {
                    "type": "string"
                },
                "type": {
                    "type": "string"
                },
                "bold": {
                    "type": "boolean"
                },
                "underline": {
                    "type": "boolean"
                },
                "italic": {
                    "type": "boolean"
                },
                "strikethrough": {
                    "type": "boolean"
                },
                "code": {
                    "type": "boolean"
                }
            },
            "required": ["text", "type"]
        },
        "linkNode": {
            "type": "object",
            "properties": {
                "url": {
                    "type": "string"
                },
                "type": {
                    "type": "string"
                },
                "children": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/textNode"
                    }
                }
            },
            "required": ["url", "type", "children"]
        },
        "listItemNode": {
            "type": "object",
            "properties": {
                "type": {
                    "type": "string"
                },
                "children": {
                    "type": "array",
                    "items": {
                        "oneOf": [
                            {
                                "$ref": "#/definitions/textNode"
                            },
                            {
                                "$ref": "#/definitions/linkNode"
                            }
                        ]
                    }
                }
            },
            "required": ["type", "children"]
        },
        "imageNode": {
            "type": "object",
            "properties": {
                "type": {
                    "type": "string"
                },
                "image": {
                    "type": "object",
                    "properties": {
                        // Define image properties here
                    },
                    "required": [
                        // List of required image properties
                    ]
                },
                "children": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/textNode"
                    }
                }
            },
            "required": ["type", "image", "children"]
        }
        // TODO: Define remaining image block params...
    }
}

“kitchen sink” content field in Admin

Resulting API Response

[
  {
    "type": "heading",
    "level": 1,
    "children": [
      {
        "text": "Heading 1",
        "type": "text"
      }
    ]
  },
  {
    "type": "heading",
    "level": 2,
    "children": [
      {
        "text": "Heading 2",
        "type": "text"
      }
    ]
  },
  {
    "type": "heading",
    "level": 3,
    "children": [
      {
        "text": "Heading 3",
        "type": "text"
      }
    ]
  },
  {
    "type": "paragraph",
    "children": [
      {
        "text": "",
        "type": "text"
      }
    ]
  },
  {
    "type": "paragraph",
    "children": [
      {
        "text": "basic paragraph",
        "type": "text"
      }
    ]
  },
  {
    "type": "paragraph",
    "children": [
      {
        "bold": true,
        "text": "this is the bold",
        "type": "text"
      }
    ]
  },
  {
    "type": "paragraph",
    "children": [
      {
        "text": "this is underlined",
        "type": "text",
        "underline": true
      }
    ]
  },
  {
    "type": "paragraph",
    "children": [
      {
        "text": "this is an italic paragraph",
        "type": "text",
        "italic": true
      }
    ]
  },
  {
    "type": "paragraph",
    "children": [
      {
        "text": "this has strikethrough",
        "type": "text",
        "strikethrough": true
      }
    ]
  },
  {
    "type": "paragraph",
    "children": [
      {
        "code": true,
        "text": "some code",
        "type": "text"
      }
    ]
  },
  {
    "type": "paragraph",
    "children": [
      {
        "text": "",
        "type": "text"
      },
      {
        "url": "https://google.com",
        "type": "link",
        "children": [
          {
            "text": "a link",
            "type": "text"
          }
        ]
      },
      {
        "text": "",
        "type": "text"
      }
    ]
  },
  {
    "type": "list",
    "format": "unordered",
    "children": [
      {
        "type": "list-item",
        "children": [
          {
            "text": "bulleted",
            "type": "text"
          }
        ]
      },
      {
        "type": "list-item",
        "children": [
          {
            "text": "list",
            "type": "text"
          }
        ]
      }
    ]
  },
  {
    "type": "list",
    "format": "ordered",
    "children": [
      {
        "type": "list-item",
        "children": [
          {
            "text": "numbered",
            "type": "text"
          }
        ]
      },
      {
        "type": "list-item",
        "children": [
          {
            "text": "list",
            "type": "text"
          }
        ]
      }
    ]
  },
  {
    "type": "quote",
    "children": [
      {
        "text": "Quote content here",
        "type": "text"
      }
    ]
  },
  {
    "type": "image",
    "image": {
      "ext": ".png",
      "url": "http://localhost:1337/uploads/mascot_1_b49785a30c.png",
      "hash": "mascot_1_b49785a30c",
      "mime": "image/png",
      "name": "mascot-1.png",
      "size": 284.58,
      "width": 1024,
      "height": 1024,
      "caption": "imagine caption",
      "formats": {
        "large": {
          "ext": ".png",
          "url": "/uploads/large_mascot_1_b49785a30c.png",
          "hash": "large_mascot_1_b49785a30c",
          "mime": "image/png",
          "name": "large_mascot-1.png",
          "path": null,
          "size": 948.28,
          "width": 1000,
          "height": 1000
        },
        "small": {
          "ext": ".png",
          "url": "/uploads/small_mascot_1_b49785a30c.png",
          "hash": "small_mascot_1_b49785a30c",
          "mime": "image/png",
          "name": "small_mascot-1.png",
          "path": null,
          "size": 267.64,
          "width": 500,
          "height": 500
        },
        "medium": {
          "ext": ".png",
          "url": "/uploads/medium_mascot_1_b49785a30c.png",
          "hash": "medium_mascot_1_b49785a30c",
          "mime": "image/png",
          "name": "medium_mascot-1.png",
          "path": null,
          "size": 570.48,
          "width": 750,
          "height": 750
        },
        "thumbnail": {
          "ext": ".png",
          "url": "/uploads/thumbnail_mascot_1_b49785a30c.png",
          "hash": "thumbnail_mascot_1_b49785a30c",
          "mime": "image/png",
          "name": "thumbnail_mascot-1.png",
          "path": null,
          "size": 31.83,
          "width": 156,
          "height": 156
        }
      },
      "provider": "local",
      "createdAt": "2024-01-18T03:02:24.519Z",
      "updatedAt": "2024-01-18T03:03:02.023Z",
      "previewUrl": null,
      "alternativeText": "alt text",
      "provider_metadata": null
    },
    "children": [
      {
        "text": "",
        "type": "text"
      }
    ]
  }
]

Hope this helps!

2 Likes