Content Picker: Filter out invalid entity types (#18660)

* Reverted `elementName` constant

+ code tidy-up

* Filter out values that don't match the configured entity-type

Fixes #18476

* Notifies the user of invalid data/configuration

with an option to remove invalid data.
This commit is contained in:
Lee Kelleher
2025-03-14 08:55:45 +00:00
committed by GitHub
parent a02db287ce
commit dd8fb66300
4 changed files with 100 additions and 22 deletions

View File

@@ -1278,6 +1278,11 @@ export default {
defineRootNode: 'Pick root node',
defineXPathOrigin: 'Specify via XPath',
defineDynamicRoot: 'Specify a Dynamic Root',
unsupportedHeadline: (type?: string) =>
`<strong>Unsupported ${type ?? 'content'} items</strong><br>The following content is no longer supported in this Editor.`,
unsupportedMessage:
'If you still require this content, please contact your administrator. Otherwise you can remove it.',
unsupportedRemove: 'Remove unsupported items?',
},
dynamicRoot: {
configurationTitle: 'Dynamic Root Query',

View File

@@ -115,7 +115,7 @@ export class UmbPickerInputContext<
color: 'danger',
headline: `Remove ${item.name}?`,
content: 'Are you sure you want to remove this item',
confirmLabel: 'Remove',
confirmLabel: '#actions_remove',
});
this.#removeItem(unique);

View File

@@ -1,14 +1,13 @@
import type { UmbContentPickerSource } from '../../types.js';
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbReferenceByUniqueAndType } from '@umbraco-cms/backoffice/models';
import type { UmbTreeStartNode } from '@umbraco-cms/backoffice/tree';
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
const elementName = 'umb-input-content';
@customElement(elementName)
@customElement('umb-input-content')
export class UmbInputContentElement extends UmbFormControlMixin<string | undefined, typeof UmbLitElement>(
UmbLitElement,
) {
@@ -174,6 +173,6 @@ export { UmbInputContentElement as element };
declare global {
interface HTMLElementTagNameMap {
[elementName]: UmbInputContentElement;
'umb-input-content': UmbInputContentElement;
}
}

View File

@@ -1,7 +1,9 @@
import { UmbContentPickerDynamicRootRepository } from './dynamic-root/repository/index.js';
import type { UmbInputContentElement } from './components/input-content/index.js';
import type { UmbContentPickerSource, UmbContentPickerSourceType } from './types.js';
import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import { css, customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
import { UMB_DOCUMENT_ENTITY_TYPE } from '@umbraco-cms/backoffice/document';
@@ -15,16 +17,13 @@ import type { UmbTreeStartNode } from '@umbraco-cms/backoffice/tree';
// import of local component
import './components/input-content/index.js';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
type UmbContentPickerValueType = UmbInputContentElement['selection'];
const elementName = 'umb-property-editor-ui-content-picker';
/**
* @element umb-property-editor-ui-content-picker
*/
@customElement(elementName)
@customElement('umb-property-editor-ui-content-picker')
export class UmbPropertyEditorUIContentPickerElement
extends UmbFormControlMixin<UmbContentPickerValueType | undefined, typeof UmbLitElement>(UmbLitElement, undefined)
implements UmbPropertyEditorUiElement
@@ -48,31 +47,34 @@ export class UmbPropertyEditorUIContentPickerElement
readonly = false;
@state()
_type: UmbContentPickerSource['type'] = 'content';
private _type: UmbContentPickerSource['type'] = 'content';
@state()
_min = 0;
private _min = 0;
@state()
_minMessage = '';
private _minMessage = '';
@state()
_max = Infinity;
private _max = Infinity;
@state()
_maxMessage = '';
private _maxMessage = '';
@state()
_allowedContentTypeUniques?: string | null;
private _allowedContentTypeUniques?: string | null;
@state()
_showOpenButton?: boolean;
private _showOpenButton?: boolean;
@state()
_rootUnique?: string | null;
private _rootUnique?: string | null;
@state()
_rootEntityType?: string;
private _rootEntityType?: string;
@state()
private _invalidData?: UmbContentPickerValueType;
#dynamicRoot?: UmbContentPickerSource['dynamicRoot'];
#dynamicRootRepository = new UmbContentPickerDynamicRootRepository(this);
@@ -92,6 +94,12 @@ export class UmbPropertyEditorUIContentPickerElement
this._rootUnique = startNode.id;
this._rootEntityType = this.#entityTypeDictionary[startNode.type];
this.#dynamicRoot = startNode.dynamicRoot;
// NOTE: Filter out any items that do not match the entity type. [LK]
this._invalidData = this.#value?.filter((x) => x.type !== this._rootEntityType);
if (this._invalidData?.length) {
this.readonly = true;
}
}
this._min = this.#parseInt(config.getValueByAlias('minNumber'), 0);
@@ -152,6 +160,19 @@ export class UmbPropertyEditorUIContentPickerElement
this.dispatchEvent(new UmbChangeEvent());
}
async #onRemoveInvalidData() {
await umbConfirmModal(this, {
color: 'danger',
headline: '#contentPicker_unsupportedRemove',
content: '#defaultdialogs_confirmSure',
confirmLabel: '#actions_remove',
});
this.value = this.value?.filter((x) => x.type === this._rootEntityType);
this._invalidData = undefined;
this.readonly = false;
}
override render() {
const startNode: UmbTreeStartNode | undefined =
this._rootUnique && this._rootEntityType
@@ -170,15 +191,68 @@ export class UmbPropertyEditorUIContentPickerElement
.allowedContentTypeIds=${this._allowedContentTypeUniques ?? ''}
?showOpenButton=${this._showOpenButton}
?readonly=${this.readonly}
@change=${this.#onChange}></umb-input-content>
@change=${this.#onChange}>
</umb-input-content>
${this.#renderInvalidData()}
`;
}
#renderInvalidData() {
if (!this._invalidData?.length) return nothing;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
const groupby = Object.groupBy(this._invalidData, (x) => x.type);
const grouped = Object.keys(groupby)
.sort((a, b) => a.localeCompare(b))
.map((key) => ({ key, items: groupby[key] }));
const toPickerType = (type: string): UmbContentPickerSourceType => {
return type === UMB_DOCUMENT_ENTITY_TYPE ? 'content' : (type as UmbContentPickerSourceType);
};
return html`
<div id="messages">
${repeat(
grouped,
(group) => group.key,
(group) => html`
<p>
<umb-localize key="contentPicker_unsupportedHeadline" .args=${[group.key]}>
<strong>Unsupported ${group.key} items</strong><br />
The following content is no longer supported in this Editor.
</umb-localize>
</p>
<umb-input-content readonly .selection=${group.items} .type=${toPickerType(group.key)}></umb-input-content>
<p>
<umb-localize key="contentPicker_unsupportedMessage">
If you still require this content, please contact your administrator. Otherwise you can remove it.
</umb-localize>
</p>
<uui-button
color="danger"
look="outline"
label=${this.localize.term('contentPicker_unsupportedRemove')}
@click=${this.#onRemoveInvalidData}></uui-button>
`,
)}
</div>
`;
}
static override readonly styles = [
css`
#messages {
color: var(--uui-color-danger-standalone);
}
`,
];
}
export { UmbPropertyEditorUIContentPickerElement as element };
declare global {
interface HTMLElementTagNameMap {
[elementName]: UmbPropertyEditorUIContentPickerElement;
'umb-property-editor-ui-content-picker': UmbPropertyEditorUIContentPickerElement;
}
}