Unpublish Document: Align UX of referenced items with trash and delete (#18860)

* Added member reference type model.

* Updated client-side types and sdk.

* Render member relations on the member info view.

* Add relation type for related member with migration.

* Extend tests for track relations to include member relations.

* Extend tests for relation repository.

* Extend tests for relation service.

* Addressed comments from Copilot review.

* Add relation notification to member deletion.

* Removed unused import.

* Updates from code review.

* make ref element globally available

* align naming

* use new reference list

* add interfaces for config

* export const

* Fixed failing integration tests.

* apply interface

* deprecate interface with wrong name

* fix import

* disable unpublish button when item or descendants are referenced

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
Mads Rasmussen
2025-03-31 09:52:46 +02:00
committed by GitHub
parent 4d8406bb64
commit 0e3c8dab8f
12 changed files with 96 additions and 73 deletions

View File

@@ -1,5 +1,5 @@
import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../../../types.js';
import { UmbDocumentReferenceRepository } from '../../../reference/index.js';
import { UMB_DOCUMENT_ITEM_REPOSITORY_ALIAS, UMB_DOCUMENT_REFERENCE_REPOSITORY_ALIAS } from '../../../constants.js';
import { UMB_DOCUMENT_CONFIGURATION_CONTEXT } from '../../../global-contexts/index.js';
import type {
UmbDocumentUnpublishModalData,
@@ -9,6 +9,11 @@ import { css, customElement, html, nothing, state } from '@umbraco-cms/backoffic
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
import type {
UmbConfirmActionModalEntityReferencesConfig,
UmbConfirmActionModalEntityReferencesElement,
} from '@umbraco-cms/backoffice/relations';
import type { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import '../../../modals/shared/document-variant-language-picker.element.js';
@@ -30,7 +35,6 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
UmbDocumentUnpublishModalValue
> {
protected readonly _selectionManager = new UmbSelectionManager<string>(this);
#referencesRepository = new UmbDocumentReferenceRepository(this);
@state()
_options: Array<UmbDocumentVariantOptionModel> = [];
@@ -39,10 +43,7 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
_selection: Array<string> = [];
@state()
_hasReferences = false;
@state()
_hasUnpublishPermission = true;
_canUnpublish = true;
@state()
_hasInvalidSelection = true;
@@ -50,6 +51,9 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
@state()
_isInvariant = false;
@state()
_referencesConfig?: UmbConfirmActionModalEntityReferencesConfig;
#pickableFilter = (option: UmbDocumentVariantOptionModel) => {
if (!option.variant) {
return false;
@@ -58,7 +62,7 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
};
override firstUpdated() {
this.#getReferences();
this.#configureReferences();
// If invariant, don't display the variant selection component.
if (this.data?.options.length === 1 && this.data.options[0].unique === 'invariant') {
@@ -70,6 +74,16 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
this.#configureSelectionManager();
}
#configureReferences() {
if (!this.data?.documentUnique) return;
this._referencesConfig = {
itemRepositoryAlias: UMB_DOCUMENT_ITEM_REPOSITORY_ALIAS,
referenceRepositoryAlias: UMB_DOCUMENT_REFERENCE_REPOSITORY_ALIAS,
unique: this.data.documentUnique,
};
}
async #configureSelectionManager() {
this._selectionManager.setMultiple(true);
this._selectionManager.setSelectable(true);
@@ -103,30 +117,8 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
);
}
async #getReferences() {
if (!this.data?.documentUnique) return;
const { data, error } = await this.#referencesRepository.requestReferencedBy(this.data?.documentUnique, 0, 1);
if (error) {
console.error(error);
return;
}
if (!data) return;
this._hasReferences = data.total > 0;
// If there are references, we also want to check if we are allowed to unpublish the document:
if (this._hasReferences) {
const documentConfigurationContext = await this.getContext(UMB_DOCUMENT_CONFIGURATION_CONTEXT);
this._hasUnpublishPermission =
(await documentConfigurationContext.getDocumentConfiguration())?.disableUnpublishWhenReferenced === false;
}
}
#submit() {
if (this._hasUnpublishPermission) {
if (this._canUnpublish) {
const selection = this._isInvariant ? ['invariant'] : this._selection;
this.value = { selection };
this.modalContext?.submit();
@@ -139,6 +131,19 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
this.modalContext?.reject();
}
async #onReferencesChange(event: UmbChangeEvent) {
event.stopPropagation();
const target = event.target as UmbConfirmActionModalEntityReferencesElement;
const getReferencedByTotal = target.getTotalReferencedBy();
const descendantsWithReferencesTotal = target.getTotalDescendantsWithReferences();
const total = getReferencedByTotal + descendantsWithReferencesTotal;
if (total > 0) {
const context = await this.getContext(UMB_DOCUMENT_CONFIGURATION_CONTEXT);
this._canUnpublish = (await context.getDocumentConfiguration())?.disableUnpublishWhenReferenced === false;
}
}
private _requiredFilter = (variantOption: UmbDocumentVariantOptionModel): boolean => {
return variantOption.language.isMandatory && !this._selection.includes(variantOption.unique);
};
@@ -166,20 +171,10 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
</umb-localize>
</p>
${this.data?.documentUnique
? html`
<umb-document-reference-table
id="references"
unique=${this.data?.documentUnique}></umb-document-reference-table>
`
: nothing}
${this._hasReferences
? html`<uui-box id="references-warning">
<umb-localize key="references_unpublishWarning">
This item or its descendants is being referenced. Unpublishing can lead to broken links on your website.
Please take the appropriate actions.
</umb-localize>
</uui-box>`
${this._referencesConfig
? html`<umb-confirm-action-modal-entity-references
.config=${this._referencesConfig}
@change=${this.#onReferencesChange}></umb-confirm-action-modal-entity-references>`
: nothing}
<div slot="actions">
@@ -187,7 +182,7 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
<uui-button
label="${this.localize.term('actions_unpublish')}"
?disabled=${this._hasInvalidSelection ||
!this._hasUnpublishPermission ||
!this._canUnpublish ||
(!this._isInvariant && this._selection.length === 0)}
look="primary"
color="warning"

View File

@@ -9,7 +9,12 @@ import {
isMemberReference,
isDefaultReference,
} from '@umbraco-cms/backoffice/relations';
import { UmbDeprecation } from '@umbraco-cms/backoffice/utils';
/**
* @deprecated Deprecated from 15.4. The element will be removed in v17.0.0. For modals use the <umb-confirm-action-modal-entity-references> or <umb-confirm-bulk-action-modal-entity-references> element instead
* @class UmbDocumentReferenceTableElement
*/
@customElement('umb-document-reference-table')
export class UmbDocumentReferenceTableElement extends UmbLitElement {
#documentReferenceRepository = new UmbDocumentReferenceRepository(this);
@@ -32,6 +37,13 @@ export class UmbDocumentReferenceTableElement extends UmbLitElement {
_errorMessage = '';
override firstUpdated() {
new UmbDeprecation({
removeInVersion: '17',
deprecated: '<umb-document-reference-table> element',
solution:
'For modals use the <umb-confirm-action-modal-entity-references> or <umb-confirm-bulk-action-modal-entity-references> element instead',
}).warn();
this.#getReferences();
}

View File

@@ -1,3 +1,4 @@
import type { UmbConfirmBulkActionModalEntityReferencesConfig } from '../../../global-components/types.js';
import type {
UmbBulkDeleteWithRelationConfirmModalData,
UmbBulkDeleteWithRelationConfirmModalValue,
@@ -15,16 +16,13 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import { umbFocus } from '@umbraco-cms/backoffice/lit-element';
// import of local component
import '../../local-components/confirm-bulk-action-entity-references.element.js';
@customElement('umb-bulk-delete-with-relation-confirm-modal')
export class UmbBulkDeleteWithRelationConfirmModalElement extends UmbModalBaseElement<
UmbBulkDeleteWithRelationConfirmModalData,
UmbBulkDeleteWithRelationConfirmModalValue
> {
@state()
_referencesConfig?: any;
_referencesConfig?: UmbConfirmBulkActionModalEntityReferencesConfig;
protected override firstUpdated(_changedProperties: PropertyValues): void {
super.firstUpdated(_changedProperties);

View File

@@ -1,3 +1,4 @@
import type { UmbConfirmBulkActionModalEntityReferencesConfig } from '../../../global-components/types.js';
import type {
UmbBulkTrashWithRelationConfirmModalData,
UmbBulkTrashWithRelationConfirmModalValue,
@@ -15,16 +16,13 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import { umbFocus } from '@umbraco-cms/backoffice/lit-element';
// import of local component
import '../../local-components/confirm-bulk-action-entity-references.element.js';
@customElement('umb-bulk-trash-with-relation-confirm-modal')
export class UmbBulkTrashWithRelationConfirmModalElement extends UmbModalBaseElement<
UmbBulkTrashWithRelationConfirmModalData,
UmbBulkTrashWithRelationConfirmModalValue
> {
@state()
_referencesConfig?: any;
_referencesConfig?: UmbConfirmBulkActionModalEntityReferencesConfig;
protected override firstUpdated(_changedProperties: PropertyValues): void {
super.firstUpdated(_changedProperties);

View File

@@ -1,3 +1,4 @@
import type { UmbConfirmActionModalEntityReferencesConfig } from '../../../global-components/types.js';
import type {
UmbDeleteWithRelationConfirmModalData,
UmbDeleteWithRelationConfirmModalValue,
@@ -17,8 +18,6 @@ import { umbFocus } from '@umbraco-cms/backoffice/lit-element';
import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import '../../local-components/confirm-action-entity-references.element.js';
@customElement('umb-delete-with-relation-confirm-modal')
export class UmbDeleteWithRelationConfirmModalElement extends UmbModalBaseElement<
UmbDeleteWithRelationConfirmModalData,
@@ -28,7 +27,7 @@ export class UmbDeleteWithRelationConfirmModalElement extends UmbModalBaseElemen
_name?: string;
@state()
_referencesConfig?: any;
_referencesConfig?: UmbConfirmActionModalEntityReferencesConfig;
#itemRepository?: UmbItemRepository<any>;

View File

@@ -1,3 +1,4 @@
import type { UmbConfirmActionModalEntityReferencesConfig } from '../../../global-components/types.js';
import type {
UmbTrashWithRelationConfirmModalData,
UmbTrashWithRelationConfirmModalValue,
@@ -17,9 +18,6 @@ import { umbFocus } from '@umbraco-cms/backoffice/lit-element';
import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
// import of local component
import '../../local-components/confirm-action-entity-references.element.js';
@customElement('umb-trash-with-relation-confirm-modal')
export class UmbTrashWithRelationConfirmModalElement extends UmbModalBaseElement<
UmbTrashWithRelationConfirmModalData,
@@ -29,7 +27,7 @@ export class UmbTrashWithRelationConfirmModalElement extends UmbModalBaseElement
_name?: string;
@state()
_referencesConfig?: any;
_referencesConfig?: UmbConfirmActionModalEntityReferencesConfig;
#itemRepository?: UmbItemRepository<any>;

View File

@@ -1,4 +1,4 @@
import type { UmbEntityReferenceRepository, UmbReferenceItemModel } from '../../reference/types.js';
import type { UmbEntityReferenceRepository, UmbReferenceItemModel } from '../reference/types.js';
import {
html,
customElement,
@@ -12,16 +12,18 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
export interface UmbConfirmActionModalEntityReferencesConfig {
itemRepositoryAlias: string;
referenceRepositoryAlias: string;
unique: string;
}
@customElement('umb-confirm-action-modal-entity-references')
export class UmbConfirmActionModalEntityReferencesElement extends UmbLitElement {
@property({ type: Object, attribute: false })
config?: {
itemRepositoryAlias: string;
referenceRepositoryAlias: string;
entityType: string;
unique: string;
};
config?: UmbConfirmActionModalEntityReferencesConfig;
@state()
_referencedByItems: Array<UmbReferenceItemModel> = [];
@@ -40,6 +42,14 @@ export class UmbConfirmActionModalEntityReferencesElement extends UmbLitElement
#limitItems = 3;
getTotalReferencedBy() {
return this._totalReferencedByItems;
}
getTotalDescendantsWithReferences() {
return this._totalDescendantsWithReferences;
}
protected override firstUpdated(_changedProperties: PropertyValues): void {
super.firstUpdated(_changedProperties);
this.#initData();
@@ -88,6 +98,7 @@ export class UmbConfirmActionModalEntityReferencesElement extends UmbLitElement
if (data) {
this._referencedByItems = [...data.items];
this._totalReferencedByItems = data.total;
this.dispatchEvent(new UmbChangeEvent());
}
}
@@ -118,6 +129,7 @@ export class UmbConfirmActionModalEntityReferencesElement extends UmbLitElement
const uniques = data.items.map((item) => item.unique).filter((unique) => unique) as Array<string>;
const { data: items } = await this.#itemRepository.requestItems(uniques);
this._descendantsWithReferences = items ?? [];
this.dispatchEvent(new UmbChangeEvent());
}
}
@@ -163,8 +175,6 @@ export class UmbConfirmActionModalEntityReferencesElement extends UmbLitElement
];
}
export { UmbConfirmActionModalEntityReferencesElement as element };
declare global {
interface HTMLElementTagNameMap {
'umb-confirm-action-modal-entity-references': UmbConfirmActionModalEntityReferencesElement;

View File

@@ -1,4 +1,4 @@
import type { UmbEntityReferenceRepository } from '../../reference/types.js';
import type { UmbEntityReferenceRepository } from '../reference/types.js';
import {
html,
customElement,
@@ -13,6 +13,12 @@ import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
export interface UmbConfirmBulkActionModalEntityReferencesConfig {
uniques: Array<string>;
itemRepositoryAlias: string;
referenceRepositoryAlias: string;
}
@customElement('umb-confirm-bulk-action-modal-entity-references')
export class UmbConfirmBulkActionModalEntityReferencesElement extends UmbLitElement {
@property({ type: Object, attribute: false })
@@ -125,8 +131,6 @@ export class UmbConfirmBulkActionModalEntityReferencesElement extends UmbLitElem
];
}
export { UmbConfirmBulkActionModalEntityReferencesElement as element };
declare global {
interface HTMLElementTagNameMap {
'umb-confirm-bulk-action-modal-entity-references': UmbConfirmBulkActionModalEntityReferencesElement;

View File

@@ -0,0 +1,5 @@
import './confirm-action-modal-entity-references.element.js';
import './confirm-bulk-action-modal-entity-references.element.js';
export * from './confirm-action-modal-entity-references.element.js';
export * from './confirm-bulk-action-modal-entity-references.element.js';

View File

@@ -0,0 +1,2 @@
export type * from './confirm-action-modal-entity-references.element.js';
export type * from './confirm-bulk-action-modal-entity-references.element.js';

View File

@@ -1,6 +1,7 @@
export * from './constants.js';
export * from './collection/index.js';
export * from './constants.js';
export * from './entity.js';
export * from './global-components/index.js';
export * from './utils.js';
export type * from './types.js';

View File

@@ -1,4 +1,5 @@
import type { UmbRelationEntityType } from './entity.js';
export type * from './global-components/types.js';
export type * from './reference/types.js';
export interface UmbRelationDetailModel {