nextjs-cms 0.9.36 → 0.9.38
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/LICENSE +21 -21
- package/README.md +279 -279
- package/dist/api/actions/files.d.ts.map +1 -1
- package/dist/api/actions/pages.d.ts +4 -3
- package/dist/api/actions/pages.d.ts.map +1 -1
- package/dist/api/actions/pages.js +1 -0
- package/dist/api/index.d.ts +974 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +13 -0
- package/dist/api/lib/serverActions.d.ts +49 -13
- package/dist/api/lib/serverActions.d.ts.map +1 -1
- package/dist/api/lib/serverActions.js +31 -103
- package/dist/api/root.d.ts +1916 -18
- package/dist/api/root.d.ts.map +1 -1
- package/dist/api/root.js +83 -18
- package/dist/api/routers/config.d.ts.map +1 -1
- package/dist/api/routers/gallery.d.ts +0 -1
- package/dist/api/routers/gallery.d.ts.map +1 -1
- package/dist/api/routers/gallery.js +8 -36
- package/dist/api/routers/hasItemsSection.d.ts +30 -5
- package/dist/api/routers/hasItemsSection.d.ts.map +1 -1
- package/dist/api/routers/navigation.d.ts +3 -3
- package/dist/api/routers/simpleSection.d.ts +15 -3
- package/dist/api/routers/simpleSection.d.ts.map +1 -1
- package/dist/api/trpc/root.d.ts +41 -7
- package/dist/api/trpc/root.d.ts.map +1 -1
- package/dist/api/trpc/routers/config.d.ts.map +1 -1
- package/dist/api/trpc/routers/hasItemsSection.d.ts.map +1 -1
- package/dist/api/trpc/routers/logs.d.ts +37 -4
- package/dist/api/trpc/routers/logs.d.ts.map +1 -1
- package/dist/api/trpc/routers/logs.js +92 -43
- package/dist/api/trpc/routers/navigation.d.ts +4 -3
- package/dist/api/trpc/routers/navigation.d.ts.map +1 -1
- package/dist/api/trpc/routers/simpleSection.d.ts.map +1 -1
- package/dist/api/trpc/server.d.ts +123 -21
- package/dist/api/trpc/server.d.ts.map +1 -1
- package/dist/auth/trpc.d.ts +1 -1
- package/dist/auth/trpc.d.ts.map +1 -1
- package/dist/auth/trpc.js +1 -0
- package/dist/cli/lib/db-config.js +10 -10
- package/dist/cli/lib/update-sections.d.ts.map +1 -1
- package/dist/cli/lib/update-sections.js +67 -34
- package/dist/core/config/config-loader.d.ts +2 -2
- package/dist/core/db/table-checker/MysqlTable.js +10 -10
- package/dist/core/factories/section-factory-with-esbuild.js +9 -9
- package/dist/core/factories/section-factory-with-jiti.js +9 -9
- package/dist/core/fields/date-range.d.ts +4 -4
- package/dist/core/fields/dateRange.d.ts +115 -0
- package/dist/core/fields/dateRange.d.ts.map +1 -0
- package/dist/core/fields/dateRange.js +149 -0
- package/dist/core/fields/slug.d.ts +55 -3
- package/dist/core/fields/slug.d.ts.map +1 -1
- package/dist/core/fields/slug.js +56 -2
- package/dist/core/sections/category.d.ts +4 -4
- package/dist/core/sections/hasItems.d.ts +4 -4
- package/dist/core/sections/section.d.ts +3 -3
- package/dist/core/sections/simple.d.ts +4 -4
- package/dist/logging/event-labels.d.ts +11 -0
- package/dist/logging/event-labels.d.ts.map +1 -0
- package/dist/logging/event-labels.js +14 -0
- package/dist/logging/log.d.ts +1 -1
- package/dist/logging/log.d.ts.map +1 -1
- package/dist/translations/base/en.d.ts +36 -0
- package/dist/translations/base/en.d.ts.map +1 -1
- package/dist/translations/base/en.js +36 -0
- package/dist/translations/client.d.ts +436 -4
- package/dist/translations/client.d.ts.map +1 -1
- package/dist/translations/locale-cookie.d.ts +24 -0
- package/dist/translations/locale-cookie.d.ts.map +1 -0
- package/dist/translations/locale-cookie.js +44 -0
- package/dist/translations/locale-utils.d.ts +8 -0
- package/dist/translations/locale-utils.d.ts.map +1 -0
- package/dist/translations/locale-utils.js +11 -0
- package/dist/translations/localization.d.ts +40 -0
- package/dist/translations/localization.d.ts.map +1 -0
- package/dist/translations/localization.js +48 -0
- package/dist/translations/server.d.ts +436 -4
- package/dist/translations/server.d.ts.map +1 -1
- package/dist/validators/dateRange.d.ts +11 -0
- package/dist/validators/dateRange.d.ts.map +1 -0
- package/dist/validators/dateRange.js +16 -0
- package/package.json +10 -3
- package/dist/api/client.d.ts +0 -30
- package/dist/api/client.d.ts.map +0 -1
- package/dist/api/client.js +0 -82
- package/dist/api/server.d.ts +0 -2748
- package/dist/api/server.d.ts.map +0 -1
- package/dist/api/server.js +0 -100
- package/dist/api/trpc/error-logging.d.ts +0 -14
- package/dist/api/trpc/error-logging.d.ts.map +0 -1
- package/dist/api/trpc/error-logging.js +0 -75
- package/dist/api/utils/async-caller-proxy.d.ts +0 -2
- package/dist/api/utils/async-caller-proxy.d.ts.map +0 -1
- package/dist/api/utils/async-caller-proxy.js +0 -36
- package/dist/api/utils/lazy-caller-proxy.d.ts +0 -2
- package/dist/api/utils/lazy-caller-proxy.d.ts.map +0 -1
- package/dist/api/utils/lazy-caller-proxy.js +0 -36
- package/dist/api/utils/router-types.d.ts +0 -7
- package/dist/api/utils/router-types.d.ts.map +0 -1
- package/dist/api/utils/router-types.js +0 -0
- package/dist/plugins/manifest.d.ts +0 -28
- package/dist/plugins/manifest.d.ts.map +0 -1
- package/dist/plugins/manifest.js +0 -83
- package/dist/plugins/registry.d.ts +0 -22
- package/dist/plugins/registry.d.ts.map +0 -1
- package/dist/plugins/registry.js +0 -25
- package/dist/utils/log.d.ts +0 -18
- package/dist/utils/log.d.ts.map +0 -1
- package/dist/utils/log.js +0 -28
|
@@ -22,12 +22,12 @@ export class MysqlTableChecker extends DbTableChecker {
|
|
|
22
22
|
return _tables;
|
|
23
23
|
}
|
|
24
24
|
static async getColumns(tableName) {
|
|
25
|
-
const statement = sql `
|
|
26
|
-
SELECT COLUMN_NAME
|
|
27
|
-
FROM information_schema.COLUMNS c
|
|
28
|
-
inner join information_schema.TABLES t ON t.TABLE_NAME = c.TABLE_NAME
|
|
29
|
-
WHERE t.TABLE_SCHEMA = DATABASE()
|
|
30
|
-
AND c.TABLE_SCHEMA = DATABASE()
|
|
25
|
+
const statement = sql `
|
|
26
|
+
SELECT COLUMN_NAME
|
|
27
|
+
FROM information_schema.COLUMNS c
|
|
28
|
+
inner join information_schema.TABLES t ON t.TABLE_NAME = c.TABLE_NAME
|
|
29
|
+
WHERE t.TABLE_SCHEMA = DATABASE()
|
|
30
|
+
AND c.TABLE_SCHEMA = DATABASE()
|
|
31
31
|
AND t.TABLE_NAME = ${tableName}`;
|
|
32
32
|
const _cols = [];
|
|
33
33
|
const _res = await db.execute(statement);
|
|
@@ -84,10 +84,10 @@ export class MysqlTableChecker extends DbTableChecker {
|
|
|
84
84
|
fullTextKeys.push({ name: key, columns: _fullTextKeyMap[key] });
|
|
85
85
|
}
|
|
86
86
|
// Foreign keys should be retrieved from information_schema
|
|
87
|
-
const [foreignKeys] = await db.execute(sql `
|
|
88
|
-
SELECT COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
|
|
89
|
-
FROM information_schema.KEY_COLUMN_USAGE
|
|
90
|
-
WHERE TABLE_NAME = ${table} AND CONSTRAINT_NAME != 'PRIMARY' AND REFERENCED_TABLE_NAME IS NOT NULL;
|
|
87
|
+
const [foreignKeys] = await db.execute(sql `
|
|
88
|
+
SELECT COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
|
|
89
|
+
FROM information_schema.KEY_COLUMN_USAGE
|
|
90
|
+
WHERE TABLE_NAME = ${table} AND CONSTRAINT_NAME != 'PRIMARY' AND REFERENCED_TABLE_NAME IS NOT NULL;
|
|
91
91
|
`);
|
|
92
92
|
return { primaryKeys, uniqueKeys, indexKeys, foreignKeys, fullTextKeys };
|
|
93
93
|
}
|
|
@@ -460,15 +460,15 @@ export class SectionFactory {
|
|
|
460
460
|
importErr.message.includes('Cannot find module') &&
|
|
461
461
|
importErr.message.includes('.section')) {
|
|
462
462
|
this.sectionProcessingErrors[file] ??= [];
|
|
463
|
-
this.sectionProcessingErrors[file].push(`❌ Invalid section import detected.
|
|
464
|
-
|
|
465
|
-
Sections MUST use extensionless relative imports:
|
|
466
|
-
|
|
467
|
-
✅ import exampleSection from './example.section'
|
|
468
|
-
❌ import exampleSection from './example.section.ts'
|
|
469
|
-
❌ import exampleSection from './example.section.js'
|
|
470
|
-
|
|
471
|
-
This file is bundled with esbuild, so Node never resolves the import directly.
|
|
463
|
+
this.sectionProcessingErrors[file].push(`❌ Invalid section import detected.
|
|
464
|
+
|
|
465
|
+
Sections MUST use extensionless relative imports:
|
|
466
|
+
|
|
467
|
+
✅ import exampleSection from './example.section'
|
|
468
|
+
❌ import exampleSection from './example.section.ts'
|
|
469
|
+
❌ import exampleSection from './example.section.js'
|
|
470
|
+
|
|
471
|
+
This file is bundled with esbuild, so Node never resolves the import directly.
|
|
472
472
|
If you added an extension manually, remove it.`);
|
|
473
473
|
this.errorCount++;
|
|
474
474
|
continue;
|
|
@@ -417,15 +417,15 @@ export class SectionFactory {
|
|
|
417
417
|
importErr.message.includes('Cannot find module') &&
|
|
418
418
|
importErr.message.includes('.section')) {
|
|
419
419
|
this.sectionProcessingErrors[file] ??= [];
|
|
420
|
-
this.sectionProcessingErrors[file].push(`❌ Invalid section import detected.
|
|
421
|
-
|
|
422
|
-
Sections MUST use extensionless relative imports:
|
|
423
|
-
|
|
424
|
-
✅ import exampleSection from './example.section'
|
|
425
|
-
❌ import exampleSection from './example.section.ts'
|
|
426
|
-
❌ import exampleSection from './example.section.js'
|
|
427
|
-
|
|
428
|
-
This file is bundled with jiti, so Node never resolves the import directly.
|
|
420
|
+
this.sectionProcessingErrors[file].push(`❌ Invalid section import detected.
|
|
421
|
+
|
|
422
|
+
Sections MUST use extensionless relative imports:
|
|
423
|
+
|
|
424
|
+
✅ import exampleSection from './example.section'
|
|
425
|
+
❌ import exampleSection from './example.section.ts'
|
|
426
|
+
❌ import exampleSection from './example.section.js'
|
|
427
|
+
|
|
428
|
+
This file is bundled with jiti, so Node never resolves the import directly.
|
|
429
429
|
If you added an extension manually, remove it.`);
|
|
430
430
|
this.errorCount++;
|
|
431
431
|
continue;
|
|
@@ -70,13 +70,13 @@ declare const optionsSchema: z.ZodObject<{
|
|
|
70
70
|
maxDate: z.ZodOptional<z.ZodUnion<readonly [z.ZodDate, z.ZodLiteral<"now">]>>;
|
|
71
71
|
defaultStartValue: z.ZodOptional<z.ZodDate>;
|
|
72
72
|
defaultEndValue: z.ZodOptional<z.ZodDate>;
|
|
73
|
-
order: z.ZodOptional<z.ZodNumber>;
|
|
74
|
-
localized: z.ZodOptional<z.ZodBoolean>;
|
|
75
73
|
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
76
74
|
required: z.ZodOptional<z.ZodBoolean>;
|
|
77
75
|
defaultValue: z.ZodOptional<z.ZodAny>;
|
|
76
|
+
order: z.ZodOptional<z.ZodNumber>;
|
|
78
77
|
conditionalRules: z.ZodOptional<z.ZodArray<z.ZodCustom<import("../types/index.js").ConditionalRule, import("../types/index.js").ConditionalRule>>>;
|
|
79
78
|
adminGenerated: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodLiteral<false>, z.ZodLiteral<"readonly">]>>;
|
|
79
|
+
localized: z.ZodOptional<z.ZodBoolean>;
|
|
80
80
|
}, z.core.$strict>;
|
|
81
81
|
declare const dateRangeFieldConfigSchema: z.ZodObject<{
|
|
82
82
|
/**
|
|
@@ -97,13 +97,13 @@ declare const dateRangeFieldConfigSchema: z.ZodObject<{
|
|
|
97
97
|
maxDate: z.ZodOptional<z.ZodUnion<readonly [z.ZodDate, z.ZodLiteral<"now">]>>;
|
|
98
98
|
defaultStartValue: z.ZodOptional<z.ZodDate>;
|
|
99
99
|
defaultEndValue: z.ZodOptional<z.ZodDate>;
|
|
100
|
-
order: z.ZodOptional<z.ZodNumber>;
|
|
101
|
-
localized: z.ZodOptional<z.ZodBoolean>;
|
|
102
100
|
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
103
101
|
required: z.ZodOptional<z.ZodBoolean>;
|
|
104
102
|
defaultValue: z.ZodOptional<z.ZodAny>;
|
|
103
|
+
order: z.ZodOptional<z.ZodNumber>;
|
|
105
104
|
conditionalRules: z.ZodOptional<z.ZodArray<z.ZodCustom<import("../types/index.js").ConditionalRule, import("../types/index.js").ConditionalRule>>>;
|
|
106
105
|
adminGenerated: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodLiteral<false>, z.ZodLiteral<"readonly">]>>;
|
|
106
|
+
localized: z.ZodOptional<z.ZodBoolean>;
|
|
107
107
|
}, z.core.$strict>;
|
|
108
108
|
export type DateRangeFieldConfig = z.infer<typeof dateRangeFieldConfigSchema>;
|
|
109
109
|
/**
|
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
declare const configSchema: z.ZodObject<{
|
|
6
|
+
startName: z.ZodString;
|
|
7
|
+
endName: z.ZodString;
|
|
8
|
+
format: z.ZodOptional<z.ZodEnum<{
|
|
9
|
+
date: "date";
|
|
10
|
+
datetime: "datetime";
|
|
11
|
+
}>>;
|
|
12
|
+
defaultStartValue: z.ZodOptional<z.ZodCustom<Date, Date>>;
|
|
13
|
+
defaultEndValue: z.ZodOptional<z.ZodCustom<Date, Date>>;
|
|
14
|
+
}, z.core.$strict>;
|
|
15
|
+
type Config = z.infer<typeof configSchema>;
|
|
16
|
+
/**
|
|
17
|
+
* DateRangeField stores two DB columns: startName and endName.
|
|
18
|
+
*
|
|
19
|
+
* Because Field expects a single `name`, we pass `startName` as `name` to the
|
|
20
|
+
* super constructor (used for label generation / identity only).
|
|
21
|
+
* The field opts out of the normal single-column SQL path via hasSqlNameAndValue()=false
|
|
22
|
+
* and instead exposes setValues() / getSqlNamesAndValues() which submit.ts calls
|
|
23
|
+
* via an is(field, DateRangeField) guard — the same pattern used for SelectField.
|
|
24
|
+
*/
|
|
25
|
+
export declare class DateRangeField extends Field<'date_range', Config> {
|
|
26
|
+
static readonly [entityKind]: string;
|
|
27
|
+
readonly startName: string;
|
|
28
|
+
readonly endName: string;
|
|
29
|
+
readonly format: 'date' | 'datetime';
|
|
30
|
+
private startValue;
|
|
31
|
+
private endValue;
|
|
32
|
+
constructor(config: Omit<BaseFieldConfig<Config>, 'name'> & Config);
|
|
33
|
+
hasSqlNameAndValue(): boolean;
|
|
34
|
+
setValues(postData: FormData): void;
|
|
35
|
+
getSqlNamesAndValues(): Record<string, string | null>;
|
|
36
|
+
getValue(): {
|
|
37
|
+
from: string | null;
|
|
38
|
+
to: string | null;
|
|
39
|
+
};
|
|
40
|
+
exportForClient(): {
|
|
41
|
+
startName: string;
|
|
42
|
+
endName: string;
|
|
43
|
+
format: "date" | "datetime";
|
|
44
|
+
startValue: any;
|
|
45
|
+
endValue: any;
|
|
46
|
+
type: "date_range";
|
|
47
|
+
name: string;
|
|
48
|
+
label: string;
|
|
49
|
+
required: boolean;
|
|
50
|
+
localized?: boolean;
|
|
51
|
+
conditionalFields: import("../types/index.js").ConditionalField[];
|
|
52
|
+
readonly: boolean;
|
|
53
|
+
defaultValue: any;
|
|
54
|
+
value: any;
|
|
55
|
+
};
|
|
56
|
+
checkRequired(): void;
|
|
57
|
+
prepareForSubmission(): Promise<void>;
|
|
58
|
+
private parseDate;
|
|
59
|
+
private formatDate;
|
|
60
|
+
}
|
|
61
|
+
export type DateRangeFieldClientConfig = ReturnType<DateRangeField['exportForClient']>;
|
|
62
|
+
declare const optionsSchema: z.ZodObject<{
|
|
63
|
+
startName: z.ZodString;
|
|
64
|
+
endName: z.ZodString;
|
|
65
|
+
format: z.ZodOptional<z.ZodEnum<{
|
|
66
|
+
date: "date";
|
|
67
|
+
datetime: "datetime";
|
|
68
|
+
}>>;
|
|
69
|
+
defaultStartValue: z.ZodOptional<z.ZodCustom<Date, Date>>;
|
|
70
|
+
defaultEndValue: z.ZodOptional<z.ZodCustom<Date, Date>>;
|
|
71
|
+
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
72
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
73
|
+
defaultValue: z.ZodOptional<z.ZodAny>;
|
|
74
|
+
order: z.ZodOptional<z.ZodNumber>;
|
|
75
|
+
conditionalRules: z.ZodOptional<z.ZodArray<z.ZodCustom<import("../types/index.js").ConditionalRule, import("../types/index.js").ConditionalRule>>>;
|
|
76
|
+
adminGenerated: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodLiteral<false>, z.ZodLiteral<"readonly">]>>;
|
|
77
|
+
localized: z.ZodOptional<z.ZodBoolean>;
|
|
78
|
+
}, z.core.$strict>;
|
|
79
|
+
declare const dateRangeFieldConfigSchema: z.ZodObject<{
|
|
80
|
+
/**
|
|
81
|
+
* Auto-computed as `${startName}|${endName}`.
|
|
82
|
+
* Satisfies the `FieldConfig` union constraint that every config has a `name` property.
|
|
83
|
+
* Never set manually — use `startName` and `endName` instead.
|
|
84
|
+
*/
|
|
85
|
+
name: z.ZodString;
|
|
86
|
+
type: z.ZodLiteral<"date_range">;
|
|
87
|
+
build: z.ZodFunction<z.core.$ZodFunctionArgs, z.ZodCustom<DateRangeField, DateRangeField>>;
|
|
88
|
+
startName: z.ZodString;
|
|
89
|
+
endName: z.ZodString;
|
|
90
|
+
format: z.ZodOptional<z.ZodEnum<{
|
|
91
|
+
date: "date";
|
|
92
|
+
datetime: "datetime";
|
|
93
|
+
}>>;
|
|
94
|
+
defaultStartValue: z.ZodOptional<z.ZodCustom<Date, Date>>;
|
|
95
|
+
defaultEndValue: z.ZodOptional<z.ZodCustom<Date, Date>>;
|
|
96
|
+
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
97
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
98
|
+
defaultValue: z.ZodOptional<z.ZodAny>;
|
|
99
|
+
order: z.ZodOptional<z.ZodNumber>;
|
|
100
|
+
conditionalRules: z.ZodOptional<z.ZodArray<z.ZodCustom<import("../types/index.js").ConditionalRule, import("../types/index.js").ConditionalRule>>>;
|
|
101
|
+
adminGenerated: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodLiteral<false>, z.ZodLiteral<"readonly">]>>;
|
|
102
|
+
localized: z.ZodOptional<z.ZodBoolean>;
|
|
103
|
+
}, z.core.$strict>;
|
|
104
|
+
export type DateRangeFieldConfig = z.infer<typeof dateRangeFieldConfigSchema>;
|
|
105
|
+
/**
|
|
106
|
+
* Helper to create a date range field configuration.
|
|
107
|
+
* Uses `startName` and `endName` instead of the usual `name`.
|
|
108
|
+
* The `name` property is auto-computed as `${startName}|${endName}`.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* dateRangeField({ startName: 'event_start', endName: 'event_end', label: 'Event Dates' })
|
|
112
|
+
*/
|
|
113
|
+
export declare function dateRangeField(field: z.infer<typeof optionsSchema>): DateRangeFieldConfig;
|
|
114
|
+
export {};
|
|
115
|
+
//# sourceMappingURL=dateRange.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dateRange.d.ts","sourceRoot":"","sources":["../../../src/core/fields/dateRange.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;AAIxB,QAAA,MAAM,YAAY;;;;;;;;;kBAMhB,CAAA;AAEF,KAAK,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA;AAE1C;;;;;;;;GAQG;AACH,qBAAa,cAAe,SAAQ,KAAK,CAAC,YAAY,EAAE,MAAM,CAAC;IAC3D,gBAAyB,CAAC,UAAU,CAAC,EAAE,MAAM,CAAmB;IAEhE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAA;IAEpC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,QAAQ,CAAK;gBAET,MAAM,EAAE,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,GAAG,MAAM;IAYzD,kBAAkB,IAAI,OAAO;IAMtC,SAAS,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IASnC,oBAAoB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IASrD,QAAQ,IAAI;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;IAO7C,eAAe;;;;;;;;;;;;;;;;IAWxB,aAAa,IAAI,IAAI;IASf,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB3C,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,UAAU;CAOrB;AAED,MAAM,MAAM,0BAA0B,GAAG,UAAU,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC,CAAA;AAItF,QAAA,MAAM,aAAa;;;;;;;;;;;;;;;;kBAGjB,CAAA;AAEF,QAAA,MAAM,0BAA0B;IAE5B;;;;OAIG;;;;;;;;;;;;;;;;;;;kBAOL,CAAA;AAEF,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAA;AAE7E;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,GAAG,oBAAoB,CAczF"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Field, baseFieldConfigSchema } from './field.js';
|
|
2
|
+
import { entityKind } from '../helpers/index.js';
|
|
3
|
+
import * as z from 'zod';
|
|
4
|
+
import dayjs from 'dayjs';
|
|
5
|
+
import getString from '../../translations/index.js';
|
|
6
|
+
const configSchema = z.strictObject({
|
|
7
|
+
startName: z.string().describe('The DB column name for the start date'),
|
|
8
|
+
endName: z.string().describe('The DB column name for the end date'),
|
|
9
|
+
format: z.enum(['date', 'datetime']).optional().describe('The format of the date columns'),
|
|
10
|
+
defaultStartValue: z.custom().optional().describe('Default value for the start date'),
|
|
11
|
+
defaultEndValue: z.custom().optional().describe('Default value for the end date'),
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* DateRangeField stores two DB columns: startName and endName.
|
|
15
|
+
*
|
|
16
|
+
* Because Field expects a single `name`, we pass `startName` as `name` to the
|
|
17
|
+
* super constructor (used for label generation / identity only).
|
|
18
|
+
* The field opts out of the normal single-column SQL path via hasSqlNameAndValue()=false
|
|
19
|
+
* and instead exposes setValues() / getSqlNamesAndValues() which submit.ts calls
|
|
20
|
+
* via an is(field, DateRangeField) guard — the same pattern used for SelectField.
|
|
21
|
+
*/
|
|
22
|
+
export class DateRangeField extends Field {
|
|
23
|
+
static [entityKind] = 'DateRangeField';
|
|
24
|
+
startName;
|
|
25
|
+
endName;
|
|
26
|
+
format;
|
|
27
|
+
startValue;
|
|
28
|
+
endValue;
|
|
29
|
+
constructor(config) {
|
|
30
|
+
// Pass startName as `name` so the base class label-generation / identity works
|
|
31
|
+
super({ ...config, name: config.startName }, 'date_range');
|
|
32
|
+
this.startName = config.startName;
|
|
33
|
+
this.endName = config.endName;
|
|
34
|
+
this.format = config.format ?? 'date';
|
|
35
|
+
this.startValue = config.defaultStartValue ?? undefined;
|
|
36
|
+
this.endValue = config.defaultEndValue ?? undefined;
|
|
37
|
+
}
|
|
38
|
+
// ─── Opt out of single-column SQL path ────────────────────────────────────
|
|
39
|
+
hasSqlNameAndValue() {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// ─── Dual-column FormData reading (called by submit.ts) ───────────────────
|
|
43
|
+
setValues(postData) {
|
|
44
|
+
const start = postData.get(this.startName);
|
|
45
|
+
const end = postData.get(this.endName);
|
|
46
|
+
if (start)
|
|
47
|
+
this.startValue = start;
|
|
48
|
+
if (end)
|
|
49
|
+
this.endValue = end;
|
|
50
|
+
}
|
|
51
|
+
// ─── Dual-column SQL output (called by submit.ts) ─────────────────────────
|
|
52
|
+
getSqlNamesAndValues() {
|
|
53
|
+
return {
|
|
54
|
+
[this.startName]: this.formatDate(this.startValue),
|
|
55
|
+
[this.endName]: this.formatDate(this.endValue),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// ─── Standard Field interface ─────────────────────────────────────────────
|
|
59
|
+
getValue() {
|
|
60
|
+
return {
|
|
61
|
+
from: this.formatDate(this.startValue),
|
|
62
|
+
to: this.formatDate(this.endValue),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
exportForClient() {
|
|
66
|
+
return {
|
|
67
|
+
...super.exportForClient(),
|
|
68
|
+
startName: this.startName,
|
|
69
|
+
endName: this.endName,
|
|
70
|
+
format: this.format,
|
|
71
|
+
startValue: this.startValue ?? null,
|
|
72
|
+
endValue: this.endValue ?? null,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
checkRequired() {
|
|
76
|
+
if (!this.required)
|
|
77
|
+
return;
|
|
78
|
+
if (!this.parseDate(this.startValue) || !this.parseDate(this.endValue)) {
|
|
79
|
+
throw new Error(getString('fieldIsRequired', this.language, { field: this.getLocalizedLabel() }));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async prepareForSubmission() {
|
|
83
|
+
const start = this.parseDate(this.startValue);
|
|
84
|
+
const end = this.parseDate(this.endValue);
|
|
85
|
+
if (this.required && (!start || !end)) {
|
|
86
|
+
throw new Error(getString('invalidDatePleaseProvideValid', this.language, { field: this.getLocalizedLabel() }));
|
|
87
|
+
}
|
|
88
|
+
if (start)
|
|
89
|
+
this.startValue = start.toISOString();
|
|
90
|
+
if (end)
|
|
91
|
+
this.endValue = end.toISOString();
|
|
92
|
+
}
|
|
93
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────
|
|
94
|
+
parseDate(value) {
|
|
95
|
+
if (value === undefined || value === null || value === '')
|
|
96
|
+
return null;
|
|
97
|
+
const date = new Date(value);
|
|
98
|
+
return isNaN(date.getTime()) ? null : date;
|
|
99
|
+
}
|
|
100
|
+
formatDate(value) {
|
|
101
|
+
const date = this.parseDate(value);
|
|
102
|
+
if (!date)
|
|
103
|
+
return null;
|
|
104
|
+
return this.format === 'datetime'
|
|
105
|
+
? dayjs(date).format('YYYY-MM-DD HH:mm:ss')
|
|
106
|
+
: dayjs(date).format('YYYY-MM-DD');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// ─── Config schema & factory ──────────────────────────────────────────────────
|
|
110
|
+
const optionsSchema = z.strictObject({
|
|
111
|
+
...baseFieldConfigSchema.omit({ name: true }).shape,
|
|
112
|
+
...configSchema.shape,
|
|
113
|
+
});
|
|
114
|
+
const dateRangeFieldConfigSchema = z.strictObject({
|
|
115
|
+
...optionsSchema.shape,
|
|
116
|
+
/**
|
|
117
|
+
* Auto-computed as `${startName}|${endName}`.
|
|
118
|
+
* Satisfies the `FieldConfig` union constraint that every config has a `name` property.
|
|
119
|
+
* Never set manually — use `startName` and `endName` instead.
|
|
120
|
+
*/
|
|
121
|
+
name: z.string(),
|
|
122
|
+
type: z.literal('date_range').describe('The type of the field'),
|
|
123
|
+
build: z
|
|
124
|
+
.function()
|
|
125
|
+
.output(z.instanceof(DateRangeField))
|
|
126
|
+
.describe('Build a DateRangeField instance from this config'),
|
|
127
|
+
});
|
|
128
|
+
/**
|
|
129
|
+
* Helper to create a date range field configuration.
|
|
130
|
+
* Uses `startName` and `endName` instead of the usual `name`.
|
|
131
|
+
* The `name` property is auto-computed as `${startName}|${endName}`.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* dateRangeField({ startName: 'event_start', endName: 'event_end', label: 'Event Dates' })
|
|
135
|
+
*/
|
|
136
|
+
export function dateRangeField(field) {
|
|
137
|
+
const result = optionsSchema.safeParse(field);
|
|
138
|
+
if (!result.success) {
|
|
139
|
+
throw new Error(`[DateRangeField: ${field.startName}/${field.endName}]: ${z.prettifyError(result.error)}`);
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
...field,
|
|
143
|
+
name: `${field.startName}|${field.endName}`,
|
|
144
|
+
type: 'date_range',
|
|
145
|
+
build() {
|
|
146
|
+
return new DateRangeField(field);
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import type { BaseFieldConfig } from './field.js';
|
|
2
|
-
import { Field } from './field.js';
|
|
3
|
-
import { entityKind } from '../helpers/index.js';
|
|
4
1
|
import * as z from 'zod';
|
|
2
|
+
import type { MultilingualString } from '../../translations/language-utils.js';
|
|
3
|
+
import type { BaseFieldConfig } from './field.js';
|
|
5
4
|
import type { TextFieldConfig } from './text.js';
|
|
5
|
+
import { entityKind } from '../helpers/index.js';
|
|
6
|
+
import { Field } from './field.js';
|
|
7
|
+
type SlugEditProtectionAlertConfig = {
|
|
8
|
+
title?: MultilingualString;
|
|
9
|
+
description?: MultilingualString;
|
|
10
|
+
};
|
|
11
|
+
type NormalizedSlugEditProtection = {
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
alert?: SlugEditProtectionAlertConfig;
|
|
14
|
+
};
|
|
6
15
|
declare const slugFieldConfigSchema: z.ZodObject<{
|
|
7
16
|
placeholder: z.ZodOptional<z.ZodString>;
|
|
8
17
|
minLength: z.ZodOptional<z.ZodNumber>;
|
|
@@ -50,6 +59,17 @@ declare const slugFieldConfigSchema: z.ZodObject<{
|
|
|
50
59
|
adminGenerated?: boolean | "readonly" | undefined;
|
|
51
60
|
localized?: boolean | undefined;
|
|
52
61
|
}>;
|
|
62
|
+
/**
|
|
63
|
+
* Prevents edit forms from automatically changing this slug when the linked field changes.
|
|
64
|
+
* Disabled by default. Set to true or provide an object config to protect edit forms.
|
|
65
|
+
*/
|
|
66
|
+
editProtection: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
67
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
68
|
+
alert: z.ZodOptional<z.ZodObject<{
|
|
69
|
+
title: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
70
|
+
description: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
71
|
+
}, z.core.$strict>>;
|
|
72
|
+
}, z.core.$strict>]>>;
|
|
53
73
|
}, z.core.$strict>;
|
|
54
74
|
type Config = z.infer<typeof slugFieldConfigSchema>;
|
|
55
75
|
export declare class SlugField extends Field<'slug', Config> {
|
|
@@ -59,9 +79,12 @@ export declare class SlugField extends Field<'slug', Config> {
|
|
|
59
79
|
readonly placeholder: string | undefined;
|
|
60
80
|
readonly forFieldName: string;
|
|
61
81
|
readonly forFieldConfig: TextFieldConfig;
|
|
82
|
+
readonly editProtection: NormalizedSlugEditProtection;
|
|
62
83
|
private readonly _defaultValue;
|
|
63
84
|
value: string | undefined;
|
|
64
85
|
constructor(config: BaseFieldConfig<Config>);
|
|
86
|
+
private static normalizeEditProtection;
|
|
87
|
+
private getEditProtectionClientConfig;
|
|
65
88
|
/**
|
|
66
89
|
* Convert a string to a URL-friendly slug.
|
|
67
90
|
* - Converts to lowercase
|
|
@@ -76,6 +99,13 @@ export declare class SlugField extends Field<'slug', Config> {
|
|
|
76
99
|
minLength: number | undefined;
|
|
77
100
|
placeholder: string | undefined;
|
|
78
101
|
forFieldName: string;
|
|
102
|
+
editProtection: {
|
|
103
|
+
enabled: boolean;
|
|
104
|
+
alert: {
|
|
105
|
+
title: string | undefined;
|
|
106
|
+
description: string | undefined;
|
|
107
|
+
};
|
|
108
|
+
};
|
|
79
109
|
type: "slug";
|
|
80
110
|
name: string;
|
|
81
111
|
label: string;
|
|
@@ -147,6 +177,17 @@ declare const optionsSchema: z.ZodObject<{
|
|
|
147
177
|
adminGenerated?: boolean | "readonly" | undefined;
|
|
148
178
|
localized?: boolean | undefined;
|
|
149
179
|
}>;
|
|
180
|
+
/**
|
|
181
|
+
* Prevents edit forms from automatically changing this slug when the linked field changes.
|
|
182
|
+
* Disabled by default. Set to true or provide an object config to protect edit forms.
|
|
183
|
+
*/
|
|
184
|
+
editProtection: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
185
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
186
|
+
alert: z.ZodOptional<z.ZodObject<{
|
|
187
|
+
title: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
188
|
+
description: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
189
|
+
}, z.core.$strict>>;
|
|
190
|
+
}, z.core.$strict>]>>;
|
|
150
191
|
name: z.ZodString;
|
|
151
192
|
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
152
193
|
required: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -204,6 +245,17 @@ declare const configSchema: z.ZodObject<{
|
|
|
204
245
|
adminGenerated?: boolean | "readonly" | undefined;
|
|
205
246
|
localized?: boolean | undefined;
|
|
206
247
|
}>;
|
|
248
|
+
/**
|
|
249
|
+
* Prevents edit forms from automatically changing this slug when the linked field changes.
|
|
250
|
+
* Disabled by default. Set to true or provide an object config to protect edit forms.
|
|
251
|
+
*/
|
|
252
|
+
editProtection: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
253
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
254
|
+
alert: z.ZodOptional<z.ZodObject<{
|
|
255
|
+
title: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
256
|
+
description: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
257
|
+
}, z.core.$strict>>;
|
|
258
|
+
}, z.core.$strict>]>>;
|
|
207
259
|
name: z.ZodString;
|
|
208
260
|
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
209
261
|
required: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -1 +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,
|
|
1
|
+
{"version":3,"file":"slug.d.ts","sourceRoot":"","sources":["../../../src/core/fields/slug.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AAExB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAA;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAGhD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EAAyB,KAAK,EAAE,MAAM,YAAY,CAAA;AAwBzD,KAAK,6BAA6B,GAAG;IACjC,KAAK,CAAC,EAAE,kBAAkB,CAAA;IAC1B,WAAW,CAAC,EAAE,kBAAkB,CAAA;CACnC,CAAA;AAED,KAAK,4BAA4B,GAAG;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,6BAA6B,CAAA;CACxC,CAAA;AAED,QAAA,MAAM,qBAAqB;;;;IAIvB;;;OAGG;;IAEH;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAEH;;;OAGG;;;;;;;;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,QAAQ,CAAC,cAAc,EAAE,4BAA4B,CAAA;IACrD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IACzC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;gBAEtB,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC;IAY3C,OAAO,CAAC,MAAM,CAAC,uBAAuB;IAetC,OAAO,CAAC,6BAA6B;IAgBrC;;;;;;;OAOG;IACH,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAUpB,eAAe;;;;;;;;;;;;;;;;;;;;;;IAW/B;;OAEG;IACH,QAAQ,IAAI,MAAM,GAAG,SAAS;IAI9B,aAAa;IAQJ,kBAAkB,IAAI,OAAO;IAOtC;;;;OAIG;IACG,oBAAoB;CAwB7B;AAED,MAAM,MAAM,qBAAqB,GAAG,UAAU,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC,CAAA;AAE5E,QAAA,MAAM,aAAa;;;;IA9Jf;;;OAGG;;IAEH;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAEH;;;OAGG;;;;;;;;;;;;;;;kBAmJL,CAAA;AAEF,QAAA,MAAM,YAAY;;;;;;IAnKd;;;OAGG;;IAEH;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAEH;;;OAGG;;;;;;;;;;;;;;;kBAyJL,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"}
|
package/dist/core/fields/slug.js
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
|
-
import { Field, baseFieldConfigSchema } from './field.js';
|
|
2
|
-
import { entityKind } from '../helpers/index.js';
|
|
3
1
|
import * as z from 'zod';
|
|
4
2
|
import getString from '../../translations/index.js';
|
|
3
|
+
import { resolveMultilingualString } from '../../translations/language-utils.js';
|
|
4
|
+
import { entityKind } from '../helpers/index.js';
|
|
5
|
+
import { baseFieldConfigSchema, Field } from './field.js';
|
|
6
|
+
const multilingualStringSchema = z
|
|
7
|
+
.union([z.string(), z.record(z.string(), z.string())])
|
|
8
|
+
.describe('A plain string or an object with language codes as keys');
|
|
9
|
+
const editProtectionAlertSchema = z.strictObject({
|
|
10
|
+
title: multilingualStringSchema.optional().describe('The alert title shown when editing a protected slug'),
|
|
11
|
+
description: multilingualStringSchema
|
|
12
|
+
.optional()
|
|
13
|
+
.describe('The alert description shown when editing a protected slug'),
|
|
14
|
+
});
|
|
15
|
+
const editProtectionSchema = z
|
|
16
|
+
.union([
|
|
17
|
+
z.boolean(),
|
|
18
|
+
z.strictObject({
|
|
19
|
+
enabled: z.boolean().optional().describe('Whether edit protection is enabled'),
|
|
20
|
+
alert: editProtectionAlertSchema.optional().describe('Custom alert text for protected slug edits'),
|
|
21
|
+
}),
|
|
22
|
+
])
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('Protects the slug from automatic changes in edit forms');
|
|
5
25
|
const slugFieldConfigSchema = z.strictObject({
|
|
6
26
|
placeholder: z.string().optional().describe('The placeholder text for the field'),
|
|
7
27
|
minLength: z.number().optional().describe('The minimum length of the slug'),
|
|
@@ -17,6 +37,11 @@ const slugFieldConfigSchema = z.strictObject({
|
|
|
17
37
|
* generate a slug from that value.
|
|
18
38
|
*/
|
|
19
39
|
for: z.custom().describe('The text field config this slug is derived from'),
|
|
40
|
+
/**
|
|
41
|
+
* Prevents edit forms from automatically changing this slug when the linked field changes.
|
|
42
|
+
* Disabled by default. Set to true or provide an object config to protect edit forms.
|
|
43
|
+
*/
|
|
44
|
+
editProtection: editProtectionSchema,
|
|
20
45
|
});
|
|
21
46
|
export class SlugField extends Field {
|
|
22
47
|
static [entityKind] = 'SlugField';
|
|
@@ -25,6 +50,7 @@ export class SlugField extends Field {
|
|
|
25
50
|
placeholder;
|
|
26
51
|
forFieldName;
|
|
27
52
|
forFieldConfig;
|
|
53
|
+
editProtection;
|
|
28
54
|
_defaultValue;
|
|
29
55
|
value;
|
|
30
56
|
constructor(config) {
|
|
@@ -36,6 +62,33 @@ export class SlugField extends Field {
|
|
|
36
62
|
this._defaultValue = config.defaultValue;
|
|
37
63
|
this.forFieldName = config.for.name;
|
|
38
64
|
this.forFieldConfig = config.for;
|
|
65
|
+
this.editProtection = SlugField.normalizeEditProtection(config.editProtection);
|
|
66
|
+
}
|
|
67
|
+
static normalizeEditProtection(editProtection) {
|
|
68
|
+
if (!editProtection) {
|
|
69
|
+
return { enabled: false };
|
|
70
|
+
}
|
|
71
|
+
if (typeof editProtection === 'boolean') {
|
|
72
|
+
return { enabled: editProtection };
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
enabled: editProtection.enabled ?? false,
|
|
76
|
+
alert: editProtection.alert,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
getEditProtectionClientConfig() {
|
|
80
|
+
const alert = this.editProtection.alert;
|
|
81
|
+
return {
|
|
82
|
+
enabled: this.editProtection.enabled,
|
|
83
|
+
alert: {
|
|
84
|
+
title: alert?.title
|
|
85
|
+
? resolveMultilingualString(alert.title, this.language, this.fallbackLanguage)
|
|
86
|
+
: undefined,
|
|
87
|
+
description: alert?.description
|
|
88
|
+
? resolveMultilingualString(alert.description, this.language, this.fallbackLanguage)
|
|
89
|
+
: undefined,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
39
92
|
}
|
|
40
93
|
/**
|
|
41
94
|
* Convert a string to a URL-friendly slug.
|
|
@@ -61,6 +114,7 @@ export class SlugField extends Field {
|
|
|
61
114
|
minLength: this.minLength,
|
|
62
115
|
placeholder: this.placeholder,
|
|
63
116
|
forFieldName: this.forFieldName,
|
|
117
|
+
editProtection: this.getEditProtectionClientConfig(),
|
|
64
118
|
};
|
|
65
119
|
}
|
|
66
120
|
/**
|