Merge branch 'main' into bugfix/silo-based-audit-log
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -804,7 +804,7 @@ export const data: Array<UmbMockDataTypeModel> = [
|
||||
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<UmbMockDataTypeModel> = [
|
||||
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' },
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<ManifestTypes>
|
||||
| ManifestBlockEditorCustomView
|
||||
| ManifestBundle<ManifestTypes>
|
||||
| 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;
|
||||
|
||||
@@ -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<UmbSearchProvider<UmbSearchResultItemModel>> {
|
||||
type: 'searchProvider';
|
||||
|
||||
meta?: MetaSearchProvider;
|
||||
}
|
||||
|
||||
export interface MetaSearchProvider {
|
||||
/**
|
||||
* The label of the provider that is shown to the user.
|
||||
*/
|
||||
label?: string;
|
||||
}
|
||||
@@ -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<string>;
|
||||
}
|
||||
@@ -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`<div id="action-modal">
|
||||
? html`<div id="action-modal" @umb:context-request=${this.#proxyContextRequests}>
|
||||
${this._headline ? html`<h3>${this.localize.string(this._headline)}</h3>` : nothing}
|
||||
<umb-entity-action-list
|
||||
@action-executed=${this.#onActionExecuted}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { UmbOpenContextMenuArgs } from './types.js';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
@@ -16,21 +17,24 @@ export class UmbSectionSidebarContext extends UmbContextBase<UmbSectionSidebarCo
|
||||
#headline = new UmbStringState<undefined>(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<UmbSectionSidebarCo
|
||||
this.#entityType.setValue(undefined);
|
||||
this.#unique.setValue(undefined);
|
||||
this.#headline.setValue(undefined);
|
||||
this.#contextElement = undefined;
|
||||
}
|
||||
|
||||
getContextElement() {
|
||||
return this.#contextElement;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface UmbOpenContextMenuArgs {
|
||||
entityType: string;
|
||||
unique: string | null | undefined;
|
||||
headline: string | undefined;
|
||||
}
|
||||
@@ -180,7 +180,11 @@ export abstract class UmbTreeItemContextBase<TreeItemType extends UmbTreeItemMod
|
||||
throw new Error('Could not request children, tree item is not set');
|
||||
}
|
||||
|
||||
this.#sectionSidebarContext?.toggleContextMenu(this.entityType, this.unique, this.getTreeItem()?.name || '');
|
||||
this.#sectionSidebarContext?.toggleContextMenu(this.getHostElement(), {
|
||||
entityType: this.entityType,
|
||||
unique: this.unique,
|
||||
headline: this.getTreeItem()?.name || '',
|
||||
});
|
||||
}
|
||||
|
||||
public select() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { UmbDataTypeCollectionFilterModel } from '../types.js';
|
||||
import type { UmbDataTypeItemModel } from '../../repository/index.js';
|
||||
import { UMB_DATA_TYPE_ENTITY_TYPE } from '../../entity.js';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
|
||||
import { DataTypeService } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import type { UmbCollectionDataSource } from '@umbraco-cms/backoffice/collection';
|
||||
@@ -53,6 +54,7 @@ export class UmbDataTypeCollectionServerDataSource implements UmbCollectionDataS
|
||||
|
||||
const mappedItems: Array<UmbDataTypeItemModel> = items.map((item: DataTypeItemResponseModel) => {
|
||||
const dataTypeDetail: UmbDataTypeItemModel = {
|
||||
entityType: UMB_DATA_TYPE_ENTITY_TYPE,
|
||||
unique: item.id,
|
||||
name: item.name,
|
||||
propertyEditorUiAlias: item.editorUiAlias!,
|
||||
|
||||
@@ -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<ManifestTypes> = [
|
||||
...collectionManifests,
|
||||
...entityActions,
|
||||
...repositoryManifests,
|
||||
...menuManifests,
|
||||
...modalManifests,
|
||||
...repositoryManifests,
|
||||
...searchProviderManifests,
|
||||
...treeManifests,
|
||||
...workspaceManifests,
|
||||
...modalManifests,
|
||||
...collectionManifests,
|
||||
];
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<string>) => 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?
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { UmbDataTypeEntityType } from '../../entity.js';
|
||||
|
||||
export interface UmbDataTypeItemModel {
|
||||
entityType: UmbDataTypeEntityType;
|
||||
unique: string;
|
||||
name: string;
|
||||
propertyEditorUiAlias: string;
|
||||
|
||||
@@ -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<UmbDataTypeSearchItemModel>, UmbApi
|
||||
{
|
||||
#dataSource: UmbDataTypeSearchServerDataSource;
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
|
||||
this.#dataSource = new UmbDataTypeSearchServerDataSource(this);
|
||||
}
|
||||
|
||||
search(args: UmbSearchRequestArgs) {
|
||||
return this.#dataSource.search(args);
|
||||
}
|
||||
}
|
||||
@@ -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<UmbDataTypeSearchItemModel> {
|
||||
#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<UmbDataTypeSearchItemModel> = 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 };
|
||||
}
|
||||
}
|
||||
@@ -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<UmbDataTypeSearchItemModel>
|
||||
{
|
||||
#repository = new UmbDataTypeSearchRepository(this);
|
||||
|
||||
async search(args: UmbSearchRequestArgs) {
|
||||
return this.#repository.search(args);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.#repository.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export { UmbDataTypeSearchProvider as api };
|
||||
@@ -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<ManifestTypes> = [
|
||||
{
|
||||
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],
|
||||
},
|
||||
];
|
||||
@@ -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<ManifestTypes> = [
|
||||
...menuManifests,
|
||||
...propertyEditorManifests,
|
||||
...repositoryManifests,
|
||||
...searchManifests,
|
||||
...treeManifests,
|
||||
...workspaceManifests,
|
||||
];
|
||||
|
||||
@@ -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<string>) => DocumentTypeService.getItemDocument
|
||||
|
||||
const mapper = (item: DocumentTypeItemResponseModel): UmbDocumentTypeItemModel => {
|
||||
return {
|
||||
entityType: UMB_DOCUMENT_TYPE_ENTITY_TYPE,
|
||||
isElement: item.isElement,
|
||||
icon: item.icon,
|
||||
unique: item.id,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { UmbDocumentTypeEntityType } from '../../entity.js';
|
||||
|
||||
export type UmbDocumentTypeItemModel = {
|
||||
entityType: UmbDocumentTypeEntityType;
|
||||
unique: string;
|
||||
name: string;
|
||||
isElement: boolean;
|
||||
|
||||
@@ -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<UmbDocumentTypeSearchItemModel>, UmbApi
|
||||
{
|
||||
#dataSource: UmbDocumentTypeSearchServerDataSource;
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
|
||||
this.#dataSource = new UmbDocumentTypeSearchServerDataSource(this);
|
||||
}
|
||||
|
||||
search(args: UmbSearchRequestArgs) {
|
||||
return this.#dataSource.search(args);
|
||||
}
|
||||
}
|
||||
@@ -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<UmbDocumentTypeSearchItemModel> {
|
||||
#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<UmbDocumentTypeSearchItemModel> = 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 };
|
||||
}
|
||||
}
|
||||
@@ -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<UmbDocumentTypeSearchItemModel>
|
||||
{
|
||||
#repository = new UmbDocumentTypeSearchRepository(this);
|
||||
|
||||
async search(args: UmbSearchRequestArgs) {
|
||||
return this.#repository.search(args);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.#repository.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export { UmbDocumentTypeSearchProvider as api };
|
||||
@@ -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<ManifestTypes> = [
|
||||
{
|
||||
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],
|
||||
},
|
||||
];
|
||||
@@ -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,
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface UmbDocumentCollectionItemModel {
|
||||
creator?: string | null;
|
||||
icon: string;
|
||||
name: string;
|
||||
sortOrder: number;
|
||||
state: string;
|
||||
updateDate: Date;
|
||||
updater?: string | null;
|
||||
|
||||
@@ -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),
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderCard(index: number, item: UmbDocumentCollectionItemModel) {
|
||||
const sortOrder = this._skip + index;
|
||||
#renderCard(item: UmbDocumentCollectionItemModel) {
|
||||
return html`
|
||||
<uui-card-content-node
|
||||
.name=${item.name ?? 'Unnamed Document'}
|
||||
@@ -114,7 +113,7 @@ export class UmbDocumentGridCollectionViewElement extends UmbLitElement {
|
||||
@selected=${() => this.#onSelect(item)}
|
||||
@deselected=${() => this.#onDeselect(item)}>
|
||||
<umb-icon slot="icon" name=${item.icon}></umb-icon>
|
||||
${this.#renderState(item)} ${this.#renderProperties(sortOrder, item)}
|
||||
${this.#renderState(item)} ${this.#renderProperties(item)}
|
||||
</uui-card-content-node>
|
||||
`;
|
||||
}
|
||||
@@ -142,15 +141,14 @@ export class UmbDocumentGridCollectionViewElement extends UmbLitElement {
|
||||
}
|
||||
}
|
||||
|
||||
#renderProperties(sortOrder: number, item: UmbDocumentCollectionItemModel) {
|
||||
#renderProperties(item: UmbDocumentCollectionItemModel) {
|
||||
if (!this._userDefinedProperties) return;
|
||||
return html`
|
||||
<ul>
|
||||
${repeat(
|
||||
this._userDefinedProperties,
|
||||
(column) => column.alias,
|
||||
(column) =>
|
||||
html`<li><span>${column.header}:</span> ${getPropertyValueByAlias(sortOrder, item, column.alias)}</li>`,
|
||||
(column) => html`<li><span>${column.header}:</span> ${getPropertyValueByAlias(item, column.alias)}</li>`,
|
||||
)}
|
||||
</ul>
|
||||
`;
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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`<uui-button
|
||||
look="default"
|
||||
color="default"
|
||||
compact
|
||||
href="${this._editDocumentPath}edit/${this.value.unique}"
|
||||
label="${this.value.name}"
|
||||
@click=${this.#onClick}></uui-button>`;
|
||||
return html`
|
||||
<uui-button
|
||||
compact
|
||||
href="${this._editDocumentPath}edit/${this.value.unique}"
|
||||
label=${this.value.name}
|
||||
@click=${this.#onClick}></uui-button>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
|
||||
@@ -41,15 +41,15 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement {
|
||||
#systemColumns: Array<UmbTableColumn> = [
|
||||
{
|
||||
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<UmbDocumentCollectionItemModel>) {
|
||||
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),
|
||||
};
|
||||
}) ?? [];
|
||||
|
||||
|
||||
@@ -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<ManifestTypes> = [
|
||||
...collectionManifests,
|
||||
...entityActionManifests,
|
||||
...entityBulkActionManifests,
|
||||
...modalManifests,
|
||||
...globalContextManifests,
|
||||
...menuManifests,
|
||||
...modalManifests,
|
||||
...propertyEditorManifests,
|
||||
...recycleBinManifests,
|
||||
...repositoryManifests,
|
||||
...searchProviderManifests,
|
||||
...trackedReferenceManifests,
|
||||
...treeManifests,
|
||||
...userPermissionManifests,
|
||||
...workspaceManifests,
|
||||
...globalContextManifests,
|
||||
];
|
||||
|
||||
@@ -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`
|
||||
<span class="item-icon">
|
||||
${this.item.icon ? html`<umb-icon name="${this.item.icon}"></umb-icon>` : this.#renderHashTag()}
|
||||
</span>
|
||||
<span class="item-name"> ${this.#getLabel()} </span>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderHashTag() {
|
||||
return html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<path fill="none" d="M0 0h24v24H0z" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M7.784 14l.42-4H4V8h4.415l.525-5h2.011l-.525 5h3.989l.525-5h2.011l-.525 5H20v2h-3.784l-.42 4H20v2h-4.415l-.525 5h-2.011l.525-5H9.585l-.525 5H7.049l.525-5H4v-2h3.784zm2.011 0h3.99l.42-4h-3.99l-.42 4z" />
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<UmbDocumentSearchItemModel>, UmbApi
|
||||
{
|
||||
#dataSource: UmbDocumentSearchServerDataSource;
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
|
||||
this.#dataSource = new UmbDocumentSearchServerDataSource(this);
|
||||
}
|
||||
|
||||
search(args: UmbSearchRequestArgs) {
|
||||
return this.#dataSource.search(args);
|
||||
}
|
||||
}
|
||||
@@ -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<UmbDocumentSearchItemModel> {
|
||||
#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<UmbDocumentSearchItemModel> = 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 };
|
||||
}
|
||||
}
|
||||
@@ -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<UmbDocumentSearchItemModel>
|
||||
{
|
||||
#repository = new UmbDocumentSearchRepository(this);
|
||||
|
||||
search(args: UmbSearchRequestArgs) {
|
||||
return this.#repository.search(args);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.#repository.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export { UmbDocumentSearchProvider as api };
|
||||
@@ -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<ManifestTypes> = [
|
||||
{
|
||||
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],
|
||||
},
|
||||
];
|
||||
@@ -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<ManifestTypes> = [
|
||||
@@ -13,4 +14,5 @@ export const manifests: Array<ManifestTypes> = [
|
||||
...treeManifests,
|
||||
...workspaceManifests,
|
||||
...propertyEditorUiManifests,
|
||||
...searchManifests,
|
||||
];
|
||||
|
||||
@@ -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<string>) => MediaTypeService.getItemMediaType({
|
||||
|
||||
const mapper = (item: MediaTypeItemResponseModel): UmbMediaTypeItemModel => {
|
||||
return {
|
||||
entityType: UMB_MEDIA_TYPE_ENTITY_TYPE,
|
||||
icon: item.icon || null,
|
||||
name: item.name,
|
||||
unique: item.id,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { UmbMediaTypeEntityType } from '../../entity.js';
|
||||
|
||||
export interface UmbMediaTypeItemModel {
|
||||
entityType: UmbMediaTypeEntityType;
|
||||
icon: string | null;
|
||||
name: string;
|
||||
unique: string;
|
||||
|
||||
@@ -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<ManifestTypes> = [
|
||||
{
|
||||
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],
|
||||
},
|
||||
];
|
||||
@@ -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<UmbMediaTypeSearchItemModel>, UmbApi
|
||||
{
|
||||
#dataSource: UmbMediaTypeSearchServerDataSource;
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
|
||||
this.#dataSource = new UmbMediaTypeSearchServerDataSource(this);
|
||||
}
|
||||
|
||||
search(args: UmbSearchRequestArgs) {
|
||||
return this.#dataSource.search(args);
|
||||
}
|
||||
}
|
||||
@@ -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<UmbMediaTypeSearchItemModel> {
|
||||
#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<UmbMediaTypeSearchItemModel> = 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 };
|
||||
}
|
||||
}
|
||||
@@ -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<UmbMediaTypeSearchItemModel>
|
||||
{
|
||||
#repository = new UmbMediaTypeSearchRepository(this);
|
||||
|
||||
async search(args: UmbSearchRequestArgs) {
|
||||
return this.#repository.search(args);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.#repository.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export { UmbMediaTypeSearchProvider as api };
|
||||
@@ -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 };
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface UmbMediaCollectionItemModel {
|
||||
creator?: string | null;
|
||||
icon: string;
|
||||
name: string;
|
||||
sortOrder: number;
|
||||
updateDate: Date;
|
||||
values: Array<{ alias: string; value: string }>;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement {
|
||||
#systemColumns: Array<UmbTableColumn> = [
|
||||
{
|
||||
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:
|
||||
|
||||
@@ -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<ManifestTypes> = [
|
||||
...propertyEditorsManifests,
|
||||
...recycleBinManifests,
|
||||
...repositoryManifests,
|
||||
...searchManifests,
|
||||
...sectionViewManifests,
|
||||
...treeManifests,
|
||||
...workspaceManifests,
|
||||
|
||||
@@ -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<string>) => MediaService.getItemMedia({ id: uni
|
||||
|
||||
const mapper = (item: MediaItemResponseModel): UmbMediaItemModel => {
|
||||
return {
|
||||
entityType: UMB_MEDIA_ENTITY_TYPE,
|
||||
unique: item.id,
|
||||
isTrashed: item.isTrashed,
|
||||
mediaType: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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<ManifestTypes> = [
|
||||
{
|
||||
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],
|
||||
},
|
||||
];
|
||||
@@ -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<UmbMediaSearchItemModel>, UmbApi
|
||||
{
|
||||
#dataSource: UmbMediaSearchServerDataSource;
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
|
||||
this.#dataSource = new UmbMediaSearchServerDataSource(this);
|
||||
}
|
||||
|
||||
search(args: UmbSearchRequestArgs) {
|
||||
return this.#dataSource.search(args);
|
||||
}
|
||||
}
|
||||
@@ -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<UmbMediaSearchItemModel> {
|
||||
#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<UmbMediaSearchItemModel> = 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 };
|
||||
}
|
||||
}
|
||||
@@ -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<UmbMediaSearchItemModel> {
|
||||
#repository = new UmbMediaSearchRepository(this);
|
||||
|
||||
async search(args: UmbSearchRequestArgs) {
|
||||
return this.#repository.search(args);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.#repository.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export { UmbMediaSearchProvider as api };
|
||||
@@ -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<ManifestTypes> = [
|
||||
...entityActionsManifests,
|
||||
...menuManifests,
|
||||
...repositoryManifests,
|
||||
...searchManifests,
|
||||
...treeManifests,
|
||||
...workspaceManifests,
|
||||
];
|
||||
|
||||
@@ -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<ManifestTypes> = [
|
||||
{
|
||||
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],
|
||||
},
|
||||
];
|
||||
@@ -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<UmbMemberTypeSearchItemModel>, UmbApi
|
||||
{
|
||||
#dataSource: UmbMemberTypeSearchServerDataSource;
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
|
||||
this.#dataSource = new UmbMemberTypeSearchServerDataSource(this);
|
||||
}
|
||||
|
||||
search(args: UmbSearchRequestArgs) {
|
||||
return this.#dataSource.search(args);
|
||||
}
|
||||
}
|
||||
@@ -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<UmbMemberTypeSearchItemModel> {
|
||||
#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<UmbMemberTypeSearchItemModel> = 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 };
|
||||
}
|
||||
}
|
||||
@@ -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<UmbMemberTypeSearchItemModel>
|
||||
{
|
||||
#repository = new UmbMemberTypeSearchRepository(this);
|
||||
|
||||
async search(args: UmbSearchRequestArgs) {
|
||||
return this.#repository.search(args);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.#repository.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export { UmbMemberTypeSearchProvider as api };
|
||||
@@ -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<ManifestTypes> = [
|
||||
...memberPickerModalManifests,
|
||||
...propertyEditorManifests,
|
||||
...repositoryManifests,
|
||||
...searchManifests,
|
||||
...sectionViewManifests,
|
||||
...workspaceManifests,
|
||||
];
|
||||
|
||||
@@ -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<string>) => MemberService.getItemMember({ id: u
|
||||
|
||||
const mapper = (item: MemberItemResponseModel): UmbMemberItemModel => {
|
||||
return {
|
||||
entityType: UMB_MEMBER_ENTITY_TYPE,
|
||||
unique: item.id,
|
||||
name: item.variants[0].name || '',
|
||||
memberType: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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<ManifestTypes> = [
|
||||
{
|
||||
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],
|
||||
},
|
||||
];
|
||||
@@ -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<UmbMemberSearchItemModel>, UmbApi
|
||||
{
|
||||
#dataSource: UmbMemberSearchServerDataSource;
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
|
||||
this.#dataSource = new UmbMemberSearchServerDataSource(this);
|
||||
}
|
||||
|
||||
search(args: UmbSearchRequestArgs) {
|
||||
return this.#dataSource.search(args);
|
||||
}
|
||||
}
|
||||
@@ -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<UmbMemberSearchItemModel> {
|
||||
#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<UmbMemberSearchItemModel> = 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 };
|
||||
}
|
||||
}
|
||||
@@ -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<UmbMemberSearchItemModel> {
|
||||
#repository = new UmbMemberSearchRepository(this);
|
||||
|
||||
async search(args: UmbSearchRequestArgs) {
|
||||
return this.#repository.search(args);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.#repository.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export { UmbMemberSearchProvider as api };
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
3
src/Umbraco.Web.UI.Client/src/packages/search/index.ts
Normal file
3
src/Umbraco.Web.UI.Client/src/packages/search/index.ts
Normal file
@@ -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';
|
||||
@@ -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<SearchResultItemType extends UmbSearchResultItemModel> {
|
||||
new (host: UmbControllerHost): UmbSearchDataSource<SearchResultItemType>;
|
||||
}
|
||||
|
||||
export interface UmbSearchDataSource<SearchResultItemType extends UmbSearchResultItemModel> {
|
||||
search(args: UmbSearchRequestArgs): Promise<UmbDataSourceResponse<UmbPagedModel<SearchResultItemType>>>;
|
||||
}
|
||||
@@ -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<SearchItem>;
|
||||
api: UmbSearchProvider<UmbSearchResultItemModel>;
|
||||
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<SearchGroupItem> = [];
|
||||
private _searchResults: Array<UmbSearchResultItemModel> = [];
|
||||
|
||||
@state()
|
||||
private _searchProviders: Array<SearchProvider> = [];
|
||||
|
||||
@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<SearchProvider> = [];
|
||||
|
||||
for (const provider of providers) {
|
||||
const api = await createExtensionApi<UmbSearchProvider<UmbSearchResultItemModel>>(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<SearchGroupItem> = 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<SearchGroupItem>);
|
||||
// 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`
|
||||
<div id="top">
|
||||
<div id="search-icon">
|
||||
<uui-icon name="search"></uui-icon>
|
||||
</div>
|
||||
<input
|
||||
value=${this._search}
|
||||
@input=${this.#onSearchChange}
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
autocomplete="off" />
|
||||
<div id="close-icon">
|
||||
<button @click=${this.#onClearSearch}>clear</button>
|
||||
${this.#renderSearchIcon()}
|
||||
<div id="input-wrapper">
|
||||
<div id="input-wrapper-fake-cursor" aria-hidden="true"></div>
|
||||
<input
|
||||
value=${this._search}
|
||||
@input=${this.#onSearchChange}
|
||||
@blur=${() => this.#setShowFakeCursor(true)}
|
||||
@focus=${() => this.#setShowFakeCursor(false)}
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${this.#renderSearchTags()}
|
||||
${this._search
|
||||
? html`<div id="main">
|
||||
${this._groups.length > 0
|
||||
? repeat(
|
||||
this._groups,
|
||||
(group) => group.name,
|
||||
(group) => this.#renderGroup(group.name, group.items),
|
||||
)
|
||||
: html`<div id="no-results">Only mock data for now <strong>Search for blog</strong></div>`}
|
||||
</div>`
|
||||
? html`<div id="main">${this._searchResults.length > 0 ? this.#renderResults() : this.#renderNoResults()}</div>`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
#renderGroup(name: string, items: Array<SearchItem>) {
|
||||
return html`
|
||||
<div class="group">
|
||||
<div class="group-name">${name}</div>
|
||||
<div class="group-items">${repeat(items, (item) => item.name, this.#renderItem.bind(this))}</div>
|
||||
</div>
|
||||
`;
|
||||
#renderSearchIcon() {
|
||||
return html` <div id="search-icon">
|
||||
${this._loading ? html`<uui-loader-circle></uui-loader-circle>` : html`<uui-icon name="search"></uui-icon>`}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
#renderItem(item: SearchItem) {
|
||||
#renderSearchTags() {
|
||||
return html`<div id="search-providers">
|
||||
${repeat(
|
||||
this._searchProviders,
|
||||
(searchProvider) => searchProvider,
|
||||
(searchProvider) =>
|
||||
html`<button
|
||||
data-provider-alias=${searchProvider.alias}
|
||||
@click=${() => this.#setCurrentProvider(searchProvider)}
|
||||
@keydown=${() => ''}
|
||||
class="search-provider ${this._currentProvider?.alias === searchProvider.alias ? 'active' : ''}">
|
||||
${searchProvider.name}
|
||||
</button>`,
|
||||
)}
|
||||
</div> `;
|
||||
}
|
||||
|
||||
#renderResults() {
|
||||
return repeat(
|
||||
this._searchResults,
|
||||
(item) => item.unique,
|
||||
(item, index) => this.#renderResultItem(item, index),
|
||||
);
|
||||
}
|
||||
|
||||
#renderResultItem(item: UmbSearchResultItemModel, index: number) {
|
||||
return html`
|
||||
<a href="${item.href}" class="item">
|
||||
<span class="item-icon">
|
||||
${item.icon ? html`<umb-icon name="${item.icon}"></umb-icon>` : this.#renderHashTag()}
|
||||
</span>
|
||||
<span class="item-name">
|
||||
${item.name} ${item.url ? html`<span class="item-url">${item.url}</span>` : nothing}
|
||||
</span>
|
||||
<span class="item-symbol">></span>
|
||||
<a
|
||||
href=${item.href}
|
||||
data-item-index=${index}
|
||||
class="search-item"
|
||||
@click=${this.#closeModal}
|
||||
@keydown=${this.#closeModal}>
|
||||
<umb-extension-slot
|
||||
type="searchResultItem"
|
||||
.props=${{ item }}
|
||||
.filter=${(manifest: ManifestSearchResultItem) => manifest.forEntityTypes.includes(item.entityType)}
|
||||
default-element="umb-search-result-item"></umb-extension-slot>
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderHashTag() {
|
||||
return html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<path fill="none" d="M0 0h24v24H0z" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M7.784 14l.42-4H4V8h4.415l.525-5h2.011l-.525 5h3.989l.525-5h2.011l-.525 5H20v2h-3.784l-.42 4H20v2h-4.415l-.525 5h-2.011l.525-5H9.585l-.525 5H7.049l.525-5H4v-2h3.784zm2.011 0h3.99l.42-4h-3.99l-.42 4z" />
|
||||
</svg>
|
||||
`;
|
||||
#renderNoResults() {
|
||||
return this._loading ? nothing : html`<div id="no-results">${this.localize.term('general_searchNoResult')}</div>`;
|
||||
}
|
||||
|
||||
#mockData: Array<SearchItem> = [
|
||||
{
|
||||
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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { UmbSearchRequestArgs, UmbSearchResultItemModel } from './types.js';
|
||||
import type { UmbRepositoryResponse, UmbPagedModel } from '@umbraco-cms/backoffice/repository';
|
||||
|
||||
export interface UmbSearchRepository<SearchResultItemType extends UmbSearchResultItemModel> {
|
||||
search(args: UmbSearchRequestArgs): Promise<UmbRepositoryResponse<UmbPagedModel<SearchResultItemType>>>;
|
||||
}
|
||||
@@ -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`
|
||||
<span class="item-icon">
|
||||
${this.item.icon ? html`<umb-icon name="${this.item.icon}"></umb-icon>` : this.#renderHashTag()}
|
||||
</span>
|
||||
<span class="item-name"> ${this.item.name} </span>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderHashTag() {
|
||||
return html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<path fill="none" d="M0 0h24v24H0z" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M7.784 14l.42-4H4V8h4.415l.525-5h2.011l-.525 5h3.989l.525-5h2.011l-.525 5H20v2h-3.784l-.42 4H20v2h-4.415l-.525 5h-2.011l.525-5H9.585l-.525 5H7.049l.525-5H4v-2h3.784zm2.011 0h3.99l.42-4h-3.99l-.42 4z" />
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
18
src/Umbraco.Web.UI.Client/src/packages/search/types.ts
Normal file
18
src/Umbraco.Web.UI.Client/src/packages/search/types.ts
Normal file
@@ -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<SearchResultItemType extends UmbSearchResultItemModel> extends UmbApi {
|
||||
search(args: UmbSearchRequestArgs): Promise<UmbRepositoryResponse<UmbPagedModel<SearchResultItemType>>>;
|
||||
}
|
||||
@@ -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') },
|
||||
];
|
||||
|
||||
@@ -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<ManifestTypes> = [
|
||||
...entityActionsManifests,
|
||||
...menuManifests,
|
||||
...modalManifests,
|
||||
...repositoryManifests,
|
||||
...menuManifests,
|
||||
...searchManifests,
|
||||
...treeManifests,
|
||||
...entityActionsManifests,
|
||||
...workspaceManifests,
|
||||
];
|
||||
|
||||
@@ -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<string>) => TemplateService.getItemTemplate({ i
|
||||
|
||||
const mapper = (item: TemplateItemResponseModel): UmbTemplateItemModel => {
|
||||
return {
|
||||
entityType: UMB_TEMPLATE_ENTITY_TYPE,
|
||||
unique: item.id,
|
||||
name: item.name,
|
||||
alias: item.alias,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { UmbTemplateEntityType } from '../../entity.js';
|
||||
|
||||
export interface UmbTemplateItemModel {
|
||||
entityType: UmbTemplateEntityType;
|
||||
unique: string;
|
||||
name: string;
|
||||
alias: string;
|
||||
|
||||
@@ -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<ManifestTypes> = [
|
||||
{
|
||||
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],
|
||||
},
|
||||
];
|
||||
@@ -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<UmbTemplateSearchItemModel>, UmbApi
|
||||
{
|
||||
#dataSource: UmbTemplateSearchServerDataSource;
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
|
||||
this.#dataSource = new UmbTemplateSearchServerDataSource(this);
|
||||
}
|
||||
|
||||
search(args: UmbSearchRequestArgs) {
|
||||
return this.#dataSource.search(args);
|
||||
}
|
||||
}
|
||||
@@ -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<UmbTemplateSearchItemModel> {
|
||||
#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<UmbTemplateSearchItemModel> = 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 };
|
||||
}
|
||||
}
|
||||
@@ -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<UmbTemplateSearchItemModel>
|
||||
{
|
||||
#repository = new UmbTemplateSearchRepository(this);
|
||||
|
||||
async search(args: UmbSearchRequestArgs) {
|
||||
return this.#repository.search(args);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.#repository.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export { UmbTemplateSearchProvider as api };
|
||||
@@ -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"],
|
||||
|
||||
Reference in New Issue
Block a user