@ethlete/contentful 2.1.1 → 3.0.0
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 +235 -0
- package/esm2022/index.mjs +12 -2
- package/esm2022/lib/components/audio/contentful-audio.component.mjs +18 -47
- package/esm2022/lib/components/audio/index.mjs +2 -0
- package/esm2022/lib/components/file/contentful-file.component.mjs +14 -36
- package/esm2022/lib/components/file/index.mjs +2 -0
- package/esm2022/lib/components/image/contentful-image.component.mjs +64 -113
- package/esm2022/lib/components/image/contentful-image.component.utils.mjs +15 -19
- package/esm2022/lib/components/image/index.mjs +3 -2
- package/esm2022/lib/components/rich-text-renderer/index.mjs +3 -2
- package/esm2022/lib/components/rich-text-renderer/rich-text-renderer.component.mjs +683 -78
- package/esm2022/lib/components/rich-text-renderer/rich-text-renderer.errors.mjs +21 -0
- package/esm2022/lib/components/rich-text-renderer/rich-text-renderer.util.mjs +4 -133
- package/esm2022/lib/components/video/contentful-video.component.mjs +14 -39
- package/esm2022/lib/components/video/index.mjs +2 -0
- package/esm2022/lib/constants/index.mjs +2 -2
- package/esm2022/lib/contentful.imports.mjs +6 -2
- package/esm2022/lib/gql/asset.fragments.mjs +1 -17
- package/esm2022/lib/gql/index.mjs +2 -0
- package/esm2022/lib/types/contentful.types.mjs +1 -1
- package/esm2022/lib/types/index.mjs +2 -2
- package/esm2022/lib/utils/contentful-config.mjs +5 -2
- package/esm2022/lib/utils/contentful.util.mjs +1 -7
- package/esm2022/lib/utils/index.mjs +3 -2
- package/fesm2022/ethlete-contentful.mjs +887 -536
- package/fesm2022/ethlete-contentful.mjs.map +1 -1
- package/index.d.ts +10 -1
- package/lib/components/audio/contentful-audio.component.d.ts +6 -11
- package/lib/components/file/contentful-file.component.d.ts +4 -9
- package/lib/components/image/contentful-image.component.d.ts +19 -24
- package/lib/components/image/contentful-image.component.utils.d.ts +2 -2
- package/lib/components/image/index.d.ts +2 -1
- package/lib/components/rich-text-renderer/index.d.ts +2 -1
- package/lib/components/rich-text-renderer/rich-text-renderer.component.d.ts +160 -15
- package/lib/components/rich-text-renderer/rich-text-renderer.errors.d.ts +12 -0
- package/lib/components/rich-text-renderer/rich-text-renderer.util.d.ts +4 -17
- package/lib/components/video/contentful-video.component.d.ts +4 -9
- package/lib/constants/index.d.ts +1 -1
- package/lib/contentful.imports.d.ts +5 -1
- package/lib/gql/asset.fragments.d.ts +0 -1
- package/lib/types/contentful.types.d.ts +92 -48
- package/lib/types/index.d.ts +1 -1
- package/lib/utils/contentful.util.d.ts +1 -3
- package/lib/utils/index.d.ts +2 -1
- package/package.json +4 -4
- package/esm2022/lib/components/audio/public-api.mjs +0 -2
- package/esm2022/lib/components/file/public-api.mjs +0 -2
- package/esm2022/lib/components/image/public-api.mjs +0 -3
- package/esm2022/lib/components/index.mjs +0 -2
- package/esm2022/lib/components/public-api.mjs +0 -6
- package/esm2022/lib/components/rich-text-renderer/public-api.mjs +0 -5
- package/esm2022/lib/components/rich-text-renderer/rich-text-renderer.constants.mjs +0 -3
- package/esm2022/lib/components/rich-text-renderer/rich-text-renderer.types.mjs +0 -2
- package/esm2022/lib/components/video/public-api.mjs +0 -2
- package/esm2022/lib/constants/public-api.mjs +0 -2
- package/esm2022/lib/gql/public-api.mjs +0 -2
- package/esm2022/lib/public-api.mjs +0 -7
- package/esm2022/lib/types/public-api.mjs +0 -2
- package/esm2022/lib/utils/public-api.mjs +0 -3
- package/lib/components/image/public-api.d.ts +0 -2
- package/lib/components/index.d.ts +0 -1
- package/lib/components/public-api.d.ts +0 -5
- package/lib/components/rich-text-renderer/public-api.d.ts +0 -4
- package/lib/components/rich-text-renderer/rich-text-renderer.constants.d.ts +0 -2
- package/lib/components/rich-text-renderer/rich-text-renderer.types.d.ts +0 -12
- package/lib/constants/public-api.d.ts +0 -1
- package/lib/public-api.d.ts +0 -6
- package/lib/types/public-api.d.ts +0 -1
- package/lib/utils/public-api.d.ts +0 -2
- /package/lib/components/audio/{public-api.d.ts → index.d.ts} +0 -0
- /package/lib/components/file/{public-api.d.ts → index.d.ts} +0 -0
- /package/lib/components/video/{public-api.d.ts → index.d.ts} +0 -0
- /package/lib/gql/{public-api.d.ts → index.d.ts} +0 -0
|
@@ -1,51 +1,88 @@
|
|
|
1
|
-
import { NgClass,
|
|
1
|
+
import { NgClass, DOCUMENT } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
3
|
+
import { input, Component, ChangeDetectionStrategy, ViewEncapsulation, InjectionToken, inject, computed, ViewContainerRef, Renderer2, ElementRef } from '@angular/core';
|
|
4
|
+
import { normalizePictureSizes, PictureComponent } from '@ethlete/cdk';
|
|
5
|
+
import { toSignal, toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
6
|
+
import { INLINES, BLOCKS } from '@contentful/rich-text-types';
|
|
7
|
+
import { RuntimeError, isObject, getObjectProperty } from '@ethlete/core';
|
|
8
|
+
import { startWith, pairwise, tap } from 'rxjs';
|
|
8
9
|
import { gql } from '@ethlete/query';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
11
|
+
class ContentfulAudioComponent {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.asset = input.required();
|
|
14
|
+
this.audioClass = input(null);
|
|
15
|
+
this.figureClass = input(null);
|
|
16
|
+
this.figcaptionClass = input(null);
|
|
17
|
+
}
|
|
18
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: ContentfulAudioComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
19
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.3", type: ContentfulAudioComponent, isStandalone: true, selector: "et-contentful-audio", inputs: { asset: { classPropertyName: "asset", publicName: "asset", isSignal: true, isRequired: true, transformFunction: null }, audioClass: { classPropertyName: "audioClass", publicName: "audioClass", isSignal: true, isRequired: false, transformFunction: null }, figureClass: { classPropertyName: "figureClass", publicName: "figureClass", isSignal: true, isRequired: false, transformFunction: null }, figcaptionClass: { classPropertyName: "figcaptionClass", publicName: "figcaptionClass", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
20
|
+
@if (asset(); as data) {
|
|
21
|
+
<figure [ngClass]="figureClass()">
|
|
22
|
+
<figcaption [ngClass]="figcaptionClass()">{{ data.fields.title }}</figcaption>
|
|
23
|
+
<audio [ngClass]="audioClass()" controls src="{{ data.fields.file.url }}"></audio>
|
|
24
|
+
</figure>
|
|
25
|
+
}
|
|
26
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
27
|
+
}
|
|
28
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: ContentfulAudioComponent, decorators: [{
|
|
29
|
+
type: Component,
|
|
30
|
+
args: [{
|
|
31
|
+
selector: 'et-contentful-audio',
|
|
32
|
+
template: `
|
|
33
|
+
@if (asset(); as data) {
|
|
34
|
+
<figure [ngClass]="figureClass()">
|
|
35
|
+
<figcaption [ngClass]="figcaptionClass()">{{ data.fields.title }}</figcaption>
|
|
36
|
+
<audio [ngClass]="audioClass()" controls src="{{ data.fields.file.url }}"></audio>
|
|
37
|
+
</figure>
|
|
38
|
+
}
|
|
39
|
+
`,
|
|
40
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
41
|
+
encapsulation: ViewEncapsulation.None,
|
|
42
|
+
standalone: true,
|
|
43
|
+
imports: [NgClass],
|
|
44
|
+
}]
|
|
45
|
+
}] });
|
|
31
46
|
|
|
32
|
-
|
|
47
|
+
class ContentfulFileComponent {
|
|
48
|
+
constructor() {
|
|
49
|
+
this.asset = input.required();
|
|
50
|
+
this.fileClass = input(null);
|
|
51
|
+
}
|
|
52
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: ContentfulFileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
53
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.3", type: ContentfulFileComponent, isStandalone: true, selector: "et-contentful-file", inputs: { asset: { classPropertyName: "asset", publicName: "asset", isSignal: true, isRequired: true, transformFunction: null }, fileClass: { classPropertyName: "fileClass", publicName: "fileClass", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
54
|
+
@if (asset(); as data) {
|
|
55
|
+
<a [href]="data.fields.file.url" [ngClass]="fileClass()" class="underline" target="_blank">
|
|
56
|
+
{{ data.fields.title }} ({{ data.fields.file.details.size }} Bytes)
|
|
57
|
+
</a>
|
|
58
|
+
}
|
|
59
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
60
|
+
}
|
|
61
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: ContentfulFileComponent, decorators: [{
|
|
62
|
+
type: Component,
|
|
63
|
+
args: [{
|
|
64
|
+
selector: 'et-contentful-file',
|
|
65
|
+
template: `
|
|
66
|
+
@if (asset(); as data) {
|
|
67
|
+
<a [href]="data.fields.file.url" [ngClass]="fileClass()" class="underline" target="_blank">
|
|
68
|
+
{{ data.fields.title }} ({{ data.fields.file.details.size }} Bytes)
|
|
69
|
+
</a>
|
|
70
|
+
}
|
|
71
|
+
`,
|
|
72
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
73
|
+
encapsulation: ViewEncapsulation.None,
|
|
74
|
+
standalone: true,
|
|
75
|
+
imports: [NgClass],
|
|
76
|
+
}]
|
|
77
|
+
}] });
|
|
33
78
|
|
|
34
|
-
const
|
|
35
|
-
return { provide: CONTENTFUL_CONFIG, useValue: createContentfulConfig(contentfulConfig) };
|
|
36
|
-
};
|
|
37
|
-
const isContentfulImage = (v) => {
|
|
38
|
-
return !!v && 'asset' in v && 'alt' in v && 'resizeBehavior' in v && 'focusArea' in v;
|
|
39
|
-
};
|
|
40
|
-
const isContentfulAsset = (v) => {
|
|
41
|
-
return !!v && 'contentType' in v && 'description' in v && 'height' in v && 'url' in v && 'width' in v;
|
|
42
|
-
};
|
|
79
|
+
const CONTENTFUL_CONFIG = new InjectionToken('ContentfulConfig');
|
|
43
80
|
|
|
44
81
|
const generateDefaultContentfulImageSource = (data) => {
|
|
45
|
-
const
|
|
82
|
+
const file = data.fields.file;
|
|
46
83
|
return {
|
|
47
|
-
type: contentType,
|
|
48
|
-
srcset: url,
|
|
84
|
+
type: file.contentType,
|
|
85
|
+
srcset: file.url,
|
|
49
86
|
};
|
|
50
87
|
};
|
|
51
88
|
/**
|
|
@@ -75,16 +112,15 @@ const parseContentfulImageSize = (size) => {
|
|
|
75
112
|
height: height ? parseInt(height, 10) : null,
|
|
76
113
|
};
|
|
77
114
|
};
|
|
78
|
-
const generateContentfulImageSources = (data, srcsetSizes, backgroundColor) => {
|
|
79
|
-
const assetData =
|
|
80
|
-
const imageData = isContentfulImage(data) ? data : null;
|
|
115
|
+
const generateContentfulImageSources = (data, srcsetSizes, backgroundColor, quality, focusArea, resizeBehavior) => {
|
|
116
|
+
const assetData = data;
|
|
81
117
|
if (!assetData) {
|
|
82
118
|
return [];
|
|
83
119
|
}
|
|
84
120
|
const sources = [];
|
|
85
121
|
const SOURCE_TYPES = ['image/avif', 'image/webp', 'image/png', 'image/jpg'];
|
|
86
122
|
for (const type of SOURCE_TYPES) {
|
|
87
|
-
const baseUrl = assetData.url;
|
|
123
|
+
const baseUrl = assetData.fields.file.url;
|
|
88
124
|
const sourceSets = [];
|
|
89
125
|
const queryParams = [];
|
|
90
126
|
// Set the format (e.g. 'fm=webp')
|
|
@@ -92,16 +128,14 @@ const generateContentfulImageSources = (data, srcsetSizes, backgroundColor) => {
|
|
|
92
128
|
if (backgroundColor) {
|
|
93
129
|
queryParams.push(`bg=rgb:${backgroundColor}`);
|
|
94
130
|
}
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
queryParams.push(`fit=${imageData.resizeBehavior}`);
|
|
104
|
-
}
|
|
131
|
+
if (quality !== null) {
|
|
132
|
+
queryParams.push(`q=${quality}`);
|
|
133
|
+
}
|
|
134
|
+
if (focusArea) {
|
|
135
|
+
queryParams.push(`f=${focusArea}`);
|
|
136
|
+
}
|
|
137
|
+
if (resizeBehavior) {
|
|
138
|
+
queryParams.push(`fit=${resizeBehavior}`);
|
|
105
139
|
}
|
|
106
140
|
if (srcsetSizes?.length) {
|
|
107
141
|
const urlWithParams = `${baseUrl}?${queryParams.join('&')}`;
|
|
@@ -128,242 +162,160 @@ const generateContentfulImageSources = (data, srcsetSizes, backgroundColor) => {
|
|
|
128
162
|
|
|
129
163
|
class ContentfulImageComponent {
|
|
130
164
|
constructor() {
|
|
131
|
-
this.
|
|
132
|
-
|
|
165
|
+
this._contentfulConfig = inject(CONTENTFUL_CONFIG);
|
|
166
|
+
this.asset = input.required();
|
|
167
|
+
this.backgroundColor = input(null);
|
|
168
|
+
this.srcsetSizes = input(this._contentfulConfig.imageOptions.srcsetSizes);
|
|
169
|
+
this.quality = input(null);
|
|
170
|
+
this.focusArea = input(null);
|
|
171
|
+
this.resizeBehavior = input(null);
|
|
172
|
+
this.hasPriority = input(false);
|
|
173
|
+
this.imgClass = input(null);
|
|
174
|
+
this.figureClass = input(null);
|
|
175
|
+
this.figcaptionClass = input(null);
|
|
176
|
+
this.pictureClass = input(null);
|
|
177
|
+
this.sizes = input(normalizePictureSizes(this._contentfulConfig.imageOptions.sizes), {
|
|
178
|
+
transform: (v) => normalizePictureSizes(v),
|
|
179
|
+
});
|
|
180
|
+
this._sources = computed(() => {
|
|
181
|
+
const asset = this.asset();
|
|
182
|
+
const backgroundColor = this.backgroundColor();
|
|
183
|
+
const srcsetSizes = this.srcsetSizes();
|
|
184
|
+
const quality = this.quality();
|
|
185
|
+
const focusArea = this.focusArea();
|
|
186
|
+
const resizeBehavior = this.resizeBehavior();
|
|
187
|
+
if (!asset) {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
return generateContentfulImageSources(asset, srcsetSizes, backgroundColor, quality, focusArea, resizeBehavior);
|
|
191
|
+
});
|
|
192
|
+
this._defaultSrc = computed(() => {
|
|
193
|
+
const asset = this.asset();
|
|
194
|
+
return asset ? generateDefaultContentfulImageSource(asset) : null;
|
|
133
195
|
});
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
this._sizes = [];
|
|
138
|
-
this._srcsetSizes = [];
|
|
139
|
-
this._backgroundColor = null;
|
|
140
|
-
this.sources = [];
|
|
141
|
-
}
|
|
142
|
-
get data() {
|
|
143
|
-
return this._data;
|
|
144
|
-
}
|
|
145
|
-
set data(v) {
|
|
146
|
-
const assetData = isContentfulImage(v) ? v.asset : v;
|
|
147
|
-
const imageData = isContentfulImage(v) ? v : null;
|
|
148
|
-
if (assetData && !assetData.contentType) {
|
|
149
|
-
this._data = null;
|
|
150
|
-
console.warn('The provided asset is invalid', v);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
if (assetData && !assetData.contentType.startsWith('image/')) {
|
|
154
|
-
throw new Error('The provided asset is not an image');
|
|
155
|
-
}
|
|
156
|
-
this._data = v ?? null;
|
|
157
|
-
this.pictureData.defaultSrc = assetData ? generateDefaultContentfulImageSource(assetData) : null;
|
|
158
|
-
this.pictureData.alt = imageData?.alt ?? assetData?.description ?? null;
|
|
159
|
-
this.pictureData.width = assetData?.width ?? null;
|
|
160
|
-
this.pictureData.height = assetData?.height ?? null;
|
|
161
|
-
this.pictureData.figcaption = imageData?.caption ?? null;
|
|
162
|
-
this.updateSizes();
|
|
163
|
-
}
|
|
164
|
-
get sizes() {
|
|
165
|
-
return this._sizes;
|
|
166
|
-
}
|
|
167
|
-
set sizes(v) {
|
|
168
|
-
this._sizes = v;
|
|
169
|
-
this.pictureData.sizes = this.sizes?.join(', ') ?? null;
|
|
170
|
-
}
|
|
171
|
-
get srcsetSizes() {
|
|
172
|
-
return this._srcsetSizes;
|
|
173
|
-
}
|
|
174
|
-
set srcsetSizes(v) {
|
|
175
|
-
this._srcsetSizes = v;
|
|
176
|
-
this.updateSizes();
|
|
177
|
-
}
|
|
178
|
-
get backgroundColor() {
|
|
179
|
-
return this._backgroundColor;
|
|
180
|
-
}
|
|
181
|
-
set backgroundColor(v) {
|
|
182
|
-
this._backgroundColor = v;
|
|
183
|
-
this.updateSizes();
|
|
184
|
-
}
|
|
185
|
-
ngOnInit() {
|
|
186
|
-
if (this._richTextData && !this.data) {
|
|
187
|
-
this.data = this._richTextData;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
updateSizes() {
|
|
191
|
-
this.sources = this._data ? generateContentfulImageSources(this._data, this.srcsetSizes, this.backgroundColor) : [];
|
|
192
|
-
this._cdr.markForCheck();
|
|
193
|
-
}
|
|
194
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.2", ngImport: i0, type: ContentfulImageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
195
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.0.2", type: ContentfulImageComponent, isStandalone: true, selector: "et-contentful-image", inputs: { data: "data", sizes: "sizes", srcsetSizes: "srcsetSizes", backgroundColor: "backgroundColor" }, host: { classAttribute: "et-contentful-image" }, hostDirectives: [{ directive: i1.PictureDataDirective, inputs: ["imgClass", "imgClass", "hasPriority", "hasPriority", "figureClass", "figureClass", "pictureClass", "pictureClass", "figcaptionClass", "figcaptionClass"] }], ngImport: i0, template: `
|
|
196
|
+
}
|
|
197
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: ContentfulImageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
198
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "17.3.3", 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 }, hasPriority: { classPropertyName: "hasPriority", publicName: "hasPriority", isSignal: true, isRequired: false, transformFunction: null }, imgClass: { classPropertyName: "imgClass", publicName: "imgClass", 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 }, pictureClass: { classPropertyName: "pictureClass", publicName: "pictureClass", 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: `
|
|
196
199
|
<et-picture
|
|
197
|
-
[imgClass]="
|
|
198
|
-
[hasPriority]="
|
|
199
|
-
[figureClass]="
|
|
200
|
-
[pictureClass]="
|
|
201
|
-
[figcaptionClass]="
|
|
202
|
-
[defaultSrc]="
|
|
203
|
-
[alt]="
|
|
204
|
-
[figcaption]="
|
|
205
|
-
[width]="
|
|
206
|
-
[height]="
|
|
207
|
-
[sizes]="
|
|
208
|
-
[sources]="
|
|
200
|
+
[imgClass]="imgClass()"
|
|
201
|
+
[hasPriority]="hasPriority()"
|
|
202
|
+
[figureClass]="figureClass()"
|
|
203
|
+
[pictureClass]="pictureClass()"
|
|
204
|
+
[figcaptionClass]="figcaptionClass()"
|
|
205
|
+
[defaultSrc]="_defaultSrc()"
|
|
206
|
+
[alt]="asset()?.fields?.title ?? null"
|
|
207
|
+
[figcaption]="asset()?.fields?.description ?? null"
|
|
208
|
+
[width]="asset()?.fields?.file?.details?.image?.width ?? null"
|
|
209
|
+
[height]="asset()?.fields?.file?.details?.image?.height ?? null"
|
|
210
|
+
[sizes]="sizes()"
|
|
211
|
+
[sources]="_sources()"
|
|
209
212
|
/>
|
|
210
|
-
`, isInline: true, dependencies: [{ kind: "component", type: PictureComponent, selector: "et-picture", inputs: ["sources"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
213
|
+
`, isInline: true, dependencies: [{ kind: "component", type: PictureComponent, selector: "et-picture", inputs: ["sources", "hasPriority", "imgClass", "figureClass", "figcaptionClass", "pictureClass", "defaultSrc", "alt", "figcaption", "width", "height", "sizes"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
211
214
|
}
|
|
212
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.
|
|
215
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: ContentfulImageComponent, decorators: [{
|
|
213
216
|
type: Component,
|
|
214
217
|
args: [{
|
|
215
218
|
selector: 'et-contentful-image',
|
|
216
219
|
template: `
|
|
217
220
|
<et-picture
|
|
218
|
-
[imgClass]="
|
|
219
|
-
[hasPriority]="
|
|
220
|
-
[figureClass]="
|
|
221
|
-
[pictureClass]="
|
|
222
|
-
[figcaptionClass]="
|
|
223
|
-
[defaultSrc]="
|
|
224
|
-
[alt]="
|
|
225
|
-
[figcaption]="
|
|
226
|
-
[width]="
|
|
227
|
-
[height]="
|
|
228
|
-
[sizes]="
|
|
229
|
-
[sources]="
|
|
221
|
+
[imgClass]="imgClass()"
|
|
222
|
+
[hasPriority]="hasPriority()"
|
|
223
|
+
[figureClass]="figureClass()"
|
|
224
|
+
[pictureClass]="pictureClass()"
|
|
225
|
+
[figcaptionClass]="figcaptionClass()"
|
|
226
|
+
[defaultSrc]="_defaultSrc()"
|
|
227
|
+
[alt]="asset()?.fields?.title ?? null"
|
|
228
|
+
[figcaption]="asset()?.fields?.description ?? null"
|
|
229
|
+
[width]="asset()?.fields?.file?.details?.image?.width ?? null"
|
|
230
|
+
[height]="asset()?.fields?.file?.details?.image?.height ?? null"
|
|
231
|
+
[sizes]="sizes()"
|
|
232
|
+
[sources]="_sources()"
|
|
230
233
|
/>
|
|
231
234
|
`,
|
|
232
235
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
233
236
|
encapsulation: ViewEncapsulation.None,
|
|
234
237
|
standalone: true,
|
|
235
|
-
imports: [
|
|
236
|
-
hostDirectives: [
|
|
237
|
-
{
|
|
238
|
-
directive: PictureDataDirective,
|
|
239
|
-
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
|
240
|
-
inputs: ['imgClass', 'hasPriority', 'figureClass', 'pictureClass', 'figcaptionClass'],
|
|
241
|
-
},
|
|
242
|
-
],
|
|
238
|
+
imports: [PictureComponent],
|
|
243
239
|
host: {
|
|
244
240
|
class: 'et-contentful-image',
|
|
245
241
|
},
|
|
246
242
|
}]
|
|
247
|
-
}]
|
|
248
|
-
type: Input
|
|
249
|
-
}], sizes: [{
|
|
250
|
-
type: Input
|
|
251
|
-
}], srcsetSizes: [{
|
|
252
|
-
type: Input
|
|
253
|
-
}], backgroundColor: [{
|
|
254
|
-
type: Input
|
|
255
|
-
}] } });
|
|
243
|
+
}] });
|
|
256
244
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
createContentfulRenderCommand(data.json, { children: commands, payload: data.json.nodeType }, resourceMap, config);
|
|
262
|
-
return commands;
|
|
263
|
-
};
|
|
264
|
-
const createContentfulRenderCommand = (node, parent, resourceMap, config) => {
|
|
265
|
-
const { nodeType, data: nodeData } = node;
|
|
266
|
-
const { children, marks, value } = getContentfulNodeProps(node);
|
|
267
|
-
const command = {
|
|
268
|
-
payload: translateContentfulNodeTypeToHtmlTag(nodeType),
|
|
269
|
-
data: value,
|
|
270
|
-
attributes: {
|
|
271
|
-
...(marks
|
|
272
|
-
? {
|
|
273
|
-
class: transformContentfulMarks(marks, config.useTailwindClasses),
|
|
274
|
-
}
|
|
275
|
-
: {}),
|
|
276
|
-
...(nodeData['uri'] ? { href: nodeData['uri'], target: '_blank' } : {}),
|
|
277
|
-
},
|
|
278
|
-
children: [],
|
|
279
|
-
};
|
|
280
|
-
const entryId = nodeData['target']?.sys?.id;
|
|
281
|
-
if (nodeType === BLOCKS.EMBEDDED_ASSET) {
|
|
282
|
-
const asset = resourceMap.assetsBlock.get(entryId);
|
|
283
|
-
if (asset) {
|
|
284
|
-
const isImage = asset.contentType.startsWith('image/');
|
|
285
|
-
const isVideo = asset.contentType.startsWith('video/');
|
|
286
|
-
const isAudio = asset.contentType.startsWith('audio/');
|
|
287
|
-
command.data = asset;
|
|
288
|
-
if (isImage) {
|
|
289
|
-
command.payload = config.components.image;
|
|
290
|
-
}
|
|
291
|
-
else if (isVideo) {
|
|
292
|
-
command.payload = config.components.video;
|
|
293
|
-
}
|
|
294
|
-
else if (isAudio) {
|
|
295
|
-
command.payload = config.components.audio;
|
|
296
|
-
}
|
|
297
|
-
else {
|
|
298
|
-
command.payload = config.components.file;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
else {
|
|
302
|
-
console.warn(`No asset found for entry id "${entryId}"!`, nodeData);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
else if (nodeType === BLOCKS.EMBEDDED_ENTRY || nodeType === INLINES.EMBEDDED_ENTRY) {
|
|
306
|
-
const entry = resourceMap.entriesBlock.get(entryId) ?? resourceMap.entriesInline.get(entryId);
|
|
307
|
-
if (entry) {
|
|
308
|
-
const type = entry?.__typename ?? 'ET_UNKNOWN';
|
|
309
|
-
// "Image" is a special wrapper content model for contentful assets.
|
|
310
|
-
// It must be created by the user.
|
|
311
|
-
const component = type === 'Image' ? ContentfulImageComponent : config.customComponents?.[type];
|
|
312
|
-
if (component) {
|
|
313
|
-
command.payload = component;
|
|
314
|
-
command.data = entry;
|
|
315
|
-
}
|
|
316
|
-
else {
|
|
317
|
-
command.data = `ERROR: No component found for embedded entry "${type}" as "${nodeType}"!`;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
else {
|
|
321
|
-
console.warn(`No entry found for entry id "${entryId}"!`, nodeData);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
if (children && (children.length !== 1 || children[0]?.nodeType !== 'text')) {
|
|
325
|
-
children.forEach((child) => {
|
|
326
|
-
createContentfulRenderCommand(child, command, resourceMap, config);
|
|
327
|
-
});
|
|
245
|
+
class ContentfulVideoComponent {
|
|
246
|
+
constructor() {
|
|
247
|
+
this.asset = input.required();
|
|
248
|
+
this.videoClass = input(null);
|
|
328
249
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
let value = null;
|
|
336
|
-
if (children && children.length === 1 && children[0]?.nodeType === 'text') {
|
|
337
|
-
marks = children[0].marks?.length ? children[0].marks : null;
|
|
338
|
-
value = children[0].value;
|
|
250
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: ContentfulVideoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
251
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.3", 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: `
|
|
252
|
+
@if (asset(); as data) {
|
|
253
|
+
<video [ngClass]="videoClass()" controls>
|
|
254
|
+
<source src="{{ data.fields.file.url }}" type="{{ data.fields.file.contentType }}" />
|
|
255
|
+
</video>
|
|
339
256
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
257
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
258
|
+
}
|
|
259
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: ContentfulVideoComponent, decorators: [{
|
|
260
|
+
type: Component,
|
|
261
|
+
args: [{
|
|
262
|
+
selector: 'et-contentful-video',
|
|
263
|
+
template: `
|
|
264
|
+
@if (asset(); as data) {
|
|
265
|
+
<video [ngClass]="videoClass()" controls>
|
|
266
|
+
<source src="{{ data.fields.file.url }}" type="{{ data.fields.file.contentType }}" />
|
|
267
|
+
</video>
|
|
343
268
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
269
|
+
`,
|
|
270
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
271
|
+
encapsulation: ViewEncapsulation.None,
|
|
272
|
+
standalone: true,
|
|
273
|
+
imports: [NgClass],
|
|
274
|
+
}]
|
|
275
|
+
}] });
|
|
276
|
+
|
|
277
|
+
const createContentfulConfig = (config = {}) => ({
|
|
278
|
+
// This default config must be inline and not saved inside a const for some weird webpack reason
|
|
279
|
+
...{
|
|
280
|
+
useTailwindClasses: false,
|
|
281
|
+
components: {
|
|
282
|
+
audio: ContentfulAudioComponent,
|
|
283
|
+
file: ContentfulFileComponent,
|
|
284
|
+
image: ContentfulImageComponent,
|
|
285
|
+
video: ContentfulVideoComponent,
|
|
286
|
+
},
|
|
287
|
+
customComponents: {},
|
|
288
|
+
imageOptions: {
|
|
289
|
+
srcsetSizes: ['375w', '1280w', '1920w', '2560w'],
|
|
290
|
+
sizes: [],
|
|
291
|
+
backgroundColor: null,
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
...(config || {}),
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const RICH_TEXT_RENDERER_ERRORS = {
|
|
298
|
+
rich_text_undefined: 'The property value given at richTextPath is undefined. Use the richTextPath input to specify the path to the rich text object.',
|
|
299
|
+
rich_text_wrong_type: 'The rich text object does not satisfy the RichTextResponse interface. It should contain a property named "nodeType" with the value "document".',
|
|
300
|
+
asset_id_not_found: 'The asset ID was not found. This node is not supported.',
|
|
301
|
+
entry_id_not_found: 'The entry ID was not found. This node is not supported.',
|
|
302
|
+
asset_not_found: 'The asset was not found. This node is not supported.',
|
|
303
|
+
entry_not_found: 'The entry was not found. This node is not supported.',
|
|
304
|
+
custom_component_not_found: 'No custom component found for entry type. Please provide one for this type.',
|
|
305
|
+
text_parent_not_found: 'The parent node is not found. This structure is not supported.',
|
|
306
|
+
text_parent_wrong_type: 'The parent node neither a html element nor a custom component. This structure is not supported.',
|
|
349
307
|
};
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
return useTailwindClasses ? 'font-mono' : mark.type;
|
|
362
|
-
default:
|
|
363
|
-
return mark.type;
|
|
364
|
-
}
|
|
365
|
-
})
|
|
366
|
-
.join(' ');
|
|
308
|
+
const RICH_TEXT_RENDERER_ERROR_CODES = Object.keys(RICH_TEXT_RENDERER_ERRORS).reduce((acc, key, index) => {
|
|
309
|
+
acc[key] = index;
|
|
310
|
+
return acc;
|
|
311
|
+
}, {});
|
|
312
|
+
const richTextRendererError = (code, devOnly, data) => {
|
|
313
|
+
const message = `<et-contentful-rich-text-renderer>: ${RICH_TEXT_RENDERER_ERRORS[code]}`;
|
|
314
|
+
throw new RuntimeError(RICH_TEXT_RENDERER_ERROR_CODES[code], message, devOnly, data);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const isRichTextRootNode = (node) => {
|
|
318
|
+
return isObject(node) && 'nodeType' in node && node['nodeType'] === 'document';
|
|
367
319
|
};
|
|
368
320
|
const translateContentfulNodeTypeToHtmlTag = (nodeType) => {
|
|
369
321
|
switch (nodeType) {
|
|
@@ -420,303 +372,712 @@ const translateContentfulNodeTypeToHtmlTag = (nodeType) => {
|
|
|
420
372
|
return 'div';
|
|
421
373
|
}
|
|
422
374
|
};
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
return resourceMap;
|
|
375
|
+
|
|
376
|
+
const RENDER_COMMAND_TYPE = {
|
|
377
|
+
HTML_OPEN: 0,
|
|
378
|
+
HTML_CLOSE: 1,
|
|
379
|
+
TEXT: 2,
|
|
380
|
+
COMPONENT: 3,
|
|
430
381
|
};
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
382
|
+
const RENDER_COMMAND_POSITION = {
|
|
383
|
+
TYPE: 0,
|
|
384
|
+
NESTING_LEVEL: 1,
|
|
385
|
+
DOM_POSITION: 2,
|
|
386
|
+
INDEX: 3,
|
|
387
|
+
};
|
|
388
|
+
const HTML_OPEN_RENDER_COMMAND_POSITION = {
|
|
389
|
+
ATTRIBUTES: 4,
|
|
390
|
+
TAG_NAME: 5,
|
|
391
|
+
ELEMENT_ID: 6,
|
|
392
|
+
};
|
|
393
|
+
const HTML_CLOSE_RENDER_COMMAND_POSITION = {
|
|
394
|
+
TAG_NAME: 4,
|
|
395
|
+
ELEMENT_ID: 5,
|
|
396
|
+
};
|
|
397
|
+
const TEXT_RENDER_COMMAND_POSITION = {
|
|
398
|
+
TEXT: 4,
|
|
399
|
+
};
|
|
400
|
+
const COMPONENT_RENDER_COMMAND_POSITION = {
|
|
401
|
+
ATTRIBUTES: 4,
|
|
402
|
+
COMPONENT: 5,
|
|
403
|
+
INPUTS: 6,
|
|
404
|
+
COMPONENT_TYPE: 7,
|
|
405
|
+
};
|
|
406
|
+
const RENDER_INSTRUCTION_POSITION = {
|
|
407
|
+
TYPE: 0,
|
|
408
|
+
COMMAND: 1,
|
|
409
|
+
};
|
|
410
|
+
const RENDER_INSTRUCTION_TYPE = {
|
|
411
|
+
CREATE: 0,
|
|
412
|
+
UPDATE: 1,
|
|
413
|
+
DELETE: 2,
|
|
414
|
+
MOVE: 3,
|
|
415
|
+
};
|
|
416
|
+
const MARK_TAILWIND_MAP = {
|
|
417
|
+
bold: 'font-bold',
|
|
418
|
+
italic: 'italic',
|
|
419
|
+
underline: 'underline',
|
|
420
|
+
code: 'font-mono',
|
|
421
|
+
};
|
|
422
|
+
const marksToClass = (marks) => {
|
|
423
|
+
const classes = [];
|
|
424
|
+
for (const mark of marks) {
|
|
425
|
+
let klass = MARK_TAILWIND_MAP[mark.type];
|
|
426
|
+
if (!klass) {
|
|
427
|
+
console.warn(`No class found for mark type! Falling back to "${mark.type}".`, mark);
|
|
428
|
+
klass = mark.type;
|
|
440
429
|
}
|
|
430
|
+
classes.push(klass);
|
|
441
431
|
}
|
|
442
|
-
return
|
|
432
|
+
return classes.join(' ');
|
|
433
|
+
};
|
|
434
|
+
const isHtmlOpenRenderCommand = (command) => {
|
|
435
|
+
return command[RENDER_COMMAND_POSITION.TYPE] === RENDER_COMMAND_TYPE.HTML_OPEN;
|
|
436
|
+
};
|
|
437
|
+
const isHtmlCloseRenderCommand = (command) => {
|
|
438
|
+
return command[RENDER_COMMAND_POSITION.TYPE] === RENDER_COMMAND_TYPE.HTML_CLOSE;
|
|
439
|
+
};
|
|
440
|
+
const isTextRenderCommand = (command) => {
|
|
441
|
+
return command[RENDER_COMMAND_POSITION.TYPE] === RENDER_COMMAND_TYPE.TEXT;
|
|
442
|
+
};
|
|
443
|
+
const isComponentRenderCommand = (command) => {
|
|
444
|
+
return command[RENDER_COMMAND_POSITION.TYPE] === RENDER_COMMAND_TYPE.COMPONENT;
|
|
445
|
+
};
|
|
446
|
+
const getRenderCommandId = (command) => {
|
|
447
|
+
if (isHtmlOpenRenderCommand(command)) {
|
|
448
|
+
return command[HTML_OPEN_RENDER_COMMAND_POSITION.ELEMENT_ID];
|
|
449
|
+
}
|
|
450
|
+
else if (isHtmlCloseRenderCommand(command)) {
|
|
451
|
+
return command[HTML_CLOSE_RENDER_COMMAND_POSITION.ELEMENT_ID];
|
|
452
|
+
}
|
|
453
|
+
else if (isTextRenderCommand(command)) {
|
|
454
|
+
return command[TEXT_RENDER_COMMAND_POSITION.TEXT];
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
return command[COMPONENT_RENDER_COMMAND_POSITION.COMPONENT_TYPE];
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
const CLASS_ATTR = 'class';
|
|
461
|
+
const DEFAULT_COMPONENT_TYPES = {
|
|
462
|
+
IMAGE: '$$$_et-image',
|
|
463
|
+
VIDEO: '$$$_et-video',
|
|
464
|
+
AUDIO: '$$$_et-audio',
|
|
465
|
+
FILE: '$$$_et-file',
|
|
466
|
+
};
|
|
467
|
+
const isExecutedComponentCommandCacheItem = (cache) => {
|
|
468
|
+
return isComponentRenderCommand(cache.command);
|
|
469
|
+
};
|
|
470
|
+
const isExecutedHtmlOrTextCommandCacheItem = (cache) => {
|
|
471
|
+
return isHtmlCloseRenderCommand(cache.command) || isHtmlOpenRenderCommand(cache.command);
|
|
472
|
+
};
|
|
473
|
+
const isExecutedTextCommandCacheItem = (cache) => {
|
|
474
|
+
return isTextRenderCommand(cache.command);
|
|
443
475
|
};
|
|
444
|
-
|
|
445
476
|
class ContentfulRichTextRendererComponent {
|
|
446
477
|
constructor() {
|
|
447
|
-
this.
|
|
478
|
+
this._viewContainerRef = inject(ViewContainerRef);
|
|
448
479
|
this._renderer = inject(Renderer2);
|
|
449
480
|
this._elementRef = inject(ElementRef);
|
|
450
|
-
this._componentFactoryResolver = inject(ComponentFactoryResolver);
|
|
451
481
|
this._document = inject(DOCUMENT);
|
|
452
|
-
this._appRef = inject(ApplicationRef);
|
|
453
|
-
this._injector = inject(Injector);
|
|
454
482
|
this._config = inject(CONTENTFUL_CONFIG, { optional: true }) ?? createContentfulConfig();
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
this.
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
483
|
+
/**
|
|
484
|
+
* A cache for all executed commands that are not deleted.
|
|
485
|
+
* This is used to keep track of all rendered elements and components.
|
|
486
|
+
* The key is the render command element ID.
|
|
487
|
+
*/
|
|
488
|
+
this._executedCommandsCache = new Map();
|
|
489
|
+
/**
|
|
490
|
+
* The contentful response gotten via their REST api.
|
|
491
|
+
* @see https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/entries/entries-collection
|
|
492
|
+
*/
|
|
493
|
+
this.content = input.required();
|
|
494
|
+
/**
|
|
495
|
+
* The path to where the rich text field is inside the contentful response. Dot and array notation is allowed.
|
|
496
|
+
* @default "items[0].fields.html"
|
|
497
|
+
*/
|
|
498
|
+
this.richTextPath = input.required();
|
|
499
|
+
/**
|
|
500
|
+
* A map of all includes in the contentful response.
|
|
501
|
+
* This is useful for looking up assets and entries that are referenced in the rich text without having to use loops.
|
|
502
|
+
*/
|
|
503
|
+
this.contentIncludesMap = computed(() => {
|
|
504
|
+
const content = this.content();
|
|
505
|
+
const assets = content?.includes.Asset;
|
|
506
|
+
const entries = content?.includes.Entry;
|
|
507
|
+
return {
|
|
508
|
+
assets: assets?.reduce((acc, asset) => {
|
|
509
|
+
acc[asset.sys.id] = asset;
|
|
510
|
+
return acc;
|
|
511
|
+
}, {}) ?? {},
|
|
512
|
+
entries: entries?.reduce((acc, entry) => {
|
|
513
|
+
acc[entry.sys.id] = entry;
|
|
514
|
+
return acc;
|
|
515
|
+
}, {}) ?? {},
|
|
516
|
+
};
|
|
517
|
+
});
|
|
518
|
+
/**
|
|
519
|
+
* The rich text data that should be rendered.
|
|
520
|
+
*/
|
|
521
|
+
this.richTextData = computed(() => {
|
|
522
|
+
const content = this.content();
|
|
523
|
+
const richTextPath = this.richTextPath();
|
|
524
|
+
if (!content) {
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
const richText = getObjectProperty(content, richTextPath);
|
|
528
|
+
if (!isObject(richText)) {
|
|
529
|
+
throw richTextRendererError('rich_text_undefined', false, { content, richTextPath });
|
|
530
|
+
}
|
|
531
|
+
if (!isRichTextRootNode(richText)) {
|
|
532
|
+
throw richTextRendererError('rich_text_wrong_type', false, { content, richTextPath });
|
|
533
|
+
}
|
|
534
|
+
return richText;
|
|
535
|
+
});
|
|
536
|
+
this.renderCommands = computed(() => {
|
|
537
|
+
const richTextData = this.richTextData();
|
|
538
|
+
if (!richTextData) {
|
|
539
|
+
return [];
|
|
540
|
+
}
|
|
541
|
+
return this._createRenderCommands(richTextData);
|
|
542
|
+
});
|
|
543
|
+
this.renderCommandHistory = toSignal(toObservable(this.renderCommands).pipe(startWith([]), pairwise()), { initialValue: [[], []] });
|
|
544
|
+
this.previousRenderCommandMap = computed(() => {
|
|
545
|
+
const [prevCommands] = this.renderCommandHistory();
|
|
546
|
+
const map = new Map();
|
|
547
|
+
for (const command of prevCommands) {
|
|
548
|
+
map.set(getRenderCommandId(command), command);
|
|
549
|
+
}
|
|
550
|
+
return map;
|
|
551
|
+
});
|
|
552
|
+
this.renderInstructions = computed(() => {
|
|
553
|
+
const commands = this.renderCommands();
|
|
554
|
+
const previousRenderCommandMap = new Map(this.previousRenderCommandMap());
|
|
555
|
+
const instructions = [];
|
|
556
|
+
// remove deleted components as well as all other elements
|
|
557
|
+
for (const [, command] of previousRenderCommandMap) {
|
|
558
|
+
if (isComponentRenderCommand(command)) {
|
|
559
|
+
// keep the component around if it's still in the new commands
|
|
560
|
+
if (commands.some((c) => getRenderCommandId(c) === getRenderCommandId(command))) {
|
|
561
|
+
continue;
|
|
499
562
|
}
|
|
500
563
|
}
|
|
501
|
-
|
|
564
|
+
instructions.push([RENDER_INSTRUCTION_TYPE.DELETE, command]);
|
|
565
|
+
// remove from map to mark as used
|
|
566
|
+
previousRenderCommandMap.delete(getRenderCommandId(command));
|
|
502
567
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
568
|
+
// Find the indexes of the commands remaining in the previousRenderCommandMap in the new commands array.
|
|
569
|
+
const lastComponentIndexes = [];
|
|
570
|
+
for (const [id, command] of previousRenderCommandMap) {
|
|
571
|
+
const index = commands.findIndex((c) => getRenderCommandId(c) === id);
|
|
572
|
+
const newCommand = commands[index];
|
|
573
|
+
if (index === -1) {
|
|
574
|
+
throw new Error('Command not found!');
|
|
575
|
+
}
|
|
576
|
+
lastComponentIndexes.push({
|
|
577
|
+
newIndex: index,
|
|
578
|
+
prevIndex: command[RENDER_COMMAND_POSITION.INDEX],
|
|
579
|
+
command: newCommand,
|
|
512
580
|
});
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
581
|
+
}
|
|
582
|
+
lastComponentIndexes.sort((a, b) => a.newIndex - b.newIndex);
|
|
583
|
+
// check if the prevIndex are in order ascending
|
|
584
|
+
// for indexes that are not in order, create a move instruction
|
|
585
|
+
// for indexes that are in order, create an update instruction
|
|
586
|
+
let moveCheckIndex = 0;
|
|
587
|
+
while (moveCheckIndex < lastComponentIndexes.length - 1) {
|
|
588
|
+
const moveCurr = lastComponentIndexes[moveCheckIndex];
|
|
589
|
+
const moveNext = lastComponentIndexes[moveCheckIndex + 1];
|
|
590
|
+
if (!moveCurr || !moveNext) {
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
593
|
+
if (moveCurr.prevIndex > moveNext.prevIndex) {
|
|
594
|
+
lastComponentIndexes.splice(moveCheckIndex + 1, 1);
|
|
595
|
+
instructions.push([RENDER_INSTRUCTION_TYPE.MOVE, moveNext.command]);
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
instructions.push([RENDER_INSTRUCTION_TYPE.UPDATE, moveNext.command]);
|
|
599
|
+
moveCheckIndex++;
|
|
520
600
|
}
|
|
521
|
-
element = ref.location.nativeElement;
|
|
522
601
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
602
|
+
for (const command of commands) {
|
|
603
|
+
const id = getRenderCommandId(command);
|
|
604
|
+
if (isComponentRenderCommand(command)) {
|
|
605
|
+
const previousCommand = previousRenderCommandMap.get(id);
|
|
606
|
+
if (!previousCommand) {
|
|
607
|
+
// create
|
|
608
|
+
instructions.push([RENDER_INSTRUCTION_TYPE.CREATE, command]);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
// create
|
|
613
|
+
instructions.push([RENDER_INSTRUCTION_TYPE.CREATE, command]);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return instructions;
|
|
617
|
+
});
|
|
618
|
+
toObservable(this.renderInstructions)
|
|
619
|
+
.pipe(takeUntilDestroyed(), tap((instructions) => {
|
|
620
|
+
this._execInstructions(instructions);
|
|
621
|
+
}))
|
|
622
|
+
.subscribe();
|
|
528
623
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
624
|
+
_createRenderCommands(richTextData) {
|
|
625
|
+
/** List of all render commands */
|
|
626
|
+
const rootCommands = [];
|
|
627
|
+
/** Counter for generating unique html element IDs. */
|
|
628
|
+
let elementId = 0;
|
|
629
|
+
/** Counter for generating unique component IDs. */
|
|
630
|
+
const componentIdMap = new Map();
|
|
631
|
+
/** The nesting level of the current node. */
|
|
632
|
+
let nestingLevel = 0;
|
|
633
|
+
/** The position (index) of the current node inside the parent node. */
|
|
634
|
+
let domPosition = 0;
|
|
635
|
+
let commandIndex = 0;
|
|
636
|
+
const traverse = (node, parent = null) => {
|
|
637
|
+
switch (node.nodeType) {
|
|
638
|
+
case 'text': {
|
|
639
|
+
// Render media
|
|
640
|
+
const type = RENDER_COMMAND_TYPE.TEXT;
|
|
641
|
+
const text = node.value;
|
|
642
|
+
if (!text)
|
|
643
|
+
break;
|
|
644
|
+
const command = [type, nestingLevel, domPosition, commandIndex++, text];
|
|
645
|
+
rootCommands.push(command);
|
|
646
|
+
domPosition++;
|
|
647
|
+
if (!parent) {
|
|
648
|
+
throw richTextRendererError('text_parent_not_found', false, { node });
|
|
649
|
+
}
|
|
650
|
+
if (!isHtmlOpenRenderCommand(parent) && !isComponentRenderCommand(parent)) {
|
|
651
|
+
throw richTextRendererError('text_parent_wrong_type', false, { node, parent });
|
|
652
|
+
}
|
|
653
|
+
const attributesPosition = parent[RENDER_COMMAND_POSITION.TYPE] === RENDER_COMMAND_TYPE.HTML_OPEN
|
|
654
|
+
? HTML_OPEN_RENDER_COMMAND_POSITION.ATTRIBUTES
|
|
655
|
+
: COMPONENT_RENDER_COMMAND_POSITION.ATTRIBUTES;
|
|
656
|
+
const attributes = parent[attributesPosition];
|
|
657
|
+
if (node.marks.length) {
|
|
658
|
+
const markClasses = marksToClass(node.marks);
|
|
659
|
+
if (CLASS_ATTR in attributes) {
|
|
660
|
+
attributes[CLASS_ATTR] += ` ${markClasses}`;
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
attributes[CLASS_ATTR] = markClasses;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
case BLOCKS.EMBEDDED_ASSET: {
|
|
669
|
+
// Render media
|
|
670
|
+
const type = RENDER_COMMAND_TYPE.COMPONENT;
|
|
671
|
+
const assetId = node.data['target']?.sys?.id;
|
|
672
|
+
if (!assetId) {
|
|
673
|
+
throw richTextRendererError('asset_id_not_found', false, { node });
|
|
674
|
+
}
|
|
675
|
+
const asset = this.contentIncludesMap().assets[assetId];
|
|
676
|
+
if (!asset) {
|
|
677
|
+
throw richTextRendererError('asset_not_found', false, { assetId, node });
|
|
678
|
+
}
|
|
679
|
+
const contentType = asset.fields.file.contentType;
|
|
680
|
+
const assetComponents = this._config.components;
|
|
681
|
+
const isImage = contentType.startsWith('image/');
|
|
682
|
+
const isVideo = contentType.startsWith('video/');
|
|
683
|
+
const isAudio = contentType.startsWith('audio/');
|
|
684
|
+
const component = isImage
|
|
685
|
+
? assetComponents.image
|
|
686
|
+
: isVideo
|
|
687
|
+
? assetComponents.video
|
|
688
|
+
: isAudio
|
|
689
|
+
? assetComponents.audio
|
|
690
|
+
: assetComponents.file;
|
|
691
|
+
const componentType = isImage
|
|
692
|
+
? DEFAULT_COMPONENT_TYPES.IMAGE
|
|
693
|
+
: isVideo
|
|
694
|
+
? DEFAULT_COMPONENT_TYPES.VIDEO
|
|
695
|
+
: isAudio
|
|
696
|
+
? DEFAULT_COMPONENT_TYPES.AUDIO
|
|
697
|
+
: DEFAULT_COMPONENT_TYPES.FILE;
|
|
698
|
+
let componentId = componentIdMap.get(componentType) ?? -1;
|
|
699
|
+
const id = componentType + ++componentId;
|
|
700
|
+
componentIdMap.set(componentType, componentId);
|
|
701
|
+
const attributes = {};
|
|
702
|
+
const inputs = {
|
|
703
|
+
asset,
|
|
704
|
+
};
|
|
705
|
+
const command = [
|
|
706
|
+
type,
|
|
707
|
+
nestingLevel,
|
|
708
|
+
domPosition,
|
|
709
|
+
commandIndex++,
|
|
710
|
+
attributes,
|
|
711
|
+
component,
|
|
712
|
+
inputs,
|
|
713
|
+
id,
|
|
714
|
+
];
|
|
715
|
+
rootCommands.push(command);
|
|
716
|
+
domPosition++;
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
case BLOCKS.EMBEDDED_ENTRY:
|
|
720
|
+
case INLINES.EMBEDDED_ENTRY: {
|
|
721
|
+
// Render component
|
|
722
|
+
const type = RENDER_COMMAND_TYPE.COMPONENT;
|
|
723
|
+
const entryId = node.data['target']?.sys?.id;
|
|
724
|
+
if (!entryId) {
|
|
725
|
+
throw richTextRendererError('entry_id_not_found', false, { node });
|
|
726
|
+
}
|
|
727
|
+
const entry = this.contentIncludesMap().entries[entryId];
|
|
728
|
+
if (!entry) {
|
|
729
|
+
throw richTextRendererError('entry_not_found', false, { entryId, node });
|
|
730
|
+
}
|
|
731
|
+
const componentType = entry.sys.contentType.sys.id;
|
|
732
|
+
const component = this._config.customComponents[componentType];
|
|
733
|
+
if (!component) {
|
|
734
|
+
throw richTextRendererError('custom_component_not_found', false, {
|
|
735
|
+
componentType,
|
|
736
|
+
customComponents: this._config.customComponents,
|
|
737
|
+
entry,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
const attributes = {};
|
|
741
|
+
let componentId = componentIdMap.get(componentType) ?? -1;
|
|
742
|
+
const id = componentType + ++componentId;
|
|
743
|
+
componentIdMap.set(componentType, componentId);
|
|
744
|
+
const inputs = {
|
|
745
|
+
fields: entry.fields,
|
|
746
|
+
metadata: entry.metadata,
|
|
747
|
+
sys: entry.sys,
|
|
748
|
+
includes: this.contentIncludesMap(),
|
|
749
|
+
};
|
|
750
|
+
const command = [
|
|
751
|
+
type,
|
|
752
|
+
nestingLevel,
|
|
753
|
+
domPosition,
|
|
754
|
+
commandIndex++,
|
|
755
|
+
attributes,
|
|
756
|
+
component,
|
|
757
|
+
inputs,
|
|
758
|
+
id,
|
|
759
|
+
];
|
|
760
|
+
rootCommands.push(command);
|
|
761
|
+
domPosition++;
|
|
762
|
+
break;
|
|
763
|
+
}
|
|
764
|
+
default: {
|
|
765
|
+
const type = RENDER_COMMAND_TYPE.HTML_OPEN;
|
|
766
|
+
const tag = translateContentfulNodeTypeToHtmlTag(node.nodeType);
|
|
767
|
+
const attributes = {};
|
|
768
|
+
const id = 'e' + elementId++;
|
|
769
|
+
const command = [type, nestingLevel, domPosition, commandIndex++, attributes, tag, id];
|
|
770
|
+
rootCommands.push(command);
|
|
771
|
+
const domPositionAtThisLevel = domPosition;
|
|
772
|
+
// Normal html elements can have children
|
|
773
|
+
for (const child of node.content) {
|
|
774
|
+
domPosition = 0;
|
|
775
|
+
nestingLevel++;
|
|
776
|
+
traverse(child, command);
|
|
777
|
+
nestingLevel--;
|
|
778
|
+
domPosition = domPositionAtThisLevel;
|
|
779
|
+
}
|
|
780
|
+
const lastCommand = rootCommands[rootCommands.length - 1];
|
|
781
|
+
if (lastCommand?.[RENDER_COMMAND_POSITION.TYPE] === RENDER_COMMAND_TYPE.HTML_OPEN) {
|
|
782
|
+
// If the last command is an open command, we can remove it since it's empty
|
|
783
|
+
rootCommands.pop();
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
const closeId = 'e' + elementId++;
|
|
787
|
+
const closeCommand = [
|
|
788
|
+
RENDER_COMMAND_TYPE.HTML_CLOSE,
|
|
789
|
+
nestingLevel,
|
|
790
|
+
domPosition,
|
|
791
|
+
commandIndex++,
|
|
792
|
+
tag,
|
|
793
|
+
closeId,
|
|
794
|
+
];
|
|
795
|
+
rootCommands.push(closeCommand);
|
|
796
|
+
domPosition++;
|
|
797
|
+
}
|
|
798
|
+
if (node.nodeType === INLINES.HYPERLINK) {
|
|
799
|
+
const uri = node.data['uri'];
|
|
800
|
+
attributes['href'] = uri;
|
|
801
|
+
const host = this._document.location.host;
|
|
802
|
+
const isExternal = !uri.includes(host);
|
|
803
|
+
if (isExternal) {
|
|
804
|
+
attributes['target'] = '_blank';
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
for (const node of richTextData.content) {
|
|
812
|
+
traverse(node, null);
|
|
813
|
+
}
|
|
814
|
+
return rootCommands;
|
|
555
815
|
}
|
|
556
|
-
|
|
557
|
-
|
|
816
|
+
_execInstructions(instructions) {
|
|
817
|
+
for (const instruction of instructions) {
|
|
818
|
+
const type = instruction[RENDER_INSTRUCTION_POSITION.TYPE];
|
|
819
|
+
const command = instruction[RENDER_INSTRUCTION_POSITION.COMMAND];
|
|
820
|
+
switch (type) {
|
|
821
|
+
case RENDER_INSTRUCTION_TYPE.CREATE:
|
|
822
|
+
this._runCreateInstruction(command);
|
|
823
|
+
break;
|
|
824
|
+
case RENDER_INSTRUCTION_TYPE.UPDATE:
|
|
825
|
+
this._runUpdateInstruction(command);
|
|
826
|
+
break;
|
|
827
|
+
case RENDER_INSTRUCTION_TYPE.MOVE:
|
|
828
|
+
this._runMoveInstruction(command);
|
|
829
|
+
break;
|
|
830
|
+
case RENDER_INSTRUCTION_TYPE.DELETE:
|
|
831
|
+
this._runDeleteInstruction(command);
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
558
835
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
836
|
+
_runCreateInstruction(command) {
|
|
837
|
+
const id = getRenderCommandId(command);
|
|
838
|
+
const parentElement = this._findParent(command);
|
|
839
|
+
const nextElement = this._findFollowingElement(command);
|
|
840
|
+
if (isComponentRenderCommand(command)) {
|
|
841
|
+
const componentRef = this._viewContainerRef.createComponent(command[COMPONENT_RENDER_COMMAND_POSITION.COMPONENT]);
|
|
842
|
+
this._updateComponentInputs(command, componentRef);
|
|
843
|
+
const rootNode = this._getComponentRootNode(componentRef);
|
|
844
|
+
this._renderInsertOrAppend(rootNode, parentElement, nextElement);
|
|
845
|
+
this._executedCommandsCache.set(id, {
|
|
846
|
+
command,
|
|
847
|
+
componentRef,
|
|
848
|
+
element: rootNode,
|
|
849
|
+
});
|
|
564
850
|
}
|
|
565
|
-
if (
|
|
566
|
-
|
|
851
|
+
else if (isTextRenderCommand(command)) {
|
|
852
|
+
const text = command[TEXT_RENDER_COMMAND_POSITION.TEXT];
|
|
853
|
+
const textNode = this._renderer.createText(text);
|
|
854
|
+
const span = this._renderer.createElement('span');
|
|
855
|
+
this._renderer.appendChild(span, textNode);
|
|
856
|
+
this._renderInsertOrAppend(span, parentElement, nextElement);
|
|
857
|
+
this._executedCommandsCache.set(id, {
|
|
858
|
+
command,
|
|
859
|
+
element: span,
|
|
860
|
+
textNode,
|
|
861
|
+
});
|
|
567
862
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
863
|
+
else if (isHtmlOpenRenderCommand(command)) {
|
|
864
|
+
const tag = command[HTML_OPEN_RENDER_COMMAND_POSITION.TAG_NAME];
|
|
865
|
+
const attributes = command[HTML_OPEN_RENDER_COMMAND_POSITION.ATTRIBUTES];
|
|
866
|
+
const element = this._renderer.createElement(tag);
|
|
867
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
868
|
+
this._renderer.setAttribute(element, key, value);
|
|
869
|
+
}
|
|
870
|
+
this._renderInsertOrAppend(element, parentElement, nextElement);
|
|
871
|
+
this._executedCommandsCache.set(id, {
|
|
872
|
+
command,
|
|
873
|
+
element,
|
|
874
|
+
});
|
|
573
875
|
}
|
|
574
876
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
877
|
+
_runUpdateInstruction(command) {
|
|
878
|
+
const id = getRenderCommandId(command);
|
|
879
|
+
const cached = this._executedCommandsCache.get(id);
|
|
880
|
+
if (!cached) {
|
|
881
|
+
throw new Error('Cached command not found!');
|
|
882
|
+
}
|
|
883
|
+
if (isComponentRenderCommand(command)) {
|
|
884
|
+
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
885
|
+
throw new Error('Cached command is not a component command!');
|
|
886
|
+
}
|
|
887
|
+
const componentRef = cached.componentRef;
|
|
888
|
+
if (!componentRef) {
|
|
889
|
+
throw new Error('Component ref not found!');
|
|
890
|
+
}
|
|
891
|
+
this._updateComponentInputs(command, componentRef);
|
|
892
|
+
}
|
|
893
|
+
this._executedCommandsCache.set(id, {
|
|
894
|
+
...cached,
|
|
895
|
+
command: command,
|
|
896
|
+
});
|
|
582
897
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
898
|
+
_runMoveInstruction(command) {
|
|
899
|
+
const id = getRenderCommandId(command);
|
|
900
|
+
const cached = this._executedCommandsCache.get(id);
|
|
901
|
+
if (!cached) {
|
|
902
|
+
throw new Error('Cached command not found!');
|
|
903
|
+
}
|
|
904
|
+
if (isComponentRenderCommand(command)) {
|
|
905
|
+
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
906
|
+
throw new Error('Cached command is not a component command!');
|
|
907
|
+
}
|
|
908
|
+
const componentRef = cached.componentRef;
|
|
909
|
+
if (!componentRef) {
|
|
910
|
+
throw new Error('Component ref not found!');
|
|
911
|
+
}
|
|
912
|
+
const rootNode = cached.element;
|
|
913
|
+
const oldParentElement = cached.element.parentElement;
|
|
914
|
+
this._renderer.removeChild(oldParentElement, rootNode);
|
|
915
|
+
const newParentElement = this._findParent(command);
|
|
916
|
+
const nextElement = this._findFollowingElement(command);
|
|
917
|
+
this._renderInsertOrAppend(rootNode, newParentElement, nextElement);
|
|
918
|
+
this._updateComponentInputs(command, componentRef);
|
|
919
|
+
this._executedCommandsCache.set(id, {
|
|
920
|
+
...cached,
|
|
921
|
+
command: command,
|
|
922
|
+
});
|
|
923
|
+
}
|
|
595
924
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
925
|
+
_runDeleteInstruction(command) {
|
|
926
|
+
const id = getRenderCommandId(command);
|
|
927
|
+
const cached = this._executedCommandsCache.get(id);
|
|
928
|
+
if (!cached) {
|
|
929
|
+
if (isHtmlCloseRenderCommand(command)) {
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
throw new Error('Cached command not found!');
|
|
933
|
+
}
|
|
934
|
+
if (isComponentRenderCommand(command)) {
|
|
935
|
+
if (!isExecutedComponentCommandCacheItem(cached)) {
|
|
936
|
+
throw new Error('Cached command is not a component command!');
|
|
937
|
+
}
|
|
938
|
+
cached.componentRef?.destroy();
|
|
939
|
+
}
|
|
940
|
+
else if (isTextRenderCommand(command)) {
|
|
941
|
+
this._renderer.removeChild(cached.element?.parentElement, cached.element);
|
|
942
|
+
}
|
|
943
|
+
else if (isHtmlOpenRenderCommand(command)) {
|
|
944
|
+
this._renderer.removeChild(cached.element?.parentElement, cached.element);
|
|
945
|
+
}
|
|
946
|
+
this._executedCommandsCache.delete(id);
|
|
617
947
|
}
|
|
618
|
-
|
|
619
|
-
return
|
|
948
|
+
_getComponentRootNode(componentRef) {
|
|
949
|
+
return componentRef.hostView.rootNodes[0];
|
|
620
950
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
951
|
+
_updateComponentInputs(command, componentRef) {
|
|
952
|
+
for (const [key, value] of Object.entries(command[COMPONENT_RENDER_COMMAND_POSITION.INPUTS])) {
|
|
953
|
+
if (!componentRef.instance[key])
|
|
954
|
+
continue;
|
|
955
|
+
componentRef.setInput(key, value);
|
|
626
956
|
}
|
|
627
|
-
this._data = v ?? null;
|
|
628
957
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
958
|
+
_findParent(command) {
|
|
959
|
+
const hostElement = this._elementRef.nativeElement;
|
|
960
|
+
const nestingLevel = command[RENDER_COMMAND_POSITION.NESTING_LEVEL];
|
|
961
|
+
let parentElement = undefined;
|
|
962
|
+
if (nestingLevel === 0) {
|
|
963
|
+
parentElement = hostElement;
|
|
632
964
|
}
|
|
965
|
+
else {
|
|
966
|
+
// Reverse search all render commands beginning from the current one using a for loop.
|
|
967
|
+
// The parent is found when the command's nesting level ist the same as the current nesting level minus 1.
|
|
968
|
+
// If the parent is not found, throw an error.
|
|
969
|
+
const allCommands = this.renderCommands();
|
|
970
|
+
const currentCommandIndex = command[RENDER_COMMAND_POSITION.INDEX];
|
|
971
|
+
let parentCommand = null;
|
|
972
|
+
for (let i = currentCommandIndex - 1; i >= 0; i--) {
|
|
973
|
+
const cmd = allCommands[i];
|
|
974
|
+
if (!cmd) {
|
|
975
|
+
throw new Error('Command not found!');
|
|
976
|
+
}
|
|
977
|
+
if (cmd[RENDER_COMMAND_POSITION.NESTING_LEVEL] === nestingLevel - 1) {
|
|
978
|
+
parentCommand = cmd;
|
|
979
|
+
break;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (!parentCommand) {
|
|
983
|
+
throw new Error('Parent command not found!');
|
|
984
|
+
}
|
|
985
|
+
parentElement = this._executedCommandsCache.get(getRenderCommandId(parentCommand))?.element;
|
|
986
|
+
}
|
|
987
|
+
if (!parentElement) {
|
|
988
|
+
throw new Error('Parent element not found!');
|
|
989
|
+
}
|
|
990
|
+
return parentElement;
|
|
633
991
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
992
|
+
_findFollowingElement(command) {
|
|
993
|
+
const cacheArray = Array.from(this._executedCommandsCache.values());
|
|
994
|
+
let nextElement = undefined;
|
|
995
|
+
const domPosition = command[RENDER_COMMAND_POSITION.DOM_POSITION];
|
|
996
|
+
const nestingLevel = command[RENDER_COMMAND_POSITION.NESTING_LEVEL];
|
|
997
|
+
const type = command[RENDER_COMMAND_POSITION.TYPE];
|
|
998
|
+
// Try to find an already rendered element with a domPosition that is greater than the current domPosition. If there are multiple, use the one with the smallest domPosition.
|
|
999
|
+
// If it does not exist, use .appendChild to simply append the element to the end of the parent element.
|
|
1000
|
+
// If it does exist, use .insertBefore to insert the element before the found element.
|
|
1001
|
+
for (const cached of cacheArray) {
|
|
1002
|
+
if (cached.command[RENDER_COMMAND_POSITION.DOM_POSITION] > domPosition &&
|
|
1003
|
+
cached.command[RENDER_COMMAND_POSITION.NESTING_LEVEL] === nestingLevel &&
|
|
1004
|
+
// Make sure the element does not find itself
|
|
1005
|
+
cached.command[RENDER_COMMAND_POSITION.TYPE] !== type) {
|
|
1006
|
+
nextElement = cached.element;
|
|
1007
|
+
break;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return nextElement;
|
|
652
1011
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}
|
|
660
|
-
type: Input
|
|
661
|
-
}], fileClass: [{
|
|
662
|
-
type: Input
|
|
663
|
-
}] } });
|
|
664
|
-
|
|
665
|
-
class ContentfulVideoComponent {
|
|
666
|
-
constructor() {
|
|
667
|
-
this._richTextData = inject(RICH_TEXT_RENDERER_COMPONENT_DATA, { optional: true });
|
|
668
|
-
this._data = null;
|
|
669
|
-
this.videoClass = null;
|
|
1012
|
+
_renderInsertOrAppend(nodeToRender, parentElement, nextElement) {
|
|
1013
|
+
if (nextElement) {
|
|
1014
|
+
this._renderer.insertBefore(parentElement, nodeToRender, nextElement);
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
this._renderer.appendChild(parentElement, nodeToRender);
|
|
1018
|
+
}
|
|
670
1019
|
}
|
|
671
|
-
|
|
672
|
-
|
|
1020
|
+
_debugVisualizeRenderCommands(commands) {
|
|
1021
|
+
const debug = commands.map((command) => this._stringifyRenderCommand(command)).flat();
|
|
1022
|
+
const prettified = formatHTML(debug);
|
|
1023
|
+
return prettified;
|
|
673
1024
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
return
|
|
1025
|
+
_stringifyRenderCommand(command) {
|
|
1026
|
+
const nestAttr = `nest="${command[RENDER_COMMAND_POSITION.NESTING_LEVEL]}"`;
|
|
1027
|
+
const domAttr = `dom="${command[RENDER_COMMAND_POSITION.DOM_POSITION]}"`;
|
|
1028
|
+
if (isHtmlOpenRenderCommand(command)) {
|
|
1029
|
+
return `<${command[HTML_OPEN_RENDER_COMMAND_POSITION.TAG_NAME]} ${nestAttr} ${domAttr}>`;
|
|
679
1030
|
}
|
|
680
|
-
if (
|
|
681
|
-
|
|
1031
|
+
else if (isHtmlCloseRenderCommand(command)) {
|
|
1032
|
+
return `</${command[HTML_CLOSE_RENDER_COMMAND_POSITION.TAG_NAME]} ${nestAttr} ${domAttr}>`;
|
|
682
1033
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
if (
|
|
687
|
-
|
|
1034
|
+
else if (isTextRenderCommand(command)) {
|
|
1035
|
+
return [`<span ${nestAttr} ${domAttr}>`, command[TEXT_RENDER_COMMAND_POSITION.TEXT], '</span>'];
|
|
1036
|
+
}
|
|
1037
|
+
else if (isComponentRenderCommand(command)) {
|
|
1038
|
+
const selector = command[COMPONENT_RENDER_COMMAND_POSITION.COMPONENT_TYPE];
|
|
1039
|
+
return [`<${selector} ${nestAttr} ${domAttr}>`, `</${selector}>`];
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
return 'UNKNOWN';
|
|
688
1043
|
}
|
|
689
1044
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
</video>
|
|
1045
|
+
_stringifyRenderInstruction(instruction) {
|
|
1046
|
+
const type = instruction[RENDER_INSTRUCTION_POSITION.TYPE];
|
|
1047
|
+
const translatedType = Object.entries(RENDER_INSTRUCTION_TYPE).find(([, value]) => value === type)?.[0];
|
|
1048
|
+
const command = this._stringifyRenderCommand(instruction[RENDER_INSTRUCTION_POSITION.COMMAND]);
|
|
1049
|
+
return `${translatedType}: ${command}`;
|
|
696
1050
|
}
|
|
697
|
-
|
|
1051
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: ContentfulRichTextRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1052
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "17.3.3", type: ContentfulRichTextRendererComponent, isStandalone: true, selector: "et-contentful-rich-text-renderer", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: true, transformFunction: null }, richTextPath: { classPropertyName: "richTextPath", publicName: "richTextPath", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "et-contentful-rich-text-renderer" }, ngImport: i0, template: ``, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
698
1053
|
}
|
|
699
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.
|
|
1054
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: ContentfulRichTextRendererComponent, decorators: [{
|
|
700
1055
|
type: Component,
|
|
701
1056
|
args: [{
|
|
702
|
-
selector: 'et-contentful-
|
|
703
|
-
template:
|
|
704
|
-
@if (data) {
|
|
705
|
-
<video [ngClass]="videoClass" controls>
|
|
706
|
-
<source src="{{ data.url }}" type="{{ data.contentType }}" />
|
|
707
|
-
</video>
|
|
708
|
-
}
|
|
709
|
-
`,
|
|
710
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1057
|
+
selector: 'et-contentful-rich-text-renderer',
|
|
1058
|
+
template: ``,
|
|
711
1059
|
encapsulation: ViewEncapsulation.None,
|
|
1060
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
712
1061
|
standalone: true,
|
|
713
|
-
|
|
1062
|
+
host: {
|
|
1063
|
+
class: 'et-contentful-rich-text-renderer',
|
|
1064
|
+
},
|
|
714
1065
|
}]
|
|
715
|
-
}],
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
1066
|
+
}], ctorParameters: () => [] });
|
|
1067
|
+
function formatHTML(html) {
|
|
1068
|
+
const result = [];
|
|
1069
|
+
let indent = 0;
|
|
1070
|
+
for (const tag of html) {
|
|
1071
|
+
if (tag.startsWith('</')) {
|
|
1072
|
+
indent--;
|
|
1073
|
+
}
|
|
1074
|
+
result.push(' '.repeat(indent * 2) + tag);
|
|
1075
|
+
if (tag.startsWith('<') && !tag.startsWith('</')) {
|
|
1076
|
+
indent++;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return result.join('\n');
|
|
1080
|
+
}
|
|
720
1081
|
|
|
721
1082
|
const ContentfulImports = [
|
|
722
1083
|
ContentfulAudioComponent,
|
|
@@ -740,26 +1101,16 @@ const GQL_FRAGMENT_CONTENTFUL_ASSET = gql `
|
|
|
740
1101
|
size
|
|
741
1102
|
}
|
|
742
1103
|
`;
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
...AssetData
|
|
750
|
-
}
|
|
751
|
-
alt
|
|
752
|
-
caption
|
|
753
|
-
resizeBehavior
|
|
754
|
-
focusArea
|
|
755
|
-
quality
|
|
756
|
-
__typename
|
|
757
|
-
}
|
|
758
|
-
`;
|
|
1104
|
+
|
|
1105
|
+
const provideContentfulConfig = (contentfulConfig = {}) => {
|
|
1106
|
+
return { provide: CONTENTFUL_CONFIG, useValue: createContentfulConfig(contentfulConfig) };
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
// Burst cache
|
|
759
1110
|
|
|
760
1111
|
/**
|
|
761
1112
|
* Generated bundle index. Do not edit.
|
|
762
1113
|
*/
|
|
763
1114
|
|
|
764
|
-
export { CONTENTFUL_CONFIG, ContentfulAudioComponent, ContentfulFileComponent, ContentfulImageComponent, ContentfulImports, ContentfulRichTextRendererComponent, ContentfulVideoComponent, GQL_FRAGMENT_CONTENTFUL_ASSET,
|
|
1115
|
+
export { COMPONENT_RENDER_COMMAND_POSITION, CONTENTFUL_CONFIG, ContentfulAudioComponent, ContentfulFileComponent, ContentfulImageComponent, ContentfulImports, ContentfulRichTextRendererComponent, ContentfulVideoComponent, GQL_FRAGMENT_CONTENTFUL_ASSET, HTML_CLOSE_RENDER_COMMAND_POSITION, HTML_OPEN_RENDER_COMMAND_POSITION, RENDER_COMMAND_POSITION, RENDER_COMMAND_TYPE, RENDER_INSTRUCTION_POSITION, RENDER_INSTRUCTION_TYPE, TEXT_RENDER_COMMAND_POSITION, createContentfulConfig, generateContentfulImageSources, generateDefaultContentfulImageSource, getRenderCommandId, isComponentRenderCommand, isExecutedComponentCommandCacheItem, isExecutedHtmlOrTextCommandCacheItem, isExecutedTextCommandCacheItem, isHtmlCloseRenderCommand, isHtmlOpenRenderCommand, isRichTextRootNode, isTextRenderCommand, marksToClass, parseContentfulImageSize, provideContentfulConfig, translateContentfulNodeTypeToHtmlTag };
|
|
765
1116
|
//# sourceMappingURL=ethlete-contentful.mjs.map
|