From 93f04544e86a4157bdc86d79c89423de50595c4b Mon Sep 17 00:00:00 2001 From: umbobabo Date: Mon, 22 Sep 2025 15:18:50 +0100 Subject: [PATCH] feat: add timeline and event nodes --- README.md | 56 ++++++++++++++------ content-tree.d.ts | 84 ++++++++++++++++++++++++++++-- schemas/body-tree.schema.json | 89 ++++++++++++++++++++++++++++++++ schemas/content-tree.schema.json | 89 ++++++++++++++++++++++++++++++++ schemas/transit-tree.schema.json | 89 ++++++++++++++++++++++++++++++++ 5 files changed, 388 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 51b18dd..48f3553 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ type BodyBlock = | Video | YoutubeVideo | Text + | Timeline | ImagePair ``` @@ -762,24 +763,24 @@ interface Table extends Parent { ```ts type CustomCodeComponentAttributes = { - [key: string]: string | boolean | undefined + [key: string]: string | boolean | undefined } interface CustomCodeComponent extends Node { - /** Component type */ - type: "custom-code-component" - /** Id taken from the CAPI url */ - id: string - /** How the component should be presented in the article page according to the column layout system */ - layoutWidth: LayoutWidth - /** Repository for the code of the component in the format "[github org]/[github repo]/[component name]". */ - external path: string - /** Semantic version of the code of the component, e.g. "^0.3.5". */ - external versionRange: string - /** Last date-time when the attributes for this block were modified, in ISO-8601 format. */ - external attributesLastModified: string - /** Configuration data to be passed to the component. */ - external attributes: CustomCodeComponentAttributes + /** Component type */ + type: "custom-code-component" + /** Id taken from the CAPI url */ + id: string + /** How the component should be presented in the article page according to the column layout system */ + layoutWidth: LayoutWidth + /** Repository for the code of the component in the format "[github org]/[github repo]/[component name]". */ + external path: string + /** Semantic version of the code of the component, e.g. "^0.3.5". */ + external versionRange: string + /** Last date-time when the attributes for this block were modified, in ISO-8601 format. */ + external attributesLastModified: string + /** Configuration data to be passed to the component. */ + external attributes: CustomCodeComponentAttributes } ``` @@ -799,6 +800,31 @@ interface ImagePair extends Parent { **ImagePair** is a set of two images +### Timeline + +```ts +/** + * Timeline nodes display a timeline of events in arbitrary order. + */ +interface Timeline extends Parent { + type: "timeline" + /** The title for the timeline */ + title: string + children: TimelineEvent[] +} + +/** + * TimelineEvent is the representation of a single event in a Timeline. + */ +interface TimelineEvent extends Parent { + type: "timeline-event" + /** The title of the event */ + title: string + /** A paragraph, a paragraph and an image set, or just an image set */ + children: [Paragraph] | [Paragraph, ImageSet] | [ImageSet]; +} +``` + ## License This software is published by the Financial Times under the [MIT licence](mit). diff --git a/content-tree.d.ts b/content-tree.d.ts index a72b240..b55b112 100644 --- a/content-tree.d.ts +++ b/content-tree.d.ts @@ -1,5 +1,5 @@ export declare namespace ContentTree { - type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | RecommendedList | Tweet | Video | YoutubeVideo | Text | ImagePair; + type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | RecommendedList | Tweet | Video | YoutubeVideo | Text | Timeline | ImagePair; type LayoutWidth = "auto" | "in-line" | "inset-left" | "inset-right" | "full-bleed" | "full-grid" | "mid-grid" | "full-width"; type Phrasing = Text | Break | Strong | Emphasis | Strikethrough | Link; interface Node { @@ -289,8 +289,27 @@ export declare namespace ContentTree { type: 'image-pair'; children: [ImageSet, ImageSet]; } + /** + * Timeline nodes display a timeline of events in arbitrary order. + */ + interface Timeline extends Parent { + type: "timeline"; + /** The title for the timeline */ + title: string; + children: TimelineEvent[]; + } + /** + * TimelineEvent is the representation of a single event in a Timeline. + */ + interface TimelineEvent extends Parent { + type: "timeline-event"; + /** The title of the event */ + title: string; + /** A paragraph, a paragraph and an image set, or just an image set */ + children: [Paragraph] | [Paragraph, ImageSet] | [ImageSet]; + } namespace full { - type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | RecommendedList | Tweet | Video | YoutubeVideo | Text | ImagePair; + type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | RecommendedList | Tweet | Video | YoutubeVideo | Text | Timeline | ImagePair; type LayoutWidth = "auto" | "in-line" | "inset-left" | "inset-right" | "full-bleed" | "full-grid" | "mid-grid" | "full-width"; type Phrasing = Text | Break | Strong | Emphasis | Strikethrough | Link; interface Node { @@ -580,9 +599,28 @@ export declare namespace ContentTree { type: 'image-pair'; children: [ImageSet, ImageSet]; } + /** + * Timeline nodes display a timeline of events in arbitrary order. + */ + interface Timeline extends Parent { + type: "timeline"; + /** The title for the timeline */ + title: string; + children: TimelineEvent[]; + } + /** + * TimelineEvent is the representation of a single event in a Timeline. + */ + interface TimelineEvent extends Parent { + type: "timeline-event"; + /** The title of the event */ + title: string; + /** A paragraph, a paragraph and an image set, or just an image set */ + children: [Paragraph] | [Paragraph, ImageSet] | [ImageSet]; + } } namespace transit { - type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | RecommendedList | Tweet | Video | YoutubeVideo | Text | ImagePair; + type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | RecommendedList | Tweet | Video | YoutubeVideo | Text | Timeline | ImagePair; type LayoutWidth = "auto" | "in-line" | "inset-left" | "inset-right" | "full-bleed" | "full-grid" | "mid-grid" | "full-width"; type Phrasing = Text | Break | Strong | Emphasis | Strikethrough | Link; interface Node { @@ -857,9 +895,28 @@ export declare namespace ContentTree { type: 'image-pair'; children: [ImageSet, ImageSet]; } + /** + * Timeline nodes display a timeline of events in arbitrary order. + */ + interface Timeline extends Parent { + type: "timeline"; + /** The title for the timeline */ + title: string; + children: TimelineEvent[]; + } + /** + * TimelineEvent is the representation of a single event in a Timeline. + */ + interface TimelineEvent extends Parent { + type: "timeline-event"; + /** The title of the event */ + title: string; + /** A paragraph, a paragraph and an image set, or just an image set */ + children: [Paragraph] | [Paragraph, ImageSet] | [ImageSet]; + } } namespace loose { - type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | RecommendedList | Tweet | Video | YoutubeVideo | Text | ImagePair; + type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | RecommendedList | Tweet | Video | YoutubeVideo | Text | Timeline | ImagePair; type LayoutWidth = "auto" | "in-line" | "inset-left" | "inset-right" | "full-bleed" | "full-grid" | "mid-grid" | "full-width"; type Phrasing = Text | Break | Strong | Emphasis | Strikethrough | Link; interface Node { @@ -1149,5 +1206,24 @@ export declare namespace ContentTree { type: 'image-pair'; children: [ImageSet, ImageSet]; } + /** + * Timeline nodes display a timeline of events in arbitrary order. + */ + interface Timeline extends Parent { + type: "timeline"; + /** The title for the timeline */ + title: string; + children: TimelineEvent[]; + } + /** + * TimelineEvent is the representation of a single event in a Timeline. + */ + interface TimelineEvent extends Parent { + type: "timeline-event"; + /** The title of the event */ + title: string; + /** A paragraph, a paragraph and an image set, or just an image set */ + children: [Paragraph] | [Paragraph, ImageSet] | [ImageSet]; + } } } diff --git a/schemas/body-tree.schema.json b/schemas/body-tree.schema.json index fbbb767..69d897f 100644 --- a/schemas/body-tree.schema.json +++ b/schemas/body-tree.schema.json @@ -126,6 +126,9 @@ { "$ref": "#/definitions/ContentTree.transit.Text" }, + { + "$ref": "#/definitions/ContentTree.transit.Timeline" + }, { "$ref": "#/definitions/ContentTree.transit.ImagePair" } @@ -1151,6 +1154,92 @@ ], "type": "object" }, + "ContentTree.transit.Timeline": { + "additionalProperties": false, + "description": "Timeline nodes display a timeline of events in arbitrary order.", + "properties": { + "children": { + "items": { + "$ref": "#/definitions/ContentTree.transit.TimelineEvent" + }, + "type": "array" + }, + "data": {}, + "title": { + "description": "The title for the timeline", + "type": "string" + }, + "type": { + "const": "timeline", + "type": "string" + } + }, + "required": [ + "children", + "title", + "type" + ], + "type": "object" + }, + "ContentTree.transit.TimelineEvent": { + "additionalProperties": false, + "description": "TimelineEvent is the representation of a single event in a Timeline.", + "properties": { + "children": { + "anyOf": [ + { + "items": [ + { + "$ref": "#/definitions/ContentTree.transit.Paragraph" + } + ], + "maxItems": 1, + "minItems": 1, + "type": "array" + }, + { + "items": [ + { + "$ref": "#/definitions/ContentTree.transit.Paragraph" + }, + { + "$ref": "#/definitions/ContentTree.transit.ImageSet" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + { + "items": [ + { + "$ref": "#/definitions/ContentTree.transit.ImageSet" + } + ], + "maxItems": 1, + "minItems": 1, + "type": "array" + } + ], + "description": "A paragraph, a paragraph and an image set, or just an image set" + }, + "data": {}, + "title": { + "description": "The title of the event", + "type": "string" + }, + "type": { + "const": "timeline-event", + "type": "string" + } + }, + "required": [ + "children", + "title", + "type" + ], + "type": "object" + }, "ContentTree.transit.Tweet": { "additionalProperties": false, "properties": { diff --git a/schemas/content-tree.schema.json b/schemas/content-tree.schema.json index c58cc64..b81d99c 100644 --- a/schemas/content-tree.schema.json +++ b/schemas/content-tree.schema.json @@ -151,6 +151,9 @@ { "$ref": "#/definitions/ContentTree.full.Text" }, + { + "$ref": "#/definitions/ContentTree.full.Timeline" + }, { "$ref": "#/definitions/ContentTree.full.ImagePair" } @@ -1932,6 +1935,92 @@ ], "type": "object" }, + "ContentTree.full.Timeline": { + "additionalProperties": false, + "description": "Timeline nodes display a timeline of events in arbitrary order.", + "properties": { + "children": { + "items": { + "$ref": "#/definitions/ContentTree.full.TimelineEvent" + }, + "type": "array" + }, + "data": {}, + "title": { + "description": "The title for the timeline", + "type": "string" + }, + "type": { + "const": "timeline", + "type": "string" + } + }, + "required": [ + "children", + "title", + "type" + ], + "type": "object" + }, + "ContentTree.full.TimelineEvent": { + "additionalProperties": false, + "description": "TimelineEvent is the representation of a single event in a Timeline.", + "properties": { + "children": { + "anyOf": [ + { + "items": [ + { + "$ref": "#/definitions/ContentTree.full.Paragraph" + } + ], + "maxItems": 1, + "minItems": 1, + "type": "array" + }, + { + "items": [ + { + "$ref": "#/definitions/ContentTree.full.Paragraph" + }, + { + "$ref": "#/definitions/ContentTree.full.ImageSet" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + { + "items": [ + { + "$ref": "#/definitions/ContentTree.full.ImageSet" + } + ], + "maxItems": 1, + "minItems": 1, + "type": "array" + } + ], + "description": "A paragraph, a paragraph and an image set, or just an image set" + }, + "data": {}, + "title": { + "description": "The title of the event", + "type": "string" + }, + "type": { + "const": "timeline-event", + "type": "string" + } + }, + "required": [ + "children", + "title", + "type" + ], + "type": "object" + }, "ContentTree.full.Tweet": { "additionalProperties": false, "properties": { diff --git a/schemas/transit-tree.schema.json b/schemas/transit-tree.schema.json index 8bebc00..aaf3fee 100644 --- a/schemas/transit-tree.schema.json +++ b/schemas/transit-tree.schema.json @@ -151,6 +151,9 @@ { "$ref": "#/definitions/ContentTree.transit.Text" }, + { + "$ref": "#/definitions/ContentTree.transit.Timeline" + }, { "$ref": "#/definitions/ContentTree.transit.ImagePair" } @@ -1176,6 +1179,92 @@ ], "type": "object" }, + "ContentTree.transit.Timeline": { + "additionalProperties": false, + "description": "Timeline nodes display a timeline of events in arbitrary order.", + "properties": { + "children": { + "items": { + "$ref": "#/definitions/ContentTree.transit.TimelineEvent" + }, + "type": "array" + }, + "data": {}, + "title": { + "description": "The title for the timeline", + "type": "string" + }, + "type": { + "const": "timeline", + "type": "string" + } + }, + "required": [ + "children", + "title", + "type" + ], + "type": "object" + }, + "ContentTree.transit.TimelineEvent": { + "additionalProperties": false, + "description": "TimelineEvent is the representation of a single event in a Timeline.", + "properties": { + "children": { + "anyOf": [ + { + "items": [ + { + "$ref": "#/definitions/ContentTree.transit.Paragraph" + } + ], + "maxItems": 1, + "minItems": 1, + "type": "array" + }, + { + "items": [ + { + "$ref": "#/definitions/ContentTree.transit.Paragraph" + }, + { + "$ref": "#/definitions/ContentTree.transit.ImageSet" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + { + "items": [ + { + "$ref": "#/definitions/ContentTree.transit.ImageSet" + } + ], + "maxItems": 1, + "minItems": 1, + "type": "array" + } + ], + "description": "A paragraph, a paragraph and an image set, or just an image set" + }, + "data": {}, + "title": { + "description": "The title of the event", + "type": "string" + }, + "type": { + "const": "timeline-event", + "type": "string" + } + }, + "required": [ + "children", + "title", + "type" + ], + "type": "object" + }, "ContentTree.transit.Tweet": { "additionalProperties": false, "properties": {