@discoverworthy/assets 0.1.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/README.md +210 -0
- package/index.d.ts +79 -0
- package/index.js +71 -0
- package/package.json +62 -0
- package/react.d.ts +122 -0
- package/react.js +108 -0
- package/server.d.ts +47 -0
- package/server.js +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# @discoverworthy/assets
|
|
2
|
+
|
|
3
|
+
Framework-agnostic image references for DiscoverWorthy Managed Assets. Rotate, optimize, and manage your images from the dashboard — on any framework, without a rebuild.
|
|
4
|
+
|
|
5
|
+
## The Three-Tier Model
|
|
6
|
+
|
|
7
|
+
**Tier 0 — Bare URL** (works everywhere)
|
|
8
|
+
```
|
|
9
|
+
https://app.discoverworthy.com/img/{siteId}/{imageKey}
|
|
10
|
+
```
|
|
11
|
+
Paste this into an `<img src>`, CSS `url()`, email, RSS, or anywhere. Rotation and transforms happen at the resolver — no SDK needed.
|
|
12
|
+
|
|
13
|
+
**Tier 1 — `urlFor()` helper** (any JS/TS framework)
|
|
14
|
+
```js
|
|
15
|
+
import { urlFor, configure } from '@discoverworthy/assets';
|
|
16
|
+
configure({ siteId: 'my-site' });
|
|
17
|
+
const src = urlFor('hero-key', { width: 640 });
|
|
18
|
+
```
|
|
19
|
+
Use in Astro frontmatter, Svelte templates, Vue bindings, Next.js, or anywhere you generate URLs.
|
|
20
|
+
|
|
21
|
+
**Tier 2 — Framework components** (optional polish)
|
|
22
|
+
```tsx
|
|
23
|
+
import { DwImage } from '@discoverworthy/assets/react';
|
|
24
|
+
<DwImage asset="hero-key" alt="Team" />
|
|
25
|
+
```
|
|
26
|
+
Per-framework components (React/Next first; others as demand appears) add responsive srcSet, lazy loading, blur-up, and fallback handling.
|
|
27
|
+
|
|
28
|
+
**Rule:** rotation and transforms are a property of **Tier 0** — the resolver, not the SDK. So even if you install nothing and just paste a URL, your images rotate from the dashboard. Tiers 1 and 2 only add developer ergonomics.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @discoverworthy/assets
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
### React / Next.js
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { configure, DwImage } from '@discoverworthy/assets';
|
|
42
|
+
|
|
43
|
+
// Configure once at app startup
|
|
44
|
+
configure({ siteId: 'my-site-123' });
|
|
45
|
+
|
|
46
|
+
export default function Hero() {
|
|
47
|
+
return (
|
|
48
|
+
<DwImage
|
|
49
|
+
asset="hero-key"
|
|
50
|
+
alt="Team in the workshop"
|
|
51
|
+
width={1024}
|
|
52
|
+
sizes="(max-width: 768px) 100vw, 50vw"
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Astro
|
|
59
|
+
|
|
60
|
+
```astro
|
|
61
|
+
---
|
|
62
|
+
import { configure, urlFor } from '@discoverworthy/assets';
|
|
63
|
+
|
|
64
|
+
configure({ siteId: 'my-site-123' });
|
|
65
|
+
|
|
66
|
+
const heroSrc = urlFor('hero-key', { width: 1024 });
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
<img src={heroSrc} alt="Team in the workshop" />
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Svelte
|
|
73
|
+
|
|
74
|
+
```svelte
|
|
75
|
+
<script>
|
|
76
|
+
import { configure, urlFor } from '@discoverworthy/assets';
|
|
77
|
+
|
|
78
|
+
configure({ siteId: 'my-site-123' });
|
|
79
|
+
|
|
80
|
+
const heroSrc = urlFor('hero-key', { width: 1024 });
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<img src={heroSrc} alt="Team in the workshop" />
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Vue
|
|
87
|
+
|
|
88
|
+
```vue
|
|
89
|
+
<template>
|
|
90
|
+
<img :src="heroSrc" alt="Team in the workshop" />
|
|
91
|
+
</template>
|
|
92
|
+
|
|
93
|
+
<script setup>
|
|
94
|
+
import { configure, urlFor } from '@discoverworthy/assets';
|
|
95
|
+
|
|
96
|
+
configure({ siteId: 'my-site-123' });
|
|
97
|
+
const heroSrc = urlFor('hero-key', { width: 1024 });
|
|
98
|
+
</script>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Plain HTML / WordPress
|
|
102
|
+
|
|
103
|
+
No install needed. Just use the Tier 0 URL:
|
|
104
|
+
|
|
105
|
+
```html
|
|
106
|
+
<img src="https://app.discoverworthy.com/img/my-site-123/hero-key" alt="Team in the workshop" />
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Image rotation, variant sizing, and all transforms work the same way. They're properties of the resolver, not the SDK.
|
|
110
|
+
|
|
111
|
+
## API
|
|
112
|
+
|
|
113
|
+
### `configure(opts)`
|
|
114
|
+
|
|
115
|
+
Set the default site ID and/or host once at startup. All subsequent `urlFor()` calls use these defaults unless overridden.
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
configure({ siteId: 'my-site-123' });
|
|
119
|
+
// Now urlFor('image-key') works without passing siteId.
|
|
120
|
+
|
|
121
|
+
configure({ siteId: 'my-site', host: 'https://dev.discoverworthy.com' });
|
|
122
|
+
// Override the host (useful for dev/staging).
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Options:**
|
|
126
|
+
- `siteId` (string) — The site ID for this deployment. Required (either here or on every `urlFor()` call).
|
|
127
|
+
- `host` (string, default: `https://app.discoverworthy.com`) — Base URL for the image resolver.
|
|
128
|
+
|
|
129
|
+
### `urlFor(assetId, opts)`
|
|
130
|
+
|
|
131
|
+
Generate a URL for a managed DiscoverWorthy asset.
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
urlFor('hero-key'); // Base URL
|
|
135
|
+
urlFor('hero-key', { width: 640 }); // With width variant
|
|
136
|
+
urlFor('hero-key', { width: 1024, siteId: 'other-site' }); // Override siteId
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Parameters:**
|
|
140
|
+
- `assetId` (string, required) — The image key from `dw_adopt_image` or the dashboard.
|
|
141
|
+
- `opts.width` (number) — Requested width. The resolver produces WebP variants at `320`, `640`, `1024`, `1600`. Other values are passed through; the resolver will ignore unknown widths.
|
|
142
|
+
- `opts.siteId` (string) — Override the configured site ID.
|
|
143
|
+
- `opts.host` (string) — Override the configured host.
|
|
144
|
+
|
|
145
|
+
**Returns:** A string URL like `https://app.discoverworthy.com/img/my-site/hero-key?w=640`.
|
|
146
|
+
|
|
147
|
+
### `ALLOWED_WIDTHS`
|
|
148
|
+
|
|
149
|
+
Exported array of allowed image widths: `[320, 640, 1024, 1600]`. Use this to build responsive srcSet or media-query logic.
|
|
150
|
+
|
|
151
|
+
```js
|
|
152
|
+
import { ALLOWED_WIDTHS, urlFor } from '@discoverworthy/assets';
|
|
153
|
+
|
|
154
|
+
const srcSet = ALLOWED_WIDTHS.map(w =>
|
|
155
|
+
`${urlFor('hero-key', { width: w })} ${w}w`
|
|
156
|
+
).join(', ');
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `<DwImage>` (React/Next)
|
|
160
|
+
|
|
161
|
+
A pre-built component that handles responsive images, lazy loading, and fallbacks.
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
import { DwImage } from '@discoverworthy/assets/react';
|
|
165
|
+
|
|
166
|
+
<DwImage
|
|
167
|
+
asset="hero-key"
|
|
168
|
+
alt="Team in the workshop"
|
|
169
|
+
width={1024}
|
|
170
|
+
sizes="(max-width: 768px) 100vw, 50vw"
|
|
171
|
+
fallback="/images/placeholder.jpg"
|
|
172
|
+
lazy={true}
|
|
173
|
+
/>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Props:**
|
|
177
|
+
- `asset` (string, required) — Image key.
|
|
178
|
+
- `alt` (string, required) — Alt text for accessibility.
|
|
179
|
+
- `width` (number) — Requested width for the `src` attribute. The component auto-builds a srcSet with all allowed widths.
|
|
180
|
+
- `sizes` (string) — CSS media queries for responsive sizing.
|
|
181
|
+
- `fallback` (string) — URL to use if the managed asset fails to load.
|
|
182
|
+
- `lazy` (boolean, default: `true`) — Use lazy loading.
|
|
183
|
+
- `siteId` (string) — Override the configured site ID.
|
|
184
|
+
- `host` (string) — Override the configured host.
|
|
185
|
+
- All standard `<img>` attributes (`className`, `style`, `onLoad`, etc.) are passed through.
|
|
186
|
+
|
|
187
|
+
## Transforms & Rotation
|
|
188
|
+
|
|
189
|
+
The resolver (`/img/{siteId}/{imageKey}`) handles all image transforms:
|
|
190
|
+
|
|
191
|
+
- **Width variants** — Add `?w=320|640|1024|1600` to produce responsive WebP variants
|
|
192
|
+
- **Rotation** — Configure a rotation policy in the dashboard (round-robin, weighted, scheduled) and images automatically cycle on the schedule you set
|
|
193
|
+
- **Fallback** — If no rotation is configured, the resolver uses the original source URL
|
|
194
|
+
|
|
195
|
+
All transforms work at **Tier 0** — even if you paste a bare URL with no SDK, your images rotate and resize.
|
|
196
|
+
|
|
197
|
+
## Why This Matters
|
|
198
|
+
|
|
199
|
+
Managed Assets closes the gap between copy-pipeline sites (where the dashboard controls every image) and owned sites (where you build in your own repo). You get:
|
|
200
|
+
|
|
201
|
+
- **Zero-rebuild image swaps** — Change hero images from the dashboard without deploying new code
|
|
202
|
+
- **Rotation & A/B testing** — Cycle seasonal variants, A/B test images, or weight favored variants
|
|
203
|
+
- **Framework-agnostic** — Works on Next, Astro, Svelte, Vue, Hugo, plain HTML, WordPress, or anything else
|
|
204
|
+
- **Optional adoption** — Use Tier 0 (bare URL) for any site; Tier 1/2 are just ergonomics
|
|
205
|
+
|
|
206
|
+
## More Information
|
|
207
|
+
|
|
208
|
+
- [DiscoverWorthy docs](https://discoverworthy.com)
|
|
209
|
+
- GitHub: [discover-worthy/saas](https://github.com/discover-worthy/saas)
|
|
210
|
+
- License: MIT
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for configuring the global asset resolver.
|
|
3
|
+
*/
|
|
4
|
+
export interface ConfigureOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Site ID for this host/deployment. Used by urlFor() if not overridden.
|
|
7
|
+
*/
|
|
8
|
+
siteId?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Base URL for the image resolver (default: https://app.discoverworthy.com).
|
|
11
|
+
* Usually not needed unless pointing at a different environment.
|
|
12
|
+
*/
|
|
13
|
+
host?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Options for building an asset URL.
|
|
18
|
+
*/
|
|
19
|
+
export interface UrlForOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Requested width (320, 640, 1024, or 1600).
|
|
22
|
+
* The resolver produces a WebP variant at this width.
|
|
23
|
+
* See ALLOWED_WIDTHS for accepted values.
|
|
24
|
+
*/
|
|
25
|
+
width?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Override the configured siteId for this call.
|
|
28
|
+
*/
|
|
29
|
+
siteId?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Override the configured host for this call.
|
|
32
|
+
*/
|
|
33
|
+
host?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Configure the default site ID and/or host for urlFor() calls.
|
|
38
|
+
* Call this once at startup to avoid passing siteId on every invocation.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* configure({ siteId: 'my-site-123' });
|
|
42
|
+
* // Then urlFor('image-key') works without passing siteId.
|
|
43
|
+
*
|
|
44
|
+
* @param opts - Configuration options
|
|
45
|
+
*/
|
|
46
|
+
export function configure(opts?: ConfigureOptions): void;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Allowed image widths supported by the resolver.
|
|
50
|
+
* Use these to build srcset attributes or responsive image logic.
|
|
51
|
+
* The resolver produces a WebP variant for each width.
|
|
52
|
+
*/
|
|
53
|
+
export const ALLOWED_WIDTHS: number[];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generate a URL for a managed DiscoverWorthy asset.
|
|
57
|
+
*
|
|
58
|
+
* The URL points to the resolver at /img/{siteId}/{assetId}, which:
|
|
59
|
+
* - Rotates images based on policy (if configured on the dashboard)
|
|
60
|
+
* - Produces WebP variants at ?w=320|640|1024|1600
|
|
61
|
+
* - Falls back to replacement_url or source_url if no rotation is set
|
|
62
|
+
*
|
|
63
|
+
* Call configure({ siteId }) once at startup, or pass siteId in opts to override.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* // After configure({ siteId: 'my-site' }):
|
|
67
|
+
* urlFor('hero-key') // → https://app.discoverworthy.com/img/my-site/hero-key
|
|
68
|
+
* urlFor('hero-key', { width: 640 }) // → https://app.discoverworthy.com/img/my-site/hero-key?w=640
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* // Or pass siteId directly:
|
|
72
|
+
* urlFor('hero-key', { siteId: 'other-site', width: 1024 })
|
|
73
|
+
*
|
|
74
|
+
* @param assetId - The image key returned by dw_adopt_image or manually assigned
|
|
75
|
+
* @param opts - Options
|
|
76
|
+
* @returns The resolver URL
|
|
77
|
+
* @throws If assetId is missing or siteId is not configured
|
|
78
|
+
*/
|
|
79
|
+
export function urlFor(assetId: string, opts?: UrlForOptions): string;
|
package/index.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const DEFAULT_HOST = 'https://app.discoverworthy.com';
|
|
2
|
+
let _config = { siteId: null, host: DEFAULT_HOST };
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configure the default site ID and/or host for urlFor() calls.
|
|
6
|
+
* Call this once at startup to avoid passing siteId on every invocation.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* configure({ siteId: 'my-site-123' });
|
|
10
|
+
* // Then urlFor('image-key') works without passing siteId.
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} opts - Configuration options
|
|
13
|
+
* @param {string} [opts.siteId] - The site ID for this host/deployment
|
|
14
|
+
* @param {string} [opts.host] - Base URL for the image resolver (default: https://app.discoverworthy.com)
|
|
15
|
+
*/
|
|
16
|
+
export function configure(opts = {}) {
|
|
17
|
+
if (opts.siteId !== undefined) _config.siteId = opts.siteId;
|
|
18
|
+
if (opts.host !== undefined) _config.host = opts.host || DEFAULT_HOST;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Allowed image widths supported by the resolver.
|
|
23
|
+
* Use these to build srcset attributes or responsive image logic.
|
|
24
|
+
* The resolver produces a WebP variant for each width.
|
|
25
|
+
*
|
|
26
|
+
* @type {number[]}
|
|
27
|
+
*/
|
|
28
|
+
export const ALLOWED_WIDTHS = [320, 640, 1024, 1600];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Generate a URL for a managed DiscoverWorthy asset.
|
|
32
|
+
*
|
|
33
|
+
* The URL points to the resolver at /img/{siteId}/{assetId}, which:
|
|
34
|
+
* - Rotates images based on policy (if configured on the dashboard)
|
|
35
|
+
* - Produces WebP variants at ?w=320|640|1024|1600
|
|
36
|
+
* - Falls back to replacement_url or source_url if no rotation is set
|
|
37
|
+
*
|
|
38
|
+
* Call configure({ siteId }) once at startup, or pass siteId in opts to override.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* // After configure({ siteId: 'my-site' }):
|
|
42
|
+
* urlFor('hero-key') // → https://app.discoverworthy.com/img/my-site/hero-key
|
|
43
|
+
* urlFor('hero-key', { width: 640 }) // → https://app.discoverworthy.com/img/my-site/hero-key?w=640
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* // Or pass siteId directly:
|
|
47
|
+
* urlFor('hero-key', { siteId: 'other-site', width: 1024 })
|
|
48
|
+
*
|
|
49
|
+
* @param {string} assetId - The image key returned by dw_adopt_image or manually assigned
|
|
50
|
+
* @param {Object} [opts] - Options
|
|
51
|
+
* @param {number} [opts.width] - Requested width (320, 640, 1024, or 1600) — resolver produces WebP
|
|
52
|
+
* @param {string} [opts.siteId] - Override the configured siteId for this call
|
|
53
|
+
* @param {string} [opts.host] - Override the configured host for this call
|
|
54
|
+
* @returns {string} The resolver URL
|
|
55
|
+
* @throws {Error} If assetId is missing or siteId is not configured
|
|
56
|
+
*/
|
|
57
|
+
export function urlFor(assetId, opts = {}) {
|
|
58
|
+
if (!assetId || typeof assetId !== 'string') {
|
|
59
|
+
throw new Error('urlFor: assetId (the image key) is required');
|
|
60
|
+
}
|
|
61
|
+
const siteId = opts.siteId || _config.siteId;
|
|
62
|
+
if (!siteId) {
|
|
63
|
+
throw new Error('urlFor: no siteId — call configure({ siteId }) once at startup, or pass { siteId } here');
|
|
64
|
+
}
|
|
65
|
+
const host = (opts.host || _config.host || DEFAULT_HOST).replace(/\/+$/, '');
|
|
66
|
+
let url = `${host}/img/${encodeURIComponent(siteId)}/${encodeURIComponent(assetId)}`;
|
|
67
|
+
if (opts.width != null) {
|
|
68
|
+
url += `?w=${encodeURIComponent(String(opts.width))}`;
|
|
69
|
+
}
|
|
70
|
+
return url;
|
|
71
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@discoverworthy/assets",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Framework-agnostic image references for DiscoverWorthy Managed Assets — urlFor() plus optional per-framework components.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./index.d.ts",
|
|
9
|
+
"default": "./index.js"
|
|
10
|
+
},
|
|
11
|
+
"./react": {
|
|
12
|
+
"types": "./react.d.ts",
|
|
13
|
+
"default": "./react.js"
|
|
14
|
+
},
|
|
15
|
+
"./server": {
|
|
16
|
+
"types": "./server.d.ts",
|
|
17
|
+
"default": "./server.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"index.js",
|
|
22
|
+
"index.d.ts",
|
|
23
|
+
"react.js",
|
|
24
|
+
"react.d.ts",
|
|
25
|
+
"server.js",
|
|
26
|
+
"server.d.ts",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"keywords": [
|
|
30
|
+
"discoverworthy",
|
|
31
|
+
"assets",
|
|
32
|
+
"images",
|
|
33
|
+
"managed-assets",
|
|
34
|
+
"managed-copy",
|
|
35
|
+
"headless-cms",
|
|
36
|
+
"collections",
|
|
37
|
+
"image-optimization",
|
|
38
|
+
"next",
|
|
39
|
+
"react",
|
|
40
|
+
"astro",
|
|
41
|
+
"svelte",
|
|
42
|
+
"vue"
|
|
43
|
+
],
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/discover-worthy/saas"
|
|
47
|
+
},
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"author": "DiscoverWorthy",
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"react": ">=17"
|
|
55
|
+
},
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"react": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"sideEffects": false
|
|
62
|
+
}
|
package/react.d.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { FC, ImgHTMLAttributes, HTMLAttributes, ReactNode, ElementType } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Props for the DwImage React component.
|
|
5
|
+
* Extends standard HTML img attributes but removes src/srcSet (managed by the component).
|
|
6
|
+
*/
|
|
7
|
+
export interface DwImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'src' | 'srcSet'> {
|
|
8
|
+
/**
|
|
9
|
+
* The image key from dw_adopt_image or manually assigned in the dashboard.
|
|
10
|
+
*/
|
|
11
|
+
asset: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Alt text for accessibility. Required.
|
|
15
|
+
*/
|
|
16
|
+
alt: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Requested width for the src attribute (320, 640, 1024, or 1600).
|
|
20
|
+
* The component automatically builds a srcSet with all allowed widths.
|
|
21
|
+
*/
|
|
22
|
+
width?: number;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* CSS media queries for responsive sizing.
|
|
26
|
+
* Passed directly to the <img> sizes attribute.
|
|
27
|
+
*/
|
|
28
|
+
sizes?: string;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* URL to use if the managed asset fails to load.
|
|
32
|
+
* The src and srcSet are replaced once on error (idempotent).
|
|
33
|
+
*/
|
|
34
|
+
fallback?: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Whether to use lazy loading (default: true).
|
|
38
|
+
* Set to false for above-the-fold images.
|
|
39
|
+
*/
|
|
40
|
+
lazy?: boolean;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Override the configured siteId for this component.
|
|
44
|
+
*/
|
|
45
|
+
siteId?: string;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Override the configured host for this component.
|
|
49
|
+
*/
|
|
50
|
+
host?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A React component that renders an optimized <img> for a DiscoverWorthy managed asset.
|
|
55
|
+
*
|
|
56
|
+
* Automatically builds:
|
|
57
|
+
* - src: the resolver URL at the optionally specified width
|
|
58
|
+
* - srcSet: all allowed widths (320w, 640w, 1024w, 1600w) for responsive loading
|
|
59
|
+
* - loading: 'lazy' by default (can be overridden with the lazy prop or rest attributes)
|
|
60
|
+
* - decoding: 'async'
|
|
61
|
+
*
|
|
62
|
+
* Supports an optional fallback URL: if the image fails to load, the src switches
|
|
63
|
+
* to the fallback (once per render).
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* // Configure once at app startup:
|
|
67
|
+
* import { configure } from '@discoverworthy/assets';
|
|
68
|
+
* configure({ siteId: 'my-site' });
|
|
69
|
+
*
|
|
70
|
+
* // Then use the component:
|
|
71
|
+
* <DwImage asset="hero-key" alt="Team in the workshop" />
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* // With responsive widths and sizes:
|
|
75
|
+
* <DwImage
|
|
76
|
+
* asset="hero-key"
|
|
77
|
+
* alt="Team in the workshop"
|
|
78
|
+
* width={1024}
|
|
79
|
+
* sizes="(max-width: 768px) 100vw, 50vw"
|
|
80
|
+
* />
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* // With a fallback for when the managed asset isn't ready yet:
|
|
84
|
+
* <DwImage
|
|
85
|
+
* asset="hero-key"
|
|
86
|
+
* alt="Team in the workshop"
|
|
87
|
+
* fallback="/images/hero.jpg"
|
|
88
|
+
* />
|
|
89
|
+
*/
|
|
90
|
+
export const DwImage: FC<DwImageProps>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Props for the DwText managed-copy component.
|
|
94
|
+
*/
|
|
95
|
+
export interface DwTextProps extends HTMLAttributes<HTMLElement> {
|
|
96
|
+
/**
|
|
97
|
+
* The copy key from dw_adopt_copy. Rendered as the `data-dw-copy` attribute;
|
|
98
|
+
* the deploy-finalize pipeline overlays the managed value onto this element.
|
|
99
|
+
*/
|
|
100
|
+
id: string;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The element/tag to render (e.g. 'h1', 'p', 'span', 'button'). Defaults to 'span'.
|
|
104
|
+
*/
|
|
105
|
+
as?: ElementType;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The default text — kept inline as the SEO / no-JS fallback until a managed
|
|
109
|
+
* override exists.
|
|
110
|
+
*/
|
|
111
|
+
children?: ReactNode;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A React component for a DiscoverWorthy managed-copy string — the text sibling
|
|
116
|
+
* of DwImage. Renders `<as data-dw-copy={id}>{children}</as>`; the managed value
|
|
117
|
+
* is injected server-side at deploy time, so the default stays in the markup.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* <DwText id="hero_headline" as="h1">Plumbing you can trust</DwText>
|
|
121
|
+
*/
|
|
122
|
+
export const DwText: FC<DwTextProps>;
|
package/react.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createElement } from 'react';
|
|
2
|
+
import { urlFor, ALLOWED_WIDTHS } from './index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A React component that renders an optimized <img> for a DiscoverWorthy managed asset.
|
|
6
|
+
*
|
|
7
|
+
* Automatically builds:
|
|
8
|
+
* - src: the resolver URL at the optionally specified width
|
|
9
|
+
* - srcSet: all allowed widths (320w, 640w, 1024w, 1600w) for responsive loading
|
|
10
|
+
* - loading: 'lazy' by default (can be overridden with the lazy prop or rest attributes)
|
|
11
|
+
* - decoding: 'async'
|
|
12
|
+
*
|
|
13
|
+
* Supports an optional fallback URL: if the image fails to load, the src switches
|
|
14
|
+
* to the fallback (once per render).
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} props - Component props
|
|
17
|
+
* @param {string} props.asset - The image key from dw_adopt_image
|
|
18
|
+
* @param {string} props.alt - Alt text for accessibility (required)
|
|
19
|
+
* @param {number} [props.width] - Requested width for the src attribute (320, 640, 1024, 1600)
|
|
20
|
+
* @param {string} [props.sizes] - CSS media queries for responsive sizing
|
|
21
|
+
* @param {string} [props.fallback] - URL to use if the image fails to load
|
|
22
|
+
* @param {boolean} [props.lazy=true] - Whether to use lazy loading (default: true)
|
|
23
|
+
* @param {string} [props.siteId] - Override the configured siteId
|
|
24
|
+
* @param {string} [props.host] - Override the configured host
|
|
25
|
+
* @param {...any} rest - Other <img> attributes (className, style, onLoad, etc.)
|
|
26
|
+
* @returns {JSX.Element} An <img> element
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // Configure once at app startup:
|
|
30
|
+
* configure({ siteId: 'my-site' });
|
|
31
|
+
*
|
|
32
|
+
* // Then use the component:
|
|
33
|
+
* <DwImage asset="hero-key" alt="Team in the workshop" />
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // With responsive widths and sizes:
|
|
37
|
+
* <DwImage
|
|
38
|
+
* asset="hero-key"
|
|
39
|
+
* alt="Team in the workshop"
|
|
40
|
+
* width={1024}
|
|
41
|
+
* sizes="(max-width: 768px) 100vw, 50vw"
|
|
42
|
+
* />
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* // With a fallback for when the managed asset isn't ready yet:
|
|
46
|
+
* <DwImage
|
|
47
|
+
* asset="hero-key"
|
|
48
|
+
* alt="Team in the workshop"
|
|
49
|
+
* fallback="/images/hero.jpg"
|
|
50
|
+
* />
|
|
51
|
+
*/
|
|
52
|
+
export function DwImage({
|
|
53
|
+
asset,
|
|
54
|
+
alt,
|
|
55
|
+
width,
|
|
56
|
+
sizes,
|
|
57
|
+
fallback,
|
|
58
|
+
lazy = true,
|
|
59
|
+
siteId,
|
|
60
|
+
host,
|
|
61
|
+
...rest
|
|
62
|
+
}) {
|
|
63
|
+
const src = urlFor(asset, { width, siteId, host });
|
|
64
|
+
|
|
65
|
+
const srcSet = ALLOWED_WIDTHS.map((w) => {
|
|
66
|
+
const url = urlFor(asset, { width: w, siteId, host });
|
|
67
|
+
return `${url} ${w}w`;
|
|
68
|
+
}).join(', ');
|
|
69
|
+
|
|
70
|
+
const handleError = (event) => {
|
|
71
|
+
if (fallback && event.currentTarget.src !== fallback) {
|
|
72
|
+
event.currentTarget.src = fallback;
|
|
73
|
+
event.currentTarget.srcSet = '';
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return createElement('img', {
|
|
78
|
+
src,
|
|
79
|
+
srcSet,
|
|
80
|
+
alt,
|
|
81
|
+
sizes,
|
|
82
|
+
loading: lazy ? 'lazy' : 'eager',
|
|
83
|
+
decoding: 'async',
|
|
84
|
+
onError: fallback ? handleError : undefined,
|
|
85
|
+
...rest,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A React component for a DiscoverWorthy managed-copy string (the text sibling
|
|
91
|
+
* of DwImage). Renders an element carrying `data-dw-copy="<id>"` with the given
|
|
92
|
+
* children as the default/fallback text. The deploy-finalize pipeline overlays
|
|
93
|
+
* the managed value onto this element server-side, so the default stays in the
|
|
94
|
+
* markup for SEO and no-JS — there's no client-side fetch.
|
|
95
|
+
*
|
|
96
|
+
* @param {Object} props
|
|
97
|
+
* @param {string} props.id - The copy key from dw_adopt_copy (goes in data-dw-copy)
|
|
98
|
+
* @param {string} [props.as='span'] - The element/tag to render (e.g. 'h1', 'p', 'button')
|
|
99
|
+
* @param {React.ReactNode} props.children - The default text, kept inline as the fallback
|
|
100
|
+
* @param {...any} rest - Other attributes (className, style, etc.)
|
|
101
|
+
* @returns {JSX.Element}
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* <DwText id="hero_headline" as="h1">Plumbing you can trust</DwText>
|
|
105
|
+
*/
|
|
106
|
+
export function DwText({ id, as = 'span', children, ...rest }) {
|
|
107
|
+
return createElement(as, { 'data-dw-copy': id, ...rest }, children);
|
|
108
|
+
}
|
package/server.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side helpers for DiscoverWorthy Managed Collections.
|
|
3
|
+
* Build-time only — uses the workspace token (a secret). Never import in client code.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface CollectionFetchOptions {
|
|
7
|
+
/** Workspace token. Defaults to process.env.DISCOVERWORTHY_DEPLOY_TOKEN. */
|
|
8
|
+
token?: string;
|
|
9
|
+
/** Override the API host. Defaults to https://app.discoverworthy.com. */
|
|
10
|
+
host?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CollectionField {
|
|
14
|
+
name: string;
|
|
15
|
+
type: 'text' | 'textarea' | 'number' | 'boolean' | 'date' | 'url' | 'image';
|
|
16
|
+
label: string;
|
|
17
|
+
required?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CollectionItem {
|
|
21
|
+
_id: string;
|
|
22
|
+
_order: number;
|
|
23
|
+
[field: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface FetchCollectionResult {
|
|
27
|
+
collection: { slug: string; label: string; fields: CollectionField[] };
|
|
28
|
+
items: CollectionItem[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ListCollectionsResult {
|
|
32
|
+
collections: Array<{
|
|
33
|
+
slug: string;
|
|
34
|
+
label: string;
|
|
35
|
+
description: string | null;
|
|
36
|
+
fields: CollectionField[];
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Fetch a collection's published items at build time.
|
|
42
|
+
* @param slug The collection slug (from dw_define_collection).
|
|
43
|
+
*/
|
|
44
|
+
export function fetchCollection(slug: string, opts?: CollectionFetchOptions): Promise<FetchCollectionResult>;
|
|
45
|
+
|
|
46
|
+
/** List every collection (slug + label + field schema) for the workspace. */
|
|
47
|
+
export function listCollections(opts?: CollectionFetchOptions): Promise<ListCollectionsResult>;
|
package/server.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side helpers for DiscoverWorthy Managed Collections (the headless-CMS
|
|
3
|
+
* content API). Build-time only — these use the workspace token, which is a
|
|
4
|
+
* SECRET. Never import this from client/browser code.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const DEFAULT_HOST = 'https://app.discoverworthy.com';
|
|
8
|
+
|
|
9
|
+
function resolveToken(opts) {
|
|
10
|
+
if (opts && opts.token) return opts.token;
|
|
11
|
+
if (typeof process !== 'undefined' && process.env && process.env.DISCOVERWORTHY_DEPLOY_TOKEN) {
|
|
12
|
+
return process.env.DISCOVERWORTHY_DEPLOY_TOKEN;
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function resolveHost(opts) {
|
|
18
|
+
return ((opts && opts.host) || DEFAULT_HOST).replace(/\/+$/, '');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Fetch a collection's published items at build time.
|
|
23
|
+
* @param {string} slug - The collection slug (from dw_define_collection).
|
|
24
|
+
* @param {{ token?: string, host?: string }} [opts] - token defaults to
|
|
25
|
+
* process.env.DISCOVERWORTHY_DEPLOY_TOKEN.
|
|
26
|
+
* @returns {Promise<{ collection: { slug: string, label: string, fields: any[] }, items: Array<Record<string, any>> }>}
|
|
27
|
+
*/
|
|
28
|
+
export async function fetchCollection(slug, opts = {}) {
|
|
29
|
+
if (!slug || typeof slug !== 'string') {
|
|
30
|
+
throw new Error('fetchCollection: slug is required');
|
|
31
|
+
}
|
|
32
|
+
const token = resolveToken(opts);
|
|
33
|
+
if (!token) {
|
|
34
|
+
throw new Error('fetchCollection: no workspace token — pass { token } or set DISCOVERWORTHY_DEPLOY_TOKEN (server-side only)');
|
|
35
|
+
}
|
|
36
|
+
const host = resolveHost(opts);
|
|
37
|
+
const url = `${host}/api/v1/hosting/content/collections/${encodeURIComponent(slug)}`;
|
|
38
|
+
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
throw new Error(`fetchCollection(${slug}) failed: ${res.status}`);
|
|
41
|
+
}
|
|
42
|
+
return res.json();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* List every collection (slug + label + field schema) for the workspace.
|
|
47
|
+
* @param {{ token?: string, host?: string }} [opts]
|
|
48
|
+
* @returns {Promise<{ collections: Array<{ slug: string, label: string, description: string | null, fields: any[] }> }>}
|
|
49
|
+
*/
|
|
50
|
+
export async function listCollections(opts = {}) {
|
|
51
|
+
const token = resolveToken(opts);
|
|
52
|
+
if (!token) {
|
|
53
|
+
throw new Error('listCollections: no workspace token — pass { token } or set DISCOVERWORTHY_DEPLOY_TOKEN (server-side only)');
|
|
54
|
+
}
|
|
55
|
+
const host = resolveHost(opts);
|
|
56
|
+
const res = await fetch(`${host}/api/v1/hosting/content/collections`, {
|
|
57
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
58
|
+
});
|
|
59
|
+
if (!res.ok) {
|
|
60
|
+
throw new Error(`listCollections failed: ${res.status}`);
|
|
61
|
+
}
|
|
62
|
+
return res.json();
|
|
63
|
+
}
|