diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts
index 1c4ee37ba0ae..d427ab38fdd7 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts
@@ -12,7 +12,7 @@ import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { DialogService } from 'primeng/dynamicdialog';
import { ToastModule } from 'primeng/toast';
-import { CLIENT_ACTIONS } from '@dotcms/client';
+import { CLIENT_ACTIONS, UVE_MODE } from '@dotcms/client';
import {
DotContentletLockerService,
DotExperimentsService,
@@ -115,7 +115,8 @@ const INITIAL_PAGE_PARAMS = {
language_id: 1,
url: 'index',
variantName: 'DEFAULT',
- 'com.dotmarketing.persona.id': 'modes.persona.no.persona'
+ 'com.dotmarketing.persona.id': 'modes.persona.no.persona',
+ editorMode: UVE_MODE.EDIT
};
const BASIC_OPTIONS = {
@@ -320,7 +321,7 @@ describe('DotEmaShellComponent', () => {
expect(spyloadPageAsset).toHaveBeenCalledWith(INITIAL_PAGE_PARAMS);
expect(spyStoreLoadPage).toHaveBeenCalledWith(INITIAL_PAGE_PARAMS);
expect(spyLocation).toHaveBeenCalledWith(
- '/?language_id=1&url=index&variantName=DEFAULT&com.dotmarketing.persona.id=modes.persona.no.persona'
+ '/?language_id=1&url=index&variantName=DEFAULT&com.dotmarketing.persona.id=modes.persona.no.persona&editorMode=edit'
);
});
@@ -376,7 +377,8 @@ describe('DotEmaShellComponent', () => {
language_id: 2,
url: 'my-awesome-page',
variantName: 'DEFAULT',
- 'com.dotmarketing.persona.id': 'SomeCoolDude'
+ 'com.dotmarketing.persona.id': 'SomeCoolDude',
+ editorMode: UVE_MODE.EDIT
};
const url = router.createUrlTree([], { queryParams: newParams });
@@ -530,6 +532,47 @@ describe('DotEmaShellComponent', () => {
});
});
+ describe('Editor Mode', () => {
+ it('should set editorMode to EDIT when wrong editorMode is passed', () => {
+ const spyStoreLoadPage = jest.spyOn(store, 'loadPageAsset');
+ const params = {
+ ...INITIAL_PAGE_PARAMS,
+ editorMode: 'WRONG'
+ };
+ overrideRouteSnashot(
+ activatedRoute,
+ SNAPSHOT_MOCK({ queryParams: params, data: UVE_CONFIG_MOCK(BASIC_OPTIONS) })
+ );
+ spectator.detectChanges();
+ expect(spyStoreLoadPage).toHaveBeenCalledWith({
+ ...INITIAL_PAGE_PARAMS,
+ editorMode: UVE_MODE.EDIT
+ });
+ });
+
+ it('should add the current date if preview param is true and publishDate is not present', () => {
+ const spyStoreLoadPage = jest.spyOn(store, 'loadPageAsset');
+ const params = {
+ ...INITIAL_PAGE_PARAMS,
+ editorMode: UVE_MODE.PREVIEW
+ };
+
+ // override the new Date() to return a fixed date
+ const fixedDate = new Date('2024-01-01');
+ jest.spyOn(global, 'Date').mockImplementation(() => fixedDate);
+
+ const data = UVE_CONFIG_MOCK(BASIC_OPTIONS);
+
+ overrideRouteSnashot(activatedRoute, SNAPSHOT_MOCK({ queryParams: params, data }));
+
+ spectator.detectChanges();
+ expect(spyStoreLoadPage).toHaveBeenCalledWith({
+ ...params,
+ publishDate: fixedDate.toISOString()
+ });
+ });
+ });
+
describe('Site Changes', () => {
it('should trigger a navigate to /pages when site changes', async () => {
const navigate = jest.spyOn(router, 'navigate');
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 fc10066c55c1..1a8e96406d19 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
@@ -11,6 +11,7 @@ import { ToastModule } from 'primeng/toast';
import { skip } from 'rxjs/operators';
+import { UVE_MODE } from '@dotcms/client';
import {
DotESContentService,
DotExperimentsService,
@@ -30,10 +31,10 @@ import { EditEmaNavigationBarComponent } from './components/edit-ema-navigation-
import { DotEmaDialogComponent } from '../components/dot-ema-dialog/dot-ema-dialog.component';
import { DotActionUrlService } from '../services/dot-action-url/dot-action-url.service';
-import { DotPageApiParams, DotPageApiService } from '../services/dot-page-api.service';
+import { DotPageApiService } from '../services/dot-page-api.service';
import { WINDOW } from '../shared/consts';
import { NG_CUSTOM_EVENTS } from '../shared/enums';
-import { DialogAction } from '../shared/models';
+import { DialogAction, DotPageAssetParams } from '../shared/models';
import { UVEStore } from '../store/dot-uve.store';
import {
checkClientHostAccess,
@@ -201,7 +202,7 @@ export class DotEmaShellComponent implements OnInit, OnDestroy {
* @return {*} {DotPageApiParams}
* @memberof DotEmaShellComponent
*/
- #getPageParams(): DotPageApiParams {
+ #getPageParams(): DotPageAssetParams {
const { queryParams, data } = this.#activatedRoute.snapshot;
const uveConfig = data?.uveConfig;
const allowedDevURLs = uveConfig?.options?.allowedDevURLs;
@@ -218,6 +219,14 @@ export class DotEmaShellComponent implements OnInit, OnDestroy {
params.clientHost = uveConfig.url;
}
+ if (params.editorMode !== UVE_MODE.EDIT && params.editorMode !== UVE_MODE.PREVIEW) {
+ params.editorMode = UVE_MODE.EDIT;
+ }
+
+ if (params.editorMode === UVE_MODE.PREVIEW && !params.publishDate) {
+ params.publishDate = new Date().toISOString();
+ }
+
return params;
}
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.html b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.html
index e5e086766816..cb5d2ae65352 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.html
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.html
@@ -12,21 +12,18 @@
@if (preview) {
}
@@ -62,7 +59,7 @@
icon="pi pi-eye"
styleClass="p-button-text p-button-sm"
data-testId="uve-toolbar-preview"
- (click)="setPreviewMode()" />
+ (click)="triggerPreviewMode()" />
@@ -89,7 +86,7 @@
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.scss b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.scss
index d8049559596f..d8302f003f62 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.scss
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.scss
@@ -4,19 +4,17 @@
.uve-toolbar {
padding: 0 $spacing-4;
transition: padding 0.2s ease;
- border-color: $color-palette-primary-200;
border-top: 1px solid transparent; // Avoid jump
}
- .uve-toolbar-preview {
- border-top-color: $color-palette-primary-200;
- padding-inline: $spacing-5;
- }
-
- .p-chip.uve-toolbar-chips {
+ .p-button.uve-toolbar-chips,
+ .p-button.uve-toolbar-chips:hover,
+ .p-button.uve-toolbar-chips:focus {
height: $field-height-sm;
background-color: $color-palette-primary-op-10;
border-color: $color-palette-primary-op-10;
+ color: var(--color-palette-primary-500);
+ cursor: pointer;
}
}
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.spec.ts
index 291efdc4c758..25c465c1153c 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.spec.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.spec.ts
@@ -9,6 +9,7 @@ import { By } from '@angular/platform-browser';
import { ConfirmationService, MessageService } from 'primeng/api';
+import { UVE_MODE } from '@dotcms/client';
import {
DotExperimentsService,
DotLanguagesService,
@@ -103,6 +104,9 @@ describe('DotUveToolbarComponent', () => {
let messageService: MessageService;
let confirmationService: ConfirmationService;
+ const fixedDate = new Date('2024-01-01');
+ jest.spyOn(global, 'Date').mockImplementation(() => fixedDate);
+
const createComponent = createComponentFactory({
component: DotUveToolbarComponent,
imports: [
@@ -249,7 +253,10 @@ describe('DotUveToolbarComponent', () => {
spectator.click(byTestId('uve-toolbar-preview'));
- expect(spy).toHaveBeenCalledWith({ preview: 'true' });
+ expect(spy).toHaveBeenCalledWith({
+ editorMode: UVE_MODE.PREVIEW,
+ publishDate: fixedDate.toISOString()
+ });
});
});
@@ -378,7 +385,10 @@ describe('DotUveToolbarComponent', () => {
beforeEach(() => {
spectator = createComponent({
providers: [
- mockProvider(UVEStore, { ...baseUVEState, $isPreviewMode: signal(true) })
+ mockProvider(UVEStore, {
+ ...baseUVEState,
+ $isPreviewMode: signal(true)
+ })
]
});
@@ -396,27 +406,56 @@ describe('DotUveToolbarComponent', () => {
spectator.click(byTestId('close-preview-mode'));
spectator.detectChanges();
- expect(spy).toHaveBeenCalledWith({ preview: null });
+ expect(spy).toHaveBeenCalledWith({ editorMode: null, publishDate: null });
+ });
+
+ it('should call store.loadPageAsset when datePreview model is updated', () => {
+ const spy = jest.spyOn(store, 'loadPageAsset');
+
+ spectator.debugElement.componentInstance.$previewDate.set(new Date('2024-02-01'));
+ spectator.detectChanges();
+
+ expect(spy).toHaveBeenCalledWith({
+ editorMode: UVE_MODE.PREVIEW,
+ publishDate: new Date('2024-02-01').toISOString()
+ });
+ });
+
+ it('should call store.loadPageAsset with currentDate when datePreview model is updated with a past date', () => {
+ const spy = jest.spyOn(store, 'loadPageAsset');
+
+ spectator.debugElement.componentInstance.$previewDate.set(new Date('2023-02-01'));
+ spectator.detectChanges();
+
+ expect(spy).toHaveBeenCalledWith({
+ editorMode: UVE_MODE.PREVIEW,
+ publishDate: fixedDate.toISOString()
+ });
});
});
it('should have desktop button', () => {
+ spectator.detectChanges();
expect(spectator.query(byTestId('desktop-preview'))).toBeTruthy();
});
it('should have mobile button', () => {
+ spectator.detectChanges();
expect(spectator.query(byTestId('mobile-preview'))).toBeTruthy();
});
it('should have tablet button', () => {
+ spectator.detectChanges();
expect(spectator.query(byTestId('tablet-preview'))).toBeTruthy();
});
it('should have more devices button', () => {
+ spectator.detectChanges();
expect(spectator.query(byTestId('more-devices-preview'))).toBeTruthy();
});
it('should not have experiments', () => {
+ spectator.detectChanges();
expect(spectator.query(byTestId('uve-toolbar-running-experiment'))).toBeFalsy();
});
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts
index 0f649288747d..4fc3be2fcbc3 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts
@@ -6,8 +6,11 @@ import {
computed,
EventEmitter,
inject,
+ model,
Output,
- viewChild
+ effect,
+ viewChild,
+ untracked
} from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@@ -18,8 +21,10 @@ import { ChipModule } from 'primeng/chip';
import { SplitButtonModule } from 'primeng/splitbutton';
import { ToolbarModule } from 'primeng/toolbar';
+import { UVE_MODE } from '@dotcms/client';
import { DotMessageService, DotPersonalizeService } from '@dotcms/data-access';
import { DotPersona, DotLanguage } from '@dotcms/dotcms-models';
+import { DotMessagePipe } from '@dotcms/ui';
import { DEFAULT_PERSONA } from '../../../shared/consts';
import { DotPage } from '../../../shared/models';
@@ -49,6 +54,8 @@ import { EditEmaPersonaSelectorComponent } from '../edit-ema-persona-selector/ed
ReactiveFormsModule,
EditEmaPersonaSelectorComponent,
EditEmaLanguageSelectorComponent,
+ ClipboardModule,
+ DotMessagePipe,
DotUveWorkflowActionsComponent,
ChipModule
],
@@ -74,6 +81,8 @@ export class DotUveToolbarComponent {
readonly $apiURL = this.#store.$apiURL;
readonly $personaSelectorProps = this.#store.$personaSelector;
+ protected readonly CURRENT_DATE = new Date();
+
readonly $styleToolbarClass = computed(() => {
if (!this.$isPreviewMode()) {
return 'uve-toolbar';
@@ -82,6 +91,35 @@ export class DotUveToolbarComponent {
return 'uve-toolbar uve-toolbar-preview';
});
+ protected readonly publishDateParam = this.#store.pageParams().publishDate;
+ protected readonly $previewDate = model(
+ this.publishDateParam ? new Date(this.publishDateParam) : null
+ );
+
+ readonly $previewDateEffect = effect(
+ () => {
+ const previewDate = this.$previewDate();
+
+ if (!previewDate) {
+ return;
+ }
+
+ // If previewDate is minor that the CURRENT DATE, set previewDate to CURRENT DATE
+ if (previewDate < this.CURRENT_DATE) {
+ this.$previewDate.set(this.CURRENT_DATE);
+
+ return;
+ }
+
+ untracked(() => {
+ this.#store.loadPageAsset({
+ editorMode: UVE_MODE.PREVIEW,
+ publishDate: previewDate?.toISOString()
+ });
+ });
+ },
+ { allowSignalWrites: true }
+ );
readonly $pageInode = computed(() => {
return this.#store.pageAPIResponse()?.page.inode;
});
@@ -89,24 +127,23 @@ export class DotUveToolbarComponent {
readonly $actions = this.#store.workflowLoading;
readonly $workflowLoding = this.#store.workflowLoading;
- protected readonly date = new Date();
-
/**
- * Set the preview mode
+ * Initialize the preview mode
*
+ * @param {Date} publishDate
* @memberof DotUveToolbarComponent
*/
- protected setPreviewMode() {
- this.#store.loadPageAsset({ preview: 'true' });
+ protected triggerPreviewMode(publishDate = new Date()) {
+ this.$previewDate.set(publishDate);
}
/**
- * Set the edit mode
+ * Initialize the edit mode
*
* @memberof DotUveToolbarComponent
*/
- protected setEditMode() {
- this.#store.loadPageAsset({ preview: null });
+ protected triggerEditMode() {
+ this.#store.loadPageAsset({ editorMode: null, publishDate: null });
}
/**
@@ -135,6 +172,11 @@ export class DotUveToolbarComponent {
this.#store.loadPageAsset({ language_id });
}
+ /**
+ * Trigger the copy toasts
+ *
+ * @memberof DotUveToolbarComponent
+ */
triggerCopyToast() {
this.#messageService.add({
severity: 'success',
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.spec.ts
index d40d3308e568..9e26efa742fe 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.spec.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.spec.ts
@@ -2918,21 +2918,6 @@ describe('EditEmaEditorComponent', () => {
describe('CUSTOMER ACTIONS', () => {
describe('CLIENT_READY', () => {
- it('should set client is ready when not extra configuration is send', () => {
- const setIsClientReadySpy = jest.spyOn(store, 'setIsClientReady');
-
- window.dispatchEvent(
- new MessageEvent('message', {
- origin: HOST,
- data: {
- action: CLIENT_ACTIONS.CLIENT_READY
- }
- })
- );
-
- expect(setIsClientReadySpy).toHaveBeenCalledWith(true);
- });
-
it('should set client GraphQL configuration and call the reload', () => {
const setClientConfigurationSpy = jest.spyOn(
store,
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.ts
index a26c028c30cf..03adcd56f94e 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.ts
@@ -1011,13 +1011,6 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy {
return;
}
- // If there is no client configuration, we just set the client as ready
- if (!clientConfig) {
- this.uveStore.setIsClientReady(true);
-
- return;
- }
-
this.uveStore.setClientConfiguration({ query, params });
this.uveStore.reloadCurrentPage();
},
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api.service.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api.service.spec.ts
index 903e24d7e79a..e881a5096d35 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api.service.spec.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api.service.spec.ts
@@ -1,5 +1,7 @@
import { createHttpFactory, HttpMethod, SpectatorHttp } from '@ngneat/spectator';
+import { UVE_MODE } from '@dotcms/client';
+
import { DotPageApiService } from './dot-page-api.service';
describe('DotPageApiService', () => {
@@ -109,13 +111,13 @@ describe('DotPageApiService', () => {
});
describe('preview', () => {
- it("should request page in preview mode if 'preview' is true", () => {
+ it("should request page in preview mode if 'editorMode' is 'preview'", () => {
spectator.service
.get({
url: 'test-url',
language_id: 'en',
'com.dotmarketing.persona.id': 'modes.persona.no.persona',
- preview: 'true'
+ editorMode: UVE_MODE.PREVIEW
})
.subscribe();
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api.service.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api.service.ts
index 252b60f6609e..3585c5387c90 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api.service.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api.service.ts
@@ -5,7 +5,7 @@ import { Injectable } from '@angular/core';
import { catchError, map, pluck } from 'rxjs/operators';
-import { graphqlToPageEntity } from '@dotcms/client';
+import { graphqlToPageEntity, UVE_MODE } from '@dotcms/client';
import { Site } from '@dotcms/dotcms-js';
import {
DEFAULT_VARIANT_ID,
@@ -19,7 +19,7 @@ import {
} from '@dotcms/dotcms-models';
import { PAGE_MODE } from '../shared/enums';
-import { DotPage, SavePagePayload } from '../shared/models';
+import { DotPage, DotPageAssetParams, SavePagePayload } from '../shared/models';
import { ClientRequestProps } from '../store/features/client/withClient';
import { createPageApiUrlWithQueryParams } from '../utils';
@@ -43,15 +43,15 @@ export interface DotPageApiParams {
url: string;
language_id: string;
'com.dotmarketing.persona.id': string;
- preview?: string;
variantName?: string;
experimentId?: string;
mode?: string;
clientHost?: string;
depth?: string;
+ publishDate?: string;
}
-export enum DotPageApiKeys {
+export enum DotPageAssetKeys {
URL = 'url',
MODE = 'mode',
DEPTH = 'depth',
@@ -60,7 +60,8 @@ export enum DotPageApiKeys {
LANGUAGE_ID = 'language_id',
EXPERIMENT_ID = 'experimentId',
PERSONA_ID = 'com.dotmarketing.persona.id',
- PREVIEW = 'preview'
+ PUBLISH_DATE = 'publishDate',
+ EDITOR_MODE = 'editorMode'
}
export interface GetPersonasParams {
@@ -92,12 +93,20 @@ export class DotPageApiService {
* @return {*} {Observable}
* @memberof DotPageApiService
*/
- get(params: DotPageApiParams): Observable {
+ get(params: DotPageAssetParams): Observable {
// Remove trailing and leading slashes
- const { clientHost, preview, depth = '0', language_id, variantName, experimentId } = params;
+ const {
+ clientHost,
+ editorMode,
+ depth = '0',
+ language_id,
+ variantName,
+ experimentId,
+ publishDate
+ } = params;
const url = params.url.replace(/^\/+|\/+$/g, '');
- const isPreview = preview === 'true';
+ const isPreview = editorMode === UVE_MODE.PREVIEW;
const pageType = clientHost ? 'json' : 'render';
const mode = isPreview ? PAGE_MODE.LIVE : PAGE_MODE.EDIT;
@@ -107,7 +116,8 @@ export class DotPageApiService {
variantName,
experimentId,
depth,
- mode
+ mode,
+ publishDate: publishDate ?? undefined
});
const apiUrl = `/api/v1/page/${pageType}/${pageApiUrl}`;
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/shared/models.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/shared/models.ts
index 35997aac5ba3..53e054093b7a 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/shared/models.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/shared/models.ts
@@ -1,4 +1,4 @@
-import { CLIENT_ACTIONS } from '@dotcms/client';
+import { UVE_MODE, CLIENT_ACTIONS } from '@dotcms/client';
import { DotCMSContentlet, DotDevice } from '@dotcms/dotcms-models';
import { InfoPage } from '@dotcms/ui';
@@ -245,3 +245,5 @@ export interface ReorderMenuPayload {
startLevel: number;
depth: number;
}
+
+export type DotPageAssetParams = DotPageApiParams & { editorMode?: UVE_MODE };
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.spec.ts
index 4755c10f8e5e..2e676a06596b 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.spec.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.spec.ts
@@ -12,6 +12,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { MessageService } from 'primeng/api';
+import { UVE_MODE } from '@dotcms/client';
import {
DotExperimentsService,
DotLanguagesService,
@@ -362,13 +363,13 @@ describe('UVEStore', () => {
describe('$isPreviewMode', () => {
it("should return true when the preview is 'true'", () => {
- store.loadPageAsset({ preview: 'true' });
+ store.loadPageAsset({ editorMode: UVE_MODE.PREVIEW });
expect(store.$isPreviewMode()).toBe(true);
});
it("should return false when the preview is not 'true'", () => {
- store.loadPageAsset({ preview: null });
+ store.loadPageAsset({ editorMode: null });
expect(store.$isPreviewMode()).toBe(false);
});
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.ts
index 429b1a36b45f..b4150688e306 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.ts
@@ -2,6 +2,8 @@ import { patchState, signalStore, withComputed, withMethods, withState } from '@
import { computed, untracked } from '@angular/core';
+import { UVE_MODE } from '@dotcms/client';
+
import { withEditor } from './features/editor/withEditor';
import { withFlags } from './features/flags/withFlags';
import { withLayout } from './features/layout/withLayout';
@@ -125,7 +127,7 @@ export const UVEStore = signalStore(
return pageAPIResponse()?.viewAs.language?.id || 1;
}),
$isPreviewMode: computed(() => {
- return pageParams()?.preview === 'true';
+ return pageParams()?.editorMode === UVE_MODE.PREVIEW;
})
};
}
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/client/withClient.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/client/withClient.ts
index 6cd615abea96..30690ba2c8f1 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/client/withClient.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/client/withClient.ts
@@ -55,6 +55,8 @@ export function withClient() {
},
setClientConfiguration: ({ query, params }: ClientRequestProps) => {
patchState(store, {
+ // Added this to avoid the client ready event to be triggered
+ isClientReady: true,
clientRequestProps: {
query,
params
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/toolbar/withUVEToolbar.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/toolbar/withUVEToolbar.ts
index 1bd25f53c8fd..7b6281746207 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/toolbar/withUVEToolbar.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/toolbar/withUVEToolbar.ts
@@ -9,6 +9,7 @@ import {
import { computed } from '@angular/core';
+import { UVE_MODE } from '@dotcms/client';
import { DotExperimentStatus } from '@dotcms/dotcms-models';
import { DEFAULT_PERSONA } from '../../../../shared/consts';
@@ -87,7 +88,7 @@ export function withUVEToolbar() {
const siteId = pageAPIResponse?.site?.identifier;
const clientHost = `${params?.clientHost ?? window.location.origin}`;
- const isPreview = params?.preview === 'true';
+ const isPreview = params?.editorMode === UVE_MODE.PREVIEW;
const prevewItem = isPreview
? {
deviceSelector: {
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/withEditor.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/withEditor.spec.ts
index 14dc9b20de07..b98da56a9667 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/withEditor.spec.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/withEditor.spec.ts
@@ -6,6 +6,7 @@ import { of } from 'rxjs';
import { ActivatedRoute, Router } from '@angular/router';
+import { UVE_MODE } from '@dotcms/client';
import { CurrentUser } from '@dotcms/dotcms-js';
import { DEFAULT_VARIANT_ID, DEFAULT_VARIANT_NAME, DotCMSContentlet } from '@dotcms/dotcms-models';
import { getRunningExperimentMock, mockDotDevices, seoOGTagsMock } from '@dotcms/utils-testing';
@@ -672,7 +673,9 @@ describe('withEditor', () => {
});
it('should not have opacity or progressBar in preview mode', () => {
- patchState(store, { pageParams: { ...emptyParams, preview: 'true' } });
+ patchState(store, {
+ pageParams: { ...emptyParams, editorMode: UVE_MODE.PREVIEW }
+ });
expect(store.$editorProps().iframe.opacity).toBe('1');
expect(store.$editorProps().progressBar).toBe(false);
@@ -831,6 +834,20 @@ describe('withEditor', () => {
expect(store.$editorProps().contentletTools).toBe(null);
});
+
+ it('should have contentletTools when the page can be edited and is in preview mode', () => {
+ patchState(store, {
+ isEditState: true,
+ canEditPage: true,
+ pageParams: {
+ ...emptyParams,
+ editorMode: UVE_MODE.PREVIEW
+ },
+ state: EDITOR_STATE.IDLE
+ });
+
+ expect(store.$editorProps().contentletTools).toEqual(null);
+ });
});
describe('dropzone', () => {
const bounds = getBoundsMock(ACTION_MOCK);
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/withEditor.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/withEditor.ts
index fe52357aa962..0e60b8be6d6c 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/withEditor.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/withEditor.ts
@@ -9,6 +9,7 @@ import {
import { computed, untracked } from '@angular/core';
+import { UVE_MODE } from '@dotcms/client';
import { DotTreeNode, SeoMetaTags } from '@dotcms/dotcms-models';
import {
@@ -115,7 +116,7 @@ export function withEditor() {
const dragItem = store.dragItem();
const isEditState = store.isEditState();
- const isPreview = params?.preview === 'true';
+ const isPreview = params?.editorMode === UVE_MODE.PREVIEW;
const isPageReady = isTraditionalPage || isClientReady || isPreview;
const isLoading = !isPageReady || store.status() === UVE_STATUS.LOADING;
@@ -129,7 +130,11 @@ export function withEditor() {
const showBlockEditorSidebar = canEditPage && isEditState && isEnterprise;
const canUserHaveContentletTools =
- !!contentletArea && canEditPage && isEditState && !isScrolling;
+ !!contentletArea &&
+ canEditPage &&
+ isEditState &&
+ !isScrolling &&
+ !isPreview;
const showDropzone = canEditPage && state === EDITOR_STATE.DRAGGING;
const showPalette = isEnterprise && canEditPage && isEditState && !isPreview;
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.ts
index fb532018d950..ca4c9ef6ae96 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.ts
@@ -12,8 +12,9 @@ import { DotExperimentsService, DotLanguagesService, DotLicenseService } from '@
import { LoginService } from '@dotcms/dotcms-js';
import { DEFAULT_VARIANT_ID } from '@dotcms/dotcms-models';
-import { DotPageApiParams, DotPageApiService } from '../../../services/dot-page-api.service';
+import { DotPageApiService } from '../../../services/dot-page-api.service';
import { UVE_STATUS } from '../../../shared/enums';
+import { DotPageAssetParams } from '../../../shared/models';
import { computeCanEditPage, computePageIsLocked, isForwardOrPage } from '../../../utils';
import { UVEState } from '../../models';
import { withClient } from '../client/withClient';
@@ -48,11 +49,11 @@ export function withLoad() {
* @param {DotPageApiParams} pageParams - The parameters used to fetch the page asset.
* @memberof DotEmaShellComponent
*/
- loadPageAsset: rxMethod>(
+ loadPageAsset: rxMethod>(
pipe(
map((params) => {
if (!store.pageParams()) {
- return params as DotPageApiParams;
+ return params as DotPageAssetParams;
}
return {
@@ -140,9 +141,7 @@ export function withLoad() {
currentUser
);
- const isPreview = pageParams.preview === 'true';
const isTraditionalPage = !pageParams.clientHost;
- const isClientReady = isTraditionalPage || isPreview;
patchState(store, {
pageAPIResponse: pageAsset,
@@ -152,7 +151,7 @@ export function withLoad() {
languages,
canEditPage,
pageIsLocked,
- isClientReady,
+ isClientReady: isTraditionalPage,
isTraditionalPage,
status: UVE_STATUS.LOADED
});
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/models.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/models.ts
index 113a528e5bcc..6e0d7e59fb3d 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/models.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/models.ts
@@ -7,15 +7,15 @@ import {
} from '@dotcms/dotcms-models';
import { InfoPage } from '@dotcms/ui';
-import { DotPageApiParams, DotPageApiResponse } from '../services/dot-page-api.service';
+import { DotPageApiResponse } from '../services/dot-page-api.service';
import { UVE_STATUS } from '../shared/enums';
-import { DotPage, NavigationBarItem } from '../shared/models';
+import { DotPage, DotPageAssetParams, NavigationBarItem } from '../shared/models';
export interface UVEState {
languages: DotLanguage[];
isEnterprise: boolean;
pageAPIResponse?: DotPageApiResponse;
- pageParams?: DotPageApiParams;
+ pageParams?: DotPageAssetParams;
currentUser?: CurrentUser;
experiment?: DotExperiment;
errorCode?: number;
diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/utils/index.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/utils/index.ts
index fa42f0370dfe..868ae4fea249 100644
--- a/core-web/libs/portlets/edit-ema/portlet/src/lib/utils/index.ts
+++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/utils/index.ts
@@ -12,7 +12,7 @@ import {
} from '@dotcms/dotcms-models';
import { EmaDragItem } from '../edit-ema-editor/components/ema-page-dropzone/types';
-import { DotPageApiKeys, DotPageApiParams } from '../services/dot-page-api.service';
+import { DotPageAssetKeys, DotPageApiParams } from '../services/dot-page-api.service';
import { COMMON_ERRORS, DEFAULT_PERSONA } from '../shared/consts';
import { EDITOR_STATE } from '../shared/enums';
import {
@@ -21,6 +21,7 @@ import {
ContentletDragPayload,
ContentTypeDragPayload,
DotPage,
+ DotPageAssetParams,
DragDatasetItem,
PageContainer
} from '../shared/models';
@@ -594,16 +595,16 @@ export const checkClientHostAccess = (
* @param {Params} params
* @return {*} {DotPageApiParams}
*/
-export function getAllowedPageParams(params: Params): DotPageApiParams {
- const allowedParams: DotPageApiKeys[] = Object.values(DotPageApiKeys);
+export function getAllowedPageParams(params: Params): DotPageAssetParams {
+ const allowedParams: DotPageAssetKeys[] = Object.values(DotPageAssetKeys);
return Object.keys(params)
- .filter((key) => key && allowedParams.includes(key as DotPageApiKeys))
+ .filter((key) => key && allowedParams.includes(key as DotPageAssetKeys))
.reduce((obj, key) => {
obj[key] = params[key];
return obj;
- }, {}) as DotPageApiParams;
+ }, {}) as DotPageAssetParams;
}
/**
diff --git a/core-web/libs/sdk/client/src/index.ts b/core-web/libs/sdk/client/src/index.ts
index ede6358c0df3..338897e46065 100644
--- a/core-web/libs/sdk/client/src/index.ts
+++ b/core-web/libs/sdk/client/src/index.ts
@@ -3,7 +3,8 @@ import { CLIENT_ACTIONS, postMessageToEditor } from './lib/editor/models/client.
import {
CustomClientParams,
DotCMSPageEditorConfig,
- EditorConfig
+ EditorConfig,
+ UVE_MODE
} from './lib/editor/models/editor.model';
import {
InlineEditorData,
@@ -18,7 +19,8 @@ import {
initEditor,
isInsideEditor,
updateNavigation,
- initInlineEditing
+ initInlineEditing,
+ getUVEContext
} from './lib/editor/sdk-editor';
import { getPageRequestParams, graphqlToPageEntity } from './lib/utils';
@@ -26,6 +28,7 @@ export {
graphqlToPageEntity,
getPageRequestParams,
isInsideEditor,
+ getUVEContext,
editContentlet,
reorderMenu,
DotCmsClient,
@@ -42,5 +45,6 @@ export {
initInlineEditing,
InlineEditEventData,
InlineEditorData,
- INLINE_EDITING_EVENT_KEY
+ INLINE_EDITING_EVENT_KEY,
+ UVE_MODE
};
diff --git a/core-web/libs/sdk/client/src/lib/editor/models/editor.model.ts b/core-web/libs/sdk/client/src/lib/editor/models/editor.model.ts
index 07f28b7784be..3848e895d750 100644
--- a/core-web/libs/sdk/client/src/lib/editor/models/editor.model.ts
+++ b/core-web/libs/sdk/client/src/lib/editor/models/editor.model.ts
@@ -66,3 +66,8 @@ export interface ReorderMenuConfig {
*/
depth: number;
}
+
+export enum UVE_MODE {
+ EDIT = 'edit',
+ PREVIEW = 'preview'
+}
diff --git a/core-web/libs/sdk/client/src/lib/editor/sdk-editor.spec.ts b/core-web/libs/sdk/client/src/lib/editor/sdk-editor.spec.ts
index 3e0b9e8ebbdd..ee3a7dc92f9a 100644
--- a/core-web/libs/sdk/client/src/lib/editor/sdk-editor.spec.ts
+++ b/core-web/libs/sdk/client/src/lib/editor/sdk-editor.spec.ts
@@ -5,8 +5,10 @@ import {
scrollHandler
} from './listeners/listeners';
import { postMessageToEditor, CLIENT_ACTIONS } from './models/client.model';
+import { UVE_MODE } from './models/editor.model';
import {
addClassToEmptyContentlets,
+ getUVEContext,
initEditor,
initInlineEditing,
isInsideEditor,
@@ -165,14 +167,28 @@ describe('DotCMSPageEditor', () => {
});
});
- it('should isInsideEditor return false when is preview mode', () => {
- Object.defineProperty(window, 'location', {
- value: {
- search: '?preview=true'
- },
- writable: true
+ describe('getUVEContext', () => {
+ it('should getUVEContext return { mode: UVE_MODE.EDITOR } when is editor mode', () => {
+ expect(getUVEContext()).toEqual({ mode: UVE_MODE.EDIT });
});
- expect(isInsideEditor()).toBe(false);
+ it('should getUVEContext return { mode: UVE_MODE.PREVIEW } when is preview mode', () => {
+ Object.defineProperty(window, 'location', {
+ value: {
+ search: '?editorMode=preview'
+ },
+ writable: true
+ });
+
+ expect(getUVEContext()).toEqual({ mode: UVE_MODE.PREVIEW });
+ });
+
+ it('should getUVEContext return null when is not inside editor', () => {
+ Object.defineProperty(window, 'parent', {
+ value: window,
+ writable: true
+ });
+ expect(getUVEContext()).toBe(null);
+ });
});
});
diff --git a/core-web/libs/sdk/client/src/lib/editor/sdk-editor.ts b/core-web/libs/sdk/client/src/lib/editor/sdk-editor.ts
index 54df6ccae698..6c3cc7515152 100644
--- a/core-web/libs/sdk/client/src/lib/editor/sdk-editor.ts
+++ b/core-web/libs/sdk/client/src/lib/editor/sdk-editor.ts
@@ -6,7 +6,7 @@ import {
subscriptions
} from './listeners/listeners';
import { CLIENT_ACTIONS, INITIAL_DOT_UVE, postMessageToEditor } from './models/client.model';
-import { DotCMSPageEditorConfig, ReorderMenuConfig } from './models/editor.model';
+import { DotCMSPageEditorConfig, UVE_MODE, ReorderMenuConfig } from './models/editor.model';
import { INLINE_EDITING_EVENT_KEY, InlineEditEventData } from './models/inline-event.model';
import { Contentlet } from '../client/content/shared/types';
@@ -110,13 +110,39 @@ export function isInsideEditor(): boolean {
return false;
}
- const preview = isPreviewMode();
+ return window.parent !== window;
+}
- if (preview) {
- return false;
+/**
+ * Detects the current DotCMS Universal View Editor (UVE) context.
+ *
+ * This function determines whether the code is running inside the DotCMS editor
+ * and in which mode (Preview or Editor).
+ *
+ * @returns {Object|null} Returns an object with the detected mode if inside editor,
+ * or null if outside editor
+ * @returns {UVE_MODE} returns.mode - The current editor mode (PREVIEW or EDITOR)
+ *
+ * @example
+ * ```ts
+ * const context = getUVEContext();
+ * if (context) {
+ * console.log(`Running in ${context.mode} mode`);
+ * } else {
+ * console.log('Not running in editor');
+ * }
+ * ```
+ */
+export function getUVEContext() {
+ if (!isInsideEditor()) {
+ return null;
}
- return window.parent !== window;
+ if (isPreviewMode()) {
+ return { mode: UVE_MODE.PREVIEW };
+ }
+
+ return { mode: UVE_MODE.EDIT };
}
export function initDotUVE() {
diff --git a/core-web/libs/sdk/client/src/lib/utils/page/common-utils.spec.ts b/core-web/libs/sdk/client/src/lib/utils/page/common-utils.spec.ts
index 7d1c40c6aa61..79afca346758 100644
--- a/core-web/libs/sdk/client/src/lib/utils/page/common-utils.spec.ts
+++ b/core-web/libs/sdk/client/src/lib/utils/page/common-utils.spec.ts
@@ -36,15 +36,15 @@ describe('Common Utils', () => {
});
describe('Is Preview Mode', () => {
- it('should return true when preview mode is enabled', () => {
+ it('should return true when editorMode is preview', () => {
jest.spyOn(window, 'location', 'get').mockReturnValueOnce({
- search: '?preview=true'
+ search: '?editorMode=preview'
} as Location);
expect(isPreviewMode()).toBe(true);
});
- it('should return false when preview mode is disabled', () => {
+ it('should return false when editorMode is not preview', () => {
jest.spyOn(window, 'location', 'get').mockReturnValueOnce({
search: ''
} as Location);
diff --git a/core-web/libs/sdk/client/src/lib/utils/page/common-utils.ts b/core-web/libs/sdk/client/src/lib/utils/page/common-utils.ts
index b9580fc444be..61c22491a027 100644
--- a/core-web/libs/sdk/client/src/lib/utils/page/common-utils.ts
+++ b/core-web/libs/sdk/client/src/lib/utils/page/common-utils.ts
@@ -1,4 +1,5 @@
import { PageApiOptions } from '../../client/sdk-js-client';
+import { UVE_MODE } from '../../editor/models/editor.model';
/**
* Interface representing the properties for page request parameters.
@@ -70,7 +71,7 @@ export const getPageRequestParams = ({
*/
export const isPreviewMode = (): boolean => {
const queryParams = new URLSearchParams(window.location.search);
- const isPreviewMode = queryParams.get('preview');
+ const editorMode = queryParams.get('editorMode');
- return isPreviewMode === 'true';
+ return editorMode === UVE_MODE.PREVIEW;
};
diff --git a/examples/nextjs/src/components/layout/header/header.js b/examples/nextjs/src/components/layout/header/header.js
index dcc5e5c79a50..adbade90d52e 100644
--- a/examples/nextjs/src/components/layout/header/header.js
+++ b/examples/nextjs/src/components/layout/header/header.js
@@ -1,6 +1,6 @@
'use client';
import Link from 'next/link';
-import { isInsideEditor } from '@dotcms/client';
+import { getUVEContext, UVE_MODE } from '@dotcms/client';
import { useEffect, useState } from 'react';
import ReorderButton from './components/reorderMenu';
@@ -9,7 +9,8 @@ function Header({ children }) {
useEffect(() => {
- setInsideEditor(isInsideEditor());
+ const uveContext = getUVEContext()
+ setInsideEditor(uveContext?.mode === UVE_MODE.EDIT);
}, [])
return (
diff --git a/examples/nextjs/src/components/shared/contentlets.js b/examples/nextjs/src/components/shared/contentlets.js
index 55e057fc6871..be6e7603c7ac 100644
--- a/examples/nextjs/src/components/shared/contentlets.js
+++ b/examples/nextjs/src/components/shared/contentlets.js
@@ -1,7 +1,7 @@
'use client';
import { useMemo } from 'react';
import Image from 'next/image';
-import { editContentlet, isInsideEditor } from '@dotcms/client';
+import { editContentlet, getUVEContext, UVE_MODE } from '@dotcms/client';
const dateFormatOptions = {
year: 'numeric',
@@ -10,7 +10,10 @@ const dateFormatOptions = {
};
function Contentlets({ contentlets }) {
- const insideEditor = useMemo(isInsideEditor, []);
+ const insideEditor = useMemo(() => {
+ const uveContext = getUVEContext();
+ return uveContext?.mode === UVE_MODE.EDIT;
+ }, []);
return (
diff --git a/examples/nextjs/src/hooks/usePageAsset.js b/examples/nextjs/src/hooks/usePageAsset.js
index c67553920274..c46270d8c84f 100644
--- a/examples/nextjs/src/hooks/usePageAsset.js
+++ b/examples/nextjs/src/hooks/usePageAsset.js
@@ -5,7 +5,6 @@ import { CLIENT_ACTIONS, isInsideEditor, postMessageToEditor } from '@dotcms/cli
export const usePageAsset = (currentPageAsset) => {
const [pageAsset, setPageAsset] = useState(null);
-
useEffect(() => {
if (!isInsideEditor()) {
return;