@terminalfour/terminalfour-js 1.0.0-rc.1

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.
Files changed (253) hide show
  1. package/LICENSE.md +106 -0
  2. package/README.md +169 -0
  3. package/dist/cjs/element-resolver.d.ts +115 -0
  4. package/dist/cjs/element-resolver.d.ts.map +1 -0
  5. package/dist/cjs/element-resolver.js +391 -0
  6. package/dist/cjs/element-resolver.js.map +1 -0
  7. package/dist/cjs/errors.d.ts +21 -0
  8. package/dist/cjs/errors.d.ts.map +1 -0
  9. package/dist/cjs/errors.js +21 -0
  10. package/dist/cjs/errors.js.map +1 -0
  11. package/dist/cjs/handlebars.d.ts +123 -0
  12. package/dist/cjs/handlebars.d.ts.map +1 -0
  13. package/dist/cjs/handlebars.js +306 -0
  14. package/dist/cjs/handlebars.js.map +1 -0
  15. package/dist/cjs/http-client.d.ts +21 -0
  16. package/dist/cjs/http-client.d.ts.map +1 -0
  17. package/dist/cjs/http-client.js +126 -0
  18. package/dist/cjs/http-client.js.map +1 -0
  19. package/dist/cjs/index.d.ts +37 -0
  20. package/dist/cjs/index.d.ts.map +1 -0
  21. package/dist/cjs/index.js +55 -0
  22. package/dist/cjs/index.js.map +1 -0
  23. package/dist/cjs/media-category-ref.d.ts +78 -0
  24. package/dist/cjs/media-category-ref.d.ts.map +1 -0
  25. package/dist/cjs/media-category-ref.js +184 -0
  26. package/dist/cjs/media-category-ref.js.map +1 -0
  27. package/dist/cjs/media-library.d.ts +30 -0
  28. package/dist/cjs/media-library.d.ts.map +1 -0
  29. package/dist/cjs/media-library.js +77 -0
  30. package/dist/cjs/media-library.js.map +1 -0
  31. package/dist/cjs/models/content-item.d.ts +80 -0
  32. package/dist/cjs/models/content-item.d.ts.map +1 -0
  33. package/dist/cjs/models/content-item.js +682 -0
  34. package/dist/cjs/models/content-item.js.map +1 -0
  35. package/dist/cjs/models/media-category-item.d.ts +29 -0
  36. package/dist/cjs/models/media-category-item.d.ts.map +1 -0
  37. package/dist/cjs/models/media-category-item.js +33 -0
  38. package/dist/cjs/models/media-category-item.js.map +1 -0
  39. package/dist/cjs/models/media-item.d.ts +74 -0
  40. package/dist/cjs/models/media-item.d.ts.map +1 -0
  41. package/dist/cjs/models/media-item.js +188 -0
  42. package/dist/cjs/models/media-item.js.map +1 -0
  43. package/dist/cjs/models/section-item.d.ts +58 -0
  44. package/dist/cjs/models/section-item.d.ts.map +1 -0
  45. package/dist/cjs/models/section-item.js +166 -0
  46. package/dist/cjs/models/section-item.js.map +1 -0
  47. package/dist/cjs/package.json +1 -0
  48. package/dist/cjs/resources/channel-resource.d.ts +112 -0
  49. package/dist/cjs/resources/channel-resource.d.ts.map +1 -0
  50. package/dist/cjs/resources/channel-resource.js +107 -0
  51. package/dist/cjs/resources/channel-resource.js.map +1 -0
  52. package/dist/cjs/resources/content-resource.d.ts +50 -0
  53. package/dist/cjs/resources/content-resource.d.ts.map +1 -0
  54. package/dist/cjs/resources/content-resource.js +286 -0
  55. package/dist/cjs/resources/content-resource.js.map +1 -0
  56. package/dist/cjs/resources/content-type-resource.d.ts +283 -0
  57. package/dist/cjs/resources/content-type-resource.d.ts.map +1 -0
  58. package/dist/cjs/resources/content-type-resource.js +970 -0
  59. package/dist/cjs/resources/content-type-resource.js.map +1 -0
  60. package/dist/cjs/resources/group-resource.d.ts +96 -0
  61. package/dist/cjs/resources/group-resource.d.ts.map +1 -0
  62. package/dist/cjs/resources/group-resource.js +213 -0
  63. package/dist/cjs/resources/group-resource.js.map +1 -0
  64. package/dist/cjs/resources/list-resource.d.ts +111 -0
  65. package/dist/cjs/resources/list-resource.d.ts.map +1 -0
  66. package/dist/cjs/resources/list-resource.js +179 -0
  67. package/dist/cjs/resources/list-resource.js.map +1 -0
  68. package/dist/cjs/resources/media-resource.d.ts +69 -0
  69. package/dist/cjs/resources/media-resource.d.ts.map +1 -0
  70. package/dist/cjs/resources/media-resource.js +210 -0
  71. package/dist/cjs/resources/media-resource.js.map +1 -0
  72. package/dist/cjs/resources/media-type-resource.d.ts +70 -0
  73. package/dist/cjs/resources/media-type-resource.d.ts.map +1 -0
  74. package/dist/cjs/resources/media-type-resource.js +195 -0
  75. package/dist/cjs/resources/media-type-resource.js.map +1 -0
  76. package/dist/cjs/resources/navigation-resource.d.ts +664 -0
  77. package/dist/cjs/resources/navigation-resource.d.ts.map +1 -0
  78. package/dist/cjs/resources/navigation-resource.js +2349 -0
  79. package/dist/cjs/resources/navigation-resource.js.map +1 -0
  80. package/dist/cjs/resources/page-layout-resource.d.ts +83 -0
  81. package/dist/cjs/resources/page-layout-resource.d.ts.map +1 -0
  82. package/dist/cjs/resources/page-layout-resource.js +214 -0
  83. package/dist/cjs/resources/page-layout-resource.js.map +1 -0
  84. package/dist/cjs/resources/user-resource.d.ts +126 -0
  85. package/dist/cjs/resources/user-resource.d.ts.map +1 -0
  86. package/dist/cjs/resources/user-resource.js +317 -0
  87. package/dist/cjs/resources/user-resource.js.map +1 -0
  88. package/dist/cjs/section-ref.d.ts +185 -0
  89. package/dist/cjs/section-ref.d.ts.map +1 -0
  90. package/dist/cjs/section-ref.js +813 -0
  91. package/dist/cjs/section-ref.js.map +1 -0
  92. package/dist/cjs/site-structure.d.ts +18 -0
  93. package/dist/cjs/site-structure.d.ts.map +1 -0
  94. package/dist/cjs/site-structure.js +57 -0
  95. package/dist/cjs/site-structure.js.map +1 -0
  96. package/dist/cjs/t4-client.d.ts +121 -0
  97. package/dist/cjs/t4-client.d.ts.map +1 -0
  98. package/dist/cjs/t4-client.js +190 -0
  99. package/dist/cjs/t4-client.js.map +1 -0
  100. package/dist/cjs/type-registry.d.ts +31 -0
  101. package/dist/cjs/type-registry.d.ts.map +1 -0
  102. package/dist/cjs/type-registry.js +76 -0
  103. package/dist/cjs/type-registry.js.map +1 -0
  104. package/dist/cjs/types.d.ts +211 -0
  105. package/dist/cjs/types.d.ts.map +1 -0
  106. package/dist/cjs/types.js +3 -0
  107. package/dist/cjs/types.js.map +1 -0
  108. package/dist/cjs/utils.d.ts +147 -0
  109. package/dist/cjs/utils.d.ts.map +1 -0
  110. package/dist/cjs/utils.js +408 -0
  111. package/dist/cjs/utils.js.map +1 -0
  112. package/dist/esm/element-resolver.d.ts +115 -0
  113. package/dist/esm/element-resolver.d.ts.map +1 -0
  114. package/dist/esm/element-resolver.js +387 -0
  115. package/dist/esm/element-resolver.js.map +1 -0
  116. package/dist/esm/errors.d.ts +21 -0
  117. package/dist/esm/errors.d.ts.map +1 -0
  118. package/dist/esm/errors.js +17 -0
  119. package/dist/esm/errors.js.map +1 -0
  120. package/dist/esm/handlebars.d.ts +123 -0
  121. package/dist/esm/handlebars.d.ts.map +1 -0
  122. package/dist/esm/handlebars.js +300 -0
  123. package/dist/esm/handlebars.js.map +1 -0
  124. package/dist/esm/http-client.d.ts +21 -0
  125. package/dist/esm/http-client.d.ts.map +1 -0
  126. package/dist/esm/http-client.js +122 -0
  127. package/dist/esm/http-client.js.map +1 -0
  128. package/dist/esm/index.d.ts +37 -0
  129. package/dist/esm/index.d.ts.map +1 -0
  130. package/dist/esm/index.js +26 -0
  131. package/dist/esm/index.js.map +1 -0
  132. package/dist/esm/media-category-ref.d.ts +78 -0
  133. package/dist/esm/media-category-ref.d.ts.map +1 -0
  134. package/dist/esm/media-category-ref.js +180 -0
  135. package/dist/esm/media-category-ref.js.map +1 -0
  136. package/dist/esm/media-library.d.ts +30 -0
  137. package/dist/esm/media-library.d.ts.map +1 -0
  138. package/dist/esm/media-library.js +73 -0
  139. package/dist/esm/media-library.js.map +1 -0
  140. package/dist/esm/models/content-item.d.ts +80 -0
  141. package/dist/esm/models/content-item.d.ts.map +1 -0
  142. package/dist/esm/models/content-item.js +676 -0
  143. package/dist/esm/models/content-item.js.map +1 -0
  144. package/dist/esm/models/media-category-item.d.ts +29 -0
  145. package/dist/esm/models/media-category-item.d.ts.map +1 -0
  146. package/dist/esm/models/media-category-item.js +29 -0
  147. package/dist/esm/models/media-category-item.js.map +1 -0
  148. package/dist/esm/models/media-item.d.ts +74 -0
  149. package/dist/esm/models/media-item.d.ts.map +1 -0
  150. package/dist/esm/models/media-item.js +184 -0
  151. package/dist/esm/models/media-item.js.map +1 -0
  152. package/dist/esm/models/section-item.d.ts +58 -0
  153. package/dist/esm/models/section-item.d.ts.map +1 -0
  154. package/dist/esm/models/section-item.js +162 -0
  155. package/dist/esm/models/section-item.js.map +1 -0
  156. package/dist/esm/resources/channel-resource.d.ts +112 -0
  157. package/dist/esm/resources/channel-resource.d.ts.map +1 -0
  158. package/dist/esm/resources/channel-resource.js +102 -0
  159. package/dist/esm/resources/channel-resource.js.map +1 -0
  160. package/dist/esm/resources/content-resource.d.ts +50 -0
  161. package/dist/esm/resources/content-resource.d.ts.map +1 -0
  162. package/dist/esm/resources/content-resource.js +282 -0
  163. package/dist/esm/resources/content-resource.js.map +1 -0
  164. package/dist/esm/resources/content-type-resource.d.ts +283 -0
  165. package/dist/esm/resources/content-type-resource.d.ts.map +1 -0
  166. package/dist/esm/resources/content-type-resource.js +964 -0
  167. package/dist/esm/resources/content-type-resource.js.map +1 -0
  168. package/dist/esm/resources/group-resource.d.ts +96 -0
  169. package/dist/esm/resources/group-resource.d.ts.map +1 -0
  170. package/dist/esm/resources/group-resource.js +208 -0
  171. package/dist/esm/resources/group-resource.js.map +1 -0
  172. package/dist/esm/resources/list-resource.d.ts +111 -0
  173. package/dist/esm/resources/list-resource.d.ts.map +1 -0
  174. package/dist/esm/resources/list-resource.js +174 -0
  175. package/dist/esm/resources/list-resource.js.map +1 -0
  176. package/dist/esm/resources/media-resource.d.ts +69 -0
  177. package/dist/esm/resources/media-resource.d.ts.map +1 -0
  178. package/dist/esm/resources/media-resource.js +206 -0
  179. package/dist/esm/resources/media-resource.js.map +1 -0
  180. package/dist/esm/resources/media-type-resource.d.ts +70 -0
  181. package/dist/esm/resources/media-type-resource.d.ts.map +1 -0
  182. package/dist/esm/resources/media-type-resource.js +190 -0
  183. package/dist/esm/resources/media-type-resource.js.map +1 -0
  184. package/dist/esm/resources/navigation-resource.d.ts +664 -0
  185. package/dist/esm/resources/navigation-resource.d.ts.map +1 -0
  186. package/dist/esm/resources/navigation-resource.js +2344 -0
  187. package/dist/esm/resources/navigation-resource.js.map +1 -0
  188. package/dist/esm/resources/page-layout-resource.d.ts +83 -0
  189. package/dist/esm/resources/page-layout-resource.d.ts.map +1 -0
  190. package/dist/esm/resources/page-layout-resource.js +209 -0
  191. package/dist/esm/resources/page-layout-resource.js.map +1 -0
  192. package/dist/esm/resources/user-resource.d.ts +126 -0
  193. package/dist/esm/resources/user-resource.d.ts.map +1 -0
  194. package/dist/esm/resources/user-resource.js +312 -0
  195. package/dist/esm/resources/user-resource.js.map +1 -0
  196. package/dist/esm/section-ref.d.ts +185 -0
  197. package/dist/esm/section-ref.d.ts.map +1 -0
  198. package/dist/esm/section-ref.js +808 -0
  199. package/dist/esm/section-ref.js.map +1 -0
  200. package/dist/esm/site-structure.d.ts +18 -0
  201. package/dist/esm/site-structure.d.ts.map +1 -0
  202. package/dist/esm/site-structure.js +53 -0
  203. package/dist/esm/site-structure.js.map +1 -0
  204. package/dist/esm/t4-client.d.ts +121 -0
  205. package/dist/esm/t4-client.d.ts.map +1 -0
  206. package/dist/esm/t4-client.js +186 -0
  207. package/dist/esm/t4-client.js.map +1 -0
  208. package/dist/esm/type-registry.d.ts +31 -0
  209. package/dist/esm/type-registry.d.ts.map +1 -0
  210. package/dist/esm/type-registry.js +72 -0
  211. package/dist/esm/type-registry.js.map +1 -0
  212. package/dist/esm/types.d.ts +211 -0
  213. package/dist/esm/types.d.ts.map +1 -0
  214. package/dist/esm/types.js +2 -0
  215. package/dist/esm/types.js.map +1 -0
  216. package/dist/esm/utils.d.ts +147 -0
  217. package/dist/esm/utils.d.ts.map +1 -0
  218. package/dist/esm/utils.js +355 -0
  219. package/dist/esm/utils.js.map +1 -0
  220. package/docs/channels.md +62 -0
  221. package/docs/content-types.md +313 -0
  222. package/docs/content.md +199 -0
  223. package/docs/error-handling.md +86 -0
  224. package/docs/getting-started.md +146 -0
  225. package/docs/groups-and-users.md +167 -0
  226. package/docs/handlebars.md +145 -0
  227. package/docs/lists.md +98 -0
  228. package/docs/media-types.md +111 -0
  229. package/docs/media.md +169 -0
  230. package/docs/navigation/a-to-z.md +62 -0
  231. package/docs/navigation/breadcrumbs.md +67 -0
  232. package/docs/navigation/css-selector.md +53 -0
  233. package/docs/navigation/generate-file.md +48 -0
  234. package/docs/navigation/keyword-search.md +134 -0
  235. package/docs/navigation/language-switcher.md +37 -0
  236. package/docs/navigation/link-menu.md +105 -0
  237. package/docs/navigation/pagination.md +80 -0
  238. package/docs/navigation/previous-next-fulltext.md +43 -0
  239. package/docs/navigation/publish-to-one-file.md +93 -0
  240. package/docs/navigation/related-content.md +79 -0
  241. package/docs/navigation/related-section-branch.md +31 -0
  242. package/docs/navigation/return-to-index.md +35 -0
  243. package/docs/navigation/section-details.md +51 -0
  244. package/docs/navigation/section-iterator.md +33 -0
  245. package/docs/navigation/section-meta-info.md +39 -0
  246. package/docs/navigation/site-map.md +58 -0
  247. package/docs/navigation/top-content.md +80 -0
  248. package/docs/navigation/top-stories.md +47 -0
  249. package/docs/navigation.md +134 -0
  250. package/docs/page-layouts.md +71 -0
  251. package/docs/sections.md +224 -0
  252. package/docs/typescript.md +124 -0
  253. package/package.json +64 -0
@@ -0,0 +1,313 @@
1
+ # Content Types
2
+
3
+ Use `t4.contentTypes` to inspect and manage content type fields and layouts.
4
+
5
+ ## Contents
6
+
7
+ - [List and read content types](#list-and-read-content-types)
8
+ - [Create a content type](#create-a-content-type)
9
+ - [Update a content type](#update-a-content-type)
10
+ - [Add or remove fields](#add-or-remove-fields)
11
+ - [Manage content layouts](#manage-content-layouts)
12
+
13
+ ## List and read content types
14
+
15
+ ### List content types
16
+
17
+ ```typescript
18
+ const types = await t4.contentTypes.list();
19
+ for (const contentType of types) {
20
+ console.log(contentType.id, contentType.name, Object.keys(contentType.fields).length);
21
+ }
22
+ ```
23
+
24
+ ### Get a content type
25
+
26
+ ```typescript
27
+ const news = await t4.contentTypes.get(44);
28
+ ```
29
+
30
+ The returned `ContentType` includes these main properties:
31
+
32
+ | Property | Example or purpose |
33
+ |---|---|
34
+ | `name` | `'News Article'` |
35
+ | `description` | Content type description |
36
+ | `minUserLevel` | `'contributor'` |
37
+ | `workflow` | Assigned workflow |
38
+ | `directEdit` | Direct edit setting |
39
+ | `primaryGroup` | Primary group |
40
+ | `sharedGroups` | Shared groups |
41
+ | `fields` | Field definitions keyed by name |
42
+
43
+ Each entry in `news.fields` can include:
44
+
45
+ | Property | Meaning |
46
+ |---|---|
47
+ | `type` | Element type, such as `'Plain Text'` |
48
+ | `required` | Whether a value is required |
49
+ | `maxSize` | Maximum size, such as `200` |
50
+ | `shown` | Whether the field is shown |
51
+ | `listId` | List ID; `0` for non-list fields |
52
+ | `listName` | Resolved list name when `listId > 0`; otherwise `''` |
53
+ | `useAsFilename` | Whether the field supplies the filename |
54
+ | `config` | Repeater settings, including `contentTypeName`, `minRepeats`, and `maxRepeats` |
55
+ | `editor` | HTML editor, such as `'TinyMCE'` or `'Standard Textarea'` |
56
+
57
+ ```typescript
58
+ for (const [name, field] of Object.entries(news.fields)) {
59
+ console.log(name, field.type, field.required, field.maxSize);
60
+ }
61
+ ```
62
+
63
+ ## Create a content type
64
+
65
+ ```typescript
66
+ const contentType = await t4.contentTypes.create({
67
+ name: 'Blog Post',
68
+ description: 'A blog post with title and body',
69
+ elements: [
70
+ { name: 'Title', type: 'Plain Text', required: true, maxSize: 200 },
71
+ { name: 'Body', type: 'HTML' },
72
+ { name: 'Summary', type: 'Plain Text', maxSize: 500, shown: false },
73
+ ],
74
+ minUserLevel: 'contributor',
75
+ directEdit: true,
76
+ });
77
+ ```
78
+
79
+ The SDK always inserts a `Name` element as the first element.
80
+
81
+ ### List-based fields
82
+
83
+ `Select Box`, `Radio Button`, `Check Box`, `Multiple Select`, `Multi-select List`, `Cascading List`, and `Keyword Selector` fields require `listId`:
84
+
85
+ ```typescript
86
+ await t4.contentTypes.create({
87
+ name: 'Categorised Article',
88
+ elements: [
89
+ { name: 'Title', type: 'Plain Text', required: true },
90
+ { name: 'Body', type: 'HTML' },
91
+ { name: 'Category', type: 'Select Box', listId: 71 },
92
+ { name: 'Tags', type: 'Check Box', listId: 72 },
93
+ ],
94
+ });
95
+ ```
96
+
97
+ ### Repeater fields
98
+
99
+ A Repeater requires the sub-content type that defines its fields:
100
+
101
+ ```typescript
102
+ await t4.contentTypes.create({
103
+ name: 'Page with Slides',
104
+ elements: [
105
+ { name: 'Title', type: 'Plain Text', required: true },
106
+ {
107
+ name: 'Slides',
108
+ type: 'Repeater',
109
+ repeater: {
110
+ contentTypeId: 99, // required: content type that defines repeater fields
111
+ layout: 'text/slides', // optional; default: ''
112
+ minRepeats: 1, // optional; default: 0
113
+ maxRepeats: 10, // optional; default: 100
114
+ },
115
+ },
116
+ ],
117
+ });
118
+ ```
119
+
120
+ ### HTML editors
121
+
122
+ Set `editor` to choose the editor shown in the T4 content editing UI:
123
+
124
+ ```typescript
125
+ await t4.contentTypes.create({
126
+ name: 'Article',
127
+ elements: [
128
+ { name: 'Title', type: 'Plain Text', required: true },
129
+ { name: 'Body', type: 'HTML', editor: 'TinyMCE' },
130
+ { name: 'Notes', type: 'HTML', editor: 'Standard Textarea' },
131
+ ],
132
+ });
133
+ ```
134
+
135
+ The editor name must exist on the T4 instance. An invalid name produces an error that lists the available options.
136
+
137
+ ## Update a content type
138
+
139
+ ### Direct update
140
+
141
+ ```typescript
142
+ await t4.contentTypes.update(44, {
143
+ name: 'Renamed',
144
+ description: 'Updated',
145
+ directEdit: false,
146
+ });
147
+ ```
148
+
149
+ Add and remove fields in the same call:
150
+
151
+ ```typescript
152
+ await t4.contentTypes.update(44, {
153
+ addFields: [
154
+ { name: 'Subtitle', type: 'Plain Text', maxSize: 200 },
155
+ {
156
+ name: 'Slides',
157
+ type: 'Repeater',
158
+ repeater: {
159
+ contentTypeId: 99,
160
+ layout: 'text/slides',
161
+ minRepeats: 1,
162
+ maxRepeats: 10,
163
+ },
164
+ },
165
+ ],
166
+ removeFields: ['Old Field'],
167
+ });
168
+ ```
169
+
170
+ ### Mutable item
171
+
172
+ ```typescript
173
+ const contentType = await t4.contentTypes.get(44);
174
+ contentType.name = 'Renamed';
175
+ contentType.description = 'Updated description';
176
+ contentType.minUserLevel = 'moderator';
177
+ contentType.fields['Title'].maxSize = 300;
178
+ contentType.fields['Title'].required = false;
179
+ contentType.fields['Body'].editor = 'Standard Textarea';
180
+ await contentType.save();
181
+ ```
182
+
183
+ Set an HTML field's `editor` to `null` when you want to use the instance default.
184
+
185
+ ## Add or remove fields
186
+
187
+ `addField()` is asynchronous because it resolves the element type name to an ID. Field additions and removals take effect when you call `save()`.
188
+
189
+ ```typescript
190
+ const contentType = await t4.contentTypes.get(44);
191
+
192
+ await contentType.addField({
193
+ name: 'Subtitle',
194
+ type: 'Plain Text',
195
+ description: 'Optional subtitle',
196
+ maxSize: 200,
197
+ required: false,
198
+ shown: true,
199
+ });
200
+
201
+ await contentType.addField({
202
+ name: 'Category',
203
+ type: 'Select Box',
204
+ listId: 71,
205
+ });
206
+
207
+ await contentType.addField({
208
+ name: 'Slides',
209
+ type: 'Repeater',
210
+ repeater: {
211
+ contentTypeId: 99,
212
+ layout: 'text/slides',
213
+ minRepeats: 1,
214
+ maxRepeats: 10,
215
+ },
216
+ });
217
+
218
+ await contentType.addField({
219
+ name: 'Body',
220
+ type: 'HTML',
221
+ editor: 'TinyMCE',
222
+ });
223
+
224
+ contentType.removeField('Old Field');
225
+ await contentType.save();
226
+ ```
227
+
228
+ Delete a content type through the resource:
229
+
230
+ ```typescript
231
+ await t4.contentTypes.delete(44);
232
+ ```
233
+
234
+ ## System content types
235
+
236
+ System content types are managed by T4 and back core features. Removing or renaming their elements can break the instance, so the SDK blocks both on `save()` (and through `update({ removeFields })`):
237
+
238
+ ```typescript
239
+ const systemType = await t4.contentTypes.get(systemTypeId);
240
+ systemType.removeField('Some Element');
241
+ await systemType.save();
242
+ // Error: Cannot remove element "Some Element" from content type "..." because it
243
+ // is a system content type. Removing elements from system content types is not allowed.
244
+ ```
245
+
246
+ On a system content type you can still:
247
+
248
+ - Add new elements with `addField()` or `update({ addFields })`
249
+ - Change an element's `maxSize`
250
+ - Change the content type's `description` and any element's `description`
251
+
252
+ Two system content types are exempt, because removing and renaming their elements is safe: the **Section Metadata** content type and the **Extended User** content type. On those two, removal and renaming work exactly like a regular content type.
253
+
254
+ The check runs when you call `save()` or `update()`, not when you call `removeField()`. Because `removeField()` only stages the change in memory, nothing is sent to T4 when the guard blocks a save.
255
+
256
+ ## Manage content layouts
257
+
258
+ Access layouts through `layouts` on a `ContentType`. Layout operations use names; layout IDs are not exposed.
259
+
260
+ ### List and read layouts
261
+
262
+ ```typescript
263
+ const contentType = await t4.contentTypes.get(44);
264
+ const layouts = await contentType.layouts.list();
265
+ // [{ name: 'text/html', lastModified: Date }]
266
+
267
+ const layout = await contentType.layouts.get('text/html');
268
+ console.log(layout.name); // 'text/html'
269
+ console.log(layout.code); // '<h1>{{Title}}</h1>'
270
+ console.log(layout.lastModified); // Date
271
+ ```
272
+
273
+ ### Create a layout
274
+
275
+ ```typescript
276
+ await contentType.layouts.create({
277
+ name: 'text/json',
278
+ code: '{ "title": "{{Title}}" }',
279
+ syntax: 'HTML/XML', // optional; default: 'HTML/XML'
280
+ processor: 'handlebars', // optional; default: 'handlebars'
281
+ extension: 'json', // optional; default: ''
282
+ });
283
+ ```
284
+
285
+ Processor options are `'handlebars'`, `'t4-tags'`, and `'programmable-layouts'`. The default is `'handlebars'`. The SDK enforces unique layout names.
286
+
287
+ ### Direct update
288
+
289
+ ```typescript
290
+ await contentType.layouts.update('text/html', {
291
+ code: '<div>{{Title}}</div>',
292
+ processor: 't4-tags',
293
+ });
294
+ ```
295
+
296
+ ### Mutable item
297
+
298
+ ```typescript
299
+ const layout = await contentType.layouts.get('text/html');
300
+ layout.code = '<div>Updated</div>';
301
+ layout.name = 'text/renamed';
302
+ await layout.save();
303
+ ```
304
+
305
+ ### Delete a layout
306
+
307
+ ```typescript
308
+ await contentType.layouts.delete('text/old-layout');
309
+ ```
310
+
311
+ ---
312
+
313
+ **Previous:** [Content](./content.md) · **Next:** [Lists](./lists.md)
@@ -0,0 +1,199 @@
1
+ # Content
2
+
3
+ Content operations are scoped to a section through `t4.section(id).content`.
4
+
5
+ ## Contents
6
+
7
+ - [List and read content](#list-and-read-content)
8
+ - [Create content](#create-content)
9
+ - [Update content](#update-content)
10
+ - [Approve, duplicate, move, or remove](#approve-duplicate-move-or-remove)
11
+ - [Element values](#element-values)
12
+ - [Values returned on read](#values-returned-on-read)
13
+
14
+ ## List and read content
15
+
16
+ ### List content
17
+
18
+ ```typescript
19
+ const items = await t4.section(482).content.list();
20
+ ```
21
+
22
+ Each summary contains `id`, `name`, `status`, `contentTypeID`, `version`, `lastModified`, `publishDate`, `expiryDate`, `reviewDate`, and `archiveSection`. Summaries do not contain `fields`; call `content.get(id)` to retrieve a full item with resolved fields.
23
+
24
+ ### Get a content item
25
+
26
+ ```typescript
27
+ const item = await t4.section(482).content.get(9132);
28
+ console.log(item.name);
29
+ console.log(item.status); // 'approved', 'pending', 'draft', 'inactive'
30
+ console.log(item.fields); // { Title: 'Hello', Category: 'Featured', ... }
31
+ console.log(item.publishDate); // Date object or null
32
+ ```
33
+
34
+ `get()` returns a mutable `ContentItem`:
35
+
36
+ | Property | Type | Mutable | Description |
37
+ |---|---|---|---|
38
+ | `id` | `number` | no | Content ID |
39
+ | `name` | `string` | yes | Content name |
40
+ | `contentTypeID` | `number` | no | Content type ID |
41
+ | `language` | `string` | no | Language code |
42
+ | `status` | `string` | yes | `'approved'`, `'pending'`, `'draft'`, or `'inactive'` |
43
+ | `version` | `number` | no | Version number |
44
+ | `lastModified` | `Date \| null` | no | Last modification date |
45
+ | `publishDate` | `Date \| null` | yes | Publish date |
46
+ | `expiryDate` | `Date \| null` | yes | Expiry date |
47
+ | `reviewDate` | `Date \| null` | yes | Review date |
48
+ | `archiveSection` | `number \| null` | yes | Archive section ID |
49
+ | `fields` | `Record<string, unknown>` | yes | Resolved content fields |
50
+
51
+ ## Create content
52
+
53
+ ```typescript
54
+ const article = await t4.section(482).content.create({
55
+ type: 44,
56
+ name: 'My Article',
57
+ status: 'draft', // default: 'pending'
58
+ fields: {
59
+ Title: 'Breaking News',
60
+ Body: '<p>Article content.</p>',
61
+ Category: 'Featured', // list value by name
62
+ 'Publish Date': new Date(),
63
+ 'Hero Image': './photo.jpg', // uploads the file automatically
64
+ Related: { sectionId: 500, linkText: 'More' }, // SS link
65
+ },
66
+ publishDate: new Date('2025-07-01'),
67
+ expiryDate: new Date('2025-12-31'),
68
+ archiveSection: 500,
69
+ owner: 38,
70
+ });
71
+ ```
72
+
73
+ The SDK maps field names to element keys, resolves list values to IDs, uploads files, creates link records, and builds the full request body. An unknown field name produces an error that lists the valid fields.
74
+
75
+ Before creating or updating content, inspect the content type when you need its field constraints:
76
+
77
+ ```typescript
78
+ const type = await t4.contentTypes.get(contentTypeId);
79
+ console.log(type.fields);
80
+ ```
81
+
82
+ Each field includes `name`, `type`, `required`, `maxSize`, `listId`, `listName`, and repeater configuration. Check these values to confirm maximum lengths, list assignments for Select Box or Radio Button fields, required fields, and Repeater sub-fields.
83
+
84
+ ## Update content
85
+
86
+ ### Direct update
87
+
88
+ Pass only the values to change. `update()` fetches the existing item, merges your changes, and posts the full body:
89
+
90
+ ```typescript
91
+ await t4.section(482).content.update(9132, {
92
+ name: 'Updated Name',
93
+ fields: { Title: 'New Title' },
94
+ status: 'approved',
95
+ publishDate: new Date('2025-08-01'),
96
+ expiryDate: null, // clear the value
97
+ archiveSection: null, // clear the value
98
+ });
99
+ ```
100
+
101
+ ### Mutable item
102
+
103
+ Retrieve an item when you need to inspect or change several current values:
104
+
105
+ ```typescript
106
+ const article = await t4.section(482).content.get(9132);
107
+ article.name = 'Updated Name';
108
+ article.fields['Title'] = 'New Title';
109
+ article.status = 'approved';
110
+ article.archiveSection = 236;
111
+ await article.save();
112
+ ```
113
+
114
+ `save()` defaults the status to `'pending'` to match T4's approval workflow. Set `item.status = 'approved'` before saving if the item must remain approved.
115
+
116
+ ## Approve, duplicate, move, or remove
117
+
118
+ ### Approve one item
119
+
120
+ ```typescript
121
+ const article = await t4.section(482).content.get(9132);
122
+ article.fields['Title'] = 'Reviewed Title';
123
+ await article.approve(); // saves with status 'approved'
124
+ ```
125
+
126
+ ### Approve all pending items
127
+
128
+ ```typescript
129
+ const count = await t4.section(482).content.approveAll();
130
+ console.log(`Approved ${count} items`);
131
+ ```
132
+
133
+ `approveAll()` lists the section content, filters pending items, and sends one bulk approval request. It returns `0` when no items are pending.
134
+
135
+ ### Duplicate an item
136
+
137
+ ```typescript
138
+ const item = await t4.section(482).content.get(9132);
139
+
140
+ await item.duplicate(); // same section: appends "(1)" to avoid a name collision
141
+ await item.duplicate(500); // another section: keeps the original name
142
+ ```
143
+
144
+ For duplicates in the same section, the SDK checks existing names and chooses the next available `(n)` suffix.
145
+
146
+ ### Delete, purge, or move
147
+
148
+ ```typescript
149
+ await t4.section(482).content.delete(9132); // soft delete
150
+ await t4.section(482).content.purge(9132); // permanent removal
151
+
152
+ const item = await t4.section(482).content.get(9132);
153
+ await item.move(500); // move to section 500
154
+ ```
155
+
156
+ ## Element values
157
+
158
+ | Type | Pass | SDK sends or performs |
159
+ |---|---|---|
160
+ | Plain Text | `"text"` | Pass-through |
161
+ | HTML | `"<p>html</p>"` | Reverts SS link anchors to T4 tags on save; otherwise pass-through |
162
+ | Date | `new Date()`, timestamp, or string | Millisecond timestamp |
163
+ | Select Box | `"Large"` | `listId:itemId` |
164
+ | Radio Button | `"Large"` | `listId:itemId` |
165
+ | Checkbox | `["Large", "Small"]` | `listId:id1,id2` |
166
+ | Multiple Select | `["Large", "Small"]` | `listId:id1,id2` |
167
+ | Multi-Select List | `["Large", "Small"]` | `listId:id1;listId:id2` |
168
+ | Cascading List | `["Soccer", "Liverpool"]` | Resolves sublists automatically |
169
+ | Media | `10928` | String ID |
170
+ | Media (inline) | `{ file: './photo.jpg', name: 'Photo', category: 391 }` | Uploads to the media library and uses the returned ID |
171
+ | File / Image | `"./path.jpg"`, URL, Blob, or `{ file, filename }` | Uploads through `/upload/` |
172
+ | Section/Content Link | `{ sectionId, contentId?, linkText? }` | Creates an SS record and T4 tag |
173
+ | Decimal | `3.14` | Pass-through |
174
+ | Whole Number | `42` | Pass-through |
175
+ | Content Owner | `38` | String; resolves to user details on read |
176
+ | Group Select | `[41, 34, 40]` | Comma-separated value; resolves to group objects on read |
177
+ | Keyword Selector | `{ or: ["Large", { and: ["Small", "Other"] }] }` | Formats OR/AND groups |
178
+ | Repeater | `[{ name: 'Slide 1', fields: { Heading: 'Hi' } }]` | Full nested resolution |
179
+
180
+ ## Values returned on read
181
+
182
+ The SDK converts raw API values before assigning them to `ContentItem.fields`:
183
+
184
+ | Element | Returned value |
185
+ |---|---|
186
+ | List | Names, such as `"Large"` instead of `"1:2"` |
187
+ | Date | `Date` object |
188
+ | Media | Object with `id`, `name`, `filename`, `description`, `mediaType`, `downloadLink`, `path`, `fileSize`, and `lastModified` |
189
+ | File / Image | Object with `filename`, `fileSize`, and `downloadLink` |
190
+ | SS link | `{ sectionId, contentId?, linkText, path }` |
191
+ | HTML SS link | Inline `<a href="#" data-t4-sslink="..." data-section-id="..." data-content-id="...">linkText</a>` converted from a T4 `<t4 sslink_id="..." />` tag |
192
+ | Content Owner | User object with `id`, `type`, `username`, `firstName`, `lastName`, and `emailAddress` |
193
+ | Group Select | Array of `{ id, name, selected }` objects |
194
+ | Keyword Selector | `{ or: [...] }` structure |
195
+ | Repeater | Array of `{ name, fields }` with recursively resolved fields |
196
+
197
+ ---
198
+
199
+ **Previous:** [Sections](./sections.md) · **Next:** [Content Types](./content-types.md)
@@ -0,0 +1,86 @@
1
+ # Error Handling
2
+
3
+ The SDK distinguishes API failures from client-side validation errors. Enable debug logging when you need request-level detail.
4
+
5
+ ## Handle API errors
6
+
7
+ API failures throw `T4ApiError`. Check its properties to identify the failed request and response:
8
+
9
+ ```typescript
10
+ import { T4Client, T4ApiError } from '@terminalfour/terminalfour-js';
11
+
12
+ try {
13
+ await t4.section(482).content.get(99999);
14
+ } catch (error) {
15
+ if (error instanceof T4ApiError) {
16
+ console.error(error.statusCode); // 404
17
+ console.error(error.statusText); // '404'
18
+ console.error(error.requestMethod); // 'GET'
19
+ console.error(error.requestUrl); // full URL
20
+ console.error(error.responseBody); // parsed response body
21
+ console.error(error.cause); // original error for network failures
22
+ }
23
+ }
24
+ ```
25
+
26
+ | Property | Description |
27
+ |---|---|
28
+ | `statusCode` | HTTP status code |
29
+ | `statusText` | HTTP status text |
30
+ | `requestMethod` | Request method |
31
+ | `requestUrl` | Full request URL |
32
+ | `responseBody` | Parsed response body |
33
+ | `cause` | Original error for network failures |
34
+
35
+ ## Handle validation errors
36
+
37
+ Client-side validation throws standard `Error` instances. Messages include the value that failed and valid options when available.
38
+
39
+ ```typescript
40
+ await t4.section(482).content.create({
41
+ type: 44,
42
+ name: 'Test',
43
+ fields: { Nonexistent: 'value' },
44
+ });
45
+ // Error: Unknown field "Nonexistent" on this content type.
46
+ // Valid fields are: "Title", "Body", "Category"
47
+ ```
48
+
49
+ Other examples:
50
+
51
+ ```text
52
+ Error: Invalid list value "Medium" for field "Size".
53
+ Valid options are: "Large", "Small"
54
+
55
+ Error: Username is required
56
+ ```
57
+
58
+ The final error can result from a call such as:
59
+
60
+ ```typescript
61
+ await t4.users.create({ username: '', ... });
62
+ ```
63
+
64
+ ## Enable debug logging
65
+
66
+ Set `T4_DEBUG=1` to print HTTP requests and internal warnings:
67
+
68
+ ```bash
69
+ T4_DEBUG=1 node my-script.js
70
+ ```
71
+
72
+ Graceful degradation paths, including failed media lookups and group name resolution, log through `debugWarn`. These messages appear only when `T4_DEBUG=1`.
73
+
74
+ ## Clear stale cache data
75
+
76
+ After changing content types, lists, or other configuration, invalidate every SDK cache:
77
+
78
+ ```typescript
79
+ t4.clearCache();
80
+ ```
81
+
82
+ This clears cached content type templates, list values, element types, meta tags, group trees, and media types immediately.
83
+
84
+ ---
85
+
86
+ **Previous:** [Handlebars](./handlebars.md) · **Next:** [TypeScript Reference](./typescript.md)