diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 327b456bd5..8eb55c55d8 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -68,6 +68,7 @@ "./repository": "./dist-cms/packages/core/repository/index.js", "./resources": "./dist-cms/packages/core/resources/index.js", "./router": "./dist-cms/packages/core/router/index.js", + "./search": "./dist-cms/packages/search/index.js", "./section": "./dist-cms/packages/core/section/index.js", "./settings": "./dist-cms/packages/settings/index.js", "./server-file-system": "./dist-cms/packages/core/server-file-system/index.js", diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts index e677d461ff..f26833d53e 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts @@ -804,7 +804,7 @@ export const data: Array = [ value: [ { alias: 'sortOrder', header: 'Sort order', isSystem: true, nameTemplate: '' }, { alias: 'updateDate', header: 'Last edited', isSystem: true }, - { alias: 'owner', header: 'Created by', isSystem: true }, + { alias: 'creator', header: 'Created by', isSystem: true }, ], }, { alias: 'orderBy', value: 'updateDate' }, @@ -849,7 +849,7 @@ export const data: Array = [ value: [ { alias: 'sortOrder', header: 'Sort order', isSystem: true, nameTemplate: '' }, { alias: 'updateDate', header: 'Last edited', isSystem: true }, - { alias: 'owner', header: 'Created by', isSystem: true }, + { alias: 'creator', header: 'Created by', isSystem: true }, ], }, { alias: 'orderBy', value: 'updateDate' }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/entity-actions-bundle/entity-actions-bundle.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/entity-actions-bundle/entity-actions-bundle.element.ts index 98b3c13bea..71090ba875 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/entity-actions-bundle/entity-actions-bundle.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/entity-actions-bundle/entity-actions-bundle.element.ts @@ -75,7 +75,11 @@ export class UmbEntityActionsBundleElement extends UmbLitElement { #openContextMenu() { if (!this.entityType) throw new Error('Entity type is not defined'); if (this.unique === undefined) throw new Error('Unique is not defined'); - this.#sectionSidebarContext?.toggleContextMenu(this.entityType, this.unique, this.label); + this.#sectionSidebarContext?.toggleContextMenu(this, { + entityType: this.entityType, + unique: this.unique, + headline: this.label, + }); } async #onFirstActionClick(event: PointerEvent) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts index 58cac8ebaa..bdd6749492 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts @@ -62,6 +62,8 @@ import type { ManifestEntityUserPermission } from './entity-user-permission.mode import type { ManifestGranularUserPermission } from './user-granular-permission.model.js'; import type { ManifestCollectionAction } from './collection-action.model.js'; import type { ManifestMfaLoginProvider } from './mfa-login-provider.model.js'; +import type { ManifestSearchProvider } from './search-provider.model.js'; +import type { ManifestSearchResultItem } from './search-result-item.model.js'; import type { ManifestAppEntryPoint } from './app-entry-point.model.js'; import type { ManifestBackofficeEntryPoint } from './backoffice-entry-point.model.js'; import type { ManifestEntryPoint } from './entry-point.model.js'; @@ -96,6 +98,8 @@ export type * from './package-view.model.js'; export type * from './property-action.model.js'; export type * from './property-editor.model.js'; export type * from './repository.model.js'; +export type * from './search-provider.model.js'; +export type * from './search-result-item.model.js'; export type * from './section-sidebar-app.model.js'; export type * from './section-view.model.js'; export type * from './section.model.js'; @@ -145,28 +149,31 @@ export type ManifestTypes = | ManifestAppEntryPoint | ManifestAuthProvider | ManifestBackofficeEntryPoint - | ManifestBundle | ManifestBlockEditorCustomView + | ManifestBundle | ManifestCollection - | ManifestCollectionView | ManifestCollectionAction + | ManifestCollectionView + | ManifestCondition | ManifestCurrentUserAction | ManifestCurrentUserActionDefaultKind - | ManifestCondition | ManifestDashboard | ManifestDashboardCollection | ManifestDynamicRootOrigin | ManifestDynamicRootQueryStep | ManifestEntityActions | ManifestEntityBulkAction + | ManifestEntityUserPermission | ManifestEntryPoint | ManifestExternalLoginProvider | ManifestGlobalContext + | ManifestGranularUserPermission | ManifestHeaderApp | ManifestHeaderAppButtonKind | ManifestHealthCheck | ManifestIcons | ManifestItemStore + | ManifestLocalization | ManifestMenu | ManifestMenuItem | ManifestMenuItemTreeKind @@ -177,6 +184,8 @@ export type ManifestTypes = | ManifestPropertyEditorSchema | ManifestPropertyEditorUi | ManifestRepository + | ManifestSearchProvider + | ManifestSearchResultItem | ManifestSection | ManifestSectionSidebarApp | ManifestSectionSidebarAppMenuKind @@ -184,7 +193,6 @@ export type ManifestTypes = | ManifestStore | ManifestTheme | ManifestTinyMcePlugin - | ManifestLocalization | ManifestTree | ManifestTreeItem | ManifestTreeStore @@ -195,6 +203,4 @@ export type ManifestTypes = | ManifestWorkspaceFooterApps | ManifestWorkspaces | ManifestWorkspaceViews - | ManifestEntityUserPermission - | ManifestGranularUserPermission | ManifestBase; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/search-provider.model.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/search-provider.model.ts new file mode 100644 index 0000000000..c5c7e7b6ed --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/search-provider.model.ts @@ -0,0 +1,18 @@ +import type { UmbSearchProvider, UmbSearchResultItemModel } from '@umbraco-cms/backoffice/search'; +import type { ManifestApi } from '@umbraco-cms/backoffice/extension-api'; + +/** + * Represents an search provider that can be used to search. + */ +export interface ManifestSearchProvider extends ManifestApi> { + type: 'searchProvider'; + + meta?: MetaSearchProvider; +} + +export interface MetaSearchProvider { + /** + * The label of the provider that is shown to the user. + */ + label?: string; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/search-result-item.model.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/search-result-item.model.ts new file mode 100644 index 0000000000..26d3b338d1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/search-result-item.model.ts @@ -0,0 +1,9 @@ +import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api'; + +/** + * Represents a search result element. + */ +export interface ManifestSearchResultItem extends ManifestElement { + type: 'searchResultItem'; + forEntityTypes: Array; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-sidebar-context-menu/section-sidebar-context-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-sidebar-context-menu/section-sidebar-context-menu.element.ts index b17d9635c8..c1cdbe7762 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-sidebar-context-menu/section-sidebar-context-menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-sidebar-context-menu/section-sidebar-context-menu.element.ts @@ -4,6 +4,8 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, nothing, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; +import type { UmbContextRequestEvent } from '@umbraco-cms/backoffice/context-api'; +import { UMB_CONTENT_REQUEST_EVENT_TYPE } from '@umbraco-cms/backoffice/context-api'; @customElement('umb-section-sidebar-context-menu') export class UmbSectionSidebarContextMenuElement extends UmbLitElement { @@ -66,6 +68,17 @@ export class UmbSectionSidebarContextMenuElement extends UmbLitElement { this.#closeContextMenu(); } + #proxyContextRequests(event: UmbContextRequestEvent) { + if (!this.#sectionSidebarContext) return; + // Note for this hack (The if-sentence): [NL] + // We do not currently have a good enough control to ensure that the proxy is last, meaning if another context is provided at this element, it might respond after the proxy event has been dispatched. + // To avoid such this hack just prevents proxying the event if its a request for its own context. + if (event.contextAlias !== UMB_SECTION_SIDEBAR_CONTEXT.contextAlias) { + event.stopImmediatePropagation(); + this.#sectionSidebarContext.getContextElement()?.dispatchEvent(event.clone()); + } + } + render() { return html` ${this.#renderBackdrop()} @@ -84,7 +97,7 @@ export class UmbSectionSidebarContextMenuElement extends UmbLitElement { #renderModal() { return this._isOpen && this._unique !== undefined && this._entityType - ? html`
+ ? html`
${this._headline ? html`

${this.localize.string(this._headline)}

` : nothing} (undefined); headline = this.#headline.asObservable(); + #contextElement: Element | undefined = undefined; + constructor(host: UmbControllerHost) { super(host, UMB_SECTION_SIDEBAR_CONTEXT); } - toggleContextMenu(entityType: string, unique: string | null | undefined, headline: string | undefined) { - this.openContextMenu(entityType, unique, headline); + toggleContextMenu(host: Element, args: UmbOpenContextMenuArgs) { + this.openContextMenu(host, args); } // TODO: we wont get notified about tree item name changes because we don't have a subscription // we need to figure out how we best can handle this when we only know the entity and unique id - openContextMenu(entityType: string, unique: string | null | undefined, headline: string | undefined) { - this.#entityType.setValue(entityType); - this.#unique.setValue(unique); - this.#headline.setValue(headline); + openContextMenu(host: Element, args: UmbOpenContextMenuArgs) { + this.#entityType.setValue(args.entityType); + this.#unique.setValue(args.unique); + this.#headline.setValue(args.headline); this.#contextMenuIsOpen.setValue(true); + this.#contextElement = host; } closeContextMenu() { @@ -38,6 +42,11 @@ export class UmbSectionSidebarContext extends UmbContextBase = items.map((item: DataTypeItemResponseModel) => { const dataTypeDetail: UmbDataTypeItemModel = { + entityType: UMB_DATA_TYPE_ENTITY_TYPE, unique: item.id, name: item.name, propertyEditorUiAlias: item.editorUiAlias!, diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/manifests.ts index c335520064..a7ed758226 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/manifests.ts @@ -1,18 +1,20 @@ +import { manifests as collectionManifests } from './collection/manifests.js'; import { manifests as entityActions } from './entity-actions/manifests.js'; -import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as menuManifests } from './menu/manifests.js'; +import { manifests as modalManifests } from './modals/manifests.js'; +import { manifests as repositoryManifests } from './repository/manifests.js'; +import { manifests as searchProviderManifests } from './search/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; -import { manifests as modalManifests } from './modals/manifests.js'; -import { manifests as collectionManifests } from './collection/manifests.js'; import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ + ...collectionManifests, ...entityActions, - ...repositoryManifests, ...menuManifests, + ...modalManifests, + ...repositoryManifests, + ...searchProviderManifests, ...treeManifests, ...workspaceManifests, - ...modalManifests, - ...collectionManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/property-editor-ui-picker/property-editor-ui-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/property-editor-ui-picker/property-editor-ui-picker-modal.element.ts index 7a1894f23f..6c19375a8e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/property-editor-ui-picker/property-editor-ui-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/property-editor-ui-picker/property-editor-ui-picker-modal.element.ts @@ -30,14 +30,13 @@ export class UmbPropertyEditorUIPickerModalElement extends UmbModalBaseElement< connectedCallback(): void { super.connectedCallback(); - this._submitLabel = this.data?.submitLabel ?? this._submitLabel; + // TODO: We never parse on a submit label, so this seem weird as we don't enable this of other places. + //this._submitLabel = this.data?.submitLabel ?? this._submitLabel; this.#usePropertyEditorUIs(); } #usePropertyEditorUIs() { - if (!this.data) return; - this.observe(umbExtensionsRegistry.byType('propertyEditorUi'), (propertyEditorUIs) => { // Only include Property Editor UIs which has Property Editor Schema Alias this._propertyEditorUIs = propertyEditorUIs.filter( diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/item/data-type-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/item/data-type-item.server.data-source.ts index 87f394b514..9845ede662 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/item/data-type-item.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/item/data-type-item.server.data-source.ts @@ -1,3 +1,4 @@ +import { UMB_DATA_TYPE_ENTITY_TYPE } from '../../entity.js'; import type { UmbDataTypeItemModel } from './types.js'; import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'; import type { DataTypeItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; @@ -43,6 +44,7 @@ const getItems = (uniques: Array) => DataTypeService.getItemDataType({ i const mapper = (item: DataTypeItemResponseModel): UmbDataTypeItemModel => { return { + entityType: UMB_DATA_TYPE_ENTITY_TYPE, unique: item.id, name: item.name, propertyEditorUiAlias: item.editorUiAlias || '', // TODO: why can this be undefined or null on the server? diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/item/types.ts index 185aa5cba4..cbd229e067 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/item/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/item/types.ts @@ -1,4 +1,7 @@ +import type { UmbDataTypeEntityType } from '../../entity.js'; + export interface UmbDataTypeItemModel { + entityType: UmbDataTypeEntityType; unique: string; name: string; propertyEditorUiAlias: string; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/search/data-type-search.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/search/data-type-search.repository.ts new file mode 100644 index 0000000000..86af02b505 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/search/data-type-search.repository.ts @@ -0,0 +1,23 @@ +import { UmbDataTypeSearchServerDataSource } from './data-type-search.server.data-source.js'; +import type { UmbDataTypeSearchItemModel } from './data-type.search-provider.js'; +import type { UmbSearchRepository, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export class UmbDataTypeSearchRepository + extends UmbControllerBase + implements UmbSearchRepository, UmbApi +{ + #dataSource: UmbDataTypeSearchServerDataSource; + + constructor(host: UmbControllerHost) { + super(host); + + this.#dataSource = new UmbDataTypeSearchServerDataSource(this); + } + + search(args: UmbSearchRequestArgs) { + return this.#dataSource.search(args); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/search/data-type-search.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/search/data-type-search.server.data-source.ts new file mode 100644 index 0000000000..20f021a3c5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/search/data-type-search.server.data-source.ts @@ -0,0 +1,55 @@ +import { UMB_DATA_TYPE_ENTITY_TYPE } from '../entity.js'; +import type { UmbDataTypeSearchItemModel } from './data-type.search-provider.js'; +import type { UmbSearchDataSource, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { DataTypeService } from '@umbraco-cms/backoffice/external/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * A data source for the Rollback that fetches data from the server + * @export + * @class UmbDataTypeSearchServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbDataTypeSearchServerDataSource implements UmbSearchDataSource { + #host: UmbControllerHost; + + /** + * Creates an instance of UmbDataTypeSearchServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbDataTypeSearchServerDataSource + */ + constructor(host: UmbControllerHost) { + this.#host = host; + } + + /** + * Get a list of versions for a data + * @return {*} + * @memberof UmbDataTypeSearchServerDataSource + */ + async search(args: UmbSearchRequestArgs) { + const { data, error } = await tryExecuteAndNotify( + this.#host, + DataTypeService.getItemDataTypeSearch({ + query: args.query, + }), + ); + + if (data) { + const mappedItems: Array = data.items.map((item) => { + return { + href: '/section/settings/workspace/data-type/edit/' + item.id, + entityType: UMB_DATA_TYPE_ENTITY_TYPE, + unique: item.id, + name: item.name, + propertyEditorUiAlias: item.editorUiAlias || '', + }; + }); + + return { data: { items: mappedItems, total: data.total } }; + } + + return { error }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/search/data-type.search-provider.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/search/data-type.search-provider.ts new file mode 100644 index 0000000000..276e663587 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/search/data-type.search-provider.ts @@ -0,0 +1,25 @@ +import type { UmbDataTypeItemModel } from '../index.js'; +import { UmbDataTypeSearchRepository } from './data-type-search.repository.js'; +import type { UmbSearchProvider, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; + +export interface UmbDataTypeSearchItemModel extends UmbDataTypeItemModel { + href: string; +} + +export class UmbDataTypeSearchProvider + extends UmbControllerBase + implements UmbSearchProvider +{ + #repository = new UmbDataTypeSearchRepository(this); + + async search(args: UmbSearchRequestArgs) { + return this.#repository.search(args); + } + + destroy(): void { + this.#repository.destroy(); + } +} + +export { UmbDataTypeSearchProvider as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/search/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/search/manifests.ts new file mode 100644 index 0000000000..248c1d2ccf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/search/manifests.ts @@ -0,0 +1,21 @@ +import { UMB_DATA_TYPE_ENTITY_TYPE } from '../entity.js'; +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + name: 'Data Type Search Provider', + alias: 'Umb.SearchProvider.DataType', + type: 'searchProvider', + api: () => import('./data-type.search-provider.js'), + weight: 400, + meta: { + label: 'Data Types', + }, + }, + { + name: 'Data Type Search Result Item ', + alias: 'Umb.SearchResultItem.DataType', + type: 'searchResultItem', + forEntityTypes: [UMB_DATA_TYPE_ENTITY_TYPE], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/manifests.ts index da281da540..f49f4353fa 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/manifests.ts @@ -2,6 +2,7 @@ import { manifests as entityActionsManifests } from './entity-actions/manifests. import { manifests as menuManifests } from './menu/manifests.js'; import { manifests as propertyEditorManifests } from './property-editors/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; +import { manifests as searchManifests } from './search/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; @@ -11,6 +12,7 @@ export const manifests: Array = [ ...menuManifests, ...propertyEditorManifests, ...repositoryManifests, + ...searchManifests, ...treeManifests, ...workspaceManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item.server.data-source.ts index 3c14064764..33b7b69346 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item.server.data-source.ts @@ -1,3 +1,4 @@ +import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../../entity.js'; import type { UmbDocumentTypeItemModel } from './types.js'; import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'; import type { DocumentTypeItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; @@ -29,6 +30,7 @@ const getItems = (uniques: Array) => DocumentTypeService.getItemDocument const mapper = (item: DocumentTypeItemResponseModel): UmbDocumentTypeItemModel => { return { + entityType: UMB_DOCUMENT_TYPE_ENTITY_TYPE, isElement: item.isElement, icon: item.icon, unique: item.id, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/types.ts index 1079e8cf02..c7dd55055c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/types.ts @@ -1,4 +1,7 @@ +import type { UmbDocumentTypeEntityType } from '../../entity.js'; + export type UmbDocumentTypeItemModel = { + entityType: UmbDocumentTypeEntityType; unique: string; name: string; isElement: boolean; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/search/document-type-search.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/search/document-type-search.repository.ts new file mode 100644 index 0000000000..93a12bba8a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/search/document-type-search.repository.ts @@ -0,0 +1,23 @@ +import { UmbDocumentTypeSearchServerDataSource } from './document-type-search.server.data-source.js'; +import type { UmbDocumentTypeSearchItemModel } from './document-type.search-provider.js'; +import type { UmbSearchRepository, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export class UmbDocumentTypeSearchRepository + extends UmbControllerBase + implements UmbSearchRepository, UmbApi +{ + #dataSource: UmbDocumentTypeSearchServerDataSource; + + constructor(host: UmbControllerHost) { + super(host); + + this.#dataSource = new UmbDocumentTypeSearchServerDataSource(this); + } + + search(args: UmbSearchRequestArgs) { + return this.#dataSource.search(args); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/search/document-type-search.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/search/document-type-search.server.data-source.ts new file mode 100644 index 0000000000..beb2bc9f0a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/search/document-type-search.server.data-source.ts @@ -0,0 +1,56 @@ +import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../entity.js'; +import type { UmbDocumentTypeSearchItemModel } from './document-type.search-provider.js'; +import type { UmbSearchDataSource, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { DocumentTypeService } from '@umbraco-cms/backoffice/external/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * A data source for the Rollback that fetches data from the server + * @export + * @class UmbDocumentTypeSearchServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbDocumentTypeSearchServerDataSource implements UmbSearchDataSource { + #host: UmbControllerHost; + + /** + * Creates an instance of UmbDocumentTypeSearchServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbDocumentTypeSearchServerDataSource + */ + constructor(host: UmbControllerHost) { + this.#host = host; + } + + /** + * Get a list of versions for a data + * @return {*} + * @memberof UmbDocumentTypeSearchServerDataSource + */ + async search(args: UmbSearchRequestArgs) { + const { data, error } = await tryExecuteAndNotify( + this.#host, + DocumentTypeService.getItemDocumentTypeSearch({ + query: args.query, + }), + ); + + if (data) { + const mappedItems: Array = data.items.map((item) => { + return { + href: '/section/settings/workspace/document-type/edit/' + item.id, + entityType: UMB_DOCUMENT_TYPE_ENTITY_TYPE, + isElement: item.isElement, + icon: item.icon, + unique: item.id, + name: item.name, + }; + }); + + return { data: { items: mappedItems, total: data.total } }; + } + + return { error }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/search/document-type.search-provider.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/search/document-type.search-provider.ts new file mode 100644 index 0000000000..d70845fc0a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/search/document-type.search-provider.ts @@ -0,0 +1,25 @@ +import type { UmbDocumentTypeItemModel } from '../index.js'; +import { UmbDocumentTypeSearchRepository } from './document-type-search.repository.js'; +import type { UmbSearchProvider, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; + +export interface UmbDocumentTypeSearchItemModel extends UmbDocumentTypeItemModel { + href: string; +} + +export class UmbDocumentTypeSearchProvider + extends UmbControllerBase + implements UmbSearchProvider +{ + #repository = new UmbDocumentTypeSearchRepository(this); + + async search(args: UmbSearchRequestArgs) { + return this.#repository.search(args); + } + + destroy(): void { + this.#repository.destroy(); + } +} + +export { UmbDocumentTypeSearchProvider as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/search/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/search/manifests.ts new file mode 100644 index 0000000000..8deaf580bd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/search/manifests.ts @@ -0,0 +1,21 @@ +import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../entity.js'; +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + name: 'Document Type Search Provider', + alias: 'Umb.SearchProvider.DocumentType', + type: 'searchProvider', + api: () => import('./document-type.search-provider.js'), + weight: 600, + meta: { + label: 'Document Types', + }, + }, + { + name: 'Document Type Search Result Item ', + alias: 'Umb.SearchResultItem.DocumentType', + type: 'searchResultItem', + forEntityTypes: [UMB_DOCUMENT_TYPE_ENTITY_TYPE], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/repository/document-collection.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/repository/document-collection.server.data-source.ts index c6899b78ed..99091d6d97 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/repository/document-collection.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/repository/document-collection.server.data-source.ts @@ -24,8 +24,8 @@ export class UmbDocumentCollectionServerDataSource implements UmbCollectionDataS orderCulture: query.orderCulture ?? 'en-US', orderDirection: query.orderDirection === 'asc' ? DirectionModel.ASCENDING : DirectionModel.DESCENDING, filter: query.filter, - skip: query.skip ?? 0, - take: query.take ?? 100, + skip: query.skip || 0, + take: query.take || 100, }; const { data, error } = await tryExecuteAndNotify(this.#host, DocumentService.getCollectionDocumentById(params)); @@ -43,6 +43,7 @@ export class UmbDocumentCollectionServerDataSource implements UmbCollectionDataS creator: item.creator, icon: item.documentType.icon, name: variant.name, + sortOrder: item.sortOrder, state: variant.state, updateDate: new Date(variant.updateDate), updater: item.updater, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/types.ts index 103dde6ac0..ffcbcb13d3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/types.ts @@ -17,6 +17,7 @@ export interface UmbDocumentCollectionItemModel { creator?: string | null; icon: string; name: string; + sortOrder: number; state: string; updateDate: Date; updater?: string | null; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/grid/document-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/grid/document-grid-collection-view.element.ts index ac83466e2f..8ec535ba7f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/grid/document-grid-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/grid/document-grid-collection-view.element.ts @@ -96,14 +96,13 @@ export class UmbDocumentGridCollectionViewElement extends UmbLitElement { ${repeat( this._items, (item) => item.unique, - (item, index) => this.#renderCard(index, item), + (item) => this.#renderCard(item), )}
`; } - #renderCard(index: number, item: UmbDocumentCollectionItemModel) { - const sortOrder = this._skip + index; + #renderCard(item: UmbDocumentCollectionItemModel) { return html` this.#onSelect(item)} @deselected=${() => this.#onDeselect(item)}> - ${this.#renderState(item)} ${this.#renderProperties(sortOrder, item)} + ${this.#renderState(item)} ${this.#renderProperties(item)} `; } @@ -142,15 +141,14 @@ export class UmbDocumentGridCollectionViewElement extends UmbLitElement { } } - #renderProperties(sortOrder: number, item: UmbDocumentCollectionItemModel) { + #renderProperties(item: UmbDocumentCollectionItemModel) { if (!this._userDefinedProperties) return; return html`
    ${repeat( this._userDefinedProperties, (column) => column.alias, - (column) => - html`
  • ${column.header}: ${getPropertyValueByAlias(sortOrder, item, column.alias)}
  • `, + (column) => html`
  • ${column.header}: ${getPropertyValueByAlias(item, column.alias)}
  • `, )}
`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/index.ts index cbc7466fb6..bad7d3c6de 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/index.ts @@ -1,25 +1,25 @@ import type { UmbDocumentCollectionItemModel } from '../types.js'; +import { fromCamelCase } from '@umbraco-cms/backoffice/utils'; export { UMB_DOCUMENT_GRID_COLLECTION_VIEW_ALIAS, UMB_DOCUMENT_TABLE_COLLECTION_VIEW_ALIAS } from './manifests.js'; -export function getPropertyValueByAlias(sortOrder: number, item: UmbDocumentCollectionItemModel, alias: string) { +export function getPropertyValueByAlias(item: UmbDocumentCollectionItemModel, alias: string) { switch (alias) { case 'contentTypeAlias': return item.contentTypeAlias; case 'createDate': return item.createDate.toLocaleString(); case 'creator': - return item.creator; - case 'entityName': - return item.name; - case 'entityState': - return item.state.replace(/([A-Z])/g, ' $1'); case 'owner': return item.creator; + case 'name': + return item.name; + case 'state': + return fromCamelCase(item.state); case 'published': return item.state !== 'Draft' ? 'True' : 'False'; case 'sortOrder': - return sortOrder; + return item.sortOrder; case 'updateDate': return item.updateDate.toLocaleString(); case 'updater': diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-name.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-name.element.ts index 2ee3f9321e..1d9e39d4b3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-name.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-name.element.ts @@ -3,6 +3,7 @@ import { css, customElement, html, property, state } from '@umbraco-cms/backoffi import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal'; import type { UmbTableColumn, UmbTableColumnLayoutElement, UmbTableItem } from '@umbraco-cms/backoffice/components'; +import type { UUIButtonElement } from '@umbraco-cms/backoffice/external/uui'; @customElement('umb-document-table-column-name') export class UmbDocumentTableColumnNameElement extends UmbLitElement implements UmbTableColumnLayoutElement { @@ -31,20 +32,20 @@ export class UmbDocumentTableColumnNameElement extends UmbLitElement implements }); } - #onClick(event: Event) { - // TODO: [LK] Review the `stopPropagation` usage, as it causes a page reload. - // But we still need a say to prevent the `umb-table` from triggering a selection event. + #onClick(event: Event & { target: UUIButtonElement }) { + event.preventDefault(); event.stopPropagation(); + window.history.pushState({}, '', event.target.href); } render() { - return html``; + return html` + + `; } static styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts index a7f20868bd..943588f16e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts @@ -41,15 +41,15 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement { #systemColumns: Array = [ { name: this.localize.term('general_name'), - alias: 'entityName', + alias: 'name', elementName: 'umb-document-table-column-name', allowSorting: true, }, { name: this.localize.term('content_publishStatus'), - alias: 'entityState', + alias: 'state', elementName: 'umb-document-table-column-state', - allowSorting: true, + allowSorting: false, }, ]; @@ -126,16 +126,14 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement { } #createTableItems(items: Array) { - this._tableItems = items.map((item, rowIndex) => { + this._tableItems = items.map((item) => { if (!item.unique) throw new Error('Item id is missing.'); - const sortOrder = this._skip + rowIndex; - const data = this._tableColumns?.map((column) => { return { columnAlias: column.alias, - value: column.elementName ? item : getPropertyValueByAlias(sortOrder, item, column.alias), + value: column.elementName ? item : getPropertyValueByAlias(item, column.alias), }; }) ?? []; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts index 49dcd123ed..66981bdf87 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts @@ -1,30 +1,32 @@ import { manifests as collectionManifests } from './collection/manifests.js'; import { manifests as entityActionManifests } from './entity-actions/manifests.js'; import { manifests as entityBulkActionManifests } from './entity-bulk-actions/manifests.js'; -import { manifests as modalManifests } from './modals/manifests.js'; +import { manifests as globalContextManifests } from './global-contexts/manifests.js'; import { manifests as menuManifests } from './menu/manifests.js'; +import { manifests as modalManifests } from './modals/manifests.js'; import { manifests as propertyEditorManifests } from './property-editors/manifests.js'; import { manifests as recycleBinManifests } from './recycle-bin/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; +import { manifests as searchProviderManifests } from './search/manifests.js'; import { manifests as trackedReferenceManifests } from './reference/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as userPermissionManifests } from './user-permissions/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; -import { manifests as globalContextManifests } from './global-contexts/manifests.js'; import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ ...collectionManifests, ...entityActionManifests, ...entityBulkActionManifests, - ...modalManifests, + ...globalContextManifests, ...menuManifests, + ...modalManifests, ...propertyEditorManifests, ...recycleBinManifests, ...repositoryManifests, + ...searchProviderManifests, ...trackedReferenceManifests, ...treeManifests, ...userPermissionManifests, ...workspaceManifests, - ...globalContextManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/document-search-result-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/document-search-result-item.element.ts new file mode 100644 index 0000000000..98d35db29e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/document-search-result-item.element.ts @@ -0,0 +1,128 @@ +import type { UmbDocumentItemModel, UmbDocumentItemVariantModel } from '../repository/item/types.js'; +import type { UmbSearchResultItemModel } from '@umbraco-cms/backoffice/search'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { css, customElement, html, nothing, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import type { UmbAppLanguageContext } from '@umbraco-cms/backoffice/language'; +import { UMB_APP_LANGUAGE_CONTEXT } from '@umbraco-cms/backoffice/language'; + +const elementName = 'umb-document-search-result-item'; +@customElement(elementName) +export class UmbSearchResultItemElement extends UmbLitElement { + @property({ type: Object }) + item?: UmbSearchResultItemModel & UmbDocumentItemModel; + + @state() + _currentCulture?: string; + + @state() + _defaultCulture?: string; + + @state() + _variant?: UmbDocumentItemVariantModel; + + constructor() { + super(); + + this.consumeContext(UMB_APP_LANGUAGE_CONTEXT, (instance) => { + this.#observeAppCulture(instance); + this.#observeDefaultCulture(instance); + }); + } + + #observeAppCulture(context: UmbAppLanguageContext) { + this.observe(context.appLanguageCulture, (value) => { + this._currentCulture = value; + this._variant = this.#getVariant(value); + }); + } + + #observeDefaultCulture(context: UmbAppLanguageContext) { + this.observe(context.appDefaultLanguage, (value) => { + this._defaultCulture = value?.unique; + }); + } + + #getVariant(culture: string | undefined) { + return this.item?.variants.find((x) => x.culture === culture); + } + + #isInvariant() { + const firstVariant = this.item?.variants[0]; + return firstVariant?.culture === null; + } + + #getLabel() { + if (this.#isInvariant()) { + return this.item?.name ?? 'Unknown'; + } + + const fallbackName = this.#getVariant(this._defaultCulture)?.name ?? this.item?.name ?? 'Unknown'; + return this._variant?.name ?? `(${fallbackName})`; + } + + render() { + if (!this.item) return nothing; + + return html` + + ${this.item.icon ? html`` : this.#renderHashTag()} + + ${this.#getLabel()} + `; + } + + #renderHashTag() { + return html` + + + + + `; + } + + static styles = [ + UmbTextStyles, + css` + :host { + padding: var(--uui-size-space-3) var(--uui-size-space-5); + border-radius: var(--uui-border-radius); + display: grid; + grid-template-columns: var(--uui-size-space-6) 1fr var(--uui-size-space-5); + align-items: center; + width: 100%; + outline-offset: -3px; + } + .item-icon { + margin-bottom: auto; + margin-top: 5px; + } + .item-icon { + opacity: 0.4; + } + .item-name { + display: flex; + flex-direction: column; + } + .item-icon > * { + height: 1rem; + display: flex; + width: min-content; + } + a { + text-decoration: none; + color: inherit; + } + `, + ]; +} + +export { UmbSearchResultItemElement as element }; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: UmbSearchResultItemElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/document-search.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/document-search.repository.ts new file mode 100644 index 0000000000..c5471d0e5d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/document-search.repository.ts @@ -0,0 +1,23 @@ +import { UmbDocumentSearchServerDataSource } from './document-search.server.data-source.js'; +import type { UmbDocumentSearchItemModel } from './document.search-provider.js'; +import type { UmbSearchRepository, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export class UmbDocumentSearchRepository + extends UmbControllerBase + implements UmbSearchRepository, UmbApi +{ + #dataSource: UmbDocumentSearchServerDataSource; + + constructor(host: UmbControllerHost) { + super(host); + + this.#dataSource = new UmbDocumentSearchServerDataSource(this); + } + + search(args: UmbSearchRequestArgs) { + return this.#dataSource.search(args); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/document-search.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/document-search.server.data-source.ts new file mode 100644 index 0000000000..824dd544ec --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/document-search.server.data-source.ts @@ -0,0 +1,68 @@ +import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js'; +import type { UmbDocumentSearchItemModel } from './document.search-provider.js'; +import type { UmbSearchDataSource, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { DocumentService } from '@umbraco-cms/backoffice/external/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * A data source for the Rollback that fetches data from the server + * @export + * @class UmbDocumentSearchServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbDocumentSearchServerDataSource implements UmbSearchDataSource { + #host: UmbControllerHost; + + /** + * Creates an instance of UmbDocumentSearchServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbDocumentSearchServerDataSource + */ + constructor(host: UmbControllerHost) { + this.#host = host; + } + + /** + * Get a list of versions for a document + * @return {*} + * @memberof UmbDocumentSearchServerDataSource + */ + async search(args: UmbSearchRequestArgs) { + const { data, error } = await tryExecuteAndNotify( + this.#host, + DocumentService.getItemDocumentSearch({ + query: args.query, + }), + ); + + if (data) { + const mappedItems: Array = data.items.map((item) => { + return { + href: '/section/content/workspace/document/edit/' + item.id, + entityType: UMB_DOCUMENT_ENTITY_TYPE, + unique: item.id, + isTrashed: item.isTrashed, + isProtected: item.isProtected, + documentType: { + unique: item.documentType.id, + icon: item.documentType.icon, + collection: item.documentType.collection ? { unique: item.documentType.collection.id } : null, + }, + variants: item.variants.map((variant) => { + return { + culture: variant.culture || null, + name: variant.name, + state: variant.state, + }; + }), + name: item.variants[0]?.name, // TODO: this is not correct. We need to get it from the variants. This is a temp solution. + }; + }); + + return { data: { items: mappedItems, total: data.total } }; + } + + return { error }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/document.search-provider.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/document.search-provider.ts new file mode 100644 index 0000000000..7029cfba73 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/document.search-provider.ts @@ -0,0 +1,24 @@ +import type { UmbDocumentItemModel } from '../index.js'; +import { UmbDocumentSearchRepository } from './document-search.repository.js'; +import type { UmbSearchProvider, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +export interface UmbDocumentSearchItemModel extends UmbDocumentItemModel { + href: string; +} + +export class UmbDocumentSearchProvider + extends UmbControllerBase + implements UmbSearchProvider +{ + #repository = new UmbDocumentSearchRepository(this); + + search(args: UmbSearchRequestArgs) { + return this.#repository.search(args); + } + + destroy(): void { + this.#repository.destroy(); + } +} + +export { UmbDocumentSearchProvider as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/manifests.ts new file mode 100644 index 0000000000..cdc2e2e024 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/manifests.ts @@ -0,0 +1,22 @@ +import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js'; +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + name: 'Document Search Provider', + alias: 'Umb.SearchProvider.Document', + type: 'searchProvider', + api: () => import('./document.search-provider.js'), + weight: 800, + meta: { + label: 'Documents', + }, + }, + { + name: 'Document Search Result Item ', + alias: 'Umb.SearchResultItem.Document', + type: 'searchResultItem', + js: () => import('./document-search-result-item.element.js'), + forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/manifests.ts index 3af5146cf3..79205a27bc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/manifests.ts @@ -4,6 +4,7 @@ import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; import { manifests as propertyEditorUiManifests } from './property-editors/manifests.js'; +import { manifests as searchManifests } from './search/manifests.js'; import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ @@ -13,4 +14,5 @@ export const manifests: Array = [ ...treeManifests, ...workspaceManifests, ...propertyEditorUiManifests, + ...searchManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.server.data-source.ts index 1eaa920d14..30eb55ee38 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.server.data-source.ts @@ -1,3 +1,4 @@ +import { UMB_MEDIA_TYPE_ENTITY_TYPE } from '../../entity.js'; import type { UmbMediaTypeItemModel } from './types.js'; import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'; import type { MediaTypeItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; @@ -32,6 +33,7 @@ const getItems = (uniques: Array) => MediaTypeService.getItemMediaType({ const mapper = (item: MediaTypeItemResponseModel): UmbMediaTypeItemModel => { return { + entityType: UMB_MEDIA_TYPE_ENTITY_TYPE, icon: item.icon || null, name: item.name, unique: item.id, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/types.ts index f284c5ca85..e905cf16b1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/types.ts @@ -1,4 +1,7 @@ +import type { UmbMediaTypeEntityType } from '../../entity.js'; + export interface UmbMediaTypeItemModel { + entityType: UmbMediaTypeEntityType; icon: string | null; name: string; unique: string; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/search/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/search/manifests.ts new file mode 100644 index 0000000000..b077dfb5be --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/search/manifests.ts @@ -0,0 +1,21 @@ +import { UMB_MEDIA_TYPE_ENTITY_TYPE } from '../entity.js'; +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + name: 'Media Type Search Provider', + alias: 'Umb.SearchProvider.MediaType', + type: 'searchProvider', + api: () => import('./media-type.search-provider.js'), + weight: 500, + meta: { + label: 'Media Types', + }, + }, + { + name: 'Media Type Search Result Item ', + alias: 'Umb.SearchResultItem.MediaType', + type: 'searchResultItem', + forEntityTypes: [UMB_MEDIA_TYPE_ENTITY_TYPE], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/search/media-type-search.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/search/media-type-search.repository.ts new file mode 100644 index 0000000000..12486f5f16 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/search/media-type-search.repository.ts @@ -0,0 +1,23 @@ +import { UmbMediaTypeSearchServerDataSource } from './media-type-search.server.data-source.js'; +import type { UmbMediaTypeSearchItemModel } from './media-type.search-provider.js'; +import type { UmbSearchRepository, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export class UmbMediaTypeSearchRepository + extends UmbControllerBase + implements UmbSearchRepository, UmbApi +{ + #dataSource: UmbMediaTypeSearchServerDataSource; + + constructor(host: UmbControllerHost) { + super(host); + + this.#dataSource = new UmbMediaTypeSearchServerDataSource(this); + } + + search(args: UmbSearchRequestArgs) { + return this.#dataSource.search(args); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/search/media-type-search.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/search/media-type-search.server.data-source.ts new file mode 100644 index 0000000000..b49edd53b6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/search/media-type-search.server.data-source.ts @@ -0,0 +1,55 @@ +import { UMB_MEDIA_TYPE_ENTITY_TYPE } from '../entity.js'; +import type { UmbMediaTypeSearchItemModel } from './media-type.search-provider.js'; +import type { UmbSearchDataSource, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { MediaTypeService } from '@umbraco-cms/backoffice/external/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * A data source for the Rollback that fetches data from the server + * @export + * @class UmbMediaTypeSearchServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbMediaTypeSearchServerDataSource implements UmbSearchDataSource { + #host: UmbControllerHost; + + /** + * Creates an instance of UmbMediaTypeSearchServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbMediaTypeSearchServerDataSource + */ + constructor(host: UmbControllerHost) { + this.#host = host; + } + + /** + * Get a list of versions for a data + * @return {*} + * @memberof UmbMediaTypeSearchServerDataSource + */ + async search(args: UmbSearchRequestArgs) { + const { data, error } = await tryExecuteAndNotify( + this.#host, + MediaTypeService.getItemMediaTypeSearch({ + query: args.query, + }), + ); + + if (data) { + const mappedItems: Array = data.items.map((item) => { + return { + href: '/section/settings/workspace/media-type/edit/' + item.id, + entityType: UMB_MEDIA_TYPE_ENTITY_TYPE, + unique: item.id, + name: item.name, + icon: item.icon || null, + }; + }); + + return { data: { items: mappedItems, total: data.total } }; + } + + return { error }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/search/media-type.search-provider.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/search/media-type.search-provider.ts new file mode 100644 index 0000000000..746805fd58 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/search/media-type.search-provider.ts @@ -0,0 +1,25 @@ +import type { UmbMediaTypeItemModel } from '../index.js'; +import { UmbMediaTypeSearchRepository } from './media-type-search.repository.js'; +import type { UmbSearchProvider, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; + +export interface UmbMediaTypeSearchItemModel extends UmbMediaTypeItemModel { + href: string; +} + +export class UmbMediaTypeSearchProvider + extends UmbControllerBase + implements UmbSearchProvider +{ + #repository = new UmbMediaTypeSearchRepository(this); + + async search(args: UmbSearchRequestArgs) { + return this.#repository.search(args); + } + + destroy(): void { + this.#repository.destroy(); + } +} + +export { UmbMediaTypeSearchProvider as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/repository/media-collection.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/repository/media-collection.server.data-source.ts index d8a243868f..b75fadfff8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/repository/media-collection.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/repository/media-collection.server.data-source.ts @@ -36,6 +36,7 @@ export class UmbMediaCollectionServerDataSource implements UmbCollectionDataSour creator: item.creator, icon: item.mediaType.icon, name: variant.name, + sortOrder: item.sortOrder, updateDate: new Date(variant.updateDate), values: item.values.map((item) => { return { alias: item.alias, value: item.value as string }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts index 9a5246af9f..1ae06b64f7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts @@ -14,6 +14,7 @@ export interface UmbMediaCollectionItemModel { creator?: string | null; icon: string; name: string; + sortOrder: number; updateDate: Date; values: Array<{ alias: string; value: string }>; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts index 96b09094ba..044508a91e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts @@ -39,7 +39,7 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { #systemColumns: Array = [ { name: this.localize.term('general_name'), - alias: 'entityName', + alias: 'name', elementName: 'umb-media-table-column-name', allowSorting: true, }, @@ -124,16 +124,14 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { this.#createTableHeadings(); } - this._tableItems = items.map((item, rowIndex) => { + this._tableItems = items.map((item) => { if (!item.unique) throw new Error('Item id is missing.'); - const sortOrder = this._skip + rowIndex; - const data = this._tableColumns?.map((column) => { return { columnAlias: column.alias, - value: column.elementName ? item : this.#getPropertyValueByAlias(sortOrder, item, column.alias), + value: column.elementName ? item : this.#getPropertyValueByAlias(item, column.alias), }; }) ?? []; @@ -145,16 +143,17 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { }); } - #getPropertyValueByAlias(sortOrder: number, item: UmbMediaCollectionItemModel, alias: string) { + #getPropertyValueByAlias(item: UmbMediaCollectionItemModel, alias: string) { switch (alias) { case 'createDate': return item.createDate.toLocaleString(); - case 'entityName': + case 'name': return item.name; + case 'creator': case 'owner': return item.creator; case 'sortOrder': - return sortOrder; + return item.sortOrder; case 'updateDate': return item.updateDate.toLocaleString(); default: diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts index 1d355b70f6..7c90a08ed1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts @@ -5,6 +5,7 @@ import { manifests as menuManifests } from './menu/manifests.js'; import { manifests as propertyEditorsManifests } from './property-editors/manifests.js'; import { manifests as recycleBinManifests } from './recycle-bin/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; +import { manifests as searchManifests } from './search/manifests.js'; import { manifests as sectionViewManifests } from './section-view/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; @@ -18,6 +19,7 @@ export const manifests: Array = [ ...propertyEditorsManifests, ...recycleBinManifests, ...repositoryManifests, + ...searchManifests, ...sectionViewManifests, ...treeManifests, ...workspaceManifests, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/item/media-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/item/media-item.server.data-source.ts index a1ffb549c7..77c9bda32c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/item/media-item.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/item/media-item.server.data-source.ts @@ -1,3 +1,4 @@ +import { UMB_MEDIA_ENTITY_TYPE, UmbMediaEntityType } from '../../entity.js'; import type { UmbMediaItemModel } from './types.js'; import type { MediaItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; import { MediaService } from '@umbraco-cms/backoffice/external/backend-api'; @@ -32,6 +33,7 @@ const getItems = (uniques: Array) => MediaService.getItemMedia({ id: uni const mapper = (item: MediaItemResponseModel): UmbMediaItemModel => { return { + entityType: UMB_MEDIA_ENTITY_TYPE, unique: item.id, isTrashed: item.isTrashed, mediaType: { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/item/types.ts index 5d9b42300e..5dd1780a06 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/item/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/item/types.ts @@ -1,6 +1,8 @@ +import type { UmbMediaEntityType } from '../../entity.js'; import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models'; export interface UmbMediaItemModel { + entityType: UmbMediaEntityType; unique: string; isTrashed: boolean; mediaType: { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/search/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/search/manifests.ts new file mode 100644 index 0000000000..6da2255c34 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/search/manifests.ts @@ -0,0 +1,21 @@ +import { UMB_MEDIA_ENTITY_TYPE } from '../entity.js'; +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + name: 'Media Search Provider', + alias: 'Umb.SearchProvider.Media', + type: 'searchProvider', + api: () => import('./media.search-provider.js'), + weight: 700, + meta: { + label: 'Media', + }, + }, + { + name: 'Media Search Result Item ', + alias: 'Umb.SearchResultItem.Media', + type: 'searchResultItem', + forEntityTypes: [UMB_MEDIA_ENTITY_TYPE], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/search/media-search.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/search/media-search.repository.ts new file mode 100644 index 0000000000..cda2d907f4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/search/media-search.repository.ts @@ -0,0 +1,23 @@ +import { UmbMediaSearchServerDataSource } from './media-search.server.data-source.js'; +import type { UmbMediaSearchItemModel } from './media.search-provider.js'; +import type { UmbSearchRepository, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export class UmbMediaSearchRepository + extends UmbControllerBase + implements UmbSearchRepository, UmbApi +{ + #dataSource: UmbMediaSearchServerDataSource; + + constructor(host: UmbControllerHost) { + super(host); + + this.#dataSource = new UmbMediaSearchServerDataSource(this); + } + + search(args: UmbSearchRequestArgs) { + return this.#dataSource.search(args); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/search/media-search.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/search/media-search.server.data-source.ts new file mode 100644 index 0000000000..21a2ae1858 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/search/media-search.server.data-source.ts @@ -0,0 +1,66 @@ +import { UMB_MEDIA_ENTITY_TYPE } from '../entity.js'; +import type { UmbMediaSearchItemModel } from './media.search-provider.js'; +import type { UmbSearchDataSource, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { MediaService } from '@umbraco-cms/backoffice/external/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * A data source for the Rollback that fetches data from the server + * @export + * @class UmbMediaSearchServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbMediaSearchServerDataSource implements UmbSearchDataSource { + #host: UmbControllerHost; + + /** + * Creates an instance of UmbMediaSearchServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbMediaSearchServerDataSource + */ + constructor(host: UmbControllerHost) { + this.#host = host; + } + + /** + * Get a list of versions for a data + * @return {*} + * @memberof UmbMediaSearchServerDataSource + */ + async search(args: UmbSearchRequestArgs) { + const { data, error } = await tryExecuteAndNotify( + this.#host, + MediaService.getItemMediaSearch({ + query: args.query, + }), + ); + + if (data) { + const mappedItems: Array = data.items.map((item) => { + return { + href: '/section/media/workspace/media/edit/' + item.id, + entityType: UMB_MEDIA_ENTITY_TYPE, + unique: item.id, + isTrashed: item.isTrashed, + mediaType: { + unique: item.mediaType.id, + icon: item.mediaType.icon, + collection: item.mediaType.collection ? { unique: item.mediaType.collection.id } : null, + }, + variants: item.variants.map((variant) => { + return { + culture: variant.culture || null, + name: variant.name, + }; + }), + name: item.variants[0]?.name, // TODO: get correct variant name + }; + }); + + return { data: { items: mappedItems, total: data.total } }; + } + + return { error }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/search/media.search-provider.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/search/media.search-provider.ts new file mode 100644 index 0000000000..9b5655ec82 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/search/media.search-provider.ts @@ -0,0 +1,22 @@ +import type { UmbMediaItemModel } from '../index.js'; +import { UmbMediaSearchRepository } from './media-search.repository.js'; +import type { UmbSearchProvider, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; + +export interface UmbMediaSearchItemModel extends UmbMediaItemModel { + href: string; +} + +export class UmbMediaSearchProvider extends UmbControllerBase implements UmbSearchProvider { + #repository = new UmbMediaSearchRepository(this); + + async search(args: UmbSearchRequestArgs) { + return this.#repository.search(args); + } + + destroy(): void { + this.#repository.destroy(); + } +} + +export { UmbMediaSearchProvider as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/manifests.ts index 94e6d5fdca..de55879f97 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/manifests.ts @@ -1,6 +1,7 @@ import { manifests as entityActionsManifests } from './entity-actions/manifests.js'; import { manifests as menuManifests } from './menu/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; +import { manifests as searchManifests } from './search/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; @@ -11,6 +12,7 @@ export const manifests: Array = [ ...entityActionsManifests, ...menuManifests, ...repositoryManifests, + ...searchManifests, ...treeManifests, ...workspaceManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/search/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/search/manifests.ts new file mode 100644 index 0000000000..786dd15a9d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/search/manifests.ts @@ -0,0 +1,21 @@ +import { UMB_MEMBER_TYPE_ENTITY_TYPE } from '../entity.js'; +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + name: 'Member Type Search Provider', + alias: 'Umb.SearchProvider.MemberType', + type: 'searchProvider', + api: () => import('./member-type.search-provider.js'), + weight: 200, + meta: { + label: 'Member Types', + }, + }, + { + name: 'Member Type Search Result Item ', + alias: 'Umb.SearchResultItem.MemberType', + type: 'searchResultItem', + forEntityTypes: [UMB_MEMBER_TYPE_ENTITY_TYPE], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/search/member-type-search.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/search/member-type-search.repository.ts new file mode 100644 index 0000000000..ad96738a6d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/search/member-type-search.repository.ts @@ -0,0 +1,23 @@ +import { UmbMemberTypeSearchServerDataSource } from './member-type-search.server.data-source.js'; +import type { UmbMemberTypeSearchItemModel } from './member-type.search-provider.js'; +import type { UmbSearchRepository, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export class UmbMemberTypeSearchRepository + extends UmbControllerBase + implements UmbSearchRepository, UmbApi +{ + #dataSource: UmbMemberTypeSearchServerDataSource; + + constructor(host: UmbControllerHost) { + super(host); + + this.#dataSource = new UmbMemberTypeSearchServerDataSource(this); + } + + search(args: UmbSearchRequestArgs) { + return this.#dataSource.search(args); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/search/member-type-search.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/search/member-type-search.server.data-source.ts new file mode 100644 index 0000000000..0fc23e69bb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/search/member-type-search.server.data-source.ts @@ -0,0 +1,55 @@ +import { UMB_MEMBER_TYPE_ENTITY_TYPE } from '../entity.js'; +import type { UmbMemberTypeSearchItemModel } from './member-type.search-provider.js'; +import type { UmbSearchDataSource, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { MemberTypeService } from '@umbraco-cms/backoffice/external/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * A data source for the Rollback that fetches data from the server + * @export + * @class UmbMemberTypeSearchServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbMemberTypeSearchServerDataSource implements UmbSearchDataSource { + #host: UmbControllerHost; + + /** + * Creates an instance of UmbMemberTypeSearchServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbMemberTypeSearchServerDataSource + */ + constructor(host: UmbControllerHost) { + this.#host = host; + } + + /** + * Get a list of versions for a data + * @return {*} + * @memberof UmbMemberTypeSearchServerDataSource + */ + async search(args: UmbSearchRequestArgs) { + const { data, error } = await tryExecuteAndNotify( + this.#host, + MemberTypeService.getItemMemberTypeSearch({ + query: args.query, + }), + ); + + if (data) { + const mappedItems: Array = data.items.map((item) => { + return { + href: '/section/settings/workspace/member-type/edit/' + item.id, + entityType: UMB_MEMBER_TYPE_ENTITY_TYPE, + unique: item.id, + name: item.name, + icon: item.icon || '', + }; + }); + + return { data: { items: mappedItems, total: data.total } }; + } + + return { error }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/search/member-type.search-provider.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/search/member-type.search-provider.ts new file mode 100644 index 0000000000..f46a3093e3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/search/member-type.search-provider.ts @@ -0,0 +1,25 @@ +import type { UmbMemberTypeItemModel } from '../repository/item/types.js'; +import { UmbMemberTypeSearchRepository } from './member-type-search.repository.js'; +import type { UmbSearchProvider, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; + +export interface UmbMemberTypeSearchItemModel extends UmbMemberTypeItemModel { + href: string; +} + +export class UmbMemberTypeSearchProvider + extends UmbControllerBase + implements UmbSearchProvider +{ + #repository = new UmbMemberTypeSearchRepository(this); + + async search(args: UmbSearchRequestArgs) { + return this.#repository.search(args); + } + + destroy(): void { + this.#repository.destroy(); + } +} + +export { UmbMemberTypeSearchProvider as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/manifests.ts index 8470189d3f..9fc3d327bf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/manifests.ts @@ -3,6 +3,7 @@ import { manifests as entityActionManifests } from './entity-actions/manifests.j import { manifests as memberPickerModalManifests } from './components/member-picker-modal/manifests.js'; import { manifests as propertyEditorManifests } from './property-editor/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; +import { manifests as searchManifests } from './search/manifests.js'; import { manifests as sectionViewManifests } from './section-view/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; @@ -13,6 +14,7 @@ export const manifests: Array = [ ...memberPickerModalManifests, ...propertyEditorManifests, ...repositoryManifests, + ...searchManifests, ...sectionViewManifests, ...workspaceManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/item/member-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/item/member-item.server.data-source.ts index 02696b0afa..08515c6646 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/item/member-item.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/item/member-item.server.data-source.ts @@ -1,3 +1,4 @@ +import { UMB_MEMBER_ENTITY_TYPE } from '../../entity.js'; import type { UmbMemberItemModel } from './types.js'; import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'; import type { MemberItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; @@ -32,6 +33,7 @@ const getItems = (uniques: Array) => MemberService.getItemMember({ id: u const mapper = (item: MemberItemResponseModel): UmbMemberItemModel => { return { + entityType: UMB_MEMBER_ENTITY_TYPE, unique: item.id, name: item.variants[0].name || '', memberType: { diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/item/types.ts index fef6a44474..9b58f77ef6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/item/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/item/types.ts @@ -1,6 +1,8 @@ +import type { UmbMemberEntityType } from '../../entity.js'; import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models'; export interface UmbMemberItemModel { + entityType: UmbMemberEntityType; unique: string; name: string; // TODO: this is not correct. We need to get it from the variants. This is a temp solution. memberType: { diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/search/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/search/manifests.ts new file mode 100644 index 0000000000..9729f06efd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/search/manifests.ts @@ -0,0 +1,21 @@ +import { UMB_MEMBER_ENTITY_TYPE } from '../entity.js'; +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + name: 'Member Search Provider', + alias: 'Umb.SearchProvider.Member', + type: 'searchProvider', + api: () => import('./member.search-provider.js'), + weight: 300, + meta: { + label: 'Members', + }, + }, + { + name: 'Member Search Result Item ', + alias: 'Umb.SearchResultItem.Member', + type: 'searchResultItem', + forEntityTypes: [UMB_MEMBER_ENTITY_TYPE], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/search/member-search.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/search/member-search.repository.ts new file mode 100644 index 0000000000..67e270b90d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/search/member-search.repository.ts @@ -0,0 +1,23 @@ +import { UmbMemberSearchServerDataSource } from './member-search.server.data-source.js'; +import type { UmbMemberSearchItemModel } from './member.search-provider.js'; +import type { UmbSearchRepository, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export class UmbMemberSearchRepository + extends UmbControllerBase + implements UmbSearchRepository, UmbApi +{ + #dataSource: UmbMemberSearchServerDataSource; + + constructor(host: UmbControllerHost) { + super(host); + + this.#dataSource = new UmbMemberSearchServerDataSource(this); + } + + search(args: UmbSearchRequestArgs) { + return this.#dataSource.search(args); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/search/member-search.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/search/member-search.server.data-source.ts new file mode 100644 index 0000000000..b56ebd9dc6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/search/member-search.server.data-source.ts @@ -0,0 +1,65 @@ +import { UMB_MEMBER_ENTITY_TYPE } from '../entity.js'; +import type { UmbMemberSearchItemModel } from './member.search-provider.js'; +import type { UmbSearchDataSource, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { MemberService } from '@umbraco-cms/backoffice/external/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * A data source for the Rollback that fetches data from the server + * @export + * @class UmbMemberSearchServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbMemberSearchServerDataSource implements UmbSearchDataSource { + #host: UmbControllerHost; + + /** + * Creates an instance of UmbMemberSearchServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbMemberSearchServerDataSource + */ + constructor(host: UmbControllerHost) { + this.#host = host; + } + + /** + * Get a list of versions for a data + * @return {*} + * @memberof UmbMemberSearchServerDataSource + */ + async search(args: UmbSearchRequestArgs) { + const { data, error } = await tryExecuteAndNotify( + this.#host, + MemberService.getItemMemberSearch({ + query: args.query, + }), + ); + + if (data) { + const mappedItems: Array = data.items.map((item) => { + return { + href: '/section/member-management/workspace/member/edit/' + item.id, + entityType: UMB_MEMBER_ENTITY_TYPE, + unique: item.id, + name: item.variants[0].name || '', + memberType: { + unique: item.memberType.id, + icon: item.memberType.icon, + collection: item.memberType.collection ? { unique: item.memberType.collection.id } : null, + }, + variants: item.variants.map((variant) => { + return { + name: variant.name, + culture: variant.culture || null, + }; + }), + }; + }); + + return { data: { items: mappedItems, total: data.total } }; + } + + return { error }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/search/member.search-provider.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/search/member.search-provider.ts new file mode 100644 index 0000000000..535a812f31 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/search/member.search-provider.ts @@ -0,0 +1,22 @@ +import type { UmbMemberItemModel } from '../repository/item/types.js'; +import { UmbMemberSearchRepository } from './member-search.repository.js'; +import type { UmbSearchProvider, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; + +export interface UmbMemberSearchItemModel extends UmbMemberItemModel { + href: string; +} + +export class UmbMemberSearchProvider extends UmbControllerBase implements UmbSearchProvider { + #repository = new UmbMemberSearchRepository(this); + + async search(args: UmbSearchRequestArgs) { + return this.#repository.search(args); + } + + destroy(): void { + this.#repository.destroy(); + } +} + +export { UmbMemberSearchProvider as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/column/components/input-collection-content-type-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/column/components/input-collection-content-type-property.element.ts index f640501968..3c54003ba6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/column/components/input-collection-content-type-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/column/components/input-collection-content-type-property.element.ts @@ -57,7 +57,12 @@ export class UmbInputCollectionContentTypePropertyElement extends UUIFormControl value: 'createDate', icon: 'icon-settings', }, - { label: this.localize.term('content_createBy'), description: 'owner', value: 'owner', icon: 'icon-settings' }, + { + label: this.localize.term('content_createBy'), + description: 'creator', + value: 'creator', + icon: 'icon-settings', + }, { label: this.localize.term('content_isPublished'), description: 'published', @@ -124,7 +129,12 @@ export class UmbInputCollectionContentTypePropertyElement extends UUIFormControl value: 'createDate', icon: 'icon-settings', }, - { label: this.localize.term('content_createBy'), description: 'owner', value: 'owner', icon: 'icon-settings' }, + { + label: this.localize.term('content_createBy'), + description: 'creator', + value: 'creator', + icon: 'icon-settings', + }, { label: this.localize.term('general_sort'), description: 'sortOrder', diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/manifests.ts index aa4f383b1c..c008f1a678 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/manifests.ts @@ -89,7 +89,7 @@ const propertyEditorUiManifest: ManifestPropertyEditorUi = { ], }, { alias: 'pageSize', value: 10 }, - { alias: 'orderBy', value: 'updateDate' }, + { alias: 'orderBy', value: 'sortOrder' }, { alias: 'orderDirection', value: 'desc' }, { alias: 'bulkActionPermissions', diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/index.ts b/src/Umbraco.Web.UI.Client/src/packages/search/index.ts new file mode 100644 index 0000000000..264c82d513 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/search/index.ts @@ -0,0 +1,3 @@ +export type { UmbSearchResultItemModel, UmbSearchRequestArgs, UmbSearchProvider } from './types.js'; +export type { UmbSearchDataSource } from './search-data-source.interface.js'; +export type { UmbSearchRepository } from './search-repository.interface.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/search-data-source.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/search/search-data-source.interface.ts new file mode 100644 index 0000000000..7b54ac9237 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/search/search-data-source.interface.ts @@ -0,0 +1,11 @@ +import type { UmbSearchRequestArgs, UmbSearchResultItemModel } from './types.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbDataSourceResponse, UmbPagedModel } from '@umbraco-cms/backoffice/repository'; + +export interface UmbSearchDataSourceConstructor { + new (host: UmbControllerHost): UmbSearchDataSource; +} + +export interface UmbSearchDataSource { + search(args: UmbSearchRequestArgs): Promise>>; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/search-modal/search-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/search/search-modal/search-modal.element.ts index 98a0c11f88..eccd95f917 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/search/search-modal/search-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/search/search-modal/search-modal.element.ts @@ -1,305 +1,444 @@ +import type { UmbSearchProvider, UmbSearchResultItemModel } from '../types.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, - LitElement, nothing, repeat, customElement, query, state, + property, } from '@umbraco-cms/backoffice/external/lit'; +import type { ManifestSearchResultItem } from '@umbraco-cms/backoffice/extension-registry'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbExtensionsManifestInitializer, createExtensionApi } from '@umbraco-cms/backoffice/extension-api'; -export type SearchItem = { +import '../search-result/search-result-item.element.js'; +import type { UmbModalContext } from '@umbraco-cms/backoffice/modal'; + +type SearchProvider = { name: string; - icon?: string; - href: string; - parent: string; - url?: string; -}; -export type SearchGroupItem = { - name: string; - items: Array; + api: UmbSearchProvider; + alias: string; }; + @customElement('umb-search-modal') -export class UmbSearchModalElement extends LitElement { +export class UmbSearchModalElement extends UmbLitElement { + @query('#input-wrapper-fake-cursor') + private _inputFakeCursor!: HTMLElement; @query('input') private _input!: HTMLInputElement; + @property({ attribute: false }) + modalContext?: UmbModalContext; + @state() private _search = ''; @state() - private _groups: Array = []; + private _searchResults: Array = []; + + @state() + private _searchProviders: Array = []; + + @state() + _currentProvider?: SearchProvider; + + @state() + _loading: boolean = false; + + #searchItemNavIndex = 0; + + #inputTimer?: NodeJS.Timeout; + #inputTimerAmount = 300; + + constructor() { + super(); + + this.#observeProviders(); + } connectedCallback() { super.connectedCallback(); + this.addEventListener('keydown', this.#onKeydown); + requestAnimationFrame(() => { - this._input.focus(); + this.#focusInput(); + }); + } + + #observeProviders() { + new UmbExtensionsManifestInitializer(this, umbExtensionsRegistry, 'searchProvider', null, async (providers) => { + const searchProviders: Array = []; + + for (const provider of providers) { + const api = await createExtensionApi>(this, provider.manifest); + if (api) { + searchProviders.push({ + name: provider.manifest.meta?.label || provider.manifest.name, + api, + alias: provider.alias, + }); + } + } + + this._searchProviders = searchProviders; + + if (this._searchProviders.length > 0) { + this._currentProvider = this._searchProviders[0]; + } + }); + } + + async #setSearchItemNavIndex(index: number) { + const prevElement = this.shadowRoot?.querySelector( + `a[data-item-index="${this.#searchItemNavIndex}"]`, + ) as HTMLElement | null; + prevElement?.classList.remove('active'); + + this.#searchItemNavIndex = index; + + const element = this.shadowRoot?.querySelector(`a[data-item-index="${index}"]`) as HTMLElement | null; + element?.classList.add('active'); + + if (!element) return; + if (!this._searchResults.length) return; + + element.focus(); + } + + #focusInput() { + this._input.focus(); + } + + async #setShowFakeCursor(show: boolean) { + if (show) { + await new Promise((resolve) => requestAnimationFrame(resolve)); + const getTextBeforeCursor = this._search.substring(0, this._input.selectionStart ?? 0); + this._inputFakeCursor.textContent = getTextBeforeCursor; + this._inputFakeCursor.style.display = 'block'; + } else { + this._inputFakeCursor.style.display = 'none'; + } + } + + #setCurrentProvider(searchProvider: SearchProvider) { + if (this._currentProvider === searchProvider) return; + + this._currentProvider = searchProvider; + + this.#focusInput(); + this._loading = true; + this._searchResults = []; + this.#updateSearchResults(); + } + + async #updateSearchResults() { + if (this._search && this._currentProvider?.api) { + const { data } = await this._currentProvider.api.search({ query: this._search }); + if (!data) return; + this._searchResults = data.items; + } else { + this._searchResults = []; + } + + this._loading = false; + this.#searchItemNavIndex = -1; + } + + #closeModal(event: MouseEvent | KeyboardEvent) { + if (event instanceof KeyboardEvent && event.key !== 'Enter') return; + + requestAnimationFrame(() => { + // In the case where the browser has not triggered focus-visible and we keyboard navigate and press enter. + // It is necessary to wait one frame. + this.modalContext?.reject(); }); } #onSearchChange(event: InputEvent) { const target = event.target as HTMLInputElement; - this._search = target.value; + this._search = target.value.trim(); - this.#updateGroups(); + clearTimeout(this.#inputTimer); + if (!this._search) { + this._loading = false; + this._searchResults = []; + return; + } + + this._loading = true; + this.#inputTimer = setTimeout(() => this.#updateSearchResults(), this.#inputTimerAmount); } - #onClearSearch() { - this._search = ''; - this._input.value = ''; - this._input.focus(); - this.#updateGroups(); - } + #onKeydown(event: KeyboardEvent) { + const root = this.shadowRoot; + if (!root) return; - #updateGroups() { - const filtered = this.#mockData.filter((item) => { - return item.name.toLowerCase().includes(this._search.toLowerCase()); - }); + if (event.key === 'Tab') { + const isFirstProvider = (element: Element) => element === root.querySelector('.search-provider:first-child'); + const isLastProvider = (element: Element) => element === root.querySelector('.search-provider:last-child'); + const setFocus = (element?: Element | null) => (element as HTMLElement)?.focus(); + const providerHasFocus = () => { + const providerElements = root.querySelectorAll('.search-provider') || []; + return Array.from(providerElements).some((element) => element === root.activeElement); + }; + const isFocusingLastProvider = () => { + const providerElements = root.querySelectorAll('.search-provider') || []; + return providerElements[providerElements.length - 1] === root.activeElement; + }; + const isFocusingFirstProvider = () => { + const providerElements = root.querySelectorAll('.search-provider') || []; + return providerElements[0] === root.activeElement; + }; - const grouped: Array = filtered.reduce((acc, item) => { - const group = acc.find((group) => group.name === item.parent); - if (group) { - group.items.push(item); - } else { - acc.push({ - name: item.parent, - items: [item], - }); + const activeProvider = root.querySelector('.search-provider.active') as HTMLElement | null; + + if (!activeProvider) return; + + // When moving backwards in search providers + if (event.shiftKey) { + // If the FOCUS is on a provider, and it is the first in the list, we need to wrap around and focus the LAST one + if (providerHasFocus()) { + if (isFocusingFirstProvider()) { + setFocus(root.querySelector('.search-provider:last-child')); + event.preventDefault(); + } + return; + } + + // If the currently ACTIVE provider is the first in the list, we need to wrap around and focus the LAST one + if (isFirstProvider(activeProvider)) { + setFocus(root.querySelector('.search-provider:last-child')); + event.preventDefault(); + return; + } + + // We set the focus to current provider, and because we don't prevent the default tab behavior, the previous provider will be focused + setFocus(activeProvider); } - return acc; - }, [] as Array); + // When moving forwards in search providers + else { + // If the FOCUS is on a provider, and it is the last in the list, we need to wrap around and focus the FIRST one + if (providerHasFocus()) { + if (isFocusingLastProvider()) { + setFocus(root.querySelector('.search-provider:first-child')); + event.preventDefault(); + } + return; + } - this._groups = grouped; + // If the currently ACTIVE provider is the last in the list, we need to wrap around and focus the FIRST one + if (isLastProvider(activeProvider)) { + setFocus(root.querySelector('.search-provider:first-child')); + event.preventDefault(); + return; + } + + // We set the focus to current provider, and because we don't prevent the default tab behavior, the next provider will be focused + setFocus(activeProvider); + } + } + + switch (event.key) { + case 'Tab': + case 'Shift': + case 'Escape': + case 'Enter': + break; + case 'ArrowDown': + event.preventDefault(); + this.#setSearchItemNavIndex(Math.min(this.#searchItemNavIndex + 1, this._searchResults.length - 1)); + break; + case 'ArrowUp': + event.preventDefault(); + this.#setSearchItemNavIndex(Math.max(this.#searchItemNavIndex - 1, 0)); + break; + default: + if (this._input === root.activeElement) return; + this.#focusInput(); + break; + } } render() { return html`
-
- -
- -
- + ${this.#renderSearchIcon()} +
+ + this.#setShowFakeCursor(true)} + @focus=${() => this.#setShowFakeCursor(false)} + type="text" + placeholder="Search..." + autocomplete="off" />
+ + ${this.#renderSearchTags()} ${this._search - ? html`
- ${this._groups.length > 0 - ? repeat( - this._groups, - (group) => group.name, - (group) => this.#renderGroup(group.name, group.items), - ) - : html`
Only mock data for now Search for blog
`} -
` + ? html`
${this._searchResults.length > 0 ? this.#renderResults() : this.#renderNoResults()}
` : nothing} `; } - #renderGroup(name: string, items: Array) { - return html` -
-
${name}
-
${repeat(items, (item) => item.name, this.#renderItem.bind(this))}
-
- `; + #renderSearchIcon() { + return html`
+ ${this._loading ? html`` : html``} +
`; } - #renderItem(item: SearchItem) { + #renderSearchTags() { + return html`
+ ${repeat( + this._searchProviders, + (searchProvider) => searchProvider, + (searchProvider) => + html``, + )} +
`; + } + + #renderResults() { + return repeat( + this._searchResults, + (item) => item.unique, + (item, index) => this.#renderResultItem(item, index), + ); + } + + #renderResultItem(item: UmbSearchResultItemModel, index: number) { return html` - - - ${item.icon ? html`` : this.#renderHashTag()} - - - ${item.name} ${item.url ? html`${item.url}` : nothing} - - > + + manifest.forEntityTypes.includes(item.entityType)} + default-element="umb-search-result-item"> `; } - #renderHashTag() { - return html` - - - - - `; + #renderNoResults() { + return this._loading ? nothing : html`
${this.localize.term('general_searchNoResult')}
`; } - #mockData: Array = [ - { - name: 'Blog', - href: '#', - icon: 'icon-thumbnail-list', - parent: 'Content', - url: '/blog/', - }, - { - name: 'Popular blogs', - href: '#', - icon: 'icon-article', - parent: 'Content', - url: '/blog/popular-blogs/', - }, - { - name: 'How to write a blog', - href: '#', - icon: 'icon-article', - parent: 'Content', - url: '/blog/how-to-write-a-blog/', - }, - { - name: 'Blog hero', - href: '#', - icon: 'icon-picture', - parent: 'Media', - }, - { - name: 'Contact form for blog', - href: '#', - parent: 'Document Types', - }, - { - name: 'Blog', - href: '#', - parent: 'Document Types', - }, - { - name: 'Blog link item', - href: '#', - parent: 'Document Types', - }, - ]; - static styles = [ UmbTextStyles, css` :host { display: flex; flex-direction: column; - width: min(500px, 100vw); - height: 100%; - background-color: var(--uui-color-background); + width: min(610px, 100vw); + height: max(600px, 80dvh); + max-height: 100dvh; + background-color: var(--uui-color-surface); box-sizing: border-box; color: var(--uui-color-text); font-size: 1rem; + padding-bottom: var(--uui-size-space-2); + } + #top { + background-color: var(--uui-color-surface); + display: flex; + height: 48px; + flex-shrink: 0; + } + #main { + display: flex; + flex-direction: column; + height: 100%; + overflow: auto; + } + #search-providers { + display: flex; + flex-wrap: wrap; + gap: var(--uui-size-space-2); + padding: 0 var(--uui-size-space-5); + padding-bottom: var(--uui-size-space-2); + } + .search-provider { + padding: var(--uui-size-space-3) var(--uui-size-space-4); + background: var(--uui-color-surface-alt); + line-height: 1; + white-space: nowrap; + border-radius: var(--uui-border-radius); + color: var(--uui-color-interactive); + cursor: pointer; + border: 2px solid transparent; + } + .search-provider:hover { + background: var(--uui-color-surface-emphasis); + color: var(--uui-color-interactive-emphasis); + } + .search-provider.active { + background: var(--uui-color-focus); + color: var(--uui-color-selected-contrast); + border-color: transparent; + } + .search-provider.active:focus { + outline-offset: -4px; + outline-color: var(--uui-color-focus); } input { all: unset; height: 100%; width: 100%; } - #search-icon, - #close-icon { + #input-wrapper { + width: 100%; + position: relative; + } + #input-wrapper-fake-cursor { + position: absolute; + left: 0; + border-right: 1px solid var(--uui-color-text); + height: 1.2rem; + color: transparent; + user-select: none; + pointer-events: none; + bottom: 14px; + animation: blink-animation 1s infinite; + } + @keyframes blink-animation { + 0%, + 50% { + border-color: var(--uui-color-text); + } + 51%, + 100% { + border-color: transparent; + } + } + button { + font-family: unset; + font-size: unset; + cursor: pointer; + } + #search-icon { display: flex; align-items: center; justify-content: center; aspect-ratio: 1; height: 100%; } - #close-icon { - padding: 0 var(--uui-size-space-4); - } - #close-icon > button { - background: var(--uui-color-surface-alt); - border: 1px solid var(--uui-color-border); - padding: 3px 6px 4px 6px; - line-height: 1; - border-radius: 3px; - color: var(--uui-color-text-alt); - font-weight: 800; - font-size: 12px; - cursor: pointer; - } - #close-icon > button:hover { - border-color: var(--uui-color-focus); - color: var(--uui-color-focus); - } - #top { - background-color: var(--uui-color-surface); - display: flex; - height: 48px; - } - #main { - display: flex; - flex-direction: column; - padding: 0px var(--uui-size-space-6) var(--uui-size-space-5) var(--uui-size-space-6); - height: 100%; - border-top: 1px solid var(--uui-color-border); - } - .group { - margin-top: var(--uui-size-space-4); - } - .group-name { - font-weight: 600; - margin-bottom: var(--uui-size-space-1); - } - .group-items { - display: flex; - flex-direction: column; - gap: var(--uui-size-space-3); - } - .item { - background: var(--uui-color-surface); - border: 1px solid var(--uui-color-border); - padding: var(--uui-size-space-3) var(--uui-size-space-4); - border-radius: var(--uui-border-radius); - color: var(--uui-color-interactive); - display: grid; - grid-template-columns: var(--uui-size-space-6) 1fr var(--uui-size-space-5); - height: min-content; - align-items: center; - } - .item:hover { - background-color: var(--uui-color-surface-emphasis); - color: var(--uui-color-interactive-emphasis); - } - .item:hover .item-symbol { - font-weight: unset; - opacity: 1; - } - .item-icon { - margin-bottom: auto; - margin-top: 5px; - } - .item-icon, - .item-symbol { - opacity: 0.4; - } - .item-url { - font-size: 0.8rem; - line-height: 1.2; - font-weight: 100; - } - .item-name { - display: flex; - flex-direction: column; - } - .item-icon > * { - height: 1rem; - display: flex; - width: min-content; - } - .item-symbol { - font-weight: 100; - } - a { - text-decoration: none; - color: inherit; - } #no-results { display: flex; flex-direction: column; @@ -309,6 +448,27 @@ export class UmbSearchModalElement extends LitElement { width: 100%; margin-top: var(--uui-size-space-5); color: var(--uui-color-text-alt); + margin: var(--uui-size-space-5) 0; + } + .search-item { + color: var(--uui-color-text); + text-decoration: none; + outline-offset: -3px; + display: flex; + } + .search-item:hover { + background: var(--uui-color-surface-emphasis); + color: var(--uui-color-interactive-emphasis); + } + .search-item:focus { + outline: 2px solid var(--uui-color-interactive-emphasis); + border-radius: 6px; + outline-offset: -4px; + } + .search-item.active:not(:focus-within) { + outline: 2px solid var(--uui-color-border); + border-radius: 6px; + outline-offset: -4px; } `, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/search-repository.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/search/search-repository.interface.ts new file mode 100644 index 0000000000..d13705beb1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/search/search-repository.interface.ts @@ -0,0 +1,6 @@ +import type { UmbSearchRequestArgs, UmbSearchResultItemModel } from './types.js'; +import type { UmbRepositoryResponse, UmbPagedModel } from '@umbraco-cms/backoffice/repository'; + +export interface UmbSearchRepository { + search(args: UmbSearchRequestArgs): Promise>>; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/search-result/search-result-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/search/search-result/search-result-item.element.ts new file mode 100644 index 0000000000..7929225caf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/search/search-result/search-result-item.element.ts @@ -0,0 +1,76 @@ +import type { UmbSearchResultItemModel } from '../types.js'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { css, customElement, html, nothing, property } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; + +const elementName = 'umb-search-result-item'; +@customElement(elementName) +export class UmbSearchResultItemElement extends UmbLitElement { + @property({ type: Object }) + item?: UmbSearchResultItemModel; + + render() { + if (!this.item) return nothing; + + return html` + + ${this.item.icon ? html`` : this.#renderHashTag()} + + ${this.item.name} + `; + } + + #renderHashTag() { + return html` + + + + + `; + } + + static styles = [ + UmbTextStyles, + css` + :host { + padding: var(--uui-size-space-3) var(--uui-size-space-5); + border-radius: var(--uui-border-radius); + display: grid; + grid-template-columns: var(--uui-size-space-6) 1fr var(--uui-size-space-5); + align-items: center; + width: 100%; + outline-offset: -3px; + } + .item-icon { + margin-bottom: auto; + margin-top: 5px; + } + .item-icon { + opacity: 0.4; + } + .item-name { + display: flex; + flex-direction: column; + } + .item-icon > * { + height: 1rem; + display: flex; + width: min-content; + } + a { + text-decoration: none; + color: inherit; + } + `, + ]; +} + +export { UmbSearchResultItemElement as element }; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: UmbSearchResultItemElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/types.ts b/src/Umbraco.Web.UI.Client/src/packages/search/types.ts new file mode 100644 index 0000000000..a1309314d4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/search/types.ts @@ -0,0 +1,18 @@ +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; +import type { UmbPagedModel, UmbRepositoryResponse } from '@umbraco-cms/backoffice/repository'; + +export type UmbSearchResultItemModel = { + entityType: string; + icon?: string | null; + name: string; + unique: string; + href: string; +}; + +export type UmbSearchRequestArgs = { + query: string; +}; + +export interface UmbSearchProvider extends UmbApi { + search(args: UmbSearchRequestArgs): Promise>>; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/modals/templating-page-field-builder/components/template-field-dropdown-list/template-field-dropdown-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/modals/templating-page-field-builder/components/template-field-dropdown-list/template-field-dropdown-list.element.ts index 725cf34067..cd6a70cece 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/modals/templating-page-field-builder/components/template-field-dropdown-list/template-field-dropdown-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/modals/templating-page-field-builder/components/template-field-dropdown-list/template-field-dropdown-list.element.ts @@ -69,7 +69,7 @@ export class UmbTemplateFieldDropdownListElement extends UmbLitElement { { alias: 'updateDate', name: this.localize.term('content_updateDate') }, { alias: 'updater', name: this.localize.term('content_updatedBy') }, { alias: 'createDate', name: this.localize.term('content_createDate') }, - { alias: 'owner', name: this.localize.term('content_createBy') }, + { alias: 'creator', name: this.localize.term('content_createBy') }, { alias: 'published', name: this.localize.term('content_isPublished') }, { alias: 'contentTypeAlias', name: this.localize.term('content_documentType') }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/manifests.ts index 73e3bc3fed..0488df8717 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/manifests.ts @@ -1,16 +1,18 @@ -import { manifests as repositoryManifests } from './repository/manifests.js'; -import { manifests as menuManifests } from './menu/manifests.js'; -import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as entityActionsManifests } from './entity-actions/manifests.js'; -import { manifests as workspaceManifests } from './workspace/manifests.js'; +import { manifests as menuManifests } from './menu/manifests.js'; import { manifests as modalManifests } from './modals/manifests.js'; +import { manifests as repositoryManifests } from './repository/manifests.js'; +import { manifests as searchManifests } from './search/manifests.js'; +import { manifests as treeManifests } from './tree/manifests.js'; +import { manifests as workspaceManifests } from './workspace/manifests.js'; import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ + ...entityActionsManifests, + ...menuManifests, ...modalManifests, ...repositoryManifests, - ...menuManifests, + ...searchManifests, ...treeManifests, - ...entityActionsManifests, ...workspaceManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/item/template-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/item/template-item.server.data-source.ts index d489a0ff3a..5d23912e28 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/item/template-item.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/item/template-item.server.data-source.ts @@ -1,3 +1,4 @@ +import { UMB_TEMPLATE_ENTITY_TYPE } from '../../entity.js'; import type { UmbTemplateItemModel } from './types.js'; import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'; import type { TemplateItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; @@ -32,6 +33,7 @@ const getItems = (uniques: Array) => TemplateService.getItemTemplate({ i const mapper = (item: TemplateItemResponseModel): UmbTemplateItemModel => { return { + entityType: UMB_TEMPLATE_ENTITY_TYPE, unique: item.id, name: item.name, alias: item.alias, diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/item/types.ts index 26c6cd7105..21b449e54f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/item/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/item/types.ts @@ -1,4 +1,7 @@ +import type { UmbTemplateEntityType } from '../../entity.js'; + export interface UmbTemplateItemModel { + entityType: UmbTemplateEntityType; unique: string; name: string; alias: string; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/search/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/search/manifests.ts new file mode 100644 index 0000000000..04c046c23b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/search/manifests.ts @@ -0,0 +1,21 @@ +import { UMB_TEMPLATE_ENTITY_TYPE } from '../entity.js'; +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + name: 'Template Search Provider', + alias: 'Umb.SearchProvider.Template', + type: 'searchProvider', + api: () => import('./template.search-provider.js'), + weight: 100, + meta: { + label: 'Templates', + }, + }, + { + name: 'Template Search Result Item ', + alias: 'Umb.SearchResultItem.Template', + type: 'searchResultItem', + forEntityTypes: [UMB_TEMPLATE_ENTITY_TYPE], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/search/template-search.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/search/template-search.repository.ts new file mode 100644 index 0000000000..8ff8d8dc75 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/search/template-search.repository.ts @@ -0,0 +1,23 @@ +import { UmbTemplateSearchServerDataSource } from './template-search.server.data-source.js'; +import type { UmbTemplateSearchItemModel } from './template.search-provider.js'; +import type { UmbSearchRepository, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export class UmbTemplateSearchRepository + extends UmbControllerBase + implements UmbSearchRepository, UmbApi +{ + #dataSource: UmbTemplateSearchServerDataSource; + + constructor(host: UmbControllerHost) { + super(host); + + this.#dataSource = new UmbTemplateSearchServerDataSource(this); + } + + search(args: UmbSearchRequestArgs) { + return this.#dataSource.search(args); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/search/template-search.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/search/template-search.server.data-source.ts new file mode 100644 index 0000000000..ba85e4e73c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/search/template-search.server.data-source.ts @@ -0,0 +1,55 @@ +import { UMB_TEMPLATE_ENTITY_TYPE } from '../entity.js'; +import type { UmbTemplateSearchItemModel } from './template.search-provider.js'; +import type { UmbSearchDataSource, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { TemplateService } from '@umbraco-cms/backoffice/external/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * A data source for the Rollback that fetches data from the server + * @export + * @class UmbTemplateSearchServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbTemplateSearchServerDataSource implements UmbSearchDataSource { + #host: UmbControllerHost; + + /** + * Creates an instance of UmbTemplateSearchServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbTemplateSearchServerDataSource + */ + constructor(host: UmbControllerHost) { + this.#host = host; + } + + /** + * Get a list of versions for a data + * @return {*} + * @memberof UmbTemplateSearchServerDataSource + */ + async search(args: UmbSearchRequestArgs) { + const { data, error } = await tryExecuteAndNotify( + this.#host, + TemplateService.getItemTemplateSearch({ + query: args.query, + }), + ); + + if (data) { + const mappedItems: Array = data.items.map((item) => { + return { + href: '/section/settings/workspace/template/edit/' + item.id, + entityType: UMB_TEMPLATE_ENTITY_TYPE, + unique: item.id, + name: item.name, + alias: item.alias, + }; + }); + + return { data: { items: mappedItems, total: data.total } }; + } + + return { error }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/search/template.search-provider.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/search/template.search-provider.ts new file mode 100644 index 0000000000..a1e8b90235 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/search/template.search-provider.ts @@ -0,0 +1,25 @@ +import type { UmbTemplateItemModel } from '../repository/item/types.js'; +import { UmbTemplateSearchRepository } from './template-search.repository.js'; +import type { UmbSearchProvider, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; + +export interface UmbTemplateSearchItemModel extends UmbTemplateItemModel { + href: string; +} + +export class UmbTemplateSearchProvider + extends UmbControllerBase + implements UmbSearchProvider +{ + #repository = new UmbTemplateSearchRepository(this); + + async search(args: UmbSearchRequestArgs) { + return this.#repository.search(args); + } + + destroy(): void { + this.#repository.destroy(); + } +} + +export { UmbTemplateSearchProvider as api }; diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 1a5c3a0f7d..e9de4e1d6e 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -86,6 +86,7 @@ "@umbraco-cms/backoffice/repository": ["./src/packages/core/repository/index.ts"], "@umbraco-cms/backoffice/resources": ["./src/packages/core/resources/index.ts"], "@umbraco-cms/backoffice/router": ["./src/packages/core/router/index.ts"], + "@umbraco-cms/backoffice/search": ["./src/packages/search/index.ts"], "@umbraco-cms/backoffice/section": ["./src/packages/core/section/index.ts"], "@umbraco-cms/backoffice/settings": ["./src/packages/settings/index.ts"], "@umbraco-cms/backoffice/server-file-system": ["./src/packages/core/server-file-system/index.ts"],