@getvitops/emdash 0.3.10 → 1.0.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.mjs CHANGED
@@ -1,7 +1,10 @@
1
- import { definePlugin } from "emdash";
1
+ import { definePlugin, pluginResponse } from "emdash";
2
2
  import { iconMap } from "@getvitops/utils";
3
+ import { designSystemPath, formatConfig, getAt, isConfig, mergePatch, resolveInput, setAt } from "@getvitops/generator/config";
4
+ import { buildReverseIndex, patchToOverrideCss, validateColorContract } from "@getvitops/generator/tokens";
5
+ import { commitFile } from "@getvitops/utils/git";
3
6
 
4
- //#region src/icon-options.ts
7
+ // src/icon-options.ts
5
8
  /**
6
9
  * Semantic icon names, as Block Kit combobox options.
7
10
  *
@@ -22,8 +25,7 @@ const SEMANTIC_ICON_OPTIONS = Object.keys(iconMap.fa7).sort().map((name) => ({
22
25
  value: name
23
26
  }));
24
27
 
25
- //#endregion
26
- //#region src/blocks.ts
28
+ // src/blocks.ts
27
29
  const BLOCKS = [
28
30
  {
29
31
  /**
@@ -267,17 +269,34 @@ const BLOCKS = [
267
269
  type: "vitops.carousel",
268
270
  label: "Carousel",
269
271
  placeholder: "Image carousel",
272
+ category: "Media",
270
273
  fields: [
274
+ {
275
+ type: "repeater",
276
+ action_id: "slides",
277
+ label: "Slides",
278
+ item_label: "slide",
279
+ min_items: 1,
280
+ fields: [{
281
+ type: "text_input",
282
+ action_id: "src",
283
+ label: "Image URL"
284
+ }, {
285
+ type: "text_input",
286
+ action_id: "alt",
287
+ label: "Alt text"
288
+ }]
289
+ },
271
290
  {
272
291
  type: "text_input",
273
292
  action_id: "images",
274
- label: "Image URLs (one per line)",
293
+ label: "Image URLs, one per line (legacy — use Slides)",
275
294
  multiline: true
276
295
  },
277
296
  {
278
297
  type: "text_input",
279
298
  action_id: "alts",
280
- label: "Alt texts (one per line, same order)",
299
+ label: "Alt texts, one per line (legacy — use Slides)",
281
300
  multiline: true
282
301
  },
283
302
  {
@@ -305,11 +324,489 @@ const BLOCKS = [
305
324
  initial_value: "16 / 9"
306
325
  }
307
326
  ]
327
+ },
328
+ {
329
+ type: "vitops.gallery",
330
+ label: "Gallery",
331
+ placeholder: "Thumbnail grid that opens a lightbox carousel",
332
+ category: "Media",
333
+ fields: [
334
+ {
335
+ type: "text_input",
336
+ action_id: "label",
337
+ label: "Accessible name",
338
+ initial_value: "Gallery"
339
+ },
340
+ {
341
+ type: "repeater",
342
+ action_id: "images",
343
+ label: "Images",
344
+ item_label: "image",
345
+ min_items: 1,
346
+ fields: [
347
+ {
348
+ type: "text_input",
349
+ action_id: "src",
350
+ label: "Image URL"
351
+ },
352
+ {
353
+ type: "text_input",
354
+ action_id: "alt",
355
+ label: "Alt text"
356
+ },
357
+ {
358
+ type: "text_input",
359
+ action_id: "caption",
360
+ label: "Caption"
361
+ }
362
+ ]
363
+ },
364
+ {
365
+ type: "text_input",
366
+ action_id: "aspect",
367
+ label: "Thumbnail aspect ratio",
368
+ initial_value: "1"
369
+ },
370
+ {
371
+ type: "toggle",
372
+ action_id: "loop",
373
+ label: "Wrap around at the ends",
374
+ initial_value: false
375
+ }
376
+ ]
377
+ },
378
+ {
379
+ type: "vitops.cards",
380
+ label: "Cards",
381
+ placeholder: "A grid of cards",
382
+ category: "Sections",
383
+ fields: [{
384
+ type: "repeater",
385
+ action_id: "cards",
386
+ label: "Cards",
387
+ item_label: "card",
388
+ min_items: 1,
389
+ fields: [
390
+ {
391
+ type: "text_input",
392
+ action_id: "title",
393
+ label: "Title"
394
+ },
395
+ {
396
+ type: "text_input",
397
+ action_id: "body",
398
+ label: "Text",
399
+ multiline: true
400
+ },
401
+ {
402
+ type: "text_input",
403
+ action_id: "image",
404
+ label: "Image URL"
405
+ },
406
+ {
407
+ type: "text_input",
408
+ action_id: "imageAlt",
409
+ label: "Image alt text"
410
+ },
411
+ {
412
+ type: "text_input",
413
+ action_id: "href",
414
+ label: "Link URL (makes the whole card clickable)"
415
+ },
416
+ {
417
+ type: "text_input",
418
+ action_id: "linkLabel",
419
+ label: "Link text",
420
+ initial_value: "Read more"
421
+ }
422
+ ]
423
+ }, {
424
+ type: "select",
425
+ action_id: "columns",
426
+ label: "Columns",
427
+ initial_value: "3",
428
+ options: [
429
+ {
430
+ label: "1",
431
+ value: "1"
432
+ },
433
+ {
434
+ label: "2",
435
+ value: "2"
436
+ },
437
+ {
438
+ label: "3",
439
+ value: "3"
440
+ },
441
+ {
442
+ label: "4",
443
+ value: "4"
444
+ }
445
+ ]
446
+ }]
447
+ },
448
+ {
449
+ type: "vitops.accordion",
450
+ label: "Accordion",
451
+ placeholder: "A group of collapsible sections",
452
+ category: "Sections",
453
+ fields: [{
454
+ type: "repeater",
455
+ action_id: "items",
456
+ label: "Items",
457
+ item_label: "item",
458
+ min_items: 1,
459
+ fields: [
460
+ {
461
+ type: "text_input",
462
+ action_id: "summary",
463
+ label: "Summary (always visible)"
464
+ },
465
+ {
466
+ type: "text_input",
467
+ action_id: "body",
468
+ label: "Body (revealed)",
469
+ multiline: true
470
+ },
471
+ {
472
+ type: "toggle",
473
+ action_id: "open",
474
+ label: "Open by default",
475
+ initial_value: false
476
+ }
477
+ ]
478
+ }, {
479
+ type: "toggle",
480
+ action_id: "exclusive",
481
+ label: "Only one open at a time",
482
+ initial_value: false
483
+ }]
484
+ },
485
+ {
486
+ type: "vitops.hero",
487
+ label: "Hero",
488
+ placeholder: "Page opener: heading, text, buttons and an optional image",
489
+ category: "Sections",
490
+ fields: [
491
+ {
492
+ type: "text_input",
493
+ action_id: "eyebrow",
494
+ label: "Eyebrow (small line above)"
495
+ },
496
+ {
497
+ type: "text_input",
498
+ action_id: "heading",
499
+ label: "Heading"
500
+ },
501
+ {
502
+ type: "text_input",
503
+ action_id: "body",
504
+ label: "Text",
505
+ multiline: true
506
+ },
507
+ {
508
+ type: "text_input",
509
+ action_id: "primaryLabel",
510
+ label: "Main button text"
511
+ },
512
+ {
513
+ type: "text_input",
514
+ action_id: "primaryHref",
515
+ label: "Main button URL"
516
+ },
517
+ {
518
+ type: "text_input",
519
+ action_id: "secondaryLabel",
520
+ label: "Second link text"
521
+ },
522
+ {
523
+ type: "text_input",
524
+ action_id: "secondaryHref",
525
+ label: "Second link URL"
526
+ },
527
+ {
528
+ type: "text_input",
529
+ action_id: "image",
530
+ label: "Image URL (split layout)"
531
+ },
532
+ {
533
+ type: "text_input",
534
+ action_id: "imageAlt",
535
+ label: "Image alt text"
536
+ },
537
+ {
538
+ type: "select",
539
+ action_id: "layout",
540
+ label: "Layout",
541
+ initial_value: "centered",
542
+ options: [{
543
+ label: "Centered",
544
+ value: "centered"
545
+ }, {
546
+ label: "Text and image side by side",
547
+ value: "split"
548
+ }]
549
+ }
550
+ ]
551
+ },
552
+ {
553
+ type: "vitops.marquee",
554
+ label: "Marquee",
555
+ placeholder: "A continuously scrolling row (logos, quotes, badges)",
556
+ category: "Media",
557
+ fields: [
558
+ {
559
+ type: "repeater",
560
+ action_id: "items",
561
+ label: "Items",
562
+ item_label: "item",
563
+ min_items: 1,
564
+ fields: [
565
+ {
566
+ type: "text_input",
567
+ action_id: "text",
568
+ label: "Text"
569
+ },
570
+ {
571
+ type: "text_input",
572
+ action_id: "image",
573
+ label: "Image URL (shown instead of text)"
574
+ },
575
+ {
576
+ type: "text_input",
577
+ action_id: "alt",
578
+ label: "Image alt text"
579
+ }
580
+ ]
581
+ },
582
+ {
583
+ type: "toggle",
584
+ action_id: "pauseOnHover",
585
+ label: "Pause on hover",
586
+ initial_value: true
587
+ },
588
+ {
589
+ type: "toggle",
590
+ action_id: "reverse",
591
+ label: "Scroll the other way",
592
+ initial_value: false
593
+ }
594
+ ]
308
595
  }
309
596
  ];
597
+ /**
598
+ * Resolve {@link BlockSelection} against {@link BLOCKS}.
599
+ *
600
+ * Pure so a misconfiguration fails at plugin construction, with the offending name in the
601
+ * message, rather than as an editor menu that is quietly missing something.
602
+ */
603
+ function selectBlocks(selection = {}) {
604
+ const known = new Set(BLOCKS.map((b) => b.type));
605
+ const check = (names, option) => {
606
+ for (const name of names ?? []) if (!known.has(name)) throw new Error(`[vitops] blocks.${option}: unknown block '${name}'. Known: ${[...known].join(", ")}.`);
607
+ };
608
+ check(selection.include, "include");
609
+ check(selection.exclude, "exclude");
610
+ const include = selection.include ? new Set(selection.include) : null;
611
+ const exclude = new Set(selection.exclude);
612
+ const base = BLOCKS.filter((b) => (!include || include.has(b.type)) && !exclude.has(b.type));
613
+ const extra = selection.extra ?? [];
614
+ const taken = new Set(BLOCKS.map((b) => b.type));
615
+ for (const block of extra) {
616
+ if (taken.has(block.type)) throw new Error(`[vitops] blocks.extra: type '${block.type}' is already defined.`);
617
+ taken.add(block.type);
618
+ }
619
+ return [...base, ...extra];
620
+ }
621
+
622
+ // src/design-system-route.ts
623
+ /**
624
+ * The `vitops.design-system` plugin route — what `<wc-theme-editor>` probes
625
+ * and POSTs its patch to when mounted inside the EmDash admin (see this
626
+ * package's README and AGENTS.md's "EmDash plugin as the design-system
627
+ * editing surface" section). Mounted at
628
+ * `/_emdash/api/plugins/vitops/design-system`.
629
+ *
630
+ * Two responsibilities, matching the dev-only `@getvitops/vite` endpoint's
631
+ * contract byte-for-byte (`GET → { writable }`, `POST { patch } → { ok } |
632
+ * { errors }`) so `<wc-theme-editor>` needs no branch for which one it's
633
+ * talking to:
634
+ *
635
+ * 1. **Immediate**: persist the patch to `ctx.settings` — read back by the
636
+ * `page:fragments` hook in `index.ts`, which turns it into a site-wide
637
+ * `:root` override `<style>` on every public page. This is what makes an
638
+ * edit visible without a rebuild.
639
+ * 2. **Canonical**: auto-commit the merged file to `site.deployment.branch`
640
+ * (default `dev`) via `@getvitops/utils/git`'s `commitFile()`, so the edit
641
+ * reaches the git-tracked `site.json` through this repo's own normal
642
+ * `dev → promote → main` review path — never a silent second source of
643
+ * truth. A commit failure (missing token, misconfigured repo, a transient
644
+ * GitHub error) is logged and surfaces as a warning, but never fails the
645
+ * save: the immediate override already works regardless.
646
+ *
647
+ * **Assumes `config` is a full site `Config`** (has `organization`/`site`),
648
+ * not a bare `design-system.json` — true for any real EmDash site (it always
649
+ * has a `site.json`), and the only case this plugin already handles anywhere
650
+ * else (`vitopsHosting()`, `BLOCKS`).
651
+ *
652
+ * The GitHub token is read as `process.env.VITOPS_GITHUB_TOKEN` — matching
653
+ * `hosting.ts`'s own `process.env.HOSTING` read in this same package, rather
654
+ * than a `cloudflare:workers`-specific import: a native EmDash plugin runs
655
+ * in-process with no isolation boundary (EmDash's own docs say plugins "can
656
+ * access environment variables" directly), and this package already targets
657
+ * both the Cloudflare and Node hosting targets through one `process.env`
658
+ * convention (`nodejs_compat` on Workers maps bindings into `process.env`).
659
+ */
660
+ const SETTINGS_KEY = "designSystemPatch";
661
+ function json(status, body) {
662
+ return pluginResponse({
663
+ status,
664
+ headers: { "content-type": "application/json" },
665
+ body: {
666
+ kind: "text",
667
+ value: JSON.stringify(body)
668
+ }
669
+ });
670
+ }
671
+ /** Fetches the currently-stored patch, clearing it if the bundled `config`
672
+ * (the last real build) already reflects every value in it — see the
673
+ * module docblock's "self-clearing" note in AGENTS.md. */
674
+ async function currentPatch(ctx, ds) {
675
+ const stored = await ctx.settings.get(SETTINGS_KEY);
676
+ if (!stored) return null;
677
+ const patchedColors = stored.patch["colors"];
678
+ if (patchedColors != null && JSON.stringify(mergePatch(ds.colors, patchedColors)) === JSON.stringify(ds.colors)) {
679
+ await ctx.settings.delete(SETTINGS_KEY);
680
+ return null;
681
+ }
682
+ return stored;
683
+ }
684
+ function createDesignSystemRoute(opts) {
685
+ const theme = opts.theme ?? "default";
686
+ return {
687
+ methods: ["GET", "POST"],
688
+ response: "raw",
689
+ permission: "plugins:manage",
690
+ handler: async (ctx) => {
691
+ const { designSystem: ds, config } = resolveInput(opts.config, {
692
+ theme,
693
+ siteEnv: "production"
694
+ });
695
+ if (ctx.request.method === "GET") {
696
+ await currentPatch(ctx, ds);
697
+ return json(200, { writable: true });
698
+ }
699
+ const patch = ctx.input?.patch;
700
+ if (patch == null || typeof patch !== "object" || Array.isArray(patch)) return json(400, { errors: ["body must be { patch: object }"] });
701
+ const mergedColors = mergePatch(ds.colors, patch["colors"] ?? {});
702
+ const contrastErrors = validateColorContract(mergedColors);
703
+ if (contrastErrors.length) return json(400, { errors: contrastErrors });
704
+ await ctx.settings.set(SETTINGS_KEY, {
705
+ patch,
706
+ at: Date.now()
707
+ });
708
+ const warnings = [];
709
+ const repoSpec = config?.site.deployment?.repo;
710
+ const token = process.env["VITOPS_GITHUB_TOKEN"];
711
+ if (!repoSpec || !token) warnings.push(!repoSpec ? "design-system sync skipped: site.deployment.repo is not set — the change is live but not yet synced to source." : "design-system sync skipped: no GitHub token configured — the change is live but not yet synced to source.");
712
+ else {
713
+ const [owner, repo] = repoSpec.split("/");
714
+ if (!owner || !repo) warnings.push(`design-system sync skipped: site.deployment.repo "${repoSpec}" isn't "owner/name".`);
715
+ else if (!isConfig(opts.config)) warnings.push("design-system sync skipped: config is not a recognised site Config.");
716
+ else {
717
+ const at = designSystemPath(opts.config, theme);
718
+ const mergedSubtree = mergePatch(getAt(opts.config, at), patch);
719
+ const content = `${formatConfig(setAt(opts.config, at, mergedSubtree))}\n`;
720
+ const result = await commitFile({
721
+ owner,
722
+ repo,
723
+ branch: config.site.deployment?.branch ?? "dev",
724
+ path: "site.json",
725
+ content,
726
+ message: "Sync design-system edit from EmDash admin",
727
+ token,
728
+ fetchImpl: ctx.http && ((url, init) => ctx.http.fetch(url, init))
729
+ });
730
+ if (!result.ok) {
731
+ ctx.log.warn("design-system sync commit failed", { message: result.message });
732
+ warnings.push(`design-system sync failed (change is still live): ${result.message}`);
733
+ }
734
+ }
735
+ }
736
+ return json(200, {
737
+ ok: true,
738
+ warnings
739
+ });
740
+ }
741
+ };
742
+ }
743
+ /**
744
+ * The `page:fragments` contribution serving the stored patch's override
745
+ * CSS site-wide. Returns `null` (no fragment) when there's nothing unsynced
746
+ * — the same "no gated tag, no cost" discipline `@getvitops/astro`'s consent
747
+ * gate already follows.
748
+ */
749
+ async function designSystemOverrideFragment(ctx, opts) {
750
+ const { designSystem: ds } = resolveInput(opts.config, { theme: opts.theme ?? "default" });
751
+ const stored = await currentPatch(ctx, ds);
752
+ if (!stored) return null;
753
+ const css = patchToOverrideCss(stored.patch, buildReverseIndex(ds));
754
+ if (!css) return null;
755
+ return {
756
+ kind: "html",
757
+ placement: "head",
758
+ html: `<style id="ds-overrides">${css}</style>`,
759
+ key: "vitops-ds-overrides"
760
+ };
761
+ }
310
762
 
311
- //#endregion
312
- //#region src/hosting.ts
763
+ // src/blocks-doc.ts
764
+ const cell = (s) => (s ?? "").replace(/\|/g, "\\|").replace(/\n/g, " ");
765
+ function describe(f) {
766
+ const bits = [];
767
+ if ("options" in f && Array.isArray(f.options) && f.options.length) {
768
+ const values = f.options.map((o) => String(o.value));
769
+ bits.push(values.length > 8 ? `one of ${values.length} values (e.g. ${values.slice(0, 4).join(", ")})` : `one of ${values.join(", ")}`);
770
+ }
771
+ if ("initial_value" in f && f.initial_value !== void 0) bits.push(`default \`${String(f.initial_value)}\``);
772
+ if (f.type === "repeater" && "min_items" in f && f.min_items) bits.push(`min ${f.min_items} rows`);
773
+ return bits.join("; ");
774
+ }
775
+ function fieldRows(fields, prefix = "") {
776
+ return fields.flatMap((f) => {
777
+ const own = `| \`${prefix}${f.action_id}\` | ${f.type} | ${cell(f.label)} | ${cell(describe(f))} |`;
778
+ return f.type === "repeater" ? [own, ...fieldRows(f.fields, `${f.action_id}[].`)] : [own];
779
+ });
780
+ }
781
+ /**
782
+ * The block reference an agent reads via `vitops docs blocks`: every Portable Text `_type`,
783
+ * its fields and what each saves. Rendered from the same `BLOCKS` the editor menu uses, so
784
+ * it cannot describe a field the editor doesn't offer.
785
+ */
786
+ function renderBlocksDoc(blocks = BLOCKS) {
787
+ const out = [
788
+ "---",
789
+ "type: concept",
790
+ "title: EmDash blocks",
791
+ "description: Portable Text block types from @getvitops/emdash, with every field",
792
+ "---",
793
+ "",
794
+ "# EmDash blocks",
795
+ "",
796
+ "Each block is a Portable Text object: `{ \"_type\": \"vitops.cards\", \"_key\": \"<unique>\", ...fields }`.",
797
+ "Blocks are flat — a block cannot contain other blocks, and a repeater row holds scalar fields only.",
798
+ "Give every block a unique `_key`; gallery and carousel derive their element id from it.",
799
+ ""
800
+ ];
801
+ for (const b of blocks) {
802
+ out.push(`## \`${b.type}\` — ${b.label}`, "");
803
+ if (b.placeholder) out.push(`${b.placeholder}.`, "");
804
+ if (b.fields?.length) out.push("| Field | Type | Label | Notes |", "| --- | --- | --- | --- |", ...fieldRows(b.fields), "");
805
+ }
806
+ return out.join("\n");
807
+ }
808
+
809
+ // src/hosting.ts
313
810
  const TARGETS = ["cloudflare", "node"];
314
811
  /**
315
812
  * Import an optional hosting dependency. The specifier is passed as a
@@ -338,7 +835,8 @@ async function vitopsHosting(options = {}) {
338
835
  adapter: cloudflare(),
339
836
  database: d1({
340
837
  binding: cf.dbBinding ?? "DB",
341
- session: cf.session ?? "auto"
838
+ session: cf.session ?? "auto",
839
+ coalesce: cf.coalesce ?? true
342
840
  }),
343
841
  storage: r2({ binding: cf.mediaBinding ?? "MEDIA" })
344
842
  };
@@ -368,12 +866,10 @@ async function vitopsHosting(options = {}) {
368
866
  throw new Error(`[vitops] unknown hosting target '${target}' (from ${process.env.HOSTING ? "HOSTING env var" : "options.target"}). Valid targets: ${TARGETS.join(", ")}.`);
369
867
  }
370
868
 
371
- //#endregion
372
- //#region package.json
373
- var version = "0.3.9";
869
+ // package.json
870
+ var version = "1.0.0";
374
871
 
375
- //#endregion
376
- //#region src/index.ts
872
+ // src/index.ts
377
873
  /**
378
874
  * @getvitops/emdash — EmDash CMS native plugin for the Vitops design system.
379
875
  *
@@ -388,10 +884,12 @@ var version = "0.3.9";
388
884
  *
389
885
  * ```js
390
886
  * // astro.config.mjs
887
+ * import config from './site.json' with { type: 'json' };
888
+ *
391
889
  * integrations: [
392
890
  * react(),
393
891
  * vitops({ css: {...} }),
394
- * emdash({ database, storage, plugins: [vitopsEmdash()] }),
892
+ * emdash({ database, storage, plugins: [vitopsEmdash({ designSystemEditor: { config } })] }),
395
893
  * ]
396
894
  * ```
397
895
  */
@@ -407,6 +905,8 @@ var version = "0.3.9";
407
905
  const VERSION = version;
408
906
  const PLUGIN_ID = "vitops";
409
907
  const FRAGMENTS_CAPABILITY = "hooks.page-fragments:register";
908
+ /** For the design-system-editor's GitHub auto-commit — see design-system-route.ts. */
909
+ const NETWORK_CAPABILITY = "network:request";
410
910
  /**
411
911
  * Descriptor factory — import in `astro.config.mjs` and pass to the emdash()
412
912
  * integration's `plugins` array.
@@ -418,19 +918,20 @@ function vitopsEmdash(options = {}) {
418
918
  format: "native",
419
919
  entrypoint: "@getvitops/emdash",
420
920
  componentsEntry: "@getvitops/emdash/astro",
921
+ ...options.designSystemEditor ? { adminEntry: "@getvitops/emdash/admin" } : {},
421
922
  options,
422
- capabilities: options.scripts === "fragments" ? [FRAGMENTS_CAPABILITY] : []
923
+ capabilities: [...options.scripts === "fragments" || options.designSystemEditor ? [FRAGMENTS_CAPABILITY] : [], ...options.designSystemEditor ? [NETWORK_CAPABILITY] : []]
423
924
  };
424
925
  }
425
926
  /** Runtime side — EmDash imports and calls this from the descriptor's entrypoint. */
426
927
  function createPlugin(options = {}) {
427
928
  const scripts = options.scripts ?? "integration";
428
929
  const wcBase = options.wcBase ?? "/vitops";
429
- return definePlugin({
430
- id: PLUGIN_ID,
431
- version: VERSION,
432
- capabilities: scripts === "fragments" ? [FRAGMENTS_CAPABILITY] : [],
433
- hooks: scripts === "fragments" ? { "page:fragments": async () => [{
930
+ const editor = options.designSystemEditor || void 0;
931
+ const fragmentsEnabled = scripts === "fragments" || !!editor;
932
+ const pageFragments = async (_event, ctx) => {
933
+ const contributions = [];
934
+ if (scripts === "fragments") contributions.push({
434
935
  kind: "html",
435
936
  placement: "head",
436
937
  html: `<link rel="modulepreload" href="${wcBase}/polyfills.js"><script type="module" src="${wcBase}/polyfills.js"><\/script><script type="module" src="${wcBase}/elements.js"><\/script>`,
@@ -441,11 +942,33 @@ function createPlugin(options = {}) {
441
942
  src: `${wcBase}/deferred.js`,
442
943
  defer: true,
443
944
  key: "vitops-deferred"
444
- }] } : {},
445
- admin: { portableTextBlocks: BLOCKS }
945
+ });
946
+ if (editor) {
947
+ const fragment = await designSystemOverrideFragment(ctx, editor);
948
+ if (fragment) contributions.push(fragment);
949
+ }
950
+ return contributions;
951
+ };
952
+ return definePlugin({
953
+ id: PLUGIN_ID,
954
+ version: VERSION,
955
+ capabilities: [...fragmentsEnabled ? [FRAGMENTS_CAPABILITY] : [], ...editor ? [NETWORK_CAPABILITY] : []],
956
+ ...editor ? { allowedHosts: ["api.github.com"] } : {},
957
+ ...fragmentsEnabled ? { hooks: { "page:fragments": pageFragments } } : {},
958
+ admin: {
959
+ portableTextBlocks: selectBlocks(options.blocks),
960
+ ...editor ? {
961
+ entry: "@getvitops/emdash/admin",
962
+ pages: [{
963
+ path: "/design-system",
964
+ label: "Design System",
965
+ icon: "palette"
966
+ }]
967
+ } : {}
968
+ },
969
+ ...editor ? { routes: { "design-system": createDesignSystemRoute(editor) } } : {}
446
970
  });
447
971
  }
448
972
 
449
- //#endregion
450
- export { BLOCKS, createPlugin, createPlugin as default, vitopsEmdash, vitopsHosting };
973
+ export { BLOCKS, createPlugin, createPlugin as default, renderBlocksDoc, selectBlocks, vitopsEmdash, vitopsHosting };
451
974
  //# sourceMappingURL=index.mjs.map