vite-plugin-decap-cms 0.2.0 → 0.3.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/README.md CHANGED
@@ -1,70 +1,70 @@
1
- # vite-plugin-decap-cms
2
-
3
- > A Vite plugin to connect Decap CMS
4
-
5
- ![NPM Version](https://img.shields.io/npm/v/vite-plugin-decap-cms)
6
- ![NPM Downloads](https://img.shields.io/npm/dm/vite-plugin-decap-cms)
7
- ![GitHub Issues or Pull Requests](https://img.shields.io/github/issues/ghostrider-05/vite-plugin-decap-cms)
8
-
9
- > [!WARNING]
10
- > This plugin has not reached a stable version, 1.0.0, and can include breaking changes following the semver specification. This plugin is open for contributions, both for code, suggestions and (missing) documentation.
11
-
12
- ## Install
13
-
14
- ```sh
15
- pnpm add vite-plugin-decap-cms -D
16
- npm install vite-plugin-decap-cms -D
17
- yarn add vite-plugin-decap-cms -D
18
- ```
19
-
20
- ## Usage
21
-
22
- ```ts
23
- // vite.config.ts
24
- import { defineConfig } from 'vite'
25
- import decap, {
26
- createFolderCollection,
27
- createField,
28
- } from 'vite-plugin-decap-cms'
29
-
30
- export default defineConfig({
31
- publicDir: 'public',
32
- plugins: [
33
- decap({
34
- config: {
35
- backend: {
36
- name: 'test-repo',
37
- },
38
- mediaFolder: '/src/public/',
39
- collections: [
40
- createFolderCollection({
41
- name: 'test',
42
- label: 'Test collection',
43
- folder: 'test',
44
- fields: [
45
- createField('markdown', { name: 'body' }),
46
- ],
47
- }),
48
- ]
49
- }
50
- })
51
- ],
52
- })
53
- ```
54
-
55
- For more options and guides, see [the documentation](https://vite-plugin-decap-cms.pages.dev)
56
-
57
- ## Example
58
-
59
- See [the documentation CMS](https://vite-plugin-decap-cms.pages.dev/admin/index.html) for an example
60
-
61
- ## Development
62
-
63
- ```sh
64
- npm run docs:dev
65
- npm run cms:dev
66
- ```
67
-
68
- ## License
69
-
70
- [MIT](LICENSE)
1
+ # vite-plugin-decap-cms
2
+
3
+ > A Vite plugin to connect Decap CMS
4
+
5
+ ![NPM Version](https://img.shields.io/npm/v/vite-plugin-decap-cms)
6
+ ![NPM Downloads](https://img.shields.io/npm/dm/vite-plugin-decap-cms)
7
+ ![GitHub Issues or Pull Requests](https://img.shields.io/github/issues/ghostrider-05/vite-plugin-decap-cms)
8
+
9
+ > [!WARNING]
10
+ > This plugin has not reached a stable version, 1.0.0, and can include breaking changes following the semver specification. This plugin is open for contributions, both for code, suggestions and (missing) documentation.
11
+
12
+ ## Install
13
+
14
+ ```sh
15
+ pnpm add vite-plugin-decap-cms -D
16
+ npm install vite-plugin-decap-cms -D
17
+ yarn add vite-plugin-decap-cms -D
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ```ts
23
+ // vite.config.ts
24
+ import { defineConfig } from 'vite'
25
+ import decap, {
26
+ createFolderCollection,
27
+ createField,
28
+ } from 'vite-plugin-decap-cms'
29
+
30
+ export default defineConfig({
31
+ publicDir: 'public',
32
+ plugins: [
33
+ decap({
34
+ config: {
35
+ backend: {
36
+ name: 'test-repo',
37
+ },
38
+ mediaFolder: '/src/public/',
39
+ collections: [
40
+ createFolderCollection({
41
+ name: 'test',
42
+ label: 'Test collection',
43
+ folder: 'test',
44
+ fields: [
45
+ createField('markdown', { name: 'body' }),
46
+ ],
47
+ }),
48
+ ]
49
+ }
50
+ })
51
+ ],
52
+ })
53
+ ```
54
+
55
+ For more options and guides, see [the documentation](https://vite-plugin-decap-cms.pages.dev)
56
+
57
+ ## Example
58
+
59
+ See [the documentation CMS](https://vite-plugin-decap-cms.pages.dev/admin/index.html) for an example
60
+
61
+ ## Development
62
+
63
+ ```sh
64
+ # in /docs/
65
+ npm run docs:dev
66
+ ```
67
+
68
+ ## License
69
+
70
+ [MIT](LICENSE)
package/dist/index.cjs CHANGED
@@ -71,10 +71,10 @@ function getGitData() {
71
71
  }
72
72
  };
73
73
  return {
74
- get branch() {
74
+ getBranch() {
75
75
  return executeGit("git rev-parse --abbrev-ref HEAD");
76
76
  },
77
- get commitSha() {
77
+ getCommitSha() {
78
78
  return executeGit("git rev-parse HEAD");
79
79
  }
80
80
  };
@@ -96,25 +96,32 @@ function createFileCollection(data) {
96
96
  function createOverwriteableField(widget, data, overwrites) {
97
97
  if (overwrites != void 0) {
98
98
  const toAdd = (key) => {
99
- if ((overwrites == null ? void 0 : overwrites[key]) != void 0 && data[key] !== overwrites[key])
100
- data[key] = overwrites[key];
99
+ if ((overwrites == null ? void 0 : overwrites[key]) != void 0 && data[key] !== overwrites[key]) data[key] = overwrites[key];
101
100
  };
102
101
  for (const key of Object.keys(overwrites)) {
103
- if (key !== "hidden") {
102
+ if (key !== "hidden" && key !== "deleted") {
104
103
  toAdd(key);
105
104
  }
106
105
  }
107
106
  }
108
- if ((overwrites == null ? void 0 : overwrites.hidden) && widget !== "hidden")
109
- return createField("hidden", data);
110
- else
111
- return __spreadProps(__spreadValues({}, data), {
112
- widget
113
- });
107
+ if (overwrites == null ? void 0 : overwrites.deleted) return void 0;
108
+ else if ((overwrites == null ? void 0 : overwrites.hidden) && widget !== "hidden") return createField("hidden", data);
109
+ else return __spreadProps(__spreadValues({}, data), {
110
+ widget
111
+ });
112
+ }
113
+ function filterUndefined(item) {
114
+ return item != void 0;
115
+ }
116
+ function omit(obj, keys) {
117
+ if (!obj) return void 0;
118
+ const validEntries = Object.entries(obj).filter(([key]) => !keys.includes(key));
119
+ return Object.fromEntries(validEntries);
114
120
  }
115
121
  var VitePress = class {
116
122
  /**
117
123
  * Create fields for:
124
+ * - layout
118
125
  * - navbar
119
126
  * - sidebar
120
127
  * - aside
@@ -131,6 +138,12 @@ var VitePress = class {
131
138
  static createDefaultThemeNormalPageFields(options) {
132
139
  const { overwrites } = options != null ? options : {};
133
140
  return [
141
+ createOverwriteableField("string", {
142
+ name: "layout",
143
+ label: "Layout",
144
+ required: false,
145
+ default: "doc"
146
+ }),
134
147
  createOverwriteableField("boolean", {
135
148
  name: "navbar",
136
149
  label: "Whether to display the navbar",
@@ -181,7 +194,7 @@ var VitePress = class {
181
194
  label: "Page class",
182
195
  required: false
183
196
  }, overwrites == null ? void 0 : overwrites.pageClass)
184
- ];
197
+ ].filter(filterUndefined);
185
198
  }
186
199
  /**
187
200
  * Create fields for:
@@ -193,7 +206,7 @@ var VitePress = class {
193
206
  * @see https://vitepress.dev/reference/frontmatter-config
194
207
  */
195
208
  static createDefaultPageFields(options) {
196
- var _a;
209
+ var _a, _b;
197
210
  const { additionalFields, overwrites } = options != null ? options : {};
198
211
  const fields = [
199
212
  createOverwriteableField("string", {
@@ -214,11 +227,123 @@ var VitePress = class {
214
227
  name: "head",
215
228
  label: "Head"
216
229
  }, overwrites == null ? void 0 : overwrites.head)
217
- ];
218
- return fields.concat(additionalFields != null ? additionalFields : []).concat(createOverwriteableField("markdown", __spreadProps(__spreadValues({}, (_a = options == null ? void 0 : options.markdownOptions) != null ? _a : {}), {
230
+ ].filter(filterUndefined);
231
+ return fields.concat(additionalFields != null ? additionalFields : []).concat((_b = createOverwriteableField("markdown", __spreadProps(__spreadValues({}, (_a = options == null ? void 0 : options.markdownOptions) != null ? _a : {}), {
219
232
  name: "body",
220
233
  label: "Page content"
221
- }), overwrites == null ? void 0 : overwrites.body));
234
+ }), overwrites == null ? void 0 : overwrites.body)) != null ? _b : []).filter(filterUndefined);
235
+ }
236
+ /**
237
+ * Create fields for:
238
+ * - layout: home (not overwriteable)
239
+ * - hero
240
+ * - features
241
+ *
242
+ * The object fields (`features`, `hero`, `heroActions`) can not be hidden and deleted.
243
+ */
244
+ static createHomePageFields(options) {
245
+ const { overwrites } = options != null ? options : {};
246
+ const keys = ["hidden", "deleted"];
247
+ return [
248
+ createField("hidden", {
249
+ name: "layout",
250
+ default: "home"
251
+ }),
252
+ createOverwriteableField("object", {
253
+ name: "hero",
254
+ label: "Hero items",
255
+ required: true,
256
+ fields: [
257
+ createOverwriteableField("string", {
258
+ name: "name",
259
+ required: false
260
+ }, overwrites == null ? void 0 : overwrites.heroName),
261
+ createOverwriteableField("string", {
262
+ name: "text"
263
+ }, overwrites == null ? void 0 : overwrites.heroText),
264
+ createOverwriteableField("string", {
265
+ name: "tagline",
266
+ required: false
267
+ }, overwrites == null ? void 0 : overwrites.heroTagline),
268
+ // TODO: add support for object options
269
+ createOverwriteableField("image", {
270
+ name: "image",
271
+ required: false
272
+ }, overwrites == null ? void 0 : overwrites.heroImage),
273
+ createOverwriteableField("list", {
274
+ name: "actions",
275
+ label: "Action buttons",
276
+ label_singular: "action",
277
+ allow_add: true,
278
+ fields: [
279
+ createOverwriteableField("string", {
280
+ name: "text"
281
+ }, overwrites == null ? void 0 : overwrites.heroActionText),
282
+ createOverwriteableField("string", {
283
+ name: "link"
284
+ }, overwrites == null ? void 0 : overwrites.heroActionLink),
285
+ createOverwriteableField("select", {
286
+ name: "theme",
287
+ required: false,
288
+ default: "brand",
289
+ options: [
290
+ "brand",
291
+ "alt"
292
+ ]
293
+ }, overwrites == null ? void 0 : overwrites.heroActionTheme),
294
+ createOverwriteableField("string", {
295
+ name: "target",
296
+ required: false
297
+ }, overwrites == null ? void 0 : overwrites.heroActionTarget),
298
+ createOverwriteableField("string", {
299
+ name: "rel",
300
+ required: false
301
+ }, overwrites == null ? void 0 : overwrites.heroActionRel)
302
+ ].filter(filterUndefined)
303
+ }, omit(overwrites == null ? void 0 : overwrites.heroActions, keys))
304
+ ].filter(filterUndefined)
305
+ }, omit(overwrites == null ? void 0 : overwrites.hero, keys)),
306
+ createOverwriteableField("list", {
307
+ name: "features",
308
+ label: "Features",
309
+ label_singular: "feature",
310
+ allow_add: true,
311
+ required: false,
312
+ fields: [
313
+ createOverwriteableField("string", {
314
+ name: "title",
315
+ required: true
316
+ }, overwrites == null ? void 0 : overwrites.featuresTitle),
317
+ createOverwriteableField("string", {
318
+ name: "details",
319
+ required: false
320
+ }, overwrites == null ? void 0 : overwrites.featuresDetails),
321
+ // TODO: add support for object options
322
+ createOverwriteableField("string", {
323
+ name: "icon",
324
+ required: false
325
+ }, overwrites == null ? void 0 : overwrites.featuresIcon),
326
+ createOverwriteableField("string", {
327
+ name: "link",
328
+ required: false
329
+ }, overwrites == null ? void 0 : overwrites.featuresLink),
330
+ createOverwriteableField("string", {
331
+ name: "linkText",
332
+ label: "Link text",
333
+ required: false
334
+ }, overwrites == null ? void 0 : overwrites.featuresLinkText),
335
+ createOverwriteableField("string", {
336
+ name: "target",
337
+ label: "Target",
338
+ required: false
339
+ }, overwrites == null ? void 0 : overwrites.featuresTarget),
340
+ createOverwriteableField("string", {
341
+ name: "rel",
342
+ required: false
343
+ }, overwrites == null ? void 0 : overwrites.featuresRel)
344
+ ].filter(filterUndefined)
345
+ }, omit(overwrites == null ? void 0 : overwrites.features, keys))
346
+ ];
222
347
  }
223
348
  static createDefaultPageFolderCollection(name, folder, options) {
224
349
  const _a = options != null ? options : {}, { collection } = _a, fieldsOptions = __objRest(_a, ["collection"]);
@@ -266,7 +391,8 @@ function getBooleanFromEnv(value, command) {
266
391
  }
267
392
  function resolveBackend(options, command) {
268
393
  const _a = options, { local, name } = _a, backend = __objRest(_a, ["local", "name"]);
269
- const branch = "useCurrentBranch" in options && getBooleanFromEnv(options.useCurrentBranch, command) ? getGitData().branch : "branch" in backend ? backend.branch : void 0;
394
+ const git = getGitData();
395
+ const branch = "useCurrentBranch" in options && getBooleanFromEnv(options.useCurrentBranch, command) ? git.getBranch() : "branch" in backend ? backend.branch : void 0;
270
396
  delete backend.useCurrentBranch;
271
397
  const resolved = {
272
398
  local_backend: typeof local === "object" ? objToSnakeCase(local) : getBooleanFromEnv(local, command),
@@ -296,8 +422,7 @@ function createConfigFile(config, command) {
296
422
  });
297
423
  })
298
424
  });
299
- } else
300
- throw new Error("Missing either fields or files property in collection");
425
+ } else throw new Error("Missing either fields or files property in collection");
301
426
  })
302
427
  });
303
428
  }
@@ -311,10 +436,8 @@ function createCmsFunction(method, items, createParams, options) {
311
436
  };
312
437
  return (items != null ? items : []).map((item) => {
313
438
  const params = createParams(item);
314
- if (!params)
315
- return null;
316
- else
317
- return create(params);
439
+ if (!params) return null;
440
+ else return create(params);
318
441
  }).filter(Boolean).join((_a = options == null ? void 0 : options.joinChar) != null ? _a : "\n");
319
442
  }
320
443
  function createScript(options) {
@@ -338,8 +461,7 @@ function createScript(options) {
338
461
  ]);
339
462
  const events = createCmsFunction("registerEventListener", Object.keys(eventHooks), (hookName) => {
340
463
  const hook = eventHooks[hookName];
341
- if (!hook)
342
- return null;
464
+ if (!hook) return null;
343
465
  else {
344
466
  const name = hookName.slice(2)[0].toLowerCase() + hookName.slice(3);
345
467
  return `{ name: '${name}', handler: data => { function ${hook.toString()}; ${hookName}({ app: CMS, ...data }) } }`;
@@ -367,7 +489,7 @@ ${editorComponents}
367
489
  }
368
490
 
369
491
  // src/files/index.ts
370
- var defaultDecapCmsCdnVersion = "3.1.3";
492
+ var defaultDecapCmsCdnVersion = "3.1.11";
371
493
  var defaultNetlifyIdentityVersion = "1";
372
494
  var addSlash = (path, slash = "/") => path.endsWith(slash) ? path : path + slash;
373
495
  function resolveCdnRoute(options) {
@@ -378,13 +500,10 @@ function resolveCdnRoute(options) {
378
500
  }
379
501
  function resolveHead(head) {
380
502
  return head.reduce((output, config) => {
381
- if (typeof config === "string")
382
- return output.concat(config);
503
+ if (typeof config === "string") return output.concat(config);
383
504
  if ("skip" in config) {
384
- if (config.skip)
385
- return output;
386
- if (typeof config.head === "string")
387
- return output.concat(config.head);
505
+ if (config.skip) return output;
506
+ if (typeof config.head === "string") return output.concat(config.head);
388
507
  }
389
508
  const item = "head" in config ? config.head : config;
390
509
  let str = `<${item[0]}`;
@@ -392,8 +511,7 @@ function resolveHead(head) {
392
511
  str += ` ${key}="${item[1][key]}"`;
393
512
  }
394
513
  str += item[0] === "meta" ? "/>" : ">";
395
- if (item[2] == void 0)
396
- return output.concat(str);
514
+ if (item[2] == void 0) return output.concat(str);
397
515
  str += item[2] + `</${item[0]}>`;
398
516
  return output.concat(str);
399
517
  }, []);
@@ -431,8 +549,7 @@ function getIndexFeatures(config, loadOptions) {
431
549
  function createIndexFile(pluginOptions) {
432
550
  var _a, _b;
433
551
  const { config, load, login: options, script } = pluginOptions;
434
- if (options == null ? void 0 : options.html)
435
- return options.html;
552
+ if (options == null ? void 0 : options.html) return options.html;
436
553
  const features = getIndexFeatures(config, load);
437
554
  return `<!DOCTYPE html>
438
555
  <html>
@@ -475,8 +592,7 @@ async function writeToFolder(folder, options) {
475
592
  function validateLoadOptions(options) {
476
593
  var _a;
477
594
  const valid = ["npm", "cdn"].includes((_a = options == null ? void 0 : options.method) != null ? _a : "cdn");
478
- if (!valid)
479
- throw new Error("Invalid load options for decap-cms provided");
595
+ if (!valid) throw new Error("Invalid load options for decap-cms provided");
480
596
  }
481
597
  function runProxy(options) {
482
598
  var _a, _b, _c, _d, _e;
@@ -489,8 +605,7 @@ function runProxy(options) {
489
605
  proxy.on("error", (err) => {
490
606
  if ("code" in err && err.code === "EADDRINUSE") {
491
607
  console.log("[PROXY] Port is already used");
492
- } else
493
- throw err;
608
+ } else throw err;
494
609
  });
495
610
  process.on("beforeExit", () => proxy.kill());
496
611
  }
@@ -521,8 +636,7 @@ async function updateConfig(options, config) {
521
636
  function VitePluginDecapCMS(options) {
522
637
  let stored = null;
523
638
  const debug = (...str) => {
524
- if (options.debug)
525
- console.debug(str);
639
+ if (options.debug) console.debug(str);
526
640
  };
527
641
  return {
528
642
  name: "vite-plugin-decap-cms",
package/dist/index.d.cts CHANGED
@@ -192,7 +192,7 @@ interface Options {
192
192
  /**
193
193
  * How to load Decap CMS
194
194
  * @default
195
- * { method: 'cdn'}
195
+ * { method: 'cdn', options: { version: '^3.1.11' }}
196
196
  */
197
197
  load?: {
198
198
  method: 'cdn';
@@ -232,8 +232,8 @@ interface Options {
232
232
  }
233
233
 
234
234
  declare function getGitData(): {
235
- readonly branch: string | undefined;
236
- readonly commitSha: string | undefined;
235
+ getBranch(): string | undefined;
236
+ getCommitSha(): string | undefined;
237
237
  };
238
238
  declare function createField<T extends DecapCmsFieldType>(widget: T, data: Omit<DecapCmsFieldWidget<T>, 'widget'>): DecapCmsFieldWidget<T>;
239
239
  declare function createFolderCollection(data: DecapCmsCollection<'folder'>): KeysToCamelCase<Omit<decap_cms_core.CmsCollection, "fields" | "files">> & {
@@ -249,6 +249,11 @@ type OverwriteOptions = Omit<CmsFieldBase, 'name'> & {
249
249
  * @default false
250
250
  */
251
251
  hidden?: boolean;
252
+ /**
253
+ * Hide this field in the CMS editor UI and do not include it in the frontmatter.
254
+ * @default false
255
+ */
256
+ deleted?: boolean;
252
257
  };
253
258
  type VitePressPageFrontmatterKeys = 'title' | 'titleTemplate' | 'description' | 'head' | 'body';
254
259
  interface BaseVitePressFieldOptions<Keys extends string> {
@@ -261,11 +266,14 @@ interface VitePressFieldOptions extends BaseVitePressFieldOptions<VitePressPageF
261
266
  */
262
267
  markdownOptions?: DecapCmsMarkdownFieldRenderOptions;
263
268
  }
264
- type VitePressDefaultThemeFrontmatterKeys = 'navbar' | 'sidebar' | 'aside' | 'outline' | 'lastUpdated' | 'editLink' | 'footer' | 'pageClass';
269
+ type VitePressDefaultThemeFrontmatterKeys = 'layout' | 'navbar' | 'sidebar' | 'aside' | 'outline' | 'lastUpdated' | 'editLink' | 'footer' | 'pageClass';
265
270
  type VitePressDefaultThemeFieldOptions = BaseVitePressFieldOptions<VitePressDefaultThemeFrontmatterKeys>;
271
+ type VitePressHomePageFrontmatterKeys = 'hero' | 'heroName' | 'heroText' | 'heroTagline' | 'heroImage' | 'heroActions' | 'heroActionTheme' | 'heroActionText' | 'heroActionLink' | 'heroActionTarget' | 'heroActionRel' | 'features' | 'featuresTitle' | 'featuresDetails' | 'featuresIcon' | 'featuresLink' | 'featuresLinkText' | 'featuresRel' | 'featuresTarget';
272
+ type VitePressHomePageFieldOptions = BaseVitePressFieldOptions<VitePressHomePageFrontmatterKeys>;
266
273
  declare class VitePress {
267
274
  /**
268
275
  * Create fields for:
276
+ * - layout
269
277
  * - navbar
270
278
  * - sidebar
271
279
  * - aside
@@ -290,6 +298,15 @@ declare class VitePress {
290
298
  * @see https://vitepress.dev/reference/frontmatter-config
291
299
  */
292
300
  static createDefaultPageFields(options?: VitePressFieldOptions): DecapCmsField[];
301
+ /**
302
+ * Create fields for:
303
+ * - layout: home (not overwriteable)
304
+ * - hero
305
+ * - features
306
+ *
307
+ * The object fields (`features`, `hero`, `heroActions`) can not be hidden and deleted.
308
+ */
309
+ static createHomePageFields(options?: VitePressHomePageFieldOptions): ((CmsFieldBase & decap_cms_core.CmsFieldList) | (CmsFieldBase & decap_cms_core.CmsFieldHidden) | undefined)[];
293
310
  static createDefaultPageFolderCollection(name: string, folder: string, options?: VitePressFieldOptions & {
294
311
  collection?: Partial<Omit<DecapCmsCollection<'folder'>, 'name' | 'fields' | 'folder'>>;
295
312
  }): DecapCmsCollection<'folder'>;
@@ -305,4 +322,4 @@ declare class VitePress {
305
322
 
306
323
  declare function VitePluginDecapCMS(options: Options): Plugin;
307
324
 
308
- export { type CdnLinkOptions, type CollectionType, type DecapCmsCollection, type DecapCmsCollectionFile, type DecapCmsConfig, type DecapCmsField, type DecapCmsFieldType, type DecapCmsFieldWidget, type DecapCmsMarkdownFieldRenderOptions, type DecapProxyOptions, type EnvContextOption, type EnvDevContextOption, type HeadConfig, type KeysToCamelCase, type KeysToSnakeCase, type LoginPageOptions, type Options, type OverwriteOptions, VitePress, type VitePressDefaultThemeFieldOptions, type VitePressDefaultThemeFrontmatterKeys, type VitePressFieldOptions, type VitePressPageFrontmatterKeys, createField, createFile, createFileCollection, createFolderCollection, VitePluginDecapCMS as default, getGitData };
325
+ export { type CdnLinkOptions, type CollectionType, type DecapCmsCollection, type DecapCmsCollectionFile, type DecapCmsConfig, type DecapCmsField, type DecapCmsFieldType, type DecapCmsFieldWidget, type DecapCmsMarkdownFieldRenderOptions, type DecapProxyOptions, type EnvContextOption, type EnvDevContextOption, type HeadConfig, type KeysToCamelCase, type KeysToSnakeCase, type LoginPageOptions, type Options, type OverwriteOptions, VitePress, type VitePressDefaultThemeFieldOptions, type VitePressDefaultThemeFrontmatterKeys, type VitePressFieldOptions, type VitePressHomePageFieldOptions, type VitePressHomePageFrontmatterKeys, type VitePressPageFrontmatterKeys, createField, createFile, createFileCollection, createFolderCollection, VitePluginDecapCMS as default, getGitData };
package/dist/index.d.ts CHANGED
@@ -192,7 +192,7 @@ interface Options {
192
192
  /**
193
193
  * How to load Decap CMS
194
194
  * @default
195
- * { method: 'cdn'}
195
+ * { method: 'cdn', options: { version: '^3.1.11' }}
196
196
  */
197
197
  load?: {
198
198
  method: 'cdn';
@@ -232,8 +232,8 @@ interface Options {
232
232
  }
233
233
 
234
234
  declare function getGitData(): {
235
- readonly branch: string | undefined;
236
- readonly commitSha: string | undefined;
235
+ getBranch(): string | undefined;
236
+ getCommitSha(): string | undefined;
237
237
  };
238
238
  declare function createField<T extends DecapCmsFieldType>(widget: T, data: Omit<DecapCmsFieldWidget<T>, 'widget'>): DecapCmsFieldWidget<T>;
239
239
  declare function createFolderCollection(data: DecapCmsCollection<'folder'>): KeysToCamelCase<Omit<decap_cms_core.CmsCollection, "fields" | "files">> & {
@@ -249,6 +249,11 @@ type OverwriteOptions = Omit<CmsFieldBase, 'name'> & {
249
249
  * @default false
250
250
  */
251
251
  hidden?: boolean;
252
+ /**
253
+ * Hide this field in the CMS editor UI and do not include it in the frontmatter.
254
+ * @default false
255
+ */
256
+ deleted?: boolean;
252
257
  };
253
258
  type VitePressPageFrontmatterKeys = 'title' | 'titleTemplate' | 'description' | 'head' | 'body';
254
259
  interface BaseVitePressFieldOptions<Keys extends string> {
@@ -261,11 +266,14 @@ interface VitePressFieldOptions extends BaseVitePressFieldOptions<VitePressPageF
261
266
  */
262
267
  markdownOptions?: DecapCmsMarkdownFieldRenderOptions;
263
268
  }
264
- type VitePressDefaultThemeFrontmatterKeys = 'navbar' | 'sidebar' | 'aside' | 'outline' | 'lastUpdated' | 'editLink' | 'footer' | 'pageClass';
269
+ type VitePressDefaultThemeFrontmatterKeys = 'layout' | 'navbar' | 'sidebar' | 'aside' | 'outline' | 'lastUpdated' | 'editLink' | 'footer' | 'pageClass';
265
270
  type VitePressDefaultThemeFieldOptions = BaseVitePressFieldOptions<VitePressDefaultThemeFrontmatterKeys>;
271
+ type VitePressHomePageFrontmatterKeys = 'hero' | 'heroName' | 'heroText' | 'heroTagline' | 'heroImage' | 'heroActions' | 'heroActionTheme' | 'heroActionText' | 'heroActionLink' | 'heroActionTarget' | 'heroActionRel' | 'features' | 'featuresTitle' | 'featuresDetails' | 'featuresIcon' | 'featuresLink' | 'featuresLinkText' | 'featuresRel' | 'featuresTarget';
272
+ type VitePressHomePageFieldOptions = BaseVitePressFieldOptions<VitePressHomePageFrontmatterKeys>;
266
273
  declare class VitePress {
267
274
  /**
268
275
  * Create fields for:
276
+ * - layout
269
277
  * - navbar
270
278
  * - sidebar
271
279
  * - aside
@@ -290,6 +298,15 @@ declare class VitePress {
290
298
  * @see https://vitepress.dev/reference/frontmatter-config
291
299
  */
292
300
  static createDefaultPageFields(options?: VitePressFieldOptions): DecapCmsField[];
301
+ /**
302
+ * Create fields for:
303
+ * - layout: home (not overwriteable)
304
+ * - hero
305
+ * - features
306
+ *
307
+ * The object fields (`features`, `hero`, `heroActions`) can not be hidden and deleted.
308
+ */
309
+ static createHomePageFields(options?: VitePressHomePageFieldOptions): ((CmsFieldBase & decap_cms_core.CmsFieldList) | (CmsFieldBase & decap_cms_core.CmsFieldHidden) | undefined)[];
293
310
  static createDefaultPageFolderCollection(name: string, folder: string, options?: VitePressFieldOptions & {
294
311
  collection?: Partial<Omit<DecapCmsCollection<'folder'>, 'name' | 'fields' | 'folder'>>;
295
312
  }): DecapCmsCollection<'folder'>;
@@ -305,4 +322,4 @@ declare class VitePress {
305
322
 
306
323
  declare function VitePluginDecapCMS(options: Options): Plugin;
307
324
 
308
- export { type CdnLinkOptions, type CollectionType, type DecapCmsCollection, type DecapCmsCollectionFile, type DecapCmsConfig, type DecapCmsField, type DecapCmsFieldType, type DecapCmsFieldWidget, type DecapCmsMarkdownFieldRenderOptions, type DecapProxyOptions, type EnvContextOption, type EnvDevContextOption, type HeadConfig, type KeysToCamelCase, type KeysToSnakeCase, type LoginPageOptions, type Options, type OverwriteOptions, VitePress, type VitePressDefaultThemeFieldOptions, type VitePressDefaultThemeFrontmatterKeys, type VitePressFieldOptions, type VitePressPageFrontmatterKeys, createField, createFile, createFileCollection, createFolderCollection, VitePluginDecapCMS as default, getGitData };
325
+ export { type CdnLinkOptions, type CollectionType, type DecapCmsCollection, type DecapCmsCollectionFile, type DecapCmsConfig, type DecapCmsField, type DecapCmsFieldType, type DecapCmsFieldWidget, type DecapCmsMarkdownFieldRenderOptions, type DecapProxyOptions, type EnvContextOption, type EnvDevContextOption, type HeadConfig, type KeysToCamelCase, type KeysToSnakeCase, type LoginPageOptions, type Options, type OverwriteOptions, VitePress, type VitePressDefaultThemeFieldOptions, type VitePressDefaultThemeFrontmatterKeys, type VitePressFieldOptions, type VitePressHomePageFieldOptions, type VitePressHomePageFrontmatterKeys, type VitePressPageFrontmatterKeys, createField, createFile, createFileCollection, createFolderCollection, VitePluginDecapCMS as default, getGitData };
package/dist/index.js CHANGED
@@ -44,10 +44,10 @@ function getGitData() {
44
44
  }
45
45
  };
46
46
  return {
47
- get branch() {
47
+ getBranch() {
48
48
  return executeGit("git rev-parse --abbrev-ref HEAD");
49
49
  },
50
- get commitSha() {
50
+ getCommitSha() {
51
51
  return executeGit("git rev-parse HEAD");
52
52
  }
53
53
  };
@@ -69,25 +69,32 @@ function createFileCollection(data) {
69
69
  function createOverwriteableField(widget, data, overwrites) {
70
70
  if (overwrites != void 0) {
71
71
  const toAdd = (key) => {
72
- if ((overwrites == null ? void 0 : overwrites[key]) != void 0 && data[key] !== overwrites[key])
73
- data[key] = overwrites[key];
72
+ if ((overwrites == null ? void 0 : overwrites[key]) != void 0 && data[key] !== overwrites[key]) data[key] = overwrites[key];
74
73
  };
75
74
  for (const key of Object.keys(overwrites)) {
76
- if (key !== "hidden") {
75
+ if (key !== "hidden" && key !== "deleted") {
77
76
  toAdd(key);
78
77
  }
79
78
  }
80
79
  }
81
- if ((overwrites == null ? void 0 : overwrites.hidden) && widget !== "hidden")
82
- return createField("hidden", data);
83
- else
84
- return __spreadProps(__spreadValues({}, data), {
85
- widget
86
- });
80
+ if (overwrites == null ? void 0 : overwrites.deleted) return void 0;
81
+ else if ((overwrites == null ? void 0 : overwrites.hidden) && widget !== "hidden") return createField("hidden", data);
82
+ else return __spreadProps(__spreadValues({}, data), {
83
+ widget
84
+ });
85
+ }
86
+ function filterUndefined(item) {
87
+ return item != void 0;
88
+ }
89
+ function omit(obj, keys) {
90
+ if (!obj) return void 0;
91
+ const validEntries = Object.entries(obj).filter(([key]) => !keys.includes(key));
92
+ return Object.fromEntries(validEntries);
87
93
  }
88
94
  var VitePress = class {
89
95
  /**
90
96
  * Create fields for:
97
+ * - layout
91
98
  * - navbar
92
99
  * - sidebar
93
100
  * - aside
@@ -104,6 +111,12 @@ var VitePress = class {
104
111
  static createDefaultThemeNormalPageFields(options) {
105
112
  const { overwrites } = options != null ? options : {};
106
113
  return [
114
+ createOverwriteableField("string", {
115
+ name: "layout",
116
+ label: "Layout",
117
+ required: false,
118
+ default: "doc"
119
+ }),
107
120
  createOverwriteableField("boolean", {
108
121
  name: "navbar",
109
122
  label: "Whether to display the navbar",
@@ -154,7 +167,7 @@ var VitePress = class {
154
167
  label: "Page class",
155
168
  required: false
156
169
  }, overwrites == null ? void 0 : overwrites.pageClass)
157
- ];
170
+ ].filter(filterUndefined);
158
171
  }
159
172
  /**
160
173
  * Create fields for:
@@ -166,7 +179,7 @@ var VitePress = class {
166
179
  * @see https://vitepress.dev/reference/frontmatter-config
167
180
  */
168
181
  static createDefaultPageFields(options) {
169
- var _a;
182
+ var _a, _b;
170
183
  const { additionalFields, overwrites } = options != null ? options : {};
171
184
  const fields = [
172
185
  createOverwriteableField("string", {
@@ -187,11 +200,123 @@ var VitePress = class {
187
200
  name: "head",
188
201
  label: "Head"
189
202
  }, overwrites == null ? void 0 : overwrites.head)
190
- ];
191
- return fields.concat(additionalFields != null ? additionalFields : []).concat(createOverwriteableField("markdown", __spreadProps(__spreadValues({}, (_a = options == null ? void 0 : options.markdownOptions) != null ? _a : {}), {
203
+ ].filter(filterUndefined);
204
+ return fields.concat(additionalFields != null ? additionalFields : []).concat((_b = createOverwriteableField("markdown", __spreadProps(__spreadValues({}, (_a = options == null ? void 0 : options.markdownOptions) != null ? _a : {}), {
192
205
  name: "body",
193
206
  label: "Page content"
194
- }), overwrites == null ? void 0 : overwrites.body));
207
+ }), overwrites == null ? void 0 : overwrites.body)) != null ? _b : []).filter(filterUndefined);
208
+ }
209
+ /**
210
+ * Create fields for:
211
+ * - layout: home (not overwriteable)
212
+ * - hero
213
+ * - features
214
+ *
215
+ * The object fields (`features`, `hero`, `heroActions`) can not be hidden and deleted.
216
+ */
217
+ static createHomePageFields(options) {
218
+ const { overwrites } = options != null ? options : {};
219
+ const keys = ["hidden", "deleted"];
220
+ return [
221
+ createField("hidden", {
222
+ name: "layout",
223
+ default: "home"
224
+ }),
225
+ createOverwriteableField("object", {
226
+ name: "hero",
227
+ label: "Hero items",
228
+ required: true,
229
+ fields: [
230
+ createOverwriteableField("string", {
231
+ name: "name",
232
+ required: false
233
+ }, overwrites == null ? void 0 : overwrites.heroName),
234
+ createOverwriteableField("string", {
235
+ name: "text"
236
+ }, overwrites == null ? void 0 : overwrites.heroText),
237
+ createOverwriteableField("string", {
238
+ name: "tagline",
239
+ required: false
240
+ }, overwrites == null ? void 0 : overwrites.heroTagline),
241
+ // TODO: add support for object options
242
+ createOverwriteableField("image", {
243
+ name: "image",
244
+ required: false
245
+ }, overwrites == null ? void 0 : overwrites.heroImage),
246
+ createOverwriteableField("list", {
247
+ name: "actions",
248
+ label: "Action buttons",
249
+ label_singular: "action",
250
+ allow_add: true,
251
+ fields: [
252
+ createOverwriteableField("string", {
253
+ name: "text"
254
+ }, overwrites == null ? void 0 : overwrites.heroActionText),
255
+ createOverwriteableField("string", {
256
+ name: "link"
257
+ }, overwrites == null ? void 0 : overwrites.heroActionLink),
258
+ createOverwriteableField("select", {
259
+ name: "theme",
260
+ required: false,
261
+ default: "brand",
262
+ options: [
263
+ "brand",
264
+ "alt"
265
+ ]
266
+ }, overwrites == null ? void 0 : overwrites.heroActionTheme),
267
+ createOverwriteableField("string", {
268
+ name: "target",
269
+ required: false
270
+ }, overwrites == null ? void 0 : overwrites.heroActionTarget),
271
+ createOverwriteableField("string", {
272
+ name: "rel",
273
+ required: false
274
+ }, overwrites == null ? void 0 : overwrites.heroActionRel)
275
+ ].filter(filterUndefined)
276
+ }, omit(overwrites == null ? void 0 : overwrites.heroActions, keys))
277
+ ].filter(filterUndefined)
278
+ }, omit(overwrites == null ? void 0 : overwrites.hero, keys)),
279
+ createOverwriteableField("list", {
280
+ name: "features",
281
+ label: "Features",
282
+ label_singular: "feature",
283
+ allow_add: true,
284
+ required: false,
285
+ fields: [
286
+ createOverwriteableField("string", {
287
+ name: "title",
288
+ required: true
289
+ }, overwrites == null ? void 0 : overwrites.featuresTitle),
290
+ createOverwriteableField("string", {
291
+ name: "details",
292
+ required: false
293
+ }, overwrites == null ? void 0 : overwrites.featuresDetails),
294
+ // TODO: add support for object options
295
+ createOverwriteableField("string", {
296
+ name: "icon",
297
+ required: false
298
+ }, overwrites == null ? void 0 : overwrites.featuresIcon),
299
+ createOverwriteableField("string", {
300
+ name: "link",
301
+ required: false
302
+ }, overwrites == null ? void 0 : overwrites.featuresLink),
303
+ createOverwriteableField("string", {
304
+ name: "linkText",
305
+ label: "Link text",
306
+ required: false
307
+ }, overwrites == null ? void 0 : overwrites.featuresLinkText),
308
+ createOverwriteableField("string", {
309
+ name: "target",
310
+ label: "Target",
311
+ required: false
312
+ }, overwrites == null ? void 0 : overwrites.featuresTarget),
313
+ createOverwriteableField("string", {
314
+ name: "rel",
315
+ required: false
316
+ }, overwrites == null ? void 0 : overwrites.featuresRel)
317
+ ].filter(filterUndefined)
318
+ }, omit(overwrites == null ? void 0 : overwrites.features, keys))
319
+ ];
195
320
  }
196
321
  static createDefaultPageFolderCollection(name, folder, options) {
197
322
  const _a = options != null ? options : {}, { collection } = _a, fieldsOptions = __objRest(_a, ["collection"]);
@@ -239,7 +364,8 @@ function getBooleanFromEnv(value, command) {
239
364
  }
240
365
  function resolveBackend(options, command) {
241
366
  const _a = options, { local, name } = _a, backend = __objRest(_a, ["local", "name"]);
242
- const branch = "useCurrentBranch" in options && getBooleanFromEnv(options.useCurrentBranch, command) ? getGitData().branch : "branch" in backend ? backend.branch : void 0;
367
+ const git = getGitData();
368
+ const branch = "useCurrentBranch" in options && getBooleanFromEnv(options.useCurrentBranch, command) ? git.getBranch() : "branch" in backend ? backend.branch : void 0;
243
369
  delete backend.useCurrentBranch;
244
370
  const resolved = {
245
371
  local_backend: typeof local === "object" ? objToSnakeCase(local) : getBooleanFromEnv(local, command),
@@ -269,8 +395,7 @@ function createConfigFile(config, command) {
269
395
  });
270
396
  })
271
397
  });
272
- } else
273
- throw new Error("Missing either fields or files property in collection");
398
+ } else throw new Error("Missing either fields or files property in collection");
274
399
  })
275
400
  });
276
401
  }
@@ -284,10 +409,8 @@ function createCmsFunction(method, items, createParams, options) {
284
409
  };
285
410
  return (items != null ? items : []).map((item) => {
286
411
  const params = createParams(item);
287
- if (!params)
288
- return null;
289
- else
290
- return create(params);
412
+ if (!params) return null;
413
+ else return create(params);
291
414
  }).filter(Boolean).join((_a = options == null ? void 0 : options.joinChar) != null ? _a : "\n");
292
415
  }
293
416
  function createScript(options) {
@@ -311,8 +434,7 @@ function createScript(options) {
311
434
  ]);
312
435
  const events = createCmsFunction("registerEventListener", Object.keys(eventHooks), (hookName) => {
313
436
  const hook = eventHooks[hookName];
314
- if (!hook)
315
- return null;
437
+ if (!hook) return null;
316
438
  else {
317
439
  const name = hookName.slice(2)[0].toLowerCase() + hookName.slice(3);
318
440
  return `{ name: '${name}', handler: data => { function ${hook.toString()}; ${hookName}({ app: CMS, ...data }) } }`;
@@ -340,7 +462,7 @@ ${editorComponents}
340
462
  }
341
463
 
342
464
  // src/files/index.ts
343
- var defaultDecapCmsCdnVersion = "3.1.3";
465
+ var defaultDecapCmsCdnVersion = "3.1.11";
344
466
  var defaultNetlifyIdentityVersion = "1";
345
467
  var addSlash = (path, slash = "/") => path.endsWith(slash) ? path : path + slash;
346
468
  function resolveCdnRoute(options) {
@@ -351,13 +473,10 @@ function resolveCdnRoute(options) {
351
473
  }
352
474
  function resolveHead(head) {
353
475
  return head.reduce((output, config) => {
354
- if (typeof config === "string")
355
- return output.concat(config);
476
+ if (typeof config === "string") return output.concat(config);
356
477
  if ("skip" in config) {
357
- if (config.skip)
358
- return output;
359
- if (typeof config.head === "string")
360
- return output.concat(config.head);
478
+ if (config.skip) return output;
479
+ if (typeof config.head === "string") return output.concat(config.head);
361
480
  }
362
481
  const item = "head" in config ? config.head : config;
363
482
  let str = `<${item[0]}`;
@@ -365,8 +484,7 @@ function resolveHead(head) {
365
484
  str += ` ${key}="${item[1][key]}"`;
366
485
  }
367
486
  str += item[0] === "meta" ? "/>" : ">";
368
- if (item[2] == void 0)
369
- return output.concat(str);
487
+ if (item[2] == void 0) return output.concat(str);
370
488
  str += item[2] + `</${item[0]}>`;
371
489
  return output.concat(str);
372
490
  }, []);
@@ -404,8 +522,7 @@ function getIndexFeatures(config, loadOptions) {
404
522
  function createIndexFile(pluginOptions) {
405
523
  var _a, _b;
406
524
  const { config, load, login: options, script } = pluginOptions;
407
- if (options == null ? void 0 : options.html)
408
- return options.html;
525
+ if (options == null ? void 0 : options.html) return options.html;
409
526
  const features = getIndexFeatures(config, load);
410
527
  return `<!DOCTYPE html>
411
528
  <html>
@@ -448,8 +565,7 @@ async function writeToFolder(folder, options) {
448
565
  function validateLoadOptions(options) {
449
566
  var _a;
450
567
  const valid = ["npm", "cdn"].includes((_a = options == null ? void 0 : options.method) != null ? _a : "cdn");
451
- if (!valid)
452
- throw new Error("Invalid load options for decap-cms provided");
568
+ if (!valid) throw new Error("Invalid load options for decap-cms provided");
453
569
  }
454
570
  function runProxy(options) {
455
571
  var _a, _b, _c, _d, _e;
@@ -462,8 +578,7 @@ function runProxy(options) {
462
578
  proxy.on("error", (err) => {
463
579
  if ("code" in err && err.code === "EADDRINUSE") {
464
580
  console.log("[PROXY] Port is already used");
465
- } else
466
- throw err;
581
+ } else throw err;
467
582
  });
468
583
  process.on("beforeExit", () => proxy.kill());
469
584
  }
@@ -494,8 +609,7 @@ async function updateConfig(options, config) {
494
609
  function VitePluginDecapCMS(options) {
495
610
  let stored = null;
496
611
  const debug = (...str) => {
497
- if (options.debug)
498
- console.debug(str);
612
+ if (options.debug) console.debug(str);
499
613
  };
500
614
  return {
501
615
  name: "vite-plugin-decap-cms",
package/package.json CHANGED
@@ -1,54 +1,56 @@
1
- {
2
- "name": "vite-plugin-decap-cms",
3
- "version": "0.2.0",
4
- "description": "Simplify the configuration of Decap cms for Vite projects",
5
- "type": "module",
6
- "license": "MIT",
7
- "keywords": [
8
- "vite",
9
- "vite-plugin",
10
- "decap-cms"
11
- ],
12
- "homepage": "https://github.com/ghostrider-05/vite-plugin-decap-cms",
13
- "bugs": "https://github.com/ghostrider-05/vite-plugin-decap-cms/issues",
14
- "repository": {
15
- "type": "git",
16
- "url": "https://github.com/ghostrider-05/vite-plugin-decap-cms"
17
- },
18
- "exports": {
19
- ".": {
20
- "types": "./dist/index.d.ts",
21
- "require": "./dist/index.cjs",
22
- "import": "./dist/index.js"
23
- }
24
- },
25
- "main": "./dist/index.cjs",
26
- "module": "./dist/index.js",
27
- "types": "./dist/index.d.ts",
28
- "files": [
29
- "dist"
30
- ],
31
- "scripts": {
32
- "build": "tsup src/index.ts --dts --format cjs,esm"
33
- },
34
- "dependencies": {
35
- "decap-server": "^3.0.4",
36
- "veaury": "^2.3.18",
37
- "yaml": "^2.4.0"
38
- },
39
- "devDependencies": {
40
- "@types/node": "18",
41
- "@typescript-eslint/eslint-plugin": "^7.4.0",
42
- "@typescript-eslint/parser": "^7.4.0",
43
- "decap-cms-core": "^3.3.5",
44
- "eslint": "^8.57.0",
45
- "tsup": "^8.0.2",
46
- "typescript": "^5.4.3",
47
- "vite": "^5.2.6",
48
- "vitest": "^1.4.0",
49
- "vue": "^3.4.21"
50
- },
51
- "optionalDependencies": {
52
- "decap-cms-app": "^3.1.5"
53
- }
54
- }
1
+ {
2
+ "name": "vite-plugin-decap-cms",
3
+ "version": "0.3.0",
4
+ "description": "Simplify the configuration of Decap cms for Vite projects",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "vite",
9
+ "vite-plugin",
10
+ "decap-cms"
11
+ ],
12
+ "homepage": "https://github.com/ghostrider-05/vite-plugin-decap-cms",
13
+ "bugs": "https://github.com/ghostrider-05/vite-plugin-decap-cms/issues",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/ghostrider-05/vite-plugin-decap-cms"
17
+ },
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "require": "./dist/index.cjs",
22
+ "import": "./dist/index.js"
23
+ }
24
+ },
25
+ "main": "./dist/index.cjs",
26
+ "module": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsup src/index.ts --dts --format cjs,esm"
33
+ },
34
+ "overrides": {
35
+ "react": "^18.3.0",
36
+ "@types/react": "^18.0.17",
37
+ "react-dom": "^18.3.0"
38
+ },
39
+ "dependencies": {
40
+ "decap-server": "^3.0.4",
41
+ "veaury": "^2.4.1",
42
+ "yaml": "^2.4.5"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "18",
46
+ "@typescript-eslint/eslint-plugin": "^7.16.0",
47
+ "@typescript-eslint/parser": "^7.16.0",
48
+ "decap-cms-core": "^3.3.7",
49
+ "eslint": "^8.57.0",
50
+ "tsup": "^8.1.0",
51
+ "typescript": "^5.5.3",
52
+ "vite": "^5.3.3",
53
+ "vitest": "^2.0.1",
54
+ "vue": "^3.4.31"
55
+ }
56
+ }