@dotcms/angular
Version:
Official Angular Components library to render a dotCMS page.
1,114 lines (1,100 loc) • 98.7 kB
JavaScript
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