@ethlete/contentful 4.0.0-next.5 → 4.0.0-next.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +23 -4
- package/fesm2022/ethlete-contentful.mjs +481 -491
- package/fesm2022/ethlete-contentful.mjs.map +1 -1
- package/generators/generators.json +9 -0
- package/generators/migrate-to-contentful-v5/migration-scope.js +35 -0
- package/generators/migrate-to-contentful-v5/migration.js +192 -0
- package/generators/migrate-to-contentful-v5/schema.json +26 -0
- package/package.json +20 -10
- package/types/ethlete-contentful.d.ts +93 -194
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { NgClass, DOCUMENT } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { input, computed, ViewEncapsulation,
|
|
4
|
-
import {
|
|
5
|
-
import { normalizePictureSizes, PictureComponent } from '@ethlete/cdk';
|
|
3
|
+
import { input, computed, ViewEncapsulation, Component, InjectionToken, inject, numberAttribute, booleanAttribute, isDevMode, ViewContainerRef, ElementRef, linkedSignal, effect, untracked, signal, reflectComponentType, inputBinding } from '@angular/core';
|
|
4
|
+
import { normalizePictureSizes, PictureComponent } from '@ethlete/components';
|
|
6
5
|
import { RouterLink } from '@angular/router';
|
|
7
|
-
import { toSignal, toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
8
|
-
import { INLINES, BLOCKS } from '@contentful/rich-text-types';
|
|
9
6
|
import { RuntimeError, isObject, injectRenderer, getObjectProperty } from '@ethlete/core';
|
|
10
|
-
import { startWith, pairwise, tap } from 'rxjs';
|
|
11
7
|
|
|
12
|
-
const GQL_FRAGMENT_CONTENTFUL_ASSET =
|
|
8
|
+
const GQL_FRAGMENT_CONTENTFUL_ASSET = `
|
|
13
9
|
fragment AssetData on Asset {
|
|
14
10
|
sys {
|
|
15
11
|
id
|
|
@@ -56,17 +52,17 @@ class ContentfulAudioComponent {
|
|
|
56
52
|
}, /* @ts-ignore */
|
|
57
53
|
...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
58
54
|
}
|
|
59
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
60
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
55
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ContentfulAudioComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
56
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", 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 } }, ngImport: i0, template: `
|
|
61
57
|
@if (data(); as data) {
|
|
62
58
|
<figure [ngClass]="figureClass()">
|
|
63
59
|
<figcaption [ngClass]="figcaptionClass()">{{ data.title }}</figcaption>
|
|
64
60
|
<audio [ngClass]="audioClass()" controls src="{{ data.url }}"></audio>
|
|
65
61
|
</figure>
|
|
66
62
|
}
|
|
67
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }],
|
|
63
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
68
64
|
}
|
|
69
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
65
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ContentfulAudioComponent, decorators: [{
|
|
70
66
|
type: Component,
|
|
71
67
|
args: [{
|
|
72
68
|
selector: 'et-contentful-audio',
|
|
@@ -78,7 +74,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
78
74
|
</figure>
|
|
79
75
|
}
|
|
80
76
|
`,
|
|
81
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
82
77
|
encapsulation: ViewEncapsulation.None,
|
|
83
78
|
imports: [NgClass],
|
|
84
79
|
}]
|
|
@@ -112,16 +107,16 @@ class ContentfulFileComponent {
|
|
|
112
107
|
}, /* @ts-ignore */
|
|
113
108
|
...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
114
109
|
}
|
|
115
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
116
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
110
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ContentfulFileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
111
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", 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 } }, ngImport: i0, template: `
|
|
117
112
|
@if (data(); as data) {
|
|
118
113
|
<a [href]="data.url" [ngClass]="fileClass()" class="underline" target="_blank">
|
|
119
114
|
{{ data.title }} ({{ data.size }} Bytes)
|
|
120
115
|
</a>
|
|
121
116
|
}
|
|
122
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }],
|
|
117
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
123
118
|
}
|
|
124
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
119
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ContentfulFileComponent, decorators: [{
|
|
125
120
|
type: Component,
|
|
126
121
|
args: [{
|
|
127
122
|
selector: 'et-contentful-file',
|
|
@@ -132,7 +127,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
132
127
|
</a>
|
|
133
128
|
}
|
|
134
129
|
`,
|
|
135
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
136
130
|
encapsulation: ViewEncapsulation.None,
|
|
137
131
|
imports: [NgClass],
|
|
138
132
|
}]
|
|
@@ -191,13 +185,13 @@ const parseContentfulImageSize = (size) => {
|
|
|
191
185
|
height: height ? parseInt(height, 10) : null,
|
|
192
186
|
};
|
|
193
187
|
};
|
|
188
|
+
const SOURCE_TYPES = ['image/avif', 'image/webp', 'image/png', 'image/jpg'];
|
|
194
189
|
const generateContentfulImageSources = (data, srcsetSizes, backgroundColor, quality, focusArea, resizeBehavior) => {
|
|
195
190
|
const assetData = data;
|
|
196
191
|
if (!assetData) {
|
|
197
192
|
return [];
|
|
198
193
|
}
|
|
199
194
|
const sources = [];
|
|
200
|
-
const SOURCE_TYPES = ['image/avif', 'image/webp', 'image/png', 'image/jpg'];
|
|
201
195
|
for (const type of SOURCE_TYPES) {
|
|
202
196
|
const baseUrl = isContentfulGqlAsset(assetData) ? assetData.url : assetData.fields.file.url;
|
|
203
197
|
const sourceSets = [];
|
|
@@ -241,28 +235,20 @@ const generateContentfulImageSources = (data, srcsetSizes, backgroundColor, qual
|
|
|
241
235
|
|
|
242
236
|
class ContentfulImageComponent {
|
|
243
237
|
constructor() {
|
|
244
|
-
this.
|
|
238
|
+
this.contentfulConfig = inject(CONTENTFUL_CONFIG);
|
|
245
239
|
this.asset = input.required(/* @ts-ignore */
|
|
246
240
|
...(ngDevMode ? [{ debugName: "asset" }] : /* istanbul ignore next */ []));
|
|
247
241
|
this.backgroundColor = input(null, /* @ts-ignore */
|
|
248
242
|
...(ngDevMode ? [{ debugName: "backgroundColor" }] : /* istanbul ignore next */ []));
|
|
249
|
-
this.srcsetSizes = input(this.
|
|
243
|
+
this.srcsetSizes = input(this.contentfulConfig.imageOptions.srcsetSizes, /* @ts-ignore */
|
|
250
244
|
...(ngDevMode ? [{ debugName: "srcsetSizes" }] : /* istanbul ignore next */ []));
|
|
251
245
|
this.quality = input(null, { ...(ngDevMode ? { debugName: "quality" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
252
246
|
this.focusArea = input(null, /* @ts-ignore */
|
|
253
247
|
...(ngDevMode ? [{ debugName: "focusArea" }] : /* istanbul ignore next */ []));
|
|
254
248
|
this.resizeBehavior = input(null, /* @ts-ignore */
|
|
255
249
|
...(ngDevMode ? [{ debugName: "resizeBehavior" }] : /* istanbul ignore next */ []));
|
|
256
|
-
this.
|
|
257
|
-
this.
|
|
258
|
-
...(ngDevMode ? [{ debugName: "imgClass" }] : /* istanbul ignore next */ []));
|
|
259
|
-
this.figureClass = input(null, /* @ts-ignore */
|
|
260
|
-
...(ngDevMode ? [{ debugName: "figureClass" }] : /* istanbul ignore next */ []));
|
|
261
|
-
this.figcaptionClass = input(null, /* @ts-ignore */
|
|
262
|
-
...(ngDevMode ? [{ debugName: "figcaptionClass" }] : /* istanbul ignore next */ []));
|
|
263
|
-
this.pictureClass = input(null, /* @ts-ignore */
|
|
264
|
-
...(ngDevMode ? [{ debugName: "pictureClass" }] : /* istanbul ignore next */ []));
|
|
265
|
-
this.sizes = input(normalizePictureSizes(this._contentfulConfig.imageOptions.sizes), { ...(ngDevMode ? { debugName: "sizes" } : /* istanbul ignore next */ {}), transform: (v) => normalizePictureSizes(v) });
|
|
250
|
+
this.priority = input(false, { ...(ngDevMode ? { debugName: "priority" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
251
|
+
this.sizes = input(normalizePictureSizes(this.contentfulConfig.imageOptions.sizes), { ...(ngDevMode ? { debugName: "sizes" } : /* istanbul ignore next */ {}), transform: (v) => normalizePictureSizes(v) });
|
|
266
252
|
this.normalizedAsset = computed(() => {
|
|
267
253
|
const asset = this.asset();
|
|
268
254
|
if (isContentfulGqlAsset(asset)) {
|
|
@@ -283,7 +269,7 @@ class ContentfulImageComponent {
|
|
|
283
269
|
}
|
|
284
270
|
}, /* @ts-ignore */
|
|
285
271
|
...(ngDevMode ? [{ debugName: "normalizedAsset" }] : /* istanbul ignore next */ []));
|
|
286
|
-
this.
|
|
272
|
+
this.sourcesValue = computed(() => {
|
|
287
273
|
const asset = this.asset();
|
|
288
274
|
const backgroundColor = this.backgroundColor();
|
|
289
275
|
const srcsetSizes = this.srcsetSizes();
|
|
@@ -295,59 +281,50 @@ class ContentfulImageComponent {
|
|
|
295
281
|
}
|
|
296
282
|
return generateContentfulImageSources(asset, srcsetSizes, backgroundColor, quality, focusArea, resizeBehavior);
|
|
297
283
|
}, /* @ts-ignore */
|
|
298
|
-
...(ngDevMode ? [{ debugName: "
|
|
299
|
-
this.
|
|
284
|
+
...(ngDevMode ? [{ debugName: "sourcesValue" }] : /* istanbul ignore next */ []));
|
|
285
|
+
this.defaultSrcValue = computed(() => {
|
|
300
286
|
const asset = this.asset();
|
|
301
287
|
return asset ? generateDefaultContentfulImageSource(asset) : null;
|
|
302
288
|
}, /* @ts-ignore */
|
|
303
|
-
...(ngDevMode ? [{ debugName: "
|
|
289
|
+
...(ngDevMode ? [{ debugName: "defaultSrcValue" }] : /* istanbul ignore next */ []));
|
|
304
290
|
}
|
|
305
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
306
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.
|
|
291
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ContentfulImageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
292
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.7", type: ContentfulImageComponent, isStandalone: true, selector: "et-contentful-image", inputs: { asset: { classPropertyName: "asset", publicName: "asset", isSignal: true, isRequired: true, transformFunction: null }, backgroundColor: { classPropertyName: "backgroundColor", publicName: "backgroundColor", isSignal: true, isRequired: false, transformFunction: null }, srcsetSizes: { classPropertyName: "srcsetSizes", publicName: "srcsetSizes", isSignal: true, isRequired: false, transformFunction: null }, quality: { classPropertyName: "quality", publicName: "quality", isSignal: true, isRequired: false, transformFunction: null }, focusArea: { classPropertyName: "focusArea", publicName: "focusArea", isSignal: true, isRequired: false, transformFunction: null }, resizeBehavior: { classPropertyName: "resizeBehavior", publicName: "resizeBehavior", isSignal: true, isRequired: false, transformFunction: null }, priority: { classPropertyName: "priority", publicName: "priority", isSignal: true, isRequired: false, transformFunction: null }, sizes: { classPropertyName: "sizes", publicName: "sizes", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "et-contentful-image" }, ngImport: i0, template: `
|
|
307
293
|
<et-picture
|
|
308
|
-
[
|
|
309
|
-
[
|
|
310
|
-
[
|
|
311
|
-
[pictureClass]="pictureClass()"
|
|
312
|
-
[figcaptionClass]="figcaptionClass()"
|
|
313
|
-
[defaultSrc]="_defaultSrc()"
|
|
314
|
-
[alt]="normalizedAsset().alt ?? null"
|
|
294
|
+
[priority]="priority()"
|
|
295
|
+
[defaultSrc]="defaultSrcValue()"
|
|
296
|
+
[alt]="normalizedAsset().alt ?? ''"
|
|
315
297
|
[figcaption]="normalizedAsset().figcaption ?? null"
|
|
316
298
|
[width]="normalizedAsset().width ?? null"
|
|
317
299
|
[height]="normalizedAsset().height ?? null"
|
|
318
300
|
[sizes]="sizes()"
|
|
319
|
-
[sources]="
|
|
301
|
+
[sources]="sourcesValue()"
|
|
320
302
|
/>
|
|
321
|
-
`, isInline: true, dependencies: [{ kind: "component", type: PictureComponent, selector: "et-picture", inputs: ["sources", "
|
|
303
|
+
`, isInline: true, dependencies: [{ kind: "component", type: PictureComponent, selector: "et-picture", inputs: ["sources", "defaultSrc", "alt", "figcaption", "priority", "width", "height", "aspectRatio", "sizes"], outputs: ["imgLoad", "imgError"] }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
322
304
|
}
|
|
323
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
305
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ContentfulImageComponent, decorators: [{
|
|
324
306
|
type: Component,
|
|
325
307
|
args: [{
|
|
326
308
|
selector: 'et-contentful-image',
|
|
327
309
|
template: `
|
|
328
310
|
<et-picture
|
|
329
|
-
[
|
|
330
|
-
[
|
|
331
|
-
[
|
|
332
|
-
[pictureClass]="pictureClass()"
|
|
333
|
-
[figcaptionClass]="figcaptionClass()"
|
|
334
|
-
[defaultSrc]="_defaultSrc()"
|
|
335
|
-
[alt]="normalizedAsset().alt ?? null"
|
|
311
|
+
[priority]="priority()"
|
|
312
|
+
[defaultSrc]="defaultSrcValue()"
|
|
313
|
+
[alt]="normalizedAsset().alt ?? ''"
|
|
336
314
|
[figcaption]="normalizedAsset().figcaption ?? null"
|
|
337
315
|
[width]="normalizedAsset().width ?? null"
|
|
338
316
|
[height]="normalizedAsset().height ?? null"
|
|
339
317
|
[sizes]="sizes()"
|
|
340
|
-
[sources]="
|
|
318
|
+
[sources]="sourcesValue()"
|
|
341
319
|
/>
|
|
342
320
|
`,
|
|
343
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
344
321
|
encapsulation: ViewEncapsulation.None,
|
|
345
322
|
imports: [PictureComponent],
|
|
346
323
|
host: {
|
|
347
324
|
class: 'et-contentful-image',
|
|
348
325
|
},
|
|
349
326
|
}]
|
|
350
|
-
}], propDecorators: { asset: [{ type: i0.Input, args: [{ isSignal: true, alias: "asset", required: true }] }], backgroundColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "backgroundColor", required: false }] }], srcsetSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "srcsetSizes", required: false }] }], quality: [{ type: i0.Input, args: [{ isSignal: true, alias: "quality", required: false }] }], focusArea: [{ type: i0.Input, args: [{ isSignal: true, alias: "focusArea", required: false }] }], resizeBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizeBehavior", required: false }] }],
|
|
327
|
+
}], propDecorators: { asset: [{ type: i0.Input, args: [{ isSignal: true, alias: "asset", required: true }] }], backgroundColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "backgroundColor", required: false }] }], srcsetSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "srcsetSizes", required: false }] }], quality: [{ type: i0.Input, args: [{ isSignal: true, alias: "quality", required: false }] }], focusArea: [{ type: i0.Input, args: [{ isSignal: true, alias: "focusArea", required: false }] }], resizeBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizeBehavior", required: false }] }], priority: [{ type: i0.Input, args: [{ isSignal: true, alias: "priority", required: false }] }], sizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "sizes", required: false }] }] } });
|
|
351
328
|
|
|
352
329
|
class ContentfulVideoComponent {
|
|
353
330
|
constructor() {
|
|
@@ -373,16 +350,16 @@ class ContentfulVideoComponent {
|
|
|
373
350
|
}, /* @ts-ignore */
|
|
374
351
|
...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
375
352
|
}
|
|
376
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
377
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
353
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ContentfulVideoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
354
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", 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 } }, ngImport: i0, template: `
|
|
378
355
|
@if (data(); as data) {
|
|
379
356
|
<video [ngClass]="videoClass()" controls>
|
|
380
357
|
<source src="{{ data.url }}" type="{{ data.contentType }}" />
|
|
381
358
|
</video>
|
|
382
359
|
}
|
|
383
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }],
|
|
360
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
384
361
|
}
|
|
385
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
362
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ContentfulVideoComponent, decorators: [{
|
|
386
363
|
type: Component,
|
|
387
364
|
args: [{
|
|
388
365
|
selector: 'et-contentful-video',
|
|
@@ -393,16 +370,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
393
370
|
</video>
|
|
394
371
|
}
|
|
395
372
|
`,
|
|
396
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
397
373
|
encapsulation: ViewEncapsulation.None,
|
|
398
374
|
imports: [NgClass],
|
|
399
375
|
}]
|
|
400
376
|
}], propDecorators: { asset: [{ type: i0.Input, args: [{ isSignal: true, alias: "asset", required: true }] }], videoClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "videoClass", required: false }] }] } });
|
|
401
377
|
|
|
378
|
+
/**
|
|
379
|
+
* The config used when no `provideContentfulConfig()` is in scope. It declares no
|
|
380
|
+
* `components`, so embedded assets are skipped and hyperlinks render as plain anchors
|
|
381
|
+
* until a config is provided.
|
|
382
|
+
*/
|
|
383
|
+
const CONTENTFUL_FALLBACK_CONFIG = {
|
|
384
|
+
internalHosts: [],
|
|
385
|
+
components: {},
|
|
386
|
+
customComponents: {},
|
|
387
|
+
imageOptions: {
|
|
388
|
+
srcsetSizes: ['375w', '1280w', '1920w', '2560w'],
|
|
389
|
+
sizes: [],
|
|
390
|
+
backgroundColor: null,
|
|
391
|
+
},
|
|
392
|
+
};
|
|
402
393
|
const createContentfulConfig = (config = {}) => ({
|
|
403
394
|
// This default config must be inline and not saved inside a const for some weird webpack reason
|
|
404
395
|
...{
|
|
405
|
-
useTailwindClasses: false,
|
|
406
396
|
internalHosts: [],
|
|
407
397
|
components: {
|
|
408
398
|
audio: ContentfulAudioComponent,
|
|
@@ -420,6 +410,11 @@ const createContentfulConfig = (config = {}) => ({
|
|
|
420
410
|
},
|
|
421
411
|
...(config || {}),
|
|
422
412
|
});
|
|
413
|
+
/**
|
|
414
|
+
* Reads the contentful config from the current injector. Falls back to a config without
|
|
415
|
+
* any `components` when none was provided via `provideContentfulConfig()`.
|
|
416
|
+
*/
|
|
417
|
+
const injectContentfulConfig = () => inject(CONTENTFUL_CONFIG, { optional: true }) ?? CONTENTFUL_FALLBACK_CONFIG;
|
|
423
418
|
|
|
424
419
|
const getPrimaryDomain = (host) => {
|
|
425
420
|
const parts = host.split('.');
|
|
@@ -427,8 +422,8 @@ const getPrimaryDomain = (host) => {
|
|
|
427
422
|
};
|
|
428
423
|
class ContentfulLinkComponent {
|
|
429
424
|
constructor() {
|
|
430
|
-
this.
|
|
431
|
-
this.
|
|
425
|
+
this.document = inject(DOCUMENT);
|
|
426
|
+
this.config = injectContentfulConfig();
|
|
432
427
|
this.href = input.required(/* @ts-ignore */
|
|
433
428
|
...(ngDevMode ? [{ debugName: "href" }] : /* istanbul ignore next */ []));
|
|
434
429
|
this.text = input.required(/* @ts-ignore */
|
|
@@ -442,7 +437,7 @@ class ContentfulLinkComponent {
|
|
|
442
437
|
return false;
|
|
443
438
|
try {
|
|
444
439
|
const parsed = new URL(href.startsWith('//') ? `https:${href}` : href);
|
|
445
|
-
const internalHosts = [this.
|
|
440
|
+
const internalHosts = [this.document.location.host, ...this.config.internalHosts];
|
|
446
441
|
return !internalHosts.includes(parsed.host);
|
|
447
442
|
}
|
|
448
443
|
catch {
|
|
@@ -457,8 +452,8 @@ class ContentfulLinkComponent {
|
|
|
457
452
|
try {
|
|
458
453
|
const parsed = new URL(href.startsWith('//') ? `https:${href}` : href);
|
|
459
454
|
const internalPrimaryDomains = [
|
|
460
|
-
getPrimaryDomain(this.
|
|
461
|
-
...this.
|
|
455
|
+
getPrimaryDomain(this.document.location.host),
|
|
456
|
+
...this.config.internalHosts.map(getPrimaryDomain),
|
|
462
457
|
];
|
|
463
458
|
return !internalPrimaryDomains.includes(getPrimaryDomain(parsed.host));
|
|
464
459
|
}
|
|
@@ -488,8 +483,8 @@ class ContentfulLinkComponent {
|
|
|
488
483
|
}, /* @ts-ignore */
|
|
489
484
|
...(ngDevMode ? [{ debugName: "linkClass" }] : /* istanbul ignore next */ []));
|
|
490
485
|
}
|
|
491
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
492
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
486
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ContentfulLinkComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
487
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", 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 } }, host: { styleAttribute: "display: contents" }, ngImport: i0, template: `
|
|
493
488
|
@if (isExternal()) {
|
|
494
489
|
<a
|
|
495
490
|
[class]="linkClass()"
|
|
@@ -501,9 +496,9 @@ class ContentfulLinkComponent {
|
|
|
501
496
|
} @else {
|
|
502
497
|
<a [class]="linkClass()" [routerLink]="internalPath()">{{ text() }}</a>
|
|
503
498
|
}
|
|
504
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }],
|
|
499
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
505
500
|
}
|
|
506
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
501
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ContentfulLinkComponent, decorators: [{
|
|
507
502
|
type: Component,
|
|
508
503
|
args: [{
|
|
509
504
|
selector: 'et-contentful-link',
|
|
@@ -520,15 +515,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
520
515
|
<a [class]="linkClass()" [routerLink]="internalPath()">{{ text() }}</a>
|
|
521
516
|
}
|
|
522
517
|
`,
|
|
518
|
+
encapsulation: ViewEncapsulation.None,
|
|
519
|
+
imports: [RouterLink],
|
|
523
520
|
host: {
|
|
524
521
|
style: 'display: contents',
|
|
525
522
|
},
|
|
526
|
-
imports: [RouterLink],
|
|
527
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
528
|
-
encapsulation: ViewEncapsulation.None,
|
|
529
523
|
}]
|
|
530
524
|
}], 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 }] }] } });
|
|
531
525
|
|
|
526
|
+
/**
|
|
527
|
+
* The `BLOCKS` node type strings of `@contentful/rich-text-types`, as a plain literal map.
|
|
528
|
+
* Keep the values in sync with the enum - they are part of the Contentful Delivery API.
|
|
529
|
+
*/
|
|
530
|
+
const CF_BLOCKS = {
|
|
531
|
+
DOCUMENT: 'document',
|
|
532
|
+
PARAGRAPH: 'paragraph',
|
|
533
|
+
HEADING_1: 'heading-1',
|
|
534
|
+
HEADING_2: 'heading-2',
|
|
535
|
+
HEADING_3: 'heading-3',
|
|
536
|
+
HEADING_4: 'heading-4',
|
|
537
|
+
HEADING_5: 'heading-5',
|
|
538
|
+
HEADING_6: 'heading-6',
|
|
539
|
+
OL_LIST: 'ordered-list',
|
|
540
|
+
UL_LIST: 'unordered-list',
|
|
541
|
+
LIST_ITEM: 'list-item',
|
|
542
|
+
HR: 'hr',
|
|
543
|
+
QUOTE: 'blockquote',
|
|
544
|
+
EMBEDDED_ENTRY: 'embedded-entry-block',
|
|
545
|
+
EMBEDDED_ASSET: 'embedded-asset-block',
|
|
546
|
+
EMBEDDED_RESOURCE: 'embedded-resource-block',
|
|
547
|
+
TABLE: 'table',
|
|
548
|
+
TABLE_ROW: 'table-row',
|
|
549
|
+
TABLE_CELL: 'table-cell',
|
|
550
|
+
TABLE_HEADER_CELL: 'table-header-cell',
|
|
551
|
+
};
|
|
552
|
+
/**
|
|
553
|
+
* The `INLINES` node type strings of `@contentful/rich-text-types`, as a plain literal map.
|
|
554
|
+
* Keep the values in sync with the enum - they are part of the Contentful Delivery API.
|
|
555
|
+
*/
|
|
556
|
+
const CF_INLINES = {
|
|
557
|
+
ASSET_HYPERLINK: 'asset-hyperlink',
|
|
558
|
+
EMBEDDED_ENTRY: 'embedded-entry-inline',
|
|
559
|
+
EMBEDDED_RESOURCE: 'embedded-resource-inline',
|
|
560
|
+
ENTRY_HYPERLINK: 'entry-hyperlink',
|
|
561
|
+
HYPERLINK: 'hyperlink',
|
|
562
|
+
RESOURCE_HYPERLINK: 'resource-hyperlink',
|
|
563
|
+
};
|
|
564
|
+
|
|
532
565
|
const RICH_TEXT_RENDERER_ERRORS = {
|
|
533
566
|
rich_text_undefined: 'The property value given at richTextPath is undefined. Use the richTextPath input to specify the path to the rich text object.',
|
|
534
567
|
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".',
|
|
@@ -540,10 +573,17 @@ const RICH_TEXT_RENDERER_ERRORS = {
|
|
|
540
573
|
text_parent_not_found: 'The parent node is not found. This structure is not supported.',
|
|
541
574
|
text_parent_wrong_type: 'The parent node neither a html element nor a custom component. This structure is not supported.',
|
|
542
575
|
};
|
|
543
|
-
const RICH_TEXT_RENDERER_ERROR_CODES =
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
576
|
+
const RICH_TEXT_RENDERER_ERROR_CODES = {
|
|
577
|
+
rich_text_undefined: 0,
|
|
578
|
+
rich_text_wrong_type: 1,
|
|
579
|
+
asset_id_not_found: 2,
|
|
580
|
+
entry_id_not_found: 3,
|
|
581
|
+
asset_not_found: 4,
|
|
582
|
+
entry_not_found: 5,
|
|
583
|
+
custom_component_not_found: 6,
|
|
584
|
+
text_parent_not_found: 7,
|
|
585
|
+
text_parent_wrong_type: 8,
|
|
586
|
+
};
|
|
547
587
|
const richTextRendererError = (code, data) => {
|
|
548
588
|
const message = `<et-contentful-rich-text-renderer>: ${RICH_TEXT_RENDERER_ERRORS[code]}`;
|
|
549
589
|
throw new RuntimeError(RICH_TEXT_RENDERER_ERROR_CODES[code], message, data);
|
|
@@ -554,160 +594,93 @@ const isRichTextRootNode = (node) => {
|
|
|
554
594
|
};
|
|
555
595
|
const translateContentfulNodeTypeToHtmlTag = (nodeType) => {
|
|
556
596
|
switch (nodeType) {
|
|
557
|
-
case
|
|
597
|
+
case CF_BLOCKS.HEADING_1:
|
|
558
598
|
return 'h1';
|
|
559
|
-
case
|
|
599
|
+
case CF_BLOCKS.HEADING_2:
|
|
560
600
|
return 'h2';
|
|
561
|
-
case
|
|
601
|
+
case CF_BLOCKS.HEADING_3:
|
|
562
602
|
return 'h3';
|
|
563
|
-
case
|
|
603
|
+
case CF_BLOCKS.HEADING_4:
|
|
564
604
|
return 'h4';
|
|
565
|
-
case
|
|
605
|
+
case CF_BLOCKS.HEADING_5:
|
|
566
606
|
return 'h5';
|
|
567
|
-
case
|
|
607
|
+
case CF_BLOCKS.HEADING_6:
|
|
568
608
|
return 'h6';
|
|
569
|
-
case
|
|
609
|
+
case CF_BLOCKS.PARAGRAPH:
|
|
570
610
|
return 'p';
|
|
571
|
-
case
|
|
611
|
+
case CF_BLOCKS.UL_LIST:
|
|
572
612
|
return 'ul';
|
|
573
|
-
case
|
|
613
|
+
case CF_BLOCKS.OL_LIST:
|
|
574
614
|
return 'ol';
|
|
575
|
-
case
|
|
615
|
+
case CF_BLOCKS.LIST_ITEM:
|
|
576
616
|
return 'li';
|
|
577
|
-
case
|
|
617
|
+
case CF_BLOCKS.HR:
|
|
578
618
|
return 'hr';
|
|
579
|
-
case
|
|
619
|
+
case CF_BLOCKS.QUOTE:
|
|
580
620
|
return 'blockquote';
|
|
581
|
-
case
|
|
621
|
+
case CF_BLOCKS.TABLE:
|
|
582
622
|
return 'table';
|
|
583
|
-
case
|
|
623
|
+
case CF_BLOCKS.TABLE_ROW:
|
|
584
624
|
return 'tr';
|
|
585
|
-
case
|
|
625
|
+
case CF_BLOCKS.TABLE_CELL:
|
|
586
626
|
return 'td';
|
|
587
|
-
case
|
|
627
|
+
case CF_BLOCKS.TABLE_HEADER_CELL:
|
|
588
628
|
return 'th';
|
|
589
|
-
case
|
|
629
|
+
case CF_BLOCKS.EMBEDDED_ASSET:
|
|
590
630
|
return 'div';
|
|
591
|
-
case
|
|
631
|
+
case CF_INLINES.ENTRY_HYPERLINK:
|
|
592
632
|
return 'a';
|
|
593
|
-
case
|
|
633
|
+
case CF_INLINES.ASSET_HYPERLINK:
|
|
594
634
|
return 'a';
|
|
595
635
|
case 'text':
|
|
596
636
|
return 'span';
|
|
597
637
|
// Will be ignored by the renderer
|
|
598
638
|
case 'document':
|
|
599
639
|
return 'div';
|
|
600
|
-
case
|
|
640
|
+
case CF_BLOCKS.EMBEDDED_ENTRY:
|
|
601
641
|
return 'div';
|
|
602
|
-
case
|
|
642
|
+
case CF_INLINES.EMBEDDED_ENTRY:
|
|
603
643
|
return 'div';
|
|
604
644
|
default:
|
|
605
645
|
return 'div';
|
|
606
646
|
}
|
|
607
647
|
};
|
|
608
648
|
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
NESTING_LEVEL: 1,
|
|
618
|
-
DOM_POSITION: 2,
|
|
619
|
-
INDEX: 3,
|
|
620
|
-
};
|
|
621
|
-
const HTML_OPEN_RENDER_COMMAND_POSITION = {
|
|
622
|
-
ATTRIBUTES: 4,
|
|
623
|
-
TAG_NAME: 5,
|
|
624
|
-
ELEMENT_ID: 6,
|
|
649
|
+
const MARK_TAG_MAP = {
|
|
650
|
+
bold: 'strong',
|
|
651
|
+
italic: 'em',
|
|
652
|
+
underline: 'u',
|
|
653
|
+
code: 'code',
|
|
654
|
+
strikethrough: 's',
|
|
655
|
+
subscript: 'sub',
|
|
656
|
+
superscript: 'sup',
|
|
625
657
|
};
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
TEXT: 5,
|
|
633
|
-
TEXT_ID: 6,
|
|
634
|
-
};
|
|
635
|
-
const COMPONENT_RENDER_COMMAND_POSITION = {
|
|
636
|
-
ATTRIBUTES: 4,
|
|
637
|
-
COMPONENT: 5,
|
|
638
|
-
INPUTS: 6,
|
|
639
|
-
COMPONENT_ID: 7,
|
|
640
|
-
};
|
|
641
|
-
const RENDER_INSTRUCTION_POSITION = {
|
|
642
|
-
TYPE: 0,
|
|
643
|
-
COMMAND: 1,
|
|
644
|
-
};
|
|
645
|
-
const RENDER_INSTRUCTION_TYPE = {
|
|
646
|
-
CREATE: 0,
|
|
647
|
-
UPDATE: 1,
|
|
648
|
-
DELETE: 2,
|
|
649
|
-
MOVE: 3,
|
|
650
|
-
};
|
|
651
|
-
const MARK_TAILWIND_MAP = {
|
|
652
|
-
bold: 'font-bold',
|
|
653
|
-
italic: 'italic',
|
|
654
|
-
underline: 'underline',
|
|
655
|
-
code: 'font-mono',
|
|
656
|
-
};
|
|
657
|
-
const marksToClass = (marks) => {
|
|
658
|
-
const classes = [];
|
|
658
|
+
/**
|
|
659
|
+
* Translates contentful text marks into the semantic html elements the marked text is
|
|
660
|
+
* wrapped in, outermost first. Unknown marks are skipped (with a dev-mode warning).
|
|
661
|
+
*/
|
|
662
|
+
const marksToTags = (marks) => {
|
|
663
|
+
const tags = [];
|
|
659
664
|
for (const mark of marks) {
|
|
660
|
-
|
|
661
|
-
if (!
|
|
662
|
-
|
|
663
|
-
|
|
665
|
+
const tag = MARK_TAG_MAP[mark.type];
|
|
666
|
+
if (!tag) {
|
|
667
|
+
if (isDevMode()) {
|
|
668
|
+
console.warn(`No element found for mark type "${mark.type}"! The mark is ignored.`, mark);
|
|
669
|
+
}
|
|
670
|
+
continue;
|
|
664
671
|
}
|
|
665
|
-
|
|
666
|
-
}
|
|
667
|
-
return classes.join(' ');
|
|
668
|
-
};
|
|
669
|
-
const isHtmlOpenRenderCommand = (command) => {
|
|
670
|
-
return command[RENDER_COMMAND_POSITION.TYPE] === RENDER_COMMAND_TYPE.HTML_OPEN;
|
|
671
|
-
};
|
|
672
|
-
const isHtmlCloseRenderCommand = (command) => {
|
|
673
|
-
return command[RENDER_COMMAND_POSITION.TYPE] === RENDER_COMMAND_TYPE.HTML_CLOSE;
|
|
674
|
-
};
|
|
675
|
-
const isTextRenderCommand = (command) => {
|
|
676
|
-
return command[RENDER_COMMAND_POSITION.TYPE] === RENDER_COMMAND_TYPE.TEXT;
|
|
677
|
-
};
|
|
678
|
-
const isComponentRenderCommand = (command) => {
|
|
679
|
-
return command[RENDER_COMMAND_POSITION.TYPE] === RENDER_COMMAND_TYPE.COMPONENT;
|
|
680
|
-
};
|
|
681
|
-
const getRenderCommandId = (command) => {
|
|
682
|
-
if (isHtmlOpenRenderCommand(command)) {
|
|
683
|
-
return command[HTML_OPEN_RENDER_COMMAND_POSITION.ELEMENT_ID];
|
|
684
|
-
}
|
|
685
|
-
else if (isHtmlCloseRenderCommand(command)) {
|
|
686
|
-
return command[HTML_CLOSE_RENDER_COMMAND_POSITION.ELEMENT_ID];
|
|
687
|
-
}
|
|
688
|
-
else if (isTextRenderCommand(command)) {
|
|
689
|
-
return command[TEXT_RENDER_COMMAND_POSITION.TEXT_ID];
|
|
690
|
-
}
|
|
691
|
-
else {
|
|
692
|
-
return command[COMPONENT_RENDER_COMMAND_POSITION.COMPONENT_ID];
|
|
672
|
+
tags.push(tag);
|
|
693
673
|
}
|
|
674
|
+
return tags;
|
|
694
675
|
};
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
LINK: '$$$_et-link',
|
|
702
|
-
};
|
|
676
|
+
/**
|
|
677
|
+
* The classes marked text inside a hyperlink is rendered with, since the link component
|
|
678
|
+
* receives its text as a plain string.
|
|
679
|
+
*/
|
|
680
|
+
const marksToClass = (marks) => marks.map((mark) => `et-contentful-rich-text-mark-${mark.type}`).join(' ');
|
|
681
|
+
const LINK_COMPONENT_TYPE = '$$$_et-link';
|
|
703
682
|
const isExecutedComponentCommandCacheItem = (cache) => {
|
|
704
|
-
return
|
|
705
|
-
};
|
|
706
|
-
const isExecutedHtmlOrTextCommandCacheItem = (cache) => {
|
|
707
|
-
return isHtmlCloseRenderCommand(cache.command) || isHtmlOpenRenderCommand(cache.command);
|
|
708
|
-
};
|
|
709
|
-
const isExecutedTextCommandCacheItem = (cache) => {
|
|
710
|
-
return isTextRenderCommand(cache.command);
|
|
683
|
+
return cache.command.kind === 'component';
|
|
711
684
|
};
|
|
712
685
|
const ET_CONTENTFUL_ANY_ENTRY_CONTENT_TYPE_SYS_ID = '$$$_et-contentful-any-entry-content-type-sys-id';
|
|
713
686
|
/**
|
|
@@ -763,17 +736,10 @@ const createContentfulIncludeMap = (config) => {
|
|
|
763
736
|
};
|
|
764
737
|
class ContentfulRichTextRendererComponent {
|
|
765
738
|
constructor() {
|
|
766
|
-
this.
|
|
767
|
-
this.
|
|
768
|
-
this.
|
|
769
|
-
this.
|
|
770
|
-
this._config = inject(CONTENTFUL_CONFIG, { optional: true }) ?? createContentfulConfig();
|
|
771
|
-
/**
|
|
772
|
-
* A cache for all executed commands that are not deleted.
|
|
773
|
-
* This is used to keep track of all rendered elements and components.
|
|
774
|
-
* The key is the render command element ID.
|
|
775
|
-
*/
|
|
776
|
-
this._executedCommandsCache = new Map();
|
|
739
|
+
this.viewContainerRef = inject(ViewContainerRef);
|
|
740
|
+
this.renderer = injectRenderer();
|
|
741
|
+
this.elementRef = inject(ElementRef);
|
|
742
|
+
this.config = injectContentfulConfig();
|
|
777
743
|
/**
|
|
778
744
|
* The contentful response gotten via their REST api.
|
|
779
745
|
* @see https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/entries/entries-collection
|
|
@@ -786,6 +752,12 @@ class ContentfulRichTextRendererComponent {
|
|
|
786
752
|
*/
|
|
787
753
|
this.richTextPath = input.required(/* @ts-ignore */
|
|
788
754
|
...(ngDevMode ? [{ debugName: "richTextPath" }] : /* istanbul ignore next */ []));
|
|
755
|
+
/**
|
|
756
|
+
* A cache for all executed commands that are not deleted.
|
|
757
|
+
* This is used to keep track of all rendered elements and components.
|
|
758
|
+
* The key is the render command ID.
|
|
759
|
+
*/
|
|
760
|
+
this.executedCommandsCache = new Map();
|
|
789
761
|
/**
|
|
790
762
|
* A map of all includes in the contentful response.
|
|
791
763
|
* This is useful for looking up assets and entries that are referenced in the rich text without having to use loops.
|
|
@@ -821,93 +793,116 @@ class ContentfulRichTextRendererComponent {
|
|
|
821
793
|
if (!richTextData) {
|
|
822
794
|
return [];
|
|
823
795
|
}
|
|
824
|
-
return this.
|
|
796
|
+
return this.createRenderCommands(richTextData);
|
|
825
797
|
}, /* @ts-ignore */
|
|
826
798
|
...(ngDevMode ? [{ debugName: "renderCommands" }] : /* istanbul ignore next */ []));
|
|
827
|
-
this.renderCommandHistory =
|
|
799
|
+
this.renderCommandHistory = linkedSignal({ ...(ngDevMode ? { debugName: "renderCommandHistory" } : /* istanbul ignore next */ {}), source: this.renderCommands,
|
|
800
|
+
computation: (commands, previous) => [previous?.source ?? [], commands] });
|
|
828
801
|
this.previousRenderCommandMap = computed(() => {
|
|
829
802
|
const [prevCommands] = this.renderCommandHistory();
|
|
830
803
|
const map = new Map();
|
|
831
804
|
for (const command of prevCommands) {
|
|
832
|
-
map.set(
|
|
805
|
+
map.set(command.id, command);
|
|
833
806
|
}
|
|
834
807
|
return map;
|
|
835
808
|
}, /* @ts-ignore */
|
|
836
809
|
...(ngDevMode ? [{ debugName: "previousRenderCommandMap" }] : /* istanbul ignore next */ []));
|
|
837
810
|
this.renderInstructions = computed(() => {
|
|
838
811
|
const commands = this.renderCommands();
|
|
839
|
-
const previousRenderCommandMap =
|
|
812
|
+
const previousRenderCommandMap = this.previousRenderCommandMap();
|
|
840
813
|
const instructions = [];
|
|
841
|
-
//
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
814
|
+
// Decide, in document order, which commands survive the change with their DOM intact.
|
|
815
|
+
// A plain element or text node survives when the same id renders the same output at the same
|
|
816
|
+
// spot inside a surviving parent. Ancestors are decided first because parents precede their
|
|
817
|
+
// children in command order. Components survive on id alone — their DOM can be reattached.
|
|
818
|
+
const preserved = new Set();
|
|
819
|
+
const needsReattach = new Set();
|
|
820
|
+
const parentIdOf = (command) => {
|
|
821
|
+
if (command.nestingLevel === 0) {
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
for (let i = command.index - 1; i >= 0; i--) {
|
|
825
|
+
const cmd = commands[i];
|
|
826
|
+
if (cmd && cmd.nestingLevel === command.nestingLevel - 1 && cmd.kind === 'htmlOpen') {
|
|
827
|
+
return cmd.id;
|
|
847
828
|
}
|
|
848
829
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
throw new Error('Command not found!');
|
|
830
|
+
return null;
|
|
831
|
+
};
|
|
832
|
+
const markTagsEqual = (a, b) => a.length === b.length && a.every((tag, index) => tag === b[index]);
|
|
833
|
+
const attributesEqual = (a, b) => {
|
|
834
|
+
const aKeys = Object.keys(a);
|
|
835
|
+
return aKeys.length === Object.keys(b).length && aKeys.every((key) => a[key] === b[key]);
|
|
836
|
+
};
|
|
837
|
+
for (const command of commands) {
|
|
838
|
+
if (command.kind === 'htmlClose') {
|
|
839
|
+
continue;
|
|
860
840
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
command: newCommand,
|
|
865
|
-
});
|
|
866
|
-
}
|
|
867
|
-
lastComponentIndexes.sort((a, b) => a.newIndex - b.newIndex);
|
|
868
|
-
// check if the prevIndex are in order ascending
|
|
869
|
-
// for indexes that are not in order, create a move instruction
|
|
870
|
-
// for indexes that are in order, create an update instruction
|
|
871
|
-
let moveCheckIndex = 0;
|
|
872
|
-
while (moveCheckIndex < lastComponentIndexes.length - 1) {
|
|
873
|
-
const moveCurr = lastComponentIndexes[moveCheckIndex];
|
|
874
|
-
const moveNext = lastComponentIndexes[moveCheckIndex + 1];
|
|
875
|
-
if (!moveCurr || !moveNext) {
|
|
876
|
-
break;
|
|
841
|
+
const previous = previousRenderCommandMap.get(command.id);
|
|
842
|
+
if (!previous || previous.kind !== command.kind) {
|
|
843
|
+
continue;
|
|
877
844
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
845
|
+
// The same key must render the same component class — a changed class means a fresh instance.
|
|
846
|
+
if (previous.kind === 'component' && command.kind === 'component' && previous.component !== command.component) {
|
|
847
|
+
continue;
|
|
881
848
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
849
|
+
const parentId = parentIdOf(command);
|
|
850
|
+
const parentPreserved = parentId === null || preserved.has(parentId);
|
|
851
|
+
if (command.kind === 'component') {
|
|
852
|
+
preserved.add(command.id);
|
|
853
|
+
const samePlace = parentPreserved &&
|
|
854
|
+
previous.nestingLevel === command.nestingLevel &&
|
|
855
|
+
previous.domPosition === command.domPosition;
|
|
856
|
+
if (!samePlace) {
|
|
857
|
+
needsReattach.add(command.id);
|
|
858
|
+
}
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
const sameOutput = previous.kind === command.kind &&
|
|
862
|
+
(previous.kind !== 'htmlOpen' || previous.tagName === command.tagName) &&
|
|
863
|
+
(previous.kind !== 'text' ||
|
|
864
|
+
(previous.text === command.text &&
|
|
865
|
+
markTagsEqual(previous.markTags, command.markTags)));
|
|
866
|
+
if (parentPreserved &&
|
|
867
|
+
sameOutput &&
|
|
868
|
+
previous.nestingLevel === command.nestingLevel &&
|
|
869
|
+
previous.domPosition === command.domPosition &&
|
|
870
|
+
attributesEqual(previous.attributes, command.attributes)) {
|
|
871
|
+
preserved.add(command.id);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
// Everything that did not survive is removed first, so the ordered pass below can rebuild
|
|
875
|
+
// into a clean tree. Close commands never render DOM and are skipped throughout.
|
|
876
|
+
for (const [id, command] of previousRenderCommandMap) {
|
|
877
|
+
if (command.kind !== 'htmlClose' && !preserved.has(id)) {
|
|
878
|
+
instructions.push({ type: 'delete', command });
|
|
885
879
|
}
|
|
886
880
|
}
|
|
881
|
+
// One pass in document order: parents are created before a child component is reattached.
|
|
882
|
+
// Surviving plain elements still get an update so the cache tracks their fresh command data.
|
|
887
883
|
for (const command of commands) {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
884
|
+
if (command.kind === 'htmlClose') {
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
if (!preserved.has(command.id)) {
|
|
888
|
+
instructions.push({ type: 'create', command });
|
|
889
|
+
}
|
|
890
|
+
else if (command.kind === 'component' && needsReattach.has(command.id)) {
|
|
891
|
+
instructions.push({ type: 'move', command });
|
|
895
892
|
}
|
|
896
893
|
else {
|
|
897
|
-
|
|
898
|
-
instructions.push([RENDER_INSTRUCTION_TYPE.CREATE, command]);
|
|
894
|
+
instructions.push({ type: 'update', command });
|
|
899
895
|
}
|
|
900
896
|
}
|
|
901
897
|
return instructions;
|
|
902
898
|
}, /* @ts-ignore */
|
|
903
899
|
...(ngDevMode ? [{ debugName: "renderInstructions" }] : /* istanbul ignore next */ []));
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
this.
|
|
907
|
-
})
|
|
908
|
-
.subscribe();
|
|
900
|
+
effect(() => {
|
|
901
|
+
const instructions = this.renderInstructions();
|
|
902
|
+
untracked(() => this.execInstructions(instructions));
|
|
903
|
+
});
|
|
909
904
|
}
|
|
910
|
-
|
|
905
|
+
createRenderCommands(richTextData) {
|
|
911
906
|
/** List of all render commands */
|
|
912
907
|
const rootCommands = [];
|
|
913
908
|
/** Counter for generating unique html element IDs. */
|
|
@@ -925,32 +920,26 @@ class ContentfulRichTextRendererComponent {
|
|
|
925
920
|
const traverse = (node) => {
|
|
926
921
|
switch (node.nodeType) {
|
|
927
922
|
case 'text': {
|
|
928
|
-
// Render media
|
|
929
|
-
const type = RENDER_COMMAND_TYPE.TEXT;
|
|
930
923
|
const text = node.value;
|
|
931
924
|
if (!text)
|
|
932
925
|
break;
|
|
933
926
|
const attributes = {
|
|
934
927
|
class: 'et-contentful-rich-text-default-element et-contentful-rich-text-default-span',
|
|
935
928
|
};
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
const command = [type, nestingLevel, domPosition, commandIndex++, attributes, text, id];
|
|
947
|
-
rootCommands.push(command);
|
|
929
|
+
rootCommands.push({
|
|
930
|
+
kind: 'text',
|
|
931
|
+
nestingLevel,
|
|
932
|
+
domPosition,
|
|
933
|
+
index: commandIndex++,
|
|
934
|
+
attributes,
|
|
935
|
+
markTags: node.marks.length ? marksToTags(node.marks) : [],
|
|
936
|
+
text,
|
|
937
|
+
id: 't' + textId++,
|
|
938
|
+
});
|
|
948
939
|
domPosition++;
|
|
949
940
|
break;
|
|
950
941
|
}
|
|
951
|
-
case
|
|
952
|
-
// Render media
|
|
953
|
-
const type = RENDER_COMMAND_TYPE.COMPONENT;
|
|
942
|
+
case CF_BLOCKS.EMBEDDED_ASSET: {
|
|
954
943
|
const assetId = node.data['target']?.sys?.id;
|
|
955
944
|
if (!assetId) {
|
|
956
945
|
throw richTextRendererError('asset_id_not_found', { node });
|
|
@@ -960,7 +949,7 @@ class ContentfulRichTextRendererComponent {
|
|
|
960
949
|
throw richTextRendererError('asset_not_found', { assetId, node });
|
|
961
950
|
}
|
|
962
951
|
const contentType = asset.fields.file.contentType;
|
|
963
|
-
const assetComponents = this.
|
|
952
|
+
const assetComponents = this.config.components;
|
|
964
953
|
// Every property inside the asset will be null if no file was provided for a translation.
|
|
965
954
|
// In this case, we can assume that the asset is missing due to user error.
|
|
966
955
|
const isMissing = !contentType && !asset.fields.file.url;
|
|
@@ -980,35 +969,29 @@ class ContentfulRichTextRendererComponent {
|
|
|
980
969
|
: isAudio
|
|
981
970
|
? assetComponents.audio
|
|
982
971
|
: assetComponents.file;
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
asset,
|
|
996
|
-
};
|
|
997
|
-
const command = [
|
|
998
|
-
type,
|
|
972
|
+
if (!component) {
|
|
973
|
+
if (isDevMode()) {
|
|
974
|
+
console.warn('No component registered for this embedded asset! The asset will be skipped. Provide one via provideContentfulConfig({ components: … }).', { asset });
|
|
975
|
+
}
|
|
976
|
+
break;
|
|
977
|
+
}
|
|
978
|
+
const occurrenceKey = 'asset:' + assetId;
|
|
979
|
+
const occurrence = (componentIdMap.get(occurrenceKey) ?? -1) + 1;
|
|
980
|
+
componentIdMap.set(occurrenceKey, occurrence);
|
|
981
|
+
const id = occurrence === 0 ? occurrenceKey : `${occurrenceKey}:${occurrence}`;
|
|
982
|
+
rootCommands.push({
|
|
983
|
+
kind: 'component',
|
|
999
984
|
nestingLevel,
|
|
1000
985
|
domPosition,
|
|
1001
|
-
commandIndex++,
|
|
1002
|
-
attributes,
|
|
986
|
+
index: commandIndex++,
|
|
1003
987
|
component,
|
|
1004
|
-
inputs,
|
|
988
|
+
inputs: { asset },
|
|
1005
989
|
id,
|
|
1006
|
-
|
|
1007
|
-
rootCommands.push(command);
|
|
990
|
+
});
|
|
1008
991
|
domPosition++;
|
|
1009
992
|
break;
|
|
1010
993
|
}
|
|
1011
|
-
case
|
|
994
|
+
case CF_INLINES.HYPERLINK: {
|
|
1012
995
|
const uri = node.data['uri'] ?? '';
|
|
1013
996
|
// Collect all text and marks from children (hyperlink children are text nodes)
|
|
1014
997
|
let linkText = '';
|
|
@@ -1019,28 +1002,67 @@ class ContentfulRichTextRendererComponent {
|
|
|
1019
1002
|
linkMarks.push(...child.marks);
|
|
1020
1003
|
}
|
|
1021
1004
|
}
|
|
1005
|
+
const linkComponent = this.config.components.link;
|
|
1006
|
+
if (!linkComponent) {
|
|
1007
|
+
// Without a link component the hyperlink still renders - as a plain anchor.
|
|
1008
|
+
rootCommands.push({
|
|
1009
|
+
kind: 'htmlOpen',
|
|
1010
|
+
nestingLevel,
|
|
1011
|
+
domPosition,
|
|
1012
|
+
index: commandIndex++,
|
|
1013
|
+
attributes: {
|
|
1014
|
+
class: 'et-contentful-rich-text-default-element et-contentful-rich-text-default-a',
|
|
1015
|
+
href: uri,
|
|
1016
|
+
},
|
|
1017
|
+
tagName: 'a',
|
|
1018
|
+
id: 'e-o' + elementOpenId++,
|
|
1019
|
+
});
|
|
1020
|
+
const anchorDomPosition = domPosition;
|
|
1021
|
+
nestingLevel++;
|
|
1022
|
+
domPosition = 0;
|
|
1023
|
+
rootCommands.push({
|
|
1024
|
+
kind: 'text',
|
|
1025
|
+
nestingLevel,
|
|
1026
|
+
domPosition,
|
|
1027
|
+
index: commandIndex++,
|
|
1028
|
+
attributes: {
|
|
1029
|
+
class: 'et-contentful-rich-text-default-element et-contentful-rich-text-default-span',
|
|
1030
|
+
},
|
|
1031
|
+
markTags: marksToTags(linkMarks),
|
|
1032
|
+
text: linkText,
|
|
1033
|
+
id: 't' + textId++,
|
|
1034
|
+
});
|
|
1035
|
+
nestingLevel--;
|
|
1036
|
+
domPosition = anchorDomPosition;
|
|
1037
|
+
rootCommands.push({
|
|
1038
|
+
kind: 'htmlClose',
|
|
1039
|
+
nestingLevel,
|
|
1040
|
+
domPosition,
|
|
1041
|
+
index: commandIndex++,
|
|
1042
|
+
tagName: 'a',
|
|
1043
|
+
id: 'e-c' + elementCloseId++,
|
|
1044
|
+
});
|
|
1045
|
+
domPosition++;
|
|
1046
|
+
break;
|
|
1047
|
+
}
|
|
1022
1048
|
const textClass = linkMarks.length ? marksToClass(linkMarks) : '';
|
|
1023
|
-
let linkComponentId = componentIdMap.get(
|
|
1024
|
-
const linkId =
|
|
1025
|
-
componentIdMap.set(
|
|
1026
|
-
|
|
1027
|
-
|
|
1049
|
+
let linkComponentId = componentIdMap.get(LINK_COMPONENT_TYPE) ?? -1;
|
|
1050
|
+
const linkId = LINK_COMPONENT_TYPE + ++linkComponentId;
|
|
1051
|
+
componentIdMap.set(LINK_COMPONENT_TYPE, linkComponentId);
|
|
1052
|
+
rootCommands.push({
|
|
1053
|
+
kind: 'component',
|
|
1028
1054
|
nestingLevel,
|
|
1029
1055
|
domPosition,
|
|
1030
|
-
commandIndex++,
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
];
|
|
1036
|
-
rootCommands.push(linkCommand);
|
|
1056
|
+
index: commandIndex++,
|
|
1057
|
+
component: linkComponent,
|
|
1058
|
+
inputs: { href: uri, text: linkText, textClass },
|
|
1059
|
+
id: linkId,
|
|
1060
|
+
});
|
|
1037
1061
|
domPosition++;
|
|
1038
1062
|
break;
|
|
1039
1063
|
}
|
|
1040
|
-
case
|
|
1041
|
-
case
|
|
1042
|
-
// Render component
|
|
1043
|
-
const type = RENDER_COMMAND_TYPE.COMPONENT;
|
|
1064
|
+
case CF_BLOCKS.EMBEDDED_ENTRY:
|
|
1065
|
+
case CF_INLINES.EMBEDDED_ENTRY: {
|
|
1044
1066
|
const entryId = node.data['target']?.sys?.id;
|
|
1045
1067
|
if (!entryId) {
|
|
1046
1068
|
throw richTextRendererError('entry_id_not_found', { node });
|
|
@@ -1050,47 +1072,49 @@ class ContentfulRichTextRendererComponent {
|
|
|
1050
1072
|
throw richTextRendererError('entry_not_found', { entryId, node });
|
|
1051
1073
|
}
|
|
1052
1074
|
const componentType = entry.sys.contentType.sys.id;
|
|
1053
|
-
const component = this.
|
|
1075
|
+
const component = this.config.customComponents[componentType];
|
|
1054
1076
|
if (!component) {
|
|
1055
1077
|
throw richTextRendererError('custom_component_not_found', {
|
|
1056
1078
|
componentType,
|
|
1057
|
-
customComponents: this.
|
|
1079
|
+
customComponents: this.config.customComponents,
|
|
1058
1080
|
entry,
|
|
1059
1081
|
});
|
|
1060
1082
|
}
|
|
1061
|
-
const
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
metadata: entry.metadata,
|
|
1068
|
-
sys: entry.sys,
|
|
1069
|
-
includes: this.contentIncludesMap(),
|
|
1070
|
-
};
|
|
1071
|
-
const command = [
|
|
1072
|
-
type,
|
|
1083
|
+
const occurrenceKey = 'entry:' + entryId;
|
|
1084
|
+
const occurrence = (componentIdMap.get(occurrenceKey) ?? -1) + 1;
|
|
1085
|
+
componentIdMap.set(occurrenceKey, occurrence);
|
|
1086
|
+
const id = occurrence === 0 ? occurrenceKey : `${occurrenceKey}:${occurrence}`;
|
|
1087
|
+
rootCommands.push({
|
|
1088
|
+
kind: 'component',
|
|
1073
1089
|
nestingLevel,
|
|
1074
1090
|
domPosition,
|
|
1075
|
-
commandIndex++,
|
|
1076
|
-
attributes,
|
|
1091
|
+
index: commandIndex++,
|
|
1077
1092
|
component,
|
|
1078
|
-
inputs
|
|
1093
|
+
inputs: {
|
|
1094
|
+
fields: entry.fields,
|
|
1095
|
+
metadata: entry.metadata,
|
|
1096
|
+
sys: entry.sys,
|
|
1097
|
+
includes: this.contentIncludesMap(),
|
|
1098
|
+
},
|
|
1079
1099
|
id,
|
|
1080
|
-
|
|
1081
|
-
rootCommands.push(command);
|
|
1100
|
+
});
|
|
1082
1101
|
domPosition++;
|
|
1083
1102
|
break;
|
|
1084
1103
|
}
|
|
1085
1104
|
default: {
|
|
1086
|
-
const type = RENDER_COMMAND_TYPE.HTML_OPEN;
|
|
1087
1105
|
const tag = translateContentfulNodeTypeToHtmlTag(node.nodeType);
|
|
1088
1106
|
const attributes = {
|
|
1089
1107
|
class: `et-contentful-rich-text-default-element et-contentful-rich-text-default-${tag}`,
|
|
1090
1108
|
};
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1109
|
+
rootCommands.push({
|
|
1110
|
+
kind: 'htmlOpen',
|
|
1111
|
+
nestingLevel,
|
|
1112
|
+
domPosition,
|
|
1113
|
+
index: commandIndex++,
|
|
1114
|
+
attributes,
|
|
1115
|
+
tagName: tag,
|
|
1116
|
+
id: 'e-o' + elementOpenId++,
|
|
1117
|
+
});
|
|
1094
1118
|
const domPositionAtThisLevel = domPosition;
|
|
1095
1119
|
// Normal html elements can have children
|
|
1096
1120
|
for (const child of node.content) {
|
|
@@ -1101,25 +1125,21 @@ class ContentfulRichTextRendererComponent {
|
|
|
1101
1125
|
domPosition = domPositionAtThisLevel;
|
|
1102
1126
|
}
|
|
1103
1127
|
const lastCommand = rootCommands[rootCommands.length - 1];
|
|
1104
|
-
if (lastCommand?.
|
|
1105
|
-
lastCommand[HTML_OPEN_RENDER_COMMAND_POSITION.TAG_NAME] !== 'td' &&
|
|
1106
|
-
lastCommand[HTML_OPEN_RENDER_COMMAND_POSITION.TAG_NAME] !== 'hr') {
|
|
1128
|
+
if (lastCommand?.kind === 'htmlOpen' && lastCommand.tagName !== 'td' && lastCommand.tagName !== 'hr') {
|
|
1107
1129
|
// If the last command is an open command, we can remove it since it's empty
|
|
1108
1130
|
rootCommands.pop();
|
|
1109
1131
|
elementOpenId--;
|
|
1110
1132
|
commandIndex--;
|
|
1111
1133
|
}
|
|
1112
1134
|
else {
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
RENDER_COMMAND_TYPE.HTML_CLOSE,
|
|
1135
|
+
rootCommands.push({
|
|
1136
|
+
kind: 'htmlClose',
|
|
1116
1137
|
nestingLevel,
|
|
1117
1138
|
domPosition,
|
|
1118
|
-
commandIndex++,
|
|
1119
|
-
tag,
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
rootCommands.push(closeCommand);
|
|
1139
|
+
index: commandIndex++,
|
|
1140
|
+
tagName: tag,
|
|
1141
|
+
id: 'e-c' + elementCloseId++,
|
|
1142
|
+
});
|
|
1123
1143
|
domPosition++;
|
|
1124
1144
|
}
|
|
1125
1145
|
break;
|
|
@@ -1131,194 +1151,171 @@ class ContentfulRichTextRendererComponent {
|
|
|
1131
1151
|
}
|
|
1132
1152
|
return rootCommands;
|
|
1133
1153
|
}
|
|
1134
|
-
|
|
1154
|
+
execInstructions(instructions) {
|
|
1135
1155
|
for (const instruction of instructions) {
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
case RENDER_INSTRUCTION_TYPE.CREATE:
|
|
1140
|
-
this._runCreateInstruction(command);
|
|
1156
|
+
switch (instruction.type) {
|
|
1157
|
+
case 'create':
|
|
1158
|
+
this.runCreateInstruction(instruction.command);
|
|
1141
1159
|
break;
|
|
1142
|
-
case
|
|
1143
|
-
this.
|
|
1160
|
+
case 'update':
|
|
1161
|
+
this.runUpdateInstruction(instruction.command);
|
|
1144
1162
|
break;
|
|
1145
|
-
case
|
|
1146
|
-
this.
|
|
1163
|
+
case 'move':
|
|
1164
|
+
this.runMoveInstruction(instruction.command);
|
|
1147
1165
|
break;
|
|
1148
|
-
case
|
|
1149
|
-
this.
|
|
1166
|
+
case 'delete':
|
|
1167
|
+
this.runDeleteInstruction(instruction.command);
|
|
1150
1168
|
break;
|
|
1151
1169
|
}
|
|
1152
1170
|
}
|
|
1153
1171
|
}
|
|
1154
|
-
|
|
1155
|
-
const
|
|
1156
|
-
const
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
const
|
|
1162
|
-
|
|
1163
|
-
|
|
1172
|
+
runCreateInstruction(command) {
|
|
1173
|
+
const parentElement = this.findParent(command);
|
|
1174
|
+
const nextElement = this.findFollowingElement(command);
|
|
1175
|
+
if (command.kind === 'component') {
|
|
1176
|
+
const inputs = signal(command.inputs, /* @ts-ignore */
|
|
1177
|
+
...(ngDevMode ? [{ debugName: "inputs" }] : /* istanbul ignore next */ []));
|
|
1178
|
+
// Components may declare any subset of the offered inputs — only bind the declared ones.
|
|
1179
|
+
const declaredInputs = reflectComponentType(command.component)?.inputs ?? [];
|
|
1180
|
+
const bindings = declaredInputs
|
|
1181
|
+
.filter((declared) => declared.propName in command.inputs)
|
|
1182
|
+
.map((declared) => inputBinding(declared.templateName, () => inputs()[declared.propName]));
|
|
1183
|
+
const componentRef = this.viewContainerRef.createComponent(command.component, { bindings });
|
|
1184
|
+
const rootNode = this.getComponentRootNode(componentRef);
|
|
1185
|
+
this.renderInsertOrAppend(rootNode, parentElement, nextElement);
|
|
1186
|
+
this.executedCommandsCache.set(command.id, {
|
|
1164
1187
|
command,
|
|
1165
1188
|
componentRef,
|
|
1189
|
+
inputs,
|
|
1166
1190
|
element: rootNode,
|
|
1167
1191
|
});
|
|
1168
1192
|
}
|
|
1169
|
-
else if (
|
|
1170
|
-
const
|
|
1171
|
-
const
|
|
1172
|
-
const
|
|
1173
|
-
|
|
1174
|
-
const textNodes = [];
|
|
1175
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
1176
|
-
this._renderer.setAttribute(span, key, value);
|
|
1193
|
+
else if (command.kind === 'text') {
|
|
1194
|
+
const span = this.renderer.createElement('span');
|
|
1195
|
+
const textSplitInLineBreaks = command.text.split('\n').filter((t) => t.trim().length > 0);
|
|
1196
|
+
for (const [key, value] of Object.entries(command.attributes)) {
|
|
1197
|
+
this.renderer.setAttribute(span, key, value);
|
|
1177
1198
|
}
|
|
1178
|
-
if (text.startsWith('\n')) {
|
|
1179
|
-
const brNode = this.
|
|
1180
|
-
this.
|
|
1199
|
+
if (command.text.startsWith('\n')) {
|
|
1200
|
+
const brNode = this.renderer.createElement('br');
|
|
1201
|
+
this.renderer.appendChild(parentElement, brNode);
|
|
1202
|
+
}
|
|
1203
|
+
// Marks are rendered as nested semantic elements inside the span, outermost mark first.
|
|
1204
|
+
let textContainer = span;
|
|
1205
|
+
for (const tag of command.markTags) {
|
|
1206
|
+
const markElement = this.renderer.createElement(tag);
|
|
1207
|
+
this.renderer.appendChild(textContainer, markElement);
|
|
1208
|
+
textContainer = markElement;
|
|
1181
1209
|
}
|
|
1182
1210
|
for (const [textPartIndex, textPart] of textSplitInLineBreaks.entries()) {
|
|
1183
1211
|
if (textPartIndex > 0) {
|
|
1184
|
-
const brNode = this.
|
|
1185
|
-
this.
|
|
1212
|
+
const brNode = this.renderer.createElement('br');
|
|
1213
|
+
this.renderer.appendChild(textContainer, brNode);
|
|
1186
1214
|
}
|
|
1187
|
-
const textNode = this.
|
|
1188
|
-
this.
|
|
1189
|
-
textNodes.push(textNode);
|
|
1215
|
+
const textNode = this.renderer.createText(textPart);
|
|
1216
|
+
this.renderer.appendChild(textContainer, textNode);
|
|
1190
1217
|
}
|
|
1191
|
-
this.
|
|
1192
|
-
this.
|
|
1218
|
+
this.renderInsertOrAppend(span, parentElement, nextElement);
|
|
1219
|
+
this.executedCommandsCache.set(command.id, {
|
|
1193
1220
|
command,
|
|
1194
1221
|
element: span,
|
|
1195
|
-
textNode: textNodes,
|
|
1196
1222
|
});
|
|
1197
1223
|
}
|
|
1198
|
-
else if (
|
|
1199
|
-
const
|
|
1200
|
-
const
|
|
1201
|
-
|
|
1202
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
1203
|
-
this._renderer.setAttribute(element, key, value);
|
|
1224
|
+
else if (command.kind === 'htmlOpen') {
|
|
1225
|
+
const element = this.renderer.createElement(command.tagName);
|
|
1226
|
+
for (const [key, value] of Object.entries(command.attributes)) {
|
|
1227
|
+
this.renderer.setAttribute(element, key, value);
|
|
1204
1228
|
}
|
|
1205
|
-
this.
|
|
1206
|
-
this.
|
|
1229
|
+
this.renderInsertOrAppend(element, parentElement, nextElement);
|
|
1230
|
+
this.executedCommandsCache.set(command.id, {
|
|
1207
1231
|
command,
|
|
1208
1232
|
element,
|
|
1209
1233
|
});
|
|
1210
1234
|
}
|
|
1211
1235
|
}
|
|
1212
|
-
|
|
1213
|
-
const
|
|
1214
|
-
const cached = this._executedCommandsCache.get(id);
|
|
1236
|
+
runUpdateInstruction(command) {
|
|
1237
|
+
const cached = this.executedCommandsCache.get(command.id);
|
|
1215
1238
|
if (!cached) {
|
|
1216
1239
|
throw new Error('Cached command not found!');
|
|
1217
1240
|
}
|
|
1218
|
-
if (
|
|
1241
|
+
if (command.kind === 'component') {
|
|
1219
1242
|
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
1220
1243
|
throw new Error('Cached command is not a component command!');
|
|
1221
1244
|
}
|
|
1222
|
-
|
|
1223
|
-
if (!componentRef) {
|
|
1224
|
-
throw new Error('Component ref not found!');
|
|
1225
|
-
}
|
|
1226
|
-
this._updateComponentInputs(command, componentRef);
|
|
1245
|
+
cached.inputs.set(command.inputs);
|
|
1227
1246
|
}
|
|
1228
|
-
|
|
1247
|
+
// `cached` and the fresh `command` share the same id and therefore the same union variant,
|
|
1248
|
+
// but TS can't correlate the two after the spread — assert the merged object as the union.
|
|
1249
|
+
this.executedCommandsCache.set(command.id, {
|
|
1229
1250
|
...cached,
|
|
1230
|
-
command
|
|
1251
|
+
command,
|
|
1231
1252
|
});
|
|
1232
1253
|
}
|
|
1233
|
-
|
|
1234
|
-
const
|
|
1235
|
-
const cached = this._executedCommandsCache.get(id);
|
|
1254
|
+
runMoveInstruction(command) {
|
|
1255
|
+
const cached = this.executedCommandsCache.get(command.id);
|
|
1236
1256
|
if (!cached) {
|
|
1237
1257
|
throw new Error('Cached command not found!');
|
|
1238
1258
|
}
|
|
1239
|
-
if (
|
|
1259
|
+
if (command.kind === 'component') {
|
|
1240
1260
|
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
1241
1261
|
throw new Error('Cached command is not a component command!');
|
|
1242
1262
|
}
|
|
1243
|
-
const componentRef = cached.componentRef;
|
|
1244
|
-
if (!componentRef) {
|
|
1245
|
-
throw new Error('Component ref not found!');
|
|
1246
|
-
}
|
|
1247
1263
|
const rootNode = cached.element;
|
|
1248
1264
|
const oldParentElement = cached.element.parentElement;
|
|
1249
1265
|
if (oldParentElement) {
|
|
1250
|
-
this.
|
|
1266
|
+
this.renderer.removeChild(oldParentElement, rootNode);
|
|
1251
1267
|
}
|
|
1252
|
-
const newParentElement = this.
|
|
1253
|
-
const nextElement = this.
|
|
1254
|
-
this.
|
|
1255
|
-
|
|
1256
|
-
this.
|
|
1268
|
+
const newParentElement = this.findParent(command);
|
|
1269
|
+
const nextElement = this.findFollowingElement(command);
|
|
1270
|
+
this.renderInsertOrAppend(rootNode, newParentElement, nextElement);
|
|
1271
|
+
cached.inputs.set(command.inputs);
|
|
1272
|
+
this.executedCommandsCache.set(command.id, {
|
|
1257
1273
|
...cached,
|
|
1258
|
-
command
|
|
1274
|
+
command,
|
|
1259
1275
|
});
|
|
1260
1276
|
}
|
|
1261
1277
|
}
|
|
1262
|
-
|
|
1263
|
-
const
|
|
1264
|
-
const cached = this._executedCommandsCache.get(id);
|
|
1278
|
+
runDeleteInstruction(command) {
|
|
1279
|
+
const cached = this.executedCommandsCache.get(command.id);
|
|
1265
1280
|
if (!cached) {
|
|
1266
|
-
if (
|
|
1281
|
+
if (command.kind === 'htmlClose') {
|
|
1267
1282
|
return;
|
|
1268
1283
|
}
|
|
1269
1284
|
throw new Error('Cached command not found!');
|
|
1270
1285
|
}
|
|
1271
|
-
if (
|
|
1286
|
+
if (command.kind === 'component') {
|
|
1272
1287
|
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
1273
1288
|
throw new Error('Cached command is not a component command!');
|
|
1274
1289
|
}
|
|
1275
|
-
cached.componentRef
|
|
1276
|
-
}
|
|
1277
|
-
else if (isTextRenderCommand(command)) {
|
|
1278
|
-
if (cached.element?.parentElement) {
|
|
1279
|
-
this._renderer.removeChild(cached.element.parentElement, cached.element);
|
|
1280
|
-
}
|
|
1290
|
+
cached.componentRef.destroy();
|
|
1281
1291
|
}
|
|
1282
|
-
else if (
|
|
1283
|
-
if (cached.element
|
|
1284
|
-
this.
|
|
1292
|
+
else if (command.kind === 'text' || command.kind === 'htmlOpen') {
|
|
1293
|
+
if (cached.element.parentElement) {
|
|
1294
|
+
this.renderer.removeChild(cached.element.parentElement, cached.element);
|
|
1285
1295
|
}
|
|
1286
1296
|
}
|
|
1287
|
-
this.
|
|
1297
|
+
this.executedCommandsCache.delete(command.id);
|
|
1288
1298
|
}
|
|
1289
|
-
|
|
1299
|
+
getComponentRootNode(componentRef) {
|
|
1290
1300
|
return componentRef.hostView.rootNodes[0];
|
|
1291
1301
|
}
|
|
1292
|
-
|
|
1293
|
-
const
|
|
1294
|
-
const inputsByProp = new Map(inputMeta.map((i) => [i.propName, i.templateName]));
|
|
1295
|
-
for (const [key, value] of Object.entries(command[COMPONENT_RENDER_COMMAND_POSITION.INPUTS])) {
|
|
1296
|
-
const publicName = inputsByProp.get(key);
|
|
1297
|
-
if (publicName === undefined)
|
|
1298
|
-
continue;
|
|
1299
|
-
componentRef.setInput(publicName, value);
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
_findParent(command) {
|
|
1303
|
-
const hostElement = this._elementRef.nativeElement;
|
|
1304
|
-
const nestingLevel = command[RENDER_COMMAND_POSITION.NESTING_LEVEL];
|
|
1302
|
+
findParent(command) {
|
|
1303
|
+
const hostElement = this.elementRef.nativeElement;
|
|
1305
1304
|
let parentElement;
|
|
1306
|
-
if (nestingLevel === 0) {
|
|
1305
|
+
if (command.nestingLevel === 0) {
|
|
1307
1306
|
parentElement = hostElement;
|
|
1308
1307
|
}
|
|
1309
1308
|
else {
|
|
1310
|
-
// Reverse search all render commands beginning from the current one
|
|
1311
|
-
// The parent is
|
|
1312
|
-
// If the parent is not found, throw an error.
|
|
1309
|
+
// Reverse search all render commands beginning from the current one.
|
|
1310
|
+
// The parent is the closest preceding html open command one nesting level up.
|
|
1313
1311
|
const allCommands = this.renderCommands();
|
|
1314
|
-
const currentCommandIndex = command[RENDER_COMMAND_POSITION.INDEX];
|
|
1315
1312
|
let parentCommand = null;
|
|
1316
|
-
for (let i =
|
|
1313
|
+
for (let i = command.index - 1; i >= 0; i--) {
|
|
1317
1314
|
const cmd = allCommands[i];
|
|
1318
1315
|
if (!cmd) {
|
|
1319
1316
|
throw new Error('Command not found!');
|
|
1320
1317
|
}
|
|
1321
|
-
if (cmd
|
|
1318
|
+
if (cmd.nestingLevel === command.nestingLevel - 1 && cmd.kind === 'htmlOpen') {
|
|
1322
1319
|
parentCommand = cmd;
|
|
1323
1320
|
break;
|
|
1324
1321
|
}
|
|
@@ -1326,51 +1323,46 @@ class ContentfulRichTextRendererComponent {
|
|
|
1326
1323
|
if (!parentCommand) {
|
|
1327
1324
|
throw new Error('Parent command not found!');
|
|
1328
1325
|
}
|
|
1329
|
-
parentElement = this.
|
|
1326
|
+
parentElement = this.executedCommandsCache.get(parentCommand.id)?.element;
|
|
1330
1327
|
}
|
|
1331
1328
|
if (!parentElement) {
|
|
1332
1329
|
throw new Error('Parent element not found!');
|
|
1333
1330
|
}
|
|
1334
1331
|
return parentElement;
|
|
1335
1332
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
const
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
if (cached.command[RENDER_COMMAND_POSITION.DOM_POSITION] > domPosition &&
|
|
1347
|
-
cached.command[RENDER_COMMAND_POSITION.NESTING_LEVEL] === nestingLevel &&
|
|
1348
|
-
// Make sure the element does not find itself
|
|
1349
|
-
cached.command[RENDER_COMMAND_POSITION.TYPE] !== type) {
|
|
1333
|
+
findFollowingElement(command) {
|
|
1334
|
+
// Find the closest already rendered element at the same nesting level with a greater
|
|
1335
|
+
// domPosition, to insert before. Without one the element is appended to the parent.
|
|
1336
|
+
let nextElement;
|
|
1337
|
+
let nextDomPosition = Infinity;
|
|
1338
|
+
for (const cached of this.executedCommandsCache.values()) {
|
|
1339
|
+
if (cached.command.domPosition > command.domPosition &&
|
|
1340
|
+
cached.command.domPosition < nextDomPosition &&
|
|
1341
|
+
cached.command.nestingLevel === command.nestingLevel &&
|
|
1342
|
+
cached.command.id !== command.id) {
|
|
1350
1343
|
nextElement = cached.element;
|
|
1351
|
-
|
|
1344
|
+
nextDomPosition = cached.command.domPosition;
|
|
1352
1345
|
}
|
|
1353
1346
|
}
|
|
1354
1347
|
return nextElement;
|
|
1355
1348
|
}
|
|
1356
|
-
|
|
1349
|
+
renderInsertOrAppend(nodeToRender, parentElement, nextElement) {
|
|
1357
1350
|
if (nextElement) {
|
|
1358
|
-
this.
|
|
1351
|
+
this.renderer.insertBefore(parentElement, nodeToRender, nextElement);
|
|
1359
1352
|
}
|
|
1360
1353
|
else {
|
|
1361
|
-
this.
|
|
1354
|
+
this.renderer.appendChild(parentElement, nodeToRender);
|
|
1362
1355
|
}
|
|
1363
1356
|
}
|
|
1364
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
1365
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.
|
|
1357
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ContentfulRichTextRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1358
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.7", 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 }); }
|
|
1366
1359
|
}
|
|
1367
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
1360
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ContentfulRichTextRendererComponent, decorators: [{
|
|
1368
1361
|
type: Component,
|
|
1369
1362
|
args: [{
|
|
1370
1363
|
selector: 'et-contentful-rich-text-renderer',
|
|
1371
1364
|
template: ``,
|
|
1372
1365
|
encapsulation: ViewEncapsulation.None,
|
|
1373
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1374
1366
|
host: {
|
|
1375
1367
|
class: 'et-contentful-rich-text-renderer',
|
|
1376
1368
|
},
|
|
@@ -1390,11 +1382,9 @@ const provideContentfulConfig = (contentfulConfig = {}) => {
|
|
|
1390
1382
|
};
|
|
1391
1383
|
const isContentfulEntryType = (entry, type) => entry.sys.contentType.sys.id === type;
|
|
1392
1384
|
|
|
1393
|
-
// Burst cache
|
|
1394
|
-
|
|
1395
1385
|
/**
|
|
1396
1386
|
* Generated bundle index. Do not edit.
|
|
1397
1387
|
*/
|
|
1398
1388
|
|
|
1399
|
-
export {
|
|
1389
|
+
export { CONTENTFUL_CONFIG, ContentfulAudioComponent, ContentfulFileComponent, ContentfulImageComponent, ContentfulImports, ContentfulLinkComponent, ContentfulRichTextRendererComponent, ContentfulVideoComponent, ET_CONTENTFUL_ANY_ENTRY_CONTENT_TYPE_SYS_ID, GQL_FRAGMENT_CONTENTFUL_ASSET, createContentfulConfig, createContentfulIncludeMap, generateContentfulImageSources, generateDefaultContentfulImageSource, injectContentfulConfig, isContentfulEntryType, isContentfulGqlAsset, isRichTextRootNode, marksToClass, marksToTags, parseContentfulImageSize, provideContentfulConfig, translateContentfulNodeTypeToHtmlTag };
|
|
1400
1390
|
//# sourceMappingURL=ethlete-contentful.mjs.map
|