UNPKG

@dotcms/angular

Version:

Official Angular Components library to render a dotCMS page.

1,114 lines (1,100 loc) 98.7 kB
import * as i0 from '@angular/core'; import { inject, ViewContainerRef, TemplateRef, Input, Directive, makeEnvironmentProviders, Renderer2, ElementRef, SecurityContext, HostListener, ViewChild, Component, ChangeDetectionStrategy, computed, signal, Injectable, HostBinding } from '@angular/core'; import { UVE_MODE, DotCMSUVEAction, UVEEventType } from '@dotcms/types'; import { getUVEState, sendMessageToUVE, initUVE, updateNavigation, createUVESubscription } from '@dotcms/uve'; import { IMAGE_LOADER, NgComponentOutlet, AsyncPipe, NgTemplateOutlet, NgStyle } from '@angular/common'; import { HttpClient } from '@angular/common/http'; import { createDotCMSClient } from '@dotcms/client'; import { EditorComponent, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular'; import { DomSanitizer } from '@angular/platform-browser'; import { __DOTCMS_UVE_EVENT__, BlockEditorDefaultBlocks } from '@dotcms/types/internal'; import { __DEFAULT_TINYMCE_CONFIG__, __BASE_TINYMCE_CONFIG_WITH_NO_DEFAULT__, __TINYMCE_PATH_ON_DOTCMS__, isValidBlocks, PRODUCTION_MODE, DEVELOPMENT_MODE, EMPTY_CONTAINER_STYLE_ANGULAR, getDotContentletAttributes, CUSTOM_NO_COMPONENT, getDotContainerAttributes, getContainersData, getContentletsInContainer, getColumnPositionClasses, combineClasses } from '@dotcms/uve/internal'; import { Subject, of } from 'rxjs'; import { finalize } from 'rxjs/operators'; /** * Directive to show a template when the UVE is in a specific mode. * * @example * <div *dotCMSShowWhen="UVE_MODE.EDIT"> * This will be shown when the UVE is in edit mode. * </div> * * @export * @class DotCMSShowWhenDirective */ class DotCMSShowWhenDirective { #when = UVE_MODE.EDIT; #hasView = false; set dotCMSShowWhen(value) { this.#when = value; this.updateViewContainer(); } #viewContainerRef = inject(ViewContainerRef); #templateRef = inject(TemplateRef); updateViewContainer() { const state = getUVEState(); const shouldShow = state?.mode === this.#when; if (shouldShow && !this.#hasView) { this.#viewContainerRef.createEmbeddedView(this.#templateRef); this.#hasView = true; } else if (!shouldShow && this.#hasView) { this.#viewContainerRef.clear(); this.#hasView = false; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotCMSShowWhenDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.9", type: DotCMSShowWhenDirective, isStandalone: true, selector: "[dotCMSShowWhen]", inputs: { dotCMSShowWhen: "dotCMSShowWhen" }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotCMSShowWhenDirective, decorators: [{ type: Directive, args: [{ selector: '[dotCMSShowWhen]' }] }], propDecorators: { dotCMSShowWhen: [{ type: Input }] } }); /** * Validates if a given path is a valid URL string * * @param path - The path to validate * @returns boolean indicating if the path is valid */ function isValidPath(path) { if (typeof path !== 'string' || path.trim() === '') { return false; } try { new URL(path); return true; } catch { return false; } } /** * Provides a DotCMS image loader configuration for the Angular Image directive * * @param path - The base URL path to the DotCMS instance, or empty to use current site * @returns An array of providers for the IMAGE_LOADER token * @throws Error if the provided path is invalid * @example * ```typescript * // In your app.config.ts * export const appConfig: ApplicationConfig = { * providers: [ * provideDotCMSImageLoader('https://demo.dotcms.com') * // Or use current site: * // provideDotCMSImageLoader() * ] * }; * ``` */ function provideDotCMSImageLoader(path) { // If path is provided, validate it if (path && !isValidPath(path)) { throw new Error(`Image loader has detected an invalid path (\`${path}\`). ` + `To fix this, supply either the full URL to the dotCMS site, or leave it empty to use the current site.`); } return [ { provide: IMAGE_LOADER, useValue: (config) => createDotCMSURL(config, path) } ]; } /** * Creates a DotCMS-compatible URL for image loading * * @param config - The image loader configuration * @param path - The base URL path to the DotCMS instance * @returns A fully qualified URL for the image * @internal */ function createDotCMSURL(config, path) { const { loaderParams, src, width } = config; const params = loaderParams; if (params?.isOutsideSRC) { return src; } // Use empty string as fallback to support using current site const dotcmsHost = path ? new URL(path).origin : ''; const imageSRC = src.includes('/dA/') ? src : `/dA/${src}`; const languageId = params?.languageId ?? '1'; const quality = params?.quality ?? 50; if (width) { return `${dotcmsHost}${imageSRC}/${width}w/${quality}q?language_id=${languageId}`; } return `${dotcmsHost}${imageSRC}/${quality}q?language_id=${languageId}`; } // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging class DotCMSClient { constructor(client) { return client; } } /** * Provides Angular environment providers for the DotCMS client. * * Registers a singleton DotCMS client instance in the Angular dependency injection system, * configured with the given options. This allows you to inject `DotCMSClient` anywhere * in your app using Angular's `inject()` function. * * Should be added to the application's providers (e.g., in `main.ts` or `app.config.ts`). * * @param options - Configuration for the DotCMS client. * @param options.dotcmsUrl - The base URL for the DotCMS instance (required). * @param options.authToken - Authentication token for API requests (required). * @param options.siteId - The site identifier (optional). * @param options.requestOptions - Additional fetch options (optional). * @param options.httpClient - Optional factory for a custom HTTP client, receives Angular's HttpClient. * @returns Angular environment providers for the DotCMS client. * * @example * import { provideDotCMSClient } from '@dotcms/angular'; * * bootstrapApplication(AppComponent, { * providers: [ * provideDotCMSClient({ * dotcmsUrl: 'https://demo.dotcms.com', * authToken: 'your-auth-token', * siteId: 'your-site-id', * httpClient: (http) => new AngularHttpClient(http) * }) * ] * }); */ function provideDotCMSClient(options) { return makeEnvironmentProviders([ { provide: DotCMSClient, useFactory: () => { const httpClient = options.httpClient ? options.httpClient(inject(HttpClient)) : undefined; const dotCMSClient = createDotCMSClient({ dotcmsUrl: options.dotcmsUrl, authToken: options.authToken, siteId: options.siteId, httpClient: httpClient }); return dotCMSClient; } } ]); } const DEFAULT_TINYMCE_CONFIG = { ...__DEFAULT_TINYMCE_CONFIG__, license_key: 'gpl' // Using self-hosted license key }; const TINYMCE_CONFIG = { minimal: { ...DEFAULT_TINYMCE_CONFIG, ...__BASE_TINYMCE_CONFIG_WITH_NO_DEFAULT__.minimal }, full: { ...DEFAULT_TINYMCE_CONFIG, ...__BASE_TINYMCE_CONFIG_WITH_NO_DEFAULT__.full }, plain: { ...DEFAULT_TINYMCE_CONFIG, ...__BASE_TINYMCE_CONFIG_WITH_NO_DEFAULT__.plain } }; /** * Dot editable text component. * This component is responsible to render a text field that can be edited inline. * * @export * @class DotCMSEditableTextComponent * @implements {OnInit} * @implements {OnChanges} */ class DotCMSEditableTextComponent { constructor() { /** * Represents the mode of the editor which can be `plain`, `minimal`, or `full` * * @type {DOT_EDITABLE_TEXT_MODE} * @memberof DotCMSEditableTextComponent */ this.mode = 'plain'; /** * Represents the format of the editor which can be `text` or `html` * * @type {DOT_EDITABLE_TEXT_FORMAT} * @memberof DotCMSEditableTextComponent */ this.format = 'text'; /** * Represents the content of the `contentlet` that can be edited * * @protected * @memberof DotCMSEditableTextComponent */ this.content = ''; this.#NotDotCMSHostMessage = 'The `dotCMSHost` parameter is not defined. Check that the UVE is sending the correct parameters.'; this.#sanitizer = inject(DomSanitizer); this.#renderer = inject(Renderer2); this.#elementRef = inject(ElementRef); } #NotDotCMSHostMessage; #sanitizer; #renderer; #elementRef; /** * The TinyMCE editor * * @readonly * @memberof DotCMSEditableTextComponent */ get editor() { return this.editorComponent?.editor; } /** * Represents if the component is inside the editor * * @protected * @type {boolean} * @memberof DotCMSEditableTextComponent */ get isEditMode() { const { mode, dotCMSHost } = getUVEState() || {}; return mode === UVE_MODE.EDIT && dotCMSHost; } /** * Returns the number of pages the contentlet is on * * @readonly * @memberof DotCMSEditableTextComponent */ get onNumberOfPages() { return this.contentlet['onNumberOfPages'] || 1; } /** * Handle copy contentlet inline editing success event * * @param {MessageEvent} { data } * @return {*} * @memberof DotCMSEditableTextComponent */ onMessage({ data }) { const { name, payload } = data; if (name !== __DOTCMS_UVE_EVENT__.UVE_COPY_CONTENTLET_INLINE_EDITING_SUCCESS) { return; } const { oldInode, inode } = payload; const currentInode = this.contentlet.inode; if (currentInode === oldInode || currentInode === inode) { this.editorComponent.editor.focus(); return; } } ngOnInit() { const { dotCMSHost } = getUVEState() || {}; if (!this.isEditMode) { this.innerHTMLToElement(); if (!dotCMSHost) { console.warn(this.#NotDotCMSHostMessage); } return; } this.init = { ...TINYMCE_CONFIG[this.mode], base_url: `${dotCMSHost}/ext/tinymcev7` }; } ngOnChanges() { this.content = this.contentlet[this.fieldName] || ''; if (this.editor) { this.editor.setContent(this.content, { format: this.format }); } } /** * Handle mouse down event * * @param {EventObj<MouseEvent>} { event } * @return {*} * @memberof DotCMSEditableTextComponent */ onMouseDown({ event }) { if (Number(this.onNumberOfPages) <= 1 || this.editorComponent.editor.hasFocus()) { return; } const { inode, languageId: language } = this.contentlet; event.stopPropagation(); event.preventDefault(); try { sendMessageToUVE({ action: DotCMSUVEAction.COPY_CONTENTLET_INLINE_EDITING, payload: { dataset: { inode, language, fieldName: this.fieldName } } }); } catch (error) { console.error('Failed to post message to editor:', error); } } /** * Handle focus out event * * @return {*} * @memberof DotCMSEditableTextComponent */ onFocusOut() { const content = this.editor.getContent({ format: this.format }); if (!this.editor.isDirty() || !this.didContentChange(content)) { return; } const { inode, languageId: langId } = this.contentlet; try { sendMessageToUVE({ action: DotCMSUVEAction.UPDATE_CONTENTLET_INLINE_EDITING, payload: { content, dataset: { inode, langId, fieldName: this.fieldName } } }); } catch (error) { console.error('Failed to post message to editor:', error); } } /** * inner HTML to element * * @private * @param {string} editedContent * @return {*} * @memberof DotCMSEditableTextComponent */ innerHTMLToElement() { const element = this.#elementRef.nativeElement; const safeHtml = this.#sanitizer.bypassSecurityTrustHtml(this.content); const content = this.#sanitizer.sanitize(SecurityContext.HTML, safeHtml) || ''; this.#renderer.setProperty(element, 'innerHTML', content); } /** * Check if the content has changed * * @private * @param {string} editedContent * @return {*} * @memberof DotCMSEditableTextComponent */ didContentChange(editedContent) { return this.content !== editedContent; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotCMSEditableTextComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: DotCMSEditableTextComponent, isStandalone: true, selector: "dotcms-editable-text", inputs: { mode: "mode", format: "format", contentlet: "contentlet", fieldName: "fieldName" }, host: { listeners: { "window:message": "onMessage($event)" } }, providers: [ { provide: TINYMCE_SCRIPT_SRC, useFactory: () => { const { dotCMSHost } = getUVEState() || {}; return `${dotCMSHost || ''}${__TINYMCE_PATH_ON_DOTCMS__}`; } } ], viewQueries: [{ propertyName: "editorComponent", first: true, predicate: EditorComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "@if (isEditMode) {\n <editor\n #tinyEditor\n [init]=\"init\"\n [initialValue]=\"content\"\n (onMouseDown)=\"onMouseDown($event)\"\n (onFocusOut)=\"onFocusOut()\" />\n}\n", styles: [":host ::ng-deep .mce-content-body:not(.mce-edit-focus){outline:2px solid #006ce7;border-radius:4px}\n"], dependencies: [{ kind: "component", type: EditorComponent, selector: "editor", inputs: ["cloudChannel", "apiKey", "init", "id", "initialValue", "outputFormat", "inline", "tagName", "plugins", "toolbar", "modelEvents", "allowedEvents", "ignoreEvents", "disabled"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotCMSEditableTextComponent, decorators: [{ type: Component, args: [{ selector: 'dotcms-editable-text', imports: [EditorComponent], providers: [ { provide: TINYMCE_SCRIPT_SRC, useFactory: () => { const { dotCMSHost } = getUVEState() || {}; return `${dotCMSHost || ''}${__TINYMCE_PATH_ON_DOTCMS__}`; } } ], template: "@if (isEditMode) {\n <editor\n #tinyEditor\n [init]=\"init\"\n [initialValue]=\"content\"\n (onMouseDown)=\"onMouseDown($event)\"\n (onFocusOut)=\"onFocusOut()\" />\n}\n", styles: [":host ::ng-deep .mce-content-body:not(.mce-edit-focus){outline:2px solid #006ce7;border-radius:4px}\n"] }] }], propDecorators: { editorComponent: [{ type: ViewChild, args: [EditorComponent] }], mode: [{ type: Input }], format: [{ type: Input }], contentlet: [{ type: Input }], fieldName: [{ type: Input }], onMessage: [{ type: HostListener, args: ['window:message', ['$event']] }] } }); class DotCodeBlock { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotCodeBlock, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.9", type: DotCodeBlock, isStandalone: true, selector: "dotcms-block-editor-renderer-code-block", ngImport: i0, template: ` <pre> <code> <ng-content /> </code> </pre> `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotCodeBlock, decorators: [{ type: Component, args: [{ selector: 'dotcms-block-editor-renderer-code-block', template: ` <pre> <code> <ng-content /> </code> </pre> `, changeDetection: ChangeDetectionStrategy.OnPush }] }] }); class DotBlockQuote { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotBlockQuote, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.9", type: DotBlockQuote, isStandalone: true, selector: "dotcms-block-editor-renderer-block-quote", ngImport: i0, template: ` <blockquote> <ng-content /> </blockquote> `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotBlockQuote, decorators: [{ type: Component, args: [{ selector: 'dotcms-block-editor-renderer-block-quote', template: ` <blockquote> <ng-content /> </blockquote> `, changeDetection: ChangeDetectionStrategy.OnPush }] }] }); class NoComponentProvided { constructor() { this.style = { backgroundColor: '#fffaf0', color: '#333', padding: '1rem', borderRadius: '0.5rem', marginBottom: '1rem', marginTop: '1rem', border: '1px solid #ed8936' }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NoComponentProvided, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.9", type: NoComponentProvided, isStandalone: true, selector: "dotcms-no-component-provided", inputs: { contentType: "contentType" }, ngImport: i0, template: ` <div data-testid="no-component-provided" [style]="style"> <strong style="color: #c05621">Dev Warning</strong> : No component or custom renderer provided for content type <strong style="color: #c05621">{{ contentType || 'Unknown' }}</strong> . <br /> Please refer to the <a href="https://dev.dotcms.com/docs/block-editor" target="_blank" rel="noopener noreferrer" style="color: #c05621"> Block Editor Custom Renderers Documentation </a> for guidance. </div> `, isInline: true }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NoComponentProvided, decorators: [{ type: Component, args: [{ selector: 'dotcms-no-component-provided', template: ` <div data-testid="no-component-provided" [style]="style"> <strong style="color: #c05621">Dev Warning</strong> : No component or custom renderer provided for content type <strong style="color: #c05621">{{ contentType || 'Unknown' }}</strong> . <br /> Please refer to the <a href="https://dev.dotcms.com/docs/block-editor" target="_blank" rel="noopener noreferrer" style="color: #c05621"> Block Editor Custom Renderers Documentation </a> for guidance. </div> ` }] }], propDecorators: { contentType: [{ type: Input }] } }); /** * DotContent component that renders content based on content type */ class DotContentletBlock { constructor() { this.$data = computed(() => this.node?.attrs?.['data'], ...(ngDevMode ? [{ debugName: "$data" }] : [])); this.DOT_CONTENT_NO_DATA_MESSAGE = '[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.'; this.DOT_CONTENT_NO_MATCHING_COMPONENT_MESSAGE = (contentType) => `[DotCMSBlockEditorRenderer]: No matching component found for content type: ${contentType}. Provide a custom renderer for this content type to fix this error.`; } get isDevMode() { return getUVEState()?.mode === UVE_MODE.EDIT; } ngOnInit() { if (!this.$data()) { console.error(this.DOT_CONTENT_NO_DATA_MESSAGE); return; } const contentType = this.$data()?.contentType || ''; this.contentComponent = this.customRenderers?.[contentType]; if (!this.contentComponent) { console.warn(this.DOT_CONTENT_NO_MATCHING_COMPONENT_MESSAGE(contentType)); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotContentletBlock, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: DotContentletBlock, isStandalone: true, selector: "dotcms-block-editor-renderer-contentlet", inputs: { customRenderers: "customRenderers", node: "node" }, ngImport: i0, template: ` @if (contentComponent) { <ng-container *ngComponentOutlet=" contentComponent | async; inputs: { node: node } "></ng-container> } @else if (isDevMode) { <dotcms-no-component-provided [contentType]="$data()?.contentType" /> } `, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: NoComponentProvided, selector: "dotcms-no-component-provided", inputs: ["contentType"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotContentletBlock, decorators: [{ type: Component, args: [{ selector: 'dotcms-block-editor-renderer-contentlet', imports: [NgComponentOutlet, AsyncPipe, NoComponentProvided], changeDetection: ChangeDetectionStrategy.OnPush, template: ` @if (contentComponent) { <ng-container *ngComponentOutlet=" contentComponent | async; inputs: { node: node } "></ng-container> } @else if (isDevMode) { <dotcms-no-component-provided [contentType]="$data()?.contentType" /> } ` }] }], propDecorators: { customRenderers: [{ type: Input }], node: [{ type: Input }] } }); class DotImageBlock { constructor() { this.$srcURL = computed(() => this.attrs?.['src'], ...(ngDevMode ? [{ debugName: "$srcURL" }] : [])); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotImageBlock, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.9", type: DotImageBlock, isStandalone: true, selector: "dotcms-block-editor-renderer-image", inputs: { attrs: "attrs" }, ngImport: i0, template: ` <img [alt]="attrs?.['alt']" [src]="$srcURL()" /> `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotImageBlock, decorators: [{ type: Component, args: [{ selector: 'dotcms-block-editor-renderer-image', template: ` <img [alt]="attrs?.['alt']" [src]="$srcURL()" /> `, changeDetection: ChangeDetectionStrategy.OnPush }] }], propDecorators: { attrs: [{ type: Input }] } }); class DotBulletList { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotBulletList, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.9", type: DotBulletList, isStandalone: true, selector: "dotcms-block-editor-renderer-bullet-list", ngImport: i0, template: ` <ul> <ng-content /> </ul> `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotBulletList, decorators: [{ type: Component, args: [{ selector: 'dotcms-block-editor-renderer-bullet-list', changeDetection: ChangeDetectionStrategy.OnPush, template: ` <ul> <ng-content /> </ul> ` }] }] }); class DotOrdererList { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotOrdererList, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.9", type: DotOrdererList, isStandalone: true, selector: "dotcms-block-editor-renderer-ordered-list", ngImport: i0, template: ` <ol> <ng-content /> </ol> `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotOrdererList, decorators: [{ type: Component, args: [{ selector: 'dotcms-block-editor-renderer-ordered-list', changeDetection: ChangeDetectionStrategy.OnPush, template: ` <ol> <ng-content /> </ol> ` }] }] }); class DotListItem { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotListItem, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.9", type: DotListItem, isStandalone: true, selector: "dotcms-block-editor-renderer-list-item", ngImport: i0, template: ` <li> <ng-content /> </li> `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotListItem, decorators: [{ type: Component, args: [{ selector: 'dotcms-block-editor-renderer-list-item', changeDetection: ChangeDetectionStrategy.OnPush, template: ` <li> <ng-content /> </li> ` }] }] }); class DotTableBlock { constructor() { this.blockEditorItem = DotCMSBlockEditorItemComponent; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotTableBlock, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: DotTableBlock, isStandalone: true, selector: "dotcms-block-editor-renderer-table", inputs: { content: "content" }, ngImport: i0, template: ` <table> <thead> @for (rowNode of content?.slice(0, 1); track rowNode.type) { <tr> @for (cellNode of rowNode.content; track cellNode.type) { <th [attr.colspan]="cellNode.attrs?.['colspan'] || 1" [attr.rowspan]="cellNode.attrs?.['rowspan'] || 1"> <ng-container *ngComponentOutlet=" blockEditorItem; inputs: { content: cellNode.content } "></ng-container> </th> } </tr> } </thead> <tbody> @for (rowNode of content?.slice(1); track rowNode.type) { <tr> @for (cellNode of rowNode.content; track cellNode.type) { <td [attr.colspan]="cellNode.attrs?.['colspan'] || 1" [attr.rowspan]="cellNode.attrs?.['rowspan'] || 1"> <ng-container *ngComponentOutlet=" blockEditorItem; inputs: { content: cellNode.content } "></ng-container> </td> } </tr> } </tbody> </table> `, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotTableBlock, decorators: [{ type: Component, args: [{ selector: 'dotcms-block-editor-renderer-table', imports: [NgComponentOutlet], template: ` <table> <thead> @for (rowNode of content?.slice(0, 1); track rowNode.type) { <tr> @for (cellNode of rowNode.content; track cellNode.type) { <th [attr.colspan]="cellNode.attrs?.['colspan'] || 1" [attr.rowspan]="cellNode.attrs?.['rowspan'] || 1"> <ng-container *ngComponentOutlet=" blockEditorItem; inputs: { content: cellNode.content } "></ng-container> </th> } </tr> } </thead> <tbody> @for (rowNode of content?.slice(1); track rowNode.type) { <tr> @for (cellNode of rowNode.content; track cellNode.type) { <td [attr.colspan]="cellNode.attrs?.['colspan'] || 1" [attr.rowspan]="cellNode.attrs?.['rowspan'] || 1"> <ng-container *ngComponentOutlet=" blockEditorItem; inputs: { content: cellNode.content } "></ng-container> </td> } </tr> } </tbody> </table> ` }] }], propDecorators: { content: [{ type: Input }] } }); class DotParagraphBlock { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotParagraphBlock, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.9", type: DotParagraphBlock, isStandalone: true, selector: "dotcms-block-editor-renderer-paragraph", ngImport: i0, template: ` <p> <ng-content /> </p> `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotParagraphBlock, decorators: [{ type: Component, args: [{ selector: 'dotcms-block-editor-renderer-paragraph', changeDetection: ChangeDetectionStrategy.OnPush, template: ` <p> <ng-content /> </p> ` }] }] }); class DotHeadingBlock { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotHeadingBlock, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: DotHeadingBlock, isStandalone: true, selector: "dotcms-block-editor-renderer-heading", inputs: { level: "level" }, ngImport: i0, template: ` @switch (level) { @case ('1') { <h1> <ng-content /> </h1> } @case ('2') { <h2> <ng-content /> </h2> } @case ('3') { <h3> <ng-content /> </h3> } @case ('4') { <h4> <ng-content /> </h4> } @case ('5') { <h5> <ng-content /> </h5> } @case ('6') { <h6> <ng-content /> </h6> } @default { <h1> <ng-content /> </h1> } } `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotHeadingBlock, decorators: [{ type: Component, args: [{ selector: 'dotcms-block-editor-renderer-heading', changeDetection: ChangeDetectionStrategy.OnPush, template: ` @switch (level) { @case ('1') { <h1> <ng-content /> </h1> } @case ('2') { <h2> <ng-content /> </h2> } @case ('3') { <h3> <ng-content /> </h3> } @case ('4') { <h4> <ng-content /> </h4> } @case ('5') { <h5> <ng-content /> </h5> } @case ('6') { <h6> <ng-content /> </h6> } @default { <h1> <ng-content /> </h1> } } ` }] }], propDecorators: { level: [{ type: Input }] } }); class DotTextBlock { constructor() { this.marks = []; this.text = ''; this.$remainingMarks = computed(() => this.marks?.slice(1), ...(ngDevMode ? [{ debugName: "$remainingMarks" }] : [])); this.$currentAttrs = computed(() => { const attrs = { ...(this.marks?.[0]?.attrs || {}) }; if (attrs['class']) { attrs['className'] = attrs['class']; delete attrs['class']; } return attrs; }, ...(ngDevMode ? [{ debugName: "$currentAttrs" }] : [])); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotTextBlock, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: DotTextBlock, isStandalone: true, selector: "dotcms-block-editor-renderer-text", inputs: { marks: "marks", text: "text" }, ngImport: i0, template: ` @switch (marks?.[0]?.type) { @case ('link') { <a [attr.href]="$currentAttrs()['href'] || ''" [attr.target]="$currentAttrs()['target'] || ''"> <dotcms-block-editor-renderer-text [marks]="$remainingMarks()" [text]="text" /> </a> } @case ('bold') { <strong> <dotcms-block-editor-renderer-text [marks]="$remainingMarks()" [text]="text" /> </strong> } @case ('underline') { <u> <dotcms-block-editor-renderer-text [marks]="$remainingMarks()" [text]="text" /> </u> } @case ('italic') { <em> <dotcms-block-editor-renderer-text [marks]="$remainingMarks()" [text]="text" /> </em> } @case ('strike') { <s> <dotcms-block-editor-renderer-text [marks]="$remainingMarks()" [text]="text" /> </s> } @case ('superscript') { <sup> <dotcms-block-editor-renderer-text [marks]="$remainingMarks()" [text]="text" /> </sup> } @case ('subscript') { <sub> <dotcms-block-editor-renderer-text [marks]="$remainingMarks()" [text]="text" /> </sub> } @default { {{ text }} } } `, isInline: true, dependencies: [{ kind: "component", type: DotTextBlock, selector: "dotcms-block-editor-renderer-text", inputs: ["marks", "text"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotTextBlock, decorators: [{ type: Component, args: [{ selector: 'dotcms-block-editor-renderer-text', changeDetection: ChangeDetectionStrategy.OnPush, template: ` @switch (marks?.[0]?.type) { @case ('link') { <a [attr.href]="$currentAttrs()['href'] || ''" [attr.target]="$currentAttrs()['target'] || ''"> <dotcms-block-editor-renderer-text [marks]="$remainingMarks()" [text]="text" /> </a> } @case ('bold') { <strong> <dotcms-block-editor-renderer-text [marks]="$remainingMarks()" [text]="text" /> </strong> } @case ('underline') { <u> <dotcms-block-editor-renderer-text [marks]="$remainingMarks()" [text]="text" /> </u> } @case ('italic') { <em> <dotcms-block-editor-renderer-text [marks]="$remainingMarks()" [text]="text" /> </em> } @case ('strike') { <s> <dotcms-block-editor-renderer-text [marks]="$remainingMarks()" [text]="text" /> </s> } @case ('superscript') { <sup> <dotcms-block-editor-renderer-text [marks]="$remainingMarks()" [text]="text" /> </sup> } @case ('subscript') { <sub> <dotcms-block-editor-renderer-text [marks]="$remainingMarks()" [text]="text" /> </sub> } @default { {{ text }} } } ` }] }], propDecorators: { marks: [{ type: Input }], text: [{ type: Input }] } }); class DotUnknownBlockComponent { constructor() { this.style = { backgroundColor: '#fff5f5', color: '#333', padding: '1rem', borderRadius: '0.5rem', marginBottom: '1rem', marginTop: '1rem', border: '1px solid #fc8181' }; } get isEditMode() { return getUVEState()?.mode === UVE_MODE.EDIT; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotUnknownBlockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: DotUnknownBlockComponent, isStandalone: true, selector: "dotcms-block-editor-renderer-unknown", inputs: { node: "node" }, ngImport: i0, template: ` @if (isEditMode) { <div [style]="style" data-testid="unknown-block-type"> <strong style="color: #c53030">Warning:</strong> The block type <strong>{{ node.type }}</strong> is not recognized. Please check your <a href="https://dev.dotcms.com/docs/block-editor" target="_blank" rel="noopener noreferrer"> configuration </a> or contact support for assistance. </div> } `, isInline: true }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotUnknownBlockComponent, decorators: [{ type: Component, args: [{ selector: 'dotcms-block-editor-renderer-unknown', template: ` @if (isEditMode) { <div [style]="style" data-testid="unknown-block-type"> <strong style="color: #c53030">Warning:</strong> The block type <strong>{{ node.type }}</strong> is not recognized. Please check your <a href="https://dev.dotcms.com/docs/block-editor" target="_blank" rel="noopener noreferrer"> configuration </a> or contact support for assistance. </div> } ` }] }], propDecorators: { node: [{ type: Input }] } }); class DotVideoBlock { constructor() { this.$srcURL = computed(() => this.attrs?.['src'], ...(ngDevMode ? [{ debugName: "$srcURL" }] : [])); this.$posterURL = computed(() => this.attrs?.['data']?.['thumbnail'], ...(ngDevMode ? [{ debugName: "$posterURL" }] : [])); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotVideoBlock, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.9", type: DotVideoBlock, isStandalone: true, selector: "dotcms-block-editor-renderer-video", inputs: { attrs: "attrs" }, ngImport: i0, template: ` <video [controls]="true" preload="metadata" [poster]="this.$posterURL()" [width]="attrs?.['width']" [height]="attrs?.['height']"> <track default kind="captions" srclang="en" /> <source [src]="this.$srcURL()" [type]="attrs?.['mimeType']" /> Your browser does not support the <code>video</code> element. </video> `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotVideoBlock, decorators: [{ type: Component, args: [{ selector: 'dotcms-block-editor-renderer-video', changeDetection: ChangeDetectionStrategy.OnPush, template: ` <video [controls]="true" preload="metadata" [poster]="this.$posterURL()" [width]="attrs?.['width']" [height]="attrs?.['height']"> <track default kind="captions" srclang="en" /> <source [src]="this.$srcURL()" [type]="attrs?.['mimeType']" /> Your browser does not support the <code>video</code> element. </video> ` }] }], propDecorators: { attrs: [{ type: Input }] } }); class DotCMSBlockEditorItemComponent { constructor() { this.BLOCKS = BlockEditorDefaultBlocks; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: DotCMSBlockEditorItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: DotCMSBlockEditorItemComponent, isStandalone: true, selector: "dotcms-block-editor-renderer-block", inputs: { content: "content", customRenderers: "customRenderers" }, ngImport: i0, template: "@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_QU