@ethlete/contentful 4.0.0-next.12 → 4.0.0-next.13

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.
@@ -1,8 +1,8 @@
1
- import { NgClass, DOCUMENT } from '@angular/common';
2
1
  import * as i0 from '@angular/core';
3
2
  import { input, computed, ViewEncapsulation, Component, InjectionToken, inject, numberAttribute, booleanAttribute, isDevMode, ViewContainerRef, ElementRef, effect, untracked, signal, reflectComponentType, inputBinding } from '@angular/core';
4
3
  import { formatFileSize, normalizePictureSizes, PictureComponent } from '@ethlete/components';
5
- import { defineLabels, toProvideFn, toInjectFn, toToken, injectUrl, RuntimeError, isObject, injectRenderer, getObjectProperty } from '@ethlete/core';
4
+ import { defineLabels, toProvideFn, toInjectFn, toToken, injectUrl, RuntimeError, isObject, getObjectProperty, injectRenderer } from '@ethlete/core';
5
+ import { DOCUMENT, Location } from '@angular/common';
6
6
  import { Router, RouterLink } from '@angular/router';
7
7
 
8
8
  const GQL_FRAGMENT_CONTENTFUL_ASSET = `
@@ -29,12 +29,6 @@ class ContentfulAudioComponent {
29
29
  constructor() {
30
30
  this.asset = input.required(/* @ts-ignore */
31
31
  ...(ngDevMode ? [{ debugName: "asset" }] : /* istanbul ignore next */ []));
32
- this.audioClass = input(null, /* @ts-ignore */
33
- ...(ngDevMode ? [{ debugName: "audioClass" }] : /* istanbul ignore next */ []));
34
- this.figureClass = input(null, /* @ts-ignore */
35
- ...(ngDevMode ? [{ debugName: "figureClass" }] : /* istanbul ignore next */ []));
36
- this.figcaptionClass = input(null, /* @ts-ignore */
37
- ...(ngDevMode ? [{ debugName: "figcaptionClass" }] : /* istanbul ignore next */ []));
38
32
  this.data = computed(() => {
39
33
  const asset = this.asset();
40
34
  if (!asset) {
@@ -57,14 +51,16 @@ class ContentfulAudioComponent {
57
51
  ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
58
52
  }
59
53
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulAudioComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
60
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ContentfulAudioComponent, isStandalone: true, selector: "et-contentful-audio", inputs: { asset: { classPropertyName: "asset", publicName: "asset", isSignal: true, isRequired: true, transformFunction: null }, audioClass: { classPropertyName: "audioClass", publicName: "audioClass", isSignal: true, isRequired: false, transformFunction: null }, figureClass: { classPropertyName: "figureClass", publicName: "figureClass", isSignal: true, isRequired: false, transformFunction: null }, figcaptionClass: { classPropertyName: "figcaptionClass", publicName: "figcaptionClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "et-contentful-audio" }, ngImport: i0, template: `
54
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ContentfulAudioComponent, isStandalone: true, selector: "et-contentful-audio", inputs: { asset: { classPropertyName: "asset", publicName: "asset", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "et-contentful-audio" }, ngImport: i0, template: `
61
55
  @if (data(); as data) {
62
- <figure [ngClass]="figureClass()">
63
- <figcaption [ngClass]="figcaptionClass()">{{ data.title }}</figcaption>
64
- <audio [ngClass]="audioClass()" [src]="data.url" controls></audio>
56
+ <figure class="et-contentful-audio-figure">
57
+ @if (data.title) {
58
+ <figcaption class="et-contentful-audio-figcaption">{{ data.title }}</figcaption>
59
+ }
60
+ <audio [src]="data.url" class="et-contentful-audio-audio" controls></audio>
65
61
  </figure>
66
62
  }
67
- `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], encapsulation: i0.ViewEncapsulation.None }); }
63
+ `, isInline: true, encapsulation: i0.ViewEncapsulation.None }); }
68
64
  }
69
65
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulAudioComponent, decorators: [{
70
66
  type: Component,
@@ -72,19 +68,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
72
68
  selector: 'et-contentful-audio',
73
69
  template: `
74
70
  @if (data(); as data) {
75
- <figure [ngClass]="figureClass()">
76
- <figcaption [ngClass]="figcaptionClass()">{{ data.title }}</figcaption>
77
- <audio [ngClass]="audioClass()" [src]="data.url" controls></audio>
71
+ <figure class="et-contentful-audio-figure">
72
+ @if (data.title) {
73
+ <figcaption class="et-contentful-audio-figcaption">{{ data.title }}</figcaption>
74
+ }
75
+ <audio [src]="data.url" class="et-contentful-audio-audio" controls></audio>
78
76
  </figure>
79
77
  }
80
78
  `,
81
79
  encapsulation: ViewEncapsulation.None,
82
- imports: [NgClass],
83
80
  host: {
84
81
  class: 'et-contentful-audio',
85
82
  },
86
83
  }]
87
- }], propDecorators: { asset: [{ type: i0.Input, args: [{ isSignal: true, alias: "asset", required: true }] }], audioClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "audioClass", required: false }] }], figureClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "figureClass", required: false }] }], figcaptionClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "figcaptionClass", required: false }] }] } });
84
+ }], propDecorators: { asset: [{ type: i0.Input, args: [{ isSignal: true, alias: "asset", required: true }] }] } });
88
85
 
89
86
  /** The built-in English labels. */
90
87
  const DEFAULT_CONTENTFUL_FILE_LABELS = {
@@ -107,8 +104,6 @@ class ContentfulFileComponent {
107
104
  this.labels = injectContentfulFileLabels();
108
105
  this.asset = input.required(/* @ts-ignore */
109
106
  ...(ngDevMode ? [{ debugName: "asset" }] : /* istanbul ignore next */ []));
110
- this.fileClass = input(null, /* @ts-ignore */
111
- ...(ngDevMode ? [{ debugName: "fileClass" }] : /* istanbul ignore next */ []));
112
107
  this.data = computed(() => {
113
108
  const asset = this.asset();
114
109
  if (!asset) {
@@ -133,16 +128,16 @@ class ContentfulFileComponent {
133
128
  ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
134
129
  }
135
130
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulFileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
136
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ContentfulFileComponent, isStandalone: true, selector: "et-contentful-file", inputs: { asset: { classPropertyName: "asset", publicName: "asset", isSignal: true, isRequired: true, transformFunction: null }, fileClass: { classPropertyName: "fileClass", publicName: "fileClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "et-contentful-file" }, ngImport: i0, template: `
131
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ContentfulFileComponent, isStandalone: true, selector: "et-contentful-file", inputs: { asset: { classPropertyName: "asset", publicName: "asset", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "et-contentful-file" }, ngImport: i0, template: `
137
132
  @if (data(); as data) {
138
- <a [href]="data.url" [ngClass]="fileClass()" target="_blank" rel="noopener noreferrer">
133
+ <a [href]="data.url" class="et-contentful-file-anchor" target="_blank" rel="noopener noreferrer">
139
134
  {{ data.title }}
140
135
  @if (data.size !== null) {
141
136
  {{ labels().fileSize(data.size) }}
142
137
  }
143
138
  </a>
144
139
  }
145
- `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], encapsulation: i0.ViewEncapsulation.None }); }
140
+ `, isInline: true, encapsulation: i0.ViewEncapsulation.None }); }
146
141
  }
147
142
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulFileComponent, decorators: [{
148
143
  type: Component,
@@ -150,7 +145,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
150
145
  selector: 'et-contentful-file',
151
146
  template: `
152
147
  @if (data(); as data) {
153
- <a [href]="data.url" [ngClass]="fileClass()" target="_blank" rel="noopener noreferrer">
148
+ <a [href]="data.url" class="et-contentful-file-anchor" target="_blank" rel="noopener noreferrer">
154
149
  {{ data.title }}
155
150
  @if (data.size !== null) {
156
151
  {{ labels().fileSize(data.size) }}
@@ -159,12 +154,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
159
154
  }
160
155
  `,
161
156
  encapsulation: ViewEncapsulation.None,
162
- imports: [NgClass],
163
157
  host: {
164
158
  class: 'et-contentful-file',
165
159
  },
166
160
  }]
167
- }], propDecorators: { asset: [{ type: i0.Input, args: [{ isSignal: true, alias: "asset", required: true }] }], fileClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileClass", required: false }] }] } });
161
+ }], propDecorators: { asset: [{ type: i0.Input, args: [{ isSignal: true, alias: "asset", required: true }] }] } });
168
162
 
169
163
  const CONTENTFUL_CONFIG = new InjectionToken('ContentfulConfig');
170
164
 
@@ -183,13 +177,16 @@ const CONTENTFUL_FALLBACK_CONFIG = {
183
177
  backgroundColor: null,
184
178
  },
185
179
  };
180
+ const withoutUndefined = (value) => Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined));
186
181
  const createContentfulConfig = (options) => {
187
182
  const config = options ?? {};
188
183
  return {
189
184
  ...CONTENTFUL_FALLBACK_CONFIG,
190
185
  ...config,
191
186
  components: config.components ?? CONTENTFUL_FALLBACK_CONFIG.components,
192
- imageOptions: { ...CONTENTFUL_FALLBACK_CONFIG.imageOptions, ...config.imageOptions },
187
+ customComponents: config.customComponents ?? CONTENTFUL_FALLBACK_CONFIG.customComponents,
188
+ internalHosts: config.internalHosts ?? CONTENTFUL_FALLBACK_CONFIG.internalHosts,
189
+ imageOptions: { ...CONTENTFUL_FALLBACK_CONFIG.imageOptions, ...withoutUndefined(config.imageOptions) },
193
190
  };
194
191
  };
195
192
  /**
@@ -199,29 +196,8 @@ const createContentfulConfig = (options) => {
199
196
  const injectContentfulConfig = () => inject(CONTENTFUL_CONFIG, { optional: true }) ?? CONTENTFUL_FALLBACK_CONFIG;
200
197
 
201
198
  const generateDefaultContentfulImageSource = (data) => {
202
- if (isContentfulGqlAsset(data)) {
203
- if (!data.contentType || !data.url) {
204
- return {
205
- type: '',
206
- srcset: '',
207
- };
208
- }
209
- return {
210
- type: data.contentType,
211
- srcset: data.url,
212
- };
213
- }
214
- const file = data.fields.file;
215
- if (!file?.contentType || !file.url) {
216
- return {
217
- type: '',
218
- srcset: '',
219
- };
220
- }
221
- return {
222
- type: file.contentType,
223
- srcset: file.url,
224
- };
199
+ const { url, contentType } = isContentfulGqlAsset(data) ? data : (data.fields.file ?? {});
200
+ return url ? { type: contentType ?? '', srcset: url } : { type: '', srcset: '' };
225
201
  };
226
202
  /**
227
203
  * Parses source set sizes into an object containing with and height. Eg.
@@ -267,7 +243,7 @@ const generateContentfulImageSources = (data, options = {}) => {
267
243
  const queryParams = [];
268
244
  queryParams.push(`fm=${type.split('/')[1]}`);
269
245
  if (backgroundColor) {
270
- queryParams.push(`bg=rgb:${backgroundColor}`);
246
+ queryParams.push(`bg=rgb:${backgroundColor.replace(/^#/, '')}`);
271
247
  }
272
248
  if (quality !== null && Number.isFinite(quality)) {
273
249
  queryParams.push(`q=${quality}`);
@@ -292,6 +268,11 @@ const generateContentfulImageSources = (data, options = {}) => {
292
268
  const derivedWidth = Math.round((height * imageDimensions.width) / imageDimensions.height);
293
269
  sourceSets.push(`${urlWithParams}&w=${derivedWidth}&h=${height} ${derivedWidth}w`);
294
270
  }
271
+ else if (ngDevMode && type === SOURCE_TYPES[0]) {
272
+ console.warn(height
273
+ ? `Contentful image: srcsetSizes entry "${size}" is dropped. A height-only size needs the asset's width and height to derive a width descriptor.`
274
+ : `Contentful image: srcsetSizes entry "${size}" is not a valid size and is dropped. Use "400", "400w", "400h" or "400x300"; viewport sizes like "50vw" belong in sizes.`);
275
+ }
295
276
  }
296
277
  }
297
278
  sources.push({
@@ -377,7 +358,7 @@ class ContentfulImageComponent {
377
358
  [sizes]="sizes()"
378
359
  [sources]="sourcesValue()"
379
360
  />
380
- `, isInline: true, dependencies: [{ kind: "component", type: PictureComponent, selector: "et-picture", inputs: ["sources", "defaultSrc", "alt", "figcaption", "priority", "width", "height", "aspectRatio", "sizes", "fit"], outputs: ["imgLoad", "imgError"] }], encapsulation: i0.ViewEncapsulation.None }); }
361
+ `, isInline: true, dependencies: [{ kind: "component", type: PictureComponent, selector: "et-picture", inputs: ["sources", "defaultSrc", "alt", "figcaption", "priority", "width", "height", "aspectRatio", "sizes", "fit"], outputs: ["imageLoad", "imageError"] }], encapsulation: i0.ViewEncapsulation.None }); }
381
362
  }
382
363
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulImageComponent, decorators: [{
383
364
  type: Component,
@@ -433,10 +414,18 @@ const isExternalWebHref = (href, context) => {
433
414
  const url = parseWebUrl(href);
434
415
  return url !== null && !isInternalWebUrl(url, context);
435
416
  };
417
+ const hasUrlScheme = (href) => /^[a-z][a-z\d+.-]*:/i.test(href);
418
+ const isRouteRelativeHref = (href) => !hasUrlScheme(href) && !href.startsWith('/');
419
+ const resolveHrefAgainstRoute = (href, routerUrl) => {
420
+ const resolved = new URL(href, 'https://contentful.invalid' + routerUrl);
421
+ return resolved.pathname + resolved.search + resolved.hash;
422
+ };
436
423
 
424
+ const RICH_TEXT_ANCHOR_CLASSES = ['et-contentful-rich-text-default-element', 'et-contentful-rich-text-default-a'];
437
425
  class ContentfulLinkComponent {
438
426
  constructor() {
439
427
  this.document = inject(DOCUMENT);
428
+ this.location = inject(Location);
440
429
  this.router = inject(Router);
441
430
  this.config = injectContentfulConfig();
442
431
  this.url = injectUrl();
@@ -444,10 +433,10 @@ class ContentfulLinkComponent {
444
433
  ...(ngDevMode ? [{ debugName: "href" }] : /* istanbul ignore next */ []));
445
434
  this.text = input.required(/* @ts-ignore */
446
435
  ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
447
- this.textClass = input('', /* @ts-ignore */
448
- ...(ngDevMode ? [{ debugName: "textClass" }] : /* istanbul ignore next */ []));
449
- this.anchorClass = input('', /* @ts-ignore */
450
- ...(ngDevMode ? [{ debugName: "anchorClass" }] : /* istanbul ignore next */ []));
436
+ this.marks = input([], /* @ts-ignore */
437
+ ...(ngDevMode ? [{ debugName: "marks" }] : /* istanbul ignore next */ []));
438
+ this.richText = input(false, /* @ts-ignore */
439
+ ...(ngDevMode ? [{ debugName: "richText" }] : /* istanbul ignore next */ []));
451
440
  this.usesRouterLink = computed(() => {
452
441
  const href = this.href();
453
442
  const absoluteUrl = parseWebUrl(href);
@@ -457,9 +446,14 @@ class ContentfulLinkComponent {
457
446
  internalHosts: this.config.internalHosts,
458
447
  });
459
448
  }
460
- return !href.startsWith('#') && !/^[a-z][a-z\d+.-]*:/i.test(href);
449
+ return !href.startsWith('#') && !hasUrlScheme(href);
461
450
  }, /* @ts-ignore */
462
451
  ...(ngDevMode ? [{ debugName: "usesRouterLink" }] : /* istanbul ignore next */ []));
452
+ this.anchorHref = computed(() => {
453
+ const href = this.href();
454
+ return href.startsWith('#') ? this.location.prepareExternalUrl(resolveHrefAgainstRoute(href, this.url())) : href;
455
+ }, /* @ts-ignore */
456
+ ...(ngDevMode ? [{ debugName: "anchorHref" }] : /* istanbul ignore next */ []));
463
457
  this.openInNewTab = computed(() => Boolean(parseWebUrl(this.href())) && !this.usesRouterLink(), /* @ts-ignore */
464
458
  ...(ngDevMode ? [{ debugName: "openInNewTab" }] : /* istanbul ignore next */ []));
465
459
  this.internalPath = computed(() => {
@@ -471,25 +465,28 @@ class ContentfulLinkComponent {
471
465
  if (href.startsWith('/')) {
472
466
  return href;
473
467
  }
474
- const resolved = new URL(href, 'https://contentful.invalid' + this.url());
475
- return resolved.pathname + resolved.search + resolved.hash;
468
+ return resolveHrefAgainstRoute(href, this.url());
476
469
  }, /* @ts-ignore */
477
470
  ...(ngDevMode ? [{ debugName: "internalPath" }] : /* istanbul ignore next */ []));
478
471
  this.internalUrlTree = computed(() => this.router.parseUrl(this.internalPath()), /* @ts-ignore */
479
472
  ...(ngDevMode ? [{ debugName: "internalUrlTree" }] : /* istanbul ignore next */ []));
480
- this.linkClass = computed(() => [this.anchorClass(), this.textClass()].filter(Boolean).join(' ') || null, /* @ts-ignore */
481
- ...(ngDevMode ? [{ debugName: "linkClass" }] : /* istanbul ignore next */ []));
473
+ this.modifierClasses = computed(() => [
474
+ ...(this.richText() ? RICH_TEXT_ANCHOR_CLASSES : []),
475
+ ...this.marks().map((mark) => `et-contentful-rich-text-mark-${mark}`),
476
+ ], /* @ts-ignore */
477
+ ...(ngDevMode ? [{ debugName: "modifierClasses" }] : /* istanbul ignore next */ []));
482
478
  }
483
479
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulLinkComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
484
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ContentfulLinkComponent, isStandalone: true, selector: "et-contentful-link", inputs: { href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: true, transformFunction: null }, text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: true, transformFunction: null }, textClass: { classPropertyName: "textClass", publicName: "textClass", isSignal: true, isRequired: false, transformFunction: null }, anchorClass: { classPropertyName: "anchorClass", publicName: "anchorClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "et-contentful-link" }, ngImport: i0, template: `
480
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ContentfulLinkComponent, isStandalone: true, selector: "et-contentful-link", inputs: { href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: true, transformFunction: null }, text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: true, transformFunction: null }, marks: { classPropertyName: "marks", publicName: "marks", isSignal: true, isRequired: false, transformFunction: null }, richText: { classPropertyName: "richText", publicName: "richText", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "et-contentful-link" }, ngImport: i0, template: `
485
481
  @if (usesRouterLink()) {
486
- <a [class]="linkClass()" [routerLink]="internalUrlTree()">{{ text() }}</a>
482
+ <a [class]="modifierClasses()" [routerLink]="internalUrlTree()" class="et-contentful-link-anchor">{{ text() }}</a>
487
483
  } @else {
488
484
  <a
489
- [class]="linkClass()"
490
- [href]="href()"
491
- [target]="openInNewTab() ? '_blank' : null"
492
- [rel]="openInNewTab() ? 'noopener noreferrer' : null"
485
+ [class]="modifierClasses()"
486
+ [href]="anchorHref()"
487
+ [attr.target]="openInNewTab() ? '_blank' : null"
488
+ [attr.rel]="openInNewTab() ? 'noopener noreferrer' : null"
489
+ class="et-contentful-link-anchor"
493
490
  >{{ text() }}</a
494
491
  >
495
492
  }
@@ -499,20 +496,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
499
496
  type: Component,
500
497
  args: [{ selector: 'et-contentful-link', template: `
501
498
  @if (usesRouterLink()) {
502
- <a [class]="linkClass()" [routerLink]="internalUrlTree()">{{ text() }}</a>
499
+ <a [class]="modifierClasses()" [routerLink]="internalUrlTree()" class="et-contentful-link-anchor">{{ text() }}</a>
503
500
  } @else {
504
501
  <a
505
- [class]="linkClass()"
506
- [href]="href()"
507
- [target]="openInNewTab() ? '_blank' : null"
508
- [rel]="openInNewTab() ? 'noopener noreferrer' : null"
502
+ [class]="modifierClasses()"
503
+ [href]="anchorHref()"
504
+ [attr.target]="openInNewTab() ? '_blank' : null"
505
+ [attr.rel]="openInNewTab() ? 'noopener noreferrer' : null"
506
+ class="et-contentful-link-anchor"
509
507
  >{{ text() }}</a
510
508
  >
511
509
  }
512
510
  `, encapsulation: ViewEncapsulation.None, imports: [RouterLink], host: {
513
511
  class: 'et-contentful-link',
514
512
  }, styles: ["@layer components{.et-contentful-link{display:contents}}\n"] }]
515
- }], propDecorators: { href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: true }] }], text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: true }] }], textClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "textClass", required: false }] }], anchorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchorClass", required: false }] }] } });
513
+ }], propDecorators: { href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: true }] }], text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: true }] }], marks: [{ type: i0.Input, args: [{ isSignal: true, alias: "marks", required: false }] }], richText: [{ type: i0.Input, args: [{ isSignal: true, alias: "richText", required: false }] }] } });
516
514
 
517
515
  /**
518
516
  * The `BLOCKS` node type strings of `@contentful/rich-text-types`, as a plain literal map.
@@ -558,8 +556,9 @@ const CF_INLINES = {
558
556
  };
559
557
 
560
558
  const RICH_TEXT_RENDERER_ERRORS = {
561
- rich_text_undefined: 'The property value given at richTextPath is undefined. Use the richTextPath input to specify the path to the rich text object.',
559
+ rich_text_not_object: (data) => `The value at richTextPath is a ${data.type}, not a rich-text document object. Point richTextPath at the rich-text field itself (e.g. items[0].fields.body).`,
562
560
  rich_text_wrong_type: 'The rich text object does not satisfy the RichTextResponse interface. It should contain a property named "nodeType" with the value "document".',
561
+ rich_text_conflicting_inputs: 'gqlRichText cannot be combined with content or richTextPath. Pass either a GraphQL rich-text field through gqlRichText, or a REST response through content and richTextPath.',
563
562
  asset_id_not_found: 'The asset ID was not found. This node is not supported.',
564
563
  entry_id_not_found: 'The entry ID was not found. This node is not supported.',
565
564
  text_parent_not_found: 'The parent node is not found. This structure is not supported.',
@@ -567,8 +566,9 @@ const RICH_TEXT_RENDERER_ERRORS = {
567
566
  cached_command_not_component: 'The rendered node for a component command is not a component. The renderer state is out of sync.',
568
567
  };
569
568
  const RICH_TEXT_RENDERER_ERROR_CODES = {
570
- rich_text_undefined: 0,
569
+ rich_text_not_object: 0,
571
570
  rich_text_wrong_type: 1,
571
+ rich_text_conflicting_inputs: 11,
572
572
  asset_id_not_found: 2,
573
573
  entry_id_not_found: 3,
574
574
  text_parent_not_found: 7,
@@ -576,10 +576,75 @@ const RICH_TEXT_RENDERER_ERROR_CODES = {
576
576
  cached_command_not_component: 10,
577
577
  };
578
578
  const richTextRendererError = (code, data) => {
579
- const message = `<et-contentful-rich-text-renderer>: ${RICH_TEXT_RENDERER_ERRORS[code]}`;
579
+ const entry = RICH_TEXT_RENDERER_ERRORS[code];
580
+ const message = `<et-contentful-rich-text-renderer>: ${typeof entry === 'function' ? entry(data) : entry}`;
580
581
  throw new RuntimeError(RICH_TEXT_RENDERER_ERROR_CODES[code], message, data);
581
582
  };
582
583
 
584
+ const lowerCaseFirst = (value) => value.charAt(0).toLowerCase() + value.slice(1);
585
+ const toRestAsset = (asset) => ({
586
+ sys: { type: 'Asset', id: asset.sys.id, createdAt: '', updatedAt: '', locale: '' },
587
+ fields: {
588
+ title: asset.title ?? '',
589
+ description: asset.description ?? '',
590
+ file: {
591
+ url: asset.url,
592
+ details: {
593
+ size: asset.size,
594
+ ...(asset.width !== null && asset.height !== null
595
+ ? { image: { width: asset.width, height: asset.height } }
596
+ : {}),
597
+ },
598
+ fileName: asset.fileName ?? null,
599
+ contentType: asset.contentType,
600
+ },
601
+ },
602
+ metadata: { tags: [] },
603
+ });
604
+ const toRestEntry = (entry) => {
605
+ const { __typename, sys, ...fields } = entry;
606
+ if (!__typename) {
607
+ return null;
608
+ }
609
+ return {
610
+ sys: {
611
+ type: 'Entry',
612
+ id: sys.id,
613
+ createdAt: '',
614
+ updatedAt: '',
615
+ locale: '',
616
+ contentType: { sys: { type: 'Link', linkType: 'ContentType', id: lowerCaseFirst(__typename) } },
617
+ },
618
+ fields,
619
+ metadata: { tags: [] },
620
+ };
621
+ };
622
+ const present = (items) => (items ?? []).filter((item) => item !== null && item !== undefined);
623
+ /**
624
+ * Maps the `links` of a GraphQL rich-text field to the entries and assets the renderer resolves
625
+ * embeds against. An entry's content type id is its `__typename` with the first letter lower-cased,
626
+ * which is how Contentful derives a GraphQL type name from a content type id. Entries without a
627
+ * `__typename` are dropped.
628
+ */
629
+ const mapContentfulGqlLinks = (links) => {
630
+ const assets = new Map();
631
+ const entries = new Map();
632
+ for (const asset of [...present(links?.assets?.block), ...present(links?.assets?.hyperlink)]) {
633
+ assets.set(asset.sys.id, toRestAsset(asset));
634
+ }
635
+ for (const gqlEntry of [
636
+ ...present(links?.entries?.block),
637
+ ...present(links?.entries?.inline),
638
+ ...present(links?.entries?.hyperlink),
639
+ ]) {
640
+ const entry = toRestEntry(gqlEntry);
641
+ if (entry) {
642
+ entries.set(entry.sys.id, entry);
643
+ }
644
+ }
645
+ return { assets: [...assets.values()], entries: [...entries.values()] };
646
+ };
647
+
583
648
  const isRichTextRootNode = (node) => {
584
649
  return isObject(node) && 'nodeType' in node && node['nodeType'] === 'document';
585
650
  };
@@ -681,7 +746,7 @@ const createContentfulIncludeMap = (config) => {
681
746
  const entry = entryMap.get(id);
682
747
  if (!entry) {
683
748
  if (isDevMode()) {
684
- console.warn('Entry not found! Will return null. Is the include query param too low?', { id, entryMap });
749
+ console.warn('Entry not found! Will return null. Is it unpublished or deleted, or is the include query param too low?', { id });
685
750
  }
686
751
  return null;
687
752
  }
@@ -720,6 +785,23 @@ const createContentfulIncludeMap = (config) => {
720
785
  getAssets,
721
786
  };
722
787
  };
788
+ const warnedRichTextPaths = /* @__PURE__ */ new Set();
789
+ const warnOnUnresolvedParentPath = (content, richTextPath) => {
790
+ const segments = richTextPath.split('.');
791
+ const root = content;
792
+ if (segments.length < 2 || isObject(getObjectProperty(root, segments.slice(0, -1).join('.'))))
793
+ return;
794
+ if (warnedRichTextPaths.has(richTextPath))
795
+ return;
796
+ warnedRichTextPaths.add(richTextPath);
797
+ let resolvedSegments = 0;
798
+ while (resolvedSegments < segments.length - 1 &&
799
+ isObject(getObjectProperty(root, segments.slice(0, resolvedSegments + 1).join('.')))) {
800
+ resolvedSegments++;
801
+ }
802
+ const resolvedPath = segments.slice(0, resolvedSegments).join('.') || '(the content itself)';
803
+ console.warn(`<et-contentful-rich-text-renderer>: richTextPath "${richTextPath}" does not resolve, so nothing is rendered. The deepest part that resolves is ${resolvedPath}; check the next segment for a typo or a missing item.`, { content, richTextPath });
804
+ };
723
805
  class ContentfulRichTextRendererComponent {
724
806
  constructor() {
725
807
  this.viewContainerRef = inject(ViewContainerRef);
@@ -727,38 +809,72 @@ class ContentfulRichTextRendererComponent {
727
809
  this.elementRef = inject(ElementRef);
728
810
  this.config = injectContentfulConfig();
729
811
  this.document = inject(DOCUMENT);
812
+ this.location = inject(Location);
813
+ this.url = injectUrl();
730
814
  /**
731
815
  * The contentful response gotten via their REST api.
732
816
  * @see https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/entries/entries-collection
733
817
  */
734
- this.content = input.required(/* @ts-ignore */
735
- ...(ngDevMode ? [{ debugName: "content" }] : /* istanbul ignore next */ []));
818
+ this.content = input(/* @ts-ignore */
819
+ ...(ngDevMode ? [undefined, { debugName: "content" }] : /* istanbul ignore next */ []));
736
820
  /**
737
821
  * The path to where the rich text field is inside the contentful response. Dot and array notation is allowed.
738
822
  * @example "items[0].fields.html"
739
823
  */
740
- this.richTextPath = input.required(/* @ts-ignore */
741
- ...(ngDevMode ? [{ debugName: "richTextPath" }] : /* istanbul ignore next */ []));
824
+ this.richTextPath = input(/* @ts-ignore */
825
+ ...(ngDevMode ? [undefined, { debugName: "richTextPath" }] : /* istanbul ignore next */ []));
826
+ /**
827
+ * A rich-text field from the Contentful GraphQL API, passed as-is. An alternative to `content` and
828
+ * `richTextPath`; setting both is an error in dev mode.
829
+ * @example { json, links: { assets: { block, hyperlink }, entries: { block, inline, hyperlink } } }
830
+ */
831
+ this.gqlRichText = input(/* @ts-ignore */
832
+ ...(ngDevMode ? [undefined, { debugName: "gqlRichText" }] : /* istanbul ignore next */ []));
742
833
  this.executedCommandsCache = new Map();
743
- this.contentIncludesMap = computed(() => {
744
- const content = this.content();
745
- const assets = content?.includes?.Asset;
746
- const entries = content?.includes?.Entry;
747
- return createContentfulIncludeMap({ assets: assets ?? [], entries: entries ?? [] });
834
+ this.includes = computed(() => {
835
+ const gqlRichText = this.gqlRichText();
836
+ if (gqlRichText) {
837
+ return mapContentfulGqlLinks(gqlRichText.links);
838
+ }
839
+ const includes = this.content()?.includes;
840
+ return { assets: includes?.Asset ?? [], entries: includes?.Entry ?? [] };
748
841
  }, /* @ts-ignore */
842
+ ...(ngDevMode ? [{ debugName: "includes" }] : /* istanbul ignore next */ []));
843
+ this.contentIncludesMap = computed(() => createContentfulIncludeMap(this.includes()), /* @ts-ignore */
749
844
  ...(ngDevMode ? [{ debugName: "contentIncludesMap" }] : /* istanbul ignore next */ []));
845
+ this.includedEntries = computed(() => new Map(this.includes().entries.map((entry) => [entry.sys.id, entry])), /* @ts-ignore */
846
+ ...(ngDevMode ? [{ debugName: "includedEntries" }] : /* istanbul ignore next */ []));
750
847
  this.richTextData = computed(() => {
848
+ const gqlRichText = this.gqlRichText();
751
849
  const content = this.content();
752
850
  const richTextPath = this.richTextPath();
851
+ if (ngDevMode && gqlRichText && (content || richTextPath)) {
852
+ throw richTextRendererError('rich_text_conflicting_inputs', { gqlRichText, content, richTextPath });
853
+ }
854
+ if (gqlRichText) {
855
+ if (!isRichTextRootNode(gqlRichText.json)) {
856
+ throw richTextRendererError('rich_text_wrong_type', { gqlRichText });
857
+ }
858
+ return gqlRichText.json;
859
+ }
753
860
  if (!content) {
754
861
  return null;
755
862
  }
863
+ if (!richTextPath) {
864
+ if (ngDevMode) {
865
+ console.warn('<et-contentful-rich-text-renderer>: content is set without richTextPath, so nothing is rendered. Set richTextPath, or pass a GraphQL field through gqlRichText.');
866
+ }
867
+ return null;
868
+ }
756
869
  const richText = getObjectProperty(content, richTextPath);
757
870
  if (richText === null || richText === undefined) {
871
+ if (ngDevMode && richText === undefined) {
872
+ warnOnUnresolvedParentPath(content, richTextPath);
873
+ }
758
874
  return null;
759
875
  }
760
876
  if (!isObject(richText)) {
761
- throw richTextRendererError('rich_text_undefined', { content, richTextPath });
877
+ throw richTextRendererError('rich_text_not_object', { content, richTextPath, type: typeof richText });
762
878
  }
763
879
  if (!isRichTextRootNode(richText)) {
764
880
  throw richTextRendererError('rich_text_wrong_type', { content, richTextPath });
@@ -892,8 +1008,7 @@ class ContentfulRichTextRendererComponent {
892
1008
  }
893
1009
  const contentType = asset.fields.file?.contentType;
894
1010
  const assetComponents = this.config.components;
895
- const isMissing = !contentType && !asset.fields.file?.url;
896
- if (isMissing) {
1011
+ if (!asset.fields.file?.url) {
897
1012
  if (isDevMode()) {
898
1013
  console.warn('Asset is missing file data! Asset will be skipped. Did you forget to upload a file for the current translation in Contentful?', { asset });
899
1014
  }
@@ -945,10 +1060,16 @@ class ContentfulRichTextRendererComponent {
945
1060
  href = url ? normalizeHref(url) : null;
946
1061
  }
947
1062
  const linkTexts = node.content.filter((child) => child.nodeType === 'text' && Boolean(child.value));
1063
+ if (!linkTexts.length) {
1064
+ break;
1065
+ }
948
1066
  const linkText = linkTexts.map((child) => child.value).join('');
949
1067
  const linkComponent = href ? this.config.components.link : null;
950
1068
  if (href && !linkComponent) {
951
- const attributes = { class: DEFAULT_ANCHOR_CLASS, href };
1069
+ const anchorHref = isRouteRelativeHref(href)
1070
+ ? this.location.prepareExternalUrl(resolveHrefAgainstRoute(href, this.url()))
1071
+ : href;
1072
+ const attributes = { class: DEFAULT_ANCHOR_CLASS, href: anchorHref };
952
1073
  if (isExternalWebHref(href, { location: this.document.location, internalHosts: this.config.internalHosts })) {
953
1074
  attributes['target'] = '_blank';
954
1075
  attributes['rel'] = 'noopener noreferrer';
@@ -993,7 +1114,6 @@ class ContentfulRichTextRendererComponent {
993
1114
  break;
994
1115
  }
995
1116
  const sharedMarks = linkTexts[0]?.marks.filter((mark) => linkTexts.every((child) => child.marks.some((other) => other.type === mark.type))) ?? [];
996
- const textClass = marksToClass(sharedMarks);
997
1117
  let linkComponentId = componentIdMap.get(LINK_COMPONENT_TYPE) ?? -1;
998
1118
  const linkId = LINK_COMPONENT_TYPE + ++linkComponentId;
999
1119
  componentIdMap.set(LINK_COMPONENT_TYPE, linkComponentId);
@@ -1003,7 +1123,7 @@ class ContentfulRichTextRendererComponent {
1003
1123
  domPosition,
1004
1124
  parentId: openElementIds.at(-1) ?? null,
1005
1125
  component: linkComponent,
1006
- inputs: { href, text: linkText, textClass, anchorClass: DEFAULT_ANCHOR_CLASS },
1126
+ inputs: { href, text: linkText, marks: sharedMarks.map((mark) => mark.type), richText: true },
1007
1127
  id: linkId,
1008
1128
  });
1009
1129
  domPosition++;
@@ -1015,10 +1135,10 @@ class ContentfulRichTextRendererComponent {
1015
1135
  if (!entryId) {
1016
1136
  throw richTextRendererError('entry_id_not_found', { node });
1017
1137
  }
1018
- const entry = this.contentIncludesMap().getEntry(entryId, ET_CONTENTFUL_ANY_ENTRY_CONTENT_TYPE_SYS_ID);
1138
+ const entry = this.includedEntries().get(entryId);
1019
1139
  if (!entry) {
1020
1140
  if (isDevMode()) {
1021
- console.warn('Embedded entry is missing from the includes! The entry will be skipped. Is it unpublished or deleted?', { entryId, node });
1141
+ console.warn('Embedded entry is missing from the includes! The entry will be skipped. Is it unpublished or deleted, or is the include query param too low?', { entryId, node });
1022
1142
  }
1023
1143
  break;
1024
1144
  }
@@ -1263,7 +1383,7 @@ class ContentfulRichTextRendererComponent {
1263
1383
  this.renderer.insertBefore(parentElement, node, previousSibling ? previousSibling.nextSibling : parentElement.firstChild);
1264
1384
  }
1265
1385
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulRichTextRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1266
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.6", type: ContentfulRichTextRendererComponent, isStandalone: true, selector: "et-contentful-rich-text-renderer", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: true, transformFunction: null }, richTextPath: { classPropertyName: "richTextPath", publicName: "richTextPath", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "et-contentful-rich-text-renderer" }, ngImport: i0, template: ``, isInline: true, encapsulation: i0.ViewEncapsulation.None }); }
1386
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.6", type: ContentfulRichTextRendererComponent, isStandalone: true, selector: "et-contentful-rich-text-renderer", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, richTextPath: { classPropertyName: "richTextPath", publicName: "richTextPath", isSignal: true, isRequired: false, transformFunction: null }, gqlRichText: { classPropertyName: "gqlRichText", publicName: "gqlRichText", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "et-contentful-rich-text-renderer" }, ngImport: i0, template: ``, isInline: true, encapsulation: i0.ViewEncapsulation.None }); }
1267
1387
  }
1268
1388
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulRichTextRendererComponent, decorators: [{
1269
1389
  type: Component,
@@ -1275,14 +1395,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
1275
1395
  class: 'et-contentful-rich-text-renderer',
1276
1396
  },
1277
1397
  }]
1278
- }], ctorParameters: () => [], propDecorators: { content: [{ type: i0.Input, args: [{ isSignal: true, alias: "content", required: true }] }], richTextPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "richTextPath", required: true }] }] } });
1398
+ }], ctorParameters: () => [], propDecorators: { content: [{ type: i0.Input, args: [{ isSignal: true, alias: "content", required: false }] }], richTextPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "richTextPath", required: false }] }], gqlRichText: [{ type: i0.Input, args: [{ isSignal: true, alias: "gqlRichText", required: false }] }] } });
1279
1399
 
1280
1400
  class ContentfulVideoComponent {
1281
1401
  constructor() {
1282
1402
  this.asset = input.required(/* @ts-ignore */
1283
1403
  ...(ngDevMode ? [{ debugName: "asset" }] : /* istanbul ignore next */ []));
1284
- this.videoClass = input(null, /* @ts-ignore */
1285
- ...(ngDevMode ? [{ debugName: "videoClass" }] : /* istanbul ignore next */ []));
1286
1404
  this.data = computed(() => {
1287
1405
  const asset = this.asset();
1288
1406
  if (!asset) {
@@ -1305,13 +1423,13 @@ class ContentfulVideoComponent {
1305
1423
  ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
1306
1424
  }
1307
1425
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulVideoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1308
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ContentfulVideoComponent, isStandalone: true, selector: "et-contentful-video", inputs: { asset: { classPropertyName: "asset", publicName: "asset", isSignal: true, isRequired: true, transformFunction: null }, videoClass: { classPropertyName: "videoClass", publicName: "videoClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "et-contentful-video" }, ngImport: i0, template: `
1426
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ContentfulVideoComponent, isStandalone: true, selector: "et-contentful-video", inputs: { asset: { classPropertyName: "asset", publicName: "asset", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "et-contentful-video" }, ngImport: i0, template: `
1309
1427
  @if (data(); as data) {
1310
- <video [ngClass]="videoClass()" controls>
1311
- <source [src]="data.url" [type]="data.contentType" />
1428
+ <video class="et-contentful-video-video" controls>
1429
+ <source [src]="data.url" [attr.type]="data.contentType || null" class="et-contentful-video-source" />
1312
1430
  </video>
1313
1431
  }
1314
- `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], encapsulation: i0.ViewEncapsulation.None }); }
1432
+ `, isInline: true, encapsulation: i0.ViewEncapsulation.None }); }
1315
1433
  }
1316
1434
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulVideoComponent, decorators: [{
1317
1435
  type: Component,
@@ -1319,18 +1437,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
1319
1437
  selector: 'et-contentful-video',
1320
1438
  template: `
1321
1439
  @if (data(); as data) {
1322
- <video [ngClass]="videoClass()" controls>
1323
- <source [src]="data.url" [type]="data.contentType" />
1440
+ <video class="et-contentful-video-video" controls>
1441
+ <source [src]="data.url" [attr.type]="data.contentType || null" class="et-contentful-video-source" />
1324
1442
  </video>
1325
1443
  }
1326
1444
  `,
1327
1445
  encapsulation: ViewEncapsulation.None,
1328
- imports: [NgClass],
1329
1446
  host: {
1330
1447
  class: 'et-contentful-video',
1331
1448
  },
1332
1449
  }]
1333
- }], propDecorators: { asset: [{ type: i0.Input, args: [{ isSignal: true, alias: "asset", required: true }] }], videoClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "videoClass", required: false }] }] } });
1450
+ }], propDecorators: { asset: [{ type: i0.Input, args: [{ isSignal: true, alias: "asset", required: true }] }] } });
1334
1451
 
1335
1452
  const ContentfulImports = [
1336
1453
  ContentfulAudioComponent,
@@ -1367,5 +1484,5 @@ const isContentfulEntryType = (entry, type) => entry.sys.contentType.sys.id ===
1367
1484
  * Generated bundle index. Do not edit.
1368
1485
  */
1369
1486
 
1370
- export { CONTENTFUL_CONFIG, CONTENTFUL_DEFAULT_COMPONENTS, CONTENTFUL_FILE_LABELS, ContentfulAudioComponent, ContentfulFileComponent, ContentfulImageComponent, ContentfulImports, ContentfulLinkComponent, ContentfulRichTextRendererComponent, ContentfulVideoComponent, DEFAULT_CONTENTFUL_FILE_LABELS, ET_CONTENTFUL_ANY_ENTRY_CONTENT_TYPE_SYS_ID, GQL_FRAGMENT_CONTENTFUL_ASSET, createContentfulConfig, createContentfulIncludeMap, generateContentfulImageSources, generateDefaultContentfulImageSource, injectContentfulConfig, injectContentfulFileLabels, isContentfulEntryType, isContentfulGqlAsset, isRichTextRootNode, marksToClass, marksToTags, parseContentfulImageSize, provideContentfulConfig, provideContentfulFileLabels, translateContentfulNodeTypeToHtmlTag };
1487
+ export { CONTENTFUL_CONFIG, CONTENTFUL_DEFAULT_COMPONENTS, CONTENTFUL_FILE_LABELS, ContentfulAudioComponent, ContentfulFileComponent, ContentfulImageComponent, ContentfulImports, ContentfulLinkComponent, ContentfulRichTextRendererComponent, ContentfulVideoComponent, DEFAULT_CONTENTFUL_FILE_LABELS, ET_CONTENTFUL_ANY_ENTRY_CONTENT_TYPE_SYS_ID, GQL_FRAGMENT_CONTENTFUL_ASSET, createContentfulConfig, createContentfulIncludeMap, generateContentfulImageSources, generateDefaultContentfulImageSource, injectContentfulConfig, injectContentfulFileLabels, isContentfulEntryType, isContentfulGqlAsset, isRichTextRootNode, mapContentfulGqlLinks, marksToClass, marksToTags, parseContentfulImageSize, provideContentfulConfig, provideContentfulFileLabels, translateContentfulNodeTypeToHtmlTag };
1371
1488
  //# sourceMappingURL=ethlete-contentful.mjs.map