@ethlete/contentful 4.0.0-next.10 → 4.0.0-next.11
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 +21 -0
- package/fesm2022/ethlete-contentful.mjs +205 -242
- package/fesm2022/ethlete-contentful.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ethlete-contentful.d.ts +52 -9
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { NgClass, DOCUMENT } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
3
|
import { input, computed, ViewEncapsulation, Component, inject, InjectionToken, numberAttribute, booleanAttribute, isDevMode, ViewContainerRef, ElementRef, effect, untracked, signal, reflectComponentType, inputBinding } from '@angular/core';
|
|
4
|
-
import { normalizePictureSizes, PictureComponent } from '@ethlete/components';
|
|
4
|
+
import { formatFileSize, normalizePictureSizes, PictureComponent } from '@ethlete/components';
|
|
5
|
+
import { defineLabels, toProvideFn, toInjectFn, toToken, injectUrl, RuntimeError, isObject, injectRenderer, getObjectProperty } from '@ethlete/core';
|
|
5
6
|
import { Router, RouterLink } from '@angular/router';
|
|
6
|
-
import { RuntimeError, isObject, injectRenderer, getObjectProperty } from '@ethlete/core';
|
|
7
7
|
|
|
8
8
|
const GQL_FRAGMENT_CONTENTFUL_ASSET = `
|
|
9
9
|
fragment ContentfulAssetData on Asset {
|
|
@@ -12,6 +12,7 @@ const GQL_FRAGMENT_CONTENTFUL_ASSET = `
|
|
|
12
12
|
}
|
|
13
13
|
url
|
|
14
14
|
title
|
|
15
|
+
fileName
|
|
15
16
|
width
|
|
16
17
|
height
|
|
17
18
|
description
|
|
@@ -42,13 +43,13 @@ class ContentfulAudioComponent {
|
|
|
42
43
|
if (isContentfulGqlAsset(asset) && asset.url) {
|
|
43
44
|
return {
|
|
44
45
|
url: asset.url,
|
|
45
|
-
title: asset.title,
|
|
46
|
+
title: asset.title || asset.fileName || null,
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
|
-
if (!isContentfulGqlAsset(asset) && asset.fields.file
|
|
49
|
+
if (!isContentfulGqlAsset(asset) && asset.fields.file?.url) {
|
|
49
50
|
return {
|
|
50
51
|
url: asset.fields.file.url,
|
|
51
|
-
title: asset.fields.title,
|
|
52
|
+
title: asset.fields.title || asset.fields.file.fileName || null,
|
|
52
53
|
};
|
|
53
54
|
}
|
|
54
55
|
return null;
|
|
@@ -85,8 +86,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
85
86
|
}]
|
|
86
87
|
}], propDecorators: { asset: [{ type: i0.Input, args: [{ isSignal: true, alias: "asset", required: true }] }], audioClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "audioClass", required: false }] }], figureClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "figureClass", required: false }] }], figcaptionClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "figcaptionClass", required: false }] }] } });
|
|
87
88
|
|
|
89
|
+
/** The built-in English labels. */
|
|
90
|
+
const DEFAULT_CONTENTFUL_FILE_LABELS = {
|
|
91
|
+
fileSize: (bytes) => `(${formatFileSize(bytes)})`,
|
|
92
|
+
};
|
|
93
|
+
const CONTENTFUL_FILE_LABELS_DEF = /* @__PURE__ */ defineLabels('CONTENTFUL_FILE_LABELS', DEFAULT_CONTENTFUL_FILE_LABELS);
|
|
94
|
+
/**
|
|
95
|
+
* Localize `et-contentful-file` for everything below this injector, and read the set in effect here as a signal.
|
|
96
|
+
* Partial - whatever you leave out keeps its {@link DEFAULT_CONTENTFUL_FILE_LABELS} value.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* provideContentfulFileLabels({ fileSize: (bytes) => `(${new Intl.NumberFormat('de').format(bytes)} Byte)` });
|
|
100
|
+
*/
|
|
101
|
+
const provideContentfulFileLabels = /* @__PURE__ */ toProvideFn(CONTENTFUL_FILE_LABELS_DEF);
|
|
102
|
+
const injectContentfulFileLabels = /* @__PURE__ */ toInjectFn(CONTENTFUL_FILE_LABELS_DEF);
|
|
103
|
+
const CONTENTFUL_FILE_LABELS = /* @__PURE__ */ toToken(CONTENTFUL_FILE_LABELS_DEF);
|
|
104
|
+
|
|
88
105
|
class ContentfulFileComponent {
|
|
89
106
|
constructor() {
|
|
107
|
+
this.labels = injectContentfulFileLabels();
|
|
90
108
|
this.asset = input.required(/* @ts-ignore */
|
|
91
109
|
...(ngDevMode ? [{ debugName: "asset" }] : /* istanbul ignore next */ []));
|
|
92
110
|
this.fileClass = input(null, /* @ts-ignore */
|
|
@@ -100,14 +118,14 @@ class ContentfulFileComponent {
|
|
|
100
118
|
return {
|
|
101
119
|
url: asset.url,
|
|
102
120
|
size: asset.size,
|
|
103
|
-
title: asset.title,
|
|
121
|
+
title: asset.title || asset.fileName || null,
|
|
104
122
|
};
|
|
105
123
|
}
|
|
106
|
-
if (!isContentfulGqlAsset(asset) && asset.fields.file
|
|
124
|
+
if (!isContentfulGqlAsset(asset) && asset.fields.file?.url) {
|
|
107
125
|
return {
|
|
108
126
|
url: asset.fields.file.url,
|
|
109
127
|
size: asset.fields.file.details.size,
|
|
110
|
-
title: asset.fields.title,
|
|
128
|
+
title: asset.fields.title || asset.fields.file.fileName || null,
|
|
111
129
|
};
|
|
112
130
|
}
|
|
113
131
|
return null;
|
|
@@ -120,7 +138,7 @@ class ContentfulFileComponent {
|
|
|
120
138
|
<a [href]="data.url" [ngClass]="fileClass()" target="_blank" rel="noopener noreferrer">
|
|
121
139
|
{{ data.title }}
|
|
122
140
|
@if (data.size !== null) {
|
|
123
|
-
|
|
141
|
+
{{ labels().fileSize(data.size) }}
|
|
124
142
|
}
|
|
125
143
|
</a>
|
|
126
144
|
}
|
|
@@ -135,7 +153,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
135
153
|
<a [href]="data.url" [ngClass]="fileClass()" target="_blank" rel="noopener noreferrer">
|
|
136
154
|
{{ data.title }}
|
|
137
155
|
@if (data.size !== null) {
|
|
138
|
-
|
|
156
|
+
{{ labels().fileSize(data.size) }}
|
|
139
157
|
}
|
|
140
158
|
</a>
|
|
141
159
|
}
|
|
@@ -172,23 +190,35 @@ const matchesHostname = (hostname, configuredHost) => {
|
|
|
172
190
|
const normalizedHost = normalizeHostname(configuredHost);
|
|
173
191
|
return hostname === normalizedHost || hostname.endsWith('.' + normalizedHost);
|
|
174
192
|
};
|
|
193
|
+
const isInternalWebUrl = (url, { location, internalHosts }) => (url.hostname === location.hostname && url.port === location.port) ||
|
|
194
|
+
internalHosts.some((host) => matchesHostname(url.hostname, host));
|
|
195
|
+
const isExternalWebHref = (href, context) => {
|
|
196
|
+
const url = parseWebUrl(href);
|
|
197
|
+
return url !== null && !isInternalWebUrl(url, context);
|
|
198
|
+
};
|
|
199
|
+
|
|
175
200
|
class ContentfulLinkComponent {
|
|
176
201
|
constructor() {
|
|
177
202
|
this.document = inject(DOCUMENT);
|
|
178
203
|
this.router = inject(Router);
|
|
179
204
|
this.config = injectContentfulConfig();
|
|
205
|
+
this.url = injectUrl();
|
|
180
206
|
this.href = input.required(/* @ts-ignore */
|
|
181
207
|
...(ngDevMode ? [{ debugName: "href" }] : /* istanbul ignore next */ []));
|
|
182
208
|
this.text = input.required(/* @ts-ignore */
|
|
183
209
|
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
184
210
|
this.textClass = input('', /* @ts-ignore */
|
|
185
211
|
...(ngDevMode ? [{ debugName: "textClass" }] : /* istanbul ignore next */ []));
|
|
212
|
+
this.anchorClass = input('', /* @ts-ignore */
|
|
213
|
+
...(ngDevMode ? [{ debugName: "anchorClass" }] : /* istanbul ignore next */ []));
|
|
186
214
|
this.usesRouterLink = computed(() => {
|
|
187
215
|
const href = this.href();
|
|
188
216
|
const absoluteUrl = parseWebUrl(href);
|
|
189
217
|
if (absoluteUrl) {
|
|
190
|
-
|
|
191
|
-
|
|
218
|
+
return isInternalWebUrl(absoluteUrl, {
|
|
219
|
+
location: this.document.location,
|
|
220
|
+
internalHosts: this.config.internalHosts,
|
|
221
|
+
});
|
|
192
222
|
}
|
|
193
223
|
return !href.startsWith('#') && !/^[a-z][a-z\d+.-]*:/i.test(href);
|
|
194
224
|
}, /* @ts-ignore */
|
|
@@ -198,23 +228,23 @@ class ContentfulLinkComponent {
|
|
|
198
228
|
this.internalPath = computed(() => {
|
|
199
229
|
const href = this.href();
|
|
200
230
|
const url = parseWebUrl(href);
|
|
201
|
-
if (
|
|
231
|
+
if (url) {
|
|
232
|
+
return url.pathname + url.search + url.hash;
|
|
233
|
+
}
|
|
234
|
+
if (href.startsWith('/')) {
|
|
202
235
|
return href;
|
|
203
236
|
}
|
|
204
|
-
|
|
237
|
+
const resolved = new URL(href, 'https://contentful.invalid' + this.url());
|
|
238
|
+
return resolved.pathname + resolved.search + resolved.hash;
|
|
205
239
|
}, /* @ts-ignore */
|
|
206
240
|
...(ngDevMode ? [{ debugName: "internalPath" }] : /* istanbul ignore next */ []));
|
|
207
241
|
this.internalUrlTree = computed(() => this.router.parseUrl(this.internalPath()), /* @ts-ignore */
|
|
208
242
|
...(ngDevMode ? [{ debugName: "internalUrlTree" }] : /* istanbul ignore next */ []));
|
|
209
|
-
this.linkClass = computed(() =>
|
|
210
|
-
const base = 'et-contentful-rich-text-default-element et-contentful-rich-text-default-a';
|
|
211
|
-
const extra = this.textClass();
|
|
212
|
-
return extra ? `${base} ${extra}` : base;
|
|
213
|
-
}, /* @ts-ignore */
|
|
243
|
+
this.linkClass = computed(() => [this.anchorClass(), this.textClass()].filter(Boolean).join(' ') || null, /* @ts-ignore */
|
|
214
244
|
...(ngDevMode ? [{ debugName: "linkClass" }] : /* istanbul ignore next */ []));
|
|
215
245
|
}
|
|
216
246
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulLinkComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
217
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ContentfulLinkComponent, isStandalone: true, selector: "et-contentful-link", inputs: { href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: true, transformFunction: null }, text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: true, transformFunction: null }, textClass: { classPropertyName: "textClass", publicName: "textClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "et-contentful-link" }, ngImport: i0, template: `
|
|
247
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ContentfulLinkComponent, isStandalone: true, selector: "et-contentful-link", inputs: { href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: true, transformFunction: null }, text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: true, transformFunction: null }, textClass: { classPropertyName: "textClass", publicName: "textClass", isSignal: true, isRequired: false, transformFunction: null }, anchorClass: { classPropertyName: "anchorClass", publicName: "anchorClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "et-contentful-link" }, ngImport: i0, template: `
|
|
218
248
|
@if (usesRouterLink()) {
|
|
219
249
|
<a [class]="linkClass()" [routerLink]="internalUrlTree()">{{ text() }}</a>
|
|
220
250
|
} @else {
|
|
@@ -245,7 +275,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
245
275
|
`, encapsulation: ViewEncapsulation.None, imports: [RouterLink], host: {
|
|
246
276
|
class: 'et-contentful-link',
|
|
247
277
|
}, styles: ["@layer components{.et-contentful-link{display:contents}}\n"] }]
|
|
248
|
-
}], 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 }] }] } });
|
|
278
|
+
}], propDecorators: { href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: true }] }], text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: true }] }], textClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "textClass", required: false }] }], anchorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchorClass", required: false }] }] } });
|
|
249
279
|
|
|
250
280
|
class ContentfulVideoComponent {
|
|
251
281
|
constructor() {
|
|
@@ -264,7 +294,7 @@ class ContentfulVideoComponent {
|
|
|
264
294
|
contentType: asset.contentType,
|
|
265
295
|
};
|
|
266
296
|
}
|
|
267
|
-
if (!isContentfulGqlAsset(asset) && asset.fields.file
|
|
297
|
+
if (!isContentfulGqlAsset(asset) && asset.fields.file?.url) {
|
|
268
298
|
return {
|
|
269
299
|
url: asset.fields.file.url,
|
|
270
300
|
contentType: asset.fields.file.contentType,
|
|
@@ -319,8 +349,8 @@ const CONTENTFUL_FALLBACK_CONFIG = {
|
|
|
319
349
|
backgroundColor: null,
|
|
320
350
|
},
|
|
321
351
|
};
|
|
322
|
-
const createContentfulConfig = (config) =>
|
|
323
|
-
|
|
352
|
+
const createContentfulConfig = (config) => {
|
|
353
|
+
const defaults = {
|
|
324
354
|
internalHosts: [],
|
|
325
355
|
components: {
|
|
326
356
|
audio: ContentfulAudioComponent,
|
|
@@ -335,9 +365,14 @@ const createContentfulConfig = (config) => ({
|
|
|
335
365
|
sizes: ['100vw'],
|
|
336
366
|
backgroundColor: null,
|
|
337
367
|
},
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
|
|
368
|
+
};
|
|
369
|
+
return {
|
|
370
|
+
...defaults,
|
|
371
|
+
...config,
|
|
372
|
+
components: { ...defaults.components, ...config?.components },
|
|
373
|
+
imageOptions: { ...defaults.imageOptions, ...config?.imageOptions },
|
|
374
|
+
};
|
|
375
|
+
};
|
|
341
376
|
/**
|
|
342
377
|
* Reads the contentful config from the current injector. Falls back to a config without
|
|
343
378
|
* any `components` when none was provided via `provideContentfulConfig()`.
|
|
@@ -357,15 +392,16 @@ const generateDefaultContentfulImageSource = (data) => {
|
|
|
357
392
|
srcset: data.url,
|
|
358
393
|
};
|
|
359
394
|
}
|
|
360
|
-
|
|
395
|
+
const file = data.fields.file;
|
|
396
|
+
if (!file?.contentType || !file.url) {
|
|
361
397
|
return {
|
|
362
398
|
type: '',
|
|
363
399
|
srcset: '',
|
|
364
400
|
};
|
|
365
401
|
}
|
|
366
402
|
return {
|
|
367
|
-
type:
|
|
368
|
-
srcset:
|
|
403
|
+
type: file.contentType,
|
|
404
|
+
srcset: file.url,
|
|
369
405
|
};
|
|
370
406
|
};
|
|
371
407
|
/**
|
|
@@ -392,13 +428,20 @@ const parseContentfulImageSize = (size) => {
|
|
|
392
428
|
return width ? { width: Number(width[1]), height: null } : { width: null, height: null };
|
|
393
429
|
};
|
|
394
430
|
const SOURCE_TYPES = ['image/avif', 'image/webp'];
|
|
395
|
-
|
|
431
|
+
/**
|
|
432
|
+
* Builds AVIF and WebP `<source>` entries for a Contentful asset through the Contentful Images API.
|
|
433
|
+
*
|
|
434
|
+
* @example
|
|
435
|
+
* generateContentfulImageSources(asset, { srcsetSizes: ['400w', '800w'], quality: 80 });
|
|
436
|
+
*/
|
|
437
|
+
const generateContentfulImageSources = (data, options = {}) => {
|
|
438
|
+
const { srcsetSizes = [], backgroundColor = null, quality = null, focusArea = null, resizeBehavior = null } = options;
|
|
396
439
|
const isGqlAsset = isContentfulGqlAsset(data);
|
|
397
|
-
const baseUrl = isGqlAsset ? data.url : data.fields.file
|
|
440
|
+
const baseUrl = isGqlAsset ? data.url : data.fields.file?.url;
|
|
398
441
|
if (!baseUrl) {
|
|
399
442
|
return [];
|
|
400
443
|
}
|
|
401
|
-
const imageDimensions = isGqlAsset ? data : data.fields.file
|
|
444
|
+
const imageDimensions = isGqlAsset ? data : data.fields.file?.details.image;
|
|
402
445
|
const sources = [];
|
|
403
446
|
for (const type of SOURCE_TYPES) {
|
|
404
447
|
const sourceSets = [];
|
|
@@ -416,7 +459,7 @@ const generateContentfulImageSources = (data, srcsetSizes, backgroundColor, qual
|
|
|
416
459
|
if (resizeBehavior) {
|
|
417
460
|
queryParams.push(`fit=${resizeBehavior}`);
|
|
418
461
|
}
|
|
419
|
-
if (srcsetSizes
|
|
462
|
+
if (srcsetSizes.length) {
|
|
420
463
|
const urlWithParams = `${baseUrl}${baseUrl.includes('?') ? '&' : '?'}${queryParams.join('&')}`;
|
|
421
464
|
for (const size of srcsetSizes) {
|
|
422
465
|
const { width, height } = parseContentfulImageSize(size);
|
|
@@ -484,15 +527,17 @@ class ContentfulImageComponent {
|
|
|
484
527
|
...(ngDevMode ? [{ debugName: "normalizedAsset" }] : /* istanbul ignore next */ []));
|
|
485
528
|
this.sourcesValue = computed(() => {
|
|
486
529
|
const asset = this.asset();
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
530
|
+
const options = {
|
|
531
|
+
srcsetSizes: this.srcsetSizes(),
|
|
532
|
+
backgroundColor: this.backgroundColor(),
|
|
533
|
+
quality: this.quality(),
|
|
534
|
+
focusArea: this.focusArea(),
|
|
535
|
+
resizeBehavior: this.resizeBehavior(),
|
|
536
|
+
};
|
|
492
537
|
if (!asset) {
|
|
493
538
|
return [];
|
|
494
539
|
}
|
|
495
|
-
return generateContentfulImageSources(asset,
|
|
540
|
+
return generateContentfulImageSources(asset, options);
|
|
496
541
|
}, /* @ts-ignore */
|
|
497
542
|
...(ngDevMode ? [{ debugName: "sourcesValue" }] : /* istanbul ignore next */ []));
|
|
498
543
|
this.defaultSrcValue = computed(() => {
|
|
@@ -587,22 +632,18 @@ const RICH_TEXT_RENDERER_ERRORS = {
|
|
|
587
632
|
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".',
|
|
588
633
|
asset_id_not_found: 'The asset ID was not found. This node is not supported.',
|
|
589
634
|
entry_id_not_found: 'The entry ID was not found. This node is not supported.',
|
|
590
|
-
asset_not_found: 'The asset was not found. This node is not supported.',
|
|
591
|
-
entry_not_found: 'The entry was not found. This node is not supported.',
|
|
592
|
-
custom_component_not_found: 'No custom component found for entry type. Please provide one for this type.',
|
|
593
635
|
text_parent_not_found: 'The parent node is not found. This structure is not supported.',
|
|
594
|
-
|
|
636
|
+
cached_command_not_found: 'No rendered node was found for a render command. The renderer state is out of sync.',
|
|
637
|
+
cached_command_not_component: 'The rendered node for a component command is not a component. The renderer state is out of sync.',
|
|
595
638
|
};
|
|
596
639
|
const RICH_TEXT_RENDERER_ERROR_CODES = {
|
|
597
640
|
rich_text_undefined: 0,
|
|
598
641
|
rich_text_wrong_type: 1,
|
|
599
642
|
asset_id_not_found: 2,
|
|
600
643
|
entry_id_not_found: 3,
|
|
601
|
-
asset_not_found: 4,
|
|
602
|
-
entry_not_found: 5,
|
|
603
|
-
custom_component_not_found: 6,
|
|
604
644
|
text_parent_not_found: 7,
|
|
605
|
-
|
|
645
|
+
cached_command_not_found: 9,
|
|
646
|
+
cached_command_not_component: 10,
|
|
606
647
|
};
|
|
607
648
|
const richTextRendererError = (code, data) => {
|
|
608
649
|
const message = `<et-contentful-rich-text-renderer>: ${RICH_TEXT_RENDERER_ERRORS[code]}`;
|
|
@@ -612,60 +653,43 @@ const richTextRendererError = (code, data) => {
|
|
|
612
653
|
const isRichTextRootNode = (node) => {
|
|
613
654
|
return isObject(node) && 'nodeType' in node && node['nodeType'] === 'document';
|
|
614
655
|
};
|
|
656
|
+
const NODE_TYPE_TAGS = /* @__PURE__ */ (() => ({
|
|
657
|
+
[CF_BLOCKS.HEADING_1]: 'h1',
|
|
658
|
+
[CF_BLOCKS.HEADING_2]: 'h2',
|
|
659
|
+
[CF_BLOCKS.HEADING_3]: 'h3',
|
|
660
|
+
[CF_BLOCKS.HEADING_4]: 'h4',
|
|
661
|
+
[CF_BLOCKS.HEADING_5]: 'h5',
|
|
662
|
+
[CF_BLOCKS.HEADING_6]: 'h6',
|
|
663
|
+
[CF_BLOCKS.PARAGRAPH]: 'p',
|
|
664
|
+
[CF_BLOCKS.UL_LIST]: 'ul',
|
|
665
|
+
[CF_BLOCKS.OL_LIST]: 'ol',
|
|
666
|
+
[CF_BLOCKS.LIST_ITEM]: 'li',
|
|
667
|
+
[CF_BLOCKS.HR]: 'hr',
|
|
668
|
+
[CF_BLOCKS.QUOTE]: 'blockquote',
|
|
669
|
+
[CF_BLOCKS.TABLE]: 'table',
|
|
670
|
+
[CF_BLOCKS.TABLE_ROW]: 'tr',
|
|
671
|
+
[CF_BLOCKS.TABLE_CELL]: 'td',
|
|
672
|
+
[CF_BLOCKS.TABLE_HEADER_CELL]: 'th',
|
|
673
|
+
}))();
|
|
674
|
+
const INLINE_NODE_TYPES = /* @__PURE__ */ (() => new Set(Object.values(CF_INLINES)))();
|
|
675
|
+
/**
|
|
676
|
+
* The html element a structural rich text node renders as. A node type without an element
|
|
677
|
+
* (an unsupported inline or block) falls back to `span` for inlines and `div` otherwise,
|
|
678
|
+
* with a dev-mode warning.
|
|
679
|
+
*/
|
|
615
680
|
const translateContentfulNodeTypeToHtmlTag = (nodeType) => {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
case CF_BLOCKS.HEADING_4:
|
|
624
|
-
return 'h4';
|
|
625
|
-
case CF_BLOCKS.HEADING_5:
|
|
626
|
-
return 'h5';
|
|
627
|
-
case CF_BLOCKS.HEADING_6:
|
|
628
|
-
return 'h6';
|
|
629
|
-
case CF_BLOCKS.PARAGRAPH:
|
|
630
|
-
return 'p';
|
|
631
|
-
case CF_BLOCKS.UL_LIST:
|
|
632
|
-
return 'ul';
|
|
633
|
-
case CF_BLOCKS.OL_LIST:
|
|
634
|
-
return 'ol';
|
|
635
|
-
case CF_BLOCKS.LIST_ITEM:
|
|
636
|
-
return 'li';
|
|
637
|
-
case CF_BLOCKS.HR:
|
|
638
|
-
return 'hr';
|
|
639
|
-
case CF_BLOCKS.QUOTE:
|
|
640
|
-
return 'blockquote';
|
|
641
|
-
case CF_BLOCKS.TABLE:
|
|
642
|
-
return 'table';
|
|
643
|
-
case CF_BLOCKS.TABLE_ROW:
|
|
644
|
-
return 'tr';
|
|
645
|
-
case CF_BLOCKS.TABLE_CELL:
|
|
646
|
-
return 'td';
|
|
647
|
-
case CF_BLOCKS.TABLE_HEADER_CELL:
|
|
648
|
-
return 'th';
|
|
649
|
-
case CF_BLOCKS.EMBEDDED_ASSET:
|
|
650
|
-
return 'div';
|
|
651
|
-
case CF_INLINES.ENTRY_HYPERLINK:
|
|
652
|
-
return 'a';
|
|
653
|
-
case CF_INLINES.ASSET_HYPERLINK:
|
|
654
|
-
return 'a';
|
|
655
|
-
case 'text':
|
|
656
|
-
return 'span';
|
|
657
|
-
// Will be ignored by the renderer
|
|
658
|
-
case 'document':
|
|
659
|
-
return 'div';
|
|
660
|
-
case CF_BLOCKS.EMBEDDED_ENTRY:
|
|
661
|
-
return 'div';
|
|
662
|
-
case CF_INLINES.EMBEDDED_ENTRY:
|
|
663
|
-
return 'div';
|
|
664
|
-
default:
|
|
665
|
-
return 'div';
|
|
681
|
+
const tag = NODE_TYPE_TAGS[nodeType];
|
|
682
|
+
if (tag) {
|
|
683
|
+
return tag;
|
|
684
|
+
}
|
|
685
|
+
const fallback = INLINE_NODE_TYPES.has(nodeType) ? 'span' : 'div';
|
|
686
|
+
if (isDevMode()) {
|
|
687
|
+
console.warn(`Unsupported rich text node type "${nodeType}"! Its content is rendered inside a <${fallback}>.`);
|
|
666
688
|
}
|
|
689
|
+
return fallback;
|
|
667
690
|
};
|
|
668
691
|
|
|
692
|
+
const DEFAULT_ANCHOR_CLASS = 'et-contentful-rich-text-default-element et-contentful-rich-text-default-a';
|
|
669
693
|
const MARK_TAG_MAP = {
|
|
670
694
|
bold: 'strong',
|
|
671
695
|
italic: 'em',
|
|
@@ -727,7 +751,7 @@ const createContentfulIncludeMap = (config) => {
|
|
|
727
751
|
const entry = entryMap.get(id);
|
|
728
752
|
if (!entry) {
|
|
729
753
|
if (isDevMode()) {
|
|
730
|
-
console.warn('Entry not found! Will return null. Is the include query param
|
|
754
|
+
console.warn('Entry not found! Will return null. Is the include query param too low?', { id, entryMap });
|
|
731
755
|
}
|
|
732
756
|
return null;
|
|
733
757
|
}
|
|
@@ -772,6 +796,7 @@ class ContentfulRichTextRendererComponent {
|
|
|
772
796
|
this.renderer = injectRenderer();
|
|
773
797
|
this.elementRef = inject(ElementRef);
|
|
774
798
|
this.config = injectContentfulConfig();
|
|
799
|
+
this.document = inject(DOCUMENT);
|
|
775
800
|
/**
|
|
776
801
|
* The contentful response gotten via their REST api.
|
|
777
802
|
* @see https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/entries/entries-collection
|
|
@@ -825,18 +850,6 @@ class ContentfulRichTextRendererComponent {
|
|
|
825
850
|
const instructions = [];
|
|
826
851
|
const preserved = new Set();
|
|
827
852
|
const needsReattach = new Set();
|
|
828
|
-
const parentIdOf = (command) => {
|
|
829
|
-
if (command.nestingLevel === 0) {
|
|
830
|
-
return null;
|
|
831
|
-
}
|
|
832
|
-
for (let i = command.index - 1; i >= 0; i--) {
|
|
833
|
-
const cmd = commands[i];
|
|
834
|
-
if (cmd && cmd.nestingLevel === command.nestingLevel - 1 && cmd.kind === 'htmlOpen') {
|
|
835
|
-
return cmd.id;
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
return null;
|
|
839
|
-
};
|
|
840
853
|
const markTagsEqual = (a, b) => a.length === b.length && a.every((tag, index) => tag === b[index]);
|
|
841
854
|
const attributesEqual = (a, b) => {
|
|
842
855
|
const aKeys = Object.keys(a);
|
|
@@ -853,8 +866,7 @@ class ContentfulRichTextRendererComponent {
|
|
|
853
866
|
if (previous.kind === 'component' && command.kind === 'component' && previous.component !== command.component) {
|
|
854
867
|
continue;
|
|
855
868
|
}
|
|
856
|
-
const
|
|
857
|
-
const parentPreserved = parentId === null || preserved.has(parentId);
|
|
869
|
+
const parentPreserved = previous.parentId === command.parentId && (command.parentId === null || preserved.has(command.parentId));
|
|
858
870
|
if (command.kind === 'component') {
|
|
859
871
|
preserved.add(command.id);
|
|
860
872
|
const samePlace = parentPreserved &&
|
|
@@ -913,7 +925,7 @@ class ContentfulRichTextRendererComponent {
|
|
|
913
925
|
let nestingLevel = 0;
|
|
914
926
|
let domPosition = 0;
|
|
915
927
|
let textId = 0;
|
|
916
|
-
|
|
928
|
+
const openElementIds = [];
|
|
917
929
|
const traverse = (node) => {
|
|
918
930
|
switch (node.nodeType) {
|
|
919
931
|
case 'text': {
|
|
@@ -927,7 +939,7 @@ class ContentfulRichTextRendererComponent {
|
|
|
927
939
|
kind: 'text',
|
|
928
940
|
nestingLevel,
|
|
929
941
|
domPosition,
|
|
930
|
-
|
|
942
|
+
parentId: openElementIds.at(-1) ?? null,
|
|
931
943
|
attributes,
|
|
932
944
|
markTags: node.marks.length ? marksToTags(node.marks) : [],
|
|
933
945
|
text,
|
|
@@ -943,11 +955,14 @@ class ContentfulRichTextRendererComponent {
|
|
|
943
955
|
}
|
|
944
956
|
const asset = this.contentIncludesMap().getAsset(assetId);
|
|
945
957
|
if (!asset) {
|
|
946
|
-
|
|
958
|
+
if (isDevMode()) {
|
|
959
|
+
console.warn('Embedded asset is missing from the includes! The asset will be skipped. Is it unpublished or deleted?', { assetId, node });
|
|
960
|
+
}
|
|
961
|
+
break;
|
|
947
962
|
}
|
|
948
|
-
const contentType = asset.fields.file
|
|
963
|
+
const contentType = asset.fields.file?.contentType;
|
|
949
964
|
const assetComponents = this.config.components;
|
|
950
|
-
const isMissing = !contentType && !asset.fields.file
|
|
965
|
+
const isMissing = !contentType && !asset.fields.file?.url;
|
|
951
966
|
if (isMissing) {
|
|
952
967
|
if (isDevMode()) {
|
|
953
968
|
console.warn('Asset is missing file data! Asset will be skipped. Did you forget to upload a file for the current translation in Contentful?', { asset });
|
|
@@ -978,7 +993,7 @@ class ContentfulRichTextRendererComponent {
|
|
|
978
993
|
kind: 'component',
|
|
979
994
|
nestingLevel,
|
|
980
995
|
domPosition,
|
|
981
|
-
|
|
996
|
+
parentId: openElementIds.at(-1) ?? null,
|
|
982
997
|
component,
|
|
983
998
|
inputs: { asset },
|
|
984
999
|
id,
|
|
@@ -996,52 +1011,42 @@ class ContentfulRichTextRendererComponent {
|
|
|
996
1011
|
else if (node.nodeType === CF_INLINES.ASSET_HYPERLINK) {
|
|
997
1012
|
const assetId = node.data['target']?.sys?.id;
|
|
998
1013
|
const asset = assetId ? this.contentIncludesMap().getAsset(assetId) : null;
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
let linkText = '';
|
|
1002
|
-
const linkMarks = [];
|
|
1003
|
-
for (const child of node.content) {
|
|
1004
|
-
if (child.nodeType === 'text') {
|
|
1005
|
-
linkText += child.value;
|
|
1006
|
-
linkMarks.push(...child.marks);
|
|
1007
|
-
}
|
|
1014
|
+
const url = asset?.fields.file?.url;
|
|
1015
|
+
href = url ? normalizeHref(url) : null;
|
|
1008
1016
|
}
|
|
1017
|
+
const linkTexts = node.content.filter((child) => child.nodeType === 'text' && Boolean(child.value));
|
|
1018
|
+
const linkText = linkTexts.map((child) => child.value).join('');
|
|
1009
1019
|
const linkComponent = href ? this.config.components.link : null;
|
|
1010
1020
|
if (href && !linkComponent) {
|
|
1021
|
+
const attributes = { class: DEFAULT_ANCHOR_CLASS, href };
|
|
1022
|
+
if (isExternalWebHref(href, { location: this.document.location, internalHosts: this.config.internalHosts })) {
|
|
1023
|
+
attributes['target'] = '_blank';
|
|
1024
|
+
attributes['rel'] = 'noopener noreferrer';
|
|
1025
|
+
}
|
|
1011
1026
|
rootCommands.push({
|
|
1012
1027
|
kind: 'htmlOpen',
|
|
1013
1028
|
nestingLevel,
|
|
1014
1029
|
domPosition,
|
|
1015
|
-
|
|
1016
|
-
attributes
|
|
1017
|
-
class: 'et-contentful-rich-text-default-element et-contentful-rich-text-default-a',
|
|
1018
|
-
href,
|
|
1019
|
-
},
|
|
1030
|
+
parentId: openElementIds.at(-1) ?? null,
|
|
1031
|
+
attributes,
|
|
1020
1032
|
tagName: 'a',
|
|
1021
1033
|
id: 'e-o' + elementOpenId++,
|
|
1022
1034
|
});
|
|
1023
1035
|
const anchorDomPosition = domPosition;
|
|
1036
|
+
openElementIds.push('e-o' + (elementOpenId - 1));
|
|
1024
1037
|
nestingLevel++;
|
|
1025
1038
|
domPosition = 0;
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
index: commandIndex++,
|
|
1031
|
-
attributes: {
|
|
1032
|
-
class: 'et-contentful-rich-text-default-element et-contentful-rich-text-default-span',
|
|
1033
|
-
},
|
|
1034
|
-
markTags: marksToTags(linkMarks),
|
|
1035
|
-
text: linkText,
|
|
1036
|
-
id: 't' + textId++,
|
|
1037
|
-
});
|
|
1039
|
+
for (const child of linkTexts) {
|
|
1040
|
+
traverse(child);
|
|
1041
|
+
}
|
|
1042
|
+
openElementIds.pop();
|
|
1038
1043
|
nestingLevel--;
|
|
1039
1044
|
domPosition = anchorDomPosition;
|
|
1040
1045
|
rootCommands.push({
|
|
1041
1046
|
kind: 'htmlClose',
|
|
1042
1047
|
nestingLevel,
|
|
1043
1048
|
domPosition,
|
|
1044
|
-
|
|
1049
|
+
parentId: openElementIds.at(-1) ?? null,
|
|
1045
1050
|
tagName: 'a',
|
|
1046
1051
|
id: 'e-c' + elementCloseId++,
|
|
1047
1052
|
});
|
|
@@ -1049,25 +1054,16 @@ class ContentfulRichTextRendererComponent {
|
|
|
1049
1054
|
break;
|
|
1050
1055
|
}
|
|
1051
1056
|
if (!href) {
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
domPosition,
|
|
1056
|
-
index: commandIndex++,
|
|
1057
|
-
attributes: {
|
|
1058
|
-
class: 'et-contentful-rich-text-default-element et-contentful-rich-text-default-span',
|
|
1059
|
-
},
|
|
1060
|
-
markTags: marksToTags(linkMarks),
|
|
1061
|
-
text: linkText,
|
|
1062
|
-
id: 't' + textId++,
|
|
1063
|
-
});
|
|
1064
|
-
domPosition++;
|
|
1057
|
+
for (const child of linkTexts) {
|
|
1058
|
+
traverse(child);
|
|
1059
|
+
}
|
|
1065
1060
|
break;
|
|
1066
1061
|
}
|
|
1067
1062
|
if (!linkComponent) {
|
|
1068
1063
|
break;
|
|
1069
1064
|
}
|
|
1070
|
-
const
|
|
1065
|
+
const sharedMarks = linkTexts[0]?.marks.filter((mark) => linkTexts.every((child) => child.marks.some((other) => other.type === mark.type))) ?? [];
|
|
1066
|
+
const textClass = marksToClass(sharedMarks);
|
|
1071
1067
|
let linkComponentId = componentIdMap.get(LINK_COMPONENT_TYPE) ?? -1;
|
|
1072
1068
|
const linkId = LINK_COMPONENT_TYPE + ++linkComponentId;
|
|
1073
1069
|
componentIdMap.set(LINK_COMPONENT_TYPE, linkComponentId);
|
|
@@ -1075,9 +1071,9 @@ class ContentfulRichTextRendererComponent {
|
|
|
1075
1071
|
kind: 'component',
|
|
1076
1072
|
nestingLevel,
|
|
1077
1073
|
domPosition,
|
|
1078
|
-
|
|
1074
|
+
parentId: openElementIds.at(-1) ?? null,
|
|
1079
1075
|
component: linkComponent,
|
|
1080
|
-
inputs: { href, text: linkText, textClass },
|
|
1076
|
+
inputs: { href, text: linkText, textClass, anchorClass: DEFAULT_ANCHOR_CLASS },
|
|
1081
1077
|
id: linkId,
|
|
1082
1078
|
});
|
|
1083
1079
|
domPosition++;
|
|
@@ -1091,16 +1087,18 @@ class ContentfulRichTextRendererComponent {
|
|
|
1091
1087
|
}
|
|
1092
1088
|
const entry = this.contentIncludesMap().getEntry(entryId, ET_CONTENTFUL_ANY_ENTRY_CONTENT_TYPE_SYS_ID);
|
|
1093
1089
|
if (!entry) {
|
|
1094
|
-
|
|
1090
|
+
if (isDevMode()) {
|
|
1091
|
+
console.warn('Embedded entry is missing from the includes! The entry will be skipped. Is it unpublished or deleted?', { entryId, node });
|
|
1092
|
+
}
|
|
1093
|
+
break;
|
|
1095
1094
|
}
|
|
1096
1095
|
const componentType = entry.sys.contentType.sys.id;
|
|
1097
1096
|
const component = this.config.customComponents[componentType];
|
|
1098
1097
|
if (!component) {
|
|
1099
|
-
|
|
1100
|
-
componentType,
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
});
|
|
1098
|
+
if (isDevMode()) {
|
|
1099
|
+
console.warn('No custom component registered for this embedded entry type! The entry will be skipped. Provide one via provideContentfulConfig({ customComponents: … }).', { componentType, customComponents: this.config.customComponents, entry });
|
|
1100
|
+
}
|
|
1101
|
+
break;
|
|
1104
1102
|
}
|
|
1105
1103
|
const occurrenceKey = 'entry:' + entryId;
|
|
1106
1104
|
const occurrence = (componentIdMap.get(occurrenceKey) ?? -1) + 1;
|
|
@@ -1110,7 +1108,7 @@ class ContentfulRichTextRendererComponent {
|
|
|
1110
1108
|
kind: 'component',
|
|
1111
1109
|
nestingLevel,
|
|
1112
1110
|
domPosition,
|
|
1113
|
-
|
|
1111
|
+
parentId: openElementIds.at(-1) ?? null,
|
|
1114
1112
|
component,
|
|
1115
1113
|
inputs: {
|
|
1116
1114
|
fields: entry.fields,
|
|
@@ -1132,17 +1130,19 @@ class ContentfulRichTextRendererComponent {
|
|
|
1132
1130
|
kind: 'htmlOpen',
|
|
1133
1131
|
nestingLevel,
|
|
1134
1132
|
domPosition,
|
|
1135
|
-
|
|
1133
|
+
parentId: openElementIds.at(-1) ?? null,
|
|
1136
1134
|
attributes,
|
|
1137
1135
|
tagName: tag,
|
|
1138
1136
|
id: 'e-o' + elementOpenId++,
|
|
1139
1137
|
});
|
|
1140
1138
|
const domPositionAtThisLevel = domPosition;
|
|
1139
|
+
openElementIds.push('e-o' + (elementOpenId - 1));
|
|
1141
1140
|
nestingLevel++;
|
|
1142
1141
|
domPosition = 0;
|
|
1143
1142
|
for (const child of node.content) {
|
|
1144
1143
|
traverse(child);
|
|
1145
1144
|
}
|
|
1145
|
+
openElementIds.pop();
|
|
1146
1146
|
nestingLevel--;
|
|
1147
1147
|
domPosition = domPositionAtThisLevel;
|
|
1148
1148
|
const lastCommand = rootCommands[rootCommands.length - 1];
|
|
@@ -1152,14 +1152,13 @@ class ContentfulRichTextRendererComponent {
|
|
|
1152
1152
|
lastCommand.tagName !== 'hr') {
|
|
1153
1153
|
rootCommands.pop();
|
|
1154
1154
|
elementOpenId--;
|
|
1155
|
-
commandIndex--;
|
|
1156
1155
|
}
|
|
1157
1156
|
else {
|
|
1158
1157
|
rootCommands.push({
|
|
1159
1158
|
kind: 'htmlClose',
|
|
1160
1159
|
nestingLevel,
|
|
1161
1160
|
domPosition,
|
|
1162
|
-
|
|
1161
|
+
parentId: openElementIds.at(-1) ?? null,
|
|
1163
1162
|
tagName: tag,
|
|
1164
1163
|
id: 'e-c' + elementCloseId++,
|
|
1165
1164
|
});
|
|
@@ -1175,26 +1174,32 @@ class ContentfulRichTextRendererComponent {
|
|
|
1175
1174
|
return rootCommands;
|
|
1176
1175
|
}
|
|
1177
1176
|
execInstructions(instructions) {
|
|
1178
|
-
|
|
1179
|
-
|
|
1177
|
+
const lastPlacedChild = new Map();
|
|
1178
|
+
for (const { type, command } of instructions) {
|
|
1179
|
+
if (type === 'delete') {
|
|
1180
|
+
this.runDeleteInstruction(command);
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1183
|
+
const previousSibling = lastPlacedChild.get(command.parentId) ?? null;
|
|
1184
|
+
switch (type) {
|
|
1180
1185
|
case 'create':
|
|
1181
|
-
this.runCreateInstruction(
|
|
1186
|
+
this.runCreateInstruction(command, previousSibling);
|
|
1182
1187
|
break;
|
|
1183
1188
|
case 'update':
|
|
1184
|
-
this.runUpdateInstruction(
|
|
1189
|
+
this.runUpdateInstruction(command);
|
|
1185
1190
|
break;
|
|
1186
1191
|
case 'move':
|
|
1187
|
-
this.runMoveInstruction(
|
|
1188
|
-
break;
|
|
1189
|
-
case 'delete':
|
|
1190
|
-
this.runDeleteInstruction(instruction.command);
|
|
1192
|
+
this.runMoveInstruction(command, previousSibling);
|
|
1191
1193
|
break;
|
|
1192
1194
|
}
|
|
1195
|
+
const element = this.executedCommandsCache.get(command.id)?.element;
|
|
1196
|
+
if (element) {
|
|
1197
|
+
lastPlacedChild.set(command.parentId, element);
|
|
1198
|
+
}
|
|
1193
1199
|
}
|
|
1194
1200
|
}
|
|
1195
|
-
runCreateInstruction(command) {
|
|
1201
|
+
runCreateInstruction(command, previousSibling) {
|
|
1196
1202
|
const parentElement = this.findParent(command);
|
|
1197
|
-
const nextElement = this.findFollowingElement(command);
|
|
1198
1203
|
if (command.kind === 'component') {
|
|
1199
1204
|
const inputs = signal(command.inputs, /* @ts-ignore */
|
|
1200
1205
|
...(ngDevMode ? [{ debugName: "inputs" }] : /* istanbul ignore next */ []));
|
|
@@ -1204,7 +1209,7 @@ class ContentfulRichTextRendererComponent {
|
|
|
1204
1209
|
.map((declared) => inputBinding(declared.templateName, () => inputs()[declared.propName]));
|
|
1205
1210
|
const componentRef = this.viewContainerRef.createComponent(command.component, { bindings });
|
|
1206
1211
|
const rootNode = this.getComponentRootNode(componentRef);
|
|
1207
|
-
this.
|
|
1212
|
+
this.renderInsertAfter(rootNode, { parentElement, previousSibling });
|
|
1208
1213
|
this.executedCommandsCache.set(command.id, {
|
|
1209
1214
|
command,
|
|
1210
1215
|
componentRef,
|
|
@@ -1234,7 +1239,7 @@ class ContentfulRichTextRendererComponent {
|
|
|
1234
1239
|
this.renderer.appendChild(textContainer, textNode);
|
|
1235
1240
|
}
|
|
1236
1241
|
}
|
|
1237
|
-
this.
|
|
1242
|
+
this.renderInsertAfter(span, { parentElement, previousSibling });
|
|
1238
1243
|
this.executedCommandsCache.set(command.id, {
|
|
1239
1244
|
command,
|
|
1240
1245
|
element: span,
|
|
@@ -1245,7 +1250,7 @@ class ContentfulRichTextRendererComponent {
|
|
|
1245
1250
|
for (const [key, value] of Object.entries(command.attributes)) {
|
|
1246
1251
|
this.renderer.setAttribute(element, key, value);
|
|
1247
1252
|
}
|
|
1248
|
-
this.
|
|
1253
|
+
this.renderInsertAfter(element, { parentElement, previousSibling });
|
|
1249
1254
|
this.executedCommandsCache.set(command.id, {
|
|
1250
1255
|
command,
|
|
1251
1256
|
element,
|
|
@@ -1255,11 +1260,11 @@ class ContentfulRichTextRendererComponent {
|
|
|
1255
1260
|
runUpdateInstruction(command) {
|
|
1256
1261
|
const cached = this.executedCommandsCache.get(command.id);
|
|
1257
1262
|
if (!cached) {
|
|
1258
|
-
throw
|
|
1263
|
+
throw richTextRendererError('cached_command_not_found', { command });
|
|
1259
1264
|
}
|
|
1260
1265
|
if (command.kind === 'component') {
|
|
1261
1266
|
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
1262
|
-
throw
|
|
1267
|
+
throw richTextRendererError('cached_command_not_component', { command });
|
|
1263
1268
|
}
|
|
1264
1269
|
cached.inputs.set(command.inputs);
|
|
1265
1270
|
}
|
|
@@ -1268,23 +1273,21 @@ class ContentfulRichTextRendererComponent {
|
|
|
1268
1273
|
command,
|
|
1269
1274
|
});
|
|
1270
1275
|
}
|
|
1271
|
-
runMoveInstruction(command) {
|
|
1276
|
+
runMoveInstruction(command, previousSibling) {
|
|
1272
1277
|
const cached = this.executedCommandsCache.get(command.id);
|
|
1273
1278
|
if (!cached) {
|
|
1274
|
-
throw
|
|
1279
|
+
throw richTextRendererError('cached_command_not_found', { command });
|
|
1275
1280
|
}
|
|
1276
1281
|
if (command.kind === 'component') {
|
|
1277
1282
|
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
1278
|
-
throw
|
|
1283
|
+
throw richTextRendererError('cached_command_not_component', { command });
|
|
1279
1284
|
}
|
|
1280
1285
|
const rootNode = cached.element;
|
|
1281
1286
|
const oldParentElement = cached.element.parentElement;
|
|
1282
1287
|
if (oldParentElement) {
|
|
1283
1288
|
this.renderer.removeChild(oldParentElement, rootNode);
|
|
1284
1289
|
}
|
|
1285
|
-
|
|
1286
|
-
const nextElement = this.findFollowingElement(command);
|
|
1287
|
-
this.renderInsertOrAppend(rootNode, newParentElement, nextElement);
|
|
1290
|
+
this.renderInsertAfter(rootNode, { parentElement: this.findParent(command), previousSibling });
|
|
1288
1291
|
cached.inputs.set(command.inputs);
|
|
1289
1292
|
this.executedCommandsCache.set(command.id, {
|
|
1290
1293
|
...cached,
|
|
@@ -1298,11 +1301,11 @@ class ContentfulRichTextRendererComponent {
|
|
|
1298
1301
|
if (command.kind === 'htmlClose') {
|
|
1299
1302
|
return;
|
|
1300
1303
|
}
|
|
1301
|
-
throw
|
|
1304
|
+
throw richTextRendererError('cached_command_not_found', { command });
|
|
1302
1305
|
}
|
|
1303
1306
|
if (command.kind === 'component') {
|
|
1304
1307
|
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
1305
|
-
throw
|
|
1308
|
+
throw richTextRendererError('cached_command_not_component', { command });
|
|
1306
1309
|
}
|
|
1307
1310
|
cached.componentRef.destroy();
|
|
1308
1311
|
}
|
|
@@ -1317,57 +1320,17 @@ class ContentfulRichTextRendererComponent {
|
|
|
1317
1320
|
return componentRef.hostView.rootNodes[0];
|
|
1318
1321
|
}
|
|
1319
1322
|
findParent(command) {
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
if (command.nestingLevel === 0) {
|
|
1323
|
-
parentElement = hostElement;
|
|
1324
|
-
}
|
|
1325
|
-
else {
|
|
1326
|
-
const allCommands = this.renderCommands();
|
|
1327
|
-
let parentCommand = null;
|
|
1328
|
-
for (let i = command.index - 1; i >= 0; i--) {
|
|
1329
|
-
const cmd = allCommands[i];
|
|
1330
|
-
if (!cmd) {
|
|
1331
|
-
throw new Error('Command not found!');
|
|
1332
|
-
}
|
|
1333
|
-
if (cmd.nestingLevel === command.nestingLevel - 1 && cmd.kind === 'htmlOpen') {
|
|
1334
|
-
parentCommand = cmd;
|
|
1335
|
-
break;
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
if (!parentCommand) {
|
|
1339
|
-
throw richTextRendererError('text_parent_not_found', { command });
|
|
1340
|
-
}
|
|
1341
|
-
parentElement = this.executedCommandsCache.get(parentCommand.id)?.element;
|
|
1323
|
+
if (command.parentId === null) {
|
|
1324
|
+
return this.elementRef.nativeElement;
|
|
1342
1325
|
}
|
|
1326
|
+
const parentElement = this.executedCommandsCache.get(command.parentId)?.element;
|
|
1343
1327
|
if (!parentElement) {
|
|
1344
|
-
throw richTextRendererError('
|
|
1328
|
+
throw richTextRendererError('text_parent_not_found', { command });
|
|
1345
1329
|
}
|
|
1346
1330
|
return parentElement;
|
|
1347
1331
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
let nextElement;
|
|
1351
|
-
let nextDomPosition = Infinity;
|
|
1352
|
-
for (const cached of this.executedCommandsCache.values()) {
|
|
1353
|
-
if (cached.command.domPosition > command.domPosition &&
|
|
1354
|
-
cached.command.domPosition < nextDomPosition &&
|
|
1355
|
-
cached.command.nestingLevel === command.nestingLevel &&
|
|
1356
|
-
cached.element.parentElement === parentElement &&
|
|
1357
|
-
cached.command.id !== command.id) {
|
|
1358
|
-
nextElement = cached.element;
|
|
1359
|
-
nextDomPosition = cached.command.domPosition;
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
return nextElement;
|
|
1363
|
-
}
|
|
1364
|
-
renderInsertOrAppend(nodeToRender, parentElement, nextElement) {
|
|
1365
|
-
if (nextElement) {
|
|
1366
|
-
this.renderer.insertBefore(parentElement, nodeToRender, nextElement);
|
|
1367
|
-
}
|
|
1368
|
-
else {
|
|
1369
|
-
this.renderer.appendChild(parentElement, nodeToRender);
|
|
1370
|
-
}
|
|
1332
|
+
renderInsertAfter(node, { parentElement, previousSibling }) {
|
|
1333
|
+
this.renderer.insertBefore(parentElement, node, previousSibling ? previousSibling.nextSibling : parentElement.firstChild);
|
|
1371
1334
|
}
|
|
1372
1335
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulRichTextRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1373
1336
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.6", type: ContentfulRichTextRendererComponent, isStandalone: true, selector: "et-contentful-rich-text-renderer", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: true, transformFunction: null }, richTextPath: { classPropertyName: "richTextPath", publicName: "richTextPath", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "et-contentful-rich-text-renderer" }, ngImport: i0, template: ``, isInline: true, encapsulation: i0.ViewEncapsulation.None }); }
|
|
@@ -1401,5 +1364,5 @@ const isContentfulEntryType = (entry, type) => entry.sys.contentType.sys.id ===
|
|
|
1401
1364
|
* Generated bundle index. Do not edit.
|
|
1402
1365
|
*/
|
|
1403
1366
|
|
|
1404
|
-
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 };
|
|
1367
|
+
export { CONTENTFUL_CONFIG, CONTENTFUL_FILE_LABELS, ContentfulAudioComponent, ContentfulFileComponent, ContentfulImageComponent, ContentfulImports, ContentfulLinkComponent, ContentfulRichTextRendererComponent, ContentfulVideoComponent, DEFAULT_CONTENTFUL_FILE_LABELS, ET_CONTENTFUL_ANY_ENTRY_CONTENT_TYPE_SYS_ID, GQL_FRAGMENT_CONTENTFUL_ASSET, createContentfulConfig, createContentfulIncludeMap, generateContentfulImageSources, generateDefaultContentfulImageSource, injectContentfulConfig, injectContentfulFileLabels, isContentfulEntryType, isContentfulGqlAsset, isRichTextRootNode, marksToClass, marksToTags, parseContentfulImageSize, provideContentfulConfig, provideContentfulFileLabels, translateContentfulNodeTypeToHtmlTag };
|
|
1405
1368
|
//# sourceMappingURL=ethlete-contentful.mjs.map
|