@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/CHANGELOG.md +328 -0
- package/README.md +66 -20
- package/dist/index.d.mts +81 -20
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +548 -25
- package/dist/index.mjs.map +1 -1
- package/package.json +34 -10
- package/src/admin/DesignSystemEditorPage.tsx +51 -0
- package/src/admin/core-editor.d.ts +7 -0
- package/src/admin/index.tsx +12 -0
- package/src/astro/blocks/Accordion.astro +39 -0
- package/src/astro/blocks/ActionLink.astro +4 -2
- package/src/astro/blocks/Cards.astro +52 -0
- package/src/astro/blocks/Carousel.astro +25 -8
- package/src/astro/blocks/Gallery.astro +40 -0
- package/src/astro/blocks/Hero.astro +61 -0
- package/src/astro/blocks/ImageCompare.astro +5 -2
- package/src/astro/blocks/Marquee.astro +38 -0
- package/src/astro/blocks/value.test.ts +21 -0
- package/src/astro/blocks/value.ts +26 -1
- package/src/astro/index.ts +10 -0
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
312
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
var version = "0.3.9";
|
|
869
|
+
// package.json
|
|
870
|
+
var version = "1.0.0";
|
|
374
871
|
|
|
375
|
-
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|