From ad9b94310baec3ee931a95a15485b8944597845f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 17:24:42 +0200 Subject: [PATCH 01/23] initial notes --- .../content-detail-workspace-base.ts | 42 ++++++++++++------- src/Umbraco.Web.UI/Segments.cs | 41 ++++++++++++++++++ src/Umbraco.Web.UI/Umbraco.Web.UI.sln | 25 +++++++++++ 3 files changed, 92 insertions(+), 16 deletions(-) create mode 100644 src/Umbraco.Web.UI/Segments.cs create mode 100644 src/Umbraco.Web.UI/Umbraco.Web.UI.sln diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts index f056ba4486c8..8411cbb829c3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts @@ -671,26 +671,36 @@ export abstract class UmbContentDetailWorkspaceContextBase< // @ts-ignore // TODO: fix type error this._data.updateCurrent({ values }); + } - /** - * Handling of Not-Culture but Segment variant properties: [NL] - * We need to ensure variant-entries across all culture variants for the given segment variant, when er property is configured to vary by segment but not culture. - * This is the only different case, in all other cases its fine to just target the given variant. - */ - if (this.getVariesByCulture() && property.variesByCulture === false && property.variesBySegment === true) { - // get all culture options: - const cultureOptions = await firstValueFrom(this.variantOptions); - for (const cultureOption of cultureOptions) { - if (cultureOption.segment === variantId.segment) { - this._data.ensureVariantData(UmbVariantId.Create(cultureOption)); - } + await this.#ensureVariantsExistsForPropertyValue(property, variantId, value); + + this.finishPropertyValueChange(); + } + + async #ensureVariantsExistsForPropertyValue(property: UmbPropertyTypeModel, variantId: UmbVariantId, value: unknown) { + // TODO: Implement queueing of these operations to ensure this does not execute too often. [NL] + + // Find inner values to determine if any of this holds variants that needs to be created. + + /** + * Handling of Not-Culture but Segment variant properties: [NL] + * We need to ensure variant-entries across all culture variants for the given segment variant, when er property is configured to vary by segment but not culture. + * This is the only different case, in all other cases its fine to just target the given variant. + */ + if (this.getVariesByCulture() && property.variesByCulture === false && property.variesBySegment === true) { + // get all culture options: + const cultureOptions = await firstValueFrom(this.variantOptions); + for (const cultureOption of cultureOptions) { + if (cultureOption.segment === variantId.segment) { + this._data.ensureVariantData(UmbVariantId.Create(cultureOption)); } - } else { - // otherwise we know the property variant-id will be matching with a variant: - this._data.ensureVariantData(variantId); } + } else { + // otherwise we know the property variant-id will be matching with a variant: + this._data.ensureVariantData(variantId); } - this.finishPropertyValueChange(); + // TODO: Make one method to ensure multiple variants, in order to gather the update into one operation. [NL] } public initiatePropertyValueChange() { diff --git a/src/Umbraco.Web.UI/Segments.cs b/src/Umbraco.Web.UI/Segments.cs new file mode 100644 index 000000000000..89fa53317d44 --- /dev/null +++ b/src/Umbraco.Web.UI/Segments.cs @@ -0,0 +1,41 @@ +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Web.UI; + +public class SegmentService : ISegmentService +{ + private static readonly Segment[] segments = new Segment[] { + new Segment + { + Alias = "segment-one", + Name = "First Segment" + }, + new Segment + { + Alias = "segment-two", + Name = "Second Segment" + }, + new Segment + { + Alias = "segment-three", + Name = "Thrird Segment" + }, + }; + + public async Task?, SegmentOperationStatus>> GetPagedSegmentsAsync(int skip = 0, int take = 100) + { + return await Task.FromResult(Attempt.SucceedWithStatus?, SegmentOperationStatus>( + SegmentOperationStatus.Success, + new PagedModel { Total = segments.Length, Items = segments.Skip(0).Take(take) })); + } +} + +public class SegmentServiceOverrideComposer : IComposer +{ + + public void Compose(IUmbracoBuilder builder) => builder.Services.AddUnique(); +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.sln b/src/Umbraco.Web.UI/Umbraco.Web.UI.sln new file mode 100644 index 000000000000..cf34f4667e05 --- /dev/null +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Web.UI", "Umbraco.Web.UI.csproj", "{A175D8F0-2D9E-47D9-82E5-E51CEF851A15}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A175D8F0-2D9E-47D9-82E5-E51CEF851A15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A175D8F0-2D9E-47D9-82E5-E51CEF851A15}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A175D8F0-2D9E-47D9-82E5-E51CEF851A15}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A175D8F0-2D9E-47D9-82E5-E51CEF851A15}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {201307DE-F6DE-4FD8-B2B6-B3642E21D183} + EndGlobalSection +EndGlobal From d503195bbd90c605c03db67a3bb112529f1069d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 18:05:07 +0200 Subject: [PATCH 02/23] flat mapper impl --- .../content-detail-workspace-base.ts | 3 + ...perty-value-flat-mapper.controller.test.ts | 0 .../property-value-flat-mapper.controller.ts | 69 +++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts index 8411cbb829c3..b852de706a1c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts @@ -682,6 +682,9 @@ export abstract class UmbContentDetailWorkspaceContextBase< // TODO: Implement queueing of these operations to ensure this does not execute too often. [NL] // Find inner values to determine if any of this holds variants that needs to be created. + if (variantId.isInvariant() && typeof value === 'object' && value !== null) { + // TODO: Use the property value flat mapper to run through values to capture variant ids. [NL] + } /** * Handling of Not-Culture but Segment variant properties: [NL] diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts new file mode 100644 index 000000000000..2edeff684728 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts @@ -0,0 +1,69 @@ +import type { UmbPropertyValueDataPotentiallyWithEditorAlias } from '../index.js'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import { createExtensionApi } from '@umbraco-cms/backoffice/extension-api'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; + +export class UmbPropertyValueFlatMapperController extends UmbControllerBase { + /** + * Maps the property values of the given property data. + * @param {UmbPropertyValueDataPotentiallyWithEditorAlias} property - The property data. + * @returns {Promise} - A promise that resolves to the mapped data. + */ + async flatMap( + property: UmbPropertyValueDataPotentiallyWithEditorAlias, + mapper: (property: UmbPropertyValueDataPotentiallyWithEditorAlias) => ReturnType | Promise, + ): Promise> { + const result = await this.#mapValues(property, mapper); + + this.destroy(); + + return result ?? []; + } + + async #mapValues( + incomingProperty: UmbPropertyValueDataPotentiallyWithEditorAlias, + mapper: (property: UmbPropertyValueDataPotentiallyWithEditorAlias) => ReturnType | Promise, + ): Promise | undefined> { + const editorAlias = (incomingProperty as any).editorAlias as string | undefined; + if (!editorAlias) { + return undefined; + } + + // Find the resolver for this editor alias: + const manifest = umbExtensionsRegistry.getByTypeAndFilter( + 'propertyValueResolver', + (x) => x.forEditorAlias === editorAlias, + )[0]; + + if (!manifest) { + return undefined; + } + + const api = await createExtensionApi(this, manifest); + if (!api) { + return undefined; + } + + (api as any).manifest = manifest; + + const propertyValue: ReturnType = await mapper(incomingProperty); + + if (api.processValues) { + return await api.processValues(incomingProperty, async (properties) => { + // Transform the values: + const mappedValues = await Promise.all( + properties.flatMap(async (property) => { + return (await this.#mapValues(property, mapper)) ?? property; + }), + ); + + mappedValues.push(propertyValue); + + return mappedValues; + }); + } + + // the api did not provide a value processor, so we will return the incoming property: + return [propertyValue]; + } +} From 7152f0ee127ea7e9f9c8da70bd6655606b3df4be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 18:23:08 +0200 Subject: [PATCH 03/23] first tests passed --- ...perty-value-flat-mapper.controller.test.ts | 224 ++++++++++++++++++ .../property-value-flat-mapper.controller.ts | 27 ++- 2 files changed, 240 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts index e69de29bb2d1..0bdfc0e8aeb9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts @@ -0,0 +1,224 @@ +import { expect } from '@open-wc/testing'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import type { + ManifestPropertyValueResolver, + ManifestPropertyValueCloner, + UmbPropertyValueData, + UmbPropertyValueResolver, + UmbPropertyValueCloner, +} from '../types.js'; +import type { UmbVariantDataModel } from '@umbraco-cms/backoffice/variant'; +import { customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; +import { UmbPropertyValueFlatMapperController } from './property-value-flat-mapper.controller'; + +@customElement('umb-test-controller-host') +export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} + +type TestPropertyValueWithId = { + id: string; +}; + +type TestPropertyValueNestedType = TestPropertyValueWithId & { + nestedValue?: UmbPropertyValueData; +}; + +export class TestPropertyValueResolver + implements + UmbPropertyValueResolver< + UmbPropertyValueData, + UmbPropertyValueData, + UmbVariantDataModel + > +{ + async processValues( + property: UmbPropertyValueData, + valuesCallback: (values: Array) => Promise | undefined>, + ): Promise> { + if (property.value) { + const nestedValue = property.value.nestedValue; + const processedValues = nestedValue ? await valuesCallback([nestedValue]) : undefined; + return { + ...property, + value: { + ...property.value, + nestedValue: processedValues ? processedValues[0] : undefined, + }, + } as UmbPropertyValueData; + } + return property; + } + + async processVariants( + property: UmbPropertyValueData, + variantsCallback: (values: Array) => Promise | undefined>, + ) { + return property; + } + + destroy(): void {} +} + +describe('UmbPropertyValueFlatMapperController', () => { + describe('Resolvers and Cloner', () => { + beforeEach(async () => { + const manifestResolver: ManifestPropertyValueResolver = { + type: 'propertyValueResolver', + name: 'test-resolver-1', + alias: 'Umb.Test.Resolver.1', + api: TestPropertyValueResolver, + forEditorAlias: 'test-editor', + }; + + umbExtensionsRegistry.register(manifestResolver); + + const manifestResolverOnly: ManifestPropertyValueResolver = { + type: 'propertyValueResolver', + name: 'test-resolver-1', + alias: 'Umb.Test.Resolver.2', + api: TestPropertyValueResolver, + forEditorAlias: 'only-resolver-editor', + }; + + umbExtensionsRegistry.register(manifestResolverOnly); + }); + afterEach(async () => { + umbExtensionsRegistry.unregister('Umb.Test.Resolver.1'); + umbExtensionsRegistry.unregister('Umb.Test.Resolver.2'); + }); + + it('mapper result is returned as an array', async () => { + const ctrlHost = new UmbTestControllerHostElement(); + const ctrl = new UmbPropertyValueFlatMapperController(ctrlHost); + + const property = { + editorAlias: 'test-editor', + alias: 'test', + culture: null, + segment: null, + value: { + id: 'not-updated-id', + }, + }; + + const result = await ctrl.flatMap(property, (property) => { + return 'hi'; + }); + + console.log('result', result); + + expect(result.length).to.be.equal(1); + expect(result[0]).to.be.equal('hi'); + }); + + it('maps only inner values', async () => { + const ctrlHost = new UmbTestControllerHostElement(); + const ctrl = new UmbPropertyValueFlatMapperController(ctrlHost); + + const property = { + editorAlias: 'only-resolver-editor', + alias: 'not-to-be-handled', + culture: null, + segment: null, + value: { + id: 'not-updated-id', + nestedValue: { + editorAlias: 'test-editor', + alias: 'some', + culture: null, + segment: null, + value: { + id: 'inner-not-updated-id', + }, + }, + }, + }; + + const result = await ctrl.flatMap(property, (property) => { + return property.value; + }); + + expect((result[1] as any)?.id).to.be.equal('not-updated-id'); + expect((result[0] as any)?.id).to.be.equal('inner-not-updated-id'); + }); + /* + it('clones value and inner values', async () => { + const ctrlHost = new UmbTestControllerHostElement(); + const ctrl = new UmbPropertyValueCloneController(ctrlHost); + + const value = { + editorAlias: 'test-editor', + alias: 'test', + culture: null, + segment: null, + value: { + id: 'not-updated-id', + nestedValue: { + editorAlias: 'test-editor', + alias: 'some', + culture: null, + segment: null, + value: { + id: 'inner-not-updated-id', + }, + }, + }, + }; + + const result = await ctrl.clone(value); + + expect((result.value as TestPropertyValueNestedType | undefined)?.id).to.be.equal('updated-id'); + expect((result.value as TestPropertyValueNestedType | undefined)?.nestedValue?.value?.id).to.be.equal( + 'updated-id', + ); + }); + + it('clones value and inner values for two levels', async () => { + const ctrlHost = new UmbTestControllerHostElement(); + const ctrl = new UmbPropertyValueCloneController(ctrlHost); + + const value = { + editorAlias: 'test-editor', + alias: 'test', + culture: null, + segment: null, + value: { + id: 'not-updated-id', + nestedValue: { + editorAlias: 'test-editor', + alias: 'some', + culture: null, + segment: null, + value: { + id: 'inner-not-updated-id', + nestedValue: { + editorAlias: 'test-editor', + alias: 'another', + culture: null, + segment: null, + value: { + id: 'inner-inner-not-updated-id', + }, + }, + }, + }, + }, + }; + + const result = await ctrl.clone(value); + + expect((result.value as TestPropertyValueNestedType | undefined)?.id).to.be.equal('updated-id'); + expect((result.value as TestPropertyValueNestedType | undefined)?.nestedValue?.value?.id).to.be.equal( + 'updated-id', + ); + expect( + ( + (result.value as TestPropertyValueNestedType | undefined)?.nestedValue?.value as + | TestPropertyValueNestedType + | undefined + )?.nestedValue?.value?.id, + ).to.be.equal('updated-id'); + }); + */ + }); +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts index 2edeff684728..2b8eb8a25fd5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts @@ -46,24 +46,29 @@ export class UmbPropertyValueFlatMapperController extends UmbControllerBase { (api as any).manifest = manifest; - const propertyValue: ReturnType = await mapper(incomingProperty); + const mapperOfThisProperty: ReturnType = await mapper(incomingProperty); if (api.processValues) { - return await api.processValues(incomingProperty, async (properties) => { + let mappedValues: Array = []; + // TODO: can we extract this method, to avoid it being recreated again and again [NL] + await api.processValues(incomingProperty, async (properties) => { // Transform the values: - const mappedValues = await Promise.all( - properties.flatMap(async (property) => { - return (await this.#mapValues(property, mapper)) ?? property; - }), - ); + for (const property of properties) { + const incomingMapValues = await this.#mapValues(property, mapper); + if (incomingMapValues) { + mappedValues = mappedValues.concat(incomingMapValues); + } + } - mappedValues.push(propertyValue); - - return mappedValues; + return undefined; }); + + mappedValues.push(mapperOfThisProperty); + + return mappedValues; } // the api did not provide a value processor, so we will return the incoming property: - return [propertyValue]; + return [mapperOfThisProperty]; } } From 29c76f98b7633e625175d92952d3a58d59847df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 20:45:58 +0200 Subject: [PATCH 04/23] return incoming value to ensure it does not result in an error from an extension --- .../property-value-flat-mapper.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts index 2b8eb8a25fd5..7b9832ec0755 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts @@ -60,7 +60,7 @@ export class UmbPropertyValueFlatMapperController extends UmbControllerBase { } } - return undefined; + return properties; }); mappedValues.push(mapperOfThisProperty); From 925c72d4e2840a92f7802c717942b21107b41a05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 20:46:18 +0200 Subject: [PATCH 05/23] define the manifest type on UmbPropertyValueResolver --- .../src/packages/core/property/property-value-resolver/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-resolver/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-resolver/types.ts index 40eaa725bf05..555b92073e7c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-resolver/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-resolver/types.ts @@ -1,6 +1,7 @@ import type { UmbPropertyValueData } from '../types/index.js'; import type { UmbVariantDataModel } from '@umbraco-cms/backoffice/variant'; import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; +import type { ManifestPropertyValueResolver } from './property-value-resolver.extension.js'; export type * from './property-value-resolver.extension.js'; @@ -9,6 +10,7 @@ export interface UmbPropertyValueResolver< InnerPropertyValueType extends UmbPropertyValueData = PropertyValueType, InnerVariantType extends UmbVariantDataModel = UmbVariantDataModel, > extends UmbApi { + manifest?: ManifestPropertyValueResolver; processValues?: UmbPropertyValueResolverValuesProcessor; processVariants?: UmbPropertyValueResolverVariantsProcessor; //ensureVariants?: UmbPropertyValueResolverEnsureVariants; From cdfb8d9a5674655fdea9b5bbc5a2caeac4f3e486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 20:56:30 +0200 Subject: [PATCH 06/23] finish property value flat-mapper --- ...perty-value-flat-mapper.controller.test.ts | 94 +++++-------------- .../property-value-flat-mapper.controller.ts | 3 +- 2 files changed, 27 insertions(+), 70 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts index 0bdfc0e8aeb9..879f3fc5406a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts @@ -1,12 +1,6 @@ import { expect } from '@open-wc/testing'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -import type { - ManifestPropertyValueResolver, - ManifestPropertyValueCloner, - UmbPropertyValueData, - UmbPropertyValueResolver, - UmbPropertyValueCloner, -} from '../types.js'; +import type { ManifestPropertyValueResolver, UmbPropertyValueData, UmbPropertyValueResolver } from '../types.js'; import type { UmbVariantDataModel } from '@umbraco-cms/backoffice/variant'; import { customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; @@ -97,21 +91,19 @@ describe('UmbPropertyValueFlatMapperController', () => { culture: null, segment: null, value: { - id: 'not-updated-id', + id: 'value', }, }; const result = await ctrl.flatMap(property, (property) => { - return 'hi'; + return (property.value as any).id; }); - console.log('result', result); - expect(result.length).to.be.equal(1); - expect(result[0]).to.be.equal('hi'); + expect(result[0]).to.be.equal('value'); }); - it('maps only inner values', async () => { + it('maps first level inner values', async () => { const ctrlHost = new UmbTestControllerHostElement(); const ctrl = new UmbPropertyValueFlatMapperController(ctrlHost); @@ -121,83 +113,53 @@ describe('UmbPropertyValueFlatMapperController', () => { culture: null, segment: null, value: { - id: 'not-updated-id', + id: 'value', nestedValue: { editorAlias: 'test-editor', alias: 'some', culture: null, segment: null, value: { - id: 'inner-not-updated-id', + id: 'inner-value', }, }, }, }; const result = await ctrl.flatMap(property, (property) => { - return property.value; + return (property.value as any).id; }); - expect((result[1] as any)?.id).to.be.equal('not-updated-id'); - expect((result[0] as any)?.id).to.be.equal('inner-not-updated-id'); - }); - /* - it('clones value and inner values', async () => { - const ctrlHost = new UmbTestControllerHostElement(); - const ctrl = new UmbPropertyValueCloneController(ctrlHost); - - const value = { - editorAlias: 'test-editor', - alias: 'test', - culture: null, - segment: null, - value: { - id: 'not-updated-id', - nestedValue: { - editorAlias: 'test-editor', - alias: 'some', - culture: null, - segment: null, - value: { - id: 'inner-not-updated-id', - }, - }, - }, - }; - - const result = await ctrl.clone(value); - - expect((result.value as TestPropertyValueNestedType | undefined)?.id).to.be.equal('updated-id'); - expect((result.value as TestPropertyValueNestedType | undefined)?.nestedValue?.value?.id).to.be.equal( - 'updated-id', - ); + expect(result.length).to.be.equal(2); + expect(result[0]).to.be.equal('value'); + expect(result[1]).to.be.equal('inner-value'); }); - it('clones value and inner values for two levels', async () => { + it('maps values for two levels', async () => { const ctrlHost = new UmbTestControllerHostElement(); - const ctrl = new UmbPropertyValueCloneController(ctrlHost); + const ctrl = new UmbPropertyValueFlatMapperController(ctrlHost); - const value = { + const property = { editorAlias: 'test-editor', alias: 'test', culture: null, segment: null, value: { - id: 'not-updated-id', + id: 'value', nestedValue: { editorAlias: 'test-editor', alias: 'some', culture: null, segment: null, value: { - id: 'inner-not-updated-id', + id: 'inner-value', nestedValue: { editorAlias: 'test-editor', alias: 'another', culture: null, segment: null, value: { - id: 'inner-inner-not-updated-id', + id: 'inner-inner-value', }, }, }, @@ -205,20 +167,14 @@ describe('UmbPropertyValueFlatMapperController', () => { }, }; - const result = await ctrl.clone(value); - - expect((result.value as TestPropertyValueNestedType | undefined)?.id).to.be.equal('updated-id'); - expect((result.value as TestPropertyValueNestedType | undefined)?.nestedValue?.value?.id).to.be.equal( - 'updated-id', - ); - expect( - ( - (result.value as TestPropertyValueNestedType | undefined)?.nestedValue?.value as - | TestPropertyValueNestedType - | undefined - )?.nestedValue?.value?.id, - ).to.be.equal('updated-id'); + const result = await ctrl.flatMap(property, (property) => { + return (property.value as any).id; + }); + + expect(result.length).to.be.equal(3); + expect(result[0]).to.be.equal('value'); + expect(result[1]).to.be.equal('inner-value'); + expect(result[2]).to.be.equal('inner-inner-value'); }); - */ }); }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts index 7b9832ec0755..d98fa2b70ee8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts @@ -63,7 +63,8 @@ export class UmbPropertyValueFlatMapperController extends UmbControllerBase { return properties; }); - mappedValues.push(mapperOfThisProperty); + // push in the front of the mapped values, so that the outer property is first in the array: + mappedValues.splice(0, 0, mapperOfThisProperty); return mappedValues; } From 45c2e825f31fe0bb6ab6e21e84e5c77cd793ed09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 20:59:56 +0200 Subject: [PATCH 07/23] make sure also to map values with no extension --- .../property-value-flat-mapper.controller.test.ts | 2 +- .../property-value-flat-mapper.controller.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts index 879f3fc5406a..dbe80b2a338e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts @@ -154,7 +154,7 @@ describe('UmbPropertyValueFlatMapperController', () => { value: { id: 'inner-value', nestedValue: { - editorAlias: 'test-editor', + editorAlias: 'test-editor-no-mapper', alias: 'another', culture: null, segment: null, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts index d98fa2b70ee8..8ba43bf4a345 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts @@ -24,9 +24,11 @@ export class UmbPropertyValueFlatMapperController extends UmbControllerBase { incomingProperty: UmbPropertyValueDataPotentiallyWithEditorAlias, mapper: (property: UmbPropertyValueDataPotentiallyWithEditorAlias) => ReturnType | Promise, ): Promise | undefined> { + const mapOfThisProperty: ReturnType = await mapper(incomingProperty); + const editorAlias = (incomingProperty as any).editorAlias as string | undefined; if (!editorAlias) { - return undefined; + return [mapOfThisProperty]; } // Find the resolver for this editor alias: @@ -36,18 +38,16 @@ export class UmbPropertyValueFlatMapperController extends UmbControllerBase { )[0]; if (!manifest) { - return undefined; + return [mapOfThisProperty]; } const api = await createExtensionApi(this, manifest); if (!api) { - return undefined; + return [mapOfThisProperty]; } (api as any).manifest = manifest; - const mapperOfThisProperty: ReturnType = await mapper(incomingProperty); - if (api.processValues) { let mappedValues: Array = []; // TODO: can we extract this method, to avoid it being recreated again and again [NL] @@ -64,12 +64,12 @@ export class UmbPropertyValueFlatMapperController extends UmbControllerBase { }); // push in the front of the mapped values, so that the outer property is first in the array: - mappedValues.splice(0, 0, mapperOfThisProperty); + mappedValues.splice(0, 0, mapOfThisProperty); return mappedValues; } // the api did not provide a value processor, so we will return the incoming property: - return [mapperOfThisProperty]; + return [mapOfThisProperty]; } } From be39229d4bbd8d70642117248ae79e0e4a2483af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 21:01:00 +0200 Subject: [PATCH 08/23] clean up test --- .../property-value-flat-mapper.controller.test.ts | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts index dbe80b2a338e..b37a150a01c2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts @@ -65,20 +65,9 @@ describe('UmbPropertyValueFlatMapperController', () => { }; umbExtensionsRegistry.register(manifestResolver); - - const manifestResolverOnly: ManifestPropertyValueResolver = { - type: 'propertyValueResolver', - name: 'test-resolver-1', - alias: 'Umb.Test.Resolver.2', - api: TestPropertyValueResolver, - forEditorAlias: 'only-resolver-editor', - }; - - umbExtensionsRegistry.register(manifestResolverOnly); }); afterEach(async () => { umbExtensionsRegistry.unregister('Umb.Test.Resolver.1'); - umbExtensionsRegistry.unregister('Umb.Test.Resolver.2'); }); it('mapper result is returned as an array', async () => { @@ -86,7 +75,7 @@ describe('UmbPropertyValueFlatMapperController', () => { const ctrl = new UmbPropertyValueFlatMapperController(ctrlHost); const property = { - editorAlias: 'test-editor', + editorAlias: 'test-editor-with-no-mapper', alias: 'test', culture: null, segment: null, @@ -108,7 +97,7 @@ describe('UmbPropertyValueFlatMapperController', () => { const ctrl = new UmbPropertyValueFlatMapperController(ctrlHost); const property = { - editorAlias: 'only-resolver-editor', + editorAlias: 'test-editor', alias: 'not-to-be-handled', culture: null, segment: null, From 1bee31ad39e499eebd0934405962e60244a8feae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 21:43:21 +0200 Subject: [PATCH 09/23] export controller --- src/Umbraco.Web.UI.Client/src/packages/core/property/index.ts | 1 + .../packages/core/property/property-value-flat-mapper/index.ts | 1 + 2 files changed, 2 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/index.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/index.ts index 84193ff39d78..44353fa02392 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/index.ts @@ -4,6 +4,7 @@ export * from './property-dataset-property-validator/index.js'; export * from './property-dataset/index.js'; export * from './property-guard-manager/index.js'; export * from './property-value-cloner/property-value-clone.controller.js'; +export * from './property-value-flat-mapper/index.js'; export * from './property-value-preset/index.js'; export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/index.ts new file mode 100644 index 000000000000..728300b62efa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/index.ts @@ -0,0 +1 @@ +export * from './property-value-flat-mapper.controller.js'; From 29b82f1003132c06acf132228cd6e6d14a0f094d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 21:50:13 +0200 Subject: [PATCH 10/23] fix block editor property resolver --- .../property-editors/block-grid-editor/manifests.ts | 4 +--- .../property-editors/block-list-editor/manifests.ts | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/manifests.ts index c575670a4609..544b8a6b4135 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/manifests.ts @@ -73,9 +73,7 @@ export const manifests: Array = alias: 'Umb.PropertyValueResolver.BlockGrid', name: 'Block Value Resolver', api: UmbStandardBlockValueResolver, - meta: { - editorAlias: UMB_BLOCK_GRID_PROPERTY_EDITOR_SCHEMA_ALIAS, - }, + forEditorAlias: UMB_BLOCK_GRID_PROPERTY_EDITOR_SCHEMA_ALIAS, }, blockGridSchemaManifest, ...propertyActionManifests, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/manifests.ts index 38122753754f..8631b8104231 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/manifests.ts @@ -51,9 +51,7 @@ export const manifests: Array = [ alias: 'Umb.PropertyValueResolver.BlockList', name: 'Block Value Resolver', api: UmbStandardBlockValueResolver, - meta: { - editorAlias: UMB_BLOCK_LIST_PROPERTY_EDITOR_SCHEMA_ALIAS, - }, + forEditorAlias: UMB_BLOCK_LIST_PROPERTY_EDITOR_SCHEMA_ALIAS, }, blockListSchemaManifest, ]; From d5367277a9dbbc3d204549e3bdc6d466a0cb0994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 21:54:18 +0200 Subject: [PATCH 11/23] fix mapper types --- .../property-value-flat-mapper.controller.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts index 8ba43bf4a345..aa4b79c70653 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts @@ -9,20 +9,24 @@ export class UmbPropertyValueFlatMapperController extends UmbControllerBase { * @param {UmbPropertyValueDataPotentiallyWithEditorAlias} property - The property data. * @returns {Promise} - A promise that resolves to the mapped data. */ - async flatMap( - property: UmbPropertyValueDataPotentiallyWithEditorAlias, - mapper: (property: UmbPropertyValueDataPotentiallyWithEditorAlias) => ReturnType | Promise, + async flatMap>( + property: PropertyType, + mapper: (property: PropertyType) => ReturnType | Promise, ): Promise> { - const result = await this.#mapValues(property, mapper); + const result = await this.#mapValues(property, mapper); this.destroy(); return result ?? []; } - async #mapValues( - incomingProperty: UmbPropertyValueDataPotentiallyWithEditorAlias, - mapper: (property: UmbPropertyValueDataPotentiallyWithEditorAlias) => ReturnType | Promise, + async #mapValues< + ReturnType, + ValueType, + PropertyType extends UmbPropertyValueDataPotentiallyWithEditorAlias, + >( + incomingProperty: PropertyType, + mapper: (property: PropertyType) => ReturnType | Promise, ): Promise | undefined> { const mapOfThisProperty: ReturnType = await mapper(incomingProperty); @@ -30,7 +34,6 @@ export class UmbPropertyValueFlatMapperController extends UmbControllerBase { if (!editorAlias) { return [mapOfThisProperty]; } - // Find the resolver for this editor alias: const manifest = umbExtensionsRegistry.getByTypeAndFilter( 'propertyValueResolver', From a6dc68fe5964380d1e81f023053cfbdf1f471128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 21:54:56 +0200 Subject: [PATCH 12/23] ensureVariantsData method --- .../content/content/manager/content-data-manager.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/manager/content-data-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/manager/content-data-manager.ts index 7c2d0b0d4d4c..d74e000d98b2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/manager/content-data-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/manager/content-data-manager.ts @@ -53,6 +53,14 @@ export class UmbContentWorkspaceDataManager< this.updateVariantData(variantId); } + ensureVariantsData(variantIds: UmbVariantId[]) { + this.initiatePropertyValueChange(); + for (const variantId of variantIds) { + this.updateVariantData(variantId); + } + this.finishPropertyValueChange(); + } + updateVariantData(variantId: UmbVariantId, update?: Partial) { if (!this.#variantScaffold) throw new Error('Variant scaffold data is missing'); From f634486d40a9ea81faaceab9fc9b45baad03ca49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 22:32:34 +0200 Subject: [PATCH 13/23] ensure Block List only updates if it has an update --- .../property-editor-ui-block-list.element.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts index 943d79c51dd6..cf3fb8502cd3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts @@ -25,7 +25,7 @@ import { UmbFormControlMixin, UmbValidationContext, } from '@umbraco-cms/backoffice/validation'; -import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; +import { jsonStringComparison, observeMultiple } from '@umbraco-cms/backoffice/observable-api'; import { debounceTime } from '@umbraco-cms/backoffice/external/rxjs'; import { UMB_CONTENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/content'; @@ -339,15 +339,22 @@ export class UmbPropertyEditorUIBlockListElement ]).pipe(debounceTime(20)), ([layouts, contents, settings, exposes]) => { if (layouts.length === 0) { + if (this.value === undefined) { + return; + } super.value = undefined; } else { - super.value = { + const newValue = { ...super.value, layout: { [UMB_BLOCK_LIST_PROPERTY_EDITOR_SCHEMA_ALIAS]: layouts }, contentData: contents, settingsData: settings, expose: exposes, }; + if (jsonStringComparison(this.value, newValue)) { + return; + } + super.value = newValue; } // If we don't have a value set from the outside or an internal value, we don't want to set the value. From 7220337d6cd65906851b1f73affe02de3a3744f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 22:33:51 +0200 Subject: [PATCH 14/23] ensure varians across for shared across segment and shared across cultures --- .../content-detail-workspace-base.ts | 64 +++++++++++-------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts index b852de706a1c..62dc739cc923 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts @@ -28,6 +28,7 @@ import { } from '@umbraco-cms/backoffice/entity-action'; import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; import { + UmbPropertyValueFlatMapperController, UmbPropertyValuePresetVariantBuilderController, UmbVariantPropertyGuardManager, } from '@umbraco-cms/backoffice/property'; @@ -287,7 +288,7 @@ export abstract class UmbContentDetailWorkspaceContextBase< if (variesByCulture && variesBySegment) { return languages.flatMap((language) => { const culture = { - variant: variants.find((x) => x.culture === language.unique), + variant: variants.find((x) => x.culture === language.unique && x.segment === null), language, culture: language.unique, segment: null, @@ -496,10 +497,7 @@ export abstract class UmbContentDetailWorkspaceContextBase< * @memberof UmbContentDetailWorkspaceContextBase */ public setName(name: string, variantId?: UmbVariantId): void { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - // TODO: fix type error - this._data.updateVariantData(variantId ?? UmbVariantId.CreateInvariant(), { name }); + this._data.updateVariantData(variantId ?? UmbVariantId.CreateInvariant(), { name } as Partial); } /** @@ -661,49 +659,65 @@ export abstract class UmbContentDetailWorkspaceContextBase< const currentData = this.getData(); if (currentData) { - const values = appendToFrozenArray( + const values: DetailModelType['values'] = appendToFrozenArray( currentData.values ?? [], entry, (x) => x.alias === alias && variantId!.compare(x), ); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - // TODO: fix type error - this._data.updateCurrent({ values }); - } + this.#ensureVariantsExistsForProperty(variantId, entry); - await this.#ensureVariantsExistsForPropertyValue(property, variantId, value); + this._data.updateCurrent({ values } as Partial); + } this.finishPropertyValueChange(); } - async #ensureVariantsExistsForPropertyValue(property: UmbPropertyTypeModel, variantId: UmbVariantId, value: unknown) { + async #ensureVariantsExistsForProperty(variantId: UmbVariantId, entry: UmbElementValueModel) { // TODO: Implement queueing of these operations to ensure this does not execute too often. [NL] + const cultureOptions = await firstValueFrom(this.variantOptions); + let valueVariantIds: Array = []; + // Find inner values to determine if any of this holds variants that needs to be created. - if (variantId.isInvariant() && typeof value === 'object' && value !== null) { - // TODO: Use the property value flat mapper to run through values to capture variant ids. [NL] + if (variantId.isInvariant() && entry.value) { + valueVariantIds = await new UmbPropertyValueFlatMapperController(this).flatMap(entry, (property) => { + return UmbVariantId.CreateFromPartial(property); + }); } + valueVariantIds.push(variantId); /** * Handling of Not-Culture but Segment variant properties: [NL] * We need to ensure variant-entries across all culture variants for the given segment variant, when er property is configured to vary by segment but not culture. * This is the only different case, in all other cases its fine to just target the given variant. */ - if (this.getVariesByCulture() && property.variesByCulture === false && property.variesBySegment === true) { - // get all culture options: - const cultureOptions = await firstValueFrom(this.variantOptions); - for (const cultureOption of cultureOptions) { - if (cultureOption.segment === variantId.segment) { - this._data.ensureVariantData(UmbVariantId.Create(cultureOption)); + let variantOptionsToCheck: Array = []; + for (const variant of valueVariantIds) { + // If a non-culture but segmented value, then spread across all cultures for the given segment: + if (this.getVariesByCulture() && variant.culture === null && variant.segment !== null) { + // get all culture options: + for (const cultureOption of cultureOptions) { + if (cultureOption.segment === variant.segment) { + variantOptionsToCheck.push(UmbVariantId.Create(cultureOption)); + } } + // If a non-segmented but culture-variant value, then spread across all segments for the given culture: + } + if (this.getVariesBySegment() && variant.culture !== null && variant.segment === null) { + // get all culture options: + for (const cultureOption of cultureOptions) { + if (cultureOption.culture === variant.culture) { + variantOptionsToCheck.push(UmbVariantId.Create(cultureOption)); + } + } + } else if (cultureOptions.some((x) => variant.compare(x))) { + // otherwise we can parse the variant-id on: + variantOptionsToCheck.push(variant); } - } else { - // otherwise we know the property variant-id will be matching with a variant: - this._data.ensureVariantData(variantId); } - // TODO: Make one method to ensure multiple variants, in order to gather the update into one operation. [NL] + + this._data.ensureVariantsData(variantOptionsToCheck); } public initiatePropertyValueChange() { From 5a43e8ab143a5b8d8379e51c5695a9db2e2ab29e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 22:52:49 +0200 Subject: [PATCH 15/23] fix variant selector hints for segments --- ...ace-split-view-variant-selector.element.ts | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts index eaf5b8d4d738..2539275dd822 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts @@ -402,11 +402,27 @@ export class UmbWorkspaceSplitViewVariantSelectorElement< const subVariantOptions = this.#getSegmentVariantOptionsForCulture(variantOption, variantId); const hint = this._hintMap.get(variantId.toString()); const active = this.#isVariantActive(variantId); + const isExpanded = this.#isVariantExpanded(variantId); + let subHint: UmbVariantHint | undefined; + if (!hint && !isExpanded) { + // Loop through the sub variants to find a hint if the culture variant does not have one. + for (const subVariant of subVariantOptions) { + const subVariantId = UmbVariantId.Create(subVariant); + console.log(subVariantId, this._hintMap); + const foundHint = this._hintMap.get(subVariantId.toString()); + if (foundHint) { + subHint = foundHint; + break; + } + } + } return html`
${this._variesBySegment && this.#isCreated(variantOption) && subVariantOptions.length > 0 - ? html`
${this.#renderExpandToggle(variantId)}
` + ? html`
+ ${this.#renderExpandToggle(variantId)}${this.#renderSubHintBadge(!isExpanded ? subHint : undefined)} +
` : nothing}
- ${this.#isVariantExpanded(variantId) - ? html` ${subVariantOptions.map((option) => this.#renderSegmentVariantOption(option))} ` - : nothing} + ${isExpanded ? html` ${subVariantOptions.map((option) => this.#renderSegmentVariantOption(option))} ` : nothing} `; } + #renderSubHintBadge(hint?: UmbVariantHint) { + if (!hint) return nothing; + return html` ${hint.text}`; + } + #renderHintBadge(hint?: UmbVariantHint) { if (!hint) return nothing; return html` @@ -478,7 +501,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement<
${this.#getVariantSpecInfo(variantOption)}
- ${this.#renderSplitViewButton(variantOption)} + ${this.#renderHintBadge(!active ? hint : undefined)} ${this.#renderSplitViewButton(variantOption)} `; } From eaafb3d06b72855b9ebc9fa8d5665b07c96ce2eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 23:06:37 +0200 Subject: [PATCH 16/23] fix hints in variant selector for segmented variants --- .../packages/core/components/badge/badge.element.ts | 11 +++++++++-- .../workspace-split-view-variant-selector.element.ts | 11 +++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/badge/badge.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/badge/badge.element.ts index 13bb1e67ad48..2a39c97fc4b4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/badge/badge.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/badge/badge.element.ts @@ -47,13 +47,20 @@ export class UmbBadgeElement extends LitElement { css` :host { position: absolute; - anchor-name: --umb-badge-anchor; /** because inset has no effect on uui-badge in this case, we then apply it here: */ inset: var(--uui-badge-inset, -8px -8px auto auto); } + :host([inline-mode]) { + position: relative; + margin-left: 12px; + } + @supports (position-anchor: --my-name) { - uui-badge { + :host(:not([inline-mode])) { + anchor-name: --umb-badge-anchor; + } + :host(:not([inline-mode])) uui-badge { position: fixed; position-anchor: --umb-badge-anchor; z-index: 1; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts index 2539275dd822..ce2b9c4c17fa 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts @@ -434,6 +434,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement<
${this.#getVariantDisplayName(variantOption)}${this.#renderReadOnlyTag(variantId.culture)} + ${this.#renderHintBadge(!active ? hint : undefined)}
${this._renderVariantDetails(variantOption)} @@ -441,7 +442,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement<
${this.#getVariantSpecInfo(variantOption)}
- ${this.#renderHintBadge(!active ? hint : undefined)} ${this.#renderSplitViewButton(variantOption)} + ${this.#renderSplitViewButton(variantOption)}
${isExpanded ? html` ${subVariantOptions.map((option) => this.#renderSegmentVariantOption(option))} ` : nothing} `; @@ -456,7 +457,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement< #renderHintBadge(hint?: UmbVariantHint) { if (!hint) return nothing; - return html` ${hint.text}`; } @@ -493,7 +494,9 @@ export class UmbWorkspaceSplitViewVariantSelectorElement< ${notCreated ? html`` : nothing}
- ${this.#getVariantDisplayName(variantOption)}${this.#renderReadOnlyTag(variantId.culture)} + ${this.#getVariantDisplayName(variantOption)}${this.#renderReadOnlyTag( + variantId.culture, + )}${this.#renderHintBadge(!active ? hint : undefined)}
${this._renderVariantDetails(variantOption)} @@ -501,7 +504,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement<
${this.#getVariantSpecInfo(variantOption)}
- ${this.#renderHintBadge(!active ? hint : undefined)} ${this.#renderSplitViewButton(variantOption)} + ${this.#renderSplitViewButton(variantOption)}
`; } From 39074688a86df542cbd7a59b4b781920c211e4b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 23:19:14 +0200 Subject: [PATCH 17/23] ensures RTE and Grid Block Editor ony updates value if there is a change --- .../property-editor-ui-block-grid.element.ts | 11 +++++++++-- .../packages/rte/components/rte-base.element.ts | 17 ++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) 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 a36e0f56587a..8ab27986e192 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 @@ -17,7 +17,7 @@ import type { UmbPropertyEditorUiElement, UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; -import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; +import { jsonStringComparison, observeMultiple } from '@umbraco-cms/backoffice/observable-api'; import { UMB_PROPERTY_CONTEXT, UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; import { UmbFormControlMixin, UmbValidationContext } from '@umbraco-cms/backoffice/validation'; import type { UmbBlockTypeGroup } from '@umbraco-cms/backoffice/block-type'; @@ -181,15 +181,22 @@ export class UmbPropertyEditorUIBlockGridElement ]).pipe(debounceTime(20)), ([layouts, contents, settings, exposes]) => { if (layouts.length === 0) { + if (this.value === undefined) { + return; + } super.value = undefined; } else { - super.value = { + const newValue = { ...super.value, layout: { [UMB_BLOCK_GRID_PROPERTY_EDITOR_SCHEMA_ALIAS]: layouts }, contentData: contents, settingsData: settings, expose: exposes, }; + if (jsonStringComparison(this.value, newValue)) { + return; + } + super.value = newValue; } // If we don't have a value set from the outside or an internal value, we don't want to set the value. 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 ef841b82fcd0..e22c9220bd1d 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 @@ -1,6 +1,6 @@ import type { UmbPropertyEditorRteValueType } from '../types.js'; import { UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS } from '../constants.js'; -import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; +import { jsonStringComparison, observeMultiple } from '@umbraco-cms/backoffice/observable-api'; import { property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbBlockRteEntriesContext, UmbBlockRteManagerContext } from '@umbraco-cms/backoffice/block-rte'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; @@ -243,9 +243,12 @@ export abstract class UmbPropertyEditorUiRteElementBase ([layouts, contents, settings, exposes]) => { if (layouts.length === 0) { if (super.value?.markup === undefined) { + if (this.value === undefined) { + return; + } super.value = undefined; } else { - super.value = { + const newValue = { ...super.value, blocks: { layout: {}, @@ -254,9 +257,13 @@ export abstract class UmbPropertyEditorUiRteElementBase expose: [], }, }; + if (jsonStringComparison(this.value, newValue)) { + return; + } + super.value = newValue; } } else { - super.value = { + const newValue = { markup: this._markup, blocks: { layout: { [UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS]: layouts }, @@ -265,6 +272,10 @@ export abstract class UmbPropertyEditorUiRteElementBase expose: exposes, }, }; + if (jsonStringComparison(this.value, newValue)) { + return; + } + super.value = newValue; } // If we don't have a value set from the outside or an internal value, we don't want to set the value. From b1dc3f3d886d3f1d16b0dc8df6ebade1bcb888db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 2 Oct 2025 23:20:03 +0200 Subject: [PATCH 18/23] remove todo --- .../property-value-flat-mapper.controller.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts index aa4b79c70653..2245bbd4b106 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts @@ -53,7 +53,6 @@ export class UmbPropertyValueFlatMapperController extends UmbControllerBase { if (api.processValues) { let mappedValues: Array = []; - // TODO: can we extract this method, to avoid it being recreated again and again [NL] await api.processValues(incomingProperty, async (properties) => { // Transform the values: for (const property of properties) { From 5f4ab4fd6dfc9b85efce283fa76539020f4fafc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 3 Oct 2025 08:53:00 +0200 Subject: [PATCH 19/23] remove segments setup --- src/Umbraco.Web.UI/Segments.cs | 41 --------------------------- src/Umbraco.Web.UI/Umbraco.Web.UI.sln | 25 ---------------- 2 files changed, 66 deletions(-) delete mode 100644 src/Umbraco.Web.UI/Segments.cs delete mode 100644 src/Umbraco.Web.UI/Umbraco.Web.UI.sln diff --git a/src/Umbraco.Web.UI/Segments.cs b/src/Umbraco.Web.UI/Segments.cs deleted file mode 100644 index 89fa53317d44..000000000000 --- a/src/Umbraco.Web.UI/Segments.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.OperationStatus; - -namespace Umbraco.Cms.Web.UI; - -public class SegmentService : ISegmentService -{ - private static readonly Segment[] segments = new Segment[] { - new Segment - { - Alias = "segment-one", - Name = "First Segment" - }, - new Segment - { - Alias = "segment-two", - Name = "Second Segment" - }, - new Segment - { - Alias = "segment-three", - Name = "Thrird Segment" - }, - }; - - public async Task?, SegmentOperationStatus>> GetPagedSegmentsAsync(int skip = 0, int take = 100) - { - return await Task.FromResult(Attempt.SucceedWithStatus?, SegmentOperationStatus>( - SegmentOperationStatus.Success, - new PagedModel { Total = segments.Length, Items = segments.Skip(0).Take(take) })); - } -} - -public class SegmentServiceOverrideComposer : IComposer -{ - - public void Compose(IUmbracoBuilder builder) => builder.Services.AddUnique(); -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.sln b/src/Umbraco.Web.UI/Umbraco.Web.UI.sln deleted file mode 100644 index cf34f4667e05..000000000000 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.002.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Web.UI", "Umbraco.Web.UI.csproj", "{A175D8F0-2D9E-47D9-82E5-E51CEF851A15}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A175D8F0-2D9E-47D9-82E5-E51CEF851A15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A175D8F0-2D9E-47D9-82E5-E51CEF851A15}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A175D8F0-2D9E-47D9-82E5-E51CEF851A15}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A175D8F0-2D9E-47D9-82E5-E51CEF851A15}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {201307DE-F6DE-4FD8-B2B6-B3642E21D183} - EndGlobalSection -EndGlobal From 2416eb7ec6c7f715fe56e78c757f7ed55304523b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 3 Oct 2025 08:55:00 +0200 Subject: [PATCH 20/23] no need for as any here. --- .../property-value-flat-mapper.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts index 2245bbd4b106..8e127ec0d99d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts @@ -49,7 +49,7 @@ export class UmbPropertyValueFlatMapperController extends UmbControllerBase { return [mapOfThisProperty]; } - (api as any).manifest = manifest; + api.manifest = manifest; if (api.processValues) { let mappedValues: Array = []; From 1dcf7d285babf19b14d0a6dc793be90faffc9b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 3 Oct 2025 08:55:21 +0200 Subject: [PATCH 21/23] no need for as any --- .../content/controller/merge-content-variant-data.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/controller/merge-content-variant-data.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/controller/merge-content-variant-data.controller.ts index 8482e32565ae..508d86bd645e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/controller/merge-content-variant-data.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/controller/merge-content-variant-data.controller.ts @@ -135,7 +135,7 @@ export class UmbMergeContentVariantDataController extends UmbControllerBase { // If api is not to be found, then we can continue using the draftValue as is. return draftValue; } - (api as any).manifest = manifest; + api.manifest = manifest; let newValue = draftValue; From af3fc032844d74bd4ea1fbddfb1f3392281ff8d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 3 Oct 2025 09:16:04 +0200 Subject: [PATCH 22/23] remove console.log --- .../workspace-split-view-variant-selector.element.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts index ce2b9c4c17fa..8f309f0858b7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts @@ -408,7 +408,6 @@ export class UmbWorkspaceSplitViewVariantSelectorElement< // Loop through the sub variants to find a hint if the culture variant does not have one. for (const subVariant of subVariantOptions) { const subVariantId = UmbVariantId.Create(subVariant); - console.log(subVariantId, this._hintMap); const foundHint = this._hintMap.get(subVariantId.toString()); if (foundHint) { subHint = foundHint; From 37dbeb4ac17455f978ad6d585961b6c2d53a0263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 3 Oct 2025 09:19:38 +0200 Subject: [PATCH 23/23] lint fixes --- src/Umbraco.Web.UI.Client/src/assets/lang/da.ts | 3 +-- src/Umbraco.Web.UI.Client/src/assets/lang/en.ts | 3 +-- .../content/workspace/content-detail-workspace-base.ts | 2 +- .../property-value-flat-mapper.controller.ts | 1 + .../packages/core/property/property-value-resolver/types.ts | 2 +- .../src/packages/core/sorter/sorter.controller.ts | 2 +- .../tree/tree-item/tree-item-base/tree-item-element-base.ts | 4 +++- .../core/workspace/kinds/default/default-workspace.context.ts | 2 +- .../src/packages/management-api/tree/types.ts | 1 - .../word-count/word-count.tiptap-statusbar-element.ts | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts index 52c51b42cb2b..be04a04f7871 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts @@ -2473,8 +2473,7 @@ export default { confirmDeleteBlockTypeNotice: 'Indholdet vil stadigt eksistere, men redigering af dette indhold vil ikke\n være muligt. Indholdet vil blive vist som ikke understøttet indhold.\n ', confirmDeleteBlockGroupTitle: 'Slet gruppe?', - confirmDeleteBlockGroupMessage: - 'Er du sikker på at du vil slette gruppen %0%?', + confirmDeleteBlockGroupMessage: 'Er du sikker på at du vil slette gruppen %0%?', confirmDeleteBlockGroupNotice: 'Indholdet af gruppens blokke vil stadigt eksistere, men redigering af dette indhold vil ikke\n være muligt. Indholdet vil blive vist som ikke understøttet indhold.\n ', blockConfigurationOverlayTitle: "Konfiguration af '%0%'", 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 d2962c1ced9b..09c96011ce5f 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -2605,8 +2605,7 @@ export default { confirmDeleteBlockTypeNotice: 'The content of this block will still be present, editing of this content will no longer be available and will be shown as unsupported content.', confirmDeleteBlockGroupTitle: 'Delete group?', - confirmDeleteBlockGroupMessage: - 'Are you sure you want to delete group %0%?', + confirmDeleteBlockGroupMessage: 'Are you sure you want to delete group %0%?', confirmDeleteBlockGroupNotice: 'The content of these Blocks will still be present, editing of this content will no longer be available and will be shown as unsupported content.', blockConfigurationOverlayTitle: "Configuration of '%0%'", diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts index 62dc739cc923..f9ba1f351f9c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts @@ -692,7 +692,7 @@ export abstract class UmbContentDetailWorkspaceContextBase< * We need to ensure variant-entries across all culture variants for the given segment variant, when er property is configured to vary by segment but not culture. * This is the only different case, in all other cases its fine to just target the given variant. */ - let variantOptionsToCheck: Array = []; + const variantOptionsToCheck: Array = []; for (const variant of valueVariantIds) { // If a non-culture but segmented value, then spread across all cultures for the given segment: if (this.getVariesByCulture() && variant.culture === null && variant.segment !== null) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts index 8e127ec0d99d..e7c045720590 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.ts @@ -7,6 +7,7 @@ export class UmbPropertyValueFlatMapperController extends UmbControllerBase { /** * Maps the property values of the given property data. * @param {UmbPropertyValueDataPotentiallyWithEditorAlias} property - The property data. + * @param mapper * @returns {Promise} - A promise that resolves to the mapped data. */ async flatMap>( diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-resolver/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-resolver/types.ts index 555b92073e7c..2d7f9cd413e8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-resolver/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-resolver/types.ts @@ -1,7 +1,7 @@ import type { UmbPropertyValueData } from '../types/index.js'; +import type { ManifestPropertyValueResolver } from './property-value-resolver.extension.js'; import type { UmbVariantDataModel } from '@umbraco-cms/backoffice/variant'; import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; -import type { ManifestPropertyValueResolver } from './property-value-resolver.extension.js'; export type * from './property-value-resolver.extension.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index d7703dc35105..c50ca8e9d9f7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -238,7 +238,7 @@ export type UmbSorterConfig = Partial, 'ignorerSelector' | 'containerSelector' | 'identifier'>>; /** - + * @class UmbSorterController * @implements {UmbControllerInterface} * @description This controller can make user able to sort items. diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts index 6a69dd17f87e..1efaafbbe00a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts @@ -165,7 +165,9 @@ export abstract class UmbTreeItemElementBase< .loading=${this._isLoading} .hasChildren=${this._hasChildren} .showChildren=${this._isOpen} - .caretLabel=${this._isOpen ? this.localize.term('visuallyHiddenTexts_collapseChildItems') + ' ' + this._label: this.localize.term('visuallyHiddenTexts_expandChildItems') + ' ' + this._label} + .caretLabel=${this._isOpen + ? this.localize.term('visuallyHiddenTexts_collapseChildItems') + ' ' + this._label + : this.localize.term('visuallyHiddenTexts_expandChildItems') + ' ' + this._label} label=${ifDefined(this._label)} href="${ifDefined(this._isSelectableContext ? undefined : this._href)}"> ${this.#renderLoadPrevButton()} ${this.renderIconContainer()} ${this.renderLabel()} ${this.#renderActions()} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/kinds/default/default-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/kinds/default/default-workspace.context.ts index 6c3e0185bf90..d5fae1e42e5d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/kinds/default/default-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/kinds/default/default-workspace.context.ts @@ -1,10 +1,10 @@ import { UMB_WORKSPACE_CONTEXT } from '../../workspace.context-token.js'; import type { UmbWorkspaceContext } from '../../workspace-context.interface.js'; +import type { ManifestWorkspaceDefaultKind } from './types.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import { UmbEntityContext, type UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; import { UmbViewContext } from '@umbraco-cms/backoffice/view'; -import type { ManifestWorkspaceDefaultKind } from './types.js'; export class UmbDefaultWorkspaceContext extends UmbContextBase implements UmbWorkspaceContext { public workspaceAlias!: string; diff --git a/src/Umbraco.Web.UI.Client/src/packages/management-api/tree/types.ts b/src/Umbraco.Web.UI.Client/src/packages/management-api/tree/types.ts index f6aed833ff7b..836566188ce0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/management-api/tree/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/management-api/tree/types.ts @@ -5,7 +5,6 @@ import type { UmbTreeRootItemsRequestArgs, } from '@umbraco-cms/backoffice/tree'; -// eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface UmbManagementApiTreeRootItemsRequestArgs extends UmbTreeRootItemsRequestArgs { paging: UmbOffsetPaginationRequestModel; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/word-count/word-count.tiptap-statusbar-element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/word-count/word-count.tiptap-statusbar-element.ts index 95b7c24b29a3..baa317b6cfab 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/word-count/word-count.tiptap-statusbar-element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/word-count/word-count.tiptap-statusbar-element.ts @@ -1,5 +1,5 @@ -import { customElement, html, state } from '@umbraco-cms/backoffice/external/lit'; import type { Editor } from '../../externals.js'; +import { customElement, html, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @customElement('umb-tiptap-statusbar-word-count')