From 11c19847cf3afba9351bb9696d1dec28a27caa8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 2 Apr 2025 06:25:34 +0200 Subject: [PATCH] Feature: highlight invariant doc with variant blocks is unsupported (#18806) * mark variant blocks in invariant docs as invalid * implement RTE Blocks --- .../src/assets/lang/en.ts | 2 + .../property-editor-ui-block-grid.element.ts | 47 +++++++++++++++++ .../property-editor-ui-block-list.element.ts | 51 ++++++++++++++++++- .../context/validation-messages.manager.ts | 10 +++- ...r-validation-to-form-control.controller.ts | 2 +- .../rte/components/rte-base.element.ts | 37 ++++++++++++++ 6 files changed, 146 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts index e610a10f35..9bd45b6275 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -2657,6 +2657,8 @@ export default { unsupportedBlockName: 'Unsupported', unsupportedBlockDescription: 'This content is no longer supported in this Editor. If you are missing this content, please contact your administrator. Otherwise delete it.', + blockVariantConfigurationNotSupported: + 'One or more Block Types of this Block Editor is using a Element-Type that is configured to Vary By Culture or Vary By Segment. This is not supported on a Content item that does not vary by Culture or Segment.', }, contentTemplatesDashboard: { whatHeadline: 'What are Document Blueprints?', diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts index b156099843..3434ca2286 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts @@ -9,6 +9,7 @@ import { css, type PropertyValueMap, ref, + nothing, } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { @@ -24,6 +25,7 @@ import { debounceTime } from '@umbraco-cms/backoffice/external/rxjs'; // TODO: consider moving the components to the property editor folder as they are only used here import '../../local-components.js'; +import { UMB_CONTENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/content'; /** * @element umb-property-editor-ui-block-grid @@ -85,9 +87,51 @@ export class UmbPropertyEditorUIBlockGridElement return super.value; } + @state() + _notSupportedVariantSetting?: boolean; + constructor() { super(); + this.consumeContext(UMB_CONTENT_WORKSPACE_CONTEXT, (context) => { + this.observe( + observeMultiple([ + this.#managerContext.blockTypes, + context.structure.variesByCulture, + context.structure.variesBySegment, + ]), + async ([blockTypes, variesByCulture, variesBySegment]) => { + if (blockTypes.length > 0 && (variesByCulture === false || variesBySegment === false)) { + // check if any of the Blocks varyByCulture or Segment and then display a warning. + const promises = await Promise.all( + blockTypes.map(async (blockType) => { + const elementType = blockType.contentElementTypeKey; + await this.#managerContext.contentTypesLoaded; + const structure = await this.#managerContext.getStructure(elementType); + if (variesByCulture === false && structure?.getVariesByCulture() === true) { + // If block varies by culture but document does not. + return true; + } else if (variesBySegment === false && structure?.getVariesBySegment() === true) { + // If block varies by segment but document does not. + return true; + } + return false; + }), + ); + this._notSupportedVariantSetting = promises.filter((x) => x === true).length > 0; + + if (this._notSupportedVariantSetting) { + this.#validationContext.messages.addMessage( + 'config', + '$', + '#blockEditor_blockVariantConfigurationNotSupported', + ); + } + } + }, + ); + }).passContextAliasMatches(); + this.consumeContext(UMB_PROPERTY_CONTEXT, (context) => { this.observe( context.dataPath, @@ -195,6 +239,9 @@ export class UmbPropertyEditorUIBlockGridElement } override render() { + if (this._notSupportedVariantSetting) { + return nothing; + } return html` = { getUniqueOfElement: (element) => { @@ -110,6 +111,8 @@ export class UmbPropertyEditorUIBlockListElement this.#managerContext.contentTypesLoaded.then(() => { const firstContentTypeName = this.#managerContext.getContentTypeNameOf(blocks[0].contentElementTypeKey); this._createButtonLabel = this.localize.term('blockEditor_addThis', this.localize.string(firstContentTypeName)); + + // If we are in a invariant context: }); } } @@ -157,9 +160,52 @@ export class UmbPropertyEditorUIBlockListElement readonly #managerContext = new UmbBlockListManagerContext(this); readonly #entriesContext = new UmbBlockListEntriesContext(this); + @state() + _notSupportedVariantSetting?: boolean; + constructor() { super(); + this.consumeContext(UMB_CONTENT_WORKSPACE_CONTEXT, (context) => { + this.observe( + observeMultiple([ + this.#managerContext.blockTypes, + context.structure.variesByCulture, + context.structure.variesBySegment, + ]), + async ([blockTypes, variesByCulture, variesBySegment]) => { + if (blockTypes.length > 0 && (variesByCulture === false || variesBySegment === false)) { + // check if any of the Blocks varyByCulture or Segment and then display a warning. + const promises = await Promise.all( + blockTypes.map(async (blockType) => { + const elementType = blockType.contentElementTypeKey; + await this.#managerContext.contentTypesLoaded; + const structure = await this.#managerContext.getStructure(elementType); + if (variesByCulture === false && structure?.getVariesByCulture() === true) { + // If block varies by culture but document does not. + return true; + } else if (variesBySegment === false && structure?.getVariesBySegment() === true) { + // If block varies by segment but document does not. + return true; + } + return false; + }), + ); + this._notSupportedVariantSetting = promises.filter((x) => x === true).length > 0; + + if (this._notSupportedVariantSetting) { + this.#validationContext.messages.addMessage( + 'config', + '$', + '#blockEditor_blockVariantConfigurationNotSupported', + 'blockConfigurationNotSupported', + ); + } + } + }, + ); + }).passContextAliasMatches(); + this.consumeContext(UMB_PROPERTY_CONTEXT, (context) => { this.#gotPropertyContext(context); }); @@ -193,7 +239,7 @@ export class UmbPropertyEditorUIBlockListElement null, ); - this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (context) => { + this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, async (context) => { this.#managerContext.setVariantId(context.getVariantId()); }); @@ -334,6 +380,9 @@ export class UmbPropertyEditorUIBlockListElement } override render() { + if (this._notSupportedVariantSetting) { + return nothing; + } return html` ${repeat( this._layouts, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/context/validation-messages.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/context/validation-messages.manager.ts index b9e152688a..ad57584422 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/context/validation-messages.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/context/validation-messages.manager.ts @@ -3,7 +3,7 @@ import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import { UmbId } from '@umbraco-cms/backoffice/id'; import { UmbArrayState, createObservablePart } from '@umbraco-cms/backoffice/observable-api'; -export type UmbValidationMessageType = 'client' | 'server'; +export type UmbValidationMessageType = 'client' | 'server' | 'config' | string; export interface UmbValidationMessage { type: UmbValidationMessageType; key: string; @@ -95,6 +95,14 @@ export class UmbValidationMessagesManager { ); } + messagesOfNotTypeAndPath(type: UmbValidationMessageType, path: string): Observable> { + //path = path.toLowerCase(); + // Find messages that matches the given type and path. + return createObservablePart(this.filteredMessages, (msgs) => + msgs.filter((x) => x.type !== type && x.path === path), + ); + } + hasMessagesOfPathAndDescendant(path: string): Observable { //path = path.toLowerCase(); return createObservablePart(this.filteredMessages, (msgs) => diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts index 6dbb7c465c..7e91dfdd14 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts @@ -43,7 +43,7 @@ export class UmbBindServerValidationToFormControl extends UmbControllerBase { this.#context = context; this.observe( - context.messages?.messagesOfTypeAndPath('server', dataPath), + context.messages?.messagesOfNotTypeAndPath('client', dataPath), (messages) => { this.#messages = messages ?? []; this.#isValid = this.#messages.length === 0; diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/components/rte-base.element.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/components/rte-base.element.ts index 0749d8fbd2..fcf6be124e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/components/rte-base.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/components/rte-base.element.ts @@ -16,6 +16,7 @@ import { UmbValidationContext, } from '@umbraco-cms/backoffice/validation'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UMB_CONTENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/content'; export abstract class UmbPropertyEditorUiRteElementBase extends UmbFormControlMixin(UmbLitElement) @@ -114,6 +115,42 @@ export abstract class UmbPropertyEditorUiRteElementBase constructor() { super(); + this.consumeContext(UMB_CONTENT_WORKSPACE_CONTEXT, (context) => { + this.observe( + observeMultiple([ + this.#managerContext.blockTypes, + context.structure.variesByCulture, + context.structure.variesBySegment, + ]), + async ([blockTypes, variesByCulture, variesBySegment]) => { + if (blockTypes.length > 0 && (variesByCulture === false || variesBySegment === false)) { + // check if any of the Blocks varyByCulture or Segment and then display a warning. + const promises = await Promise.all( + blockTypes.map(async (blockType) => { + const elementType = blockType.contentElementTypeKey; + await this.#managerContext.contentTypesLoaded; + const structure = await this.#managerContext.getStructure(elementType); + if (variesByCulture === false && structure?.getVariesByCulture() === true) { + // If block varies by culture but document does not. + return true; + } else if (variesBySegment === false && structure?.getVariesBySegment() === true) { + // If block varies by segment but document does not. + return true; + } + return false; + }), + ); + const notSupportedVariantSetting = promises.filter((x) => x === true).length > 0; + + if (notSupportedVariantSetting) { + this.setCustomValidity('#blockEditor_blockVariantConfigurationNotSupported'); + this.checkValidity(); + } + } + }, + ); + }).passContextAliasMatches(); + this.consumeContext(UMB_PROPERTY_CONTEXT, (context) => { this.#gotPropertyContext(context); });