@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.
@@ -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 = [...content.matchAll(CONTENTFUL_IMPORT)].find((match) => /\bprovideContentfulConfig\b/.test(match[1] ?? ''));
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 calls below need it by hand:',
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 (!filePath.endsWith('.ts') || filePath.endsWith('.d.ts'))
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} call(s) need the default components by hand — see ${CONTENTFUL_DEFAULT_COMPONENTS_REPORT_PATH}.`);
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": "Add withContentfulDefaultComponents() to provideContentfulConfig",
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 v5 dropped — the picture element now only carries static `et-picture-*` classes. */
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 v5 migration tasks',
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 v5...');
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
- "description": "Add withContentfulDefaultComponents() to provideContentfulConfig, and report the calls that need it by hand.",
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.12",
3
+ "version": "4.0.0-next.13",
4
4
  "license": "MIT",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
7
- "@angular/common": "22.1.6",
8
- "@angular/core": "22.1.6",
9
- "@angular/router": "22.1.6",
10
- "@contentful/rich-text-types": "17.2.7",
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.1.0"
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": true; "isSignal": true; }; "richTextPath": { "alias": "richTextPath"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
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
- textClass: InputSignal<string>;
198
- anchorClass?: InputSignal<string>;
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; }; "audioClass": { "alias": "audioClass"; "required": false; "isSignal": true; }; "figureClass": { "alias": "figureClass"; "required": false; "isSignal": true; }; "figcaptionClass": { "alias": "figcaptionClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
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; }; "fileClass": { "alias": "fileClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
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
- textClass: _angular_core.InputSignal<string>;
452
- anchorClass: _angular_core.InputSignal<string>;
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 linkClass: _angular_core.Signal<string | null>;
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; }; "textClass": { "alias": "textClass"; "required": false; "isSignal": true; }; "anchorClass": { "alias": "anchorClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
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; }; "videoClass": { "alias": "videoClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
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 };