@ethlete/contentful 4.0.0-next.12 → 4.0.0-next.14
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 +29 -0
- package/fesm2022/ethlete-contentful.mjs +327 -128
- package/fesm2022/ethlete-contentful.mjs.map +1 -1
- package/generators/generators.json +6 -1
- package/generators/migrate-contentful-asset-classes/migration.js +163 -0
- package/generators/migrate-contentful-asset-classes/schema.json +26 -0
- package/generators/migrate-contentful-default-components/contentful-default-components.js +15 -1
- package/generators/migrate-contentful-default-components/migration.js +22 -5
- package/generators/migrate-contentful-default-components/schema.json +1 -1
- package/generators/migrate-to-contentful-v5/migration.js +3 -3
- package/migrations.json +12 -1
- package/package.json +6 -6
- package/types/ethlete-contentful.d.ts +88 -26
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { formatFiles } from '@nx/devkit';
|
|
2
|
+
import { createMigrationScope } from '../migrate-to-contentful-v5/migration-scope.js';
|
|
3
|
+
export const CONTENTFUL_ASSET_CLASSES_REPORT_PATH = 'contentful-asset-classes-migration-tasks.md';
|
|
4
|
+
const REMOVED_CLASS_INPUTS = {
|
|
5
|
+
'et-contentful-video': { videoClass: 'et-contentful-video-video' },
|
|
6
|
+
'et-contentful-audio': {
|
|
7
|
+
audioClass: 'et-contentful-audio-audio',
|
|
8
|
+
figureClass: 'et-contentful-audio-figure',
|
|
9
|
+
figcaptionClass: 'et-contentful-audio-figcaption',
|
|
10
|
+
},
|
|
11
|
+
'et-contentful-file': { fileClass: 'et-contentful-file-anchor' },
|
|
12
|
+
'et-contentful-link': { textClass: 'et-contentful-link-anchor', anchorClass: 'et-contentful-link-anchor' },
|
|
13
|
+
};
|
|
14
|
+
const TAG_REGEX = new RegExp(`<(${Object.keys(REMOVED_CLASS_INPUTS).join('|')})(?=[\\s/>])`, 'g');
|
|
15
|
+
const INLINE_TEMPLATE_REGEX = /template\s*:\s*`([\s\S]*?)`/g;
|
|
16
|
+
const LINK_INPUT_DECLARATION_REGEX = /\b(textClass|anchorClass)\s*=\s*input\b/g;
|
|
17
|
+
const lineOf = (source, index) => source.slice(0, index).split('\n').length;
|
|
18
|
+
const findTagEnd = (source, from) => {
|
|
19
|
+
let quote = null;
|
|
20
|
+
for (let index = from; index < source.length; index++) {
|
|
21
|
+
const character = source[index];
|
|
22
|
+
if (quote) {
|
|
23
|
+
if (character === quote)
|
|
24
|
+
quote = null;
|
|
25
|
+
}
|
|
26
|
+
else if (character === '"' || character === "'") {
|
|
27
|
+
quote = character;
|
|
28
|
+
}
|
|
29
|
+
else if (character === '>') {
|
|
30
|
+
return index;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return -1;
|
|
34
|
+
};
|
|
35
|
+
const isInsideQuotes = (tag, index) => {
|
|
36
|
+
let quote = null;
|
|
37
|
+
for (let position = 0; position < index; position++) {
|
|
38
|
+
const character = tag[position];
|
|
39
|
+
if (quote) {
|
|
40
|
+
if (character === quote)
|
|
41
|
+
quote = null;
|
|
42
|
+
}
|
|
43
|
+
else if (character === '"' || character === "'") {
|
|
44
|
+
quote = character;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return quote !== null;
|
|
48
|
+
};
|
|
49
|
+
const removeClassInputs = (template) => {
|
|
50
|
+
const removals = [];
|
|
51
|
+
let content = '';
|
|
52
|
+
let cursor = 0;
|
|
53
|
+
for (const tagMatch of template.matchAll(TAG_REGEX)) {
|
|
54
|
+
const start = tagMatch.index ?? 0;
|
|
55
|
+
if (start < cursor)
|
|
56
|
+
continue;
|
|
57
|
+
const end = findTagEnd(template, start);
|
|
58
|
+
if (end === -1)
|
|
59
|
+
break;
|
|
60
|
+
const tagName = tagMatch[1] ?? '';
|
|
61
|
+
const inputs = Object.keys(REMOVED_CLASS_INPUTS[tagName] ?? {});
|
|
62
|
+
const attributeRegex = new RegExp(`(\\s+)(\\[?(${inputs.join('|')})\\]?(?:\\s*=\\s*(?:"[^"]*"|'[^']*'))?)(?=[\\s/>])`, 'g');
|
|
63
|
+
const tag = template.slice(start, end + 1);
|
|
64
|
+
const attributeMatches = [...tag.matchAll(attributeRegex)].filter((attributeMatch) => !isInsideQuotes(tag, attributeMatch.index ?? 0));
|
|
65
|
+
let nextTag = tag;
|
|
66
|
+
for (const attributeMatch of [...attributeMatches].reverse()) {
|
|
67
|
+
const index = attributeMatch.index ?? 0;
|
|
68
|
+
nextTag = nextTag.slice(0, index) + nextTag.slice(index + attributeMatch[0].length);
|
|
69
|
+
}
|
|
70
|
+
for (const attributeMatch of attributeMatches) {
|
|
71
|
+
removals.push({
|
|
72
|
+
offset: start + (attributeMatch.index ?? 0) + (attributeMatch[1] ?? '').length,
|
|
73
|
+
tagName,
|
|
74
|
+
input: attributeMatch[3] ?? '',
|
|
75
|
+
attribute: attributeMatch[2] ?? '',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
content += template.slice(cursor, start) + nextTag;
|
|
79
|
+
cursor = end + 1;
|
|
80
|
+
}
|
|
81
|
+
content += template.slice(cursor);
|
|
82
|
+
return { content, removals };
|
|
83
|
+
};
|
|
84
|
+
const createRemovalTask = (filePath, line, removal) => {
|
|
85
|
+
const replacement = REMOVED_CLASS_INPUTS[removal.tagName]?.[removal.input] ?? '';
|
|
86
|
+
return {
|
|
87
|
+
id: `asset-class-input:${filePath}:${line}:${removal.input}`,
|
|
88
|
+
file: filePath,
|
|
89
|
+
line,
|
|
90
|
+
message: `Removed \`${removal.attribute}\` from \`${removal.tagName}\`. Style the static \`.${replacement}\` class instead.`,
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
const migrateHtmlFile = (filePath, source) => {
|
|
94
|
+
const { content, removals } = removeClassInputs(source);
|
|
95
|
+
return {
|
|
96
|
+
content,
|
|
97
|
+
tasks: removals.map((removal) => createRemovalTask(filePath, lineOf(source, removal.offset), removal)),
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
const migrateTsFile = (filePath, source) => {
|
|
101
|
+
const tasks = [];
|
|
102
|
+
const content = source.replace(INLINE_TEMPLATE_REGEX, (match, template, offset) => {
|
|
103
|
+
const templateOffset = offset + match.indexOf('`') + 1;
|
|
104
|
+
const result = removeClassInputs(template);
|
|
105
|
+
for (const removal of result.removals) {
|
|
106
|
+
tasks.push(createRemovalTask(filePath, lineOf(source, templateOffset + removal.offset), removal));
|
|
107
|
+
}
|
|
108
|
+
return result.removals.length > 0 ? match.replace(template, () => result.content) : match;
|
|
109
|
+
});
|
|
110
|
+
for (const declaration of source.matchAll(LINK_INPUT_DECLARATION_REGEX)) {
|
|
111
|
+
const line = lineOf(source, declaration.index ?? 0);
|
|
112
|
+
tasks.push({
|
|
113
|
+
id: `link-class-input:${filePath}:${line}`,
|
|
114
|
+
file: filePath,
|
|
115
|
+
line,
|
|
116
|
+
message: `\`${declaration[1]}\` is declared as an input. If this is a custom \`components.link\`, the rich-text renderer no longer sets it - it passes \`marks\` (the shared mark types) and \`richText: true\` instead.`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return { content, tasks };
|
|
120
|
+
};
|
|
121
|
+
const renderReport = (tasks) => [
|
|
122
|
+
'# Contentful asset class inputs migration tasks',
|
|
123
|
+
'',
|
|
124
|
+
'The class inputs of `et-contentful-video`, `et-contentful-audio`, `et-contentful-file` and',
|
|
125
|
+
'`et-contentful-link` are gone. Every inner element carries a static `et-contentful-*` class instead.',
|
|
126
|
+
'The migration removed the bindings below; move their styling onto the named class. Delete this file when done.',
|
|
127
|
+
'',
|
|
128
|
+
...tasks.map((task) => [`## \`${task.id}\``, '', `- ${task.file}:${task.line}`, `- ${task.message}`, ''].join('\n')),
|
|
129
|
+
].join('\n');
|
|
130
|
+
export default async function migrateContentfulAssetClasses(tree, schema) {
|
|
131
|
+
console.log('\n🔄 Removing the Contentful asset class inputs...');
|
|
132
|
+
const scope = createMigrationScope(tree, schema);
|
|
133
|
+
console.log(` Scope: ${scope.describe()}`);
|
|
134
|
+
const tasks = [];
|
|
135
|
+
let filesChanged = 0;
|
|
136
|
+
scope.visit(tree, (filePath) => {
|
|
137
|
+
const isHtml = filePath.endsWith('.html');
|
|
138
|
+
const isTs = filePath.endsWith('.ts') && !filePath.endsWith('.d.ts');
|
|
139
|
+
if (!isHtml && !isTs)
|
|
140
|
+
return;
|
|
141
|
+
const before = tree.read(filePath, 'utf-8');
|
|
142
|
+
if (!before)
|
|
143
|
+
return;
|
|
144
|
+
const result = isHtml ? migrateHtmlFile(filePath, before) : migrateTsFile(filePath, before);
|
|
145
|
+
tasks.push(...result.tasks);
|
|
146
|
+
if (result.content !== before) {
|
|
147
|
+
tree.write(filePath, result.content);
|
|
148
|
+
filesChanged++;
|
|
149
|
+
console.log(` ✓ ${filePath}`);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
if (tasks.length > 0) {
|
|
153
|
+
tree.write(CONTENTFUL_ASSET_CLASSES_REPORT_PATH, renderReport(tasks));
|
|
154
|
+
}
|
|
155
|
+
if (!schema.skipFormat) {
|
|
156
|
+
await formatFiles(tree);
|
|
157
|
+
}
|
|
158
|
+
console.log(`\n✅ Updated ${filesChanged} file(s).`);
|
|
159
|
+
if (tasks.length > 0) {
|
|
160
|
+
console.log(`⚠️ ${tasks.length} site(s) need their styling moved — see ${CONTENTFUL_ASSET_CLASSES_REPORT_PATH}.`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "migrate-contentful-asset-classes",
|
|
4
|
+
"title": "Remove the Contentful asset and link class inputs",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"projects": {
|
|
8
|
+
"type": "array",
|
|
9
|
+
"items": { "type": "string" },
|
|
10
|
+
"description": "Nx projects to migrate. Defaults to every project in the workspace. Every site is rewritten independently, so any scope is safe — use this to keep the diff reviewable.",
|
|
11
|
+
"alias": "project"
|
|
12
|
+
},
|
|
13
|
+
"include": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": { "type": "string" },
|
|
16
|
+
"description": "Path prefixes to migrate, relative to the workspace root (e.g. 'libs/ui'). Unioned with `projects`. Defaults to the whole workspace.",
|
|
17
|
+
"alias": "includePath"
|
|
18
|
+
},
|
|
19
|
+
"skipFormat": {
|
|
20
|
+
"type": "boolean",
|
|
21
|
+
"description": "Skip formatting files after migration",
|
|
22
|
+
"default": false
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": []
|
|
26
|
+
}
|
|
@@ -19,9 +19,23 @@ const namesOwnComponents = (content, braceIndex) => {
|
|
|
19
19
|
}
|
|
20
20
|
return /(^|[,\s])components\s*:/.test(topLevel);
|
|
21
21
|
};
|
|
22
|
+
const contentfulImportNaming = (content, name) => [...content.matchAll(CONTENTFUL_IMPORT)].find((match) => name.test(match[1] ?? ''));
|
|
23
|
+
export const importsProvideContentfulConfig = (content) => contentfulImportNaming(content, /\bprovideContentfulConfig\b/) !== undefined;
|
|
24
|
+
export const findUnprovidedRendererTask = (filePath, content) => {
|
|
25
|
+
const match = contentfulImportNaming(content, /\b(ContentfulRichTextRendererComponent|ContentfulImports)\b/);
|
|
26
|
+
if (!match)
|
|
27
|
+
return null;
|
|
28
|
+
const line = lineOf(content, match.index ?? 0);
|
|
29
|
+
return {
|
|
30
|
+
id: taskId(filePath, line),
|
|
31
|
+
file: filePath,
|
|
32
|
+
line,
|
|
33
|
+
message: `Renders Contentful rich text, but no file calls \`provideContentfulConfig\`. Without it embedded assets are skipped and links lose router navigation. Add \`provideContentfulConfig({ ${SPREAD} })\` to the application providers.`,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
22
36
|
export const scanContentfulDefaultComponentsInFile = (filePath, content) => {
|
|
23
37
|
const tasks = [];
|
|
24
|
-
const importStatement =
|
|
38
|
+
const importStatement = contentfulImportNaming(content, /\bprovideContentfulConfig\b/);
|
|
25
39
|
if (!importStatement || content.includes(CONSTANT))
|
|
26
40
|
return { next: null, tasks };
|
|
27
41
|
const report = (index, message) => {
|
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
import { formatFiles } from '@nx/devkit';
|
|
1
|
+
import { formatFiles, visitNotIgnoredFiles } from '@nx/devkit';
|
|
2
2
|
import { createMigrationScope } from '../migrate-to-contentful-v5/migration-scope.js';
|
|
3
|
-
import { scanContentfulDefaultComponentsInFile, } from './contentful-default-components.js';
|
|
3
|
+
import { findUnprovidedRendererTask, importsProvideContentfulConfig, scanContentfulDefaultComponentsInFile, } from './contentful-default-components.js';
|
|
4
4
|
export const CONTENTFUL_DEFAULT_COMPONENTS_REPORT_PATH = 'contentful-default-components-migration-tasks.md';
|
|
5
|
+
const isSourceFile = (filePath) => filePath.endsWith('.ts') && !filePath.endsWith('.d.ts');
|
|
6
|
+
const workspaceProvidesContentfulConfig = (tree) => {
|
|
7
|
+
let found = false;
|
|
8
|
+
visitNotIgnoredFiles(tree, '', (filePath) => {
|
|
9
|
+
if (found || !isSourceFile(filePath))
|
|
10
|
+
return;
|
|
11
|
+
found = importsProvideContentfulConfig(tree.read(filePath, 'utf-8') ?? '');
|
|
12
|
+
});
|
|
13
|
+
return found;
|
|
14
|
+
};
|
|
5
15
|
const renderReport = (tasks) => [
|
|
6
16
|
'# Contentful default components migration tasks',
|
|
7
17
|
'',
|
|
8
18
|
'The shipped asset and link components are opt-in. Without them embedded assets are skipped and',
|
|
9
19
|
'hyperlinks render as plain anchors. The migration spread `CONTENTFUL_DEFAULT_COMPONENTS` into every',
|
|
10
|
-
'literal `provideContentfulConfig` call; the
|
|
20
|
+
'literal `provideContentfulConfig` call; the places below need it by hand:',
|
|
11
21
|
'',
|
|
12
22
|
'```ts',
|
|
13
23
|
"import { CONTENTFUL_DEFAULT_COMPONENTS, provideContentfulConfig } from '@ethlete/contentful';",
|
|
@@ -25,19 +35,26 @@ export default async function migrateContentfulDefaultComponents(tree, schema) {
|
|
|
25
35
|
console.log(` Scope: ${scope.describe()}`);
|
|
26
36
|
const changed = [];
|
|
27
37
|
const tasks = [];
|
|
38
|
+
const rendererTasks = [];
|
|
28
39
|
scope.visit(tree, (filePath) => {
|
|
29
|
-
if (!
|
|
40
|
+
if (!isSourceFile(filePath))
|
|
30
41
|
return;
|
|
31
42
|
const content = tree.read(filePath, 'utf-8');
|
|
32
43
|
if (!content)
|
|
33
44
|
return;
|
|
34
45
|
const result = scanContentfulDefaultComponentsInFile(filePath, content);
|
|
46
|
+
const rendererTask = findUnprovidedRendererTask(filePath, content);
|
|
35
47
|
tasks.push(...result.tasks);
|
|
48
|
+
if (rendererTask)
|
|
49
|
+
rendererTasks.push(rendererTask);
|
|
36
50
|
if (result.next !== null) {
|
|
37
51
|
tree.write(filePath, result.next);
|
|
38
52
|
changed.push(filePath);
|
|
39
53
|
}
|
|
40
54
|
});
|
|
55
|
+
if (rendererTasks.length > 0 && !workspaceProvidesContentfulConfig(tree)) {
|
|
56
|
+
tasks.push(...rendererTasks);
|
|
57
|
+
}
|
|
41
58
|
if (tasks.length > 0) {
|
|
42
59
|
tree.write(CONTENTFUL_DEFAULT_COMPONENTS_REPORT_PATH, renderReport(tasks));
|
|
43
60
|
}
|
|
@@ -48,7 +65,7 @@ export default async function migrateContentfulDefaultComponents(tree, schema) {
|
|
|
48
65
|
console.log(`\n✅ Spread CONTENTFUL_DEFAULT_COMPONENTS in ${changed.length} file(s).`);
|
|
49
66
|
}
|
|
50
67
|
if (tasks.length > 0) {
|
|
51
|
-
console.log(`\n⚠️ ${tasks.length}
|
|
68
|
+
console.log(`\n⚠️ ${tasks.length} place(s) need the default components by hand — see ${CONTENTFUL_DEFAULT_COMPONENTS_REPORT_PATH}.`);
|
|
52
69
|
}
|
|
53
70
|
else if (changed.length === 0) {
|
|
54
71
|
console.log('\n✅ Nothing to do.');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/schema",
|
|
3
3
|
"$id": "migrate-contentful-default-components",
|
|
4
|
-
"title": "
|
|
4
|
+
"title": "Spread CONTENTFUL_DEFAULT_COMPONENTS into provideContentfulConfig",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
7
7
|
"projects": {
|
|
@@ -3,7 +3,7 @@ import { createMigrationScope } from './migration-scope.js';
|
|
|
3
3
|
export const CONTENTFUL_V5_REPORT_PATH = 'contentful-v5-migration-tasks.md';
|
|
4
4
|
const COMPONENTS_PACKAGE = '@ethlete/components';
|
|
5
5
|
const COMPONENTS_VERSION = '^1.0.0-next.32';
|
|
6
|
-
/** Inputs that
|
|
6
|
+
/** Inputs that 4.0 dropped — the picture element now only carries static `et-picture-*` classes. */
|
|
7
7
|
const REMOVED_CLASS_INPUTS = ['imgClass', 'figureClass', 'figcaptionClass', 'pictureClass'];
|
|
8
8
|
/** Renderer internals that are no longer exported from `@ethlete/contentful`. */
|
|
9
9
|
const REMOVED_EXPORTS = [
|
|
@@ -188,7 +188,7 @@ const migratePackageJson = (tree, filePath) => {
|
|
|
188
188
|
return { changed, tasks };
|
|
189
189
|
};
|
|
190
190
|
const renderReport = (tasks) => [
|
|
191
|
-
'# @ethlete/contentful
|
|
191
|
+
'# @ethlete/contentful 4 migration tasks',
|
|
192
192
|
'',
|
|
193
193
|
'The codemod renamed the `et-contentful-image` `hasPriority` input to `priority`, removed the dropped',
|
|
194
194
|
'`useTailwindClasses` config option and made sure `@ethlete/components` is declared. The sites below',
|
|
@@ -205,7 +205,7 @@ const renderReport = (tasks) => [
|
|
|
205
205
|
...tasks.map((task) => [`## \`${task.id}\``, '', `- ${task.file}${task.line ? `:${task.line}` : ''}`, `- ${task.message}`, ''].join('\n')),
|
|
206
206
|
].join('\n');
|
|
207
207
|
export default async function migrateToContentfulV5(tree, schema) {
|
|
208
|
-
console.log('\n🔄 Migrating to @ethlete/contentful
|
|
208
|
+
console.log('\n🔄 Migrating to @ethlete/contentful 4...');
|
|
209
209
|
const scope = createMigrationScope(tree, schema);
|
|
210
210
|
console.log(` Scope: ${scope.describe()}`);
|
|
211
211
|
const tasks = [];
|
package/migrations.json
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"name": "to-contentful-v5",
|
|
5
5
|
"version": "4.0.0-next.7",
|
|
6
6
|
"kind": "auto",
|
|
7
|
+
"level": "required",
|
|
7
8
|
"description": "Move to the components picture, the renamed image inputs and the removed renderer internals.",
|
|
8
9
|
"generator": "@ethlete/contentful:migrate-to-contentful-v5",
|
|
9
10
|
"docs": "/contentful/"
|
|
@@ -12,9 +13,19 @@
|
|
|
12
13
|
"name": "contentful-default-components",
|
|
13
14
|
"version": "4.0.0-next.12",
|
|
14
15
|
"kind": "auto",
|
|
15
|
-
"
|
|
16
|
+
"level": "required",
|
|
17
|
+
"description": "Spread CONTENTFUL_DEFAULT_COMPONENTS into provideContentfulConfig, and report the calls that need it by hand.",
|
|
16
18
|
"generator": "@ethlete/contentful:migrate-contentful-default-components",
|
|
17
19
|
"docs": "/contentful/"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "contentful-asset-classes",
|
|
23
|
+
"version": "4.0.0-next.13",
|
|
24
|
+
"kind": "auto",
|
|
25
|
+
"level": "required",
|
|
26
|
+
"description": "Remove the dropped video, audio, file and link class inputs, and report each removal with the static class that replaces it.",
|
|
27
|
+
"generator": "@ethlete/contentful:migrate-contentful-asset-classes",
|
|
28
|
+
"docs": "/contentful/"
|
|
18
29
|
}
|
|
19
30
|
]
|
|
20
31
|
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ethlete/contentful",
|
|
3
|
-
"version": "4.0.0-next.
|
|
3
|
+
"version": "4.0.0-next.14",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"peerDependencies": {
|
|
7
|
-
"@angular/common": "22.1.
|
|
8
|
-
"@angular/core": "22.1.
|
|
9
|
-
"@angular/router": "22.1.
|
|
10
|
-
"@contentful/rich-text-types": "17.
|
|
7
|
+
"@angular/common": "^22.1.0",
|
|
8
|
+
"@angular/core": "^22.1.0",
|
|
9
|
+
"@angular/router": "^22.1.0",
|
|
10
|
+
"@contentful/rich-text-types": "^17.0.0",
|
|
11
11
|
"@ethlete/components": "^1.0.0-next.32",
|
|
12
12
|
"@ethlete/core": "^5.0.0-beta.11",
|
|
13
13
|
"@ethlete/query": "^6.0.0-beta.8",
|
|
14
|
-
"@nx/devkit": "23.
|
|
14
|
+
"@nx/devkit": "^23.0.0"
|
|
15
15
|
},
|
|
16
16
|
"peerDependenciesMeta": {
|
|
17
17
|
"@nx/devkit": {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import * as _angular_core from '@angular/core';
|
|
2
2
|
import { Type, InputSignal, InjectionToken, Provider } from '@angular/core';
|
|
3
|
-
import * as _ethlete_core from '@ethlete/core';
|
|
4
|
-
import { NgClassType } from '@ethlete/core';
|
|
5
3
|
import { GQL } from '@ethlete/query';
|
|
6
4
|
import { Mark, BLOCKS, INLINES, NodeData, Block } from '@contentful/rich-text-types';
|
|
5
|
+
import * as _ethlete_core from '@ethlete/core';
|
|
7
6
|
import * as _ethlete_contentful from '@ethlete/contentful';
|
|
8
7
|
import * as _ethlete_components from '@ethlete/components';
|
|
9
8
|
import { PictureSource } from '@ethlete/components';
|
|
@@ -88,6 +87,43 @@ type ContentfulGqlCollectionFilterVariables<TCustomWhere extends Record<string,
|
|
|
88
87
|
locale?: string;
|
|
89
88
|
};
|
|
90
89
|
|
|
90
|
+
type ContentfulGqlRichTextEntry = {
|
|
91
|
+
__typename?: string | null;
|
|
92
|
+
sys: {
|
|
93
|
+
id: string;
|
|
94
|
+
};
|
|
95
|
+
[field: string]: unknown;
|
|
96
|
+
};
|
|
97
|
+
type ContentfulGqlLinkList<T> = (T | null | undefined)[] | null;
|
|
98
|
+
/**
|
|
99
|
+
* A rich-text field as returned by the Contentful GraphQL API. Select `json` and the `links` the
|
|
100
|
+
* document references, and pass the field to `[gqlRichText]` as-is.
|
|
101
|
+
*/
|
|
102
|
+
type ContentfulGqlRichText = {
|
|
103
|
+
json: RichTextResponse;
|
|
104
|
+
links?: {
|
|
105
|
+
assets?: {
|
|
106
|
+
block?: ContentfulGqlLinkList<ContentfulGqlAsset>;
|
|
107
|
+
hyperlink?: ContentfulGqlLinkList<ContentfulGqlAsset>;
|
|
108
|
+
} | null;
|
|
109
|
+
entries?: {
|
|
110
|
+
block?: ContentfulGqlLinkList<ContentfulGqlRichTextEntry>;
|
|
111
|
+
inline?: ContentfulGqlLinkList<ContentfulGqlRichTextEntry>;
|
|
112
|
+
hyperlink?: ContentfulGqlLinkList<ContentfulGqlRichTextEntry>;
|
|
113
|
+
} | null;
|
|
114
|
+
} | null;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Maps the `links` of a GraphQL rich-text field to the entries and assets the renderer resolves
|
|
118
|
+
* embeds against. An entry's content type id is the id in `contentTypeIds` whose GraphQL type name
|
|
119
|
+
* (PascalCase, separators dropped: `product-teaser` → `ProductTeaser`) equals its `__typename`, else the
|
|
120
|
+
* `__typename` with the first letter lower-cased. Entries without a `__typename` are dropped.
|
|
121
|
+
*/
|
|
122
|
+
declare const mapContentfulGqlLinks: (links: ContentfulGqlRichText["links"], contentTypeIds?: readonly string[]) => {
|
|
123
|
+
assets: ContentfulRestAsset[];
|
|
124
|
+
entries: ContentfulEntry[];
|
|
125
|
+
};
|
|
126
|
+
|
|
91
127
|
type MarkTagName = 'strong' | 'em' | 'u' | 'code' | 's' | 'sub' | 'sup';
|
|
92
128
|
/**
|
|
93
129
|
* Translates contentful text marks into the semantic html elements the marked text is
|
|
@@ -149,6 +185,9 @@ declare class ContentfulRichTextRendererComponent {
|
|
|
149
185
|
private elementRef;
|
|
150
186
|
private config;
|
|
151
187
|
private document;
|
|
188
|
+
private location;
|
|
189
|
+
private url;
|
|
190
|
+
private injector;
|
|
152
191
|
/**
|
|
153
192
|
* The contentful response gotten via their REST api.
|
|
154
193
|
* @see https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/entries/entries-collection
|
|
@@ -158,9 +197,25 @@ declare class ContentfulRichTextRendererComponent {
|
|
|
158
197
|
* The path to where the rich text field is inside the contentful response. Dot and array notation is allowed.
|
|
159
198
|
* @example "items[0].fields.html"
|
|
160
199
|
*/
|
|
161
|
-
richTextPath: _angular_core.InputSignal<string>;
|
|
200
|
+
richTextPath: _angular_core.InputSignal<string | undefined>;
|
|
201
|
+
/**
|
|
202
|
+
* A rich-text field from the Contentful GraphQL API, passed as-is. An alternative to `content` and
|
|
203
|
+
* `richTextPath`; setting both is an error in dev mode.
|
|
204
|
+
* @example { json, links: { assets: { block, hyperlink }, entries: { block, inline, hyperlink } } }
|
|
205
|
+
*/
|
|
206
|
+
gqlRichText: _angular_core.InputSignal<ContentfulGqlRichText | null | undefined>;
|
|
207
|
+
/**
|
|
208
|
+
* A bare rich-text document, e.g. a rich-text field of an embedded entry. Pair it with `includes` so its
|
|
209
|
+
* embeds resolve. An alternative to `content` / `richTextPath` and `gqlRichText`.
|
|
210
|
+
* @example <et-contentful-rich-text-renderer [richText]="fields().body" [includes]="includes()" />
|
|
211
|
+
*/
|
|
212
|
+
richText: _angular_core.InputSignal<RichTextResponse | null | undefined>;
|
|
213
|
+
/** The linked entries and assets `richText` resolves its embeds against, e.g. a custom component's `includes`. */
|
|
214
|
+
includes: _angular_core.InputSignal<ContentfulIncludeMap | null | undefined>;
|
|
215
|
+
private ancestorEntryIds;
|
|
162
216
|
private readonly executedCommandsCache;
|
|
163
217
|
private contentIncludesMap;
|
|
218
|
+
private linkLookup;
|
|
164
219
|
private richTextData;
|
|
165
220
|
private renderCommands;
|
|
166
221
|
private renderInstructions;
|
|
@@ -175,7 +230,7 @@ declare class ContentfulRichTextRendererComponent {
|
|
|
175
230
|
private findParent;
|
|
176
231
|
private renderInsertAfter;
|
|
177
232
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ContentfulRichTextRendererComponent, never>;
|
|
178
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentfulRichTextRendererComponent, "et-contentful-rich-text-renderer", never, { "content": { "alias": "content"; "required":
|
|
233
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentfulRichTextRendererComponent, "et-contentful-rich-text-renderer", never, { "content": { "alias": "content"; "required": false; "isSignal": true; }; "richTextPath": { "alias": "richTextPath"; "required": false; "isSignal": true; }; "gqlRichText": { "alias": "gqlRichText"; "required": false; "isSignal": true; }; "richText": { "alias": "richText"; "required": false; "isSignal": true; }; "includes": { "alias": "includes"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
179
234
|
}
|
|
180
235
|
|
|
181
236
|
declare const isRichTextRootNode: (node: unknown) => node is RichTextResponse;
|
|
@@ -194,8 +249,8 @@ type ComponentLikeWithAsset = Type<{
|
|
|
194
249
|
type ComponentLikeWithLink = Type<{
|
|
195
250
|
href: InputSignal<string>;
|
|
196
251
|
text: InputSignal<string>;
|
|
197
|
-
|
|
198
|
-
|
|
252
|
+
marks?: InputSignal<readonly string[]>;
|
|
253
|
+
richText?: InputSignal<boolean>;
|
|
199
254
|
}>;
|
|
200
255
|
type ComponentLikeWithContentfulRendererInputs = Type<{
|
|
201
256
|
fields?: InputSignal<any>;
|
|
@@ -221,14 +276,21 @@ type ContentfulConfig = {
|
|
|
221
276
|
*/
|
|
222
277
|
customComponents: Record<string, ComponentLikeWithContentfulRendererInputs>;
|
|
223
278
|
/**
|
|
224
|
-
* Additional
|
|
279
|
+
* Additional hosts that should be treated as internal links (in addition to the current page's host).
|
|
225
280
|
* Useful when the app runs on localhost during development but Contentful content references the production domain.
|
|
226
|
-
* A
|
|
227
|
-
*
|
|
281
|
+
* A host matches exactly, so a subdomain needs its own entry; `'*.example.com'` matches every subdomain of
|
|
282
|
+
* `example.com` (but not `example.com` itself).
|
|
228
283
|
*
|
|
229
|
-
* @example ['example.com']
|
|
284
|
+
* @example ['example.com', '*.example.com']
|
|
230
285
|
*/
|
|
231
286
|
internalHosts: string[];
|
|
287
|
+
/**
|
|
288
|
+
* Turns the entry an `entry-hyperlink` points at into an href. Return `null` to render the link text without a link.
|
|
289
|
+
* Without it, entry hyperlinks render as text.
|
|
290
|
+
*
|
|
291
|
+
* @example (entry) => (entry.sys.contentType.sys.id === 'page' ? `/${entry.fields.slug}` : null)
|
|
292
|
+
*/
|
|
293
|
+
entryHref: ((entry: ContentfulEntry) => string | null) | null;
|
|
232
294
|
/**
|
|
233
295
|
* Default options for the contentful image api
|
|
234
296
|
*/
|
|
@@ -250,12 +312,15 @@ type ContentfulConfig = {
|
|
|
250
312
|
*/
|
|
251
313
|
sizes: string[];
|
|
252
314
|
/**
|
|
253
|
-
* Background color for the image in hex. Eg. `"000000"`
|
|
315
|
+
* Background color for the image in hex. Eg. `"000000"` (a leading `#` is stripped)
|
|
254
316
|
*/
|
|
255
317
|
backgroundColor: string | null;
|
|
256
318
|
};
|
|
257
319
|
};
|
|
258
|
-
type ContentfulConfigOptions = Partial<ContentfulConfig
|
|
320
|
+
type ContentfulConfigOptions = Partial<Omit<ContentfulConfig, 'imageOptions'>> & {
|
|
321
|
+
/** Merged one level deep into the defaults, so a single option can be overridden. */
|
|
322
|
+
imageOptions?: Partial<ContentfulConfig['imageOptions']>;
|
|
323
|
+
};
|
|
259
324
|
type ContentfulLinkType = 'Space' | 'ContentType' | 'Environment' | 'Entry' | 'Asset' | 'Tag';
|
|
260
325
|
type ContentfulLink<T extends ContentfulLinkType> = {
|
|
261
326
|
type: 'Link';
|
|
@@ -349,15 +414,12 @@ type RichTextResponse = {
|
|
|
349
414
|
|
|
350
415
|
declare class ContentfulAudioComponent {
|
|
351
416
|
asset: _angular_core.InputSignal<ContentfulGqlAsset | ContentfulRestAsset | null | undefined>;
|
|
352
|
-
audioClass: _angular_core.InputSignal<NgClassType>;
|
|
353
|
-
figureClass: _angular_core.InputSignal<NgClassType>;
|
|
354
|
-
figcaptionClass: _angular_core.InputSignal<NgClassType>;
|
|
355
417
|
protected data: _angular_core.Signal<{
|
|
356
418
|
url: string;
|
|
357
419
|
title: string | null;
|
|
358
420
|
} | null>;
|
|
359
421
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ContentfulAudioComponent, never>;
|
|
360
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentfulAudioComponent, "et-contentful-audio", never, { "asset": { "alias": "asset"; "required": true; "isSignal": true; };
|
|
422
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentfulAudioComponent, "et-contentful-audio", never, { "asset": { "alias": "asset"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
361
423
|
}
|
|
362
424
|
|
|
363
425
|
/** Every string `et-contentful-file` renders itself. Override them app-wide with {@link provideContentfulFileLabels}. */
|
|
@@ -381,14 +443,13 @@ declare const CONTENTFUL_FILE_LABELS: _angular_core.InjectionToken<_ethlete_core
|
|
|
381
443
|
declare class ContentfulFileComponent {
|
|
382
444
|
protected labels: _angular_core.Signal<_ethlete_contentful.ContentfulFileLabels>;
|
|
383
445
|
asset: _angular_core.InputSignal<ContentfulGqlAsset | ContentfulRestAsset | null | undefined>;
|
|
384
|
-
fileClass: _angular_core.InputSignal<NgClassType>;
|
|
385
446
|
protected data: _angular_core.Signal<{
|
|
386
447
|
url: string;
|
|
387
448
|
size: number | null;
|
|
388
449
|
title: string | null;
|
|
389
450
|
} | null>;
|
|
390
451
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ContentfulFileComponent, never>;
|
|
391
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentfulFileComponent, "et-contentful-file", never, { "asset": { "alias": "asset"; "required": true; "isSignal": true; };
|
|
452
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentfulFileComponent, "et-contentful-file", never, { "asset": { "alias": "asset"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
392
453
|
}
|
|
393
454
|
|
|
394
455
|
declare class ContentfulImageComponent {
|
|
@@ -443,31 +504,32 @@ declare const generateContentfulImageSources: (data: ContentfulRestAsset | Conte
|
|
|
443
504
|
|
|
444
505
|
declare class ContentfulLinkComponent {
|
|
445
506
|
private document;
|
|
507
|
+
private location;
|
|
446
508
|
private router;
|
|
447
509
|
private config;
|
|
448
510
|
private url;
|
|
449
511
|
href: _angular_core.InputSignal<string>;
|
|
450
512
|
text: _angular_core.InputSignal<string>;
|
|
451
|
-
|
|
452
|
-
|
|
513
|
+
marks: _angular_core.InputSignal<readonly string[]>;
|
|
514
|
+
richText: _angular_core.InputSignal<boolean>;
|
|
453
515
|
protected usesRouterLink: _angular_core.Signal<boolean>;
|
|
516
|
+
protected anchorHref: _angular_core.Signal<string>;
|
|
454
517
|
protected openInNewTab: _angular_core.Signal<boolean>;
|
|
455
518
|
protected internalPath: _angular_core.Signal<string>;
|
|
456
519
|
protected internalUrlTree: _angular_core.Signal<_angular_router.UrlTree>;
|
|
457
|
-
protected
|
|
520
|
+
protected modifierClasses: _angular_core.Signal<string[]>;
|
|
458
521
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ContentfulLinkComponent, never>;
|
|
459
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentfulLinkComponent, "et-contentful-link", never, { "href": { "alias": "href"; "required": true; "isSignal": true; }; "text": { "alias": "text"; "required": true; "isSignal": true; }; "
|
|
522
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentfulLinkComponent, "et-contentful-link", never, { "href": { "alias": "href"; "required": true; "isSignal": true; }; "text": { "alias": "text"; "required": true; "isSignal": true; }; "marks": { "alias": "marks"; "required": false; "isSignal": true; }; "richText": { "alias": "richText"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
460
523
|
}
|
|
461
524
|
|
|
462
525
|
declare class ContentfulVideoComponent {
|
|
463
526
|
asset: _angular_core.InputSignal<ContentfulGqlAsset | ContentfulRestAsset | null | undefined>;
|
|
464
|
-
videoClass: _angular_core.InputSignal<NgClassType>;
|
|
465
527
|
protected data: _angular_core.Signal<{
|
|
466
528
|
url: string;
|
|
467
529
|
contentType: string | null;
|
|
468
530
|
} | null>;
|
|
469
531
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ContentfulVideoComponent, never>;
|
|
470
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentfulVideoComponent, "et-contentful-video", never, { "asset": { "alias": "asset"; "required": true; "isSignal": true; };
|
|
532
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentfulVideoComponent, "et-contentful-video", never, { "asset": { "alias": "asset"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
471
533
|
}
|
|
472
534
|
|
|
473
535
|
declare const CONTENTFUL_CONFIG: InjectionToken<ContentfulConfig>;
|
|
@@ -502,5 +564,5 @@ declare const CONTENTFUL_DEFAULT_COMPONENTS: {
|
|
|
502
564
|
declare const provideContentfulConfig: (contentfulConfig?: ContentfulConfigOptions | null) => Provider;
|
|
503
565
|
declare const isContentfulEntryType: <T extends ContentfulEntry>(entry: ContentfulEntry, type: string) => entry is T;
|
|
504
566
|
|
|
505
|
-
export { CONTENTFUL_CONFIG, CONTENTFUL_DEFAULT_COMPONENTS, CONTENTFUL_FILE_LABELS, ContentfulAudioComponent, ContentfulFileComponent, ContentfulImageComponent, ContentfulImports, ContentfulLinkComponent, ContentfulRichTextRendererComponent, ContentfulVideoComponent, DEFAULT_CONTENTFUL_FILE_LABELS, ET_CONTENTFUL_ANY_ENTRY_CONTENT_TYPE_SYS_ID, GQL_FRAGMENT_CONTENTFUL_ASSET, createContentfulConfig, createContentfulIncludeMap, generateContentfulImageSources, generateDefaultContentfulImageSource, injectContentfulConfig, injectContentfulFileLabels, isContentfulEntryType, isContentfulGqlAsset, isRichTextRootNode, marksToClass, marksToTags, parseContentfulImageSize, provideContentfulConfig, provideContentfulFileLabels, translateContentfulNodeTypeToHtmlTag };
|
|
506
|
-
export type { ComponentLikeWithAsset, ComponentLikeWithContentfulRendererInputs, ComponentLikeWithLink, ContentfulAssetComponents, ContentfulAssetFileData, ContentfulAssetImageData, ContentfulAssetLink, ContentfulAssetLinkItem, ContentfulCollection, ContentfulConfig, ContentfulConfigOptions, ContentfulContentTypeLink, ContentfulEntry, ContentfulEntryLink, ContentfulEntryLinkItem, ContentfulEntrySys, ContentfulEnvironmentLink, ContentfulFileLabels, ContentfulGqlAsset, ContentfulGqlCollectionFilterVariables, ContentfulGqlOrder, ContentfulGqlWhereFilter, ContentfulImageFocusArea, ContentfulImageResizeBehavior, ContentfulImageSourcesOptions, ContentfulIncludeMap, ContentfulLink, ContentfulLinkType, ContentfulMetadata, ContentfulRestAsset, ContentfulSpaceLink, ContentfulSys, ContentfulTagLink, ContentfulTagLinkItem, CreateContentfulIncludeMapConfig, RichTextResponse };
|
|
567
|
+
export { CONTENTFUL_CONFIG, CONTENTFUL_DEFAULT_COMPONENTS, CONTENTFUL_FILE_LABELS, ContentfulAudioComponent, ContentfulFileComponent, ContentfulImageComponent, ContentfulImports, ContentfulLinkComponent, ContentfulRichTextRendererComponent, ContentfulVideoComponent, DEFAULT_CONTENTFUL_FILE_LABELS, ET_CONTENTFUL_ANY_ENTRY_CONTENT_TYPE_SYS_ID, GQL_FRAGMENT_CONTENTFUL_ASSET, createContentfulConfig, createContentfulIncludeMap, generateContentfulImageSources, generateDefaultContentfulImageSource, injectContentfulConfig, injectContentfulFileLabels, isContentfulEntryType, isContentfulGqlAsset, isRichTextRootNode, mapContentfulGqlLinks, marksToClass, marksToTags, parseContentfulImageSize, provideContentfulConfig, provideContentfulFileLabels, translateContentfulNodeTypeToHtmlTag };
|
|
568
|
+
export type { ComponentLikeWithAsset, ComponentLikeWithContentfulRendererInputs, ComponentLikeWithLink, ContentfulAssetComponents, ContentfulAssetFileData, ContentfulAssetImageData, ContentfulAssetLink, ContentfulAssetLinkItem, ContentfulCollection, ContentfulConfig, ContentfulConfigOptions, ContentfulContentTypeLink, ContentfulEntry, ContentfulEntryLink, ContentfulEntryLinkItem, ContentfulEntrySys, ContentfulEnvironmentLink, ContentfulFileLabels, ContentfulGqlAsset, ContentfulGqlCollectionFilterVariables, ContentfulGqlOrder, ContentfulGqlRichText, ContentfulGqlRichTextEntry, ContentfulGqlWhereFilter, ContentfulImageFocusArea, ContentfulImageResizeBehavior, ContentfulImageSourcesOptions, ContentfulIncludeMap, ContentfulLink, ContentfulLinkType, ContentfulMetadata, ContentfulRestAsset, ContentfulSpaceLink, ContentfulSys, ContentfulTagLink, ContentfulTagLinkItem, CreateContentfulIncludeMapConfig, RichTextResponse };
|