@xleddyl/nuxt-cms 0.1.60 → 0.2.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.
Files changed (142) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +137 -9
  3. package/dist/runtime/app/components/cms/BlocksField.vue +86 -10
  4. package/dist/runtime/app/components/cms/BrandMark.d.vue.ts +6 -0
  5. package/dist/runtime/app/components/cms/BrandMark.vue +19 -0
  6. package/dist/runtime/app/components/cms/BrandMark.vue.d.ts +6 -0
  7. package/dist/runtime/app/components/cms/Checkbox.d.vue.ts +11 -0
  8. package/dist/runtime/app/components/cms/Checkbox.vue +21 -0
  9. package/dist/runtime/app/components/cms/Checkbox.vue.d.ts +11 -0
  10. package/dist/runtime/app/components/cms/EmptyState.vue +4 -2
  11. package/dist/runtime/app/components/cms/EntryDrawer.vue +1 -0
  12. package/dist/runtime/app/components/cms/EntryForm.d.vue.ts +1 -0
  13. package/dist/runtime/app/components/cms/EntryForm.vue +105 -30
  14. package/dist/runtime/app/components/cms/EntryForm.vue.d.ts +1 -0
  15. package/dist/runtime/app/components/cms/FieldInput.vue +20 -5
  16. package/dist/runtime/app/components/cms/FormField.d.vue.ts +5 -3
  17. package/dist/runtime/app/components/cms/FormField.vue +8 -2
  18. package/dist/runtime/app/components/cms/FormField.vue.d.ts +5 -3
  19. package/dist/runtime/app/components/cms/Icon.vue +1 -1
  20. package/dist/runtime/app/components/cms/MediaDetail.d.vue.ts +16 -0
  21. package/dist/runtime/app/components/cms/MediaDetail.vue +169 -0
  22. package/dist/runtime/app/components/cms/MediaDetail.vue.d.ts +16 -0
  23. package/dist/runtime/app/components/cms/MediaField.vue +9 -2
  24. package/dist/runtime/app/components/cms/MediaFolderPicker.d.vue.ts +2 -2
  25. package/dist/runtime/app/components/cms/MediaFolderPicker.vue +55 -54
  26. package/dist/runtime/app/components/cms/MediaFolderPicker.vue.d.ts +2 -2
  27. package/dist/runtime/app/components/cms/MediaGallery.d.vue.ts +8 -2
  28. package/dist/runtime/app/components/cms/MediaGallery.vue +927 -336
  29. package/dist/runtime/app/components/cms/MediaGallery.vue.d.ts +8 -2
  30. package/dist/runtime/app/components/cms/MediaUpload.d.vue.ts +1 -0
  31. package/dist/runtime/app/components/cms/MediaUpload.vue +76 -139
  32. package/dist/runtime/app/components/cms/MediaUpload.vue.d.ts +1 -0
  33. package/dist/runtime/app/components/cms/PageHeader.d.vue.ts +5 -3
  34. package/dist/runtime/app/components/cms/PageHeader.vue +16 -6
  35. package/dist/runtime/app/components/cms/PageHeader.vue.d.ts +5 -3
  36. package/dist/runtime/app/components/cms/PasswordInput.d.vue.ts +17 -0
  37. package/dist/runtime/app/components/cms/PasswordInput.vue +77 -0
  38. package/dist/runtime/app/components/cms/PasswordInput.vue.d.ts +17 -0
  39. package/dist/runtime/app/components/cms/SelectMenu.vue +3 -5
  40. package/dist/runtime/app/components/cms/SettingsAccount.d.vue.ts +3 -0
  41. package/dist/runtime/app/components/cms/SettingsAccount.vue +138 -0
  42. package/dist/runtime/app/components/cms/SettingsAccount.vue.d.ts +3 -0
  43. package/dist/runtime/app/components/cms/SettingsColumns.d.vue.ts +16 -0
  44. package/dist/runtime/app/components/cms/SettingsColumns.vue +102 -0
  45. package/dist/runtime/app/components/cms/SettingsColumns.vue.d.ts +16 -0
  46. package/dist/runtime/app/components/cms/SettingsEntry.d.vue.ts +6 -0
  47. package/dist/runtime/app/components/cms/SettingsEntry.vue +133 -0
  48. package/dist/runtime/app/components/cms/SettingsEntry.vue.d.ts +6 -0
  49. package/dist/runtime/app/components/cms/SettingsLayout.d.vue.ts +16 -0
  50. package/dist/runtime/app/components/cms/SettingsLayout.vue +177 -0
  51. package/dist/runtime/app/components/cms/SettingsLayout.vue.d.ts +16 -0
  52. package/dist/runtime/app/components/cms/SettingsModal.d.vue.ts +16 -0
  53. package/dist/runtime/app/components/cms/SettingsModal.vue +179 -0
  54. package/dist/runtime/app/components/cms/SettingsModal.vue.d.ts +16 -0
  55. package/dist/runtime/app/components/cms/SettingsUsers.d.vue.ts +3 -0
  56. package/dist/runtime/app/components/cms/SettingsUsers.vue +270 -0
  57. package/dist/runtime/app/components/cms/SettingsUsers.vue.d.ts +3 -0
  58. package/dist/runtime/app/components/cms/Spinner.vue +2 -2
  59. package/dist/runtime/app/components/cms/Table.d.vue.ts +7 -2
  60. package/dist/runtime/app/components/cms/Table.vue +38 -1
  61. package/dist/runtime/app/components/cms/Table.vue.d.ts +7 -2
  62. package/dist/runtime/app/components/cms/Toaster.vue +1 -1
  63. package/dist/runtime/app/components/cms/WelcomeModal.d.vue.ts +3 -0
  64. package/dist/runtime/app/components/cms/WelcomeModal.vue +69 -0
  65. package/dist/runtime/app/components/cms/WelcomeModal.vue.d.ts +3 -0
  66. package/dist/runtime/app/composables/cms-account.d.ts +11 -0
  67. package/dist/runtime/app/composables/cms-account.js +23 -0
  68. package/dist/runtime/app/composables/cms-media-library.d.ts +75 -0
  69. package/dist/runtime/app/composables/cms-media-library.js +151 -0
  70. package/dist/runtime/app/composables/cms-media-uploader.d.ts +22 -0
  71. package/dist/runtime/app/composables/cms-media-uploader.js +244 -0
  72. package/dist/runtime/app/composables/cms-settings.d.ts +13 -0
  73. package/dist/runtime/app/composables/cms-settings.js +59 -0
  74. package/dist/runtime/app/composables/cms-theme.d.ts +9 -0
  75. package/dist/runtime/app/composables/cms-theme.js +27 -0
  76. package/dist/runtime/app/layouts/cms-admin.d.vue.ts +2 -2
  77. package/dist/runtime/app/layouts/cms-admin.vue +71 -17
  78. package/dist/runtime/app/layouts/cms-admin.vue.d.ts +2 -2
  79. package/dist/runtime/app/pages/admin-collection.vue +127 -101
  80. package/dist/runtime/app/pages/admin-entry.vue +1 -0
  81. package/dist/runtime/app/pages/admin-login.vue +18 -3
  82. package/dist/runtime/app/pages/admin-media.vue +19 -3
  83. package/dist/runtime/app/utils/ui.d.ts +2 -0
  84. package/dist/runtime/app/utils/ui.js +18 -0
  85. package/dist/runtime/assets/main.css +1 -1
  86. package/dist/runtime/server/api/account-password.put.d.ts +4 -0
  87. package/dist/runtime/server/api/account-password.put.js +38 -0
  88. package/dist/runtime/server/api/account-welcome.post.d.ts +4 -0
  89. package/dist/runtime/server/api/account-welcome.post.js +14 -0
  90. package/dist/runtime/server/api/account.put.d.ts +3 -0
  91. package/dist/runtime/server/api/account.put.js +25 -0
  92. package/dist/runtime/server/api/media-delete.post.d.ts +4 -0
  93. package/dist/runtime/server/api/media-delete.post.js +23 -0
  94. package/dist/runtime/server/api/media-folder.delete.js +6 -16
  95. package/dist/runtime/server/api/media-folder.patch.d.ts +5 -0
  96. package/dist/runtime/server/api/media-folder.patch.js +68 -0
  97. package/dist/runtime/server/api/media-folder.post.js +3 -15
  98. package/dist/runtime/server/api/media-move.post.d.ts +5 -0
  99. package/dist/runtime/server/api/media-move.post.js +28 -0
  100. package/dist/runtime/server/api/media-usage.post.d.ts +5 -0
  101. package/dist/runtime/server/api/media-usage.post.js +15 -0
  102. package/dist/runtime/server/api/settings.delete.d.ts +4 -0
  103. package/dist/runtime/server/api/settings.delete.js +12 -0
  104. package/dist/runtime/server/api/settings.get.d.ts +3 -0
  105. package/dist/runtime/server/api/settings.get.js +15 -0
  106. package/dist/runtime/server/api/settings.put.d.ts +3 -0
  107. package/dist/runtime/server/api/settings.put.js +14 -0
  108. package/dist/runtime/server/api/user.delete.d.ts +4 -0
  109. package/dist/runtime/server/api/user.delete.js +12 -0
  110. package/dist/runtime/server/api/user.put.d.ts +3 -0
  111. package/dist/runtime/server/api/user.put.js +27 -0
  112. package/dist/runtime/server/api/users.get.d.ts +3 -0
  113. package/dist/runtime/server/api/users.get.js +11 -0
  114. package/dist/runtime/server/api/users.post.d.ts +3 -0
  115. package/dist/runtime/server/api/users.post.js +32 -0
  116. package/dist/runtime/server/plugins/session-validate.d.ts +2 -0
  117. package/dist/runtime/server/plugins/session-validate.js +13 -0
  118. package/dist/runtime/server/routes/auth/login.post.js +18 -8
  119. package/dist/runtime/server/utils/media-folders.d.ts +86 -0
  120. package/dist/runtime/server/utils/media-folders.js +37 -0
  121. package/dist/runtime/server/utils/media-references.d.ts +7 -1
  122. package/dist/runtime/server/utils/media-references.js +40 -0
  123. package/dist/runtime/server/utils/media-usage.d.ts +2 -0
  124. package/dist/runtime/server/utils/media-usage.js +18 -0
  125. package/dist/runtime/server/utils/password.d.ts +3 -0
  126. package/dist/runtime/server/utils/password.js +31 -0
  127. package/dist/runtime/server/utils/require-admin.d.ts +6 -2
  128. package/dist/runtime/server/utils/require-admin.js +33 -28
  129. package/dist/runtime/server/utils/same-origin.d.ts +2 -0
  130. package/dist/runtime/server/utils/same-origin.js +24 -0
  131. package/dist/runtime/server/utils/settings.d.ts +16 -0
  132. package/dist/runtime/server/utils/settings.js +31 -0
  133. package/dist/runtime/server/utils/users.d.ts +15 -0
  134. package/dist/runtime/server/utils/users.js +34 -0
  135. package/dist/runtime/shared/index.d.ts +29 -1
  136. package/dist/runtime/shared/index.js +36 -2
  137. package/dist/runtime/shared/layout.d.ts +34 -0
  138. package/dist/runtime/shared/layout.js +130 -0
  139. package/dist/runtime/shared/users.d.ts +18 -0
  140. package/dist/runtime/shared/users.js +1 -0
  141. package/dist/runtime/shared/validation.js +5 -4
  142. package/package.json +3 -3
@@ -0,0 +1,34 @@
1
+ import { eq } from "drizzle-orm";
2
+ import { useDb } from "#cms-db";
3
+ import { cms_users } from "#cms-tables";
4
+ export async function findUserByEmail(email) {
5
+ const [row] = await useDb().select().from(cms_users).where(eq(cms_users.email, email.toLowerCase()));
6
+ return row;
7
+ }
8
+ export async function findUserById(id) {
9
+ const [row] = await useDb().select().from(cms_users).where(eq(cms_users.id, id));
10
+ return row;
11
+ }
12
+ export function passwordStamp(passwordHash) {
13
+ return passwordHash.slice(-16);
14
+ }
15
+ export function sessionUserFor(row) {
16
+ return {
17
+ id: row.id,
18
+ email: row.email,
19
+ name: row.name ?? null,
20
+ role: "admin",
21
+ firstLogin: !!row.mustChangePassword,
22
+ passwordStamp: passwordStamp(row.passwordHash)
23
+ };
24
+ }
25
+ export function toAccount(row) {
26
+ return {
27
+ id: row.id,
28
+ email: row.email,
29
+ name: row.name ?? null,
30
+ role: row.role,
31
+ lastLoginAt: row.lastLoginAt ?? null,
32
+ createdAt: String(row.createdAt)
33
+ };
34
+ }
@@ -1,5 +1,6 @@
1
1
  import type { PgTable } from 'drizzle-orm/pg-core';
2
2
  import type { SQLiteTable } from 'drizzle-orm/sqlite-core';
3
+ import type { CmsLayoutBlock, CmsListInput } from './layout.js';
3
4
  export type CmsTable = SQLiteTable | PgTable;
4
5
  export type FieldType = 'text' | 'richtext' | 'number' | 'boolean' | 'date' | 'email' | 'slug' | 'select' | 'json' | 'media' | 'relation' | 'blocks';
5
6
  export interface CmsI18n {
@@ -23,7 +24,15 @@ export declare const MEDIA_FOLDER_MAX_DEPTH = 4;
23
24
  export declare const MEDIA_FOLDER_MARKER = ".keep";
24
25
  export declare function mediaFolderMarkerKey(folder: string): string;
25
26
  export declare function isMediaFolderMarker(key: string): boolean;
27
+ export declare function mediaFolderSegments(value: string | null | undefined): string[];
26
28
  export declare function normalizeMediaFolder(value: string | null | undefined): string | null;
29
+ export declare function mediaFolderDepth(folder: string | null | undefined): number;
30
+ export declare function mediaFolderParent(folder: string): string | null;
31
+ export declare function mediaFolderName(folder: string): string;
32
+ export declare function mediaFolderAncestors(folder: string): string[];
33
+ export declare function isWithinMediaFolder(folder: string | null | undefined, parent: string | null): boolean;
34
+ export declare function rebaseMediaFolder(folder: string, from: string, to: string): string;
35
+ export declare function expandMediaFolders(folders: Iterable<string | null | undefined>): string[];
27
36
  export interface MediaItem {
28
37
  id?: number;
29
38
  key: string;
@@ -43,6 +52,14 @@ export interface MediaSourceInfo {
43
52
  root: string;
44
53
  builtAt: string | null;
45
54
  }
55
+ export interface MediaUsage {
56
+ collection: string;
57
+ label: string;
58
+ kind: CmsEntryKind;
59
+ id: string | null;
60
+ title: string | null;
61
+ field: string;
62
+ }
46
63
  export interface BlockConfig {
47
64
  label: string;
48
65
  fields: Record<string, FieldConfig>;
@@ -60,6 +77,8 @@ export interface CmsTab {
60
77
  export interface FieldConfig {
61
78
  label: string;
62
79
  type: FieldType;
80
+ description?: string;
81
+ placeholder?: string;
63
82
  tab?: string;
64
83
  required?: boolean;
65
84
  private?: boolean;
@@ -122,6 +141,9 @@ export interface CmsEntry {
122
141
  columns?: string[];
123
142
  tabs?: CmsTab[];
124
143
  table?: CmsTable;
144
+ icon?: string;
145
+ layout?: CmsLayoutBlock[];
146
+ list?: CmsListInput;
125
147
  }
126
148
  export declare const MOBILE_MEDIA_SUFFIX = "Mobile";
127
149
  export declare function mobileMediaKey(key: string): string;
@@ -152,6 +174,8 @@ export declare function blocksFieldTypeName(entryName: string, fieldKey: string)
152
174
  export declare function blockTypeName(entryName: string, fieldKey: string, blockName: string): string;
153
175
  interface FieldInputBase {
154
176
  label: string;
177
+ description?: string;
178
+ placeholder?: string;
155
179
  required?: boolean;
156
180
  private?: boolean;
157
181
  tab?: string;
@@ -218,6 +242,9 @@ export type CmsFieldInput = TextFieldInput | RichtextFieldInput | NumberFieldInp
218
242
  interface CmsEntryInputBase {
219
243
  id: string;
220
244
  label: string;
245
+ icon?: string;
246
+ layout?: CmsLayoutBlock[];
247
+ list?: CmsListInput;
221
248
  drafts?: boolean;
222
249
  tabs?: CmsTab[];
223
250
  fields: Record<string, CmsFieldInput>;
@@ -245,4 +272,5 @@ export interface CmsPageInput extends CmsEntryInputBase {
245
272
  export type CmsEntryInput = CmsCollectionInput | CmsSingleInput | CmsPageInput;
246
273
  export type CmsConfigInput = Record<string, CmsEntryInput>;
247
274
  export declare function defineCmsConfig<T extends CmsConfigInput>(config: T): T;
248
- export {};
275
+ export * from './layout.js';
276
+ export * from './users.js';
@@ -73,11 +73,43 @@ export function mediaFolderMarkerKey(folder) {
73
73
  export function isMediaFolderMarker(key) {
74
74
  return key.endsWith(`/${MEDIA_FOLDER_MARKER}`);
75
75
  }
76
+ export function mediaFolderSegments(value) {
77
+ return value ? value.split("/").map(slugify).filter(Boolean) : [];
78
+ }
76
79
  export function normalizeMediaFolder(value) {
77
- if (!value) return null;
78
- const segments = value.split("/").map(slugify).filter(Boolean).slice(0, MEDIA_FOLDER_MAX_DEPTH);
80
+ const segments = mediaFolderSegments(value).slice(0, MEDIA_FOLDER_MAX_DEPTH);
79
81
  return segments.length ? segments.join("/") : null;
80
82
  }
83
+ export function mediaFolderDepth(folder) {
84
+ return folder ? folder.split("/").length : 0;
85
+ }
86
+ export function mediaFolderParent(folder) {
87
+ const index = folder.lastIndexOf("/");
88
+ return index === -1 ? null : folder.slice(0, index);
89
+ }
90
+ export function mediaFolderName(folder) {
91
+ return folder.slice(folder.lastIndexOf("/") + 1);
92
+ }
93
+ export function mediaFolderAncestors(folder) {
94
+ const segments = folder.split("/");
95
+ return segments.map((_, index) => segments.slice(0, index + 1).join("/"));
96
+ }
97
+ export function isWithinMediaFolder(folder, parent) {
98
+ if (!parent) return true;
99
+ return !!folder && (folder === parent || folder.startsWith(`${parent}/`));
100
+ }
101
+ export function rebaseMediaFolder(folder, from, to) {
102
+ if (folder === from) return to;
103
+ return folder.startsWith(`${from}/`) ? `${to}${folder.slice(from.length)}` : folder;
104
+ }
105
+ export function expandMediaFolders(folders) {
106
+ const expanded = /* @__PURE__ */ new Set();
107
+ for (const folder of folders) {
108
+ if (!folder) continue;
109
+ for (const ancestor of mediaFolderAncestors(folder)) expanded.add(ancestor);
110
+ }
111
+ return [...expanded].sort();
112
+ }
81
113
  export function isPrivateField(field) {
82
114
  return !!field.private;
83
115
  }
@@ -347,3 +379,5 @@ export function blockTypeName(entryName, fieldKey, blockName) {
347
379
  export function defineCmsConfig(config) {
348
380
  return config;
349
381
  }
382
+ export * from "./layout.js";
383
+ export * from "./users.js";
@@ -0,0 +1,34 @@
1
+ import type { FieldConfig } from './index.js';
2
+ export declare const MAX_LAYOUT_ROW_FIELDS = 4;
3
+ export type CmsLayoutRow = string | string[];
4
+ export interface CmsLayoutSectionInput {
5
+ title: string;
6
+ description?: string;
7
+ collapsed?: boolean;
8
+ rows: CmsLayoutRow[];
9
+ }
10
+ export type CmsLayoutBlock = CmsLayoutRow | CmsLayoutSectionInput;
11
+ export interface CmsLayoutSection {
12
+ title?: string;
13
+ description?: string;
14
+ collapsed?: boolean;
15
+ rows: string[][];
16
+ }
17
+ export interface CmsFormLayout {
18
+ main: CmsLayoutSection[];
19
+ }
20
+ export interface CmsListInput {
21
+ columns?: string[];
22
+ }
23
+ export interface CmsEntrySettings {
24
+ columns?: string[];
25
+ layout?: CmsFormLayout;
26
+ }
27
+ export type CmsSettingsMap = Record<string, CmsEntrySettings>;
28
+ export declare function layoutFromConfig(input: CmsLayoutBlock[] | undefined): CmsFormLayout;
29
+ export declare function layoutKeys(input: CmsLayoutBlock[] | undefined): string[];
30
+ export declare function resolveFormLayout(fields: Record<string, FieldConfig>, layout: CmsFormLayout | undefined): CmsFormLayout;
31
+ export declare function defaultListColumns(fields: Record<string, FieldConfig>, list: CmsListInput | undefined, extra?: string[]): string[];
32
+ export declare function resolveListColumns(fields: Record<string, FieldConfig>, saved: string[] | undefined, list: CmsListInput | undefined, extra?: string[]): string[];
33
+ export declare function layoutErrors(at: string, fields: Record<string, FieldConfig>, input: CmsLayoutBlock[] | undefined): string[];
34
+ export declare function listErrors(at: string, fields: Record<string, FieldConfig>, list: CmsListInput | undefined, extra?: string[]): string[];
@@ -0,0 +1,130 @@
1
+ export const MAX_LAYOUT_ROW_FIELDS = 4;
2
+ function isSectionInput(block) {
3
+ return typeof block === "object" && !Array.isArray(block);
4
+ }
5
+ function toRow(row) {
6
+ return Array.isArray(row) ? [...row] : [row];
7
+ }
8
+ function sectionsFromBlocks(blocks) {
9
+ const sections = [];
10
+ let loose = null;
11
+ for (const block of blocks ?? []) {
12
+ if (isSectionInput(block)) {
13
+ loose = null;
14
+ sections.push({
15
+ title: block.title,
16
+ ...block.description ? { description: block.description } : {},
17
+ ...block.collapsed ? { collapsed: true } : {},
18
+ rows: block.rows.map(toRow)
19
+ });
20
+ continue;
21
+ }
22
+ if (!loose) {
23
+ loose = { rows: [] };
24
+ sections.push(loose);
25
+ }
26
+ loose.rows.push(toRow(block));
27
+ }
28
+ return sections;
29
+ }
30
+ export function layoutFromConfig(input) {
31
+ return { main: sectionsFromBlocks(input) };
32
+ }
33
+ export function layoutKeys(input) {
34
+ return layoutFromConfig(input).main.flatMap((section) => section.rows.flat());
35
+ }
36
+ function cloneSection(section) {
37
+ return { ...section, rows: section.rows.map((row) => [...row]) };
38
+ }
39
+ export function resolveFormLayout(fields, layout) {
40
+ const known = new Set(Object.keys(fields));
41
+ const mobileOf = new Map(
42
+ Object.entries(fields).filter(([, field]) => field.mobileOf && known.has(field.mobileOf)).map(([key, field]) => [field.mobileOf, key])
43
+ );
44
+ const placed = /* @__PURE__ */ new Set();
45
+ const clean = (sections) => (sections ?? []).map(cloneSection).map((section) => ({
46
+ ...section,
47
+ rows: section.rows.map(
48
+ (row) => row.filter((key) => {
49
+ if (!known.has(key) || placed.has(key)) return false;
50
+ placed.add(key);
51
+ return true;
52
+ })
53
+ ).filter((row) => row.length)
54
+ }));
55
+ const main = clean(layout?.main);
56
+ for (const section of main) {
57
+ for (let r = 0; r < section.rows.length; r++) {
58
+ const row = section.rows[r];
59
+ for (const [parent, mobile] of mobileOf) {
60
+ const index = row.indexOf(parent);
61
+ if (index === -1 || placed.has(mobile)) continue;
62
+ if (row.length < MAX_LAYOUT_ROW_FIELDS) row.splice(index + 1, 0, mobile);
63
+ else section.rows.splice(r + 1, 0, [mobile]);
64
+ placed.add(mobile);
65
+ }
66
+ }
67
+ }
68
+ const missing = Object.keys(fields).filter((key) => !placed.has(key));
69
+ if (missing.length) {
70
+ const rows = [];
71
+ for (const key of missing) {
72
+ if (placed.has(key)) continue;
73
+ const mobile = mobileOf.get(key);
74
+ const row = mobile && !placed.has(mobile) ? [key, mobile] : [key];
75
+ row.forEach((k) => placed.add(k));
76
+ rows.push(row);
77
+ }
78
+ const last = main[main.length - 1];
79
+ if (last && !last.title) last.rows.push(...rows);
80
+ else main.push({ rows });
81
+ }
82
+ return { main };
83
+ }
84
+ export function defaultListColumns(fields, list, extra = []) {
85
+ const available = [...Object.keys(fields), ...extra];
86
+ const configured = list?.columns?.filter((key) => available.includes(key));
87
+ if (configured?.length) return configured;
88
+ return [...Object.keys(fields).slice(0, 4), ...extra];
89
+ }
90
+ export function resolveListColumns(fields, saved, list, extra = []) {
91
+ const available = /* @__PURE__ */ new Set([...Object.keys(fields), ...extra]);
92
+ const kept = saved?.filter((key) => available.has(key));
93
+ return kept?.length ? [...new Set(kept)] : defaultListColumns(fields, list, extra);
94
+ }
95
+ export function layoutErrors(at, fields, input) {
96
+ if (!input) return [];
97
+ const errors = [];
98
+ if (!Array.isArray(input)) return [`${at}: layout must be an array of rows and sections`];
99
+ const blocks = input;
100
+ const seen = /* @__PURE__ */ new Set();
101
+ const checkRow = (row) => {
102
+ const keys = toRow(row);
103
+ if (!keys.length) errors.push(`${at}: layout has an empty row`);
104
+ if (keys.length > MAX_LAYOUT_ROW_FIELDS)
105
+ errors.push(
106
+ `${at}: layout row [${keys.join(", ")}] has more than ${MAX_LAYOUT_ROW_FIELDS} fields`
107
+ );
108
+ for (const key of keys) {
109
+ if (!Object.hasOwn(fields, key))
110
+ errors.push(`${at}: layout names '${key}', which is not a declared field`);
111
+ else if (seen.has(key)) errors.push(`${at}: layout names '${key}' more than once`);
112
+ seen.add(key);
113
+ }
114
+ };
115
+ for (const block of blocks) {
116
+ if (isSectionInput(block)) {
117
+ if (!block.title) errors.push(`${at}: every layout section needs a title`);
118
+ if (!Array.isArray(block.rows))
119
+ errors.push(`${at}: layout section '${block.title}' needs a rows array`);
120
+ else block.rows.forEach(checkRow);
121
+ } else {
122
+ checkRow(block);
123
+ }
124
+ }
125
+ return errors;
126
+ }
127
+ export function listErrors(at, fields, list, extra = []) {
128
+ if (!list?.columns) return [];
129
+ return list.columns.filter((key) => !Object.hasOwn(fields, key) && !extra.includes(key)).map((key) => `${at}: list.columns names '${key}', which is not a declared field`);
130
+ }
@@ -0,0 +1,18 @@
1
+ export type CmsUserRole = 'superadmin' | 'admin';
2
+ export interface CmsSessionUser {
3
+ id?: string;
4
+ email: string;
5
+ name?: string | null;
6
+ role?: CmsUserRole;
7
+ firstLogin?: boolean;
8
+ passwordStamp?: string;
9
+ }
10
+ export interface CmsAccount {
11
+ id: string;
12
+ email: string;
13
+ name: string | null;
14
+ role: CmsUserRole;
15
+ lastLoginAt: string | null;
16
+ createdAt: string;
17
+ }
18
+ export declare const MIN_PASSWORD_LENGTH = 8;
@@ -0,0 +1 @@
1
+ export const MIN_PASSWORD_LENGTH = 8;
@@ -31,8 +31,9 @@ function scalarSchema(field, m) {
31
31
  return field.required ? z.string().trim().min(1, m.required) : z.string();
32
32
  }
33
33
  }
34
- function optionalize(field, base) {
35
- return field.required ? base : base.nullish().transform((v) => v ?? null);
34
+ function optionalize(field, base, m) {
35
+ if (!field.required) return base.nullish().transform((v) => v ?? null);
36
+ return z.any().refine((v) => v !== null && v !== void 0, m.required).pipe(base);
36
37
  }
37
38
  function translatableSchema(field, i18n, m) {
38
39
  const { locales, defaultLocale } = i18n;
@@ -42,7 +43,7 @@ function translatableSchema(field, i18n, m) {
42
43
  function blockFieldSchema(blockField, i18n, m) {
43
44
  if (isTranslatableField(blockField) && i18n.locales.length)
44
45
  return translatableSchema(blockField, i18n, m);
45
- return optionalize(blockField, scalarSchema(blockField, m));
46
+ return optionalize(blockField, scalarSchema(blockField, m), m);
46
47
  }
47
48
  function blocksSchema(field, i18n, m) {
48
49
  const variants = Object.entries(field.blocks ?? {}).map(
@@ -99,7 +100,7 @@ export function buildEntrySchema(entry, i18n, messages) {
99
100
  shape[key] = field.required ? json.refine((v) => v !== void 0 && v !== null, m.required) : json.nullish().transform((v) => v ?? null);
100
101
  continue;
101
102
  }
102
- shape[key] = optionalize(field, scalarSchema(field, m));
103
+ shape[key] = optionalize(field, scalarSchema(field, m), m);
103
104
  }
104
105
  if (entry.drafts) {
105
106
  shape.status = z.enum(["draft", "published"]).nullish().transform((v) => v ?? void 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xleddyl/nuxt-cms",
3
- "version": "0.1.60",
3
+ "version": "0.2.0",
4
4
  "description": "Lightweight CMS that ships with your Nuxt app: runs on the Nitro server, content types defined in code, /cms admin panel, GraphQL API, SQLite or Postgres. No external CMS needed!",
5
5
  "license": "MIT",
6
6
  "author": "Edoardo Alberti (https://github.com/xleddyl)",
@@ -60,8 +60,8 @@
60
60
  "dist"
61
61
  ],
62
62
  "dependencies": {
63
- "@fontsource-variable/hanken-grotesk": "^5.3.0",
64
- "@fontsource/fragment-mono": "^5.3.0",
63
+ "@fontsource-variable/geist": "^5.3.0",
64
+ "@fontsource-variable/geist-mono": "^5.3.0",
65
65
  "@nuxt/kit": "^4.4.8",
66
66
  "@tailwindcss/vite": "^4.3.2",
67
67
  "@tiptap/starter-kit": "^3.0.0",