@financial-times/content-tree 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ The MIT License (MIT)
2
+ Copyright © 2026 The Financial Times Ltd
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,219 @@
1
+ # ![content-tree][logo]
2
+
3
+ **content-tree** is a specification that describes the shape of an FT article as an abstract tree. It implements the [unist](https://github.com/syntax-tree/unist) spec. It is intended to be a shared data contract across our CMS ([Spark](https://github.com/Financial-Times/spark)), Content APIs, and Front End systems.
4
+
5
+ To read the spec, go to [SPEC.md](./SPEC.md). You should read the spec if you are implementing an article renderer, or adding or amending an article component.
6
+
7
+ ---
8
+
9
+ ## Contents
10
+
11
+ - [Overview](#overview)
12
+ - [Concepts](#concepts)
13
+ - [Using `content-tree`](#using-content-tree)
14
+ - [Typescript](#typescript)
15
+ - [Go](#go-libraries)
16
+ - [JSON Schema](#json-schemas)
17
+ - [Contributing](#contributing)
18
+ - [Releasing](#releasing)
19
+ - [License](#license)
20
+
21
+ ## Overview
22
+
23
+ The main content-tree specification is defined in a markdown file
24
+
25
+ - [**`SPEC.md`**](./SPEC.md): a markdown document defining the specification for the tree. Written in a typescript-like grammar, augmented with a custom `[external](#concepts)` property modifier.
26
+
27
+ From this spec, we automatically generate the following as part of the build process:
28
+
29
+ - **`content-tree.d.ts`** - Typescript types, which are automatically generated from the markdown spec
30
+ - **`/schemas`** - JSON schemas, which are automatically generated from the markdown spec
31
+
32
+ The [Content & Metadata](https://biz-ops.in.ft.com/Team/content) team maintain some [Go](https://go.dev/) code to support working with content-tree inside Golang:
33
+ - **`content_tree.go`** - Go structs containing type definitions for the content tree spec to use in Golang applications. Manually updated by Content & Metadata.
34
+ - **`/libraries`** - Go libraries used to transform the content-tree data structure between other formats
35
+
36
+ Supporting code:
37
+ - **`/tests`** - tests to validate the schema and transformers
38
+ - **`/tools`** - utilities for building and generating the schemas and types
39
+
40
+
41
+ ## Concepts
42
+
43
+ ### (Abstract) Syntax Tree
44
+
45
+ An Abstract Syntax Tree, or AST, is a structured representation of content where the meaning and structure of content are represented as a tree, independent of any particular rendering instruction or representation.
46
+
47
+ Content Tree is an AST that represents the _semantic_ structure of an article - for example paragraphs, headings, images, other editorial components - without enforcing a particular markup language or visual representation. This makes it easier to use the same content across different products and platforms and contexts.
48
+
49
+ - **`Node`** - an element in the tree with a `type` that represents a unit of content
50
+ - **`Parent`** - a `Node` which has `children` nodes
51
+ - **`Root`** - the single, top-level `Node` in a tree
52
+
53
+ ### In Transit vs At Rest
54
+
55
+ An FT article may contain supplementary assets that are published independently of the content (e.g. images, video clips), or editorial components that pull in data from external systems (e.g. flourish charts, social media posts). When an article is being transmitted over the network (e.g. being fetched via the FT Content API), these external resources are typically referenced by `id`, and it is up to the consuming application to fetch these resources.
56
+
57
+ To support both of these use cases, content-tree can exist in different states: a **full** tree (with resolved external data) and a **transit** tree (where external resources are referenced by ID). The content and editorial intent remain the same across both states.
58
+
59
+ ### `external`
60
+
61
+ To distinguish between transit and full node properties, we use an additional modifier on our typescript properties, called `external`, which indicates that the property is omitted from the transit representation and must be supplied by the consuming application in order to construct a full tree.
62
+
63
+ **Example:** a tweet/X post will be published with an ID, but the actual content of the post should be fetched at render time from the X API.
64
+
65
+ <table>
66
+ <thead>
67
+ <tr>
68
+ <td><strong>spec</strong></td>
69
+ <td><strong>transit</strong></td>
70
+ <td><strong>full</strong></td>
71
+ <td><strong>loose</strong></td>
72
+ </tr>
73
+ <thead>
74
+ <tbody>
75
+ <tr>
76
+ <td>
77
+
78
+ ```ts
79
+ interface Tweet extends Node {
80
+ id: string
81
+ type: "tweet"
82
+ external html: string
83
+ }
84
+ ```
85
+ </td>
86
+
87
+ <td>
88
+
89
+ ```ts
90
+ interface Tweet extends Node {
91
+ id: string
92
+ type: "tweet"
93
+ }
94
+ ```
95
+ </td>
96
+ <td>
97
+
98
+ ```ts
99
+ interface Tweet extends Node {
100
+ id: string
101
+ type: "tweet"
102
+ html: string
103
+ }
104
+ ```
105
+ </td>
106
+ <td>
107
+
108
+ ```ts
109
+ interface Tweet extends Node {
110
+ id: string
111
+ type: "tweet"
112
+ html?: string
113
+ }
114
+ ```
115
+ </td>
116
+ </tr>
117
+ <thead>
118
+ </table>
119
+
120
+ ## Using `content-tree`
121
+
122
+ ### Typescript
123
+
124
+ This package provides typescript types in `content-tree.d.ts` that can be used to validate the shape of data in a JS/TS application. These types are automatically generated from [SPEC.md](./SPEC.md).
125
+
126
+ There are three different namespaces exposed, for the different states a tree can be in (see [In Transit vs At Rest](#in-transit-vs-at-rest))
127
+
128
+ - `ContentTree.transit` - contains only the fields that are published and available from the Content API's response
129
+ - `ContentTree.full` - contains the full representation of the content, including any data required from external resources (also exposed on the top level `ContentTree` namespace)
130
+ - `ContentTree.loose` - contains the full representation of the content, including any data required from external resources as optional
131
+
132
+
133
+ 1. Install this repository as a dependency:
134
+
135
+ ```sh notangle
136
+ npm install https://github.com/Financial-Times/content-tree
137
+ ```
138
+
139
+ 2. Use it in your Typescript / JSDoc code:
140
+
141
+ ```ts notangle
142
+ import type { ContentTree } from "@financial-times/content-tree"
143
+
144
+ function makeBigNumber(): ContentTree.BigNumber {
145
+ return {
146
+ type: "big-number", //will autocomplete in code editor, and be valid
147
+ number: "1.2m",
148
+ description: "People affected worldwide"
149
+ }
150
+ }
151
+
152
+ function makeImageSetNoFixins(): ContentTree.transit.ImageSet {
153
+ return {
154
+ type: "image-set",
155
+ id: "79acd774-6ca7-487d-a257-cbf64d2498d9",
156
+ // if you try to add a `picture` here it will get mad
157
+ }
158
+ }
159
+ ```
160
+
161
+ ### JSON Schema
162
+
163
+ There are also a few JSON schemas generated from the spec.
164
+
165
+ - `content-tree.schema.json` - JSON schema for the full content-tree shape
166
+ - `transit-tree.schema.json` - JSON schema for content-tree excluding any `external` properties
167
+ - `body-tree.schema.json` - JSON schema for the transit tree _without_ the `Root` node, containing just the [Body](./SPEC.md#body) definition. This schema defines the data returned in the `bodyTree` field of the FT `/content-tree` API.
168
+
169
+
170
+ ### Go Libraries
171
+
172
+ These libraries are designed for internal use by the Content & Metadata teams, and are used to ensure that all of our Content API representations are aligned with changes in the content-tree spec.
173
+
174
+ - `from-bodyxml` - converts the legacy `bodyXML` field from C&M's internal representation, to a valid `bodyTree` JSON.
175
+ - `to-external-bodyxml` - converts content-tree to a stable XML representation, used as a the `bodyXML` field in the FT's `/enrichedcontent` API
176
+ - `to-string` - converts content-tree to a plain text string
177
+
178
+ (TODO: how to install / use)
179
+
180
+
181
+ ## Contributing
182
+
183
+ To make a change to the content tree spec:
184
+
185
+ - Clone this repo and run `npm install`
186
+ - Update [SPEC.md](./SPEC.md) with your changes:
187
+ - To add a new formatting node, add the definition under the [`Formatting Blocks`](./SPEC.md#formatting-blocks). If it is formatting that can be applied to text in a paragraph, ensure it is added to the [`Phrasing`](./SPEC.md#phrasing) type
188
+ - To add a new storyblock, add the definition under the [`Storyblocks`](./SPEC.md#storyblocks). If the block can appear at the top level of the article body, ensure it is also added to the [`BodyBlock`](./SPEC.md#bodyblock) type definition
189
+ - Run `npm run build` to update `content-tree.d.ts` and (if required) the `schemas` files
190
+
191
+ Once the PR is created, liaise with the [Content & Metadata](https://biz-ops.in.ft.com/Team/content) team to ensure the relevant changes are made in the Go libraries and transformers.
192
+
193
+ For major or non-standard changes, consider creating an issue first, or discussing in the `#content-pipeline` Slack channel.
194
+
195
+ ## Releasing
196
+
197
+ A single semantic change to the content tree is usually split across multiple pull requests, with Go and JS changes implemented by separate teams. It is important therefore that releases are coordinated across C&M, Spark and CP to ensure that each release contains all the changes required to support the new version of the content tree.
198
+
199
+ Before making a release you will also need to raise a pull request to increment the package.json version number, this should be made in its own PR so that at least one member of each team can approve it. Please get sign-off in the [#dynamic-storytelling-team](https://financialtimes.enterprise.slack.com/archives/C035P5DCHMH) slack channel and share this PR there. Releases are then made via GitHub Releases: they must include a SemVer tag `vX.Y.Z` which matches the one within the package json. The release notes should be completed to communicate what changed in the release. Cutting a release will trigger CI to publish to npm for JS consumers. Go consumers can depend on the new git tag as the module version.
200
+
201
+ ## License
202
+
203
+ This software is published by the Financial Times under the [MIT licence](mit).
204
+
205
+ Derived from [unist][unist] © [Titus Wormer][titus]
206
+
207
+ [mit]: http://opensource.org/licenses/MIT
208
+ [titus]: https://wooorm.com
209
+ [logo]: ./logo.png
210
+ [unist]: https://github.com/syntax-tree/unist
211
+ [js]: https://www.ecma-international.org/ecma-262/9.0/index.html
212
+ [webidl]: https://heycam.github.io/webidl/
213
+ [term-tree]: https://github.com/syntax-tree/unist#tree
214
+ [term-literal]: https://github.com/syntax-tree/unist#tree
215
+ [term-parent]: https://github.com/syntax-tree/unist#parent
216
+ [term-child]: https://github.com/syntax-tree/unist#child
217
+ [term-root]: https://github.com/syntax-tree/unist#root
218
+ [term-leaf]: https://github.com/syntax-tree/unist#leaf
219
+ [unist-utilities]: https://github.com/syntax-tree/unist#utilities