@allejo/decap-extras 0.0.5 → 0.0.7

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/README.md CHANGED
@@ -31,11 +31,10 @@ npm install decap-cms-core
31
31
 
32
32
  ### Widget factories
33
33
 
34
- All factory functions follow the same signature: `widget(label, name, options?)`.
34
+ All factory functions follow a similar signature of `widget(label, name, options?)`.
35
35
 
36
36
  ```ts
37
37
  import {
38
- BARE_MARKDOWN,
39
38
  boolWidget,
40
39
  imageWidget,
41
40
  INLINE_MARKDOWN,
@@ -44,24 +43,216 @@ import {
44
43
  numberWidget,
45
44
  objectWidget,
46
45
  optional,
46
+ RICH_MARKDOWN,
47
47
  selectWidget,
48
48
  stringWidget,
49
49
  textWidget,
50
50
  } from '@allejo/decap-extras';
51
51
  ```
52
52
 
53
- #### Scalar widgets
53
+ #### Widget modifiers
54
+
55
+ ##### `optional(widget)`
56
+
57
+ Marks a widget field as optional, allowing it to be left empty in DecapCMS. This modifier can be applied to any widget type to make it non-required.
58
+
59
+ **TypeScript:**
60
+
61
+ ```ts
62
+ optional(stringWidget('Middle Name', 'middleName'));
63
+ ```
64
+
65
+ **YAML Configuration:**
66
+
67
+ ```yaml
68
+ - label: Middle Name
69
+ name: middleName
70
+ widget: string
71
+ required: false # All fields are required by default, so this is necessary to make it optional
72
+ ```
73
+
74
+ **Data Store YAML:**
75
+
76
+ ```yaml
77
+ middleName: # Can be left empty
78
+ ```
79
+
80
+ The `optional` modifier can be used with all widget types including complex ones:
81
+
82
+ ```ts
83
+ optional(
84
+ objectWidget('Metadata', 'metadata', [stringWidget('Author', 'author')]),
85
+ );
86
+ optional(listWidget('Tags', 'tags', stringWidget('Tag', 'value')));
87
+ ```
88
+
89
+ The `optional` modifier affects both the Decap CMS UI (field is not required) and the derived TypeScript type (field becomes `T | undefined`).
90
+
91
+ **Learn more:** [DecapCMS Common Widget Options](https://decapcms.org/docs/widgets/#common-widget-options)
92
+
93
+ #### Primitive widgets
94
+
95
+ ##### `stringWidget(label, name, opts?)`
96
+
97
+ Creates a single-line text input field for short text content.
98
+
99
+ **TypeScript:**
100
+
101
+ ```ts
102
+ stringWidget('Title', 'title');
103
+ ```
104
+
105
+ **YAML Configuration:**
106
+
107
+ ```yaml
108
+ - label: Title
109
+ name: title
110
+ widget: string
111
+ ```
112
+
113
+ **Data Store YAML:**
114
+
115
+ ```yaml
116
+ title: Welcome to My Blog
117
+ ```
118
+
119
+ Unlike the `textWidget` which provides multi-line input, the string widget is designed for concise, single-line values like titles, slugs, or names.
120
+
121
+ ```ts
122
+ optional(stringWidget('Slug', 'slug'));
123
+ ```
124
+
125
+ **See also:** [`textWidget`](#textwidgetlabel-name-opts), [`markdownWidget`](#markdownwidgetlabel-name-opts)
126
+ **Learn more:** [DecapCMS String Widget Docs](https://decapcms.org/docs/widgets/#string)
127
+
128
+ ##### `textWidget(label, name, opts?)`
129
+
130
+ Creates a multi-line text input field for longer text content.
131
+
132
+ **TypeScript:**
133
+
134
+ ```ts
135
+ textWidget('Description', 'description');
136
+ ```
137
+
138
+ **YAML Configuration:**
139
+
140
+ ```yaml
141
+ - label: Description
142
+ name: description
143
+ widget: text
144
+ ```
145
+
146
+ **Data Store YAML:**
147
+
148
+ ```yaml
149
+ description: |-
150
+ This is a longer description that spans
151
+ multiple lines in the data store.
152
+ ```
153
+
154
+ Unlike the `stringWidget` which provides single-line input, the text widget is designed for paragraphs and multi-line content.
155
+
156
+ ```ts
157
+ optional(textWidget('Notes', 'notes'));
158
+ ```
159
+
160
+ **See also:** [`stringWidget`](#stringwidgetlabel-name-opts), [`markdownWidget`](#markdownwidgetlabel-name-opts)
161
+ **Learn more:** [DecapCMS Text Widget Docs](https://decapcms.org/docs/widgets/#text)
162
+
163
+ ##### `numberWidget(label, name, opts?)`
164
+
165
+ Creates a numeric input field with optional min, max, and step constraints.
166
+
167
+ **TypeScript:**
168
+
169
+ ```ts
170
+ numberWidget('Priority', 'priority');
171
+ ```
172
+
173
+ **YAML Configuration:**
174
+
175
+ ```yaml
176
+ - label: Priority
177
+ name: priority
178
+ widget: number
179
+ ```
180
+
181
+ **Data Store YAML:**
182
+
183
+ ```yaml
184
+ priority: 1
185
+ ```
186
+
187
+ The number widget provides numeric input capabilities:
188
+
189
+ ```ts
190
+ optional(numberWidget('Views', 'views'));
191
+ ```
192
+
193
+ **Learn more:** [DecapCMS Number Widget Docs](https://decapcms.org/docs/widgets/#number)
194
+
195
+ ##### `boolWidget(label, name, opts?)`
196
+
197
+ Creates a checkbox input field for boolean values.
198
+
199
+ **TypeScript:**
200
+
201
+ ```ts
202
+ boolWidget('Published', 'published');
203
+ ```
204
+
205
+ **YAML Configuration:**
206
+
207
+ ```yaml
208
+ - label: Published
209
+ name: published
210
+ widget: boolean
211
+ ```
212
+
213
+ **Data Store YAML:**
214
+
215
+ ```yaml
216
+ published: true
217
+ ```
218
+
219
+ The boolean widget creates a checkbox input in the editor:
54
220
 
55
221
  ```ts
56
- const titleField = stringWidget('Title', 'title');
57
- const bodyField = textWidget('Body', 'body');
58
- const countField = numberWidget('Count', 'count');
59
- const activeField = boolWidget('Active', 'active');
222
+ optional(boolWidget('Draft', 'isDraft'));
60
223
  ```
61
224
 
62
- #### Select widget
225
+ **Learn more:** [DecapCMS Boolean Widget Docs](https://decapcms.org/docs/widgets/#boolean)
226
+
227
+ ##### `selectWidget(label, name, choices, opts?)`
228
+
229
+ Creates a dropdown menu with predefined options.
63
230
 
64
- Pass the choices array `as const` to get a narrowed literal union type; omit `as const` to get `string`.
231
+ **TypeScript:**
232
+
233
+ ```ts
234
+ selectWidget('Status', 'status', ['draft', 'published', 'archived'] as const);
235
+ ```
236
+
237
+ **YAML Configuration:**
238
+
239
+ ```yaml
240
+ - label: Status
241
+ name: status
242
+ widget: select
243
+ options:
244
+ - draft
245
+ - published
246
+ - archived
247
+ ```
248
+
249
+ **Data Store YAML:**
250
+
251
+ ```yaml
252
+ status: published
253
+ ```
254
+
255
+ Pass the choices array `as const` to get a narrowed literal union type; omit `as const` to get `string`:
65
256
 
66
257
  ```ts
67
258
  const themeField = selectWidget('Theme', 'theme', ['light', 'dark'] as const);
@@ -71,64 +262,247 @@ const looseField = selectWidget('Size', 'size', ['sm', 'md', 'lg']);
71
262
  // TypeScript type: string
72
263
  ```
73
264
 
74
- #### Object widget
265
+ The select widget can be made optional:
266
+
267
+ ```ts
268
+ optional(
269
+ selectWidget('Category', 'category', [
270
+ 'tech',
271
+ 'lifestyle',
272
+ 'business',
273
+ ] as const),
274
+ );
275
+ ```
276
+
277
+ **Learn more:** [DecapCMS Select Widget Docs](https://decapcms.org/docs/widgets/#select)
278
+
279
+ ##### `imageWidget(label, name, opts?)`
280
+
281
+ Creates a file upload field with image preview capabilities.
282
+
283
+ **TypeScript:**
284
+
285
+ ```ts
286
+ imageWidget('Featured Image', 'featuredImage');
287
+ ```
288
+
289
+ **YAML Configuration:**
290
+
291
+ ```yaml
292
+ - label: Featured Image
293
+ name: featuredImage
294
+ widget: image
295
+ ```
296
+
297
+ **Data Store YAML:**
298
+
299
+ ```yaml
300
+ featuredImage: /images/hero.jpg
301
+ ```
302
+
303
+ The image widget provides file upload functionality with image preview. To pass a default transformation (supported by services like Cloudinary), use the `opts` parameter to pass a `media_library` configuration:
304
+
305
+ ```ts
306
+ imageWidget('Hero Image', 'hero');
307
+ optional(imageWidget('Avatar', 'avatar'));
308
+ ```
309
+
310
+ **Learn more:** [DecapCMS Image Widget Docs](https://decapcms.org/docs/widgets/#image)
311
+
312
+ #### Complex widgets
313
+
314
+ ##### `objectWidget(label, name, fields, opts?)`
315
+
316
+ Groups multiple fields together as a nested object in the data store.
317
+
318
+ **TypeScript:**
75
319
 
76
320
  ```ts
77
- const metaField = objectWidget('Meta', 'meta', [
78
- stringWidget('Title', 'title'),
79
- stringWidget('Description', 'description'),
321
+ objectWidget('Author', 'author', [
322
+ stringWidget('Name', 'name'),
323
+ stringWidget('Email', 'email'),
80
324
  ]);
81
325
  ```
82
326
 
83
- #### List widget
327
+ **YAML Configuration:**
328
+
329
+ ```yaml
330
+ - label: Author
331
+ name: author
332
+ widget: object
333
+ fields:
334
+ - label: Name
335
+ name: name
336
+ widget: string
337
+ - label: Email
338
+ name: email
339
+ widget: string
340
+ ```
341
+
342
+ **Data Store YAML:**
84
343
 
85
- Pass a single field to get `Array<T>`, or an array of fields to get `Array<{ ... }>`.
344
+ ```yaml
345
+ author:
346
+ name: Jane Doe
347
+ email: jane@example.com
348
+ ```
349
+
350
+ This is useful for creating structured data with related properties. Objects can be nested within lists or other objects to create complex data structures:
86
351
 
87
352
  ```ts
88
- // Single-field list → Array<string>
89
- const tagsField = listWidget('Tags', 'tags', stringWidget('Tag', 'tag'));
353
+ optional(
354
+ objectWidget('Metadata', 'metadata', [
355
+ stringWidget('SEO Title', 'seoTitle'),
356
+ textWidget('Description', 'description'),
357
+ ]),
358
+ );
359
+ ```
360
+
361
+ **Learn more:** [DecapCMS Object Widget Docs](https://decapcms.org/docs/widgets/#object)
362
+
363
+ ##### `listWidget(label, name, field/fields, opts?)`
364
+
365
+ Creates a list widget that can contain either single-field items or multi-field items.
366
+
367
+ **Single-field list:**
90
368
 
91
- // Multi-field list → Array<{ label: string; value: string }>
92
- const tagObjectsField = listWidget('Tag Objects', 'tagObjects', [
93
- stringWidget('Label', 'label'),
94
- stringWidget('Value', 'value'),
369
+ TypeScript:
370
+
371
+ ```ts
372
+ listWidget('Tags', 'tags', stringWidget('Tag', 'value'));
373
+ ```
374
+
375
+ YAML Configuration:
376
+
377
+ ```yaml
378
+ - label: Tags
379
+ name: tags
380
+ widget: list
381
+ field:
382
+ label: Tag
383
+ name: value
384
+ widget: string
385
+ ```
386
+
387
+ Data Store YAML:
388
+
389
+ ```yaml
390
+ tags:
391
+ - value: React
392
+ - value: TypeScript
393
+ ```
394
+
395
+ **Multi-field list:**
396
+
397
+ TypeScript:
398
+
399
+ ```ts
400
+ listWidget('Authors', 'authors', [
401
+ stringWidget('Name', 'name'),
402
+ imageWidget('Avatar', 'avatar'),
95
403
  ]);
96
404
  ```
97
405
 
98
- #### Image widget
406
+ YAML Configuration:
407
+
408
+ ```yaml
409
+ - label: Authors
410
+ name: authors
411
+ widget: list
412
+ fields:
413
+ - label: Name
414
+ name: name
415
+ widget: string
416
+ - label: Avatar
417
+ name: avatar
418
+ widget: image
419
+ ```
420
+
421
+ Data Store YAML:
99
422
 
100
- To pass a default transformation (supported by services like Cloudinary), use the `opts` parameter to pass a `media_library` configuration.
423
+ ```yaml
424
+ authors:
425
+ - name: Jane Doe
426
+ avatar: /images/jane.jpg
427
+ - name: John Smith
428
+ avatar: /images/john.jpg
429
+ ```
430
+
431
+ > [!WARNING]
432
+ >
433
+ > When passing an Object widget (e.g., `objectWidget(...)`), this function will unwrap the object and return `Array<{ ... }>`, not `Array<T>`. This is done as a convenience so you don't have an extra layer of nesting in your data structure. For example, by unwrapping the object, you can have a list of items like this:
434
+ >
435
+ > ```yaml
436
+ > listOfItems:
437
+ > - label: 'Item 1'
438
+ > value: 'Value 1'
439
+ > ```
440
+ >
441
+ > If the object was not unwrapped, the data structure would look like this:
442
+ >
443
+ > ```yaml
444
+ > listOfItems:
445
+ > - item:
446
+ > label: 'Item 1'
447
+ > value: 'Value 1'
448
+ > ```
449
+ >
450
+ > If you do want the extra nesting, simply wrap the object in an array when passing it to `listWidget()`, like this:
451
+ >
452
+ > ```typescript
453
+ > const nestedField = listWidget('Nested Items', 'nestedItems', [
454
+ > objectWidget('Item', 'item', [
455
+ > stringWidget('Label', 'label'),
456
+ > stringWidget('Value', 'value'),
457
+ > ]),
458
+ > ]);
459
+ > ```
460
+
461
+ The list widget can be made optional:
101
462
 
102
463
  ```ts
103
- const heroField = imageWidget('Hero Image', 'hero');
464
+ optional(listWidget('Tags', 'tags', stringWidget('Tag', 'value')));
104
465
  ```
105
466
 
467
+ **Learn more:** [DecapCMS List Widget Docs](https://decapcms.org/docs/widgets/#list)
468
+
106
469
  #### Markdown widget
107
470
 
471
+ ##### `markdownWidget(label, name, opts?)`
472
+
473
+ Creates a rich text editor for markdown content.
474
+
475
+ **TypeScript:**
476
+
108
477
  ```ts
109
478
  // Full markdown editor
110
- const contentField = markdownWidget('Content', 'content');
479
+ markdownWidget('Content', 'content');
111
480
 
112
481
  // Restricted to bold, italic, lists, links, and quotes — no images or code blocks
113
- const descriptionField = markdownWidget(
114
- 'Description',
115
- 'description',
116
- BARE_MARKDOWN,
117
- );
482
+ markdownWidget('Description', 'description', RICH_MARKDOWN);
118
483
 
119
484
  // Inline elements only: bold, italic, and links
120
- const captionField = markdownWidget('Caption', 'caption', INLINE_MARKDOWN);
485
+ markdownWidget('Caption', 'caption', INLINE_MARKDOWN);
121
486
  ```
122
487
 
123
- #### Optional fields
488
+ **YAML Configuration:**
124
489
 
125
- Wrap any widget with `optional()` to mark it as not required in both the Decap CMS UI and the derived TypeScript type.
490
+ ```yaml
491
+ - label: Content
492
+ name: content
493
+ widget: richtext
494
+ ```
126
495
 
127
- ```ts
128
- const subtitleField = optional(stringWidget('Subtitle', 'subtitle'));
129
- // TypeScript type: string | undefined
496
+ **Data Store YAML:**
497
+
498
+ ```yaml
499
+ content: |
500
+ # Heading
501
+ This is **bold** and *italic* text with [links](https://example.com).
130
502
  ```
131
503
 
504
+ **Learn more:** [DecapCMS RichText Widget Docs](https://decapcms.org/docs/widgets/#richtext-beta)
505
+
132
506
  #### Common options
133
507
 
134
508
  Every factory accepts an optional last argument for [common widget options](https://decapcms.org/docs/widgets/#common-widget-options):
@@ -148,12 +522,12 @@ Define your CMS config using the widget factories and `as const`, then let `Prop
148
522
  ```ts
149
523
  // cms/config.ts
150
524
  import {
151
- BARE_MARKDOWN,
152
525
  boolWidget,
153
526
  imageWidget,
154
527
  markdownWidget,
155
528
  objectWidget,
156
529
  optional,
530
+ RICH_MARKDOWN,
157
531
  selectWidget,
158
532
  stringWidget,
159
533
  } from '@allejo/decap-extras';
@@ -172,7 +546,7 @@ export const config = {
172
546
  stringWidget('Title', 'title'),
173
547
  optional(stringWidget('Subtitle', 'subtitle')),
174
548
  imageWidget('Hero Image', 'hero'),
175
- markdownWidget('Body', 'body', BARE_MARKDOWN),
549
+ markdownWidget('Body', 'body', RICH_MARKDOWN),
176
550
  selectWidget('Theme', 'theme', ['light', 'dark'] as const),
177
551
  objectWidget('SEO', 'seo', [
178
552
  stringWidget('Meta Title', 'metaTitle'),
@@ -449,31 +823,47 @@ export default function Admin({ cssFiles }: Props) {
449
823
  }
450
824
  ```
451
825
 
452
-
453
826
  ## API reference
454
827
 
455
- ### Widget functions
456
-
457
- | Function | Decap widget | TypeScript type |
458
- | -------------------------------------------- | -------------------------------------------------------------- | ------------------------- |
459
- | `stringWidget(label, name, opts?)` | [`string`](https://decapcms.org/docs/widgets/#string) | `string` |
460
- | `textWidget(label, name, opts?)` | [`text`](https://decapcms.org/docs/widgets/#text) | `string` |
461
- | `markdownWidget(label, name, opts?)` | [`richtext`](https://decapcms.org/docs/widgets/#richtext-beta) | `string` |
462
- | `imageWidget(label, name, opts?)` | [`image`](https://decapcms.org/docs/widgets/#image) | `string` |
463
- | `numberWidget(label, name, opts?)` | [`number`](https://decapcms.org/docs/widgets/#number) | `number` |
464
- | `boolWidget(label, name, opts?)` | [`boolean`](https://decapcms.org/docs/widgets/#boolean) | `boolean` |
465
- | `selectWidget(label, name, choices, opts?)` | [`select`](https://decapcms.org/docs/widgets/#select) | literal union or `string` |
466
- | `listWidget(label, name, field, opts?)` | [`list`](https://decapcms.org/docs/widgets/#list) | `Array<T>` |
467
- | `listWidget(label, name, fields[], opts?)` | [`list`](https://decapcms.org/docs/widgets/#list) | `Array<{ ... }>` |
468
- | `objectWidget(label, name, fields[], opts?)` | [`object`](https://decapcms.org/docs/widgets/#object) | `{ ... }` |
469
- | `optional(widget)` | | marks field optional |
828
+ ### Widget API reference
829
+
830
+ #### Widget modifiers
831
+
832
+ | Function | Description |
833
+ | ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
834
+ | `optional(widget)` | Marks a widget field as optional (non-required). [Learn more](https://decapcms.org/docs/widgets/#common-widget-options) |
835
+
836
+ #### Primitive widgets
837
+
838
+ | Function | Decap widget | TypeScript type |
839
+ | ------------------------------------------- | ------------------------------------------------------- | ------------------------- |
840
+ | `stringWidget(label, name, opts?)` | [`string`](https://decapcms.org/docs/widgets/#string) | `string` |
841
+ | `textWidget(label, name, opts?)` | [`text`](https://decapcms.org/docs/widgets/#text) | `string` |
842
+ | `numberWidget(label, name, opts?)` | [`number`](https://decapcms.org/docs/widgets/#number) | `number` |
843
+ | `boolWidget(label, name, opts?)` | [`boolean`](https://decapcms.org/docs/widgets/#boolean) | `boolean` |
844
+ | `selectWidget(label, name, choices, opts?)` | [`select`](https://decapcms.org/docs/widgets/#select) | literal union or `string` |
845
+ | `imageWidget(label, name, opts?)` | [`image`](https://decapcms.org/docs/widgets/#image) | `string` |
846
+
847
+ #### Complex widgets
848
+
849
+ | Function | Decap widget | TypeScript type |
850
+ | -------------------------------------------- | ----------------------------------------------------- | ---------------- |
851
+ | `listWidget(label, name, field, opts?)` | [`list`](https://decapcms.org/docs/widgets/#list) | `Array<T>` |
852
+ | `listWidget(label, name, fields[], opts?)` | [`list`](https://decapcms.org/docs/widgets/#list) | `Array<{ ... }>` |
853
+ | `objectWidget(label, name, fields[], opts?)` | [`object`](https://decapcms.org/docs/widgets/#object) | `{ ... }` |
854
+
855
+ #### Rich text widgets
856
+
857
+ | Function | Decap widget | TypeScript type |
858
+ | ------------------------------------ | -------------------------------------------------------------- | --------------- |
859
+ | `markdownWidget(label, name, opts?)` | [`richtext`](https://decapcms.org/docs/widgets/#richtext-beta) | `string` |
470
860
 
471
861
  ### Markdown presets
472
862
 
473
- | Constant | Description |
474
- | ----------------- | ------------------------------------------------------------- |
475
- | `BARE_MARKDOWN` | Bold, italic, lists, links, quotes. No images or code blocks. |
476
- | `INLINE_MARKDOWN` | Bold, italic, and links only. No block-level elements. |
863
+ | Constant | Description |
864
+ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
865
+ | `RICH_MARKDOWN` | Stripped down markdown editor with bold, italic, lists (bullet + numbered), quotes, and links. No images or code blocks. Rich Text preview only. |
866
+ | `INLINE_MARKDOWN` | Stripped down markdown widget that allows only inline elements (bold, italic, and links). No block-level elements like lists or quotes. Rich Text preview only. |
477
867
 
478
868
  ### Type utilities
479
869