@easy-cms/core 0.10.0 → 0.12.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 +56 -1
- package/dist/index.js +155 -2
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -211,6 +211,8 @@ interface BaseField<TType extends string, TValue> {
|
|
|
211
211
|
* hasMany fields: localize the fields inside a group or array instead.
|
|
212
212
|
*/
|
|
213
213
|
readonly localized?: boolean;
|
|
214
|
+
/** `sidebar`: shown in the edit page's side panel instead of the main form (top-level fields). */
|
|
215
|
+
readonly position?: 'sidebar';
|
|
214
216
|
}
|
|
215
217
|
interface TextField extends BaseField<'text', string> {
|
|
216
218
|
readonly minLength?: number;
|
|
@@ -434,6 +436,14 @@ interface CollectionConfig {
|
|
|
434
436
|
readonly singular?: Label;
|
|
435
437
|
readonly plural?: Label;
|
|
436
438
|
};
|
|
439
|
+
/** Icon in the admin menu. Default `file-text`. */
|
|
440
|
+
readonly icon?: AdminIcon;
|
|
441
|
+
/**
|
|
442
|
+
* How the admin opens documents from the list: `drawer` slides a panel over the list, handy
|
|
443
|
+
* for small collections (categories, tags). Default `page`. Collections with drafts, versions
|
|
444
|
+
* or live preview always use the page.
|
|
445
|
+
*/
|
|
446
|
+
readonly editIn?: 'page' | 'drawer';
|
|
437
447
|
readonly fields: readonly Field[];
|
|
438
448
|
/** Top-level field shown as the document title in the admin UI. */
|
|
439
449
|
readonly useAsTitle?: string;
|
|
@@ -455,6 +465,8 @@ interface CollectionConfig {
|
|
|
455
465
|
interface GlobalConfig {
|
|
456
466
|
readonly slug: string;
|
|
457
467
|
readonly label?: Label;
|
|
468
|
+
/** Icon in the admin menu. Default `settings`. */
|
|
469
|
+
readonly icon?: AdminIcon;
|
|
458
470
|
readonly fields: readonly Field[];
|
|
459
471
|
readonly drafts?: boolean;
|
|
460
472
|
/** Keep a snapshot of every save. See `CollectionConfig.versions`. */
|
|
@@ -467,11 +479,35 @@ interface GlobalConfig {
|
|
|
467
479
|
readonly hooks?: GlobalHooks;
|
|
468
480
|
}
|
|
469
481
|
type AdminLocale = 'en' | 'th';
|
|
482
|
+
/** Icons a collection or global can show in the admin menu ([Lucide](https://lucide.dev) names). */
|
|
483
|
+
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"];
|
|
484
|
+
type AdminIcon = (typeof ADMIN_ICONS)[number];
|
|
485
|
+
/** Your or your client's brand in the admin UI. */
|
|
486
|
+
interface AdminBrand {
|
|
487
|
+
/** Shown in the menu, on the login page and in the browser tab. Default "Easy CMS". */
|
|
488
|
+
readonly name?: string;
|
|
489
|
+
/** Logo URL: a path on your site (`/logo.svg`) or an `https://` address. */
|
|
490
|
+
readonly logo?: string;
|
|
491
|
+
/** Main color as `#rrggbb`; lighter and darker shades are derived from it. */
|
|
492
|
+
readonly color?: string;
|
|
493
|
+
}
|
|
470
494
|
interface AdminConfig {
|
|
471
495
|
/** Where the admin UI is served. Default `/admin`. */
|
|
472
496
|
readonly path?: string;
|
|
473
497
|
/** Default admin UI language. Default `en`. */
|
|
474
498
|
readonly locale?: AdminLocale;
|
|
499
|
+
/** Name, logo and color of the admin UI. */
|
|
500
|
+
readonly brand?: AdminBrand;
|
|
501
|
+
/**
|
|
502
|
+
* The public site, for the admin's "View site" link: a path (`/`) or an `https://` URL.
|
|
503
|
+
* Default `/` with Nuxt and Next.js; none with the standalone server.
|
|
504
|
+
*/
|
|
505
|
+
readonly siteUrl?: string;
|
|
506
|
+
/**
|
|
507
|
+
* Order of collections in the admin menu, by slug, e.g. `['posts', 'categories', 'media']`.
|
|
508
|
+
* Collections not listed follow in config order, with the media library last.
|
|
509
|
+
*/
|
|
510
|
+
readonly menu?: readonly string[];
|
|
475
511
|
}
|
|
476
512
|
interface ImageSize {
|
|
477
513
|
/** Key in `media.sizes`, e.g. `thumbnail`. */
|
|
@@ -1047,6 +1083,17 @@ declare class EasyCMS<C extends Config = Config> {
|
|
|
1047
1083
|
private scheduleTarget;
|
|
1048
1084
|
private globalScheduleTarget;
|
|
1049
1085
|
private addJob;
|
|
1086
|
+
/**
|
|
1087
|
+
* The next pending jobs across all collections and globals, soonest first; with access
|
|
1088
|
+
* enforced, only those the user may update.
|
|
1089
|
+
*/
|
|
1090
|
+
upcomingJobs(options?: AccessOptions & {
|
|
1091
|
+
limit?: number;
|
|
1092
|
+
}): Promise<(ScheduledJob & {
|
|
1093
|
+
collection?: string;
|
|
1094
|
+
global?: string;
|
|
1095
|
+
doc: ID;
|
|
1096
|
+
})[]>;
|
|
1050
1097
|
private pendingJobs;
|
|
1051
1098
|
private removeJob;
|
|
1052
1099
|
/** @internal */
|
|
@@ -1349,6 +1396,8 @@ interface AdminField {
|
|
|
1349
1396
|
readOnly?: boolean;
|
|
1350
1397
|
/** One value per content locale. */
|
|
1351
1398
|
localized?: boolean;
|
|
1399
|
+
/** Shown in the edit page's side panel. */
|
|
1400
|
+
position?: 'sidebar';
|
|
1352
1401
|
}
|
|
1353
1402
|
interface AdminCollection {
|
|
1354
1403
|
slug: string;
|
|
@@ -1356,7 +1405,10 @@ interface AdminCollection {
|
|
|
1356
1405
|
singular?: Label;
|
|
1357
1406
|
plural?: Label;
|
|
1358
1407
|
};
|
|
1408
|
+
icon?: string;
|
|
1359
1409
|
useAsTitle?: string;
|
|
1410
|
+
/** Documents open in a panel over the list. */
|
|
1411
|
+
editIn?: 'drawer';
|
|
1360
1412
|
drafts: boolean;
|
|
1361
1413
|
versions: boolean;
|
|
1362
1414
|
/** Has live preview (`preview` in the config). */
|
|
@@ -1374,6 +1426,7 @@ interface AdminCollection {
|
|
|
1374
1426
|
interface AdminGlobal {
|
|
1375
1427
|
slug: string;
|
|
1376
1428
|
label?: Label;
|
|
1429
|
+
icon?: string;
|
|
1377
1430
|
drafts: boolean;
|
|
1378
1431
|
versions: boolean;
|
|
1379
1432
|
/** Has live preview (`preview` in the config). */
|
|
@@ -1388,6 +1441,8 @@ interface AdminGlobal {
|
|
|
1388
1441
|
}
|
|
1389
1442
|
interface AdminSchema {
|
|
1390
1443
|
locale: AdminLocale;
|
|
1444
|
+
/** Menu order of collections (`admin.menu`); unlisted ones follow. */
|
|
1445
|
+
menu: string[];
|
|
1391
1446
|
/** Content locales, when the config has `localization`. */
|
|
1392
1447
|
localization: {
|
|
1393
1448
|
locales: string[];
|
|
@@ -1425,4 +1480,4 @@ declare const BUILTIN_COLLECTIONS: readonly ["users", "media"];
|
|
|
1425
1480
|
/** Checks a config and returns every problem found. An empty array means it is valid. */
|
|
1426
1481
|
declare function validateConfig(config: Config): ConfigIssue[];
|
|
1427
1482
|
|
|
1428
|
-
export { type Access, type AccessArgs, type AccessOptions, type AdminCollection, type AdminConfig, type AdminField, type AdminGlobal, type AdminLocale, type AdminSchema, type AfterChangeHook, type AfterDeleteHook, type AfterReadHook, type ArrayField, Auth, type AuthConfig, type AuthUser, BUILTIN_COLLECTIONS, type BeforeChangeHook, type BeforeDeleteHook, type BeforeValidateHook, type Block, type BlocksField, type BooleanField, type BuiltinMediaCollection, type BuiltinUsersCollection, type BundleHints, CONFIG_FILE_NAMES, CSRF_COOKIE, CSRF_HEADER, type CollectionAccess, type CollectionConfig, type CollectionDocument, type CollectionHooks, type CollectionSlug, type Config, ConfigError, type ConfigIssue, type CreateEasyCMSOptions, type CreateInput, type CreateMigrationResult, DEFAULT_ADMIN_PATH, DEFAULT_API_PATH, DEFAULT_DEPTH, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_VERSIONS, DEFAULT_TOKEN_EXPIRATION, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DateField, type DepthOptions, EXTENSIONS, EasyCMS, EasyCMSError, type EmailField, FIELD_TYPES, type Field, type FieldAccess, type FieldAccessFn, type FieldError, type FieldType, type FieldValidate, type FieldValidateContext, type FieldValue, type FieldsInput, type FieldsValue, 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, PayloadTooLargeError, type Plugin, 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 StorageAdapter, type StoredFile, type TextField, type TextareaField, TooManyRequestsError, UnauthorizedError, type UpdateInput, 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, consoleLogger, createEasyCMS, createRestHandler, defineConfig, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isLoggedIn, loadConfig, localStorage, mergeForUpdate, mimeAllowed, parseId, populate, readCookie, resolveConfig, rowFields, silentLogger, singularize, slugify, sniffMimeType, validateConfig, validateFields, verifyPassword };
|
|
1483
|
+
export { ADMIN_ICONS, type Access, type AccessArgs, type AccessOptions, type AdminBrand, type AdminCollection, type AdminConfig, type AdminField, type AdminGlobal, type AdminIcon, type AdminLocale, type AdminSchema, type AfterChangeHook, type AfterDeleteHook, type AfterReadHook, type ArrayField, Auth, type AuthConfig, type AuthUser, BUILTIN_COLLECTIONS, type BeforeChangeHook, type BeforeDeleteHook, type BeforeValidateHook, type Block, type BlocksField, type BooleanField, type BuiltinMediaCollection, type BuiltinUsersCollection, type BundleHints, CONFIG_FILE_NAMES, CSRF_COOKIE, CSRF_HEADER, type CollectionAccess, type CollectionConfig, type CollectionDocument, type CollectionHooks, type CollectionSlug, type Config, ConfigError, type ConfigIssue, type CreateEasyCMSOptions, type CreateInput, type CreateMigrationResult, DEFAULT_ADMIN_PATH, DEFAULT_API_PATH, DEFAULT_DEPTH, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_VERSIONS, DEFAULT_TOKEN_EXPIRATION, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DateField, type DepthOptions, EXTENSIONS, EasyCMS, EasyCMSError, type EmailField, FIELD_TYPES, type Field, type FieldAccess, type FieldAccessFn, type FieldError, type FieldType, type FieldValidate, type FieldValidateContext, type FieldValue, type FieldsInput, type FieldsValue, 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, PayloadTooLargeError, type Plugin, 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 StorageAdapter, type StoredFile, type TextField, type TextareaField, TooManyRequestsError, UnauthorizedError, type UpdateInput, 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, consoleLogger, createEasyCMS, createRestHandler, defineConfig, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isLoggedIn, loadConfig, localStorage, mergeForUpdate, mimeAllowed, parseId, populate, readCookie, resolveConfig, rowFields, silentLogger, singularize, slugify, sniffMimeType, validateConfig, validateFields, verifyPassword };
|
package/dist/index.js
CHANGED
|
@@ -242,6 +242,7 @@ function withUsers(config) {
|
|
|
242
242
|
slug: USERS,
|
|
243
243
|
labels: { singular: { en: "User", th: "\u0E1C\u0E39\u0E49\u0E43\u0E0A\u0E49" }, plural: { en: "Users", th: "\u0E1C\u0E39\u0E49\u0E43\u0E0A\u0E49" } },
|
|
244
244
|
useAsTitle: "email",
|
|
245
|
+
icon: "users",
|
|
245
246
|
...custom,
|
|
246
247
|
fields: [...userFields(roles), ...custom?.fields ?? []],
|
|
247
248
|
access: {
|
|
@@ -302,6 +303,7 @@ function withMedia(config) {
|
|
|
302
303
|
const media = {
|
|
303
304
|
slug: MEDIA,
|
|
304
305
|
labels: { singular: { en: "Media", th: "\u0E2A\u0E37\u0E48\u0E2D" }, plural: { en: "Media", th: "\u0E04\u0E25\u0E31\u0E07\u0E2A\u0E37\u0E48\u0E2D" } },
|
|
306
|
+
icon: "image",
|
|
305
307
|
useAsTitle: "filename",
|
|
306
308
|
...custom,
|
|
307
309
|
fields: [...mediaFields(), ...custom?.fields ?? []],
|
|
@@ -695,6 +697,55 @@ var Auth = class {
|
|
|
695
697
|
};
|
|
696
698
|
|
|
697
699
|
// src/config.ts
|
|
700
|
+
var ADMIN_ICONS = [
|
|
701
|
+
"file-text",
|
|
702
|
+
"newspaper",
|
|
703
|
+
"book-open",
|
|
704
|
+
"notebook",
|
|
705
|
+
"folder",
|
|
706
|
+
"tag",
|
|
707
|
+
"tags",
|
|
708
|
+
"image",
|
|
709
|
+
"images",
|
|
710
|
+
"video",
|
|
711
|
+
"music",
|
|
712
|
+
"file",
|
|
713
|
+
"users",
|
|
714
|
+
"user",
|
|
715
|
+
"building",
|
|
716
|
+
"store",
|
|
717
|
+
"shopping-bag",
|
|
718
|
+
"shopping-cart",
|
|
719
|
+
"package",
|
|
720
|
+
"box",
|
|
721
|
+
"calendar",
|
|
722
|
+
"calendar-days",
|
|
723
|
+
"map-pin",
|
|
724
|
+
"globe",
|
|
725
|
+
"house",
|
|
726
|
+
"layout-grid",
|
|
727
|
+
"layers",
|
|
728
|
+
"star",
|
|
729
|
+
"heart",
|
|
730
|
+
"message-square",
|
|
731
|
+
"mail",
|
|
732
|
+
"phone",
|
|
733
|
+
"briefcase",
|
|
734
|
+
"graduation-cap",
|
|
735
|
+
"utensils",
|
|
736
|
+
"car",
|
|
737
|
+
"settings",
|
|
738
|
+
"sliders-horizontal",
|
|
739
|
+
"palette",
|
|
740
|
+
"megaphone",
|
|
741
|
+
"bell",
|
|
742
|
+
"link",
|
|
743
|
+
"quote",
|
|
744
|
+
"circle-help",
|
|
745
|
+
"award",
|
|
746
|
+
"ticket",
|
|
747
|
+
"camera"
|
|
748
|
+
];
|
|
698
749
|
function defineConfig(config) {
|
|
699
750
|
return config;
|
|
700
751
|
}
|
|
@@ -1228,6 +1279,17 @@ function validateConfig(config) {
|
|
|
1228
1279
|
const slug = checkSlug(collection2, `collections[${i}]`, seenCollections, add);
|
|
1229
1280
|
if (slug) collectionSlugs.add(slug);
|
|
1230
1281
|
});
|
|
1282
|
+
const menu = config.admin?.menu;
|
|
1283
|
+
if (menu !== void 0) {
|
|
1284
|
+
if (!Array.isArray(menu)) {
|
|
1285
|
+
add("admin.menu", "must be an array of collection slugs", "e.g. menu: ['posts', 'media']");
|
|
1286
|
+
} else {
|
|
1287
|
+
for (const [i, slug] of menu.entries()) {
|
|
1288
|
+
if (typeof slug !== "string" || !collectionSlugs.has(slug))
|
|
1289
|
+
add(`admin.menu[${i}]`, `unknown collection ${JSON.stringify(slug)}`);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1231
1293
|
const seenGlobals = /* @__PURE__ */ new Set();
|
|
1232
1294
|
globals.forEach((global2, i) => {
|
|
1233
1295
|
checkSlug(global2, `globals[${i}]`, seenGlobals, add);
|
|
@@ -1236,9 +1298,15 @@ function validateConfig(config) {
|
|
|
1236
1298
|
const path = `collections.${collection2.slug ?? `[${i}]`}`;
|
|
1237
1299
|
validateContainer(collection2, path, collectionSlugs, add);
|
|
1238
1300
|
validateUseAsTitle(collection2, path, add);
|
|
1301
|
+
validateIcon(collection2, path, add);
|
|
1302
|
+
const editIn = collection2.editIn;
|
|
1303
|
+
if (editIn !== void 0 && editIn !== "page" && editIn !== "drawer")
|
|
1304
|
+
add(`${path}.editIn`, `must be "page" or "drawer" (got ${JSON.stringify(editIn)})`);
|
|
1239
1305
|
});
|
|
1240
1306
|
globals.forEach((global2, i) => {
|
|
1241
|
-
|
|
1307
|
+
const path = `globals.${global2.slug ?? `[${i}]`}`;
|
|
1308
|
+
validateContainer(global2, path, collectionSlugs, add);
|
|
1309
|
+
validateIcon(global2, path, add);
|
|
1242
1310
|
});
|
|
1243
1311
|
const plugins = config.plugins;
|
|
1244
1312
|
if (plugins !== void 0 && (!Array.isArray(plugins) || plugins.some((p) => typeof p !== "function"))) {
|
|
@@ -1267,6 +1335,39 @@ function validateAdmin(config, add) {
|
|
|
1267
1335
|
if (admin.locale !== void 0 && admin.locale !== "en" && admin.locale !== "th") {
|
|
1268
1336
|
add("admin.locale", `must be "en" or "th" (got ${JSON.stringify(admin.locale)})`);
|
|
1269
1337
|
}
|
|
1338
|
+
const siteUrl = admin.siteUrl;
|
|
1339
|
+
if (siteUrl !== void 0 && (typeof siteUrl !== "string" || !/^(\/|https?:\/\/)/.test(siteUrl))) {
|
|
1340
|
+
add(
|
|
1341
|
+
"admin.siteUrl",
|
|
1342
|
+
'must be a path starting with "/" or an http(s) URL',
|
|
1343
|
+
"e.g. siteUrl: 'https://example.com'"
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
const brand = admin.brand;
|
|
1347
|
+
if (brand === void 0) return;
|
|
1348
|
+
if (typeof brand !== "object" || brand === null) {
|
|
1349
|
+
add("admin.brand", "must be an object", "e.g. brand: { name: 'Acme', color: '#0f766e' }");
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
const { name, logo, color } = brand;
|
|
1353
|
+
if (name !== void 0 && (typeof name !== "string" || name.trim() === "")) {
|
|
1354
|
+
add("admin.brand.name", "must be a non-empty string");
|
|
1355
|
+
}
|
|
1356
|
+
if (logo !== void 0 && (typeof logo !== "string" || !/^(\/|https?:\/\/|data:image\/)/.test(logo))) {
|
|
1357
|
+
add(
|
|
1358
|
+
"admin.brand.logo",
|
|
1359
|
+
'must be a path starting with "/" or an http(s) URL',
|
|
1360
|
+
"e.g. logo: '/logo.svg'"
|
|
1361
|
+
);
|
|
1362
|
+
}
|
|
1363
|
+
if (color !== void 0 && (typeof color !== "string" || !/^#[0-9a-f]{6}$/i.test(color))) {
|
|
1364
|
+
add("admin.brand.color", `must be a hex color like "#0f766e" (got ${JSON.stringify(color)})`);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
function validateIcon(container, path, add) {
|
|
1368
|
+
const icon = container.icon;
|
|
1369
|
+
if (icon === void 0 || ADMIN_ICONS.includes(icon)) return;
|
|
1370
|
+
add(`${path}.icon`, `unknown icon ${JSON.stringify(icon)}`, `one of: ${ADMIN_ICONS.join(", ")}`);
|
|
1270
1371
|
}
|
|
1271
1372
|
function validateAuth(config, add) {
|
|
1272
1373
|
const auth = config.auth;
|
|
@@ -1516,6 +1617,10 @@ function validateFields2(fields, path, reserved, collectionSlugs, add) {
|
|
|
1516
1617
|
}
|
|
1517
1618
|
}
|
|
1518
1619
|
function validateField(field, path, siblings, collectionSlugs, add) {
|
|
1620
|
+
const position = field.position;
|
|
1621
|
+
if (position !== void 0 && position !== "sidebar") {
|
|
1622
|
+
add(`${path}.position`, `must be "sidebar" (got ${JSON.stringify(position)})`);
|
|
1623
|
+
}
|
|
1519
1624
|
switch (field.type) {
|
|
1520
1625
|
case "text":
|
|
1521
1626
|
case "textarea":
|
|
@@ -1665,7 +1770,10 @@ async function resolveConfig(input) {
|
|
|
1665
1770
|
routes: { api: `/${(config.routes?.api ?? DEFAULT_API_PATH).replace(/^\/+|\/+$/g, "")}` },
|
|
1666
1771
|
admin: {
|
|
1667
1772
|
path: config.admin?.path ?? DEFAULT_ADMIN_PATH,
|
|
1668
|
-
locale: config.admin?.locale ?? "en"
|
|
1773
|
+
locale: config.admin?.locale ?? "en",
|
|
1774
|
+
brand: config.admin?.brand ?? {},
|
|
1775
|
+
siteUrl: config.admin?.siteUrl ?? "",
|
|
1776
|
+
menu: config.admin?.menu ?? []
|
|
1669
1777
|
},
|
|
1670
1778
|
upload: {
|
|
1671
1779
|
dir: config.upload?.dir ?? "uploads",
|
|
@@ -3040,6 +3148,42 @@ var EasyCMS = class {
|
|
|
3040
3148
|
});
|
|
3041
3149
|
return toJob(row);
|
|
3042
3150
|
}
|
|
3151
|
+
/**
|
|
3152
|
+
* The next pending jobs across all collections and globals, soonest first; with access
|
|
3153
|
+
* enforced, only those the user may update.
|
|
3154
|
+
*/
|
|
3155
|
+
async upcomingJobs(options = {}) {
|
|
3156
|
+
if (!this.config.collections.some((c) => c.slug === SCHEDULED_JOBS)) return [];
|
|
3157
|
+
const guard = guardOf(options);
|
|
3158
|
+
const limit = options.limit ?? 10;
|
|
3159
|
+
const rows = await this.db.find({
|
|
3160
|
+
collection: SCHEDULED_JOBS,
|
|
3161
|
+
where: { state: { equals: "pending" } },
|
|
3162
|
+
sort: ["runAt"],
|
|
3163
|
+
limit: limit * 5,
|
|
3164
|
+
page: 1
|
|
3165
|
+
});
|
|
3166
|
+
const allowed2 = /* @__PURE__ */ new Map();
|
|
3167
|
+
const may = async (parent) => {
|
|
3168
|
+
if (!guard.enforce) return true;
|
|
3169
|
+
const cached = allowed2.get(parent);
|
|
3170
|
+
if (cached !== void 0) return cached;
|
|
3171
|
+
const config = parent.startsWith("global:") ? this.config.globals.find((g) => g.slug === parent.slice("global:".length)) : this.config.collections.find((c) => c.slug === parent);
|
|
3172
|
+
const access = config ? await evaluateAccess(config.access?.update, { user: guard.user }) : false;
|
|
3173
|
+
const result = access !== false;
|
|
3174
|
+
allowed2.set(parent, result);
|
|
3175
|
+
return result;
|
|
3176
|
+
};
|
|
3177
|
+
const jobs = [];
|
|
3178
|
+
for (const row of rows.docs) {
|
|
3179
|
+
const parent = String(row.parent);
|
|
3180
|
+
if (!await may(parent)) continue;
|
|
3181
|
+
const target = parent.startsWith("global:") ? { global: parent.slice("global:".length) } : { collection: parent };
|
|
3182
|
+
jobs.push({ ...toJob(row), ...target, doc: row.doc });
|
|
3183
|
+
if (jobs.length === limit) break;
|
|
3184
|
+
}
|
|
3185
|
+
return jobs;
|
|
3186
|
+
}
|
|
3043
3187
|
async pendingJobs(parent, doc) {
|
|
3044
3188
|
const rows = await this.db.find({
|
|
3045
3189
|
collection: SCHEDULED_JOBS,
|
|
@@ -3530,6 +3674,7 @@ async function serializeFields(fields, update, localized) {
|
|
|
3530
3674
|
if (field.defaultValue !== void 0) f.defaultValue = field.defaultValue;
|
|
3531
3675
|
if (!await update.allows(field)) f.readOnly = true;
|
|
3532
3676
|
if (field.localized && localized) f.localized = true;
|
|
3677
|
+
if (field.position === "sidebar") f.position = "sidebar";
|
|
3533
3678
|
switch (field.type) {
|
|
3534
3679
|
case "text":
|
|
3535
3680
|
case "textarea":
|
|
@@ -3601,6 +3746,9 @@ async function collection(config, user, localized) {
|
|
|
3601
3746
|
};
|
|
3602
3747
|
if (config.labels) result.labels = config.labels;
|
|
3603
3748
|
if (config.useAsTitle) result.useAsTitle = config.useAsTitle;
|
|
3749
|
+
if (config.icon) result.icon = config.icon;
|
|
3750
|
+
if (config.editIn === "drawer" && !result.drafts && !result.versions && !result.preview)
|
|
3751
|
+
result.editIn = "drawer";
|
|
3604
3752
|
return result;
|
|
3605
3753
|
}
|
|
3606
3754
|
async function global(config, user, localized) {
|
|
@@ -3621,6 +3769,7 @@ async function global(config, user, localized) {
|
|
|
3621
3769
|
}
|
|
3622
3770
|
};
|
|
3623
3771
|
if (config.label !== void 0) result.label = config.label;
|
|
3772
|
+
if (config.icon) result.icon = config.icon;
|
|
3624
3773
|
return result;
|
|
3625
3774
|
}
|
|
3626
3775
|
async function adminSchema(cms, user) {
|
|
@@ -3629,6 +3778,7 @@ async function adminSchema(cms, user) {
|
|
|
3629
3778
|
const localized = localization !== null;
|
|
3630
3779
|
return {
|
|
3631
3780
|
locale: cms.config.admin.locale,
|
|
3781
|
+
menu: [...cms.config.admin.menu],
|
|
3632
3782
|
localization: localization ? { locales: [...localization.locales], defaultLocale: localization.defaultLocale } : null,
|
|
3633
3783
|
collections: await Promise.all(collections.map((c) => collection(c, user, localized))),
|
|
3634
3784
|
globals: await Promise.all(cms.config.globals.map((g) => global(g, user, localized)))
|
|
@@ -3863,6 +4013,8 @@ async function route(cms, ctx, method, segments, options) {
|
|
|
3863
4013
|
if (!ctx.user) throw new UnauthorizedError();
|
|
3864
4014
|
if (second === "schema" && third === void 0)
|
|
3865
4015
|
return { body: await adminSchema(cms, ctx.user) };
|
|
4016
|
+
if (second === "scheduled" && third === void 0)
|
|
4017
|
+
return { body: await cms.upcomingJobs({ user: ctx.user, overrideAccess: false }) };
|
|
3866
4018
|
const [, , collection3, id2, extra] = segments;
|
|
3867
4019
|
if (second === "access" && collection3 && id2 && extra === void 0) {
|
|
3868
4020
|
if (INTERNAL_COLLECTIONS.has(collection3) || !cms.config.collections.some((c) => c.slug === collection3)) {
|
|
@@ -4392,6 +4544,7 @@ ${map(n.globals)}
|
|
|
4392
4544
|
return parts.join("\n");
|
|
4393
4545
|
}
|
|
4394
4546
|
export {
|
|
4547
|
+
ADMIN_ICONS,
|
|
4395
4548
|
Auth,
|
|
4396
4549
|
BUILTIN_COLLECTIONS,
|
|
4397
4550
|
CONFIG_FILE_NAMES,
|