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/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-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/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, ]; 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. 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; 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'); 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..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 @@ -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,36 +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 }); - - /** - * 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) { + this.#ensureVariantsExistsForProperty(variantId, entry); + + this._data.updateCurrent({ values } as Partial); + } + + this.finishPropertyValueChange(); + } + + 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() && 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. + */ + 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) { // 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)); + if (cultureOption.segment === variant.segment) { + variantOptionsToCheck.push(UmbVariantId.Create(cultureOption)); } } - } else { - // otherwise we know the property variant-id will be matching with a variant: - this._data.ensureVariantData(variantId); + // 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); } } - this.finishPropertyValueChange(); + + this._data.ensureVariantsData(variantOptionsToCheck); } public initiatePropertyValueChange() { 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/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'; 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..b37a150a01c2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-flat-mapper/property-value-flat-mapper.controller.test.ts @@ -0,0 +1,169 @@ +import { expect } from '@open-wc/testing'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +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'; +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); + }); + afterEach(async () => { + umbExtensionsRegistry.unregister('Umb.Test.Resolver.1'); + }); + + it('mapper result is returned as an array', async () => { + const ctrlHost = new UmbTestControllerHostElement(); + const ctrl = new UmbPropertyValueFlatMapperController(ctrlHost); + + const property = { + editorAlias: 'test-editor-with-no-mapper', + alias: 'test', + culture: null, + segment: null, + value: { + id: 'value', + }, + }; + + const result = await ctrl.flatMap(property, (property) => { + return (property.value as any).id; + }); + + expect(result.length).to.be.equal(1); + expect(result[0]).to.be.equal('value'); + }); + + it('maps first level inner values', async () => { + const ctrlHost = new UmbTestControllerHostElement(); + const ctrl = new UmbPropertyValueFlatMapperController(ctrlHost); + + const property = { + editorAlias: 'test-editor', + alias: 'not-to-be-handled', + culture: null, + segment: null, + value: { + id: 'value', + nestedValue: { + editorAlias: 'test-editor', + alias: 'some', + culture: null, + segment: null, + value: { + id: 'inner-value', + }, + }, + }, + }; + + const result = await ctrl.flatMap(property, (property) => { + return (property.value as any).id; + }); + + expect(result.length).to.be.equal(2); + expect(result[0]).to.be.equal('value'); + expect(result[1]).to.be.equal('inner-value'); + }); + + it('maps values for two levels', async () => { + const ctrlHost = new UmbTestControllerHostElement(); + const ctrl = new UmbPropertyValueFlatMapperController(ctrlHost); + + const property = { + editorAlias: 'test-editor', + alias: 'test', + culture: null, + segment: null, + value: { + id: 'value', + nestedValue: { + editorAlias: 'test-editor', + alias: 'some', + culture: null, + segment: null, + value: { + id: 'inner-value', + nestedValue: { + editorAlias: 'test-editor-no-mapper', + alias: 'another', + culture: null, + segment: null, + value: { + id: 'inner-inner-value', + }, + }, + }, + }, + }, + }; + + 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 new file mode 100644 index 000000000000..e7c045720590 --- /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,78 @@ +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. + * @param mapper + * @returns {Promise} - A promise that resolves to the mapped data. + */ + async flatMap>( + property: PropertyType, + mapper: (property: PropertyType) => ReturnType | Promise, + ): Promise> { + const result = await this.#mapValues(property, mapper); + + this.destroy(); + + return result ?? []; + } + + async #mapValues< + ReturnType, + ValueType, + PropertyType extends UmbPropertyValueDataPotentiallyWithEditorAlias, + >( + incomingProperty: PropertyType, + mapper: (property: PropertyType) => ReturnType | Promise, + ): Promise | undefined> { + const mapOfThisProperty: ReturnType = await mapper(incomingProperty); + + const editorAlias = (incomingProperty as any).editorAlias as string | undefined; + if (!editorAlias) { + return [mapOfThisProperty]; + } + // Find the resolver for this editor alias: + const manifest = umbExtensionsRegistry.getByTypeAndFilter( + 'propertyValueResolver', + (x) => x.forEditorAlias === editorAlias, + )[0]; + + if (!manifest) { + return [mapOfThisProperty]; + } + + const api = await createExtensionApi(this, manifest); + if (!api) { + return [mapOfThisProperty]; + } + + api.manifest = manifest; + + if (api.processValues) { + let mappedValues: Array = []; + await api.processValues(incomingProperty, async (properties) => { + // Transform the values: + for (const property of properties) { + const incomingMapValues = await this.#mapValues(property, mapper); + if (incomingMapValues) { + mappedValues = mappedValues.concat(incomingMapValues); + } + } + + return properties; + }); + + // push in the front of the mapped values, so that the outer property is first in the array: + mappedValues.splice(0, 0, mapOfThisProperty); + + return mappedValues; + } + + // the api did not provide a value processor, so we will return the incoming property: + return [mapOfThisProperty]; + } +} 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..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,4 +1,5 @@ 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'; @@ -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; 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/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..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 @@ -402,11 +402,26 @@ 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); + 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.#renderHintBadge(!active ? hint : undefined)} ${this.#renderSplitViewButton(variantOption)} + ${this.#renderSplitViewButton(variantOption)}
- ${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` ${hint.text}`; } @@ -458,6 +479,8 @@ export class UmbWorkspaceSplitViewVariantSelectorElement< #renderSegmentVariantOption(variantOption: VariantOptionModelType) { const variantId = UmbVariantId.Create(variantOption); const notCreated = this.#isCreateMode(variantOption, variantId); + const hint = this._hintMap.get(variantId.toString()); + const active = this.#isVariantActive(variantId); return html`
@@ -470,7 +493,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)} 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/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. 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')