@ethlete/contentful 4.0.0-next.10 → 4.0.0-next.12

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,57 @@
1
+ import { formatFiles } from '@nx/devkit';
2
+ import { createMigrationScope } from '../migrate-to-contentful-v5/migration-scope.js';
3
+ import { scanContentfulDefaultComponentsInFile, } from './contentful-default-components.js';
4
+ export const CONTENTFUL_DEFAULT_COMPONENTS_REPORT_PATH = 'contentful-default-components-migration-tasks.md';
5
+ const renderReport = (tasks) => [
6
+ '# Contentful default components migration tasks',
7
+ '',
8
+ 'The shipped asset and link components are opt-in. Without them embedded assets are skipped and',
9
+ 'hyperlinks render as plain anchors. The migration spread `CONTENTFUL_DEFAULT_COMPONENTS` into every',
10
+ 'literal `provideContentfulConfig` call; the calls below need it by hand:',
11
+ '',
12
+ '```ts',
13
+ "import { CONTENTFUL_DEFAULT_COMPONENTS, provideContentfulConfig } from '@ethlete/contentful';",
14
+ '',
15
+ 'provideContentfulConfig({ ...CONTENTFUL_DEFAULT_COMPONENTS });',
16
+ '```',
17
+ '',
18
+ 'A config that names every asset and link component itself needs nothing. Delete this file when done.',
19
+ '',
20
+ ...tasks.map((task) => [`## \`${task.id}\``, '', `- ${task.file}:${task.line}`, `- ${task.message}`, ''].join('\n')),
21
+ ].join('\n');
22
+ export default async function migrateContentfulDefaultComponents(tree, schema) {
23
+ console.log('\n🔎 Spreading CONTENTFUL_DEFAULT_COMPONENTS into provideContentfulConfig...');
24
+ const scope = createMigrationScope(tree, schema);
25
+ console.log(` Scope: ${scope.describe()}`);
26
+ const changed = [];
27
+ const tasks = [];
28
+ scope.visit(tree, (filePath) => {
29
+ if (!filePath.endsWith('.ts') || filePath.endsWith('.d.ts'))
30
+ return;
31
+ const content = tree.read(filePath, 'utf-8');
32
+ if (!content)
33
+ return;
34
+ const result = scanContentfulDefaultComponentsInFile(filePath, content);
35
+ tasks.push(...result.tasks);
36
+ if (result.next !== null) {
37
+ tree.write(filePath, result.next);
38
+ changed.push(filePath);
39
+ }
40
+ });
41
+ if (tasks.length > 0) {
42
+ tree.write(CONTENTFUL_DEFAULT_COMPONENTS_REPORT_PATH, renderReport(tasks));
43
+ }
44
+ if (!schema.skipFormat) {
45
+ await formatFiles(tree);
46
+ }
47
+ if (changed.length > 0) {
48
+ console.log(`\n✅ Spread CONTENTFUL_DEFAULT_COMPONENTS in ${changed.length} file(s).`);
49
+ }
50
+ if (tasks.length > 0) {
51
+ console.log(`\n⚠️ ${tasks.length} call(s) need the default components by hand — see ${CONTENTFUL_DEFAULT_COMPONENTS_REPORT_PATH}.`);
52
+ }
53
+ else if (changed.length === 0) {
54
+ console.log('\n✅ Nothing to do.');
55
+ }
56
+ }
57
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "migrate-contentful-default-components",
4
+ "title": "Add withContentfulDefaultComponents() to provideContentfulConfig",
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
+ }
package/migrations.json CHANGED
@@ -7,6 +7,14 @@
7
7
  "description": "Move to the components picture, the renamed image inputs and the removed renderer internals.",
8
8
  "generator": "@ethlete/contentful:migrate-to-contentful-v5",
9
9
  "docs": "/contentful/"
10
+ },
11
+ {
12
+ "name": "contentful-default-components",
13
+ "version": "4.0.0-next.12",
14
+ "kind": "auto",
15
+ "description": "Add withContentfulDefaultComponents() to provideContentfulConfig, and report the calls that need it by hand.",
16
+ "generator": "@ethlete/contentful:migrate-contentful-default-components",
17
+ "docs": "/contentful/"
10
18
  }
11
19
  ]
12
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ethlete/contentful",
3
- "version": "4.0.0-next.10",
3
+ "version": "4.0.0-next.12",
4
4
  "license": "MIT",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
@@ -1,8 +1,10 @@
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';
3
4
  import { NgClassType } from '@ethlete/core';
4
5
  import { GQL } from '@ethlete/query';
5
6
  import { Mark, BLOCKS, INLINES, NodeData, Block } from '@contentful/rich-text-types';
7
+ import * as _ethlete_contentful from '@ethlete/contentful';
6
8
  import * as _ethlete_components from '@ethlete/components';
7
9
  import { PictureSource } from '@ethlete/components';
8
10
  import * as _angular_router from '@angular/router';
@@ -13,6 +15,7 @@ type ContentfulGqlAsset = {
13
15
  id: string;
14
16
  };
15
17
  title: string | null;
18
+ fileName?: string | null;
16
19
  contentType: string | null;
17
20
  url: string | null;
18
21
  description: string | null;
@@ -145,6 +148,7 @@ declare class ContentfulRichTextRendererComponent {
145
148
  private renderer;
146
149
  private elementRef;
147
150
  private config;
151
+ private document;
148
152
  /**
149
153
  * The contentful response gotten via their REST api.
150
154
  * @see https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/entries/entries-collection
@@ -169,14 +173,18 @@ declare class ContentfulRichTextRendererComponent {
169
173
  private runDeleteInstruction;
170
174
  private getComponentRootNode;
171
175
  private findParent;
172
- private findFollowingElement;
173
- private renderInsertOrAppend;
176
+ private renderInsertAfter;
174
177
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ContentfulRichTextRendererComponent, never>;
175
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>;
176
179
  }
177
180
 
178
181
  declare const isRichTextRootNode: (node: unknown) => node is RichTextResponse;
179
- declare const translateContentfulNodeTypeToHtmlTag: (nodeType: "text" | BLOCKS | INLINES) => "hr" | "blockquote" | "table" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "ul" | "ol" | "li" | "tr" | "td" | "th" | "div" | "a" | "span";
182
+ /**
183
+ * The html element a structural rich text node renders as. A node type without an element
184
+ * (an unsupported inline or block) falls back to `span` for inlines and `div` otherwise,
185
+ * with a dev-mode warning.
186
+ */
187
+ declare const translateContentfulNodeTypeToHtmlTag: (nodeType: "text" | BLOCKS | INLINES) => keyof HTMLElementTagNameMap;
180
188
 
181
189
  type ContentfulImageResizeBehavior = 'pad' | 'crop' | 'fill' | 'scale' | 'thumb' | 'fit';
182
190
  type ContentfulImageFocusArea = 'top' | 'bottom' | 'left' | 'right' | 'center' | 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right' | 'face' | 'faces';
@@ -187,6 +195,7 @@ type ComponentLikeWithLink = Type<{
187
195
  href: InputSignal<string>;
188
196
  text: InputSignal<string>;
189
197
  textClass: InputSignal<string>;
198
+ anchorClass?: InputSignal<string>;
190
199
  }>;
191
200
  type ComponentLikeWithContentfulRendererInputs = Type<{
192
201
  fields?: InputSignal<any>;
@@ -203,7 +212,8 @@ type ContentfulAssetComponents = {
203
212
  };
204
213
  type ContentfulConfig = {
205
214
  /**
206
- * Default components for rendering contentful assets
215
+ * Components for rendering contentful assets and hyperlinks. Empty unless
216
+ * `CONTENTFUL_DEFAULT_COMPONENTS` is spread into the config or a component is named here.
207
217
  */
208
218
  components: ContentfulAssetComponents;
209
219
  /**
@@ -245,6 +255,7 @@ type ContentfulConfig = {
245
255
  backgroundColor: string | null;
246
256
  };
247
257
  };
258
+ type ContentfulConfigOptions = Partial<ContentfulConfig>;
248
259
  type ContentfulLinkType = 'Space' | 'ContentType' | 'Environment' | 'Entry' | 'Asset' | 'Tag';
249
260
  type ContentfulLink<T extends ContentfulLinkType> = {
250
261
  type: 'Link';
@@ -306,7 +317,7 @@ type ContentfulRestAsset = {
306
317
  fields: {
307
318
  title: string;
308
319
  description: string;
309
- file: ContentfulAssetFileData;
320
+ file?: ContentfulAssetFileData;
310
321
  };
311
322
  metadata: ContentfulMetadata;
312
323
  };
@@ -349,7 +360,26 @@ declare class ContentfulAudioComponent {
349
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>;
350
361
  }
351
362
 
363
+ /** Every string `et-contentful-file` renders itself. Override them app-wide with {@link provideContentfulFileLabels}. */
364
+ type ContentfulFileLabels = {
365
+ /** The size shown after the file title, e.g. `'(1.5 MB)'`. */
366
+ fileSize: (bytes: number) => string;
367
+ };
368
+ /** The built-in English labels. */
369
+ declare const DEFAULT_CONTENTFUL_FILE_LABELS: ContentfulFileLabels;
370
+ /**
371
+ * Localize `et-contentful-file` for everything below this injector, and read the set in effect here as a signal.
372
+ * Partial - whatever you leave out keeps its {@link DEFAULT_CONTENTFUL_FILE_LABELS} value.
373
+ *
374
+ * @example
375
+ * provideContentfulFileLabels({ fileSize: (bytes) => `(${new Intl.NumberFormat('de').format(bytes)} Byte)` });
376
+ */
377
+ declare const provideContentfulFileLabels: (labels: _ethlete_core.LabelsSource<ContentfulFileLabels>) => _angular_core.Provider;
378
+ declare const injectContentfulFileLabels: () => _angular_core.Signal<ContentfulFileLabels>;
379
+ declare const CONTENTFUL_FILE_LABELS: _angular_core.InjectionToken<_ethlete_core.LabelsSource<ContentfulFileLabels>>;
380
+
352
381
  declare class ContentfulFileComponent {
382
+ protected labels: _angular_core.Signal<_ethlete_contentful.ContentfulFileLabels>;
353
383
  asset: _angular_core.InputSignal<ContentfulGqlAsset | ContentfulRestAsset | null | undefined>;
354
384
  fileClass: _angular_core.InputSignal<NgClassType>;
355
385
  protected data: _angular_core.Signal<{
@@ -396,22 +426,37 @@ declare const parseContentfulImageSize: (size: string) => {
396
426
  width: number | null;
397
427
  height: number | null;
398
428
  };
399
- declare const generateContentfulImageSources: (data: ContentfulRestAsset | ContentfulGqlAsset, srcsetSizes: string[], backgroundColor: string | null, quality: number | null, focusArea: ContentfulImageFocusArea | null, resizeBehavior: ContentfulImageResizeBehavior | null) => PictureSource[];
429
+ type ContentfulImageSourcesOptions = {
430
+ srcsetSizes?: string[];
431
+ backgroundColor?: string | null;
432
+ quality?: number | null;
433
+ focusArea?: ContentfulImageFocusArea | null;
434
+ resizeBehavior?: ContentfulImageResizeBehavior | null;
435
+ };
436
+ /**
437
+ * Builds AVIF and WebP `<source>` entries for a Contentful asset through the Contentful Images API.
438
+ *
439
+ * @example
440
+ * generateContentfulImageSources(asset, { srcsetSizes: ['400w', '800w'], quality: 80 });
441
+ */
442
+ declare const generateContentfulImageSources: (data: ContentfulRestAsset | ContentfulGqlAsset, options?: ContentfulImageSourcesOptions) => PictureSource[];
400
443
 
401
444
  declare class ContentfulLinkComponent {
402
445
  private document;
403
446
  private router;
404
447
  private config;
448
+ private url;
405
449
  href: _angular_core.InputSignal<string>;
406
450
  text: _angular_core.InputSignal<string>;
407
451
  textClass: _angular_core.InputSignal<string>;
452
+ anchorClass: _angular_core.InputSignal<string>;
408
453
  protected usesRouterLink: _angular_core.Signal<boolean>;
409
454
  protected openInNewTab: _angular_core.Signal<boolean>;
410
455
  protected internalPath: _angular_core.Signal<string>;
411
456
  protected internalUrlTree: _angular_core.Signal<_angular_router.UrlTree>;
412
- protected linkClass: _angular_core.Signal<string>;
457
+ protected linkClass: _angular_core.Signal<string | null>;
413
458
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ContentfulLinkComponent, never>;
414
- 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; }; }, {}, never, never, true, 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>;
415
460
  }
416
461
 
417
462
  declare class ContentfulVideoComponent {
@@ -429,15 +474,33 @@ declare const CONTENTFUL_CONFIG: InjectionToken<ContentfulConfig>;
429
474
 
430
475
  declare const ContentfulImports: readonly [typeof ContentfulAudioComponent, typeof ContentfulFileComponent, typeof ContentfulImageComponent, typeof ContentfulVideoComponent, typeof ContentfulRichTextRendererComponent];
431
476
 
432
- declare const createContentfulConfig: (config?: Partial<ContentfulConfig> | null) => ContentfulConfig;
477
+ declare const createContentfulConfig: (options?: ContentfulConfigOptions | null) => ContentfulConfig;
433
478
  /**
434
479
  * Reads the contentful config from the current injector. Falls back to a config without
435
480
  * any `components` when none was provided via `provideContentfulConfig()`.
436
481
  */
437
482
  declare const injectContentfulConfig: () => ContentfulConfig;
438
483
 
439
- declare const provideContentfulConfig: (contentfulConfig?: Partial<ContentfulConfig> | null) => Provider;
484
+ /**
485
+ * The shipped audio, file, image, video and link components, to spread into `provideContentfulConfig`.
486
+ * An app that brings its own components, or renders text only, leaves this out and bundles none of them.
487
+ * To replace one, spread the map instead: `components: { ...CONTENTFUL_DEFAULT_COMPONENTS.components, image: MyImage }`.
488
+ *
489
+ * @example
490
+ * provideContentfulConfig({ ...CONTENTFUL_DEFAULT_COMPONENTS });
491
+ */
492
+ declare const CONTENTFUL_DEFAULT_COMPONENTS: {
493
+ components: {
494
+ audio: typeof ContentfulAudioComponent;
495
+ file: typeof ContentfulFileComponent;
496
+ image: typeof ContentfulImageComponent;
497
+ video: typeof ContentfulVideoComponent;
498
+ link: typeof ContentfulLinkComponent;
499
+ };
500
+ };
501
+
502
+ declare const provideContentfulConfig: (contentfulConfig?: ContentfulConfigOptions | null) => Provider;
440
503
  declare const isContentfulEntryType: <T extends ContentfulEntry>(entry: ContentfulEntry, type: string) => entry is T;
441
504
 
442
- export { CONTENTFUL_CONFIG, ContentfulAudioComponent, ContentfulFileComponent, ContentfulImageComponent, ContentfulImports, ContentfulLinkComponent, ContentfulRichTextRendererComponent, ContentfulVideoComponent, ET_CONTENTFUL_ANY_ENTRY_CONTENT_TYPE_SYS_ID, GQL_FRAGMENT_CONTENTFUL_ASSET, createContentfulConfig, createContentfulIncludeMap, generateContentfulImageSources, generateDefaultContentfulImageSource, injectContentfulConfig, isContentfulEntryType, isContentfulGqlAsset, isRichTextRootNode, marksToClass, marksToTags, parseContentfulImageSize, provideContentfulConfig, translateContentfulNodeTypeToHtmlTag };
443
- export type { ComponentLikeWithAsset, ComponentLikeWithContentfulRendererInputs, ComponentLikeWithLink, ContentfulAssetComponents, ContentfulAssetFileData, ContentfulAssetImageData, ContentfulAssetLink, ContentfulAssetLinkItem, ContentfulCollection, ContentfulConfig, ContentfulContentTypeLink, ContentfulEntry, ContentfulEntryLink, ContentfulEntryLinkItem, ContentfulEntrySys, ContentfulEnvironmentLink, ContentfulGqlAsset, ContentfulGqlCollectionFilterVariables, ContentfulGqlOrder, ContentfulGqlWhereFilter, ContentfulImageFocusArea, ContentfulImageResizeBehavior, ContentfulIncludeMap, ContentfulLink, ContentfulLinkType, ContentfulMetadata, ContentfulRestAsset, ContentfulSpaceLink, ContentfulSys, ContentfulTagLink, ContentfulTagLinkItem, CreateContentfulIncludeMapConfig, RichTextResponse };
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 };