From 4083afa9e9f1bfa2155772fb35c85afac684bae5 Mon Sep 17 00:00:00 2001 From: Dipankar Maikap Date: Fri, 24 Jul 2026 18:37:47 +0530 Subject: [PATCH 01/15] feat(openapi): add richtext node/mark types to spec and aliases Expands richtext-field-value.yaml from a loose type: object into a fully discriminated schema with $defs for all 17 node types and 12 mark types, mirroring richtext-attrs.ts. - overlay.openapi.yaml: add RichTextNode and RichTextMark as top-level schema refs so they are emitted as named types - aliases.ts: register RichtextDoc, RichTextNode, RichTextMark aliases - index.ts: fix copyWrapperTemplates to skip writing the empty types/_sources.ts when no wrapper templates are requested - richtext-field-value.yaml: BlokNode body items reference block-content.yaml instead of an inline duplicate schema Fixes DX-487 --- .../specs/overlay.openapi.yaml | 4 + .../field-types/richtext-field-value.yaml | 574 +++++++++++++++++- tools/openapi-codegen/src/aliases.ts | 3 + tools/openapi-codegen/src/index.ts | 6 +- 4 files changed, 579 insertions(+), 8 deletions(-) diff --git a/tools/openapi-codegen/specs/overlay.openapi.yaml b/tools/openapi-codegen/specs/overlay.openapi.yaml index 8a6620423..21558f003 100644 --- a/tools/openapi-codegen/specs/overlay.openapi.yaml +++ b/tools/openapi-codegen/specs/overlay.openapi.yaml @@ -23,6 +23,10 @@ components: $ref: ./shared/stories/field-types/plugin-field-value.yaml RichtextFieldValue: $ref: ./shared/stories/field-types/richtext-field-value.yaml + RichTextNode: + $ref: ./shared/stories/field-types/richtext-field-value.yaml#/$defs/RichTextNode + RichTextMark: + $ref: ./shared/stories/field-types/richtext-field-value.yaml#/$defs/RichTextMark TableFieldValue: $ref: ./shared/stories/field-types/table-field-value.yaml ComponentSchemaField: diff --git a/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml b/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml index ff1718615..af1faa982 100644 --- a/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml +++ b/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml @@ -1,18 +1,580 @@ # See: https://www.storyblok.com/docs/api/management/components/possible-field-types # See: https://www.storyblok.com/docs/packages/storyblok-richtext type: object -description: Richtext field type - structured rich text document (ProseMirror format) +description: Richtext field type - structured rich text document (ProseMirror/Tiptap format) required: - type + - content properties: type: type: string enum: [doc] - description: Root node type for richtext documents + description: Root node type — always "doc" content: type: array - description: Array of richtext nodes (paragraphs, headings, lists, bloks, etc.) + description: Top-level richtext nodes items: - type: object - additionalProperties: true - description: Richtext node structure varies by type (paragraph, heading, bullet_list, ordered_list, blockquote, code_block, horizontal_rule, hard_break, image, blok, etc.) + $ref: '#/$defs/RichTextNode' + +$defs: + # ----------------------------------------------------------------------- + # Marks + # ----------------------------------------------------------------------- + + RichTextMark: + description: Inline formatting mark applied to a text node + oneOf: + - $ref: '#/$defs/LinkMark' + - $ref: '#/$defs/BoldMark' + - $ref: '#/$defs/ItalicMark' + - $ref: '#/$defs/StrikeMark' + - $ref: '#/$defs/UnderlineMark' + - $ref: '#/$defs/CodeMark' + - $ref: '#/$defs/SuperscriptMark' + - $ref: '#/$defs/SubscriptMark' + - $ref: '#/$defs/HighlightMark' + - $ref: '#/$defs/TextStyleMark' + - $ref: '#/$defs/AnchorMark' + - $ref: '#/$defs/StyledMark' + + LinkMark: + type: object + required: [type, attrs] + properties: + type: + type: string + enum: [link] + attrs: + type: object + description: Link attributes + required: [href, uuid, anchor, target, linktype] + properties: + href: + type: [string, 'null'] + description: Link URL + uuid: + type: [string, 'null'] + description: UUID of the linked story (for internal links) + anchor: + type: [string, 'null'] + description: Anchor/fragment identifier + target: + description: Link target attribute + oneOf: + - type: string + enum: [_self, _blank, _parent, _top] + - type: 'null' + linktype: + description: Type of link + oneOf: + - type: string + enum: [story, url, email, asset] + - type: 'null' + custom: + type: object + additionalProperties: true + description: Custom link attributes + + BoldMark: + type: object + required: [type] + properties: + type: + type: string + enum: [bold] + + ItalicMark: + type: object + required: [type] + properties: + type: + type: string + enum: [italic] + + StrikeMark: + type: object + required: [type] + properties: + type: + type: string + enum: [strike] + + UnderlineMark: + type: object + required: [type] + properties: + type: + type: string + enum: [underline] + + CodeMark: + type: object + required: [type] + properties: + type: + type: string + enum: [code] + + SuperscriptMark: + type: object + required: [type] + properties: + type: + type: string + enum: [superscript] + + SubscriptMark: + type: object + required: [type] + properties: + type: + type: string + enum: [subscript] + + HighlightMark: + type: object + required: [type, attrs] + properties: + type: + type: string + enum: [highlight] + attrs: + type: object + required: [color] + properties: + color: + type: string + description: Highlight color (CSS color value) + + TextStyleMark: + type: object + required: [type, attrs] + properties: + type: + type: string + enum: [textStyle] + attrs: + type: object + properties: + color: + type: [string, 'null'] + id: + type: [string, 'null'] + class: + type: [string, 'null'] + + AnchorMark: + type: object + required: [type, attrs] + properties: + type: + type: string + enum: [anchor] + attrs: + type: object + required: [id] + properties: + id: + type: string + description: Anchor identifier + + StyledMark: + type: object + required: [type, attrs] + properties: + type: + type: string + enum: [styled] + attrs: + type: object + required: [class] + properties: + class: + type: [string, 'null'] + description: CSS class name + + # ----------------------------------------------------------------------- + # Nodes + # ----------------------------------------------------------------------- + + RichTextNode: + description: A richtext document node + oneOf: + - $ref: '#/$defs/ParagraphNode' + - $ref: '#/$defs/TextNode' + - $ref: '#/$defs/HeadingNode' + - $ref: '#/$defs/BlockquoteNode' + - $ref: '#/$defs/BulletListNode' + - $ref: '#/$defs/OrderedListNode' + - $ref: '#/$defs/ListItemNode' + - $ref: '#/$defs/CodeBlockNode' + - $ref: '#/$defs/HardBreakNode' + - $ref: '#/$defs/HorizontalRuleNode' + - $ref: '#/$defs/ImageNode' + - $ref: '#/$defs/EmojiNode' + - $ref: '#/$defs/TableNode' + - $ref: '#/$defs/TableRowNode' + - $ref: '#/$defs/TableCellNode' + - $ref: '#/$defs/TableHeaderNode' + - $ref: '#/$defs/BlokNode' + + ParagraphNode: + type: object + required: [type, attrs] + properties: + type: + type: string + enum: [paragraph] + attrs: + type: object + required: [textAlign] + properties: + textAlign: + description: Text alignment + oneOf: + - type: string + enum: [left, center, right, justify] + - type: 'null' + content: + type: array + items: + $ref: '#/$defs/RichTextNode' + marks: + type: array + items: + $ref: '#/$defs/RichTextMark' + + TextNode: + type: object + required: [type, text] + properties: + type: + type: string + enum: [text] + text: + type: string + description: The text content + marks: + type: array + items: + $ref: '#/$defs/RichTextMark' + + HeadingNode: + type: object + required: [type, attrs] + properties: + type: + type: string + enum: [heading] + attrs: + type: object + required: [textAlign] + properties: + level: + description: Heading level (h1–h6) + oneOf: + - type: integer + enum: [1, 2, 3, 4, 5, 6] + - type: 'null' + textAlign: + description: Text alignment + oneOf: + - type: string + enum: [left, center, right, justify] + - type: 'null' + content: + type: array + items: + $ref: '#/$defs/RichTextNode' + marks: + type: array + items: + $ref: '#/$defs/RichTextMark' + + BlockquoteNode: + type: object + required: [type] + properties: + type: + type: string + enum: [blockquote] + content: + type: array + items: + $ref: '#/$defs/RichTextNode' + marks: + type: array + items: + $ref: '#/$defs/RichTextMark' + + BulletListNode: + type: object + required: [type] + properties: + type: + type: string + enum: [bullet_list] + content: + type: array + items: + $ref: '#/$defs/RichTextNode' + marks: + type: array + items: + $ref: '#/$defs/RichTextMark' + + OrderedListNode: + type: object + required: [type, attrs] + properties: + type: + type: string + enum: [ordered_list] + attrs: + type: object + properties: + order: + type: integer + description: Starting number for the ordered list + content: + type: array + items: + $ref: '#/$defs/RichTextNode' + marks: + type: array + items: + $ref: '#/$defs/RichTextMark' + + ListItemNode: + type: object + required: [type] + properties: + type: + type: string + enum: [list_item] + content: + type: array + items: + $ref: '#/$defs/RichTextNode' + marks: + type: array + items: + $ref: '#/$defs/RichTextMark' + + CodeBlockNode: + type: object + required: [type, attrs] + properties: + type: + type: string + enum: [code_block] + attrs: + type: object + required: [class] + properties: + class: + type: [string, 'null'] + description: Language class (e.g. "language-typescript") + content: + type: array + items: + $ref: '#/$defs/RichTextNode' + marks: + type: array + items: + $ref: '#/$defs/RichTextMark' + + HardBreakNode: + type: object + required: [type] + properties: + type: + type: string + enum: [hard_break] + marks: + type: array + items: + $ref: '#/$defs/RichTextMark' + + HorizontalRuleNode: + type: object + required: [type] + properties: + type: + type: string + enum: [horizontal_rule] + + ImageNode: + type: object + required: [type, attrs] + properties: + type: + type: string + enum: [image] + attrs: + type: object + required: [src, id, alt, title, source, copyright, meta_data] + properties: + id: + type: [integer, 'null'] + description: Storyblok asset ID + src: + type: string + description: Image URL + alt: + type: [string, 'null'] + description: Alternative text + title: + type: [string, 'null'] + source: + type: [string, 'null'] + copyright: + type: [string, 'null'] + meta_data: + description: Asset metadata + oneOf: + - type: object + required: [alt, title, source, copyright] + properties: + alt: + type: [string, 'null'] + title: + type: [string, 'null'] + source: + type: [string, 'null'] + copyright: + type: [string, 'null'] + - type: 'null' + marks: + type: array + items: + $ref: '#/$defs/RichTextMark' + + EmojiNode: + type: object + required: [type, attrs] + properties: + type: + type: string + enum: [emoji] + attrs: + type: object + required: [name, emoji, fallbackImage] + properties: + name: + type: string + description: Emoji name/slug + emoji: + type: string + description: Emoji character + fallbackImage: + type: string + description: URL to a fallback image for unsupported environments + content: + type: array + items: + $ref: '#/$defs/RichTextNode' + marks: + type: array + items: + $ref: '#/$defs/RichTextMark' + + TableNode: + type: object + required: [type] + properties: + type: + type: string + enum: [table] + content: + type: array + items: + $ref: '#/$defs/RichTextNode' + + TableRowNode: + type: object + required: [type] + properties: + type: + type: string + enum: [tableRow] + content: + type: array + items: + $ref: '#/$defs/RichTextNode' + + TableCellNode: + type: object + required: [type, attrs] + properties: + type: + type: string + enum: [tableCell] + attrs: + type: object + properties: + colspan: + type: integer + rowspan: + type: integer + colwidth: + description: Column widths in pixels + oneOf: + - type: array + items: + type: integer + - type: 'null' + backgroundColor: + type: [string, 'null'] + content: + type: array + items: + $ref: '#/$defs/RichTextNode' + + TableHeaderNode: + type: object + required: [type, attrs] + properties: + type: + type: string + enum: [tableHeader] + attrs: + type: object + properties: + colspan: + type: integer + rowspan: + type: integer + colwidth: + description: Column widths in pixels + oneOf: + - type: array + items: + type: integer + - type: 'null' + content: + type: array + items: + $ref: '#/$defs/RichTextNode' + + BlokNode: + type: object + required: [type, attrs] + properties: + type: + type: string + enum: [blok] + attrs: + type: object + required: [id, body] + properties: + id: + type: [string, 'null'] + description: Blok instance ID + body: + description: Array of embedded component instances + oneOf: + - type: array + items: + $ref: ../block-content.yaml + - type: 'null' diff --git a/tools/openapi-codegen/src/aliases.ts b/tools/openapi-codegen/src/aliases.ts index 7709791c2..5f7df5e20 100644 --- a/tools/openapi-codegen/src/aliases.ts +++ b/tools/openapi-codegen/src/aliases.ts @@ -80,6 +80,9 @@ export const ALIASES = [ { source: 'MultilinkFieldValue', spec: 'overlay', emitAs: 'MultilinkFieldValue' }, { source: 'PluginFieldValue', spec: 'overlay', emitAs: 'PluginFieldValue' }, { source: 'RichtextFieldValue', spec: 'overlay', emitAs: 'RichtextFieldValue' }, + { source: 'RichtextFieldValue', spec: 'overlay', emitAs: 'RichtextDoc' }, + { source: 'RichTextNode', spec: 'overlay', emitAs: 'RichTextNode' }, + { source: 'RichTextMark', spec: 'overlay', emitAs: 'RichTextMark' }, { source: 'TableFieldValue', spec: 'overlay', emitAs: 'TableFieldValue' }, { source: 'ComponentSchemaField', spec: 'overlay', emitAs: 'Field' }, ] as const satisfies readonly AliasSpec[]; diff --git a/tools/openapi-codegen/src/index.ts b/tools/openapi-codegen/src/index.ts index 865582e7c..0773f48f3 100644 --- a/tools/openapi-codegen/src/index.ts +++ b/tools/openapi-codegen/src/index.ts @@ -292,6 +292,9 @@ function copyWrapperTemplates( leafLocation: ReadonlyMap, sdk: 'mapi' | 'capi' | false | undefined, ): void { + if (wrappers.size === 0) { + return; + } const typesDir = resolve(outDir, 'types'); mkdirSync(typesDir, { recursive: true }); @@ -312,9 +315,8 @@ function copyWrapperTemplates( // instead of being redefined per template. if (wrappers.size > 0) { writeFileSync(resolve(typesDir, '_utils.ts'), buildUtilsFile(), 'utf8'); + writeFileSync(resolve(typesDir, '_sources.ts'), buildSourcesFile(publicPerSpec, leafPerSpec, leafLocation, sdk), 'utf8'); } - - writeFileSync(resolve(typesDir, '_sources.ts'), buildSourcesFile(publicPerSpec, leafPerSpec, leafLocation, sdk), 'utf8'); } function buildUtilsFile(): string { From 2e0b21b9bdee9863478331343c79beeee6c3dc86 Mon Sep 17 00:00:00 2001 From: Dipankar Maikap Date: Fri, 24 Jul 2026 18:42:22 +0530 Subject: [PATCH 02/15] feat(openapi): add RichText node/mark types to spec and aliases Expands richtext-field-value.yaml from a loose type: object into a fully discriminated schema with $defs for all 17 node types and 12 mark types, mirroring richtext-attrs.ts. - overlay.openapi.yaml: add RichTextNode and RichTextMark as top-level schema refs so they are emitted as named types - aliases.ts: register RichTextDoc, RichTextNode, RichTextMark aliases - index.ts: fix copyWrapperTemplates to skip writing the empty types/_sources.ts when no wrapper templates are requested - richtext-field-value.yaml: BlockNode body items reference block-content.yaml instead of an inline duplicate schema Naming: use RichText (not Richtext) for all type names and Block (not Blok) for the embedded component node type. Wire values (enum: richtext, enum: blok) are unchanged as they are API contract values. Fixes DX-487 --- .../specs/mapi/components/field-types/richtext-field.yaml | 2 +- tools/openapi-codegen/specs/overlay.openapi.yaml | 2 +- .../shared/stories/field-types/richtext-field-value.yaml | 8 ++++---- tools/openapi-codegen/src/aliases.ts | 4 ++-- tools/openapi-codegen/src/known-types.ts | 2 +- tools/openapi-codegen/src/templates.ts | 4 ++-- tools/openapi-codegen/templates/field.ts | 6 +++--- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tools/openapi-codegen/specs/mapi/components/field-types/richtext-field.yaml b/tools/openapi-codegen/specs/mapi/components/field-types/richtext-field.yaml index 54cc39a72..a5e83a18e 100644 --- a/tools/openapi-codegen/specs/mapi/components/field-types/richtext-field.yaml +++ b/tools/openapi-codegen/specs/mapi/components/field-types/richtext-field.yaml @@ -2,7 +2,7 @@ allOf: - $ref: ./base-field.yaml - $ref: ./value-field.yaml - type: object - description: Richtext field configuration + description: RichText field configuration required: - type properties: diff --git a/tools/openapi-codegen/specs/overlay.openapi.yaml b/tools/openapi-codegen/specs/overlay.openapi.yaml index 21558f003..c5ac8cdf6 100644 --- a/tools/openapi-codegen/specs/overlay.openapi.yaml +++ b/tools/openapi-codegen/specs/overlay.openapi.yaml @@ -21,7 +21,7 @@ components: $ref: ./shared/stories/field-types/multilink-field-value.yaml PluginFieldValue: $ref: ./shared/stories/field-types/plugin-field-value.yaml - RichtextFieldValue: + RichTextFieldValue: $ref: ./shared/stories/field-types/richtext-field-value.yaml RichTextNode: $ref: ./shared/stories/field-types/richtext-field-value.yaml#/$defs/RichTextNode diff --git a/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml b/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml index af1faa982..c18bdc47f 100644 --- a/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml +++ b/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml @@ -1,7 +1,7 @@ # See: https://www.storyblok.com/docs/api/management/components/possible-field-types # See: https://www.storyblok.com/docs/packages/storyblok-richtext type: object -description: Richtext field type - structured rich text document (ProseMirror/Tiptap format) +description: RichText field type - structured rich text document (ProseMirror/Tiptap format) required: - type - content @@ -216,7 +216,7 @@ $defs: - $ref: '#/$defs/TableRowNode' - $ref: '#/$defs/TableCellNode' - $ref: '#/$defs/TableHeaderNode' - - $ref: '#/$defs/BlokNode' + - $ref: '#/$defs/BlockNode' ParagraphNode: type: object @@ -557,7 +557,7 @@ $defs: items: $ref: '#/$defs/RichTextNode' - BlokNode: + BlockNode: type: object required: [type, attrs] properties: @@ -570,7 +570,7 @@ $defs: properties: id: type: [string, 'null'] - description: Blok instance ID + description: Block instance ID body: description: Array of embedded component instances oneOf: diff --git a/tools/openapi-codegen/src/aliases.ts b/tools/openapi-codegen/src/aliases.ts index 5f7df5e20..ecc69e228 100644 --- a/tools/openapi-codegen/src/aliases.ts +++ b/tools/openapi-codegen/src/aliases.ts @@ -79,8 +79,8 @@ export const ALIASES = [ { source: 'AssetFieldValue', spec: 'overlay', emitAs: 'AssetFieldValue' }, { source: 'MultilinkFieldValue', spec: 'overlay', emitAs: 'MultilinkFieldValue' }, { source: 'PluginFieldValue', spec: 'overlay', emitAs: 'PluginFieldValue' }, - { source: 'RichtextFieldValue', spec: 'overlay', emitAs: 'RichtextFieldValue' }, - { source: 'RichtextFieldValue', spec: 'overlay', emitAs: 'RichtextDoc' }, + { source: 'RichTextFieldValue', spec: 'overlay', emitAs: 'RichTextFieldValue' }, + { source: 'RichTextFieldValue', spec: 'overlay', emitAs: 'RichTextDoc' }, { source: 'RichTextNode', spec: 'overlay', emitAs: 'RichTextNode' }, { source: 'RichTextMark', spec: 'overlay', emitAs: 'RichTextMark' }, { source: 'TableFieldValue', spec: 'overlay', emitAs: 'TableFieldValue' }, diff --git a/tools/openapi-codegen/src/known-types.ts b/tools/openapi-codegen/src/known-types.ts index f087dfac7..877cac02a 100644 --- a/tools/openapi-codegen/src/known-types.ts +++ b/tools/openapi-codegen/src/known-types.ts @@ -352,7 +352,7 @@ export const UPSTREAM_SCHEMAS = { 'ComponentSchemaField', 'MultilinkFieldValue', 'PluginFieldValue', - 'RichtextFieldValue', + 'RichTextFieldValue', 'TableFieldValue', ] as const, } as const; diff --git a/tools/openapi-codegen/src/templates.ts b/tools/openapi-codegen/src/templates.ts index f273610e6..d48945fa4 100644 --- a/tools/openapi-codegen/src/templates.ts +++ b/tools/openapi-codegen/src/templates.ts @@ -31,9 +31,9 @@ export const TEMPLATES = { sourceLeaves: ['Component', 'Field'], }, 'field': { - provides: ['BlockContent', 'BlockContentInput', 'BlocksFieldValue', 'FieldType', 'FieldValue', 'FieldValueInput', 'Field', 'AssetFieldValue', 'MultilinkFieldValue', 'PluginFieldValue', 'RichtextFieldValue', 'TableFieldValue'], + provides: ['BlockContent', 'BlockContentInput', 'BlocksFieldValue', 'FieldType', 'FieldValue', 'FieldValueInput', 'Field', 'AssetFieldValue', 'MultilinkFieldValue', 'PluginFieldValue', 'RichTextFieldValue', 'TableFieldValue'], templateDeps: ['block'], - sourceLeaves: ['AssetFieldValue', 'BlockContentBase', 'BlockContentInputBase', 'Field', 'MultilinkFieldValue', 'PluginFieldValue', 'RichtextFieldValue', 'TableFieldValue'], + sourceLeaves: ['AssetFieldValue', 'BlockContentBase', 'BlockContentInputBase', 'Field', 'MultilinkFieldValue', 'PluginFieldValue', 'RichTextFieldValue', 'TableFieldValue'], }, 'story': { provides: ['Story'], diff --git a/tools/openapi-codegen/templates/field.ts b/tools/openapi-codegen/templates/field.ts index bc72e0b09..c90998d98 100644 --- a/tools/openapi-codegen/templates/field.ts +++ b/tools/openapi-codegen/templates/field.ts @@ -5,14 +5,14 @@ import type { Field, MultilinkFieldValue, PluginFieldValue, - RichtextFieldValue, + RichTextFieldValue, TableFieldValue, } from './_sources'; import type { Prettify } from './_utils'; import type { Block, BlockFields } from './block'; export type { Field }; -export type { AssetFieldValue, MultilinkFieldValue, PluginFieldValue, RichtextFieldValue, TableFieldValue }; +export type { AssetFieldValue, MultilinkFieldValue, PluginFieldValue, RichTextFieldValue, TableFieldValue }; /** * Registry of all blocks in the space, used to resolve nested `bloks` fields. @@ -81,7 +81,7 @@ export type FieldType = Field['type']; interface FieldTypeValueMap { text: string; textarea: string; - richtext: RichtextFieldValue; + richtext: RichTextFieldValue; markdown: string; number: number; datetime: string; From 848fae8c4a57248d7ffa26abc09a4bcab7f10ca5 Mon Sep 17 00:00:00 2001 From: Dipankar Maikap Date: Fri, 24 Jul 2026 18:46:44 +0530 Subject: [PATCH 03/15] chore(openapi): use 'rich text' in prose descriptions Type names keep RichText (PascalCase). In description strings, use 'Rich text' at sentence start and 'rich text' mid-sentence. Wire values (enum: richtext, toggle-richtext) are unchanged. Fixes DX-487 --- .../specs/mapi/components/field-types/richtext-field.yaml | 4 ++-- .../shared/stories/field-types/richtext-field-value.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/openapi-codegen/specs/mapi/components/field-types/richtext-field.yaml b/tools/openapi-codegen/specs/mapi/components/field-types/richtext-field.yaml index a5e83a18e..265b7e1b7 100644 --- a/tools/openapi-codegen/specs/mapi/components/field-types/richtext-field.yaml +++ b/tools/openapi-codegen/specs/mapi/components/field-types/richtext-field.yaml @@ -2,7 +2,7 @@ allOf: - $ref: ./base-field.yaml - $ref: ./value-field.yaml - type: object - description: RichText field configuration + description: Rich text field configuration required: - type properties: @@ -73,7 +73,7 @@ allOf: - align-justify style_options: type: array - description: Custom CSS styles for the richtext field + description: Custom CSS styles for the rich text field items: type: object properties: diff --git a/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml b/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml index c18bdc47f..47039491f 100644 --- a/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml +++ b/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml @@ -1,7 +1,7 @@ # See: https://www.storyblok.com/docs/api/management/components/possible-field-types # See: https://www.storyblok.com/docs/packages/storyblok-richtext type: object -description: RichText field type - structured rich text document (ProseMirror/Tiptap format) +description: Rich text field type - structured rich text document (ProseMirror/Tiptap format) required: - type - content @@ -12,7 +12,7 @@ properties: description: Root node type — always "doc" content: type: array - description: Top-level richtext nodes + description: Top-level rich text nodes items: $ref: '#/$defs/RichTextNode' @@ -198,7 +198,7 @@ $defs: # ----------------------------------------------------------------------- RichTextNode: - description: A richtext document node + description: A rich text document node oneOf: - $ref: '#/$defs/ParagraphNode' - $ref: '#/$defs/TextNode' From edad0de56df88d920202e9b4aa5ae1c145ba033a Mon Sep 17 00:00:00 2001 From: Dipankar Maikap Date: Fri, 24 Jul 2026 19:16:30 +0530 Subject: [PATCH 04/15] fix(openapi): tighten richtext node schemas and add RichTextMark/RichTextNode to known types --- .../field-types/richtext-field-value.yaml | 18 +----------------- tools/openapi-codegen/src/known-types.ts | 2 ++ 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml b/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml index 47039491f..3224ec59a 100644 --- a/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml +++ b/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml @@ -268,7 +268,7 @@ $defs: enum: [heading] attrs: type: object - required: [textAlign] + required: [textAlign, level] properties: level: description: Heading level (h1–h6) @@ -391,10 +391,6 @@ $defs: type: type: string enum: [hard_break] - marks: - type: array - items: - $ref: '#/$defs/RichTextMark' HorizontalRuleNode: type: object @@ -445,10 +441,6 @@ $defs: copyright: type: [string, 'null'] - type: 'null' - marks: - type: array - items: - $ref: '#/$defs/RichTextMark' EmojiNode: type: object @@ -470,14 +462,6 @@ $defs: fallbackImage: type: string description: URL to a fallback image for unsupported environments - content: - type: array - items: - $ref: '#/$defs/RichTextNode' - marks: - type: array - items: - $ref: '#/$defs/RichTextMark' TableNode: type: object diff --git a/tools/openapi-codegen/src/known-types.ts b/tools/openapi-codegen/src/known-types.ts index 877cac02a..62592827c 100644 --- a/tools/openapi-codegen/src/known-types.ts +++ b/tools/openapi-codegen/src/known-types.ts @@ -353,6 +353,8 @@ export const UPSTREAM_SCHEMAS = { 'MultilinkFieldValue', 'PluginFieldValue', 'RichTextFieldValue', + 'RichTextMark', + 'RichTextNode', 'TableFieldValue', ] as const, } as const; From e18dd3e7b4a382b6a2f124a28c00f03345327cb9 Mon Sep 17 00:00:00 2001 From: Dipankar Maikap Date: Mon, 27 Jul 2026 13:21:09 +0530 Subject: [PATCH 05/15] chore(openapi): add deprecated RichtextFieldValue alias for backward compat RichtextFieldValue is renamed to RichTextFieldValue in this PR. To avoid breaking consumer packages that already import the old name, export a deprecated type alias from the field template so each package can migrate at its own pace. - templates/field.ts: export `RichtextFieldValue = RichTextFieldValue` with @deprecated JSDoc - src/templates.ts: register RichtextFieldValue in the field template's provides list Fixes DX-487 --- tools/openapi-codegen/src/templates.ts | 2 +- tools/openapi-codegen/templates/field.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/openapi-codegen/src/templates.ts b/tools/openapi-codegen/src/templates.ts index d48945fa4..6da89da5c 100644 --- a/tools/openapi-codegen/src/templates.ts +++ b/tools/openapi-codegen/src/templates.ts @@ -31,7 +31,7 @@ export const TEMPLATES = { sourceLeaves: ['Component', 'Field'], }, 'field': { - provides: ['BlockContent', 'BlockContentInput', 'BlocksFieldValue', 'FieldType', 'FieldValue', 'FieldValueInput', 'Field', 'AssetFieldValue', 'MultilinkFieldValue', 'PluginFieldValue', 'RichTextFieldValue', 'TableFieldValue'], + provides: ['BlockContent', 'BlockContentInput', 'BlocksFieldValue', 'FieldType', 'FieldValue', 'FieldValueInput', 'Field', 'AssetFieldValue', 'MultilinkFieldValue', 'PluginFieldValue', 'RichTextFieldValue', 'RichtextFieldValue', 'TableFieldValue'], templateDeps: ['block'], sourceLeaves: ['AssetFieldValue', 'BlockContentBase', 'BlockContentInputBase', 'Field', 'MultilinkFieldValue', 'PluginFieldValue', 'RichTextFieldValue', 'TableFieldValue'], }, diff --git a/tools/openapi-codegen/templates/field.ts b/tools/openapi-codegen/templates/field.ts index c90998d98..31b8977b9 100644 --- a/tools/openapi-codegen/templates/field.ts +++ b/tools/openapi-codegen/templates/field.ts @@ -14,6 +14,11 @@ import type { Block, BlockFields } from './block'; export type { Field }; export type { AssetFieldValue, MultilinkFieldValue, PluginFieldValue, RichTextFieldValue, TableFieldValue }; +/** + * @deprecated Use {@link RichTextFieldValue} instead. Will be removed in a future major version. + */ +export type RichtextFieldValue = RichTextFieldValue; + /** * Registry of all blocks in the space, used to resolve nested `bloks` fields. * A `Block` union resolves nested content against the registry; `NoBlocks` From 33eeb23091be4e23823922d9cf24647ffbc2525b Mon Sep 17 00:00:00 2001 From: Dipankar Maikap Date: Tue, 28 Jul 2026 12:46:14 +0530 Subject: [PATCH 06/15] fix(openapi): rename richtext-field-value to rich-text-field-value for correct casing Fixes RichtextFieldValue* type prefix to RichTextFieldValue* (PascalCase). Fixes DX-487 --- tools/openapi-codegen/specs/overlay.openapi.yaml | 6 +++--- .../specs/shared/stories/block-content-input.yaml | 2 +- .../openapi-codegen/specs/shared/stories/block-content.yaml | 2 +- ...richtext-field-value.yaml => rich-text-field-value.yaml} | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename tools/openapi-codegen/specs/shared/stories/field-types/{richtext-field-value.yaml => rich-text-field-value.yaml} (100%) diff --git a/tools/openapi-codegen/specs/overlay.openapi.yaml b/tools/openapi-codegen/specs/overlay.openapi.yaml index c5ac8cdf6..319569ffc 100644 --- a/tools/openapi-codegen/specs/overlay.openapi.yaml +++ b/tools/openapi-codegen/specs/overlay.openapi.yaml @@ -22,11 +22,11 @@ components: PluginFieldValue: $ref: ./shared/stories/field-types/plugin-field-value.yaml RichTextFieldValue: - $ref: ./shared/stories/field-types/richtext-field-value.yaml + $ref: ./shared/stories/field-types/rich-text-field-value.yaml RichTextNode: - $ref: ./shared/stories/field-types/richtext-field-value.yaml#/$defs/RichTextNode + $ref: ./shared/stories/field-types/rich-text-field-value.yaml#/$defs/RichTextNode RichTextMark: - $ref: ./shared/stories/field-types/richtext-field-value.yaml#/$defs/RichTextMark + $ref: ./shared/stories/field-types/rich-text-field-value.yaml#/$defs/RichTextMark TableFieldValue: $ref: ./shared/stories/field-types/table-field-value.yaml ComponentSchemaField: diff --git a/tools/openapi-codegen/specs/shared/stories/block-content-input.yaml b/tools/openapi-codegen/specs/shared/stories/block-content-input.yaml index ca1f8c358..7591db810 100644 --- a/tools/openapi-codegen/specs/shared/stories/block-content-input.yaml +++ b/tools/openapi-codegen/specs/shared/stories/block-content-input.yaml @@ -45,5 +45,5 @@ additionalProperties: - $ref: ./field-types/asset-field-value.yaml - $ref: ./field-types/multilink-field-value.yaml - $ref: ./field-types/table-field-value.yaml - - $ref: ./field-types/richtext-field-value.yaml + - $ref: ./field-types/rich-text-field-value.yaml - $ref: ./field-types/plugin-field-value.yaml diff --git a/tools/openapi-codegen/specs/shared/stories/block-content.yaml b/tools/openapi-codegen/specs/shared/stories/block-content.yaml index 98b1a47b7..69c6b7273 100644 --- a/tools/openapi-codegen/specs/shared/stories/block-content.yaml +++ b/tools/openapi-codegen/specs/shared/stories/block-content.yaml @@ -53,5 +53,5 @@ additionalProperties: - $ref: ./field-types/asset-field-value.yaml - $ref: ./field-types/multilink-field-value.yaml - $ref: ./field-types/table-field-value.yaml - - $ref: ./field-types/richtext-field-value.yaml + - $ref: ./field-types/rich-text-field-value.yaml - $ref: ./field-types/plugin-field-value.yaml diff --git a/tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml b/tools/openapi-codegen/specs/shared/stories/field-types/rich-text-field-value.yaml similarity index 100% rename from tools/openapi-codegen/specs/shared/stories/field-types/richtext-field-value.yaml rename to tools/openapi-codegen/specs/shared/stories/field-types/rich-text-field-value.yaml From 8f2782aeb2c5d7f427a0de51d507392f68eda9fe Mon Sep 17 00:00:00 2001 From: Dipankar Maikap Date: Tue, 28 Jul 2026 13:08:18 +0530 Subject: [PATCH 07/15] fix(openapi): make ParagraphNode attrs optional An empty paragraph emitted by the editor is just {"type": "paragraph"} with no attrs or content. Requiring attrs caused schema validation to fail for that case. Fixes DX-487 --- .../specs/shared/stories/field-types/rich-text-field-value.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/openapi-codegen/specs/shared/stories/field-types/rich-text-field-value.yaml b/tools/openapi-codegen/specs/shared/stories/field-types/rich-text-field-value.yaml index 3224ec59a..e87dee31d 100644 --- a/tools/openapi-codegen/specs/shared/stories/field-types/rich-text-field-value.yaml +++ b/tools/openapi-codegen/specs/shared/stories/field-types/rich-text-field-value.yaml @@ -220,7 +220,7 @@ $defs: ParagraphNode: type: object - required: [type, attrs] + required: [type] properties: type: type: string From 3a7eb8031e3e7b4d77ff44e907d48edb9f0b2ddc Mon Sep 17 00:00:00 2001 From: Dipankar Maikap Date: Wed, 29 Jul 2026 12:19:44 +0530 Subject: [PATCH 08/15] fix(openapi): improve richtext node schemas - Add optional dir (ltr | rtl | null) to paragraph, heading, blockquote, list_item, and code_block nodes - Make meta_data properties optional and allow custom additionalProperties (string | null) - Make HighlightMark color nullable - Make ImageNode src nullable Fixes DX-487 --- .../field-types/rich-text-field-value.yaml | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/tools/openapi-codegen/specs/shared/stories/field-types/rich-text-field-value.yaml b/tools/openapi-codegen/specs/shared/stories/field-types/rich-text-field-value.yaml index e87dee31d..215f0f5de 100644 --- a/tools/openapi-codegen/specs/shared/stories/field-types/rich-text-field-value.yaml +++ b/tools/openapi-codegen/specs/shared/stories/field-types/rich-text-field-value.yaml @@ -143,7 +143,7 @@ $defs: required: [color] properties: color: - type: string + type: [string, 'null'] description: Highlight color (CSS color value) TextStyleMark: @@ -235,6 +235,12 @@ $defs: - type: string enum: [left, center, right, justify] - type: 'null' + dir: + description: Text direction + oneOf: + - type: string + enum: [ltr, rtl] + - type: 'null' content: type: array items: @@ -282,6 +288,12 @@ $defs: - type: string enum: [left, center, right, justify] - type: 'null' + dir: + description: Text direction + oneOf: + - type: string + enum: [ltr, rtl] + - type: 'null' content: type: array items: @@ -298,6 +310,15 @@ $defs: type: type: string enum: [blockquote] + attrs: + type: object + properties: + dir: + description: Text direction + oneOf: + - type: string + enum: [ltr, rtl] + - type: 'null' content: type: array items: @@ -352,6 +373,15 @@ $defs: type: type: string enum: [list_item] + attrs: + type: object + properties: + dir: + description: Text direction + oneOf: + - type: string + enum: [ltr, rtl] + - type: 'null' content: type: array items: @@ -375,6 +405,12 @@ $defs: class: type: [string, 'null'] description: Language class (e.g. "language-typescript") + dir: + description: Text direction + oneOf: + - type: string + enum: [ltr, rtl] + - type: 'null' content: type: array items: @@ -415,7 +451,7 @@ $defs: type: [integer, 'null'] description: Storyblok asset ID src: - type: string + type: [string, 'null'] description: Image URL alt: type: [string, 'null'] @@ -430,7 +466,6 @@ $defs: description: Asset metadata oneOf: - type: object - required: [alt, title, source, copyright] properties: alt: type: [string, 'null'] @@ -440,6 +475,8 @@ $defs: type: [string, 'null'] copyright: type: [string, 'null'] + additionalProperties: + type: [string, 'null'] - type: 'null' EmojiNode: From 3ddbebd296fd75b71f1f971cd215dd438fbe91d6 Mon Sep 17 00:00:00 2001 From: Dipankar Maikap Date: Wed, 29 Jul 2026 17:38:45 +0530 Subject: [PATCH 09/15] feat(schema): regenerate for RichTextFieldValue rename and add backward-compat aliases - Regenerate src/generated with RichTextFieldValue (new canonical name) - scripts/generate.ts: update include list entry to RichTextFieldValue - helpers/define-field.ts: re-export RichTextFieldValue alongside deprecated RichtextFieldValue - src/index.ts: export RichTextFieldValue (new) alongside kept RichtextFieldValue - validators/internal-schemas.ts: import zRichTextFieldValue; export deprecated zRichtextFieldValue alias - validators/validate-story.ts: use zRichTextFieldValue internally - validators/validate-story.test.ts: add _uid to richtext embedded blok fixtures (required by tightened zBlockContentRoot) Fixes DX-487 --- packages/schema/scripts/generate.ts | 2 +- .../src/generated/overlay/_internal.gen.ts | 343 +++++++++++++- .../schema/src/generated/overlay/zod.gen.ts | 420 +++++++++++++++++- .../schema/src/generated/types/_sources.ts | 2 +- packages/schema/src/generated/types/field.ts | 11 +- packages/schema/src/helpers/define-field.ts | 2 + packages/schema/src/index.ts | 1 + .../schema/src/validators/internal-schemas.ts | 7 +- .../src/validators/validate-story.test.ts | 4 +- .../schema/src/validators/validate-story.ts | 4 +- 10 files changed, 766 insertions(+), 30 deletions(-) diff --git a/packages/schema/scripts/generate.ts b/packages/schema/scripts/generate.ts index fdcbbd5d7..25bfb4b63 100644 --- a/packages/schema/scripts/generate.ts +++ b/packages/schema/scripts/generate.ts @@ -33,7 +33,7 @@ await generate({ 'AssetFieldValue', 'MultilinkFieldValue', 'PluginFieldValue', - 'RichtextFieldValue', + 'RichTextFieldValue', 'TableFieldValue', 'Datasource', ], diff --git a/packages/schema/src/generated/overlay/_internal.gen.ts b/packages/schema/src/generated/overlay/_internal.gen.ts index b2d0cbc68..865be724b 100644 --- a/packages/schema/src/generated/overlay/_internal.gen.ts +++ b/packages/schema/src/generated/overlay/_internal.gen.ts @@ -15,7 +15,7 @@ export type MultilinkFieldValue = MultilinkFieldValueRoot; export type PluginFieldValue = PluginFieldValueRoot; -export type RichtextFieldValue = RichtextFieldValueRoot; +export type RichTextFieldValue = RichTextFieldValueRoot; export type TableFieldValue = TableFieldValueRoot; @@ -95,7 +95,7 @@ export type RichtextFieldRoot = BaseFieldRoot & ValueFieldRoot & { */ toolbar?: Array<'ai-complete' | 'ai-shorten' | 'ai-extend' | 'ai-rephrase' | 'ai-summarize' | 'ai-simplify' | 'ai-tone' | 'ai-emoji' | 'ai-translate' | 'ai-tldr' | 'ai-spelling' | 'blok' | 'ltr' | 'rtl' | 'export' | 'import' | 'bold' | 'list' | 'inlinecode' | 'code' | 'color' | 'copy' | 'cut' | 'emoji' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'highlight' | 'unset' | 'hrule' | 'image' | 'italic' | 'link' | 'anchor' | 'olist' | 'paragraph' | 'paste-action' | 'paste' | 'quote' | 'redo' | 'strike' | 'subscript' | 'superscript' | 'underline' | 'undo' | 'align-left' | 'align-center' | 'align-right' | 'align-justify'>; /** - * Custom CSS styles for the richtext field + * Custom CSS styles for the rich text field */ style_options?: Array<{ _uid?: string; @@ -678,7 +678,7 @@ export type BlockContentRoot = { * Storyblok editor markup string for inline editing (present in draft/preview mode) */ _editable?: string; - [key: string]: string | number | boolean | Array | AssetFieldValueRoot | MultilinkFieldValueRoot | TableFieldValueRoot | RichtextFieldValueRoot | PluginFieldValueRoot | string | undefined; + [key: string]: string | number | boolean | Array | AssetFieldValueRoot | MultilinkFieldValueRoot | TableFieldValueRoot | RichTextFieldValueRoot | PluginFieldValueRoot | string | undefined; }; /** @@ -697,7 +697,7 @@ export type BlockContentInputRoot = { * Storyblok editor markup string for inline editing (present in draft/preview mode) */ _editable?: string; - [key: string]: null | string | number | boolean | Array | AssetFieldValueRoot | MultilinkFieldValueRoot | TableFieldValueRoot | RichtextFieldValueRoot | PluginFieldValueRoot | string | undefined; + [key: string]: null | string | number | boolean | Array | AssetFieldValueRoot | MultilinkFieldValueRoot | TableFieldValueRoot | RichTextFieldValueRoot | PluginFieldValueRoot | string | undefined; }; /** @@ -750,19 +750,17 @@ export type PluginFieldValueRoot = { }; /** - * Richtext field type - structured rich text document (ProseMirror format) + * Rich text field type - structured rich text document (ProseMirror/Tiptap format) */ -export type RichtextFieldValueRoot = { +export type RichTextFieldValueRoot = { /** - * Root node type for richtext documents + * Root node type — always "doc" */ type: 'doc'; /** - * Array of richtext nodes (paragraphs, headings, lists, bloks, etc.) + * Top-level rich text nodes */ - content?: Array<{ - [key: string]: unknown; - }>; + content: Array; }; /** @@ -879,3 +877,326 @@ export type ValueFieldRoot = { */ force_merge?: boolean; }; + +/** + * A rich text document node + */ +export type RichTextFieldValueRichTextNode = RichTextFieldValueParagraphNode | RichTextFieldValueTextNode | RichTextFieldValueHeadingNode | RichTextFieldValueBlockquoteNode | RichTextFieldValueBulletListNode | RichTextFieldValueOrderedListNode | RichTextFieldValueListItemNode | RichTextFieldValueCodeBlockNode | RichTextFieldValueHardBreakNode | RichTextFieldValueHorizontalRuleNode | RichTextFieldValueImageNode | RichTextFieldValueEmojiNode | RichTextFieldValueTableNode | RichTextFieldValueTableRowNode | RichTextFieldValueTableCellNode | RichTextFieldValueTableHeaderNode | RichTextFieldValueBlockNode; + +export type RichTextFieldValueParagraphNode = { + type: 'paragraph'; + attrs?: { + /** + * Text alignment + */ + textAlign: 'left' | 'center' | 'right' | 'justify' | null; + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueTextNode = { + type: 'text'; + /** + * The text content + */ + text: string; + marks?: Array; +}; + +export type RichTextFieldValueHeadingNode = { + type: 'heading'; + attrs: { + /** + * Heading level (h1–h6) + */ + level: 1 | 2 | 3 | 4 | 5 | 6 | null; + /** + * Text alignment + */ + textAlign: 'left' | 'center' | 'right' | 'justify' | null; + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueBlockquoteNode = { + type: 'blockquote'; + attrs?: { + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueBulletListNode = { + type: 'bullet_list'; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueOrderedListNode = { + type: 'ordered_list'; + attrs: { + /** + * Starting number for the ordered list + */ + order?: number; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueListItemNode = { + type: 'list_item'; + attrs?: { + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueCodeBlockNode = { + type: 'code_block'; + attrs: { + /** + * Language class (e.g. "language-typescript") + */ + class: string | null; + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueHardBreakNode = { + type: 'hard_break'; +}; + +export type RichTextFieldValueHorizontalRuleNode = { + type: 'horizontal_rule'; +}; + +export type RichTextFieldValueImageNode = { + type: 'image'; + attrs: { + /** + * Storyblok asset ID + */ + id: number | null; + /** + * Image URL + */ + src: string | null; + /** + * Alternative text + */ + alt: string | null; + title: string | null; + source: string | null; + copyright: string | null; + /** + * Asset metadata + */ + meta_data: { + alt?: string | null; + title?: string | null; + source?: string | null; + copyright?: string | null; + [key: string]: string | null | string | null | string | null | string | null | string | null | undefined; + } | null; + }; +}; + +export type RichTextFieldValueEmojiNode = { + type: 'emoji'; + attrs: { + /** + * Emoji name/slug + */ + name: string; + /** + * Emoji character + */ + emoji: string; + /** + * URL to a fallback image for unsupported environments + */ + fallbackImage: string; + }; +}; + +export type RichTextFieldValueTableNode = { + type: 'table'; + content?: Array; +}; + +export type RichTextFieldValueTableRowNode = { + type: 'tableRow'; + content?: Array; +}; + +export type RichTextFieldValueTableCellNode = { + type: 'tableCell'; + attrs: { + colspan?: number; + rowspan?: number; + /** + * Column widths in pixels + */ + colwidth?: Array | null; + backgroundColor?: string | null; + }; + content?: Array; +}; + +export type RichTextFieldValueTableHeaderNode = { + type: 'tableHeader'; + attrs: { + colspan?: number; + rowspan?: number; + /** + * Column widths in pixels + */ + colwidth?: Array | null; + }; + content?: Array; +}; + +export type RichTextFieldValueBlockNode = { + type: 'blok'; + attrs: { + /** + * Block instance ID + */ + id: string | null; + /** + * Array of embedded component instances + */ + body: Array | null; + }; +}; + +/** + * Inline formatting mark applied to a text node + */ +export type RichTextFieldValueRichTextMark = RichTextFieldValueLinkMark | RichTextFieldValueBoldMark | RichTextFieldValueItalicMark | RichTextFieldValueStrikeMark | RichTextFieldValueUnderlineMark | RichTextFieldValueCodeMark | RichTextFieldValueSuperscriptMark | RichTextFieldValueSubscriptMark | RichTextFieldValueHighlightMark | RichTextFieldValueTextStyleMark | RichTextFieldValueAnchorMark | RichTextFieldValueStyledMark; + +export type RichTextFieldValueLinkMark = { + type: 'link'; + /** + * Link attributes + */ + attrs: { + /** + * Link URL + */ + href: string | null; + /** + * UUID of the linked story (for internal links) + */ + uuid: string | null; + /** + * Anchor/fragment identifier + */ + anchor: string | null; + /** + * Link target attribute + */ + target: '_self' | '_blank' | '_parent' | '_top' | null; + /** + * Type of link + */ + linktype: 'story' | 'url' | 'email' | 'asset' | null; + /** + * Custom link attributes + */ + custom?: { + [key: string]: unknown; + }; + }; +}; + +export type RichTextFieldValueBoldMark = { + type: 'bold'; +}; + +export type RichTextFieldValueItalicMark = { + type: 'italic'; +}; + +export type RichTextFieldValueStrikeMark = { + type: 'strike'; +}; + +export type RichTextFieldValueUnderlineMark = { + type: 'underline'; +}; + +export type RichTextFieldValueCodeMark = { + type: 'code'; +}; + +export type RichTextFieldValueSuperscriptMark = { + type: 'superscript'; +}; + +export type RichTextFieldValueSubscriptMark = { + type: 'subscript'; +}; + +export type RichTextFieldValueHighlightMark = { + type: 'highlight'; + attrs: { + /** + * Highlight color (CSS color value) + */ + color: string | null; + }; +}; + +export type RichTextFieldValueTextStyleMark = { + type: 'textStyle'; + attrs: { + color?: string | null; + id?: string | null; + class?: string | null; + }; +}; + +export type RichTextFieldValueAnchorMark = { + type: 'anchor'; + attrs: { + /** + * Anchor identifier + */ + id: string; + }; +}; + +export type RichTextFieldValueStyledMark = { + type: 'styled'; + attrs: { + /** + * CSS class name + */ + class: string | null; + }; +}; diff --git a/packages/schema/src/generated/overlay/zod.gen.ts b/packages/schema/src/generated/overlay/zod.gen.ts index 72d4cb984..0915ad749 100644 --- a/packages/schema/src/generated/overlay/zod.gen.ts +++ b/packages/schema/src/generated/overlay/zod.gen.ts @@ -406,15 +406,205 @@ export const zPluginFieldValueRoot = z.object({ export const zPluginFieldValue = zPluginFieldValueRoot; +export const zRichTextFieldValueAnchorMark = z.object({ + type: z.enum(['anchor']), + attrs: z.object({ + id: z.string() + }) +}); + +export const zRichTextFieldValueBoldMark = z.object({ + type: z.enum(['bold']) +}); + +export const zRichTextFieldValueCodeMark = z.object({ + type: z.enum(['code']) +}); + +export const zRichTextFieldValueEmojiNode = z.object({ + type: z.enum(['emoji']), + attrs: z.object({ + name: z.string(), + emoji: z.string(), + fallbackImage: z.string() + }) +}); + +export const zRichTextFieldValueHardBreakNode = z.object({ + type: z.enum(['hard_break']) +}); + +export const zRichTextFieldValueHighlightMark = z.object({ + type: z.enum(['highlight']), + attrs: z.object({ + color: z.union([ + z.string(), + z.null() + ]) + }) +}); + +export const zRichTextFieldValueHorizontalRuleNode = z.object({ + type: z.enum(['horizontal_rule']) +}); + +export const zRichTextFieldValueImageNode = z.object({ + type: z.enum(['image']), + attrs: z.object({ + id: z.union([ + z.int(), + z.null() + ]), + src: z.union([ + z.string(), + z.null() + ]), + alt: z.union([ + z.string(), + z.null() + ]), + title: z.union([ + z.string(), + z.null() + ]), + source: z.union([ + z.string(), + z.null() + ]), + copyright: z.union([ + z.string(), + z.null() + ]), + meta_data: z.union([ + z.object({ + alt: z.optional(z.union([ + z.string(), + z.null() + ])), + title: z.optional(z.union([ + z.string(), + z.null() + ])), + source: z.optional(z.union([ + z.string(), + z.null() + ])), + copyright: z.optional(z.union([ + z.string(), + z.null() + ])) + }), + z.null() + ]) + }) +}); + +export const zRichTextFieldValueItalicMark = z.object({ + type: z.enum(['italic']) +}); + +export const zRichTextFieldValueLinkMark = z.object({ + type: z.enum(['link']), + attrs: z.object({ + href: z.union([ + z.string(), + z.null() + ]), + uuid: z.union([ + z.string(), + z.null() + ]), + anchor: z.union([ + z.string(), + z.null() + ]), + target: z.union([ + z.literal('_self'), + z.literal('_blank'), + z.literal('_parent'), + z.literal('_top'), + z.null() + ]), + linktype: z.union([ + z.literal('story'), + z.literal('url'), + z.literal('email'), + z.literal('asset'), + z.null() + ]), + custom: z.optional(z.record(z.string(), z.unknown())) + }) +}); + +export const zRichTextFieldValueStrikeMark = z.object({ + type: z.enum(['strike']) +}); + +export const zRichTextFieldValueStyledMark = z.object({ + type: z.enum(['styled']), + attrs: z.object({ + class: z.union([ + z.string(), + z.null() + ]) + }) +}); + +export const zRichTextFieldValueSubscriptMark = z.object({ + type: z.enum(['subscript']) +}); + +export const zRichTextFieldValueSuperscriptMark = z.object({ + type: z.enum(['superscript']) +}); + +export const zRichTextFieldValueTextStyleMark = z.object({ + type: z.enum(['textStyle']), + attrs: z.object({ + color: z.optional(z.union([ + z.string(), + z.null() + ])), + id: z.optional(z.union([ + z.string(), + z.null() + ])), + class: z.optional(z.union([ + z.string(), + z.null() + ])) + }) +}); + +export const zRichTextFieldValueUnderlineMark = z.object({ + type: z.enum(['underline']) +}); + /** - * Richtext field type - structured rich text document (ProseMirror format) + * Inline formatting mark applied to a text node */ -export const zRichtextFieldValueRoot = z.object({ - type: z.enum(['doc']), - content: z.optional(z.array(z.record(z.string(), z.unknown()))) -}); +export const zRichTextFieldValueRichTextMark = z.union([ + zRichTextFieldValueLinkMark, + zRichTextFieldValueBoldMark, + zRichTextFieldValueItalicMark, + zRichTextFieldValueStrikeMark, + zRichTextFieldValueUnderlineMark, + zRichTextFieldValueCodeMark, + zRichTextFieldValueSuperscriptMark, + zRichTextFieldValueSubscriptMark, + zRichTextFieldValueHighlightMark, + zRichTextFieldValueTextStyleMark, + zRichTextFieldValueAnchorMark, + zRichTextFieldValueStyledMark +]); -export const zRichtextFieldValue = zRichtextFieldValueRoot; +export const zRichTextMark = zRichTextFieldValueRichTextMark; + +export const zRichTextFieldValueTextNode = z.object({ + type: z.enum(['text']), + text: z.string(), + marks: z.optional(z.array(zRichTextFieldValueRichTextMark)) +}); /** * Table field type - structured table data @@ -438,6 +628,14 @@ export const zTableFieldValueRoot = z.object({ export const zTableFieldValue = zTableFieldValueRoot; +export const zBlockContent = z.lazy((): any => zBlockContentRoot); + +export const zBlockContentInput = z.lazy((): any => zBlockContentInputRoot); + +export const zRichTextFieldValue = z.lazy((): any => zRichTextFieldValueRoot); + +export const zRichTextNode = z.lazy((): any => zRichTextFieldValueRichTextNode); + /** * Content object for creating or updating a component instance. Contains a component technical name and dynamic fields whose values depend on the component's schema field types. The _uid is optional — Storyblok will auto-generate one if not provided. */ @@ -447,8 +645,6 @@ export const zBlockContentInputRoot = z.object({ _editable: z.optional(z.string()) }); -export const zBlockContentInput = zBlockContentInputRoot; - /** * Content object representing a component instance. Contains a _uid, a component technical name, and dynamic fields whose values depend on the component's schema field types (text, textarea, richtext, markdown, number, datetime, boolean, option, options, asset, multiasset, multilink, bloks, table, section, custom/plugin). */ @@ -458,4 +654,210 @@ export const zBlockContentRoot = z.object({ _editable: z.optional(z.string()) }); -export const zBlockContent = zBlockContentRoot; +/** + * Rich text field type - structured rich text document (ProseMirror/Tiptap format) + */ +export const zRichTextFieldValueRoot = z.object({ + type: z.enum(['doc']), + get content() { + return z.array(z.lazy((): any => zRichTextFieldValueRichTextNode)); + } +}); + +export const zRichTextFieldValueBlockNode = z.object({ + type: z.enum(['blok']), + attrs: z.object({ + id: z.union([ + z.string(), + z.null() + ]), + body: z.union([ + z.array(zBlockContentRoot), + z.null() + ]) + }) +}); + +export const zRichTextFieldValueBlockquoteNode = z.object({ + type: z.enum(['blockquote']), + attrs: z.optional(z.object({ + dir: z.optional(z.union([ + z.literal('ltr'), + z.literal('rtl'), + z.null() + ])) + })), + get content() { + return z.optional(z.array(z.lazy((): any => zRichTextFieldValueRichTextNode))); + }, + marks: z.optional(z.array(zRichTextFieldValueRichTextMark)) +}); + +export const zRichTextFieldValueBulletListNode = z.object({ + type: z.enum(['bullet_list']), + get content() { + return z.optional(z.array(z.lazy((): any => zRichTextFieldValueRichTextNode))); + }, + marks: z.optional(z.array(zRichTextFieldValueRichTextMark)) +}); + +export const zRichTextFieldValueCodeBlockNode = z.object({ + type: z.enum(['code_block']), + attrs: z.object({ + class: z.union([ + z.string(), + z.null() + ]), + dir: z.optional(z.union([ + z.literal('ltr'), + z.literal('rtl'), + z.null() + ])) + }), + get content() { + return z.optional(z.array(z.lazy((): any => zRichTextFieldValueRichTextNode))); + }, + marks: z.optional(z.array(zRichTextFieldValueRichTextMark)) +}); + +export const zRichTextFieldValueHeadingNode = z.object({ + type: z.enum(['heading']), + attrs: z.object({ + level: z.union([ + z.literal(1), + z.literal(2), + z.literal(3), + z.literal(4), + z.literal(5), + z.literal(6), + z.null() + ]), + textAlign: z.union([ + z.literal('left'), + z.literal('center'), + z.literal('right'), + z.literal('justify'), + z.null() + ]), + dir: z.optional(z.union([ + z.literal('ltr'), + z.literal('rtl'), + z.null() + ])) + }), + get content() { + return z.optional(z.array(z.lazy((): any => zRichTextFieldValueRichTextNode))); + }, + marks: z.optional(z.array(zRichTextFieldValueRichTextMark)) +}); + +export const zRichTextFieldValueListItemNode = z.object({ + type: z.enum(['list_item']), + attrs: z.optional(z.object({ + dir: z.optional(z.union([ + z.literal('ltr'), + z.literal('rtl'), + z.null() + ])) + })), + get content() { + return z.optional(z.array(z.lazy((): any => zRichTextFieldValueRichTextNode))); + }, + marks: z.optional(z.array(zRichTextFieldValueRichTextMark)) +}); + +export const zRichTextFieldValueOrderedListNode = z.object({ + type: z.enum(['ordered_list']), + attrs: z.object({ + order: z.optional(z.int()) + }), + get content() { + return z.optional(z.array(z.lazy((): any => zRichTextFieldValueRichTextNode))); + }, + marks: z.optional(z.array(zRichTextFieldValueRichTextMark)) +}); + +export const zRichTextFieldValueParagraphNode = z.object({ + type: z.enum(['paragraph']), + attrs: z.optional(z.object({ + textAlign: z.union([ + z.literal('left'), + z.literal('center'), + z.literal('right'), + z.literal('justify'), + z.null() + ]), + dir: z.optional(z.union([ + z.literal('ltr'), + z.literal('rtl'), + z.null() + ])) + })), + get content() { + return z.optional(z.array(z.lazy((): any => zRichTextFieldValueRichTextNode))); + }, + marks: z.optional(z.array(zRichTextFieldValueRichTextMark)) +}); + +/** + * A rich text document node + */ +export const zRichTextFieldValueRichTextNode = z.union([ + zRichTextFieldValueParagraphNode, + zRichTextFieldValueTextNode, + zRichTextFieldValueHeadingNode, + zRichTextFieldValueBlockquoteNode, + zRichTextFieldValueBulletListNode, + zRichTextFieldValueOrderedListNode, + zRichTextFieldValueListItemNode, + zRichTextFieldValueCodeBlockNode, + zRichTextFieldValueHardBreakNode, + zRichTextFieldValueHorizontalRuleNode, + zRichTextFieldValueImageNode, + zRichTextFieldValueEmojiNode, + z.lazy((): any => zRichTextFieldValueTableNode), + z.lazy((): any => zRichTextFieldValueTableRowNode), + z.lazy((): any => zRichTextFieldValueTableCellNode), + z.lazy((): any => zRichTextFieldValueTableHeaderNode), + zRichTextFieldValueBlockNode +]); + +export const zRichTextFieldValueTableCellNode = z.object({ + type: z.enum(['tableCell']), + attrs: z.object({ + colspan: z.optional(z.int()), + rowspan: z.optional(z.int()), + colwidth: z.optional(z.union([ + z.array(z.int()), + z.null() + ])), + backgroundColor: z.optional(z.union([ + z.string(), + z.null() + ])) + }), + content: z.optional(z.array(zRichTextFieldValueRichTextNode)) +}); + +export const zRichTextFieldValueTableHeaderNode = z.object({ + type: z.enum(['tableHeader']), + attrs: z.object({ + colspan: z.optional(z.int()), + rowspan: z.optional(z.int()), + colwidth: z.optional(z.union([ + z.array(z.int()), + z.null() + ])) + }), + content: z.optional(z.array(zRichTextFieldValueRichTextNode)) +}); + +export const zRichTextFieldValueTableNode = z.object({ + type: z.enum(['table']), + content: z.optional(z.array(zRichTextFieldValueRichTextNode)) +}); + +export const zRichTextFieldValueTableRowNode = z.object({ + type: z.enum(['tableRow']), + content: z.optional(z.array(zRichTextFieldValueRichTextNode)) +}); diff --git a/packages/schema/src/generated/types/_sources.ts b/packages/schema/src/generated/types/_sources.ts index 2b3397e30..0e177c685 100644 --- a/packages/schema/src/generated/types/_sources.ts +++ b/packages/schema/src/generated/types/_sources.ts @@ -2,4 +2,4 @@ export type { CapiStory } from '../capi/_internal.gen'; export type { Component, MapiStory, StoryCreate, StoryUpdate } from '../mapi/_internal.gen'; -export type { AssetFieldValue, BlockContentBase, BlockContentInputBase, Field, MultilinkFieldValue, PluginFieldValue, RichtextFieldValue, TableFieldValue } from '../overlay/_internal.gen'; +export type { AssetFieldValue, BlockContentBase, BlockContentInputBase, Field, MultilinkFieldValue, PluginFieldValue, RichTextFieldValue, TableFieldValue } from '../overlay/_internal.gen'; diff --git a/packages/schema/src/generated/types/field.ts b/packages/schema/src/generated/types/field.ts index 1f10a191d..dbdc5746e 100644 --- a/packages/schema/src/generated/types/field.ts +++ b/packages/schema/src/generated/types/field.ts @@ -8,14 +8,19 @@ import type { Field, MultilinkFieldValue, PluginFieldValue, - RichtextFieldValue, + RichTextFieldValue, TableFieldValue, } from './_sources'; import type { Prettify } from './_utils'; import type { Block, BlockFields } from './block'; export type { Field }; -export type { AssetFieldValue, MultilinkFieldValue, PluginFieldValue, RichtextFieldValue, TableFieldValue }; +export type { AssetFieldValue, MultilinkFieldValue, PluginFieldValue, RichTextFieldValue, TableFieldValue }; + +/** + * @deprecated Use {@link RichTextFieldValue} instead. Will be removed in a future major version. + */ +export type RichtextFieldValue = RichTextFieldValue; /** * Registry of all blocks in the space, used to resolve nested `bloks` fields. @@ -84,7 +89,7 @@ export type FieldType = Field['type']; interface FieldTypeValueMap { text: string; textarea: string; - richtext: RichtextFieldValue; + richtext: RichTextFieldValue; markdown: string; number: number; datetime: string; diff --git a/packages/schema/src/helpers/define-field.ts b/packages/schema/src/helpers/define-field.ts index 26aada453..6c694a636 100644 --- a/packages/schema/src/helpers/define-field.ts +++ b/packages/schema/src/helpers/define-field.ts @@ -9,6 +9,7 @@ import type { FieldValueInput, MultilinkFieldValue, PluginFieldValue, + RichTextFieldValue, RichtextFieldValue, TableFieldValue, } from '../generated/types/field'; @@ -27,6 +28,7 @@ export type { FieldValueInput, MultilinkFieldValue, PluginFieldValue, + RichTextFieldValue, RichtextFieldValue, TableFieldValue, }; diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index 5a20c2cd8..4f2882908 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -33,6 +33,7 @@ export type { FieldValueInput, MultilinkFieldValue, PluginFieldValue, + RichTextFieldValue, RichtextFieldValue, TableFieldValue, } from './helpers/define-field'; diff --git a/packages/schema/src/validators/internal-schemas.ts b/packages/schema/src/validators/internal-schemas.ts index 744632959..c0c12a835 100644 --- a/packages/schema/src/validators/internal-schemas.ts +++ b/packages/schema/src/validators/internal-schemas.ts @@ -7,6 +7,11 @@ export { zAssetFieldValue, zMultilinkFieldValue, zPluginFieldValue, - zRichtextFieldValue, + zRichTextFieldValue, zTableFieldValue, } from '../generated/overlay/zod.gen'; + +/** + * @deprecated Use {@link zRichTextFieldValue} instead. Will be removed in a future major version. + */ +export { zRichTextFieldValue as zRichtextFieldValue } from '../generated/overlay/zod.gen'; diff --git a/packages/schema/src/validators/validate-story.test.ts b/packages/schema/src/validators/validate-story.test.ts index e7330a705..a6728cb04 100644 --- a/packages/schema/src/validators/validate-story.test.ts +++ b/packages/schema/src/validators/validate-story.test.ts @@ -388,7 +388,7 @@ describe('validateStory — richtext allow entries', () => { it('allows an embedded blok whose folder is inside an allowed folder', () => { const result = validateStory({ - content: { component: 'page', body: richtextWith([{ component: 'hero', title: 'Hi' }]) }, + content: { component: 'page', body: richtextWith([{ _uid: 'uid-1', component: 'hero', title: 'Hi' }]) }, }, schema); expect(result.issues.find(i => i.code === 'disallowed_component')).toBeUndefined(); expect(result.ok).toBe(true); @@ -396,7 +396,7 @@ describe('validateStory — richtext allow entries', () => { it('rejects an embedded blok outside the allowed folder', () => { const result = validateStory({ - content: { component: 'page', body: richtextWith([{ component: 'teaser', text: 'hi' }]) }, + content: { component: 'page', body: richtextWith([{ _uid: 'uid-1', component: 'teaser', text: 'hi' }]) }, }, schema); const disallowed = result.issues.find(i => i.code === 'disallowed_component'); expect(disallowed).toBeDefined(); diff --git a/packages/schema/src/validators/validate-story.ts b/packages/schema/src/validators/validate-story.ts index 2d924ce04..1a51e6824 100644 --- a/packages/schema/src/validators/validate-story.ts +++ b/packages/schema/src/validators/validate-story.ts @@ -5,7 +5,7 @@ import type { ValidationIssue, ValidationResult } from './types'; import { zAssetFieldValue, zMultilinkFieldValue, - zRichtextFieldValue, + zRichTextFieldValue, zTableFieldValue, } from './internal-schemas'; import { isRecord, toValues } from './shapes'; @@ -89,7 +89,7 @@ function validateFieldValue( checkValue(zTableFieldValue, value, path, entity, issues); break; case 'richtext': - checkValue(zRichtextFieldValue, value, path, entity, issues); + checkValue(zRichTextFieldValue, value, path, entity, issues); validateRichtextBloks(value, field, blocksByName, fieldPluginsByType, path, entity, issues); break; case 'custom': { From 46f56bba29b3756cdbbfde1d631db332beae042a Mon Sep 17 00:00:00 2001 From: Dipankar Maikap Date: Wed, 29 Jul 2026 17:39:06 +0530 Subject: [PATCH 10/15] feat(capi-client): regenerate for RichTextFieldValue rename - Regenerate src/generated with RichTextFieldValue (new canonical name) - resources/stories.ts: update internal import to RichTextFieldValue Fixes DX-487 --- .../src/generated/overlay/_internal.gen.ts | 343 +++++++++++++++++- .../src/generated/types/_sources.ts | 2 +- .../capi-client/src/generated/types/field.ts | 11 +- packages/capi-client/src/resources/stories.ts | 4 +- 4 files changed, 343 insertions(+), 17 deletions(-) diff --git a/packages/capi-client/src/generated/overlay/_internal.gen.ts b/packages/capi-client/src/generated/overlay/_internal.gen.ts index b2d0cbc68..865be724b 100644 --- a/packages/capi-client/src/generated/overlay/_internal.gen.ts +++ b/packages/capi-client/src/generated/overlay/_internal.gen.ts @@ -15,7 +15,7 @@ export type MultilinkFieldValue = MultilinkFieldValueRoot; export type PluginFieldValue = PluginFieldValueRoot; -export type RichtextFieldValue = RichtextFieldValueRoot; +export type RichTextFieldValue = RichTextFieldValueRoot; export type TableFieldValue = TableFieldValueRoot; @@ -95,7 +95,7 @@ export type RichtextFieldRoot = BaseFieldRoot & ValueFieldRoot & { */ toolbar?: Array<'ai-complete' | 'ai-shorten' | 'ai-extend' | 'ai-rephrase' | 'ai-summarize' | 'ai-simplify' | 'ai-tone' | 'ai-emoji' | 'ai-translate' | 'ai-tldr' | 'ai-spelling' | 'blok' | 'ltr' | 'rtl' | 'export' | 'import' | 'bold' | 'list' | 'inlinecode' | 'code' | 'color' | 'copy' | 'cut' | 'emoji' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'highlight' | 'unset' | 'hrule' | 'image' | 'italic' | 'link' | 'anchor' | 'olist' | 'paragraph' | 'paste-action' | 'paste' | 'quote' | 'redo' | 'strike' | 'subscript' | 'superscript' | 'underline' | 'undo' | 'align-left' | 'align-center' | 'align-right' | 'align-justify'>; /** - * Custom CSS styles for the richtext field + * Custom CSS styles for the rich text field */ style_options?: Array<{ _uid?: string; @@ -678,7 +678,7 @@ export type BlockContentRoot = { * Storyblok editor markup string for inline editing (present in draft/preview mode) */ _editable?: string; - [key: string]: string | number | boolean | Array | AssetFieldValueRoot | MultilinkFieldValueRoot | TableFieldValueRoot | RichtextFieldValueRoot | PluginFieldValueRoot | string | undefined; + [key: string]: string | number | boolean | Array | AssetFieldValueRoot | MultilinkFieldValueRoot | TableFieldValueRoot | RichTextFieldValueRoot | PluginFieldValueRoot | string | undefined; }; /** @@ -697,7 +697,7 @@ export type BlockContentInputRoot = { * Storyblok editor markup string for inline editing (present in draft/preview mode) */ _editable?: string; - [key: string]: null | string | number | boolean | Array | AssetFieldValueRoot | MultilinkFieldValueRoot | TableFieldValueRoot | RichtextFieldValueRoot | PluginFieldValueRoot | string | undefined; + [key: string]: null | string | number | boolean | Array | AssetFieldValueRoot | MultilinkFieldValueRoot | TableFieldValueRoot | RichTextFieldValueRoot | PluginFieldValueRoot | string | undefined; }; /** @@ -750,19 +750,17 @@ export type PluginFieldValueRoot = { }; /** - * Richtext field type - structured rich text document (ProseMirror format) + * Rich text field type - structured rich text document (ProseMirror/Tiptap format) */ -export type RichtextFieldValueRoot = { +export type RichTextFieldValueRoot = { /** - * Root node type for richtext documents + * Root node type — always "doc" */ type: 'doc'; /** - * Array of richtext nodes (paragraphs, headings, lists, bloks, etc.) + * Top-level rich text nodes */ - content?: Array<{ - [key: string]: unknown; - }>; + content: Array; }; /** @@ -879,3 +877,326 @@ export type ValueFieldRoot = { */ force_merge?: boolean; }; + +/** + * A rich text document node + */ +export type RichTextFieldValueRichTextNode = RichTextFieldValueParagraphNode | RichTextFieldValueTextNode | RichTextFieldValueHeadingNode | RichTextFieldValueBlockquoteNode | RichTextFieldValueBulletListNode | RichTextFieldValueOrderedListNode | RichTextFieldValueListItemNode | RichTextFieldValueCodeBlockNode | RichTextFieldValueHardBreakNode | RichTextFieldValueHorizontalRuleNode | RichTextFieldValueImageNode | RichTextFieldValueEmojiNode | RichTextFieldValueTableNode | RichTextFieldValueTableRowNode | RichTextFieldValueTableCellNode | RichTextFieldValueTableHeaderNode | RichTextFieldValueBlockNode; + +export type RichTextFieldValueParagraphNode = { + type: 'paragraph'; + attrs?: { + /** + * Text alignment + */ + textAlign: 'left' | 'center' | 'right' | 'justify' | null; + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueTextNode = { + type: 'text'; + /** + * The text content + */ + text: string; + marks?: Array; +}; + +export type RichTextFieldValueHeadingNode = { + type: 'heading'; + attrs: { + /** + * Heading level (h1–h6) + */ + level: 1 | 2 | 3 | 4 | 5 | 6 | null; + /** + * Text alignment + */ + textAlign: 'left' | 'center' | 'right' | 'justify' | null; + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueBlockquoteNode = { + type: 'blockquote'; + attrs?: { + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueBulletListNode = { + type: 'bullet_list'; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueOrderedListNode = { + type: 'ordered_list'; + attrs: { + /** + * Starting number for the ordered list + */ + order?: number; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueListItemNode = { + type: 'list_item'; + attrs?: { + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueCodeBlockNode = { + type: 'code_block'; + attrs: { + /** + * Language class (e.g. "language-typescript") + */ + class: string | null; + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueHardBreakNode = { + type: 'hard_break'; +}; + +export type RichTextFieldValueHorizontalRuleNode = { + type: 'horizontal_rule'; +}; + +export type RichTextFieldValueImageNode = { + type: 'image'; + attrs: { + /** + * Storyblok asset ID + */ + id: number | null; + /** + * Image URL + */ + src: string | null; + /** + * Alternative text + */ + alt: string | null; + title: string | null; + source: string | null; + copyright: string | null; + /** + * Asset metadata + */ + meta_data: { + alt?: string | null; + title?: string | null; + source?: string | null; + copyright?: string | null; + [key: string]: string | null | string | null | string | null | string | null | string | null | undefined; + } | null; + }; +}; + +export type RichTextFieldValueEmojiNode = { + type: 'emoji'; + attrs: { + /** + * Emoji name/slug + */ + name: string; + /** + * Emoji character + */ + emoji: string; + /** + * URL to a fallback image for unsupported environments + */ + fallbackImage: string; + }; +}; + +export type RichTextFieldValueTableNode = { + type: 'table'; + content?: Array; +}; + +export type RichTextFieldValueTableRowNode = { + type: 'tableRow'; + content?: Array; +}; + +export type RichTextFieldValueTableCellNode = { + type: 'tableCell'; + attrs: { + colspan?: number; + rowspan?: number; + /** + * Column widths in pixels + */ + colwidth?: Array | null; + backgroundColor?: string | null; + }; + content?: Array; +}; + +export type RichTextFieldValueTableHeaderNode = { + type: 'tableHeader'; + attrs: { + colspan?: number; + rowspan?: number; + /** + * Column widths in pixels + */ + colwidth?: Array | null; + }; + content?: Array; +}; + +export type RichTextFieldValueBlockNode = { + type: 'blok'; + attrs: { + /** + * Block instance ID + */ + id: string | null; + /** + * Array of embedded component instances + */ + body: Array | null; + }; +}; + +/** + * Inline formatting mark applied to a text node + */ +export type RichTextFieldValueRichTextMark = RichTextFieldValueLinkMark | RichTextFieldValueBoldMark | RichTextFieldValueItalicMark | RichTextFieldValueStrikeMark | RichTextFieldValueUnderlineMark | RichTextFieldValueCodeMark | RichTextFieldValueSuperscriptMark | RichTextFieldValueSubscriptMark | RichTextFieldValueHighlightMark | RichTextFieldValueTextStyleMark | RichTextFieldValueAnchorMark | RichTextFieldValueStyledMark; + +export type RichTextFieldValueLinkMark = { + type: 'link'; + /** + * Link attributes + */ + attrs: { + /** + * Link URL + */ + href: string | null; + /** + * UUID of the linked story (for internal links) + */ + uuid: string | null; + /** + * Anchor/fragment identifier + */ + anchor: string | null; + /** + * Link target attribute + */ + target: '_self' | '_blank' | '_parent' | '_top' | null; + /** + * Type of link + */ + linktype: 'story' | 'url' | 'email' | 'asset' | null; + /** + * Custom link attributes + */ + custom?: { + [key: string]: unknown; + }; + }; +}; + +export type RichTextFieldValueBoldMark = { + type: 'bold'; +}; + +export type RichTextFieldValueItalicMark = { + type: 'italic'; +}; + +export type RichTextFieldValueStrikeMark = { + type: 'strike'; +}; + +export type RichTextFieldValueUnderlineMark = { + type: 'underline'; +}; + +export type RichTextFieldValueCodeMark = { + type: 'code'; +}; + +export type RichTextFieldValueSuperscriptMark = { + type: 'superscript'; +}; + +export type RichTextFieldValueSubscriptMark = { + type: 'subscript'; +}; + +export type RichTextFieldValueHighlightMark = { + type: 'highlight'; + attrs: { + /** + * Highlight color (CSS color value) + */ + color: string | null; + }; +}; + +export type RichTextFieldValueTextStyleMark = { + type: 'textStyle'; + attrs: { + color?: string | null; + id?: string | null; + class?: string | null; + }; +}; + +export type RichTextFieldValueAnchorMark = { + type: 'anchor'; + attrs: { + /** + * Anchor identifier + */ + id: string; + }; +}; + +export type RichTextFieldValueStyledMark = { + type: 'styled'; + attrs: { + /** + * CSS class name + */ + class: string | null; + }; +}; diff --git a/packages/capi-client/src/generated/types/_sources.ts b/packages/capi-client/src/generated/types/_sources.ts index 8c4a7405f..a17a0b3e0 100644 --- a/packages/capi-client/src/generated/types/_sources.ts +++ b/packages/capi-client/src/generated/types/_sources.ts @@ -2,4 +2,4 @@ export type { CapiStory } from '../capi/_internal.gen'; export type { Component } from '../mapi/_internal.gen'; -export type { AssetFieldValue, BlockContentBase, BlockContentInputBase, Field, MultilinkFieldValue, PluginFieldValue, RichtextFieldValue, TableFieldValue } from '../overlay/_internal.gen'; +export type { AssetFieldValue, BlockContentBase, BlockContentInputBase, Field, MultilinkFieldValue, PluginFieldValue, RichTextFieldValue, TableFieldValue } from '../overlay/_internal.gen'; diff --git a/packages/capi-client/src/generated/types/field.ts b/packages/capi-client/src/generated/types/field.ts index 1f10a191d..dbdc5746e 100644 --- a/packages/capi-client/src/generated/types/field.ts +++ b/packages/capi-client/src/generated/types/field.ts @@ -8,14 +8,19 @@ import type { Field, MultilinkFieldValue, PluginFieldValue, - RichtextFieldValue, + RichTextFieldValue, TableFieldValue, } from './_sources'; import type { Prettify } from './_utils'; import type { Block, BlockFields } from './block'; export type { Field }; -export type { AssetFieldValue, MultilinkFieldValue, PluginFieldValue, RichtextFieldValue, TableFieldValue }; +export type { AssetFieldValue, MultilinkFieldValue, PluginFieldValue, RichTextFieldValue, TableFieldValue }; + +/** + * @deprecated Use {@link RichTextFieldValue} instead. Will be removed in a future major version. + */ +export type RichtextFieldValue = RichTextFieldValue; /** * Registry of all blocks in the space, used to resolve nested `bloks` fields. @@ -84,7 +89,7 @@ export type FieldType = Field['type']; interface FieldTypeValueMap { text: string; textarea: string; - richtext: RichtextFieldValue; + richtext: RichTextFieldValue; markdown: string; number: number; datetime: string; diff --git a/packages/capi-client/src/resources/stories.ts b/packages/capi-client/src/resources/stories.ts index be9504181..a0901fd99 100644 --- a/packages/capi-client/src/resources/stories.ts +++ b/packages/capi-client/src/resources/stories.ts @@ -5,7 +5,7 @@ import type { BlockContent as BlokContent, MultilinkFieldValue, PluginFieldValue, - RichtextFieldValue, + RichTextFieldValue, TableFieldValue, } from '../generated/types/field'; import { inlineStoriesContent, inlineStoryContent, resolveRelationMap } from '../utils/inline-relations'; @@ -21,7 +21,7 @@ type InlinedStoryContentField = | AssetFieldValue | MultilinkFieldValue | TableFieldValue - | RichtextFieldValue + | RichTextFieldValue | PluginFieldValue | StoryWithInlinedRelations | undefined; From 6d86516d853177696b0264473eb48f04182a093b Mon Sep 17 00:00:00 2001 From: Dipankar Maikap Date: Wed, 29 Jul 2026 17:39:11 +0530 Subject: [PATCH 11/15] feat(mapi-client): regenerate for RichTextFieldValue rename and add backward-compat alias - Regenerate src/generated with RichTextFieldValue (new canonical name) - src/index.ts: export RichTextFieldValue (new) alongside kept deprecated RichtextFieldValue Fixes DX-487 --- .../src/generated/overlay/_internal.gen.ts | 343 +++++++++++++++++- .../src/generated/types/_sources.ts | 2 +- .../mapi-client/src/generated/types/field.ts | 11 +- packages/mapi-client/src/index.ts | 1 + 4 files changed, 342 insertions(+), 15 deletions(-) diff --git a/packages/mapi-client/src/generated/overlay/_internal.gen.ts b/packages/mapi-client/src/generated/overlay/_internal.gen.ts index b2d0cbc68..865be724b 100644 --- a/packages/mapi-client/src/generated/overlay/_internal.gen.ts +++ b/packages/mapi-client/src/generated/overlay/_internal.gen.ts @@ -15,7 +15,7 @@ export type MultilinkFieldValue = MultilinkFieldValueRoot; export type PluginFieldValue = PluginFieldValueRoot; -export type RichtextFieldValue = RichtextFieldValueRoot; +export type RichTextFieldValue = RichTextFieldValueRoot; export type TableFieldValue = TableFieldValueRoot; @@ -95,7 +95,7 @@ export type RichtextFieldRoot = BaseFieldRoot & ValueFieldRoot & { */ toolbar?: Array<'ai-complete' | 'ai-shorten' | 'ai-extend' | 'ai-rephrase' | 'ai-summarize' | 'ai-simplify' | 'ai-tone' | 'ai-emoji' | 'ai-translate' | 'ai-tldr' | 'ai-spelling' | 'blok' | 'ltr' | 'rtl' | 'export' | 'import' | 'bold' | 'list' | 'inlinecode' | 'code' | 'color' | 'copy' | 'cut' | 'emoji' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'highlight' | 'unset' | 'hrule' | 'image' | 'italic' | 'link' | 'anchor' | 'olist' | 'paragraph' | 'paste-action' | 'paste' | 'quote' | 'redo' | 'strike' | 'subscript' | 'superscript' | 'underline' | 'undo' | 'align-left' | 'align-center' | 'align-right' | 'align-justify'>; /** - * Custom CSS styles for the richtext field + * Custom CSS styles for the rich text field */ style_options?: Array<{ _uid?: string; @@ -678,7 +678,7 @@ export type BlockContentRoot = { * Storyblok editor markup string for inline editing (present in draft/preview mode) */ _editable?: string; - [key: string]: string | number | boolean | Array | AssetFieldValueRoot | MultilinkFieldValueRoot | TableFieldValueRoot | RichtextFieldValueRoot | PluginFieldValueRoot | string | undefined; + [key: string]: string | number | boolean | Array | AssetFieldValueRoot | MultilinkFieldValueRoot | TableFieldValueRoot | RichTextFieldValueRoot | PluginFieldValueRoot | string | undefined; }; /** @@ -697,7 +697,7 @@ export type BlockContentInputRoot = { * Storyblok editor markup string for inline editing (present in draft/preview mode) */ _editable?: string; - [key: string]: null | string | number | boolean | Array | AssetFieldValueRoot | MultilinkFieldValueRoot | TableFieldValueRoot | RichtextFieldValueRoot | PluginFieldValueRoot | string | undefined; + [key: string]: null | string | number | boolean | Array | AssetFieldValueRoot | MultilinkFieldValueRoot | TableFieldValueRoot | RichTextFieldValueRoot | PluginFieldValueRoot | string | undefined; }; /** @@ -750,19 +750,17 @@ export type PluginFieldValueRoot = { }; /** - * Richtext field type - structured rich text document (ProseMirror format) + * Rich text field type - structured rich text document (ProseMirror/Tiptap format) */ -export type RichtextFieldValueRoot = { +export type RichTextFieldValueRoot = { /** - * Root node type for richtext documents + * Root node type — always "doc" */ type: 'doc'; /** - * Array of richtext nodes (paragraphs, headings, lists, bloks, etc.) + * Top-level rich text nodes */ - content?: Array<{ - [key: string]: unknown; - }>; + content: Array; }; /** @@ -879,3 +877,326 @@ export type ValueFieldRoot = { */ force_merge?: boolean; }; + +/** + * A rich text document node + */ +export type RichTextFieldValueRichTextNode = RichTextFieldValueParagraphNode | RichTextFieldValueTextNode | RichTextFieldValueHeadingNode | RichTextFieldValueBlockquoteNode | RichTextFieldValueBulletListNode | RichTextFieldValueOrderedListNode | RichTextFieldValueListItemNode | RichTextFieldValueCodeBlockNode | RichTextFieldValueHardBreakNode | RichTextFieldValueHorizontalRuleNode | RichTextFieldValueImageNode | RichTextFieldValueEmojiNode | RichTextFieldValueTableNode | RichTextFieldValueTableRowNode | RichTextFieldValueTableCellNode | RichTextFieldValueTableHeaderNode | RichTextFieldValueBlockNode; + +export type RichTextFieldValueParagraphNode = { + type: 'paragraph'; + attrs?: { + /** + * Text alignment + */ + textAlign: 'left' | 'center' | 'right' | 'justify' | null; + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueTextNode = { + type: 'text'; + /** + * The text content + */ + text: string; + marks?: Array; +}; + +export type RichTextFieldValueHeadingNode = { + type: 'heading'; + attrs: { + /** + * Heading level (h1–h6) + */ + level: 1 | 2 | 3 | 4 | 5 | 6 | null; + /** + * Text alignment + */ + textAlign: 'left' | 'center' | 'right' | 'justify' | null; + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueBlockquoteNode = { + type: 'blockquote'; + attrs?: { + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueBulletListNode = { + type: 'bullet_list'; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueOrderedListNode = { + type: 'ordered_list'; + attrs: { + /** + * Starting number for the ordered list + */ + order?: number; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueListItemNode = { + type: 'list_item'; + attrs?: { + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueCodeBlockNode = { + type: 'code_block'; + attrs: { + /** + * Language class (e.g. "language-typescript") + */ + class: string | null; + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueHardBreakNode = { + type: 'hard_break'; +}; + +export type RichTextFieldValueHorizontalRuleNode = { + type: 'horizontal_rule'; +}; + +export type RichTextFieldValueImageNode = { + type: 'image'; + attrs: { + /** + * Storyblok asset ID + */ + id: number | null; + /** + * Image URL + */ + src: string | null; + /** + * Alternative text + */ + alt: string | null; + title: string | null; + source: string | null; + copyright: string | null; + /** + * Asset metadata + */ + meta_data: { + alt?: string | null; + title?: string | null; + source?: string | null; + copyright?: string | null; + [key: string]: string | null | string | null | string | null | string | null | string | null | undefined; + } | null; + }; +}; + +export type RichTextFieldValueEmojiNode = { + type: 'emoji'; + attrs: { + /** + * Emoji name/slug + */ + name: string; + /** + * Emoji character + */ + emoji: string; + /** + * URL to a fallback image for unsupported environments + */ + fallbackImage: string; + }; +}; + +export type RichTextFieldValueTableNode = { + type: 'table'; + content?: Array; +}; + +export type RichTextFieldValueTableRowNode = { + type: 'tableRow'; + content?: Array; +}; + +export type RichTextFieldValueTableCellNode = { + type: 'tableCell'; + attrs: { + colspan?: number; + rowspan?: number; + /** + * Column widths in pixels + */ + colwidth?: Array | null; + backgroundColor?: string | null; + }; + content?: Array; +}; + +export type RichTextFieldValueTableHeaderNode = { + type: 'tableHeader'; + attrs: { + colspan?: number; + rowspan?: number; + /** + * Column widths in pixels + */ + colwidth?: Array | null; + }; + content?: Array; +}; + +export type RichTextFieldValueBlockNode = { + type: 'blok'; + attrs: { + /** + * Block instance ID + */ + id: string | null; + /** + * Array of embedded component instances + */ + body: Array | null; + }; +}; + +/** + * Inline formatting mark applied to a text node + */ +export type RichTextFieldValueRichTextMark = RichTextFieldValueLinkMark | RichTextFieldValueBoldMark | RichTextFieldValueItalicMark | RichTextFieldValueStrikeMark | RichTextFieldValueUnderlineMark | RichTextFieldValueCodeMark | RichTextFieldValueSuperscriptMark | RichTextFieldValueSubscriptMark | RichTextFieldValueHighlightMark | RichTextFieldValueTextStyleMark | RichTextFieldValueAnchorMark | RichTextFieldValueStyledMark; + +export type RichTextFieldValueLinkMark = { + type: 'link'; + /** + * Link attributes + */ + attrs: { + /** + * Link URL + */ + href: string | null; + /** + * UUID of the linked story (for internal links) + */ + uuid: string | null; + /** + * Anchor/fragment identifier + */ + anchor: string | null; + /** + * Link target attribute + */ + target: '_self' | '_blank' | '_parent' | '_top' | null; + /** + * Type of link + */ + linktype: 'story' | 'url' | 'email' | 'asset' | null; + /** + * Custom link attributes + */ + custom?: { + [key: string]: unknown; + }; + }; +}; + +export type RichTextFieldValueBoldMark = { + type: 'bold'; +}; + +export type RichTextFieldValueItalicMark = { + type: 'italic'; +}; + +export type RichTextFieldValueStrikeMark = { + type: 'strike'; +}; + +export type RichTextFieldValueUnderlineMark = { + type: 'underline'; +}; + +export type RichTextFieldValueCodeMark = { + type: 'code'; +}; + +export type RichTextFieldValueSuperscriptMark = { + type: 'superscript'; +}; + +export type RichTextFieldValueSubscriptMark = { + type: 'subscript'; +}; + +export type RichTextFieldValueHighlightMark = { + type: 'highlight'; + attrs: { + /** + * Highlight color (CSS color value) + */ + color: string | null; + }; +}; + +export type RichTextFieldValueTextStyleMark = { + type: 'textStyle'; + attrs: { + color?: string | null; + id?: string | null; + class?: string | null; + }; +}; + +export type RichTextFieldValueAnchorMark = { + type: 'anchor'; + attrs: { + /** + * Anchor identifier + */ + id: string; + }; +}; + +export type RichTextFieldValueStyledMark = { + type: 'styled'; + attrs: { + /** + * CSS class name + */ + class: string | null; + }; +}; diff --git a/packages/mapi-client/src/generated/types/_sources.ts b/packages/mapi-client/src/generated/types/_sources.ts index fa8c498cb..fb9d5da84 100644 --- a/packages/mapi-client/src/generated/types/_sources.ts +++ b/packages/mapi-client/src/generated/types/_sources.ts @@ -3,4 +3,4 @@ export type { CapiStory } from '../capi/_internal.gen'; export type { MapiStory, StoryCreate, StoryUpdate } from '../mapi/_internal.gen'; export type { Component } from '../mapi/types-aliased.gen'; -export type { AssetFieldValue, BlockContentBase, BlockContentInputBase, Field, MultilinkFieldValue, PluginFieldValue, RichtextFieldValue, TableFieldValue } from '../overlay/_internal.gen'; +export type { AssetFieldValue, BlockContentBase, BlockContentInputBase, Field, MultilinkFieldValue, PluginFieldValue, RichTextFieldValue, TableFieldValue } from '../overlay/_internal.gen'; diff --git a/packages/mapi-client/src/generated/types/field.ts b/packages/mapi-client/src/generated/types/field.ts index 1f10a191d..dbdc5746e 100644 --- a/packages/mapi-client/src/generated/types/field.ts +++ b/packages/mapi-client/src/generated/types/field.ts @@ -8,14 +8,19 @@ import type { Field, MultilinkFieldValue, PluginFieldValue, - RichtextFieldValue, + RichTextFieldValue, TableFieldValue, } from './_sources'; import type { Prettify } from './_utils'; import type { Block, BlockFields } from './block'; export type { Field }; -export type { AssetFieldValue, MultilinkFieldValue, PluginFieldValue, RichtextFieldValue, TableFieldValue }; +export type { AssetFieldValue, MultilinkFieldValue, PluginFieldValue, RichTextFieldValue, TableFieldValue }; + +/** + * @deprecated Use {@link RichTextFieldValue} instead. Will be removed in a future major version. + */ +export type RichtextFieldValue = RichTextFieldValue; /** * Registry of all blocks in the space, used to resolve nested `bloks` fields. @@ -84,7 +89,7 @@ export type FieldType = Field['type']; interface FieldTypeValueMap { text: string; textarea: string; - richtext: RichtextFieldValue; + richtext: RichTextFieldValue; markdown: string; number: number; datetime: string; diff --git a/packages/mapi-client/src/index.ts b/packages/mapi-client/src/index.ts index 2c3740cea..9b79b2179 100644 --- a/packages/mapi-client/src/index.ts +++ b/packages/mapi-client/src/index.ts @@ -73,6 +73,7 @@ export type { BlocksFieldValue as BloksFieldValue, MultilinkFieldValue, PluginFieldValue, + RichTextFieldValue, RichtextFieldValue, TableFieldValue, } from './generated/types/field'; From 8725cc53d66bc8a0194b85fe503be4bcc2c9691e Mon Sep 17 00:00:00 2001 From: Dipankar Maikap Date: Wed, 29 Jul 2026 17:39:23 +0530 Subject: [PATCH 12/15] chore(live-preview): regenerate for RichTextFieldValue rename Fixes DX-487 --- .../src/generated/overlay/_internal.gen.ts | 343 +++++++++++++++++- .../src/generated/types/_sources.ts | 2 +- .../live-preview/src/generated/types/field.ts | 11 +- 3 files changed, 341 insertions(+), 15 deletions(-) diff --git a/packages/live-preview/src/generated/overlay/_internal.gen.ts b/packages/live-preview/src/generated/overlay/_internal.gen.ts index b2d0cbc68..865be724b 100644 --- a/packages/live-preview/src/generated/overlay/_internal.gen.ts +++ b/packages/live-preview/src/generated/overlay/_internal.gen.ts @@ -15,7 +15,7 @@ export type MultilinkFieldValue = MultilinkFieldValueRoot; export type PluginFieldValue = PluginFieldValueRoot; -export type RichtextFieldValue = RichtextFieldValueRoot; +export type RichTextFieldValue = RichTextFieldValueRoot; export type TableFieldValue = TableFieldValueRoot; @@ -95,7 +95,7 @@ export type RichtextFieldRoot = BaseFieldRoot & ValueFieldRoot & { */ toolbar?: Array<'ai-complete' | 'ai-shorten' | 'ai-extend' | 'ai-rephrase' | 'ai-summarize' | 'ai-simplify' | 'ai-tone' | 'ai-emoji' | 'ai-translate' | 'ai-tldr' | 'ai-spelling' | 'blok' | 'ltr' | 'rtl' | 'export' | 'import' | 'bold' | 'list' | 'inlinecode' | 'code' | 'color' | 'copy' | 'cut' | 'emoji' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'highlight' | 'unset' | 'hrule' | 'image' | 'italic' | 'link' | 'anchor' | 'olist' | 'paragraph' | 'paste-action' | 'paste' | 'quote' | 'redo' | 'strike' | 'subscript' | 'superscript' | 'underline' | 'undo' | 'align-left' | 'align-center' | 'align-right' | 'align-justify'>; /** - * Custom CSS styles for the richtext field + * Custom CSS styles for the rich text field */ style_options?: Array<{ _uid?: string; @@ -678,7 +678,7 @@ export type BlockContentRoot = { * Storyblok editor markup string for inline editing (present in draft/preview mode) */ _editable?: string; - [key: string]: string | number | boolean | Array | AssetFieldValueRoot | MultilinkFieldValueRoot | TableFieldValueRoot | RichtextFieldValueRoot | PluginFieldValueRoot | string | undefined; + [key: string]: string | number | boolean | Array | AssetFieldValueRoot | MultilinkFieldValueRoot | TableFieldValueRoot | RichTextFieldValueRoot | PluginFieldValueRoot | string | undefined; }; /** @@ -697,7 +697,7 @@ export type BlockContentInputRoot = { * Storyblok editor markup string for inline editing (present in draft/preview mode) */ _editable?: string; - [key: string]: null | string | number | boolean | Array | AssetFieldValueRoot | MultilinkFieldValueRoot | TableFieldValueRoot | RichtextFieldValueRoot | PluginFieldValueRoot | string | undefined; + [key: string]: null | string | number | boolean | Array | AssetFieldValueRoot | MultilinkFieldValueRoot | TableFieldValueRoot | RichTextFieldValueRoot | PluginFieldValueRoot | string | undefined; }; /** @@ -750,19 +750,17 @@ export type PluginFieldValueRoot = { }; /** - * Richtext field type - structured rich text document (ProseMirror format) + * Rich text field type - structured rich text document (ProseMirror/Tiptap format) */ -export type RichtextFieldValueRoot = { +export type RichTextFieldValueRoot = { /** - * Root node type for richtext documents + * Root node type — always "doc" */ type: 'doc'; /** - * Array of richtext nodes (paragraphs, headings, lists, bloks, etc.) + * Top-level rich text nodes */ - content?: Array<{ - [key: string]: unknown; - }>; + content: Array; }; /** @@ -879,3 +877,326 @@ export type ValueFieldRoot = { */ force_merge?: boolean; }; + +/** + * A rich text document node + */ +export type RichTextFieldValueRichTextNode = RichTextFieldValueParagraphNode | RichTextFieldValueTextNode | RichTextFieldValueHeadingNode | RichTextFieldValueBlockquoteNode | RichTextFieldValueBulletListNode | RichTextFieldValueOrderedListNode | RichTextFieldValueListItemNode | RichTextFieldValueCodeBlockNode | RichTextFieldValueHardBreakNode | RichTextFieldValueHorizontalRuleNode | RichTextFieldValueImageNode | RichTextFieldValueEmojiNode | RichTextFieldValueTableNode | RichTextFieldValueTableRowNode | RichTextFieldValueTableCellNode | RichTextFieldValueTableHeaderNode | RichTextFieldValueBlockNode; + +export type RichTextFieldValueParagraphNode = { + type: 'paragraph'; + attrs?: { + /** + * Text alignment + */ + textAlign: 'left' | 'center' | 'right' | 'justify' | null; + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueTextNode = { + type: 'text'; + /** + * The text content + */ + text: string; + marks?: Array; +}; + +export type RichTextFieldValueHeadingNode = { + type: 'heading'; + attrs: { + /** + * Heading level (h1–h6) + */ + level: 1 | 2 | 3 | 4 | 5 | 6 | null; + /** + * Text alignment + */ + textAlign: 'left' | 'center' | 'right' | 'justify' | null; + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueBlockquoteNode = { + type: 'blockquote'; + attrs?: { + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueBulletListNode = { + type: 'bullet_list'; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueOrderedListNode = { + type: 'ordered_list'; + attrs: { + /** + * Starting number for the ordered list + */ + order?: number; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueListItemNode = { + type: 'list_item'; + attrs?: { + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueCodeBlockNode = { + type: 'code_block'; + attrs: { + /** + * Language class (e.g. "language-typescript") + */ + class: string | null; + /** + * Text direction + */ + dir?: 'ltr' | 'rtl' | null; + }; + content?: Array; + marks?: Array; +}; + +export type RichTextFieldValueHardBreakNode = { + type: 'hard_break'; +}; + +export type RichTextFieldValueHorizontalRuleNode = { + type: 'horizontal_rule'; +}; + +export type RichTextFieldValueImageNode = { + type: 'image'; + attrs: { + /** + * Storyblok asset ID + */ + id: number | null; + /** + * Image URL + */ + src: string | null; + /** + * Alternative text + */ + alt: string | null; + title: string | null; + source: string | null; + copyright: string | null; + /** + * Asset metadata + */ + meta_data: { + alt?: string | null; + title?: string | null; + source?: string | null; + copyright?: string | null; + [key: string]: string | null | string | null | string | null | string | null | string | null | undefined; + } | null; + }; +}; + +export type RichTextFieldValueEmojiNode = { + type: 'emoji'; + attrs: { + /** + * Emoji name/slug + */ + name: string; + /** + * Emoji character + */ + emoji: string; + /** + * URL to a fallback image for unsupported environments + */ + fallbackImage: string; + }; +}; + +export type RichTextFieldValueTableNode = { + type: 'table'; + content?: Array; +}; + +export type RichTextFieldValueTableRowNode = { + type: 'tableRow'; + content?: Array; +}; + +export type RichTextFieldValueTableCellNode = { + type: 'tableCell'; + attrs: { + colspan?: number; + rowspan?: number; + /** + * Column widths in pixels + */ + colwidth?: Array | null; + backgroundColor?: string | null; + }; + content?: Array; +}; + +export type RichTextFieldValueTableHeaderNode = { + type: 'tableHeader'; + attrs: { + colspan?: number; + rowspan?: number; + /** + * Column widths in pixels + */ + colwidth?: Array | null; + }; + content?: Array; +}; + +export type RichTextFieldValueBlockNode = { + type: 'blok'; + attrs: { + /** + * Block instance ID + */ + id: string | null; + /** + * Array of embedded component instances + */ + body: Array | null; + }; +}; + +/** + * Inline formatting mark applied to a text node + */ +export type RichTextFieldValueRichTextMark = RichTextFieldValueLinkMark | RichTextFieldValueBoldMark | RichTextFieldValueItalicMark | RichTextFieldValueStrikeMark | RichTextFieldValueUnderlineMark | RichTextFieldValueCodeMark | RichTextFieldValueSuperscriptMark | RichTextFieldValueSubscriptMark | RichTextFieldValueHighlightMark | RichTextFieldValueTextStyleMark | RichTextFieldValueAnchorMark | RichTextFieldValueStyledMark; + +export type RichTextFieldValueLinkMark = { + type: 'link'; + /** + * Link attributes + */ + attrs: { + /** + * Link URL + */ + href: string | null; + /** + * UUID of the linked story (for internal links) + */ + uuid: string | null; + /** + * Anchor/fragment identifier + */ + anchor: string | null; + /** + * Link target attribute + */ + target: '_self' | '_blank' | '_parent' | '_top' | null; + /** + * Type of link + */ + linktype: 'story' | 'url' | 'email' | 'asset' | null; + /** + * Custom link attributes + */ + custom?: { + [key: string]: unknown; + }; + }; +}; + +export type RichTextFieldValueBoldMark = { + type: 'bold'; +}; + +export type RichTextFieldValueItalicMark = { + type: 'italic'; +}; + +export type RichTextFieldValueStrikeMark = { + type: 'strike'; +}; + +export type RichTextFieldValueUnderlineMark = { + type: 'underline'; +}; + +export type RichTextFieldValueCodeMark = { + type: 'code'; +}; + +export type RichTextFieldValueSuperscriptMark = { + type: 'superscript'; +}; + +export type RichTextFieldValueSubscriptMark = { + type: 'subscript'; +}; + +export type RichTextFieldValueHighlightMark = { + type: 'highlight'; + attrs: { + /** + * Highlight color (CSS color value) + */ + color: string | null; + }; +}; + +export type RichTextFieldValueTextStyleMark = { + type: 'textStyle'; + attrs: { + color?: string | null; + id?: string | null; + class?: string | null; + }; +}; + +export type RichTextFieldValueAnchorMark = { + type: 'anchor'; + attrs: { + /** + * Anchor identifier + */ + id: string; + }; +}; + +export type RichTextFieldValueStyledMark = { + type: 'styled'; + attrs: { + /** + * CSS class name + */ + class: string | null; + }; +}; diff --git a/packages/live-preview/src/generated/types/_sources.ts b/packages/live-preview/src/generated/types/_sources.ts index 8c4a7405f..a17a0b3e0 100644 --- a/packages/live-preview/src/generated/types/_sources.ts +++ b/packages/live-preview/src/generated/types/_sources.ts @@ -2,4 +2,4 @@ export type { CapiStory } from '../capi/_internal.gen'; export type { Component } from '../mapi/_internal.gen'; -export type { AssetFieldValue, BlockContentBase, BlockContentInputBase, Field, MultilinkFieldValue, PluginFieldValue, RichtextFieldValue, TableFieldValue } from '../overlay/_internal.gen'; +export type { AssetFieldValue, BlockContentBase, BlockContentInputBase, Field, MultilinkFieldValue, PluginFieldValue, RichTextFieldValue, TableFieldValue } from '../overlay/_internal.gen'; diff --git a/packages/live-preview/src/generated/types/field.ts b/packages/live-preview/src/generated/types/field.ts index 1f10a191d..dbdc5746e 100644 --- a/packages/live-preview/src/generated/types/field.ts +++ b/packages/live-preview/src/generated/types/field.ts @@ -8,14 +8,19 @@ import type { Field, MultilinkFieldValue, PluginFieldValue, - RichtextFieldValue, + RichTextFieldValue, TableFieldValue, } from './_sources'; import type { Prettify } from './_utils'; import type { Block, BlockFields } from './block'; export type { Field }; -export type { AssetFieldValue, MultilinkFieldValue, PluginFieldValue, RichtextFieldValue, TableFieldValue }; +export type { AssetFieldValue, MultilinkFieldValue, PluginFieldValue, RichTextFieldValue, TableFieldValue }; + +/** + * @deprecated Use {@link RichTextFieldValue} instead. Will be removed in a future major version. + */ +export type RichtextFieldValue = RichTextFieldValue; /** * Registry of all blocks in the space, used to resolve nested `bloks` fields. @@ -84,7 +89,7 @@ export type FieldType = Field['type']; interface FieldTypeValueMap { text: string; textarea: string; - richtext: RichtextFieldValue; + richtext: RichTextFieldValue; markdown: string; number: number; datetime: string; From a29546fcfd4cc49cb64718f2b39f18feeecfb779 Mon Sep 17 00:00:00 2001 From: Dipankar Maikap Date: Wed, 29 Jul 2026 17:39:30 +0530 Subject: [PATCH 13/15] chore(experiments): regenerate for RichTextFieldValue rename Fixes DX-487 --- packages/experiments/src/generated/types/_sources.ts | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 packages/experiments/src/generated/types/_sources.ts diff --git a/packages/experiments/src/generated/types/_sources.ts b/packages/experiments/src/generated/types/_sources.ts deleted file mode 100644 index 399623570..000000000 --- a/packages/experiments/src/generated/types/_sources.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Generated by @storyblok/openapi-codegen. Do not edit by hand. - - From 56a09c22a4263c9fdc5efc32f8a44b5ce9c88ab5 Mon Sep 17 00:00:00 2001 From: Dipankar Maikap Date: Wed, 29 Jul 2026 17:39:34 +0530 Subject: [PATCH 14/15] feat(migrations): export RichTextFieldValue alongside deprecated RichtextFieldValue Fixes DX-487 --- packages/migrations/src/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/migrations/src/types.ts b/packages/migrations/src/types.ts index 571095555..ff2ff08b1 100644 --- a/packages/migrations/src/types.ts +++ b/packages/migrations/src/types.ts @@ -5,6 +5,7 @@ export type { Component, Datasource, MultilinkFieldValue, + RichTextFieldValue, RichtextFieldValue, Story, TableFieldValue, From 9a89ccdc451981a604146a5b66ebebff2051bfb7 Mon Sep 17 00:00:00 2001 From: Dipankar Maikap Date: Thu, 30 Jul 2026 18:20:42 +0530 Subject: [PATCH 15/15] fix(openapi): use block-content-input for richtext BlockNode body and make link custom attr nullable - BlockNode.body items now reference block-content-input.yaml so _uid is optional when writing richtext content with embedded bloks - LinkMark attrs.custom changed to type [object, 'null'] to correctly reflect that the field may be null --- .../capi-client/src/generated/overlay/_internal.gen.ts | 4 ++-- .../live-preview/src/generated/overlay/_internal.gen.ts | 4 ++-- .../mapi-client/src/generated/overlay/_internal.gen.ts | 4 ++-- packages/schema/src/generated/overlay/_internal.gen.ts | 4 ++-- packages/schema/src/generated/overlay/zod.gen.ts | 7 +++++-- .../shared/stories/field-types/rich-text-field-value.yaml | 4 ++-- 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/capi-client/src/generated/overlay/_internal.gen.ts b/packages/capi-client/src/generated/overlay/_internal.gen.ts index 865be724b..38fb80e5b 100644 --- a/packages/capi-client/src/generated/overlay/_internal.gen.ts +++ b/packages/capi-client/src/generated/overlay/_internal.gen.ts @@ -1090,7 +1090,7 @@ export type RichTextFieldValueBlockNode = { /** * Array of embedded component instances */ - body: Array | null; + body: Array | null; }; }; @@ -1130,7 +1130,7 @@ export type RichTextFieldValueLinkMark = { */ custom?: { [key: string]: unknown; - }; + } | null; }; }; diff --git a/packages/live-preview/src/generated/overlay/_internal.gen.ts b/packages/live-preview/src/generated/overlay/_internal.gen.ts index 865be724b..38fb80e5b 100644 --- a/packages/live-preview/src/generated/overlay/_internal.gen.ts +++ b/packages/live-preview/src/generated/overlay/_internal.gen.ts @@ -1090,7 +1090,7 @@ export type RichTextFieldValueBlockNode = { /** * Array of embedded component instances */ - body: Array | null; + body: Array | null; }; }; @@ -1130,7 +1130,7 @@ export type RichTextFieldValueLinkMark = { */ custom?: { [key: string]: unknown; - }; + } | null; }; }; diff --git a/packages/mapi-client/src/generated/overlay/_internal.gen.ts b/packages/mapi-client/src/generated/overlay/_internal.gen.ts index 865be724b..38fb80e5b 100644 --- a/packages/mapi-client/src/generated/overlay/_internal.gen.ts +++ b/packages/mapi-client/src/generated/overlay/_internal.gen.ts @@ -1090,7 +1090,7 @@ export type RichTextFieldValueBlockNode = { /** * Array of embedded component instances */ - body: Array | null; + body: Array | null; }; }; @@ -1130,7 +1130,7 @@ export type RichTextFieldValueLinkMark = { */ custom?: { [key: string]: unknown; - }; + } | null; }; }; diff --git a/packages/schema/src/generated/overlay/_internal.gen.ts b/packages/schema/src/generated/overlay/_internal.gen.ts index 865be724b..38fb80e5b 100644 --- a/packages/schema/src/generated/overlay/_internal.gen.ts +++ b/packages/schema/src/generated/overlay/_internal.gen.ts @@ -1090,7 +1090,7 @@ export type RichTextFieldValueBlockNode = { /** * Array of embedded component instances */ - body: Array | null; + body: Array | null; }; }; @@ -1130,7 +1130,7 @@ export type RichTextFieldValueLinkMark = { */ custom?: { [key: string]: unknown; - }; + } | null; }; }; diff --git a/packages/schema/src/generated/overlay/zod.gen.ts b/packages/schema/src/generated/overlay/zod.gen.ts index 0915ad749..13804ad0f 100644 --- a/packages/schema/src/generated/overlay/zod.gen.ts +++ b/packages/schema/src/generated/overlay/zod.gen.ts @@ -532,7 +532,10 @@ export const zRichTextFieldValueLinkMark = z.object({ z.literal('asset'), z.null() ]), - custom: z.optional(z.record(z.string(), z.unknown())) + custom: z.optional(z.union([ + z.record(z.string(), z.unknown()), + z.null() + ])) }) }); @@ -672,7 +675,7 @@ export const zRichTextFieldValueBlockNode = z.object({ z.null() ]), body: z.union([ - z.array(zBlockContentRoot), + z.array(zBlockContentInputRoot), z.null() ]) }) diff --git a/tools/openapi-codegen/specs/shared/stories/field-types/rich-text-field-value.yaml b/tools/openapi-codegen/specs/shared/stories/field-types/rich-text-field-value.yaml index 215f0f5de..676dd37ba 100644 --- a/tools/openapi-codegen/specs/shared/stories/field-types/rich-text-field-value.yaml +++ b/tools/openapi-codegen/specs/shared/stories/field-types/rich-text-field-value.yaml @@ -71,7 +71,7 @@ $defs: enum: [story, url, email, asset] - type: 'null' custom: - type: object + type: [object, 'null'] additionalProperties: true description: Custom link attributes @@ -597,5 +597,5 @@ $defs: oneOf: - type: array items: - $ref: ../block-content.yaml + $ref: ../block-content-input.yaml - type: 'null'