@dotcms/angular
Version:
Official Angular Components library to render a dotCMS page.
864 lines (849 loc) • 39 kB
JavaScript
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