@getvitops/emdash 0.2.3 → 0.3.1
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 +62 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +123 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -2
- package/src/astro/blocks/ActionLink.astro +77 -0
- package/src/astro/index.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,67 @@
|
|
|
1
1
|
# @getvitops/emdash
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [20e518e]
|
|
8
|
+
- Updated dependencies [9bf975a]
|
|
9
|
+
- @getvitops/utils@2.1.0
|
|
10
|
+
|
|
11
|
+
## 0.3.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- 04a51d8: New `vitops.actionLink` block: a link or button with optional icons at either end.
|
|
16
|
+
|
|
17
|
+
Editors get a label, URL, style (link / button / call-to-action), colour role, new-tab toggle, and
|
|
18
|
+
searchable pickers for a start and end icon. Icons render from the generated SVG sprite, so the
|
|
19
|
+
block needs no JavaScript, and they use the sprite's set-independent aliases — content authored in
|
|
20
|
+
the CMS survives the site changing icon sets.
|
|
21
|
+
|
|
22
|
+
Fields are flattened (`label`, `href`, … as siblings rather than a nested object) because Block Kit
|
|
23
|
+
has no object-group element. The icon pickers are comboboxes over the semantic name list for the
|
|
24
|
+
same reason: Block Kit's element union is closed, so a richer icon picker isn't mountable inside a
|
|
25
|
+
block modal today.
|
|
26
|
+
|
|
27
|
+
- bf453b0: **Breaking:** this package now depends on the toolchain instead of pretending it doesn't.
|
|
28
|
+
|
|
29
|
+
It shipped with no `@getvitops/*` dependency at all, on the reasoning that the independence let it
|
|
30
|
+
version separately from the fixed toolchain group. That reasoning stopped being true when the blocks
|
|
31
|
+
started rendering from the generated SVG sprite: `vitops.actionLink` emits
|
|
32
|
+
`<use href="/vitops/icons.svg#icon-menu">`, and those ids only exist if the site's toolchain built
|
|
33
|
+
the sprite with the matching semantic vocabulary. Installed against an older toolchain the block
|
|
34
|
+
rendered an **empty box** — no install-time error, no console warning, nothing to grep for.
|
|
35
|
+
|
|
36
|
+
Two changes make the coupling real:
|
|
37
|
+
- **`@getvitops/utils` is a hard dependency**, and `SEMANTIC_ICON_OPTIONS` is now derived from its
|
|
38
|
+
`iconMap` rather than generated into `src/icon-options.ts` by a script. The editor's icon list and
|
|
39
|
+
the sprite's aliases are one source, so they cannot drift. `pnpm gen:icons` is gone; there is
|
|
40
|
+
nothing left to regenerate.
|
|
41
|
+
- **`@getvitops/astro` is a peer** (`>=2.0.0`). It is the integration that emits the sprite into
|
|
42
|
+
`public/`, and it belongs to the site — a hard dependency would let a version mismatch install two
|
|
43
|
+
copies of an Astro integration rather than telling you about it.
|
|
44
|
+
|
|
45
|
+
**Migration:** install or upgrade `@getvitops/astro` (and the rest of the toolchain, which moves in
|
|
46
|
+
lockstep) to `2.x` alongside this release. If your package manager reports an unmet peer on
|
|
47
|
+
`@getvitops/astro`, that is this change working — it is the error that replaced the empty box.
|
|
48
|
+
|
|
49
|
+
### Patch Changes
|
|
50
|
+
|
|
51
|
+
- Updated dependencies [4756788]
|
|
52
|
+
- Updated dependencies [bf453b0]
|
|
53
|
+
- Updated dependencies [bf453b0]
|
|
54
|
+
- Updated dependencies [bf453b0]
|
|
55
|
+
- Updated dependencies [bf453b0]
|
|
56
|
+
- Updated dependencies [04a51d8]
|
|
57
|
+
- Updated dependencies [bf453b0]
|
|
58
|
+
- Updated dependencies [bf453b0]
|
|
59
|
+
- Updated dependencies [bf453b0]
|
|
60
|
+
- Updated dependencies [feae1a3]
|
|
61
|
+
- Updated dependencies [bf453b0]
|
|
62
|
+
- @getvitops/astro@2.0.0
|
|
63
|
+
- @getvitops/utils@2.0.0
|
|
64
|
+
|
|
3
65
|
## 0.2.3
|
|
4
66
|
|
|
5
67
|
### Patch Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -4,7 +4,7 @@ import { AstroIntegration } from "astro";
|
|
|
4
4
|
//#region src/blocks.d.ts
|
|
5
5
|
/** The v1 subset of EmDash Block Kit field elements used by these blocks. */
|
|
6
6
|
type BlockKitFieldDef = Extract<NonNullable<PortableTextBlockConfig['fields']>[number], {
|
|
7
|
-
type: 'text_input' | 'number_input' | 'select' | 'toggle';
|
|
7
|
+
type: 'text_input' | 'number_input' | 'select' | 'toggle' | 'combobox';
|
|
8
8
|
}>;
|
|
9
9
|
type PortableTextBlockDef = Omit<PortableTextBlockConfig, 'fields'> & {
|
|
10
10
|
fields?: BlockKitFieldDef[];
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/blocks.ts","../src/hosting.ts","../src/index.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/blocks.ts","../src/hosting.ts","../src/index.ts"],"mappings":";;;;;KAgBY,gBAAA,GAAmB,OAAA,CAC7B,WAAA,CAAY,uBAAA;EACV,IAAA;AAAA;AAAA,KAGQ,oBAAA,GAAuB,IAAA,CAAK,uBAAA;EACtC,MAAA,GAAS,gBAAA;AAAA;AAAA,cAGE,MAAA,EAAQ,oBAAoB;;;KCO7B,aAAA;AAAA,UAIK,oBAAA;EDwJhB;ECtJC,MAAA,GAAS,aAAa;EACtB,UAAA;IDduC,yDCgBrC,SAAA;IAEA,YAAA,WAXQ;IAaR,OAAA;EAAA;EAEF,IAAA;IAfuB,2EAiBrB,WAAA,WAbiC;IAejC,QAAA,YAboB;IAepB,UAAA,WAfO;IAiBP,OAAA;EAAA;AAAA;AAAA,UAIa,aAAA;EACf,MAAA,EAAQ,aAAA;EACR,OAAA,EAAS,gBAAgB;EAVvB;EAYF,QAAA;EARE;EAUF,OAAA;AAAA;AAAA,iBAoBoB,aAAA,CAAc,OAAA,GAAS,oBAAA,GAA4B,OAAA,CAAQ,aAAA;;;UCxChE,mBAAA;;;;;;;;;;;EAWf,OAAA;EFtCM;EEwCN,MAAM;AAAA;;;;;iBAOQ,YAAA,CAAa,OAAA,GAAS,mBAAA,GAA2B,gBAAgB;;iBAajE,YAAA,CAAa,OAAA,GAAS,mBAAwB,oBAAA,cAAA,kBAAA,mBAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,129 @@
|
|
|
1
1
|
import { definePlugin } from "emdash";
|
|
2
|
+
import { iconMap } from "@getvitops/utils";
|
|
2
3
|
|
|
4
|
+
//#region src/icon-options.ts
|
|
5
|
+
/**
|
|
6
|
+
* Semantic icon names, as Block Kit combobox options.
|
|
7
|
+
*
|
|
8
|
+
* Derived from `@getvitops/utils`' `iconMap` at import time. This used to be a
|
|
9
|
+
* generated copy, so that the package carried no runtime `@getvitops/*`
|
|
10
|
+
* dependency and could version independently of the fixed toolchain group —
|
|
11
|
+
* but that independence was already fiction: the blocks render from the
|
|
12
|
+
* toolchain's SVG sprite (`<use href="…/icons.svg#icon-menu">`), so an emdash
|
|
13
|
+
* built against one icon vocabulary and a sprite built against another produce
|
|
14
|
+
* empty boxes, silently and with no install-time error. Depending on the real
|
|
15
|
+
* map makes the vocabulary one thing rather than two that can drift.
|
|
16
|
+
*
|
|
17
|
+
* `fa7` is the reference collection: every semantic name is defined there, and
|
|
18
|
+
* the other sets are alternate resolutions of the same names.
|
|
19
|
+
*/
|
|
20
|
+
const SEMANTIC_ICON_OPTIONS = Object.keys(iconMap.fa7).sort().map((name) => ({
|
|
21
|
+
label: name,
|
|
22
|
+
value: name
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
3
26
|
//#region src/blocks.ts
|
|
4
27
|
const BLOCKS = [
|
|
28
|
+
{
|
|
29
|
+
/**
|
|
30
|
+
* A link or button, optionally with an icon at either end.
|
|
31
|
+
*
|
|
32
|
+
* Flattened on purpose: Block Kit has no object-group element, so a shape
|
|
33
|
+
* like `{ label, url }` becomes sibling fields. Same constraint the
|
|
34
|
+
* marketing-blocks plugin documents.
|
|
35
|
+
*
|
|
36
|
+
* The icons are `combobox` — a searchable typeahead over a STATIC option
|
|
37
|
+
* list, which is exactly what the semantic name list is. It is the closest
|
|
38
|
+
* fit Block Kit offers: its element union is closed (no custom element, no
|
|
39
|
+
* raw HTML), so `<icon-picker>` cannot be mounted inside a block modal
|
|
40
|
+
* however much nicer it would be. Revisit if EmDash opens that union.
|
|
41
|
+
*/
|
|
42
|
+
type: "vitops.actionLink",
|
|
43
|
+
label: "Link or button",
|
|
44
|
+
icon: "link",
|
|
45
|
+
placeholder: "A link or button, with optional icons",
|
|
46
|
+
fields: [
|
|
47
|
+
{
|
|
48
|
+
type: "text_input",
|
|
49
|
+
action_id: "label",
|
|
50
|
+
label: "Label"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: "text_input",
|
|
54
|
+
action_id: "href",
|
|
55
|
+
label: "URL"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: "select",
|
|
59
|
+
action_id: "variant",
|
|
60
|
+
label: "Style",
|
|
61
|
+
initial_value: "link",
|
|
62
|
+
options: [
|
|
63
|
+
{
|
|
64
|
+
label: "Link",
|
|
65
|
+
value: "link"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
label: "Button",
|
|
69
|
+
value: "btn"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
label: "Call to action",
|
|
73
|
+
value: "cta"
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: "combobox",
|
|
79
|
+
action_id: "startIcon",
|
|
80
|
+
label: "Icon before the label",
|
|
81
|
+
placeholder: "None",
|
|
82
|
+
options: SEMANTIC_ICON_OPTIONS
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: "combobox",
|
|
86
|
+
action_id: "endIcon",
|
|
87
|
+
label: "Icon after the label",
|
|
88
|
+
placeholder: "None",
|
|
89
|
+
options: SEMANTIC_ICON_OPTIONS
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
type: "select",
|
|
93
|
+
action_id: "role",
|
|
94
|
+
label: "Colour role",
|
|
95
|
+
initial_value: "",
|
|
96
|
+
options: [
|
|
97
|
+
{
|
|
98
|
+
label: "Default",
|
|
99
|
+
value: ""
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
label: "Success",
|
|
103
|
+
value: "success"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
label: "Danger",
|
|
107
|
+
value: "danger"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
label: "Warning",
|
|
111
|
+
value: "warning"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
label: "Info",
|
|
115
|
+
value: "info"
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
type: "toggle",
|
|
121
|
+
action_id: "newTab",
|
|
122
|
+
label: "Open in a new tab",
|
|
123
|
+
initial_value: false
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
},
|
|
5
127
|
{
|
|
6
128
|
type: "vitops.imageCompare",
|
|
7
129
|
label: "Image compare",
|
|
@@ -236,7 +358,7 @@ async function vitopsHosting(options = {}) {
|
|
|
236
358
|
|
|
237
359
|
//#endregion
|
|
238
360
|
//#region package.json
|
|
239
|
-
var version = "0.
|
|
361
|
+
var version = "0.3.1";
|
|
240
362
|
|
|
241
363
|
//#endregion
|
|
242
364
|
//#region src/index.ts
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["pkg.version"],"sources":["../src/blocks.ts","../src/hosting.ts","../package.json","../src/index.ts"],"sourcesContent":["/**\n * Portable Text block-type declarations for the EmDash editor.\n *\n * v1 exposes only Vitops patterns whose data model fits EmDash's flat Block Kit\n * fields (text_input / number_input / select / toggle). Repeating structured\n * patterns (Cards, wc-entries, FAQ, forms) go through EmDash Sections or\n * Field Kit `list` widgets on json collection fields instead — see the README.\n *\n * Every `action_id` here must have a matching prop consumed by the block's\n * .astro component in `src/astro/blocks/` (enforced by blocks.test.ts).\n */\n\nimport type { PortableTextBlockConfig } from 'emdash';\n\n/** The v1 subset of EmDash Block Kit field elements used by these blocks. */\nexport type BlockKitFieldDef = Extract<\n NonNullable<PortableTextBlockConfig['fields']>[number],\n { type: 'text_input' | 'number_input' | 'select' | 'toggle' }\n>;\n\nexport type PortableTextBlockDef = Omit<PortableTextBlockConfig, 'fields'> & {\n fields?: BlockKitFieldDef[];\n};\n\nexport const BLOCKS: PortableTextBlockDef[] = [\n {\n type: 'vitops.imageCompare',\n label: 'Image compare',\n placeholder: 'Before/after image slider',\n fields: [\n { type: 'text_input', action_id: 'before', label: 'Before image URL' },\n { type: 'text_input', action_id: 'beforeAlt', label: 'Before alt text' },\n {\n type: 'text_input',\n action_id: 'beforeLabel',\n label: 'Before label',\n initial_value: 'Before',\n },\n { type: 'text_input', action_id: 'after', label: 'After image URL' },\n { type: 'text_input', action_id: 'afterAlt', label: 'After alt text' },\n { type: 'text_input', action_id: 'afterLabel', label: 'After label', initial_value: 'After' },\n ],\n },\n {\n type: 'vitops.copyButton',\n label: 'Copy snippet',\n icon: 'code',\n placeholder: 'Text with a copy-to-clipboard button',\n fields: [\n { type: 'text_input', action_id: 'text', label: 'Text to copy', multiline: true },\n { type: 'text_input', action_id: 'label', label: 'Button label', initial_value: 'Copy' },\n {\n type: 'select',\n action_id: 'display',\n label: 'Display',\n initial_value: 'code-block',\n options: [\n { label: 'Code block', value: 'code-block' },\n { label: 'Inline', value: 'inline' },\n ],\n },\n ],\n },\n {\n type: 'vitops.banner',\n label: 'Banner',\n placeholder: 'Announcement or notice',\n fields: [\n { type: 'text_input', action_id: 'message', label: 'Message', multiline: true },\n {\n type: 'select',\n action_id: 'tone',\n label: 'Tone',\n initial_value: 'info',\n options: [\n { label: 'Info', value: 'info' },\n { label: 'Success', value: 'success' },\n { label: 'Warning', value: 'warning' },\n { label: 'Danger', value: 'danger' },\n ],\n },\n { type: 'toggle', action_id: 'dismissible', label: 'Dismissible', initial_value: true },\n ],\n },\n {\n type: 'vitops.details',\n label: 'Disclosure',\n placeholder: 'Collapsible details/summary',\n fields: [\n { type: 'text_input', action_id: 'summary', label: 'Summary (always visible)' },\n { type: 'text_input', action_id: 'body', label: 'Body (revealed)', multiline: true },\n { type: 'toggle', action_id: 'open', label: 'Open by default', initial_value: false },\n ],\n },\n {\n type: 'vitops.carousel',\n label: 'Carousel',\n placeholder: 'Image carousel',\n fields: [\n {\n type: 'text_input',\n action_id: 'images',\n label: 'Image URLs (one per line)',\n multiline: true,\n },\n {\n type: 'text_input',\n action_id: 'alts',\n label: 'Alt texts (one per line, same order)',\n multiline: true,\n },\n {\n type: 'text_input',\n action_id: 'label',\n label: 'Accessible name',\n initial_value: 'Carousel',\n },\n {\n type: 'number_input',\n action_id: 'autoplay',\n label: 'Autoplay interval (ms, 0 = off)',\n initial_value: 0,\n },\n ],\n },\n];\n","/**\n * Hosting seam — one call in astro.config.mjs resolves the adapter + EmDash\n * database/storage for the chosen hosting target, so a site can start on\n * Cloudflare and move to a Node host (VPS / docker-compose / k8s) — or back —\n * by flipping a single value instead of rewiring its config:\n *\n * ```js\n * import { vitopsEmdash, vitopsHosting } from '@getvitops/emdash';\n * const { adapter, database, storage } = await vitopsHosting();\n * export default defineConfig({\n * output: 'server',\n * adapter,\n * integrations: [react(), emdash({ database, storage, plugins: [vitopsEmdash()] })],\n * });\n * ```\n *\n * Targets:\n * - `'cloudflare'` (default) — `@astrojs/cloudflare` + D1/R2 bindings.\n * Needs `@astrojs/cloudflare` and `@emdash-cms/cloudflare` installed.\n * - `'node'` — `@astrojs/node` (standalone) + SQLite file + local uploads.\n * Needs `@astrojs/node` and `better-sqlite3` installed. Scheduled publishing\n * runs in-process on Node (no worker/cron trigger needed). For production,\n * move storage to S3-compatible (`s3()` from `emdash/astro`) and/or the\n * database to Postgres (`postgres()` from `emdash/db`) via `options.node`.\n *\n * The adapter packages are optional peer dependencies, resolved lazily — a\n * site only installs the stack for the target it uses. Content lives in the\n * database and media in the storage backend, so switching hosts is a data\n * migration (D1 export / EmDash seed round-trip + media copy), not a rewrite.\n */\nimport type { AstroIntegration } from 'astro';\n\nexport type HostingTarget = 'cloudflare' | 'node';\n\nconst TARGETS: HostingTarget[] = ['cloudflare', 'node'];\n\nexport interface VitopsHostingOptions {\n /** Hosting target. Overridden by the `HOSTING` env var. Default 'cloudflare'. */\n target?: HostingTarget;\n cloudflare?: {\n /** D1 binding name in wrangler config (default 'DB'). */\n dbBinding?: string;\n /** R2 binding name in wrangler config (default 'MEDIA'). */\n mediaBinding?: string;\n /** D1 Sessions API read-replication mode (default 'auto'). */\n session?: string;\n };\n node?: {\n /** SQLite URL (default `DATABASE_PATH` env or 'file:./data/emdash.db'). */\n databaseUrl?: string;\n /** Full database descriptor override (e.g. postgres() from 'emdash/db'). */\n database?: unknown;\n /** Local uploads directory (default './data/uploads'). */\n uploadsDir?: string;\n /** Full storage descriptor override (e.g. s3() from 'emdash/astro'). */\n storage?: unknown;\n };\n}\n\nexport interface VitopsHosting {\n target: HostingTarget;\n adapter: AstroIntegration;\n /** EmDash database descriptor — pass to emdash({ database }). */\n database: unknown;\n /** EmDash storage descriptor — pass to emdash({ storage }). */\n storage: unknown;\n}\n\n/**\n * Import an optional hosting dependency. The specifier is passed as a\n * variable so bundlers/type-checkers leave it as a plain runtime import —\n * these packages are optional peers a consumer installs per target.\n */\nasync function importFor(target: HostingTarget, specifier: string, install: string) {\n try {\n return await import(/* @vite-ignore */ specifier);\n } catch (cause) {\n throw new Error(\n `[vitops] hosting target '${target}' needs ${specifier}, which isn't installed.\\n` +\n `Install the ${target} stack first: ${install}`,\n { cause },\n );\n }\n}\n\nexport async function vitopsHosting(options: VitopsHostingOptions = {}): Promise<VitopsHosting> {\n const target = (process.env.HOSTING as HostingTarget) || options.target || 'cloudflare';\n\n if (target === 'cloudflare') {\n const install = 'pnpm add @astrojs/cloudflare @emdash-cms/cloudflare';\n const cf = options.cloudflare ?? {};\n const { default: cloudflare } = await importFor(target, '@astrojs/cloudflare', install);\n const { d1, r2 } = await importFor(target, '@emdash-cms/cloudflare', install);\n return {\n target,\n adapter: cloudflare(),\n database: d1({ binding: cf.dbBinding ?? 'DB', session: cf.session ?? 'auto' }),\n storage: r2({ binding: cf.mediaBinding ?? 'MEDIA' }),\n };\n }\n\n if (target === 'node') {\n const install = 'pnpm add @astrojs/node better-sqlite3';\n const opts = options.node ?? {};\n const { default: node } = await importFor(target, '@astrojs/node', install);\n // Required peer of this package, so always present — but still routed\n // through importFor: a literal import('emdash/db') would be statically\n // analyzable, and bundling the plugin runtime would drag EmDash's whole\n // integration graph into the site's server/client bundles.\n const { sqlite } = await importFor(target, 'emdash/db', install);\n const { local } = await importFor(target, 'emdash/astro', install);\n const { mkdirSync } = await import('node:fs');\n const { dirname } = await import('node:path');\n\n const databaseUrl = opts.databaseUrl ?? process.env.DATABASE_PATH ?? 'file:./data/emdash.db';\n const uploadsDir = opts.uploadsDir ?? './data/uploads';\n // better-sqlite3 refuses to create parent directories, so make sure the\n // defaults exist (runs at astro config load — dev and build). A deployed\n // host still needs the data directory mounted/persisted as a volume.\n if (!opts.database && databaseUrl.startsWith('file:')) {\n mkdirSync(dirname(databaseUrl.slice('file:'.length)), { recursive: true });\n }\n if (!opts.storage) mkdirSync(uploadsDir, { recursive: true });\n\n return {\n target,\n adapter: node({ mode: 'standalone' }),\n database: opts.database ?? sqlite({ url: databaseUrl }),\n storage: opts.storage ?? local({ directory: uploadsDir, baseUrl: '/_emdash/api/media/file' }),\n };\n }\n\n throw new Error(\n `[vitops] unknown hosting target '${target}' (from ${process.env.HOSTING ? 'HOSTING env var' : 'options.target'}). ` +\n `Valid targets: ${TARGETS.join(', ')}.`,\n );\n}\n","","/**\n * @getvitops/emdash — EmDash CMS native plugin for the Vitops design system.\n *\n * Declares Vitops Portable Text block types (editable from the EmDash admin's\n * slash menu) and ships the Astro components that render them via the\n * `componentsEntry` (`@getvitops/emdash/astro`).\n *\n * Composes with — does not replace — the `getvitops()` Astro integration from\n * `@getvitops/astro`: the integration owns design-system CSS generation and\n * copies the web-component bundles into `public/<wcBase>/`; this plugin only\n * adds the editor-facing layer. Register both in the consumer site:\n *\n * ```js\n * // astro.config.mjs\n * integrations: [\n * react(),\n * getvitops({ css: {...} }),\n * emdash({ database, storage, plugins: [vitopsEmdash()] }),\n * ]\n * ```\n */\nimport { definePlugin } from 'emdash';\nimport type { PluginDescriptor } from 'emdash';\nimport { BLOCKS } from './blocks.ts';\n\nexport { BLOCKS } from './blocks.ts';\nexport type { BlockKitFieldDef, PortableTextBlockDef } from './blocks.ts';\nexport { vitopsHosting } from './hosting.ts';\nexport type { HostingTarget, VitopsHosting, VitopsHostingOptions } from './hosting.ts';\n\n/**\n * Read from package.json rather than hand-maintained.\n *\n * This was a literal with a \"keep in sync\" comment, which drifted on every\n * release: `changeset version` bumps package.json and leaves the literal alone.\n * `blocks.test.ts` asserts they match, but the release chain didn't run tests,\n * so `@getvitops/emdash@0.2.1` shipped a descriptor reporting `0.2.0`. Deriving\n * it removes the failure mode instead of relying on remembering.\n */\nimport pkg from '../package.json' with { type: 'json' };\n\nconst VERSION = pkg.version;\nconst PLUGIN_ID = 'vitops';\nconst FRAGMENTS_CAPABILITY = 'hooks.page-fragments:register';\n\nexport interface VitopsEmdashOptions {\n /**\n * How the web-component runtime scripts reach the page:\n * - `'integration'` (default) — the site's layout renders `<Head />` from\n * `@getvitops/astro`, which emits the script tags; this plugin adds none.\n * - `'fragments'` — for layouts using EmDash's `<EmDashHead/>`/`<EmDashBodyEnd/>`\n * instead: the plugin injects the script tags via the `page:fragments` hook.\n * Either way the bundles themselves must exist under `public/<wcBase>/`,\n * which the `getvitops()` integration provides (webComponents: true).\n * Do not enable both delivery paths — the scripts would load twice.\n */\n scripts?: 'integration' | 'fragments';\n /** Public base path of the web-component bundles (default '/vitops'). */\n wcBase?: string;\n}\n\n/**\n * Descriptor factory — import in `astro.config.mjs` and pass to the emdash()\n * integration's `plugins` array.\n */\nexport function vitopsEmdash(options: VitopsEmdashOptions = {}): PluginDescriptor {\n return {\n id: PLUGIN_ID,\n version: VERSION,\n format: 'native',\n entrypoint: '@getvitops/emdash',\n componentsEntry: '@getvitops/emdash/astro',\n options,\n capabilities: options.scripts === 'fragments' ? [FRAGMENTS_CAPABILITY] : [],\n } as PluginDescriptor;\n}\n\n/** Runtime side — EmDash imports and calls this from the descriptor's entrypoint. */\nexport function createPlugin(options: VitopsEmdashOptions = {}) {\n const scripts = options.scripts ?? 'integration';\n const wcBase = options.wcBase ?? '/vitops';\n\n return definePlugin({\n id: PLUGIN_ID,\n version: VERSION,\n capabilities: scripts === 'fragments' ? [FRAGMENTS_CAPABILITY] : [],\n\n hooks:\n scripts === 'fragments'\n ? {\n // Mirrors @getvitops/astro Head.astro: module polyfills + elements\n // in head, deferred enhancements at end of body.\n 'page:fragments': async () => [\n {\n kind: 'html',\n placement: 'head',\n html:\n `<link rel=\"modulepreload\" href=\"${wcBase}/polyfills.js\">` +\n `<script type=\"module\" src=\"${wcBase}/polyfills.js\"></script>` +\n `<script type=\"module\" src=\"${wcBase}/elements.js\"></script>`,\n key: 'vitops-wc',\n },\n {\n kind: 'external-script',\n placement: 'body:end',\n src: `${wcBase}/deferred.js`,\n defer: true,\n key: 'vitops-deferred',\n },\n ],\n }\n : {},\n\n admin: {\n portableTextBlocks: BLOCKS,\n },\n });\n}\n\nexport default createPlugin;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["pkg.version"],"sources":["../src/icon-options.ts","../src/blocks.ts","../src/hosting.ts","../package.json","../src/index.ts"],"sourcesContent":["/**\n * Semantic icon names, as Block Kit combobox options.\n *\n * Derived from `@getvitops/utils`' `iconMap` at import time. This used to be a\n * generated copy, so that the package carried no runtime `@getvitops/*`\n * dependency and could version independently of the fixed toolchain group —\n * but that independence was already fiction: the blocks render from the\n * toolchain's SVG sprite (`<use href=\"…/icons.svg#icon-menu\">`), so an emdash\n * built against one icon vocabulary and a sprite built against another produce\n * empty boxes, silently and with no install-time error. Depending on the real\n * map makes the vocabulary one thing rather than two that can drift.\n *\n * `fa7` is the reference collection: every semantic name is defined there, and\n * the other sets are alternate resolutions of the same names.\n */\nimport { iconMap } from '@getvitops/utils';\n\nexport const SEMANTIC_ICON_OPTIONS: { label: string; value: string }[] = Object.keys(iconMap.fa7)\n .sort()\n // Label and value are the same string on purpose: the editor picks a semantic\n // name, and showing the set-specific glyph id would leak the icon set into\n // authored content — the thing the semantic layer exists to prevent.\n .map((name) => ({ label: name, value: name }));\n","/**\n * Portable Text block-type declarations for the EmDash editor.\n *\n * v1 exposes only Vitops patterns whose data model fits EmDash's flat Block Kit\n * fields (text_input / number_input / select / toggle). Repeating structured\n * patterns (Cards, wc-entries, FAQ, forms) go through EmDash Sections or\n * Field Kit `list` widgets on json collection fields instead — see the README.\n *\n * Every `action_id` here must have a matching prop consumed by the block's\n * .astro component in `src/astro/blocks/` (enforced by blocks.test.ts).\n */\n\nimport type { PortableTextBlockConfig } from 'emdash';\nimport { SEMANTIC_ICON_OPTIONS } from './icon-options.ts';\n\n/** The v1 subset of EmDash Block Kit field elements used by these blocks. */\nexport type BlockKitFieldDef = Extract<\n NonNullable<PortableTextBlockConfig['fields']>[number],\n { type: 'text_input' | 'number_input' | 'select' | 'toggle' | 'combobox' }\n>;\n\nexport type PortableTextBlockDef = Omit<PortableTextBlockConfig, 'fields'> & {\n fields?: BlockKitFieldDef[];\n};\n\nexport const BLOCKS: PortableTextBlockDef[] = [\n {\n /**\n * A link or button, optionally with an icon at either end.\n *\n * Flattened on purpose: Block Kit has no object-group element, so a shape\n * like `{ label, url }` becomes sibling fields. Same constraint the\n * marketing-blocks plugin documents.\n *\n * The icons are `combobox` — a searchable typeahead over a STATIC option\n * list, which is exactly what the semantic name list is. It is the closest\n * fit Block Kit offers: its element union is closed (no custom element, no\n * raw HTML), so `<icon-picker>` cannot be mounted inside a block modal\n * however much nicer it would be. Revisit if EmDash opens that union.\n */\n type: 'vitops.actionLink',\n label: 'Link or button',\n icon: 'link',\n placeholder: 'A link or button, with optional icons',\n fields: [\n { type: 'text_input', action_id: 'label', label: 'Label' },\n { type: 'text_input', action_id: 'href', label: 'URL' },\n {\n type: 'select',\n action_id: 'variant',\n label: 'Style',\n initial_value: 'link',\n options: [\n { label: 'Link', value: 'link' },\n { label: 'Button', value: 'btn' },\n { label: 'Call to action', value: 'cta' },\n ],\n },\n {\n type: 'combobox',\n action_id: 'startIcon',\n label: 'Icon before the label',\n placeholder: 'None',\n options: SEMANTIC_ICON_OPTIONS,\n },\n {\n type: 'combobox',\n action_id: 'endIcon',\n label: 'Icon after the label',\n placeholder: 'None',\n options: SEMANTIC_ICON_OPTIONS,\n },\n {\n type: 'select',\n action_id: 'role',\n label: 'Colour role',\n initial_value: '',\n options: [\n { label: 'Default', value: '' },\n { label: 'Success', value: 'success' },\n { label: 'Danger', value: 'danger' },\n { label: 'Warning', value: 'warning' },\n { label: 'Info', value: 'info' },\n ],\n },\n { type: 'toggle', action_id: 'newTab', label: 'Open in a new tab', initial_value: false },\n ],\n },\n {\n type: 'vitops.imageCompare',\n label: 'Image compare',\n placeholder: 'Before/after image slider',\n fields: [\n { type: 'text_input', action_id: 'before', label: 'Before image URL' },\n { type: 'text_input', action_id: 'beforeAlt', label: 'Before alt text' },\n {\n type: 'text_input',\n action_id: 'beforeLabel',\n label: 'Before label',\n initial_value: 'Before',\n },\n { type: 'text_input', action_id: 'after', label: 'After image URL' },\n { type: 'text_input', action_id: 'afterAlt', label: 'After alt text' },\n { type: 'text_input', action_id: 'afterLabel', label: 'After label', initial_value: 'After' },\n ],\n },\n {\n type: 'vitops.copyButton',\n label: 'Copy snippet',\n icon: 'code',\n placeholder: 'Text with a copy-to-clipboard button',\n fields: [\n { type: 'text_input', action_id: 'text', label: 'Text to copy', multiline: true },\n { type: 'text_input', action_id: 'label', label: 'Button label', initial_value: 'Copy' },\n {\n type: 'select',\n action_id: 'display',\n label: 'Display',\n initial_value: 'code-block',\n options: [\n { label: 'Code block', value: 'code-block' },\n { label: 'Inline', value: 'inline' },\n ],\n },\n ],\n },\n {\n type: 'vitops.banner',\n label: 'Banner',\n placeholder: 'Announcement or notice',\n fields: [\n { type: 'text_input', action_id: 'message', label: 'Message', multiline: true },\n {\n type: 'select',\n action_id: 'tone',\n label: 'Tone',\n initial_value: 'info',\n options: [\n { label: 'Info', value: 'info' },\n { label: 'Success', value: 'success' },\n { label: 'Warning', value: 'warning' },\n { label: 'Danger', value: 'danger' },\n ],\n },\n { type: 'toggle', action_id: 'dismissible', label: 'Dismissible', initial_value: true },\n ],\n },\n {\n type: 'vitops.details',\n label: 'Disclosure',\n placeholder: 'Collapsible details/summary',\n fields: [\n { type: 'text_input', action_id: 'summary', label: 'Summary (always visible)' },\n { type: 'text_input', action_id: 'body', label: 'Body (revealed)', multiline: true },\n { type: 'toggle', action_id: 'open', label: 'Open by default', initial_value: false },\n ],\n },\n {\n type: 'vitops.carousel',\n label: 'Carousel',\n placeholder: 'Image carousel',\n fields: [\n {\n type: 'text_input',\n action_id: 'images',\n label: 'Image URLs (one per line)',\n multiline: true,\n },\n {\n type: 'text_input',\n action_id: 'alts',\n label: 'Alt texts (one per line, same order)',\n multiline: true,\n },\n {\n type: 'text_input',\n action_id: 'label',\n label: 'Accessible name',\n initial_value: 'Carousel',\n },\n {\n type: 'number_input',\n action_id: 'autoplay',\n label: 'Autoplay interval (ms, 0 = off)',\n initial_value: 0,\n },\n ],\n },\n];\n","/**\n * Hosting seam — one call in astro.config.mjs resolves the adapter + EmDash\n * database/storage for the chosen hosting target, so a site can start on\n * Cloudflare and move to a Node host (VPS / docker-compose / k8s) — or back —\n * by flipping a single value instead of rewiring its config:\n *\n * ```js\n * import { vitopsEmdash, vitopsHosting } from '@getvitops/emdash';\n * const { adapter, database, storage } = await vitopsHosting();\n * export default defineConfig({\n * output: 'server',\n * adapter,\n * integrations: [react(), emdash({ database, storage, plugins: [vitopsEmdash()] })],\n * });\n * ```\n *\n * Targets:\n * - `'cloudflare'` (default) — `@astrojs/cloudflare` + D1/R2 bindings.\n * Needs `@astrojs/cloudflare` and `@emdash-cms/cloudflare` installed.\n * - `'node'` — `@astrojs/node` (standalone) + SQLite file + local uploads.\n * Needs `@astrojs/node` and `better-sqlite3` installed. Scheduled publishing\n * runs in-process on Node (no worker/cron trigger needed). For production,\n * move storage to S3-compatible (`s3()` from `emdash/astro`) and/or the\n * database to Postgres (`postgres()` from `emdash/db`) via `options.node`.\n *\n * The adapter packages are optional peer dependencies, resolved lazily — a\n * site only installs the stack for the target it uses. Content lives in the\n * database and media in the storage backend, so switching hosts is a data\n * migration (D1 export / EmDash seed round-trip + media copy), not a rewrite.\n */\nimport type { AstroIntegration } from 'astro';\n\nexport type HostingTarget = 'cloudflare' | 'node';\n\nconst TARGETS: HostingTarget[] = ['cloudflare', 'node'];\n\nexport interface VitopsHostingOptions {\n /** Hosting target. Overridden by the `HOSTING` env var. Default 'cloudflare'. */\n target?: HostingTarget;\n cloudflare?: {\n /** D1 binding name in wrangler config (default 'DB'). */\n dbBinding?: string;\n /** R2 binding name in wrangler config (default 'MEDIA'). */\n mediaBinding?: string;\n /** D1 Sessions API read-replication mode (default 'auto'). */\n session?: string;\n };\n node?: {\n /** SQLite URL (default `DATABASE_PATH` env or 'file:./data/emdash.db'). */\n databaseUrl?: string;\n /** Full database descriptor override (e.g. postgres() from 'emdash/db'). */\n database?: unknown;\n /** Local uploads directory (default './data/uploads'). */\n uploadsDir?: string;\n /** Full storage descriptor override (e.g. s3() from 'emdash/astro'). */\n storage?: unknown;\n };\n}\n\nexport interface VitopsHosting {\n target: HostingTarget;\n adapter: AstroIntegration;\n /** EmDash database descriptor — pass to emdash({ database }). */\n database: unknown;\n /** EmDash storage descriptor — pass to emdash({ storage }). */\n storage: unknown;\n}\n\n/**\n * Import an optional hosting dependency. The specifier is passed as a\n * variable so bundlers/type-checkers leave it as a plain runtime import —\n * these packages are optional peers a consumer installs per target.\n */\nasync function importFor(target: HostingTarget, specifier: string, install: string) {\n try {\n return await import(/* @vite-ignore */ specifier);\n } catch (cause) {\n throw new Error(\n `[vitops] hosting target '${target}' needs ${specifier}, which isn't installed.\\n` +\n `Install the ${target} stack first: ${install}`,\n { cause },\n );\n }\n}\n\nexport async function vitopsHosting(options: VitopsHostingOptions = {}): Promise<VitopsHosting> {\n const target = (process.env.HOSTING as HostingTarget) || options.target || 'cloudflare';\n\n if (target === 'cloudflare') {\n const install = 'pnpm add @astrojs/cloudflare @emdash-cms/cloudflare';\n const cf = options.cloudflare ?? {};\n const { default: cloudflare } = await importFor(target, '@astrojs/cloudflare', install);\n const { d1, r2 } = await importFor(target, '@emdash-cms/cloudflare', install);\n return {\n target,\n adapter: cloudflare(),\n database: d1({ binding: cf.dbBinding ?? 'DB', session: cf.session ?? 'auto' }),\n storage: r2({ binding: cf.mediaBinding ?? 'MEDIA' }),\n };\n }\n\n if (target === 'node') {\n const install = 'pnpm add @astrojs/node better-sqlite3';\n const opts = options.node ?? {};\n const { default: node } = await importFor(target, '@astrojs/node', install);\n // Required peer of this package, so always present — but still routed\n // through importFor: a literal import('emdash/db') would be statically\n // analyzable, and bundling the plugin runtime would drag EmDash's whole\n // integration graph into the site's server/client bundles.\n const { sqlite } = await importFor(target, 'emdash/db', install);\n const { local } = await importFor(target, 'emdash/astro', install);\n const { mkdirSync } = await import('node:fs');\n const { dirname } = await import('node:path');\n\n const databaseUrl = opts.databaseUrl ?? process.env.DATABASE_PATH ?? 'file:./data/emdash.db';\n const uploadsDir = opts.uploadsDir ?? './data/uploads';\n // better-sqlite3 refuses to create parent directories, so make sure the\n // defaults exist (runs at astro config load — dev and build). A deployed\n // host still needs the data directory mounted/persisted as a volume.\n if (!opts.database && databaseUrl.startsWith('file:')) {\n mkdirSync(dirname(databaseUrl.slice('file:'.length)), { recursive: true });\n }\n if (!opts.storage) mkdirSync(uploadsDir, { recursive: true });\n\n return {\n target,\n adapter: node({ mode: 'standalone' }),\n database: opts.database ?? sqlite({ url: databaseUrl }),\n storage: opts.storage ?? local({ directory: uploadsDir, baseUrl: '/_emdash/api/media/file' }),\n };\n }\n\n throw new Error(\n `[vitops] unknown hosting target '${target}' (from ${process.env.HOSTING ? 'HOSTING env var' : 'options.target'}). ` +\n `Valid targets: ${TARGETS.join(', ')}.`,\n );\n}\n","","/**\n * @getvitops/emdash — EmDash CMS native plugin for the Vitops design system.\n *\n * Declares Vitops Portable Text block types (editable from the EmDash admin's\n * slash menu) and ships the Astro components that render them via the\n * `componentsEntry` (`@getvitops/emdash/astro`).\n *\n * Composes with — does not replace — the `getvitops()` Astro integration from\n * `@getvitops/astro`: the integration owns design-system CSS generation and\n * copies the web-component bundles into `public/<wcBase>/`; this plugin only\n * adds the editor-facing layer. Register both in the consumer site:\n *\n * ```js\n * // astro.config.mjs\n * integrations: [\n * react(),\n * getvitops({ css: {...} }),\n * emdash({ database, storage, plugins: [vitopsEmdash()] }),\n * ]\n * ```\n */\nimport { definePlugin } from 'emdash';\nimport type { PluginDescriptor } from 'emdash';\nimport { BLOCKS } from './blocks.ts';\n\nexport { BLOCKS } from './blocks.ts';\nexport type { BlockKitFieldDef, PortableTextBlockDef } from './blocks.ts';\nexport { vitopsHosting } from './hosting.ts';\nexport type { HostingTarget, VitopsHosting, VitopsHostingOptions } from './hosting.ts';\n\n/**\n * Read from package.json rather than hand-maintained.\n *\n * This was a literal with a \"keep in sync\" comment, which drifted on every\n * release: `changeset version` bumps package.json and leaves the literal alone.\n * `blocks.test.ts` asserts they match, but the release chain didn't run tests,\n * so `@getvitops/emdash@0.2.1` shipped a descriptor reporting `0.2.0`. Deriving\n * it removes the failure mode instead of relying on remembering.\n */\nimport pkg from '../package.json' with { type: 'json' };\n\nconst VERSION = pkg.version;\nconst PLUGIN_ID = 'vitops';\nconst FRAGMENTS_CAPABILITY = 'hooks.page-fragments:register';\n\nexport interface VitopsEmdashOptions {\n /**\n * How the web-component runtime scripts reach the page:\n * - `'integration'` (default) — the site's layout renders `<Head />` from\n * `@getvitops/astro`, which emits the script tags; this plugin adds none.\n * - `'fragments'` — for layouts using EmDash's `<EmDashHead/>`/`<EmDashBodyEnd/>`\n * instead: the plugin injects the script tags via the `page:fragments` hook.\n * Either way the bundles themselves must exist under `public/<wcBase>/`,\n * which the `getvitops()` integration provides (webComponents: true).\n * Do not enable both delivery paths — the scripts would load twice.\n */\n scripts?: 'integration' | 'fragments';\n /** Public base path of the web-component bundles (default '/vitops'). */\n wcBase?: string;\n}\n\n/**\n * Descriptor factory — import in `astro.config.mjs` and pass to the emdash()\n * integration's `plugins` array.\n */\nexport function vitopsEmdash(options: VitopsEmdashOptions = {}): PluginDescriptor {\n return {\n id: PLUGIN_ID,\n version: VERSION,\n format: 'native',\n entrypoint: '@getvitops/emdash',\n componentsEntry: '@getvitops/emdash/astro',\n options,\n capabilities: options.scripts === 'fragments' ? [FRAGMENTS_CAPABILITY] : [],\n } as PluginDescriptor;\n}\n\n/** Runtime side — EmDash imports and calls this from the descriptor's entrypoint. */\nexport function createPlugin(options: VitopsEmdashOptions = {}) {\n const scripts = options.scripts ?? 'integration';\n const wcBase = options.wcBase ?? '/vitops';\n\n return definePlugin({\n id: PLUGIN_ID,\n version: VERSION,\n capabilities: scripts === 'fragments' ? [FRAGMENTS_CAPABILITY] : [],\n\n hooks:\n scripts === 'fragments'\n ? {\n // Mirrors @getvitops/astro Head.astro: module polyfills + elements\n // in head, deferred enhancements at end of body.\n 'page:fragments': async () => [\n {\n kind: 'html',\n placement: 'head',\n html:\n `<link rel=\"modulepreload\" href=\"${wcBase}/polyfills.js\">` +\n `<script type=\"module\" src=\"${wcBase}/polyfills.js\"></script>` +\n `<script type=\"module\" src=\"${wcBase}/elements.js\"></script>`,\n key: 'vitops-wc',\n },\n {\n kind: 'external-script',\n placement: 'body:end',\n src: `${wcBase}/deferred.js`,\n defer: true,\n key: 'vitops-deferred',\n },\n ],\n }\n : {},\n\n admin: {\n portableTextBlocks: BLOCKS,\n },\n });\n}\n\nexport default createPlugin;\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getvitops/emdash",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "EmDash CMS native plugin for the Vitops design system — Portable Text block types editors can insert from the admin, rendered with the design-system web components and CSS framework.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -21,16 +21,21 @@
|
|
|
21
21
|
"publishConfig": {
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@getvitops/utils": "2.1.0"
|
|
26
|
+
},
|
|
24
27
|
"devDependencies": {
|
|
25
28
|
"@astrojs/cloudflare": "^14.0.0",
|
|
26
29
|
"@emdash-cms/cloudflare": "^0.31.0",
|
|
27
30
|
"astro": "^7.1.2",
|
|
28
31
|
"emdash": "^0.31.0",
|
|
29
|
-
"vitest": "^4.1.9"
|
|
32
|
+
"vitest": "^4.1.9",
|
|
33
|
+
"@getvitops/astro": "2.1.0"
|
|
30
34
|
},
|
|
31
35
|
"peerDependencies": {
|
|
32
36
|
"@astrojs/cloudflare": ">=14",
|
|
33
37
|
"@emdash-cms/cloudflare": ">=0.31.0",
|
|
38
|
+
"@getvitops/astro": ">=2.0.0",
|
|
34
39
|
"astro": ">=7",
|
|
35
40
|
"emdash": ">=0.31.0"
|
|
36
41
|
},
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* A link or button with optional icons at either end.
|
|
4
|
+
*
|
|
5
|
+
* Icons come from the generated SVG sprite, not astro-icon: this package has no
|
|
6
|
+
* astro-icon peer and shouldn't acquire one, and a platform wrapper that needed
|
|
7
|
+
* runtime JS would be the wrong tier. `<use href="…#icon-menu">` is plain markup
|
|
8
|
+
* that inherits `currentColor`.
|
|
9
|
+
*
|
|
10
|
+
* The `icon-<name>` ids are the sprite's set-independent aliases, so content
|
|
11
|
+
* authored here survives the site changing icon sets — the same guarantee
|
|
12
|
+
* resolveIcon() gives Astro call sites.
|
|
13
|
+
*
|
|
14
|
+
* No new CSS: `.cta` and `:where(button, .btn)` are already
|
|
15
|
+
* `inline-flex; align-items: center; gap`, so an adornment is child order. `.link`
|
|
16
|
+
* is not a flex container, so its icons sit inline via the same `.icon` box.
|
|
17
|
+
*
|
|
18
|
+
* Every action_id declared in blocks.ts must be NAMED in the destructure below —
|
|
19
|
+
* blocks.test.ts checks by literal substring, so reaching them via a spread would
|
|
20
|
+
* pass type-checking and fail the contract test.
|
|
21
|
+
*/
|
|
22
|
+
import { resolveBlockValue } from './value.ts';
|
|
23
|
+
|
|
24
|
+
interface Value {
|
|
25
|
+
label?: string;
|
|
26
|
+
href?: string;
|
|
27
|
+
variant?: 'link' | 'btn' | 'cta';
|
|
28
|
+
startIcon?: string;
|
|
29
|
+
endIcon?: string;
|
|
30
|
+
role?: string;
|
|
31
|
+
newTab?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface Props {
|
|
35
|
+
/** Base URL of the sprite. Matches VitopsEmdashOptions.wcBase, where the integration puts it. */
|
|
36
|
+
spriteBase?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const value = resolveBlockValue<Value>(Astro.props);
|
|
40
|
+
const { label, href, variant = 'link', startIcon, endIcon, role, newTab = false } = value;
|
|
41
|
+
const { spriteBase = '/vitops' } = Astro.props as Props;
|
|
42
|
+
|
|
43
|
+
const sprite = `${spriteBase}/icons.svg`;
|
|
44
|
+
// `link` is the pattern's own bare class; a role variant is `<pattern>-<role>`.
|
|
45
|
+
const classes = [variant, role ? `${variant}-${role}` : null, newTab ? 'link--external' : null]
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.join(' ');
|
|
48
|
+
|
|
49
|
+
// Only render when there is somewhere to go and something to read — a bare icon
|
|
50
|
+
// with no label would be an unlabelled link.
|
|
51
|
+
const renderable = Boolean(label && href);
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
{
|
|
55
|
+
renderable && (
|
|
56
|
+
<a
|
|
57
|
+
class={classes}
|
|
58
|
+
href={href}
|
|
59
|
+
{...(newTab ? { target: '_blank', rel: 'noopener noreferrer' } : {})}>
|
|
60
|
+
{startIcon && (
|
|
61
|
+
<span class="icon" aria-hidden="true">
|
|
62
|
+
<svg>
|
|
63
|
+
<use href={`${sprite}#icon-${startIcon}`} />
|
|
64
|
+
</svg>
|
|
65
|
+
</span>
|
|
66
|
+
)}
|
|
67
|
+
<span>{label}</span>
|
|
68
|
+
{endIcon && (
|
|
69
|
+
<span class="icon" aria-hidden="true">
|
|
70
|
+
<svg>
|
|
71
|
+
<use href={`${sprite}#icon-${endIcon}`} />
|
|
72
|
+
</svg>
|
|
73
|
+
</span>
|
|
74
|
+
)}
|
|
75
|
+
</a>
|
|
76
|
+
)
|
|
77
|
+
}
|
package/src/astro/index.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* <PortableText> automatically — the export name MUST be `blockComponents`
|
|
4
4
|
* and keys MUST match the block `type`s declared in ../blocks.ts.
|
|
5
5
|
*/
|
|
6
|
+
import ActionLink from './blocks/ActionLink.astro';
|
|
6
7
|
import Banner from './blocks/Banner.astro';
|
|
7
8
|
import Carousel from './blocks/Carousel.astro';
|
|
8
9
|
import CopyButton from './blocks/CopyButton.astro';
|
|
@@ -10,6 +11,7 @@ import Details from './blocks/Details.astro';
|
|
|
10
11
|
import ImageCompare from './blocks/ImageCompare.astro';
|
|
11
12
|
|
|
12
13
|
export const blockComponents = {
|
|
14
|
+
'vitops.actionLink': ActionLink,
|
|
13
15
|
'vitops.imageCompare': ImageCompare,
|
|
14
16
|
'vitops.copyButton': CopyButton,
|
|
15
17
|
'vitops.banner': Banner,
|