Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
297d123
Initial implementation of non existing property editor
lauraneto Aug 22, 2025
9f00aef
Adjust `MissingPropertyEditor` to not require registering in Property…
lauraneto Aug 26, 2025
e83b3f6
Add `MissingPropertyEditor.name` back
lauraneto Aug 26, 2025
d42e367
Remove unused dependencies from DataTypeService
lauraneto Aug 26, 2025
c085672
Removed reference to non existing property
lauraneto Aug 26, 2025
c9d074f
Add parameterless constructor back to MissingPropertyEditor
lauraneto Aug 26, 2025
0cf91ab
Merge branch 'main' into v16/feature/non-existing-property-editor
lauraneto Aug 27, 2025
911fdd1
Add validation error on document open to property with missing editor
lauraneto Sep 1, 2025
0fe3af6
Update labels
lauraneto Sep 2, 2025
29d6922
Removed public editor alias const
lauraneto Sep 2, 2025
0d995b9
Update src/Umbraco.Web.UI.Client/src/packages/property-editors/missin…
lauraneto Sep 2, 2025
beda961
Add test that checks whether the new MissingPropertyEditor is returne…
lauraneto Sep 3, 2025
ef0f19b
Also check if the editor UI alias is correct in the test
lauraneto Sep 3, 2025
4101e07
Apply suggestions from code review
lauraneto Sep 3, 2025
b459589
Share property editor instances between properties
lauraneto Sep 3, 2025
b2f04f4
Only store missing property editors in memory in `ContentMapDefinitio…
lauraneto Sep 8, 2025
653fe65
Merge branch 'main' into v16/feature/non-existing-property-editor
lauraneto Sep 8, 2025
4877b89
Add value converter for the missing property editor to always return …
lauraneto Sep 8, 2025
4980648
Small improvements to code block
lauraneto Sep 8, 2025
1f68b6b
Adjust property validation to accept missing property editors
lauraneto Sep 8, 2025
f927cc7
Return the current value when trying to update a property with a miss…
lauraneto Sep 8, 2025
30acdee
Fix failing unit tests
lauraneto Sep 8, 2025
9368ff5
Small fix
lauraneto Sep 8, 2025
1d15238
Add unit test
lauraneto Sep 8, 2025
ca875ae
Merge branch 'main' into v16/feature/non-existing-property-editor-all…
lauraneto Sep 15, 2025
2e757a4
Remove client validation
lauraneto Sep 16, 2025
c52c69f
UI adjustments
lauraneto Sep 16, 2025
44446e5
Merge branch 'main' into v16/feature/non-existing-property-editor-all…
lauraneto Sep 16, 2025
08a6c60
Adjustments from code review
lauraneto Sep 17, 2025
8f39e72
Adjust test
lauraneto Sep 17, 2025
7bec8b8
Merge branch 'main' into v16/feature/non-existing-property-editor-all…
lauraneto Sep 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/Umbraco.Core/Services/ContentEditingServiceBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -526,8 +526,12 @@ private void RemoveMissingProperties(ContentEditingModelBase contentEditingModel
// this should already have been validated by now, so it's OK to throw exceptions here
if (_propertyEditorCollection.TryGet(propertyType.PropertyEditorAlias, out IDataEditor? dataEditor) == false)
{
_logger.LogWarning("Unable to retrieve property value - no data editor found for property editor: {PropertyEditorAlias}", propertyType.PropertyEditorAlias);
return null;
_logger.LogWarning(
"Unable to find property editor {PropertyEditorAlias}, for property {PropertyAlias}. Leaving property value unchanged.",
propertyType.PropertyEditorAlias,
propertyType.Alias);

return content.GetValue(propertyType.Alias, culture, segment);
}

IDataValueEditor dataValueEditor = dataEditor.GetValueEditor();
Expand Down
7 changes: 3 additions & 4 deletions src/Umbraco.Core/Services/PropertyValidationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class PropertyValidationService : IPropertyValidationService
private readonly ILanguageService _languageService;
private readonly ContentSettings _contentSettings;

[Obsolete("Use the constructor that accepts ILanguageService and ContentSettings options. Will be removed in V17.")]
[Obsolete("Use the non-obsolete constructor. Will be removed in V17.")]
Comment thread
lauraneto marked this conversation as resolved.
public PropertyValidationService(
PropertyEditorCollection propertyEditors,
IDataTypeService dataTypeService,
Expand Down Expand Up @@ -76,10 +76,9 @@ public IEnumerable<ValidationResult> ValidatePropertyValue(
}

IDataEditor? dataEditor = GetDataEditor(propertyType);
if (dataEditor == null)
if (dataEditor is null)
{
throw new InvalidOperationException("No property editor found by alias " +
propertyType.PropertyEditorAlias);
return [];
}

// only validate culture invariant properties if
Expand Down
11 changes: 9 additions & 2 deletions src/Umbraco.Web.UI.Client/src/assets/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2834,10 +2834,17 @@ export default {
resetUrlLabel: 'Reset',
},
missingEditor: {
title: 'This property type is no longer available.',
description:
'<p><strong>Error!</strong> This property type is no longer available. Please reach out to your administrator.</p>',
"Don't worry, your content is safe and publishing this document won't overwrite it or remove it.<br/>Please contact your site administrator to resolve this issue.",
detailsTitle: 'Additional details',
detailsDescription:
'<p>This property type is no longer available.<br/>Please contact your administrator so they can either delete this property or restore the property type.</p><p><strong>Data:</strong></p>',
"To resolve this you should either restore the property editor, change the property to use a supported data type or remove the property if it's no longer needed.",
detailsDataType: 'Data type',
detailsPropertyEditor: 'Property editor',
detailsData: 'Data',
detailsHide: 'Hide details',
detailsShow: 'Show details',
},
uiCulture: {
ar: 'العربية',
Expand Down
11 changes: 9 additions & 2 deletions src/Umbraco.Web.UI.Client/src/assets/lang/pt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2832,9 +2832,16 @@ export default {
resetUrlLabel: 'Redefinir',
},
missingEditor: {
title: 'Este tipo de propriedade já não se encontra disponível.',
description:
'<p><strong>Erro!</strong> Este tipo de propriedade já não se encontra disponível. Por favor, contacte o administrador.</p>',
'Não se preocupe, o seu conteúdo está seguro e a publicação deste documento não o substituirá nem removerá.<br/>Entre em contacto com o administrador do site para resolver o problema.',
detailsTitle: 'Detalhes adicionais',
detailsDescription:
'<p>Este tipo de propriedade já não se encontra disponível.<br/>Por favor, contacte o administrador para que ele possa apagar a propriedade ou restaurar o tipo de propriedade.</p><p><strong>Dados:</strong></p>',
'Para resolver o problema, deverá ou restaurar o editor de propriedades, ou alterar a propriedade para usar um tipo de dados compatível ou remover a propriedade se ela não for mais necessária.',
detailsDataType: 'Tipo de dados',
detailsPropertyEditor: 'Editor de propriedades',
detailsData: 'Dados',
detailsHide: 'Esconder detalhes',
detailsShow: 'Mostrar detalhes',
},
} as UmbLocalizationDictionary;
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { manifests as modalManifests } from './modal/manifests.js';

export const manifests: Array<UmbExtensionManifest> = [
{
type: 'propertyEditorUi',
Expand All @@ -14,5 +12,4 @@ export const manifests: Array<UmbExtensionManifest> = [
supportsReadOnly: true,
},
},
...modalManifests,
];

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,50 +1,129 @@
import { UMB_MISSING_PROPERTY_EDITOR_MODAL } from './modal/missing-editor-modal.token.js';
import { customElement, html } from '@umbraco-cms/backoffice/external/lit';
import { css, customElement, html, nothing, property, query, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor';
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
import { UMB_PROPERTY_TYPE_BASED_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/content';
import { UmbDataTypeDetailRepository, type UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type';
import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';

/**
* @element umb-property-editor-ui-missing
*/
@customElement('umb-property-editor-ui-missing')
export class UmbPropertyEditorUIMissingElement
extends UmbFormControlMixin<unknown, typeof UmbLitElement>(UmbLitElement, undefined)
implements UmbPropertyEditorUiElement
{
export class UmbPropertyEditorUIMissingElement extends UmbLitElement implements UmbPropertyEditorUiElement {
@property()
value = '';

@state()
private _expanded = false;

@query('#details')
focalPointElement!: HTMLElement;

private _dataTypeDetailModel?: UmbDataTypeDetailModel | undefined;
private _dataTypeDetailRepository = new UmbDataTypeDetailRepository(this);

constructor() {
super();

this.addValidator(
'customError',
() => this.localize.term('errors_propertyHasErrors'),
() => true,
);

this.pristine = false;
this.consumeContext(UMB_PROPERTY_TYPE_BASED_PROPERTY_CONTEXT, (propertyContext) => {
if (!propertyContext?.dataType) return;
this.observe(propertyContext.dataType, (dt) => {
if (!dt?.unique) return;
this._updateEditorAlias(dt);
});
});
}

async #onDetails(event: Event) {
event.stopPropagation();
private async _updateEditorAlias(dataType: UmbPropertyTypeModel['dataType']) {
this.observe(await this._dataTypeDetailRepository.byUnique(dataType.unique), (dataType) => {
this._dataTypeDetailModel = dataType;
});
}

await umbOpenModal(this, UMB_MISSING_PROPERTY_EDITOR_MODAL, {
data: {
// If the value is an object, we stringify it to make sure we can display it properly.
// If it's a primitive value, we just convert it to string.
value: typeof this.value === 'object' ? JSON.stringify(this.value, null, 2) : String(this.value),
},
}).catch(() => undefined);
async #onDetails() {
this._expanded = !this._expanded;
if (this._expanded) {
await this.updateComplete;
this.focalPointElement?.focus();
}
}

override render() {
return html`<umb-localize key="missingEditor_description"></umb-localize>
return html`<uui-box id="info">
<div slot="headline">
<uui-icon id="alert" name="alert"></uui-icon>${this.localize.term('missingEditor_title')}
</div>
<div id="content">
<umb-localize key="missingEditor_description"></umb-localize>
${this._expanded ? this._renderDetails() : nothing}
</div>

<uui-button
id="details-button"
look="secondary"
label=${this.localize.term('general_details')}
@click=${this.#onDetails}></uui-button>`;
compact
label="${this.localize.term(this._expanded ? 'missingEditor_detailsHide' : 'missingEditor_detailsShow')}"
@click=${this.#onDetails}>
<span>${this.localize.term(this._expanded ? 'missingEditor_detailsHide' : 'missingEditor_detailsShow')}</span
><uui-symbol-expand id="expand-symbol" .open=${this._expanded}></uui-symbol-expand>
</uui-button>
</uui-box>`;
}

private _renderDetails() {
return html` <div id="details" tabindex="0">
<umb-localize id="details-title" key="missingEditor_detailsTitle"></umb-localize>
<p>
<umb-localize key="missingEditor_detailsDescription"></umb-localize>
</p>
<p>
<strong><umb-localize key="missingEditor_detailsDataType"></umb-localize></strong>:
<code>${this._dataTypeDetailModel?.name}</code><br />
<strong><umb-localize key="missingEditor_detailsPropertyEditor"></umb-localize></strong>:
<code>${this._dataTypeDetailModel?.editorAlias}</code>
</p>
<umb-code-block id="codeblock" copy language="${this.localize.term('missingEditor_detailsData')}"
>${typeof this.value === 'object' ? JSON.stringify(this.value, null, 2) : String(this.value)}</umb-code-block
>
</div>`;
}

static override styles = [
css`
:host {
display: flex;
flex-direction: column;
gap: var(--uui-size-space-3);
--uui-box-default-padding: 0;
}
#content {
padding: var(--uui-size-space-5);
padding-bottom: var(--uui-size-space-3);
}
#alert {
padding-right: var(--uui-size-space-2);
}
#details-button {
float: right;
}
#details {
margin-top: var(--uui-size-space-5);
}
#details-title {
font-weight: 800;
}
#expand-symbol {
transform: rotate(90deg);
}
#expand-symbol[open] {
transform: rotate(180deg);
}
#codeblock {
max-height: 400px;
display: flex;
flex-direction: column;
}
`,
];
}

export default UmbPropertyEditorUIMissingElement;
Expand Down
Loading
Loading