@ethlete/contentful 4.0.0-next.12 → 4.0.0-next.13
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 +23 -0
- package/fesm2022/ethlete-contentful.mjs +224 -107
- 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 +68 -22
|
@@ -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.13",
|
|
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 its `__typename` with the first letter lower-cased,
|
|
119
|
+
* which is how Contentful derives a GraphQL type name from a content type id. Entries without a
|
|
120
|
+
* `__typename` are dropped.
|
|
121
|
+
*/
|
|
122
|
+
declare const mapContentfulGqlLinks: (links: ContentfulGqlRichText["links"]) => {
|
|
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,8 @@ declare class ContentfulRichTextRendererComponent {
|
|
|
149
185
|
private elementRef;
|
|
150
186
|
private config;
|
|
151
187
|
private document;
|
|
188
|
+
private location;
|
|
189
|
+
private url;
|
|
152
190
|
/**
|
|
153
191
|
* The contentful response gotten via their REST api.
|
|
154
192
|
* @see https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/entries/entries-collection
|
|
@@ -158,9 +196,17 @@ declare class ContentfulRichTextRendererComponent {
|
|
|
158
196
|
* The path to where the rich text field is inside the contentful response. Dot and array notation is allowed.
|
|
159
197
|
* @example "items[0].fields.html"
|
|
160
198
|
*/
|
|
161
|
-
richTextPath: _angular_core.InputSignal<string>;
|
|
199
|
+
richTextPath: _angular_core.InputSignal<string | undefined>;
|
|
200
|
+
/**
|
|
201
|
+
* A rich-text field from the Contentful GraphQL API, passed as-is. An alternative to `content` and
|
|
202
|
+
* `richTextPath`; setting both is an error in dev mode.
|
|
203
|
+
* @example { json, links: { assets: { block, hyperlink }, entries: { block, inline, hyperlink } } }
|
|
204
|
+
*/
|
|
205
|
+
gqlRichText: _angular_core.InputSignal<ContentfulGqlRichText | null | undefined>;
|
|
162
206
|
private readonly executedCommandsCache;
|
|
207
|
+
private includes;
|
|
163
208
|
private contentIncludesMap;
|
|
209
|
+
private includedEntries;
|
|
164
210
|
private richTextData;
|
|
165
211
|
private renderCommands;
|
|
166
212
|
private renderInstructions;
|
|
@@ -175,7 +221,7 @@ declare class ContentfulRichTextRendererComponent {
|
|
|
175
221
|
private findParent;
|
|
176
222
|
private renderInsertAfter;
|
|
177
223
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ContentfulRichTextRendererComponent, never>;
|
|
178
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentfulRichTextRendererComponent, "et-contentful-rich-text-renderer", never, { "content": { "alias": "content"; "required":
|
|
224
|
+
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; }; }, {}, never, never, true, never>;
|
|
179
225
|
}
|
|
180
226
|
|
|
181
227
|
declare const isRichTextRootNode: (node: unknown) => node is RichTextResponse;
|
|
@@ -194,8 +240,8 @@ type ComponentLikeWithAsset = Type<{
|
|
|
194
240
|
type ComponentLikeWithLink = Type<{
|
|
195
241
|
href: InputSignal<string>;
|
|
196
242
|
text: InputSignal<string>;
|
|
197
|
-
|
|
198
|
-
|
|
243
|
+
marks?: InputSignal<readonly string[]>;
|
|
244
|
+
richText?: InputSignal<boolean>;
|
|
199
245
|
}>;
|
|
200
246
|
type ComponentLikeWithContentfulRendererInputs = Type<{
|
|
201
247
|
fields?: InputSignal<any>;
|
|
@@ -250,12 +296,15 @@ type ContentfulConfig = {
|
|
|
250
296
|
*/
|
|
251
297
|
sizes: string[];
|
|
252
298
|
/**
|
|
253
|
-
* Background color for the image in hex. Eg. `"000000"`
|
|
299
|
+
* Background color for the image in hex. Eg. `"000000"` (a leading `#` is stripped)
|
|
254
300
|
*/
|
|
255
301
|
backgroundColor: string | null;
|
|
256
302
|
};
|
|
257
303
|
};
|
|
258
|
-
type ContentfulConfigOptions = Partial<ContentfulConfig
|
|
304
|
+
type ContentfulConfigOptions = Partial<Omit<ContentfulConfig, 'imageOptions'>> & {
|
|
305
|
+
/** Merged one level deep into the defaults, so a single option can be overridden. */
|
|
306
|
+
imageOptions?: Partial<ContentfulConfig['imageOptions']>;
|
|
307
|
+
};
|
|
259
308
|
type ContentfulLinkType = 'Space' | 'ContentType' | 'Environment' | 'Entry' | 'Asset' | 'Tag';
|
|
260
309
|
type ContentfulLink<T extends ContentfulLinkType> = {
|
|
261
310
|
type: 'Link';
|
|
@@ -349,15 +398,12 @@ type RichTextResponse = {
|
|
|
349
398
|
|
|
350
399
|
declare class ContentfulAudioComponent {
|
|
351
400
|
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
401
|
protected data: _angular_core.Signal<{
|
|
356
402
|
url: string;
|
|
357
403
|
title: string | null;
|
|
358
404
|
} | null>;
|
|
359
405
|
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; };
|
|
406
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentfulAudioComponent, "et-contentful-audio", never, { "asset": { "alias": "asset"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
361
407
|
}
|
|
362
408
|
|
|
363
409
|
/** Every string `et-contentful-file` renders itself. Override them app-wide with {@link provideContentfulFileLabels}. */
|
|
@@ -381,14 +427,13 @@ declare const CONTENTFUL_FILE_LABELS: _angular_core.InjectionToken<_ethlete_core
|
|
|
381
427
|
declare class ContentfulFileComponent {
|
|
382
428
|
protected labels: _angular_core.Signal<_ethlete_contentful.ContentfulFileLabels>;
|
|
383
429
|
asset: _angular_core.InputSignal<ContentfulGqlAsset | ContentfulRestAsset | null | undefined>;
|
|
384
|
-
fileClass: _angular_core.InputSignal<NgClassType>;
|
|
385
430
|
protected data: _angular_core.Signal<{
|
|
386
431
|
url: string;
|
|
387
432
|
size: number | null;
|
|
388
433
|
title: string | null;
|
|
389
434
|
} | null>;
|
|
390
435
|
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; };
|
|
436
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentfulFileComponent, "et-contentful-file", never, { "asset": { "alias": "asset"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
392
437
|
}
|
|
393
438
|
|
|
394
439
|
declare class ContentfulImageComponent {
|
|
@@ -443,31 +488,32 @@ declare const generateContentfulImageSources: (data: ContentfulRestAsset | Conte
|
|
|
443
488
|
|
|
444
489
|
declare class ContentfulLinkComponent {
|
|
445
490
|
private document;
|
|
491
|
+
private location;
|
|
446
492
|
private router;
|
|
447
493
|
private config;
|
|
448
494
|
private url;
|
|
449
495
|
href: _angular_core.InputSignal<string>;
|
|
450
496
|
text: _angular_core.InputSignal<string>;
|
|
451
|
-
|
|
452
|
-
|
|
497
|
+
marks: _angular_core.InputSignal<readonly string[]>;
|
|
498
|
+
richText: _angular_core.InputSignal<boolean>;
|
|
453
499
|
protected usesRouterLink: _angular_core.Signal<boolean>;
|
|
500
|
+
protected anchorHref: _angular_core.Signal<string>;
|
|
454
501
|
protected openInNewTab: _angular_core.Signal<boolean>;
|
|
455
502
|
protected internalPath: _angular_core.Signal<string>;
|
|
456
503
|
protected internalUrlTree: _angular_core.Signal<_angular_router.UrlTree>;
|
|
457
|
-
protected
|
|
504
|
+
protected modifierClasses: _angular_core.Signal<string[]>;
|
|
458
505
|
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; }; "
|
|
506
|
+
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
507
|
}
|
|
461
508
|
|
|
462
509
|
declare class ContentfulVideoComponent {
|
|
463
510
|
asset: _angular_core.InputSignal<ContentfulGqlAsset | ContentfulRestAsset | null | undefined>;
|
|
464
|
-
videoClass: _angular_core.InputSignal<NgClassType>;
|
|
465
511
|
protected data: _angular_core.Signal<{
|
|
466
512
|
url: string;
|
|
467
513
|
contentType: string | null;
|
|
468
514
|
} | null>;
|
|
469
515
|
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; };
|
|
516
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentfulVideoComponent, "et-contentful-video", never, { "asset": { "alias": "asset"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
471
517
|
}
|
|
472
518
|
|
|
473
519
|
declare const CONTENTFUL_CONFIG: InjectionToken<ContentfulConfig>;
|
|
@@ -502,5 +548,5 @@ declare const CONTENTFUL_DEFAULT_COMPONENTS: {
|
|
|
502
548
|
declare const provideContentfulConfig: (contentfulConfig?: ContentfulConfigOptions | null) => Provider;
|
|
503
549
|
declare const isContentfulEntryType: <T extends ContentfulEntry>(entry: ContentfulEntry, type: string) => entry is T;
|
|
504
550
|
|
|
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 };
|
|
551
|
+
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 };
|
|
552
|
+
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 };
|