@ethlete/contentful 4.0.0-next.6 → 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 +27 -0
- package/README.md +23 -4
- package/fesm2022/ethlete-contentful.mjs +477 -481
- 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, Component, InjectionToken, inject, numberAttribute, booleanAttribute, isDevMode, ViewContainerRef, ElementRef, reflectComponentType } from '@angular/core';
|
|
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,8 +52,8 @@ 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>
|
|
@@ -66,7 +62,7 @@ class ContentfulAudioComponent {
|
|
|
66
62
|
}
|
|
67
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',
|
|
@@ -111,8 +107,8 @@ class ContentfulFileComponent {
|
|
|
111
107
|
}, /* @ts-ignore */
|
|
112
108
|
...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
113
109
|
}
|
|
114
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
115
|
-
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: `
|
|
116
112
|
@if (data(); as data) {
|
|
117
113
|
<a [href]="data.url" [ngClass]="fileClass()" class="underline" target="_blank">
|
|
118
114
|
{{ data.title }} ({{ data.size }} Bytes)
|
|
@@ -120,7 +116,7 @@ class ContentfulFileComponent {
|
|
|
120
116
|
}
|
|
121
117
|
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
122
118
|
}
|
|
123
|
-
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: [{
|
|
124
120
|
type: Component,
|
|
125
121
|
args: [{
|
|
126
122
|
selector: 'et-contentful-file',
|
|
@@ -189,13 +185,13 @@ const parseContentfulImageSize = (size) => {
|
|
|
189
185
|
height: height ? parseInt(height, 10) : null,
|
|
190
186
|
};
|
|
191
187
|
};
|
|
188
|
+
const SOURCE_TYPES = ['image/avif', 'image/webp', 'image/png', 'image/jpg'];
|
|
192
189
|
const generateContentfulImageSources = (data, srcsetSizes, backgroundColor, quality, focusArea, resizeBehavior) => {
|
|
193
190
|
const assetData = data;
|
|
194
191
|
if (!assetData) {
|
|
195
192
|
return [];
|
|
196
193
|
}
|
|
197
194
|
const sources = [];
|
|
198
|
-
const SOURCE_TYPES = ['image/avif', 'image/webp', 'image/png', 'image/jpg'];
|
|
199
195
|
for (const type of SOURCE_TYPES) {
|
|
200
196
|
const baseUrl = isContentfulGqlAsset(assetData) ? assetData.url : assetData.fields.file.url;
|
|
201
197
|
const sourceSets = [];
|
|
@@ -239,28 +235,20 @@ const generateContentfulImageSources = (data, srcsetSizes, backgroundColor, qual
|
|
|
239
235
|
|
|
240
236
|
class ContentfulImageComponent {
|
|
241
237
|
constructor() {
|
|
242
|
-
this.
|
|
238
|
+
this.contentfulConfig = inject(CONTENTFUL_CONFIG);
|
|
243
239
|
this.asset = input.required(/* @ts-ignore */
|
|
244
240
|
...(ngDevMode ? [{ debugName: "asset" }] : /* istanbul ignore next */ []));
|
|
245
241
|
this.backgroundColor = input(null, /* @ts-ignore */
|
|
246
242
|
...(ngDevMode ? [{ debugName: "backgroundColor" }] : /* istanbul ignore next */ []));
|
|
247
|
-
this.srcsetSizes = input(this.
|
|
243
|
+
this.srcsetSizes = input(this.contentfulConfig.imageOptions.srcsetSizes, /* @ts-ignore */
|
|
248
244
|
...(ngDevMode ? [{ debugName: "srcsetSizes" }] : /* istanbul ignore next */ []));
|
|
249
245
|
this.quality = input(null, { ...(ngDevMode ? { debugName: "quality" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
250
246
|
this.focusArea = input(null, /* @ts-ignore */
|
|
251
247
|
...(ngDevMode ? [{ debugName: "focusArea" }] : /* istanbul ignore next */ []));
|
|
252
248
|
this.resizeBehavior = input(null, /* @ts-ignore */
|
|
253
249
|
...(ngDevMode ? [{ debugName: "resizeBehavior" }] : /* istanbul ignore next */ []));
|
|
254
|
-
this.
|
|
255
|
-
this.
|
|
256
|
-
...(ngDevMode ? [{ debugName: "imgClass" }] : /* istanbul ignore next */ []));
|
|
257
|
-
this.figureClass = input(null, /* @ts-ignore */
|
|
258
|
-
...(ngDevMode ? [{ debugName: "figureClass" }] : /* istanbul ignore next */ []));
|
|
259
|
-
this.figcaptionClass = input(null, /* @ts-ignore */
|
|
260
|
-
...(ngDevMode ? [{ debugName: "figcaptionClass" }] : /* istanbul ignore next */ []));
|
|
261
|
-
this.pictureClass = input(null, /* @ts-ignore */
|
|
262
|
-
...(ngDevMode ? [{ debugName: "pictureClass" }] : /* istanbul ignore next */ []));
|
|
263
|
-
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) });
|
|
264
252
|
this.normalizedAsset = computed(() => {
|
|
265
253
|
const asset = this.asset();
|
|
266
254
|
if (isContentfulGqlAsset(asset)) {
|
|
@@ -281,7 +269,7 @@ class ContentfulImageComponent {
|
|
|
281
269
|
}
|
|
282
270
|
}, /* @ts-ignore */
|
|
283
271
|
...(ngDevMode ? [{ debugName: "normalizedAsset" }] : /* istanbul ignore next */ []));
|
|
284
|
-
this.
|
|
272
|
+
this.sourcesValue = computed(() => {
|
|
285
273
|
const asset = this.asset();
|
|
286
274
|
const backgroundColor = this.backgroundColor();
|
|
287
275
|
const srcsetSizes = this.srcsetSizes();
|
|
@@ -293,49 +281,41 @@ class ContentfulImageComponent {
|
|
|
293
281
|
}
|
|
294
282
|
return generateContentfulImageSources(asset, srcsetSizes, backgroundColor, quality, focusArea, resizeBehavior);
|
|
295
283
|
}, /* @ts-ignore */
|
|
296
|
-
...(ngDevMode ? [{ debugName: "
|
|
297
|
-
this.
|
|
284
|
+
...(ngDevMode ? [{ debugName: "sourcesValue" }] : /* istanbul ignore next */ []));
|
|
285
|
+
this.defaultSrcValue = computed(() => {
|
|
298
286
|
const asset = this.asset();
|
|
299
287
|
return asset ? generateDefaultContentfulImageSource(asset) : null;
|
|
300
288
|
}, /* @ts-ignore */
|
|
301
|
-
...(ngDevMode ? [{ debugName: "
|
|
289
|
+
...(ngDevMode ? [{ debugName: "defaultSrcValue" }] : /* istanbul ignore next */ []));
|
|
302
290
|
}
|
|
303
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
304
|
-
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: `
|
|
305
293
|
<et-picture
|
|
306
|
-
[
|
|
307
|
-
[
|
|
308
|
-
[
|
|
309
|
-
[pictureClass]="pictureClass()"
|
|
310
|
-
[figcaptionClass]="figcaptionClass()"
|
|
311
|
-
[defaultSrc]="_defaultSrc()"
|
|
312
|
-
[alt]="normalizedAsset().alt ?? null"
|
|
294
|
+
[priority]="priority()"
|
|
295
|
+
[defaultSrc]="defaultSrcValue()"
|
|
296
|
+
[alt]="normalizedAsset().alt ?? ''"
|
|
313
297
|
[figcaption]="normalizedAsset().figcaption ?? null"
|
|
314
298
|
[width]="normalizedAsset().width ?? null"
|
|
315
299
|
[height]="normalizedAsset().height ?? null"
|
|
316
300
|
[sizes]="sizes()"
|
|
317
|
-
[sources]="
|
|
301
|
+
[sources]="sourcesValue()"
|
|
318
302
|
/>
|
|
319
|
-
`, 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 }); }
|
|
320
304
|
}
|
|
321
|
-
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: [{
|
|
322
306
|
type: Component,
|
|
323
307
|
args: [{
|
|
324
308
|
selector: 'et-contentful-image',
|
|
325
309
|
template: `
|
|
326
310
|
<et-picture
|
|
327
|
-
[
|
|
328
|
-
[
|
|
329
|
-
[
|
|
330
|
-
[pictureClass]="pictureClass()"
|
|
331
|
-
[figcaptionClass]="figcaptionClass()"
|
|
332
|
-
[defaultSrc]="_defaultSrc()"
|
|
333
|
-
[alt]="normalizedAsset().alt ?? null"
|
|
311
|
+
[priority]="priority()"
|
|
312
|
+
[defaultSrc]="defaultSrcValue()"
|
|
313
|
+
[alt]="normalizedAsset().alt ?? ''"
|
|
334
314
|
[figcaption]="normalizedAsset().figcaption ?? null"
|
|
335
315
|
[width]="normalizedAsset().width ?? null"
|
|
336
316
|
[height]="normalizedAsset().height ?? null"
|
|
337
317
|
[sizes]="sizes()"
|
|
338
|
-
[sources]="
|
|
318
|
+
[sources]="sourcesValue()"
|
|
339
319
|
/>
|
|
340
320
|
`,
|
|
341
321
|
encapsulation: ViewEncapsulation.None,
|
|
@@ -344,7 +324,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
344
324
|
class: 'et-contentful-image',
|
|
345
325
|
},
|
|
346
326
|
}]
|
|
347
|
-
}], 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 }] }] } });
|
|
348
328
|
|
|
349
329
|
class ContentfulVideoComponent {
|
|
350
330
|
constructor() {
|
|
@@ -370,8 +350,8 @@ class ContentfulVideoComponent {
|
|
|
370
350
|
}, /* @ts-ignore */
|
|
371
351
|
...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
372
352
|
}
|
|
373
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
374
|
-
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: `
|
|
375
355
|
@if (data(); as data) {
|
|
376
356
|
<video [ngClass]="videoClass()" controls>
|
|
377
357
|
<source src="{{ data.url }}" type="{{ data.contentType }}" />
|
|
@@ -379,7 +359,7 @@ class ContentfulVideoComponent {
|
|
|
379
359
|
}
|
|
380
360
|
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
381
361
|
}
|
|
382
|
-
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: [{
|
|
383
363
|
type: Component,
|
|
384
364
|
args: [{
|
|
385
365
|
selector: 'et-contentful-video',
|
|
@@ -395,10 +375,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
395
375
|
}]
|
|
396
376
|
}], propDecorators: { asset: [{ type: i0.Input, args: [{ isSignal: true, alias: "asset", required: true }] }], videoClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "videoClass", required: false }] }] } });
|
|
397
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
|
+
};
|
|
398
393
|
const createContentfulConfig = (config = {}) => ({
|
|
399
394
|
// This default config must be inline and not saved inside a const for some weird webpack reason
|
|
400
395
|
...{
|
|
401
|
-
useTailwindClasses: false,
|
|
402
396
|
internalHosts: [],
|
|
403
397
|
components: {
|
|
404
398
|
audio: ContentfulAudioComponent,
|
|
@@ -416,6 +410,11 @@ const createContentfulConfig = (config = {}) => ({
|
|
|
416
410
|
},
|
|
417
411
|
...(config || {}),
|
|
418
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;
|
|
419
418
|
|
|
420
419
|
const getPrimaryDomain = (host) => {
|
|
421
420
|
const parts = host.split('.');
|
|
@@ -423,8 +422,8 @@ const getPrimaryDomain = (host) => {
|
|
|
423
422
|
};
|
|
424
423
|
class ContentfulLinkComponent {
|
|
425
424
|
constructor() {
|
|
426
|
-
this.
|
|
427
|
-
this.
|
|
425
|
+
this.document = inject(DOCUMENT);
|
|
426
|
+
this.config = injectContentfulConfig();
|
|
428
427
|
this.href = input.required(/* @ts-ignore */
|
|
429
428
|
...(ngDevMode ? [{ debugName: "href" }] : /* istanbul ignore next */ []));
|
|
430
429
|
this.text = input.required(/* @ts-ignore */
|
|
@@ -438,7 +437,7 @@ class ContentfulLinkComponent {
|
|
|
438
437
|
return false;
|
|
439
438
|
try {
|
|
440
439
|
const parsed = new URL(href.startsWith('//') ? `https:${href}` : href);
|
|
441
|
-
const internalHosts = [this.
|
|
440
|
+
const internalHosts = [this.document.location.host, ...this.config.internalHosts];
|
|
442
441
|
return !internalHosts.includes(parsed.host);
|
|
443
442
|
}
|
|
444
443
|
catch {
|
|
@@ -453,8 +452,8 @@ class ContentfulLinkComponent {
|
|
|
453
452
|
try {
|
|
454
453
|
const parsed = new URL(href.startsWith('//') ? `https:${href}` : href);
|
|
455
454
|
const internalPrimaryDomains = [
|
|
456
|
-
getPrimaryDomain(this.
|
|
457
|
-
...this.
|
|
455
|
+
getPrimaryDomain(this.document.location.host),
|
|
456
|
+
...this.config.internalHosts.map(getPrimaryDomain),
|
|
458
457
|
];
|
|
459
458
|
return !internalPrimaryDomains.includes(getPrimaryDomain(parsed.host));
|
|
460
459
|
}
|
|
@@ -484,8 +483,8 @@ class ContentfulLinkComponent {
|
|
|
484
483
|
}, /* @ts-ignore */
|
|
485
484
|
...(ngDevMode ? [{ debugName: "linkClass" }] : /* istanbul ignore next */ []));
|
|
486
485
|
}
|
|
487
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
488
|
-
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: `
|
|
489
488
|
@if (isExternal()) {
|
|
490
489
|
<a
|
|
491
490
|
[class]="linkClass()"
|
|
@@ -499,7 +498,7 @@ class ContentfulLinkComponent {
|
|
|
499
498
|
}
|
|
500
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 }); }
|
|
501
500
|
}
|
|
502
|
-
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: [{
|
|
503
502
|
type: Component,
|
|
504
503
|
args: [{
|
|
505
504
|
selector: 'et-contentful-link',
|
|
@@ -516,14 +515,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
516
515
|
<a [class]="linkClass()" [routerLink]="internalPath()">{{ text() }}</a>
|
|
517
516
|
}
|
|
518
517
|
`,
|
|
518
|
+
encapsulation: ViewEncapsulation.None,
|
|
519
|
+
imports: [RouterLink],
|
|
519
520
|
host: {
|
|
520
521
|
style: 'display: contents',
|
|
521
522
|
},
|
|
522
|
-
imports: [RouterLink],
|
|
523
|
-
encapsulation: ViewEncapsulation.None,
|
|
524
523
|
}]
|
|
525
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 }] }] } });
|
|
526
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
|
+
|
|
527
565
|
const RICH_TEXT_RENDERER_ERRORS = {
|
|
528
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.',
|
|
529
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".',
|
|
@@ -535,10 +573,17 @@ const RICH_TEXT_RENDERER_ERRORS = {
|
|
|
535
573
|
text_parent_not_found: 'The parent node is not found. This structure is not supported.',
|
|
536
574
|
text_parent_wrong_type: 'The parent node neither a html element nor a custom component. This structure is not supported.',
|
|
537
575
|
};
|
|
538
|
-
const RICH_TEXT_RENDERER_ERROR_CODES =
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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
|
+
};
|
|
542
587
|
const richTextRendererError = (code, data) => {
|
|
543
588
|
const message = `<et-contentful-rich-text-renderer>: ${RICH_TEXT_RENDERER_ERRORS[code]}`;
|
|
544
589
|
throw new RuntimeError(RICH_TEXT_RENDERER_ERROR_CODES[code], message, data);
|
|
@@ -549,160 +594,93 @@ const isRichTextRootNode = (node) => {
|
|
|
549
594
|
};
|
|
550
595
|
const translateContentfulNodeTypeToHtmlTag = (nodeType) => {
|
|
551
596
|
switch (nodeType) {
|
|
552
|
-
case
|
|
597
|
+
case CF_BLOCKS.HEADING_1:
|
|
553
598
|
return 'h1';
|
|
554
|
-
case
|
|
599
|
+
case CF_BLOCKS.HEADING_2:
|
|
555
600
|
return 'h2';
|
|
556
|
-
case
|
|
601
|
+
case CF_BLOCKS.HEADING_3:
|
|
557
602
|
return 'h3';
|
|
558
|
-
case
|
|
603
|
+
case CF_BLOCKS.HEADING_4:
|
|
559
604
|
return 'h4';
|
|
560
|
-
case
|
|
605
|
+
case CF_BLOCKS.HEADING_5:
|
|
561
606
|
return 'h5';
|
|
562
|
-
case
|
|
607
|
+
case CF_BLOCKS.HEADING_6:
|
|
563
608
|
return 'h6';
|
|
564
|
-
case
|
|
609
|
+
case CF_BLOCKS.PARAGRAPH:
|
|
565
610
|
return 'p';
|
|
566
|
-
case
|
|
611
|
+
case CF_BLOCKS.UL_LIST:
|
|
567
612
|
return 'ul';
|
|
568
|
-
case
|
|
613
|
+
case CF_BLOCKS.OL_LIST:
|
|
569
614
|
return 'ol';
|
|
570
|
-
case
|
|
615
|
+
case CF_BLOCKS.LIST_ITEM:
|
|
571
616
|
return 'li';
|
|
572
|
-
case
|
|
617
|
+
case CF_BLOCKS.HR:
|
|
573
618
|
return 'hr';
|
|
574
|
-
case
|
|
619
|
+
case CF_BLOCKS.QUOTE:
|
|
575
620
|
return 'blockquote';
|
|
576
|
-
case
|
|
621
|
+
case CF_BLOCKS.TABLE:
|
|
577
622
|
return 'table';
|
|
578
|
-
case
|
|
623
|
+
case CF_BLOCKS.TABLE_ROW:
|
|
579
624
|
return 'tr';
|
|
580
|
-
case
|
|
625
|
+
case CF_BLOCKS.TABLE_CELL:
|
|
581
626
|
return 'td';
|
|
582
|
-
case
|
|
627
|
+
case CF_BLOCKS.TABLE_HEADER_CELL:
|
|
583
628
|
return 'th';
|
|
584
|
-
case
|
|
629
|
+
case CF_BLOCKS.EMBEDDED_ASSET:
|
|
585
630
|
return 'div';
|
|
586
|
-
case
|
|
631
|
+
case CF_INLINES.ENTRY_HYPERLINK:
|
|
587
632
|
return 'a';
|
|
588
|
-
case
|
|
633
|
+
case CF_INLINES.ASSET_HYPERLINK:
|
|
589
634
|
return 'a';
|
|
590
635
|
case 'text':
|
|
591
636
|
return 'span';
|
|
592
637
|
// Will be ignored by the renderer
|
|
593
638
|
case 'document':
|
|
594
639
|
return 'div';
|
|
595
|
-
case
|
|
640
|
+
case CF_BLOCKS.EMBEDDED_ENTRY:
|
|
596
641
|
return 'div';
|
|
597
|
-
case
|
|
642
|
+
case CF_INLINES.EMBEDDED_ENTRY:
|
|
598
643
|
return 'div';
|
|
599
644
|
default:
|
|
600
645
|
return 'div';
|
|
601
646
|
}
|
|
602
647
|
};
|
|
603
648
|
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
NESTING_LEVEL: 1,
|
|
613
|
-
DOM_POSITION: 2,
|
|
614
|
-
INDEX: 3,
|
|
615
|
-
};
|
|
616
|
-
const HTML_OPEN_RENDER_COMMAND_POSITION = {
|
|
617
|
-
ATTRIBUTES: 4,
|
|
618
|
-
TAG_NAME: 5,
|
|
619
|
-
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',
|
|
620
657
|
};
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
TEXT: 5,
|
|
628
|
-
TEXT_ID: 6,
|
|
629
|
-
};
|
|
630
|
-
const COMPONENT_RENDER_COMMAND_POSITION = {
|
|
631
|
-
ATTRIBUTES: 4,
|
|
632
|
-
COMPONENT: 5,
|
|
633
|
-
INPUTS: 6,
|
|
634
|
-
COMPONENT_ID: 7,
|
|
635
|
-
};
|
|
636
|
-
const RENDER_INSTRUCTION_POSITION = {
|
|
637
|
-
TYPE: 0,
|
|
638
|
-
COMMAND: 1,
|
|
639
|
-
};
|
|
640
|
-
const RENDER_INSTRUCTION_TYPE = {
|
|
641
|
-
CREATE: 0,
|
|
642
|
-
UPDATE: 1,
|
|
643
|
-
DELETE: 2,
|
|
644
|
-
MOVE: 3,
|
|
645
|
-
};
|
|
646
|
-
const MARK_TAILWIND_MAP = {
|
|
647
|
-
bold: 'font-bold',
|
|
648
|
-
italic: 'italic',
|
|
649
|
-
underline: 'underline',
|
|
650
|
-
code: 'font-mono',
|
|
651
|
-
};
|
|
652
|
-
const marksToClass = (marks) => {
|
|
653
|
-
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 = [];
|
|
654
664
|
for (const mark of marks) {
|
|
655
|
-
|
|
656
|
-
if (!
|
|
657
|
-
|
|
658
|
-
|
|
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;
|
|
659
671
|
}
|
|
660
|
-
|
|
661
|
-
}
|
|
662
|
-
return classes.join(' ');
|
|
663
|
-
};
|
|
664
|
-
const isHtmlOpenRenderCommand = (command) => {
|
|
665
|
-
return command[RENDER_COMMAND_POSITION.TYPE] === RENDER_COMMAND_TYPE.HTML_OPEN;
|
|
666
|
-
};
|
|
667
|
-
const isHtmlCloseRenderCommand = (command) => {
|
|
668
|
-
return command[RENDER_COMMAND_POSITION.TYPE] === RENDER_COMMAND_TYPE.HTML_CLOSE;
|
|
669
|
-
};
|
|
670
|
-
const isTextRenderCommand = (command) => {
|
|
671
|
-
return command[RENDER_COMMAND_POSITION.TYPE] === RENDER_COMMAND_TYPE.TEXT;
|
|
672
|
-
};
|
|
673
|
-
const isComponentRenderCommand = (command) => {
|
|
674
|
-
return command[RENDER_COMMAND_POSITION.TYPE] === RENDER_COMMAND_TYPE.COMPONENT;
|
|
675
|
-
};
|
|
676
|
-
const getRenderCommandId = (command) => {
|
|
677
|
-
if (isHtmlOpenRenderCommand(command)) {
|
|
678
|
-
return command[HTML_OPEN_RENDER_COMMAND_POSITION.ELEMENT_ID];
|
|
679
|
-
}
|
|
680
|
-
else if (isHtmlCloseRenderCommand(command)) {
|
|
681
|
-
return command[HTML_CLOSE_RENDER_COMMAND_POSITION.ELEMENT_ID];
|
|
682
|
-
}
|
|
683
|
-
else if (isTextRenderCommand(command)) {
|
|
684
|
-
return command[TEXT_RENDER_COMMAND_POSITION.TEXT_ID];
|
|
685
|
-
}
|
|
686
|
-
else {
|
|
687
|
-
return command[COMPONENT_RENDER_COMMAND_POSITION.COMPONENT_ID];
|
|
672
|
+
tags.push(tag);
|
|
688
673
|
}
|
|
674
|
+
return tags;
|
|
689
675
|
};
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
LINK: '$$$_et-link',
|
|
697
|
-
};
|
|
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';
|
|
698
682
|
const isExecutedComponentCommandCacheItem = (cache) => {
|
|
699
|
-
return
|
|
700
|
-
};
|
|
701
|
-
const isExecutedHtmlOrTextCommandCacheItem = (cache) => {
|
|
702
|
-
return isHtmlCloseRenderCommand(cache.command) || isHtmlOpenRenderCommand(cache.command);
|
|
703
|
-
};
|
|
704
|
-
const isExecutedTextCommandCacheItem = (cache) => {
|
|
705
|
-
return isTextRenderCommand(cache.command);
|
|
683
|
+
return cache.command.kind === 'component';
|
|
706
684
|
};
|
|
707
685
|
const ET_CONTENTFUL_ANY_ENTRY_CONTENT_TYPE_SYS_ID = '$$$_et-contentful-any-entry-content-type-sys-id';
|
|
708
686
|
/**
|
|
@@ -758,17 +736,10 @@ const createContentfulIncludeMap = (config) => {
|
|
|
758
736
|
};
|
|
759
737
|
class ContentfulRichTextRendererComponent {
|
|
760
738
|
constructor() {
|
|
761
|
-
this.
|
|
762
|
-
this.
|
|
763
|
-
this.
|
|
764
|
-
this.
|
|
765
|
-
this._config = inject(CONTENTFUL_CONFIG, { optional: true }) ?? createContentfulConfig();
|
|
766
|
-
/**
|
|
767
|
-
* A cache for all executed commands that are not deleted.
|
|
768
|
-
* This is used to keep track of all rendered elements and components.
|
|
769
|
-
* The key is the render command element ID.
|
|
770
|
-
*/
|
|
771
|
-
this._executedCommandsCache = new Map();
|
|
739
|
+
this.viewContainerRef = inject(ViewContainerRef);
|
|
740
|
+
this.renderer = injectRenderer();
|
|
741
|
+
this.elementRef = inject(ElementRef);
|
|
742
|
+
this.config = injectContentfulConfig();
|
|
772
743
|
/**
|
|
773
744
|
* The contentful response gotten via their REST api.
|
|
774
745
|
* @see https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/entries/entries-collection
|
|
@@ -781,6 +752,12 @@ class ContentfulRichTextRendererComponent {
|
|
|
781
752
|
*/
|
|
782
753
|
this.richTextPath = input.required(/* @ts-ignore */
|
|
783
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();
|
|
784
761
|
/**
|
|
785
762
|
* A map of all includes in the contentful response.
|
|
786
763
|
* This is useful for looking up assets and entries that are referenced in the rich text without having to use loops.
|
|
@@ -816,93 +793,116 @@ class ContentfulRichTextRendererComponent {
|
|
|
816
793
|
if (!richTextData) {
|
|
817
794
|
return [];
|
|
818
795
|
}
|
|
819
|
-
return this.
|
|
796
|
+
return this.createRenderCommands(richTextData);
|
|
820
797
|
}, /* @ts-ignore */
|
|
821
798
|
...(ngDevMode ? [{ debugName: "renderCommands" }] : /* istanbul ignore next */ []));
|
|
822
|
-
this.renderCommandHistory =
|
|
799
|
+
this.renderCommandHistory = linkedSignal({ ...(ngDevMode ? { debugName: "renderCommandHistory" } : /* istanbul ignore next */ {}), source: this.renderCommands,
|
|
800
|
+
computation: (commands, previous) => [previous?.source ?? [], commands] });
|
|
823
801
|
this.previousRenderCommandMap = computed(() => {
|
|
824
802
|
const [prevCommands] = this.renderCommandHistory();
|
|
825
803
|
const map = new Map();
|
|
826
804
|
for (const command of prevCommands) {
|
|
827
|
-
map.set(
|
|
805
|
+
map.set(command.id, command);
|
|
828
806
|
}
|
|
829
807
|
return map;
|
|
830
808
|
}, /* @ts-ignore */
|
|
831
809
|
...(ngDevMode ? [{ debugName: "previousRenderCommandMap" }] : /* istanbul ignore next */ []));
|
|
832
810
|
this.renderInstructions = computed(() => {
|
|
833
811
|
const commands = this.renderCommands();
|
|
834
|
-
const previousRenderCommandMap =
|
|
812
|
+
const previousRenderCommandMap = this.previousRenderCommandMap();
|
|
835
813
|
const instructions = [];
|
|
836
|
-
//
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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;
|
|
842
828
|
}
|
|
843
829
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
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;
|
|
855
840
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
command: newCommand,
|
|
860
|
-
});
|
|
861
|
-
}
|
|
862
|
-
lastComponentIndexes.sort((a, b) => a.newIndex - b.newIndex);
|
|
863
|
-
// check if the prevIndex are in order ascending
|
|
864
|
-
// for indexes that are not in order, create a move instruction
|
|
865
|
-
// for indexes that are in order, create an update instruction
|
|
866
|
-
let moveCheckIndex = 0;
|
|
867
|
-
while (moveCheckIndex < lastComponentIndexes.length - 1) {
|
|
868
|
-
const moveCurr = lastComponentIndexes[moveCheckIndex];
|
|
869
|
-
const moveNext = lastComponentIndexes[moveCheckIndex + 1];
|
|
870
|
-
if (!moveCurr || !moveNext) {
|
|
871
|
-
break;
|
|
841
|
+
const previous = previousRenderCommandMap.get(command.id);
|
|
842
|
+
if (!previous || previous.kind !== command.kind) {
|
|
843
|
+
continue;
|
|
872
844
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
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;
|
|
876
848
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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 });
|
|
880
879
|
}
|
|
881
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.
|
|
882
883
|
for (const command of commands) {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
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 });
|
|
890
892
|
}
|
|
891
893
|
else {
|
|
892
|
-
|
|
893
|
-
instructions.push([RENDER_INSTRUCTION_TYPE.CREATE, command]);
|
|
894
|
+
instructions.push({ type: 'update', command });
|
|
894
895
|
}
|
|
895
896
|
}
|
|
896
897
|
return instructions;
|
|
897
898
|
}, /* @ts-ignore */
|
|
898
899
|
...(ngDevMode ? [{ debugName: "renderInstructions" }] : /* istanbul ignore next */ []));
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
this.
|
|
902
|
-
})
|
|
903
|
-
.subscribe();
|
|
900
|
+
effect(() => {
|
|
901
|
+
const instructions = this.renderInstructions();
|
|
902
|
+
untracked(() => this.execInstructions(instructions));
|
|
903
|
+
});
|
|
904
904
|
}
|
|
905
|
-
|
|
905
|
+
createRenderCommands(richTextData) {
|
|
906
906
|
/** List of all render commands */
|
|
907
907
|
const rootCommands = [];
|
|
908
908
|
/** Counter for generating unique html element IDs. */
|
|
@@ -920,32 +920,26 @@ class ContentfulRichTextRendererComponent {
|
|
|
920
920
|
const traverse = (node) => {
|
|
921
921
|
switch (node.nodeType) {
|
|
922
922
|
case 'text': {
|
|
923
|
-
// Render media
|
|
924
|
-
const type = RENDER_COMMAND_TYPE.TEXT;
|
|
925
923
|
const text = node.value;
|
|
926
924
|
if (!text)
|
|
927
925
|
break;
|
|
928
926
|
const attributes = {
|
|
929
927
|
class: 'et-contentful-rich-text-default-element et-contentful-rich-text-default-span',
|
|
930
928
|
};
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
const command = [type, nestingLevel, domPosition, commandIndex++, attributes, text, id];
|
|
942
|
-
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
|
+
});
|
|
943
939
|
domPosition++;
|
|
944
940
|
break;
|
|
945
941
|
}
|
|
946
|
-
case
|
|
947
|
-
// Render media
|
|
948
|
-
const type = RENDER_COMMAND_TYPE.COMPONENT;
|
|
942
|
+
case CF_BLOCKS.EMBEDDED_ASSET: {
|
|
949
943
|
const assetId = node.data['target']?.sys?.id;
|
|
950
944
|
if (!assetId) {
|
|
951
945
|
throw richTextRendererError('asset_id_not_found', { node });
|
|
@@ -955,7 +949,7 @@ class ContentfulRichTextRendererComponent {
|
|
|
955
949
|
throw richTextRendererError('asset_not_found', { assetId, node });
|
|
956
950
|
}
|
|
957
951
|
const contentType = asset.fields.file.contentType;
|
|
958
|
-
const assetComponents = this.
|
|
952
|
+
const assetComponents = this.config.components;
|
|
959
953
|
// Every property inside the asset will be null if no file was provided for a translation.
|
|
960
954
|
// In this case, we can assume that the asset is missing due to user error.
|
|
961
955
|
const isMissing = !contentType && !asset.fields.file.url;
|
|
@@ -975,35 +969,29 @@ class ContentfulRichTextRendererComponent {
|
|
|
975
969
|
: isAudio
|
|
976
970
|
? assetComponents.audio
|
|
977
971
|
: assetComponents.file;
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
asset,
|
|
991
|
-
};
|
|
992
|
-
const command = [
|
|
993
|
-
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',
|
|
994
984
|
nestingLevel,
|
|
995
985
|
domPosition,
|
|
996
|
-
commandIndex++,
|
|
997
|
-
attributes,
|
|
986
|
+
index: commandIndex++,
|
|
998
987
|
component,
|
|
999
|
-
inputs,
|
|
988
|
+
inputs: { asset },
|
|
1000
989
|
id,
|
|
1001
|
-
|
|
1002
|
-
rootCommands.push(command);
|
|
990
|
+
});
|
|
1003
991
|
domPosition++;
|
|
1004
992
|
break;
|
|
1005
993
|
}
|
|
1006
|
-
case
|
|
994
|
+
case CF_INLINES.HYPERLINK: {
|
|
1007
995
|
const uri = node.data['uri'] ?? '';
|
|
1008
996
|
// Collect all text and marks from children (hyperlink children are text nodes)
|
|
1009
997
|
let linkText = '';
|
|
@@ -1014,28 +1002,67 @@ class ContentfulRichTextRendererComponent {
|
|
|
1014
1002
|
linkMarks.push(...child.marks);
|
|
1015
1003
|
}
|
|
1016
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
|
+
}
|
|
1017
1048
|
const textClass = linkMarks.length ? marksToClass(linkMarks) : '';
|
|
1018
|
-
let linkComponentId = componentIdMap.get(
|
|
1019
|
-
const linkId =
|
|
1020
|
-
componentIdMap.set(
|
|
1021
|
-
|
|
1022
|
-
|
|
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',
|
|
1023
1054
|
nestingLevel,
|
|
1024
1055
|
domPosition,
|
|
1025
|
-
commandIndex++,
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
];
|
|
1031
|
-
rootCommands.push(linkCommand);
|
|
1056
|
+
index: commandIndex++,
|
|
1057
|
+
component: linkComponent,
|
|
1058
|
+
inputs: { href: uri, text: linkText, textClass },
|
|
1059
|
+
id: linkId,
|
|
1060
|
+
});
|
|
1032
1061
|
domPosition++;
|
|
1033
1062
|
break;
|
|
1034
1063
|
}
|
|
1035
|
-
case
|
|
1036
|
-
case
|
|
1037
|
-
// Render component
|
|
1038
|
-
const type = RENDER_COMMAND_TYPE.COMPONENT;
|
|
1064
|
+
case CF_BLOCKS.EMBEDDED_ENTRY:
|
|
1065
|
+
case CF_INLINES.EMBEDDED_ENTRY: {
|
|
1039
1066
|
const entryId = node.data['target']?.sys?.id;
|
|
1040
1067
|
if (!entryId) {
|
|
1041
1068
|
throw richTextRendererError('entry_id_not_found', { node });
|
|
@@ -1045,47 +1072,49 @@ class ContentfulRichTextRendererComponent {
|
|
|
1045
1072
|
throw richTextRendererError('entry_not_found', { entryId, node });
|
|
1046
1073
|
}
|
|
1047
1074
|
const componentType = entry.sys.contentType.sys.id;
|
|
1048
|
-
const component = this.
|
|
1075
|
+
const component = this.config.customComponents[componentType];
|
|
1049
1076
|
if (!component) {
|
|
1050
1077
|
throw richTextRendererError('custom_component_not_found', {
|
|
1051
1078
|
componentType,
|
|
1052
|
-
customComponents: this.
|
|
1079
|
+
customComponents: this.config.customComponents,
|
|
1053
1080
|
entry,
|
|
1054
1081
|
});
|
|
1055
1082
|
}
|
|
1056
|
-
const
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
metadata: entry.metadata,
|
|
1063
|
-
sys: entry.sys,
|
|
1064
|
-
includes: this.contentIncludesMap(),
|
|
1065
|
-
};
|
|
1066
|
-
const command = [
|
|
1067
|
-
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',
|
|
1068
1089
|
nestingLevel,
|
|
1069
1090
|
domPosition,
|
|
1070
|
-
commandIndex++,
|
|
1071
|
-
attributes,
|
|
1091
|
+
index: commandIndex++,
|
|
1072
1092
|
component,
|
|
1073
|
-
inputs
|
|
1093
|
+
inputs: {
|
|
1094
|
+
fields: entry.fields,
|
|
1095
|
+
metadata: entry.metadata,
|
|
1096
|
+
sys: entry.sys,
|
|
1097
|
+
includes: this.contentIncludesMap(),
|
|
1098
|
+
},
|
|
1074
1099
|
id,
|
|
1075
|
-
|
|
1076
|
-
rootCommands.push(command);
|
|
1100
|
+
});
|
|
1077
1101
|
domPosition++;
|
|
1078
1102
|
break;
|
|
1079
1103
|
}
|
|
1080
1104
|
default: {
|
|
1081
|
-
const type = RENDER_COMMAND_TYPE.HTML_OPEN;
|
|
1082
1105
|
const tag = translateContentfulNodeTypeToHtmlTag(node.nodeType);
|
|
1083
1106
|
const attributes = {
|
|
1084
1107
|
class: `et-contentful-rich-text-default-element et-contentful-rich-text-default-${tag}`,
|
|
1085
1108
|
};
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1109
|
+
rootCommands.push({
|
|
1110
|
+
kind: 'htmlOpen',
|
|
1111
|
+
nestingLevel,
|
|
1112
|
+
domPosition,
|
|
1113
|
+
index: commandIndex++,
|
|
1114
|
+
attributes,
|
|
1115
|
+
tagName: tag,
|
|
1116
|
+
id: 'e-o' + elementOpenId++,
|
|
1117
|
+
});
|
|
1089
1118
|
const domPositionAtThisLevel = domPosition;
|
|
1090
1119
|
// Normal html elements can have children
|
|
1091
1120
|
for (const child of node.content) {
|
|
@@ -1096,25 +1125,21 @@ class ContentfulRichTextRendererComponent {
|
|
|
1096
1125
|
domPosition = domPositionAtThisLevel;
|
|
1097
1126
|
}
|
|
1098
1127
|
const lastCommand = rootCommands[rootCommands.length - 1];
|
|
1099
|
-
if (lastCommand?.
|
|
1100
|
-
lastCommand[HTML_OPEN_RENDER_COMMAND_POSITION.TAG_NAME] !== 'td' &&
|
|
1101
|
-
lastCommand[HTML_OPEN_RENDER_COMMAND_POSITION.TAG_NAME] !== 'hr') {
|
|
1128
|
+
if (lastCommand?.kind === 'htmlOpen' && lastCommand.tagName !== 'td' && lastCommand.tagName !== 'hr') {
|
|
1102
1129
|
// If the last command is an open command, we can remove it since it's empty
|
|
1103
1130
|
rootCommands.pop();
|
|
1104
1131
|
elementOpenId--;
|
|
1105
1132
|
commandIndex--;
|
|
1106
1133
|
}
|
|
1107
1134
|
else {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
RENDER_COMMAND_TYPE.HTML_CLOSE,
|
|
1135
|
+
rootCommands.push({
|
|
1136
|
+
kind: 'htmlClose',
|
|
1111
1137
|
nestingLevel,
|
|
1112
1138
|
domPosition,
|
|
1113
|
-
commandIndex++,
|
|
1114
|
-
tag,
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
rootCommands.push(closeCommand);
|
|
1139
|
+
index: commandIndex++,
|
|
1140
|
+
tagName: tag,
|
|
1141
|
+
id: 'e-c' + elementCloseId++,
|
|
1142
|
+
});
|
|
1118
1143
|
domPosition++;
|
|
1119
1144
|
}
|
|
1120
1145
|
break;
|
|
@@ -1126,194 +1151,171 @@ class ContentfulRichTextRendererComponent {
|
|
|
1126
1151
|
}
|
|
1127
1152
|
return rootCommands;
|
|
1128
1153
|
}
|
|
1129
|
-
|
|
1154
|
+
execInstructions(instructions) {
|
|
1130
1155
|
for (const instruction of instructions) {
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
case RENDER_INSTRUCTION_TYPE.CREATE:
|
|
1135
|
-
this._runCreateInstruction(command);
|
|
1156
|
+
switch (instruction.type) {
|
|
1157
|
+
case 'create':
|
|
1158
|
+
this.runCreateInstruction(instruction.command);
|
|
1136
1159
|
break;
|
|
1137
|
-
case
|
|
1138
|
-
this.
|
|
1160
|
+
case 'update':
|
|
1161
|
+
this.runUpdateInstruction(instruction.command);
|
|
1139
1162
|
break;
|
|
1140
|
-
case
|
|
1141
|
-
this.
|
|
1163
|
+
case 'move':
|
|
1164
|
+
this.runMoveInstruction(instruction.command);
|
|
1142
1165
|
break;
|
|
1143
|
-
case
|
|
1144
|
-
this.
|
|
1166
|
+
case 'delete':
|
|
1167
|
+
this.runDeleteInstruction(instruction.command);
|
|
1145
1168
|
break;
|
|
1146
1169
|
}
|
|
1147
1170
|
}
|
|
1148
1171
|
}
|
|
1149
|
-
|
|
1150
|
-
const
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
const
|
|
1157
|
-
|
|
1158
|
-
|
|
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, {
|
|
1159
1187
|
command,
|
|
1160
1188
|
componentRef,
|
|
1189
|
+
inputs,
|
|
1161
1190
|
element: rootNode,
|
|
1162
1191
|
});
|
|
1163
1192
|
}
|
|
1164
|
-
else if (
|
|
1165
|
-
const
|
|
1166
|
-
const
|
|
1167
|
-
const
|
|
1168
|
-
|
|
1169
|
-
const textNodes = [];
|
|
1170
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
1171
|
-
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);
|
|
1172
1198
|
}
|
|
1173
|
-
if (text.startsWith('\n')) {
|
|
1174
|
-
const brNode = this.
|
|
1175
|
-
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;
|
|
1176
1209
|
}
|
|
1177
1210
|
for (const [textPartIndex, textPart] of textSplitInLineBreaks.entries()) {
|
|
1178
1211
|
if (textPartIndex > 0) {
|
|
1179
|
-
const brNode = this.
|
|
1180
|
-
this.
|
|
1212
|
+
const brNode = this.renderer.createElement('br');
|
|
1213
|
+
this.renderer.appendChild(textContainer, brNode);
|
|
1181
1214
|
}
|
|
1182
|
-
const textNode = this.
|
|
1183
|
-
this.
|
|
1184
|
-
textNodes.push(textNode);
|
|
1215
|
+
const textNode = this.renderer.createText(textPart);
|
|
1216
|
+
this.renderer.appendChild(textContainer, textNode);
|
|
1185
1217
|
}
|
|
1186
|
-
this.
|
|
1187
|
-
this.
|
|
1218
|
+
this.renderInsertOrAppend(span, parentElement, nextElement);
|
|
1219
|
+
this.executedCommandsCache.set(command.id, {
|
|
1188
1220
|
command,
|
|
1189
1221
|
element: span,
|
|
1190
|
-
textNode: textNodes,
|
|
1191
1222
|
});
|
|
1192
1223
|
}
|
|
1193
|
-
else if (
|
|
1194
|
-
const
|
|
1195
|
-
const
|
|
1196
|
-
|
|
1197
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
1198
|
-
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);
|
|
1199
1228
|
}
|
|
1200
|
-
this.
|
|
1201
|
-
this.
|
|
1229
|
+
this.renderInsertOrAppend(element, parentElement, nextElement);
|
|
1230
|
+
this.executedCommandsCache.set(command.id, {
|
|
1202
1231
|
command,
|
|
1203
1232
|
element,
|
|
1204
1233
|
});
|
|
1205
1234
|
}
|
|
1206
1235
|
}
|
|
1207
|
-
|
|
1208
|
-
const
|
|
1209
|
-
const cached = this._executedCommandsCache.get(id);
|
|
1236
|
+
runUpdateInstruction(command) {
|
|
1237
|
+
const cached = this.executedCommandsCache.get(command.id);
|
|
1210
1238
|
if (!cached) {
|
|
1211
1239
|
throw new Error('Cached command not found!');
|
|
1212
1240
|
}
|
|
1213
|
-
if (
|
|
1241
|
+
if (command.kind === 'component') {
|
|
1214
1242
|
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
1215
1243
|
throw new Error('Cached command is not a component command!');
|
|
1216
1244
|
}
|
|
1217
|
-
|
|
1218
|
-
if (!componentRef) {
|
|
1219
|
-
throw new Error('Component ref not found!');
|
|
1220
|
-
}
|
|
1221
|
-
this._updateComponentInputs(command, componentRef);
|
|
1245
|
+
cached.inputs.set(command.inputs);
|
|
1222
1246
|
}
|
|
1223
|
-
|
|
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, {
|
|
1224
1250
|
...cached,
|
|
1225
|
-
command
|
|
1251
|
+
command,
|
|
1226
1252
|
});
|
|
1227
1253
|
}
|
|
1228
|
-
|
|
1229
|
-
const
|
|
1230
|
-
const cached = this._executedCommandsCache.get(id);
|
|
1254
|
+
runMoveInstruction(command) {
|
|
1255
|
+
const cached = this.executedCommandsCache.get(command.id);
|
|
1231
1256
|
if (!cached) {
|
|
1232
1257
|
throw new Error('Cached command not found!');
|
|
1233
1258
|
}
|
|
1234
|
-
if (
|
|
1259
|
+
if (command.kind === 'component') {
|
|
1235
1260
|
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
1236
1261
|
throw new Error('Cached command is not a component command!');
|
|
1237
1262
|
}
|
|
1238
|
-
const componentRef = cached.componentRef;
|
|
1239
|
-
if (!componentRef) {
|
|
1240
|
-
throw new Error('Component ref not found!');
|
|
1241
|
-
}
|
|
1242
1263
|
const rootNode = cached.element;
|
|
1243
1264
|
const oldParentElement = cached.element.parentElement;
|
|
1244
1265
|
if (oldParentElement) {
|
|
1245
|
-
this.
|
|
1266
|
+
this.renderer.removeChild(oldParentElement, rootNode);
|
|
1246
1267
|
}
|
|
1247
|
-
const newParentElement = this.
|
|
1248
|
-
const nextElement = this.
|
|
1249
|
-
this.
|
|
1250
|
-
|
|
1251
|
-
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, {
|
|
1252
1273
|
...cached,
|
|
1253
|
-
command
|
|
1274
|
+
command,
|
|
1254
1275
|
});
|
|
1255
1276
|
}
|
|
1256
1277
|
}
|
|
1257
|
-
|
|
1258
|
-
const
|
|
1259
|
-
const cached = this._executedCommandsCache.get(id);
|
|
1278
|
+
runDeleteInstruction(command) {
|
|
1279
|
+
const cached = this.executedCommandsCache.get(command.id);
|
|
1260
1280
|
if (!cached) {
|
|
1261
|
-
if (
|
|
1281
|
+
if (command.kind === 'htmlClose') {
|
|
1262
1282
|
return;
|
|
1263
1283
|
}
|
|
1264
1284
|
throw new Error('Cached command not found!');
|
|
1265
1285
|
}
|
|
1266
|
-
if (
|
|
1286
|
+
if (command.kind === 'component') {
|
|
1267
1287
|
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
1268
1288
|
throw new Error('Cached command is not a component command!');
|
|
1269
1289
|
}
|
|
1270
|
-
cached.componentRef
|
|
1271
|
-
}
|
|
1272
|
-
else if (isTextRenderCommand(command)) {
|
|
1273
|
-
if (cached.element?.parentElement) {
|
|
1274
|
-
this._renderer.removeChild(cached.element.parentElement, cached.element);
|
|
1275
|
-
}
|
|
1290
|
+
cached.componentRef.destroy();
|
|
1276
1291
|
}
|
|
1277
|
-
else if (
|
|
1278
|
-
if (cached.element
|
|
1279
|
-
this.
|
|
1292
|
+
else if (command.kind === 'text' || command.kind === 'htmlOpen') {
|
|
1293
|
+
if (cached.element.parentElement) {
|
|
1294
|
+
this.renderer.removeChild(cached.element.parentElement, cached.element);
|
|
1280
1295
|
}
|
|
1281
1296
|
}
|
|
1282
|
-
this.
|
|
1297
|
+
this.executedCommandsCache.delete(command.id);
|
|
1283
1298
|
}
|
|
1284
|
-
|
|
1299
|
+
getComponentRootNode(componentRef) {
|
|
1285
1300
|
return componentRef.hostView.rootNodes[0];
|
|
1286
1301
|
}
|
|
1287
|
-
|
|
1288
|
-
const
|
|
1289
|
-
const inputsByProp = new Map(inputMeta.map((i) => [i.propName, i.templateName]));
|
|
1290
|
-
for (const [key, value] of Object.entries(command[COMPONENT_RENDER_COMMAND_POSITION.INPUTS])) {
|
|
1291
|
-
const publicName = inputsByProp.get(key);
|
|
1292
|
-
if (publicName === undefined)
|
|
1293
|
-
continue;
|
|
1294
|
-
componentRef.setInput(publicName, value);
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
_findParent(command) {
|
|
1298
|
-
const hostElement = this._elementRef.nativeElement;
|
|
1299
|
-
const nestingLevel = command[RENDER_COMMAND_POSITION.NESTING_LEVEL];
|
|
1302
|
+
findParent(command) {
|
|
1303
|
+
const hostElement = this.elementRef.nativeElement;
|
|
1300
1304
|
let parentElement;
|
|
1301
|
-
if (nestingLevel === 0) {
|
|
1305
|
+
if (command.nestingLevel === 0) {
|
|
1302
1306
|
parentElement = hostElement;
|
|
1303
1307
|
}
|
|
1304
1308
|
else {
|
|
1305
|
-
// Reverse search all render commands beginning from the current one
|
|
1306
|
-
// The parent is
|
|
1307
|
-
// 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.
|
|
1308
1311
|
const allCommands = this.renderCommands();
|
|
1309
|
-
const currentCommandIndex = command[RENDER_COMMAND_POSITION.INDEX];
|
|
1310
1312
|
let parentCommand = null;
|
|
1311
|
-
for (let i =
|
|
1313
|
+
for (let i = command.index - 1; i >= 0; i--) {
|
|
1312
1314
|
const cmd = allCommands[i];
|
|
1313
1315
|
if (!cmd) {
|
|
1314
1316
|
throw new Error('Command not found!');
|
|
1315
1317
|
}
|
|
1316
|
-
if (cmd
|
|
1318
|
+
if (cmd.nestingLevel === command.nestingLevel - 1 && cmd.kind === 'htmlOpen') {
|
|
1317
1319
|
parentCommand = cmd;
|
|
1318
1320
|
break;
|
|
1319
1321
|
}
|
|
@@ -1321,45 +1323,41 @@ class ContentfulRichTextRendererComponent {
|
|
|
1321
1323
|
if (!parentCommand) {
|
|
1322
1324
|
throw new Error('Parent command not found!');
|
|
1323
1325
|
}
|
|
1324
|
-
parentElement = this.
|
|
1326
|
+
parentElement = this.executedCommandsCache.get(parentCommand.id)?.element;
|
|
1325
1327
|
}
|
|
1326
1328
|
if (!parentElement) {
|
|
1327
1329
|
throw new Error('Parent element not found!');
|
|
1328
1330
|
}
|
|
1329
1331
|
return parentElement;
|
|
1330
1332
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
if (cached.command[RENDER_COMMAND_POSITION.DOM_POSITION] > domPosition &&
|
|
1342
|
-
cached.command[RENDER_COMMAND_POSITION.NESTING_LEVEL] === nestingLevel &&
|
|
1343
|
-
// Make sure the element does not find itself
|
|
1344
|
-
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) {
|
|
1345
1343
|
nextElement = cached.element;
|
|
1346
|
-
|
|
1344
|
+
nextDomPosition = cached.command.domPosition;
|
|
1347
1345
|
}
|
|
1348
1346
|
}
|
|
1349
1347
|
return nextElement;
|
|
1350
1348
|
}
|
|
1351
|
-
|
|
1349
|
+
renderInsertOrAppend(nodeToRender, parentElement, nextElement) {
|
|
1352
1350
|
if (nextElement) {
|
|
1353
|
-
this.
|
|
1351
|
+
this.renderer.insertBefore(parentElement, nodeToRender, nextElement);
|
|
1354
1352
|
}
|
|
1355
1353
|
else {
|
|
1356
|
-
this.
|
|
1354
|
+
this.renderer.appendChild(parentElement, nodeToRender);
|
|
1357
1355
|
}
|
|
1358
1356
|
}
|
|
1359
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
1360
|
-
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 }); }
|
|
1361
1359
|
}
|
|
1362
|
-
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: [{
|
|
1363
1361
|
type: Component,
|
|
1364
1362
|
args: [{
|
|
1365
1363
|
selector: 'et-contentful-rich-text-renderer',
|
|
@@ -1384,11 +1382,9 @@ const provideContentfulConfig = (contentfulConfig = {}) => {
|
|
|
1384
1382
|
};
|
|
1385
1383
|
const isContentfulEntryType = (entry, type) => entry.sys.contentType.sys.id === type;
|
|
1386
1384
|
|
|
1387
|
-
// Burst cache
|
|
1388
|
-
|
|
1389
1385
|
/**
|
|
1390
1386
|
* Generated bundle index. Do not edit.
|
|
1391
1387
|
*/
|
|
1392
1388
|
|
|
1393
|
-
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 };
|
|
1394
1390
|
//# sourceMappingURL=ethlete-contentful.mjs.map
|