@easy-cms/core 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +186 -57
- package/dist/index.js +265 -9
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -254,6 +254,58 @@ declare function consoleEmail(options?: {
|
|
|
254
254
|
})[];
|
|
255
255
|
};
|
|
256
256
|
|
|
257
|
+
interface ConfigIssue {
|
|
258
|
+
/** Where the problem is, e.g. `collections.posts.fields.slug.from`. */
|
|
259
|
+
readonly path: string;
|
|
260
|
+
readonly message: string;
|
|
261
|
+
/** How to fix it. */
|
|
262
|
+
readonly hint?: string;
|
|
263
|
+
}
|
|
264
|
+
declare class ConfigError extends Error {
|
|
265
|
+
readonly issues: readonly ConfigIssue[];
|
|
266
|
+
constructor(issues: readonly ConfigIssue[]);
|
|
267
|
+
static format(issues: readonly ConfigIssue[]): string;
|
|
268
|
+
}
|
|
269
|
+
/** Base class for errors that map to an HTTP status in the REST API. */
|
|
270
|
+
declare class EasyCMSError extends Error {
|
|
271
|
+
readonly status: number;
|
|
272
|
+
constructor(message: string, status: number);
|
|
273
|
+
}
|
|
274
|
+
interface FieldError {
|
|
275
|
+
/** Dotted path, e.g. `links.0.url`. */
|
|
276
|
+
readonly field: string;
|
|
277
|
+
readonly message: string;
|
|
278
|
+
}
|
|
279
|
+
declare class ValidationError extends EasyCMSError {
|
|
280
|
+
readonly errors: readonly FieldError[];
|
|
281
|
+
constructor(collection: string, errors: readonly FieldError[]);
|
|
282
|
+
}
|
|
283
|
+
declare class NotFoundError extends EasyCMSError {
|
|
284
|
+
constructor(collection: string, id: unknown);
|
|
285
|
+
}
|
|
286
|
+
/** A bad `where`, `sort` or unknown collection/global. */
|
|
287
|
+
declare class QueryError extends EasyCMSError {
|
|
288
|
+
constructor(message: string);
|
|
289
|
+
}
|
|
290
|
+
/** The database schema does not match the config (pending or missing migrations). */
|
|
291
|
+
declare class SchemaError extends EasyCMSError {
|
|
292
|
+
constructor(message: string);
|
|
293
|
+
}
|
|
294
|
+
/** Not logged in, or the session is invalid. */
|
|
295
|
+
declare class UnauthorizedError extends EasyCMSError {
|
|
296
|
+
constructor(message?: string);
|
|
297
|
+
}
|
|
298
|
+
/** Logged in, but not allowed to do this. */
|
|
299
|
+
declare class ForbiddenError extends EasyCMSError {
|
|
300
|
+
constructor(message?: string);
|
|
301
|
+
}
|
|
302
|
+
declare class TooManyRequestsError extends EasyCMSError {
|
|
303
|
+
constructor(message: string);
|
|
304
|
+
}
|
|
305
|
+
declare class PayloadTooLargeError extends EasyCMSError {
|
|
306
|
+
constructor(message: string);
|
|
307
|
+
}
|
|
308
|
+
|
|
257
309
|
/** What a preview token opens: one document of a collection, or one global. */
|
|
258
310
|
type PreviewTarget = {
|
|
259
311
|
readonly collection: string;
|
|
@@ -533,7 +585,13 @@ type FieldValue<F extends Field, C extends Config = Config> = F extends {
|
|
|
533
585
|
} ? FieldsValue<Sub, C> : F extends {
|
|
534
586
|
readonly type: 'blocks';
|
|
535
587
|
readonly blocks: infer B extends readonly Block[];
|
|
536
|
-
} ? BlockValue<B[number], C>[] :
|
|
588
|
+
} ? BlockValue<B[number], C>[] : CustomValue<F>;
|
|
589
|
+
/** The value of a field of an added type (`CustomFieldTypes`). */
|
|
590
|
+
type CustomValue<F> = F extends {
|
|
591
|
+
readonly type: infer T extends keyof CustomFieldTypes;
|
|
592
|
+
} ? CustomFieldTypes[T] extends {
|
|
593
|
+
value: infer V;
|
|
594
|
+
} ? V : unknown : never;
|
|
537
595
|
/** One row of a `blocks` field: the block's fields plus `id` and `blockType` (distributes over kinds). */
|
|
538
596
|
type BlockValue<B, C extends Config> = B extends {
|
|
539
597
|
readonly slug: infer S;
|
|
@@ -1082,7 +1140,31 @@ interface FieldAdmin {
|
|
|
1082
1140
|
readonly component?: AdminComponent;
|
|
1083
1141
|
/** Shown below the field, e.g. a length meter or a preview. */
|
|
1084
1142
|
readonly after?: readonly AdminComponent[];
|
|
1143
|
+
/** Shows the value in the admin's lists (e.g. a color swatch). Default: the plain value. */
|
|
1144
|
+
readonly cell?: AdminComponent;
|
|
1085
1145
|
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Field types that packages add (`fieldTypes` in the config), for the types inferred from the
|
|
1148
|
+
* config. A package declares its own by augmenting this interface:
|
|
1149
|
+
*
|
|
1150
|
+
* ```ts
|
|
1151
|
+
* declare module '@easy-cms/core' {
|
|
1152
|
+
* interface CustomFieldTypes {
|
|
1153
|
+
* color: { value: string; options: { readonly presets?: readonly string[] } }
|
|
1154
|
+
* }
|
|
1155
|
+
* }
|
|
1156
|
+
* ```
|
|
1157
|
+
*/
|
|
1158
|
+
interface CustomFieldTypes {
|
|
1159
|
+
}
|
|
1160
|
+
/** A field of a type from `CustomFieldTypes`: the common options, plus the type's own. */
|
|
1161
|
+
type CustomField = {
|
|
1162
|
+
[K in keyof CustomFieldTypes]: BaseField<K & string, CustomFieldTypes[K] extends {
|
|
1163
|
+
value: infer V;
|
|
1164
|
+
} ? V : unknown> & (CustomFieldTypes[K] extends {
|
|
1165
|
+
options: infer O;
|
|
1166
|
+
} ? O : unknown);
|
|
1167
|
+
}[keyof CustomFieldTypes];
|
|
1086
1168
|
interface BaseField<TType extends string, TValue> {
|
|
1087
1169
|
readonly type: TType;
|
|
1088
1170
|
readonly name: string;
|
|
@@ -1104,6 +1186,8 @@ interface BaseField<TType extends string, TValue> {
|
|
|
1104
1186
|
readonly position?: 'sidebar';
|
|
1105
1187
|
/** Custom admin components for this field. */
|
|
1106
1188
|
readonly admin?: FieldAdmin;
|
|
1189
|
+
/** Set by Easy CMS on fields of an added type (`fieldTypes`): the type's name, e.g. `color`. */
|
|
1190
|
+
readonly customType?: string;
|
|
1107
1191
|
}
|
|
1108
1192
|
interface TextField extends BaseField<'text', string> {
|
|
1109
1193
|
readonly minLength?: number;
|
|
@@ -1220,10 +1304,48 @@ declare function mimeAllowedBy(mimeType: string, patterns: readonly string[]): b
|
|
|
1220
1304
|
declare const hasRows: (field: Field) => field is ArrayField | BlocksField;
|
|
1221
1305
|
/** The fields of one row of an `array` or `blocks` field (`undefined` for an unknown block). */
|
|
1222
1306
|
declare function rowFields(field: ArrayField | BlocksField, row: unknown): readonly Field[] | undefined;
|
|
1223
|
-
type Field = TextField | TextareaField | NumberField | BooleanField | DateField | EmailField | JsonField | SelectField | SlugField | RichTextField | UploadField | RelationshipField | ArrayField | GroupField | BlocksField;
|
|
1307
|
+
type Field = CustomField | TextField | TextareaField | NumberField | BooleanField | DateField | EmailField | JsonField | SelectField | SlugField | RichTextField | UploadField | RelationshipField | ArrayField | GroupField | BlocksField;
|
|
1224
1308
|
type FieldType = Field['type'];
|
|
1225
1309
|
declare const FIELD_TYPES: readonly ["text", "textarea", "number", "boolean", "date", "email", "json", "select", "slug", "richText", "upload", "relationship", "array", "group", "blocks"];
|
|
1226
1310
|
|
|
1311
|
+
/** Built-in types a field type can be stored as. */
|
|
1312
|
+
declare const FIELD_TYPE_BASES: readonly ["text", "textarea", "email", "number", "boolean", "date", "json"];
|
|
1313
|
+
type FieldTypeBase = (typeof FIELD_TYPE_BASES)[number];
|
|
1314
|
+
/** What a field type's `validate` gets besides the value: the field, with its own options. */
|
|
1315
|
+
interface FieldTypeValidateContext extends FieldValidateContext {
|
|
1316
|
+
readonly field: Readonly<Record<string, unknown>> & {
|
|
1317
|
+
readonly name: string;
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* A field type a package adds, e.g. `color`: stored, queried and translated like its `base`
|
|
1322
|
+
* type, with its own checks and admin components. List it in the config's `fieldTypes`.
|
|
1323
|
+
*/
|
|
1324
|
+
interface FieldTypeDefinition {
|
|
1325
|
+
/** The `type` fields use, e.g. `color`. Lowercase; not one of the built-in types. */
|
|
1326
|
+
readonly name: string;
|
|
1327
|
+
/** How values are stored and queried: one of the built-in scalar types. */
|
|
1328
|
+
readonly base: FieldTypeBase;
|
|
1329
|
+
/** Checks a value (not empty ones; `required` covers those). Return `true` or a message. */
|
|
1330
|
+
readonly validate?: (value: unknown, ctx: FieldTypeValidateContext) => true | string | Promise<true | string>;
|
|
1331
|
+
/** Checks a field's own options when the config loads; return a message for a mistake. */
|
|
1332
|
+
readonly checkOptions?: (field: Readonly<Record<string, unknown>>) => string | undefined;
|
|
1333
|
+
readonly admin?: {
|
|
1334
|
+
/** The input: a Web Component from `module`. */
|
|
1335
|
+
readonly component?: string;
|
|
1336
|
+
/** Shown in the admin's lists. */
|
|
1337
|
+
readonly cell?: string;
|
|
1338
|
+
/** The admin module (package export or file) that defines the components. */
|
|
1339
|
+
readonly module?: string;
|
|
1340
|
+
/** The field options its components get (as their `options`), e.g. `['presets']`. */
|
|
1341
|
+
readonly props?: readonly string[];
|
|
1342
|
+
};
|
|
1343
|
+
/** The value's TypeScript type for `generate:types`. Default: the base type's. */
|
|
1344
|
+
readonly typescript?: string;
|
|
1345
|
+
}
|
|
1346
|
+
/** Declares a field type; returns it unchanged. */
|
|
1347
|
+
declare function defineFieldType(definition: FieldTypeDefinition): FieldTypeDefinition;
|
|
1348
|
+
|
|
1227
1349
|
declare const WEBHOOK_EVENTS: readonly ["create", "update", "delete", "publish", "unpublish", "draft"];
|
|
1228
1350
|
/**
|
|
1229
1351
|
* `create`, `update`, `delete`: the stored document changed. `publish` / `unpublish`: its status
|
|
@@ -1404,7 +1526,7 @@ interface GlobalConfig {
|
|
|
1404
1526
|
}
|
|
1405
1527
|
type AdminLocale = 'en' | 'th';
|
|
1406
1528
|
/** Icons a collection or global can show in the admin menu ([Lucide](https://lucide.dev) names). */
|
|
1407
|
-
declare const ADMIN_ICONS: readonly ["file-text", "newspaper", "book-open", "notebook", "folder", "tag", "tags", "image", "images", "video", "music", "file", "users", "user", "building", "store", "shopping-bag", "shopping-cart", "package", "box", "calendar", "calendar-days", "map-pin", "globe", "house", "layout-grid", "layers", "star", "heart", "message-square", "mail", "phone", "briefcase", "graduation-cap", "utensils", "car", "settings", "sliders-horizontal", "palette", "megaphone", "bell", "link", "quote", "circle-help", "award", "ticket", "camera", "key"];
|
|
1529
|
+
declare const ADMIN_ICONS: readonly ["file-text", "newspaper", "book-open", "notebook", "folder", "tag", "tags", "image", "images", "video", "music", "file", "users", "user", "building", "store", "shopping-bag", "shopping-cart", "package", "box", "calendar", "calendar-days", "map-pin", "globe", "house", "layout-grid", "layers", "star", "heart", "message-square", "mail", "phone", "briefcase", "graduation-cap", "utensils", "car", "settings", "sliders-horizontal", "palette", "megaphone", "bell", "link", "quote", "circle-help", "award", "ticket", "camera", "key", "chart-column", "chart-line"];
|
|
1408
1530
|
type AdminIcon = (typeof ADMIN_ICONS)[number];
|
|
1409
1531
|
/** Your or your client's brand in the admin UI. */
|
|
1410
1532
|
interface AdminBrand {
|
|
@@ -1415,6 +1537,34 @@ interface AdminBrand {
|
|
|
1415
1537
|
/** Main color as `#rrggbb`; lighter and darker shades are derived from it. */
|
|
1416
1538
|
readonly color?: string;
|
|
1417
1539
|
}
|
|
1540
|
+
/** Who may see an admin page or dashboard widget. Default: every logged-in user. */
|
|
1541
|
+
type AdminViewAccess = (args: {
|
|
1542
|
+
readonly user: AuthUser;
|
|
1543
|
+
}) => boolean | Promise<boolean>;
|
|
1544
|
+
/** A page of its own in the admin, e.g. a plugin's report, at `<admin>/p/<path>`. */
|
|
1545
|
+
interface AdminPage {
|
|
1546
|
+
/** Its address under `<admin>/p/`: lowercase letters, digits and `-`, e.g. `forms-overview`. */
|
|
1547
|
+
readonly path: string;
|
|
1548
|
+
/** The page's body: a Web Component from an admin module. The admin draws the header. */
|
|
1549
|
+
readonly component: AdminComponent;
|
|
1550
|
+
/** Its title in the header and the menu. */
|
|
1551
|
+
readonly label: Label;
|
|
1552
|
+
/** Icon in the admin menu. Default `file-text`. */
|
|
1553
|
+
readonly icon?: AdminIcon;
|
|
1554
|
+
/** Listed under Content (default) or Settings in the menu, or not listed (`false`): reached by links. */
|
|
1555
|
+
readonly group?: 'content' | 'settings' | false;
|
|
1556
|
+
/** Who may open it. Pages a user may not open are left out of their admin. */
|
|
1557
|
+
readonly access?: AdminViewAccess;
|
|
1558
|
+
}
|
|
1559
|
+
/** A panel on the admin's dashboard, after the built-in ones. */
|
|
1560
|
+
interface DashboardWidget {
|
|
1561
|
+
/** A Web Component from an admin module. */
|
|
1562
|
+
readonly component: AdminComponent;
|
|
1563
|
+
/** Half the dashboard's width (default) or all of it. Full width on phones. */
|
|
1564
|
+
readonly width?: 'half' | 'full';
|
|
1565
|
+
/** Who sees it. */
|
|
1566
|
+
readonly access?: AdminViewAccess;
|
|
1567
|
+
}
|
|
1418
1568
|
interface AdminConfig {
|
|
1419
1569
|
/** Where the admin UI is served. Default `/admin`. */
|
|
1420
1570
|
readonly path?: string;
|
|
@@ -1440,6 +1590,10 @@ interface AdminConfig {
|
|
|
1440
1590
|
* module file. Remote URLs are not allowed.
|
|
1441
1591
|
*/
|
|
1442
1592
|
readonly modules?: readonly string[];
|
|
1593
|
+
/** Pages of their own, e.g. from plugins, at `<admin>/p/<path>`. */
|
|
1594
|
+
readonly pages?: readonly AdminPage[];
|
|
1595
|
+
/** Panels on the dashboard, after the built-in ones. */
|
|
1596
|
+
readonly dashboard?: readonly DashboardWidget[];
|
|
1443
1597
|
}
|
|
1444
1598
|
interface ImageSize {
|
|
1445
1599
|
/** Key in `media.sizes`, e.g. `thumbnail`. */
|
|
@@ -1621,6 +1775,11 @@ interface Config {
|
|
|
1621
1775
|
readonly endpoints?: readonly Endpoint[];
|
|
1622
1776
|
/** Extra `easy-cms <name>` CLI commands, e.g. from plugins. */
|
|
1623
1777
|
readonly commands?: readonly CliCommand[];
|
|
1778
|
+
/**
|
|
1779
|
+
* Field types from packages, e.g. `color` from `@easy-cms/fields`: fields then use
|
|
1780
|
+
* `type: 'color'`. See `defineFieldType`.
|
|
1781
|
+
*/
|
|
1782
|
+
readonly fieldTypes?: readonly FieldTypeDefinition[];
|
|
1624
1783
|
/**
|
|
1625
1784
|
* API keys for scripts and other apps (`Authorization: Bearer ecms_…`), managed in the admin
|
|
1626
1785
|
* under Settings. A key acts as its owner, limited to the collections and operations it lists.
|
|
@@ -1647,6 +1806,7 @@ interface ResolvedConfig extends Omit<Config, 'cors' | 'localization' | 'routes'
|
|
|
1647
1806
|
readonly globals: readonly GlobalConfig[];
|
|
1648
1807
|
readonly endpoints: readonly Endpoint[];
|
|
1649
1808
|
readonly commands: readonly CliCommand[];
|
|
1809
|
+
readonly fieldTypes: readonly FieldTypeDefinition[];
|
|
1650
1810
|
}
|
|
1651
1811
|
/**
|
|
1652
1812
|
* Declares the Easy CMS config. Returns it unchanged, keeping literal types
|
|
@@ -1720,58 +1880,6 @@ declare function copyDatabase(source: Database, target: Database, options?: {
|
|
|
1720
1880
|
onTable?: (progress: CopyProgress) => void;
|
|
1721
1881
|
}): Promise<CopyResult>;
|
|
1722
1882
|
|
|
1723
|
-
interface ConfigIssue {
|
|
1724
|
-
/** Where the problem is, e.g. `collections.posts.fields.slug.from`. */
|
|
1725
|
-
readonly path: string;
|
|
1726
|
-
readonly message: string;
|
|
1727
|
-
/** How to fix it. */
|
|
1728
|
-
readonly hint?: string;
|
|
1729
|
-
}
|
|
1730
|
-
declare class ConfigError extends Error {
|
|
1731
|
-
readonly issues: readonly ConfigIssue[];
|
|
1732
|
-
constructor(issues: readonly ConfigIssue[]);
|
|
1733
|
-
static format(issues: readonly ConfigIssue[]): string;
|
|
1734
|
-
}
|
|
1735
|
-
/** Base class for errors that map to an HTTP status in the REST API. */
|
|
1736
|
-
declare class EasyCMSError extends Error {
|
|
1737
|
-
readonly status: number;
|
|
1738
|
-
constructor(message: string, status: number);
|
|
1739
|
-
}
|
|
1740
|
-
interface FieldError {
|
|
1741
|
-
/** Dotted path, e.g. `links.0.url`. */
|
|
1742
|
-
readonly field: string;
|
|
1743
|
-
readonly message: string;
|
|
1744
|
-
}
|
|
1745
|
-
declare class ValidationError extends EasyCMSError {
|
|
1746
|
-
readonly errors: readonly FieldError[];
|
|
1747
|
-
constructor(collection: string, errors: readonly FieldError[]);
|
|
1748
|
-
}
|
|
1749
|
-
declare class NotFoundError extends EasyCMSError {
|
|
1750
|
-
constructor(collection: string, id: unknown);
|
|
1751
|
-
}
|
|
1752
|
-
/** A bad `where`, `sort` or unknown collection/global. */
|
|
1753
|
-
declare class QueryError extends EasyCMSError {
|
|
1754
|
-
constructor(message: string);
|
|
1755
|
-
}
|
|
1756
|
-
/** The database schema does not match the config (pending or missing migrations). */
|
|
1757
|
-
declare class SchemaError extends EasyCMSError {
|
|
1758
|
-
constructor(message: string);
|
|
1759
|
-
}
|
|
1760
|
-
/** Not logged in, or the session is invalid. */
|
|
1761
|
-
declare class UnauthorizedError extends EasyCMSError {
|
|
1762
|
-
constructor(message?: string);
|
|
1763
|
-
}
|
|
1764
|
-
/** Logged in, but not allowed to do this. */
|
|
1765
|
-
declare class ForbiddenError extends EasyCMSError {
|
|
1766
|
-
constructor(message?: string);
|
|
1767
|
-
}
|
|
1768
|
-
declare class TooManyRequestsError extends EasyCMSError {
|
|
1769
|
-
constructor(message: string);
|
|
1770
|
-
}
|
|
1771
|
-
declare class PayloadTooLargeError extends EasyCMSError {
|
|
1772
|
-
constructor(message: string);
|
|
1773
|
-
}
|
|
1774
|
-
|
|
1775
1883
|
type Localization = Required<LocalizationConfig>;
|
|
1776
1884
|
|
|
1777
1885
|
type Data = Record<string, unknown>;
|
|
@@ -1934,11 +2042,14 @@ interface AdminField {
|
|
|
1934
2042
|
localized?: boolean;
|
|
1935
2043
|
/** Shown in the edit page's side panel. */
|
|
1936
2044
|
position?: 'sidebar';
|
|
1937
|
-
/** Components from admin modules: instead of the input,
|
|
2045
|
+
/** Components from admin modules: instead of the input, below the field, and in lists. */
|
|
1938
2046
|
admin?: {
|
|
1939
2047
|
component?: AdminComponentRef;
|
|
1940
2048
|
after?: AdminComponentRef[];
|
|
2049
|
+
cell?: AdminComponentRef;
|
|
1941
2050
|
};
|
|
2051
|
+
/** A field of an added type (`fieldTypes`), e.g. `color`; `type` is the base it's stored as. */
|
|
2052
|
+
customType?: string;
|
|
1942
2053
|
}
|
|
1943
2054
|
interface AdminCollection {
|
|
1944
2055
|
slug: string;
|
|
@@ -1990,6 +2101,20 @@ interface AdminGlobal {
|
|
|
1990
2101
|
update: boolean;
|
|
1991
2102
|
};
|
|
1992
2103
|
}
|
|
2104
|
+
/** A page of its own in the admin (`admin.pages`) that this user may open. */
|
|
2105
|
+
interface AdminPageRef {
|
|
2106
|
+
path: string;
|
|
2107
|
+
label: Label;
|
|
2108
|
+
icon?: string;
|
|
2109
|
+
/** Where it is listed in the menu; `false`: not listed. */
|
|
2110
|
+
group: 'content' | 'settings' | false;
|
|
2111
|
+
component: AdminComponentRef;
|
|
2112
|
+
}
|
|
2113
|
+
/** A dashboard panel (`admin.dashboard`) this user may see. */
|
|
2114
|
+
interface AdminWidgetRef {
|
|
2115
|
+
component: AdminComponentRef;
|
|
2116
|
+
width: 'half' | 'full';
|
|
2117
|
+
}
|
|
1993
2118
|
interface AdminSchema {
|
|
1994
2119
|
locale: AdminLocale;
|
|
1995
2120
|
/** Admins can email users links to set their password (needs `email` and `serverURL`). */
|
|
@@ -2005,6 +2130,10 @@ interface AdminSchema {
|
|
|
2005
2130
|
globals: AdminGlobal[];
|
|
2006
2131
|
/** URLs under the API of the admin modules to load (`admin.modules`). */
|
|
2007
2132
|
modules: string[];
|
|
2133
|
+
/** Pages this user may open (`admin.pages`). */
|
|
2134
|
+
pages: AdminPageRef[];
|
|
2135
|
+
/** Dashboard panels this user may see (`admin.dashboard`). */
|
|
2136
|
+
dashboard: AdminWidgetRef[];
|
|
2008
2137
|
}
|
|
2009
2138
|
|
|
2010
2139
|
declare const SESSION_COOKIE = "ecms-session";
|
|
@@ -2042,4 +2171,4 @@ declare const BUILTIN_COLLECTIONS: readonly ["users", "media"];
|
|
|
2042
2171
|
/** Checks a config and returns every problem found. An empty array means it is valid. */
|
|
2043
2172
|
declare function validateConfig(config: Config): ConfigIssue[];
|
|
2044
2173
|
|
|
2045
|
-
export { ADMIN_ICONS, API_KEYS, API_KEY_GLOBAL_OPERATIONS, API_KEY_OPERATIONS, type Access, type AccessArgs, type AccessOptions, type AdminBrand, type AdminCollection, type AdminComponent, type AdminComponentRef, type AdminConfig, type AdminField, type AdminGlobal, type AdminIcon, type AdminLocale, type AdminSchema, type AfterChangeHook, type AfterDeleteHook, type AfterReadHook, type ApiKeyContext, type ApiKeyGlobalOperation, type ApiKeyOperation, type ApiKeyPermissions, type ArrayField, Auth, type AuthConfig, type AuthUser, BUILTIN_COLLECTIONS, type BeforeChangeHook, type BeforeDeleteHook, type BeforeValidateHook, type Block, type BlocksField, type BooleanField, type BuiltinApiKeysCollection, type BuiltinMediaCollection, type BuiltinUsersCollection, type BundleHints, CONFIG_FILE_NAMES, CSRF_COOKIE, CSRF_HEADER, type CliCommand, type CollectionAccess, type CollectionAdmin, type CollectionConfig, type CollectionDocument, type CollectionHooks, type CollectionListAdmin, type CollectionSlug, type Config, ConfigError, type ConfigIssue, type ContainerAdmin, type CopyProgress, type CopyResult, type CreateEasyCMSOptions, type CreateInput, type CreateMigrationResult, DEFAULT_ADMIN_PATH, DEFAULT_API_PATH, DEFAULT_DEPTH, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_VERSIONS, DEFAULT_PASSWORD_EMAILS, DEFAULT_TOKEN_EXPIRATION, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DatabaseTransfer, type DateField, type DepthOptions, type DocumentOf, EXTENSIONS, EasyCMS, EasyCMSError, type EmailAdapter, type EmailField, type EmailMessage, type Endpoint, type EndpointRequest, FIELD_TYPES, type Field, type FieldAccess, type FieldAccessFn, type FieldAdmin, type FieldError, type FieldType, type FieldValidate, type FieldValidateContext, type FieldValue, type FieldsInput, type FieldsValue, type FilterOptions, type FilterOptionsArgs, type FindArgs, type FindOptions, ForbiddenError, type GlobalAccess, type GlobalConfig, type GlobalDocument, type GlobalHooks, type GlobalInput, type GlobalSlug, type GroupField, type ID, INTERNAL_COLLECTIONS, type ImageSize, type InferCollection, type InferGlobal, type JsonField, type Label, type LivePreview, type LoadConfigOptions, type LocalStorageOptions, type LocalizationConfig, type Logger, type LoginArgs, MAX_DEPTH, MEDIA, MIN_PASSWORD_LENGTH, MIN_SECRET_LENGTH, type MediaDocument, type MediaSize, type MigrationInfo, NotFoundError, type NumberField, type Operation, type PaginatedDocs, type PasswordEmail, type PasswordEmailArgs, type PasswordEmailFn, type PasswordLinkOptions, PayloadTooLargeError, type Plugin, type PluginTypes, type PreviewTarget, type PreviewURL, QueryError, type RawDocument, type ReadOptions, type RelationshipField, type ResolvedConfig, type RestHandler, type RestHandlerOptions, type RichTextDocument, type RichTextField, type Role, type RoutesConfig, SESSION_COOKIE, type ScheduledJob, SchemaError, type SchemaMode, type SelectField, type SelectOption, type Session, type SlugField, type SlugOf, type StorageAdapter, type StoredFile, type TextField, type TextareaField, TooManyRequestsError, type TypedPlugin, UnauthorizedError, type UpdateInput, type UpdateOptions, type UploadConfig, type UploadField, ValidationError, type Version, type VersionSummary, type VersionsConfig, WEBHOOK_EVENTS, type WebhookConfig, type WebhookEvent, type WebhookPayload, type Where, type WhereOperators, anyone, applyDefaults, applyPlugins, configSignature, consoleEmail, consoleLogger, copyDatabase, createEasyCMS, createRestHandler, createRootEndpointHandler, defineConfig, definePlugin, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isHasMany, isLoggedIn, keyAllows, loadConfig, localStorage, mergeForUpdate, mimeAllowed, mimeAllowedBy, parseId, populate, readCookie, resolveAdminModule, resolveConfig, rowFields, silentLogger, singularize, slugify, sniffMimeType, validateConfig, validateFields, verifyPassword };
|
|
2174
|
+
export { ADMIN_ICONS, API_KEYS, API_KEY_GLOBAL_OPERATIONS, API_KEY_OPERATIONS, type Access, type AccessArgs, type AccessOptions, type AdminBrand, type AdminCollection, type AdminComponent, type AdminComponentRef, type AdminConfig, type AdminField, type AdminGlobal, type AdminIcon, type AdminLocale, type AdminPage, type AdminPageRef, type AdminSchema, type AdminViewAccess, type AdminWidgetRef, type AfterChangeHook, type AfterDeleteHook, type AfterReadHook, type ApiKeyContext, type ApiKeyGlobalOperation, type ApiKeyOperation, type ApiKeyPermissions, type ArrayField, Auth, type AuthConfig, type AuthUser, BUILTIN_COLLECTIONS, type BeforeChangeHook, type BeforeDeleteHook, type BeforeValidateHook, type Block, type BlocksField, type BooleanField, type BuiltinApiKeysCollection, type BuiltinMediaCollection, type BuiltinUsersCollection, type BundleHints, CONFIG_FILE_NAMES, CSRF_COOKIE, CSRF_HEADER, type CliCommand, type CollectionAccess, type CollectionAdmin, type CollectionConfig, type CollectionDocument, type CollectionHooks, type CollectionListAdmin, type CollectionSlug, type Config, ConfigError, type ConfigIssue, type ContainerAdmin, type CopyProgress, type CopyResult, type CreateEasyCMSOptions, type CreateInput, type CreateMigrationResult, type CustomField, type CustomFieldTypes, DEFAULT_ADMIN_PATH, DEFAULT_API_PATH, DEFAULT_DEPTH, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_VERSIONS, DEFAULT_PASSWORD_EMAILS, DEFAULT_TOKEN_EXPIRATION, type DashboardWidget, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DatabaseTransfer, type DateField, type DepthOptions, type DocumentOf, EXTENSIONS, EasyCMS, EasyCMSError, type EmailAdapter, type EmailField, type EmailMessage, type Endpoint, type EndpointRequest, FIELD_TYPES, FIELD_TYPE_BASES, type Field, type FieldAccess, type FieldAccessFn, type FieldAdmin, type FieldError, type FieldType, type FieldTypeBase, type FieldTypeDefinition, type FieldTypeValidateContext, type FieldValidate, type FieldValidateContext, type FieldValue, type FieldsInput, type FieldsValue, type FilterOptions, type FilterOptionsArgs, type FindArgs, type FindOptions, ForbiddenError, type GlobalAccess, type GlobalConfig, type GlobalDocument, type GlobalHooks, type GlobalInput, type GlobalSlug, type GroupField, type ID, INTERNAL_COLLECTIONS, type ImageSize, type InferCollection, type InferGlobal, type JsonField, type Label, type LivePreview, type LoadConfigOptions, type LocalStorageOptions, type LocalizationConfig, type Logger, type LoginArgs, MAX_DEPTH, MEDIA, MIN_PASSWORD_LENGTH, MIN_SECRET_LENGTH, type MediaDocument, type MediaSize, type MigrationInfo, NotFoundError, type NumberField, type Operation, type PaginatedDocs, type PasswordEmail, type PasswordEmailArgs, type PasswordEmailFn, type PasswordLinkOptions, PayloadTooLargeError, type Plugin, type PluginTypes, type PreviewTarget, type PreviewURL, QueryError, type RawDocument, type ReadOptions, type RelationshipField, type ResolvedConfig, type RestHandler, type RestHandlerOptions, type RichTextDocument, type RichTextField, type Role, type RoutesConfig, SESSION_COOKIE, type ScheduledJob, SchemaError, type SchemaMode, type SelectField, type SelectOption, type Session, type SlugField, type SlugOf, type StorageAdapter, type StoredFile, type TextField, type TextareaField, TooManyRequestsError, type TypedPlugin, UnauthorizedError, type UpdateInput, type UpdateOptions, type UploadConfig, type UploadField, ValidationError, type Version, type VersionSummary, type VersionsConfig, WEBHOOK_EVENTS, type WebhookConfig, type WebhookEvent, type WebhookPayload, type Where, type WhereOperators, anyone, applyDefaults, applyPlugins, configSignature, consoleEmail, consoleLogger, copyDatabase, createEasyCMS, createRestHandler, createRootEndpointHandler, defineConfig, defineFieldType, definePlugin, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isHasMany, isLoggedIn, keyAllows, loadConfig, localStorage, mergeForUpdate, mimeAllowed, mimeAllowedBy, parseId, populate, readCookie, resolveAdminModule, resolveConfig, rowFields, silentLogger, singularize, slugify, sniffMimeType, validateConfig, validateFields, verifyPassword };
|
package/dist/index.js
CHANGED
|
@@ -1138,7 +1138,9 @@ var ADMIN_ICONS = [
|
|
|
1138
1138
|
"award",
|
|
1139
1139
|
"ticket",
|
|
1140
1140
|
"camera",
|
|
1141
|
-
"key"
|
|
1141
|
+
"key",
|
|
1142
|
+
"chart-column",
|
|
1143
|
+
"chart-line"
|
|
1142
1144
|
];
|
|
1143
1145
|
function definePlugin(plugin) {
|
|
1144
1146
|
return plugin;
|
|
@@ -1669,6 +1671,126 @@ var Mailer = class {
|
|
|
1669
1671
|
}
|
|
1670
1672
|
};
|
|
1671
1673
|
|
|
1674
|
+
// src/field-types.ts
|
|
1675
|
+
var FIELD_TYPE_BASES = [
|
|
1676
|
+
"text",
|
|
1677
|
+
"textarea",
|
|
1678
|
+
"email",
|
|
1679
|
+
"number",
|
|
1680
|
+
"boolean",
|
|
1681
|
+
"date",
|
|
1682
|
+
"json"
|
|
1683
|
+
];
|
|
1684
|
+
function defineFieldType(definition) {
|
|
1685
|
+
return definition;
|
|
1686
|
+
}
|
|
1687
|
+
var NAME = /^[a-z][a-zA-Z0-9]*$/;
|
|
1688
|
+
function fieldTypeModules(config) {
|
|
1689
|
+
const list = config.fieldTypes;
|
|
1690
|
+
if (!Array.isArray(list)) return [];
|
|
1691
|
+
return list.map((t) => t?.admin?.module).filter((m) => typeof m === "string");
|
|
1692
|
+
}
|
|
1693
|
+
function applyFieldTypes(config) {
|
|
1694
|
+
const issues = [];
|
|
1695
|
+
const list = config.fieldTypes;
|
|
1696
|
+
if (list === void 0) return { config, issues };
|
|
1697
|
+
if (!Array.isArray(list)) {
|
|
1698
|
+
issues.push({ path: "fieldTypes", message: "must be an array of field types" });
|
|
1699
|
+
return { config, issues };
|
|
1700
|
+
}
|
|
1701
|
+
const types = /* @__PURE__ */ new Map();
|
|
1702
|
+
for (const [i, type] of list.entries()) {
|
|
1703
|
+
const path = `fieldTypes[${i}]`;
|
|
1704
|
+
if (!type || typeof type.name !== "string" || !NAME.test(type.name)) {
|
|
1705
|
+
issues.push({ path: `${path}.name`, message: 'must be a lowercase name like "color"' });
|
|
1706
|
+
} else if (FIELD_TYPES.includes(type.name)) {
|
|
1707
|
+
issues.push({ path: `${path}.name`, message: `"${type.name}" is a built-in field type` });
|
|
1708
|
+
} else if (types.has(type.name)) {
|
|
1709
|
+
issues.push({ path: `${path}.name`, message: `"${type.name}" is listed twice` });
|
|
1710
|
+
} else if (!FIELD_TYPE_BASES.includes(type.base)) {
|
|
1711
|
+
issues.push({
|
|
1712
|
+
path: `${path}.base`,
|
|
1713
|
+
message: `must be one of ${FIELD_TYPE_BASES.join(", ")}`
|
|
1714
|
+
});
|
|
1715
|
+
} else {
|
|
1716
|
+
types.set(type.name, type);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
if (types.size === 0) return { config, issues };
|
|
1720
|
+
const component = (tag, field, props) => {
|
|
1721
|
+
if (!tag) return void 0;
|
|
1722
|
+
const options = Object.fromEntries(
|
|
1723
|
+
(props ?? []).filter((key2) => field[key2] !== void 0).map((key2) => [key2, field[key2]])
|
|
1724
|
+
);
|
|
1725
|
+
return Object.keys(options).length ? { tag, props: options } : tag;
|
|
1726
|
+
};
|
|
1727
|
+
const convert = (fields, path) => fields.map((field, i) => {
|
|
1728
|
+
const here = `${path}[${i}]`;
|
|
1729
|
+
const own = field;
|
|
1730
|
+
const type = types.get(own.type);
|
|
1731
|
+
if (!type) {
|
|
1732
|
+
if ((field.type === "group" || field.type === "array") && Array.isArray(field.fields))
|
|
1733
|
+
return { ...field, fields: convert(field.fields, `${here}.fields`) };
|
|
1734
|
+
if (field.type === "blocks" && Array.isArray(field.blocks))
|
|
1735
|
+
return {
|
|
1736
|
+
...field,
|
|
1737
|
+
blocks: field.blocks.map((block, b) => ({
|
|
1738
|
+
...block,
|
|
1739
|
+
fields: convert(block.fields, `${here}.blocks[${b}].fields`)
|
|
1740
|
+
}))
|
|
1741
|
+
};
|
|
1742
|
+
return field;
|
|
1743
|
+
}
|
|
1744
|
+
const problem = type.checkOptions?.(own);
|
|
1745
|
+
if (problem) issues.push({ path: here, message: problem });
|
|
1746
|
+
const ownValidate = own.validate;
|
|
1747
|
+
const check = type.validate;
|
|
1748
|
+
const admin = own.admin ?? {};
|
|
1749
|
+
const input = admin.component ?? component(type.admin?.component, own, type.admin?.props);
|
|
1750
|
+
const cell = admin.cell ?? component(type.admin?.cell, own, type.admin?.props);
|
|
1751
|
+
return {
|
|
1752
|
+
...own,
|
|
1753
|
+
type: type.base,
|
|
1754
|
+
customType: type.name,
|
|
1755
|
+
...check || ownValidate ? {
|
|
1756
|
+
validate: async (value, ctx) => {
|
|
1757
|
+
const empty = value === null || value === void 0 || value === "";
|
|
1758
|
+
if (check && !empty) {
|
|
1759
|
+
const result = await check(value, { ...ctx, field: own });
|
|
1760
|
+
if (result !== true) return result;
|
|
1761
|
+
}
|
|
1762
|
+
return ownValidate ? ownValidate(value, ctx) : true;
|
|
1763
|
+
}
|
|
1764
|
+
} : {},
|
|
1765
|
+
...input || cell ? {
|
|
1766
|
+
admin: {
|
|
1767
|
+
...admin,
|
|
1768
|
+
...input ? { component: input } : {},
|
|
1769
|
+
...cell ? { cell } : {}
|
|
1770
|
+
}
|
|
1771
|
+
} : {}
|
|
1772
|
+
};
|
|
1773
|
+
});
|
|
1774
|
+
return {
|
|
1775
|
+
config: {
|
|
1776
|
+
...config,
|
|
1777
|
+
...config.collections ? {
|
|
1778
|
+
collections: config.collections.map((c, i) => ({
|
|
1779
|
+
...c,
|
|
1780
|
+
fields: convert(c.fields, `collections[${i}].fields`)
|
|
1781
|
+
}))
|
|
1782
|
+
} : {},
|
|
1783
|
+
...config.globals ? {
|
|
1784
|
+
globals: config.globals.map((g, i) => ({
|
|
1785
|
+
...g,
|
|
1786
|
+
fields: convert(g.fields, `globals[${i}].fields`)
|
|
1787
|
+
}))
|
|
1788
|
+
} : {}
|
|
1789
|
+
},
|
|
1790
|
+
issues
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1672
1794
|
// src/load-config.ts
|
|
1673
1795
|
import { existsSync } from "fs";
|
|
1674
1796
|
import { resolve } from "path";
|
|
@@ -1874,6 +1996,7 @@ function validateConfig(config) {
|
|
|
1874
1996
|
add("db", "is required", "pass a database adapter, e.g. db: sqlite({ url: 'file:./cms.db' })");
|
|
1875
1997
|
}
|
|
1876
1998
|
validateAdmin(config, add);
|
|
1999
|
+
validateAdminViews(config, add);
|
|
1877
2000
|
validateUpload(config, add);
|
|
1878
2001
|
validateAuth(config, add);
|
|
1879
2002
|
validateCors(config.cors, add);
|
|
@@ -1994,6 +2117,83 @@ function validateAdmin(config, add) {
|
|
|
1994
2117
|
add("admin.brand.color", `must be a hex color like "#0f766e" (got ${JSON.stringify(color)})`);
|
|
1995
2118
|
}
|
|
1996
2119
|
}
|
|
2120
|
+
var PAGE_PATH = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
2121
|
+
function validateAdminViews(config, add) {
|
|
2122
|
+
const access = (value, path) => {
|
|
2123
|
+
if (value !== void 0 && typeof value !== "function")
|
|
2124
|
+
add(
|
|
2125
|
+
`${path}.access`,
|
|
2126
|
+
"must be a function",
|
|
2127
|
+
'e.g. access: ({ user }) => user.role === "admin"'
|
|
2128
|
+
);
|
|
2129
|
+
};
|
|
2130
|
+
const pages2 = config.admin?.pages;
|
|
2131
|
+
if (pages2 !== void 0) {
|
|
2132
|
+
if (!Array.isArray(pages2)) {
|
|
2133
|
+
add(
|
|
2134
|
+
"admin.pages",
|
|
2135
|
+
"must be an array",
|
|
2136
|
+
"e.g. pages: [{ path: 'stats', label: 'Stats', component: 'ecms-stats' }]"
|
|
2137
|
+
);
|
|
2138
|
+
} else {
|
|
2139
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2140
|
+
pages2.forEach((page, i) => {
|
|
2141
|
+
const path = `admin.pages[${i}]`;
|
|
2142
|
+
if (typeof page !== "object" || page === null) {
|
|
2143
|
+
add(path, "must be an object");
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
const p = page;
|
|
2147
|
+
if (typeof p.path !== "string" || !PAGE_PATH.test(p.path)) {
|
|
2148
|
+
add(
|
|
2149
|
+
`${path}.path`,
|
|
2150
|
+
`must be lowercase letters, digits and "-" (got ${JSON.stringify(p.path)})`,
|
|
2151
|
+
"e.g. path: 'forms-overview'"
|
|
2152
|
+
);
|
|
2153
|
+
} else if (seen.has(p.path)) {
|
|
2154
|
+
add(`${path}.path`, `"${p.path}" is used by another page`);
|
|
2155
|
+
} else {
|
|
2156
|
+
seen.add(p.path);
|
|
2157
|
+
}
|
|
2158
|
+
validateComponent(p.component, `${path}.component`, add);
|
|
2159
|
+
const label = p.label;
|
|
2160
|
+
if (!(typeof label === "string" && label.trim() !== "") && !(typeof label === "object" && label !== null && !Array.isArray(label)))
|
|
2161
|
+
add(
|
|
2162
|
+
`${path}.label`,
|
|
2163
|
+
"must be a string or { en, th }",
|
|
2164
|
+
"e.g. label: { en: 'Stats', th: '\u0E2A\u0E16\u0E34\u0E15\u0E34' }"
|
|
2165
|
+
);
|
|
2166
|
+
validateIcon(p, path, add);
|
|
2167
|
+
if (p.group !== void 0 && p.group !== "content" && p.group !== "settings" && p.group !== false)
|
|
2168
|
+
add(`${path}.group`, 'must be "content", "settings" or false');
|
|
2169
|
+
access(p.access, path);
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
const widgets2 = config.admin?.dashboard;
|
|
2174
|
+
if (widgets2 !== void 0) {
|
|
2175
|
+
if (!Array.isArray(widgets2)) {
|
|
2176
|
+
add(
|
|
2177
|
+
"admin.dashboard",
|
|
2178
|
+
"must be an array",
|
|
2179
|
+
"e.g. dashboard: [{ component: 'ecms-stats-widget' }]"
|
|
2180
|
+
);
|
|
2181
|
+
} else {
|
|
2182
|
+
widgets2.forEach((widget, i) => {
|
|
2183
|
+
const path = `admin.dashboard[${i}]`;
|
|
2184
|
+
if (typeof widget !== "object" || widget === null) {
|
|
2185
|
+
add(path, "must be an object");
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
const w = widget;
|
|
2189
|
+
validateComponent(w.component, `${path}.component`, add);
|
|
2190
|
+
if (w.width !== void 0 && w.width !== "half" && w.width !== "full")
|
|
2191
|
+
add(`${path}.width`, 'must be "half" or "full"');
|
|
2192
|
+
access(w.access, path);
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
1997
2197
|
function validateIcon(container, path, add) {
|
|
1998
2198
|
const icon = container.icon;
|
|
1999
2199
|
if (icon === void 0 || ADMIN_ICONS.includes(icon)) return;
|
|
@@ -2326,9 +2526,10 @@ function validateField(field, path, siblings, collectionSlugs, add) {
|
|
|
2326
2526
|
if (typeof admin !== "object" || admin === null) {
|
|
2327
2527
|
add(`${path}.admin`, "must be an object", "e.g. admin: { component: 'ecms-color-picker' }");
|
|
2328
2528
|
} else {
|
|
2329
|
-
const { component, after } = admin;
|
|
2529
|
+
const { component, after, cell } = admin;
|
|
2330
2530
|
if (component !== void 0) validateComponent(component, `${path}.admin.component`, add);
|
|
2331
2531
|
if (after !== void 0) validateComponents(after, `${path}.admin.after`, add);
|
|
2532
|
+
if (cell !== void 0) validateComponent(cell, `${path}.admin.cell`, add);
|
|
2332
2533
|
}
|
|
2333
2534
|
}
|
|
2334
2535
|
switch (field.type) {
|
|
@@ -2593,6 +2794,15 @@ async function applyPlugins(input) {
|
|
|
2593
2794
|
if (typeof plugin !== "function") break;
|
|
2594
2795
|
config = await plugin(config);
|
|
2595
2796
|
}
|
|
2797
|
+
const modules = fieldTypeModules(config);
|
|
2798
|
+
if (modules.length > 0)
|
|
2799
|
+
config = {
|
|
2800
|
+
...config,
|
|
2801
|
+
admin: {
|
|
2802
|
+
...config.admin,
|
|
2803
|
+
modules: [.../* @__PURE__ */ new Set([...config.admin?.modules ?? [], ...modules])]
|
|
2804
|
+
}
|
|
2805
|
+
};
|
|
2596
2806
|
return config;
|
|
2597
2807
|
}
|
|
2598
2808
|
async function resolveConfig(input) {
|
|
@@ -2601,7 +2811,9 @@ async function resolveConfig(input) {
|
|
|
2601
2811
|
if (typeof config === "object" && config !== null && Array.isArray(config.collections ?? [])) {
|
|
2602
2812
|
config = withMedia(withUsers(config));
|
|
2603
2813
|
}
|
|
2604
|
-
const
|
|
2814
|
+
const applied = applyFieldTypes(config);
|
|
2815
|
+
config = applied.config;
|
|
2816
|
+
const issues = [...applied.issues, ...validateConfig(config)];
|
|
2605
2817
|
if (issues.length > 0) throw new ConfigError(issues);
|
|
2606
2818
|
const { plugins: _plugins, ...rest } = config;
|
|
2607
2819
|
const result = {
|
|
@@ -2619,7 +2831,9 @@ async function resolveConfig(input) {
|
|
|
2619
2831
|
brand: config.admin?.brand ?? {},
|
|
2620
2832
|
siteUrl: config.admin?.siteUrl ?? "",
|
|
2621
2833
|
menu: config.admin?.menu ?? [],
|
|
2622
|
-
modules: config.admin?.modules ?? []
|
|
2834
|
+
modules: config.admin?.modules ?? [],
|
|
2835
|
+
pages: config.admin?.pages ?? [],
|
|
2836
|
+
dashboard: config.admin?.dashboard ?? []
|
|
2623
2837
|
},
|
|
2624
2838
|
upload: {
|
|
2625
2839
|
dir: config.upload?.dir ?? "uploads",
|
|
@@ -2649,7 +2863,8 @@ async function resolveConfig(input) {
|
|
|
2649
2863
|
],
|
|
2650
2864
|
globals: config.globals ?? [],
|
|
2651
2865
|
endpoints: config.endpoints ?? [],
|
|
2652
|
-
commands: config.commands ?? []
|
|
2866
|
+
commands: config.commands ?? [],
|
|
2867
|
+
fieldTypes: config.fieldTypes ?? []
|
|
2653
2868
|
};
|
|
2654
2869
|
resolved.add(result);
|
|
2655
2870
|
return result;
|
|
@@ -4740,12 +4955,14 @@ async function serializeFields(fields, update, localized) {
|
|
|
4740
4955
|
if (!await update.allows(field)) f.readOnly = true;
|
|
4741
4956
|
if (field.localized && localized) f.localized = true;
|
|
4742
4957
|
if (field.position === "sidebar") f.position = "sidebar";
|
|
4743
|
-
if (field.admin?.component || field.admin?.after?.length) {
|
|
4958
|
+
if (field.admin?.component || field.admin?.after?.length || field.admin?.cell) {
|
|
4744
4959
|
f.admin = {
|
|
4745
4960
|
...field.admin.component ? { component: componentRef(field.admin.component) } : {},
|
|
4746
|
-
...field.admin.after?.length ? { after: field.admin.after.map(componentRef) } : {}
|
|
4961
|
+
...field.admin.after?.length ? { after: field.admin.after.map(componentRef) } : {},
|
|
4962
|
+
...field.admin.cell ? { cell: componentRef(field.admin.cell) } : {}
|
|
4747
4963
|
};
|
|
4748
4964
|
}
|
|
4965
|
+
if (field.customType) f.customType = field.customType;
|
|
4749
4966
|
switch (field.type) {
|
|
4750
4967
|
case "text":
|
|
4751
4968
|
case "textarea":
|
|
@@ -4866,9 +5083,41 @@ async function adminSchema(cms, user, origin) {
|
|
|
4866
5083
|
localization: localization ? { locales: [...localization.locales], defaultLocale: localization.defaultLocale } : null,
|
|
4867
5084
|
collections: await Promise.all(collections.map((c) => collection(c, user, localized))),
|
|
4868
5085
|
globals: await Promise.all(cms.config.globals.map((g) => global(g, user, localized))),
|
|
4869
|
-
modules: adminModuleUrls(cms)
|
|
5086
|
+
modules: adminModuleUrls(cms),
|
|
5087
|
+
pages: await pages(cms, user),
|
|
5088
|
+
dashboard: await widgets(cms, user)
|
|
4870
5089
|
};
|
|
4871
5090
|
}
|
|
5091
|
+
async function shown(access, user) {
|
|
5092
|
+
if (!access) return true;
|
|
5093
|
+
try {
|
|
5094
|
+
return await access({ user }) === true;
|
|
5095
|
+
} catch {
|
|
5096
|
+
return false;
|
|
5097
|
+
}
|
|
5098
|
+
}
|
|
5099
|
+
async function pages(cms, user) {
|
|
5100
|
+
const out = [];
|
|
5101
|
+
for (const page of cms.config.admin.pages) {
|
|
5102
|
+
if (!await shown(page.access, user)) continue;
|
|
5103
|
+
out.push({
|
|
5104
|
+
path: page.path,
|
|
5105
|
+
label: page.label,
|
|
5106
|
+
...page.icon ? { icon: page.icon } : {},
|
|
5107
|
+
group: page.group ?? "content",
|
|
5108
|
+
component: componentRef(page.component)
|
|
5109
|
+
});
|
|
5110
|
+
}
|
|
5111
|
+
return out;
|
|
5112
|
+
}
|
|
5113
|
+
async function widgets(cms, user) {
|
|
5114
|
+
const out = [];
|
|
5115
|
+
for (const widget of cms.config.admin.dashboard) {
|
|
5116
|
+
if (!await shown(widget.access, user)) continue;
|
|
5117
|
+
out.push({ component: componentRef(widget.component), width: widget.width ?? "half" });
|
|
5118
|
+
}
|
|
5119
|
+
return out;
|
|
5120
|
+
}
|
|
4872
5121
|
|
|
4873
5122
|
// src/rest/filter-options.ts
|
|
4874
5123
|
function fieldAt(fields, path) {
|
|
@@ -5680,9 +5929,14 @@ function names(config) {
|
|
|
5680
5929
|
}
|
|
5681
5930
|
const globals = /* @__PURE__ */ new Map();
|
|
5682
5931
|
for (const g of config.globals) globals.set(g.slug, claim(pascal(g.slug), "Global"));
|
|
5683
|
-
|
|
5932
|
+
const custom = new Map(
|
|
5933
|
+
(config.fieldTypes ?? []).filter((t) => t.typescript).map((t) => [t.name, t.typescript])
|
|
5934
|
+
);
|
|
5935
|
+
return { collections, globals, custom };
|
|
5684
5936
|
}
|
|
5685
5937
|
function fieldType(field, n, indent) {
|
|
5938
|
+
const custom = field.customType ? n.custom.get(field.customType) : void 0;
|
|
5939
|
+
if (custom) return custom;
|
|
5686
5940
|
switch (field.type) {
|
|
5687
5941
|
case "text":
|
|
5688
5942
|
case "textarea":
|
|
@@ -5836,6 +6090,7 @@ export {
|
|
|
5836
6090
|
EasyCMS,
|
|
5837
6091
|
EasyCMSError,
|
|
5838
6092
|
FIELD_TYPES,
|
|
6093
|
+
FIELD_TYPE_BASES,
|
|
5839
6094
|
ForbiddenError,
|
|
5840
6095
|
INTERNAL_COLLECTIONS,
|
|
5841
6096
|
MAX_DEPTH,
|
|
@@ -5862,6 +6117,7 @@ export {
|
|
|
5862
6117
|
createRestHandler,
|
|
5863
6118
|
createRootEndpointHandler,
|
|
5864
6119
|
defineConfig,
|
|
6120
|
+
defineFieldType,
|
|
5865
6121
|
definePlugin,
|
|
5866
6122
|
fillMissing,
|
|
5867
6123
|
findConfigFile,
|