@easy-cms/core 0.11.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;
@@ -436,6 +438,12 @@ interface CollectionConfig {
436
438
  };
437
439
  /** Icon in the admin menu. Default `file-text`. */
438
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';
439
447
  readonly fields: readonly Field[];
440
448
  /** Top-level field shown as the document title in the admin UI. */
441
449
  readonly useAsTitle?: string;
@@ -490,6 +498,16 @@ interface AdminConfig {
490
498
  readonly locale?: AdminLocale;
491
499
  /** Name, logo and color of the admin UI. */
492
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[];
493
511
  }
494
512
  interface ImageSize {
495
513
  /** Key in `media.sizes`, e.g. `thumbnail`. */
@@ -1378,6 +1396,8 @@ interface AdminField {
1378
1396
  readOnly?: boolean;
1379
1397
  /** One value per content locale. */
1380
1398
  localized?: boolean;
1399
+ /** Shown in the edit page's side panel. */
1400
+ position?: 'sidebar';
1381
1401
  }
1382
1402
  interface AdminCollection {
1383
1403
  slug: string;
@@ -1387,6 +1407,8 @@ interface AdminCollection {
1387
1407
  };
1388
1408
  icon?: string;
1389
1409
  useAsTitle?: string;
1410
+ /** Documents open in a panel over the list. */
1411
+ editIn?: 'drawer';
1390
1412
  drafts: boolean;
1391
1413
  versions: boolean;
1392
1414
  /** Has live preview (`preview` in the config). */
@@ -1419,6 +1441,8 @@ interface AdminGlobal {
1419
1441
  }
1420
1442
  interface AdminSchema {
1421
1443
  locale: AdminLocale;
1444
+ /** Menu order of collections (`admin.menu`); unlisted ones follow. */
1445
+ menu: string[];
1422
1446
  /** Content locales, when the config has `localization`. */
1423
1447
  localization: {
1424
1448
  locales: string[];
package/dist/index.js CHANGED
@@ -1279,6 +1279,17 @@ function validateConfig(config) {
1279
1279
  const slug = checkSlug(collection2, `collections[${i}]`, seenCollections, add);
1280
1280
  if (slug) collectionSlugs.add(slug);
1281
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
+ }
1282
1293
  const seenGlobals = /* @__PURE__ */ new Set();
1283
1294
  globals.forEach((global2, i) => {
1284
1295
  checkSlug(global2, `globals[${i}]`, seenGlobals, add);
@@ -1288,6 +1299,9 @@ function validateConfig(config) {
1288
1299
  validateContainer(collection2, path, collectionSlugs, add);
1289
1300
  validateUseAsTitle(collection2, path, add);
1290
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)})`);
1291
1305
  });
1292
1306
  globals.forEach((global2, i) => {
1293
1307
  const path = `globals.${global2.slug ?? `[${i}]`}`;
@@ -1321,6 +1335,14 @@ function validateAdmin(config, add) {
1321
1335
  if (admin.locale !== void 0 && admin.locale !== "en" && admin.locale !== "th") {
1322
1336
  add("admin.locale", `must be "en" or "th" (got ${JSON.stringify(admin.locale)})`);
1323
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
+ }
1324
1346
  const brand = admin.brand;
1325
1347
  if (brand === void 0) return;
1326
1348
  if (typeof brand !== "object" || brand === null) {
@@ -1595,6 +1617,10 @@ function validateFields2(fields, path, reserved, collectionSlugs, add) {
1595
1617
  }
1596
1618
  }
1597
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
+ }
1598
1624
  switch (field.type) {
1599
1625
  case "text":
1600
1626
  case "textarea":
@@ -1745,7 +1771,9 @@ async function resolveConfig(input) {
1745
1771
  admin: {
1746
1772
  path: config.admin?.path ?? DEFAULT_ADMIN_PATH,
1747
1773
  locale: config.admin?.locale ?? "en",
1748
- brand: config.admin?.brand ?? {}
1774
+ brand: config.admin?.brand ?? {},
1775
+ siteUrl: config.admin?.siteUrl ?? "",
1776
+ menu: config.admin?.menu ?? []
1749
1777
  },
1750
1778
  upload: {
1751
1779
  dir: config.upload?.dir ?? "uploads",
@@ -3646,6 +3674,7 @@ async function serializeFields(fields, update, localized) {
3646
3674
  if (field.defaultValue !== void 0) f.defaultValue = field.defaultValue;
3647
3675
  if (!await update.allows(field)) f.readOnly = true;
3648
3676
  if (field.localized && localized) f.localized = true;
3677
+ if (field.position === "sidebar") f.position = "sidebar";
3649
3678
  switch (field.type) {
3650
3679
  case "text":
3651
3680
  case "textarea":
@@ -3718,6 +3747,8 @@ async function collection(config, user, localized) {
3718
3747
  if (config.labels) result.labels = config.labels;
3719
3748
  if (config.useAsTitle) result.useAsTitle = config.useAsTitle;
3720
3749
  if (config.icon) result.icon = config.icon;
3750
+ if (config.editIn === "drawer" && !result.drafts && !result.versions && !result.preview)
3751
+ result.editIn = "drawer";
3721
3752
  return result;
3722
3753
  }
3723
3754
  async function global(config, user, localized) {
@@ -3747,6 +3778,7 @@ async function adminSchema(cms, user) {
3747
3778
  const localized = localization !== null;
3748
3779
  return {
3749
3780
  locale: cms.config.admin.locale,
3781
+ menu: [...cms.config.admin.menu],
3750
3782
  localization: localization ? { locales: [...localization.locales], defaultLocale: localization.defaultLocale } : null,
3751
3783
  collections: await Promise.all(collections.map((c) => collection(c, user, localized))),
3752
3784
  globals: await Promise.all(cms.config.globals.map((g) => global(g, user, localized)))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easy-cms/core",
3
- "version": "0.11.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",