(() => {
+ 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 5236a116a5ec..a6265c188176 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
@@ -1,14 +1,14 @@
@if (store.selected().length) {
-
- @for (category of store.selected(); track category.key) {
-
- }
-
+
+ @for (category of store.selected(); track category.key) {
+
+ }
+
}
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 e8048b2b97b6..eac713c13f88 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
@@ -24,3 +24,8 @@ export type DotCategoryFieldItem = { index: number; item: DotCategory };
export interface DotCategoryFieldCategory extends DotCategory {
checked?: boolean;
}
+
+export interface DotCategoryFieldCategorySearchedItems
+ extends Pick {
+ path: string;
+}
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 477d9bf4cdaa..8e36f9d07096 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
@@ -13,6 +13,22 @@ export const API_URL = '/api/v1/categories';
export const ITEMS_PER_PAGE = 7000;
+export interface GetChildrenParams {
+ inode: string;
+ per_page: number;
+ direction: string;
+ showChildrenCount: boolean;
+ filter?: string;
+ allLevels?: boolean;
+}
+
+const DEFAULT_PARAMS: Omit = {
+ per_page: 7000,
+ direction: 'ASC',
+ showChildrenCount: true,
+ allLevels: false
+};
+
/**
* CategoriesService class.
*
@@ -26,19 +42,52 @@ export class CategoriesService {
* Retrieves the children of a given inode.
*
* @param {string} inode - The inode of the parent node.
+ * @param params
* @returns {Observable} - An Observable that emits the children of the given inode as an array of DotCategory objects.
*/
- getChildren(inode: string): Observable {
- const params = new HttpParams()
- .set('per_page', ITEMS_PER_PAGE)
- .set('direction', 'ASC')
- .set('inode', inode)
- .set('showChildrenCount', 'true');
+ getChildren(
+ inode: string,
+ params: Partial = {}
+ ): Observable {
+ const mergedParams = this.mergeParams({ ...params, inode });
+ const httpParams = this.toHttpParams(mergedParams);
return this.#http
.get>(`${API_URL}/children`, {
- params
+ params: httpParams
})
.pipe(pluck('entity'));
}
+
+ /**
+ * Merges default parameters with provided parameters.
+ *
+ * @param {Partial} params - The provided parameters.
+ * @returns {GetChildrenParams} - The merged parameters.
+ */
+ private mergeParams(params: Partial): GetChildrenParams {
+ return { ...DEFAULT_PARAMS, ...params } as GetChildrenParams;
+ }
+
+ /**
+ * Converts an object to HttpParams.
+ *
+ * @param {GetChildrenParams} params - The parameters object.
+ * @returns {HttpParams} - The HttpParams object.
+ */
+ private toHttpParams(params: GetChildrenParams): HttpParams {
+ let httpParams = new HttpParams()
+ .set('inode', params.inode)
+ .set('per_page', params.per_page.toString())
+ .set('direction', params.direction);
+
+ if (!params.filter) {
+ // No add showChildrenCount when we use filter
+ httpParams = httpParams.set('showChildrenCount', params.showChildrenCount.toString());
+ } else {
+ httpParams = httpParams.set('filter', params.filter).set('allLevels', 'true');
+ }
+
+ return httpParams;
+ }
}
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 a62b6b7960ff..10bd7048e182 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,7 +5,7 @@ import { pipe } from 'rxjs';
import { computed, inject } from '@angular/core';
-import { filter, switchMap, tap } from 'rxjs/operators';
+import { delay, filter, switchMap, tap } from 'rxjs/operators';
import {
ComponentStatus,
@@ -14,8 +14,10 @@ import {
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';
@@ -26,7 +28,9 @@ import {
clearCategoriesAfterIndex,
clearParentPathAfterIndex,
getSelectedCategories,
- updateChecked
+ transformedData,
+ updateChecked,
+ updateSelectedFromSearch
} from '../utils/category-field.utils';
export type CategoryFieldState = {
@@ -35,6 +39,10 @@ export type CategoryFieldState = {
categories: DotCategoryFieldCategory[][];
parentPath: string[];
state: ComponentStatus;
+ // search
+ mode: CategoryFieldViewMode;
+ filter: string;
+ searchCategories: DotCategoryFieldCategory[];
};
export const initialState: CategoryFieldState = {
@@ -42,7 +50,10 @@ export const initialState: CategoryFieldState = {
selected: [],
categories: [],
parentPath: [],
- state: ComponentStatus.IDLE
+ state: ComponentStatus.IDLE,
+ mode: 'list',
+ filter: '',
+ searchCategories: []
};
/**
@@ -52,33 +63,46 @@ export const initialState: CategoryFieldState = {
*/
export const CategoryFieldStore = signalStore(
withState(initialState),
- withComputed(({ field, categories, selected, parentPath }) => ({
+ withComputed((store) => ({
/**
* Current selected items (key) from the contentlet
*/
- selectedCategoriesValues: computed(() => selected().map((item) => item.key)),
+ selectedCategoriesValues: computed(() => store.selected().map((item) => item.key)),
/**
* Categories for render with added properties
*/
categoryList: computed(() =>
- categories().map((column) => addMetadata(column, parentPath()))
+ store.categories().map((column) => addMetadata(column, store.parentPath()))
),
/**
* Indicates whether any categories are selected.
*/
- hasSelectedCategories: computed(() => !!selected().length),
+ hasSelectedCategories: computed(() => !!store.selected().length),
/**
* Get the root category inode.
*/
- rootCategoryInode: computed(() => field().values),
+ rootCategoryInode: computed(() => store.field().values),
/**
* Retrieves the value of the field variable.
*/
- fieldVariableName: computed(() => field().variable)
+ fieldVariableName: computed(() => store.field().variable),
+
+ // Search
+ getSearchedCategories: computed(() => transformedData(store.searchCategories())), // remove this one
+
+ isSearchLoading: computed(
+ () => store.mode() === 'search' && store.state() === ComponentStatus.LOADING
+ ),
+ searchCategoriesFound: computed(
+ () =>
+ store.mode() === 'search' &&
+ store.state() === ComponentStatus.LOADED &&
+ store.filter()
+ )
})),
withMethods((store, categoryService = inject(CategoriesService)) => ({
/**
@@ -92,6 +116,14 @@ export const CategoryFieldStore = signalStore(
});
},
+ setMode(mode: 'list' | 'search'): void {
+ patchState(store, {
+ mode,
+ searchCategories: [],
+ filter: ''
+ });
+ },
+
/**
* Updates the selected items based on the provided item.
*/
@@ -107,6 +139,25 @@ export const CategoryFieldStore = signalStore(
});
},
+ updateSelectedFromSearch(selectedItems: DotCategoryFieldCategorySearchedItems[]): void {
+ const currentChecked: DotCategoryFieldKeyValueObj[] = updateSelectedFromSearch(
+ store.selected(),
+ selectedItems,
+ store.searchCategories()
+ );
+
+ patchState(store, {
+ selected: [...currentChecked]
+ });
+ },
+
+ /**
+ * Clears all categories from the store, effectively resetting state related to categories and their parent paths.
+ */
+ clean() {
+ patchState(store, { categories: [], parentPath: [], mode: 'list', filter: '' });
+ },
+
/**
* Fetches categories from a given iNode category parent.
* This method accepts either void to get the parent, or an index and item returned after clicking an item with children.
@@ -169,11 +220,27 @@ 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: [] });
- }
+ search: rxMethod(
+ pipe(
+ 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,
+ state: ComponentStatus.LOADED
+ });
+ },
+ error: () => {
+ // TODO: Add Error Handler
+ patchState(store, { state: ComponentStatus.IDLE });
+ }
+ })
+ );
+ })
+ )
+ )
}))
);
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 1af7af5a369a..75adf9569fe0 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,6 +2,7 @@ import { DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models'
import {
DotCategoryFieldCategory,
+ DotCategoryFieldCategorySearchedItems,
DotCategoryFieldKeyValueObj
} from '../models/dot-category-field.models';
@@ -46,6 +47,25 @@ export const addMetadata = (
});
};
+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(' / ');
+
+ return {
+ categoryName: item.categoryName,
+ key: item.key,
+ inode: item.inode,
+ path: path
+ };
+ });
+};
+
/**
* Deep copy of the matrix
* @param array
@@ -92,6 +112,39 @@ 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.
*
diff --git a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties
index 2a07847b5276..dfd2580af605 100644
--- a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties
+++ b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties
@@ -5746,3 +5746,8 @@ content.type.form.banner.message= Enable Edit Content Beta for a fresh ed
edit.content.category-field.show-categories-dialog=Select
edit.content.category-field.sidebar.header.select-categories=Select categories
edit.content.category-field.sidebar.button.clear-all=Clear all
+
+edit.content.category-field.search.name=Name
+edit.content.category-field.search.assignee=Assignee
+edit.content.category-field.search.input.placeholder=Search
+