@momentumcms/server-analog 0.1.10 → 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/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## 0.3.0 (2026-02-20)
2
+
3
+ ### 🚀 Features
4
+
5
+ - add named tabs support with nested data grouping and UI improvements ([#30](https://github.com/DonaldMurillo/momentum-cms/pull/30))
6
+
7
+ ### ❤️ Thank You
8
+
9
+ - Claude Opus 4.6
10
+ - Donald Murillo @DonaldMurillo
11
+
12
+ ## 0.2.0 (2026-02-17)
13
+
14
+ This was a version bump only for server-analog to align it with other projects, there were no code changes.
15
+
1
16
  ## 0.1.10 (2026-02-17)
2
17
 
3
18
  ### 🩹 Fixes
package/index.cjs CHANGED
@@ -301,12 +301,26 @@ var ReferentialIntegrityError = class extends Error {
301
301
  this.constraint = constraint;
302
302
  }
303
303
  };
304
+ function isNamedTab(tab) {
305
+ return typeof tab.name === "string" && tab.name.length > 0;
306
+ }
304
307
  function flattenDataFields(fields) {
305
308
  const result = [];
306
309
  for (const field of fields) {
307
310
  if (field.type === "tabs") {
308
311
  for (const tab of field.tabs) {
309
- result.push(...flattenDataFields(tab.fields));
312
+ if (isNamedTab(tab)) {
313
+ const syntheticGroup = {
314
+ name: tab.name,
315
+ type: "group",
316
+ label: tab.label,
317
+ description: tab.description,
318
+ fields: tab.fields
319
+ };
320
+ result.push(syntheticGroup);
321
+ } else {
322
+ result.push(...flattenDataFields(tab.fields));
323
+ }
310
324
  }
311
325
  } else if (field.type === "collapsible" || field.type === "row") {
312
326
  result.push(...flattenDataFields(field.fields));
@@ -458,6 +472,9 @@ var MediaCollection = defineCollection({
458
472
  singular: "Media",
459
473
  plural: "Media"
460
474
  },
475
+ upload: {
476
+ mimeTypes: ["image/*", "application/pdf", "video/*", "audio/*"]
477
+ },
461
478
  admin: {
462
479
  useAsTitle: "filename",
463
480
  defaultColumns: ["filename", "mimeType", "filesize", "createdAt"]
@@ -478,7 +495,6 @@ var MediaCollection = defineCollection({
478
495
  description: "File size in bytes"
479
496
  }),
480
497
  text("path", {
481
- required: true,
482
498
  label: "Storage Path",
483
499
  description: "Path/key where the file is stored",
484
500
  admin: {
@@ -670,7 +686,20 @@ async function runFieldHooks(hookType, fields, data, req, operation) {
670
686
  for (const field of fields) {
671
687
  if (field.type === "tabs") {
672
688
  for (const tab of field.tabs) {
673
- processedData = await runFieldHooks(hookType, tab.fields, processedData, req, operation);
689
+ if (isNamedTab(tab)) {
690
+ const nested = processedData[tab.name];
691
+ if (nested && typeof nested === "object" && !Array.isArray(nested)) {
692
+ processedData[tab.name] = await runFieldHooks(
693
+ hookType,
694
+ tab.fields,
695
+ nested,
696
+ req,
697
+ operation
698
+ );
699
+ }
700
+ } else {
701
+ processedData = await runFieldHooks(hookType, tab.fields, processedData, req, operation);
702
+ }
674
703
  }
675
704
  continue;
676
705
  }
@@ -680,6 +709,7 @@ async function runFieldHooks(hookType, fields, data, req, operation) {
680
709
  }
681
710
  const hooks = field.hooks?.[hookType];
682
711
  if (hooks && hooks.length > 0) {
712
+ const fieldExistsInData = field.name in processedData;
683
713
  let value = processedData[field.name];
684
714
  for (const hook of hooks) {
685
715
  const result = await Promise.resolve(
@@ -694,7 +724,9 @@ async function runFieldHooks(hookType, fields, data, req, operation) {
694
724
  value = result;
695
725
  }
696
726
  }
697
- processedData[field.name] = value;
727
+ if (fieldExistsInData || value !== void 0) {
728
+ processedData[field.name] = value;
729
+ }
698
730
  }
699
731
  if (field.type === "group" && processedData[field.name] && typeof processedData[field.name] === "object" && !Array.isArray(processedData[field.name])) {
700
732
  processedData[field.name] = await runFieldHooks(
package/index.js CHANGED
@@ -263,12 +263,26 @@ var ReferentialIntegrityError = class extends Error {
263
263
  this.constraint = constraint;
264
264
  }
265
265
  };
266
+ function isNamedTab(tab) {
267
+ return typeof tab.name === "string" && tab.name.length > 0;
268
+ }
266
269
  function flattenDataFields(fields) {
267
270
  const result = [];
268
271
  for (const field of fields) {
269
272
  if (field.type === "tabs") {
270
273
  for (const tab of field.tabs) {
271
- result.push(...flattenDataFields(tab.fields));
274
+ if (isNamedTab(tab)) {
275
+ const syntheticGroup = {
276
+ name: tab.name,
277
+ type: "group",
278
+ label: tab.label,
279
+ description: tab.description,
280
+ fields: tab.fields
281
+ };
282
+ result.push(syntheticGroup);
283
+ } else {
284
+ result.push(...flattenDataFields(tab.fields));
285
+ }
272
286
  }
273
287
  } else if (field.type === "collapsible" || field.type === "row") {
274
288
  result.push(...flattenDataFields(field.fields));
@@ -420,6 +434,9 @@ var MediaCollection = defineCollection({
420
434
  singular: "Media",
421
435
  plural: "Media"
422
436
  },
437
+ upload: {
438
+ mimeTypes: ["image/*", "application/pdf", "video/*", "audio/*"]
439
+ },
423
440
  admin: {
424
441
  useAsTitle: "filename",
425
442
  defaultColumns: ["filename", "mimeType", "filesize", "createdAt"]
@@ -440,7 +457,6 @@ var MediaCollection = defineCollection({
440
457
  description: "File size in bytes"
441
458
  }),
442
459
  text("path", {
443
- required: true,
444
460
  label: "Storage Path",
445
461
  description: "Path/key where the file is stored",
446
462
  admin: {
@@ -632,7 +648,20 @@ async function runFieldHooks(hookType, fields, data, req, operation) {
632
648
  for (const field of fields) {
633
649
  if (field.type === "tabs") {
634
650
  for (const tab of field.tabs) {
635
- processedData = await runFieldHooks(hookType, tab.fields, processedData, req, operation);
651
+ if (isNamedTab(tab)) {
652
+ const nested = processedData[tab.name];
653
+ if (nested && typeof nested === "object" && !Array.isArray(nested)) {
654
+ processedData[tab.name] = await runFieldHooks(
655
+ hookType,
656
+ tab.fields,
657
+ nested,
658
+ req,
659
+ operation
660
+ );
661
+ }
662
+ } else {
663
+ processedData = await runFieldHooks(hookType, tab.fields, processedData, req, operation);
664
+ }
636
665
  }
637
666
  continue;
638
667
  }
@@ -642,6 +671,7 @@ async function runFieldHooks(hookType, fields, data, req, operation) {
642
671
  }
643
672
  const hooks = field.hooks?.[hookType];
644
673
  if (hooks && hooks.length > 0) {
674
+ const fieldExistsInData = field.name in processedData;
645
675
  let value = processedData[field.name];
646
676
  for (const hook of hooks) {
647
677
  const result = await Promise.resolve(
@@ -656,7 +686,9 @@ async function runFieldHooks(hookType, fields, data, req, operation) {
656
686
  value = result;
657
687
  }
658
688
  }
659
- processedData[field.name] = value;
689
+ if (fieldExistsInData || value !== void 0) {
690
+ processedData[field.name] = value;
691
+ }
660
692
  }
661
693
  if (field.type === "group" && processedData[field.name] && typeof processedData[field.name] === "object" && !Array.isArray(processedData[field.name])) {
662
694
  processedData[field.name] = await runFieldHooks(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momentumcms/server-analog",
3
- "version": "0.1.10",
3
+ "version": "0.3.0",
4
4
  "description": "Nitro/h3 adapter for Momentum CMS with Analog.js support",
5
5
  "license": "MIT",
6
6
  "author": "Momentum CMS Contributors",