nextjs-cms 0.6.4 → 0.6.6
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/dist/core/config/config-loader.d.ts +12 -0
- package/dist/core/config/config-loader.d.ts.map +1 -1
- package/dist/core/config/config-loader.js +17 -1
- package/dist/core/fields/field.d.ts +1 -1
- package/dist/core/fields/field.d.ts.map +1 -1
- package/dist/core/fields/index.d.ts +4 -1
- package/dist/core/fields/index.d.ts.map +1 -1
- package/dist/core/fields/index.js +1 -0
- package/dist/core/fields/photo.d.ts +84 -6
- package/dist/core/fields/photo.d.ts.map +1 -1
- package/dist/core/fields/photo.js +52 -11
- package/dist/core/fields/richText.d.ts +9 -9
- package/dist/core/fields/slug.d.ts +245 -0
- package/dist/core/fields/slug.d.ts.map +1 -0
- package/dist/core/fields/slug.js +200 -0
- package/dist/core/sections/category.d.ts +34 -34
- package/dist/core/sections/hasItems.d.ts +112 -40
- package/dist/core/sections/hasItems.d.ts.map +1 -1
- package/dist/core/sections/section.d.ts +17 -17
- package/dist/core/sections/simple.d.ts +8 -8
- package/dist/core/submit/submit.d.ts +7 -0
- package/dist/core/submit/submit.d.ts.map +1 -1
- package/dist/core/submit/submit.js +21 -3
- package/dist/core/types/index.d.ts +1 -1
- package/dist/core/types/index.d.ts.map +1 -1
- package/dist/validators/index.d.ts +1 -0
- package/dist/validators/index.d.ts.map +1 -1
- package/dist/validators/index.js +1 -0
- package/dist/validators/slug.d.ts +4 -0
- package/dist/validators/slug.d.ts.map +1 -0
- package/dist/validators/slug.js +20 -0
- package/package.json +2 -2
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import type { BaseFieldConfig } from './field.js';
|
|
2
|
+
import { Field } from './field.js';
|
|
3
|
+
import { entityKind } from '../helpers/index.js';
|
|
4
|
+
import * as z from 'zod';
|
|
5
|
+
import type { TextFieldConfig } from './text.js';
|
|
6
|
+
/**
|
|
7
|
+
* Context passed to SlugField.prepareForSubmission for duplicate checking.
|
|
8
|
+
*/
|
|
9
|
+
export type SlugFieldSubmissionContext = {
|
|
10
|
+
tableName: string;
|
|
11
|
+
};
|
|
12
|
+
declare const slugFieldConfigSchema: z.ZodObject<{
|
|
13
|
+
placeholder: z.ZodOptional<z.ZodString>;
|
|
14
|
+
minLength: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
maxLength: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
/**
|
|
17
|
+
* The default value of the field.
|
|
18
|
+
* If set, the field will be pre-populated with this value when the form is loaded.
|
|
19
|
+
*/
|
|
20
|
+
defaultValue: z.ZodOptional<z.ZodString>;
|
|
21
|
+
/**
|
|
22
|
+
* The text field config that this slug field is derived from.
|
|
23
|
+
* When the source field's value changes, this field will automatically
|
|
24
|
+
* generate a slug from that value.
|
|
25
|
+
*/
|
|
26
|
+
for: z.ZodCustom<{
|
|
27
|
+
type: "text";
|
|
28
|
+
build: z.core.$InferOuterFunctionType<z.core.$ZodFunctionArgs, z.ZodCustom<import("./text.js").TextField, import("./text.js").TextField>>;
|
|
29
|
+
name: string;
|
|
30
|
+
placeholder?: string | undefined;
|
|
31
|
+
minLength?: number | undefined;
|
|
32
|
+
maxLength?: number | undefined;
|
|
33
|
+
defaultValue?: string | undefined;
|
|
34
|
+
sanitize?: boolean | undefined;
|
|
35
|
+
label?: string | Record<string, string> | undefined;
|
|
36
|
+
required?: boolean | undefined;
|
|
37
|
+
order?: number | undefined;
|
|
38
|
+
conditionalRules?: import("../types/index.js").ConditionalRule[] | undefined;
|
|
39
|
+
adminGenerated?: boolean | "readonly" | undefined;
|
|
40
|
+
}, {
|
|
41
|
+
type: "text";
|
|
42
|
+
build: z.core.$InferOuterFunctionType<z.core.$ZodFunctionArgs, z.ZodCustom<import("./text.js").TextField, import("./text.js").TextField>>;
|
|
43
|
+
name: string;
|
|
44
|
+
placeholder?: string | undefined;
|
|
45
|
+
minLength?: number | undefined;
|
|
46
|
+
maxLength?: number | undefined;
|
|
47
|
+
defaultValue?: string | undefined;
|
|
48
|
+
sanitize?: boolean | undefined;
|
|
49
|
+
label?: string | Record<string, string> | undefined;
|
|
50
|
+
required?: boolean | undefined;
|
|
51
|
+
order?: number | undefined;
|
|
52
|
+
conditionalRules?: import("../types/index.js").ConditionalRule[] | undefined;
|
|
53
|
+
adminGenerated?: boolean | "readonly" | undefined;
|
|
54
|
+
}>;
|
|
55
|
+
}, z.core.$strict>;
|
|
56
|
+
type Config = z.infer<typeof slugFieldConfigSchema>;
|
|
57
|
+
export declare class SlugField extends Field<'slug', Config> {
|
|
58
|
+
static readonly [entityKind]: string;
|
|
59
|
+
readonly maxLength: number | undefined;
|
|
60
|
+
readonly minLength: number | undefined;
|
|
61
|
+
readonly placeholder: string | undefined;
|
|
62
|
+
readonly forFieldName: string;
|
|
63
|
+
readonly forFieldConfig: TextFieldConfig;
|
|
64
|
+
private readonly _defaultValue;
|
|
65
|
+
value: string | undefined;
|
|
66
|
+
constructor(config: BaseFieldConfig<Config>);
|
|
67
|
+
/**
|
|
68
|
+
* Convert a string to a URL-friendly slug.
|
|
69
|
+
* - Converts to lowercase
|
|
70
|
+
* - Replaces spaces with hyphens
|
|
71
|
+
* - Removes special characters (keeps Unicode letters, numbers, and hyphens)
|
|
72
|
+
* - Removes leading/trailing hyphens
|
|
73
|
+
* - Collapses multiple consecutive hyphens into one
|
|
74
|
+
*/
|
|
75
|
+
static toSlug(value: string): string;
|
|
76
|
+
exportForClient(): {
|
|
77
|
+
maxLength: number | undefined;
|
|
78
|
+
minLength: number | undefined;
|
|
79
|
+
placeholder: string | undefined;
|
|
80
|
+
forFieldName: string;
|
|
81
|
+
type: "slug";
|
|
82
|
+
name: string;
|
|
83
|
+
label: import("../../index.js").LocalizedString;
|
|
84
|
+
required: boolean;
|
|
85
|
+
conditionalFields: import("../types/index.js").ConditionalField[];
|
|
86
|
+
readonly: boolean;
|
|
87
|
+
defaultValue: any;
|
|
88
|
+
value: any;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Get the value of the field
|
|
92
|
+
*/
|
|
93
|
+
getValue(): string | undefined;
|
|
94
|
+
checkRequired(): void;
|
|
95
|
+
hasSqlNameAndValue(): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Check if a slug already exists in the database table.
|
|
98
|
+
* Uses dynamic import to avoid loading db client during module initialization.
|
|
99
|
+
* @param tableName - The table to check for duplicates
|
|
100
|
+
* @returns true if a duplicate exists, false otherwise
|
|
101
|
+
*/
|
|
102
|
+
private checkDuplicateSlug;
|
|
103
|
+
/**
|
|
104
|
+
* Get a user-friendly label for the "for" field.
|
|
105
|
+
* Falls back to the field name if no label is available.
|
|
106
|
+
*/
|
|
107
|
+
private getForFieldLabel;
|
|
108
|
+
/**
|
|
109
|
+
* Prepare the field for submission
|
|
110
|
+
* @param context - Optional context containing tableName for duplicate checking
|
|
111
|
+
*/
|
|
112
|
+
prepareForSubmission(context?: SlugFieldSubmissionContext): Promise<void>;
|
|
113
|
+
}
|
|
114
|
+
export type SlugFieldClientConfig = ReturnType<SlugField['exportForClient']>;
|
|
115
|
+
declare const optionsSchema: z.ZodObject<{
|
|
116
|
+
placeholder: z.ZodOptional<z.ZodString>;
|
|
117
|
+
minLength: z.ZodOptional<z.ZodNumber>;
|
|
118
|
+
maxLength: z.ZodOptional<z.ZodNumber>;
|
|
119
|
+
/**
|
|
120
|
+
* The default value of the field.
|
|
121
|
+
* If set, the field will be pre-populated with this value when the form is loaded.
|
|
122
|
+
*/
|
|
123
|
+
defaultValue: z.ZodOptional<z.ZodString>;
|
|
124
|
+
/**
|
|
125
|
+
* The text field config that this slug field is derived from.
|
|
126
|
+
* When the source field's value changes, this field will automatically
|
|
127
|
+
* generate a slug from that value.
|
|
128
|
+
*/
|
|
129
|
+
for: z.ZodCustom<{
|
|
130
|
+
type: "text";
|
|
131
|
+
build: z.core.$InferOuterFunctionType<z.core.$ZodFunctionArgs, z.ZodCustom<import("./text.js").TextField, import("./text.js").TextField>>;
|
|
132
|
+
name: string;
|
|
133
|
+
placeholder?: string | undefined;
|
|
134
|
+
minLength?: number | undefined;
|
|
135
|
+
maxLength?: number | undefined;
|
|
136
|
+
defaultValue?: string | undefined;
|
|
137
|
+
sanitize?: boolean | undefined;
|
|
138
|
+
label?: string | Record<string, string> | undefined;
|
|
139
|
+
required?: boolean | undefined;
|
|
140
|
+
order?: number | undefined;
|
|
141
|
+
conditionalRules?: import("../types/index.js").ConditionalRule[] | undefined;
|
|
142
|
+
adminGenerated?: boolean | "readonly" | undefined;
|
|
143
|
+
}, {
|
|
144
|
+
type: "text";
|
|
145
|
+
build: z.core.$InferOuterFunctionType<z.core.$ZodFunctionArgs, z.ZodCustom<import("./text.js").TextField, import("./text.js").TextField>>;
|
|
146
|
+
name: string;
|
|
147
|
+
placeholder?: string | undefined;
|
|
148
|
+
minLength?: number | undefined;
|
|
149
|
+
maxLength?: number | undefined;
|
|
150
|
+
defaultValue?: string | undefined;
|
|
151
|
+
sanitize?: boolean | undefined;
|
|
152
|
+
label?: string | Record<string, string> | undefined;
|
|
153
|
+
required?: boolean | undefined;
|
|
154
|
+
order?: number | undefined;
|
|
155
|
+
conditionalRules?: import("../types/index.js").ConditionalRule[] | undefined;
|
|
156
|
+
adminGenerated?: boolean | "readonly" | undefined;
|
|
157
|
+
}>;
|
|
158
|
+
name: z.ZodString;
|
|
159
|
+
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
160
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
161
|
+
order: z.ZodOptional<z.ZodNumber>;
|
|
162
|
+
conditionalRules: z.ZodOptional<z.ZodArray<z.ZodCustom<import("../types/index.js").ConditionalRule, import("../types/index.js").ConditionalRule>>>;
|
|
163
|
+
adminGenerated: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodLiteral<false>, z.ZodLiteral<"readonly">]>>;
|
|
164
|
+
}, z.core.$strict>;
|
|
165
|
+
declare const configSchema: z.ZodObject<{
|
|
166
|
+
type: z.ZodLiteral<"slug">;
|
|
167
|
+
build: z.ZodFunction<z.core.$ZodFunctionArgs, z.ZodCustom<SlugField, SlugField>>;
|
|
168
|
+
placeholder: z.ZodOptional<z.ZodString>;
|
|
169
|
+
minLength: z.ZodOptional<z.ZodNumber>;
|
|
170
|
+
maxLength: z.ZodOptional<z.ZodNumber>;
|
|
171
|
+
/**
|
|
172
|
+
* The default value of the field.
|
|
173
|
+
* If set, the field will be pre-populated with this value when the form is loaded.
|
|
174
|
+
*/
|
|
175
|
+
defaultValue: z.ZodOptional<z.ZodString>;
|
|
176
|
+
/**
|
|
177
|
+
* The text field config that this slug field is derived from.
|
|
178
|
+
* When the source field's value changes, this field will automatically
|
|
179
|
+
* generate a slug from that value.
|
|
180
|
+
*/
|
|
181
|
+
for: z.ZodCustom<{
|
|
182
|
+
type: "text";
|
|
183
|
+
build: z.core.$InferOuterFunctionType<z.core.$ZodFunctionArgs, z.ZodCustom<import("./text.js").TextField, import("./text.js").TextField>>;
|
|
184
|
+
name: string;
|
|
185
|
+
placeholder?: string | undefined;
|
|
186
|
+
minLength?: number | undefined;
|
|
187
|
+
maxLength?: number | undefined;
|
|
188
|
+
defaultValue?: string | undefined;
|
|
189
|
+
sanitize?: boolean | undefined;
|
|
190
|
+
label?: string | Record<string, string> | undefined;
|
|
191
|
+
required?: boolean | undefined;
|
|
192
|
+
order?: number | undefined;
|
|
193
|
+
conditionalRules?: import("../types/index.js").ConditionalRule[] | undefined;
|
|
194
|
+
adminGenerated?: boolean | "readonly" | undefined;
|
|
195
|
+
}, {
|
|
196
|
+
type: "text";
|
|
197
|
+
build: z.core.$InferOuterFunctionType<z.core.$ZodFunctionArgs, z.ZodCustom<import("./text.js").TextField, import("./text.js").TextField>>;
|
|
198
|
+
name: string;
|
|
199
|
+
placeholder?: string | undefined;
|
|
200
|
+
minLength?: number | undefined;
|
|
201
|
+
maxLength?: number | undefined;
|
|
202
|
+
defaultValue?: string | undefined;
|
|
203
|
+
sanitize?: boolean | undefined;
|
|
204
|
+
label?: string | Record<string, string> | undefined;
|
|
205
|
+
required?: boolean | undefined;
|
|
206
|
+
order?: number | undefined;
|
|
207
|
+
conditionalRules?: import("../types/index.js").ConditionalRule[] | undefined;
|
|
208
|
+
adminGenerated?: boolean | "readonly" | undefined;
|
|
209
|
+
}>;
|
|
210
|
+
name: z.ZodString;
|
|
211
|
+
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
212
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
213
|
+
order: z.ZodOptional<z.ZodNumber>;
|
|
214
|
+
conditionalRules: z.ZodOptional<z.ZodArray<z.ZodCustom<import("../types/index.js").ConditionalRule, import("../types/index.js").ConditionalRule>>>;
|
|
215
|
+
adminGenerated: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodLiteral<false>, z.ZodLiteral<"readonly">]>>;
|
|
216
|
+
}, z.core.$strict>;
|
|
217
|
+
/**
|
|
218
|
+
* Slug field configuration type
|
|
219
|
+
* This is a plain object that can be serialized and used anywhere
|
|
220
|
+
* The build() method allows creating a SlugField instance when needed
|
|
221
|
+
*/
|
|
222
|
+
export type SlugFieldConfig = z.infer<typeof configSchema>;
|
|
223
|
+
/**
|
|
224
|
+
* Helper function to create a slug field configuration
|
|
225
|
+
* Returns a config object with a build() method that can be serialized and used anywhere
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* ```typescript
|
|
229
|
+
* const titleField = textField({ name: 'title', label: 'Title', required: true })
|
|
230
|
+
* const slugFieldConfig = slugField({
|
|
231
|
+
* name: 'slug',
|
|
232
|
+
* label: 'URL Slug',
|
|
233
|
+
* for: titleField,
|
|
234
|
+
* required: true,
|
|
235
|
+
* order: 2
|
|
236
|
+
* })
|
|
237
|
+
* // The slug field will automatically generate a slug from the title field's value
|
|
238
|
+
* const fieldInstance = slugFieldConfig.build()
|
|
239
|
+
* ```
|
|
240
|
+
*
|
|
241
|
+
* @param field
|
|
242
|
+
*/
|
|
243
|
+
export declare function slugField(field: z.infer<typeof optionsSchema>): SlugFieldConfig;
|
|
244
|
+
export {};
|
|
245
|
+
//# sourceMappingURL=slug.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slug.d.ts","sourceRoot":"","sources":["../../../src/core/fields/slug.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,EAAE,KAAK,EAAyB,MAAM,YAAY,CAAA;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AACxB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAGhD;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACrC,SAAS,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,QAAA,MAAM,qBAAqB;;;;IAIvB;;;OAGG;;IAEH;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAEL,CAAA;AAEF,KAAK,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAA;AAEnD,qBAAa,SAAU,SAAQ,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC;IAChD,gBAAyB,CAAC,UAAU,CAAC,EAAE,MAAM,CAAc;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IACxC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,cAAc,EAAE,eAAe,CAAA;IACxC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IACzC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;gBAEtB,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC;IAW3C;;;;;;;OAOG;IACH,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAUpB,eAAe;;;;;;;;;;;;;;IAU/B;;OAEG;IACH,QAAQ,IAAI,MAAM,GAAG,SAAS;IAI9B,aAAa;IAQJ,kBAAkB,IAAI,OAAO;IAOtC;;;;;OAKG;YACW,kBAAkB;IAmBhC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IASxB;;;OAGG;IACG,oBAAoB,CAAC,OAAO,CAAC,EAAE,0BAA0B;CAuClE;AAED,MAAM,MAAM,qBAAqB,GAAG,UAAU,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC,CAAA;AAE5E,QAAA,MAAM,aAAa;;;;IA3Kf;;;OAGG;;IAEH;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAqKL,CAAA;AAEF,QAAA,MAAM,YAAY;;;;;;IAhLd;;;OAGG;;IAEH;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA2KL,CAAA;AAEF;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA;AAE1D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,GAAG,eAAe,CAkB/E"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { Field, baseFieldConfigSchema } from './field.js';
|
|
2
|
+
import { entityKind } from '../helpers/index.js';
|
|
3
|
+
import * as z from 'zod';
|
|
4
|
+
import { resolveLocalizedString } from '../../translations/localization.js';
|
|
5
|
+
const slugFieldConfigSchema = z.strictObject({
|
|
6
|
+
placeholder: z.string().optional().describe('The placeholder text for the field'),
|
|
7
|
+
minLength: z.number().optional().describe('The minimum length of the slug'),
|
|
8
|
+
maxLength: z.number().optional().describe('The maximum length of the slug'),
|
|
9
|
+
/**
|
|
10
|
+
* The default value of the field.
|
|
11
|
+
* If set, the field will be pre-populated with this value when the form is loaded.
|
|
12
|
+
*/
|
|
13
|
+
defaultValue: z.string().optional().describe('The default value of the field'),
|
|
14
|
+
/**
|
|
15
|
+
* The text field config that this slug field is derived from.
|
|
16
|
+
* When the source field's value changes, this field will automatically
|
|
17
|
+
* generate a slug from that value.
|
|
18
|
+
*/
|
|
19
|
+
for: z.custom().describe('The text field config this slug is derived from'),
|
|
20
|
+
});
|
|
21
|
+
export class SlugField extends Field {
|
|
22
|
+
static [entityKind] = 'SlugField';
|
|
23
|
+
maxLength;
|
|
24
|
+
minLength;
|
|
25
|
+
placeholder;
|
|
26
|
+
forFieldName;
|
|
27
|
+
forFieldConfig;
|
|
28
|
+
_defaultValue;
|
|
29
|
+
value;
|
|
30
|
+
constructor(config) {
|
|
31
|
+
super(config, 'slug');
|
|
32
|
+
this.maxLength = config.maxLength;
|
|
33
|
+
this.minLength = config.minLength;
|
|
34
|
+
this.placeholder = config.placeholder;
|
|
35
|
+
this.value = config.defaultValue;
|
|
36
|
+
this._defaultValue = config.defaultValue;
|
|
37
|
+
this.forFieldName = config.for.name;
|
|
38
|
+
this.forFieldConfig = config.for;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Convert a string to a URL-friendly slug.
|
|
42
|
+
* - Converts to lowercase
|
|
43
|
+
* - Replaces spaces with hyphens
|
|
44
|
+
* - Removes special characters (keeps Unicode letters, numbers, and hyphens)
|
|
45
|
+
* - Removes leading/trailing hyphens
|
|
46
|
+
* - Collapses multiple consecutive hyphens into one
|
|
47
|
+
*/
|
|
48
|
+
static toSlug(value) {
|
|
49
|
+
return value
|
|
50
|
+
.toLowerCase()
|
|
51
|
+
.trim()
|
|
52
|
+
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
|
53
|
+
.replace(/[^\p{L}\p{N}-]/gu, '') // Remove special characters (keep Unicode letters/numbers)
|
|
54
|
+
.replace(/-+/g, '-') // Collapse multiple hyphens
|
|
55
|
+
.replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
|
|
56
|
+
}
|
|
57
|
+
exportForClient() {
|
|
58
|
+
return {
|
|
59
|
+
...super.exportForClient(),
|
|
60
|
+
maxLength: this.maxLength,
|
|
61
|
+
minLength: this.minLength,
|
|
62
|
+
placeholder: this.placeholder,
|
|
63
|
+
forFieldName: this.forFieldName,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get the value of the field
|
|
68
|
+
*/
|
|
69
|
+
getValue() {
|
|
70
|
+
return this.value;
|
|
71
|
+
}
|
|
72
|
+
checkRequired() {
|
|
73
|
+
if (this.required) {
|
|
74
|
+
if (!this.value || this.value.trim().length === 0) {
|
|
75
|
+
throw new Error(`Field ${this.label} is required`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
hasSqlNameAndValue() {
|
|
80
|
+
/**
|
|
81
|
+
* Include the field in the SQL if it is admin-generated, or not admin-generated but has a default value
|
|
82
|
+
*/
|
|
83
|
+
return this.adminGenerated === true || this._defaultValue !== undefined;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if a slug already exists in the database table.
|
|
87
|
+
* Uses dynamic import to avoid loading db client during module initialization.
|
|
88
|
+
* @param tableName - The table to check for duplicates
|
|
89
|
+
* @returns true if a duplicate exists, false otherwise
|
|
90
|
+
*/
|
|
91
|
+
async checkDuplicateSlug(tableName) {
|
|
92
|
+
if (!this.value)
|
|
93
|
+
return false;
|
|
94
|
+
// Dynamic imports to avoid loading db client during module initialization
|
|
95
|
+
const { sql } = await import('drizzle-orm');
|
|
96
|
+
const { db } = await import('../../db/client.js');
|
|
97
|
+
const [rows] = await db.execute(sql `SELECT COUNT(*) as count FROM ${sql.raw(tableName)} WHERE ${sql.raw(this.name)} = ${this.value}`);
|
|
98
|
+
if (Array.isArray(rows) && rows.length > 0) {
|
|
99
|
+
const count = Number(rows[0].count);
|
|
100
|
+
return count > 0;
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get a user-friendly label for the "for" field.
|
|
106
|
+
* Falls back to the field name if no label is available.
|
|
107
|
+
*/
|
|
108
|
+
getForFieldLabel() {
|
|
109
|
+
const label = this.forFieldConfig.label;
|
|
110
|
+
if (!label) {
|
|
111
|
+
return Field.generateLabel(this.forFieldName);
|
|
112
|
+
}
|
|
113
|
+
// Resolve localized string with English as fallback
|
|
114
|
+
return resolveLocalizedString(label, 'en', 'en');
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Prepare the field for submission
|
|
118
|
+
* @param context - Optional context containing tableName for duplicate checking
|
|
119
|
+
*/
|
|
120
|
+
async prepareForSubmission(context) {
|
|
121
|
+
if (this.value) {
|
|
122
|
+
// Ensure the value is a valid slug
|
|
123
|
+
this.value = SlugField.toSlug(this.value);
|
|
124
|
+
/**
|
|
125
|
+
* Check minimum length
|
|
126
|
+
*/
|
|
127
|
+
if (this.minLength) {
|
|
128
|
+
if (this.minLength > this.value.length) {
|
|
129
|
+
throw new Error(`Field ${this.label} must be at least ${this.minLength} characters long`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Check maximum length
|
|
134
|
+
*/
|
|
135
|
+
if (this.maxLength) {
|
|
136
|
+
if (this.maxLength < this.value.length) {
|
|
137
|
+
throw new Error(`Field ${this.label} must be at most ${this.maxLength} characters long`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Check for duplicate slugs in the database
|
|
142
|
+
*/
|
|
143
|
+
if (context?.tableName) {
|
|
144
|
+
const isDuplicate = await this.checkDuplicateSlug(context.tableName);
|
|
145
|
+
if (isDuplicate) {
|
|
146
|
+
const forFieldLabel = this.getForFieldLabel();
|
|
147
|
+
throw new Error(`A record with the slug "${this.value}" already exists. ` +
|
|
148
|
+
`Please change the "${forFieldLabel}" field to generate a different slug, ` +
|
|
149
|
+
`or manually edit the slug value (not recommended).`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const optionsSchema = z.strictObject({
|
|
156
|
+
...baseFieldConfigSchema.shape,
|
|
157
|
+
...slugFieldConfigSchema.shape,
|
|
158
|
+
});
|
|
159
|
+
const configSchema = z.strictObject({
|
|
160
|
+
...optionsSchema.shape,
|
|
161
|
+
type: z.literal('slug').describe('The type of the field'),
|
|
162
|
+
build: z.function().output(z.instanceof(SlugField)).describe('Build a SlugField instance from this config'),
|
|
163
|
+
});
|
|
164
|
+
/**
|
|
165
|
+
* Helper function to create a slug field configuration
|
|
166
|
+
* Returns a config object with a build() method that can be serialized and used anywhere
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```typescript
|
|
170
|
+
* const titleField = textField({ name: 'title', label: 'Title', required: true })
|
|
171
|
+
* const slugFieldConfig = slugField({
|
|
172
|
+
* name: 'slug',
|
|
173
|
+
* label: 'URL Slug',
|
|
174
|
+
* for: titleField,
|
|
175
|
+
* required: true,
|
|
176
|
+
* order: 2
|
|
177
|
+
* })
|
|
178
|
+
* // The slug field will automatically generate a slug from the title field's value
|
|
179
|
+
* const fieldInstance = slugFieldConfig.build()
|
|
180
|
+
* ```
|
|
181
|
+
*
|
|
182
|
+
* @param field
|
|
183
|
+
*/
|
|
184
|
+
export function slugField(field) {
|
|
185
|
+
/**
|
|
186
|
+
* Validate the field config
|
|
187
|
+
*/
|
|
188
|
+
const result = optionsSchema.safeParse(field);
|
|
189
|
+
if (!result.success) {
|
|
190
|
+
throw new Error(`[Field: ${field.name}]: ${z.prettifyError(result.error)}`);
|
|
191
|
+
}
|
|
192
|
+
const config = {
|
|
193
|
+
...field,
|
|
194
|
+
type: 'slug',
|
|
195
|
+
build() {
|
|
196
|
+
return new SlugField(field);
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
return config;
|
|
200
|
+
}
|
|
@@ -118,23 +118,9 @@ declare const optionsSchema: z.ZodObject<{
|
|
|
118
118
|
*/
|
|
119
119
|
allowRecursiveDelete: z.ZodOptional<z.ZodBoolean>;
|
|
120
120
|
fields: z.ZodUnion<[z.ZodArray<z.ZodCustom<FieldConfig, FieldConfig>>, z.ZodArray<z.ZodCustom<FieldGroupConfig, FieldGroupConfig>>]>;
|
|
121
|
-
name: z.ZodString;
|
|
122
|
-
gallery: z.ZodOptional<z.ZodObject<{
|
|
123
|
-
db: z.ZodObject<{
|
|
124
|
-
tableName: z.ZodString;
|
|
125
|
-
identifierField: z.ZodOptional<z.ZodString>;
|
|
126
|
-
photoNameField: z.ZodOptional<z.ZodString>;
|
|
127
|
-
metaField: z.ZodOptional<z.ZodString>;
|
|
128
|
-
}, z.core.$strict>;
|
|
129
|
-
watermark: z.ZodOptional<z.ZodBoolean>;
|
|
130
|
-
thumbnail: z.ZodOptional<z.ZodObject<{
|
|
131
|
-
width: z.ZodNumber;
|
|
132
|
-
height: z.ZodNumber;
|
|
133
|
-
crop: z.ZodBoolean;
|
|
134
|
-
quality: z.ZodNumber;
|
|
135
|
-
}, z.core.$strict>>;
|
|
136
|
-
}, z.core.$strict>>;
|
|
137
121
|
readonly: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
|
|
122
|
+
name: z.ZodString;
|
|
123
|
+
order: z.ZodNumber;
|
|
138
124
|
icon: z.ZodOptional<z.ZodString>;
|
|
139
125
|
db: z.ZodObject<{
|
|
140
126
|
table: z.ZodString;
|
|
@@ -160,7 +146,21 @@ declare const optionsSchema: z.ZodObject<{
|
|
|
160
146
|
}, z.core.$strict>>>;
|
|
161
147
|
}, z.core.$strict>;
|
|
162
148
|
variants: z.ZodOptional<z.ZodArray<z.ZodCustom<import("../types/index.js").Variant, import("../types/index.js").Variant>>>;
|
|
163
|
-
|
|
149
|
+
gallery: z.ZodOptional<z.ZodObject<{
|
|
150
|
+
db: z.ZodObject<{
|
|
151
|
+
tableName: z.ZodString;
|
|
152
|
+
identifierField: z.ZodOptional<z.ZodString>;
|
|
153
|
+
photoNameField: z.ZodOptional<z.ZodString>;
|
|
154
|
+
metaField: z.ZodOptional<z.ZodString>;
|
|
155
|
+
}, z.core.$strict>;
|
|
156
|
+
watermark: z.ZodOptional<z.ZodBoolean>;
|
|
157
|
+
thumbnail: z.ZodOptional<z.ZodObject<{
|
|
158
|
+
width: z.ZodNumber;
|
|
159
|
+
height: z.ZodNumber;
|
|
160
|
+
crop: z.ZodBoolean;
|
|
161
|
+
quality: z.ZodNumber;
|
|
162
|
+
}, z.core.$strict>>;
|
|
163
|
+
}, z.core.$strict>>;
|
|
164
164
|
hooks: z.ZodOptional<z.ZodCustom<import("./section.js").Hooks, import("./section.js").Hooks>>;
|
|
165
165
|
}, z.core.$strict>;
|
|
166
166
|
export declare const categorySectionConfigSchema: z.ZodObject<{
|
|
@@ -215,23 +215,9 @@ export declare const categorySectionConfigSchema: z.ZodObject<{
|
|
|
215
215
|
* @default false
|
|
216
216
|
*/
|
|
217
217
|
allowRecursiveDelete: z.ZodOptional<z.ZodBoolean>;
|
|
218
|
-
name: z.ZodString;
|
|
219
|
-
gallery: z.ZodOptional<z.ZodObject<{
|
|
220
|
-
db: z.ZodObject<{
|
|
221
|
-
tableName: z.ZodString;
|
|
222
|
-
identifierField: z.ZodOptional<z.ZodString>;
|
|
223
|
-
photoNameField: z.ZodOptional<z.ZodString>;
|
|
224
|
-
metaField: z.ZodOptional<z.ZodString>;
|
|
225
|
-
}, z.core.$strict>;
|
|
226
|
-
watermark: z.ZodOptional<z.ZodBoolean>;
|
|
227
|
-
thumbnail: z.ZodOptional<z.ZodObject<{
|
|
228
|
-
width: z.ZodNumber;
|
|
229
|
-
height: z.ZodNumber;
|
|
230
|
-
crop: z.ZodBoolean;
|
|
231
|
-
quality: z.ZodNumber;
|
|
232
|
-
}, z.core.$strict>>;
|
|
233
|
-
}, z.core.$strict>>;
|
|
234
218
|
readonly: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
|
|
219
|
+
name: z.ZodString;
|
|
220
|
+
order: z.ZodNumber;
|
|
235
221
|
icon: z.ZodOptional<z.ZodString>;
|
|
236
222
|
db: z.ZodObject<{
|
|
237
223
|
table: z.ZodString;
|
|
@@ -257,7 +243,21 @@ export declare const categorySectionConfigSchema: z.ZodObject<{
|
|
|
257
243
|
}, z.core.$strict>>>;
|
|
258
244
|
}, z.core.$strict>;
|
|
259
245
|
variants: z.ZodOptional<z.ZodArray<z.ZodCustom<import("../types/index.js").Variant, import("../types/index.js").Variant>>>;
|
|
260
|
-
|
|
246
|
+
gallery: z.ZodOptional<z.ZodObject<{
|
|
247
|
+
db: z.ZodObject<{
|
|
248
|
+
tableName: z.ZodString;
|
|
249
|
+
identifierField: z.ZodOptional<z.ZodString>;
|
|
250
|
+
photoNameField: z.ZodOptional<z.ZodString>;
|
|
251
|
+
metaField: z.ZodOptional<z.ZodString>;
|
|
252
|
+
}, z.core.$strict>;
|
|
253
|
+
watermark: z.ZodOptional<z.ZodBoolean>;
|
|
254
|
+
thumbnail: z.ZodOptional<z.ZodObject<{
|
|
255
|
+
width: z.ZodNumber;
|
|
256
|
+
height: z.ZodNumber;
|
|
257
|
+
crop: z.ZodBoolean;
|
|
258
|
+
quality: z.ZodNumber;
|
|
259
|
+
}, z.core.$strict>>;
|
|
260
|
+
}, z.core.$strict>>;
|
|
261
261
|
hooks: z.ZodOptional<z.ZodCustom<import("./section.js").Hooks, import("./section.js").Hooks>>;
|
|
262
262
|
}, z.core.$strict>;
|
|
263
263
|
export type CategorySectionOptions = z.infer<typeof optionsSchema>;
|