From 38d1e3438038c88aedd8896adb530ec382ffb516 Mon Sep 17 00:00:00 2001 From: Arcadio Quintero Date: Mon, 8 Jul 2024 11:23:56 -0400 Subject: [PATCH] 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 | 29 +++++- .../dot-table-skeleton.component.html | 27 ++++++ .../dot-table-skeleton.component.scss | 8 ++ .../dot-table-skeleton.component.ts | 37 +++++++ ...edit-content-category-field.component.html | 20 ++-- .../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 | 5 + 21 files changed, 616 insertions(+), 37 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 +