@ethlete/contentful 4.0.0-next.1 → 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 +86 -0
- package/README.md +23 -4
- package/fesm2022/ethlete-contentful.mjs +785 -743
- 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 +78 -226
|
@@ -1,16 +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 { RouterLink } from '@angular/router';
|
|
7
|
-
import { toSignal, toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
8
|
-
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';
|
|
9
6
|
import { RuntimeError, isObject, injectRenderer, getObjectProperty } from '@ethlete/core';
|
|
10
|
-
import { startWith, pairwise, tap } from 'rxjs';
|
|
11
7
|
|
|
12
|
-
const GQL_FRAGMENT_CONTENTFUL_ASSET =
|
|
13
|
-
fragment
|
|
8
|
+
const GQL_FRAGMENT_CONTENTFUL_ASSET = `
|
|
9
|
+
fragment ContentfulAssetData on Asset {
|
|
14
10
|
sys {
|
|
15
11
|
id
|
|
16
12
|
}
|
|
@@ -25,43 +21,51 @@ const GQL_FRAGMENT_CONTENTFUL_ASSET = gql `
|
|
|
25
21
|
`;
|
|
26
22
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
23
|
const isContentfulGqlAsset = (asset) => {
|
|
28
|
-
return asset
|
|
24
|
+
return Boolean(asset && typeof asset === 'object' && asset.sys?.id && !('fields' in asset));
|
|
29
25
|
};
|
|
30
26
|
|
|
31
27
|
class ContentfulAudioComponent {
|
|
32
28
|
constructor() {
|
|
33
|
-
this.asset = input.required(
|
|
34
|
-
|
|
35
|
-
this.
|
|
36
|
-
|
|
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 */ []));
|
|
37
37
|
this.data = computed(() => {
|
|
38
38
|
const asset = this.asset();
|
|
39
39
|
if (!asset) {
|
|
40
40
|
return null;
|
|
41
41
|
}
|
|
42
|
-
if (isContentfulGqlAsset(asset)) {
|
|
42
|
+
if (isContentfulGqlAsset(asset) && asset.url) {
|
|
43
43
|
return {
|
|
44
44
|
url: asset.url,
|
|
45
45
|
title: asset.title,
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 */ []));
|
|
53
57
|
}
|
|
54
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
55
|
-
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: `
|
|
56
60
|
@if (data(); as data) {
|
|
57
61
|
<figure [ngClass]="figureClass()">
|
|
58
62
|
<figcaption [ngClass]="figcaptionClass()">{{ data.title }}</figcaption>
|
|
59
|
-
<audio [ngClass]="audioClass()"
|
|
63
|
+
<audio [ngClass]="audioClass()" [src]="data.url" controls></audio>
|
|
60
64
|
</figure>
|
|
61
65
|
}
|
|
62
|
-
`, 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 }); }
|
|
63
67
|
}
|
|
64
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
68
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulAudioComponent, decorators: [{
|
|
65
69
|
type: Component,
|
|
66
70
|
args: [{
|
|
67
71
|
selector: 'et-contentful-audio',
|
|
@@ -69,69 +73,277 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
|
|
|
69
73
|
@if (data(); as data) {
|
|
70
74
|
<figure [ngClass]="figureClass()">
|
|
71
75
|
<figcaption [ngClass]="figcaptionClass()">{{ data.title }}</figcaption>
|
|
72
|
-
<audio [ngClass]="audioClass()"
|
|
76
|
+
<audio [ngClass]="audioClass()" [src]="data.url" controls></audio>
|
|
73
77
|
</figure>
|
|
74
78
|
}
|
|
75
79
|
`,
|
|
76
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
77
80
|
encapsulation: ViewEncapsulation.None,
|
|
78
81
|
imports: [NgClass],
|
|
82
|
+
host: {
|
|
83
|
+
class: 'et-contentful-audio',
|
|
84
|
+
},
|
|
79
85
|
}]
|
|
80
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 }] }] } });
|
|
81
87
|
|
|
82
88
|
class ContentfulFileComponent {
|
|
83
89
|
constructor() {
|
|
84
|
-
this.asset = input.required(
|
|
85
|
-
|
|
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 */ []));
|
|
86
94
|
this.data = computed(() => {
|
|
87
95
|
const asset = this.asset();
|
|
88
96
|
if (!asset) {
|
|
89
97
|
return null;
|
|
90
98
|
}
|
|
91
|
-
if (isContentfulGqlAsset(asset)) {
|
|
99
|
+
if (isContentfulGqlAsset(asset) && asset.url) {
|
|
92
100
|
return {
|
|
93
101
|
url: asset.url,
|
|
94
|
-
contentType: asset.contentType,
|
|
95
102
|
size: asset.size,
|
|
96
103
|
title: asset.title,
|
|
97
104
|
};
|
|
98
105
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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 */ []));
|
|
106
116
|
}
|
|
107
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
108
|
-
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: `
|
|
109
119
|
@if (data(); as data) {
|
|
110
|
-
<a [href]="data.url" [ngClass]="fileClass()"
|
|
111
|
-
{{ 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
|
+
}
|
|
112
125
|
</a>
|
|
113
126
|
}
|
|
114
|
-
`, 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 }); }
|
|
115
128
|
}
|
|
116
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
129
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulFileComponent, decorators: [{
|
|
117
130
|
type: Component,
|
|
118
131
|
args: [{
|
|
119
132
|
selector: 'et-contentful-file',
|
|
120
133
|
template: `
|
|
121
134
|
@if (data(); as data) {
|
|
122
|
-
<a [href]="data.url" [ngClass]="fileClass()"
|
|
123
|
-
{{ 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
|
+
}
|
|
124
140
|
</a>
|
|
125
141
|
}
|
|
126
142
|
`,
|
|
127
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
128
143
|
encapsulation: ViewEncapsulation.None,
|
|
129
144
|
imports: [NgClass],
|
|
145
|
+
host: {
|
|
146
|
+
class: 'et-contentful-file',
|
|
147
|
+
},
|
|
130
148
|
}]
|
|
131
149
|
}], propDecorators: { asset: [{ type: i0.Input, args: [{ isSignal: true, alias: "asset", required: true }] }], fileClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileClass", required: false }] }] } });
|
|
132
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
|
+
|
|
133
305
|
const CONTENTFUL_CONFIG = new InjectionToken('ContentfulConfig');
|
|
134
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
|
+
|
|
135
347
|
const generateDefaultContentfulImageSource = (data) => {
|
|
136
348
|
if (isContentfulGqlAsset(data)) {
|
|
137
349
|
if (!data.contentType || !data.url) {
|
|
@@ -165,41 +377,37 @@ const generateDefaultContentfulImageSource = (data) => {
|
|
|
165
377
|
* - `"400wx300h"` - 400px width and 300px height
|
|
166
378
|
**/
|
|
167
379
|
const parseContentfulImageSize = (size) => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
else if (size.includes('h')) {
|
|
176
|
-
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
|
+
};
|
|
177
386
|
}
|
|
178
|
-
|
|
179
|
-
|
|
387
|
+
const height = /^(\d+)h$/.exec(size);
|
|
388
|
+
if (height) {
|
|
389
|
+
return { width: null, height: Number(height[1]) };
|
|
180
390
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
height: height ? parseInt(height, 10) : null,
|
|
184
|
-
};
|
|
391
|
+
const width = /^(\d+)w?$/.exec(size);
|
|
392
|
+
return width ? { width: Number(width[1]), height: null } : { width: null, height: null };
|
|
185
393
|
};
|
|
394
|
+
const SOURCE_TYPES = ['image/avif', 'image/webp'];
|
|
186
395
|
const generateContentfulImageSources = (data, srcsetSizes, backgroundColor, quality, focusArea, resizeBehavior) => {
|
|
187
|
-
const
|
|
188
|
-
|
|
396
|
+
const isGqlAsset = isContentfulGqlAsset(data);
|
|
397
|
+
const baseUrl = isGqlAsset ? data.url : data.fields.file.url;
|
|
398
|
+
if (!baseUrl) {
|
|
189
399
|
return [];
|
|
190
400
|
}
|
|
401
|
+
const imageDimensions = isGqlAsset ? data : data.fields.file.details.image;
|
|
191
402
|
const sources = [];
|
|
192
|
-
const SOURCE_TYPES = ['image/avif', 'image/webp', 'image/png', 'image/jpg'];
|
|
193
403
|
for (const type of SOURCE_TYPES) {
|
|
194
|
-
const baseUrl = isContentfulGqlAsset(assetData) ? assetData.url : assetData.fields.file.url;
|
|
195
404
|
const sourceSets = [];
|
|
196
405
|
const queryParams = [];
|
|
197
|
-
// Set the format (e.g. 'fm=webp')
|
|
198
406
|
queryParams.push(`fm=${type.split('/')[1]}`);
|
|
199
407
|
if (backgroundColor) {
|
|
200
408
|
queryParams.push(`bg=rgb:${backgroundColor}`);
|
|
201
409
|
}
|
|
202
|
-
if (quality !== null) {
|
|
410
|
+
if (quality !== null && Number.isFinite(quality)) {
|
|
203
411
|
queryParams.push(`q=${quality}`);
|
|
204
412
|
}
|
|
205
413
|
if (focusArea) {
|
|
@@ -209,7 +417,7 @@ const generateContentfulImageSources = (data, srcsetSizes, backgroundColor, qual
|
|
|
209
417
|
queryParams.push(`fit=${resizeBehavior}`);
|
|
210
418
|
}
|
|
211
419
|
if (srcsetSizes?.length) {
|
|
212
|
-
const urlWithParams = `${baseUrl}
|
|
420
|
+
const urlWithParams = `${baseUrl}${baseUrl.includes('?') ? '&' : '?'}${queryParams.join('&')}`;
|
|
213
421
|
for (const size of srcsetSizes) {
|
|
214
422
|
const { width, height } = parseContentfulImageSize(size);
|
|
215
423
|
if (width && height) {
|
|
@@ -218,34 +426,42 @@ const generateContentfulImageSources = (data, srcsetSizes, backgroundColor, qual
|
|
|
218
426
|
else if (width) {
|
|
219
427
|
sourceSets.push(`${urlWithParams}&w=${width} ${width}w`);
|
|
220
428
|
}
|
|
221
|
-
else if (height) {
|
|
222
|
-
|
|
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`);
|
|
223
432
|
}
|
|
224
433
|
}
|
|
225
434
|
}
|
|
226
435
|
sources.push({
|
|
227
436
|
type,
|
|
228
|
-
srcset: sourceSets.length
|
|
437
|
+
srcset: sourceSets.length
|
|
438
|
+
? sourceSets.join(', ')
|
|
439
|
+
: `${baseUrl}${baseUrl.includes('?') ? '&' : '?'}${queryParams.join('&')}`,
|
|
229
440
|
});
|
|
230
441
|
}
|
|
231
442
|
return sources;
|
|
232
443
|
};
|
|
233
444
|
|
|
445
|
+
const optionalNumberAttribute = (value) => {
|
|
446
|
+
const transformed = numberAttribute(value);
|
|
447
|
+
return Number.isFinite(transformed) ? transformed : null;
|
|
448
|
+
};
|
|
234
449
|
class ContentfulImageComponent {
|
|
235
450
|
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.
|
|
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) });
|
|
249
465
|
this.normalizedAsset = computed(() => {
|
|
250
466
|
const asset = this.asset();
|
|
251
467
|
if (isContentfulGqlAsset(asset)) {
|
|
@@ -264,8 +480,9 @@ class ContentfulImageComponent {
|
|
|
264
480
|
height: asset?.fields?.file?.details?.image?.height ?? null,
|
|
265
481
|
};
|
|
266
482
|
}
|
|
267
|
-
},
|
|
268
|
-
|
|
483
|
+
}, /* @ts-ignore */
|
|
484
|
+
...(ngDevMode ? [{ debugName: "normalizedAsset" }] : /* istanbul ignore next */ []));
|
|
485
|
+
this.sourcesValue = computed(() => {
|
|
269
486
|
const asset = this.asset();
|
|
270
487
|
const backgroundColor = this.backgroundColor();
|
|
271
488
|
const srcsetSizes = this.srcsetSizes();
|
|
@@ -276,228 +493,94 @@ class ContentfulImageComponent {
|
|
|
276
493
|
return [];
|
|
277
494
|
}
|
|
278
495
|
return generateContentfulImageSources(asset, srcsetSizes, backgroundColor, quality, focusArea, resizeBehavior);
|
|
279
|
-
},
|
|
280
|
-
|
|
496
|
+
}, /* @ts-ignore */
|
|
497
|
+
...(ngDevMode ? [{ debugName: "sourcesValue" }] : /* istanbul ignore next */ []));
|
|
498
|
+
this.defaultSrcValue = computed(() => {
|
|
281
499
|
const asset = this.asset();
|
|
282
500
|
return asset ? generateDefaultContentfulImageSource(asset) : null;
|
|
283
|
-
},
|
|
501
|
+
}, /* @ts-ignore */
|
|
502
|
+
...(ngDevMode ? [{ debugName: "defaultSrcValue" }] : /* istanbul ignore next */ []));
|
|
284
503
|
}
|
|
285
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
286
|
-
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: `
|
|
287
506
|
<et-picture
|
|
288
|
-
[
|
|
289
|
-
[
|
|
290
|
-
[
|
|
291
|
-
[pictureClass]="pictureClass()"
|
|
292
|
-
[figcaptionClass]="figcaptionClass()"
|
|
293
|
-
[defaultSrc]="_defaultSrc()"
|
|
294
|
-
[alt]="normalizedAsset().alt ?? null"
|
|
507
|
+
[priority]="priority()"
|
|
508
|
+
[defaultSrc]="defaultSrcValue()"
|
|
509
|
+
[alt]="normalizedAsset().alt ?? ''"
|
|
295
510
|
[figcaption]="normalizedAsset().figcaption ?? null"
|
|
296
511
|
[width]="normalizedAsset().width ?? null"
|
|
297
512
|
[height]="normalizedAsset().height ?? null"
|
|
298
513
|
[sizes]="sizes()"
|
|
299
|
-
[sources]="
|
|
514
|
+
[sources]="sourcesValue()"
|
|
300
515
|
/>
|
|
301
|
-
`, 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 }); }
|
|
302
517
|
}
|
|
303
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
518
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulImageComponent, decorators: [{
|
|
304
519
|
type: Component,
|
|
305
520
|
args: [{
|
|
306
521
|
selector: 'et-contentful-image',
|
|
307
522
|
template: `
|
|
308
523
|
<et-picture
|
|
309
|
-
[
|
|
310
|
-
[
|
|
311
|
-
[
|
|
312
|
-
[pictureClass]="pictureClass()"
|
|
313
|
-
[figcaptionClass]="figcaptionClass()"
|
|
314
|
-
[defaultSrc]="_defaultSrc()"
|
|
315
|
-
[alt]="normalizedAsset().alt ?? null"
|
|
524
|
+
[priority]="priority()"
|
|
525
|
+
[defaultSrc]="defaultSrcValue()"
|
|
526
|
+
[alt]="normalizedAsset().alt ?? ''"
|
|
316
527
|
[figcaption]="normalizedAsset().figcaption ?? null"
|
|
317
528
|
[width]="normalizedAsset().width ?? null"
|
|
318
529
|
[height]="normalizedAsset().height ?? null"
|
|
319
530
|
[sizes]="sizes()"
|
|
320
|
-
[sources]="
|
|
531
|
+
[sources]="sourcesValue()"
|
|
321
532
|
/>
|
|
322
533
|
`,
|
|
323
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
324
534
|
encapsulation: ViewEncapsulation.None,
|
|
325
535
|
imports: [PictureComponent],
|
|
326
536
|
host: {
|
|
327
537
|
class: 'et-contentful-image',
|
|
328
538
|
},
|
|
329
539
|
}]
|
|
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 }] }],
|
|
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 }] }] } });
|
|
331
541
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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 }] }] } });
|
|
378
|
-
|
|
379
|
-
const createContentfulConfig = (config = {}) => ({
|
|
380
|
-
// This default config must be inline and not saved inside a const for some weird webpack reason
|
|
381
|
-
...{
|
|
382
|
-
useTailwindClasses: false,
|
|
383
|
-
internalHosts: [],
|
|
384
|
-
components: {
|
|
385
|
-
audio: ContentfulAudioComponent,
|
|
386
|
-
file: ContentfulFileComponent,
|
|
387
|
-
image: ContentfulImageComponent,
|
|
388
|
-
video: ContentfulVideoComponent,
|
|
389
|
-
link: ContentfulLinkComponent,
|
|
390
|
-
},
|
|
391
|
-
customComponents: {},
|
|
392
|
-
imageOptions: {
|
|
393
|
-
srcsetSizes: ['375w', '1280w', '1920w', '2560w'],
|
|
394
|
-
sizes: [],
|
|
395
|
-
backgroundColor: null,
|
|
396
|
-
},
|
|
397
|
-
},
|
|
398
|
-
...(config || {}),
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
const getPrimaryDomain = (host) => {
|
|
402
|
-
const parts = host.split('.');
|
|
403
|
-
return parts.length > 2 ? parts.slice(-2).join('.') : host;
|
|
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',
|
|
404
583
|
};
|
|
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
584
|
|
|
502
585
|
const RICH_TEXT_RENDERER_ERRORS = {
|
|
503
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.',
|
|
@@ -510,13 +593,20 @@ const RICH_TEXT_RENDERER_ERRORS = {
|
|
|
510
593
|
text_parent_not_found: 'The parent node is not found. This structure is not supported.',
|
|
511
594
|
text_parent_wrong_type: 'The parent node neither a html element nor a custom component. This structure is not supported.',
|
|
512
595
|
};
|
|
513
|
-
const RICH_TEXT_RENDERER_ERROR_CODES =
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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) => {
|
|
518
608
|
const message = `<et-contentful-rich-text-renderer>: ${RICH_TEXT_RENDERER_ERRORS[code]}`;
|
|
519
|
-
throw new RuntimeError(RICH_TEXT_RENDERER_ERROR_CODES[code], message,
|
|
609
|
+
throw new RuntimeError(RICH_TEXT_RENDERER_ERROR_CODES[code], message, data);
|
|
520
610
|
};
|
|
521
611
|
|
|
522
612
|
const isRichTextRootNode = (node) => {
|
|
@@ -524,160 +614,105 @@ const isRichTextRootNode = (node) => {
|
|
|
524
614
|
};
|
|
525
615
|
const translateContentfulNodeTypeToHtmlTag = (nodeType) => {
|
|
526
616
|
switch (nodeType) {
|
|
527
|
-
case
|
|
617
|
+
case CF_BLOCKS.HEADING_1:
|
|
528
618
|
return 'h1';
|
|
529
|
-
case
|
|
619
|
+
case CF_BLOCKS.HEADING_2:
|
|
530
620
|
return 'h2';
|
|
531
|
-
case
|
|
621
|
+
case CF_BLOCKS.HEADING_3:
|
|
532
622
|
return 'h3';
|
|
533
|
-
case
|
|
623
|
+
case CF_BLOCKS.HEADING_4:
|
|
534
624
|
return 'h4';
|
|
535
|
-
case
|
|
625
|
+
case CF_BLOCKS.HEADING_5:
|
|
536
626
|
return 'h5';
|
|
537
|
-
case
|
|
627
|
+
case CF_BLOCKS.HEADING_6:
|
|
538
628
|
return 'h6';
|
|
539
|
-
case
|
|
629
|
+
case CF_BLOCKS.PARAGRAPH:
|
|
540
630
|
return 'p';
|
|
541
|
-
case
|
|
631
|
+
case CF_BLOCKS.UL_LIST:
|
|
542
632
|
return 'ul';
|
|
543
|
-
case
|
|
633
|
+
case CF_BLOCKS.OL_LIST:
|
|
544
634
|
return 'ol';
|
|
545
|
-
case
|
|
635
|
+
case CF_BLOCKS.LIST_ITEM:
|
|
546
636
|
return 'li';
|
|
547
|
-
case
|
|
637
|
+
case CF_BLOCKS.HR:
|
|
548
638
|
return 'hr';
|
|
549
|
-
case
|
|
639
|
+
case CF_BLOCKS.QUOTE:
|
|
550
640
|
return 'blockquote';
|
|
551
|
-
case
|
|
641
|
+
case CF_BLOCKS.TABLE:
|
|
552
642
|
return 'table';
|
|
553
|
-
case
|
|
643
|
+
case CF_BLOCKS.TABLE_ROW:
|
|
554
644
|
return 'tr';
|
|
555
|
-
case
|
|
645
|
+
case CF_BLOCKS.TABLE_CELL:
|
|
556
646
|
return 'td';
|
|
557
|
-
case
|
|
647
|
+
case CF_BLOCKS.TABLE_HEADER_CELL:
|
|
558
648
|
return 'th';
|
|
559
|
-
case
|
|
649
|
+
case CF_BLOCKS.EMBEDDED_ASSET:
|
|
560
650
|
return 'div';
|
|
561
|
-
case
|
|
651
|
+
case CF_INLINES.ENTRY_HYPERLINK:
|
|
562
652
|
return 'a';
|
|
563
|
-
case
|
|
653
|
+
case CF_INLINES.ASSET_HYPERLINK:
|
|
564
654
|
return 'a';
|
|
565
655
|
case 'text':
|
|
566
656
|
return 'span';
|
|
567
657
|
// Will be ignored by the renderer
|
|
568
658
|
case 'document':
|
|
569
659
|
return 'div';
|
|
570
|
-
case
|
|
660
|
+
case CF_BLOCKS.EMBEDDED_ENTRY:
|
|
571
661
|
return 'div';
|
|
572
|
-
case
|
|
662
|
+
case CF_INLINES.EMBEDDED_ENTRY:
|
|
573
663
|
return 'div';
|
|
574
664
|
default:
|
|
575
665
|
return 'div';
|
|
576
666
|
}
|
|
577
667
|
};
|
|
578
668
|
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
NESTING_LEVEL: 1,
|
|
588
|
-
DOM_POSITION: 2,
|
|
589
|
-
INDEX: 3,
|
|
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,
|
|
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',
|
|
599
677
|
};
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
const
|
|
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 = [];
|
|
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 = [];
|
|
629
684
|
for (const mark of marks) {
|
|
630
|
-
|
|
631
|
-
if (!
|
|
632
|
-
|
|
633
|
-
|
|
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;
|
|
634
691
|
}
|
|
635
|
-
|
|
692
|
+
tags.push(tag);
|
|
636
693
|
}
|
|
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;
|
|
694
|
+
return tags;
|
|
644
695
|
};
|
|
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];
|
|
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;
|
|
657
705
|
}
|
|
658
|
-
|
|
659
|
-
|
|
706
|
+
try {
|
|
707
|
+
const url = new URL(href, 'https://contentful.invalid');
|
|
708
|
+
return ['http:', 'https:', 'mailto:', 'tel:', 'ftp:'].includes(url.protocol) ? href : null;
|
|
660
709
|
}
|
|
661
|
-
|
|
662
|
-
return
|
|
710
|
+
catch {
|
|
711
|
+
return null;
|
|
663
712
|
}
|
|
664
713
|
};
|
|
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
714
|
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);
|
|
715
|
+
return cache.command.kind === 'component';
|
|
681
716
|
};
|
|
682
717
|
const ET_CONTENTFUL_ANY_ENTRY_CONTENT_TYPE_SYS_ID = '$$$_et-contentful-any-entry-content-type-sys-id';
|
|
683
718
|
/**
|
|
@@ -733,40 +768,30 @@ const createContentfulIncludeMap = (config) => {
|
|
|
733
768
|
};
|
|
734
769
|
class ContentfulRichTextRendererComponent {
|
|
735
770
|
constructor() {
|
|
736
|
-
this.
|
|
737
|
-
this.
|
|
738
|
-
this.
|
|
739
|
-
this.
|
|
740
|
-
this._config = inject(CONTENTFUL_CONFIG, { optional: true }) ?? createContentfulConfig();
|
|
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();
|
|
771
|
+
this.viewContainerRef = inject(ViewContainerRef);
|
|
772
|
+
this.renderer = injectRenderer();
|
|
773
|
+
this.elementRef = inject(ElementRef);
|
|
774
|
+
this.config = injectContentfulConfig();
|
|
747
775
|
/**
|
|
748
776
|
* The contentful response gotten via their REST api.
|
|
749
777
|
* @see https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/entries/entries-collection
|
|
750
778
|
*/
|
|
751
|
-
this.content = input.required(
|
|
779
|
+
this.content = input.required(/* @ts-ignore */
|
|
780
|
+
...(ngDevMode ? [{ debugName: "content" }] : /* istanbul ignore next */ []));
|
|
752
781
|
/**
|
|
753
782
|
* The path to where the rich text field is inside the contentful response. Dot and array notation is allowed.
|
|
754
783
|
* @example "items[0].fields.html"
|
|
755
784
|
*/
|
|
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
|
-
*/
|
|
785
|
+
this.richTextPath = input.required(/* @ts-ignore */
|
|
786
|
+
...(ngDevMode ? [{ debugName: "richTextPath" }] : /* istanbul ignore next */ []));
|
|
787
|
+
this.executedCommandsCache = new Map();
|
|
761
788
|
this.contentIncludesMap = computed(() => {
|
|
762
789
|
const content = this.content();
|
|
763
|
-
const assets = content?.includes
|
|
764
|
-
const entries = content?.includes
|
|
790
|
+
const assets = content?.includes?.Asset;
|
|
791
|
+
const entries = content?.includes?.Entry;
|
|
765
792
|
return createContentfulIncludeMap({ assets: assets ?? [], entries: entries ?? [] });
|
|
766
|
-
},
|
|
767
|
-
|
|
768
|
-
* The rich text data that should be rendered.
|
|
769
|
-
*/
|
|
793
|
+
}, /* @ts-ignore */
|
|
794
|
+
...(ngDevMode ? [{ debugName: "contentIncludesMap" }] : /* istanbul ignore next */ []));
|
|
770
795
|
this.richTextData = computed(() => {
|
|
771
796
|
const content = this.content();
|
|
772
797
|
const richTextPath = this.richTextPath();
|
|
@@ -774,158 +799,154 @@ class ContentfulRichTextRendererComponent {
|
|
|
774
799
|
return null;
|
|
775
800
|
}
|
|
776
801
|
const richText = getObjectProperty(content, richTextPath);
|
|
802
|
+
if (richText === null || richText === undefined) {
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
777
805
|
if (!isObject(richText)) {
|
|
778
|
-
throw richTextRendererError('rich_text_undefined',
|
|
806
|
+
throw richTextRendererError('rich_text_undefined', { content, richTextPath });
|
|
779
807
|
}
|
|
780
808
|
if (!isRichTextRootNode(richText)) {
|
|
781
|
-
throw richTextRendererError('rich_text_wrong_type',
|
|
809
|
+
throw richTextRendererError('rich_text_wrong_type', { content, richTextPath });
|
|
782
810
|
}
|
|
783
811
|
return richText;
|
|
784
|
-
},
|
|
812
|
+
}, /* @ts-ignore */
|
|
813
|
+
...(ngDevMode ? [{ debugName: "richTextData" }] : /* istanbul ignore next */ []));
|
|
785
814
|
this.renderCommands = computed(() => {
|
|
786
815
|
const richTextData = this.richTextData();
|
|
787
816
|
if (!richTextData) {
|
|
788
817
|
return [];
|
|
789
818
|
}
|
|
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" }] : []));
|
|
819
|
+
return this.createRenderCommands(richTextData);
|
|
820
|
+
}, /* @ts-ignore */
|
|
821
|
+
...(ngDevMode ? [{ debugName: "renderCommands" }] : /* istanbul ignore next */ []));
|
|
801
822
|
this.renderInstructions = computed(() => {
|
|
802
823
|
const commands = this.renderCommands();
|
|
803
|
-
const previousRenderCommandMap = new Map(this.
|
|
824
|
+
const previousRenderCommandMap = new Map([...this.executedCommandsCache.entries()].map(([id, item]) => [id, item.command]));
|
|
804
825
|
const instructions = [];
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
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;
|
|
811
836
|
}
|
|
812
837
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
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;
|
|
824
848
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
command: newCommand,
|
|
829
|
-
});
|
|
830
|
-
}
|
|
831
|
-
lastComponentIndexes.sort((a, b) => a.newIndex - b.newIndex);
|
|
832
|
-
// check if the prevIndex are in order ascending
|
|
833
|
-
// for indexes that are not in order, create a move instruction
|
|
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;
|
|
849
|
+
const previous = previousRenderCommandMap.get(command.id);
|
|
850
|
+
if (!previous || previous.kind !== command.kind) {
|
|
851
|
+
continue;
|
|
841
852
|
}
|
|
842
|
-
if (
|
|
843
|
-
|
|
844
|
-
instructions.push([RENDER_INSTRUCTION_TYPE.MOVE, moveNext.command]);
|
|
853
|
+
if (previous.kind === 'component' && command.kind === 'component' && previous.component !== command.component) {
|
|
854
|
+
continue;
|
|
845
855
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
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 });
|
|
849
884
|
}
|
|
850
885
|
}
|
|
851
886
|
for (const command of commands) {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
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 });
|
|
859
895
|
}
|
|
860
896
|
else {
|
|
861
|
-
|
|
862
|
-
instructions.push([RENDER_INSTRUCTION_TYPE.CREATE, command]);
|
|
897
|
+
instructions.push({ type: 'update', command });
|
|
863
898
|
}
|
|
864
899
|
}
|
|
865
900
|
return instructions;
|
|
866
|
-
},
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
this.
|
|
870
|
-
|
|
871
|
-
|
|
901
|
+
}, /* @ts-ignore */
|
|
902
|
+
...(ngDevMode ? [{ debugName: "renderInstructions" }] : /* istanbul ignore next */ []));
|
|
903
|
+
effect(() => {
|
|
904
|
+
const instructions = this.renderInstructions();
|
|
905
|
+
untracked(() => this.execInstructions(instructions));
|
|
906
|
+
});
|
|
872
907
|
}
|
|
873
|
-
|
|
874
|
-
/** List of all render commands */
|
|
908
|
+
createRenderCommands(richTextData) {
|
|
875
909
|
const rootCommands = [];
|
|
876
|
-
/** Counter for generating unique html element IDs. */
|
|
877
910
|
let elementOpenId = 0;
|
|
878
|
-
/** Counter for generating unique html element IDs. */
|
|
879
911
|
let elementCloseId = 0;
|
|
880
|
-
/** Counter for generating unique component IDs. */
|
|
881
912
|
const componentIdMap = new Map();
|
|
882
|
-
/** The nesting level of the current node. */
|
|
883
913
|
let nestingLevel = 0;
|
|
884
|
-
/** The position (index) of the current node inside the parent node. */
|
|
885
914
|
let domPosition = 0;
|
|
886
915
|
let textId = 0;
|
|
887
916
|
let commandIndex = 0;
|
|
888
917
|
const traverse = (node) => {
|
|
889
918
|
switch (node.nodeType) {
|
|
890
919
|
case 'text': {
|
|
891
|
-
// Render media
|
|
892
|
-
const type = RENDER_COMMAND_TYPE.TEXT;
|
|
893
920
|
const text = node.value;
|
|
894
921
|
if (!text)
|
|
895
922
|
break;
|
|
896
923
|
const attributes = {
|
|
897
924
|
class: 'et-contentful-rich-text-default-element et-contentful-rich-text-default-span',
|
|
898
925
|
};
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
const command = [type, nestingLevel, domPosition, commandIndex++, attributes, text, id];
|
|
910
|
-
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
|
+
});
|
|
911
936
|
domPosition++;
|
|
912
937
|
break;
|
|
913
938
|
}
|
|
914
|
-
case
|
|
915
|
-
// Render media
|
|
916
|
-
const type = RENDER_COMMAND_TYPE.COMPONENT;
|
|
939
|
+
case CF_BLOCKS.EMBEDDED_ASSET: {
|
|
917
940
|
const assetId = node.data['target']?.sys?.id;
|
|
918
941
|
if (!assetId) {
|
|
919
|
-
throw richTextRendererError('asset_id_not_found',
|
|
942
|
+
throw richTextRendererError('asset_id_not_found', { node });
|
|
920
943
|
}
|
|
921
944
|
const asset = this.contentIncludesMap().getAsset(assetId);
|
|
922
945
|
if (!asset) {
|
|
923
|
-
throw richTextRendererError('asset_not_found',
|
|
946
|
+
throw richTextRendererError('asset_not_found', { assetId, node });
|
|
924
947
|
}
|
|
925
948
|
const contentType = asset.fields.file.contentType;
|
|
926
|
-
const assetComponents = this.
|
|
927
|
-
// Every property inside the asset will be null if no file was provided for a translation.
|
|
928
|
-
// In this case, we can assume that the asset is missing due to user error.
|
|
949
|
+
const assetComponents = this.config.components;
|
|
929
950
|
const isMissing = !contentType && !asset.fields.file.url;
|
|
930
951
|
if (isMissing) {
|
|
931
952
|
if (isDevMode()) {
|
|
@@ -943,37 +964,40 @@ class ContentfulRichTextRendererComponent {
|
|
|
943
964
|
: isAudio
|
|
944
965
|
? assetComponents.audio
|
|
945
966
|
: assetComponents.file;
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
asset,
|
|
959
|
-
};
|
|
960
|
-
const command = [
|
|
961
|
-
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',
|
|
962
979
|
nestingLevel,
|
|
963
980
|
domPosition,
|
|
964
|
-
commandIndex++,
|
|
965
|
-
attributes,
|
|
981
|
+
index: commandIndex++,
|
|
966
982
|
component,
|
|
967
|
-
inputs,
|
|
983
|
+
inputs: { asset },
|
|
968
984
|
id,
|
|
969
|
-
|
|
970
|
-
rootCommands.push(command);
|
|
985
|
+
});
|
|
971
986
|
domPosition++;
|
|
972
987
|
break;
|
|
973
988
|
}
|
|
974
|
-
case
|
|
975
|
-
|
|
976
|
-
|
|
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
|
+
}
|
|
977
1001
|
let linkText = '';
|
|
978
1002
|
const linkMarks = [];
|
|
979
1003
|
for (const child of node.content) {
|
|
@@ -982,107 +1006,163 @@ class ContentfulRichTextRendererComponent {
|
|
|
982
1006
|
linkMarks.push(...child.marks);
|
|
983
1007
|
}
|
|
984
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
|
+
}
|
|
985
1070
|
const textClass = linkMarks.length ? marksToClass(linkMarks) : '';
|
|
986
|
-
let linkComponentId = componentIdMap.get(
|
|
987
|
-
const linkId =
|
|
988
|
-
componentIdMap.set(
|
|
989
|
-
|
|
990
|
-
|
|
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',
|
|
991
1076
|
nestingLevel,
|
|
992
1077
|
domPosition,
|
|
993
|
-
commandIndex++,
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
];
|
|
999
|
-
rootCommands.push(linkCommand);
|
|
1078
|
+
index: commandIndex++,
|
|
1079
|
+
component: linkComponent,
|
|
1080
|
+
inputs: { href, text: linkText, textClass },
|
|
1081
|
+
id: linkId,
|
|
1082
|
+
});
|
|
1000
1083
|
domPosition++;
|
|
1001
1084
|
break;
|
|
1002
1085
|
}
|
|
1003
|
-
case
|
|
1004
|
-
case
|
|
1005
|
-
// Render component
|
|
1006
|
-
const type = RENDER_COMMAND_TYPE.COMPONENT;
|
|
1086
|
+
case CF_BLOCKS.EMBEDDED_ENTRY:
|
|
1087
|
+
case CF_INLINES.EMBEDDED_ENTRY: {
|
|
1007
1088
|
const entryId = node.data['target']?.sys?.id;
|
|
1008
1089
|
if (!entryId) {
|
|
1009
|
-
throw richTextRendererError('entry_id_not_found',
|
|
1090
|
+
throw richTextRendererError('entry_id_not_found', { node });
|
|
1010
1091
|
}
|
|
1011
1092
|
const entry = this.contentIncludesMap().getEntry(entryId, ET_CONTENTFUL_ANY_ENTRY_CONTENT_TYPE_SYS_ID);
|
|
1012
1093
|
if (!entry) {
|
|
1013
|
-
throw richTextRendererError('entry_not_found',
|
|
1094
|
+
throw richTextRendererError('entry_not_found', { entryId, node });
|
|
1014
1095
|
}
|
|
1015
1096
|
const componentType = entry.sys.contentType.sys.id;
|
|
1016
|
-
const component = this.
|
|
1097
|
+
const component = this.config.customComponents[componentType];
|
|
1017
1098
|
if (!component) {
|
|
1018
|
-
throw richTextRendererError('custom_component_not_found',
|
|
1099
|
+
throw richTextRendererError('custom_component_not_found', {
|
|
1019
1100
|
componentType,
|
|
1020
|
-
customComponents: this.
|
|
1101
|
+
customComponents: this.config.customComponents,
|
|
1021
1102
|
entry,
|
|
1022
1103
|
});
|
|
1023
1104
|
}
|
|
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,
|
|
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',
|
|
1036
1111
|
nestingLevel,
|
|
1037
1112
|
domPosition,
|
|
1038
|
-
commandIndex++,
|
|
1039
|
-
attributes,
|
|
1113
|
+
index: commandIndex++,
|
|
1040
1114
|
component,
|
|
1041
|
-
inputs
|
|
1115
|
+
inputs: {
|
|
1116
|
+
fields: entry.fields,
|
|
1117
|
+
metadata: entry.metadata,
|
|
1118
|
+
sys: entry.sys,
|
|
1119
|
+
includes: this.contentIncludesMap(),
|
|
1120
|
+
},
|
|
1042
1121
|
id,
|
|
1043
|
-
|
|
1044
|
-
rootCommands.push(command);
|
|
1122
|
+
});
|
|
1045
1123
|
domPosition++;
|
|
1046
1124
|
break;
|
|
1047
1125
|
}
|
|
1048
1126
|
default: {
|
|
1049
|
-
const type = RENDER_COMMAND_TYPE.HTML_OPEN;
|
|
1050
1127
|
const tag = translateContentfulNodeTypeToHtmlTag(node.nodeType);
|
|
1051
1128
|
const attributes = {
|
|
1052
1129
|
class: `et-contentful-rich-text-default-element et-contentful-rich-text-default-${tag}`,
|
|
1053
1130
|
};
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1131
|
+
rootCommands.push({
|
|
1132
|
+
kind: 'htmlOpen',
|
|
1133
|
+
nestingLevel,
|
|
1134
|
+
domPosition,
|
|
1135
|
+
index: commandIndex++,
|
|
1136
|
+
attributes,
|
|
1137
|
+
tagName: tag,
|
|
1138
|
+
id: 'e-o' + elementOpenId++,
|
|
1139
|
+
});
|
|
1057
1140
|
const domPositionAtThisLevel = domPosition;
|
|
1058
|
-
|
|
1141
|
+
nestingLevel++;
|
|
1142
|
+
domPosition = 0;
|
|
1059
1143
|
for (const child of node.content) {
|
|
1060
|
-
domPosition = 0;
|
|
1061
|
-
nestingLevel++;
|
|
1062
1144
|
traverse(child);
|
|
1063
|
-
nestingLevel--;
|
|
1064
|
-
domPosition = domPositionAtThisLevel;
|
|
1065
1145
|
}
|
|
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
1155
|
commandIndex--;
|
|
1074
1156
|
}
|
|
1075
1157
|
else {
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
RENDER_COMMAND_TYPE.HTML_CLOSE,
|
|
1158
|
+
rootCommands.push({
|
|
1159
|
+
kind: 'htmlClose',
|
|
1079
1160
|
nestingLevel,
|
|
1080
1161
|
domPosition,
|
|
1081
|
-
commandIndex++,
|
|
1082
|
-
tag,
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
rootCommands.push(closeCommand);
|
|
1162
|
+
index: commandIndex++,
|
|
1163
|
+
tagName: tag,
|
|
1164
|
+
id: 'e-c' + elementCloseId++,
|
|
1165
|
+
});
|
|
1086
1166
|
domPosition++;
|
|
1087
1167
|
}
|
|
1088
1168
|
break;
|
|
@@ -1094,246 +1174,210 @@ class ContentfulRichTextRendererComponent {
|
|
|
1094
1174
|
}
|
|
1095
1175
|
return rootCommands;
|
|
1096
1176
|
}
|
|
1097
|
-
|
|
1177
|
+
execInstructions(instructions) {
|
|
1098
1178
|
for (const instruction of instructions) {
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
case RENDER_INSTRUCTION_TYPE.CREATE:
|
|
1103
|
-
this._runCreateInstruction(command);
|
|
1179
|
+
switch (instruction.type) {
|
|
1180
|
+
case 'create':
|
|
1181
|
+
this.runCreateInstruction(instruction.command);
|
|
1104
1182
|
break;
|
|
1105
|
-
case
|
|
1106
|
-
this.
|
|
1183
|
+
case 'update':
|
|
1184
|
+
this.runUpdateInstruction(instruction.command);
|
|
1107
1185
|
break;
|
|
1108
|
-
case
|
|
1109
|
-
this.
|
|
1186
|
+
case 'move':
|
|
1187
|
+
this.runMoveInstruction(instruction.command);
|
|
1110
1188
|
break;
|
|
1111
|
-
case
|
|
1112
|
-
this.
|
|
1189
|
+
case 'delete':
|
|
1190
|
+
this.runDeleteInstruction(instruction.command);
|
|
1113
1191
|
break;
|
|
1114
1192
|
}
|
|
1115
1193
|
}
|
|
1116
1194
|
}
|
|
1117
|
-
|
|
1118
|
-
const
|
|
1119
|
-
const
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
const
|
|
1125
|
-
|
|
1126
|
-
|
|
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, {
|
|
1127
1209
|
command,
|
|
1128
1210
|
componentRef,
|
|
1211
|
+
inputs,
|
|
1129
1212
|
element: rootNode,
|
|
1130
1213
|
});
|
|
1131
1214
|
}
|
|
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);
|
|
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);
|
|
1140
1220
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
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;
|
|
1144
1226
|
}
|
|
1145
1227
|
for (const [textPartIndex, textPart] of textSplitInLineBreaks.entries()) {
|
|
1146
1228
|
if (textPartIndex > 0) {
|
|
1147
|
-
const brNode = this.
|
|
1148
|
-
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);
|
|
1149
1235
|
}
|
|
1150
|
-
const textNode = this._renderer.createText(textPart);
|
|
1151
|
-
this._renderer.appendChild(span, textNode);
|
|
1152
|
-
textNodes.push(textNode);
|
|
1153
1236
|
}
|
|
1154
|
-
this.
|
|
1155
|
-
this.
|
|
1237
|
+
this.renderInsertOrAppend(span, parentElement, nextElement);
|
|
1238
|
+
this.executedCommandsCache.set(command.id, {
|
|
1156
1239
|
command,
|
|
1157
1240
|
element: span,
|
|
1158
|
-
textNode: textNodes,
|
|
1159
1241
|
});
|
|
1160
1242
|
}
|
|
1161
|
-
else if (
|
|
1162
|
-
const
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1165
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
1166
|
-
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);
|
|
1167
1247
|
}
|
|
1168
|
-
this.
|
|
1169
|
-
this.
|
|
1248
|
+
this.renderInsertOrAppend(element, parentElement, nextElement);
|
|
1249
|
+
this.executedCommandsCache.set(command.id, {
|
|
1170
1250
|
command,
|
|
1171
1251
|
element,
|
|
1172
1252
|
});
|
|
1173
1253
|
}
|
|
1174
1254
|
}
|
|
1175
|
-
|
|
1176
|
-
const
|
|
1177
|
-
const cached = this._executedCommandsCache.get(id);
|
|
1255
|
+
runUpdateInstruction(command) {
|
|
1256
|
+
const cached = this.executedCommandsCache.get(command.id);
|
|
1178
1257
|
if (!cached) {
|
|
1179
1258
|
throw new Error('Cached command not found!');
|
|
1180
1259
|
}
|
|
1181
|
-
if (
|
|
1260
|
+
if (command.kind === 'component') {
|
|
1182
1261
|
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
1183
1262
|
throw new Error('Cached command is not a component command!');
|
|
1184
1263
|
}
|
|
1185
|
-
|
|
1186
|
-
if (!componentRef) {
|
|
1187
|
-
throw new Error('Component ref not found!');
|
|
1188
|
-
}
|
|
1189
|
-
this._updateComponentInputs(command, componentRef);
|
|
1264
|
+
cached.inputs.set(command.inputs);
|
|
1190
1265
|
}
|
|
1191
|
-
this.
|
|
1266
|
+
this.executedCommandsCache.set(command.id, {
|
|
1192
1267
|
...cached,
|
|
1193
|
-
command
|
|
1268
|
+
command,
|
|
1194
1269
|
});
|
|
1195
1270
|
}
|
|
1196
|
-
|
|
1197
|
-
const
|
|
1198
|
-
const cached = this._executedCommandsCache.get(id);
|
|
1271
|
+
runMoveInstruction(command) {
|
|
1272
|
+
const cached = this.executedCommandsCache.get(command.id);
|
|
1199
1273
|
if (!cached) {
|
|
1200
1274
|
throw new Error('Cached command not found!');
|
|
1201
1275
|
}
|
|
1202
|
-
if (
|
|
1276
|
+
if (command.kind === 'component') {
|
|
1203
1277
|
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
1204
1278
|
throw new Error('Cached command is not a component command!');
|
|
1205
1279
|
}
|
|
1206
|
-
const componentRef = cached.componentRef;
|
|
1207
|
-
if (!componentRef) {
|
|
1208
|
-
throw new Error('Component ref not found!');
|
|
1209
|
-
}
|
|
1210
1280
|
const rootNode = cached.element;
|
|
1211
1281
|
const oldParentElement = cached.element.parentElement;
|
|
1212
1282
|
if (oldParentElement) {
|
|
1213
|
-
this.
|
|
1283
|
+
this.renderer.removeChild(oldParentElement, rootNode);
|
|
1214
1284
|
}
|
|
1215
|
-
const newParentElement = this.
|
|
1216
|
-
const nextElement = this.
|
|
1217
|
-
this.
|
|
1218
|
-
|
|
1219
|
-
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, {
|
|
1220
1290
|
...cached,
|
|
1221
|
-
command
|
|
1291
|
+
command,
|
|
1222
1292
|
});
|
|
1223
1293
|
}
|
|
1224
1294
|
}
|
|
1225
|
-
|
|
1226
|
-
const
|
|
1227
|
-
const cached = this._executedCommandsCache.get(id);
|
|
1295
|
+
runDeleteInstruction(command) {
|
|
1296
|
+
const cached = this.executedCommandsCache.get(command.id);
|
|
1228
1297
|
if (!cached) {
|
|
1229
|
-
if (
|
|
1298
|
+
if (command.kind === 'htmlClose') {
|
|
1230
1299
|
return;
|
|
1231
1300
|
}
|
|
1232
1301
|
throw new Error('Cached command not found!');
|
|
1233
1302
|
}
|
|
1234
|
-
if (
|
|
1303
|
+
if (command.kind === 'component') {
|
|
1235
1304
|
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
1236
1305
|
throw new Error('Cached command is not a component command!');
|
|
1237
1306
|
}
|
|
1238
|
-
cached.componentRef
|
|
1307
|
+
cached.componentRef.destroy();
|
|
1239
1308
|
}
|
|
1240
|
-
else if (
|
|
1241
|
-
if (cached.element
|
|
1242
|
-
this.
|
|
1309
|
+
else if (command.kind === 'text' || command.kind === 'htmlOpen') {
|
|
1310
|
+
if (cached.element.parentElement) {
|
|
1311
|
+
this.renderer.removeChild(cached.element.parentElement, cached.element);
|
|
1243
1312
|
}
|
|
1244
1313
|
}
|
|
1245
|
-
|
|
1246
|
-
if (cached.element?.parentElement) {
|
|
1247
|
-
this._renderer.removeChild(cached.element.parentElement, cached.element);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
this._executedCommandsCache.delete(id);
|
|
1314
|
+
this.executedCommandsCache.delete(command.id);
|
|
1251
1315
|
}
|
|
1252
|
-
|
|
1316
|
+
getComponentRootNode(componentRef) {
|
|
1253
1317
|
return componentRef.hostView.rootNodes[0];
|
|
1254
1318
|
}
|
|
1255
|
-
|
|
1256
|
-
const
|
|
1257
|
-
const inputsByProp = new Map(inputMeta.map((i) => [i.propName, i.templateName]));
|
|
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];
|
|
1319
|
+
findParent(command) {
|
|
1320
|
+
const hostElement = this.elementRef.nativeElement;
|
|
1268
1321
|
let parentElement;
|
|
1269
|
-
if (nestingLevel === 0) {
|
|
1322
|
+
if (command.nestingLevel === 0) {
|
|
1270
1323
|
parentElement = hostElement;
|
|
1271
1324
|
}
|
|
1272
1325
|
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
1326
|
const allCommands = this.renderCommands();
|
|
1277
|
-
const currentCommandIndex = command[RENDER_COMMAND_POSITION.INDEX];
|
|
1278
1327
|
let parentCommand = null;
|
|
1279
|
-
for (let i =
|
|
1328
|
+
for (let i = command.index - 1; i >= 0; i--) {
|
|
1280
1329
|
const cmd = allCommands[i];
|
|
1281
1330
|
if (!cmd) {
|
|
1282
1331
|
throw new Error('Command not found!');
|
|
1283
1332
|
}
|
|
1284
|
-
if (cmd
|
|
1333
|
+
if (cmd.nestingLevel === command.nestingLevel - 1 && cmd.kind === 'htmlOpen') {
|
|
1285
1334
|
parentCommand = cmd;
|
|
1286
1335
|
break;
|
|
1287
1336
|
}
|
|
1288
1337
|
}
|
|
1289
1338
|
if (!parentCommand) {
|
|
1290
|
-
throw
|
|
1339
|
+
throw richTextRendererError('text_parent_not_found', { command });
|
|
1291
1340
|
}
|
|
1292
|
-
parentElement = this.
|
|
1341
|
+
parentElement = this.executedCommandsCache.get(parentCommand.id)?.element;
|
|
1293
1342
|
}
|
|
1294
1343
|
if (!parentElement) {
|
|
1295
|
-
throw
|
|
1344
|
+
throw richTextRendererError('text_parent_wrong_type', { command });
|
|
1296
1345
|
}
|
|
1297
1346
|
return parentElement;
|
|
1298
1347
|
}
|
|
1299
|
-
|
|
1300
|
-
const
|
|
1301
|
-
let nextElement
|
|
1302
|
-
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
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) {
|
|
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) {
|
|
1313
1358
|
nextElement = cached.element;
|
|
1314
|
-
|
|
1359
|
+
nextDomPosition = cached.command.domPosition;
|
|
1315
1360
|
}
|
|
1316
1361
|
}
|
|
1317
1362
|
return nextElement;
|
|
1318
1363
|
}
|
|
1319
|
-
|
|
1364
|
+
renderInsertOrAppend(nodeToRender, parentElement, nextElement) {
|
|
1320
1365
|
if (nextElement) {
|
|
1321
|
-
this.
|
|
1366
|
+
this.renderer.insertBefore(parentElement, nodeToRender, nextElement);
|
|
1322
1367
|
}
|
|
1323
1368
|
else {
|
|
1324
|
-
this.
|
|
1369
|
+
this.renderer.appendChild(parentElement, nodeToRender);
|
|
1325
1370
|
}
|
|
1326
1371
|
}
|
|
1327
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1328
|
-
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 }); }
|
|
1329
1374
|
}
|
|
1330
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1375
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContentfulRichTextRendererComponent, decorators: [{
|
|
1331
1376
|
type: Component,
|
|
1332
1377
|
args: [{
|
|
1333
1378
|
selector: 'et-contentful-rich-text-renderer',
|
|
1334
1379
|
template: ``,
|
|
1335
1380
|
encapsulation: ViewEncapsulation.None,
|
|
1336
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1337
1381
|
host: {
|
|
1338
1382
|
class: 'et-contentful-rich-text-renderer',
|
|
1339
1383
|
},
|
|
@@ -1348,16 +1392,14 @@ const ContentfulImports = [
|
|
|
1348
1392
|
ContentfulRichTextRendererComponent,
|
|
1349
1393
|
];
|
|
1350
1394
|
|
|
1351
|
-
const provideContentfulConfig = (contentfulConfig
|
|
1395
|
+
const provideContentfulConfig = (contentfulConfig) => {
|
|
1352
1396
|
return { provide: CONTENTFUL_CONFIG, useValue: createContentfulConfig(contentfulConfig) };
|
|
1353
1397
|
};
|
|
1354
1398
|
const isContentfulEntryType = (entry, type) => entry.sys.contentType.sys.id === type;
|
|
1355
1399
|
|
|
1356
|
-
// Burst cache
|
|
1357
|
-
|
|
1358
1400
|
/**
|
|
1359
1401
|
* Generated bundle index. Do not edit.
|
|
1360
1402
|
*/
|
|
1361
1403
|
|
|
1362
|
-
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 };
|
|
1363
1405
|
//# sourceMappingURL=ethlete-contentful.mjs.map
|