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 18f0285203..f13bfebce7 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 @@ -22,7 +22,12 @@ import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type'; import '../../components/block-list-entry/index.js'; import { UMB_PROPERTY_CONTEXT, UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; -import { ExtractJsonQueryProps, UmbFormControlMixin, UmbValidationContext } from '@umbraco-cms/backoffice/validation'; +import { + extractJsonQueryProps, + UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, + UmbFormControlMixin, + UmbValidationContext, +} from '@umbraco-cms/backoffice/validation'; import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; import { debounceTime } from '@umbraco-cms/backoffice/external/rxjs'; @@ -134,6 +139,12 @@ export class UmbPropertyEditorUIBlockListElement } #readonly = false; + @property({ type: Boolean }) + mandatory?: boolean; + + @property({ type: String }) + mandatoryMessage?: string | undefined; + @state() private _limitMin?: number; @state() @@ -155,37 +166,17 @@ export class UmbPropertyEditorUIBlockListElement super(); this.consumeContext(UMB_PROPERTY_CONTEXT, (context) => { - this.observe( - context.dataPath, - (dataPath) => { - // Translate paths for content/settings: - this.#contentDataPathTranslator?.destroy(); - this.#settingsDataPathTranslator?.destroy(); - if (dataPath) { - // Set the data path for the local validation context: - this.#validationContext.setDataPath(dataPath); + this.#gotPropertyContext(context); + }); - this.#contentDataPathTranslator = new UmbBlockElementDataValidationPathTranslator(this, 'contentData'); - this.#settingsDataPathTranslator = new UmbBlockElementDataValidationPathTranslator(this, 'settingsData'); - } - }, - 'observeDataPath', - ); - - this.observe( - context?.alias, - (alias) => { - this.#managerContext.setPropertyAlias(alias); - }, - 'observePropertyAlias', - ); - - // Observe Blocks and clean up validation messages for content/settings that are not in the block list anymore: - this.observe(this.#managerContext.layouts, (layouts) => { + // Observe Blocks and clean up validation messages for content/settings that are not in the block list anymore: + this.observe( + this.#managerContext.layouts, + (layouts) => { const contentKeys = layouts.map((x) => x.contentKey); this.#validationContext.messages.getMessagesOfPathAndDescendant('$.contentData').forEach((message) => { // get the KEY from this string: $.contentData[?(@.key == 'KEY')] - const key = ExtractJsonQueryProps(message.path).key; + const key = extractJsonQueryProps(message.path).key; if (key && contentKeys.indexOf(key) === -1) { this.#validationContext.messages.removeMessageByKey(message.key); } @@ -194,66 +185,14 @@ export class UmbPropertyEditorUIBlockListElement const settingsKeys = layouts.map((x) => x.settingsKey).filter((x) => x !== undefined) as string[]; this.#validationContext.messages.getMessagesOfPathAndDescendant('$.settingsData').forEach((message) => { // get the key from this string: $.settingsData[?(@.key == 'KEY')] - const key = ExtractJsonQueryProps(message.path).key; + const key = extractJsonQueryProps(message.path).key; if (key && settingsKeys.indexOf(key) === -1) { this.#validationContext.messages.removeMessageByKey(message.key); } }); - }); - - this.observe( - observeMultiple([ - this.#managerContext.layouts, - this.#managerContext.contents, - this.#managerContext.settings, - this.#managerContext.exposes, - ]).pipe(debounceTime(20)), - ([layouts, contents, settings, exposes]) => { - if (layouts.length === 0) { - super.value = undefined; - } else { - super.value = { - ...super.value, - layout: { [UMB_BLOCK_LIST_PROPERTY_EDITOR_SCHEMA_ALIAS]: layouts }, - contentData: contents, - settingsData: settings, - expose: exposes, - }; - } - - // If we don't have a value set from the outside or an internal value, we don't want to set the value. - // This is added to prevent the block list from setting an empty value on startup. - if (this.#lastValue === undefined && super.value === undefined) { - return; - } - - context.setValue(super.value); - }, - 'motherObserver', - ); - - // If the current property is readonly all inner block content should also be readonly. - this.observe( - observeMultiple([context.isReadOnly, context.variantId]), - ([isReadOnly, variantId]) => { - const unique = 'UMB_PROPERTY_EDITOR_UI'; - if (variantId === undefined) return; - - if (isReadOnly) { - const state = { - unique, - variantId, - message: '', - }; - - this.#managerContext.readOnlyState.addState(state); - } else { - this.#managerContext.readOnlyState.removeState(unique); - } - }, - 'observeIsReadOnly', - ); - }); + }, + null, + ); this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (context) => { this.#managerContext.setVariantId(context.getVariantId()); @@ -272,25 +211,128 @@ export class UmbPropertyEditorUIBlockListElement this.addValidator( 'rangeOverflow', - () => this.localize.term('validation_entriesExceed', this._limitMax, this.#entriesContext.getLength() - (this._limitMax || 0)), + () => + this.localize.term( + 'validation_entriesExceed', + this._limitMax, + this.#entriesContext.getLength() - (this._limitMax || 0), + ), () => !!this._limitMax && this.#entriesContext.getLength() > this._limitMax, ); - this.observe(this.#entriesContext.layoutEntries, (layouts) => { - this._layouts = layouts; - // Update sorter. - this.#sorter.setModel(layouts); - // Update manager: - this.#managerContext.setLayouts(layouts); - }); + this.addValidator( + 'valueMissing', + () => this.mandatoryMessage ?? UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, + () => !!this.mandatory && this.#entriesContext.getLength() === 0, + ); - this.observe(this.#managerContext.blockTypes, (blockTypes) => { - this._blocks = blockTypes; - }); + this.observe( + this.#entriesContext.layoutEntries, + (layouts) => { + this._layouts = layouts; + // Update sorter. + this.#sorter.setModel(layouts); + // Update manager: + this.#managerContext.setLayouts(layouts); + }, + null, + ); - this.observe(this.#entriesContext.catalogueRouteBuilder, (routeBuilder) => { - this._catalogueRouteBuilder = routeBuilder; - }); + this.observe( + this.#managerContext.blockTypes, + (blockTypes) => { + this._blocks = blockTypes; + }, + null, + ); + + this.observe( + this.#entriesContext.catalogueRouteBuilder, + (routeBuilder) => { + this._catalogueRouteBuilder = routeBuilder; + }, + null, + ); + } + + #gotPropertyContext(context: typeof UMB_PROPERTY_CONTEXT.TYPE) { + this.observe( + context.dataPath, + (dataPath) => { + // Translate paths for content/settings: + this.#contentDataPathTranslator?.destroy(); + this.#settingsDataPathTranslator?.destroy(); + if (dataPath) { + // Set the data path for the local validation context: + this.#validationContext.setDataPath(dataPath); + + this.#contentDataPathTranslator = new UmbBlockElementDataValidationPathTranslator(this, 'contentData'); + this.#settingsDataPathTranslator = new UmbBlockElementDataValidationPathTranslator(this, 'settingsData'); + } + }, + 'observeDataPath', + ); + + this.observe( + context?.alias, + (alias) => { + this.#managerContext.setPropertyAlias(alias); + }, + 'observePropertyAlias', + ); + + this.observe( + observeMultiple([ + this.#managerContext.layouts, + this.#managerContext.contents, + this.#managerContext.settings, + this.#managerContext.exposes, + ]).pipe(debounceTime(20)), + ([layouts, contents, settings, exposes]) => { + if (layouts.length === 0) { + super.value = undefined; + } else { + super.value = { + ...super.value, + layout: { [UMB_BLOCK_LIST_PROPERTY_EDITOR_SCHEMA_ALIAS]: layouts }, + contentData: contents, + settingsData: settings, + expose: exposes, + }; + } + + // If we don't have a value set from the outside or an internal value, we don't want to set the value. + // This is added to prevent the block list from setting an empty value on startup. + if (this.#lastValue === undefined && super.value === undefined) { + return; + } + + context.setValue(super.value); + }, + 'motherObserver', + ); + + // If the current property is readonly all inner block content should also be readonly. + this.observe( + observeMultiple([context.isReadOnly, context.variantId]), + ([isReadOnly, variantId]) => { + const unique = 'UMB_PROPERTY_EDITOR_UI'; + if (variantId === undefined) return; + + if (isReadOnly) { + const state = { + unique, + variantId, + message: '', + }; + + this.#managerContext.readOnlyState.addState(state); + } else { + this.#managerContext.readOnlyState.removeState(unique); + } + }, + 'observeIsReadOnly', + ); } protected override getFormElement() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/property-value-cloner/property-value-cloner-block-rte.cloner.test.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/property-value-cloner/property-value-cloner-block-rte.cloner.test.ts index cfc393562d..8a4d804811 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/property-value-cloner/property-value-cloner-block-rte.cloner.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/property-value-cloner/property-value-cloner-block-rte.cloner.test.ts @@ -6,7 +6,7 @@ import { UmbPropertyValueCloneController } from '@umbraco-cms/backoffice/propert import { manifests } from './manifests'; import { UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS, - type UmbPropertyEditorUiValueType, + type UmbPropertyEditorRteValueType, } from '@umbraco-cms/backoffice/rte'; @customElement('umb-test-controller-host') @@ -69,7 +69,7 @@ describe('UmbBlockRtePropertyValueCloner', () => { }, }; - const result = (await ctrl.clone(value)) as { value: UmbPropertyEditorUiValueType | undefined }; + const result = (await ctrl.clone(value)) as { value: UmbPropertyEditorRteValueType | undefined }; const newContentKey = result.value?.blocks.layout[UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS]?.[0].contentKey; const newSettingsKey = result.value?.blocks.layout[UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS]?.[0].settingsKey; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/property-value-cloner/property-value-cloner-block-rte.cloner.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/property-value-cloner/property-value-cloner-block-rte.cloner.ts index ea0ea1a18b..104afd7e0f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/property-value-cloner/property-value-cloner-block-rte.cloner.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/property-value-cloner/property-value-cloner-block-rte.cloner.ts @@ -1,15 +1,15 @@ import { UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS, - type UmbPropertyEditorUiValueType, + type UmbPropertyEditorRteValueType, } from '@umbraco-cms/backoffice/rte'; import type { UmbPropertyValueCloner } from '@umbraco-cms/backoffice/property'; import { UmbFlatLayoutBlockPropertyValueCloner } from '@umbraco-cms/backoffice/block'; -export class UmbBlockRTEPropertyValueCloner implements UmbPropertyValueCloner { +export class UmbBlockRTEPropertyValueCloner implements UmbPropertyValueCloner { #markup?: string; #markupDoc?: Document; - async cloneValue(value: UmbPropertyEditorUiValueType) { + async cloneValue(value: UmbPropertyEditorRteValueType) { if (value) { this.#markup = value.markup; @@ -19,7 +19,7 @@ export class UmbBlockRTEPropertyValueCloner implements UmbPropertyValueCloner { +export function extractJsonQueryProps(query: string): Record { // Object to hold property-value pairs const propsMap: Record = {}; let match; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/extract-json-query-properties.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/extract-json-query-properties.test.ts index a7ce999c23..7d122183eb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/extract-json-query-properties.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/extract-json-query-properties.test.ts @@ -1,10 +1,10 @@ import { expect } from '@open-wc/testing'; -import { ExtractJsonQueryProps } from './extract-json-query-properties.function.js'; +import { extractJsonQueryProps } from './extract-json-query-properties.function.js'; describe('UmbJsonPathFunctions', () => { it('retrieve property value', () => { const query = `?(@.culture == 'en-us' && @.segment == 'mySegment')`; - const result = ExtractJsonQueryProps(query); + const result = extractJsonQueryProps(query); expect(result.culture).to.eq('en-us'); expect(result.segment).to.eq('mySegment'); 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 07b52ae1b5..e217f72f46 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,4 +1,4 @@ -import type { UmbPropertyEditorUiValueType } from '../types.js'; +import type { UmbPropertyEditorRteValueType } from '../types.js'; import { UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS } from '../constants.js'; import { property, state } from '@umbraco-cms/backoffice/external/lit'; import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; @@ -11,8 +11,17 @@ import type { UmbPropertyEditorUiElement, UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; +import { + UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, + UmbFormControlMixin, + UmbValidationContext, +} from '@umbraco-cms/backoffice/validation'; +import { UmbBlockElementDataValidationPathTranslator } from '@umbraco-cms/backoffice/block'; -export abstract class UmbPropertyEditorUiRteElementBase extends UmbLitElement implements UmbPropertyEditorUiElement { +export abstract class UmbPropertyEditorUiRteElementBase + extends UmbFormControlMixin(UmbLitElement) + implements UmbPropertyEditorUiElement +{ public set config(config: UmbPropertyEditorConfigCollection | undefined) { if (!config) return; @@ -27,13 +36,13 @@ export abstract class UmbPropertyEditorUiRteElementBase extends UmbLitElement im @property({ attribute: false, type: Object, - hasChanged(value?: UmbPropertyEditorUiValueType, oldValue?: UmbPropertyEditorUiValueType) { + hasChanged(value?: UmbPropertyEditorRteValueType, oldValue?: UmbPropertyEditorRteValueType) { return value?.markup !== oldValue?.markup; }, }) - public set value(value: UmbPropertyEditorUiValueType | undefined) { + public override set value(value: UmbPropertyEditorRteValueType | undefined) { if (!value) { - this._value = undefined; + super.value = undefined; this._markup = ''; this.#managerContext.setLayouts([]); this.#managerContext.setContents([]); @@ -42,18 +51,18 @@ export abstract class UmbPropertyEditorUiRteElementBase extends UmbLitElement im return; } - const buildUpValue: Partial = value ? { ...value } : {}; + const buildUpValue: Partial = value ? { ...value } : {}; buildUpValue.markup ??= ''; buildUpValue.blocks ??= { layout: {}, contentData: [], settingsData: [], expose: [] }; buildUpValue.blocks.layout ??= {}; buildUpValue.blocks.contentData ??= []; buildUpValue.blocks.settingsData ??= []; buildUpValue.blocks.expose ??= []; - this._value = buildUpValue as UmbPropertyEditorUiValueType; + super.value = buildUpValue as UmbPropertyEditorRteValueType; // Only update the actual editor markup if it is not the same as the value. - if (this._markup !== this._value.markup) { - this._markup = this._value.markup; + if (this._markup !== super.value.markup) { + this._markup = super.value.markup; } this.#managerContext.setLayouts(buildUpValue.blocks.layout[UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS] ?? []); @@ -61,8 +70,8 @@ export abstract class UmbPropertyEditorUiRteElementBase extends UmbLitElement im this.#managerContext.setSettings(buildUpValue.blocks.settingsData); this.#managerContext.setExposes(buildUpValue.blocks.expose); } - public get value() { - return this._value; + public override get value(): UmbPropertyEditorRteValueType | undefined { + return super.value; } /** @@ -72,11 +81,25 @@ export abstract class UmbPropertyEditorUiRteElementBase extends UmbLitElement im @property({ type: Boolean, reflect: true }) readonly = false; + @property({ type: Boolean }) + mandatory?: boolean; + + @property({ type: String }) + mandatoryMessage?: string | undefined; + @state() protected _config?: UmbPropertyEditorConfigCollection; + /** + * @deprecated _value is depreacated, use `super.value` instead. + */ @state() - protected _value?: UmbPropertyEditorUiValueType | undefined; + protected get _value(): UmbPropertyEditorRteValueType | undefined { + return super.value; + } + protected set _value(value: UmbPropertyEditorRteValueType | undefined) { + super.value = value; + } /** * Separate state for markup, to avoid re-rendering/re-setting the value of the Tiptap editor when the value does not really change. @@ -87,85 +110,107 @@ export abstract class UmbPropertyEditorUiRteElementBase extends UmbLitElement im readonly #managerContext = new UmbBlockRteManagerContext(this); readonly #entriesContext = new UmbBlockRteEntriesContext(this); + readonly #validationContext = new UmbValidationContext(this); + #contentDataPathTranslator?: UmbBlockElementDataValidationPathTranslator; + #settingsDataPathTranslator?: UmbBlockElementDataValidationPathTranslator; + constructor() { super(); this.consumeContext(UMB_PROPERTY_CONTEXT, (context) => { - // TODO: Implement validation translation for RTE Blocks: - /* - this.observe( - context.dataPath, - (dataPath) => { - // Translate paths for content/settings: - this.#contentDataPathTranslator?.destroy(); - this.#settingsDataPathTranslator?.destroy(); - if (dataPath) { - // Set the data path for the local validation context: - this.#validationContext.setDataPath(dataPath); - - this.#contentDataPathTranslator = new UmbBlockElementDataValidationPathTranslator(this, 'contentData'); - this.#settingsDataPathTranslator = new UmbBlockElementDataValidationPathTranslator(this, 'settingsData'); - } - }, - 'observeDataPath', - ); - */ - - this.observe( - context?.alias, - (alias) => { - this.#managerContext.setPropertyAlias(alias); - }, - 'observePropertyAlias', - ); - - this.observe(this.#entriesContext.layoutEntries, (layouts) => { - // Update manager: - this.#managerContext.setLayouts(layouts); - }); - - this.observe( - observeMultiple([ - this.#managerContext.layouts, - this.#managerContext.contents, - this.#managerContext.settings, - this.#managerContext.exposes, - ]), - ([layouts, contents, settings, exposes]) => { - if (layouts.length === 0) { - this._value = undefined; - } else { - this._value = { - markup: this._markup, - blocks: { - layout: { [UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS]: layouts }, - contentData: contents, - settingsData: settings, - expose: exposes, - }, - }; - } - - // If we don't have a value set from the outside or an internal value, we don't want to set the value. - // This is added to prevent the block list from setting an empty value on startup. - if (this._value?.markup === undefined) { - return; - } - - context.setValue(this._value); - }, - 'motherObserver', - ); + this.#gotPropertyContext(context); }); this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (context) => { this.#managerContext.setVariantId(context.getVariantId()); }); - this.observe(this.#entriesContext.layoutEntries, (layouts) => { - // Update manager: - this.#managerContext.setLayouts(layouts); - }); + this.observe( + this.#entriesContext.layoutEntries, + (layouts) => { + // Update manager: + this.#managerContext.setLayouts(layouts); + }, + null, + ); + + this.addValidator( + 'valueMissing', + () => this.mandatoryMessage ?? UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, + () => !!this.mandatory && this.value === undefined, + ); + } + + #gotPropertyContext(context: typeof UMB_PROPERTY_CONTEXT.TYPE) { + this.observe( + context.dataPath, + (dataPath) => { + // Translate paths for content/settings: + this.#contentDataPathTranslator?.destroy(); + this.#settingsDataPathTranslator?.destroy(); + if (dataPath) { + // Set the data path for the local validation context: + this.#validationContext.setDataPath(dataPath + '.blocks'); + + this.#contentDataPathTranslator = new UmbBlockElementDataValidationPathTranslator(this, 'contentData'); + this.#settingsDataPathTranslator = new UmbBlockElementDataValidationPathTranslator(this, 'settingsData'); + } + }, + 'observeDataPath', + ); + + this.observe( + context?.alias, + (alias) => { + this.#managerContext.setPropertyAlias(alias); + }, + 'observePropertyAlias', + ); + + this.observe( + observeMultiple([ + this.#managerContext.layouts, + this.#managerContext.contents, + this.#managerContext.settings, + this.#managerContext.exposes, + ]), + ([layouts, contents, settings, exposes]) => { + if (layouts.length === 0) { + if (super.value?.markup === undefined) { + super.value = undefined; + } else { + super.value = { + ...super.value, + blocks: { + layout: {}, + contentData: [], + settingsData: [], + expose: [], + }, + }; + } + } else { + super.value = { + markup: this._markup, + blocks: { + layout: { [UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS]: layouts }, + contentData: contents, + settingsData: settings, + expose: exposes, + }, + }; + } + + // If we don't have a value set from the outside or an internal value, we don't want to set the value. + // This is added to prevent the block list from setting an empty value on startup. + if (super.value?.markup === undefined) { + return; + } + + context.setValue(super.value); + }, + 'motherObserver', + ); } protected _filterUnusedBlocks(usedContentKeys: (string | null)[]) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/property-value-resolver/rte-block-value-resolver.api.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/property-value-resolver/rte-block-value-resolver.api.ts index b9d77a5d15..1a2b41cfdd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/property-value-resolver/rte-block-value-resolver.api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/property-value-resolver/rte-block-value-resolver.api.ts @@ -1,4 +1,4 @@ -import type { UmbPropertyEditorUiValueType } from '../types.js'; +import type { UmbPropertyEditorRteValueType } from '../types.js'; import { UmbBlockValueResolver, type UmbBlockDataValueModel, @@ -6,9 +6,9 @@ import { } from '@umbraco-cms/backoffice/block'; import type { UmbElementValueModel } from '@umbraco-cms/backoffice/content'; -export class UmbRteBlockValueResolver extends UmbBlockValueResolver { +export class UmbRteBlockValueResolver extends UmbBlockValueResolver { async processValues( - property: UmbElementValueModel, + property: UmbElementValueModel, valuesCallback: (values: Array) => Promise | undefined>, ) { if (property.value) { @@ -24,7 +24,7 @@ export class UmbRteBlockValueResolver extends UmbBlockValueResolver, + property: UmbElementValueModel, variantsCallback: (values: Array) => Promise | undefined>, ) { if (property.value) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/types.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/types.ts index ebcf7bc8ab..15a7844db9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/types.ts @@ -1,7 +1,13 @@ import type { UmbBlockValueType } from '@umbraco-cms/backoffice/block'; import type { UmbBlockRteLayoutModel } from '@umbraco-cms/backoffice/block-rte'; -export interface UmbPropertyEditorUiValueType { +export interface UmbPropertyEditorRteValueType { markup: string; blocks: UmbBlockValueType; } + +/** + * @deprecated Use `UmbPropertyEditorRteValueType` instead, will be removed in v.17.0.0 + */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface UmbPropertyEditorUiValueType extends UmbPropertyEditorRteValueType {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/property-editor-ui-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/property-editor-ui-tiny-mce.element.ts index bbbf2d8071..5e796a3d85 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/property-editor-ui-tiny-mce.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/property-editor-ui-tiny-mce.element.ts @@ -38,21 +38,14 @@ export class UmbPropertyEditorUITinyMceElement extends UmbPropertyEditorUiRteEle blockElement.getAttribute(UMB_BLOCK_RTE_DATA_CONTENT_KEY), ); - this._filterUnusedBlocks(usedContentKeys); - - // Then get the content of the editor and update the value. - // maybe in this way doc.body.innerHTML; - - this._markup = markup; - - if (this.value) { - this.value = { - ...this.value, - markup: this._markup, + if (super.value) { + super.value = { + ...super.value, + markup: markup, }; } else { this.value = { - markup: this._markup, + markup: markup, blocks: { layout: {}, contentData: [], @@ -62,6 +55,9 @@ export class UmbPropertyEditorUITinyMceElement extends UmbPropertyEditorUiRteEle }; } + // lets run this one after we set the value, to make sure we don't reset the value. + this._filterUnusedBlocks(usedContentKeys); + this._fireChangeEvent(); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts index 620a816fec..03b4824fc7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts @@ -35,6 +35,16 @@ export class UmbInputTiptapElement extends UmbFormControlMixin this.requiredMessage ?? 'Value is required', + () => !!this.required && this.isEmpty(), + ); + } + protected override async firstUpdated() { await Promise.all([await this.#loadExtensions(), await this.#loadEditor()]); } @@ -131,6 +151,7 @@ export class UmbInputTiptapElement extends UmbFormControlMixin { this.#value = editor.getHTML(); + this._runValidators(); this.dispatchEvent(new UmbChangeEvent()); }, }); @@ -186,6 +207,15 @@ export class UmbInputTiptapElement extends UmbFormControlMixin | Map): void { + super.firstUpdated(_changedProperties); + + this.addFormControlElement(this.shadowRoot?.querySelector('umb-input-tiptap') as UmbInputTiptapElement); + } + #onChange(event: CustomEvent & { target: UmbInputTiptapElement }) { const tipTapElement = event.target; - const value = tipTapElement.value; + const markup = tipTapElement.value; // If we don't get any markup clear the property editor value. if (tipTapElement.isEmpty()) { @@ -28,24 +34,20 @@ export class UmbPropertyEditorUiTiptapElement extends UmbPropertyEditorUiRteElem /(?:)?<\/umb-rte-block(?:-inline)?>/gi, ); let blockElement: RegExpExecArray | null; - while ((blockElement = regex.exec(value)) !== null) { + while ((blockElement = regex.exec(markup)) !== null) { if (blockElement.groups?.key) { usedContentKeys.push(blockElement.groups.key); } } - this._filterUnusedBlocks(usedContentKeys); - - this._markup = value; - if (this.value) { this.value = { ...this.value, - markup: this._markup, + markup: markup, }; } else { this.value = { - markup: this._markup, + markup: markup, blocks: { layout: {}, contentData: [], @@ -55,6 +57,9 @@ export class UmbPropertyEditorUiTiptapElement extends UmbPropertyEditorUiRteElem }; } + // lets run this one after we set the value, to make sure we don't reset the value. + this._filterUnusedBlocks(usedContentKeys); + this._fireChangeEvent(); } @@ -64,6 +69,8 @@ export class UmbPropertyEditorUiTiptapElement extends UmbPropertyEditorUiRteElem .configuration=${this._config} .value=${this._markup} ?readonly=${this.readonly} + ?required=${this.mandatory} + ?required-message=${this.mandatoryMessage} @change=${this.#onChange}> `; }