@ethlete/contentful 4.0.0-next.1 → 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 +107 -0
- package/README.md +23 -4
- package/fesm2022/ethlete-contentful.mjs +842 -837
- package/fesm2022/ethlete-contentful.mjs.map +1 -1
- package/generators/generators.json +9 -0
- package/generators/migrate-to-contentful-v5/migration-scope.js +41 -0
- package/generators/migrate-to-contentful-v5/migration.js +256 -0
- package/generators/migrate-to-contentful-v5/schema.json +26 -0
- package/migrations.json +12 -0
- package/package.json +19 -13
- package/types/ethlete-contentful.d.ts +124 -229
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import { NgClass, DOCUMENT } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { input, computed, ViewEncapsulation,
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
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
|
-
import { RuntimeError, isObject, injectRenderer, getObjectProperty } from '@ethlete/core';
|
|
10
|
-
import { startWith, pairwise, tap } from 'rxjs';
|
|
3
|
+
import { input, computed, ViewEncapsulation, Component, inject, InjectionToken, numberAttribute, booleanAttribute, isDevMode, ViewContainerRef, ElementRef, effect, untracked, signal, reflectComponentType, inputBinding } from '@angular/core';
|
|
4
|
+
import { formatFileSize, normalizePictureSizes, PictureComponent } from '@ethlete/components';
|
|
5
|
+
import { defineLabels, toProvideFn, toInjectFn, toToken, injectUrl, RuntimeError, isObject, injectRenderer, getObjectProperty } from '@ethlete/core';
|
|
6
|
+
import { Router, RouterLink } from '@angular/router';
|
|
11
7
|
|
|
12
|
-
const GQL_FRAGMENT_CONTENTFUL_ASSET =
|
|
13
|
-
fragment
|
|
8
|
+
const GQL_FRAGMENT_CONTENTFUL_ASSET = `
|
|
9
|
+
fragment ContentfulAssetData on Asset {
|
|
14
10
|
sys {
|
|
15
11
|
id
|
|
16
12
|
}
|
|
17
13
|
url
|
|
18
14
|
title
|
|
15
|
+
fileName
|
|
19
16
|
width
|
|
20
17
|
height
|
|
21
18
|
description
|
|
@@ -25,43 +22,51 @@ const GQL_FRAGMENT_CONTENTFUL_ASSET = gql `
|
|
|
25
22
|
`;
|
|
26
23
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
24
|
const isContentfulGqlAsset = (asset) => {
|
|
28
|
-
return asset
|
|
25
|
+
return Boolean(asset && typeof asset === 'object' && asset.sys?.id && !('fields' in asset));
|
|
29
26
|
};
|
|
30
27
|
|
|
31
28
|
class ContentfulAudioComponent {
|
|
32
29
|
constructor() {
|
|
33
|
-
this.asset = input.required(
|
|
34
|
-
|
|
35
|
-
this.
|
|
36
|
-
|
|
30
|
+
this.asset = input.required(/* @ts-ignore */
|
|
31
|
+
...(ngDevMode ? [{ debugName: "asset" }] : /* istanbul ignore next */ []));
|
|
32
|
+
this.audioClass = input(null, /* @ts-ignore */
|
|
33
|
+
...(ngDevMode ? [{ debugName: "audioClass" }] : /* istanbul ignore next */ []));
|
|
34
|
+
this.figureClass = input(null, /* @ts-ignore */
|
|
35
|
+
...(ngDevMode ? [{ debugName: "figureClass" }] : /* istanbul ignore next */ []));
|
|
36
|
+
this.figcaptionClass = input(null, /* @ts-ignore */
|
|
37
|
+
...(ngDevMode ? [{ debugName: "figcaptionClass" }] : /* istanbul ignore next */ []));
|
|
37
38
|
this.data = computed(() => {
|
|
38
39
|
const asset = this.asset();
|
|
39
40
|
if (!asset) {
|
|
40
41
|
return null;
|
|
41
42
|
}
|
|
42
|
-
if (isContentfulGqlAsset(asset)) {
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
if (!isContentfulGqlAsset(asset) && asset.fields.file?.url) {
|
|
50
|
+
return {
|
|
51
|
+
url: asset.fields.file.url,
|
|
52
|
+
title: asset.fields.title || asset.fields.file.fileName || null,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}, /* @ts-ignore */
|
|
57
|
+
...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
53
58
|
}
|
|
54
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
55
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
59
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulAudioComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
60
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ContentfulAudioComponent, isStandalone: true, selector: "et-contentful-audio", inputs: { asset: { classPropertyName: "asset", publicName: "asset", isSignal: true, isRequired: true, transformFunction: null }, audioClass: { classPropertyName: "audioClass", publicName: "audioClass", isSignal: true, isRequired: false, transformFunction: null }, figureClass: { classPropertyName: "figureClass", publicName: "figureClass", isSignal: true, isRequired: false, transformFunction: null }, figcaptionClass: { classPropertyName: "figcaptionClass", publicName: "figcaptionClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "et-contentful-audio" }, ngImport: i0, template: `
|
|
56
61
|
@if (data(); as data) {
|
|
57
62
|
<figure [ngClass]="figureClass()">
|
|
58
63
|
<figcaption [ngClass]="figcaptionClass()">{{ data.title }}</figcaption>
|
|
59
|
-
<audio [ngClass]="audioClass()"
|
|
64
|
+
<audio [ngClass]="audioClass()" [src]="data.url" controls></audio>
|
|
60
65
|
</figure>
|
|
61
66
|
}
|
|
62
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }],
|
|
67
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
63
68
|
}
|
|
64
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
69
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulAudioComponent, decorators: [{
|
|
65
70
|
type: Component,
|
|
66
71
|
args: [{
|
|
67
72
|
selector: 'et-contentful-audio',
|
|
@@ -69,69 +74,311 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
|
|
|
69
74
|
@if (data(); as data) {
|
|
70
75
|
<figure [ngClass]="figureClass()">
|
|
71
76
|
<figcaption [ngClass]="figcaptionClass()">{{ data.title }}</figcaption>
|
|
72
|
-
<audio [ngClass]="audioClass()"
|
|
77
|
+
<audio [ngClass]="audioClass()" [src]="data.url" controls></audio>
|
|
73
78
|
</figure>
|
|
74
79
|
}
|
|
75
80
|
`,
|
|
76
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
77
81
|
encapsulation: ViewEncapsulation.None,
|
|
78
82
|
imports: [NgClass],
|
|
83
|
+
host: {
|
|
84
|
+
class: 'et-contentful-audio',
|
|
85
|
+
},
|
|
79
86
|
}]
|
|
80
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 }] }] } });
|
|
81
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
|
+
|
|
82
105
|
class ContentfulFileComponent {
|
|
83
106
|
constructor() {
|
|
84
|
-
this.
|
|
85
|
-
this.
|
|
107
|
+
this.labels = injectContentfulFileLabels();
|
|
108
|
+
this.asset = input.required(/* @ts-ignore */
|
|
109
|
+
...(ngDevMode ? [{ debugName: "asset" }] : /* istanbul ignore next */ []));
|
|
110
|
+
this.fileClass = input(null, /* @ts-ignore */
|
|
111
|
+
...(ngDevMode ? [{ debugName: "fileClass" }] : /* istanbul ignore next */ []));
|
|
86
112
|
this.data = computed(() => {
|
|
87
113
|
const asset = this.asset();
|
|
88
114
|
if (!asset) {
|
|
89
115
|
return null;
|
|
90
116
|
}
|
|
91
|
-
if (isContentfulGqlAsset(asset)) {
|
|
117
|
+
if (isContentfulGqlAsset(asset) && asset.url) {
|
|
92
118
|
return {
|
|
93
119
|
url: asset.url,
|
|
94
|
-
contentType: asset.contentType,
|
|
95
120
|
size: asset.size,
|
|
96
|
-
title: asset.title,
|
|
121
|
+
title: asset.title || asset.fileName || null,
|
|
97
122
|
};
|
|
98
123
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
124
|
+
if (!isContentfulGqlAsset(asset) && asset.fields.file?.url) {
|
|
125
|
+
return {
|
|
126
|
+
url: asset.fields.file.url,
|
|
127
|
+
size: asset.fields.file.details.size,
|
|
128
|
+
title: asset.fields.title || asset.fields.file.fileName || null,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}, /* @ts-ignore */
|
|
133
|
+
...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
106
134
|
}
|
|
107
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
108
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
135
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulFileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
136
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ContentfulFileComponent, isStandalone: true, selector: "et-contentful-file", inputs: { asset: { classPropertyName: "asset", publicName: "asset", isSignal: true, isRequired: true, transformFunction: null }, fileClass: { classPropertyName: "fileClass", publicName: "fileClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "et-contentful-file" }, ngImport: i0, template: `
|
|
109
137
|
@if (data(); as data) {
|
|
110
|
-
<a [href]="data.url" [ngClass]="fileClass()"
|
|
111
|
-
{{ data.title }}
|
|
138
|
+
<a [href]="data.url" [ngClass]="fileClass()" target="_blank" rel="noopener noreferrer">
|
|
139
|
+
{{ data.title }}
|
|
140
|
+
@if (data.size !== null) {
|
|
141
|
+
{{ labels().fileSize(data.size) }}
|
|
142
|
+
}
|
|
112
143
|
</a>
|
|
113
144
|
}
|
|
114
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }],
|
|
145
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
115
146
|
}
|
|
116
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
147
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulFileComponent, decorators: [{
|
|
117
148
|
type: Component,
|
|
118
149
|
args: [{
|
|
119
150
|
selector: 'et-contentful-file',
|
|
120
151
|
template: `
|
|
121
152
|
@if (data(); as data) {
|
|
122
|
-
<a [href]="data.url" [ngClass]="fileClass()"
|
|
123
|
-
{{ data.title }}
|
|
153
|
+
<a [href]="data.url" [ngClass]="fileClass()" target="_blank" rel="noopener noreferrer">
|
|
154
|
+
{{ data.title }}
|
|
155
|
+
@if (data.size !== null) {
|
|
156
|
+
{{ labels().fileSize(data.size) }}
|
|
157
|
+
}
|
|
124
158
|
</a>
|
|
125
159
|
}
|
|
126
160
|
`,
|
|
127
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
128
161
|
encapsulation: ViewEncapsulation.None,
|
|
129
162
|
imports: [NgClass],
|
|
163
|
+
host: {
|
|
164
|
+
class: 'et-contentful-file',
|
|
165
|
+
},
|
|
130
166
|
}]
|
|
131
167
|
}], propDecorators: { asset: [{ type: i0.Input, args: [{ isSignal: true, alias: "asset", required: true }] }], fileClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileClass", required: false }] }] } });
|
|
132
168
|
|
|
169
|
+
const parseWebUrl = (href) => {
|
|
170
|
+
if (!/^(?:https?:)?\/\//i.test(href)) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
return new URL(href.startsWith('//') ? 'https:' + href : href);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
const normalizeHostname = (host) => {
|
|
181
|
+
const value = host.trim().toLowerCase();
|
|
182
|
+
try {
|
|
183
|
+
return new URL(value.includes('://') ? value : 'https://' + value).hostname;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return value.split(':')[0] ?? value;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const matchesHostname = (hostname, configuredHost) => {
|
|
190
|
+
const normalizedHost = normalizeHostname(configuredHost);
|
|
191
|
+
return hostname === normalizedHost || hostname.endsWith('.' + normalizedHost);
|
|
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
|
+
|
|
200
|
+
class ContentfulLinkComponent {
|
|
201
|
+
constructor() {
|
|
202
|
+
this.document = inject(DOCUMENT);
|
|
203
|
+
this.router = inject(Router);
|
|
204
|
+
this.config = injectContentfulConfig();
|
|
205
|
+
this.url = injectUrl();
|
|
206
|
+
this.href = input.required(/* @ts-ignore */
|
|
207
|
+
...(ngDevMode ? [{ debugName: "href" }] : /* istanbul ignore next */ []));
|
|
208
|
+
this.text = input.required(/* @ts-ignore */
|
|
209
|
+
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
210
|
+
this.textClass = input('', /* @ts-ignore */
|
|
211
|
+
...(ngDevMode ? [{ debugName: "textClass" }] : /* istanbul ignore next */ []));
|
|
212
|
+
this.anchorClass = input('', /* @ts-ignore */
|
|
213
|
+
...(ngDevMode ? [{ debugName: "anchorClass" }] : /* istanbul ignore next */ []));
|
|
214
|
+
this.usesRouterLink = computed(() => {
|
|
215
|
+
const href = this.href();
|
|
216
|
+
const absoluteUrl = parseWebUrl(href);
|
|
217
|
+
if (absoluteUrl) {
|
|
218
|
+
return isInternalWebUrl(absoluteUrl, {
|
|
219
|
+
location: this.document.location,
|
|
220
|
+
internalHosts: this.config.internalHosts,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return !href.startsWith('#') && !/^[a-z][a-z\d+.-]*:/i.test(href);
|
|
224
|
+
}, /* @ts-ignore */
|
|
225
|
+
...(ngDevMode ? [{ debugName: "usesRouterLink" }] : /* istanbul ignore next */ []));
|
|
226
|
+
this.openInNewTab = computed(() => Boolean(parseWebUrl(this.href())) && !this.usesRouterLink(), /* @ts-ignore */
|
|
227
|
+
...(ngDevMode ? [{ debugName: "openInNewTab" }] : /* istanbul ignore next */ []));
|
|
228
|
+
this.internalPath = computed(() => {
|
|
229
|
+
const href = this.href();
|
|
230
|
+
const url = parseWebUrl(href);
|
|
231
|
+
if (url) {
|
|
232
|
+
return url.pathname + url.search + url.hash;
|
|
233
|
+
}
|
|
234
|
+
if (href.startsWith('/')) {
|
|
235
|
+
return href;
|
|
236
|
+
}
|
|
237
|
+
const resolved = new URL(href, 'https://contentful.invalid' + this.url());
|
|
238
|
+
return resolved.pathname + resolved.search + resolved.hash;
|
|
239
|
+
}, /* @ts-ignore */
|
|
240
|
+
...(ngDevMode ? [{ debugName: "internalPath" }] : /* istanbul ignore next */ []));
|
|
241
|
+
this.internalUrlTree = computed(() => this.router.parseUrl(this.internalPath()), /* @ts-ignore */
|
|
242
|
+
...(ngDevMode ? [{ debugName: "internalUrlTree" }] : /* istanbul ignore next */ []));
|
|
243
|
+
this.linkClass = computed(() => [this.anchorClass(), this.textClass()].filter(Boolean).join(' ') || null, /* @ts-ignore */
|
|
244
|
+
...(ngDevMode ? [{ debugName: "linkClass" }] : /* istanbul ignore next */ []));
|
|
245
|
+
}
|
|
246
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulLinkComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
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: `
|
|
248
|
+
@if (usesRouterLink()) {
|
|
249
|
+
<a [class]="linkClass()" [routerLink]="internalUrlTree()">{{ text() }}</a>
|
|
250
|
+
} @else {
|
|
251
|
+
<a
|
|
252
|
+
[class]="linkClass()"
|
|
253
|
+
[href]="href()"
|
|
254
|
+
[target]="openInNewTab() ? '_blank' : null"
|
|
255
|
+
[rel]="openInNewTab() ? 'noopener noreferrer' : null"
|
|
256
|
+
>{{ text() }}</a
|
|
257
|
+
>
|
|
258
|
+
}
|
|
259
|
+
`, isInline: true, styles: ["@layer components{.et-contentful-link{display:contents}}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
260
|
+
}
|
|
261
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulLinkComponent, decorators: [{
|
|
262
|
+
type: Component,
|
|
263
|
+
args: [{ selector: 'et-contentful-link', template: `
|
|
264
|
+
@if (usesRouterLink()) {
|
|
265
|
+
<a [class]="linkClass()" [routerLink]="internalUrlTree()">{{ text() }}</a>
|
|
266
|
+
} @else {
|
|
267
|
+
<a
|
|
268
|
+
[class]="linkClass()"
|
|
269
|
+
[href]="href()"
|
|
270
|
+
[target]="openInNewTab() ? '_blank' : null"
|
|
271
|
+
[rel]="openInNewTab() ? 'noopener noreferrer' : null"
|
|
272
|
+
>{{ text() }}</a
|
|
273
|
+
>
|
|
274
|
+
}
|
|
275
|
+
`, encapsulation: ViewEncapsulation.None, imports: [RouterLink], host: {
|
|
276
|
+
class: 'et-contentful-link',
|
|
277
|
+
}, styles: ["@layer components{.et-contentful-link{display:contents}}\n"] }]
|
|
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 }] }] } });
|
|
279
|
+
|
|
280
|
+
class ContentfulVideoComponent {
|
|
281
|
+
constructor() {
|
|
282
|
+
this.asset = input.required(/* @ts-ignore */
|
|
283
|
+
...(ngDevMode ? [{ debugName: "asset" }] : /* istanbul ignore next */ []));
|
|
284
|
+
this.videoClass = input(null, /* @ts-ignore */
|
|
285
|
+
...(ngDevMode ? [{ debugName: "videoClass" }] : /* istanbul ignore next */ []));
|
|
286
|
+
this.data = computed(() => {
|
|
287
|
+
const asset = this.asset();
|
|
288
|
+
if (!asset) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
if (isContentfulGqlAsset(asset) && asset.url) {
|
|
292
|
+
return {
|
|
293
|
+
url: asset.url,
|
|
294
|
+
contentType: asset.contentType,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
if (!isContentfulGqlAsset(asset) && asset.fields.file?.url) {
|
|
298
|
+
return {
|
|
299
|
+
url: asset.fields.file.url,
|
|
300
|
+
contentType: asset.fields.file.contentType,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
}, /* @ts-ignore */
|
|
305
|
+
...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
306
|
+
}
|
|
307
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulVideoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
308
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ContentfulVideoComponent, isStandalone: true, selector: "et-contentful-video", inputs: { asset: { classPropertyName: "asset", publicName: "asset", isSignal: true, isRequired: true, transformFunction: null }, videoClass: { classPropertyName: "videoClass", publicName: "videoClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "et-contentful-video" }, ngImport: i0, template: `
|
|
309
|
+
@if (data(); as data) {
|
|
310
|
+
<video [ngClass]="videoClass()" controls>
|
|
311
|
+
<source [src]="data.url" [type]="data.contentType" />
|
|
312
|
+
</video>
|
|
313
|
+
}
|
|
314
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
315
|
+
}
|
|
316
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulVideoComponent, decorators: [{
|
|
317
|
+
type: Component,
|
|
318
|
+
args: [{
|
|
319
|
+
selector: 'et-contentful-video',
|
|
320
|
+
template: `
|
|
321
|
+
@if (data(); as data) {
|
|
322
|
+
<video [ngClass]="videoClass()" controls>
|
|
323
|
+
<source [src]="data.url" [type]="data.contentType" />
|
|
324
|
+
</video>
|
|
325
|
+
}
|
|
326
|
+
`,
|
|
327
|
+
encapsulation: ViewEncapsulation.None,
|
|
328
|
+
imports: [NgClass],
|
|
329
|
+
host: {
|
|
330
|
+
class: 'et-contentful-video',
|
|
331
|
+
},
|
|
332
|
+
}]
|
|
333
|
+
}], propDecorators: { asset: [{ type: i0.Input, args: [{ isSignal: true, alias: "asset", required: true }] }], videoClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "videoClass", required: false }] }] } });
|
|
334
|
+
|
|
133
335
|
const CONTENTFUL_CONFIG = new InjectionToken('ContentfulConfig');
|
|
134
336
|
|
|
337
|
+
/**
|
|
338
|
+
* The config used when no `provideContentfulConfig()` is in scope. It declares no
|
|
339
|
+
* `components`, so embedded assets are skipped and hyperlinks render as plain anchors
|
|
340
|
+
* until a config is provided.
|
|
341
|
+
*/
|
|
342
|
+
const CONTENTFUL_FALLBACK_CONFIG = {
|
|
343
|
+
internalHosts: [],
|
|
344
|
+
components: {},
|
|
345
|
+
customComponents: {},
|
|
346
|
+
imageOptions: {
|
|
347
|
+
srcsetSizes: ['375w', '1280w', '1920w', '2560w'],
|
|
348
|
+
sizes: ['100vw'],
|
|
349
|
+
backgroundColor: null,
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
const createContentfulConfig = (config) => {
|
|
353
|
+
const defaults = {
|
|
354
|
+
internalHosts: [],
|
|
355
|
+
components: {
|
|
356
|
+
audio: ContentfulAudioComponent,
|
|
357
|
+
file: ContentfulFileComponent,
|
|
358
|
+
image: ContentfulImageComponent,
|
|
359
|
+
video: ContentfulVideoComponent,
|
|
360
|
+
link: ContentfulLinkComponent,
|
|
361
|
+
},
|
|
362
|
+
customComponents: {},
|
|
363
|
+
imageOptions: {
|
|
364
|
+
srcsetSizes: ['375w', '1280w', '1920w', '2560w'],
|
|
365
|
+
sizes: ['100vw'],
|
|
366
|
+
backgroundColor: null,
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
return {
|
|
370
|
+
...defaults,
|
|
371
|
+
...config,
|
|
372
|
+
components: { ...defaults.components, ...config?.components },
|
|
373
|
+
imageOptions: { ...defaults.imageOptions, ...config?.imageOptions },
|
|
374
|
+
};
|
|
375
|
+
};
|
|
376
|
+
/**
|
|
377
|
+
* Reads the contentful config from the current injector. Falls back to a config without
|
|
378
|
+
* any `components` when none was provided via `provideContentfulConfig()`.
|
|
379
|
+
*/
|
|
380
|
+
const injectContentfulConfig = () => inject(CONTENTFUL_CONFIG, { optional: true }) ?? CONTENTFUL_FALLBACK_CONFIG;
|
|
381
|
+
|
|
135
382
|
const generateDefaultContentfulImageSource = (data) => {
|
|
136
383
|
if (isContentfulGqlAsset(data)) {
|
|
137
384
|
if (!data.contentType || !data.url) {
|
|
@@ -145,15 +392,16 @@ const generateDefaultContentfulImageSource = (data) => {
|
|
|
145
392
|
srcset: data.url,
|
|
146
393
|
};
|
|
147
394
|
}
|
|
148
|
-
|
|
395
|
+
const file = data.fields.file;
|
|
396
|
+
if (!file?.contentType || !file.url) {
|
|
149
397
|
return {
|
|
150
398
|
type: '',
|
|
151
399
|
srcset: '',
|
|
152
400
|
};
|
|
153
401
|
}
|
|
154
402
|
return {
|
|
155
|
-
type:
|
|
156
|
-
srcset:
|
|
403
|
+
type: file.contentType,
|
|
404
|
+
srcset: file.url,
|
|
157
405
|
};
|
|
158
406
|
};
|
|
159
407
|
/**
|
|
@@ -165,41 +413,44 @@ const generateDefaultContentfulImageSource = (data) => {
|
|
|
165
413
|
* - `"400wx300h"` - 400px width and 300px height
|
|
166
414
|
**/
|
|
167
415
|
const parseContentfulImageSize = (size) => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
else if (size.includes('h')) {
|
|
176
|
-
height = size;
|
|
416
|
+
const widthAndHeight = /^(\d+)w?x(\d+)h?$/.exec(size);
|
|
417
|
+
if (widthAndHeight) {
|
|
418
|
+
return {
|
|
419
|
+
width: Number(widthAndHeight[1]),
|
|
420
|
+
height: Number(widthAndHeight[2]),
|
|
421
|
+
};
|
|
177
422
|
}
|
|
178
|
-
|
|
179
|
-
|
|
423
|
+
const height = /^(\d+)h$/.exec(size);
|
|
424
|
+
if (height) {
|
|
425
|
+
return { width: null, height: Number(height[1]) };
|
|
180
426
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
height: height ? parseInt(height, 10) : null,
|
|
184
|
-
};
|
|
427
|
+
const width = /^(\d+)w?$/.exec(size);
|
|
428
|
+
return width ? { width: Number(width[1]), height: null } : { width: null, height: null };
|
|
185
429
|
};
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
430
|
+
const SOURCE_TYPES = ['image/avif', 'image/webp'];
|
|
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;
|
|
439
|
+
const isGqlAsset = isContentfulGqlAsset(data);
|
|
440
|
+
const baseUrl = isGqlAsset ? data.url : data.fields.file?.url;
|
|
441
|
+
if (!baseUrl) {
|
|
189
442
|
return [];
|
|
190
443
|
}
|
|
444
|
+
const imageDimensions = isGqlAsset ? data : data.fields.file?.details.image;
|
|
191
445
|
const sources = [];
|
|
192
|
-
const SOURCE_TYPES = ['image/avif', 'image/webp', 'image/png', 'image/jpg'];
|
|
193
446
|
for (const type of SOURCE_TYPES) {
|
|
194
|
-
const baseUrl = isContentfulGqlAsset(assetData) ? assetData.url : assetData.fields.file.url;
|
|
195
447
|
const sourceSets = [];
|
|
196
448
|
const queryParams = [];
|
|
197
|
-
// Set the format (e.g. 'fm=webp')
|
|
198
449
|
queryParams.push(`fm=${type.split('/')[1]}`);
|
|
199
450
|
if (backgroundColor) {
|
|
200
451
|
queryParams.push(`bg=rgb:${backgroundColor}`);
|
|
201
452
|
}
|
|
202
|
-
if (quality !== null) {
|
|
453
|
+
if (quality !== null && Number.isFinite(quality)) {
|
|
203
454
|
queryParams.push(`q=${quality}`);
|
|
204
455
|
}
|
|
205
456
|
if (focusArea) {
|
|
@@ -208,8 +459,8 @@ const generateContentfulImageSources = (data, srcsetSizes, backgroundColor, qual
|
|
|
208
459
|
if (resizeBehavior) {
|
|
209
460
|
queryParams.push(`fit=${resizeBehavior}`);
|
|
210
461
|
}
|
|
211
|
-
if (srcsetSizes
|
|
212
|
-
const urlWithParams = `${baseUrl}
|
|
462
|
+
if (srcsetSizes.length) {
|
|
463
|
+
const urlWithParams = `${baseUrl}${baseUrl.includes('?') ? '&' : '?'}${queryParams.join('&')}`;
|
|
213
464
|
for (const size of srcsetSizes) {
|
|
214
465
|
const { width, height } = parseContentfulImageSize(size);
|
|
215
466
|
if (width && height) {
|
|
@@ -218,34 +469,42 @@ const generateContentfulImageSources = (data, srcsetSizes, backgroundColor, qual
|
|
|
218
469
|
else if (width) {
|
|
219
470
|
sourceSets.push(`${urlWithParams}&w=${width} ${width}w`);
|
|
220
471
|
}
|
|
221
|
-
else if (height) {
|
|
222
|
-
|
|
472
|
+
else if (height && imageDimensions?.width && imageDimensions.height) {
|
|
473
|
+
const derivedWidth = Math.round((height * imageDimensions.width) / imageDimensions.height);
|
|
474
|
+
sourceSets.push(`${urlWithParams}&w=${derivedWidth}&h=${height} ${derivedWidth}w`);
|
|
223
475
|
}
|
|
224
476
|
}
|
|
225
477
|
}
|
|
226
478
|
sources.push({
|
|
227
479
|
type,
|
|
228
|
-
srcset: sourceSets.length
|
|
480
|
+
srcset: sourceSets.length
|
|
481
|
+
? sourceSets.join(', ')
|
|
482
|
+
: `${baseUrl}${baseUrl.includes('?') ? '&' : '?'}${queryParams.join('&')}`,
|
|
229
483
|
});
|
|
230
484
|
}
|
|
231
485
|
return sources;
|
|
232
486
|
};
|
|
233
487
|
|
|
488
|
+
const optionalNumberAttribute = (value) => {
|
|
489
|
+
const transformed = numberAttribute(value);
|
|
490
|
+
return Number.isFinite(transformed) ? transformed : null;
|
|
491
|
+
};
|
|
234
492
|
class ContentfulImageComponent {
|
|
235
493
|
constructor() {
|
|
236
|
-
this.
|
|
237
|
-
this.asset = input.required(
|
|
238
|
-
|
|
239
|
-
this.
|
|
240
|
-
|
|
241
|
-
this.
|
|
242
|
-
|
|
243
|
-
this.
|
|
244
|
-
this.
|
|
245
|
-
|
|
246
|
-
this.
|
|
247
|
-
|
|
248
|
-
this.
|
|
494
|
+
this.contentfulConfig = injectContentfulConfig();
|
|
495
|
+
this.asset = input.required(/* @ts-ignore */
|
|
496
|
+
...(ngDevMode ? [{ debugName: "asset" }] : /* istanbul ignore next */ []));
|
|
497
|
+
this.backgroundColor = input(this.contentfulConfig.imageOptions.backgroundColor, /* @ts-ignore */
|
|
498
|
+
...(ngDevMode ? [{ debugName: "backgroundColor" }] : /* istanbul ignore next */ []));
|
|
499
|
+
this.srcsetSizes = input(this.contentfulConfig.imageOptions.srcsetSizes, /* @ts-ignore */
|
|
500
|
+
...(ngDevMode ? [{ debugName: "srcsetSizes" }] : /* istanbul ignore next */ []));
|
|
501
|
+
this.quality = input(null, { ...(ngDevMode ? { debugName: "quality" } : /* istanbul ignore next */ {}), transform: optionalNumberAttribute });
|
|
502
|
+
this.focusArea = input(null, /* @ts-ignore */
|
|
503
|
+
...(ngDevMode ? [{ debugName: "focusArea" }] : /* istanbul ignore next */ []));
|
|
504
|
+
this.resizeBehavior = input(null, /* @ts-ignore */
|
|
505
|
+
...(ngDevMode ? [{ debugName: "resizeBehavior" }] : /* istanbul ignore next */ []));
|
|
506
|
+
this.priority = input(false, { ...(ngDevMode ? { debugName: "priority" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
507
|
+
this.sizes = input(normalizePictureSizes(this.contentfulConfig.imageOptions.sizes), { ...(ngDevMode ? { debugName: "sizes" } : /* istanbul ignore next */ {}), transform: (v) => normalizePictureSizes(v) });
|
|
249
508
|
this.normalizedAsset = computed(() => {
|
|
250
509
|
const asset = this.asset();
|
|
251
510
|
if (isContentfulGqlAsset(asset)) {
|
|
@@ -264,420 +523,220 @@ class ContentfulImageComponent {
|
|
|
264
523
|
height: asset?.fields?.file?.details?.image?.height ?? null,
|
|
265
524
|
};
|
|
266
525
|
}
|
|
267
|
-
},
|
|
268
|
-
|
|
526
|
+
}, /* @ts-ignore */
|
|
527
|
+
...(ngDevMode ? [{ debugName: "normalizedAsset" }] : /* istanbul ignore next */ []));
|
|
528
|
+
this.sourcesValue = computed(() => {
|
|
269
529
|
const asset = this.asset();
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
530
|
+
const options = {
|
|
531
|
+
srcsetSizes: this.srcsetSizes(),
|
|
532
|
+
backgroundColor: this.backgroundColor(),
|
|
533
|
+
quality: this.quality(),
|
|
534
|
+
focusArea: this.focusArea(),
|
|
535
|
+
resizeBehavior: this.resizeBehavior(),
|
|
536
|
+
};
|
|
275
537
|
if (!asset) {
|
|
276
538
|
return [];
|
|
277
539
|
}
|
|
278
|
-
return generateContentfulImageSources(asset,
|
|
279
|
-
},
|
|
280
|
-
|
|
540
|
+
return generateContentfulImageSources(asset, options);
|
|
541
|
+
}, /* @ts-ignore */
|
|
542
|
+
...(ngDevMode ? [{ debugName: "sourcesValue" }] : /* istanbul ignore next */ []));
|
|
543
|
+
this.defaultSrcValue = computed(() => {
|
|
281
544
|
const asset = this.asset();
|
|
282
545
|
return asset ? generateDefaultContentfulImageSource(asset) : null;
|
|
283
|
-
},
|
|
546
|
+
}, /* @ts-ignore */
|
|
547
|
+
...(ngDevMode ? [{ debugName: "defaultSrcValue" }] : /* istanbul ignore next */ []));
|
|
284
548
|
}
|
|
285
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
286
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "
|
|
549
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulImageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
550
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.6", 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: `
|
|
287
551
|
<et-picture
|
|
288
|
-
[
|
|
289
|
-
[
|
|
290
|
-
[
|
|
291
|
-
[pictureClass]="pictureClass()"
|
|
292
|
-
[figcaptionClass]="figcaptionClass()"
|
|
293
|
-
[defaultSrc]="_defaultSrc()"
|
|
294
|
-
[alt]="normalizedAsset().alt ?? null"
|
|
552
|
+
[priority]="priority()"
|
|
553
|
+
[defaultSrc]="defaultSrcValue()"
|
|
554
|
+
[alt]="normalizedAsset().alt ?? ''"
|
|
295
555
|
[figcaption]="normalizedAsset().figcaption ?? null"
|
|
296
556
|
[width]="normalizedAsset().width ?? null"
|
|
297
557
|
[height]="normalizedAsset().height ?? null"
|
|
298
558
|
[sizes]="sizes()"
|
|
299
|
-
[sources]="
|
|
559
|
+
[sources]="sourcesValue()"
|
|
300
560
|
/>
|
|
301
|
-
`, isInline: true, dependencies: [{ kind: "component", type: PictureComponent, selector: "et-picture", inputs: ["sources", "
|
|
561
|
+
`, isInline: true, dependencies: [{ kind: "component", type: PictureComponent, selector: "et-picture", inputs: ["sources", "defaultSrc", "alt", "figcaption", "priority", "width", "height", "aspectRatio", "sizes", "fit"], outputs: ["imgLoad", "imgError"] }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
302
562
|
}
|
|
303
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
563
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulImageComponent, decorators: [{
|
|
304
564
|
type: Component,
|
|
305
565
|
args: [{
|
|
306
566
|
selector: 'et-contentful-image',
|
|
307
567
|
template: `
|
|
308
568
|
<et-picture
|
|
309
|
-
[
|
|
310
|
-
[
|
|
311
|
-
[
|
|
312
|
-
[pictureClass]="pictureClass()"
|
|
313
|
-
[figcaptionClass]="figcaptionClass()"
|
|
314
|
-
[defaultSrc]="_defaultSrc()"
|
|
315
|
-
[alt]="normalizedAsset().alt ?? null"
|
|
569
|
+
[priority]="priority()"
|
|
570
|
+
[defaultSrc]="defaultSrcValue()"
|
|
571
|
+
[alt]="normalizedAsset().alt ?? ''"
|
|
316
572
|
[figcaption]="normalizedAsset().figcaption ?? null"
|
|
317
573
|
[width]="normalizedAsset().width ?? null"
|
|
318
574
|
[height]="normalizedAsset().height ?? null"
|
|
319
575
|
[sizes]="sizes()"
|
|
320
|
-
[sources]="
|
|
576
|
+
[sources]="sourcesValue()"
|
|
321
577
|
/>
|
|
322
578
|
`,
|
|
323
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
324
579
|
encapsulation: ViewEncapsulation.None,
|
|
325
580
|
imports: [PictureComponent],
|
|
326
581
|
host: {
|
|
327
582
|
class: 'et-contentful-image',
|
|
328
583
|
},
|
|
329
584
|
}]
|
|
330
|
-
}], 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 }] }],
|
|
331
|
-
|
|
332
|
-
class ContentfulVideoComponent {
|
|
333
|
-
constructor() {
|
|
334
|
-
this.asset = input.required(...(ngDevMode ? [{ debugName: "asset" }] : []));
|
|
335
|
-
this.videoClass = input(null, ...(ngDevMode ? [{ debugName: "videoClass" }] : []));
|
|
336
|
-
this.data = computed(() => {
|
|
337
|
-
const asset = this.asset();
|
|
338
|
-
if (!asset) {
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
if (isContentfulGqlAsset(asset)) {
|
|
342
|
-
return {
|
|
343
|
-
url: asset.url,
|
|
344
|
-
contentType: asset.contentType,
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
return {
|
|
348
|
-
url: asset.fields.file.url,
|
|
349
|
-
contentType: asset.fields.file.contentType,
|
|
350
|
-
};
|
|
351
|
-
}, ...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
352
|
-
}
|
|
353
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ContentfulVideoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
354
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", 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: `
|
|
355
|
-
@if (data(); as data) {
|
|
356
|
-
<video [ngClass]="videoClass()" controls>
|
|
357
|
-
<source src="{{ data.url }}" type="{{ data.contentType }}" />
|
|
358
|
-
</video>
|
|
359
|
-
}
|
|
360
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
361
|
-
}
|
|
362
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ContentfulVideoComponent, decorators: [{
|
|
363
|
-
type: Component,
|
|
364
|
-
args: [{
|
|
365
|
-
selector: 'et-contentful-video',
|
|
366
|
-
template: `
|
|
367
|
-
@if (data(); as data) {
|
|
368
|
-
<video [ngClass]="videoClass()" controls>
|
|
369
|
-
<source src="{{ data.url }}" type="{{ data.contentType }}" />
|
|
370
|
-
</video>
|
|
371
|
-
}
|
|
372
|
-
`,
|
|
373
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
374
|
-
encapsulation: ViewEncapsulation.None,
|
|
375
|
-
imports: [NgClass],
|
|
376
|
-
}]
|
|
377
|
-
}], propDecorators: { asset: [{ type: i0.Input, args: [{ isSignal: true, alias: "asset", required: true }] }], videoClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "videoClass", required: false }] }] } });
|
|
585
|
+
}], 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 }] }] } });
|
|
378
586
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
587
|
+
/**
|
|
588
|
+
* The `BLOCKS` node type strings of `@contentful/rich-text-types`, as a plain literal map.
|
|
589
|
+
* Keep the values in sync with the enum - they are part of the Contentful Delivery API.
|
|
590
|
+
*
|
|
591
|
+
* These maps exist because `BLOCKS` and `INLINES` are TS enums: reading one in a *value* position
|
|
592
|
+
* anywhere in this lib emits a runtime import and costs every consumer ~2.4 kB gz. Use the enums for
|
|
593
|
+
* types only - the `contentful-deps` size golden fails if that slips.
|
|
594
|
+
*/
|
|
595
|
+
const CF_BLOCKS = {
|
|
596
|
+
DOCUMENT: 'document',
|
|
597
|
+
PARAGRAPH: 'paragraph',
|
|
598
|
+
HEADING_1: 'heading-1',
|
|
599
|
+
HEADING_2: 'heading-2',
|
|
600
|
+
HEADING_3: 'heading-3',
|
|
601
|
+
HEADING_4: 'heading-4',
|
|
602
|
+
HEADING_5: 'heading-5',
|
|
603
|
+
HEADING_6: 'heading-6',
|
|
604
|
+
OL_LIST: 'ordered-list',
|
|
605
|
+
UL_LIST: 'unordered-list',
|
|
606
|
+
LIST_ITEM: 'list-item',
|
|
607
|
+
HR: 'hr',
|
|
608
|
+
QUOTE: 'blockquote',
|
|
609
|
+
EMBEDDED_ENTRY: 'embedded-entry-block',
|
|
610
|
+
EMBEDDED_ASSET: 'embedded-asset-block',
|
|
611
|
+
EMBEDDED_RESOURCE: 'embedded-resource-block',
|
|
612
|
+
TABLE: 'table',
|
|
613
|
+
TABLE_ROW: 'table-row',
|
|
614
|
+
TABLE_CELL: 'table-cell',
|
|
615
|
+
TABLE_HEADER_CELL: 'table-header-cell',
|
|
616
|
+
};
|
|
617
|
+
/**
|
|
618
|
+
* The `INLINES` node type strings of `@contentful/rich-text-types`, as a plain literal map.
|
|
619
|
+
* Keep the values in sync with the enum - they are part of the Contentful Delivery API.
|
|
620
|
+
*/
|
|
621
|
+
const CF_INLINES = {
|
|
622
|
+
ASSET_HYPERLINK: 'asset-hyperlink',
|
|
623
|
+
EMBEDDED_ENTRY: 'embedded-entry-inline',
|
|
624
|
+
EMBEDDED_RESOURCE: 'embedded-resource-inline',
|
|
625
|
+
ENTRY_HYPERLINK: 'entry-hyperlink',
|
|
626
|
+
HYPERLINK: 'hyperlink',
|
|
627
|
+
RESOURCE_HYPERLINK: 'resource-hyperlink',
|
|
404
628
|
};
|
|
405
|
-
class ContentfulLinkComponent {
|
|
406
|
-
constructor() {
|
|
407
|
-
this._document = inject(DOCUMENT);
|
|
408
|
-
this._config = inject(CONTENTFUL_CONFIG, { optional: true }) ?? createContentfulConfig();
|
|
409
|
-
this.href = input.required(...(ngDevMode ? [{ debugName: "href" }] : []));
|
|
410
|
-
this.text = input.required(...(ngDevMode ? [{ debugName: "text" }] : []));
|
|
411
|
-
this.textClass = input('', ...(ngDevMode ? [{ debugName: "textClass" }] : []));
|
|
412
|
-
this.isExternal = computed(() => {
|
|
413
|
-
const href = this.href();
|
|
414
|
-
const isAbsolute = href.startsWith('http://') || href.startsWith('https://') || href.startsWith('//');
|
|
415
|
-
if (!isAbsolute)
|
|
416
|
-
return false;
|
|
417
|
-
try {
|
|
418
|
-
const parsed = new URL(href.startsWith('//') ? `https:${href}` : href);
|
|
419
|
-
const internalHosts = [this._document.location.host, ...this._config.internalHosts];
|
|
420
|
-
return !internalHosts.includes(parsed.host);
|
|
421
|
-
}
|
|
422
|
-
catch {
|
|
423
|
-
return true;
|
|
424
|
-
}
|
|
425
|
-
}, ...(ngDevMode ? [{ debugName: "isExternal" }] : []));
|
|
426
|
-
this.openInNewTab = computed(() => {
|
|
427
|
-
if (!this.isExternal())
|
|
428
|
-
return false;
|
|
429
|
-
const href = this.href();
|
|
430
|
-
try {
|
|
431
|
-
const parsed = new URL(href.startsWith('//') ? `https:${href}` : href);
|
|
432
|
-
const internalPrimaryDomains = [
|
|
433
|
-
getPrimaryDomain(this._document.location.host),
|
|
434
|
-
...this._config.internalHosts.map(getPrimaryDomain),
|
|
435
|
-
];
|
|
436
|
-
return !internalPrimaryDomains.includes(getPrimaryDomain(parsed.host));
|
|
437
|
-
}
|
|
438
|
-
catch {
|
|
439
|
-
return true;
|
|
440
|
-
}
|
|
441
|
-
}, ...(ngDevMode ? [{ debugName: "openInNewTab" }] : []));
|
|
442
|
-
this.internalPath = computed(() => {
|
|
443
|
-
const href = this.href();
|
|
444
|
-
if (!href.startsWith('http://') && !href.startsWith('https://') && !href.startsWith('//')) {
|
|
445
|
-
return href;
|
|
446
|
-
}
|
|
447
|
-
try {
|
|
448
|
-
const url = new URL(href.startsWith('//') ? `https:${href}` : href);
|
|
449
|
-
return url.pathname + url.search + url.hash;
|
|
450
|
-
}
|
|
451
|
-
catch {
|
|
452
|
-
return href;
|
|
453
|
-
}
|
|
454
|
-
}, ...(ngDevMode ? [{ debugName: "internalPath" }] : []));
|
|
455
|
-
this.linkClass = computed(() => {
|
|
456
|
-
const base = 'et-contentful-rich-text-default-element et-contentful-rich-text-default-a';
|
|
457
|
-
const extra = this.textClass();
|
|
458
|
-
return extra ? `${base} ${extra}` : base;
|
|
459
|
-
}, ...(ngDevMode ? [{ debugName: "linkClass" }] : []));
|
|
460
|
-
}
|
|
461
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ContentfulLinkComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
462
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", 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: `
|
|
463
|
-
@if (isExternal()) {
|
|
464
|
-
<a
|
|
465
|
-
[class]="linkClass()"
|
|
466
|
-
[href]="href()"
|
|
467
|
-
[target]="openInNewTab() ? '_blank' : null"
|
|
468
|
-
[rel]="openInNewTab() ? 'noopener noreferrer' : null"
|
|
469
|
-
>{{ text() }}</a
|
|
470
|
-
>
|
|
471
|
-
} @else {
|
|
472
|
-
<a [class]="linkClass()" [routerLink]="internalPath()">{{ text() }}</a>
|
|
473
|
-
}
|
|
474
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
475
|
-
}
|
|
476
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ContentfulLinkComponent, decorators: [{
|
|
477
|
-
type: Component,
|
|
478
|
-
args: [{
|
|
479
|
-
selector: 'et-contentful-link',
|
|
480
|
-
template: `
|
|
481
|
-
@if (isExternal()) {
|
|
482
|
-
<a
|
|
483
|
-
[class]="linkClass()"
|
|
484
|
-
[href]="href()"
|
|
485
|
-
[target]="openInNewTab() ? '_blank' : null"
|
|
486
|
-
[rel]="openInNewTab() ? 'noopener noreferrer' : null"
|
|
487
|
-
>{{ text() }}</a
|
|
488
|
-
>
|
|
489
|
-
} @else {
|
|
490
|
-
<a [class]="linkClass()" [routerLink]="internalPath()">{{ text() }}</a>
|
|
491
|
-
}
|
|
492
|
-
`,
|
|
493
|
-
host: {
|
|
494
|
-
style: 'display: contents',
|
|
495
|
-
},
|
|
496
|
-
imports: [RouterLink],
|
|
497
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
498
|
-
encapsulation: ViewEncapsulation.None,
|
|
499
|
-
}]
|
|
500
|
-
}], 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 }] }] } });
|
|
501
629
|
|
|
502
630
|
const RICH_TEXT_RENDERER_ERRORS = {
|
|
503
631
|
rich_text_undefined: 'The property value given at richTextPath is undefined. Use the richTextPath input to specify the path to the rich text object.',
|
|
504
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".',
|
|
505
633
|
asset_id_not_found: 'The asset ID was not found. This node is not supported.',
|
|
506
634
|
entry_id_not_found: 'The entry ID was not found. This node is not supported.',
|
|
507
|
-
asset_not_found: 'The asset was not found. This node is not supported.',
|
|
508
|
-
entry_not_found: 'The entry was not found. This node is not supported.',
|
|
509
|
-
custom_component_not_found: 'No custom component found for entry type. Please provide one for this type.',
|
|
510
635
|
text_parent_not_found: 'The parent node is not found. This structure is not supported.',
|
|
511
|
-
|
|
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.',
|
|
638
|
+
};
|
|
639
|
+
const RICH_TEXT_RENDERER_ERROR_CODES = {
|
|
640
|
+
rich_text_undefined: 0,
|
|
641
|
+
rich_text_wrong_type: 1,
|
|
642
|
+
asset_id_not_found: 2,
|
|
643
|
+
entry_id_not_found: 3,
|
|
644
|
+
text_parent_not_found: 7,
|
|
645
|
+
cached_command_not_found: 9,
|
|
646
|
+
cached_command_not_component: 10,
|
|
512
647
|
};
|
|
513
|
-
const
|
|
514
|
-
acc[key] = index;
|
|
515
|
-
return acc;
|
|
516
|
-
}, {});
|
|
517
|
-
const richTextRendererError = (code, devOnly, data) => {
|
|
648
|
+
const richTextRendererError = (code, data) => {
|
|
518
649
|
const message = `<et-contentful-rich-text-renderer>: ${RICH_TEXT_RENDERER_ERRORS[code]}`;
|
|
519
|
-
throw new RuntimeError(RICH_TEXT_RENDERER_ERROR_CODES[code], message,
|
|
650
|
+
throw new RuntimeError(RICH_TEXT_RENDERER_ERROR_CODES[code], message, data);
|
|
520
651
|
};
|
|
521
652
|
|
|
522
653
|
const isRichTextRootNode = (node) => {
|
|
523
654
|
return isObject(node) && 'nodeType' in node && node['nodeType'] === 'document';
|
|
524
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
|
+
*/
|
|
525
680
|
const translateContentfulNodeTypeToHtmlTag = (nodeType) => {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
case BLOCKS.HEADING_4:
|
|
534
|
-
return 'h4';
|
|
535
|
-
case BLOCKS.HEADING_5:
|
|
536
|
-
return 'h5';
|
|
537
|
-
case BLOCKS.HEADING_6:
|
|
538
|
-
return 'h6';
|
|
539
|
-
case BLOCKS.PARAGRAPH:
|
|
540
|
-
return 'p';
|
|
541
|
-
case BLOCKS.UL_LIST:
|
|
542
|
-
return 'ul';
|
|
543
|
-
case BLOCKS.OL_LIST:
|
|
544
|
-
return 'ol';
|
|
545
|
-
case BLOCKS.LIST_ITEM:
|
|
546
|
-
return 'li';
|
|
547
|
-
case BLOCKS.HR:
|
|
548
|
-
return 'hr';
|
|
549
|
-
case BLOCKS.QUOTE:
|
|
550
|
-
return 'blockquote';
|
|
551
|
-
case BLOCKS.TABLE:
|
|
552
|
-
return 'table';
|
|
553
|
-
case BLOCKS.TABLE_ROW:
|
|
554
|
-
return 'tr';
|
|
555
|
-
case BLOCKS.TABLE_CELL:
|
|
556
|
-
return 'td';
|
|
557
|
-
case BLOCKS.TABLE_HEADER_CELL:
|
|
558
|
-
return 'th';
|
|
559
|
-
case BLOCKS.EMBEDDED_ASSET:
|
|
560
|
-
return 'div';
|
|
561
|
-
case INLINES.ENTRY_HYPERLINK:
|
|
562
|
-
return 'a';
|
|
563
|
-
case INLINES.ASSET_HYPERLINK:
|
|
564
|
-
return 'a';
|
|
565
|
-
case 'text':
|
|
566
|
-
return 'span';
|
|
567
|
-
// Will be ignored by the renderer
|
|
568
|
-
case 'document':
|
|
569
|
-
return 'div';
|
|
570
|
-
case BLOCKS.EMBEDDED_ENTRY:
|
|
571
|
-
return 'div';
|
|
572
|
-
case INLINES.EMBEDDED_ENTRY:
|
|
573
|
-
return 'div';
|
|
574
|
-
default:
|
|
575
|
-
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}>.`);
|
|
576
688
|
}
|
|
689
|
+
return fallback;
|
|
577
690
|
};
|
|
578
691
|
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
692
|
+
const DEFAULT_ANCHOR_CLASS = 'et-contentful-rich-text-default-element et-contentful-rich-text-default-a';
|
|
693
|
+
const MARK_TAG_MAP = {
|
|
694
|
+
bold: 'strong',
|
|
695
|
+
italic: 'em',
|
|
696
|
+
underline: 'u',
|
|
697
|
+
code: 'code',
|
|
698
|
+
strikethrough: 's',
|
|
699
|
+
subscript: 'sub',
|
|
700
|
+
superscript: 'sup',
|
|
584
701
|
};
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const HTML_OPEN_RENDER_COMMAND_POSITION = {
|
|
592
|
-
ATTRIBUTES: 4,
|
|
593
|
-
TAG_NAME: 5,
|
|
594
|
-
ELEMENT_ID: 6,
|
|
595
|
-
};
|
|
596
|
-
const HTML_CLOSE_RENDER_COMMAND_POSITION = {
|
|
597
|
-
TAG_NAME: 4,
|
|
598
|
-
ELEMENT_ID: 5,
|
|
599
|
-
};
|
|
600
|
-
const TEXT_RENDER_COMMAND_POSITION = {
|
|
601
|
-
ATTRIBUTES: 4,
|
|
602
|
-
TEXT: 5,
|
|
603
|
-
TEXT_ID: 6,
|
|
604
|
-
};
|
|
605
|
-
const COMPONENT_RENDER_COMMAND_POSITION = {
|
|
606
|
-
ATTRIBUTES: 4,
|
|
607
|
-
COMPONENT: 5,
|
|
608
|
-
INPUTS: 6,
|
|
609
|
-
COMPONENT_ID: 7,
|
|
610
|
-
};
|
|
611
|
-
const RENDER_INSTRUCTION_POSITION = {
|
|
612
|
-
TYPE: 0,
|
|
613
|
-
COMMAND: 1,
|
|
614
|
-
};
|
|
615
|
-
const RENDER_INSTRUCTION_TYPE = {
|
|
616
|
-
CREATE: 0,
|
|
617
|
-
UPDATE: 1,
|
|
618
|
-
DELETE: 2,
|
|
619
|
-
MOVE: 3,
|
|
620
|
-
};
|
|
621
|
-
const MARK_TAILWIND_MAP = {
|
|
622
|
-
bold: 'font-bold',
|
|
623
|
-
italic: 'italic',
|
|
624
|
-
underline: 'underline',
|
|
625
|
-
code: 'font-mono',
|
|
626
|
-
};
|
|
627
|
-
const marksToClass = (marks) => {
|
|
628
|
-
const classes = [];
|
|
702
|
+
/**
|
|
703
|
+
* Translates contentful text marks into the semantic html elements the marked text is
|
|
704
|
+
* wrapped in, outermost first. Unknown marks are skipped (with a dev-mode warning).
|
|
705
|
+
*/
|
|
706
|
+
const marksToTags = (marks) => {
|
|
707
|
+
const tags = [];
|
|
629
708
|
for (const mark of marks) {
|
|
630
|
-
|
|
631
|
-
if (!
|
|
632
|
-
|
|
633
|
-
|
|
709
|
+
const tag = MARK_TAG_MAP[mark.type];
|
|
710
|
+
if (!tag) {
|
|
711
|
+
if (isDevMode()) {
|
|
712
|
+
console.warn(`No element found for mark type "${mark.type}"! The mark is ignored.`, mark);
|
|
713
|
+
}
|
|
714
|
+
continue;
|
|
634
715
|
}
|
|
635
|
-
|
|
716
|
+
tags.push(tag);
|
|
636
717
|
}
|
|
637
|
-
return
|
|
638
|
-
};
|
|
639
|
-
const isHtmlOpenRenderCommand = (command) => {
|
|
640
|
-
return command[RENDER_COMMAND_POSITION.TYPE] === RENDER_COMMAND_TYPE.HTML_OPEN;
|
|
641
|
-
};
|
|
642
|
-
const isHtmlCloseRenderCommand = (command) => {
|
|
643
|
-
return command[RENDER_COMMAND_POSITION.TYPE] === RENDER_COMMAND_TYPE.HTML_CLOSE;
|
|
718
|
+
return tags;
|
|
644
719
|
};
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const
|
|
652
|
-
if (
|
|
653
|
-
return
|
|
654
|
-
}
|
|
655
|
-
else if (isHtmlCloseRenderCommand(command)) {
|
|
656
|
-
return command[HTML_CLOSE_RENDER_COMMAND_POSITION.ELEMENT_ID];
|
|
720
|
+
/**
|
|
721
|
+
* The classes marked text inside a hyperlink is rendered with, since the link component
|
|
722
|
+
* receives its text as a plain string.
|
|
723
|
+
*/
|
|
724
|
+
const marksToClass = (marks) => marks.map((mark) => `et-contentful-rich-text-mark-${mark.type}`).join(' ');
|
|
725
|
+
const LINK_COMPONENT_TYPE = '$$$_et-link';
|
|
726
|
+
const normalizeHref = (href) => {
|
|
727
|
+
if (!href.trim()) {
|
|
728
|
+
return null;
|
|
657
729
|
}
|
|
658
|
-
|
|
659
|
-
|
|
730
|
+
try {
|
|
731
|
+
const url = new URL(href, 'https://contentful.invalid');
|
|
732
|
+
return ['http:', 'https:', 'mailto:', 'tel:', 'ftp:'].includes(url.protocol) ? href : null;
|
|
660
733
|
}
|
|
661
|
-
|
|
662
|
-
return
|
|
734
|
+
catch {
|
|
735
|
+
return null;
|
|
663
736
|
}
|
|
664
737
|
};
|
|
665
|
-
const CLASS_ATTR = 'class';
|
|
666
|
-
const DEFAULT_COMPONENT_TYPES = {
|
|
667
|
-
IMAGE: '$$$_et-image',
|
|
668
|
-
VIDEO: '$$$_et-video',
|
|
669
|
-
AUDIO: '$$$_et-audio',
|
|
670
|
-
FILE: '$$$_et-file',
|
|
671
|
-
LINK: '$$$_et-link',
|
|
672
|
-
};
|
|
673
738
|
const isExecutedComponentCommandCacheItem = (cache) => {
|
|
674
|
-
return
|
|
675
|
-
};
|
|
676
|
-
const isExecutedHtmlOrTextCommandCacheItem = (cache) => {
|
|
677
|
-
return isHtmlCloseRenderCommand(cache.command) || isHtmlOpenRenderCommand(cache.command);
|
|
678
|
-
};
|
|
679
|
-
const isExecutedTextCommandCacheItem = (cache) => {
|
|
680
|
-
return isTextRenderCommand(cache.command);
|
|
739
|
+
return cache.command.kind === 'component';
|
|
681
740
|
};
|
|
682
741
|
const ET_CONTENTFUL_ANY_ENTRY_CONTENT_TYPE_SYS_ID = '$$$_et-contentful-any-entry-content-type-sys-id';
|
|
683
742
|
/**
|
|
@@ -692,7 +751,7 @@ const createContentfulIncludeMap = (config) => {
|
|
|
692
751
|
const entry = entryMap.get(id);
|
|
693
752
|
if (!entry) {
|
|
694
753
|
if (isDevMode()) {
|
|
695
|
-
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 });
|
|
696
755
|
}
|
|
697
756
|
return null;
|
|
698
757
|
}
|
|
@@ -733,40 +792,31 @@ const createContentfulIncludeMap = (config) => {
|
|
|
733
792
|
};
|
|
734
793
|
class ContentfulRichTextRendererComponent {
|
|
735
794
|
constructor() {
|
|
736
|
-
this.
|
|
737
|
-
this.
|
|
738
|
-
this.
|
|
739
|
-
this.
|
|
740
|
-
this.
|
|
741
|
-
/**
|
|
742
|
-
* A cache for all executed commands that are not deleted.
|
|
743
|
-
* This is used to keep track of all rendered elements and components.
|
|
744
|
-
* The key is the render command element ID.
|
|
745
|
-
*/
|
|
746
|
-
this._executedCommandsCache = new Map();
|
|
795
|
+
this.viewContainerRef = inject(ViewContainerRef);
|
|
796
|
+
this.renderer = injectRenderer();
|
|
797
|
+
this.elementRef = inject(ElementRef);
|
|
798
|
+
this.config = injectContentfulConfig();
|
|
799
|
+
this.document = inject(DOCUMENT);
|
|
747
800
|
/**
|
|
748
801
|
* The contentful response gotten via their REST api.
|
|
749
802
|
* @see https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/entries/entries-collection
|
|
750
803
|
*/
|
|
751
|
-
this.content = input.required(
|
|
804
|
+
this.content = input.required(/* @ts-ignore */
|
|
805
|
+
...(ngDevMode ? [{ debugName: "content" }] : /* istanbul ignore next */ []));
|
|
752
806
|
/**
|
|
753
807
|
* The path to where the rich text field is inside the contentful response. Dot and array notation is allowed.
|
|
754
808
|
* @example "items[0].fields.html"
|
|
755
809
|
*/
|
|
756
|
-
this.richTextPath = input.required(
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
* This is useful for looking up assets and entries that are referenced in the rich text without having to use loops.
|
|
760
|
-
*/
|
|
810
|
+
this.richTextPath = input.required(/* @ts-ignore */
|
|
811
|
+
...(ngDevMode ? [{ debugName: "richTextPath" }] : /* istanbul ignore next */ []));
|
|
812
|
+
this.executedCommandsCache = new Map();
|
|
761
813
|
this.contentIncludesMap = computed(() => {
|
|
762
814
|
const content = this.content();
|
|
763
|
-
const assets = content?.includes
|
|
764
|
-
const entries = content?.includes
|
|
815
|
+
const assets = content?.includes?.Asset;
|
|
816
|
+
const entries = content?.includes?.Entry;
|
|
765
817
|
return createContentfulIncludeMap({ assets: assets ?? [], entries: entries ?? [] });
|
|
766
|
-
},
|
|
767
|
-
|
|
768
|
-
* The rich text data that should be rendered.
|
|
769
|
-
*/
|
|
818
|
+
}, /* @ts-ignore */
|
|
819
|
+
...(ngDevMode ? [{ debugName: "contentIncludesMap" }] : /* istanbul ignore next */ []));
|
|
770
820
|
this.richTextData = computed(() => {
|
|
771
821
|
const content = this.content();
|
|
772
822
|
const richTextPath = this.richTextPath();
|
|
@@ -774,159 +824,145 @@ class ContentfulRichTextRendererComponent {
|
|
|
774
824
|
return null;
|
|
775
825
|
}
|
|
776
826
|
const richText = getObjectProperty(content, richTextPath);
|
|
827
|
+
if (richText === null || richText === undefined) {
|
|
828
|
+
return null;
|
|
829
|
+
}
|
|
777
830
|
if (!isObject(richText)) {
|
|
778
|
-
throw richTextRendererError('rich_text_undefined',
|
|
831
|
+
throw richTextRendererError('rich_text_undefined', { content, richTextPath });
|
|
779
832
|
}
|
|
780
833
|
if (!isRichTextRootNode(richText)) {
|
|
781
|
-
throw richTextRendererError('rich_text_wrong_type',
|
|
834
|
+
throw richTextRendererError('rich_text_wrong_type', { content, richTextPath });
|
|
782
835
|
}
|
|
783
836
|
return richText;
|
|
784
|
-
},
|
|
837
|
+
}, /* @ts-ignore */
|
|
838
|
+
...(ngDevMode ? [{ debugName: "richTextData" }] : /* istanbul ignore next */ []));
|
|
785
839
|
this.renderCommands = computed(() => {
|
|
786
840
|
const richTextData = this.richTextData();
|
|
787
841
|
if (!richTextData) {
|
|
788
842
|
return [];
|
|
789
843
|
}
|
|
790
|
-
return this.
|
|
791
|
-
},
|
|
792
|
-
|
|
793
|
-
this.previousRenderCommandMap = computed(() => {
|
|
794
|
-
const [prevCommands] = this.renderCommandHistory();
|
|
795
|
-
const map = new Map();
|
|
796
|
-
for (const command of prevCommands) {
|
|
797
|
-
map.set(getRenderCommandId(command), command);
|
|
798
|
-
}
|
|
799
|
-
return map;
|
|
800
|
-
}, ...(ngDevMode ? [{ debugName: "previousRenderCommandMap" }] : []));
|
|
844
|
+
return this.createRenderCommands(richTextData);
|
|
845
|
+
}, /* @ts-ignore */
|
|
846
|
+
...(ngDevMode ? [{ debugName: "renderCommands" }] : /* istanbul ignore next */ []));
|
|
801
847
|
this.renderInstructions = computed(() => {
|
|
802
848
|
const commands = this.renderCommands();
|
|
803
|
-
const previousRenderCommandMap = new Map(this.
|
|
849
|
+
const previousRenderCommandMap = new Map([...this.executedCommandsCache.entries()].map(([id, item]) => [id, item.command]));
|
|
804
850
|
const instructions = [];
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
851
|
+
const preserved = new Set();
|
|
852
|
+
const needsReattach = new Set();
|
|
853
|
+
const markTagsEqual = (a, b) => a.length === b.length && a.every((tag, index) => tag === b[index]);
|
|
854
|
+
const attributesEqual = (a, b) => {
|
|
855
|
+
const aKeys = Object.keys(a);
|
|
856
|
+
return aKeys.length === Object.keys(b).length && aKeys.every((key) => a[key] === b[key]);
|
|
857
|
+
};
|
|
858
|
+
for (const command of commands) {
|
|
859
|
+
if (command.kind === 'htmlClose') {
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
const previous = previousRenderCommandMap.get(command.id);
|
|
863
|
+
if (!previous || previous.kind !== command.kind) {
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
if (previous.kind === 'component' && command.kind === 'component' && previous.component !== command.component) {
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
const parentPreserved = previous.parentId === command.parentId && (command.parentId === null || preserved.has(command.parentId));
|
|
870
|
+
if (command.kind === 'component') {
|
|
871
|
+
preserved.add(command.id);
|
|
872
|
+
const samePlace = parentPreserved &&
|
|
873
|
+
previous.nestingLevel === command.nestingLevel &&
|
|
874
|
+
previous.domPosition === command.domPosition;
|
|
875
|
+
if (!samePlace) {
|
|
876
|
+
needsReattach.add(command.id);
|
|
811
877
|
}
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
const sameOutput = previous.kind === command.kind &&
|
|
881
|
+
(previous.kind !== 'htmlOpen' || previous.tagName === command.tagName) &&
|
|
882
|
+
(previous.kind !== 'text' ||
|
|
883
|
+
(previous.text === command.text &&
|
|
884
|
+
markTagsEqual(previous.markTags, command.markTags)));
|
|
885
|
+
if (parentPreserved &&
|
|
886
|
+
sameOutput &&
|
|
887
|
+
previous.nestingLevel === command.nestingLevel &&
|
|
888
|
+
previous.domPosition === command.domPosition &&
|
|
889
|
+
attributesEqual(previous.attributes, command.attributes)) {
|
|
890
|
+
preserved.add(command.id);
|
|
812
891
|
}
|
|
813
|
-
instructions.push([RENDER_INSTRUCTION_TYPE.DELETE, command]);
|
|
814
|
-
// remove from map to mark as used
|
|
815
|
-
previousRenderCommandMap.delete(getRenderCommandId(command));
|
|
816
892
|
}
|
|
817
|
-
// Find the indexes of the commands remaining in the previousRenderCommandMap in the new commands array.
|
|
818
|
-
const lastComponentIndexes = [];
|
|
819
893
|
for (const [id, command] of previousRenderCommandMap) {
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
if (index === -1) {
|
|
823
|
-
throw new Error('Command not found!');
|
|
894
|
+
if (command.kind !== 'htmlClose' && !preserved.has(id)) {
|
|
895
|
+
instructions.push({ type: 'delete', command });
|
|
824
896
|
}
|
|
825
|
-
lastComponentIndexes.push({
|
|
826
|
-
newIndex: index,
|
|
827
|
-
prevIndex: command[RENDER_COMMAND_POSITION.INDEX],
|
|
828
|
-
command: newCommand,
|
|
829
|
-
});
|
|
830
897
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
// for indexes that are in order, create an update instruction
|
|
835
|
-
let moveCheckIndex = 0;
|
|
836
|
-
while (moveCheckIndex < lastComponentIndexes.length - 1) {
|
|
837
|
-
const moveCurr = lastComponentIndexes[moveCheckIndex];
|
|
838
|
-
const moveNext = lastComponentIndexes[moveCheckIndex + 1];
|
|
839
|
-
if (!moveCurr || !moveNext) {
|
|
840
|
-
break;
|
|
841
|
-
}
|
|
842
|
-
if (moveCurr.prevIndex > moveNext.prevIndex) {
|
|
843
|
-
lastComponentIndexes.splice(moveCheckIndex + 1, 1);
|
|
844
|
-
instructions.push([RENDER_INSTRUCTION_TYPE.MOVE, moveNext.command]);
|
|
898
|
+
for (const command of commands) {
|
|
899
|
+
if (command.kind === 'htmlClose') {
|
|
900
|
+
continue;
|
|
845
901
|
}
|
|
846
|
-
|
|
847
|
-
instructions.push(
|
|
848
|
-
moveCheckIndex++;
|
|
902
|
+
if (!preserved.has(command.id)) {
|
|
903
|
+
instructions.push({ type: 'create', command });
|
|
849
904
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
const id = getRenderCommandId(command);
|
|
853
|
-
if (isComponentRenderCommand(command)) {
|
|
854
|
-
const previousCommand = previousRenderCommandMap.get(id);
|
|
855
|
-
if (!previousCommand) {
|
|
856
|
-
// create
|
|
857
|
-
instructions.push([RENDER_INSTRUCTION_TYPE.CREATE, command]);
|
|
858
|
-
}
|
|
905
|
+
else if (command.kind === 'component' && needsReattach.has(command.id)) {
|
|
906
|
+
instructions.push({ type: 'move', command });
|
|
859
907
|
}
|
|
860
908
|
else {
|
|
861
|
-
|
|
862
|
-
instructions.push([RENDER_INSTRUCTION_TYPE.CREATE, command]);
|
|
909
|
+
instructions.push({ type: 'update', command });
|
|
863
910
|
}
|
|
864
911
|
}
|
|
865
912
|
return instructions;
|
|
866
|
-
},
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
this.
|
|
870
|
-
|
|
871
|
-
|
|
913
|
+
}, /* @ts-ignore */
|
|
914
|
+
...(ngDevMode ? [{ debugName: "renderInstructions" }] : /* istanbul ignore next */ []));
|
|
915
|
+
effect(() => {
|
|
916
|
+
const instructions = this.renderInstructions();
|
|
917
|
+
untracked(() => this.execInstructions(instructions));
|
|
918
|
+
});
|
|
872
919
|
}
|
|
873
|
-
|
|
874
|
-
/** List of all render commands */
|
|
920
|
+
createRenderCommands(richTextData) {
|
|
875
921
|
const rootCommands = [];
|
|
876
|
-
/** Counter for generating unique html element IDs. */
|
|
877
922
|
let elementOpenId = 0;
|
|
878
|
-
/** Counter for generating unique html element IDs. */
|
|
879
923
|
let elementCloseId = 0;
|
|
880
|
-
/** Counter for generating unique component IDs. */
|
|
881
924
|
const componentIdMap = new Map();
|
|
882
|
-
/** The nesting level of the current node. */
|
|
883
925
|
let nestingLevel = 0;
|
|
884
|
-
/** The position (index) of the current node inside the parent node. */
|
|
885
926
|
let domPosition = 0;
|
|
886
927
|
let textId = 0;
|
|
887
|
-
|
|
928
|
+
const openElementIds = [];
|
|
888
929
|
const traverse = (node) => {
|
|
889
930
|
switch (node.nodeType) {
|
|
890
931
|
case 'text': {
|
|
891
|
-
// Render media
|
|
892
|
-
const type = RENDER_COMMAND_TYPE.TEXT;
|
|
893
932
|
const text = node.value;
|
|
894
933
|
if (!text)
|
|
895
934
|
break;
|
|
896
935
|
const attributes = {
|
|
897
936
|
class: 'et-contentful-rich-text-default-element et-contentful-rich-text-default-span',
|
|
898
937
|
};
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
const command = [type, nestingLevel, domPosition, commandIndex++, attributes, text, id];
|
|
910
|
-
rootCommands.push(command);
|
|
938
|
+
rootCommands.push({
|
|
939
|
+
kind: 'text',
|
|
940
|
+
nestingLevel,
|
|
941
|
+
domPosition,
|
|
942
|
+
parentId: openElementIds.at(-1) ?? null,
|
|
943
|
+
attributes,
|
|
944
|
+
markTags: node.marks.length ? marksToTags(node.marks) : [],
|
|
945
|
+
text,
|
|
946
|
+
id: 't' + textId++,
|
|
947
|
+
});
|
|
911
948
|
domPosition++;
|
|
912
949
|
break;
|
|
913
950
|
}
|
|
914
|
-
case
|
|
915
|
-
// Render media
|
|
916
|
-
const type = RENDER_COMMAND_TYPE.COMPONENT;
|
|
951
|
+
case CF_BLOCKS.EMBEDDED_ASSET: {
|
|
917
952
|
const assetId = node.data['target']?.sys?.id;
|
|
918
953
|
if (!assetId) {
|
|
919
|
-
throw richTextRendererError('asset_id_not_found',
|
|
954
|
+
throw richTextRendererError('asset_id_not_found', { node });
|
|
920
955
|
}
|
|
921
956
|
const asset = this.contentIncludesMap().getAsset(assetId);
|
|
922
957
|
if (!asset) {
|
|
923
|
-
|
|
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;
|
|
924
962
|
}
|
|
925
|
-
const contentType = asset.fields.file
|
|
926
|
-
const assetComponents = this.
|
|
927
|
-
|
|
928
|
-
// In this case, we can assume that the asset is missing due to user error.
|
|
929
|
-
const isMissing = !contentType && !asset.fields.file.url;
|
|
963
|
+
const contentType = asset.fields.file?.contentType;
|
|
964
|
+
const assetComponents = this.config.components;
|
|
965
|
+
const isMissing = !contentType && !asset.fields.file?.url;
|
|
930
966
|
if (isMissing) {
|
|
931
967
|
if (isDevMode()) {
|
|
932
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 });
|
|
@@ -943,146 +979,189 @@ class ContentfulRichTextRendererComponent {
|
|
|
943
979
|
: isAudio
|
|
944
980
|
? assetComponents.audio
|
|
945
981
|
: assetComponents.file;
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
asset,
|
|
959
|
-
};
|
|
960
|
-
const command = [
|
|
961
|
-
type,
|
|
982
|
+
if (!component) {
|
|
983
|
+
if (isDevMode()) {
|
|
984
|
+
console.warn('No component registered for this embedded asset! The asset will be skipped. Provide one via provideContentfulConfig({ components: … }).', { asset });
|
|
985
|
+
}
|
|
986
|
+
break;
|
|
987
|
+
}
|
|
988
|
+
const occurrenceKey = 'asset:' + assetId;
|
|
989
|
+
const occurrence = (componentIdMap.get(occurrenceKey) ?? -1) + 1;
|
|
990
|
+
componentIdMap.set(occurrenceKey, occurrence);
|
|
991
|
+
const id = occurrence === 0 ? occurrenceKey : `${occurrenceKey}:${occurrence}`;
|
|
992
|
+
rootCommands.push({
|
|
993
|
+
kind: 'component',
|
|
962
994
|
nestingLevel,
|
|
963
995
|
domPosition,
|
|
964
|
-
|
|
965
|
-
attributes,
|
|
996
|
+
parentId: openElementIds.at(-1) ?? null,
|
|
966
997
|
component,
|
|
967
|
-
inputs,
|
|
998
|
+
inputs: { asset },
|
|
968
999
|
id,
|
|
969
|
-
|
|
970
|
-
rootCommands.push(command);
|
|
1000
|
+
});
|
|
971
1001
|
domPosition++;
|
|
972
1002
|
break;
|
|
973
1003
|
}
|
|
974
|
-
case
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
let
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1004
|
+
case CF_INLINES.HYPERLINK:
|
|
1005
|
+
case CF_INLINES.ASSET_HYPERLINK:
|
|
1006
|
+
case CF_INLINES.ENTRY_HYPERLINK: {
|
|
1007
|
+
let href = null;
|
|
1008
|
+
if (node.nodeType === CF_INLINES.HYPERLINK) {
|
|
1009
|
+
href = normalizeHref(node.data['uri'] ?? '');
|
|
1010
|
+
}
|
|
1011
|
+
else if (node.nodeType === CF_INLINES.ASSET_HYPERLINK) {
|
|
1012
|
+
const assetId = node.data['target']?.sys?.id;
|
|
1013
|
+
const asset = assetId ? this.contentIncludesMap().getAsset(assetId) : null;
|
|
1014
|
+
const url = asset?.fields.file?.url;
|
|
1015
|
+
href = url ? normalizeHref(url) : null;
|
|
1016
|
+
}
|
|
1017
|
+
const linkTexts = node.content.filter((child) => child.nodeType === 'text' && Boolean(child.value));
|
|
1018
|
+
const linkText = linkTexts.map((child) => child.value).join('');
|
|
1019
|
+
const linkComponent = href ? this.config.components.link : null;
|
|
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';
|
|
983
1025
|
}
|
|
1026
|
+
rootCommands.push({
|
|
1027
|
+
kind: 'htmlOpen',
|
|
1028
|
+
nestingLevel,
|
|
1029
|
+
domPosition,
|
|
1030
|
+
parentId: openElementIds.at(-1) ?? null,
|
|
1031
|
+
attributes,
|
|
1032
|
+
tagName: 'a',
|
|
1033
|
+
id: 'e-o' + elementOpenId++,
|
|
1034
|
+
});
|
|
1035
|
+
const anchorDomPosition = domPosition;
|
|
1036
|
+
openElementIds.push('e-o' + (elementOpenId - 1));
|
|
1037
|
+
nestingLevel++;
|
|
1038
|
+
domPosition = 0;
|
|
1039
|
+
for (const child of linkTexts) {
|
|
1040
|
+
traverse(child);
|
|
1041
|
+
}
|
|
1042
|
+
openElementIds.pop();
|
|
1043
|
+
nestingLevel--;
|
|
1044
|
+
domPosition = anchorDomPosition;
|
|
1045
|
+
rootCommands.push({
|
|
1046
|
+
kind: 'htmlClose',
|
|
1047
|
+
nestingLevel,
|
|
1048
|
+
domPosition,
|
|
1049
|
+
parentId: openElementIds.at(-1) ?? null,
|
|
1050
|
+
tagName: 'a',
|
|
1051
|
+
id: 'e-c' + elementCloseId++,
|
|
1052
|
+
});
|
|
1053
|
+
domPosition++;
|
|
1054
|
+
break;
|
|
984
1055
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1056
|
+
if (!href) {
|
|
1057
|
+
for (const child of linkTexts) {
|
|
1058
|
+
traverse(child);
|
|
1059
|
+
}
|
|
1060
|
+
break;
|
|
1061
|
+
}
|
|
1062
|
+
if (!linkComponent) {
|
|
1063
|
+
break;
|
|
1064
|
+
}
|
|
1065
|
+
const sharedMarks = linkTexts[0]?.marks.filter((mark) => linkTexts.every((child) => child.marks.some((other) => other.type === mark.type))) ?? [];
|
|
1066
|
+
const textClass = marksToClass(sharedMarks);
|
|
1067
|
+
let linkComponentId = componentIdMap.get(LINK_COMPONENT_TYPE) ?? -1;
|
|
1068
|
+
const linkId = LINK_COMPONENT_TYPE + ++linkComponentId;
|
|
1069
|
+
componentIdMap.set(LINK_COMPONENT_TYPE, linkComponentId);
|
|
1070
|
+
rootCommands.push({
|
|
1071
|
+
kind: 'component',
|
|
991
1072
|
nestingLevel,
|
|
992
1073
|
domPosition,
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
];
|
|
999
|
-
rootCommands.push(linkCommand);
|
|
1074
|
+
parentId: openElementIds.at(-1) ?? null,
|
|
1075
|
+
component: linkComponent,
|
|
1076
|
+
inputs: { href, text: linkText, textClass, anchorClass: DEFAULT_ANCHOR_CLASS },
|
|
1077
|
+
id: linkId,
|
|
1078
|
+
});
|
|
1000
1079
|
domPosition++;
|
|
1001
1080
|
break;
|
|
1002
1081
|
}
|
|
1003
|
-
case
|
|
1004
|
-
case
|
|
1005
|
-
// Render component
|
|
1006
|
-
const type = RENDER_COMMAND_TYPE.COMPONENT;
|
|
1082
|
+
case CF_BLOCKS.EMBEDDED_ENTRY:
|
|
1083
|
+
case CF_INLINES.EMBEDDED_ENTRY: {
|
|
1007
1084
|
const entryId = node.data['target']?.sys?.id;
|
|
1008
1085
|
if (!entryId) {
|
|
1009
|
-
throw richTextRendererError('entry_id_not_found',
|
|
1086
|
+
throw richTextRendererError('entry_id_not_found', { node });
|
|
1010
1087
|
}
|
|
1011
1088
|
const entry = this.contentIncludesMap().getEntry(entryId, ET_CONTENTFUL_ANY_ENTRY_CONTENT_TYPE_SYS_ID);
|
|
1012
1089
|
if (!entry) {
|
|
1013
|
-
|
|
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;
|
|
1014
1094
|
}
|
|
1015
1095
|
const componentType = entry.sys.contentType.sys.id;
|
|
1016
|
-
const component = this.
|
|
1096
|
+
const component = this.config.customComponents[componentType];
|
|
1017
1097
|
if (!component) {
|
|
1018
|
-
|
|
1019
|
-
componentType,
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
});
|
|
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;
|
|
1023
1102
|
}
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
metadata: entry.metadata,
|
|
1031
|
-
sys: entry.sys,
|
|
1032
|
-
includes: this.contentIncludesMap(),
|
|
1033
|
-
};
|
|
1034
|
-
const command = [
|
|
1035
|
-
type,
|
|
1103
|
+
const occurrenceKey = 'entry:' + entryId;
|
|
1104
|
+
const occurrence = (componentIdMap.get(occurrenceKey) ?? -1) + 1;
|
|
1105
|
+
componentIdMap.set(occurrenceKey, occurrence);
|
|
1106
|
+
const id = occurrence === 0 ? occurrenceKey : `${occurrenceKey}:${occurrence}`;
|
|
1107
|
+
rootCommands.push({
|
|
1108
|
+
kind: 'component',
|
|
1036
1109
|
nestingLevel,
|
|
1037
1110
|
domPosition,
|
|
1038
|
-
|
|
1039
|
-
attributes,
|
|
1111
|
+
parentId: openElementIds.at(-1) ?? null,
|
|
1040
1112
|
component,
|
|
1041
|
-
inputs
|
|
1113
|
+
inputs: {
|
|
1114
|
+
fields: entry.fields,
|
|
1115
|
+
metadata: entry.metadata,
|
|
1116
|
+
sys: entry.sys,
|
|
1117
|
+
includes: this.contentIncludesMap(),
|
|
1118
|
+
},
|
|
1042
1119
|
id,
|
|
1043
|
-
|
|
1044
|
-
rootCommands.push(command);
|
|
1120
|
+
});
|
|
1045
1121
|
domPosition++;
|
|
1046
1122
|
break;
|
|
1047
1123
|
}
|
|
1048
1124
|
default: {
|
|
1049
|
-
const type = RENDER_COMMAND_TYPE.HTML_OPEN;
|
|
1050
1125
|
const tag = translateContentfulNodeTypeToHtmlTag(node.nodeType);
|
|
1051
1126
|
const attributes = {
|
|
1052
1127
|
class: `et-contentful-rich-text-default-element et-contentful-rich-text-default-${tag}`,
|
|
1053
1128
|
};
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1129
|
+
rootCommands.push({
|
|
1130
|
+
kind: 'htmlOpen',
|
|
1131
|
+
nestingLevel,
|
|
1132
|
+
domPosition,
|
|
1133
|
+
parentId: openElementIds.at(-1) ?? null,
|
|
1134
|
+
attributes,
|
|
1135
|
+
tagName: tag,
|
|
1136
|
+
id: 'e-o' + elementOpenId++,
|
|
1137
|
+
});
|
|
1057
1138
|
const domPositionAtThisLevel = domPosition;
|
|
1058
|
-
|
|
1139
|
+
openElementIds.push('e-o' + (elementOpenId - 1));
|
|
1140
|
+
nestingLevel++;
|
|
1141
|
+
domPosition = 0;
|
|
1059
1142
|
for (const child of node.content) {
|
|
1060
|
-
domPosition = 0;
|
|
1061
|
-
nestingLevel++;
|
|
1062
1143
|
traverse(child);
|
|
1063
|
-
nestingLevel--;
|
|
1064
|
-
domPosition = domPositionAtThisLevel;
|
|
1065
1144
|
}
|
|
1145
|
+
openElementIds.pop();
|
|
1146
|
+
nestingLevel--;
|
|
1147
|
+
domPosition = domPositionAtThisLevel;
|
|
1066
1148
|
const lastCommand = rootCommands[rootCommands.length - 1];
|
|
1067
|
-
if (lastCommand?.
|
|
1068
|
-
lastCommand
|
|
1069
|
-
lastCommand
|
|
1070
|
-
|
|
1149
|
+
if (lastCommand?.kind === 'htmlOpen' &&
|
|
1150
|
+
lastCommand.tagName !== 'td' &&
|
|
1151
|
+
lastCommand.tagName !== 'th' &&
|
|
1152
|
+
lastCommand.tagName !== 'hr') {
|
|
1071
1153
|
rootCommands.pop();
|
|
1072
1154
|
elementOpenId--;
|
|
1073
|
-
commandIndex--;
|
|
1074
1155
|
}
|
|
1075
1156
|
else {
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
RENDER_COMMAND_TYPE.HTML_CLOSE,
|
|
1157
|
+
rootCommands.push({
|
|
1158
|
+
kind: 'htmlClose',
|
|
1079
1159
|
nestingLevel,
|
|
1080
1160
|
domPosition,
|
|
1081
|
-
|
|
1082
|
-
tag,
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
rootCommands.push(closeCommand);
|
|
1161
|
+
parentId: openElementIds.at(-1) ?? null,
|
|
1162
|
+
tagName: tag,
|
|
1163
|
+
id: 'e-c' + elementCloseId++,
|
|
1164
|
+
});
|
|
1086
1165
|
domPosition++;
|
|
1087
1166
|
}
|
|
1088
1167
|
break;
|
|
@@ -1094,246 +1173,174 @@ class ContentfulRichTextRendererComponent {
|
|
|
1094
1173
|
}
|
|
1095
1174
|
return rootCommands;
|
|
1096
1175
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1176
|
+
execInstructions(instructions) {
|
|
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;
|
|
1101
1184
|
switch (type) {
|
|
1102
|
-
case
|
|
1103
|
-
this.
|
|
1104
|
-
break;
|
|
1105
|
-
case RENDER_INSTRUCTION_TYPE.UPDATE:
|
|
1106
|
-
this._runUpdateInstruction(command);
|
|
1185
|
+
case 'create':
|
|
1186
|
+
this.runCreateInstruction(command, previousSibling);
|
|
1107
1187
|
break;
|
|
1108
|
-
case
|
|
1109
|
-
this.
|
|
1188
|
+
case 'update':
|
|
1189
|
+
this.runUpdateInstruction(command);
|
|
1110
1190
|
break;
|
|
1111
|
-
case
|
|
1112
|
-
this.
|
|
1191
|
+
case 'move':
|
|
1192
|
+
this.runMoveInstruction(command, previousSibling);
|
|
1113
1193
|
break;
|
|
1114
1194
|
}
|
|
1195
|
+
const element = this.executedCommandsCache.get(command.id)?.element;
|
|
1196
|
+
if (element) {
|
|
1197
|
+
lastPlacedChild.set(command.parentId, element);
|
|
1198
|
+
}
|
|
1115
1199
|
}
|
|
1116
1200
|
}
|
|
1117
|
-
|
|
1118
|
-
const
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
this.
|
|
1201
|
+
runCreateInstruction(command, previousSibling) {
|
|
1202
|
+
const parentElement = this.findParent(command);
|
|
1203
|
+
if (command.kind === 'component') {
|
|
1204
|
+
const inputs = signal(command.inputs, /* @ts-ignore */
|
|
1205
|
+
...(ngDevMode ? [{ debugName: "inputs" }] : /* istanbul ignore next */ []));
|
|
1206
|
+
const declaredInputs = reflectComponentType(command.component)?.inputs ?? [];
|
|
1207
|
+
const bindings = declaredInputs
|
|
1208
|
+
.filter((declared) => declared.propName in command.inputs)
|
|
1209
|
+
.map((declared) => inputBinding(declared.templateName, () => inputs()[declared.propName]));
|
|
1210
|
+
const componentRef = this.viewContainerRef.createComponent(command.component, { bindings });
|
|
1211
|
+
const rootNode = this.getComponentRootNode(componentRef);
|
|
1212
|
+
this.renderInsertAfter(rootNode, { parentElement, previousSibling });
|
|
1213
|
+
this.executedCommandsCache.set(command.id, {
|
|
1127
1214
|
command,
|
|
1128
1215
|
componentRef,
|
|
1216
|
+
inputs,
|
|
1129
1217
|
element: rootNode,
|
|
1130
1218
|
});
|
|
1131
1219
|
}
|
|
1132
|
-
else if (
|
|
1133
|
-
const
|
|
1134
|
-
const
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
const textNodes = [];
|
|
1138
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
1139
|
-
this._renderer.setAttribute(span, key, value);
|
|
1220
|
+
else if (command.kind === 'text') {
|
|
1221
|
+
const span = this.renderer.createElement('span');
|
|
1222
|
+
const textSplitInLineBreaks = command.text.split('\n');
|
|
1223
|
+
for (const [key, value] of Object.entries(command.attributes)) {
|
|
1224
|
+
this.renderer.setAttribute(span, key, value);
|
|
1140
1225
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
this.
|
|
1226
|
+
let textContainer = span;
|
|
1227
|
+
for (const tag of command.markTags) {
|
|
1228
|
+
const markElement = this.renderer.createElement(tag);
|
|
1229
|
+
this.renderer.appendChild(textContainer, markElement);
|
|
1230
|
+
textContainer = markElement;
|
|
1144
1231
|
}
|
|
1145
1232
|
for (const [textPartIndex, textPart] of textSplitInLineBreaks.entries()) {
|
|
1146
1233
|
if (textPartIndex > 0) {
|
|
1147
|
-
const brNode = this.
|
|
1148
|
-
this.
|
|
1234
|
+
const brNode = this.renderer.createElement('br');
|
|
1235
|
+
this.renderer.appendChild(textContainer, brNode);
|
|
1236
|
+
}
|
|
1237
|
+
if (textPart) {
|
|
1238
|
+
const textNode = this.renderer.createText(textPart);
|
|
1239
|
+
this.renderer.appendChild(textContainer, textNode);
|
|
1149
1240
|
}
|
|
1150
|
-
const textNode = this._renderer.createText(textPart);
|
|
1151
|
-
this._renderer.appendChild(span, textNode);
|
|
1152
|
-
textNodes.push(textNode);
|
|
1153
1241
|
}
|
|
1154
|
-
this.
|
|
1155
|
-
this.
|
|
1242
|
+
this.renderInsertAfter(span, { parentElement, previousSibling });
|
|
1243
|
+
this.executedCommandsCache.set(command.id, {
|
|
1156
1244
|
command,
|
|
1157
1245
|
element: span,
|
|
1158
|
-
textNode: textNodes,
|
|
1159
1246
|
});
|
|
1160
1247
|
}
|
|
1161
|
-
else if (
|
|
1162
|
-
const
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1165
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
1166
|
-
this._renderer.setAttribute(element, key, value);
|
|
1248
|
+
else if (command.kind === 'htmlOpen') {
|
|
1249
|
+
const element = this.renderer.createElement(command.tagName);
|
|
1250
|
+
for (const [key, value] of Object.entries(command.attributes)) {
|
|
1251
|
+
this.renderer.setAttribute(element, key, value);
|
|
1167
1252
|
}
|
|
1168
|
-
this.
|
|
1169
|
-
this.
|
|
1253
|
+
this.renderInsertAfter(element, { parentElement, previousSibling });
|
|
1254
|
+
this.executedCommandsCache.set(command.id, {
|
|
1170
1255
|
command,
|
|
1171
1256
|
element,
|
|
1172
1257
|
});
|
|
1173
1258
|
}
|
|
1174
1259
|
}
|
|
1175
|
-
|
|
1176
|
-
const
|
|
1177
|
-
const cached = this._executedCommandsCache.get(id);
|
|
1260
|
+
runUpdateInstruction(command) {
|
|
1261
|
+
const cached = this.executedCommandsCache.get(command.id);
|
|
1178
1262
|
if (!cached) {
|
|
1179
|
-
throw
|
|
1263
|
+
throw richTextRendererError('cached_command_not_found', { command });
|
|
1180
1264
|
}
|
|
1181
|
-
if (
|
|
1265
|
+
if (command.kind === 'component') {
|
|
1182
1266
|
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
1183
|
-
throw
|
|
1184
|
-
}
|
|
1185
|
-
const componentRef = cached.componentRef;
|
|
1186
|
-
if (!componentRef) {
|
|
1187
|
-
throw new Error('Component ref not found!');
|
|
1267
|
+
throw richTextRendererError('cached_command_not_component', { command });
|
|
1188
1268
|
}
|
|
1189
|
-
|
|
1269
|
+
cached.inputs.set(command.inputs);
|
|
1190
1270
|
}
|
|
1191
|
-
this.
|
|
1271
|
+
this.executedCommandsCache.set(command.id, {
|
|
1192
1272
|
...cached,
|
|
1193
|
-
command
|
|
1273
|
+
command,
|
|
1194
1274
|
});
|
|
1195
1275
|
}
|
|
1196
|
-
|
|
1197
|
-
const
|
|
1198
|
-
const cached = this._executedCommandsCache.get(id);
|
|
1276
|
+
runMoveInstruction(command, previousSibling) {
|
|
1277
|
+
const cached = this.executedCommandsCache.get(command.id);
|
|
1199
1278
|
if (!cached) {
|
|
1200
|
-
throw
|
|
1279
|
+
throw richTextRendererError('cached_command_not_found', { command });
|
|
1201
1280
|
}
|
|
1202
|
-
if (
|
|
1281
|
+
if (command.kind === 'component') {
|
|
1203
1282
|
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
1204
|
-
throw
|
|
1205
|
-
}
|
|
1206
|
-
const componentRef = cached.componentRef;
|
|
1207
|
-
if (!componentRef) {
|
|
1208
|
-
throw new Error('Component ref not found!');
|
|
1283
|
+
throw richTextRendererError('cached_command_not_component', { command });
|
|
1209
1284
|
}
|
|
1210
1285
|
const rootNode = cached.element;
|
|
1211
1286
|
const oldParentElement = cached.element.parentElement;
|
|
1212
1287
|
if (oldParentElement) {
|
|
1213
|
-
this.
|
|
1288
|
+
this.renderer.removeChild(oldParentElement, rootNode);
|
|
1214
1289
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
this.
|
|
1218
|
-
this._updateComponentInputs(command, componentRef);
|
|
1219
|
-
this._executedCommandsCache.set(id, {
|
|
1290
|
+
this.renderInsertAfter(rootNode, { parentElement: this.findParent(command), previousSibling });
|
|
1291
|
+
cached.inputs.set(command.inputs);
|
|
1292
|
+
this.executedCommandsCache.set(command.id, {
|
|
1220
1293
|
...cached,
|
|
1221
|
-
command
|
|
1294
|
+
command,
|
|
1222
1295
|
});
|
|
1223
1296
|
}
|
|
1224
1297
|
}
|
|
1225
|
-
|
|
1226
|
-
const
|
|
1227
|
-
const cached = this._executedCommandsCache.get(id);
|
|
1298
|
+
runDeleteInstruction(command) {
|
|
1299
|
+
const cached = this.executedCommandsCache.get(command.id);
|
|
1228
1300
|
if (!cached) {
|
|
1229
|
-
if (
|
|
1301
|
+
if (command.kind === 'htmlClose') {
|
|
1230
1302
|
return;
|
|
1231
1303
|
}
|
|
1232
|
-
throw
|
|
1304
|
+
throw richTextRendererError('cached_command_not_found', { command });
|
|
1233
1305
|
}
|
|
1234
|
-
if (
|
|
1306
|
+
if (command.kind === 'component') {
|
|
1235
1307
|
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
1236
|
-
throw
|
|
1308
|
+
throw richTextRendererError('cached_command_not_component', { command });
|
|
1237
1309
|
}
|
|
1238
|
-
cached.componentRef
|
|
1310
|
+
cached.componentRef.destroy();
|
|
1239
1311
|
}
|
|
1240
|
-
else if (
|
|
1241
|
-
if (cached.element
|
|
1242
|
-
this.
|
|
1312
|
+
else if (command.kind === 'text' || command.kind === 'htmlOpen') {
|
|
1313
|
+
if (cached.element.parentElement) {
|
|
1314
|
+
this.renderer.removeChild(cached.element.parentElement, cached.element);
|
|
1243
1315
|
}
|
|
1244
1316
|
}
|
|
1245
|
-
|
|
1246
|
-
if (cached.element?.parentElement) {
|
|
1247
|
-
this._renderer.removeChild(cached.element.parentElement, cached.element);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
this._executedCommandsCache.delete(id);
|
|
1317
|
+
this.executedCommandsCache.delete(command.id);
|
|
1251
1318
|
}
|
|
1252
|
-
|
|
1319
|
+
getComponentRootNode(componentRef) {
|
|
1253
1320
|
return componentRef.hostView.rootNodes[0];
|
|
1254
1321
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
for (const [key, value] of Object.entries(command[COMPONENT_RENDER_COMMAND_POSITION.INPUTS])) {
|
|
1259
|
-
const publicName = inputsByProp.get(key);
|
|
1260
|
-
if (publicName === undefined)
|
|
1261
|
-
continue;
|
|
1262
|
-
componentRef.setInput(publicName, value);
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
_findParent(command) {
|
|
1266
|
-
const hostElement = this._elementRef.nativeElement;
|
|
1267
|
-
const nestingLevel = command[RENDER_COMMAND_POSITION.NESTING_LEVEL];
|
|
1268
|
-
let parentElement;
|
|
1269
|
-
if (nestingLevel === 0) {
|
|
1270
|
-
parentElement = hostElement;
|
|
1271
|
-
}
|
|
1272
|
-
else {
|
|
1273
|
-
// Reverse search all render commands beginning from the current one using a for loop.
|
|
1274
|
-
// The parent is found when the command's nesting level ist the same as the current nesting level minus 1.
|
|
1275
|
-
// If the parent is not found, throw an error.
|
|
1276
|
-
const allCommands = this.renderCommands();
|
|
1277
|
-
const currentCommandIndex = command[RENDER_COMMAND_POSITION.INDEX];
|
|
1278
|
-
let parentCommand = null;
|
|
1279
|
-
for (let i = currentCommandIndex - 1; i >= 0; i--) {
|
|
1280
|
-
const cmd = allCommands[i];
|
|
1281
|
-
if (!cmd) {
|
|
1282
|
-
throw new Error('Command not found!');
|
|
1283
|
-
}
|
|
1284
|
-
if (cmd[RENDER_COMMAND_POSITION.NESTING_LEVEL] === nestingLevel - 1 && isHtmlOpenRenderCommand(cmd)) {
|
|
1285
|
-
parentCommand = cmd;
|
|
1286
|
-
break;
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
if (!parentCommand) {
|
|
1290
|
-
throw new Error('Parent command not found!');
|
|
1291
|
-
}
|
|
1292
|
-
parentElement = this._executedCommandsCache.get(getRenderCommandId(parentCommand))?.element;
|
|
1322
|
+
findParent(command) {
|
|
1323
|
+
if (command.parentId === null) {
|
|
1324
|
+
return this.elementRef.nativeElement;
|
|
1293
1325
|
}
|
|
1326
|
+
const parentElement = this.executedCommandsCache.get(command.parentId)?.element;
|
|
1294
1327
|
if (!parentElement) {
|
|
1295
|
-
throw
|
|
1328
|
+
throw richTextRendererError('text_parent_not_found', { command });
|
|
1296
1329
|
}
|
|
1297
1330
|
return parentElement;
|
|
1298
1331
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
let nextElement = undefined;
|
|
1302
|
-
const domPosition = command[RENDER_COMMAND_POSITION.DOM_POSITION];
|
|
1303
|
-
const nestingLevel = command[RENDER_COMMAND_POSITION.NESTING_LEVEL];
|
|
1304
|
-
const type = command[RENDER_COMMAND_POSITION.TYPE];
|
|
1305
|
-
// Try to find an already rendered element with a domPosition that is greater than the current domPosition. If there are multiple, use the one with the smallest domPosition.
|
|
1306
|
-
// If it does not exist, use .appendChild to simply append the element to the end of the parent element.
|
|
1307
|
-
// If it does exist, use .insertBefore to insert the element before the found element.
|
|
1308
|
-
for (const cached of cacheArray) {
|
|
1309
|
-
if (cached.command[RENDER_COMMAND_POSITION.DOM_POSITION] > domPosition &&
|
|
1310
|
-
cached.command[RENDER_COMMAND_POSITION.NESTING_LEVEL] === nestingLevel &&
|
|
1311
|
-
// Make sure the element does not find itself
|
|
1312
|
-
cached.command[RENDER_COMMAND_POSITION.TYPE] !== type) {
|
|
1313
|
-
nextElement = cached.element;
|
|
1314
|
-
break;
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
return nextElement;
|
|
1332
|
+
renderInsertAfter(node, { parentElement, previousSibling }) {
|
|
1333
|
+
this.renderer.insertBefore(parentElement, node, previousSibling ? previousSibling.nextSibling : parentElement.firstChild);
|
|
1318
1334
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
this._renderer.insertBefore(parentElement, nodeToRender, nextElement);
|
|
1322
|
-
}
|
|
1323
|
-
else {
|
|
1324
|
-
this._renderer.appendChild(parentElement, nodeToRender);
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ContentfulRichTextRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1328
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.1", 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, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
1335
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulRichTextRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
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 }); }
|
|
1329
1337
|
}
|
|
1330
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1338
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulRichTextRendererComponent, decorators: [{
|
|
1331
1339
|
type: Component,
|
|
1332
1340
|
args: [{
|
|
1333
1341
|
selector: 'et-contentful-rich-text-renderer',
|
|
1334
1342
|
template: ``,
|
|
1335
1343
|
encapsulation: ViewEncapsulation.None,
|
|
1336
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1337
1344
|
host: {
|
|
1338
1345
|
class: 'et-contentful-rich-text-renderer',
|
|
1339
1346
|
},
|
|
@@ -1348,16 +1355,14 @@ const ContentfulImports = [
|
|
|
1348
1355
|
ContentfulRichTextRendererComponent,
|
|
1349
1356
|
];
|
|
1350
1357
|
|
|
1351
|
-
const provideContentfulConfig = (contentfulConfig
|
|
1358
|
+
const provideContentfulConfig = (contentfulConfig) => {
|
|
1352
1359
|
return { provide: CONTENTFUL_CONFIG, useValue: createContentfulConfig(contentfulConfig) };
|
|
1353
1360
|
};
|
|
1354
1361
|
const isContentfulEntryType = (entry, type) => entry.sys.contentType.sys.id === type;
|
|
1355
1362
|
|
|
1356
|
-
// Burst cache
|
|
1357
|
-
|
|
1358
1363
|
/**
|
|
1359
1364
|
* Generated bundle index. Do not edit.
|
|
1360
1365
|
*/
|
|
1361
1366
|
|
|
1362
|
-
export {
|
|
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 };
|
|
1363
1368
|
//# sourceMappingURL=ethlete-contentful.mjs.map
|