From 218b1594af1e6eb245e1d0f494c5ca220f48b577 Mon Sep 17 00:00:00 2001 From: umbobabo Date: Mon, 22 Sep 2025 15:18:50 +0100 Subject: [PATCH 1/4] feat: add timeline and event nodes --- README.md | 56 +++++++++++++++++++-------- content-tree.d.ts | 52 +++++++++++++++++++++++-- schemas/body-tree.schema.json | 66 ++++++++++++++++++++++++++++++++ schemas/content-tree.schema.json | 66 ++++++++++++++++++++++++++++++++ schemas/transit-tree.schema.json | 66 ++++++++++++++++++++++++++++++++ 5 files changed, 287 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 7e2e08c..8807988 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ type BodyBlock = | Video | YoutubeVideo | Text + | Timeline ``` `BodyBlock` nodes are the only things that are valid as the top level of a `Body`. @@ -736,24 +737,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 } ``` @@ -762,6 +763,31 @@ interface CustomCodeComponent extends Node { - The basic interface in Spark to make reference to this system above (eg. the git repo URL or a public S3 bucket), and provide some data for it if necessary. This will be the Custom Component storyblock. - The data Spark receives from entering a specific ID will be used to render dynamic fields (the `attributes`). +### Timeline + +```ts +type TimelineLayoutWidth = Extract + +interface Timeline extends Parent { + type: "timeline" + layoutWidth: TimelineLayoutWidth + children: [Heading, ...TimelineEvent[]] +} +``` + +**Timeline** nodes display a timeline of events in arbitrary order. + +### TimelineEvent + +```ts +interface TimelineEvent extends Parent { + type: "timeline-event" + dateLabel: string + children: Paragraph[] +} +``` + +**TimelineEvent** nodes represents a single event in a timeline. ## License diff --git a/content-tree.d.ts b/content-tree.d.ts index bbe0cff..abe9cd2 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 | Tweet | Video | YoutubeVideo | Text; + type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | Text | Timeline; 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 { @@ -278,8 +278,19 @@ export declare namespace ContentTree { /** Configuration data to be passed to the component. */ attributes: CustomCodeComponentAttributes; } + type TimelineLayoutWidth = Extract; + interface Timeline extends Parent { + type: "timeline"; + layoutWidth: TimelineLayoutWidth; + children: [Heading, ...Event[]]; + } + interface Event extends Parent { + type: "event"; + dateLabel: string; + children: Paragraph[]; + } namespace full { - type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | Text; + type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | Text | Timeline; 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 { @@ -558,9 +569,20 @@ export declare namespace ContentTree { /** Configuration data to be passed to the component. */ attributes: CustomCodeComponentAttributes; } + type TimelineLayoutWidth = Extract; + interface Timeline extends Parent { + type: "timeline"; + layoutWidth: TimelineLayoutWidth; + children: [Heading, ...Event[]]; + } + interface Event extends Parent { + type: "event"; + dateLabel: string; + children: Paragraph[]; + } } namespace transit { - type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | Text; + type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | Text | Timeline; 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 { @@ -824,9 +846,20 @@ export declare namespace ContentTree { /** How the component should be presented in the article page according to the column layout system */ layoutWidth: LayoutWidth; } + type TimelineLayoutWidth = Extract; + interface Timeline extends Parent { + type: "timeline"; + layoutWidth: TimelineLayoutWidth; + children: [Heading, ...Event[]]; + } + interface Event extends Parent { + type: "event"; + dateLabel: string; + children: Paragraph[]; + } } namespace loose { - type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | Text; + type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | Text | Timeline; 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 { @@ -1105,5 +1138,16 @@ export declare namespace ContentTree { /** Configuration data to be passed to the component. */ attributes?: CustomCodeComponentAttributes; } + type TimelineLayoutWidth = Extract; + interface Timeline extends Parent { + type: "timeline"; + layoutWidth: TimelineLayoutWidth; + children: [Heading, ...Event[]]; + } + interface Event extends Parent { + type: "event"; + dateLabel: string; + children: Paragraph[]; + } } } diff --git a/schemas/body-tree.schema.json b/schemas/body-tree.schema.json index 1c331ad..351852e 100644 --- a/schemas/body-tree.schema.json +++ b/schemas/body-tree.schema.json @@ -122,6 +122,9 @@ }, { "$ref": "#/definitions/ContentTree.transit.Text" + }, + { + "$ref": "#/definitions/ContentTree.transit.Timeline" } ] }, @@ -185,6 +188,31 @@ ], "type": "object" }, + "ContentTree.transit.Event": { + "additionalProperties": false, + "properties": { + "children": { + "items": { + "$ref": "#/definitions/ContentTree.transit.Paragraph" + }, + "type": "array" + }, + "data": {}, + "dateLabel": { + "type": "string" + }, + "type": { + "const": "event", + "type": "string" + } + }, + "required": [ + "children", + "dateLabel", + "type" + ], + "type": "object" + }, "ContentTree.transit.Flourish": { "additionalProperties": false, "properties": { @@ -1089,6 +1117,44 @@ ], "type": "object" }, + "ContentTree.transit.Timeline": { + "additionalProperties": false, + "properties": { + "children": { + "additionalItems": { + "$ref": "#/definitions/ContentTree.transit.Event" + }, + "items": [ + { + "$ref": "#/definitions/ContentTree.transit.Heading" + } + ], + "minItems": 1, + "type": "array" + }, + "data": {}, + "layoutWidth": { + "$ref": "#/definitions/ContentTree.transit.TimelineLayoutWidth" + }, + "type": { + "const": "timeline", + "type": "string" + } + }, + "required": [ + "children", + "layoutWidth", + "type" + ], + "type": "object" + }, + "ContentTree.transit.TimelineLayoutWidth": { + "enum": [ + "full-width", + "inset-left" + ], + "type": "string" + }, "ContentTree.transit.Tweet": { "additionalProperties": false, "properties": { diff --git a/schemas/content-tree.schema.json b/schemas/content-tree.schema.json index 703a637..881ddf5 100644 --- a/schemas/content-tree.schema.json +++ b/schemas/content-tree.schema.json @@ -147,6 +147,9 @@ }, { "$ref": "#/definitions/ContentTree.full.Text" + }, + { + "$ref": "#/definitions/ContentTree.full.Timeline" } ] }, @@ -236,6 +239,31 @@ ], "type": "object" }, + "ContentTree.full.Event": { + "additionalProperties": false, + "properties": { + "children": { + "items": { + "$ref": "#/definitions/ContentTree.full.Paragraph" + }, + "type": "array" + }, + "data": {}, + "dateLabel": { + "type": "string" + }, + "type": { + "const": "event", + "type": "string" + } + }, + "required": [ + "children", + "dateLabel", + "type" + ], + "type": "object" + }, "ContentTree.full.Flourish": { "additionalProperties": false, "properties": { @@ -1867,6 +1895,44 @@ ], "type": "object" }, + "ContentTree.full.Timeline": { + "additionalProperties": false, + "properties": { + "children": { + "additionalItems": { + "$ref": "#/definitions/ContentTree.full.Event" + }, + "items": [ + { + "$ref": "#/definitions/ContentTree.full.Heading" + } + ], + "minItems": 1, + "type": "array" + }, + "data": {}, + "layoutWidth": { + "$ref": "#/definitions/ContentTree.full.TimelineLayoutWidth" + }, + "type": { + "const": "timeline", + "type": "string" + } + }, + "required": [ + "children", + "layoutWidth", + "type" + ], + "type": "object" + }, + "ContentTree.full.TimelineLayoutWidth": { + "enum": [ + "full-width", + "inset-left" + ], + "type": "string" + }, "ContentTree.full.Tweet": { "additionalProperties": false, "properties": { diff --git a/schemas/transit-tree.schema.json b/schemas/transit-tree.schema.json index 273226b..e53c778 100644 --- a/schemas/transit-tree.schema.json +++ b/schemas/transit-tree.schema.json @@ -147,6 +147,9 @@ }, { "$ref": "#/definitions/ContentTree.transit.Text" + }, + { + "$ref": "#/definitions/ContentTree.transit.Timeline" } ] }, @@ -210,6 +213,31 @@ ], "type": "object" }, + "ContentTree.transit.Event": { + "additionalProperties": false, + "properties": { + "children": { + "items": { + "$ref": "#/definitions/ContentTree.transit.Paragraph" + }, + "type": "array" + }, + "data": {}, + "dateLabel": { + "type": "string" + }, + "type": { + "const": "event", + "type": "string" + } + }, + "required": [ + "children", + "dateLabel", + "type" + ], + "type": "object" + }, "ContentTree.transit.Flourish": { "additionalProperties": false, "properties": { @@ -1114,6 +1142,44 @@ ], "type": "object" }, + "ContentTree.transit.Timeline": { + "additionalProperties": false, + "properties": { + "children": { + "additionalItems": { + "$ref": "#/definitions/ContentTree.transit.Event" + }, + "items": [ + { + "$ref": "#/definitions/ContentTree.transit.Heading" + } + ], + "minItems": 1, + "type": "array" + }, + "data": {}, + "layoutWidth": { + "$ref": "#/definitions/ContentTree.transit.TimelineLayoutWidth" + }, + "type": { + "const": "timeline", + "type": "string" + } + }, + "required": [ + "children", + "layoutWidth", + "type" + ], + "type": "object" + }, + "ContentTree.transit.TimelineLayoutWidth": { + "enum": [ + "full-width", + "inset-left" + ], + "type": "string" + }, "ContentTree.transit.Tweet": { "additionalProperties": false, "properties": { From 92150dd581bb90b6bbdf5e4733d90b1ee90317ca Mon Sep 17 00:00:00 2001 From: umbobabo Date: Fri, 26 Sep 2025 13:07:25 +0000 Subject: [PATCH 2/4] Update TypeScript definition file --- content-tree.d.ts | 32 +++++++++---------- schemas/body-tree.schema.json | 53 ++++++++++++++++---------------- schemas/content-tree.schema.json | 53 ++++++++++++++++---------------- schemas/transit-tree.schema.json | 53 ++++++++++++++++---------------- 4 files changed, 97 insertions(+), 94 deletions(-) diff --git a/content-tree.d.ts b/content-tree.d.ts index abe9cd2..7e67da0 100644 --- a/content-tree.d.ts +++ b/content-tree.d.ts @@ -278,14 +278,14 @@ export declare namespace ContentTree { /** Configuration data to be passed to the component. */ attributes: CustomCodeComponentAttributes; } - type TimelineLayoutWidth = Extract; + type TimelineLayoutWidth = Extract; interface Timeline extends Parent { type: "timeline"; layoutWidth: TimelineLayoutWidth; - children: [Heading, ...Event[]]; + children: [Heading, ...TimelineEvent[]]; } - interface Event extends Parent { - type: "event"; + interface TimelineEvent extends Parent { + type: "timeline-event"; dateLabel: string; children: Paragraph[]; } @@ -569,14 +569,14 @@ export declare namespace ContentTree { /** Configuration data to be passed to the component. */ attributes: CustomCodeComponentAttributes; } - type TimelineLayoutWidth = Extract; + type TimelineLayoutWidth = Extract; interface Timeline extends Parent { type: "timeline"; layoutWidth: TimelineLayoutWidth; - children: [Heading, ...Event[]]; + children: [Heading, ...TimelineEvent[]]; } - interface Event extends Parent { - type: "event"; + interface TimelineEvent extends Parent { + type: "timeline-event"; dateLabel: string; children: Paragraph[]; } @@ -846,14 +846,14 @@ export declare namespace ContentTree { /** How the component should be presented in the article page according to the column layout system */ layoutWidth: LayoutWidth; } - type TimelineLayoutWidth = Extract; + type TimelineLayoutWidth = Extract; interface Timeline extends Parent { type: "timeline"; layoutWidth: TimelineLayoutWidth; - children: [Heading, ...Event[]]; + children: [Heading, ...TimelineEvent[]]; } - interface Event extends Parent { - type: "event"; + interface TimelineEvent extends Parent { + type: "timeline-event"; dateLabel: string; children: Paragraph[]; } @@ -1138,14 +1138,14 @@ export declare namespace ContentTree { /** Configuration data to be passed to the component. */ attributes?: CustomCodeComponentAttributes; } - type TimelineLayoutWidth = Extract; + type TimelineLayoutWidth = Extract; interface Timeline extends Parent { type: "timeline"; layoutWidth: TimelineLayoutWidth; - children: [Heading, ...Event[]]; + children: [Heading, ...TimelineEvent[]]; } - interface Event extends Parent { - type: "event"; + interface TimelineEvent extends Parent { + type: "timeline-event"; dateLabel: string; children: Paragraph[]; } diff --git a/schemas/body-tree.schema.json b/schemas/body-tree.schema.json index 351852e..9f2a5c0 100644 --- a/schemas/body-tree.schema.json +++ b/schemas/body-tree.schema.json @@ -188,31 +188,6 @@ ], "type": "object" }, - "ContentTree.transit.Event": { - "additionalProperties": false, - "properties": { - "children": { - "items": { - "$ref": "#/definitions/ContentTree.transit.Paragraph" - }, - "type": "array" - }, - "data": {}, - "dateLabel": { - "type": "string" - }, - "type": { - "const": "event", - "type": "string" - } - }, - "required": [ - "children", - "dateLabel", - "type" - ], - "type": "object" - }, "ContentTree.transit.Flourish": { "additionalProperties": false, "properties": { @@ -1122,7 +1097,7 @@ "properties": { "children": { "additionalItems": { - "$ref": "#/definitions/ContentTree.transit.Event" + "$ref": "#/definitions/ContentTree.transit.TimelineEvent" }, "items": [ { @@ -1148,8 +1123,34 @@ ], "type": "object" }, + "ContentTree.transit.TimelineEvent": { + "additionalProperties": false, + "properties": { + "children": { + "items": { + "$ref": "#/definitions/ContentTree.transit.Paragraph" + }, + "type": "array" + }, + "data": {}, + "dateLabel": { + "type": "string" + }, + "type": { + "const": "timeline-event", + "type": "string" + } + }, + "required": [ + "children", + "dateLabel", + "type" + ], + "type": "object" + }, "ContentTree.transit.TimelineLayoutWidth": { "enum": [ + "full-grid", "full-width", "inset-left" ], diff --git a/schemas/content-tree.schema.json b/schemas/content-tree.schema.json index 881ddf5..097ad9e 100644 --- a/schemas/content-tree.schema.json +++ b/schemas/content-tree.schema.json @@ -239,31 +239,6 @@ ], "type": "object" }, - "ContentTree.full.Event": { - "additionalProperties": false, - "properties": { - "children": { - "items": { - "$ref": "#/definitions/ContentTree.full.Paragraph" - }, - "type": "array" - }, - "data": {}, - "dateLabel": { - "type": "string" - }, - "type": { - "const": "event", - "type": "string" - } - }, - "required": [ - "children", - "dateLabel", - "type" - ], - "type": "object" - }, "ContentTree.full.Flourish": { "additionalProperties": false, "properties": { @@ -1900,7 +1875,7 @@ "properties": { "children": { "additionalItems": { - "$ref": "#/definitions/ContentTree.full.Event" + "$ref": "#/definitions/ContentTree.full.TimelineEvent" }, "items": [ { @@ -1926,8 +1901,34 @@ ], "type": "object" }, + "ContentTree.full.TimelineEvent": { + "additionalProperties": false, + "properties": { + "children": { + "items": { + "$ref": "#/definitions/ContentTree.full.Paragraph" + }, + "type": "array" + }, + "data": {}, + "dateLabel": { + "type": "string" + }, + "type": { + "const": "timeline-event", + "type": "string" + } + }, + "required": [ + "children", + "dateLabel", + "type" + ], + "type": "object" + }, "ContentTree.full.TimelineLayoutWidth": { "enum": [ + "full-grid", "full-width", "inset-left" ], diff --git a/schemas/transit-tree.schema.json b/schemas/transit-tree.schema.json index e53c778..1abd053 100644 --- a/schemas/transit-tree.schema.json +++ b/schemas/transit-tree.schema.json @@ -213,31 +213,6 @@ ], "type": "object" }, - "ContentTree.transit.Event": { - "additionalProperties": false, - "properties": { - "children": { - "items": { - "$ref": "#/definitions/ContentTree.transit.Paragraph" - }, - "type": "array" - }, - "data": {}, - "dateLabel": { - "type": "string" - }, - "type": { - "const": "event", - "type": "string" - } - }, - "required": [ - "children", - "dateLabel", - "type" - ], - "type": "object" - }, "ContentTree.transit.Flourish": { "additionalProperties": false, "properties": { @@ -1147,7 +1122,7 @@ "properties": { "children": { "additionalItems": { - "$ref": "#/definitions/ContentTree.transit.Event" + "$ref": "#/definitions/ContentTree.transit.TimelineEvent" }, "items": [ { @@ -1173,8 +1148,34 @@ ], "type": "object" }, + "ContentTree.transit.TimelineEvent": { + "additionalProperties": false, + "properties": { + "children": { + "items": { + "$ref": "#/definitions/ContentTree.transit.Paragraph" + }, + "type": "array" + }, + "data": {}, + "dateLabel": { + "type": "string" + }, + "type": { + "const": "timeline-event", + "type": "string" + } + }, + "required": [ + "children", + "dateLabel", + "type" + ], + "type": "object" + }, "ContentTree.transit.TimelineLayoutWidth": { "enum": [ + "full-grid", "full-width", "inset-left" ], From c01e9aad406f90fa72ed85395d0ea720af7f71b4 Mon Sep 17 00:00:00 2001 From: umbobabo Date: Tue, 14 Oct 2025 14:19:01 +0100 Subject: [PATCH 3/4] feat: support Imageset in Timeline and title for initial field instead of date --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8807988..854b561 100644 --- a/README.md +++ b/README.md @@ -782,8 +782,8 @@ interface Timeline extends Parent { ```ts interface TimelineEvent extends Parent { type: "timeline-event" - dateLabel: string - children: Paragraph[] + title: string + children: (Paragraph | ImageSet)[] } ``` From c2a6944a2f6dae4594e5373d7d984d07e89cd546 Mon Sep 17 00:00:00 2001 From: umbobabo Date: Tue, 14 Oct 2025 14:35:54 +0100 Subject: [PATCH 4/4] doc: JSDO doc approach --- README.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 854b561..f0dba0d 100644 --- a/README.md +++ b/README.md @@ -766,20 +766,31 @@ interface CustomCodeComponent extends Node { ### Timeline ```ts +/** + * Allowed layout widths for a Timeline. + * @typedef {'full-width' | 'inset-left' | 'full-grid'} TimelineLayoutWidth + */ type TimelineLayoutWidth = Extract +/** + * Timeline nodes display a timeline of events in arbitrary order. + * + * @typedef Timeline + * @property {TimelineLayoutWidth} layoutWidth - How the component should be presented. + **/ interface Timeline extends Parent { type: "timeline" layoutWidth: TimelineLayoutWidth children: [Heading, ...TimelineEvent[]] } -``` - -**Timeline** nodes display a timeline of events in arbitrary order. - -### TimelineEvent -```ts +/** + * TimelineEvent is the representation of a single event + * + * @typedef TimelineEvent + * @property {string} title - The title of the event + * @property {(Paragraph | ImageSet)[]} children - Elements that describe the event + **/ interface TimelineEvent extends Parent { type: "timeline-event" title: string @@ -787,8 +798,6 @@ interface TimelineEvent extends Parent { } ``` -**TimelineEvent** nodes represents a single event in a timeline. - ## License This software is published by the Financial Times under the [MIT licence](mit).