@obelism/content 0.16.0 → 0.17.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 +54 -0
- package/dist/bin/content.js +32 -1
- package/dist/index.d.mts +53 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +53 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +86 -0
- package/dist/index.mjs +82 -1
- package/dist/{types-m3j4tltp.d.ts → types-e5nvgudo.d.ts} +32 -2
- package/dist/types-e5nvgudo.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/types-m3j4tltp.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -295,6 +295,27 @@ const xml = sitemapXml(pages, {
|
|
|
295
295
|
})
|
|
296
296
|
```
|
|
297
297
|
|
|
298
|
+
## Links
|
|
299
|
+
|
|
300
|
+
A `link` field goes to a page of the space or to any address, each with an
|
|
301
|
+
optional query and anchor. It is delivered as `Link`: the page's `id`,
|
|
302
|
+
`title`, `slug` and `path` (never its fields), or the `url`, plus `query` and
|
|
303
|
+
`hash` without their `?` and `#`. A link to a page that isn't published is
|
|
304
|
+
`null`. Where a page's path lives on your site is your routing, so
|
|
305
|
+
`linkHref` takes it as `url` (default `/<path>`) and adds the query and hash:
|
|
306
|
+
|
|
307
|
+
```tsx
|
|
308
|
+
import { linkHref } from '@obelism/content'
|
|
309
|
+
|
|
310
|
+
;<a
|
|
311
|
+
href={
|
|
312
|
+
linkHref(button.link, { url: (path) => `/${locale}/${path}` }) ?? undefined
|
|
313
|
+
}
|
|
314
|
+
>
|
|
315
|
+
{button.label}
|
|
316
|
+
</a>
|
|
317
|
+
```
|
|
318
|
+
|
|
298
319
|
## A/B tests and feature flags
|
|
299
320
|
|
|
300
321
|
Any blocks field can hold the built-in improve block (`ImproveBlock`,
|
|
@@ -319,6 +340,38 @@ const blocks = layout.flatMap((block) => {
|
|
|
319
340
|
variant, else the first one, when the option is `null` (the test isn't
|
|
320
341
|
active or the visitor isn't in it) or has no variant.
|
|
321
342
|
|
|
343
|
+
## Shopify
|
|
344
|
+
|
|
345
|
+
A `shopify` field holds a collection or product of the Shopify store
|
|
346
|
+
connected to the space (Settings in Obelism Content), as `ShopifyItem`:
|
|
347
|
+
`{ id, handle }`. Content stores only that, so fetch prices, stock and images
|
|
348
|
+
live from Shopify's Storefront API with the store's domain and Storefront
|
|
349
|
+
token (Settings → Copy as .env):
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
import { fetchShopifyCollection } from '@obelism/content'
|
|
353
|
+
|
|
354
|
+
// A productGrid block: { heading, collection, columns }.
|
|
355
|
+
const collection = await fetchShopifyCollection(block.collection, {
|
|
356
|
+
shop: process.env.SHOPIFY_STORE_DOMAIN!,
|
|
357
|
+
storefrontToken: process.env.SHOPIFY_STOREFRONT_TOKEN!,
|
|
358
|
+
first: 12,
|
|
359
|
+
country: 'NL', // optional: the market's prices
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
collection?.products.map((product) => ({
|
|
363
|
+
href: `/products/${product.handle}`,
|
|
364
|
+
title: product.title,
|
|
365
|
+
price: product.priceRange.minVariantPrice,
|
|
366
|
+
image: product.featuredImage,
|
|
367
|
+
}))
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
`fetchShopifyProducts(items, options)` does the same for a product field
|
|
371
|
+
(one item or a list), in the field's order. Both leave out what the store no
|
|
372
|
+
longer has. A Hydrogen or Liquid site can use the `id` with its own
|
|
373
|
+
Storefront queries instead.
|
|
374
|
+
|
|
322
375
|
## CLI
|
|
323
376
|
|
|
324
377
|
Start a site's repository on a space, in its root:
|
|
@@ -372,6 +425,7 @@ locale, rich text as a document), and `| null` when empty:
|
|
|
372
425
|
| select | the options' values, `'a' \| 'b'` (a list when multiple) |
|
|
373
426
|
| asset | `Asset` (a list when multiple) |
|
|
374
427
|
| reference | the allowed entry types or their `EntryStub`s (a list when multiple) |
|
|
428
|
+
| link | `Link` |
|
|
375
429
|
| blocks | a list of the allowed block types and `ImproveBlock` |
|
|
376
430
|
| richtext | `RichTextDocument` |
|
|
377
431
|
| json | `unknown` |
|
package/dist/bin/content.js
CHANGED
|
@@ -146,6 +146,31 @@ const buildExamples = (model, improve)=>{
|
|
|
146
146
|
improveBlock(field.allowedBlocks, depth + 1)
|
|
147
147
|
] : blocks;
|
|
148
148
|
}
|
|
149
|
+
case 'link':
|
|
150
|
+
return {
|
|
151
|
+
type: 'entry',
|
|
152
|
+
entry: {
|
|
153
|
+
id: 'entry_example',
|
|
154
|
+
title: 'Example page',
|
|
155
|
+
slug: 'example',
|
|
156
|
+
path: 'example'
|
|
157
|
+
},
|
|
158
|
+
query: null,
|
|
159
|
+
hash: null
|
|
160
|
+
};
|
|
161
|
+
case 'shopify':
|
|
162
|
+
{
|
|
163
|
+
const item = field.resource === 'collection' ? {
|
|
164
|
+
id: 'gid://shopify/Collection/1',
|
|
165
|
+
handle: 'example-collection'
|
|
166
|
+
} : {
|
|
167
|
+
id: 'gid://shopify/Product/1',
|
|
168
|
+
handle: 'example-product'
|
|
169
|
+
};
|
|
170
|
+
return field.multiple ? [
|
|
171
|
+
item
|
|
172
|
+
] : item;
|
|
173
|
+
}
|
|
149
174
|
case 'json':
|
|
150
175
|
return {};
|
|
151
176
|
}
|
|
@@ -535,7 +560,9 @@ const SDK_TYPES = [
|
|
|
535
560
|
'Entry',
|
|
536
561
|
'EntryStub',
|
|
537
562
|
'ImproveBlock',
|
|
538
|
-
'
|
|
563
|
+
'Link',
|
|
564
|
+
'RichTextDocument',
|
|
565
|
+
'ShopifyItem'
|
|
539
566
|
];
|
|
540
567
|
// The unions of every entry and block type the file ends with.
|
|
541
568
|
const UNION_NAMES = [
|
|
@@ -607,6 +634,10 @@ const generateTypes = (model)=>{
|
|
|
607
634
|
improve
|
|
608
635
|
]), true);
|
|
609
636
|
}
|
|
637
|
+
case 'link':
|
|
638
|
+
return use('Link');
|
|
639
|
+
case 'shopify':
|
|
640
|
+
return list(use('ShopifyItem'), field.multiple);
|
|
610
641
|
case 'json':
|
|
611
642
|
return 'unknown';
|
|
612
643
|
}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RichTextFormat, E as Entry, a as RedirectList, b as Redirect, c as EntryList, M as ModelFile, A as Asset, B as Block, I as ImproveBlock, d as EntryStub, e as RichTextNode } from './types-
|
|
2
|
-
export { C as ContentTypeDefinition, f as ContentTypeKind, g as Environment, F as FieldDefinition, h as FieldType, i as
|
|
1
|
+
import { R as RichTextFormat, E as Entry, a as RedirectList, b as Redirect, c as EntryList, M as ModelFile, A as Asset, B as Block, I as ImproveBlock, L as Link, S as ShopifyItem, d as EntryStub, e as RichTextNode } from './types-e5nvgudo.js';
|
|
2
|
+
export { C as ContentTypeDefinition, f as ContentTypeKind, g as Environment, F as FieldDefinition, h as FieldType, i as LinkTarget, j as RichTextDocument, k as RichTextMark } from './types-e5nvgudo.js';
|
|
3
3
|
|
|
4
4
|
type ContentClientOptions = {
|
|
5
5
|
token: string;
|
|
@@ -96,6 +96,11 @@ declare const IMPROVE_BLOCK = "_improve";
|
|
|
96
96
|
declare const isImproveBlock: (block: Block) => block is ImproveBlock;
|
|
97
97
|
declare const pickImproveVariant: <TBlock extends Block>(block: ImproveBlock<TBlock>, option: string | null | undefined, fallback?: string) => TBlock[];
|
|
98
98
|
|
|
99
|
+
type LinkHrefOptions = {
|
|
100
|
+
url?: ((path: string) => string) | undefined;
|
|
101
|
+
};
|
|
102
|
+
declare const linkHref: (link: Link | null | undefined, { url }?: LinkHrefOptions) => string | null;
|
|
103
|
+
|
|
99
104
|
declare const PREVIEW_PARAMS: {
|
|
100
105
|
readonly preview: "cms_preview";
|
|
101
106
|
readonly entry: "cms_entry";
|
|
@@ -110,6 +115,50 @@ type PreviewParams = {
|
|
|
110
115
|
type SearchParamsInput = URL | URLSearchParams | string | Record<string, string | string[] | undefined>;
|
|
111
116
|
declare const readPreviewParams: (input: SearchParamsInput) => PreviewParams | null;
|
|
112
117
|
|
|
118
|
+
declare const SHOPIFY_STOREFRONT_API_VERSION = "2026-10";
|
|
119
|
+
type ShopifyStorefrontOptions = {
|
|
120
|
+
shop: string;
|
|
121
|
+
storefrontToken: string;
|
|
122
|
+
country?: string | undefined;
|
|
123
|
+
language?: string | undefined;
|
|
124
|
+
init?: RequestInit | undefined;
|
|
125
|
+
fetch?: typeof fetch | undefined;
|
|
126
|
+
};
|
|
127
|
+
type ShopifyMoney = {
|
|
128
|
+
amount: string;
|
|
129
|
+
currencyCode: string;
|
|
130
|
+
};
|
|
131
|
+
type ShopifyImage = {
|
|
132
|
+
url: string;
|
|
133
|
+
altText: string | null;
|
|
134
|
+
width: number | null;
|
|
135
|
+
height: number | null;
|
|
136
|
+
};
|
|
137
|
+
type ShopifyProduct = {
|
|
138
|
+
id: string;
|
|
139
|
+
handle: string;
|
|
140
|
+
title: string;
|
|
141
|
+
availableForSale: boolean;
|
|
142
|
+
featuredImage: ShopifyImage | null;
|
|
143
|
+
priceRange: {
|
|
144
|
+
minVariantPrice: ShopifyMoney;
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
type ShopifyCollection = {
|
|
148
|
+
id: string;
|
|
149
|
+
handle: string;
|
|
150
|
+
title: string;
|
|
151
|
+
products: ShopifyProduct[];
|
|
152
|
+
};
|
|
153
|
+
declare class ShopifyStorefrontError extends Error {
|
|
154
|
+
readonly status: number;
|
|
155
|
+
constructor(status: number, message: string);
|
|
156
|
+
}
|
|
157
|
+
declare const fetchShopifyCollection: (item: ShopifyItem, options: ShopifyStorefrontOptions & {
|
|
158
|
+
first?: number | undefined;
|
|
159
|
+
}) => Promise<ShopifyCollection | null>;
|
|
160
|
+
declare const fetchShopifyProducts: (items: ShopifyItem | ShopifyItem[] | null | undefined, options: ShopifyStorefrontOptions) => Promise<ShopifyProduct[]>;
|
|
161
|
+
|
|
113
162
|
declare const SAFE_HREF: RegExp;
|
|
114
163
|
declare const escapeHtml: (value: string) => string;
|
|
115
164
|
type RichTextHtmlOptions = {
|
|
@@ -135,6 +184,6 @@ type SitemapOptions = LocalizedUrlOptions & {
|
|
|
135
184
|
};
|
|
136
185
|
declare const sitemapXml: (entries: Iterable<Page>, { exclude, ...options }: SitemapOptions) => string;
|
|
137
186
|
|
|
138
|
-
export { Asset, Block, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, IMPROVE_BLOCK, ImproveBlock, ModelFile, PREVIEW_PARAMS, Redirect, RedirectList, RichTextFormat, RichTextNode, SAFE_HREF, UID_ATTRIBUTE, alternateLinks, editable, escapeHtml, headingLevel, imageSrcSet, imageUrl, isImproveBlock, pickImproveVariant, readPreviewParams, richTextToHtml, richTextToPlainText, sitemapXml };
|
|
139
|
-
export type { AlternateLink, ContentClientOptions, EntriesQuery, ImageTransform, LocalizedUrlOptions, PageLookup, PreviewParams, RedirectsQuery, RequestOptions, RichTextHtmlOptions, SitemapOptions };
|
|
187
|
+
export { Asset, Block, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, IMPROVE_BLOCK, ImproveBlock, Link, ModelFile, PREVIEW_PARAMS, Redirect, RedirectList, RichTextFormat, RichTextNode, SAFE_HREF, SHOPIFY_STOREFRONT_API_VERSION, ShopifyItem, ShopifyStorefrontError, UID_ATTRIBUTE, alternateLinks, editable, escapeHtml, fetchShopifyCollection, fetchShopifyProducts, headingLevel, imageSrcSet, imageUrl, isImproveBlock, linkHref, pickImproveVariant, readPreviewParams, richTextToHtml, richTextToPlainText, sitemapXml };
|
|
188
|
+
export type { AlternateLink, ContentClientOptions, EntriesQuery, ImageTransform, LinkHrefOptions, LocalizedUrlOptions, PageLookup, PreviewParams, RedirectsQuery, RequestOptions, RichTextHtmlOptions, ShopifyCollection, ShopifyImage, ShopifyMoney, ShopifyProduct, ShopifyStorefrontOptions, SitemapOptions };
|
|
140
189
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sources":["../src/client.ts","../src/config.ts","../src/editable.ts","../src/errors.ts","../src/images.ts","../src/improve.ts","../src/preview.ts","../src/richText.ts","../src/sitemap.ts"],"mappings":";;;AACA,KAAK,oBAAoB;AACzB;AACA;AACA;AACA;AACA,eAAe,cAAc;AAC7B;AACA,WAAW,WAAW;AACtB,mBAAmB,KAAK;AACxB;AACA,KAAK,cAAc;AACnB;AACA;AACA,eAAe,cAAc;AAC7B,WAAW,WAAW;AACtB;AACA,KAAK,gBAAgB;AACrB,KAAK,YAAY,GAAG,cAAc;AAClC;AACA;AACA,aAAa,MAAM,SAAS,gBAAgB,YAAY,gBAAgB;AACxE;AACA;AACA;AACA;AACA,KAAK,cAAc;AACnB;AACA;AACA;AACA,WAAW,WAAW;AACtB;AACA,KAAK,UAAU,gBAAgB,KAAK,GAAG,KAAK;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,aAAa;AAC3B;AACA;AACA,2FAA2F,oBAAoB;AAC/G,6BAA6B,KAAK,GAAG,KAAK,0BAA0B,cAAc,KAAK,OAAO;AAC9F,8BAA8B,KAAK,GAAG,KAAK,0BAA0B,cAAc,KAAK,OAAO,CAAC,UAAU;AAC1G,oDAAoD,cAAc,KAAK,OAAO,CAAC,YAAY;AAC3F,4BAA4B,IAAI,CAAC,cAAc,YAAY,cAAc,CAAC,QAAQ;AAClF,8BAA8B,KAAK,GAAG,KAAK,wBAAwB,cAAc,KAAK,OAAO;AAC7F,gCAAgC,KAAK,GAAG,KAAK,wEAAwE,YAAY,KAAK,OAAO,CAAC,SAAS;AACvJ,mCAAmC,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,wBAAwB,OAAO;AAC1G,iCAAiC,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,YAAY,cAAc;AACnG,gCAAgC,IAAI,CAAC,cAAc,cAAc,OAAO,CAAC,SAAS;AAClF;;ACtDA,cAAc,WAAW;;ACAzB,cAAc,aAAa;AAC3B,cAAc,eAAe;AAC7B,KAAK,QAAQ;AACb;AACA;AACA;AACA,cAAc,QAAQ;AACtB;AACA;AACA;AACA;AACA,yBAAyB,QAAQ;;ACXjC,cAAc,eAAe,SAAS,KAAK;AAC3C;AACA;AACA;AACA;AACA;;ACJA,KAAK,cAAc;AACnB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,QAAQ,WAAW,IAAI,CAAC,KAAK,WAAW,OAAO,CAAC,IAAI,CAAC,KAAK,wCAAwC,cAAc;AAC9H,cAAc,WAAW,WAAW,IAAI,CAAC,KAAK,WAAW,OAAO,CAAC,IAAI,CAAC,KAAK,0DAA0D,IAAI,CAAC,cAAc;;ACZxJ,cAAc,aAAa;AAC3B,cAAc,cAAc,UAAU,KAAK,cAAc,YAAY;AACrE,cAAc,kBAAkB,kBAAkB,KAAK,SAAS,YAAY;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","sources":["../src/client.ts","../src/config.ts","../src/editable.ts","../src/errors.ts","../src/images.ts","../src/improve.ts","../src/links.ts","../src/preview.ts","../src/shopify.ts","../src/richText.ts","../src/sitemap.ts"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RichTextFormat, E as Entry, a as RedirectList, b as Redirect, c as EntryList, M as ModelFile, A as Asset, B as Block, I as ImproveBlock, d as EntryStub, e as RichTextNode } from './types-
|
|
2
|
-
export { C as ContentTypeDefinition, f as ContentTypeKind, g as Environment, F as FieldDefinition, h as FieldType, i as
|
|
1
|
+
import { R as RichTextFormat, E as Entry, a as RedirectList, b as Redirect, c as EntryList, M as ModelFile, A as Asset, B as Block, I as ImproveBlock, L as Link, S as ShopifyItem, d as EntryStub, e as RichTextNode } from './types-e5nvgudo.js';
|
|
2
|
+
export { C as ContentTypeDefinition, f as ContentTypeKind, g as Environment, F as FieldDefinition, h as FieldType, i as LinkTarget, j as RichTextDocument, k as RichTextMark } from './types-e5nvgudo.js';
|
|
3
3
|
|
|
4
4
|
type ContentClientOptions = {
|
|
5
5
|
token: string;
|
|
@@ -96,6 +96,11 @@ declare const IMPROVE_BLOCK = "_improve";
|
|
|
96
96
|
declare const isImproveBlock: (block: Block) => block is ImproveBlock;
|
|
97
97
|
declare const pickImproveVariant: <TBlock extends Block>(block: ImproveBlock<TBlock>, option: string | null | undefined, fallback?: string) => TBlock[];
|
|
98
98
|
|
|
99
|
+
type LinkHrefOptions = {
|
|
100
|
+
url?: ((path: string) => string) | undefined;
|
|
101
|
+
};
|
|
102
|
+
declare const linkHref: (link: Link | null | undefined, { url }?: LinkHrefOptions) => string | null;
|
|
103
|
+
|
|
99
104
|
declare const PREVIEW_PARAMS: {
|
|
100
105
|
readonly preview: "cms_preview";
|
|
101
106
|
readonly entry: "cms_entry";
|
|
@@ -110,6 +115,50 @@ type PreviewParams = {
|
|
|
110
115
|
type SearchParamsInput = URL | URLSearchParams | string | Record<string, string | string[] | undefined>;
|
|
111
116
|
declare const readPreviewParams: (input: SearchParamsInput) => PreviewParams | null;
|
|
112
117
|
|
|
118
|
+
declare const SHOPIFY_STOREFRONT_API_VERSION = "2026-10";
|
|
119
|
+
type ShopifyStorefrontOptions = {
|
|
120
|
+
shop: string;
|
|
121
|
+
storefrontToken: string;
|
|
122
|
+
country?: string | undefined;
|
|
123
|
+
language?: string | undefined;
|
|
124
|
+
init?: RequestInit | undefined;
|
|
125
|
+
fetch?: typeof fetch | undefined;
|
|
126
|
+
};
|
|
127
|
+
type ShopifyMoney = {
|
|
128
|
+
amount: string;
|
|
129
|
+
currencyCode: string;
|
|
130
|
+
};
|
|
131
|
+
type ShopifyImage = {
|
|
132
|
+
url: string;
|
|
133
|
+
altText: string | null;
|
|
134
|
+
width: number | null;
|
|
135
|
+
height: number | null;
|
|
136
|
+
};
|
|
137
|
+
type ShopifyProduct = {
|
|
138
|
+
id: string;
|
|
139
|
+
handle: string;
|
|
140
|
+
title: string;
|
|
141
|
+
availableForSale: boolean;
|
|
142
|
+
featuredImage: ShopifyImage | null;
|
|
143
|
+
priceRange: {
|
|
144
|
+
minVariantPrice: ShopifyMoney;
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
type ShopifyCollection = {
|
|
148
|
+
id: string;
|
|
149
|
+
handle: string;
|
|
150
|
+
title: string;
|
|
151
|
+
products: ShopifyProduct[];
|
|
152
|
+
};
|
|
153
|
+
declare class ShopifyStorefrontError extends Error {
|
|
154
|
+
readonly status: number;
|
|
155
|
+
constructor(status: number, message: string);
|
|
156
|
+
}
|
|
157
|
+
declare const fetchShopifyCollection: (item: ShopifyItem, options: ShopifyStorefrontOptions & {
|
|
158
|
+
first?: number | undefined;
|
|
159
|
+
}) => Promise<ShopifyCollection | null>;
|
|
160
|
+
declare const fetchShopifyProducts: (items: ShopifyItem | ShopifyItem[] | null | undefined, options: ShopifyStorefrontOptions) => Promise<ShopifyProduct[]>;
|
|
161
|
+
|
|
113
162
|
declare const SAFE_HREF: RegExp;
|
|
114
163
|
declare const escapeHtml: (value: string) => string;
|
|
115
164
|
type RichTextHtmlOptions = {
|
|
@@ -135,6 +184,6 @@ type SitemapOptions = LocalizedUrlOptions & {
|
|
|
135
184
|
};
|
|
136
185
|
declare const sitemapXml: (entries: Iterable<Page>, { exclude, ...options }: SitemapOptions) => string;
|
|
137
186
|
|
|
138
|
-
export { Asset, Block, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, IMPROVE_BLOCK, ImproveBlock, ModelFile, PREVIEW_PARAMS, Redirect, RedirectList, RichTextFormat, RichTextNode, SAFE_HREF, UID_ATTRIBUTE, alternateLinks, editable, escapeHtml, headingLevel, imageSrcSet, imageUrl, isImproveBlock, pickImproveVariant, readPreviewParams, richTextToHtml, richTextToPlainText, sitemapXml };
|
|
139
|
-
export type { AlternateLink, ContentClientOptions, EntriesQuery, ImageTransform, LocalizedUrlOptions, PageLookup, PreviewParams, RedirectsQuery, RequestOptions, RichTextHtmlOptions, SitemapOptions };
|
|
187
|
+
export { Asset, Block, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, IMPROVE_BLOCK, ImproveBlock, Link, ModelFile, PREVIEW_PARAMS, Redirect, RedirectList, RichTextFormat, RichTextNode, SAFE_HREF, SHOPIFY_STOREFRONT_API_VERSION, ShopifyItem, ShopifyStorefrontError, UID_ATTRIBUTE, alternateLinks, editable, escapeHtml, fetchShopifyCollection, fetchShopifyProducts, headingLevel, imageSrcSet, imageUrl, isImproveBlock, linkHref, pickImproveVariant, readPreviewParams, richTextToHtml, richTextToPlainText, sitemapXml };
|
|
188
|
+
export type { AlternateLink, ContentClientOptions, EntriesQuery, ImageTransform, LinkHrefOptions, LocalizedUrlOptions, PageLookup, PreviewParams, RedirectsQuery, RequestOptions, RichTextHtmlOptions, ShopifyCollection, ShopifyImage, ShopifyMoney, ShopifyProduct, ShopifyStorefrontOptions, SitemapOptions };
|
|
140
189
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sources":["../src/client.ts","../src/config.ts","../src/editable.ts","../src/errors.ts","../src/images.ts","../src/improve.ts","../src/preview.ts","../src/richText.ts","../src/sitemap.ts"],"mappings":";;;AACA,KAAK,oBAAoB;AACzB;AACA;AACA;AACA;AACA,eAAe,cAAc;AAC7B;AACA,WAAW,WAAW;AACtB,mBAAmB,KAAK;AACxB;AACA,KAAK,cAAc;AACnB;AACA;AACA,eAAe,cAAc;AAC7B,WAAW,WAAW;AACtB;AACA,KAAK,gBAAgB;AACrB,KAAK,YAAY,GAAG,cAAc;AAClC;AACA;AACA,aAAa,MAAM,SAAS,gBAAgB,YAAY,gBAAgB;AACxE;AACA;AACA;AACA;AACA,KAAK,cAAc;AACnB;AACA;AACA;AACA,WAAW,WAAW;AACtB;AACA,KAAK,UAAU,gBAAgB,KAAK,GAAG,KAAK;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,aAAa;AAC3B;AACA;AACA,2FAA2F,oBAAoB;AAC/G,6BAA6B,KAAK,GAAG,KAAK,0BAA0B,cAAc,KAAK,OAAO;AAC9F,8BAA8B,KAAK,GAAG,KAAK,0BAA0B,cAAc,KAAK,OAAO,CAAC,UAAU;AAC1G,oDAAoD,cAAc,KAAK,OAAO,CAAC,YAAY;AAC3F,4BAA4B,IAAI,CAAC,cAAc,YAAY,cAAc,CAAC,QAAQ;AAClF,8BAA8B,KAAK,GAAG,KAAK,wBAAwB,cAAc,KAAK,OAAO;AAC7F,gCAAgC,KAAK,GAAG,KAAK,wEAAwE,YAAY,KAAK,OAAO,CAAC,SAAS;AACvJ,mCAAmC,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,wBAAwB,OAAO;AAC1G,iCAAiC,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,YAAY,cAAc;AACnG,gCAAgC,IAAI,CAAC,cAAc,cAAc,OAAO,CAAC,SAAS;AAClF;;ACtDA,cAAc,WAAW;;ACAzB,cAAc,aAAa;AAC3B,cAAc,eAAe;AAC7B,KAAK,QAAQ;AACb;AACA;AACA;AACA,cAAc,QAAQ;AACtB;AACA;AACA;AACA;AACA,yBAAyB,QAAQ;;ACXjC,cAAc,eAAe,SAAS,KAAK;AAC3C;AACA;AACA;AACA;AACA;;ACJA,KAAK,cAAc;AACnB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,QAAQ,WAAW,IAAI,CAAC,KAAK,WAAW,OAAO,CAAC,IAAI,CAAC,KAAK,wCAAwC,cAAc;AAC9H,cAAc,WAAW,WAAW,IAAI,CAAC,KAAK,WAAW,OAAO,CAAC,IAAI,CAAC,KAAK,0DAA0D,IAAI,CAAC,cAAc;;ACZxJ,cAAc,aAAa;AAC3B,cAAc,cAAc,UAAU,KAAK,cAAc,YAAY;AACrE,cAAc,kBAAkB,kBAAkB,KAAK,SAAS,YAAY;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sources":["../src/client.ts","../src/config.ts","../src/editable.ts","../src/errors.ts","../src/images.ts","../src/improve.ts","../src/links.ts","../src/preview.ts","../src/shopify.ts","../src/richText.ts","../src/sitemap.ts"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -266,6 +266,87 @@ const pickImproveVariant = (block, option, fallback)=>{
|
|
|
266
266
|
return Object.values(variants)[0] ?? [];
|
|
267
267
|
};
|
|
268
268
|
|
|
269
|
+
const linkHref = (link, { url = (path)=>`/${path}` } = {})=>{
|
|
270
|
+
if (!link) return null;
|
|
271
|
+
if (link.type === 'entry' && link.entry.path === null) return null;
|
|
272
|
+
const base = link.type === 'url' ? link.url : url(link.entry.path);
|
|
273
|
+
const hashAt = base.indexOf('#');
|
|
274
|
+
const address = hashAt === -1 ? base : base.slice(0, hashAt);
|
|
275
|
+
const ownHash = hashAt === -1 ? null : base.slice(hashAt + 1);
|
|
276
|
+
const withQuery = link.query ? `${address}${address.includes('?') ? '&' : '?'}${link.query}` : address;
|
|
277
|
+
const hash = link.hash ?? ownHash;
|
|
278
|
+
return hash ? `${withQuery}#${hash}` : withQuery;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// What a `shopify` field points at, live from the store: the Storefront API
|
|
282
|
+
// with the space's Storefront token (Settings → Copy as .env:
|
|
283
|
+
// SHOPIFY_STORE_DOMAIN and SHOPIFY_STOREFRONT_TOKEN, safe in the browser).
|
|
284
|
+
// Content stores only `{ id, handle }`, so prices and stock are never stale.
|
|
285
|
+
// A Hydrogen or Liquid site can query by the id itself instead.
|
|
286
|
+
// The Storefront API version the queries are written for.
|
|
287
|
+
const SHOPIFY_STOREFRONT_API_VERSION = '2026-10';
|
|
288
|
+
class ShopifyStorefrontError extends Error {
|
|
289
|
+
constructor(status, message){
|
|
290
|
+
super(message), this.status = status;
|
|
291
|
+
this.name = 'ShopifyStorefrontError';
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const PRODUCT_FIELDS = 'id handle title availableForSale featuredImage { url altText width height } priceRange { minVariantPrice { amount currencyCode } }';
|
|
295
|
+
const CONTEXT_VARIABLES = '$country: CountryCode, $language: LanguageCode';
|
|
296
|
+
const IN_CONTEXT = '@inContext(country: $country, language: $language)';
|
|
297
|
+
const storefront = async (options, query, variables)=>{
|
|
298
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
299
|
+
const response = await fetchImpl(`https://${options.shop}/api/${SHOPIFY_STOREFRONT_API_VERSION}/graphql.json`, {
|
|
300
|
+
...options.init,
|
|
301
|
+
method: 'POST',
|
|
302
|
+
headers: {
|
|
303
|
+
...options.init?.headers,
|
|
304
|
+
'content-type': 'application/json',
|
|
305
|
+
'x-shopify-storefront-access-token': options.storefrontToken
|
|
306
|
+
},
|
|
307
|
+
body: JSON.stringify({
|
|
308
|
+
query,
|
|
309
|
+
variables: {
|
|
310
|
+
...variables,
|
|
311
|
+
country: options.country?.toUpperCase() ?? null,
|
|
312
|
+
language: options.language?.toUpperCase() ?? null
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
});
|
|
316
|
+
if (!response.ok) throw new ShopifyStorefrontError(response.status, `Shopify's Storefront API answered ${response.status}`);
|
|
317
|
+
const body = await response.json();
|
|
318
|
+
if (body.errors?.length || !body.data) throw new ShopifyStorefrontError(response.status, body.errors?.map((error)=>error.message).join('; ') || 'No data from the Storefront API');
|
|
319
|
+
return body.data;
|
|
320
|
+
};
|
|
321
|
+
// A collection field's collection with its first `first` products (default
|
|
322
|
+
// 12, at most 250), in the collection's order; null when the store no
|
|
323
|
+
// longer has it or it isn't published to the token's sales channel.
|
|
324
|
+
const fetchShopifyCollection = async (item, options)=>{
|
|
325
|
+
const first = Math.min(Math.max(Math.trunc(options.first ?? 12), 1), 250);
|
|
326
|
+
const data = await storefront(options, `query Collection($id: ID!, $first: Int!, ${CONTEXT_VARIABLES}) ${IN_CONTEXT} { collection(id: $id) { id handle title products(first: $first) { nodes { ${PRODUCT_FIELDS} } } } }`, {
|
|
327
|
+
id: item.id,
|
|
328
|
+
first
|
|
329
|
+
});
|
|
330
|
+
if (!data.collection) return null;
|
|
331
|
+
const { products, ...collection } = data.collection;
|
|
332
|
+
return {
|
|
333
|
+
...collection,
|
|
334
|
+
products: products.nodes
|
|
335
|
+
};
|
|
336
|
+
};
|
|
337
|
+
// A product field's products, in the field's order; one the store no
|
|
338
|
+
// longer has (or doesn't sell through this channel) is left out.
|
|
339
|
+
const fetchShopifyProducts = async (items, options)=>{
|
|
340
|
+
const ids = (Array.isArray(items) ? items : items ? [
|
|
341
|
+
items
|
|
342
|
+
] : []).map(({ id })=>id).slice(0, 250);
|
|
343
|
+
if (ids.length === 0) return [];
|
|
344
|
+
const data = await storefront(options, `query Products($ids: [ID!]!, ${CONTEXT_VARIABLES}) ${IN_CONTEXT} { nodes(ids: $ids) { ... on Product { ${PRODUCT_FIELDS} } } }`, {
|
|
345
|
+
ids
|
|
346
|
+
});
|
|
347
|
+
return data.nodes.filter((node)=>Boolean(node && 'id' in node));
|
|
348
|
+
};
|
|
349
|
+
|
|
269
350
|
// Links are only rendered for these; anything else (`javascript:` …) keeps its
|
|
270
351
|
// text without the link. The same rule the CMS validates with.
|
|
271
352
|
const SAFE_HREF = /^(https?:\/\/|mailto:|tel:|#|\/(?!\/))/i;
|
|
@@ -435,12 +516,17 @@ exports.ContentApiError = ContentApiError;
|
|
|
435
516
|
exports.ContentClient = ContentClient;
|
|
436
517
|
exports.IMPROVE_BLOCK = IMPROVE_BLOCK;
|
|
437
518
|
exports.SAFE_HREF = SAFE_HREF;
|
|
519
|
+
exports.SHOPIFY_STOREFRONT_API_VERSION = SHOPIFY_STOREFRONT_API_VERSION;
|
|
520
|
+
exports.ShopifyStorefrontError = ShopifyStorefrontError;
|
|
438
521
|
exports.alternateLinks = alternateLinks;
|
|
439
522
|
exports.escapeHtml = escapeHtml;
|
|
523
|
+
exports.fetchShopifyCollection = fetchShopifyCollection;
|
|
524
|
+
exports.fetchShopifyProducts = fetchShopifyProducts;
|
|
440
525
|
exports.headingLevel = headingLevel;
|
|
441
526
|
exports.imageSrcSet = imageSrcSet;
|
|
442
527
|
exports.imageUrl = imageUrl;
|
|
443
528
|
exports.isImproveBlock = isImproveBlock;
|
|
529
|
+
exports.linkHref = linkHref;
|
|
444
530
|
exports.pickImproveVariant = pickImproveVariant;
|
|
445
531
|
exports.richTextToHtml = richTextToHtml;
|
|
446
532
|
exports.richTextToPlainText = richTextToPlainText;
|
package/dist/index.mjs
CHANGED
|
@@ -264,6 +264,87 @@ const pickImproveVariant = (block, option, fallback)=>{
|
|
|
264
264
|
return Object.values(variants)[0] ?? [];
|
|
265
265
|
};
|
|
266
266
|
|
|
267
|
+
const linkHref = (link, { url = (path)=>`/${path}` } = {})=>{
|
|
268
|
+
if (!link) return null;
|
|
269
|
+
if (link.type === 'entry' && link.entry.path === null) return null;
|
|
270
|
+
const base = link.type === 'url' ? link.url : url(link.entry.path);
|
|
271
|
+
const hashAt = base.indexOf('#');
|
|
272
|
+
const address = hashAt === -1 ? base : base.slice(0, hashAt);
|
|
273
|
+
const ownHash = hashAt === -1 ? null : base.slice(hashAt + 1);
|
|
274
|
+
const withQuery = link.query ? `${address}${address.includes('?') ? '&' : '?'}${link.query}` : address;
|
|
275
|
+
const hash = link.hash ?? ownHash;
|
|
276
|
+
return hash ? `${withQuery}#${hash}` : withQuery;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// What a `shopify` field points at, live from the store: the Storefront API
|
|
280
|
+
// with the space's Storefront token (Settings → Copy as .env:
|
|
281
|
+
// SHOPIFY_STORE_DOMAIN and SHOPIFY_STOREFRONT_TOKEN, safe in the browser).
|
|
282
|
+
// Content stores only `{ id, handle }`, so prices and stock are never stale.
|
|
283
|
+
// A Hydrogen or Liquid site can query by the id itself instead.
|
|
284
|
+
// The Storefront API version the queries are written for.
|
|
285
|
+
const SHOPIFY_STOREFRONT_API_VERSION = '2026-10';
|
|
286
|
+
class ShopifyStorefrontError extends Error {
|
|
287
|
+
constructor(status, message){
|
|
288
|
+
super(message), this.status = status;
|
|
289
|
+
this.name = 'ShopifyStorefrontError';
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const PRODUCT_FIELDS = 'id handle title availableForSale featuredImage { url altText width height } priceRange { minVariantPrice { amount currencyCode } }';
|
|
293
|
+
const CONTEXT_VARIABLES = '$country: CountryCode, $language: LanguageCode';
|
|
294
|
+
const IN_CONTEXT = '@inContext(country: $country, language: $language)';
|
|
295
|
+
const storefront = async (options, query, variables)=>{
|
|
296
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
297
|
+
const response = await fetchImpl(`https://${options.shop}/api/${SHOPIFY_STOREFRONT_API_VERSION}/graphql.json`, {
|
|
298
|
+
...options.init,
|
|
299
|
+
method: 'POST',
|
|
300
|
+
headers: {
|
|
301
|
+
...options.init?.headers,
|
|
302
|
+
'content-type': 'application/json',
|
|
303
|
+
'x-shopify-storefront-access-token': options.storefrontToken
|
|
304
|
+
},
|
|
305
|
+
body: JSON.stringify({
|
|
306
|
+
query,
|
|
307
|
+
variables: {
|
|
308
|
+
...variables,
|
|
309
|
+
country: options.country?.toUpperCase() ?? null,
|
|
310
|
+
language: options.language?.toUpperCase() ?? null
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
});
|
|
314
|
+
if (!response.ok) throw new ShopifyStorefrontError(response.status, `Shopify's Storefront API answered ${response.status}`);
|
|
315
|
+
const body = await response.json();
|
|
316
|
+
if (body.errors?.length || !body.data) throw new ShopifyStorefrontError(response.status, body.errors?.map((error)=>error.message).join('; ') || 'No data from the Storefront API');
|
|
317
|
+
return body.data;
|
|
318
|
+
};
|
|
319
|
+
// A collection field's collection with its first `first` products (default
|
|
320
|
+
// 12, at most 250), in the collection's order; null when the store no
|
|
321
|
+
// longer has it or it isn't published to the token's sales channel.
|
|
322
|
+
const fetchShopifyCollection = async (item, options)=>{
|
|
323
|
+
const first = Math.min(Math.max(Math.trunc(options.first ?? 12), 1), 250);
|
|
324
|
+
const data = await storefront(options, `query Collection($id: ID!, $first: Int!, ${CONTEXT_VARIABLES}) ${IN_CONTEXT} { collection(id: $id) { id handle title products(first: $first) { nodes { ${PRODUCT_FIELDS} } } } }`, {
|
|
325
|
+
id: item.id,
|
|
326
|
+
first
|
|
327
|
+
});
|
|
328
|
+
if (!data.collection) return null;
|
|
329
|
+
const { products, ...collection } = data.collection;
|
|
330
|
+
return {
|
|
331
|
+
...collection,
|
|
332
|
+
products: products.nodes
|
|
333
|
+
};
|
|
334
|
+
};
|
|
335
|
+
// A product field's products, in the field's order; one the store no
|
|
336
|
+
// longer has (or doesn't sell through this channel) is left out.
|
|
337
|
+
const fetchShopifyProducts = async (items, options)=>{
|
|
338
|
+
const ids = (Array.isArray(items) ? items : items ? [
|
|
339
|
+
items
|
|
340
|
+
] : []).map(({ id })=>id).slice(0, 250);
|
|
341
|
+
if (ids.length === 0) return [];
|
|
342
|
+
const data = await storefront(options, `query Products($ids: [ID!]!, ${CONTEXT_VARIABLES}) ${IN_CONTEXT} { nodes(ids: $ids) { ... on Product { ${PRODUCT_FIELDS} } } }`, {
|
|
343
|
+
ids
|
|
344
|
+
});
|
|
345
|
+
return data.nodes.filter((node)=>Boolean(node && 'id' in node));
|
|
346
|
+
};
|
|
347
|
+
|
|
267
348
|
// Links are only rendered for these; anything else (`javascript:` …) keeps its
|
|
268
349
|
// text without the link. The same rule the CMS validates with.
|
|
269
350
|
const SAFE_HREF = /^(https?:\/\/|mailto:|tel:|#|\/(?!\/))/i;
|
|
@@ -423,4 +504,4 @@ const sitemapXml = (entries, { exclude = hidden, ...options })=>{
|
|
|
423
504
|
].join('\n');
|
|
424
505
|
};
|
|
425
506
|
|
|
426
|
-
export { ContentApiError, ContentClient, DEFAULT_URL, IMPROVE_BLOCK, SAFE_HREF, alternateLinks, escapeHtml, headingLevel, imageSrcSet, imageUrl, isImproveBlock, pickImproveVariant, richTextToHtml, richTextToPlainText, sitemapXml };
|
|
507
|
+
export { ContentApiError, ContentClient, DEFAULT_URL, IMPROVE_BLOCK, SAFE_HREF, SHOPIFY_STOREFRONT_API_VERSION, ShopifyStorefrontError, alternateLinks, escapeHtml, fetchShopifyCollection, fetchShopifyProducts, headingLevel, imageSrcSet, imageUrl, isImproveBlock, linkHref, pickImproveVariant, richTextToHtml, richTextToPlainText, sitemapXml };
|
|
@@ -31,6 +31,23 @@ type EntryStub<TContentType extends string = string> = {
|
|
|
31
31
|
id: string;
|
|
32
32
|
contentType: TContentType;
|
|
33
33
|
};
|
|
34
|
+
type LinkTarget = {
|
|
35
|
+
id: string;
|
|
36
|
+
title: string | Record<string, string>;
|
|
37
|
+
slug: string | null;
|
|
38
|
+
path: string | null;
|
|
39
|
+
};
|
|
40
|
+
type Link = {
|
|
41
|
+
type: 'entry';
|
|
42
|
+
entry: LinkTarget;
|
|
43
|
+
query: string | null;
|
|
44
|
+
hash: string | null;
|
|
45
|
+
} | {
|
|
46
|
+
type: 'url';
|
|
47
|
+
url: string;
|
|
48
|
+
query: string | null;
|
|
49
|
+
hash: string | null;
|
|
50
|
+
};
|
|
34
51
|
type Block<TFields extends Record<string, unknown> = Record<string, unknown>, TType extends string = string> = {
|
|
35
52
|
_uid: string;
|
|
36
53
|
_type: TType;
|
|
@@ -71,6 +88,10 @@ type RedirectList = {
|
|
|
71
88
|
limit: number;
|
|
72
89
|
skip: number;
|
|
73
90
|
};
|
|
91
|
+
type ShopifyItem = {
|
|
92
|
+
id: string;
|
|
93
|
+
handle: string;
|
|
94
|
+
};
|
|
74
95
|
type ContentTypeKind = 'page' | 'data' | 'block';
|
|
75
96
|
type BaseField = {
|
|
76
97
|
apiId: string;
|
|
@@ -128,11 +149,20 @@ type FieldDefinition = BaseField & ({
|
|
|
128
149
|
contentTypes?: string[];
|
|
129
150
|
min?: number;
|
|
130
151
|
max?: number;
|
|
152
|
+
} | {
|
|
153
|
+
type: 'link';
|
|
154
|
+
contentTypes?: string[];
|
|
131
155
|
} | {
|
|
132
156
|
type: 'blocks';
|
|
133
157
|
allowedBlocks?: string[];
|
|
134
158
|
min?: number;
|
|
135
159
|
max?: number;
|
|
160
|
+
} | {
|
|
161
|
+
type: 'shopify';
|
|
162
|
+
resource: 'collection' | 'product';
|
|
163
|
+
multiple?: boolean;
|
|
164
|
+
min?: number;
|
|
165
|
+
max?: number;
|
|
136
166
|
} | {
|
|
137
167
|
type: 'json';
|
|
138
168
|
});
|
|
@@ -150,5 +180,5 @@ type ModelFile = {
|
|
|
150
180
|
contentTypes: ContentTypeDefinition[];
|
|
151
181
|
};
|
|
152
182
|
|
|
153
|
-
export type { Asset as A, Block as B, ContentTypeDefinition as C, Entry as E, FieldDefinition as F, ImproveBlock as I, ModelFile as M, RichTextFormat as R, RedirectList as a, Redirect as b, EntryList as c, EntryStub as d, RichTextNode as e, ContentTypeKind as f, Environment as g, FieldType as h,
|
|
154
|
-
//# sourceMappingURL=types-
|
|
183
|
+
export type { Asset as A, Block as B, ContentTypeDefinition as C, Entry as E, FieldDefinition as F, ImproveBlock as I, Link as L, ModelFile as M, RichTextFormat as R, ShopifyItem as S, RedirectList as a, Redirect as b, EntryList as c, EntryStub as d, RichTextNode as e, ContentTypeKind as f, Environment as g, FieldType as h, LinkTarget as i, RichTextDocument as j, RichTextMark as k };
|
|
184
|
+
//# sourceMappingURL=types-e5nvgudo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-e5nvgudo.d.ts","sources":["../src/types.ts"],"mappings":"AAAA,KAAK,WAAW;AAChB,KAAK,cAAc;AACnB,KAAK,KAAK;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,MAAM;AAC1B,kBAAkB,MAAM;AACxB;AACA;AACA;AACA;AACA;AACA,KAAK,KAAK,iBAAiB,MAAM,oBAAoB,MAAM;AAC3D;AACA;AACA;AACA;AACA;AACA,WAAW,MAAM;AACjB;AACA;AACA;AACA;AACA;AACA,KAAK,SAAS;AACd;AACA;AACA;AACA,KAAK,UAAU;AACf;AACA,oBAAoB,MAAM;AAC1B;AACA;AACA;AACA,KAAK,IAAI;AACT;AACA,WAAW,UAAU;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,KAAK,iBAAiB,MAAM,oBAAoB,MAAM;AAC3D;AACA;AACA;AACA,KAAK,YAAY,gBAAgB,KAAK,GAAG,KAAK,IAAI,KAAK;AACvD;AACA;AACA,cAAc,MAAM;AACpB;AACA,KAAK,YAAY;AACjB;AACA,YAAY,MAAM;AAClB;AACA,KAAK,YAAY;AACjB;AACA,YAAY,MAAM;AAClB,cAAc,YAAY;AAC1B;AACA,YAAY,YAAY;AACxB;AACA,KAAK,gBAAgB,GAAG,YAAY;AACpC;AACA;AACA,KAAK,SAAS,gBAAgB,KAAK,GAAG,KAAK;AAC3C;AACA;AACA;AACA;AACA;AACA,KAAK,QAAQ;AACb;AACA;AACA;AACA;AACA,KAAK,YAAY;AACjB,WAAW,QAAQ;AACnB;AACA;AACA;AACA;AACA,KAAK,WAAW;AAChB;AACA;AACA;AACA,KAAK,eAAe;AACpB,KAAK,SAAS;AACd;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,eAAe,GAAG,SAAS;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,SAAS,GAAG,eAAe;AAChC,KAAK,qBAAqB;AAC1B;AACA;AACA,UAAU,eAAe;AACzB;AACA,YAAY,eAAe;AAC3B;AACA,KAAK,SAAS;AACd;AACA;AACA,kBAAkB,qBAAqB;AACvC;;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@obelism/content",
|
|
3
3
|
"description": "Obelism Content SDK: the delivery API client, the visual editor bridge with prompt editing, editing on your own site, webhook verification, and the content model CLI",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.17.0",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cms",
|
|
7
7
|
"headless-cms",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-m3j4tltp.d.ts","sources":["../src/types.ts"],"mappings":"AAAA,KAAK,WAAW;AAChB,KAAK,cAAc;AACnB,KAAK,KAAK;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,MAAM;AAC1B,kBAAkB,MAAM;AACxB;AACA;AACA;AACA;AACA;AACA,KAAK,KAAK,iBAAiB,MAAM,oBAAoB,MAAM;AAC3D;AACA;AACA;AACA;AACA;AACA,WAAW,MAAM;AACjB;AACA;AACA;AACA;AACA;AACA,KAAK,SAAS;AACd;AACA;AACA;AACA,KAAK,KAAK,iBAAiB,MAAM,oBAAoB,MAAM;AAC3D;AACA;AACA;AACA,KAAK,YAAY,gBAAgB,KAAK,GAAG,KAAK,IAAI,KAAK;AACvD;AACA;AACA,cAAc,MAAM;AACpB;AACA,KAAK,YAAY;AACjB;AACA,YAAY,MAAM;AAClB;AACA,KAAK,YAAY;AACjB;AACA,YAAY,MAAM;AAClB,cAAc,YAAY;AAC1B;AACA,YAAY,YAAY;AACxB;AACA,KAAK,gBAAgB,GAAG,YAAY;AACpC;AACA;AACA,KAAK,SAAS,gBAAgB,KAAK,GAAG,KAAK;AAC3C;AACA;AACA;AACA;AACA;AACA,KAAK,QAAQ;AACb;AACA;AACA;AACA;AACA,KAAK,YAAY;AACjB,WAAW,QAAQ;AACnB;AACA;AACA;AACA;AACA,KAAK,eAAe;AACpB,KAAK,SAAS;AACd;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,eAAe,GAAG,SAAS;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,SAAS,GAAG,eAAe;AAChC,KAAK,qBAAqB;AAC1B;AACA;AACA,UAAU,eAAe;AACzB;AACA,YAAY,eAAe;AAC3B;AACA,KAAK,SAAS;AACd;AACA;AACA,kBAAkB,qBAAqB;AACvC;;","names":[]}
|