@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 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
- validateContainer(global2, `globals.${global2.slug ?? `[${i}]`}`, collectionSlugs, add);
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easy-cms/core",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Embedded, code-first headless CMS for Nuxt and Next.js",
5
5
  "license": "MIT",
6
6
  "type": "module",