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