From 20899cc85bf076ccd9d60a9673e42e7f938b5323 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Monroy Date: Sat, 9 Nov 2024 04:52:27 -0400 Subject: [PATCH] feat(edit-content) show files according to host/folder (#30612) ### Parent Issue #30216 ### Proposed Changes This pull request includes several changes across multiple files to enhance the functionality and organization of the `dot-site` and `dot-edit-content-file-field` components. The key changes involve adding a new method to fetch content by folder, refactoring imports for better modularity, and updating UI components for improved user interaction. ### Enhancements to `dot-site.service.ts`: * Added a new interface `ContentByFolderParams` to define parameters for fetching content by folder. * Introduced a new method `getContentByFolder` in `DotSiteService` to fetch content based on the provided parameters. ### Refactoring imports in `dot-edit-content-file-field` components: * Updated import paths to use more specific model files for better modularity in various components, including `dot-file-field-preview.component.ts`, `dot-file-field-ui-message.component.ts`, `dot-form-file-editor.component.ts`, `form-file-editor.store.ts`, `dot-form-import-url.component.spec.ts`, and `form-import-url.store.ts`. [[1]](diffhunk://#diff-9d3250303440cce67e959a59ed17f44ad49d690c04e36cbf4fadc011f206001eL29-R32) [[2]](diffhunk://#diff-fbc6f3568490a6db73eb7a1d91fb7d56c6195f18637a179b3e2e0a9a8971e65bL6-R6) [[3]](diffhunk://#diff-4e07289537a18f138b8657c2505e3416523ee356c698aeb38f3d519a2257dadaL24-R24) [[4]](diffhunk://#diff-5184979843abf3baa538afc8613a1fa040a7907c9614bfb64ddc472c31b4cfa7L13-R13) [[5]](diffhunk://#diff-0d18173fe874af517af41ad1e4422ff7681ba1c2ad4daf75c9806a07fa37e7b2L17-R17) [[6]](diffhunk://#diff-2e100516ca0ff5570c84b4cf072dd03ae77565e92c8c5de65d77505745955651L12-R12) ### UI Improvements in `dot-select-existing-file` components: * Enhanced `dot-dataview.component.html` to include better data binding and UI elements such as loading indicators, search functionality, and improved table structure. [[1]](diffhunk://#diff-f3ba4db0d4c81db7982bda57d151d7f05bbb2151eb78f5ce460ccd71184b4c7dR1-R41) [[2]](diffhunk://#diff-f3ba4db0d4c81db7982bda57d151d7f05bbb2151eb78f5ce460ccd71184b4c7dL27-R66) * Updated `dot-dataview.component.scss` to include styles for new UI elements like truncation and thumbnail display. * Refactored `dot-dataview.component.ts` to use new Angular features like signals and models for state management. * Improved `dot-sidebar.component.html` and `dot-sidebar.component.ts` to handle node selection and state management more effectively. [[1]](diffhunk://#diff-c3ff3590364bc12ded7173d7374838dfab13f67598f80daa198dfd96c935d459L5-R13) [[2]](diffhunk://#diff-b6e7f5a1ad7c221178b3ddf3f3dfc06021ba87b4e18ef9d7f6c6c9a6eab016d3R8-R11) [[3]](diffhunk://#diff-b6e7f5a1ad7c221178b3ddf3f3dfc06021ba87b4e18ef9d7f6c6c9a6eab016d3R22-R23) [[4]](diffhunk://#diff-b6e7f5a1ad7c221178b3ddf3f3dfc06021ba87b4e18ef9d7f6c6c9a6eab016d3R61-R62) [[5]](diffhunk://#diff-b6e7f5a1ad7c221178b3ddf3f3dfc06021ba87b4e18ef9d7f6c6c9a6eab016d3R71-R89) * Updated `dot-select-existing-file.component.html` and `dot-select-existing-file.component.ts` to handle folder loading and content selection more efficiently. [[1]](diffhunk://#diff-485fc35db79ca9cdecc82b1dde0499e2e25f88f07f9ce43a4624fac358ac7848R1-R16) [[2]](diffhunk://#diff-485fc35db79ca9cdecc82b1dde0499e2e25f88f07f9ce43a4624fac358ac7848L20-R27) [[3]](diffhunk://#diff-18bf6e06cc08560259db33d84f324194172c43e47220f07b1de0990cf71077acR13) [[4]](diffhunk://#diff-18bf6e06cc08560259db33d84f324194172c43e47220f07b1de0990cf71077acR41-R42) [[5]](diffhunk://#diff-18bf6e06cc08560259db33d84f324194172c43e47220f07b1de0990cf71077acL66-R70) These changes collectively enhance the functionality, modularity, and user experience of the `dot-site` and `dot-edit-content-file-field` components. ### Checklist - [x] Tests - [x] Translations - [x] Security Implications Contemplated (add notes if applicable) ### Additional Info https://github.com/user-attachments/assets/5f9ed27e-1b74-41f8-aa37-612d96c043fa --- .../src/lib/dot-site/dot-site.service.ts | 27 ++++ .../angular/dotcms-theme/_misc.scss | 6 + .../dot-file-field-preview.component.ts | 5 +- .../dot-file-field-ui-message.component.ts | 3 +- .../dot-form-file-editor.component.ts | 3 +- .../store/form-file-editor.store.ts | 2 +- .../dot-form-import-url.component.spec.ts | 2 +- .../dot-form-import-url.component.ts | 5 +- .../store/form-import-url.store.ts | 2 +- .../dot-dataview/dot-dataview.component.html | 73 ++++++----- .../dot-dataview/dot-dataview.component.scss | 16 ++- .../dot-dataview/dot-dataview.component.ts | 47 ++++--- .../dot-sidebar/dot-sidebar.component.html | 9 +- .../dot-sidebar/dot-sidebar.component.scss | 7 ++ .../dot-sidebar/dot-sidebar.component.ts | 38 +++++- .../dot-select-existing-file.component.html | 21 ++-- .../dot-select-existing-file.component.scss | 9 +- .../dot-select-existing-file.component.ts | 16 ++- .../store/select-existing-file.store.ts | 70 +++++++---- .../dot-edit-content-file-field.component.ts | 4 +- .../dot-edit-content-file-field.const.ts | 2 +- .../dot-edit-content-file-field.stories.ts | 4 +- .../upload-file/upload-file.service.ts | 4 +- .../store/file-field.store.spec.ts | 2 +- .../store/file-field.store.ts | 8 +- .../utils/messages.spec.ts | 2 +- .../utils/messages.ts | 6 +- ...ontent-host-folder-field.component.spec.ts | 118 +++++++----------- .../dot-edit-content-file.model.ts} | 0 ...dit-content-host-folder-field.interface.ts | 8 +- .../lib/services/dot-edit-content.service.ts | 19 +++ .../src/lib/utils/fake-rx-method.ts | 18 --- .../libs/edit-content/src/lib/utils/mocks.ts | 8 ++ core-web/package.json | 2 +- core-web/yarn.lock | 8 +- .../WEB-INF/messages/Language.properties | 3 + 36 files changed, 370 insertions(+), 207 deletions(-) rename core-web/libs/edit-content/src/lib/{fields/dot-edit-content-file-field/models/index.ts => models/dot-edit-content-file.model.ts} (100%) delete mode 100644 core-web/libs/edit-content/src/lib/utils/fake-rx-method.ts diff --git a/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts b/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts index 287299670349..4588b71d36db 100644 --- a/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts +++ b/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts @@ -6,6 +6,7 @@ import { Injectable, inject } from '@angular/core'; import { pluck } from 'rxjs/operators'; import { Site } from '@dotcms/dotcms-js'; +import { DotCMSContentlet } from '@dotcms/dotcms-models'; export interface SiteParams { archived: boolean; @@ -13,6 +14,20 @@ export interface SiteParams { system: boolean; } +export interface ContentByFolderParams { + hostFolderId: string; + showLinks?: boolean; + showDotAssets?: boolean; + showArchived?: boolean; + sortByDesc?: boolean; + showPages?: boolean; + showFiles?: boolean; + showFolders?: boolean; + showWorking?: boolean; + extensions?: string[]; + mimeTypes?: string[]; +} + export const BASE_SITE_URL = '/api/v1/site'; export const DEFAULT_PER_PAGE = 10; @@ -73,4 +88,16 @@ export class DotSiteService { .get<{ entity: Site }>(`${BASE_SITE_URL}/currentSite`) .pipe(pluck('entity')); } + + /** + * Retrieves contentlets from a specified folder. + * + * @param params - Parameters defining the folder and retrieval options. + * @returns An observable emitting an array of `DotCMSContentlet` items. + */ + getContentByFolder(params: ContentByFolderParams) { + return this.#http + .post<{ entity: { list: DotCMSContentlet[] } }>('/api/v1/browser', params) + .pipe(pluck('entity', 'list')); + } } diff --git a/core-web/libs/dotcms-scss/angular/dotcms-theme/_misc.scss b/core-web/libs/dotcms-scss/angular/dotcms-theme/_misc.scss index 9a77aee782b9..31e70d62c688 100644 --- a/core-web/libs/dotcms-scss/angular/dotcms-theme/_misc.scss +++ b/core-web/libs/dotcms-scss/angular/dotcms-theme/_misc.scss @@ -81,3 +81,9 @@ a.p-button { width: $icon-lg-box; font-size: $icon-lg; } + +.truncate-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-preview/dot-file-field-preview.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-preview/dot-file-field-preview.component.ts index f194051b3e88..61754a7bc1df 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-preview/dot-file-field-preview.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-preview/dot-file-field-preview.component.ts @@ -19,6 +19,10 @@ import { catchError } from 'rxjs/operators'; import { DotResourceLinksService } from '@dotcms/data-access'; import { DotCMSBaseTypesContentTypes, DotCMSContentlet } from '@dotcms/dotcms-models'; +import { + DotPreviewResourceLink, + UploadedFile +} from '@dotcms/edit-content/models/dot-edit-content-file.model'; import { DotTempFileThumbnailComponent, DotFileSizeFormatPipe, @@ -26,7 +30,6 @@ import { DotCopyButtonComponent } from '@dotcms/ui'; -import { DotPreviewResourceLink, UploadedFile } from '../../models'; import { getFileMetadata } from '../../utils'; @Component({ diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-ui-message/dot-file-field-ui-message.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-ui-message/dot-file-field-ui-message.component.ts index 3ffdbf45fa3c..4d14177c9958 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-ui-message/dot-file-field-ui-message.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-ui-message/dot-file-field-ui-message.component.ts @@ -1,10 +1,9 @@ import { NgClass } from '@angular/common'; import { ChangeDetectionStrategy, Component, input } from '@angular/core'; +import { UIMessage } from '@dotcms/edit-content/models/dot-edit-content-file.model'; import { DotMessagePipe } from '@dotcms/ui'; -import { UIMessage } from '../../models'; - @Component({ selector: 'dot-file-field-ui-message', standalone: true, diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/dot-form-file-editor.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/dot-form-file-editor.component.ts index edc56360c0ed..43982c895e9e 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/dot-form-file-editor.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/dot-form-file-editor.component.ts @@ -17,12 +17,11 @@ import { InputTextModule } from 'primeng/inputtext'; import { debounceTime, distinctUntilChanged, filter } from 'rxjs/operators'; +import { UploadedFile } from '@dotcms/edit-content/models/dot-edit-content-file.model'; import { DotMessagePipe, DotFieldValidationMessageComponent } from '@dotcms/ui'; import { FormFileEditorStore } from './store/form-file-editor.store'; -import { UploadedFile } from '../../models'; - type DialogProps = { allowFileNameEdit: boolean; userMonacoOptions: Partial; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/store/form-file-editor.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/store/form-file-editor.store.ts index 22dacea3f112..81cb23ba2281 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/store/form-file-editor.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/store/form-file-editor.store.ts @@ -10,7 +10,7 @@ import { switchMap, tap } from 'rxjs/operators'; import { ComponentStatus, DotHttpErrorResponse } from '@dotcms/dotcms-models'; -import { UPLOAD_TYPE, UploadedFile } from '../../../models'; +import { UPLOAD_TYPE, UploadedFile } from '../../../../../models/dot-edit-content-file.model'; import { DotFileFieldUploadService } from '../../../services/upload-file/upload-file.service'; import { extractFileExtension, getInfoByLang } from '../../../utils/editor'; import { DEFAULT_MONACO_CONFIG } from '../dot-form-file-editor.conts'; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.spec.ts index 278f73bdad29..0523ffa7092a 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.spec.ts @@ -9,12 +9,12 @@ import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dy import { DotMessageService } from '@dotcms/data-access'; import { ComponentStatus } from '@dotcms/dotcms-models'; +import { UploadedFile } from '@dotcms/edit-content/models/dot-edit-content-file.model'; import { DotFormImportUrlComponent } from './dot-form-import-url.component'; import { FormImportUrlStore } from './store/form-import-url.store'; import { NEW_FILE_MOCK } from '../../../../utils/mocks'; -import { UploadedFile } from '../../models'; import { DotFileFieldUploadService } from '../../services/upload-file/upload-file.service'; describe('DotFormImportUrlComponent', () => { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.ts index a97121b6daac..9308f75df601 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.ts @@ -12,12 +12,11 @@ import { ButtonModule } from 'primeng/button'; import { DynamicDialogRef, DynamicDialogConfig } from 'primeng/dynamicdialog'; import { InputTextModule } from 'primeng/inputtext'; +import { INPUT_TYPES } from '@dotcms/edit-content/models/dot-edit-content-file.model'; import { DotMessagePipe, DotFieldValidationMessageComponent, DotValidators } from '@dotcms/ui'; import { FormImportUrlStore } from './store/form-import-url.store'; -import { INPUT_TYPE } from '../../../dot-edit-content-text-field/utils'; - @Component({ selector: 'dot-form-import-url', standalone: true, @@ -38,7 +37,7 @@ export class DotFormImportUrlComponent implements OnInit { readonly #formBuilder = inject(FormBuilder); readonly #dialogRef = inject(DynamicDialogRef); readonly #dialogConfig = inject( - DynamicDialogConfig<{ inputType: INPUT_TYPE; acceptedFiles: string[] }> + DynamicDialogConfig<{ inputType: INPUT_TYPES; acceptedFiles: string[] }> ); #abortController: AbortController | null = null; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/store/form-import-url.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/store/form-import-url.store.ts index de1dad6995a7..7648d15dcbdc 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/store/form-import-url.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/store/form-import-url.store.ts @@ -9,7 +9,7 @@ import { switchMap, tap } from 'rxjs/operators'; import { ComponentStatus, DotHttpErrorResponse } from '@dotcms/dotcms-models'; -import { UploadedFile, UPLOAD_TYPE } from '../../../models'; +import { UploadedFile, UPLOAD_TYPE } from '../../../../../models/dot-edit-content-file.model'; import { DotFileFieldUploadService } from '../../../services/upload-file/upload-file.service'; export interface FormImportUrlState { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html index 6fc9ea595e40..91c71784680c 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html @@ -1,19 +1,44 @@ +@let loading = $loading(); +@let data = $data(); +@let rowsPerPage = $rowsPerPage(); + + #datatable + [value]="data" + [loading]="loading" + [paginator]="data.length >= rowsPerPage" + [rows]="rowsPerPage" + selectionMode="single" + dataKey="identifier" + [(selection)]="$selectedProduct" + (onRowSelect)="onRowSelect.emit($event.data)" + [globalFilterFields]="['title', 'modUserName']" + styleClass="flex flex-column h-full justify-content-between"> + + + + {{ 'dot.file.field.dialog.select.existing.file.table.emptymessage' | dm }} + + +
- +
+ + {{ 'dot.file.field.dialog.select.existing.file.table.thumbnail' | dm }} + {{ 'dot.file.field.dialog.select.existing.file.table.title' | dm }} {{ 'dot.file.field.dialog.select.existing.file.table.modified.by' | dm }} @@ -24,31 +49,21 @@ - - -
- - -

{{ content.title }}

+ + +
+
- {{ content.modifiedBy }} - {{ content.lastModified | date }} - - - - - @for (col of columns; track $index) { - - - - } + +

{{ content.title }}

+ + {{ content.modUserName }} + {{ content.modDate | date }}
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.scss index 8acfaa4d306e..adedadaa3b9f 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.scss +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.scss @@ -6,12 +6,24 @@ } ::ng-deep { p-table { - .p-datatable .p-datatable-header { - background-color: $color-palette-gray-100; + .p-datatable { + .p-datatable-header { + background-color: $color-palette-gray-100; + } + .p-datatable-wrapper { + height: 100%; + } } } } +.dataview-thumbnail { + width: 100%; + max-height: 3rem; + overflow: hidden; + position: relative; +} + .file-selector__table_header { th { font-weight: $font-weight-bold; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.ts index f86bbe7e649f..0725fbfed51d 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.ts @@ -1,50 +1,51 @@ -import { DatePipe, NgOptimizedImage } from '@angular/common'; -import { ChangeDetectionStrategy, Component, input } from '@angular/core'; +import { DatePipe } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + CUSTOM_ELEMENTS_SCHEMA, + input, + model, + output, + signal +} from '@angular/core'; import { ButtonModule } from 'primeng/button'; -import { DataViewModule } from 'primeng/dataview'; import { IconFieldModule } from 'primeng/iconfield'; -import { ImageModule } from 'primeng/image'; import { InputIconModule } from 'primeng/inputicon'; import { InputTextModule } from 'primeng/inputtext'; import { SkeletonModule } from 'primeng/skeleton'; import { TableModule } from 'primeng/table'; -import { TagModule } from 'primeng/tag'; +import { DotCMSContentlet } from '@dotcms/dotcms-models'; import { DotMessagePipe } from '@dotcms/ui'; -import { Content } from '../../store/select-existing-file.store'; - @Component({ selector: 'dot-dataview', standalone: true, imports: [ - DataViewModule, - TagModule, ButtonModule, TableModule, IconFieldModule, InputIconModule, InputTextModule, SkeletonModule, - ImageModule, - NgOptimizedImage, DatePipe, DotMessagePipe ], templateUrl: './dot-dataview.component.html', styleUrls: ['./dot-dataview.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class DotDataViewComponent { /** * Represents an observable stream of content data. * - * @type {Observable} + * @type {Observable} * @alias data * @required */ - $data = input.required({ alias: 'data' }); + $data = input.required({ alias: 'data' }); /** * A boolean observable that indicates the loading state. * This is typically used to show or hide a loading indicator in the UI. @@ -52,4 +53,22 @@ export class DotDataViewComponent { * @type {boolean} */ $loading = input.required({ alias: 'loading' }); + + /** + * Signal representing the number of rows per page in the data view. + * + * @type {number} + */ + $rowsPerPage = signal(9); + + /** + * Reactive model holding the currently selected product. + * Can be a `DotCMSContentlet` or `null`. + */ + $selectedProduct = model(null); + + /** + * Emits the selected `DotCMSContentlet` when a row is selected. + */ + onRowSelect = output(); } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html index cd96d04248bc..0db420b65008 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html @@ -2,15 +2,18 @@ @if (!loading) { - {{ node.label | truncatePath | slice: 0 : 18 }} + {{ node.label | truncatePath }} } @else { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.scss index 9c94b8ec9f4d..2b3dc10fc538 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.scss +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.scss @@ -1,4 +1,11 @@ :host { height: 100%; display: flex; + ::ng-deep { + .p-treenode-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts index 17ccc9e90a01..d6170306cf52 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts @@ -1,12 +1,13 @@ import { faker } from '@faker-js/faker'; -import { SlicePipe } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + computed, inject, input, + model, output, signal } from '@angular/core'; @@ -17,10 +18,12 @@ import { TreeModule, TreeNodeExpandEvent } from 'primeng/tree'; import { TruncatePathPipe } from '@dotcms/edit-content/pipes/truncate-path.pipe'; +import { SYSTEM_HOST_ID } from '../../store/select-existing-file.store'; + @Component({ selector: 'dot-sidebar', standalone: true, - imports: [TreeModule, SlicePipe, TruncatePathPipe, SkeletonModule], + imports: [TreeModule, TruncatePathPipe, SkeletonModule], templateUrl: './dot-sidebar.component.html', styleUrls: ['./dot-sidebar.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush @@ -54,6 +57,11 @@ export class DotSideBarComponent { */ $fakeColumns = signal(Array.from({ length: 50 }).map((_) => this.getPercentage())); + /** + * Reactive model representing the currently selected file. + */ + $selectedFile = model(null); + /** * Event emitter for when a tree node is expanded. * @@ -62,6 +70,32 @@ export class DotSideBarComponent { */ onNodeExpand = output(); + /** + * Event emitter for when a node is selected in the tree. + * + * @event onNodeSelect + * @type {TreeNodeExpandEvent} + */ + onNodeSelect = output(); + + /** + * Computed property representing the component's state. + * + * @returns An object containing: + * - `folders`: An array of folders obtained from `$folders()`. + * - `selectedFile`: A signal of the selected file, initialized to the file whose `data.identifier` matches `SYSTEM_HOST_ID`. + */ + $state = computed(() => { + const folders = this.$folders(); + + const selectedFile = folders.find((f) => f.data.identifier === SYSTEM_HOST_ID); + + return { + folders, + selectedFile: signal(selectedFile) + }; + }); + /** * Triggers change detection manually. * This method is used to ensure that the view is updated when the model changes. diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html index bc7e68dda668..8fc8c0b4c8ad 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html @@ -1,13 +1,19 @@ +@let folders = store.folders(); +
-
-
+
+
- +
@@ -17,11 +23,8 @@ [text]="true" severity="primary" /> -
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.scss index 9e493b3997b2..3a1d2cc7f920 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.scss +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.scss @@ -1,15 +1,16 @@ @use "variables" as *; .file-selector { - height: 43rem; + height: 45rem; .file-selector__sidebar { + flex: 0 0 auto; + min-width: 16rem; + width: 25%; border-right: $field-border-size solid $color-palette-gray-400; ::ng-deep { .p-tree { border: 0px; - padding-right: 0px; - padding-top: 0px; - padding-bottom: 0px; + padding: 0px; } } } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts index 050d6f019a89..6b32a1326532 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts @@ -10,6 +10,7 @@ import { import { ButtonModule } from 'primeng/button'; import { DynamicDialogRef } from 'primeng/dynamicdialog'; +import { DotFileFieldUploadService } from '@dotcms/edit-content/fields/dot-edit-content-file-field/services/upload-file/upload-file.service'; import { DotMessagePipe } from '@dotcms/ui'; import { DotDataViewComponent } from './components/dot-dataview/dot-dataview.component'; @@ -37,6 +38,8 @@ export class DotSelectExistingFileComponent implements OnInit { * This store is used to manage the state and actions related to selecting existing files. */ readonly store = inject(SelectExisingFileStore); + + readonly #uploadService = inject(DotFileFieldUploadService); /** * A reference to the dynamic dialog instance. * This is a read-only property that is injected using Angular's dependency injection. @@ -63,8 +66,8 @@ export class DotSelectExistingFileComponent implements OnInit { } ngOnInit() { - this.store.loadContent(); this.store.loadFolders(); + this.store.loadContent(); } /** @@ -77,4 +80,15 @@ export class DotSelectExistingFileComponent implements OnInit { closeDialog(): void { this.#dialogRef.close(); } + + /** + * Retrieves the selected content from the store, fetches it by ID using the upload service, + * and closes the dialog with the retrieved content. + */ + addContent(): void { + const content = this.store.selectedContent(); + this.#uploadService.getContentById(content.identifier).subscribe((content) => { + this.#dialogRef.close(content); + }); + } } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts index e2c2524e31e8..e6fc13147bf5 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts @@ -1,4 +1,3 @@ -import { faker } from '@faker-js/faker'; import { tapResponse } from '@ngrx/component-store'; import { patchState, signalStore, withComputed, withMethods, withState } from '@ngrx/signals'; import { rxMethod } from '@ngrx/signals/rxjs-interop'; @@ -6,20 +5,19 @@ import { pipe } from 'rxjs'; import { computed, inject } from '@angular/core'; -import { TreeNode } from 'primeng/api'; - import { exhaustMap, switchMap, tap } from 'rxjs/operators'; import { ComponentStatus, DotCMSContentlet } from '@dotcms/dotcms-models'; - import { TreeNodeItem, TreeNodeSelectItem -} from '../../../../../models/dot-edit-content-host-folder-field.interface'; -import { DotEditContentService } from '../../../../../services/dot-edit-content.service'; +} from '@dotcms/edit-content/models/dot-edit-content-host-folder-field.interface'; +import { DotEditContentService } from '@dotcms/edit-content/services/dot-edit-content.service'; export const PEER_PAGE_LIMIT = 1000; +export const SYSTEM_HOST_ID = 'SYSTEM_HOST'; + export interface Content { id: string; image: string; @@ -35,11 +33,11 @@ export interface SelectExisingFileState { nodeExpaned: TreeNodeSelectItem['node'] | null; }; content: { - data: Content[]; + data: DotCMSContentlet[]; status: ComponentStatus; }; - selectedFolder: TreeNode | null; - selectedFile: DotCMSContentlet | null; + currentSite: TreeNodeItem | null; + selectedContent: DotCMSContentlet | null; searchQuery: string; viewMode: 'list' | 'grid'; } @@ -54,8 +52,8 @@ const initialState: SelectExisingFileState = { data: [], status: ComponentStatus.INIT }, - selectedFolder: null, - selectedFile: null, + currentSite: null, + selectedContent: null, searchQuery: '', viewMode: 'list' }; @@ -70,25 +68,43 @@ export const SelectExisingFileStore = signalStore( const dotEditContentService = inject(DotEditContentService); return { - loadContent: () => { - const mockContent = faker.helpers.multiple( - () => ({ - id: faker.string.uuid(), - image: faker.image.url(), - title: faker.commerce.productName(), - modifiedBy: faker.internet.displayName(), - lastModified: faker.date.recent() - }), - { count: 100 } - ); - + setSelectedContent: (selectedContent: DotCMSContentlet) => { patchState(store, { - content: { - data: mockContent, - status: ComponentStatus.LOADED - } + selectedContent }); }, + loadContent: rxMethod( + pipe( + tap(() => + patchState(store, { + content: { ...store.content(), status: ComponentStatus.LOADING } + }) + ), + switchMap((event) => { + const content = store.content(); + + let identifier = SYSTEM_HOST_ID; + + if (event) { + identifier = event.node.data.identifier; + } + + return dotEditContentService.getContentByFolder(identifier).pipe( + tapResponse({ + next: (data) => { + patchState(store, { + content: { data, status: ComponentStatus.LOADED } + }); + }, + error: () => + patchState(store, { + content: { ...content, status: ComponentStatus.ERROR } + }) + }) + ); + }) + ) + ), loadFolders: rxMethod( pipe( tap(() => diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.ts index 6f7d14061d31..368a8a454641 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.ts @@ -21,6 +21,7 @@ import { filter, map } from 'rxjs/operators'; import { DotAiService, DotMessageService } from '@dotcms/data-access'; import { DotCMSContentTypeField, DotGeneratedAIImage } from '@dotcms/dotcms-models'; +import { INPUT_TYPES, UploadedFile } from '@dotcms/edit-content/models/dot-edit-content-file.model'; import { DotDropZoneComponent, DotMessagePipe, @@ -35,7 +36,6 @@ import { DotFileFieldUiMessageComponent } from './components/dot-file-field-ui-m import { DotFormFileEditorComponent } from './components/dot-form-file-editor/dot-form-file-editor.component'; import { DotFormImportUrlComponent } from './components/dot-form-import-url/dot-form-import-url.component'; import { DotSelectExistingFileComponent } from './components/dot-select-existing-file/dot-select-existing-file.component'; -import { INPUT_TYPES, UploadedFile } from './models'; import { DotFileFieldUploadService } from './services/upload-file/upload-file.service'; import { FileFieldStore } from './store/file-field.store'; import { getUiMessage } from './utils/messages'; @@ -415,7 +415,7 @@ export class DotEditContentFileFieldComponent implements ControlValueAccessor, O takeUntilDestroyed(this.#destroyRef) ) .subscribe((file) => { - this.store.setPreviewFile(file); + this.store.setPreviewFile({ source: 'contentlet', file }); }); } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.const.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.const.ts index 2cd8dcccd076..24b69514ecc2 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.const.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.const.ts @@ -1,4 +1,4 @@ -import { INPUT_TYPES } from './models'; +import { INPUT_TYPES } from '../../models/dot-edit-content-file.model'; type Actions = { allowExistingFile: boolean; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.stories.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.stories.ts index 30af8811f100..4fcf5647498c 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.stories.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.stories.ts @@ -13,14 +13,14 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { DotMessageService, DotUploadFileService } from '@dotcms/data-access'; import { DotCMSContentTypeField } from '@dotcms/dotcms-models'; +import { UIMessage } from '@dotcms/edit-content/models/dot-edit-content-file.model'; +import { DotEditContentService } from '@dotcms/edit-content/services/dot-edit-content.service'; import { DotEditContentFileFieldComponent } from './dot-edit-content-file-field.component'; -import { UIMessage } from './models'; import { DotFileFieldUploadService } from './services/upload-file/upload-file.service'; import { FileFieldStore } from './store/file-field.store'; import { MessageServiceMock } from './utils/mocks'; -import { DotEditContentService } from '../../services/dot-edit-content.service'; import { BINARY_FIELD_MOCK, FILE_FIELD_MOCK, diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/services/upload-file/upload-file.service.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/services/upload-file/upload-file.service.ts index dc34ea275c7d..e8cd7c65e588 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/services/upload-file/upload-file.service.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/services/upload-file/upload-file.service.ts @@ -7,9 +7,9 @@ import { map, switchMap, tap } from 'rxjs/operators'; import { DotUploadFileService, DotUploadService } from '@dotcms/data-access'; import { DotCMSContentlet, DotCMSTempFile } from '@dotcms/dotcms-models'; +import { UploadedFile, UPLOAD_TYPE } from '@dotcms/edit-content/models/dot-edit-content-file.model'; +import { DotEditContentService } from '@dotcms/edit-content/services/dot-edit-content.service'; -import { DotEditContentService } from '../../../../services/dot-edit-content.service'; -import { UploadedFile, UPLOAD_TYPE } from '../../models'; import { getFileMetadata, getFileVersion, checkMimeType } from '../../utils'; export type UploadFileProps = { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.spec.ts index e52df720c598..8511fa9f2f18 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.spec.ts @@ -5,8 +5,8 @@ import { TestBed, fakeAsync, tick } from '@angular/core/testing'; import { FileFieldStore } from './file-field.store'; +import { UIMessage } from '../../../models/dot-edit-content-file.model'; import { NEW_FILE_MOCK } from '../../../utils/mocks'; -import { UIMessage } from '../models'; import { DotFileFieldUploadService } from '../services/upload-file/upload-file.service'; import { getUiMessage } from '../utils/messages'; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.ts index c5995e7f5c1a..d31d091f2f19 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.ts @@ -7,8 +7,14 @@ import { computed, inject } from '@angular/core'; import { filter, switchMap, tap } from 'rxjs/operators'; +import { + INPUT_TYPES, + FILE_STATUS, + UIMessage, + UploadedFile +} from '@dotcms/edit-content/models/dot-edit-content-file.model'; + import { INPUT_CONFIG } from '../dot-edit-content-file-field.const'; -import { INPUT_TYPES, FILE_STATUS, UIMessage, UploadedFile } from '../models'; import { DotFileFieldUploadService } from '../services/upload-file/upload-file.service'; import { getUiMessage } from '../utils/messages'; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/messages.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/messages.spec.ts index f4406a10887c..9c44e93ac60f 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/messages.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/messages.spec.ts @@ -1,6 +1,6 @@ import { getUiMessage, UiMessageMap } from './messages'; -import { MESSAGES_TYPES } from '../models'; +import { MESSAGES_TYPES } from '../../../models/dot-edit-content-file.model'; describe('getUiMessage function', () => { it('should return the correct uiMessage for a valid key', () => { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/messages.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/messages.ts index b2f3d5fa491b..ce923fd5e552 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/messages.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/messages.ts @@ -1,4 +1,8 @@ -import { MESSAGES_TYPES, UIMessage, UIMessagesMap } from '../models'; +import { + MESSAGES_TYPES, + UIMessage, + UIMessagesMap +} from '../../../models/dot-edit-content-file.model'; export const UiMessageMap: UIMessagesMap = { DEFAULT: { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-host-folder-field/dot-edit-content-host-folder-field.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-host-folder-field/dot-edit-content-host-folder-field.component.spec.ts index 00858e0dd86c..8748b9022d5f 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-host-folder-field/dot-edit-content-host-folder-field.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-host-folder-field/dot-edit-content-host-folder-field.component.spec.ts @@ -1,18 +1,16 @@ -import { Spectator, SpyObject, createComponentFactory } from '@ngneat/spectator/jest'; +import { createFakeEvent } from '@ngneat/spectator'; +import { Spectator, createComponentFactory, mockProvider } from '@ngneat/spectator/jest'; +import { of } from 'rxjs'; -import { signal } from '@angular/core'; +import { fakeAsync, tick } from '@angular/core/testing'; import { ControlContainer, FormGroupDirective } from '@angular/forms'; +import { DotEditContentService } from '@dotcms/edit-content/services/dot-edit-content.service'; import { mockMatchMedia } from '@dotcms/utils-testing'; import { DotEditContentHostFolderFieldComponent } from './dot-edit-content-host-folder-field.component'; import { HostFolderFiledStore } from './store/host-folder-field.store'; -import { - TreeNodeItem, - TreeNodeSelectItem -} from '../../models/dot-edit-content-host-folder-field.interface'; -import { newFakeRxMethod, getRxMethodFake } from '../../utils/fake-rx-method'; import { HOST_FOLDER_TEXT_MOCK, TREE_SELECT_SITES_MOCK, @@ -20,47 +18,30 @@ import { createFormGroupDirectiveMock } from '../../utils/mocks'; -class MockHostFolderFiledStore { - nodeSelected = signal(null); - nodeExpaned = signal(null); - tree = signal([]); - status = signal<'idle' | 'pending' | 'fulfilled' | { error: string }>('idle'); - - iconClasses = signal(['']); - - loadSites = newFakeRxMethod(); - loadChildren = newFakeRxMethod(); - chooseNode = newFakeRxMethod(); -} - -type TypeMock = SpyObject; - describe('DotEditContentHostFolderFieldComponent', () => { let spectator: Spectator; - let component: DotEditContentHostFolderFieldComponent; - let store: TypeMock; + let store: InstanceType; const createComponent = createComponentFactory({ component: DotEditContentHostFolderFieldComponent, componentViewProviders: [ { provide: ControlContainer, useValue: createFormGroupDirectiveMock() } ], - componentProviders: [ - { - provide: HostFolderFiledStore, - useClass: MockHostFolderFiledStore - } + componentProviders: [HostFolderFiledStore], + providers: [ + FormGroupDirective, + mockProvider(DotEditContentService, { + getSitesTreePath: jest.fn(() => of(TREE_SELECT_SITES_MOCK)) + }) ], - providers: [FormGroupDirective], detectChanges: false }); beforeEach(() => { spectator = createComponent(); spectator.setInput('field', { ...HOST_FOLDER_TEXT_MOCK }); - store = spectator.inject(HostFolderFiledStore, true) as unknown as TypeMock; - component = spectator.component; - component.formControl.setValue(null); + store = spectator.inject(HostFolderFiledStore, true); + spectator.component.formControl.setValue(null); mockMatchMedia(); }); @@ -71,15 +52,13 @@ describe('DotEditContentHostFolderFieldComponent', () => { }); it('should show options', () => { - store.tree.set(TREE_SELECT_SITES_MOCK); - const spyloadSites = getRxMethodFake(store.loadSites); - spectator.detectChanges(); + const loadSitesSpy = jest.spyOn(store, 'loadSites'); - const options = component.store.tree(); + spectator.component.ngOnInit(); - expect(options).toBe(TREE_SELECT_SITES_MOCK); - expect(component.$treeSelect().options).toBe(TREE_SELECT_SITES_MOCK); - expect(spyloadSites).toHaveBeenCalled(); + expect(store.tree()).toBe(TREE_SELECT_SITES_MOCK); + expect(spectator.component.$treeSelect().options).toBe(TREE_SELECT_SITES_MOCK); + expect(loadSitesSpy).toHaveBeenCalled(); }); it('should tree selection height and virtual scroll height be the same', async () => { @@ -98,43 +77,42 @@ describe('DotEditContentHostFolderFieldComponent', () => { }); describe('The init value with the root path', () => { - it('should show a root path', () => { - store.tree.set(TREE_SELECT_SITES_MOCK); + it('should show a root path', fakeAsync(() => { const nodeSelected = TREE_SELECT_SITES_MOCK[0]; - store.nodeSelected.set(nodeSelected); - component.formControl.setValue(nodeSelected.key); + spectator.component.formControl.setValue(nodeSelected.key); spectator.detectChanges(); - expect(component.formControl.value).toBe('demo.dotcms.com'); - expect(component.pathControl.value.key).toBe(nodeSelected.key); - expect(component.$treeSelect().value.label).toBe(nodeSelected.label); - }); - }); - describe('The init value with the one levels', () => { - it('should show a path selected', () => { - store.tree.set(TREE_SELECT_MOCK); - const nodeSelected = TREE_SELECT_MOCK[0].children[0]; - store.nodeSelected.set(nodeSelected); - component.formControl.setValue(nodeSelected.label); - spectator.detectChanges(); + tick(50); - expect(component.formControl.value).toBe('demo.dotcms.com/level1/'); - expect(component.pathControl.value.key).toBe(nodeSelected.key); - expect(component.$treeSelect().value.label).toBe(nodeSelected.label); - }); - }); + store.chooseNode({ + originalEvent: createFakeEvent('click'), + node: nodeSelected + }); + + tick(50); + + expect(spectator.component.formControl.value).toBe('demo.dotcms.com:/'); + expect(spectator.component.pathControl.value.key).toBe(nodeSelected.key); + expect(spectator.component.$treeSelect().value.label).toBe(nodeSelected.label); + })); - describe('The init value with the two levels', () => { - it('should show a path selected', () => { - store.tree.set(TREE_SELECT_MOCK); + it('should show a path selected with the two levels', fakeAsync(() => { const nodeSelected = TREE_SELECT_MOCK[0].children[0].children[0]; - store.nodeSelected.set(nodeSelected); - component.formControl.setValue(nodeSelected.label); + spectator.component.formControl.setValue(nodeSelected.label); spectator.detectChanges(); - expect(component.formControl.value).toBe('demo.dotcms.com/level1/child1/'); - expect(component.pathControl.value.key).toBe(nodeSelected.key); - expect(component.$treeSelect().value.label).toBe(nodeSelected.label); - }); + tick(50); + + store.chooseNode({ + originalEvent: createFakeEvent('click'), + node: nodeSelected + }); + + tick(50); + + expect(spectator.component.formControl.value).toBe('demo.dotcms.com:/level1/child1/'); + expect(spectator.component.pathControl.value.key).toBe(nodeSelected.key); + expect(spectator.component.$treeSelect().value.label).toBe(nodeSelected.label); + })); }); }); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/models/index.ts b/core-web/libs/edit-content/src/lib/models/dot-edit-content-file.model.ts similarity index 100% rename from core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/models/index.ts rename to core-web/libs/edit-content/src/lib/models/dot-edit-content-file.model.ts diff --git a/core-web/libs/edit-content/src/lib/models/dot-edit-content-host-folder-field.interface.ts b/core-web/libs/edit-content/src/lib/models/dot-edit-content-host-folder-field.interface.ts index 56ad0d7f5bf4..58369e26e8b9 100644 --- a/core-web/libs/edit-content/src/lib/models/dot-edit-content-host-folder-field.interface.ts +++ b/core-web/libs/edit-content/src/lib/models/dot-edit-content-host-folder-field.interface.ts @@ -1,6 +1,11 @@ import { TreeNode } from 'primeng/api'; -export type TreeNodeData = { type: 'site' | 'folder'; path: string; hostname: string }; +export type TreeNodeData = { + type: 'site' | 'folder'; + path: string; + hostname: string; + identifier: string; +}; export type TreeNodeItem = TreeNode; @@ -22,6 +27,7 @@ export interface TreeNodeSelectEvent { } export interface DotFolder { + identifier: string; hostName: string; path: string; addChildrenAllowed: boolean; diff --git a/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.ts b/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.ts index 10f245b9cb4c..06a39b11a5fd 100644 --- a/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.ts +++ b/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.ts @@ -88,6 +88,7 @@ export class DotEditContentService { key: site.hostname, label: `//${site.hostname}`, data: { + identifier: site.identifier, hostname: `//${site.hostname}`, path: '', type: 'site' @@ -132,6 +133,7 @@ export class DotEditContentService { key: `${folder.hostName}${folder.path}`.replace(/[/]/g, ''), label: `//${folder.hostName}${folder.path}`, data: { + identifier: folder.identifier, hostname: `//${folder.hostName}`, path: folder.path, type: 'folder' @@ -202,6 +204,7 @@ export class DotEditContentService { key: site.hostname, label: `//${site.hostname}`, data: { + identifier: site.identifier, hostname: `//${site.hostname}`, path: '', type: 'site' @@ -223,4 +226,20 @@ export class DotEditContentService { .get<{ entity: { count: number } }>(`/api/v1/content/${identifier}/references/count`) .pipe(map((response) => response.entity.count)); } + + getContentByFolder(folderId: string) { + const params = { + hostFolderId: folderId, + showLinks: false, + showDotAssets: true, + showPages: true, + showFiles: true, + showFolders: false, + showWorking: true, + showArchived: true, + sortByDesc: true + }; + + return this.#siteService.getContentByFolder(params); + } } diff --git a/core-web/libs/edit-content/src/lib/utils/fake-rx-method.ts b/core-web/libs/edit-content/src/lib/utils/fake-rx-method.ts deleted file mode 100644 index c245af5da62e..000000000000 --- a/core-web/libs/edit-content/src/lib/utils/fake-rx-method.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { jest } from '@jest/globals'; -import { rxMethod } from '@ngrx/signals/rxjs-interop'; - -import { tap } from 'rxjs/operators'; - -export const FAKE_RX_METHOD = Symbol('FAKE_RX_METHOD'); - -export function newFakeRxMethod() { - const f = jest.fn(); - const r = rxMethod(tap((x) => f(x))); - r[FAKE_RX_METHOD] = f; - - return r; -} - -export function getRxMethodFake(rxMethod) { - return rxMethod[FAKE_RX_METHOD]; -} diff --git a/core-web/libs/edit-content/src/lib/utils/mocks.ts b/core-web/libs/edit-content/src/lib/utils/mocks.ts index 221a569c8c9b..8d2cde606293 100644 --- a/core-web/libs/edit-content/src/lib/utils/mocks.ts +++ b/core-web/libs/edit-content/src/lib/utils/mocks.ts @@ -1303,6 +1303,7 @@ export const TREE_SELECT_SITES_MOCK: TreeNodeItem[] = [ key: 'demo.dotcms.com', label: 'demo.dotcms.com', data: { + identifier: 'demo.dotcms.com', hostname: 'demo.dotcms.com', path: '', type: 'site' @@ -1314,6 +1315,7 @@ export const TREE_SELECT_SITES_MOCK: TreeNodeItem[] = [ key: 'nico.dotcms.com', label: 'nico.dotcms.com', data: { + identifier: 'nico.dotcms.com', hostname: 'nico.dotcms.com', path: '', type: 'site' @@ -1325,6 +1327,7 @@ export const TREE_SELECT_SITES_MOCK: TreeNodeItem[] = [ key: 'System Host', label: 'System Host', data: { + identifier: 'System Host', hostname: 'System Host', path: '', type: 'site' @@ -1339,6 +1342,7 @@ export const TREE_SELECT_MOCK: TreeNodeItem[] = [ key: 'demo.dotcms.com', label: 'demo.dotcms.com', data: { + identifier: 'demo.dotcms.com', hostname: 'demo.dotcms.com', path: '', type: 'site' @@ -1350,6 +1354,7 @@ export const TREE_SELECT_MOCK: TreeNodeItem[] = [ key: 'demo.dotcms.comlevel1', label: 'demo.dotcms.com/level1/', data: { + identifier: 'demo.dotcms.comlevel1', hostname: 'demo.dotcms.com', path: '/level1/', type: 'folder' @@ -1361,6 +1366,7 @@ export const TREE_SELECT_MOCK: TreeNodeItem[] = [ key: 'demo.dotcms.comlevel1child1', label: 'demo.dotcms.com/level1/child1/', data: { + identifier: 'demo.dotcms.comlevel1child1', hostname: 'demo.dotcms.com', path: '/level1/child1/', type: 'folder' @@ -1374,6 +1380,7 @@ export const TREE_SELECT_MOCK: TreeNodeItem[] = [ key: 'demo.dotcms.comlevel2', label: 'demo.dotcms.com/level2/', data: { + identifier: 'demo.dotcms.comlevel2', hostname: 'demo.dotcms.com', path: '/level2/', type: 'folder' @@ -1387,6 +1394,7 @@ export const TREE_SELECT_MOCK: TreeNodeItem[] = [ key: 'nico.dotcms.com', label: 'nico.dotcms.com', data: { + identifier: 'nico.dotcms.com', hostname: 'nico.dotcms.com', path: '', type: 'site' diff --git a/core-web/package.json b/core-web/package.json index 0ed2eb181bc2..7872f940db08 100644 --- a/core-web/package.json +++ b/core-web/package.json @@ -115,7 +115,7 @@ "node-fetch": "^2.6.1", "primeflex": "3.3.1", "primeicons": "7.0.0", - "primeng": "17.18.9", + "primeng": "17.18.11", "prosemirror-model": "^1.16.1", "prosemirror-view": "^1.23.12", "react": "^18.2.0", diff --git a/core-web/yarn.lock b/core-web/yarn.lock index 698361a2cd83..5a303d59070f 100644 --- a/core-web/yarn.lock +++ b/core-web/yarn.lock @@ -18117,10 +18117,10 @@ primeicons@7.0.0: resolved "https://registry.yarnpkg.com/primeicons/-/primeicons-7.0.0.tgz#6b25c3fdcb29bb745a3035bdc1ed5902f4a419cf" integrity sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw== -primeng@17.18.9: - version "17.18.9" - resolved "https://registry.yarnpkg.com/primeng/-/primeng-17.18.9.tgz#dc3b65689548b53ce07ce7032a0aa3d1eb5dfbea" - integrity sha512-1FT0B8wtgvs/joduB1DDOLe2IsP1pegOiEfSPAHSbc6otgNx/6iLR0k2M/xr2c9Ur1aC7tAikkVfH3FGpWof3w== +primeng@17.18.11: + version "17.18.11" + resolved "https://registry.yarnpkg.com/primeng/-/primeng-17.18.11.tgz#8e5b1b921b303767611f31a356ee972e78ccc98e" + integrity sha512-LzV0fFZmb3GdnaRqi1+GP+RPtW0a+jztL5pH1zRWY7+7pyQ0n1YNyTXzmqVcdks/CmoyjNhutWEmexwi6vFVeA== dependencies: tslib "^2.3.0" diff --git a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties index 53ceab023a81..ccb22318d995 100644 --- a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties +++ b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties @@ -1191,6 +1191,9 @@ dot.file.field.action.import.from.url=Import from URL dot.file.field.action.import.from.url.error.message=The URL you requested is not valid. Please try again. dot.file.field.action.remove=Remove dot.file.field.dialog.select.existing.file.header=Select Existing File +dot.file.field.dialog.select.existing.file.table.emptymessage=No content found +dot.file.field.dialog.select.existing.file.table.thumbnail=Thumbnail +dot.file.field.dialog.select.existing.file.table.search=Search content dot.file.field.dialog.select.existing.file.table.title=Title dot.file.field.dialog.select.existing.file.table.modified.by=Modified by dot.file.field.dialog.select.existing.file.table.last.modified=Last Modified