UNPKG

@dotcms/angular

Version:

Official Angular Components library to render a dotCMS page.

1 lines 109 kB
{"version":3,"file":"dotcms-angular.mjs","sources":["../../../../../libs/sdk/angular/src/lib/directives/dotcms-show-when/dotcms-show-when.directive.ts","../../../../../libs/sdk/angular/src/lib/providers/dotcms-image-loader/dotcms-image_loader.ts","../../../../../libs/sdk/angular/src/lib/providers/dotcms-client/dotcms-client.provider.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-editable-text/utils.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-editable-text/dotcms-editable-text.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-editable-text/dotcms-editable-text.component.html","../../../../../libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer/blocks/code.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer/blocks/dot-contentlet.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer/blocks/image.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer/blocks/list.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer/blocks/table.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer/blocks/text.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer/blocks/unknown.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer/blocks/video.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer/item/dotcms-block-editor-item.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer/item/dotcms-block-editor-item.component.html","../../../../../libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer/dotcms-block-editor-renderer.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer/dotcms-block-editor-renderer.component.html","../../../../../libs/sdk/angular/src/lib/components/dotcms-layout-body/components/page-error-message/page-error-message.component.ts","../../../../../libs/sdk/angular/src/lib/store/dotcms.store.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-layout-body/components/container/components/container-not-found/container-not-found.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-layout-body/components/container/components/empty-container/empty-container.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-layout-body/components/fallback-component/fallback-component.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-layout-body/components/contentlet/contentlet.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-layout-body/components/container/container.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-layout-body/components/column/column.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-layout-body/components/row/row.component.ts","../../../../../libs/sdk/angular/src/lib/components/dotcms-layout-body/dotcms-layout-body.component.ts","../../../../../libs/sdk/angular/src/lib/services/dotcms-editable-page.service.ts","../../../../../libs/sdk/angular/src/dotcms-angular.ts"],"sourcesContent":["import { Directive, Input, ViewContainerRef, TemplateRef, inject } from '@angular/core';\n\nimport { UVE_MODE, UVEState } from '@dotcms/types';\nimport { getUVEState } from '@dotcms/uve';\n\n/**\n * Directive to show a template when the UVE is in a specific mode.\n *\n * @example\n * <div *dotCMSShowWhen=\"UVE_MODE.EDIT\">\n * This will be shown when the UVE is in edit mode.\n * </div>\n *\n * @export\n * @class DotCMSShowWhenDirective\n */\n@Directive({\n selector: '[dotCMSShowWhen]',\n standalone: true\n})\nexport class DotCMSShowWhenDirective {\n #when: UVE_MODE = UVE_MODE.EDIT;\n #hasView = false;\n\n @Input() set dotCMSShowWhen(value: UVE_MODE) {\n this.#when = value;\n this.updateViewContainer();\n }\n\n #viewContainerRef = inject(ViewContainerRef);\n #templateRef = inject(TemplateRef);\n\n private updateViewContainer() {\n const state: UVEState | undefined = getUVEState();\n\n const shouldShow = state?.mode === this.#when;\n\n if (shouldShow && !this.#hasView) {\n this.#viewContainerRef.createEmbeddedView(this.#templateRef);\n this.#hasView = true;\n } else if (!shouldShow && this.#hasView) {\n this.#viewContainerRef.clear();\n this.#hasView = false;\n }\n }\n}\n","import { IMAGE_LOADER, ImageLoaderConfig } from '@angular/common';\nimport { Provider } from '@angular/core';\n\n/**\n * Type definition for the DotCMS image loader parameters\n */\ninterface DotCMSImageLoaderParams {\n isOutsideSRC?: boolean;\n languageId?: string;\n /**\n * Quality of the image\n * @default 50\n */\n quality?: number;\n}\n\n/**\n * Validates if a given path is a valid URL string\n *\n * @param path - The path to validate\n * @returns boolean indicating if the path is valid\n */\nfunction isValidPath(path: unknown): boolean {\n if (typeof path !== 'string' || path.trim() === '') {\n return false;\n }\n\n try {\n new URL(path);\n\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Provides a DotCMS image loader configuration for the Angular Image directive\n *\n * @param path - The base URL path to the DotCMS instance, or empty to use current site\n * @returns An array of providers for the IMAGE_LOADER token\n * @throws Error if the provided path is invalid\n * @example\n * ```typescript\n * // In your app.config.ts\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideDotCMSImageLoader('https://demo.dotcms.com')\n * // Or use current site:\n * // provideDotCMSImageLoader()\n * ]\n * };\n * ```\n */\nexport function provideDotCMSImageLoader(path?: string): Provider[] {\n // If path is provided, validate it\n if (path && !isValidPath(path)) {\n throw new Error(\n `Image loader has detected an invalid path (\\`${path}\\`). ` +\n `To fix this, supply either the full URL to the dotCMS site, or leave it empty to use the current site.`\n );\n }\n\n return [\n {\n provide: IMAGE_LOADER,\n useValue: (config: ImageLoaderConfig) => createDotCMSURL(config, path)\n }\n ];\n}\n\n/**\n * Creates a DotCMS-compatible URL for image loading\n *\n * @param config - The image loader configuration\n * @param path - The base URL path to the DotCMS instance\n * @returns A fully qualified URL for the image\n * @internal\n */\nfunction createDotCMSURL(config: ImageLoaderConfig, path?: string): string {\n const { loaderParams, src, width } = config;\n const params = loaderParams as DotCMSImageLoaderParams;\n\n if (params?.isOutsideSRC) {\n return src;\n }\n\n // Use empty string as fallback to support using current site\n const dotcmsHost = path ? new URL(path).origin : '';\n const imageSRC = src.includes('/dA/') ? src : `/dA/${src}`;\n const languageId = params?.languageId ?? '1';\n const quality = params?.quality ?? 50;\n\n if (width) {\n return `${dotcmsHost}${imageSRC}/${width}w/${quality}q?language_id=${languageId}`;\n }\n\n return `${dotcmsHost}${imageSRC}/${quality}q?language_id=${languageId}`;\n}\n","import { HttpClient } from '@angular/common/http';\nimport { EnvironmentProviders, inject, makeEnvironmentProviders } from '@angular/core';\n\nimport { createDotCMSClient } from '@dotcms/client';\nimport { DotCMSClientConfig, DotHttpClient } from '@dotcms/types';\n\n/**\n * Type alias for the return type of createDotCMSClient function.\n * Used to ensure type consistency across the DotCMSClient interface and class.\n */\ntype ClientType = ReturnType<typeof createDotCMSClient>;\n\n// This is a hack inspired by https://github.com/angular/angularfire/blob/c1c6af9779154caff6bc0d9b837f6c3e2d913456/src/firestore/firestore.ts#L8\n\n/**\n * Interface that extends the client type created by createDotCMSClient.\n * This interface provides type safety and IntelliSense support for the DotCMS client\n * when used as a dependency injection token in Angular applications.\n *\n * @example\n * ```typescript\n * dotcmsClient = inject(DotCMSClient);\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging, @typescript-eslint/no-empty-interface, @typescript-eslint/no-empty-object-type\nexport interface DotCMSClient extends ClientType {}\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging\nexport class DotCMSClient {\n constructor(client: ClientType) {\n return client;\n }\n}\n\n/**\n * Provides Angular environment providers for the DotCMS client.\n *\n * Registers a singleton DotCMS client instance in the Angular dependency injection system,\n * configured with the given options. This allows you to inject `DotCMSClient` anywhere\n * in your app using Angular's `inject()` function.\n *\n * Should be added to the application's providers (e.g., in `main.ts` or `app.config.ts`).\n *\n * @param options - Configuration for the DotCMS client.\n * @param options.dotcmsUrl - The base URL for the DotCMS instance (required).\n * @param options.authToken - Authentication token for API requests (required).\n * @param options.siteId - The site identifier (optional).\n * @param options.requestOptions - Additional fetch options (optional).\n * @param options.httpClient - Optional factory for a custom HTTP client, receives Angular's HttpClient.\n * @returns Angular environment providers for the DotCMS client.\n *\n * @example\n * import { provideDotCMSClient } from '@dotcms/angular';\n *\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideDotCMSClient({\n * dotcmsUrl: 'https://demo.dotcms.com',\n * authToken: 'your-auth-token',\n * siteId: 'your-site-id',\n * httpClient: (http) => new AngularHttpClient(http)\n * })\n * ]\n * });\n */\nexport function provideDotCMSClient(options: DotCMSAngularProviderConfig): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: DotCMSClient,\n useFactory: () => {\n const httpClient = options.httpClient\n ? options.httpClient(inject(HttpClient))\n : undefined;\n const dotCMSClient = createDotCMSClient({\n dotcmsUrl: options.dotcmsUrl,\n authToken: options.authToken,\n siteId: options.siteId,\n httpClient: httpClient\n });\n\n return dotCMSClient;\n }\n }\n ]);\n}\n\n/**\n * Configuration interface for the DotCMS Angular provider.\n *\n * Extends the base `DotCMSClientConfig` but replaces the `httpClient` property\n * with an Angular-specific factory function that receives Angular's `HttpClient`\n * and returns a `DotHttpClient` implementation.\n *\n * This interface is designed to work seamlessly with Angular's dependency injection\n * system, allowing you to leverage Angular's built-in HTTP client while maintaining\n * compatibility with the DotCMS client's expected interface.\n *\n * @example\n * ```typescript\n * const config: DotCMSAngularProviderConfig = {\n * dotcmsUrl: 'https://demo.dotcms.com',\n * authToken: 'your-auth-token',\n * siteId: 'your-site-id',\n * httpClient: (http: HttpClient) => new AngularHttpClient(http)\n * };\n * ```\n *\n * @example\n * ```typescript\n * // Using with provideDotCMSClient\n * provideDotCMSClient({\n * dotcmsUrl: 'https://demo.dotcms.com',\n * authToken: 'your-auth-token',\n * httpClient: (http) => new AngularHttpClient(http)\n * })\n * ```\n */\nexport interface DotCMSAngularProviderConfig extends Omit<DotCMSClientConfig, 'httpClient'> {\n /**\n * Optional factory function that creates a custom HTTP client implementation.\n *\n * This function receives Angular's `HttpClient` instance and should return\n * a `DotHttpClient` implementation. If not provided, the DotCMS client will\n * use its default HTTP client implementation.\n *\n * @param http - Angular's HttpClient instance from dependency injection\n * @returns A DotHttpClient implementation\n *\n * @example\n * ```typescript\n * httpClient: (http: HttpClient) => {\n * return new AngularHttpClient(http);\n * }\n * ```\n */\n httpClient?: (http: HttpClient) => DotHttpClient;\n}\n","import { EditorComponent } from '@tinymce/tinymce-angular';\n\nimport {\n __BASE_TINYMCE_CONFIG_WITH_NO_DEFAULT__,\n __DEFAULT_TINYMCE_CONFIG__\n} from '@dotcms/uve/internal';\n\nexport type DOT_EDITABLE_TEXT_MODE = 'minimal' | 'full' | 'plain';\n\nexport type DOT_EDITABLE_TEXT_FORMAT = 'html' | 'text';\n\nconst DEFAULT_TINYMCE_CONFIG: EditorComponent['init'] = {\n ...__DEFAULT_TINYMCE_CONFIG__,\n license_key: 'gpl' // Using self-hosted license key\n};\n\nexport const TINYMCE_CONFIG: {\n [key in DOT_EDITABLE_TEXT_MODE]: EditorComponent['init'];\n} = {\n minimal: {\n ...DEFAULT_TINYMCE_CONFIG,\n ...__BASE_TINYMCE_CONFIG_WITH_NO_DEFAULT__.minimal\n },\n full: {\n ...DEFAULT_TINYMCE_CONFIG,\n ...__BASE_TINYMCE_CONFIG_WITH_NO_DEFAULT__.full\n },\n plain: {\n ...DEFAULT_TINYMCE_CONFIG,\n ...__BASE_TINYMCE_CONFIG_WITH_NO_DEFAULT__.plain\n }\n};\n","import { EditorComponent, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular';\nimport { EventObj } from '@tinymce/tinymce-angular/editor/Events';\n\nimport {\n Component,\n ElementRef,\n HostListener,\n inject,\n Input,\n OnChanges,\n OnInit,\n Renderer2,\n SecurityContext,\n ViewChild\n} from '@angular/core';\nimport { DomSanitizer } from '@angular/platform-browser';\n\nimport { DotCMSBasicContentlet, DotCMSUVEAction, UVE_MODE } from '@dotcms/types';\nimport { __DOTCMS_UVE_EVENT__ } from '@dotcms/types/internal';\nimport { getUVEState, sendMessageToUVE } from '@dotcms/uve';\nimport { __TINYMCE_PATH_ON_DOTCMS__ } from '@dotcms/uve/internal';\n\nimport { TINYMCE_CONFIG, DOT_EDITABLE_TEXT_FORMAT, DOT_EDITABLE_TEXT_MODE } from './utils';\n\n/**\n * Dot editable text component.\n * This component is responsible to render a text field that can be edited inline.\n *\n * @export\n * @class DotCMSEditableTextComponent\n * @implements {OnInit}\n * @implements {OnChanges}\n */\n@Component({\n selector: 'dotcms-editable-text',\n templateUrl: './dotcms-editable-text.component.html',\n styleUrl: './dotcms-editable-text.component.css',\n imports: [EditorComponent],\n providers: [\n {\n provide: TINYMCE_SCRIPT_SRC,\n useFactory: () => {\n const { dotCMSHost } = getUVEState() || {};\n\n return `${dotCMSHost || ''}${__TINYMCE_PATH_ON_DOTCMS__}`;\n }\n }\n ]\n})\nexport class DotCMSEditableTextComponent<T extends DotCMSBasicContentlet>\n implements OnInit, OnChanges\n{\n @ViewChild(EditorComponent) editorComponent!: EditorComponent;\n\n /**\n * Represents the mode of the editor which can be `plain`, `minimal`, or `full`\n *\n * @type {DOT_EDITABLE_TEXT_MODE}\n * @memberof DotCMSEditableTextComponent\n */\n @Input() mode: DOT_EDITABLE_TEXT_MODE = 'plain';\n /**\n * Represents the format of the editor which can be `text` or `html`\n *\n * @type {DOT_EDITABLE_TEXT_FORMAT}\n * @memberof DotCMSEditableTextComponent\n */\n @Input() format: DOT_EDITABLE_TEXT_FORMAT = 'text';\n /**\n * Represents the `contentlet` that can be inline edited\n *\n * @type {DotCMSContentlet}\n * @memberof DotCMSEditableTextComponent\n */\n @Input() contentlet!: T;\n /**\n * Represents the field name of the `contentlet` that can be edited\n *\n * @memberof DotCMSEditableTextComponent\n */\n @Input() fieldName!: keyof T;\n\n /**\n * Represents the content of the `contentlet` that can be edited\n *\n * @protected\n * @memberof DotCMSEditableTextComponent\n */\n protected content = '';\n /**\n * Represents the configuration of the editor\n *\n * @protected\n * @type {EditorComponent['init']}\n * @memberof DotCMSEditableTextComponent\n */\n protected init!: EditorComponent['init'];\n\n readonly #NotDotCMSHostMessage =\n 'The `dotCMSHost` parameter is not defined. Check that the UVE is sending the correct parameters.';\n\n readonly #sanitizer = inject<DomSanitizer>(DomSanitizer);\n readonly #renderer = inject<Renderer2>(Renderer2);\n readonly #elementRef = inject<ElementRef>(ElementRef);\n\n /**\n * The TinyMCE editor\n *\n * @readonly\n * @memberof DotCMSEditableTextComponent\n */\n get editor() {\n return this.editorComponent?.editor;\n }\n\n /**\n * Represents if the component is inside the editor\n *\n * @protected\n * @type {boolean}\n * @memberof DotCMSEditableTextComponent\n */\n protected get isEditMode() {\n const { mode, dotCMSHost } = getUVEState() || {};\n\n return mode === UVE_MODE.EDIT && dotCMSHost;\n }\n\n /**\n * Returns the number of pages the contentlet is on\n *\n * @readonly\n * @memberof DotCMSEditableTextComponent\n */\n get onNumberOfPages() {\n return this.contentlet['onNumberOfPages'] || 1;\n }\n\n /**\n * Handle copy contentlet inline editing success event\n *\n * @param {MessageEvent} { data }\n * @return {*}\n * @memberof DotCMSEditableTextComponent\n */\n @HostListener('window:message', ['$event'])\n onMessage({ data }: MessageEvent) {\n const { name, payload } = data;\n if (name !== __DOTCMS_UVE_EVENT__.UVE_COPY_CONTENTLET_INLINE_EDITING_SUCCESS) {\n return;\n }\n\n const { oldInode, inode } = payload;\n const currentInode = this.contentlet.inode;\n\n if (currentInode === oldInode || currentInode === inode) {\n this.editorComponent.editor.focus();\n\n return;\n }\n }\n\n ngOnInit() {\n const { dotCMSHost } = getUVEState() || {};\n\n if (!this.isEditMode) {\n this.innerHTMLToElement();\n\n if (!dotCMSHost) {\n console.warn(this.#NotDotCMSHostMessage);\n }\n\n return;\n }\n\n this.init = {\n ...TINYMCE_CONFIG[this.mode],\n base_url: `${dotCMSHost}/ext/tinymcev7`\n };\n }\n\n ngOnChanges() {\n this.content = (this.contentlet[this.fieldName] as string) || '';\n if (this.editor) {\n this.editor.setContent(this.content, { format: this.format });\n }\n }\n\n /**\n * Handle mouse down event\n *\n * @param {EventObj<MouseEvent>} { event }\n * @return {*}\n * @memberof DotCMSEditableTextComponent\n */\n onMouseDown({ event }: EventObj<MouseEvent>) {\n if (Number(this.onNumberOfPages) <= 1 || this.editorComponent.editor.hasFocus()) {\n return;\n }\n\n const { inode, languageId: language } = this.contentlet;\n\n event.stopPropagation();\n event.preventDefault();\n\n try {\n sendMessageToUVE({\n action: DotCMSUVEAction.COPY_CONTENTLET_INLINE_EDITING,\n payload: {\n dataset: {\n inode,\n language,\n fieldName: this.fieldName\n }\n }\n });\n } catch (error) {\n console.error('Failed to post message to editor:', error);\n }\n }\n /**\n * Handle focus out event\n *\n * @return {*}\n * @memberof DotCMSEditableTextComponent\n */\n onFocusOut() {\n const content = this.editor.getContent({ format: this.format });\n\n if (!this.editor.isDirty() || !this.didContentChange(content)) {\n return;\n }\n\n const { inode, languageId: langId } = this.contentlet;\n\n try {\n sendMessageToUVE({\n action: DotCMSUVEAction.UPDATE_CONTENTLET_INLINE_EDITING,\n payload: {\n content,\n dataset: {\n inode,\n langId,\n fieldName: this.fieldName\n }\n }\n });\n } catch (error) {\n console.error('Failed to post message to editor:', error);\n }\n }\n\n /**\n * inner HTML to element\n *\n * @private\n * @param {string} editedContent\n * @return {*}\n * @memberof DotCMSEditableTextComponent\n */\n private innerHTMLToElement() {\n const element = this.#elementRef.nativeElement;\n const safeHtml = this.#sanitizer.bypassSecurityTrustHtml(this.content);\n const content = this.#sanitizer.sanitize(SecurityContext.HTML, safeHtml) || '';\n\n this.#renderer.setProperty(element, 'innerHTML', content);\n }\n\n /**\n * Check if the content has changed\n *\n * @private\n * @param {string} editedContent\n * @return {*}\n * @memberof DotCMSEditableTextComponent\n */\n private didContentChange(editedContent: string) {\n return this.content !== editedContent;\n }\n}\n","@if (isEditMode) {\n <editor\n #tinyEditor\n [init]=\"init\"\n [initialValue]=\"content\"\n (onMouseDown)=\"onMouseDown($event)\"\n (onFocusOut)=\"onFocusOut()\" />\n}\n","import { ChangeDetectionStrategy, Component } from '@angular/core';\n\n@Component({\n selector: 'dotcms-block-editor-renderer-code-block',\n standalone: true,\n template: `\n <pre>\n <code>\n <ng-content />\n </code>\n </pre>\n `,\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class DotCodeBlock {}\n\n@Component({\n selector: 'dotcms-block-editor-renderer-block-quote',\n standalone: true,\n template: `\n <blockquote>\n <ng-content />\n </blockquote>\n `,\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class DotBlockQuote {}\n","import { AsyncPipe, NgComponentOutlet } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, computed, Input } from '@angular/core';\n\nimport { BlockEditorNode, UVE_MODE } from '@dotcms/types';\nimport { getUVEState } from '@dotcms/uve';\n\nimport { DynamicComponentEntity } from '../../../models';\nimport { CustomRenderer } from '../dotcms-block-editor-renderer.component';\n\n@Component({\n selector: 'dotcms-no-component-provided',\n standalone: true,\n template: `\n <div data-testid=\"no-component-provided\" [style]=\"style\">\n <strong style=\"color: #c05621\">Dev Warning</strong>\n : No component or custom renderer provided for content type\n <strong style=\"color: #c05621\">{{ contentType || 'Unknown' }}</strong>\n .\n <br />\n Please refer to the\n <a\n href=\"https://dev.dotcms.com/docs/block-editor\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n style=\"color: #c05621\">\n Block Editor Custom Renderers Documentation\n </a>\n for guidance.\n </div>\n `\n})\nexport class NoComponentProvided {\n @Input() contentType: string | undefined;\n protected readonly style = {\n backgroundColor: '#fffaf0',\n color: '#333',\n padding: '1rem',\n borderRadius: '0.5rem',\n marginBottom: '1rem',\n marginTop: '1rem',\n border: '1px solid #ed8936'\n };\n}\n\n/**\n * DotContent component that renders content based on content type\n */\n@Component({\n selector: 'dotcms-block-editor-renderer-contentlet',\n imports: [NgComponentOutlet, AsyncPipe, NoComponentProvided],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @if (contentComponent) {\n <ng-container\n *ngComponentOutlet=\"\n contentComponent | async;\n inputs: { node: node }\n \"></ng-container>\n } @else if (isDevMode) {\n <dotcms-no-component-provided [contentType]=\"$data()?.contentType\" />\n }\n `\n})\nexport class DotContentletBlock {\n @Input() customRenderers: CustomRenderer | undefined;\n @Input() node: BlockEditorNode | undefined;\n\n contentComponent: DynamicComponentEntity | undefined;\n protected readonly $data = computed(() => this.node?.attrs?.['data']);\n\n private readonly DOT_CONTENT_NO_DATA_MESSAGE =\n '[DotCMSBlockEditorRenderer]: No data provided for Contentlet Block. Try to add a contentlet to the block editor. If the error persists, please contact the DotCMS support team.';\n private readonly DOT_CONTENT_NO_MATCHING_COMPONENT_MESSAGE = (contentType: string) =>\n `[DotCMSBlockEditorRenderer]: No matching component found for content type: ${contentType}. Provide a custom renderer for this content type to fix this error.`;\n protected get isDevMode() {\n return getUVEState()?.mode === UVE_MODE.EDIT;\n }\n\n ngOnInit() {\n if (!this.$data()) {\n console.error(this.DOT_CONTENT_NO_DATA_MESSAGE);\n\n return;\n }\n\n const contentType = this.$data()?.contentType || '';\n this.contentComponent = this.customRenderers?.[contentType];\n\n if (!this.contentComponent) {\n console.warn(this.DOT_CONTENT_NO_MATCHING_COMPONENT_MESSAGE(contentType));\n }\n }\n}\n","import { ChangeDetectionStrategy, Component, computed, Input } from '@angular/core';\n\nimport { BlockEditorNode } from '@dotcms/types';\n\n@Component({\n selector: 'dotcms-block-editor-renderer-image',\n standalone: true,\n template: `\n <img [alt]=\"attrs?.['alt']\" [src]=\"$srcURL()\" />\n `,\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class DotImageBlock {\n @Input() attrs!: BlockEditorNode['attrs'];\n\n protected readonly $srcURL = computed(() => this.attrs?.['src']);\n}\n","import { ChangeDetectionStrategy, Component } from '@angular/core';\n\n@Component({\n selector: 'dotcms-block-editor-renderer-bullet-list',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <ul>\n <ng-content />\n </ul>\n `\n})\nexport class DotBulletList {}\n\n@Component({\n selector: 'dotcms-block-editor-renderer-ordered-list',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <ol>\n <ng-content />\n </ol>\n `\n})\nexport class DotOrdererList {}\n\n@Component({\n selector: 'dotcms-block-editor-renderer-list-item',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <li>\n <ng-content />\n </li>\n `\n})\nexport class DotListItem {}\n","import { NgComponentOutlet } from '@angular/common';\nimport { Component, Input } from '@angular/core';\n\nimport { BlockEditorNode } from '@dotcms/types';\n\nimport { DotCMSBlockEditorItemComponent } from '../item/dotcms-block-editor-item.component';\n@Component({\n selector: 'dotcms-block-editor-renderer-table',\n imports: [NgComponentOutlet],\n template: `\n <table>\n <thead>\n @for (rowNode of content?.slice(0, 1); track rowNode.type) {\n <tr>\n @for (cellNode of rowNode.content; track cellNode.type) {\n <th\n [attr.colspan]=\"cellNode.attrs?.['colspan'] || 1\"\n [attr.rowspan]=\"cellNode.attrs?.['rowspan'] || 1\">\n <ng-container\n *ngComponentOutlet=\"\n blockEditorItem;\n inputs: { content: cellNode.content }\n \"></ng-container>\n </th>\n }\n </tr>\n }\n </thead>\n <tbody>\n @for (rowNode of content?.slice(1); track rowNode.type) {\n <tr>\n @for (cellNode of rowNode.content; track cellNode.type) {\n <td\n [attr.colspan]=\"cellNode.attrs?.['colspan'] || 1\"\n [attr.rowspan]=\"cellNode.attrs?.['rowspan'] || 1\">\n <ng-container\n *ngComponentOutlet=\"\n blockEditorItem;\n inputs: { content: cellNode.content }\n \"></ng-container>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n `\n})\nexport class DotTableBlock {\n @Input() content: BlockEditorNode[] | undefined;\n blockEditorItem = DotCMSBlockEditorItemComponent;\n}\n","import { ChangeDetectionStrategy, Component, computed, Input } from '@angular/core';\n\nimport { BlockEditorMark } from '@dotcms/types';\n\n@Component({\n selector: 'dotcms-block-editor-renderer-paragraph',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <p>\n <ng-content />\n </p>\n `\n})\nexport class DotParagraphBlock {}\n\n@Component({\n selector: 'dotcms-block-editor-renderer-heading',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @switch (level) {\n @case ('1') {\n <h1>\n <ng-content />\n </h1>\n }\n @case ('2') {\n <h2>\n <ng-content />\n </h2>\n }\n @case ('3') {\n <h3>\n <ng-content />\n </h3>\n }\n @case ('4') {\n <h4>\n <ng-content />\n </h4>\n }\n @case ('5') {\n <h5>\n <ng-content />\n </h5>\n }\n @case ('6') {\n <h6>\n <ng-content />\n </h6>\n }\n @default {\n <h1>\n <ng-content />\n </h1>\n }\n }\n `\n})\nexport class DotHeadingBlock {\n @Input() level!: string;\n}\n\ninterface TextBlockProps {\n marks?: BlockEditorMark[];\n text?: string;\n}\n\n@Component({\n selector: 'dotcms-block-editor-renderer-text',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @switch (marks?.[0]?.type) {\n @case ('link') {\n <a\n [attr.href]=\"$currentAttrs()['href'] || ''\"\n [attr.target]=\"$currentAttrs()['target'] || ''\">\n <dotcms-block-editor-renderer-text [marks]=\"$remainingMarks()\" [text]=\"text\" />\n </a>\n }\n @case ('bold') {\n <strong>\n <dotcms-block-editor-renderer-text [marks]=\"$remainingMarks()\" [text]=\"text\" />\n </strong>\n }\n @case ('underline') {\n <u>\n <dotcms-block-editor-renderer-text [marks]=\"$remainingMarks()\" [text]=\"text\" />\n </u>\n }\n @case ('italic') {\n <em>\n <dotcms-block-editor-renderer-text [marks]=\"$remainingMarks()\" [text]=\"text\" />\n </em>\n }\n @case ('strike') {\n <s>\n <dotcms-block-editor-renderer-text [marks]=\"$remainingMarks()\" [text]=\"text\" />\n </s>\n }\n @case ('superscript') {\n <sup>\n <dotcms-block-editor-renderer-text [marks]=\"$remainingMarks()\" [text]=\"text\" />\n </sup>\n }\n @case ('subscript') {\n <sub>\n <dotcms-block-editor-renderer-text [marks]=\"$remainingMarks()\" [text]=\"text\" />\n </sub>\n }\n @default {\n {{ text }}\n }\n }\n `\n})\nexport class DotTextBlock {\n @Input() marks: TextBlockProps['marks'] = [];\n @Input() text = '';\n\n protected readonly $remainingMarks = computed(() => this.marks?.slice(1));\n\n protected readonly $currentAttrs = computed(() => {\n const attrs = { ...(this.marks?.[0]?.attrs || {}) };\n\n if (attrs['class']) {\n attrs['className'] = attrs['class'];\n delete attrs['class'];\n }\n\n return attrs;\n });\n}\n","import { Component, Input } from '@angular/core';\n\nimport { BlockEditorNode, UVE_MODE } from '@dotcms/types';\nimport { getUVEState } from '@dotcms/uve';\n\n@Component({\n selector: 'dotcms-block-editor-renderer-unknown',\n standalone: true,\n template: `\n @if (isEditMode) {\n <div [style]=\"style\" data-testid=\"unknown-block-type\">\n <strong style=\"color: #c53030\">Warning:</strong>\n The block type\n <strong>{{ node.type }}</strong>\n is not recognized. Please check your\n <a\n href=\"https://dev.dotcms.com/docs/block-editor\"\n target=\"_blank\"\n rel=\"noopener noreferrer\">\n configuration\n </a>\n or contact support for assistance.\n </div>\n }\n `\n})\nexport class DotUnknownBlockComponent {\n @Input() node!: BlockEditorNode;\n\n get isEditMode() {\n return getUVEState()?.mode === UVE_MODE.EDIT;\n }\n\n protected readonly style = {\n backgroundColor: '#fff5f5',\n color: '#333',\n padding: '1rem',\n borderRadius: '0.5rem',\n marginBottom: '1rem',\n marginTop: '1rem',\n border: '1px solid #fc8181'\n };\n}\n","import { ChangeDetectionStrategy, Component, computed, Input } from '@angular/core';\n\nimport { BlockEditorNode } from '@dotcms/types';\n\n@Component({\n selector: 'dotcms-block-editor-renderer-video',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <video\n [controls]=\"true\"\n preload=\"metadata\"\n [poster]=\"this.$posterURL()\"\n [width]=\"attrs?.['width']\"\n [height]=\"attrs?.['height']\">\n <track default kind=\"captions\" srclang=\"en\" />\n <source [src]=\"this.$srcURL()\" [type]=\"attrs?.['mimeType']\" />\n Your browser does not support the\n <code>video</code>\n element.\n </video>\n `\n})\nexport class DotVideoBlock {\n @Input() attrs!: BlockEditorNode['attrs'];\n\n protected readonly $srcURL = computed(() => this.attrs?.['src']);\n\n protected readonly $posterURL = computed(() => this.attrs?.['data']?.['thumbnail']);\n}\n","import { AsyncPipe, NgComponentOutlet, NgTemplateOutlet } from '@angular/common';\nimport { Component, Input } from '@angular/core';\n\nimport { BlockEditorNode } from '@dotcms/types';\nimport { BlockEditorDefaultBlocks } from '@dotcms/types/internal';\n\nimport { DotCodeBlock, DotBlockQuote } from '../blocks/code.component';\nimport { DotContentletBlock } from '../blocks/dot-contentlet.component';\nimport { DotImageBlock } from '../blocks/image.component';\nimport { DotBulletList, DotOrdererList, DotListItem } from '../blocks/list.component';\nimport { DotTableBlock } from '../blocks/table.component';\nimport { DotParagraphBlock, DotTextBlock, DotHeadingBlock } from '../blocks/text.component';\nimport { DotUnknownBlockComponent } from '../blocks/unknown.component';\nimport { DotVideoBlock } from '../blocks/video.component';\nimport { CustomRenderer } from '../dotcms-block-editor-renderer.component';\n\n@Component({\n selector: 'dotcms-block-editor-renderer-block',\n templateUrl: './dotcms-block-editor-item.component.html',\n styleUrls: ['./dotcms-block-editor-item.component.scss'],\n imports: [\n NgTemplateOutlet,\n NgComponentOutlet,\n AsyncPipe,\n DotParagraphBlock,\n DotTextBlock,\n DotHeadingBlock,\n DotBulletList,\n DotOrdererList,\n DotListItem,\n DotCodeBlock,\n DotBlockQuote,\n DotImageBlock,\n DotVideoBlock,\n DotTableBlock,\n DotContentletBlock,\n DotUnknownBlockComponent\n ]\n})\nexport class DotCMSBlockEditorItemComponent {\n @Input() content: BlockEditorNode[] | undefined;\n @Input() customRenderers: CustomRenderer | undefined;\n\n BLOCKS = BlockEditorDefaultBlocks;\n}\n","@for (node of content; track node) {\n @if (customRenderers?.[node.type]) {\n <ng-container\n *ngTemplateOutlet=\"\n customRender;\n context: { customRender: customRenderers?.[node.type], node: node }\n \"></ng-container>\n } @else {\n @switch (node.type) {\n @case (BLOCKS.PARAGRAPH) {\n <dotcms-block-editor-renderer-paragraph [style]=\"node.attrs\">\n <dotcms-block-editor-renderer-block\n [content]=\"node.content\"\n [customRenderers]=\"customRenderers\" />\n </dotcms-block-editor-renderer-paragraph>\n }\n\n @case (BLOCKS.TEXT) {\n <dotcms-block-editor-renderer-text [marks]=\"node.marks\" [text]=\"node.text || ''\" />\n }\n\n @case (BLOCKS.HEADING) {\n <dotcms-block-editor-renderer-heading\n [style]=\"node.attrs || {}\"\n [level]=\"node.attrs?.['level'] || '1'\">\n <dotcms-block-editor-renderer-block\n [content]=\"node.content\"\n [customRenderers]=\"customRenderers\" />\n </dotcms-block-editor-renderer-heading>\n }\n\n @case (BLOCKS.BULLET_LIST) {\n <dotcms-block-editor-renderer-bullet-list>\n <dotcms-block-editor-renderer-block\n [content]=\"node.content\"\n [customRenderers]=\"customRenderers\" />\n </dotcms-block-editor-renderer-bullet-list>\n }\n\n @case (BLOCKS.ORDERED_LIST) {\n <dotcms-block-editor-renderer-ordered-list>\n <dotcms-block-editor-renderer-block\n [content]=\"node.content\"\n [customRenderers]=\"customRenderers\" />\n </dotcms-block-editor-renderer-ordered-list>\n }\n\n @case (BLOCKS.LIST_ITEM) {\n <dotcms-block-editor-renderer-list-item>\n <dotcms-block-editor-renderer-block\n [content]=\"node.content\"\n [customRenderers]=\"customRenderers\" />\n </dotcms-block-editor-renderer-list-item>\n }\n\n @case (BLOCKS.BLOCK_QUOTE) {\n <dotcms-block-editor-renderer-block-quote>\n <dotcms-block-editor-renderer-block\n [content]=\"node.content\"\n [customRenderers]=\"customRenderers\" />\n </dotcms-block-editor-renderer-block-quote>\n }\n\n @case (BLOCKS.CODE_BLOCK) {\n <dotcms-block-editor-renderer-code-block>\n <dotcms-block-editor-renderer-block\n [content]=\"node.content\"\n [customRenderers]=\"customRenderers\" />\n </dotcms-block-editor-renderer-code-block>\n }\n\n @case (BLOCKS.HARDBREAK) {\n <br />\n }\n\n @case (BLOCKS.HORIZONTAL_RULE) {\n <hr />\n }\n\n @case (BLOCKS.DOT_IMAGE) {\n <dotcms-block-editor-renderer-image [attrs]=\"node.attrs || {}\" />\n }\n\n @case (BLOCKS.DOT_VIDEO) {\n <dotcms-block-editor-renderer-video [attrs]=\"node.attrs || {}\" />\n }\n\n @case (BLOCKS.TABLE) {\n <dotcms-block-editor-renderer-table [content]=\"node.content\" />\n }\n\n @case (BLOCKS.DOT_CONTENT) {\n <dotcms-block-editor-renderer-contentlet\n [node]=\"node\"\n [customRenderers]=\"customRenderers\" />\n }\n\n @default {\n <dotcms-block-editor-renderer-unknown [node]=\"node\" />\n }\n }\n }\n}\n\n<ng-template #customRender let-customRender=\"customRender\" let-node=\"node\">\n <ng-container *ngComponentOutlet=\"customRender | async; inputs: { node: node }\"></ng-container>\n</ng-template>\n","import { Component, Input, signal } from '@angular/core';\n\nimport { UVE_MODE, BlockEditorNode } from '@dotcms/types';\nimport { BlockEditorState } from '@dotcms/types/internal';\nimport { getUVEState } from '@dotcms/uve';\nimport { isValidBlocks } from '@dotcms/uve/internal';\n\nimport { DotCMSBlockEditorItemComponent } from './item/dotcms-block-editor-item.component';\n\nimport { DynamicComponentEntity } from '../../models';\n\n/**\n * Represents a Custom Renderer used by the Block Editor Component\n *\n * @export\n * @interface CustomRenderer\n */\nexport type CustomRenderer = Record<string, DynamicComponentEntity>;\n\n/**\n * A component that renders content from DotCMS's Block Editor field.\n *\n * This component provides an easy way to render Block Editor content in your Angular applications.\n * It handles the rendering of standard blocks and allows customization through custom renderers.\n *\n * For more information about Block Editor, see {@link https://dev.dotcms.com/docs/block-editor}\n *\n * @example\n * ```html\n * <dotcms-block-editor-renderer\n * [blocks]=\"myBlockEditorContent\"\n * [customRenderers]=\"myCustomRenderers\">\n * </dotcms-block-editor-renderer>\n * ```\n */\n@Component({\n selector: 'dotcms-block-editor-renderer',\n templateUrl: './dotcms-block-editor-renderer.component.html',\n styleUrls: ['./dotcms-block-editor-renderer.component.scss'],\n imports: [DotCMSBlockEditorItemComponent]\n})\nexport class DotCMSBlockEditorRendererComponent {\n @Input() blocks!: BlockEditorNode;\n @Input() customRenderers: CustomRenderer | undefined;\n @Input() class: string | undefined;\n @Input() style: string | Record<string, string> | undefined;\n\n $blockEditorState = signal<BlockEditorState>({ error: null });\n $isInEditMode = signal(getUVEState()?.mode === UVE_MODE.EDIT);\n\n ngOnInit() {\n const state = isValidBlocks(this.blocks);\n\n if (state.error) {\n console.error('Error in dotcms-block-editor-renderer: ', state.error);\n }\n\n this.$blockEditorState.set(isValidBlocks(this.blocks));\n }\n}\n","@if ($blockEditorState().error && $isInEditMode()) {\n <div data-testid=\"invalid-blocks-message\">\n {{ $blockEditorState().error }}\n </div>\n} @else if (!$blockEditorState().error) {\n <div [class]=\"class\" [style]=\"style\">\n <dotcms-block-editor-renderer-block\n [content]=\"blocks.content\"\n [customRenderers]=\"customRenderers\" />\n </div>\n}\n","import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';\n\n/**\n * @description This component is used to display a message when a page is missing the required `layout.body` property.\n * @internal\n * @class PageErrorMessageComponent\n */\n@Component({\n selector: 'dotcms-page-error-message',\n imports: [],\n template: `\n <div\n data-testid=\"error-message\"\n style=\"padding: 1rem; border: 1px solid #e0e0e0; border-radius: 4px;\">\n <p style=\"margin: 0 0 0.5rem; color: #666;\">\n The\n <code>page</code>\n is missing the required\n <code>layout.body</code>\n property.\n </p>\n <p style=\"margin: 0; color: #666;\">\n Make sure the page asset is properly loaded and includes a layout configuration.\n </p>\n </div>\n `,\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class PageErrorMessageComponent implements OnInit {\n ngOnInit() {\n console.warn('Missing required layout.body property in page');\n }\n}\n","import { computed, Injectable, signal } from '@angular/core';\n\nimport { DotCMSPageAsset, UVE_MODE } from '@dotcms/types';\nimport { getUVEState } from '@dotcms/uve';\nimport { DEVELOPMENT_MODE, PRODUCTION_MODE } from '@dotcms/uve/internal';\n\nimport { DotCMSPageStore } from '../models';\n\nexport const EMPTY_DOTCMS_PAGE_STORE: DotCMSPageStore = {\n page: {} as DotCMSPageAsset,\n components: {},\n mode: PRODUCTION_MODE\n};\n\n/**\n * @description This service is responsible for managing the page context.\n * @internal\n * @author dotCMS\n * @export\n * @class DotCMSStore\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class DotCMSStore {\n private $store = signal<DotCMSPageStore>(EMPTY_DOTCMS_PAGE_STORE);\n\n /**\n * @description Get the store\n * @readonly\n * @type {DotCMSPageStore}\n * @memberof DotCMSStore\n */\n get store(): DotCMSPageStore {\n return this.$store();\n }\n\n /**\n * @description Set the store\n * @param {DotCMSPageStore} value\n * @memberof DotCMSStore\n */\n setStore(store: DotCMSPageStore): void {\n this.$store.set(store);\n }\n\n /**\n * @description Get if the current context is in development mode\n * @readonly\n * @type {boolean}\n * @memberof DotCMSStore\n */\n $isDevMode = computed(() => {\n const uveState = getUVEState();\n\n if (uveState?.mode) {\n return uveState?.mode === UVE_MODE.EDIT;\n }\n\n return this.$store()?.mode === DEVELOPMENT_MODE;\n });\n}\n","import { NgStyle } from '@angular/common';\nimport { Component, inject, Input, OnInit } from '@angular/core';\n\nimport { EMPTY_CONTAINER_STYLE_ANGULAR } from '@dotcms/uve/internal';\n\nimport { DotCMSStore } from '../../../../../../store/dotcms.store';\n\n/**\n * @description This component is used to display a message when a container is not found.\n * @export\n * @internal\n * @class ContainerNotFoundComponent\n * @implements {OnInit}\n */\n@Component({\n selector: 'dotcms-container-not-found',\n imports: [NgStyle],\n template: `\n @if ($isDevMode()) {\n <div [attr.data-testid]=\"'container-not-found'\" [ngStyle]=\"emptyContainerStyle\">\n This container with identifier {{ identifier }} was not found.\n </div>\n }\n `\n})\nexport class ContainerNotFoundComponent implements OnInit {\n @Input() identifier = 'unknown';\n\n #dotcmsContextService = inject(DotCMSStore);\n\n $isDevMode = this.#dotcmsContextService.$isDevMode;\n emptyContainerStyle = EMPTY_CONTAINER_STYLE_ANGULAR;\n\n ngOnInit() {\n if (this.$isDevMode()) {\n console.error(`Container with identifier ${this.identifier} not found`);\n }\n }\n}\n","import { NgStyle } from '@angular/common';\nimport { Component, inject } from '@angular/core';\n\nimport { EMPTY_CONTAINER_STYLE_ANGULAR } from '@dotcms/uve/internal';\n\nimport { DotCMSStore } from '../../../../../../store/dotcms.store';\n\n/**\n * @description This component is used to display a message when a container is empty.\n * @export\n * @internal\n * @class EmptyContainerComponent\n */\n@Component({\n selector: 'dotcms-empty-container',\n imports: [NgStyle],\n template: `\n @if ($isDevMode()) {\n <div [ngStyle]=\"emptyContainerStyle\" data-testid=\"empty-container\">\n <span data-testid=\"empty-container-message\" data-dot-object=\"empty-content\">\n This container is empty.\n </span>\n </div>\n }\n `\n})\nexport class EmptyContainerComponent {\n emptyContainerStyle = EMPTY_CONTAINER_STYLE_ANGULAR;\n\n #dotCMSStore = inject(DotCMSStore);\n\n $isDevMode = this.#dotCMSStore.$isDevMode;\n}\n","import { AsyncPipe, NgComponentOutlet } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, Input } from '@angular/core';\n\nimport { DotCMSBasicContentlet } from '@dotcms/types';\n\nimport { DynamicComponentEntity } from '../../../../models';\n\n/**\n * @description Fallback component that renders when