@obelism/content 0.15.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 +90 -1
- package/dist/index.mjs +86 -2
- 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
|
@@ -186,7 +186,10 @@ class ContentClient {
|
|
|
186
186
|
headers: {
|
|
187
187
|
...headersToObject(merged.headers),
|
|
188
188
|
Accept: 'application/json',
|
|
189
|
-
|
|
189
|
+
// Not `Authorization`: Cloudflare never caches a request that
|
|
190
|
+
// carries it, and this header lets the API answer a delivery
|
|
191
|
+
// token from its edge cache.
|
|
192
|
+
'Obelism-Token': this.#token
|
|
190
193
|
},
|
|
191
194
|
signal: merged.signal ? AbortSignal.any([
|
|
192
195
|
merged.signal,
|
|
@@ -263,6 +266,87 @@ const pickImproveVariant = (block, option, fallback)=>{
|
|
|
263
266
|
return Object.values(variants)[0] ?? [];
|
|
264
267
|
};
|
|
265
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
|
+
|
|
266
350
|
// Links are only rendered for these; anything else (`javascript:` …) keeps its
|
|
267
351
|
// text without the link. The same rule the CMS validates with.
|
|
268
352
|
const SAFE_HREF = /^(https?:\/\/|mailto:|tel:|#|\/(?!\/))/i;
|
|
@@ -432,12 +516,17 @@ exports.ContentApiError = ContentApiError;
|
|
|
432
516
|
exports.ContentClient = ContentClient;
|
|
433
517
|
exports.IMPROVE_BLOCK = IMPROVE_BLOCK;
|
|
434
518
|
exports.SAFE_HREF = SAFE_HREF;
|
|
519
|
+
exports.SHOPIFY_STOREFRONT_API_VERSION = SHOPIFY_STOREFRONT_API_VERSION;
|
|
520
|
+
exports.ShopifyStorefrontError = ShopifyStorefrontError;
|
|
435
521
|
exports.alternateLinks = alternateLinks;
|
|
436
522
|
exports.escapeHtml = escapeHtml;
|
|
523
|
+
exports.fetchShopifyCollection = fetchShopifyCollection;
|
|
524
|
+
exports.fetchShopifyProducts = fetchShopifyProducts;
|
|
437
525
|
exports.headingLevel = headingLevel;
|
|
438
526
|
exports.imageSrcSet = imageSrcSet;
|
|
439
527
|
exports.imageUrl = imageUrl;
|
|
440
528
|
exports.isImproveBlock = isImproveBlock;
|
|
529
|
+
exports.linkHref = linkHref;
|
|
441
530
|
exports.pickImproveVariant = pickImproveVariant;
|
|
442
531
|
exports.richTextToHtml = richTextToHtml;
|
|
443
532
|
exports.richTextToPlainText = richTextToPlainText;
|
package/dist/index.mjs
CHANGED
|
@@ -184,7 +184,10 @@ class ContentClient {
|
|
|
184
184
|
headers: {
|
|
185
185
|
...headersToObject(merged.headers),
|
|
186
186
|
Accept: 'application/json',
|
|
187
|
-
|
|
187
|
+
// Not `Authorization`: Cloudflare never caches a request that
|
|
188
|
+
// carries it, and this header lets the API answer a delivery
|
|
189
|
+
// token from its edge cache.
|
|
190
|
+
'Obelism-Token': this.#token
|
|
188
191
|
},
|
|
189
192
|
signal: merged.signal ? AbortSignal.any([
|
|
190
193
|
merged.signal,
|
|
@@ -261,6 +264,87 @@ const pickImproveVariant = (block, option, fallback)=>{
|
|
|
261
264
|
return Object.values(variants)[0] ?? [];
|
|
262
265
|
};
|
|
263
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
|
+
|
|
264
348
|
// Links are only rendered for these; anything else (`javascript:` …) keeps its
|
|
265
349
|
// text without the link. The same rule the CMS validates with.
|
|
266
350
|
const SAFE_HREF = /^(https?:\/\/|mailto:|tel:|#|\/(?!\/))/i;
|
|
@@ -420,4 +504,4 @@ const sitemapXml = (entries, { exclude = hidden, ...options })=>{
|
|
|
420
504
|
].join('\n');
|
|
421
505
|
};
|
|
422
506
|
|
|
423
|
-
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":[]}
|