diff --git a/core-web/libs/dotcms-models/src/lib/dot-content-state.model.ts b/core-web/libs/dotcms-models/src/lib/dot-content-state.model.ts index e9941fdfd2cd..5331577a9db0 100644 --- a/core-web/libs/dotcms-models/src/lib/dot-content-state.model.ts +++ b/core-web/libs/dotcms-models/src/lib/dot-content-state.model.ts @@ -6,5 +6,5 @@ export interface DotContentState { deleted?: string | boolean; live: string | boolean; working: string | boolean; - hasLiveVersion: string | boolean; + hasLiveVersion?: string | boolean; } diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-information/dot-edit-content-sidebar-information.component.html b/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-information/dot-edit-content-sidebar-information.component.html index dc2af9807517..f249c93e1f18 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-information/dot-edit-content-sidebar-information.component.html +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-information/dot-edit-content-sidebar-information.component.html @@ -6,9 +6,7 @@
- @if (contentlet | contentletStatusTag; as status) { - - } + @if (contentlet?.inode) { { const mockContentlet = { inode: '123', + identifier: 'id-123', + hasLiveVersion: true, live: true, working: false, archived: false, @@ -49,11 +49,10 @@ describe('DotEditContentSidebarInformationComponent', () => { component: DotEditContentSidebarInformationComponent, imports: [ RouterTestingModule, - TagModule, SkeletonModule, TooltipModule, DotNameFormatPipe, - ContentletStatusTagPipe, + DotContentletStatusChipComponent, DotRelativeDatePipe, DotMessagePipe ], @@ -90,9 +89,8 @@ describe('DotEditContentSidebarInformationComponent', () => { spectator.detectChanges(); }); - it('should show status tag', () => { - const tag = spectator.query('p-tag'); - expect(tag).toBeTruthy(); + it('should show contentlet status chip', () => { + expect(spectator.query('dot-contentlet-status-chip')).toBeTruthy(); }); it('should show json link', () => { @@ -138,10 +136,8 @@ describe('DotEditContentSidebarInformationComponent', () => { spectator.detectChanges(); }); - it('should show New status tag for new contentlet', () => { - const tag = spectator.query('p-tag'); - expect(tag).toBeTruthy(); - expect(tag?.textContent).toContain('New'); + it('should show status chip when contentlet is null', () => { + expect(spectator.query('dot-contentlet-status-chip')).toBeTruthy(); }); it('should not show json link', () => { diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-information/dot-edit-content-sidebar-information.component.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-information/dot-edit-content-sidebar-information.component.ts index 449fbff09371..3f3cbab48807 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-information/dot-edit-content-sidebar-information.component.ts +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-information/dot-edit-content-sidebar-information.component.ts @@ -12,21 +12,19 @@ import { RouterLink } from '@angular/router'; import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog'; import { SkeletonModule } from 'primeng/skeleton'; -import { TagModule } from 'primeng/tag'; import { TooltipModule } from 'primeng/tooltip'; import { DotMessageService } from '@dotcms/data-access'; import { DotCMSContentlet, DotCMSContentType } from '@dotcms/dotcms-models'; -import { DotMessagePipe, DotRelativeDatePipe } from '@dotcms/ui'; +import { DotContentletStatusChipComponent, DotMessagePipe, DotRelativeDatePipe } from '@dotcms/ui'; import { DotEditContentSidebarReferencesDialogComponent } from './dot-edit-content-sidebar-references-dialog/dot-edit-content-sidebar-references-dialog.component'; import { DotReferencesDialogData } from '../../../../models/dot-edit-content.model'; -import { ContentletStatusTagPipe } from '../../../../pipes/contentlet-status-tag.pipe'; import { DotNameFormatPipe } from '../../../../pipes/name-format.pipe'; interface ContentSidebarInformation { - contentlet: DotCMSContentlet; + contentlet: DotCMSContentlet | null; contentType: DotCMSContentType; loading: boolean; referencesPageCount: string; @@ -41,8 +39,7 @@ interface ContentSidebarInformation { RouterLink, TooltipModule, SkeletonModule, - TagModule, - ContentletStatusTagPipe, + DotContentletStatusChipComponent, DotRelativeDatePipe, DotMessagePipe, DotNameFormatPipe, @@ -66,7 +63,9 @@ export class DotEditContentSidebarInformationComponent { readonly $data = input.required({ alias: 'data' }); /** URL to fetch the contentlet as JSON via the REST API. */ - readonly $jsonUrl = computed(() => `/api/v1/content/${this.$data().contentlet.identifier}`); + readonly $jsonUrl = computed( + () => `/api/v1/content/${this.$data().contentlet?.identifier ?? ''}` + ); /** Tooltip message shown when the contentlet has no creation date yet. */ readonly $createdTooltipMessage = computed(() => { diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-untranslated-locale/dot-edit-content-sidebar-untranslated-locale.component.html b/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-untranslated-locale/dot-edit-content-sidebar-untranslated-locale.component.html deleted file mode 100644 index 5b43d14b5718..000000000000 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-untranslated-locale/dot-edit-content-sidebar-untranslated-locale.component.html +++ /dev/null @@ -1,30 +0,0 @@ -

{{ 'edit.content.sidebar.locales.untranslated.text' | dm }}

-
- - -
-
- - -
- -
- - -
diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-untranslated-locale/dot-edit-content-sidebar-untranslated-locale.component.scss b/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-untranslated-locale/dot-edit-content-sidebar-untranslated-locale.component.scss deleted file mode 100644 index 157e54695030..000000000000 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-untranslated-locale/dot-edit-content-sidebar-untranslated-locale.component.scss +++ /dev/null @@ -1,16 +0,0 @@ -@use "../../../../../../../dotcms-scss/shared/spacing"; - -@use "variables" as *; - -:host { - display: flex; - flex-direction: column; - gap: spacing.$spacing-1; - - .untranslated-locale__actions { - margin-top: spacing.$spacing-3; - display: flex; - gap: spacing.$spacing-2; - justify-content: flex-end; - } -} diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-untranslated-locale/dot-edit-content-sidebar-untranslated-locale.component.spec.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-untranslated-locale/dot-edit-content-sidebar-untranslated-locale.component.spec.ts deleted file mode 100644 index 44e22bb48876..000000000000 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-untranslated-locale/dot-edit-content-sidebar-untranslated-locale.component.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Spectator, createComponentFactory, byTestId } from '@ngneat/spectator/jest'; - -import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; - -import { ButtonDirective } from 'primeng/button'; -import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; -import { RadioButtonModule } from 'primeng/radiobutton'; - -import { DotMessageService } from '@dotcms/data-access'; -import { DotMessagePipe } from '@dotcms/ui'; -import { MockDotMessageService } from '@dotcms/utils-testing'; - -import { DotEditContentSidebarUntranslatedLocaleComponent } from './dot-edit-content-sidebar-untranslated-locale.component'; - -const messageServiceMock = new MockDotMessageService({ - 'edit.content.sidebar.locales.untranslated.populate': 'Populate from Current Locale' -}); - -describe('DotEditContentSidebarUntranslatedLocaleComponent', () => { - let spectator: Spectator; - const createComponent = createComponentFactory({ - component: DotEditContentSidebarUntranslatedLocaleComponent, - imports: [CommonModule, RadioButtonModule, FormsModule, ButtonDirective, DotMessagePipe], - providers: [ - { provide: DynamicDialogRef, useValue: { close: jest.fn() } }, - { - provide: DynamicDialogConfig, - useValue: { data: { currentLocale: { isoCode: 'en-us' } } } - }, - { - provide: DotMessageService, - useValue: messageServiceMock - } - ] - }); - - beforeEach(() => { - spectator = createComponent(); - }); - - it('should close dialog with selected option on continue button click', () => { - const continueButton = spectator.query(byTestId('continue-button')); - const dialogRefCloseSpy = jest.spyOn(spectator.component.dialogRef, 'close'); - - spectator.click(continueButton); - - expect(dialogRefCloseSpy).toHaveBeenCalledWith(spectator.component.selectedOption); - }); - - it('should close dialog on cancel button click', () => { - const cancelButton = spectator.query(byTestId('cancel-button')); - const dialogRefCloseSpy = jest.spyOn(spectator.component.dialogRef, 'close'); - - spectator.click(cancelButton); - - expect(dialogRefCloseSpy).toHaveBeenCalledWith(); - }); - - it('should display the correct label for the populate radio button', () => { - expect(spectator.query(byTestId('populate-label')).textContent).toContain( - 'Populate from Current Locale en-US' - ); - }); -}); diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-untranslated-locale/dot-edit-content-sidebar-untranslated-locale.component.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-untranslated-locale/dot-edit-content-sidebar-untranslated-locale.component.ts deleted file mode 100644 index ad8406608dad..000000000000 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-untranslated-locale/dot-edit-content-sidebar-untranslated-locale.component.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; -import { FormsModule } from '@angular/forms'; - -import { ButtonDirective } from 'primeng/button'; -import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; -import { RadioButtonModule } from 'primeng/radiobutton'; - -import { DotIsoCodePipe, DotMessagePipe } from '@dotcms/ui'; - -@Component({ - selector: 'dot-edit-content-sidebar-untranslated-locale', - imports: [RadioButtonModule, DotMessagePipe, FormsModule, ButtonDirective, DotIsoCodePipe], - templateUrl: './dot-edit-content-sidebar-untranslated-locale.component.html', - styleUrl: './dot-edit-content-sidebar-untranslated-locale.component.scss', - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class DotEditContentSidebarUntranslatedLocaleComponent { - selectedOption = 'populate'; - - dialogRef = inject(DynamicDialogRef); - config = inject(DynamicDialogConfig); -} diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/dot-edit-content-sidebar.component.html b/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/dot-edit-content-sidebar.component.html index 53dba168b327..24a8ffc0fd67 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/dot-edit-content-sidebar.component.html +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/dot-edit-content-sidebar.component.html @@ -16,17 +16,29 @@ data-testId="sidebar-tabs"> - + - + - + @if (!isNew) { - + } diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/dot-edit-content-sidebar.component.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/dot-edit-content-sidebar.component.ts index 0ad84a7fd216..074bb7347172 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/dot-edit-content-sidebar.component.ts +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/dot-edit-content-sidebar.component.ts @@ -14,6 +14,7 @@ import { ConfirmDialogModule } from 'primeng/confirmdialog'; import { DialogModule } from 'primeng/dialog'; import { SelectModule } from 'primeng/select'; import { TabsModule } from 'primeng/tabs'; +import { TooltipModule } from 'primeng/tooltip'; import { DotCMSBaseTypesContentTypes } from '@dotcms/dotcms-models'; import { DotCopyButtonComponent, DotMessagePipe } from '@dotcms/ui'; @@ -47,6 +48,7 @@ import { DotEditContentStore } from '../../store/edit-content.store'; DotEditContentSidebarInformationComponent, DotEditContentSidebarWorkflowComponent, TabsModule, + TooltipModule, DotEditContentSidebarSectionComponent, DotCopyButtonComponent, ConfirmDialogModule, diff --git a/core-web/libs/edit-content/src/lib/pipes/contentlet-status-tag.pipe.spec.ts b/core-web/libs/edit-content/src/lib/pipes/contentlet-status-tag.pipe.spec.ts deleted file mode 100644 index 7a45d6520436..000000000000 --- a/core-web/libs/edit-content/src/lib/pipes/contentlet-status-tag.pipe.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { DotMessageService } from '@dotcms/data-access'; -import { DotCMSContentlet } from '@dotcms/dotcms-models'; - -import { ContentletStatusTagPipe } from './contentlet-status-tag.pipe'; - -describe('ContentletStatusTagPipe', () => { - let pipe: ContentletStatusTagPipe; - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - ContentletStatusTagPipe, - { provide: DotMessageService, useValue: { get: (arg: string) => arg } } - ] - }); - pipe = TestBed.inject(ContentletStatusTagPipe); - }); - - it('should return Published with success severity', () => { - const contentlet = { live: true, working: false, archived: false } as DotCMSContentlet; - const result = pipe.transform(contentlet); - expect(result).toEqual({ label: 'Published', severity: 'success' }); - }); - - it('should return Draft with secondary severity', () => { - const contentlet = { live: false, working: true, archived: false } as DotCMSContentlet; - const result = pipe.transform(contentlet); - expect(result).toEqual({ label: 'Draft', severity: 'secondary' }); - }); - - it('should return Changed with warn severity', () => { - const contentlet = { - live: true, - working: true, - workingInode: '1', - liveInode: '2', - archived: false - } as DotCMSContentlet; - const result = pipe.transform(contentlet); - expect(result).toEqual({ label: 'Changed', severity: 'warn' }); - }); - - it('should return Archived with contrast severity', () => { - const contentlet = { live: false, working: false, archived: true } as DotCMSContentlet; - const result = pipe.transform(contentlet); - expect(result).toEqual({ label: 'Archived', severity: 'contrast' }); - }); - - it('should return null for unknown status', () => { - const contentlet = { live: false, working: false, archived: false } as DotCMSContentlet; - const result = pipe.transform(contentlet); - expect(result).toBeNull(); - }); - - it('should return New with info severity for undefined contentlet', () => { - const result = pipe.transform(undefined); - expect(result).toEqual({ label: 'New', severity: 'info' }); - }); -}); diff --git a/core-web/libs/edit-content/src/lib/pipes/contentlet-status-tag.pipe.ts b/core-web/libs/edit-content/src/lib/pipes/contentlet-status-tag.pipe.ts deleted file mode 100644 index 8102a5dc4fc4..000000000000 --- a/core-web/libs/edit-content/src/lib/pipes/contentlet-status-tag.pipe.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Pipe, PipeTransform, inject } from '@angular/core'; - -import { DotMessageService } from '@dotcms/data-access'; -import { DotCMSContentlet } from '@dotcms/dotcms-models'; - -import { DotEditContentStatus } from '../models/dot-edit-content-status.enum'; - -/** PrimeNG Tag severity for p-tag */ -export type ContentletStatusTagSeverity = - | 'success' - | 'secondary' - | 'info' - | 'warn' - | 'danger' - | 'contrast'; - -export interface ContentletStatusTagResult { - label: string; - severity: ContentletStatusTagSeverity; -} - -const STATUS_TAG_CONFIG: Partial< - Record -> = { - [DotEditContentStatus.PUBLISHED]: { label: 'Published', severity: 'success' }, - [DotEditContentStatus.DRAFT]: { label: 'Draft', severity: 'secondary' }, - [DotEditContentStatus.CHANGED]: { label: 'Changed', severity: 'warn' }, - [DotEditContentStatus.ARCHIVED]: { label: 'Archived', severity: 'contrast' }, - [DotEditContentStatus.UNKNOWN]: { label: '', severity: 'secondary' } -}; - -/** - * Pipe that returns contentlet status as label + PrimeNG Tag severity for use with p-tag. - * Returns null when there is no label to show (e.g. UNKNOWN). - */ -@Pipe({ - name: 'contentletStatusTag' -}) -export class ContentletStatusTagPipe implements PipeTransform { - private readonly dotMessage = inject(DotMessageService); - - transform(contentlet?: DotCMSContentlet): ContentletStatusTagResult | null { - if (!contentlet) { - return { - label: this.dotMessage.get('New'), - severity: 'info' - }; - } - - const status = this.getContentletStatus(contentlet); - const config = STATUS_TAG_CONFIG[status]; - const label = this.dotMessage.get(config.label); - - return label ? { label, severity: config.severity } : null; - } - - private getContentletStatus(contentlet: DotCMSContentlet): DotEditContentStatus { - if (contentlet.archived) { - return DotEditContentStatus.ARCHIVED; - } - if (contentlet.live) { - return contentlet.workingInode === contentlet.liveInode - ? DotEditContentStatus.PUBLISHED - : DotEditContentStatus.CHANGED; - } - if (contentlet.working) { - return DotEditContentStatus.DRAFT; - } - return DotEditContentStatus.UNKNOWN; - } -} diff --git a/core-web/libs/edit-content/src/lib/store/features/locales/locales.feature.spec.ts b/core-web/libs/edit-content/src/lib/store/features/locales/locales.feature.spec.ts index 9d2c56214317..07db509dc8ee 100644 --- a/core-web/libs/edit-content/src/lib/store/features/locales/locales.feature.spec.ts +++ b/core-web/libs/edit-content/src/lib/store/features/locales/locales.feature.spec.ts @@ -181,7 +181,7 @@ describe('LocalesFeature', () => { onClose: of('populate') } as DynamicDialogRef); - tick(); + spectator.flushEffects(); store.switchLocale(MOCK_LANGUAGES[2]); tick(); @@ -209,7 +209,7 @@ describe('LocalesFeature', () => { onClose: of('manual') } as DynamicDialogRef); - tick(); + spectator.flushEffects(); store.switchLocale(MOCK_LANGUAGES[2]); tick(); diff --git a/core-web/libs/edit-content/src/lib/store/features/locales/locales.feature.ts b/core-web/libs/edit-content/src/lib/store/features/locales/locales.feature.ts index 522b4dcafa4f..2ab13488f97f 100644 --- a/core-web/libs/edit-content/src/lib/store/features/locales/locales.feature.ts +++ b/core-web/libs/edit-content/src/lib/store/features/locales/locales.feature.ts @@ -32,8 +32,12 @@ import { DotContentletDepths, DotLanguage } from '@dotcms/dotcms-models'; +import { + BINARY_OPTION, + BinaryOptionDialogData, + DotBinaryOptionSelectorComponent +} from '@dotcms/ui'; -import { DotEditContentSidebarUntranslatedLocaleComponent } from '../../../components/dot-edit-content-sidebar/components/dot-edit-content-sidebar-untranslated-locale/dot-edit-content-sidebar-untranslated-locale.component'; import { DotEditContentService } from '../../../services/dot-edit-content.service'; import { prepareContentletForCopy, @@ -241,19 +245,53 @@ export function withLocales() { }) ); } else { - const ref = dialogService.open( - DotEditContentSidebarUntranslatedLocaleComponent, - { - header: dotMessageService.get( - 'edit.content.sidebar.locales.untranslated.locale' + const currentLocale = store.currentLocale(); + if (!currentLocale) return of(null); + + const languageLabel = currentLocale.countryCode + ? `${currentLocale.language} (${currentLocale.countryCode})` + : currentLocale.language; + + const options: BINARY_OPTION = { + option1: { + value: 'populate', + label: dotMessageService.get( + 'edit.content.sidebar.locales.untranslated.populate', + languageLabel + ), + message: dotMessageService.get( + 'edit.content.sidebar.locales.untranslated.populate.message', + languageLabel ), - width: '35rem', - data: { - currentLocale: store.currentLocale() - }, - modal: true + buttonLabel: 'edit.content.sidebar.locales.continue' + }, + option2: { + value: 'manual', + label: dotMessageService.get( + 'edit.content.sidebar.locales.untranslated.manually' + ), + message: dotMessageService.get( + 'edit.content.sidebar.locales.untranslated.manually.message' + ), + buttonLabel: 'edit.content.sidebar.locales.continue' } - ); + }; + + const ref = dialogService.open(DotBinaryOptionSelectorComponent, { + header: dotMessageService.get( + 'edit.content.sidebar.locales.untranslated.locale' + ), + width: '35rem', + contentStyle: { padding: '0' }, + closable: true, + closeOnEscape: true, + modal: true, + data: { + options, + description: + 'edit.content.sidebar.locales.untranslated.text' + } satisfies BinaryOptionDialogData + }); ref.onClose .pipe( diff --git a/core-web/libs/ui/src/lib/components/dot-binary-option-selector/dot-binary-option-selector.component.html b/core-web/libs/ui/src/lib/components/dot-binary-option-selector/dot-binary-option-selector.component.html index a4cb0c976067..14134444bda0 100644 --- a/core-web/libs/ui/src/lib/components/dot-binary-option-selector/dot-binary-option-selector.component.html +++ b/core-web/libs/ui/src/lib/components/dot-binary-option-selector/dot-binary-option-selector.component.html @@ -1,3 +1,6 @@ +@if (description) { +

{{ description | dm }}

+}