From 8faa841da7919e83a60fd64a4139dac2efcb0e72 Mon Sep 17 00:00:00 2001 From: Arcadio Quintero Date: Mon, 8 Jul 2024 11:23:56 -0400 Subject: [PATCH 01/17] feat(edit-content) add search to category-field #28879 --- .../src/lib/dot-categories.model.ts | 5 + ...-category-field-search-list.component.html | 41 ++++++++ ...-category-field-search-list.component.scss | 11 +++ ...tegory-field-search-list.component.spec.ts | 22 +++++ ...ot-category-field-search-list.component.ts | 68 +++++++++++++ .../dot-category-field-search.component.html | 21 ++++ .../dot-category-field-search.component.scss | 14 +++ ...ot-category-field-search.component.spec.ts | 22 +++++ .../dot-category-field-search.component.ts | 84 ++++++++++++++++ .../dot-category-field-sidebar.component.html | 20 +++- .../dot-category-field-sidebar.component.scss | 1 - .../dot-category-field-sidebar.component.ts | 30 +++++- .../dot-table-skeleton.component.html | 27 ++++++ .../dot-table-skeleton.component.scss | 8 ++ .../dot-table-skeleton.component.ts | 37 +++++++ .../models/dot-category-field.models.ts | 5 + .../services/categories.service.ts | 63 ++++++++++-- .../store/content-category-field.store.ts | 97 ++++++++++++++++--- .../utils/category-field.utils.ts | 53 ++++++++++ .../WEB-INF/messages/Language.properties | 6 ++ 20 files changed, 608 insertions(+), 27 deletions(-) create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.html create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.scss create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.spec.ts create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.ts create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.scss create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.spec.ts create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.ts create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.html create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.scss create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.ts diff --git a/core-web/libs/dotcms-models/src/lib/dot-categories.model.ts b/core-web/libs/dotcms-models/src/lib/dot-categories.model.ts index 1ce0bc233f5a..f23a57952775 100644 --- a/core-web/libs/dotcms-models/src/lib/dot-categories.model.ts +++ b/core-web/libs/dotcms-models/src/lib/dot-categories.model.ts @@ -29,4 +29,9 @@ export interface DotCategory { parentPermissionable?: { hostname: string; }; + parentList?: Array<{ + categoryName: string; + key: string; + inode: string; + }>; } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.html new file mode 100644 index 000000000000..8a4a8368eb1f --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.html @@ -0,0 +1,41 @@ +
+ @if (!isLoading()) { + + + + + + + {{ 'edit.content.category-field.search.name' | dm }} + {{ 'edit.content.category-field.search.assignee' | dm }} + + + + + + + + + {{ category.categoryName }} + {{ category.path }} + + + + } @else { + + + + } +
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.scss new file mode 100644 index 000000000000..12bf59a0ae80 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.scss @@ -0,0 +1,11 @@ +.category-field__search-list { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; +} + +::ng-deep .dotTable.p-datatable > .p-datatable-wrapper { + border-radius: 0; + border: none; +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.spec.ts new file mode 100644 index 000000000000..26ec9cf9ab65 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DotCategoryFieldSearchListComponent } from './dot-category-field-search-list.component'; + +describe('DotCategoryFieldSearchListComponent', () => { + let component: DotCategoryFieldSearchListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DotCategoryFieldSearchListComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(DotCategoryFieldSearchListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.ts new file mode 100644 index 000000000000..ade87e0662b5 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.ts @@ -0,0 +1,68 @@ +import { CommonModule } from '@angular/common'; +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + DestroyRef, + ElementRef, + EventEmitter, + inject, + input, + Output, + signal, + ViewChild +} from '@angular/core'; + +import { SkeletonModule } from 'primeng/skeleton'; +import { TableModule } from 'primeng/table'; + +import { DotMessagePipe } from '@dotcms/ui'; + +import { + DotCategoryFieldCategory, + DotCategoryFieldCategorySearchedItems +} from '../../models/dot-category-field.models'; +import { DotTableSkeletonComponent } from '../dot-table-skeleton/dot-table-skeleton.component'; + +@Component({ + selector: 'dot-category-field-search-list', + standalone: true, + imports: [CommonModule, TableModule, SkeletonModule, DotTableSkeletonComponent, DotMessagePipe], + templateUrl: './dot-category-field-search-list.component.html', + styleUrl: './dot-category-field-search-list.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DotCategoryFieldSearchListComponent implements AfterViewInit { + @ViewChild('tableContainer', { static: false }) tableContainer!: ElementRef; + scrollHeight = signal('0px'); + + /** + * Represents the required categories input for DotCategoryFieldCategory. + * + * @typedef {DotCategoryFieldCategory[]} RequiredCategories + */ + categories = input.required(); + + @Output() selected = new EventEmitter(); + + /** + * Represents the selected categories in the DotCategoryFieldCategory class. + */ + selectedCategories: DotCategoryFieldCategory; + isLoading = input.required(); + #destroyRef = inject(DestroyRef); + + ngAfterViewInit(): void { + this.calculateScrollHeight(); + window.addEventListener('resize', this.calculateScrollHeight.bind(this)); + + this.#destroyRef.onDestroy(() => { + window.removeEventListener('resize', this.calculateScrollHeight.bind(this)); + }); + } + + private calculateScrollHeight(): void { + const containerHeight = this.tableContainer.nativeElement.offsetHeight; + this.scrollHeight.set(`${containerHeight}px`); + } +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html new file mode 100644 index 000000000000..3026e470f0db --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html @@ -0,0 +1,21 @@ +
+ + @if (searchInput.value && !isLoading()) { + + + + } @if (isLoading()) { + + + + } + + + + +
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.scss new file mode 100644 index 000000000000..a61a6f707664 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.scss @@ -0,0 +1,14 @@ +@use "variables" as *; + +.category-field__search-input.p-inputtext.p-element, +.category-field__search-icon-clear, +.category-field__search-icon-loading { + border-right: none; +} + +.category-field__search--icon-search, +.category-field__search-icon-clear, +.category-field__search-icon-loading { + color: $color-palette-primary; + padding: 0; +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.spec.ts new file mode 100644 index 000000000000..564d56ce5810 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DotCategoryFieldSearchComponent } from './dot-category-field-search.component'; + +describe('DotCategoryFieldSearchComponent', () => { + let component: DotCategoryFieldSearchComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DotCategoryFieldSearchComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(DotCategoryFieldSearchComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.ts new file mode 100644 index 000000000000..62a78ab4fa00 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.ts @@ -0,0 +1,84 @@ +import { fromEvent } from 'rxjs'; + +import { CommonModule } from '@angular/common'; +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + DestroyRef, + ElementRef, + EventEmitter, + inject, + input, + Output, + ViewChild +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +import { InputTextModule } from 'primeng/inputtext'; + +import { debounceTime, filter, map } from 'rxjs/operators'; + +import { DotMessagePipe } from '@dotcms/ui'; + +const DEBOUNCE_TIME = 300; +const MINIMUM_CHARACTERS = 3; + +@Component({ + selector: 'dot-category-field-search', + standalone: true, + imports: [CommonModule, DotMessagePipe, InputTextModule], + templateUrl: './dot-category-field-search.component.html', + styleUrl: './dot-category-field-search.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DotCategoryFieldSearchComponent implements AfterViewInit { + @ViewChild('searchInput') searchInput!: ElementRef; + + /** + * Represent a EventEmitter for the term the user want to filter + * + */ + @Output() term = new EventEmitter(); + + /** + * Represent a EventEmitter to notify we want change the mode to `list`. + */ + @Output() changeMode = new EventEmitter(); + isLoading = input.required(); + #destroyRef = inject(DestroyRef); + + ngAfterViewInit(): void { + this.listenInputChanges(); + } + + /** + * Clears the value of the search input field and emits a change mode event. + * + * @return {void} + */ + clearInput(): void { + this.searchInput.nativeElement.value = ''; + this.changeMode.emit('list'); + } + + /** + * Sets up the search input observable to listen to input events, + * debounce the input, filter by minimum character length, + * and emit the search query value. + * + * @private + */ + private listenInputChanges(): void { + fromEvent(this.searchInput.nativeElement, 'input') + .pipe( + takeUntilDestroyed(this.#destroyRef), + map((event: Event) => (event.target as HTMLInputElement).value), + debounceTime(DEBOUNCE_TIME), + filter((value: string) => value.length >= MINIMUM_CHARACTERS) + ) + .subscribe((value: string) => { + this.term.emit(value); + }); + } +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html index aaf0042de765..6ec4766df9a4 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html @@ -22,18 +22,34 @@
- +
+ @if (store.mode() === 'list') { + } @else { + + }
-
Selected Categories
+
+ Selected Categories Component +
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.ts index 5547c3320aef..0879219bae46 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.ts @@ -1,4 +1,5 @@ import { animate, state, style, transition, trigger } from '@angular/animations'; +import { JsonPipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, @@ -16,7 +17,6 @@ import { SidebarModule } from 'primeng/sidebar'; import { DotMessagePipe } from '@dotcms/ui'; -import { DotCategoryFieldCategorySearchedItems } from '../../models/dot-category-field.models'; import { CategoryFieldStore } from '../../store/content-category-field.store'; import { DotCategoryFieldCategoryListComponent } from '../dot-category-field-category-list/dot-category-field-category-list.component'; import { DotCategoryFieldSearchComponent } from '../dot-category-field-search/dot-category-field-search.component'; @@ -43,7 +43,8 @@ import { DotCategoryFieldCategoryListComponent, InputTextModule, DotCategoryFieldSearchComponent, - DotCategoryFieldSearchListComponent + DotCategoryFieldSearchListComponent, + JsonPipe ], templateUrl: './dot-category-field-sidebar.component.html', styleUrl: './dot-category-field-sidebar.component.scss', @@ -71,7 +72,7 @@ export class DotCategoryFieldSidebarComponent implements OnInit { */ @Output() closedSidebar = new EventEmitter(); - readonly store = inject(CategoryFieldStore); + readonly store: InstanceType = inject(CategoryFieldStore); readonly #destroyRef = inject(DestroyRef); @@ -82,8 +83,4 @@ export class DotCategoryFieldSidebarComponent implements OnInit { this.store.clean(); }); } - - selectedSearch($event: DotCategoryFieldCategorySearchedItems[]) { - this.store.updateSelectedFromSearch($event); - } } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.html index 16fa93ebf57b..ac1641579823 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.html @@ -14,3 +14,4 @@ pButton>
+
{{ store.selected() | json }}
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.ts index 5bdbdb811e5c..d6ccfe0e7d6f 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.ts @@ -1,4 +1,4 @@ -import { NgClass } from '@angular/common'; +import { JsonPipe, NgClass } from '@angular/common'; import { ChangeDetectionStrategy, Component, @@ -80,7 +80,7 @@ export class DotEditContentCategoryFieldComponent implements OnInit { */ contentlet = input.required(); - readonly store = inject(CategoryFieldStore); + readonly store: InstanceType = inject(CategoryFieldStore); readonly #form = inject(ControlContainer).control as FormGroup; readonly #destroyRef = inject(DestroyRef); #componentRef: ComponentRef; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/models/dot-category-field.models.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/models/dot-category-field.models.ts index eac713c13f88..99388530a42d 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/models/dot-category-field.models.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/models/dot-category-field.models.ts @@ -6,14 +6,17 @@ import { DotCategory } from '@dotcms/dotcms-models'; */ export interface DotCategoryFieldKeyValueObj { key: string; + inode: string; value: string; path?: string; + clicked?: boolean; + hasChildren?: boolean; } /** * Represents an clicked item in a DotCategoryField. */ -export type DotCategoryFieldItem = { index: number; item: DotCategory }; +export type DotCategoryFieldItem = { index: number; item: DotCategoryFieldKeyValueObj }; /** * Represents a category for a Dot field with a checkbox. @@ -25,7 +28,22 @@ export interface DotCategoryFieldCategory extends DotCategory { checked?: boolean; } -export interface DotCategoryFieldCategorySearchedItems - extends Pick { - path: string; +/** + * Represents an event when a row is selected in a table. + * + * @template T - The type of the data associated with the selected row. + */ +export interface DotTableRowSelectEvent { + originalEvent?: Event; + data?: T; + type?: string; + index?: number; +} + +/** + * Represents an event emitted when the header checkbox of a table is selected. + */ +export interface DotTableHeaderCheckboxSelectEvent { + originalEvent?: Event; + checked: boolean; } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts index 10bd7048e182..87665917646e 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts @@ -7,40 +7,34 @@ import { computed, inject } from '@angular/core'; import { delay, filter, switchMap, tap } from 'rxjs/operators'; -import { - ComponentStatus, - DotCategory, - DotCMSContentlet, - DotCMSContentTypeField -} from '@dotcms/dotcms-models'; +import { ComponentStatus, DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models'; import { CategoryFieldViewMode } from '../components/dot-category-field-sidebar/dot-category-field-sidebar.component'; import { DotCategoryFieldCategory, - DotCategoryFieldCategorySearchedItems, DotCategoryFieldItem, DotCategoryFieldKeyValueObj } from '../models/dot-category-field.models'; import { CategoriesService } from '../services/categories.service'; import { - addMetadata, + addSelected, checkIfClickedIsLastItem, clearCategoriesAfterIndex, clearParentPathAfterIndex, - getSelectedCategories, - transformedData, - updateChecked, - updateSelectedFromSearch + removeItemByKey, + transformCategories, + transformSelectedCategories, + updateChecked } from '../utils/category-field.utils'; export type CategoryFieldState = { field: DotCMSContentTypeField; - selected: DotCategoryFieldKeyValueObj[]; + selected: DotCategoryFieldKeyValueObj[]; // <- source of selected categories: DotCategoryFieldCategory[][]; - parentPath: string[]; + keyParentPath: string[]; // Delete when we have the endpoint for this state: ComponentStatus; - // search mode: CategoryFieldViewMode; + // search filter: string; searchCategories: DotCategoryFieldCategory[]; }; @@ -49,7 +43,7 @@ export const initialState: CategoryFieldState = { field: {} as DotCMSContentTypeField, selected: [], categories: [], - parentPath: [], + keyParentPath: [], state: ComponentStatus.IDLE, mode: 'list', filter: '', @@ -73,7 +67,7 @@ export const CategoryFieldStore = signalStore( * Categories for render with added properties */ categoryList: computed(() => - store.categories().map((column) => addMetadata(column, store.parentPath())) + store.categories().map((column) => transformCategories(column, store.keyParentPath())) ), /** @@ -92,8 +86,6 @@ export const CategoryFieldStore = signalStore( fieldVariableName: computed(() => store.field().variable), // Search - getSearchedCategories: computed(() => transformedData(store.searchCategories())), // remove this one - isSearchLoading: computed( () => store.mode() === 'search' && store.state() === ComponentStatus.LOADING ), @@ -102,6 +94,14 @@ export const CategoryFieldStore = signalStore( store.mode() === 'search' && store.state() === ComponentStatus.LOADED && store.filter() + ), + /** + * Categories for render with added properties + */ + searchCategoryList: computed(() => + store + .searchCategories() + .map((column) => transformCategories(column, store.keyParentPath())) ) })), withMethods((store, categoryService = inject(CategoriesService)) => ({ @@ -109,7 +109,7 @@ export const CategoryFieldStore = signalStore( * Sets a given iNode as the main parent and loads selected categories into the store. */ load(field: DotCMSContentTypeField, contentlet: DotCMSContentlet): void { - const selected = getSelectedCategories(field, contentlet); + const selected = transformSelectedCategories(field, contentlet); patchState(store, { field, selected @@ -127,7 +127,7 @@ export const CategoryFieldStore = signalStore( /** * Updates the selected items based on the provided item. */ - updateSelected(selected: string[], item: DotCategory): void { + updateSelected(selected: string[], item: DotCategoryFieldKeyValueObj): void { const currentChecked: DotCategoryFieldKeyValueObj[] = updateChecked( store.selected(), selected, @@ -139,15 +139,32 @@ export const CategoryFieldStore = signalStore( }); }, - updateSelectedFromSearch(selectedItems: DotCategoryFieldCategorySearchedItems[]): void { - const currentChecked: DotCategoryFieldKeyValueObj[] = updateSelectedFromSearch( - store.selected(), - selectedItems, - store.searchCategories() - ); + /** + * Adds the selected item(s) to the store's selected state. + * + * @param {DotCategoryFieldKeyValueObj | DotCategoryFieldKeyValueObj[]} selectedItem - The item(s) to be added. + * @returns {void} + */ + addSelected( + selectedItem: DotCategoryFieldKeyValueObj | DotCategoryFieldKeyValueObj[] + ): void { + const updatedSelected = addSelected(store.selected(), selectedItem); + patchState(store, { + selected: updatedSelected + }); + }, + + /** + * Removes the selected items with the given key(s). + * + * @param {string | string[]} key - The key(s) of the item(s) to be removed. + * @return {void} + */ + removeSelected(key: string | string[]): void { + const newSelected = removeItemByKey(store.selected(), key); patchState(store, { - selected: [...currentChecked] + selected: newSelected }); }, @@ -155,7 +172,7 @@ export const CategoryFieldStore = signalStore( * Clears all categories from the store, effectively resetting state related to categories and their parent paths. */ clean() { - patchState(store, { categories: [], parentPath: [], mode: 'list', filter: '' }); + patchState(store, { categories: [], keyParentPath: [], mode: 'list', filter: '' }); }, /** @@ -174,8 +191,8 @@ export const CategoryFieldStore = signalStore( categories: [ ...clearCategoriesAfterIndex(currentCategories, index) ], - parentPath: [ - ...clearParentPathAfterIndex(store.parentPath(), index) + keyParentPath: [ + ...clearParentPathAfterIndex(store.keyParentPath(), index) ] }); } @@ -185,8 +202,7 @@ export const CategoryFieldStore = signalStore( filter( (event) => !event || - (event.item.childrenCount > 0 && - !store.parentPath().includes(event.item.inode)) + (event.item.hasChildren && !store.keyParentPath().includes(event.item.key)) ), tap(() => patchState(store, { state: ComponentStatus.LOADING })), switchMap((event) => { @@ -201,7 +217,7 @@ export const CategoryFieldStore = signalStore( patchState(store, { categories: [...store.categories(), newCategories], state: ComponentStatus.LOADED, - parentPath: [...store.parentPath(), event.item.inode] + keyParentPath: [...store.keyParentPath(), event.item.key] }); } else { patchState(store, { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.spec.ts index 384ec61340c6..f7ecfc512e0d 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.spec.ts @@ -1,9 +1,9 @@ import { - addMetadata, categoryDeepCopy, clearCategoriesAfterIndex, clearParentPathAfterIndex, - getSelectedCategories, + transformCategories, + transformSelectedCategories, updateChecked } from './category-field.utils'; @@ -20,7 +20,7 @@ import { describe('CategoryFieldUtils', () => { describe('getSelectedCategories', () => { it('should return an empty array if contentlet is null', () => { - const result = getSelectedCategories(CATEGORY_FIELD_MOCK, null); + const result = transformSelectedCategories(CATEGORY_FIELD_MOCK, null); expect(result).toEqual([]); }); @@ -29,7 +29,7 @@ describe('CategoryFieldUtils', () => { { key: '33333', value: 'Electrical' }, { key: '22222', value: 'Doors & Windows' } ]; - const result = getSelectedCategories( + const result = transformSelectedCategories( CATEGORY_FIELD_MOCK, CATEGORY_FIELD_CONTENTLET_MOCK ); @@ -45,7 +45,7 @@ describe('CategoryFieldUtils', () => { index === 0 ? { ...item, checked: true } : { ...item, checked: false } ); - const result = addMetadata(CATEGORY_LEVEL_1, PARENT_PATH_MOCK); + const result = transformCategories(CATEGORY_LEVEL_1, PARENT_PATH_MOCK); expect(result).toEqual(expected); }); @@ -55,7 +55,7 @@ describe('CategoryFieldUtils', () => { return { ...item, checked: false }; }); - const result = addMetadata(CATEGORY_LEVEL_1, PATH_MOCK); + const result = transformCategories(CATEGORY_LEVEL_1, PATH_MOCK); expect(result).toEqual(expected); }); @@ -65,7 +65,7 @@ describe('CategoryFieldUtils', () => { return { ...item, checked: false }; }); - const result = addMetadata(CATEGORY_LEVEL_1, PATH_MOCK); + const result = transformCategories(CATEGORY_LEVEL_1, PATH_MOCK); expect(result).toEqual(expected); }); }); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.ts index 75adf9569fe0..a3f471849a5f 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.ts @@ -2,7 +2,6 @@ import { DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models' import { DotCategoryFieldCategory, - DotCategoryFieldCategorySearchedItems, DotCategoryFieldKeyValueObj } from '../models/dot-category-field.models'; @@ -13,7 +12,7 @@ import { * @param {DotCMSContentlet} contentlet - The contentlet from which to retrieve the selected categories. * @returns {DotCategoryFieldKeyValueObj[]} - An array of objects representing the selected categories. */ -export const getSelectedCategories = ( +export const transformSelectedCategories = ( { variable }: DotCMSContentTypeField, contentlet: DotCMSContentlet ): DotCategoryFieldKeyValueObj[] => { @@ -32,38 +31,32 @@ export const getSelectedCategories = ( /** * Add calculated properties to the categories - * @param categories - * @param parentPath + * @param categories - Single category or array of categories to transform + * @param keyParentPath - Path of keys to determine clicked state + * @returns Transformed category or array of transformed categories with additional properties */ -export const addMetadata = ( - categories: DotCategoryFieldCategory[], - parentPath: string[] -): DotCategoryFieldCategory[] => { - return categories.map((category) => { - return { - ...category, - checked: parentPath.includes(category.inode) && category.childrenCount > 0 - }; - }); -}; - -export const transformedData = ( - categories: DotCategoryFieldCategory[] -): DotCategoryFieldCategorySearchedItems[] => { - return categories.map((item) => { - // const path = item.parentList.map((parent) => parent.categoryName).join(' / '); - const path = item.parentList - .slice(1) - .map((parent) => parent.categoryName) - .join(' / '); +export const transformCategories = ( + categories: DotCategoryFieldCategory | DotCategoryFieldCategory[], + keyParentPath: string[] +): DotCategoryFieldKeyValueObj | DotCategoryFieldKeyValueObj[] => { + const transformCategory = (category: DotCategoryFieldCategory): DotCategoryFieldKeyValueObj => { + const { key, inode, categoryName, childrenCount } = category; + const hasChildren = childrenCount > 0; return { - categoryName: item.categoryName, - key: item.key, - inode: item.inode, - path: path + key, + inode, + value: categoryName, + hasChildren, + clicked: hasChildren && keyParentPath.includes(key) }; - }); + }; + + if (Array.isArray(categories)) { + return categories.map(transformCategory); + } else { + return transformCategory(categories); + } }; /** @@ -112,39 +105,6 @@ export const checkIfClickedIsLastItem = ( return index + 1 === categories.length; }; -/** - * Update storedSelected from search results. - * - * @param {DotCategoryFieldKeyValueObj[]} storedSelected - The array of items currently stored as selected. - * @param {DotCategoryFieldCategorySearchedItems[]} selected - The array of items that were selected from the search results. - * @param {DotCategoryFieldCategory[]} searchedItems - The array of items obtained from the search. - * @returns {DotCategoryFieldKeyValueObj[]} The updated array of selected items. - */ -export const updateSelectedFromSearch = ( - storedSelected: DotCategoryFieldKeyValueObj[], - selected: DotCategoryFieldCategorySearchedItems[], - searchedItems: DotCategoryFieldCategory[] -): DotCategoryFieldKeyValueObj[] => { - // Create a map for quick lookup of searched items - const searchedItemsMap = new Map(searchedItems.map((item) => [item.key, item])); - - // Remove items from storedSelected that are in searchedItems - const currentChecked = storedSelected.filter((item) => !searchedItemsMap.has(item.key)); - - // Add new selected items to currentChecked - for (const item of selected) { - if (!currentChecked.some((checkedItem) => checkedItem.key === item.key)) { - currentChecked.push({ - value: item.categoryName, - key: item.key, - path: item.path - }); - } - } - - return currentChecked; -}; - /** * Updates the array of selected items based on the current selection and most recently interacted with item. * @@ -156,13 +116,16 @@ export const updateSelectedFromSearch = ( export const updateChecked = ( storedSelected: DotCategoryFieldKeyValueObj[], selected: string[], - item: DotCategoryFieldCategory + item: DotCategoryFieldKeyValueObj ): DotCategoryFieldKeyValueObj[] => { let currentChecked = [...storedSelected]; if (selected.includes(item.key)) { if (!currentChecked.some((entry) => entry.key === item.key)) { - currentChecked = [...currentChecked, { key: item.key, value: item.categoryName }]; + currentChecked = [ + ...currentChecked, + { key: item.key, value: item.value, inode: item.inode } + ]; } } else { currentChecked = currentChecked.filter((entry) => entry.key !== item.key); @@ -170,3 +133,55 @@ export const updateChecked = ( return currentChecked; }; + +/** + * Retrieves the parent path of a given category item. + * + * @param {DotCategoryFieldCategory} item - The category item. + * @returns {string} - The parent path of the category item. + */ +export const getParentPath = (item: DotCategoryFieldCategory): string => { + return item.parentList + .slice(1) + .map((parent) => parent.categoryName) + .join(' / '); +}; + +/** + * Removes items from an array of objects based on a specified key or an array of keys. + * + * @param {DotCategoryFieldKeyValueObj[]} array - The array of objects to remove items from. + * @param {string | string[]} key - The key (or keys if an array) used to identify the items to remove. + * @returns {DotCategoryFieldKeyValueObj[]} The updated array without the removed items. + */ +export const removeItemByKey = ( + array: DotCategoryFieldKeyValueObj[], + key: string | string[] +): DotCategoryFieldKeyValueObj[] => { + if (Array.isArray(key)) { + const keysSet = new Set(key); + + return array.filter((item) => !keysSet.has(item.key)); + } else { + return array.filter((item) => item.key !== key); + } +}; + +/** + * Adds selected items to the existing array of DotCategoryFieldKeyValueObj. + * + * @param {DotCategoryFieldKeyValueObj[]} array - The original array. + * @param {DotCategoryFieldKeyValueObj | DotCategoryFieldKeyValueObj[]} items - The item(s) to be added to the array. + * @returns {DotCategoryFieldKeyValueObj[]} - The updated array with the selected items added. + */ +export const addSelected = ( + array: DotCategoryFieldKeyValueObj[], + items: DotCategoryFieldKeyValueObj | DotCategoryFieldKeyValueObj[] +): DotCategoryFieldKeyValueObj[] => { + const itemsArray = Array.isArray(items) ? items : [items]; + const itemSet = new Set(array.map((item) => item.key)); + + const newItems = itemsArray.filter((item) => !itemSet.has(item.key)); + + return [...array, ...newItems]; +}; From 9737e2cb7ce4ca6325ee3814c266a741a13a3390 Mon Sep 17 00:00:00 2001 From: Arcadio Quintero Date: Tue, 9 Jul 2024 15:10:31 -0400 Subject: [PATCH 03/17] feat(edit-content) fix comments #28879 --- core-web/.prettierrc | 1 + .../src/lib/dot-categories.model.ts | 12 ++-- ...ategory-field-category-list.component.html | 8 +-- ...-category-field-search-list.component.html | 4 +- ...ot-category-field-search-list.component.ts | 64 +++++++------------ .../dot-category-field-search.component.html | 6 +- .../dot-category-field-search.component.ts | 22 +++---- .../dot-category-field-sidebar.component.html | 2 +- .../dot-table-skeleton.component.html | 2 +- .../dot-table-skeleton.component.ts | 3 +- ...edit-content-category-field.component.html | 1 - ...t-edit-content-category-field.component.ts | 2 +- .../models/dot-category-field.models.ts | 12 ---- .../services/categories.service.ts | 10 +-- .../store/content-category-field.store.ts | 24 ++++--- .../utils/category-field.utils.ts | 38 ++++++----- 16 files changed, 87 insertions(+), 124 deletions(-) diff --git a/core-web/.prettierrc b/core-web/.prettierrc index 4ef649c49d5d..048b159b17fb 100644 --- a/core-web/.prettierrc +++ b/core-web/.prettierrc @@ -6,6 +6,7 @@ "trailingComma": "none", "arrowParens": "always", "bracketSameLine": true, + "htmlWhitespaceSensitivity": "ignore", "overrides": [ { "files": "*.scss", diff --git a/core-web/libs/dotcms-models/src/lib/dot-categories.model.ts b/core-web/libs/dotcms-models/src/lib/dot-categories.model.ts index f23a57952775..e31cf0879854 100644 --- a/core-web/libs/dotcms-models/src/lib/dot-categories.model.ts +++ b/core-web/libs/dotcms-models/src/lib/dot-categories.model.ts @@ -29,9 +29,11 @@ export interface DotCategory { parentPermissionable?: { hostname: string; }; - parentList?: Array<{ - categoryName: string; - key: string; - inode: string; - }>; + parentList?: DotCategoryParent[]; } + +type DotCategoryParent = { + categoryName: string; + key: string; + inode: string; +}; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.html index b5b2e1a723c2..a48526f95b00 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.html @@ -19,10 +19,10 @@ + [class.cursor-pointer]="item.hasChildren" + [for]="item.key"> + {{ item.value }} + @if (item.hasChildren) { @if (!isLoading()) { ('0px'); - + $scrollHeight = signal('0px'); /** * Represents the categories found with the filter */ - categories = input.required(); - + categories = input.required(); /** * Represent the selected items in the store */ selected = input.required(); - /** * EventEmitter for emit the selected category(ies). */ @Output() itemChecked = new EventEmitter< DotCategoryFieldKeyValueObj | DotCategoryFieldKeyValueObj[] >(); - /** * EventEmitter that emits events to remove a selected item(s). */ @Output() removeItem = new EventEmitter(); - /** * Represents a variable indicating if the component is in loading state. */ isLoading = input.required(); - /** * Computed variable to store the search results parsed. * */ - searchResults = computed(() => { + $searchResults = computed(() => { return this.categories().map((item) => { const path = getParentPath(item); return { key: item.key, value: item.categoryName, path: path, inode: item.inode }; }); }); - /** * Model of the items selected */ itemsSelected: DotCategoryFieldKeyValueObj[]; - /** * Represents an array of temporary selected items. */ temporarySelectedAll: string[] = []; - + #renderer = inject(Renderer2); #destroyRef = inject(DestroyRef); readonly #effectRef = effect(() => { @@ -113,21 +98,6 @@ export class DotCategoryFieldSearchListComponent implements AfterViewInit { this.itemsSelected = this.selected(); }); - private resizeSubject = new Subject(); - - ngAfterViewInit(): void { - this.resizeSubject - .pipe(takeUntilDestroyed(this.#destroyRef), debounceTime(DELAY_FOR_LISTENER)) - .subscribe(() => this.calculateScrollHeight()); - - window.addEventListener('resize', () => this.resizeSubject.next()); - - this.#destroyRef.onDestroy(() => { - window.removeEventListener('resize', () => this.resizeSubject.next()); - this.#effectRef.destroy(); - }); - } - /** * This method is called when an item is selected. * @@ -157,8 +127,8 @@ export class DotCategoryFieldSearchListComponent implements AfterViewInit { */ onHeaderCheckboxToggle({ checked }: DotTableHeaderCheckboxSelectEvent): void { if (checked) { - const values = this.searchResults().map((item) => item.key); - this.itemChecked.emit(this.searchResults()); + const values = this.$searchResults().map((item) => item.key); + this.itemChecked.emit(this.$searchResults()); this.temporarySelectedAll = [...values]; } else { this.removeItem.emit(this.temporarySelectedAll); @@ -166,12 +136,24 @@ export class DotCategoryFieldSearchListComponent implements AfterViewInit { } } + ngAfterViewInit(): void { + this.setTableScrollHeight(); + this.#renderer.listen('window', 'resize', this.setTableScrollHeight.bind(this)); + + this.#destroyRef.onDestroy(() => { + this.#renderer.listen('window', 'resize', null); + this.#effectRef.destroy(); + }); + } + /** * Calculate the high of the container for the virtual scroll * @private */ - private calculateScrollHeight(): void { - const containerHeight = this.tableContainer.nativeElement.offsetHeight; - this.scrollHeight.set(`${containerHeight}px`); + private setTableScrollHeight() { + if (this.tableContainer) { + const containerHeight = this.tableContainer.nativeElement.clientHeight; + this.$scrollHeight.set(`${containerHeight}px`); + } } } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html index 3026e470f0db..f7049dcedac6 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html @@ -1,11 +1,11 @@
- @if (searchInput.value && !isLoading()) { + @if (searchControl.value && !isLoading()) { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.ts index 62a78ab4fa00..f8b7d1bdae54 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.ts @@ -1,23 +1,20 @@ -import { fromEvent } from 'rxjs'; - import { CommonModule } from '@angular/common'; import { AfterViewInit, ChangeDetectionStrategy, Component, DestroyRef, - ElementRef, EventEmitter, inject, input, - Output, - ViewChild + Output } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { InputTextModule } from 'primeng/inputtext'; -import { debounceTime, filter, map } from 'rxjs/operators'; +import { debounceTime, filter } from 'rxjs/operators'; import { DotMessagePipe } from '@dotcms/ui'; @@ -27,13 +24,13 @@ const MINIMUM_CHARACTERS = 3; @Component({ selector: 'dot-category-field-search', standalone: true, - imports: [CommonModule, DotMessagePipe, InputTextModule], + imports: [CommonModule, DotMessagePipe, InputTextModule, ReactiveFormsModule], templateUrl: './dot-category-field-search.component.html', styleUrl: './dot-category-field-search.component.scss', changeDetection: ChangeDetectionStrategy.OnPush }) export class DotCategoryFieldSearchComponent implements AfterViewInit { - @ViewChild('searchInput') searchInput!: ElementRef; + searchControl = new FormControl(); /** * Represent a EventEmitter for the term the user want to filter @@ -45,6 +42,10 @@ export class DotCategoryFieldSearchComponent implements AfterViewInit { * Represent a EventEmitter to notify we want change the mode to `list`. */ @Output() changeMode = new EventEmitter(); + + /** + * Represents the boolean variable isLoading. + */ isLoading = input.required(); #destroyRef = inject(DestroyRef); @@ -58,7 +59,7 @@ export class DotCategoryFieldSearchComponent implements AfterViewInit { * @return {void} */ clearInput(): void { - this.searchInput.nativeElement.value = ''; + this.searchControl.setValue(''); this.changeMode.emit('list'); } @@ -70,10 +71,9 @@ export class DotCategoryFieldSearchComponent implements AfterViewInit { * @private */ private listenInputChanges(): void { - fromEvent(this.searchInput.nativeElement, 'input') + this.searchControl.valueChanges .pipe( takeUntilDestroyed(this.#destroyRef), - map((event: Event) => (event.target as HTMLInputElement).value), debounceTime(DEBOUNCE_TIME), filter((value: string) => value.length >= MINIMUM_CHARACTERS) ) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html index 834e11b9ab7e..5e4b7f1290a3 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html @@ -31,7 +31,7 @@
@if (store.mode() === 'list') { - {{ store.searchCategories() | json }} + diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.ts index 155708539261..0d64e9c33987 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.ts @@ -30,8 +30,7 @@ export class DotTableSkeletonComponent { */ rows = input(5); - data = computed(() => { + $data = computed(() => { return Array.from({ length: this.rows() }).map((_, i) => `${i}`); - // return Array(this.rows()).fill(null); }); } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.html index ac1641579823..16fa93ebf57b 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.html @@ -14,4 +14,3 @@ pButton>
-
{{ store.selected() | json }}
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.ts index d6ccfe0e7d6f..2ec13659f1ac 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.ts @@ -1,4 +1,4 @@ -import { JsonPipe, NgClass } from '@angular/common'; +import { NgClass } from '@angular/common'; import { ChangeDetectionStrategy, Component, diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/models/dot-category-field.models.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/models/dot-category-field.models.ts index 99388530a42d..423bd19a7760 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/models/dot-category-field.models.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/models/dot-category-field.models.ts @@ -1,5 +1,3 @@ -import { DotCategory } from '@dotcms/dotcms-models'; - /** * Object representing a key-value pair. * @interface @@ -18,16 +16,6 @@ export interface DotCategoryFieldKeyValueObj { */ export type DotCategoryFieldItem = { index: number; item: DotCategoryFieldKeyValueObj }; -/** - * Represents a category for a Dot field with a checkbox. - * - * @interface - * @extends DotCategory - */ -export interface DotCategoryFieldCategory extends DotCategory { - checked?: boolean; -} - /** * Represents an event when a row is selected in a table. * diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/services/categories.service.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/services/categories.service.ts index 8e36f9d07096..c10d22254928 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/services/categories.service.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/services/categories.service.ts @@ -6,8 +6,7 @@ import { inject, Injectable } from '@angular/core'; import { pluck } from 'rxjs/operators'; import { DotCMSResponse } from '@dotcms/dotcms-js'; - -import { DotCategoryFieldCategory } from '../models/dot-category-field.models'; +import { DotCategory } from '@dotcms/dotcms-models'; export const API_URL = '/api/v1/categories'; @@ -45,15 +44,12 @@ export class CategoriesService { * @param params * @returns {Observable} - An Observable that emits the children of the given inode as an array of DotCategory objects. */ - getChildren( - inode: string, - params: Partial = {} - ): Observable { + getChildren(inode: string, params: Partial = {}): Observable { const mergedParams = this.mergeParams({ ...params, inode }); const httpParams = this.toHttpParams(mergedParams); return this.#http - .get>(`${API_URL}/children`, { + .get>(`${API_URL}/children`, { params: httpParams }) .pipe(pluck('entity')); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts index 87665917646e..e4bced830b97 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts @@ -5,13 +5,17 @@ import { pipe } from 'rxjs'; import { computed, inject } from '@angular/core'; -import { delay, filter, switchMap, tap } from 'rxjs/operators'; +import { filter, switchMap, tap } from 'rxjs/operators'; -import { ComponentStatus, DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models'; +import { + ComponentStatus, + DotCategory, + DotCMSContentlet, + DotCMSContentTypeField +} from '@dotcms/dotcms-models'; import { CategoryFieldViewMode } from '../components/dot-category-field-sidebar/dot-category-field-sidebar.component'; import { - DotCategoryFieldCategory, DotCategoryFieldItem, DotCategoryFieldKeyValueObj } from '../models/dot-category-field.models'; @@ -30,13 +34,13 @@ import { export type CategoryFieldState = { field: DotCMSContentTypeField; selected: DotCategoryFieldKeyValueObj[]; // <- source of selected - categories: DotCategoryFieldCategory[][]; + categories: DotCategory[][]; keyParentPath: string[]; // Delete when we have the endpoint for this state: ComponentStatus; mode: CategoryFieldViewMode; // search filter: string; - searchCategories: DotCategoryFieldCategory[]; + searchCategories: DotCategory[]; }; export const initialState: CategoryFieldState = { @@ -89,12 +93,7 @@ export const CategoryFieldStore = signalStore( isSearchLoading: computed( () => store.mode() === 'search' && store.state() === ComponentStatus.LOADING ), - searchCategoriesFound: computed( - () => - store.mode() === 'search' && - store.state() === ComponentStatus.LOADED && - store.filter() - ), + /** * Categories for render with added properties */ @@ -241,11 +240,10 @@ export const CategoryFieldStore = signalStore( tap(() => patchState(store, { mode: 'search', state: ComponentStatus.LOADING })), switchMap((filter) => { return categoryService.getChildren(store.rootCategoryInode(), { filter }).pipe( - delay(300), tapResponse({ next: (categories) => { patchState(store, { - searchCategories: categories, + searchCategories: [...categories], state: ComponentStatus.LOADED }); }, diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.ts index a3f471849a5f..f77d98bf12b7 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.ts @@ -1,9 +1,6 @@ -import { DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models'; +import { DotCategory, DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models'; -import { - DotCategoryFieldCategory, - DotCategoryFieldKeyValueObj -} from '../models/dot-category-field.models'; +import { DotCategoryFieldKeyValueObj } from '../models/dot-category-field.models'; /** * Retrieves selected categories from a contentlet. @@ -36,10 +33,10 @@ export const transformSelectedCategories = ( * @returns Transformed category or array of transformed categories with additional properties */ export const transformCategories = ( - categories: DotCategoryFieldCategory | DotCategoryFieldCategory[], + categories: DotCategory | DotCategory[], keyParentPath: string[] ): DotCategoryFieldKeyValueObj | DotCategoryFieldKeyValueObj[] => { - const transformCategory = (category: DotCategoryFieldCategory): DotCategoryFieldKeyValueObj => { + const transformCategory = (category: DotCategory): DotCategoryFieldKeyValueObj => { const { key, inode, categoryName, childrenCount } = category; const hasChildren = childrenCount > 0; @@ -75,10 +72,10 @@ export const categoryDeepCopy = (array: T[][]): T[][] => { * @param index */ export const clearCategoriesAfterIndex = ( - array: DotCategoryFieldCategory[][], + array: DotCategory[][], index: number -): DotCategoryFieldCategory[][] => { - const newArray = categoryDeepCopy(array); +): DotCategory[][] => { + const newArray = categoryDeepCopy(array); newArray.splice(index + 1); return newArray; @@ -98,10 +95,7 @@ export const clearParentPathAfterIndex = (parentPath: string[], index: number): * @param index * @param categories */ -export const checkIfClickedIsLastItem = ( - index: number, - categories: DotCategoryFieldCategory[][] -) => { +export const checkIfClickedIsLastItem = (index: number, categories: DotCategory[][]) => { return index + 1 === categories.length; }; @@ -137,14 +131,18 @@ export const updateChecked = ( /** * Retrieves the parent path of a given category item. * - * @param {DotCategoryFieldCategory} item - The category item. + * @param {DotCategory} item - The category item. * @returns {string} - The parent path of the category item. */ -export const getParentPath = (item: DotCategoryFieldCategory): string => { - return item.parentList - .slice(1) - .map((parent) => parent.categoryName) - .join(' / '); +export const getParentPath = (item: DotCategory): string => { + if (item.parentList) { + return item.parentList + .slice(1) + .map((parent) => parent.categoryName) + .join(' / '); + } + + return ''; }; /** From cd1c922a3ec0cd3bda4d239eaa565dc38de40350 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Tue, 9 Jul 2024 16:04:05 -0400 Subject: [PATCH 04/17] chore(editor-content): use resize observer --- ...ategory-field-category-list.component.html | 56 +++++++------- ...-category-field-search-list.component.html | 74 +++++++++---------- ...ot-category-field-search-list.component.ts | 32 +++++--- .../dot-category-field-search.component.html | 15 ++-- .../dot-category-field-sidebar.component.html | 25 +++---- .../dot-table-skeleton.component.html | 16 ++-- 6 files changed, 113 insertions(+), 105 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.html index a48526f95b00..0709021be1f8 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.html @@ -3,41 +3,41 @@ [ngClass]="{ 'no-overflow-x-yet': emptyColumns().length }" class="flex-1 category-list__category-list"> @for (column of categories(); let index = $index; track index) { - -
- @for (item of column; track item.key) { -
- + +
+ @for (item of column; track item.key) { +
+ - + - @if (item.hasChildren) { - + @if (item.hasChildren) { + + } +
}
- } -
} @for (_ of emptyColumns(); track _) { -
+
}
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.html index 534a90e52bca..aef3f4f13348 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.html @@ -1,44 +1,42 @@
@if (!isLoading()) { - - - - - - - {{ 'edit.content.category-field.search.name' | dm }} - {{ 'edit.content.category-field.search.assignee' | dm }} - - + + + + + + + {{ 'edit.content.category-field.search.name' | dm }} + {{ 'edit.content.category-field.search.assignee' | dm }} + + - - - - - - {{ category.value }} - {{ category.path }} - - - + + + + + + {{ category.value }} + {{ category.path }} + + + } @else { - - - + }
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.ts index 16352766d781..aaf6add297a0 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.ts @@ -1,24 +1,27 @@ +import { Subject } from 'rxjs'; + import { CommonModule } from '@angular/common'; import { AfterViewInit, ChangeDetectionStrategy, Component, computed, - DestroyRef, effect, ElementRef, EventEmitter, - inject, input, + OnDestroy, Output, - Renderer2, signal, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { SkeletonModule } from 'primeng/skeleton'; import { TableModule } from 'primeng/table'; +import { debounceTime } from 'rxjs/operators'; + import { DotCategory } from '@dotcms/dotcms-models'; import { DotMessagePipe } from '@dotcms/ui'; @@ -38,7 +41,7 @@ import { DotTableSkeletonComponent } from '../dot-table-skeleton/dot-table-skele styleUrl: './dot-category-field-search-list.component.scss', changeDetection: ChangeDetectionStrategy.OnPush }) -export class DotCategoryFieldSearchListComponent implements AfterViewInit { +export class DotCategoryFieldSearchListComponent implements AfterViewInit, OnDestroy { /** * Represents a reference to a table container element in the DOM to calculate the * viewport to use in the virtual scroll @@ -90,14 +93,21 @@ export class DotCategoryFieldSearchListComponent implements AfterViewInit { * Represents an array of temporary selected items. */ temporarySelectedAll: string[] = []; - #renderer = inject(Renderer2); - #destroyRef = inject(DestroyRef); readonly #effectRef = effect(() => { // Todo: find a better way to update this this.itemsSelected = this.selected(); }); + readonly #resize$ = new Subject(); + readonly #resizeObserver = new ResizeObserver((entries) => this.#resize$.next(entries[0])); + + constructor() { + this.#resize$.pipe(debounceTime(500), takeUntilDestroyed()).subscribe(() => { + this.setTableScrollHeight(); + }); + } + /** * This method is called when an item is selected. * @@ -138,12 +148,12 @@ export class DotCategoryFieldSearchListComponent implements AfterViewInit { ngAfterViewInit(): void { this.setTableScrollHeight(); - this.#renderer.listen('window', 'resize', this.setTableScrollHeight.bind(this)); + this.#resizeObserver.observe(this.tableContainer.nativeElement); + } - this.#destroyRef.onDestroy(() => { - this.#renderer.listen('window', 'resize', null); - this.#effectRef.destroy(); - }); + ngOnDestroy() { + this.#effectRef.destroy(); + this.#resizeObserver.unobserve(this.tableContainer.nativeElement); } /** diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html index f7049dcedac6..b527a52a4827 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html @@ -6,13 +6,14 @@ pInputText type="text" /> @if (searchControl.value && !isLoading()) { - - - - } @if (isLoading()) { - - - + + + + } + @if (isLoading()) { + + + } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html index 5e4b7f1290a3..59c5eda9c383 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html @@ -31,20 +31,19 @@
@if (store.mode() === 'list') { - - + } @else { - + }
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.html index 8d7f6659edb7..99d939794bb0 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.html @@ -6,7 +6,7 @@ @for (col of columns; track $index) { - {{ col }} + {{ col }} } @@ -14,13 +14,13 @@ @for (col of columns; track $index) { - - @if (col === '') { - - } @else { - - } - + + @if (col === '') { + + } @else { + + } + } From a36aac67ad76db647cfe881af60ff058feb142d7 Mon Sep 17 00:00:00 2001 From: Arcadio Quintero Date: Thu, 11 Jul 2024 15:47:43 -0400 Subject: [PATCH 05/17] feat(edit-content) fix failing tests #28879 --- ...gory-field-category-list.component.spec.ts | 27 ++++- ...tegory-field-search-list.component.spec.ts | 40 +++++-- ...ot-category-field-search.component.spec.ts | 35 ++++-- ...t-content-category-field.component.spec.ts | 5 +- .../mocks/category-field.mocks.ts | 52 ++++++--- .../models/dot-category-field.models.ts | 2 +- .../services/categories.service.spec.ts | 13 ++- .../content-category-field.store.spec.ts | 19 +++- .../utils/category-field.utils.spec.ts | 106 ++++++++---------- .../utils/category-field.utils.ts | 3 +- 10 files changed, 184 insertions(+), 118 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.spec.ts index 581d5d97d387..14c30bccdda8 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.spec.ts @@ -8,7 +8,12 @@ import { MINIMUM_CATEGORY_COLUMNS } from './dot-category-field-category-list.component'; -import { CATEGORY_LIST_MOCK, SELECTED_LIST_MOCK } from '../../mocks/category-field.mocks'; +import { + CATEGORY_LIST_MOCK, + CATEGORY_LIST_MOCK_TRANSFORMED, + CATEGORY_MOCK_TRANSFORMED, + SELECTED_LIST_MOCK +} from '../../mocks/category-field.mocks'; describe('DotCategoryFieldCategoryListComponent', () => { let spectator: Spectator; @@ -21,7 +26,7 @@ describe('DotCategoryFieldCategoryListComponent', () => { beforeEach(() => { spectator = createComponent({ props: { - categories: CATEGORY_LIST_MOCK, + categories: CATEGORY_LIST_MOCK_TRANSFORMED, selected: SELECTED_LIST_MOCK } }); @@ -68,19 +73,29 @@ describe('DotCategoryFieldCategoryListComponent', () => { expect(emitSpy).toHaveBeenCalledWith({ index: 0, - item: CATEGORY_LIST_MOCK[0][0] + item: CATEGORY_LIST_MOCK_TRANSFORMED[0][0] }); }); it('should apply selected class to the correct item', () => { + spectator = createComponent({ + props: { + categories: CATEGORY_MOCK_TRANSFORMED, + selected: SELECTED_LIST_MOCK + } + }); + + spectator.detectChanges(); + const items = spectator.queryAll(byTestId('category-item')); - expect(items[1].className).toContain('category-list__item--selected'); - expect(items[2].className).toContain('category-list__item--selected'); + + expect(items[0].className).toContain('category-list__item--selected'); + expect(items[1].className).not.toContain('category-list__item--selected'); }); it('should not render any empty columns when there are enough categories', () => { const minColumns = 4; - const testCategories = Array(minColumns).fill(CATEGORY_LIST_MOCK[0]); + const testCategories = Array(minColumns).fill(CATEGORY_LIST_MOCK_TRANSFORMED[0]); spectator = createComponent({ props: { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.spec.ts index 26ec9cf9ab65..891bf62ff8d6 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.spec.ts @@ -1,22 +1,40 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest'; + +import { DotMessageService } from '@dotcms/data-access'; import { DotCategoryFieldSearchListComponent } from './dot-category-field-search-list.component'; +import { MockResizeObserver } from '../../../../utils/mocks'; +import { CATEGORY_MOCK_TRANSFORMED, SELECTED_LIST_MOCK } from '../../mocks/category-field.mocks'; + describe('DotCategoryFieldSearchListComponent', () => { - let component: DotCategoryFieldSearchListComponent; - let fixture: ComponentFixture; + let spectator: Spectator; + const createComponent = createComponentFactory({ + component: DotCategoryFieldSearchListComponent, + providers: [mockProvider(DotMessageService)] + }); + + beforeEach(() => { + spectator = createComponent({ + props: { + categories: CATEGORY_MOCK_TRANSFORMED, + selected: SELECTED_LIST_MOCK, + isLoading: false + } + }); - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [DotCategoryFieldSearchListComponent] - }).compileComponents(); + spectator.detectChanges(); + }); + + beforeAll(() => { + global.ResizeObserver = MockResizeObserver; + }); - fixture = TestBed.createComponent(DotCategoryFieldSearchListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); + afterEach(() => { + jest.resetAllMocks(); }); it('should create', () => { - expect(component).toBeTruthy(); + expect(spectator.component).toBeTruthy(); }); }); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.spec.ts index 564d56ce5810..288c0de3b0c0 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.spec.ts @@ -1,22 +1,33 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { expect } from '@jest/globals'; +import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest'; + +import { DotMessageService } from '@dotcms/data-access'; import { DotCategoryFieldSearchComponent } from './dot-category-field-search.component'; describe('DotCategoryFieldSearchComponent', () => { - let component: DotCategoryFieldSearchComponent; - let fixture: ComponentFixture; + let spectator: Spectator; + + const createComponent = createComponentFactory({ + component: DotCategoryFieldSearchComponent, + providers: [mockProvider(DotMessageService)] + }); - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [DotCategoryFieldSearchComponent] - }).compileComponents(); + beforeEach(() => { + spectator = createComponent({ + props: { + isLoading: false + } + }); + + spectator.detectChanges(); + }); - fixture = TestBed.createComponent(DotCategoryFieldSearchComponent); - component = fixture.componentInstance; - fixture.detectChanges(); + afterEach(() => { + jest.resetAllMocks(); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('should .....', () => { + expect(spectator.component).not.toBeNull(); }); }); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.spec.ts index fa0cc64214cb..72c6055d8073 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.spec.ts @@ -143,7 +143,10 @@ describe('DotEditContentCategoryFieldComponent', () => { // Check if the form is updated - expect(setValueSpy).toHaveBeenCalledWith(['33333', '22222']); + expect(setValueSpy).toHaveBeenCalledWith([ + '1f208488057007cedda0e0b5d52ee3b3', + 'cb83dc32c0a198fd0ca427b3b587f4ce' + ]); })); }); }); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/mocks/category-field.mocks.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/mocks/category-field.mocks.ts index c1d9b67f6aae..53cefbf61914 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/mocks/category-field.mocks.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/mocks/category-field.mocks.ts @@ -1,10 +1,8 @@ -import { DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models'; +import { DotCategory, DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models'; import { MockDotMessageService } from '@dotcms/utils-testing'; -import { - DotCategoryFieldCategory, - DotCategoryFieldKeyValueObj -} from '../models/dot-category-field.models'; +import { DotCategoryFieldKeyValueObj } from "../models/dot-category-field.models"; +import { transformCategories } from "../utils/category-field.utils"; export const CATEGORY_FIELD_VARIABLE_NAME = 'categorias'; @@ -17,10 +15,10 @@ export const CATEGORY_FIELD_CONTENTLET_MOCK: DotCMSContentlet = { baseType: 'CONTENT', [CATEGORY_FIELD_VARIABLE_NAME]: [ { - '33333': 'Electrical' + '1f208488057007cedda0e0b5d52ee3b3': 'Electrical' }, { - '22222': 'Doors & Windows' + cb83dc32c0a198fd0ca427b3b587f4ce: 'Doors & Windows' } ], contentType: 'TEST', @@ -88,7 +86,7 @@ export const CATEGORY_FIELD_MOCK: DotCMSContentTypeField = { /** * Represent a Category List of level 1 with children `childrenCount` */ -export const CATEGORY_LEVEL_1: DotCategoryFieldCategory[] = [ +export const CATEGORY_LEVEL_1: DotCategory[] = [ { active: true, categoryName: 'Cleaning Supplies', @@ -119,8 +117,7 @@ export const CATEGORY_LEVEL_1: DotCategoryFieldCategory[] = [ modDate: 1718916176666, owner: '', sortOrder: 0, - type: 'category', - checked: true + type: 'category' }, { active: true, @@ -136,15 +133,14 @@ export const CATEGORY_LEVEL_1: DotCategoryFieldCategory[] = [ modDate: 1718916175804, owner: '', sortOrder: 0, - type: 'category', - checked: true + type: 'category' } ]; /** * Represent a Category List of level 2 */ -export const CATEGORY_LEVEL_2: DotCategoryFieldCategory[] = [ +export const CATEGORY_LEVEL_2: DotCategory[] = [ { active: true, categoryName: 'Concrete & Cement', @@ -198,15 +194,35 @@ export const CATEGORY_LEVEL_2: DotCategoryFieldCategory[] = [ /** * Represent a Category List handling 2 levels */ -export const CATEGORY_LIST_MOCK: DotCategoryFieldCategory[][] = [ - [...CATEGORY_LEVEL_1], - [...CATEGORY_LEVEL_2] -]; +export const CATEGORY_LIST_MOCK: DotCategory[][] = [[...CATEGORY_LEVEL_1], [...CATEGORY_LEVEL_2]]; /** * Represent the selected categories */ -export const SELECTED_LIST_MOCK = [CATEGORY_LEVEL_1[1].inode, CATEGORY_LEVEL_1[2].inode]; +export const SELECTED_LIST_MOCK = [CATEGORY_LEVEL_1[0].key, CATEGORY_LEVEL_1[1].key]; + +export const CATEGORY_LIST_MOCK_TRANSFORMED: DotCategoryFieldKeyValueObj[][] = + CATEGORY_LIST_MOCK.map( + (categoryLevel) => transformCategories(categoryLevel) as DotCategoryFieldKeyValueObj[], + SELECTED_LIST_MOCK + ); + +export const CATEGORY_MOCK_TRANSFORMED: DotCategoryFieldKeyValueObj[][] = [ + [ + { + key: CATEGORY_LEVEL_1[0].key, + value: CATEGORY_LEVEL_1[0].categoryName, + hasChildren: true, + clicked: true + }, + { + key: CATEGORY_LEVEL_1[1].key, + value: CATEGORY_LEVEL_1[1].categoryName, + hasChildren: true, + clicked: false + } + ] +]; export const CATEGORIES_KEY_VALUE: DotCategoryFieldKeyValueObj[] = [ { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/models/dot-category-field.models.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/models/dot-category-field.models.ts index 423bd19a7760..42806d525a50 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/models/dot-category-field.models.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/models/dot-category-field.models.ts @@ -4,8 +4,8 @@ */ export interface DotCategoryFieldKeyValueObj { key: string; - inode: string; value: string; + inode?: string; path?: string; clicked?: boolean; hasChildren?: boolean; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/services/categories.service.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/services/categories.service.spec.ts index 8060bcf90fe4..018660ad4d7e 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/services/categories.service.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/services/categories.service.spec.ts @@ -13,7 +13,18 @@ describe('CategoriesService', () => { const inode = 'inode-identifier'; spectator.service.getChildren(inode).subscribe(); spectator.expectOne( - `${API_URL}/children?per_page=${ITEMS_PER_PAGE}&direction=ASC&inode=${inode}&showChildrenCount=true`, + `${API_URL}/children?inode=${inode}&per_page=${ITEMS_PER_PAGE}&direction=ASC&showChildrenCount=true`, + HttpMethod.GET + ); + }); + + it('can getChildren with inode & filter', () => { + const inode = 'inode-identifier'; + const filter = 'query'; + spectator.service.getChildren(inode, { filter }).subscribe(); + spectator.expectOne( + `${API_URL}/children?inode=${inode}&per_page=${ITEMS_PER_PAGE}&direction=ASC&filter=${filter}&allLevels=true`, + HttpMethod.GET ); }); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.spec.ts index d7dca96a63a0..5af2a19e3845 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.spec.ts @@ -19,6 +19,7 @@ import { } from '../mocks/category-field.mocks'; import { DotCategoryFieldKeyValueObj } from '../models/dot-category-field.models'; import { CategoriesService } from '../services/categories.service'; +import { transformCategories } from '../utils/category-field.utils'; const EMPTY_ARRAY = []; @@ -48,7 +49,7 @@ describe('CategoryFieldStore', () => { it('should initialize with default state', () => { expect(store.categories()).toEqual(EMPTY_ARRAY); expect(store.selectedCategoriesValues()).toEqual(EMPTY_ARRAY); - expect(store.parentPath()).toEqual(EMPTY_ARRAY); + expect(store.keyParentPath()).toEqual(EMPTY_ARRAY); expect(store.state()).toEqual(ComponentStatus.IDLE); // computed expect(store.selected()).toEqual(EMPTY_ARRAY); @@ -59,11 +60,11 @@ describe('CategoryFieldStore', () => { it('should set the correct rootCategoryInode and categoriesValue', () => { const expectedCategoryValues: DotCategoryFieldKeyValueObj[] = [ { - key: '33333', + key: '1f208488057007cedda0e0b5d52ee3b3', value: 'Electrical' }, { - key: '22222', + key: 'cb83dc32c0a198fd0ca427b3b587f4ce', value: 'Doors & Windows' } ]; @@ -94,7 +95,10 @@ describe('CategoryFieldStore', () => { .spyOn(categoriesService, 'getChildren') .mockReturnValue(of(CATEGORY_LEVEL_2)); - store.getCategories({ index: 0, item: CATEGORY_LEVEL_1[0] }); + const item = transformCategories( + CATEGORY_LEVEL_1[0] + ) as DotCategoryFieldKeyValueObj; + store.getCategories({ index: 0, item }); expect(getChildrenSpy).toHaveBeenCalledWith(CATEGORY_LEVEL_1[0].inode); }); @@ -103,7 +107,12 @@ describe('CategoryFieldStore', () => { categoriesService.getChildren.mockReturnValue(of(CATEGORY_LEVEL_1)); store.getCategories(); categoriesService.getChildren.mockReturnValue(of(CATEGORY_LEVEL_2)); - store.getCategories({ index: 0, item: CATEGORY_LEVEL_1[0] }); + + const item = transformCategories( + CATEGORY_LEVEL_1[0] + ) as DotCategoryFieldKeyValueObj; + + store.getCategories({ index: 0, item }); expect(store.categories().length).toBe(2); }); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.spec.ts index f7ecfc512e0d..7b87770b3935 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.spec.ts @@ -1,8 +1,9 @@ +import { DotCategory } from '@dotcms/dotcms-models'; + import { categoryDeepCopy, clearCategoriesAfterIndex, clearParentPathAfterIndex, - transformCategories, transformSelectedCategories, updateChecked } from './category-field.utils'; @@ -12,10 +13,7 @@ import { CATEGORY_FIELD_MOCK, CATEGORY_LEVEL_1 } from '../mocks/category-field.mocks'; -import { - DotCategoryFieldCategory, - DotCategoryFieldKeyValueObj -} from '../models/dot-category-field.models'; +import { DotCategoryFieldKeyValueObj } from '../models/dot-category-field.models'; describe('CategoryFieldUtils', () => { describe('getSelectedCategories', () => { @@ -26,8 +24,8 @@ describe('CategoryFieldUtils', () => { it('should return parsed the values', () => { const expected: DotCategoryFieldKeyValueObj[] = [ - { key: '33333', value: 'Electrical' }, - { key: '22222', value: 'Doors & Windows' } + { key: '1f208488057007cedda0e0b5d52ee3b3', value: 'Electrical' }, + { key: 'cb83dc32c0a198fd0ca427b3b587f4ce', value: 'Doors & Windows' } ]; const result = transformSelectedCategories( CATEGORY_FIELD_MOCK, @@ -38,41 +36,9 @@ describe('CategoryFieldUtils', () => { }); }); - describe('addMetadata', () => { - it('should `checked: true` when category has children and exist in the parentPath', () => { - const PARENT_PATH_MOCK = [CATEGORY_LEVEL_1[0].inode]; - const expected = CATEGORY_LEVEL_1.map((item, index) => - index === 0 ? { ...item, checked: true } : { ...item, checked: false } - ); - - const result = transformCategories(CATEGORY_LEVEL_1, PARENT_PATH_MOCK); - expect(result).toEqual(expected); - }); - - it('should `checked: false` when category has children and do not exist in the parentPath', () => { - const PATH_MOCK = []; - const expected = CATEGORY_LEVEL_1.map((item) => { - return { ...item, checked: false }; - }); - - const result = transformCategories(CATEGORY_LEVEL_1, PATH_MOCK); - expect(result).toEqual(expected); - }); - - it('should `checked: false` when category do not has children and do not exist in the parentPath', () => { - const PATH_MOCK = []; - const expected = CATEGORY_LEVEL_1.map((item) => { - return { ...item, checked: false }; - }); - - const result = transformCategories(CATEGORY_LEVEL_1, PATH_MOCK); - expect(result).toEqual(expected); - }); - }); - describe('categoryDeepCopy', () => { it('should create a deep copy of a two-dimensional array of DotCategoryFieldCategory', () => { - const array: DotCategoryFieldCategory[][] = [ + const array: DotCategory[][] = [ CATEGORY_LEVEL_1, [{ ...CATEGORY_LEVEL_1[0], categoryName: 'New Category' }] ]; @@ -87,7 +53,7 @@ describe('CategoryFieldUtils', () => { }); it('should create a deep copy of an empty array', () => { - const array: DotCategoryFieldCategory[][] = []; + const array: DotCategory[][] = []; const copy = categoryDeepCopy(array); // The copy should be equal to the original @@ -95,7 +61,7 @@ describe('CategoryFieldUtils', () => { }); it('should handle mixed content arrays correctly', () => { - const array: DotCategoryFieldCategory[][] = [ + const array: DotCategory[][] = [ CATEGORY_LEVEL_1, [{ ...CATEGORY_LEVEL_1[0], categoryName: 'New Category' }] ]; @@ -112,7 +78,7 @@ describe('CategoryFieldUtils', () => { describe('clearCategoriesAfterIndex', () => { it('should remove all items after the specified index + 1', () => { - const array: DotCategoryFieldCategory[][] = [ + const array: DotCategory[][] = [ CATEGORY_LEVEL_1, [{ ...CATEGORY_LEVEL_1[0], categoryName: 'New Category' }], [{ ...CATEGORY_LEVEL_1[0], categoryName: 'Another Category' }] @@ -129,7 +95,7 @@ describe('CategoryFieldUtils', () => { }); it('should handle an empty array', () => { - const array: DotCategoryFieldCategory[][] = []; + const array: DotCategory[][] = []; const index = 0; const result = clearCategoriesAfterIndex(array, index); @@ -139,7 +105,7 @@ describe('CategoryFieldUtils', () => { }); it('should handle index greater than array length', () => { - const array: DotCategoryFieldCategory[][] = [ + const array: DotCategory[][] = [ CATEGORY_LEVEL_1, [{ ...CATEGORY_LEVEL_1[0], categoryName: 'New Category' }] ]; @@ -199,19 +165,18 @@ describe('CategoryFieldUtils', () => { const storedSelected: DotCategoryFieldKeyValueObj[] = [ { key: CATEGORY_LEVEL_1[0].key, - value: CATEGORY_LEVEL_1[0].categoryName + value: CATEGORY_LEVEL_1[0].categoryName, + inode: CATEGORY_LEVEL_1[0].inode } ]; const selected = [storedSelected[0].key, CATEGORY_LEVEL_1[1].key]; - const item: DotCategoryFieldCategory = { ...CATEGORY_LEVEL_1[1] }; + const item: DotCategoryFieldKeyValueObj = { + key: CATEGORY_LEVEL_1[1].key, + value: CATEGORY_LEVEL_1[1].categoryName, + inode: CATEGORY_LEVEL_1[1].inode + }; - const expected: DotCategoryFieldKeyValueObj[] = [ - ...storedSelected, - { - key: CATEGORY_LEVEL_1[1].key, - value: CATEGORY_LEVEL_1[1].categoryName - } - ]; + const expected: DotCategoryFieldKeyValueObj[] = [...storedSelected, item]; const result = updateChecked(storedSelected, selected, item); @@ -222,11 +187,16 @@ describe('CategoryFieldUtils', () => { const storedSelected: DotCategoryFieldKeyValueObj[] = [ { key: CATEGORY_LEVEL_1[0].key, - value: CATEGORY_LEVEL_1[0].categoryName + value: CATEGORY_LEVEL_1[0].categoryName, + inode: CATEGORY_LEVEL_1[1].inode } ]; const selected = [storedSelected[0].key]; - const item: DotCategoryFieldCategory = { ...CATEGORY_LEVEL_1[0] }; + const item: DotCategoryFieldKeyValueObj = { + key: CATEGORY_LEVEL_1[0].key, + value: CATEGORY_LEVEL_1[0].categoryName, + inode: CATEGORY_LEVEL_1[1].inode + }; const expected: DotCategoryFieldKeyValueObj[] = [...storedSelected]; @@ -239,20 +209,27 @@ describe('CategoryFieldUtils', () => { const storedSelected: DotCategoryFieldKeyValueObj[] = [ { key: CATEGORY_LEVEL_1[0].key, - value: CATEGORY_LEVEL_1[0].categoryName + value: CATEGORY_LEVEL_1[0].categoryName, + inode: CATEGORY_LEVEL_1[0].inode }, { key: CATEGORY_LEVEL_1[1].key, - value: CATEGORY_LEVEL_1[1].categoryName + value: CATEGORY_LEVEL_1[1].categoryName, + inode: CATEGORY_LEVEL_1[1].inode } ]; const selected = [storedSelected[0].key]; - const item: DotCategoryFieldCategory = { ...CATEGORY_LEVEL_1[1] }; + const item: DotCategoryFieldKeyValueObj = { + key: CATEGORY_LEVEL_1[1].key, + value: CATEGORY_LEVEL_1[1].categoryName, + inode: CATEGORY_LEVEL_1[1].inode + }; const expected: DotCategoryFieldKeyValueObj[] = [ { key: CATEGORY_LEVEL_1[0].key, - value: CATEGORY_LEVEL_1[0].categoryName + value: CATEGORY_LEVEL_1[0].categoryName, + inode: CATEGORY_LEVEL_1[0].inode } ]; @@ -265,11 +242,16 @@ describe('CategoryFieldUtils', () => { const storedSelected: DotCategoryFieldKeyValueObj[] = [ { key: CATEGORY_LEVEL_1[0].key, - value: CATEGORY_LEVEL_1[0].categoryName + value: CATEGORY_LEVEL_1[0].categoryName, + inode: CATEGORY_LEVEL_1[0].inode } ]; const selected = [storedSelected[0].key]; - const item: DotCategoryFieldCategory = { ...CATEGORY_LEVEL_1[1] }; + const item: DotCategoryFieldKeyValueObj = { + key: CATEGORY_LEVEL_1[1].key, + value: CATEGORY_LEVEL_1[1].categoryName, + inode: CATEGORY_LEVEL_1[1].inode + }; const expected: DotCategoryFieldKeyValueObj[] = [...storedSelected]; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.ts index f77d98bf12b7..653d3cf30ce5 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.ts @@ -32,9 +32,10 @@ export const transformSelectedCategories = ( * @param keyParentPath - Path of keys to determine clicked state * @returns Transformed category or array of transformed categories with additional properties */ + export const transformCategories = ( categories: DotCategory | DotCategory[], - keyParentPath: string[] + keyParentPath: string[] = [] ): DotCategoryFieldKeyValueObj | DotCategoryFieldKeyValueObj[] => { const transformCategory = (category: DotCategory): DotCategoryFieldKeyValueObj => { const { key, inode, categoryName, childrenCount } = category; From 8ca24bb180c50d2544cc450086d7cb85f31be3f9 Mon Sep 17 00:00:00 2001 From: Arcadio Quintero Date: Thu, 11 Jul 2024 15:48:13 -0400 Subject: [PATCH 06/17] feat(edit-content) fix failing tests #28879 --- ...ategory-field-category-list.component.html | 56 +++++++-------- ...-category-field-search-list.component.html | 72 +++++++++---------- .../dot-category-field-search.component.html | 15 ++-- .../dot-category-field-sidebar.component.html | 24 +++---- .../dot-table-skeleton.component.html | 16 ++--- 5 files changed, 91 insertions(+), 92 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.html index 0709021be1f8..a48526f95b00 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.html @@ -3,41 +3,41 @@ [ngClass]="{ 'no-overflow-x-yet': emptyColumns().length }" class="flex-1 category-list__category-list"> @for (column of categories(); let index = $index; track index) { - -
- @for (item of column; track item.key) { -
- + +
+ @for (item of column; track item.key) { +
+ - + - @if (item.hasChildren) { - - } -
+ @if (item.hasChildren) { + }
+ } +
} @for (_ of emptyColumns(); track _) { -
+
}
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.html index aef3f4f13348..700857ffb40a 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.html @@ -1,42 +1,42 @@
@if (!isLoading()) { - - - - - - - {{ 'edit.content.category-field.search.name' | dm }} - {{ 'edit.content.category-field.search.assignee' | dm }} - - + + + + + + + {{ 'edit.content.category-field.search.name' | dm }} + {{ 'edit.content.category-field.search.assignee' | dm }} + + - - - - - - {{ category.value }} - {{ category.path }} - - - + + + + + + {{ category.value }} + {{ category.path }} + + + } @else { - + }
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html index b527a52a4827..f7049dcedac6 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html @@ -6,14 +6,13 @@ pInputText type="text" /> @if (searchControl.value && !isLoading()) { - - - - } - @if (isLoading()) { - - - + + + + } @if (isLoading()) { + + + } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html index 59c5eda9c383..2164d9a9ed52 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html @@ -31,19 +31,19 @@
@if (store.mode() === 'list') { - + } @else { - + }
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.html index 99d939794bb0..8d7f6659edb7 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.html @@ -6,7 +6,7 @@ @for (col of columns; track $index) { - {{ col }} + {{ col }} } @@ -14,13 +14,13 @@ @for (col of columns; track $index) { - - @if (col === '') { - - } @else { - - } - + + @if (col === '') { + + } @else { + + } + } From dfe26c5d9dc706734a6875e7143dc0ccf7c2fed6 Mon Sep 17 00:00:00 2001 From: Arcadio Quintero Date: Fri, 12 Jul 2024 11:18:33 -0400 Subject: [PATCH 07/17] feat(edit-content) add tests #28879 --- ...-category-field-search-list.component.html | 18 +++-- ...-category-field-search-list.component.scss | 2 +- ...tegory-field-search-list.component.spec.ts | 32 ++++++++- ...ot-category-field-search-list.component.ts | 20 ++---- .../dot-category-field-search.component.html | 14 +++- ...ot-category-field-search.component.spec.ts | 69 +++++++++++++++++-- .../dot-category-field-search.component.ts | 3 +- .../dot-category-field-sidebar.component.html | 2 +- .../store/content-category-field.store.ts | 8 ++- .../utils/category-field.utils.ts | 5 +- 10 files changed, 135 insertions(+), 38 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.html index 700857ffb40a..1964eba92838 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search-list/dot-category-field-search-list.component.html @@ -1,9 +1,10 @@
@if (!isLoading()) { - - + + - {{ 'edit.content.category-field.search.name' | dm }} - {{ 'edit.content.category-field.search.assignee' | dm }} + + {{ 'edit.content.category-field.search.name' | dm }} + + + {{ 'edit.content.category-field.search.assignee' | dm }} + - + @@ -33,6 +38,7 @@ } @else { @if (searchControl.value && !isLoading()) { - + } @if (isLoading()) { - + } - +
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.spec.ts index 288c0de3b0c0..53481930f26e 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.spec.ts @@ -1,10 +1,15 @@ -import { expect } from '@jest/globals'; -import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest'; +import { byTestId, createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest'; + +import { discardPeriodicTasks, fakeAsync } from '@angular/core/testing'; import { DotMessageService } from '@dotcms/data-access'; -import { DotCategoryFieldSearchComponent } from './dot-category-field-search.component'; +import { + DEBOUNCE_TIME, + DotCategoryFieldSearchComponent +} from './dot-category-field-search.component'; +const TERM_TO_SEARCH = 'Wood'; describe('DotCategoryFieldSearchComponent', () => { let spectator: Spectator; @@ -27,7 +32,61 @@ describe('DotCategoryFieldSearchComponent', () => { jest.resetAllMocks(); }); - it('should .....', () => { - expect(spectator.component).not.toBeNull(); + it('should show only the search icon', () => { + spectator.detectChanges(); + + expect(spectator.query(byTestId('search-icon-search'))).toBeTruthy(); + expect(spectator.query(byTestId('search-icon-clear'))).not.toBeTruthy(); + expect(spectator.query(byTestId('search-icon-loading'))).not.toBeTruthy(); + }); + + it('should emit "term" with correct value on input change', fakeAsync(() => { + const termSpy = jest.spyOn(spectator.component.term, 'emit'); + const input = spectator.query(byTestId('search-input')) as HTMLInputElement; + + spectator.typeInElement(TERM_TO_SEARCH, input); + + spectator.tick(DEBOUNCE_TIME + 100); + + expect(termSpy).toHaveBeenCalledWith(TERM_TO_SEARCH); + + discardPeriodicTasks(); + })); + + it('should clear input and emit "changeMode" when clear icon is clicked', fakeAsync(() => { + const changeModeSpy = jest.spyOn(spectator.component.changeMode, 'emit'); + const input = spectator.query(byTestId('search-input')) as HTMLInputElement; + spectator.typeInElement(TERM_TO_SEARCH, input); + spectator.tick(DEBOUNCE_TIME + 100); + + spectator.detectChanges(); + + spectator.click(spectator.query(byTestId('search-icon-clear'))); + + expect(input.value).toBe(''); + expect(changeModeSpy).toHaveBeenCalledWith('list'); + discardPeriodicTasks(); + })); + + it('should show loading icon when isLoading is true', () => { + spectator.setInput('isLoading', true); + spectator.detectChanges(); + + expect(spectator.query(byTestId('search-icon-clear'))).not.toBeTruthy(); + expect(spectator.query(byTestId('search-icon-loading'))).toBeTruthy(); }); + + it('should show clear icon when there is input and not loading', fakeAsync(() => { + const input = spectator.query(byTestId('search-input')) as HTMLInputElement; + spectator.typeInElement('search term', input); + spectator.tick(DEBOUNCE_TIME + 100); + + spectator.setInput('isLoading', false); + spectator.detectChanges(); + + const clearIcon = spectator.query(byTestId('search-icon-clear')); + expect(clearIcon).toBeTruthy(); + + discardPeriodicTasks(); + })); }); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.ts index f8b7d1bdae54..26dca4ed7fb7 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.ts @@ -18,7 +18,8 @@ import { debounceTime, filter } from 'rxjs/operators'; import { DotMessagePipe } from '@dotcms/ui'; -const DEBOUNCE_TIME = 300; +export const DEBOUNCE_TIME = 300; + const MINIMUM_CHARACTERS = 3; @Component({ diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html index 2164d9a9ed52..22b14a3d4cb8 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html @@ -42,7 +42,7 @@ (itemChecked)="store.addSelected($event)" (removeItem)="store.removeSelected($event)" [isLoading]="store.isSearchLoading()" - [categories]="store.searchCategories()" + [categories]="store.searchCategoryList()" [selected]="store.selected()" /> } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts index e4bced830b97..82112116c85b 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts @@ -171,7 +171,13 @@ export const CategoryFieldStore = signalStore( * Clears all categories from the store, effectively resetting state related to categories and their parent paths. */ clean() { - patchState(store, { categories: [], keyParentPath: [], mode: 'list', filter: '' }); + patchState(store, { + categories: [], + keyParentPath: [], + mode: 'list', + filter: '', + searchCategories: [] + }); }, /** diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.ts index 653d3cf30ce5..4637e04b24a4 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.ts @@ -41,12 +41,15 @@ export const transformCategories = ( const { key, inode, categoryName, childrenCount } = category; const hasChildren = childrenCount > 0; + const path = category.parentList ? getParentPath(category) : ''; + return { key, inode, value: categoryName, hasChildren, - clicked: hasChildren && keyParentPath.includes(key) + clicked: hasChildren && keyParentPath.includes(key), + path }; }; From 809b153541d68792277ee8824ac2b5ec2cb490a6 Mon Sep 17 00:00:00 2001 From: Arcadio Quintero Date: Fri, 12 Jul 2024 11:21:10 -0400 Subject: [PATCH 08/17] feat(edit-content) add id and scope to th #28879 --- .../dot-table-skeleton/dot-table-skeleton.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.html index 8d7f6659edb7..3e11f0fba333 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-table-skeleton/dot-table-skeleton.component.html @@ -6,7 +6,7 @@ @for (col of columns; track $index) { - {{ col }} + {{ col }} } From 662536ebb70e276183cf88b10088e784feebdec8 Mon Sep 17 00:00:00 2001 From: Arcadio Quintero Date: Fri, 12 Jul 2024 11:54:01 -0400 Subject: [PATCH 09/17] feat(edit-content) add utility tests #28879 --- .../utils/category-field.utils.spec.ts | 333 +++++++++++++++++- 1 file changed, 332 insertions(+), 1 deletion(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.spec.ts index 7b87770b3935..0accca70bc7e 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/utils/category-field.utils.spec.ts @@ -1,9 +1,12 @@ -import { DotCategory } from '@dotcms/dotcms-models'; +import { DotCategory, DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models'; import { + addSelected, categoryDeepCopy, clearCategoriesAfterIndex, clearParentPathAfterIndex, + removeItemByKey, + transformCategories, transformSelectedCategories, updateChecked } from './category-field.utils'; @@ -11,6 +14,7 @@ import { import { CATEGORY_FIELD_CONTENTLET_MOCK, CATEGORY_FIELD_MOCK, + CATEGORY_FIELD_VARIABLE_NAME, CATEGORY_LEVEL_1 } from '../mocks/category-field.mocks'; import { DotCategoryFieldKeyValueObj } from '../models/dot-category-field.models'; @@ -260,4 +264,331 @@ describe('CategoryFieldUtils', () => { expect(result).toEqual(expected); }); }); + + describe('removeItemByKey', () => { + let array: DotCategoryFieldKeyValueObj[]; + + beforeEach(() => { + array = [ + { key: '1', value: 'Category 1', inode: 'inode1', path: 'path1' }, + { key: '2', value: 'Category 2', inode: 'inode2', path: 'path2' }, + { key: '3', value: 'Category 3', inode: 'inode3', path: 'path3' } + ]; + }); + + it('should remove item with a single key', () => { + const key = '2'; + const result = removeItemByKey(array, key); + expect(result).toEqual([ + { key: '1', value: 'Category 1', inode: 'inode1', path: 'path1' }, + { key: '3', value: 'Category 3', inode: 'inode3', path: 'path3' } + ]); + }); + + it('should remove items with an array of keys', () => { + const keys = ['1', '3']; + const result = removeItemByKey(array, keys); + expect(result).toEqual([ + { key: '2', value: 'Category 2', inode: 'inode2', path: 'path2' } + ]); + }); + + it('should return the same array if key is not found', () => { + const key = '4'; + const result = removeItemByKey(array, key); + expect(result).toEqual(array); + }); + + it('should return the same array if keys array is empty', () => { + const keys: string[] = []; + const result = removeItemByKey(array, keys); + expect(result).toEqual(array); + }); + + it('should handle an empty array input', () => { + const emptyArray: DotCategoryFieldKeyValueObj[] = []; + const key = '1'; + const result = removeItemByKey(emptyArray, key); + expect(result).toEqual([]); + }); + + it('should handle an empty array input with keys array', () => { + const emptyArray: DotCategoryFieldKeyValueObj[] = []; + const keys = ['1', '2']; + const result = removeItemByKey(emptyArray, keys); + expect(result).toEqual([]); + }); + }); + describe('addSelected', () => { + let array: DotCategoryFieldKeyValueObj[]; + + beforeEach(() => { + array = [ + { key: '1', value: 'Category 1', inode: 'inode1', path: 'path1' }, + { key: '2', value: 'Category 2', inode: 'inode2', path: 'path2' } + ]; + }); + + it('should add a single item to the array', () => { + const newItem = { key: '3', value: 'Category 3', inode: 'inode3', path: 'path3' }; + const result = addSelected(array, newItem); + expect(result).toEqual([ + { key: '1', value: 'Category 1', inode: 'inode1', path: 'path1' }, + { key: '2', value: 'Category 2', inode: 'inode2', path: 'path2' }, + { key: '3', value: 'Category 3', inode: 'inode3', path: 'path3' } + ]); + }); + + it('should add multiple items to the array', () => { + const newItems = [ + { key: '3', value: 'Category 3', inode: 'inode3', path: 'path3' }, + { key: '4', value: 'Category 4', inode: 'inode4', path: 'path4' } + ]; + const result = addSelected(array, newItems); + expect(result).toEqual([ + { key: '1', value: 'Category 1', inode: 'inode1', path: 'path1' }, + { key: '2', value: 'Category 2', inode: 'inode2', path: 'path2' }, + { key: '3', value: 'Category 3', inode: 'inode3', path: 'path3' }, + { key: '4', value: 'Category 4', inode: 'inode4', path: 'path4' } + ]); + }); + + it('should not add duplicate items to the array', () => { + const newItem = { key: '2', value: 'Category 2', inode: 'inode2', path: 'path2' }; + const result = addSelected(array, newItem); + expect(result).toEqual([ + { key: '1', value: 'Category 1', inode: 'inode1', path: 'path1' }, + { key: '2', value: 'Category 2', inode: 'inode2', path: 'path2' } + ]); + }); + + it('should handle adding items to an empty array', () => { + const emptyArray: DotCategoryFieldKeyValueObj[] = []; + const newItem = { key: '1', value: 'Category 1', inode: 'inode1', path: 'path1' }; + const result = addSelected(emptyArray, newItem); + expect(result).toEqual([ + { key: '1', value: 'Category 1', inode: 'inode1', path: 'path1' } + ]); + }); + + it('should handle adding an empty array of items', () => { + const newItems: DotCategoryFieldKeyValueObj[] = []; + const result = addSelected(array, newItems); + expect(result).toEqual(array); + }); + + it('should add items correctly when array is empty', () => { + const emptyArray: DotCategoryFieldKeyValueObj[] = []; + const newItems = [ + { key: '1', value: 'Category 1', inode: 'inode1', path: 'path1' }, + { key: '2', value: 'Category 2', inode: 'inode2', path: 'path2' } + ]; + const result = addSelected(emptyArray, newItems); + expect(result).toEqual(newItems); + }); + }); + + describe('transformCategories', () => { + const keyParentPath = ['1']; // make true clicked + + it('should transform a single category', () => { + const category: DotCategory = { + key: '1', + inode: 'inode1', + categoryName: 'Category 1', + childrenCount: 2, + active: true, + categoryVelocityVarName: '', + description: null, + iDate: 0, + identifier: null, + keywords: null, + modDate: 0, + owner: '', + sortOrder: 0, + type: '', + parentList: [ + { key: 'root', categoryName: 'Root Parent', inode: 'rootInode' }, + { + key: 'parent1', + categoryName: 'Parent 1', + inode: 'parentInode1' + } + ] + }; + + const result = transformCategories(category, keyParentPath); + + expect(result).toEqual({ + key: '1', + inode: 'inode1', + value: 'Category 1', + hasChildren: true, + clicked: true, // from keyParentPath + path: 'Parent 1' + }); + }); + + it('should transform an array of categories', () => { + const categories: DotCategory[] = [ + { + key: '1', + inode: 'inode1', + categoryName: 'Category 1', + childrenCount: 2, + active: true, + categoryVelocityVarName: '', + description: null, + iDate: 0, + identifier: null, + keywords: null, + modDate: 0, + owner: '', + sortOrder: 0, + type: '', + parentList: [ + { key: 'root', categoryName: 'Root Parent', inode: 'rootInode' }, + { + key: 'parent1', + categoryName: 'Parent 1', + inode: 'parentInode1' + } + ] + }, + { + key: '2', + inode: 'inode2', + categoryName: 'Category 2', + childrenCount: 0, + active: true, + categoryVelocityVarName: '', + description: null, + iDate: 0, + identifier: null, + keywords: null, + modDate: 0, + owner: '', + sortOrder: 0, + type: '', + parentList: [ + { key: 'root', categoryName: 'Root Parent', inode: 'rootInode' }, + { + key: 'parent1', + categoryName: 'Parent 1', + inode: 'parentInode1' + } + ] + } + ]; + + const result = transformCategories(categories, keyParentPath); + + expect(result).toEqual([ + { + key: '1', + inode: 'inode1', + value: 'Category 1', + hasChildren: true, + clicked: true, + path: 'Parent 1' + }, + { + key: '2', + inode: 'inode2', + value: 'Category 2', + hasChildren: false, + clicked: false, + path: 'Parent 1' + } + ]); + }); + + it('should handle category with no parentList', () => { + const category: DotCategory = { + key: '1', + inode: 'inode1', + categoryName: 'Category 1', + childrenCount: 0, + active: true, + categoryVelocityVarName: '', + description: null, + iDate: 0, + identifier: null, + keywords: null, + modDate: 0, + owner: '', + sortOrder: 0, + type: '' + }; + + const result = transformCategories(category, keyParentPath); + + expect(result).toEqual({ + key: '1', + inode: 'inode1', + value: 'Category 1', + hasChildren: false, + clicked: false, + path: '' + }); + }); + + it('should handle empty array of categories', () => { + const categories: DotCategory[] = []; + + const result = transformCategories(categories, keyParentPath); + + expect(result).toEqual([]); + }); + }); + describe('transformSelectedCategories', () => { + it('should return an empty array if contentlet is not provided', () => { + const result = transformSelectedCategories(CATEGORY_FIELD_MOCK, null as never); + expect(result).toEqual([]); + }); + + it('should return an empty array if variable is not provided', () => { + const variableField: DotCMSContentTypeField = { ...CATEGORY_FIELD_MOCK, variable: '' }; + const result = transformSelectedCategories( + variableField, + CATEGORY_FIELD_CONTENTLET_MOCK + ); + expect(result).toEqual([]); + }); + + it('should return an empty array if selected categories are not present in contentlet', () => { + const variableField: DotCMSContentTypeField = { + ...CATEGORY_FIELD_MOCK, + variable: 'nonexistentField' + }; + const result = transformSelectedCategories( + variableField, + CATEGORY_FIELD_CONTENTLET_MOCK + ); + expect(result).toEqual([]); + }); + + it('should transform selected categories correctly', () => { + const result = transformSelectedCategories( + CATEGORY_FIELD_MOCK, + CATEGORY_FIELD_CONTENTLET_MOCK + ); + expect(result).toEqual([ + { key: '1f208488057007cedda0e0b5d52ee3b3', value: 'Electrical' }, + { key: 'cb83dc32c0a198fd0ca427b3b587f4ce', value: 'Doors & Windows' } + ]); + }); + + it('should handle empty selected categories in contentlet', () => { + const contentletWithEmptyCategories: DotCMSContentlet = { + ...CATEGORY_FIELD_CONTENTLET_MOCK, + [CATEGORY_FIELD_VARIABLE_NAME]: [] + }; + const result = transformSelectedCategories( + CATEGORY_FIELD_MOCK, + contentletWithEmptyCategories + ); + expect(result).toEqual([]); + }); + }); }); From 3ad8d507c4d36ac1183bac7aa92fbcf1fb039f57 Mon Sep 17 00:00:00 2001 From: Arcadio Quintero Date: Fri, 12 Jul 2024 15:38:12 -0400 Subject: [PATCH 10/17] feat(edit-content) fix merge #28879 --- .../dot-category-field-sidebar.component.ts | 4 +--- .../mocks/category-field.mocks.ts | 4 ++-- .../models/dot-category-field.models.ts | 5 +++++ .../store/content-category-field.store.ts | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.ts index 0879219bae46..2bdeef23b378 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.ts @@ -20,9 +20,7 @@ import { DotMessagePipe } from '@dotcms/ui'; import { CategoryFieldStore } from '../../store/content-category-field.store'; import { DotCategoryFieldCategoryListComponent } from '../dot-category-field-category-list/dot-category-field-category-list.component'; import { DotCategoryFieldSearchComponent } from '../dot-category-field-search/dot-category-field-search.component'; -import { - DotCategoryFieldSearchListComponent -} from "../dot-category-field-search-list/dot-category-field-search-list.component"; +import { DotCategoryFieldSearchListComponent } from '../dot-category-field-search-list/dot-category-field-search-list.component'; /** * The DotCategoryFieldSidebarComponent is a sidebar panel that allows editing of content category field. diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/mocks/category-field.mocks.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/mocks/category-field.mocks.ts index 53cefbf61914..c260c778826c 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/mocks/category-field.mocks.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/mocks/category-field.mocks.ts @@ -1,8 +1,8 @@ import { DotCategory, DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models'; import { MockDotMessageService } from '@dotcms/utils-testing'; -import { DotCategoryFieldKeyValueObj } from "../models/dot-category-field.models"; -import { transformCategories } from "../utils/category-field.utils"; +import { DotCategoryFieldKeyValueObj } from '../models/dot-category-field.models'; +import { transformCategories } from '../utils/category-field.utils'; export const CATEGORY_FIELD_VARIABLE_NAME = 'categorias'; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/models/dot-category-field.models.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/models/dot-category-field.models.ts index 42806d525a50..c6c0df9b6ef0 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/models/dot-category-field.models.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/models/dot-category-field.models.ts @@ -35,3 +35,8 @@ export interface DotTableHeaderCheckboxSelectEvent { originalEvent?: Event; checked: boolean; } + +/** + * Represents the view mode for a category field. + */ +export type CategoryFieldViewMode = 'list' | 'search'; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts index 82112116c85b..cbca89e46de7 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts @@ -14,8 +14,8 @@ import { DotCMSContentTypeField } from '@dotcms/dotcms-models'; -import { CategoryFieldViewMode } from '../components/dot-category-field-sidebar/dot-category-field-sidebar.component'; import { + CategoryFieldViewMode, DotCategoryFieldItem, DotCategoryFieldKeyValueObj } from '../models/dot-category-field.models'; From 2e72fc969b716e37ddef22636d3b01fb0674465c Mon Sep 17 00:00:00 2001 From: Arcadio Quintero Date: Fri, 12 Jul 2024 16:31:31 -0400 Subject: [PATCH 11/17] feat(edit-content) fix comments #28879 --- .../dot-category-field-search.component.html | 4 +- ...ot-category-field-search.component.spec.ts | 5 +- .../dot-category-field-search.component.ts | 47 +++++-------------- 3 files changed, 17 insertions(+), 39 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html index 607886cae825..2ff2d3c63adb 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.html @@ -6,14 +6,14 @@ data-testId="search-input" pInputText type="text" /> - @if (searchControl.value && !isLoading()) { + @if (searchControl.value && !$isLoading()) { - } @if (isLoading()) { + } @if ($isLoading()) { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.spec.ts index 53481930f26e..3f755626efed 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.spec.ts @@ -20,11 +20,10 @@ describe('DotCategoryFieldSearchComponent', () => { beforeEach(() => { spectator = createComponent({ - props: { - isLoading: false - } + detectChanges: false }); + spectator.setInput('isLoading', false); spectator.detectChanges(); }); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.ts index 26dca4ed7fb7..48630a266255 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-search/dot-category-field-search.component.ts @@ -1,14 +1,5 @@ import { CommonModule } from '@angular/common'; -import { - AfterViewInit, - ChangeDetectionStrategy, - Component, - DestroyRef, - EventEmitter, - inject, - input, - Output -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, input, Output } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; @@ -30,7 +21,7 @@ const MINIMUM_CHARACTERS = 3; styleUrl: './dot-category-field-search.component.scss', changeDetection: ChangeDetectionStrategy.OnPush }) -export class DotCategoryFieldSearchComponent implements AfterViewInit { +export class DotCategoryFieldSearchComponent { searchControl = new FormControl(); /** @@ -47,11 +38,18 @@ export class DotCategoryFieldSearchComponent implements AfterViewInit { /** * Represents the boolean variable isLoading. */ - isLoading = input.required(); - #destroyRef = inject(DestroyRef); + $isLoading = input(false, { alias: 'isLoading' }); - ngAfterViewInit(): void { - this.listenInputChanges(); + constructor() { + this.searchControl.valueChanges + .pipe( + takeUntilDestroyed(), + debounceTime(DEBOUNCE_TIME), + filter((value: string) => value.length >= MINIMUM_CHARACTERS) + ) + .subscribe((value: string) => { + this.term.emit(value); + }); } /** @@ -63,23 +61,4 @@ export class DotCategoryFieldSearchComponent implements AfterViewInit { this.searchControl.setValue(''); this.changeMode.emit('list'); } - - /** - * Sets up the search input observable to listen to input events, - * debounce the input, filter by minimum character length, - * and emit the search query value. - * - * @private - */ - private listenInputChanges(): void { - this.searchControl.valueChanges - .pipe( - takeUntilDestroyed(this.#destroyRef), - debounceTime(DEBOUNCE_TIME), - filter((value: string) => value.length >= MINIMUM_CHARACTERS) - ) - .subscribe((value: string) => { - this.term.emit(value); - }); - } } From 4126baa595e8f0a76d363d2152c63f0a71bef4e9 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Fri, 12 Jul 2024 17:39:10 -0400 Subject: [PATCH 12/17] feat(edit-content): add remove functionality to category chips --- .../dot-category-field-chips.component.html | 3 +- ...dot-category-field-chips.component.spec.ts | 28 +++++++++--- .../dot-category-field-chips.component.ts | 21 ++++++++- ...edit-content-category-field.component.html | 4 +- ...t-content-category-field.component.spec.ts | 45 +++++++++++++++++-- ...t-edit-content-category-field.component.ts | 13 +++--- 6 files changed, 97 insertions(+), 17 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-chips/dot-category-field-chips.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-chips/dot-category-field-chips.component.html index a3bf0f1018ef..bfe6f9f75d89 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-chips/dot-category-field-chips.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-chips/dot-category-field-chips.component.html @@ -5,7 +5,8 @@ [removable]="true" [label]="category.value" tooltipPosition="top" - styleClass="p-chip-sm" /> + styleClass="p-chip-sm" + (onRemove)="onRemove(category)" /> } @if ($showAllBtn()) {