diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/dot-ema-dialog.component.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/dot-ema-dialog.component.ts index 28230b862661..7df667473ceb 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/dot-ema-dialog.component.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/dot-ema-dialog.component.ts @@ -13,7 +13,7 @@ import { inject, signal } from '@angular/core'; -import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MessageService } from 'primeng/api'; import { DialogModule } from 'primeng/dialog'; @@ -21,28 +21,15 @@ import { DialogModule } from 'primeng/dialog'; import { take } from 'rxjs/operators'; import { DotMessageService } from '@dotcms/data-access'; -import { - DotCMSBaseTypesContentTypes, - DotCMSContentlet, - DotCMSWorkflowActionEvent, - DotContentCompareEvent -} from '@dotcms/dotcms-models'; +import { DotCMSWorkflowActionEvent, DotContentCompareEvent } from '@dotcms/dotcms-models'; import { DotContentCompareModule } from '@dotcms/portlets/dot-ema/ui'; import { DotSpinnerModule, SafeUrlPipe } from '@dotcms/ui'; -import { DotEmaDialogStore } from './store/dot-ema-dialog.store'; +import { DotEditorDialogService } from './services/dot-ema-dialog.service'; import { DotEmaWorkflowActionsService } from '../../services/dot-ema-workflow-actions/dot-ema-workflow-actions.service'; import { DialogStatus, NG_CUSTOM_EVENTS } from '../../shared/enums'; -import { - ActionPayload, - CreateContentletAction, - CreateFromPaletteAction, - DialogAction, - DotPage, - EditContentletPayload, - VTLFile -} from '../../shared/models'; +import { DialogAction } from '../../shared/models'; import { EmaFormSelectorComponent } from '../ema-form-selector/ema-form-selector.component'; @Component({ @@ -57,7 +44,7 @@ import { EmaFormSelectorComponent } from '../ema-form-selector/ema-form-selector DotSpinnerModule, DotContentCompareModule ], - providers: [DotEmaDialogStore, DotEmaWorkflowActionsService] + providers: [DotEmaWorkflowActionsService] }) export class DotEmaDialogComponent { @ViewChild('iframe') iframe: ElementRef; @@ -70,164 +57,26 @@ export class DotEmaDialogComponent { $compareDataExists = computed(() => !!this.$compareData()); private readonly destroyRef$ = inject(DestroyRef); - private readonly store = inject(DotEmaDialogStore); + private readonly dialogService = inject(DotEditorDialogService); private readonly workflowActions = inject(DotEmaWorkflowActionsService); private readonly ngZone = inject(NgZone); private readonly dotMessageService = inject(DotMessageService); private readonly messageService = inject(MessageService); - protected readonly dialogState = toSignal(this.store.dialogState$); + protected readonly dialogState = this.dialogService.state; protected readonly dialogStatus = DialogStatus; protected get ds() { return this.dialogState(); } - /** - * - * @memberof DotEmaDialogComponent - */ - resetDialog() { - this.store.resetDialog(); - } - - /** - * Add contentlet - * - * @param {ActionPayload} actionPayload - * @memberof EditEmaEditorComponent - */ - addContentlet(actionPayload: ActionPayload): void { - this.store.addContentlet({ - containerId: actionPayload.container.identifier, - acceptTypes: actionPayload.container.acceptTypes ?? '*', - language_id: actionPayload.language_id, - actionPayload - }); - } - - /** - * Add Form - * - * @param {ActionPayload} actionPayload - * @memberof EditEmaEditorComponent - */ - addForm(payload: ActionPayload): void { - this.store.addFormContentlet(payload); - } - - /** - * Add Widget - * - * @param {ActionPayload} actionPayload - * @memberof EditEmaEditorComponent - */ - addWidget(actionPayload: ActionPayload): void { - this.store.addContentlet({ - containerId: actionPayload.container.identifier, - acceptTypes: DotCMSBaseTypesContentTypes.WIDGET, - language_id: actionPayload.language_id, - actionPayload - }); - } - - /** - * Edit contentlet - * - * @param {EditContentletPayload} contentlet - * @memberof DotEmaDialogComponent - */ - editContentlet(payload: EditContentletPayload) { - this.store.editContentlet(payload); - } - - /** - * Edits a VTL contentlet. - * - * @param {VTLFile} vtlFile - The VTL file to edit. - * @memberof DotEmaDialogComponent - */ - editVTLContentlet(vtlFile: VTLFile) { - this.store.editContentlet({ - inode: vtlFile.inode, - title: vtlFile.name - }); - } - - /** - * Translate page - * - * @param {({ page: DotPage; newLanguage: number | string })} { page, newLanguage } - * @memberof DotEmaDialogComponent - */ - translatePage({ page, newLanguage }: { page: DotPage; newLanguage: number | string }) { - this.store.translatePage({ page, newLanguage }); - } - - /** - * Edit URL Content Map Contentlet - * - * @param {DotCMSContentlet} { inode, title } - * @memberof DotEmaDialogComponent - */ - editUrlContentMapContentlet({ inode, title }: DotCMSContentlet) { - this.store.editUrlContentMapContentlet({ - inode, - title - }); - } - - /** - * Create contentlet in the edit content - * - * @param {CreateContentletAction} { url, contentType, payload } - * @memberof DotEmaDialogComponent - */ - createContentlet({ url, contentType, actionPayload }: CreateContentletAction) { - this.store.createContentlet({ - url, - contentType, - actionPayload - }); - } - - /** - * Create contentlet from palette - * - * @param {CreateFromPaletteAction} { variable, name, payload } - * @memberof DotEmaDialogComponent - */ - createContentletFromPalette({ - variable, - name, - actionPayload, - language_id - }: CreateFromPaletteAction) { - this.store.createContentletFromPalette({ - variable, - name, - actionPayload, - language_id - }); - } - - /** - * Show loading iframe - * - * @param {string} [title] - * @memberof DotEmaDialogComponent - */ - showLoadingIframe(title?: string) { - this.store.loadingIframe(title ?? ''); - } - /** * Handle workflow event * * @param {DotCMSWorkflowActionEvent} event * @memberof DotEmaDialogComponent */ - handleWorkflowEvent(event: DotCMSWorkflowActionEvent) { + private handleWorkflowEvent(event: DotCMSWorkflowActionEvent) { this.workflowActions .handleWorkflowAction(event, this.callEmbeddedFunction.bind(this)) .pipe(take(1)) @@ -257,7 +106,7 @@ export class DotEmaDialogComponent { * * @memberof DotEmaDialogComponent */ - reloadIframe() { + private reloadIframe() { this.iframe.nativeElement.contentWindow.location.reload(); } @@ -280,17 +129,6 @@ export class DotEmaDialogComponent { }); } - /** - * Open dialog on URL - * - * @param {string} url - * @param {string} title - * @memberof DotEmaDialogComponent - */ - openDialogOnUrl(url: string, title: string) { - this.store.openDialogOnURL({ url, title }); - } - protected onHide() { const event = new CustomEvent('ng-event', { detail: { @@ -299,7 +137,7 @@ export class DotEmaDialogComponent { }); this.emitAction(event); - this.resetDialog(); + this.dialogService.resetDialog(); } /** @@ -309,7 +147,7 @@ export class DotEmaDialogComponent { * @memberof DotEmaDialogComponent */ protected onIframeLoad() { - this.store.setStatus(this.dialogStatus.INIT); + this.dialogService.setStatus(this.dialogStatus.INIT); // This event is destroyed when you close the dialog fromEvent( @@ -323,7 +161,7 @@ export class DotEmaDialogComponent { switch (event.detail.name) { case NG_CUSTOM_EVENTS.DIALOG_CLOSED: { - this.store.resetDialog(); + this.dialogService.resetDialog(); break; } @@ -338,13 +176,13 @@ export class DotEmaDialogComponent { case NG_CUSTOM_EVENTS.EDIT_CONTENTLET_UPDATED: { // The edit content emits this for savings when translating a page and does not emit anything when changing the content if (this.dialogState().form.isTranslation) { - this.store.setSaved(); + this.dialogService.setSaved(); if (event.detail.payload.isMoveAction) { this.reloadIframe(); } } else { - this.store.setDirty(); + this.dialogService.setDirty(); } break; @@ -356,7 +194,7 @@ export class DotEmaDialogComponent { } case NG_CUSTOM_EVENTS.SAVE_PAGE: { - this.store.setSaved(); + this.dialogService.setSaved(); if (event.detail.payload.isMoveAction) { this.reloadIframe(); @@ -409,8 +247,10 @@ export class DotEmaDialogComponent { } private emitAction(event: CustomEvent) { - const { actionPayload, form, clientAction } = this.dialogState(); + const dialogState = this.dialogState(); + const { actionPayload, form, clientAction } = dialogState; - this.action.emit({ event, actionPayload, form, clientAction }); + const dialogAction: DialogAction = { event, actionPayload, form, clientAction }; + this.action.emit(dialogAction); } } diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/services/dot-ema-dialog.service.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/services/dot-ema-dialog.service.ts new file mode 100644 index 000000000000..6b477dfdd11e --- /dev/null +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/services/dot-ema-dialog.service.ts @@ -0,0 +1,331 @@ +import { Injectable, inject, signal } from '@angular/core'; + +import { CLIENT_ACTIONS } from '@dotcms/client'; +import { DotMessageService } from '@dotcms/data-access'; +import { DEFAULT_VARIANT_ID, DotCMSBaseTypesContentTypes } from '@dotcms/dotcms-models'; + +import { DotActionUrlService } from '../../../services/dot-action-url/dot-action-url.service'; +import { LAYOUT_URL, CONTENTLET_SELECTOR_URL } from '../../../shared/consts'; +import { DialogStatus, FormStatus } from '../../../shared/enums'; +import { + ActionPayload, + AddContentletAction, + CreateContentletAction, + CreateFromPaletteAction, + DotPage, + EditContentletPayload, + EditEmaDialogState +} from '../../../shared/models'; + +@Injectable({ + providedIn: 'root' +}) +export class DotEditorDialogService { + private readonly dotActionUrlService = inject(DotActionUrlService); + private readonly dotMessageService = inject(DotMessageService); + + readonly state = signal({ + header: '', + url: '', + type: null, + status: DialogStatus.IDLE, + form: { + status: FormStatus.PRISTINE, + isTranslation: false + }, + clientAction: CLIENT_ACTIONS.NOOP + }); + + /** + * Create a contentlet from the palette + */ + createContentletFromPalette(params: CreateFromPaletteAction): void { + const { name, variable, actionPayload, language_id = 1 } = params; + + this.dotActionUrlService.getCreateContentletUrl(variable, language_id).subscribe((url) => { + this.createContentlet({ + url, + contentType: name, + actionPayload + }); + }); + } + + /** + * Open a dialog with a specific URL + */ + openDialogOnURL({ url, title }: { url: string; title: string }): void { + this.state.update((state) => ({ + ...state, + header: title, + status: DialogStatus.LOADING, + url, + type: 'content' + })); + } + + /** + * Create a contentlet + */ + createContentlet({ url, contentType, actionPayload }: CreateContentletAction): void { + const completeURL = new URL(url, window.location.origin); + completeURL.searchParams.set('variantName', this.getVariantName()); + + this.state.update((state) => ({ + ...state, + url: completeURL.toString(), + actionPayload, + header: this.dotMessageService.get( + 'contenttypes.content.create.contenttype', + contentType + ), + status: DialogStatus.LOADING, + type: 'content' + })); + } + + /** + * Set loading state for iframe + */ + loadingIframe(title: string): void { + this.state.update((state) => ({ + ...state, + header: title, + status: DialogStatus.LOADING, + url: '', + type: 'content' + })); + } + + /** + * Edit a contentlet + */ + editContentlet({ + inode, + title, + clientAction = CLIENT_ACTIONS.NOOP, + angularCurrentPortlet + }: EditContentletPayload): void { + this.state.update((state) => ({ + ...state, + clientAction, + header: title, + status: DialogStatus.LOADING, + type: 'content', + url: this.createEditContentletUrl(inode, angularCurrentPortlet) + })); + } + + /** + * Edit a URL content map contentlet + */ + editUrlContentMapContentlet({ inode, title }: EditContentletPayload): void { + const url = this.createEditContentletUrl(inode, null) + '&isURLMap=true'; + + this.state.update((state) => ({ + ...state, + header: title, + status: DialogStatus.LOADING, + type: 'content', + url + })); + } + + /** + * Translate a page + */ + translatePage({ page, newLanguage }: { page: DotPage; newLanguage: number | string }): void { + this.state.update((state) => ({ + ...state, + header: page.title, + status: DialogStatus.LOADING, + type: 'content', + url: this.createTranslatePageUrl(page, newLanguage), + form: { + status: FormStatus.PRISTINE, + isTranslation: true + } + })); + } + + addWidget(actionPayload: ActionPayload): void { + const { container, language_id } = actionPayload; + this.openContentlet({ + containerId: container.identifier, + acceptTypes: DotCMSBaseTypesContentTypes.WIDGET, + language_id, + actionPayload + }); + } + + /** + * Add a contentlet + */ + addContentlet(actionPayload: ActionPayload): void { + const { container, language_id } = actionPayload; + this.openContentlet({ + containerId: container.identifier, + acceptTypes: container.acceptTypes ?? '*', + language_id, + actionPayload + }); + } + + /** + * Add a form contentlet + */ + addForm(actionPayload: ActionPayload): void { + this.state.update((state) => ({ + ...state, + header: this.dotMessageService.get('edit.ema.page.dialog.header.search.form'), + status: DialogStatus.LOADING, + url: null, + type: 'form', + actionPayload + })); + } + + private openContentlet(data: AddContentletAction): void { + const { actionPayload, ...contentData } = data; + this.state.update((state) => ({ + ...state, + header: this.dotMessageService.get('edit.ema.page.dialog.header.search.content'), + status: DialogStatus.LOADING, + url: this.createAddContentletUrl(contentData), + type: 'content', + actionPayload + })); + } + + /** + * Set dirty state + */ + setDirty(): void { + this.state.update((state) => ({ + ...state, + form: { + ...state.form, + status: FormStatus.DIRTY + } + })); + } + + /** + * Set saved state + */ + setSaved(): void { + this.state.update((state) => ({ + ...state, + form: { + ...state.form, + status: FormStatus.SAVED + } + })); + } + + /** + * Reset dialog state + */ + resetDialog(): void { + this.state.update((state) => ({ + ...state, + url: '', + header: '', + status: DialogStatus.IDLE, + type: null, + actionPayload: undefined, + form: { + status: FormStatus.PRISTINE, + isTranslation: false + }, + clientAction: CLIENT_ACTIONS.NOOP + })); + } + + /** + * Set dialog status + */ + setStatus(status: DialogStatus): void { + this.state.update((state) => ({ + ...state, + status + })); + } + + /** + * Create the url to edit a contentlet + */ + private createEditContentletUrl(inode: string, angularCurrentPortlet: string): string { + const queryParams = new URLSearchParams({ + p_p_id: 'content', + p_p_action: '1', + p_p_state: 'maximized', + p_p_mode: 'view', + _content_struts_action: '/ext/contentlet/edit_contentlet', + _content_cmd: 'edit', + inode: inode, + angularCurrentPortlet: angularCurrentPortlet, + variantName: this.getVariantName() + }); + + return `${LAYOUT_URL}?${queryParams.toString()}`; + } + + /** + * Create the url to add a contentlet + */ + private createAddContentletUrl({ + containerId, + acceptTypes, + language_id + }: { + containerId: string; + acceptTypes: string; + language_id: string; + }): string { + const queryParams = new URLSearchParams({ + ng: 'true', + container_id: containerId, + add: acceptTypes, + language_id, + variantName: this.getVariantName() + }); + + return `${CONTENTLET_SELECTOR_URL}?${queryParams.toString()}`; + } + + /** + * Create the url to translate a page + */ + private createTranslatePageUrl(page: DotPage, newLanguage: number | string): string { + const { working, workingInode, inode } = page; + const pageInode = working ? workingInode : inode; + const queryParams = new URLSearchParams({ + p_p_id: 'content', + p_p_action: '1', + p_p_state: 'maximized', + angularCurrentPortlet: 'edit-page', + _content_sibbling: pageInode, + _content_cmd: 'edit', + p_p_mode: 'view', + _content_sibblingStructure: pageInode, + _content_struts_action: '/ext/contentlet/edit_contentlet', + inode: '', + lang: newLanguage.toString(), + populateaccept: 'true', + reuseLastLang: 'true', + variantName: this.getVariantName() + }); + + return `${LAYOUT_URL}?${queryParams.toString()}`; + } + + /** + * Get the variant name from the url + */ + private getVariantName(): string { + const url = new URL(window.location.href); + + return url.searchParams.get('variantName') || DEFAULT_VARIANT_ID; + } +} diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/services/dot-ema-dialog.store.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/services/dot-ema-dialog.store.spec.ts new file mode 100644 index 000000000000..5c9a012ff9d1 --- /dev/null +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/services/dot-ema-dialog.store.spec.ts @@ -0,0 +1,468 @@ +// import { expect, it, describe } from '@jest/globals'; +// import { SpectatorService, createServiceFactory } from '@ngneat/spectator/jest'; +// import { of } from 'rxjs'; + +// import { signal } from '@angular/core'; + +// import { CLIENT_ACTIONS } from '@dotcms/client'; +// import { DotMessageService } from '@dotcms/data-access'; +// import { MockDotMessageService } from '@dotcms/utils-testing'; + +// import { DotEmaDialogStore } from '../store/dot-ema-dialog.store'; + +// import { DotActionUrlService } from '../../../services/dot-action-url/dot-action-url.service'; +// import { LAYOUT_URL } from '../../../shared/consts'; +// import { DialogStatus, FormStatus } from '../../../shared/enums'; +// import { PAYLOAD_MOCK } from '../../../shared/mocks'; +// import { DotPage } from '../../../shared/models'; +// import { UVEStore } from '../../../store/dot-uve.store'; + +// const TEST_VARIANT = 'my-test-variant'; + +// describe('DotEmaDialogStoreService', () => { +// let spectator: SpectatorService; + +// const createService = createServiceFactory({ +// service: DotEmaDialogStore, +// mocks: [DotActionUrlService], +// providers: [ +// { +// provide: UVEStore, +// useValue: { +// pageParams: signal({ +// variantName: TEST_VARIANT // Is the only thing we need to test the component +// }) +// } +// }, + +// { +// provide: DotMessageService, +// useValue: new MockDotMessageService({ +// 'edit.ema.page.dialog.header.search.content': 'Search Content', +// 'edit.ema.page.dialog.header.search.form': 'Search Form', +// 'contenttypes.content.create.contenttype': 'Create {0}' +// }) +// } +// ] +// }); + +// beforeEach(() => { +// spectator = createService(); +// }); + +// it('should update dialog status', (done) => { +// spectator.service.setStatus(DialogStatus.LOADING); + +// spectator.service.dialogState$.subscribe((state) => { +// expect(state).toEqual({ +// url: '', +// header: '', +// type: null, +// status: DialogStatus.LOADING, +// form: { +// status: FormStatus.PRISTINE, +// isTranslation: false +// }, +// clientAction: CLIENT_ACTIONS.NOOP +// }); +// done(); +// }); +// }); + +// it("should set the form state to 'DIRTY'", (done) => { +// spectator.service.setDirty(); + +// spectator.service.dialogState$.subscribe((state) => { +// expect(state.form.status).toBe(FormStatus.DIRTY); +// done(); +// }); +// }); + +// it("should set the form state to 'SAVED'", (done) => { +// spectator.service.setSaved(); + +// spectator.service.dialogState$.subscribe((state) => { +// expect(state.form.status).toBe(FormStatus.SAVED); +// done(); +// }); +// }); + +// it('should reset iframe properties', (done) => { +// spectator.service.setStatus(DialogStatus.LOADING); + +// spectator.service.resetDialog(); + +// spectator.service.dialogState$.subscribe((state) => { +// expect(state).toEqual({ +// url: '', +// status: DialogStatus.IDLE, +// header: '', +// type: null, +// actionPayload: undefined, +// form: { +// status: FormStatus.PRISTINE, +// isTranslation: false +// }, +// clientAction: CLIENT_ACTIONS.NOOP +// }); +// done(); +// }); +// }); + +// it('should initialize with edit iframe properties', (done) => { +// spectator.service.editContentlet({ +// inode: '123', +// title: 'test' +// }); + +// const queryParams = new URLSearchParams({ +// p_p_id: 'content', +// p_p_action: '1', +// p_p_state: 'maximized', +// p_p_mode: 'view', +// _content_struts_action: '/ext/contentlet/edit_contentlet', +// _content_cmd: 'edit', +// inode: '123', +// angularCurrentPortlet: 'undefined', +// variantName: TEST_VARIANT +// }); + +// spectator.service.dialogState$.subscribe((state) => { +// expect(state).toEqual({ +// url: LAYOUT_URL + '?' + queryParams.toString(), +// status: DialogStatus.LOADING, +// header: 'test', +// type: 'content', +// form: { +// status: FormStatus.PRISTINE, +// isTranslation: false +// }, +// clientAction: CLIENT_ACTIONS.NOOP +// }); +// done(); +// }); +// }); + +// it('should initialize with edit iframe properties and with clientAction', (done) => { +// spectator.service.editContentlet({ +// inode: '123', +// title: 'test', +// clientAction: CLIENT_ACTIONS.EDIT_CONTENTLET +// }); + +// const queryParams = new URLSearchParams({ +// p_p_id: 'content', +// p_p_action: '1', +// p_p_state: 'maximized', +// p_p_mode: 'view', +// _content_struts_action: '/ext/contentlet/edit_contentlet', +// _content_cmd: 'edit', +// inode: '123', +// angularCurrentPortlet: 'undefined', +// variantName: TEST_VARIANT +// }); + +// spectator.service.dialogState$.subscribe((state) => { +// expect(state).toEqual({ +// url: LAYOUT_URL + '?' + queryParams.toString(), +// status: DialogStatus.LOADING, +// header: 'test', +// type: 'content', +// form: { +// status: FormStatus.PRISTINE, +// isTranslation: false +// }, +// clientAction: CLIENT_ACTIONS.EDIT_CONTENTLET +// }); +// done(); +// }); +// }); + +// it('should initialize with edit iframe properties', (done) => { +// spectator.service.editUrlContentMapContentlet({ +// inode: '123', +// title: 'test' +// }); + +// const queryParams = new URLSearchParams({ +// p_p_id: 'content', +// p_p_action: '1', +// p_p_state: 'maximized', +// p_p_mode: 'view', +// _content_struts_action: '/ext/contentlet/edit_contentlet', +// _content_cmd: 'edit', +// inode: '123', +// angularCurrentPortlet: null, +// variantName: TEST_VARIANT +// }); + +// spectator.service.dialogState$.subscribe((state) => { +// expect(state).toEqual({ +// url: LAYOUT_URL + '?' + queryParams.toString() + '&isURLMap=true', +// status: DialogStatus.LOADING, +// header: 'test', +// type: 'content', +// form: { +// status: FormStatus.PRISTINE, +// isTranslation: false +// }, +// clientAction: CLIENT_ACTIONS.NOOP +// }); +// done(); +// }); +// }); + +// it('should initialize with addA iframe properties', (done) => { +// spectator.service.addContentlet({ +// containerId: '1234', +// acceptTypes: 'test', +// language_id: '1', +// actionPayload: PAYLOAD_MOCK +// }); + +// spectator.service.dialogState$.subscribe((state) => { +// expect(state).toEqual({ +// url: +// '/html/ng-contentlet-selector.jsp?ng=true&container_id=1234&add=test&language_id=1&' + +// new URLSearchParams({ variantName: TEST_VARIANT }).toString(), +// header: 'Search Content', +// type: 'content', +// status: DialogStatus.LOADING, +// actionPayload: PAYLOAD_MOCK, +// form: { +// status: FormStatus.PRISTINE, +// isTranslation: false +// }, +// clientAction: CLIENT_ACTIONS.NOOP +// }); +// done(); +// }); +// }); + +// it('should initialize with Form Iframe properties', (done) => { +// spectator.service.addFormContentlet(PAYLOAD_MOCK); + +// spectator.service.dialogState$.subscribe((state) => { +// expect(state).toEqual({ +// header: 'Search Form', +// status: DialogStatus.LOADING, +// url: null, +// type: 'form', +// actionPayload: PAYLOAD_MOCK, +// form: { +// status: FormStatus.PRISTINE, +// isTranslation: false +// }, +// clientAction: CLIENT_ACTIONS.NOOP +// }); +// done(); +// }); +// }); + +// it('should initialize with create iframe properties', (done) => { +// spectator.service.createContentlet({ +// contentType: 'test', +// url: 'some/really/long/url', +// actionPayload: PAYLOAD_MOCK +// }); + +// spectator.service.dialogState$.subscribe((state) => { +// expect(state).toEqual({ +// url: +// 'http://localhost/some/really/long/url?' + +// new URLSearchParams({ variantName: TEST_VARIANT }).toString(), +// status: DialogStatus.LOADING, +// header: 'Create test', +// type: 'content', +// actionPayload: PAYLOAD_MOCK, +// form: { +// status: FormStatus.PRISTINE, +// isTranslation: false +// }, +// clientAction: CLIENT_ACTIONS.NOOP +// }); +// done(); +// }); +// }); + +// it('should update dialog state', (done) => { +// spectator.service.createContentlet({ +// url: 'some/really/long/url', +// contentType: 'Blog Posts', +// actionPayload: PAYLOAD_MOCK +// }); + +// spectator.service.dialogState$.subscribe((state) => { +// expect(state.header).toBe('Create Blog Posts'); +// expect(state.status).toBe(DialogStatus.LOADING); +// expect(state.url).toBe( +// 'http://localhost/some/really/long/url?' + +// new URLSearchParams({ variantName: TEST_VARIANT }).toString() +// ); +// expect(state.type).toBe('content'); +// expect(state.actionPayload).toEqual(PAYLOAD_MOCK); +// done(); +// }); +// }); + +// it('should update state to show dialog for create content from palette', (done) => { +// const dotActionUrlService = spectator.inject(DotActionUrlService); + +// dotActionUrlService.getCreateContentletUrl.andReturn(of('https://demo.dotcms.com/jsp.jsp')); + +// spectator.service.createContentletFromPalette({ +// variable: 'blogPost', +// name: 'Blog', +// actionPayload: PAYLOAD_MOCK, +// language_id: 2 +// }); + +// spectator.service.dialogState$.subscribe((state) => { +// expect(state.header).toBe('Create Blog'); +// expect(state.status).toBe(DialogStatus.LOADING); + +// expect(state.url).toBe( +// 'https://demo.dotcms.com/jsp.jsp?' + +// new URLSearchParams({ variantName: TEST_VARIANT }).toString() +// ); +// expect(state.type).toBe('content'); +// expect(state.actionPayload).toEqual(PAYLOAD_MOCK); +// done(); +// }); + +// expect(dotActionUrlService.getCreateContentletUrl).toHaveBeenCalledWith('blogPost', 2); +// }); + +// it('should initialize with loading iframe properties', (done) => { +// spectator.service.loadingIframe('test'); + +// spectator.service.dialogState$.subscribe((state) => { +// expect(state).toEqual({ +// url: '', +// status: DialogStatus.LOADING, +// header: 'test', +// type: 'content', +// form: { +// status: FormStatus.PRISTINE, +// isTranslation: false +// }, +// clientAction: CLIENT_ACTIONS.NOOP +// }); +// done(); +// }); +// }); + +// it('should update the state to show dialog with a specific URL', (done) => { +// spectator.service.openDialogOnURL({ +// url: 'https://demo.dotcms.com/jsp.jsp', +// title: 'test' +// }); + +// spectator.service.dialogState$.subscribe((state) => { +// expect(state).toEqual({ +// url: 'https://demo.dotcms.com/jsp.jsp', +// status: DialogStatus.LOADING, +// header: 'test', +// type: 'content', +// form: { +// status: FormStatus.PRISTINE, +// isTranslation: false +// }, +// clientAction: CLIENT_ACTIONS.NOOP +// }); +// done(); +// }); +// }); + +// describe('Dialog for translation', () => { +// it('should update the state to show dialog for a translation', () => { +// spectator.service.translatePage({ +// page: { +// inode: '123', +// liveInode: '1234', +// stInode: '12345', +// live: true, +// title: 'test' +// } as DotPage, +// newLanguage: 2 +// }); + +// const queryParams = new URLSearchParams({ +// p_p_id: 'content', +// p_p_action: '1', +// p_p_state: 'maximized', +// angularCurrentPortlet: 'edit-page', +// _content_sibbling: '123', +// _content_cmd: 'edit', +// p_p_mode: 'view', +// _content_sibblingStructure: '123', +// _content_struts_action: '/ext/contentlet/edit_contentlet', +// inode: '', +// lang: '2', +// populateaccept: 'true', +// reuseLastLang: 'true', +// variantName: TEST_VARIANT +// }); + +// spectator.service.dialogState$.subscribe((state) => { +// expect(state).toEqual({ +// url: LAYOUT_URL + '?' + queryParams.toString(), +// status: DialogStatus.LOADING, +// header: 'test', +// type: 'content', +// form: { +// status: FormStatus.PRISTINE, +// isTranslation: true +// }, +// clientAction: CLIENT_ACTIONS.NOOP +// }); +// }); +// }); + +// it('should update the state to show dialog for a translation with working inode', () => { +// spectator.service.translatePage({ +// page: { +// inode: '123', +// liveInode: '1234', +// stInode: '12345', +// live: true, +// title: 'test', +// working: true, +// workingInode: '56789' +// } as DotPage, +// newLanguage: 2 +// }); + +// const queryParams = new URLSearchParams({ +// p_p_id: 'content', +// p_p_action: '1', +// p_p_state: 'maximized', +// angularCurrentPortlet: 'edit-page', +// _content_sibbling: '56789', +// _content_cmd: 'edit', +// p_p_mode: 'view', +// _content_sibblingStructure: '56789', +// _content_struts_action: '/ext/contentlet/edit_contentlet', +// inode: '', +// lang: '2', +// populateaccept: 'true', +// reuseLastLang: 'true', +// variantName: TEST_VARIANT +// }); + +// spectator.service.dialogState$.subscribe((state) => { +// expect(state).toEqual({ +// url: LAYOUT_URL + '?' + queryParams.toString(), +// status: DialogStatus.LOADING, +// header: 'test', +// type: 'content', +// form: { +// status: FormStatus.PRISTINE, +// isTranslation: true +// }, +// clientAction: CLIENT_ACTIONS.NOOP +// }); +// }); +// }); +// }); +// }); diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/store/dot-ema-dialog.store.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/store/dot-ema-dialog.store.spec.ts deleted file mode 100644 index 16a9d3a66aa4..000000000000 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/store/dot-ema-dialog.store.spec.ts +++ /dev/null @@ -1,468 +0,0 @@ -import { expect, it, describe } from '@jest/globals'; -import { SpectatorService, createServiceFactory } from '@ngneat/spectator/jest'; -import { of } from 'rxjs'; - -import { signal } from '@angular/core'; - -import { CLIENT_ACTIONS } from '@dotcms/client'; -import { DotMessageService } from '@dotcms/data-access'; -import { MockDotMessageService } from '@dotcms/utils-testing'; - -import { DotEmaDialogStore } from './dot-ema-dialog.store'; - -import { DotActionUrlService } from '../../../services/dot-action-url/dot-action-url.service'; -import { LAYOUT_URL } from '../../../shared/consts'; -import { DialogStatus, FormStatus } from '../../../shared/enums'; -import { PAYLOAD_MOCK } from '../../../shared/mocks'; -import { DotPage } from '../../../shared/models'; -import { UVEStore } from '../../../store/dot-uve.store'; - -const TEST_VARIANT = 'my-test-variant'; - -describe('DotEmaDialogStoreService', () => { - let spectator: SpectatorService; - - const createService = createServiceFactory({ - service: DotEmaDialogStore, - mocks: [DotActionUrlService], - providers: [ - { - provide: UVEStore, - useValue: { - pageParams: signal({ - variantName: TEST_VARIANT // Is the only thing we need to test the component - }) - } - }, - - { - provide: DotMessageService, - useValue: new MockDotMessageService({ - 'edit.ema.page.dialog.header.search.content': 'Search Content', - 'edit.ema.page.dialog.header.search.form': 'Search Form', - 'contenttypes.content.create.contenttype': 'Create {0}' - }) - } - ] - }); - - beforeEach(() => { - spectator = createService(); - }); - - it('should update dialog status', (done) => { - spectator.service.setStatus(DialogStatus.LOADING); - - spectator.service.dialogState$.subscribe((state) => { - expect(state).toEqual({ - url: '', - header: '', - type: null, - status: DialogStatus.LOADING, - form: { - status: FormStatus.PRISTINE, - isTranslation: false - }, - clientAction: CLIENT_ACTIONS.NOOP - }); - done(); - }); - }); - - it("should set the form state to 'DIRTY'", (done) => { - spectator.service.setDirty(); - - spectator.service.dialogState$.subscribe((state) => { - expect(state.form.status).toBe(FormStatus.DIRTY); - done(); - }); - }); - - it("should set the form state to 'SAVED'", (done) => { - spectator.service.setSaved(); - - spectator.service.dialogState$.subscribe((state) => { - expect(state.form.status).toBe(FormStatus.SAVED); - done(); - }); - }); - - it('should reset iframe properties', (done) => { - spectator.service.setStatus(DialogStatus.LOADING); - - spectator.service.resetDialog(); - - spectator.service.dialogState$.subscribe((state) => { - expect(state).toEqual({ - url: '', - status: DialogStatus.IDLE, - header: '', - type: null, - actionPayload: undefined, - form: { - status: FormStatus.PRISTINE, - isTranslation: false - }, - clientAction: CLIENT_ACTIONS.NOOP - }); - done(); - }); - }); - - it('should initialize with edit iframe properties', (done) => { - spectator.service.editContentlet({ - inode: '123', - title: 'test' - }); - - const queryParams = new URLSearchParams({ - p_p_id: 'content', - p_p_action: '1', - p_p_state: 'maximized', - p_p_mode: 'view', - _content_struts_action: '/ext/contentlet/edit_contentlet', - _content_cmd: 'edit', - inode: '123', - angularCurrentPortlet: 'undefined', - variantName: TEST_VARIANT - }); - - spectator.service.dialogState$.subscribe((state) => { - expect(state).toEqual({ - url: LAYOUT_URL + '?' + queryParams.toString(), - status: DialogStatus.LOADING, - header: 'test', - type: 'content', - form: { - status: FormStatus.PRISTINE, - isTranslation: false - }, - clientAction: CLIENT_ACTIONS.NOOP - }); - done(); - }); - }); - - it('should initialize with edit iframe properties and with clientAction', (done) => { - spectator.service.editContentlet({ - inode: '123', - title: 'test', - clientAction: CLIENT_ACTIONS.EDIT_CONTENTLET - }); - - const queryParams = new URLSearchParams({ - p_p_id: 'content', - p_p_action: '1', - p_p_state: 'maximized', - p_p_mode: 'view', - _content_struts_action: '/ext/contentlet/edit_contentlet', - _content_cmd: 'edit', - inode: '123', - angularCurrentPortlet: 'undefined', - variantName: TEST_VARIANT - }); - - spectator.service.dialogState$.subscribe((state) => { - expect(state).toEqual({ - url: LAYOUT_URL + '?' + queryParams.toString(), - status: DialogStatus.LOADING, - header: 'test', - type: 'content', - form: { - status: FormStatus.PRISTINE, - isTranslation: false - }, - clientAction: CLIENT_ACTIONS.EDIT_CONTENTLET - }); - done(); - }); - }); - - it('should initialize with edit iframe properties', (done) => { - spectator.service.editUrlContentMapContentlet({ - inode: '123', - title: 'test' - }); - - const queryParams = new URLSearchParams({ - p_p_id: 'content', - p_p_action: '1', - p_p_state: 'maximized', - p_p_mode: 'view', - _content_struts_action: '/ext/contentlet/edit_contentlet', - _content_cmd: 'edit', - inode: '123', - angularCurrentPortlet: null, - variantName: TEST_VARIANT - }); - - spectator.service.dialogState$.subscribe((state) => { - expect(state).toEqual({ - url: LAYOUT_URL + '?' + queryParams.toString() + '&isURLMap=true', - status: DialogStatus.LOADING, - header: 'test', - type: 'content', - form: { - status: FormStatus.PRISTINE, - isTranslation: false - }, - clientAction: CLIENT_ACTIONS.NOOP - }); - done(); - }); - }); - - it('should initialize with addA iframe properties', (done) => { - spectator.service.addContentlet({ - containerId: '1234', - acceptTypes: 'test', - language_id: '1', - actionPayload: PAYLOAD_MOCK - }); - - spectator.service.dialogState$.subscribe((state) => { - expect(state).toEqual({ - url: - '/html/ng-contentlet-selector.jsp?ng=true&container_id=1234&add=test&language_id=1&' + - new URLSearchParams({ variantName: TEST_VARIANT }).toString(), - header: 'Search Content', - type: 'content', - status: DialogStatus.LOADING, - actionPayload: PAYLOAD_MOCK, - form: { - status: FormStatus.PRISTINE, - isTranslation: false - }, - clientAction: CLIENT_ACTIONS.NOOP - }); - done(); - }); - }); - - it('should initialize with Form Iframe properties', (done) => { - spectator.service.addFormContentlet(PAYLOAD_MOCK); - - spectator.service.dialogState$.subscribe((state) => { - expect(state).toEqual({ - header: 'Search Form', - status: DialogStatus.LOADING, - url: null, - type: 'form', - actionPayload: PAYLOAD_MOCK, - form: { - status: FormStatus.PRISTINE, - isTranslation: false - }, - clientAction: CLIENT_ACTIONS.NOOP - }); - done(); - }); - }); - - it('should initialize with create iframe properties', (done) => { - spectator.service.createContentlet({ - contentType: 'test', - url: 'some/really/long/url', - actionPayload: PAYLOAD_MOCK - }); - - spectator.service.dialogState$.subscribe((state) => { - expect(state).toEqual({ - url: - 'http://localhost/some/really/long/url?' + - new URLSearchParams({ variantName: TEST_VARIANT }).toString(), - status: DialogStatus.LOADING, - header: 'Create test', - type: 'content', - actionPayload: PAYLOAD_MOCK, - form: { - status: FormStatus.PRISTINE, - isTranslation: false - }, - clientAction: CLIENT_ACTIONS.NOOP - }); - done(); - }); - }); - - it('should update dialog state', (done) => { - spectator.service.createContentlet({ - url: 'some/really/long/url', - contentType: 'Blog Posts', - actionPayload: PAYLOAD_MOCK - }); - - spectator.service.dialogState$.subscribe((state) => { - expect(state.header).toBe('Create Blog Posts'); - expect(state.status).toBe(DialogStatus.LOADING); - expect(state.url).toBe( - 'http://localhost/some/really/long/url?' + - new URLSearchParams({ variantName: TEST_VARIANT }).toString() - ); - expect(state.type).toBe('content'); - expect(state.actionPayload).toEqual(PAYLOAD_MOCK); - done(); - }); - }); - - it('should update state to show dialog for create content from palette', (done) => { - const dotActionUrlService = spectator.inject(DotActionUrlService); - - dotActionUrlService.getCreateContentletUrl.andReturn(of('https://demo.dotcms.com/jsp.jsp')); - - spectator.service.createContentletFromPalette({ - variable: 'blogPost', - name: 'Blog', - actionPayload: PAYLOAD_MOCK, - language_id: 2 - }); - - spectator.service.dialogState$.subscribe((state) => { - expect(state.header).toBe('Create Blog'); - expect(state.status).toBe(DialogStatus.LOADING); - - expect(state.url).toBe( - 'https://demo.dotcms.com/jsp.jsp?' + - new URLSearchParams({ variantName: TEST_VARIANT }).toString() - ); - expect(state.type).toBe('content'); - expect(state.actionPayload).toEqual(PAYLOAD_MOCK); - done(); - }); - - expect(dotActionUrlService.getCreateContentletUrl).toHaveBeenCalledWith('blogPost', 2); - }); - - it('should initialize with loading iframe properties', (done) => { - spectator.service.loadingIframe('test'); - - spectator.service.dialogState$.subscribe((state) => { - expect(state).toEqual({ - url: '', - status: DialogStatus.LOADING, - header: 'test', - type: 'content', - form: { - status: FormStatus.PRISTINE, - isTranslation: false - }, - clientAction: CLIENT_ACTIONS.NOOP - }); - done(); - }); - }); - - it('should update the state to show dialog with a specific URL', (done) => { - spectator.service.openDialogOnURL({ - url: 'https://demo.dotcms.com/jsp.jsp', - title: 'test' - }); - - spectator.service.dialogState$.subscribe((state) => { - expect(state).toEqual({ - url: 'https://demo.dotcms.com/jsp.jsp', - status: DialogStatus.LOADING, - header: 'test', - type: 'content', - form: { - status: FormStatus.PRISTINE, - isTranslation: false - }, - clientAction: CLIENT_ACTIONS.NOOP - }); - done(); - }); - }); - - describe('Dialog for translation', () => { - it('should update the state to show dialog for a translation', () => { - spectator.service.translatePage({ - page: { - inode: '123', - liveInode: '1234', - stInode: '12345', - live: true, - title: 'test' - } as DotPage, - newLanguage: 2 - }); - - const queryParams = new URLSearchParams({ - p_p_id: 'content', - p_p_action: '1', - p_p_state: 'maximized', - angularCurrentPortlet: 'edit-page', - _content_sibbling: '123', - _content_cmd: 'edit', - p_p_mode: 'view', - _content_sibblingStructure: '123', - _content_struts_action: '/ext/contentlet/edit_contentlet', - inode: '', - lang: '2', - populateaccept: 'true', - reuseLastLang: 'true', - variantName: TEST_VARIANT - }); - - spectator.service.dialogState$.subscribe((state) => { - expect(state).toEqual({ - url: LAYOUT_URL + '?' + queryParams.toString(), - status: DialogStatus.LOADING, - header: 'test', - type: 'content', - form: { - status: FormStatus.PRISTINE, - isTranslation: true - }, - clientAction: CLIENT_ACTIONS.NOOP - }); - }); - }); - - it('should update the state to show dialog for a translation with working inode', () => { - spectator.service.translatePage({ - page: { - inode: '123', - liveInode: '1234', - stInode: '12345', - live: true, - title: 'test', - working: true, - workingInode: '56789' - } as DotPage, - newLanguage: 2 - }); - - const queryParams = new URLSearchParams({ - p_p_id: 'content', - p_p_action: '1', - p_p_state: 'maximized', - angularCurrentPortlet: 'edit-page', - _content_sibbling: '56789', - _content_cmd: 'edit', - p_p_mode: 'view', - _content_sibblingStructure: '56789', - _content_struts_action: '/ext/contentlet/edit_contentlet', - inode: '', - lang: '2', - populateaccept: 'true', - reuseLastLang: 'true', - variantName: TEST_VARIANT - }); - - spectator.service.dialogState$.subscribe((state) => { - expect(state).toEqual({ - url: LAYOUT_URL + '?' + queryParams.toString(), - status: DialogStatus.LOADING, - header: 'test', - type: 'content', - form: { - status: FormStatus.PRISTINE, - isTranslation: true - }, - clientAction: CLIENT_ACTIONS.NOOP - }); - }); - }); - }); -}); diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/store/dot-ema-dialog.store.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/store/dot-ema-dialog.store.ts deleted file mode 100644 index 5e2914b42f5c..000000000000 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/components/dot-ema-dialog/store/dot-ema-dialog.store.ts +++ /dev/null @@ -1,373 +0,0 @@ -import { ComponentStore } from '@ngrx/component-store'; -import { tapResponse } from '@ngrx/operators'; -import { Observable } from 'rxjs'; - -import { Injectable, inject } from '@angular/core'; - -import { switchMap } from 'rxjs/operators'; - -import { CLIENT_ACTIONS } from '@dotcms/client'; -import { DotMessageService } from '@dotcms/data-access'; - -import { DotActionUrlService } from '../../../services/dot-action-url/dot-action-url.service'; -import { LAYOUT_URL, CONTENTLET_SELECTOR_URL } from '../../../shared/consts'; -import { DialogStatus, FormStatus } from '../../../shared/enums'; -import { - ActionPayload, - AddContentletAction, - CreateContentletAction, - CreateFromPaletteAction, - DotPage, - EditContentletPayload, - EditEmaDialogState -} from '../../../shared/models'; -import { UVEStore } from '../../../store/dot-uve.store'; - -@Injectable() -export class DotEmaDialogStore extends ComponentStore { - constructor() { - super({ - header: '', - url: '', - type: null, - status: DialogStatus.IDLE, - form: { - status: FormStatus.PRISTINE, - isTranslation: false - }, - clientAction: CLIENT_ACTIONS.NOOP - }); - } - - private dotActionUrlService = inject(DotActionUrlService); - - private dotMessageService = inject(DotMessageService); - - private uveStore = inject(UVEStore); - - readonly dialogState$ = this.select((state) => state); - - /** - * Create a contentlet from the palette - * - * @memberof EditEmaStore - */ - readonly createContentletFromPalette = this.effect( - (contentTypeVariable$: Observable) => { - return contentTypeVariable$.pipe( - switchMap(({ name, variable, actionPayload, language_id = 1 }) => { - return this.dotActionUrlService - .getCreateContentletUrl(variable, language_id) - .pipe( - tapResponse( - (url) => { - this.createContentlet({ - url, - contentType: name, - actionPayload - }); - }, - (e) => { - console.error(e); - } - ) - ); - }) - ); - } - ); - - /** - * This method is called when we need to open a dialog with a specific URL - * - * @memberof DotEmaDialogStore - */ - readonly openDialogOnURL = this.updater( - (state, { url, title }: { url: string; title: string }) => { - return { - ...state, - header: title, - status: DialogStatus.LOADING, - url, - type: 'content' - }; - } - ); - - /** - * This method is called when the user clicks in the + button in the jsp dialog or drag a contentType from the palette - * - * @memberof DotEmaDialogStore - */ - readonly createContentlet = this.updater( - (state, { url, contentType, actionPayload }: CreateContentletAction) => { - const completeURL = new URL(url, window.location.origin); - - completeURL.searchParams.set('variantName', this.uveStore.pageParams().variantName); - - return { - ...state, - url: completeURL.toString(), - actionPayload, - header: this.dotMessageService.get( - 'contenttypes.content.create.contenttype', - contentType - ), - status: DialogStatus.LOADING, - type: 'content' - }; - } - ); - - /** - * This method is called when the user clicks on the edit button - * - * @memberof DotEmaDialogStore - */ - readonly loadingIframe = this.updater((state, title: string) => { - return { - ...state, - header: title, - status: DialogStatus.LOADING, - url: '', - type: 'content' - }; - }); - - /** - * This method is called when the user clicks on the edit button - * - * @memberof DotEmaDialogStore - */ - readonly editContentlet = this.updater( - ( - state, - { - inode, - title, - clientAction = CLIENT_ACTIONS.NOOP, - angularCurrentPortlet - }: EditContentletPayload - ) => { - return { - ...state, - clientAction, //In case it is undefined we set it to "noop" - header: title, - status: DialogStatus.LOADING, - type: 'content', - url: this.createEditContentletUrl(inode, angularCurrentPortlet) - }; - } - ); - - /** - * This method is called when the user clicks on the edit URL Content Map button - * - * @memberof DotEmaDialogStore - */ - readonly editUrlContentMapContentlet = this.updater( - (state, { inode, title }: EditContentletPayload) => { - const url = this.createEditContentletUrl(inode, null) + '&isURLMap=true'; - - return { - ...state, - header: title, - status: DialogStatus.LOADING, - type: 'content', - url - }; - } - ); - - /** - * This method is called when the user tries to navigate to a different language - * - * @memberof DotEmaDialogStore - */ - readonly translatePage = this.updater( - (state, { page, newLanguage }: { page: DotPage; newLanguage: number | string }) => { - return { - ...state, - header: page.title, - status: DialogStatus.LOADING, - type: 'content', - url: this.createTranslatePageUrl(page, newLanguage), - form: { - status: FormStatus.PRISTINE, - isTranslation: true - } - }; - } - ); - - /** - * This method is called when the user clicks on the [+ add] button - * - * @memberof DotEmaDialogStore - */ - readonly addContentlet = this.updater((state, data: AddContentletAction) => { - return { - ...state, - header: this.dotMessageService.get('edit.ema.page.dialog.header.search.content'), - status: DialogStatus.LOADING, - url: this.createAddContentletUrl(data), - type: 'content', - actionPayload: data.actionPayload - }; - }); - - /** - * This method is called when the user make changes in the form - * - * @memberof DotEmaDialogStore - */ - readonly setDirty = this.updater((state) => { - return { - ...state, - form: { - ...state.form, - status: FormStatus.DIRTY - } - }; - }); - - /** - * This method is called when the user save the form - * - * @memberof DotEmaDialogStore - */ - readonly setSaved = this.updater((state) => { - return { - ...state, - form: { - ...state.form, - status: FormStatus.SAVED - } - }; - }); - - /** - * This method is called when the user clicks on the [+ add] button and selects form as content type - * - * @memberof DotEmaDialogStore - */ - readonly addFormContentlet = this.updater((state, payload: ActionPayload) => { - return { - ...state, - header: this.dotMessageService.get('edit.ema.page.dialog.header.search.form'), - status: DialogStatus.LOADING, - url: null, - type: 'form', - actionPayload: payload - }; - }); - - /** - * This method resets the properties that are being used in for the dialog - * - * @memberof DotEmaDialogStore - */ - readonly resetDialog = this.updater((state) => { - return { - ...state, - url: '', - header: '', - status: DialogStatus.IDLE, - type: null, - actionPayload: undefined, - form: { - status: FormStatus.PRISTINE, - isTranslation: false - }, - clientAction: CLIENT_ACTIONS.NOOP - }; - }); - - /** - * This method sets the loading property - * - * @memberof DotEmaDialogStore - */ - readonly setStatus = this.updater((state, status: DialogStatus) => { - return { - ...state, - status - }; - }); - - /** - * Create the url to edit a contentlet - * - * @private - * @param {string} inode - * @param angularCurrentPortlet - * @return {*} - * @memberof DotEmaComponent - */ - private createEditContentletUrl(inode: string, angularCurrentPortlet: string): string { - const queryParams = new URLSearchParams({ - p_p_id: 'content', - p_p_action: '1', - p_p_state: 'maximized', - p_p_mode: 'view', - _content_struts_action: '/ext/contentlet/edit_contentlet', - _content_cmd: 'edit', - inode: inode, - angularCurrentPortlet: angularCurrentPortlet, - variantName: this.uveStore.pageParams().variantName - }); - - return `${LAYOUT_URL}?${queryParams.toString()}`; - } - - /** - * Create the url to add a contentlet - * - * @private - * @param {{containerID: string, acceptTypes: string}} {containerID, acceptTypes} - * @return {*} {string} - * @memberof EditEmaStore - */ - private createAddContentletUrl({ - containerId, - acceptTypes, - language_id - }: { - containerId: string; - acceptTypes: string; - language_id: string; - }): string { - const queryParams = new URLSearchParams({ - ng: 'true', - container_id: containerId, - add: acceptTypes, - language_id, - variantName: this.uveStore.pageParams().variantName - }); - - return `${CONTENTLET_SELECTOR_URL}?${queryParams.toString()}`; - } - - private createTranslatePageUrl(page: DotPage, newLanguage: number | string) { - const { working, workingInode, inode } = page; - const pageInode = working ? workingInode : inode; - const queryParams = new URLSearchParams({ - p_p_id: 'content', - p_p_action: '1', - p_p_state: 'maximized', - angularCurrentPortlet: 'edit-page', - _content_sibbling: pageInode, - _content_cmd: 'edit', - p_p_mode: 'view', - _content_sibblingStructure: pageInode, - _content_struts_action: '/ext/contentlet/edit_contentlet', - inode: '', - lang: newLanguage.toString(), - populateaccept: 'true', - reuseLastLang: 'true', - variantName: this.uveStore.pageParams().variantName - }); - - return `${LAYOUT_URL}?${queryParams.toString()}`; - } -} diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.html b/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.html index d203f8561398..513689a6d8fe 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.html +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.html @@ -1,13 +1,11 @@ @if ($shellProps()?.canRead) { - + - - + (action)="handleItemAction($event)" /> + + } @else { @if ($shellProps().error?.code === 401) { @@ -17,16 +15,13 @@ } + (reloadFromDialog)="reloadFromDialog()" /> + data-testId="confirm-dialog" /> diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.ts index b3d9211f5a81..aa99018aa1b4 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.ts @@ -30,20 +30,20 @@ import { UVE_MODE } from '@dotcms/uve/types'; import { EditEmaNavigationBarComponent } from './components/edit-ema-navigation-bar/edit-ema-navigation-bar.component'; import { DotEmaDialogComponent } from '../components/dot-ema-dialog/dot-ema-dialog.component'; +import { DotEditorDialogService } from '../components/dot-ema-dialog/services/dot-ema-dialog.service'; import { DotActionUrlService } from '../services/dot-action-url/dot-action-url.service'; +import { DotUVENgEvenHandlerService } from '../services/dot-ng-event-handler/dot-ng-event-handler.service'; import { DotPageApiService } from '../services/dot-page-api.service'; -import { NG_CUSTOM_EVENTS } from '../shared/enums'; import { DialogAction, DotPageAssetParams } from '../shared/models'; import { UVEStore } from '../store/dot-uve.store'; import { DotUveViewParams } from '../store/models'; import { checkClientHostAccess, getAllowedPageParams, - getTargetUrl, normalizeQueryParams, - sanitizeURL, - shouldNavigate + sanitizeURL } from '../utils'; + @Component({ selector: 'dot-ema-shell', standalone: true, @@ -62,6 +62,7 @@ import { DotSeoMetaTagsService, DotSeoMetaTagsUtilService, DotWorkflowsActionsService, + DotUVENgEvenHandlerService, { provide: WINDOW, useValue: window @@ -84,15 +85,16 @@ import { ] }) export class DotEmaShellComponent implements OnInit { - @ViewChild('dialog') dialog!: DotEmaDialogComponent; @ViewChild('pageTools') pageTools!: DotPageToolsSeoComponent; readonly uveStore = inject(UVEStore); - readonly #activatedRoute = inject(ActivatedRoute); readonly #router = inject(Router); - readonly #siteService = inject(SiteService); readonly #location = inject(Location); + readonly #siteService = inject(SiteService); + readonly #activatedRoute = inject(ActivatedRoute); + readonly #dialogService = inject(DotEditorDialogService); + readonly #dotNgEventHandlerService = inject(DotUVENgEvenHandlerService); protected readonly $shellProps = this.uveStore.$shellProps; @@ -128,42 +130,6 @@ export class DotEmaShellComponent implements OnInit { .subscribe(() => this.#router.navigate(['/pages'])); } - handleNgEvent({ event }: DialogAction) { - switch (event.detail.name) { - case NG_CUSTOM_EVENTS.UPDATE_WORKFLOW_ACTION: { - const pageAPIResponse = this.uveStore.pageAPIResponse(); - this.uveStore.getWorkflowActions(pageAPIResponse.page.inode); - break; - } - - case NG_CUSTOM_EVENTS.SAVE_PAGE: { - this.handleSavePageEvent(event); - break; - } - } - } - - /** - * Handles the save page event triggered from the dialog. - * - * @param {CustomEvent} event - The event object containing details about the save action. - * @return {void} - */ - private handleSavePageEvent(event: CustomEvent): void { - const htmlPageReferer = event.detail.payload?.htmlPageReferer; - const url = new URL(htmlPageReferer, window.location.origin); // Add base for relative URLs - const targetUrl = getTargetUrl(url.pathname, this.uveStore.pageAPIResponse().urlContentMap); - - if (shouldNavigate(targetUrl, this.uveStore.pageParams().url)) { - // Navigate to the new URL if it's different from the current one - this.uveStore.loadPageAsset({ url: targetUrl }); - - return; - } - - this.uveStore.reloadCurrentPage(); - } - /** * Handle actions from nav bar * @@ -176,7 +142,7 @@ export class DotEmaShellComponent implements OnInit { } else if (itemId === 'properties') { const page = this.uveStore.pageAPIResponse().page; - this.dialog.editContentlet({ + this.#dialogService.editContentlet({ inode: page.inode, title: page.title, identifier: page.identifier, @@ -265,4 +231,14 @@ export class DotEmaShellComponent implements OnInit { const urlTree = this.#router.createUrlTree([], { queryParams }); this.#location.go(urlTree.toString()); } + + /** + * Handle the event triggered from the dialog. + * + * @param {DialogAction} event - The event object containing details about the action. + * @memberof DotEmaShellComponent + */ + protected handleNgEvent(event: DialogAction) { + this.#dotNgEventHandlerService.handleNgEvent(event); + } } diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.html b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.html index d9d2a3ebd104..b458da523a74 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.html +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.html @@ -56,9 +56,9 @@ (edit)="handleEditContentlet($event)" (editVTL)="handleEditVTL($event)" (delete)="deleteContentlet($event)" - (addWidget)="dialog.addWidget($event)" - (addForm)="dialog.addForm($event)" - (addContent)="dialog.addContentlet($event)" + (addWidget)="dialogService.addWidget($event)" + (addForm)="dialogService.addForm($event)" + (addContent)="dialogService.addContentlet($event)" [hide]="contentletTools.hide" [contentletArea]="contentletTools.contentletArea" [isEnterprise]="contentletTools.isEnterprise" @@ -85,13 +85,7 @@ [variantId]="palette.variantId" data-testId="palette" /> } -@if ($editorProps().showDialogs) { - -} + { - this.messageService.add({ - severity: 'error', - summary: this.dotMessageService.get( - 'editpage.content.contentlet.menu.reorder.title' - ), - detail: this.dotMessageService.get( - 'error.menu.reorder.user_has_not_permission' - ), - life: 2000 - }); - }, - [NG_CUSTOM_EVENTS.CANCEL_SAVING_MENU_ORDER]: () => { - this.dialog.resetDialog(); - this.cd.detectChanges(); - }, - [NG_CUSTOM_EVENTS.LANGUAGE_IS_CHANGED]: () => { - const htmlPageReferer = event.detail.payload?.htmlPageReferer; - const url = new URL(htmlPageReferer, window.location.origin); // Add base for relative URLs - const targetUrl = getTargetUrl( - url.pathname, - this.uveStore.pageAPIResponse().urlContentMap - ); - const language_id = url.searchParams.get('com.dotmarketing.htmlpage.language'); - - if (shouldNavigate(targetUrl, this.uveStore.pageParams().url)) { - // Navigate to the new URL if it's different from the current one - this.uveStore.loadPageAsset({ url: targetUrl, language_id }); - - return; - } - - this.uveStore.loadPageAsset({ - language_id - }); - } - })[detail.name]; - } - /** * Handle the post message event * @@ -1024,7 +854,7 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy { this.uveStore.setIsClientReady(true); }, [CLIENT_ACTIONS.EDIT_CONTENTLET]: (contentlet: DotCMSContentlet) => { - this.dialog.editContentlet({ ...contentlet, clientAction: action }); + this.dialogService.editContentlet({ ...contentlet, clientAction: action }); }, [CLIENT_ACTIONS.REORDER_MENU]: ({ startLevel, depth }: ReorderMenuPayload) => { const urlObject = createReorderMenuURL({ @@ -1034,10 +864,12 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy { hostId: this.uveStore.pageAPIResponse().site.identifier }); - this.dialog.openDialogOnUrl( - urlObject, - this.dotMessageService.get('editpage.content.contentlet.menu.reorder.title') - ); + this.dialogService.openDialogOnURL({ + url: urlObject, + title: this.dotMessageService.get( + 'editpage.content.contentlet.menu.reorder.title' + ) + }); }, [CLIENT_ACTIONS.INIT_INLINE_EDITING]: (payload) => this.#handleInlineEditingEvent(payload), @@ -1074,8 +906,7 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy { }); this.uveStore.resetEditorProperties(); - - this.dialog.resetDialog(); + this.dialogService.resetDialog(); } /** @@ -1091,7 +922,7 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy { const { onNumberOfPages = '1', title } = contentlet; if (Number(onNumberOfPages) <= 1) { - this.dialog?.editContentlet(contentlet); + this.dialogService.editContentlet(contentlet); return; } @@ -1106,13 +937,13 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy { return of(contentlet); } - this.dialog.showLoadingIframe(title); + this.dialogService.loadingIframe(title); return this.handleCopyContent(currentTreeNode); }) ) .subscribe((contentlet) => { - this.dialog.editContentlet(contentlet); + this.dialogService.editContentlet(contentlet); }); } @@ -1123,7 +954,10 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy { * @memberof EditEmaEditorComponent */ handleEditVTL(vtlFile: VTLFile) { - this.dialog.editVTLContentlet(vtlFile); + this.dialogService.editContentlet({ + inode: vtlFile.inode, + title: vtlFile.name + }); } /** @@ -1134,7 +968,7 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy { * @memberof EditEmaEditorComponent */ protected editContentMap(contentlet: DotCMSContentlet): void { - this.dialog.editUrlContentMapContentlet(contentlet); + this.dialogService.editUrlContentMapContentlet(contentlet); } /** @@ -1148,7 +982,7 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy { return this.dotCopyContentService.copyInPage(currentTreeNode).pipe( catchError((error) => this.dotHttpErrorManagerService.handle(error).pipe( - tap(() => this.dialog.resetDialog()), // If there is an error, we set the status to idle + tap(() => this.dialogService.resetDialog()), // If there is an error, we set the status to idle map(() => null) ) ), @@ -1442,7 +1276,7 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy { } translatePage(event: { page: DotPage; newLanguage: number }) { - this.dialog.translatePage(event); + this.dialogService.translatePage(event); } /** diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-action-url/dot-action-url.service.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-action-url/dot-action-url.service.ts index b6da934e881f..7e0cfaa1ca92 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-action-url/dot-action-url.service.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-action-url/dot-action-url.service.ts @@ -5,7 +5,9 @@ import { Injectable, inject } from '@angular/core'; import { catchError, pluck } from 'rxjs/operators'; -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class DotActionUrlService { private http = inject(HttpClient); diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-ng-event-handler/dot-ng-event-handler.service.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-ng-event-handler/dot-ng-event-handler.service.ts new file mode 100644 index 000000000000..5e99833517d7 --- /dev/null +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-ng-event-handler/dot-ng-event-handler.service.ts @@ -0,0 +1,428 @@ +import { EMPTY } from 'rxjs'; + +import { HttpErrorResponse } from '@angular/common/http'; +import { ChangeDetectorRef, inject, Injectable } from '@angular/core'; + +import { MessageService } from 'primeng/api'; + +import { catchError, filter, map, take, tap } from 'rxjs/operators'; + +import { CLIENT_ACTIONS } from '@dotcms/client'; +import { + DotContentletService, + DotHttpErrorManagerService, + DotMessageService +} from '@dotcms/data-access'; + +import { DotEditorDialogService } from '../../components/dot-ema-dialog/services/dot-ema-dialog.service'; +import { EDITOR_STATE, NG_CUSTOM_EVENTS, UVE_STATUS } from '../../shared/enums'; +import { ActionPayload, DialogAction } from '../../shared/models'; +import { UVEStore } from '../../store/dot-uve.store'; +import { insertContentletInContainer, getTargetUrl, shouldNavigate } from '../../utils'; +import { DotPageApiService } from '../dot-page-api.service'; + +// Define interface for event detail structure +interface EventDetail { + data: { + identifier?: string; + contentType?: string; + url?: string; + [key: string]: unknown; + }; + name?: string; + payload?: { + htmlPageReferer?: string; + newContentletId?: string; + shouldReloadPage?: boolean; + contentletIdentifier?: string; + [key: string]: unknown; + }; +} + +@Injectable({ + providedIn: 'root' +}) +export class DotUVENgEvenHandlerService { + readonly uveStore = inject(UVEStore); + readonly #dialogService = inject(DotEditorDialogService); + readonly #cd = inject(ChangeDetectorRef); + readonly #messageService = inject(MessageService); + readonly #dotMessageService = inject(DotMessageService); + readonly #dotPageApiService = inject(DotPageApiService); + readonly #dotContentletService = inject(DotContentletService); + readonly #dotHttpErrorManagerService = inject(DotHttpErrorManagerService); + + /** + * Handles the event triggered from the dialog. + * + * @param {DialogAction} dialogAction - The event object containing details about the action. + * @return {void} + */ + public handleNgEvent(dialogAction: DialogAction) { + const { event, actionPayload, clientAction } = dialogAction; + const { detail } = event; + + switch (event.detail.name) { + case NG_CUSTOM_EVENTS.UPDATE_WORKFLOW_ACTION: { + this.handleUpdateWorkflowAction(); + break; + } + + case NG_CUSTOM_EVENTS.CONTENT_SEARCH_SELECT: { + this.handleContentSearchSelect(detail as EventDetail, actionPayload); + break; + } + + case NG_CUSTOM_EVENTS.CREATE_CONTENTLET: { + this.handleCreateContentlet(detail as EventDetail, actionPayload); + break; + } + + case NG_CUSTOM_EVENTS.CANCEL_SAVING_MENU_ORDER: { + this.handleCancelSavingMenuOrder(); + break; + } + + case NG_CUSTOM_EVENTS.SAVE_MENU_ORDER: { + this.handleSaveMenuOrder(); + break; + } + + case NG_CUSTOM_EVENTS.LANGUAGE_IS_CHANGED: { + this.handleLanguageChanged(event); + break; + } + + case NG_CUSTOM_EVENTS.ERROR_SAVING_MENU_ORDER: { + this.handleErrorSavingMenuOrder(); + break; + } + + case NG_CUSTOM_EVENTS.FORM_SELECTED: { + this.handleFormSelected(detail as EventDetail, actionPayload); + break; + } + + case NG_CUSTOM_EVENTS.SAVE_PAGE: { + this.handleSavePage(event, actionPayload, clientAction); + break; + } + } + } + + /** + * Handles the update workflow action event + * This event is trigger in any change of the workflow action + * Including: + * - Lock + * - Unlock + * - Publish + * - Unpublish + * - Save + * + * @private + */ + private handleUpdateWorkflowAction(): void { + // With the identifier, we always get the latest workflow actions + const pageIdentifier = this.uveStore.pageAPIResponse().page.identifier; + this.uveStore.getWorkflowActions(pageIdentifier); + } + + /** + * Handles the content search select event + * + * This event is trigger when the user select a contentlet in the content search + * @private + * @param {EventDetail} detail - The event detail + * @param {ActionPayload} actionPayload - The action payload + */ + private handleContentSearchSelect(detail: EventDetail, actionPayload: ActionPayload): void { + const payload = { + ...actionPayload, + newContentletId: detail.data.identifier + }; + + // If you see this, remind me to test this case in Headless and traditional pages + // Not sure if `this.uveStore.setUveStatus(UVE_STATUS.LOADED);` is needed here + this.handleContentletAdded(payload); + } + + /** + * Handles the create contentlet event + * + * This event is trigger when the user create a new contentlet + * @private + * @param {EventDetail} detail - The event detail + * @param {ActionPayload} actionPayload - The action payload + */ + private handleCreateContentlet(detail: EventDetail, actionPayload: ActionPayload): void { + this.#dialogService.createContentlet({ + contentType: detail.data.contentType, + url: detail.data.url, + actionPayload + }); + + this.#cd.detectChanges(); + } + + /** + * Handles the cancel saving menu order event + * + * @private + */ + private handleCancelSavingMenuOrder(): void { + this.#dialogService.resetDialog(); + this.#cd.detectChanges(); + } + + /** + * Handles the save menu order event + * + * This event is trigger when the user attempt to reorder the navigation menu + * @private + */ + private handleSaveMenuOrder(): void { + this.#messageService.add({ + severity: 'success', + summary: this.#dotMessageService.get('editpage.content.contentlet.menu.reorder.title'), + detail: this.#dotMessageService.get('message.menu.reordered'), + life: 2000 + }); + + this.uveStore.reloadCurrentPage(); + this.#dialogService.resetDialog(); + } + + /** + * Handles the language changed event + * + * This event is trigger when the user change the language of the page + * @private + * @param {CustomEvent} event - The event + */ + private handleLanguageChanged(event: CustomEvent): void { + const htmlPageReferer = event.detail.payload?.htmlPageReferer; + const url = new URL(htmlPageReferer, window.location.origin); // Add base for relative URLs + const targetUrl = getTargetUrl(url.pathname, this.uveStore.pageAPIResponse().urlContentMap); + // CHECK THIS PARAMETER + const language_id = url.searchParams.get('com.dotmarketing.htmlpage.language'); + + if (shouldNavigate(targetUrl, this.uveStore.pageParams().url)) { + // Navigate to the new URL if it's different from the current one + this.uveStore.loadPageAsset({ url: targetUrl, language_id }); + + return; + } + + this.uveStore.loadPageAsset({ + language_id + }); + } + + /** + * Handles the error saving menu order event + * + * @private + */ + private handleErrorSavingMenuOrder(): void { + this.#messageService.add({ + severity: 'error', + summary: this.#dotMessageService.get('editpage.content.contentlet.menu.reorder.title'), + detail: this.#dotMessageService.get('error.menu.reorder.user_has_not_permission'), + life: 2000 + }); + } + + /** + * Handles the form selected event + * + * @private + * @param {EventDetail} detail - The event detail + * @param {ActionPayload} actionPayload - The action payload + */ + private handleFormSelected(detail: EventDetail, actionPayload: ActionPayload): void { + const formId = detail.data.identifier; + + this.#dotPageApiService + .getFormIndetifier(actionPayload.container.identifier, formId) + .pipe( + tap(() => { + this.uveStore.setUveStatus(UVE_STATUS.LOADING); + }), + map((newFormId: string) => { + return { + ...actionPayload, + newContentletId: newFormId + }; + }), + catchError(() => EMPTY), + take(1) + ) + .subscribe((response) => { + this.handleContentletAdded(response); + }); + } + + /** + * Handles the save page event + * + * @private + * @param {CustomEvent} event - The event + * @param {ActionPayload} actionPayload - The action payload + * @param {string} clientAction - The client action + */ + private handleSavePage( + event: CustomEvent, + actionPayload: ActionPayload, + clientAction: string + ): void { + const { shouldReloadPage, contentletIdentifier } = event.detail.payload ?? {}; + const pageIdentifier = this.uveStore.pageAPIResponse().page.identifier; + const isGraphqlPage = !!this.uveStore.graphql(); + + if (shouldReloadPage) { + this.reloadURLContentMapPage(contentletIdentifier); + + return; + } + + if (clientAction === CLIENT_ACTIONS.EDIT_CONTENTLET || !isGraphqlPage) { + this.notifyContentOutsidePageHasChanged(); + } + + if (contentletIdentifier === pageIdentifier || !actionPayload) { + this.handleReloadPage(event); + + return; + } + + this.handleContentSave(event, actionPayload); + } + + /** + * Handles the save page event triggered from the dialog. + * + * @param {CustomEvent} event - The event object containing details about the save action. + * @return {void} + */ + private handleReloadPage(event: CustomEvent): void { + // Move this to the GetTargetUrl function + const htmlPageReferer = event.detail.payload?.htmlPageReferer; + const url = new URL(htmlPageReferer, window.location.origin); + const targetUrl = getTargetUrl(url.pathname, this.uveStore.pageAPIResponse().urlContentMap); + // END + + if (shouldNavigate(targetUrl, this.uveStore.pageParams().url)) { + // Navigate to the new URL if it's different from the current one + this.uveStore.loadPageAsset({ url: targetUrl }); + + return; + } + + this.uveStore.reloadCurrentPage(); + } + + /** + * Handles saving content + * + * @private + * @param {CustomEvent} event - The event + * @param {ActionPayload} actionPayload - The action payload + */ + private handleContentSave(event: CustomEvent, actionPayload: ActionPayload): void { + const newContentletId = event.detail.payload?.newContentletId ?? ''; + + const payload = { + ...actionPayload, + newContentletId + }; + + // If you see this, remind me to test this case in Headless and traditional pages + // Not sure if `this.uveStore.setUveStatus(UVE_STATUS.LOADED);` is needed here + this.handleContentletAdded(payload); + } + + /** + * Handles duplicated contentlet message + * + * @private + */ + private handleDuplicatedContentlet(): void { + this.#messageService.add({ + severity: 'info', + summary: this.#dotMessageService.get('editpage.content.add.already.title'), + detail: this.#dotMessageService.get('editpage.content.add.already.message'), + life: 2000 + }); + + this.#dialogService.resetDialog(); + this.uveStore.resetEditorProperties(); + this.uveStore.setUveStatus(UVE_STATUS.LOADED); + } + + /** + * Reload the URL content map page + * + * @private + * @param {string} inodeOrIdentifier + * @memberof EditEmaEditorComponent + */ + private reloadURLContentMapPage(inodeOrIdentifier: string): void { + // Set loading state to prevent the user to interact with the iframe + this.uveStore.setUveStatus(UVE_STATUS.LOADING); + + this.#dotContentletService + .getContentletByInode(inodeOrIdentifier) + .pipe( + catchError((error) => this.handlerError(error)), + filter((contentlet) => !!contentlet) + ) + .subscribe(({ URL_MAP_FOR_CONTENT }) => { + if (URL_MAP_FOR_CONTENT != this.uveStore.pageParams().url) { + // If the URL is different, we need to navigate to the new URL + this.uveStore.loadPageAsset({ url: URL_MAP_FOR_CONTENT }); + + return; + } + + // If the URL is the same, we need to fetch the new page data + this.uveStore.reloadCurrentPage(); + }); + } + + /** + * Handle the error + * + * @private + * @param {HttpErrorResponse} error + * @return {*} + * @memberof EditEmaEditorComponent + */ + private handlerError(error: HttpErrorResponse) { + this.uveStore.setEditorState(EDITOR_STATE.ERROR); + + return this.#dotHttpErrorManagerService.handle(error).pipe(map(() => null)); + } + + /** + * Notify that content outside the page has changed + * + * @private + */ + private notifyContentOutsidePageHasChanged(): void { + // this.contentWindow?.postMessage( + // { name: __DOTCMS_UVE_EVENT__.UVE_RELOAD_PAGE }, + // "*" + // ); + } + + private handleContentletAdded(actionPayload: ActionPayload): void { + const { pageContainers, didInsert } = insertContentletInContainer(actionPayload); + + if (!didInsert) { + this.handleDuplicatedContentlet(); + } + + this.uveStore.savePage(pageContainers); + } +}