UNPKG

@dotcms/angular

Version:

Official Angular Components library to render a dotCMS page.

864 lines (849 loc) 39 kB
import { TINYMCE_SCRIPT_SRC, EditorComponent } from '@tinymce/tinymce-angular'; import * as i0 from '@angular/core'; import { inject, Renderer2, ElementRef, SecurityContext, Component, ViewChild, Input, HostListener, Injectable, ChangeDetectionStrategy, HostBinding, signal, computed, DestroyRef } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; import { NOTIFY_CLIENT, isInsideEditor, DotCmsClient, postMessageToEditor, CLIENT_ACTIONS, initEditor, updateNavigation } from '@dotcms/client'; import { AsyncPipe, NgComponentOutlet } from '@angular/common'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ActivatedRoute } from '@angular/router'; import { BehaviorSubject } from 'rxjs'; import { map } from 'rxjs/operators'; const DEFAULT_TINYMCE_CONFIG = { menubar: false, inline: true, valid_styles: { '*': 'font-size,font-family,color,text-decoration,text-align' }, powerpaste_word_import: 'clean', powerpaste_html_import: 'clean', suffix: '.min', // Suffix to use when loading resources license_key: 'gpl' }; const TINYMCE_CONFIG = { minimal: { ...DEFAULT_TINYMCE_CONFIG, plugins: 'link autolink', toolbar: 'bold italic underline | link', valid_elements: 'strong,em,span[style],a[href]' }, full: { ...DEFAULT_TINYMCE_CONFIG, plugins: 'link lists autolink charmap', style_formats: [ { title: 'Paragraph', format: 'p' }, { title: 'Header 1', format: 'h1' }, { title: 'Header 2', format: 'h2' }, { title: 'Header 3', format: 'h3' }, { title: 'Header 4', format: 'h4' }, { title: 'Header 5', format: 'h5' }, { title: 'Header 6', format: 'h6' }, { title: 'Pre', format: 'pre' }, { title: 'Code', format: 'code' } ], toolbar: [ 'styleselect undo redo | bold italic underline | forecolor backcolor | alignleft aligncenter alignright alignfull | numlist bullist outdent indent | hr charmap removeformat | link' ] }, plain: { ...DEFAULT_TINYMCE_CONFIG, plugins: '', toolbar: '' } }; /** * Dot editable text component. * This component is responsible to render a text field that can be edited inline. * * @export * @class DotEditableTextComponent * @implements {OnInit} * @implements {OnChanges} */ class DotEditableTextComponent { constructor() { /** * Represents the mode of the editor which can be `plain`, `minimal`, or `full` * * @type {DOT_EDITABLE_TEXT_MODE} * @memberof DotEditableTextComponent */ this.mode = 'plain'; /** * Represents the format of the editor which can be `text` or `html` * * @type {DOT_EDITABLE_TEXT_FORMAT} * @memberof DotEditableTextComponent */ this.format = 'text'; /** * Represents the field name of the `contentlet` that can be edited * * @memberof DotEditableTextComponent */ this.fieldName = ''; /** * Represents the content of the `contentlet` that can be edited * * @protected * @memberof DotEditableTextComponent */ this.content = ''; this.#sanitizer = inject(DomSanitizer); this.#renderer = inject(Renderer2); this.#elementRef = inject(ElementRef); } #sanitizer; #renderer; #elementRef; /** * The TinyMCE editor * * @readonly * @memberof DotEditableTextComponent */ get editor() { return this.editorComponent?.editor; } /** * Returns the number of pages the contentlet is on * * @readonly * @memberof DotEditableTextComponent */ get onNumberOfPages() { return this.contentlet['onNumberOfPages'] || 1; } /** * Handle copy contentlet inline editing success event * * @param {MessageEvent} { data } * @return {*} * @memberof DotEditableTextComponent */ onMessage({ data }) { const { name, payload } = data; if (name !== NOTIFY_CLIENT.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() { this.isInsideEditor = isInsideEditor(); if (!this.isInsideEditor) { this.innerHTMLToElement(); return; } this.init = { ...TINYMCE_CONFIG[this.mode], base_url: `${DotCmsClient.dotcmsUrl}/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 DotEditableTextComponent */ onMouseDown({ event }) { if (this.onNumberOfPages <= 1 || this.editorComponent.editor.hasFocus()) { return; } const { inode, languageId: language } = this.contentlet; event.stopPropagation(); event.preventDefault(); try { postMessageToEditor({ action: CLIENT_ACTIONS.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 DotEditableTextComponent */ onFocusOut() { const content = this.editor.getContent({ format: this.format }); if (!this.editor.isDirty() || !this.didContentChange(content)) { return; } const { inode, languageId: langId } = this.contentlet; try { postMessageToEditor({ action: CLIENT_ACTIONS.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 DotEditableTextComponent */ 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 DotEditableTextComponent */ didContentChange(editedContent) { return this.content !== editedContent; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.3", ngImport: i0, type: DotEditableTextComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.3", type: DotEditableTextComponent, isStandalone: true, selector: "dot-editable-text", inputs: { mode: "mode", format: "format", contentlet: "contentlet", fieldName: "fieldName" }, host: { listeners: { "window:message": "onMessage($event)" } }, providers: [ { provide: TINYMCE_SCRIPT_SRC, useFactory: () => { return `${DotCmsClient.dotcmsUrl}/ext/tinymcev7/tinymce.min.js`; } } ], viewQueries: [{ propertyName: "editorComponent", first: true, predicate: EditorComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "@if (isInsideEditor) {\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):hover{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: "18.2.3", ngImport: i0, type: DotEditableTextComponent, decorators: [{ type: Component, args: [{ selector: 'dot-editable-text', standalone: true, imports: [EditorComponent], providers: [ { provide: TINYMCE_SCRIPT_SRC, useFactory: () => { return `${DotCmsClient.dotcmsUrl}/ext/tinymcev7/tinymce.min.js`; } } ], template: "@if (isInsideEditor) {\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):hover{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']] }] } }); /** * @author dotCMS * @description This service is responsible for managing the page context. * @export * @class PageContextService */ class PageContextService { constructor() { this.context$ = new BehaviorSubject(null); } /** * @description Get the context * @readonly * @type {DotCMSPageContext} * @memberof PageContextService */ get context() { return this.context$.getValue(); } /** * @description Get the context as an observable * @readonly * @memberof PageContextService */ get contextObs$() { return this.context$.asObservable(); } /** * @description Get the current page asset * @readonly * @type {(Observable<DotCMSPageAsset | null>)} * @memberof PageContextService */ get currentPage$() { return this.contextObs$.pipe(map((context) => context?.pageAsset || null)); } /** * * @description Set the context * @param {DotCMSPageAsset} value * @memberof DotcmsContextService */ setContext(pageAsset, components) { this.context$.next({ pageAsset, components, isInsideEditor: isInsideEditor() }); } /** * @description Set the page asset in the context * @param {DotCMSPageAsset} pageAsset * @memberof PageContextService */ setPageAsset(pageAsset) { this.context$.next({ ...this.context, pageAsset }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.3", ngImport: i0, type: PageContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.3", ngImport: i0, type: PageContextService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.3", ngImport: i0, type: PageContextService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); //Changed the type, to avoid SQ issue. //This should be put inside a lib /** * Represents a mapping of numbers to corresponding CSS class names for column end values. * @typedef {Record<number, string | null>} EndClassMap */ const endClassMap = { 1: 'col-end-1', 2: 'col-end-2', 3: 'col-end-3', 4: 'col-end-4', 5: 'col-end-5', 6: 'col-end-6', 7: 'col-end-7', 8: 'col-end-8', 9: 'col-end-9', 10: 'col-end-10', 11: 'col-end-11', 12: 'col-end-12', 13: 'col-end-13' }; //Changed the type, to avoid SQ issue. //This should be put inside a lib /** * Represents a mapping of numbers to CSS class names for starting columns. * @typedef {Record<number, string | null>} StartClassMap */ const startClassMap = { 1: 'col-start-1', 2: 'col-start-2', 3: 'col-start-3', 4: 'col-start-4', 5: 'col-start-5', 6: 'col-start-6', 7: 'col-start-7', 8: 'col-start-8', 9: 'col-start-9', 10: 'col-start-10', 11: 'col-start-11', 12: 'col-start-12' }; /** * Retrieves the data for a set of containers. * * @param containers - The DotCMSPageAssetContainer object containing the containers. * @param containerRef - The DotCMSContainer object representing the container reference. * @returns An object containing the container data, accept types, contentlets, and variant ID. */ const getContainersData = (containers, containerRef) => { const { identifier, uuid } = containerRef; const { containerStructures, container } = containers[identifier]; const { variantId } = container?.parentPermissionable || {}; const acceptTypes = containerStructures .map((structure) => structure.contentTypeVar) .join(','); // Get the contentlets for "this" container const contentlets = containers[identifier].contentlets[`uuid-${uuid}`] ?? containers[identifier].contentlets[`uuid-dotParser_${uuid}`]; if (!contentlets) { console.warn(`We couldn't find the contentlets for the container with the identifier ${identifier} and the uuid ${uuid} becareful by adding content to this container.\nWe recommend to change the container in the layout and add the content again.`); } return { ...containers[identifier].container, acceptTypes, contentlets: contentlets ?? [], variantId }; }; /** * Returns the position style classes based on the start and end values. * Used to set the grid column start and end values. * @param start - The start value. * @param end - The end value. * @returns An object containing the startClass and endClass. */ const getPositionStyleClasses = (start, end) => { const startClass = startClassMap[start]; const endClass = endClassMap[end]; return { startClass, endClass }; }; /** * This component is responsible to display a message when there is no component for a contentlet. * * @export * @class NoComponent */ class NoComponent { constructor() { /** * The data-testid attribute used for identifying the component during testing. */ this.testId = 'no-component'; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.3", ngImport: i0, type: NoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.3", type: NoComponent, isStandalone: true, selector: "dotcms-no-component", inputs: { contentlet: "contentlet" }, host: { properties: { "attr.data-testid": "this.testId" } }, ngImport: i0, template: ` No Component for {{ contentlet.contentType }} `, isInline: true, styles: [":host{display:block}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.3", ngImport: i0, type: NoComponent, decorators: [{ type: Component, args: [{ selector: 'dotcms-no-component', standalone: true, template: ` No Component for {{ contentlet.contentType }} `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}\n"] }] }], propDecorators: { contentlet: [{ type: Input }], testId: [{ type: HostBinding, args: ['attr.data-testid'] }] } }); /** * This component is responsible to display a contentlet. * * @export * @class ContentletComponent * @implements {OnChanges} */ class ContentletComponent { constructor() { /** * The identifier of contentlet component. * * @type {(string | null)} * @memberof ContentletComponent */ this.identifier = null; /** * The base type of contentlet component. * * @type {(string | null)} * @memberof ContentletComponent */ this.baseType = null; /** * The title of contentlet component. * * @type {(string | null)} * @memberof ContentletComponent */ this.title = null; /** * The inode of contentlet component. * * @type {(string | null)} * @memberof ContentletComponent */ this.inode = null; /** * The type of contentlet component. * * @type {(string | null)} * @memberof ContentletComponent */ this.dotType = null; /** * The container of contentlet component. * * @type {(string | null)} * @memberof ContentletComponent */ this.dotContainer = null; /** * The number of pages where the contentlet appears * * @type {(string | null)} * @memberof ContentletComponent */ this.numberOfPages = null; /** * The content of contentlet component. * * @type {(string | null)} * @memberof ContentletComponent */ this.dotContent = null; } ngOnChanges() { this.identifier = this.contentlet.identifier; this.baseType = this.contentlet.baseType; this.title = this.contentlet.title; this.inode = this.contentlet.inode; this.dotType = this.contentlet.contentType; this.dotContainer = this.container; this.numberOfPages = this.contentlet['onNumberOfPages']; this.dotContent = 'contentlet'; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.3", ngImport: i0, type: ContentletComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.3", type: ContentletComponent, isStandalone: true, selector: "dotcms-contentlet-wrapper", inputs: { contentlet: "contentlet", container: "container" }, host: { properties: { "attr.data-dot-identifier": "this.identifier", "attr.data-dot-basetype": "this.baseType", "attr.data-dot-title": "this.title", "attr.data-dot-inode": "this.inode", "attr.data-dot-type": "this.dotType", "attr.data-dot-container": "this.dotContainer", "attr.data-dot-on-number-of-pages": "this.numberOfPages", "attr.data-dot-object": "this.dotContent" } }, usesOnChanges: true, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.3", ngImport: i0, type: ContentletComponent, decorators: [{ type: Component, args: [{ selector: 'dotcms-contentlet-wrapper', standalone: true, template: '<ng-content></ng-content>', changeDetection: ChangeDetectionStrategy.OnPush }] }], propDecorators: { contentlet: [{ type: Input, args: [{ required: true }] }], container: [{ type: Input }], identifier: [{ type: HostBinding, args: ['attr.data-dot-identifier'] }], baseType: [{ type: HostBinding, args: ['attr.data-dot-basetype'] }], title: [{ type: HostBinding, args: ['attr.data-dot-title'] }], inode: [{ type: HostBinding, args: ['attr.data-dot-inode'] }], dotType: [{ type: HostBinding, args: ['attr.data-dot-type'] }], dotContainer: [{ type: HostBinding, args: ['attr.data-dot-container'] }], numberOfPages: [{ type: HostBinding, args: ['attr.data-dot-on-number-of-pages'] }], dotContent: [{ type: HostBinding, args: ['attr.data-dot-object'] }] } }); /** * This component is responsible to display a container with contentlets. * * @export * @class ContainerComponent * @implements {OnChanges} */ class ContainerComponent { constructor() { this.pageContextService = inject(PageContextService); this.NoComponent = NoComponent; this.$isInsideEditor = signal(false); this.$contentlets = signal([]); this.$dotContainer = signal(null); this.$dotContainerAsString = computed(() => JSON.stringify(this.$dotContainer())); /** * The accept types for the container component. * * @type {(string | null)} * @memberof ContainerComponent */ this.acceptTypes = null; /** * The identifier for the container component. * * @type {(string | null)} * @memberof ContainerComponent */ this.identifier = null; /** * The max contentlets for the container component. * * @type {(number | null)} * @memberof ContainerComponent */ this.maxContentlets = null; /** * The uuid for the container component. * * @type {(string | null)} * @memberof ContainerComponent */ this.uuid = null; /** * The class for the container component. * * @type {(string | null)} * @memberof ContainerComponent */ this.class = null; /** * The dot object for the container component. * * @type {(string | null)} * @memberof ContainerComponent */ this.dotObject = null; /** * The data-testid attribute used for identifying the component during testing. * * @memberof ContainerComponent */ this.testId = 'dot-container'; } ngOnChanges() { const { pageAsset, components, isInsideEditor } = this.pageContextService.context; const { acceptTypes, maxContentlets, variantId, path, contentlets } = getContainersData(pageAsset.containers, this.container); const { identifier, uuid } = this.container; this.componentsMap = components; this.$isInsideEditor.set(isInsideEditor); this.$contentlets.set(contentlets); this.$dotContainer.set({ identifier: path ?? identifier, acceptTypes, maxContentlets, variantId, uuid }); if (this.$isInsideEditor()) { this.acceptTypes = acceptTypes; this.identifier = identifier; this.maxContentlets = maxContentlets; this.uuid = uuid; this.class = this.$contentlets().length ? null : 'empty-container'; this.dotObject = 'container'; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.3", ngImport: i0, type: ContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.3", type: ContainerComponent, isStandalone: true, selector: "dotcms-container", inputs: { container: "container" }, host: { properties: { "attr.data-dot-accept-types": "this.acceptTypes", "attr.data-dot-identifier": "this.identifier", "attr.data-max-contentlets": "this.maxContentlets", "attr.data-dot-uuid": "this.uuid", "class": "this.class", "attr.data-dot-object": "this.dotObject", "attr.data-testid": "this.testId" } }, usesOnChanges: true, ngImport: i0, template: "@if ($isInsideEditor()) {\n @if ($contentlets().length) {\n @for (contentlet of $contentlets(); track $index) {\n <dotcms-contentlet-wrapper\n [contentlet]=\"contentlet\"\n [container]=\"$dotContainerAsString()\">\n <ng-container\n *ngComponentOutlet=\"\n (componentsMap[contentlet.contentType] || componentsMap['CustomNoComponent']\n | async) || NoComponent;\n inputs: { contentlet }\n \" />\n </dotcms-contentlet-wrapper>\n }\n } @else {\n This container is empty.\n }\n} @else {\n @for (contentlet of $contentlets(); track $index) {\n <ng-container\n *ngComponentOutlet=\"\n componentsMap[contentlet.contentType] | async;\n inputs: { contentlet }\n \" />\n }\n}\n", styles: [":host.empty-container{width:100%;background-color:#ecf0fd;display:flex;justify-content:center;align-items:center;color:#030e32;height:10rem}\n"], dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }, { kind: "component", type: ContentletComponent, selector: "dotcms-contentlet-wrapper", inputs: ["contentlet", "container"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.3", ngImport: i0, type: ContainerComponent, decorators: [{ type: Component, args: [{ selector: 'dotcms-container', standalone: true, imports: [AsyncPipe, NgComponentOutlet, NoComponent, ContentletComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if ($isInsideEditor()) {\n @if ($contentlets().length) {\n @for (contentlet of $contentlets(); track $index) {\n <dotcms-contentlet-wrapper\n [contentlet]=\"contentlet\"\n [container]=\"$dotContainerAsString()\">\n <ng-container\n *ngComponentOutlet=\"\n (componentsMap[contentlet.contentType] || componentsMap['CustomNoComponent']\n | async) || NoComponent;\n inputs: { contentlet }\n \" />\n </dotcms-contentlet-wrapper>\n }\n } @else {\n This container is empty.\n }\n} @else {\n @for (contentlet of $contentlets(); track $index) {\n <ng-container\n *ngComponentOutlet=\"\n componentsMap[contentlet.contentType] | async;\n inputs: { contentlet }\n \" />\n }\n}\n", styles: [":host.empty-container{width:100%;background-color:#ecf0fd;display:flex;justify-content:center;align-items:center;color:#030e32;height:10rem}\n"] }] }], propDecorators: { container: [{ type: Input, args: [{ required: true }] }], acceptTypes: [{ type: HostBinding, args: ['attr.data-dot-accept-types'] }], identifier: [{ type: HostBinding, args: ['attr.data-dot-identifier'] }], maxContentlets: [{ type: HostBinding, args: ['attr.data-max-contentlets'] }], uuid: [{ type: HostBinding, args: ['attr.data-dot-uuid'] }], class: [{ type: HostBinding, args: ['class'] }], dotObject: [{ type: HostBinding, args: ['attr.data-dot-object'] }], testId: [{ type: HostBinding, args: ['attr.data-testid'] }] } }); /** * This component is responsible to display a column with containers. * * @export * @class ColumnComponent * @implements {OnInit} */ class ColumnComponent { constructor() { /** * The data-testid attribute used for identifying the component during testing. * * @memberof ColumnComponent */ this.containerClasses = ''; } ngOnInit() { const { startClass, endClass } = getPositionStyleClasses(this.column.leftOffset, this.column.width + this.column.leftOffset); this.containerClasses = `${startClass} ${endClass}`; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.3", ngImport: i0, type: ColumnComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.3", type: ColumnComponent, isStandalone: true, selector: "dotcms-column", inputs: { column: "column" }, host: { properties: { "class": "this.containerClasses" } }, ngImport: i0, template: ` @for (container of column.containers; track $index) { <dotcms-container [container]="container" /> } `, isInline: true, styles: [":host.col-start-1{grid-column-start:1}:host.col-start-2{grid-column-start:2}:host.col-start-3{grid-column-start:3}:host.col-start-4{grid-column-start:4}:host.col-start-5{grid-column-start:5}:host.col-start-6{grid-column-start:6}:host.col-start-7{grid-column-start:7}:host.col-start-8{grid-column-start:8}:host.col-start-9{grid-column-start:9}:host.col-start-10{grid-column-start:10}:host.col-start-11{grid-column-start:11}:host.col-start-12{grid-column-start:12}:host.col-end-1{grid-column-end:1}:host.col-end-2{grid-column-end:2}:host.col-end-3{grid-column-end:3}:host.col-end-4{grid-column-end:4}:host.col-end-5{grid-column-end:5}:host.col-end-6{grid-column-end:6}:host.col-end-7{grid-column-end:7}:host.col-end-8{grid-column-end:8}:host.col-end-9{grid-column-end:9}:host.col-end-10{grid-column-end:10}:host.col-end-11{grid-column-end:11}:host.col-end-12{grid-column-end:12}:host.col-end-13{grid-column-end:13}\n"], dependencies: [{ kind: "component", type: ContainerComponent, selector: "dotcms-container", inputs: ["container"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.3", ngImport: i0, type: ColumnComponent, decorators: [{ type: Component, args: [{ selector: 'dotcms-column', standalone: true, imports: [ContainerComponent], template: ` @for (container of column.containers; track $index) { <dotcms-container [container]="container" /> } `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host.col-start-1{grid-column-start:1}:host.col-start-2{grid-column-start:2}:host.col-start-3{grid-column-start:3}:host.col-start-4{grid-column-start:4}:host.col-start-5{grid-column-start:5}:host.col-start-6{grid-column-start:6}:host.col-start-7{grid-column-start:7}:host.col-start-8{grid-column-start:8}:host.col-start-9{grid-column-start:9}:host.col-start-10{grid-column-start:10}:host.col-start-11{grid-column-start:11}:host.col-start-12{grid-column-start:12}:host.col-end-1{grid-column-end:1}:host.col-end-2{grid-column-end:2}:host.col-end-3{grid-column-end:3}:host.col-end-4{grid-column-end:4}:host.col-end-5{grid-column-end:5}:host.col-end-6{grid-column-end:6}:host.col-end-7{grid-column-end:7}:host.col-end-8{grid-column-end:8}:host.col-end-9{grid-column-end:9}:host.col-end-10{grid-column-end:10}:host.col-end-11{grid-column-end:11}:host.col-end-12{grid-column-end:12}:host.col-end-13{grid-column-end:13}\n"] }] }], propDecorators: { column: [{ type: Input }], containerClasses: [{ type: HostBinding, args: ['class'] }] } }); /** * This component is responsible to display a row with columns. * * @export * @class RowComponent */ class RowComponent { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.3", ngImport: i0, type: RowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.3", type: RowComponent, isStandalone: true, selector: "dotcms-row", inputs: { row: "row" }, ngImport: i0, template: ` @for (column of row.columns; track $index) { <dotcms-column [column]="column" /> } `, isInline: true, styles: [":host{display:grid;grid-template-columns:repeat(12,1fr);gap:1rem;row-gap:1rem}\n"], dependencies: [{ kind: "component", type: ColumnComponent, selector: "dotcms-column", inputs: ["column"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.3", ngImport: i0, type: RowComponent, decorators: [{ type: Component, args: [{ selector: 'dotcms-row', standalone: true, imports: [ColumnComponent], template: ` @for (column of row.columns; track $index) { <dotcms-column [column]="column" /> } `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:grid;grid-template-columns:repeat(12,1fr);gap:1rem;row-gap:1rem}\n"] }] }], propDecorators: { row: [{ type: Input, args: [{ required: true }] }] } }); /** * `DotcmsLayoutComponent` is a class that represents the layout for a DotCMS page. * It includes a `pageAsset` property that represents the DotCMS page asset and a `components` property that represents the dynamic components for the page. * * @export * @class DotcmsLayoutComponent */ class DotcmsLayoutComponent { constructor() { this.route = inject(ActivatedRoute); this.pageContextService = inject(PageContextService); this.destroyRef$ = inject(DestroyRef); this.pageAsset$ = this.pageContextService.currentPage$; } /** * Represents the DotCMS page asset. * * @type {DotCMSPageAsset} * @memberof DotcmsLayoutComponent */ set pageAsset(value) { this._pageAsset = value; if (!value.layout) { console.warn('Warning: pageAsset does not have a `layout` property. Might be using an advaced template or your dotCMS instance not have a enterprise license.'); } } /** * Returns the DotCMS page asset. * * @readonly * @type {DotCMSPageAsset} * @memberof DotcmsLayoutComponent */ get pageAsset() { return this._pageAsset; } ngOnInit() { this.pageContextService.setContext(this.pageAsset, this.components); if (!isInsideEditor()) { return; } this.client = DotCmsClient.instance; this.route.url.pipe(takeUntilDestroyed(this.destroyRef$)).subscribe((urlSegments) => { const pathname = '/' + urlSegments.join('/'); initEditor({ pathname }); updateNavigation(pathname || '/'); }); this.client.editor.on('changes', (data) => { if (this.onReload) { this.onReload(); return; } this.pageContextService.setPageAsset(data); }); postMessageToEditor({ action: CLIENT_ACTIONS.CLIENT_READY, payload: this.editor }); } ngOnDestroy() { if (!isInsideEditor()) { return; } this.client.editor.off('changes'); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.3", ngImport: i0, type: DotcmsLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.3", type: DotcmsLayoutComponent, isStandalone: true, selector: "dotcms-layout", inputs: { pageAsset: "pageAsset", components: "components", onReload: "onReload", editor: "editor" }, ngImport: i0, template: ` @if (pageAsset$ | async; as page) { @for (row of page?.layout?.body?.rows; track $index) { <dotcms-row [row]="row" /> } } `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type: RowComponent, selector: "dotcms-row", inputs: ["row"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.3", ngImport: i0, type: DotcmsLayoutComponent, decorators: [{ type: Component, args: [{ selector: 'dotcms-layout', standalone: true, imports: [RowComponent, AsyncPipe], template: ` @if (pageAsset$ | async; as page) { @for (row of page?.layout?.body?.rows; track $index) { <dotcms-row [row]="row" /> } } `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}\n"] }] }], propDecorators: { pageAsset: [{ type: Input, args: [{ required: true }] }], components: [{ type: Input, args: [{ required: true }] }], onReload: [{ type: Input }], editor: [{ type: Input }] } }); /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */ /** * Generated bundle index. Do not edit. */ export { DotEditableTextComponent, DotcmsLayoutComponent, PageContextService }; //# sourceMappingURL=dotcms-angular.mjs.map