Merge branch 'v15/dev' into v16/merge-from-15

# Conflicts:
#	src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheController.cs
#	src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs
#	src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs
#	src/Umbraco.Core/Services/ContentEditingService.cs
#	src/Umbraco.Core/Services/DataTypeService.cs
#	src/Umbraco.Core/Services/IContentEditingService.cs
#	src/Umbraco.Core/Services/IDataTypeService.cs
#	src/Umbraco.Core/Services/ITrackedReferencesService.cs
#	src/Umbraco.Core/Services/RelationService.cs
#	src/Umbraco.Core/Services/TrackedReferencesService.cs
#	src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexHandleContentTypeChanges.cs
#	src/Umbraco.Infrastructure/Examine/DeliveryApiIndexingHandler.cs
#	src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs
#	src/Umbraco.Web.UI.Client/src/external/backend-api/src/sdk.gen.ts
#	src/Umbraco.Web.UI.Client/src/mocks/data/document-blueprint/document-blueprint.data.ts
#	src/Umbraco.Web.UI.Client/src/mocks/data/document/document.db.ts
#	src/Umbraco.Web.UI.Client/src/packages/core/router/modal-registration/modal-route-registration.controller.ts
#	src/Umbraco.Web.UI.Client/src/packages/core/router/route/route.context.ts
#	src/Umbraco.Web.UI.Client/src/packages/core/router/route/route.interface.ts
#	src/Umbraco.Web.UI.Client/src/packages/core/router/route/router-slot.element.ts
#	src/Umbraco.Web.UI.Client/src/packages/core/router/router-slot/model.ts
#	src/Umbraco.Web.UI.Client/src/packages/data-type/reference/repository/data-type-reference.server.data.ts
#	src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.server.data-source.ts
#	src/Umbraco.Web.UI.Client/src/packages/documents/documents/rollback/entity-action/rollback.action.ts
#	tests/Umbraco.Tests.AcceptanceTest/package-lock.json
#	tests/Umbraco.Tests.AcceptanceTest/package.json
#	tests/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs
#	tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TemporaryFileServiceTests.cs
This commit is contained in:
Andy Butland
2025-04-09 22:05:59 +02:00
91 changed files with 2817 additions and 368 deletions

View File

@@ -1,10 +1,10 @@
import { html, customElement, css, state, when } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UMB_VALIDATION_CONTEXT, umbBindToValidation, UmbValidationContext } from '@umbraco-cms/backoffice/validation';
import type { UmbValidationMessage } from 'src/packages/core/validation/context/validation-messages.manager';
@customElement('umb-example-validation-context-dashboard')
export class UmbExampleValidationContextDashboard extends UmbLitElement {
export class UmbExampleValidationContextDashboardElement extends UmbLitElement {
readonly validation = new UmbValidationContext(this);
@state()
@@ -20,7 +20,7 @@ export class UmbExampleValidationContextDashboard extends UmbLitElement {
country = '';
@state()
messages? : any[]
messages?: UmbValidationMessage[];
@state()
totalErrorCount = 0;
@@ -32,87 +32,101 @@ export class UmbExampleValidationContextDashboard extends UmbLitElement {
tab2ErrorCount = 0;
@state()
tab = "1";
tab = '1';
constructor() {
super();
this.consumeContext(UMB_VALIDATION_CONTEXT,(validationContext)=>{
this.observe(validationContext.messages.messages,(messages)=>{
this.messages = messages;
},'observeValidationMessages')
this.consumeContext(UMB_VALIDATION_CONTEXT, (validationContext) => {
this.observe(
validationContext.messages.messages,
(messages) => {
this.messages = messages;
},
'observeValidationMessages',
);
// Observe all errors
this.validation.messages.messagesOfPathAndDescendant('$.form').subscribe((value)=>{
this.totalErrorCount = [...new Set(value.map(x=>x.path))].length;
this.validation.messages.messagesOfPathAndDescendant('$.form').subscribe((value) => {
this.totalErrorCount = [...new Set(value.map((x) => x.path))].length;
});
// Observe errors for tab1, note that we only use part of the full JSONPath
this.validation.messages.messagesOfPathAndDescendant('$.form.tab1').subscribe((value)=>{
this.tab1ErrorCount = [...new Set(value.map(x=>x.path))].length;
this.validation.messages.messagesOfPathAndDescendant('$.form.tab1').subscribe((value) => {
this.tab1ErrorCount = [...new Set(value.map((x) => x.path))].length;
});
// Observe errors for tab2, note that we only use part of the full JSONPath
this.validation.messages.messagesOfPathAndDescendant('$.form.tab2').subscribe((value)=>{
this.tab2ErrorCount = [...new Set(value.map(x=>x.path))].length;
this.validation.messages.messagesOfPathAndDescendant('$.form.tab2').subscribe((value) => {
this.tab2ErrorCount = [...new Set(value.map((x) => x.path))].length;
});
});
});
}
#onTabChange(e:Event) {
#onTabChange(e: Event) {
this.tab = (e.target as HTMLElement).getAttribute('data-tab') as string;
}
#handleSave() {
// fake server validation-errors for all fields
if(this.name == '')
this.validation.messages.addMessage('server','$.form.tab1.name','Name server-error message','4875e113-cd0c-4c57-ac92-53d677ba31ec');
if(this.email == '')
this.validation.messages.addMessage('server','$.form.tab1.email','Email server-error message','a47e287b-4ce6-4e8b-8e05-614e7cec1a2a');
if(this.city == '')
this.validation.messages.addMessage('server','$.form.tab2.city','City server-error message','8dfc2f15-fb9a-463b-bcec-2c5d3ba2d07d');
if(this.country == '')
this.validation.messages.addMessage('server','$.form.tab2.country','Country server-error message','d98624f6-82a2-4e94-822a-776b44b01495');
if (this.name == '')
this.validation.messages.addMessage(
'server',
'$.form.tab1.name',
'Name server-error message',
'4875e113-cd0c-4c57-ac92-53d677ba31ec',
);
if (this.email == '')
this.validation.messages.addMessage(
'server',
'$.form.tab1.email',
'Email server-error message',
'a47e287b-4ce6-4e8b-8e05-614e7cec1a2a',
);
if (this.city == '')
this.validation.messages.addMessage(
'server',
'$.form.tab2.city',
'City server-error message',
'8dfc2f15-fb9a-463b-bcec-2c5d3ba2d07d',
);
if (this.country == '')
this.validation.messages.addMessage(
'server',
'$.form.tab2.country',
'Country server-error message',
'd98624f6-82a2-4e94-822a-776b44b01495',
);
}
override render() {
return html`
<uui-box>
This is a demo of how the Validation Context can be used to validate a form with multiple steps. Start typing in the form or press Save to trigger validation.
<hr/>
This is a demo of how the Validation Context can be used to validate a form with multiple steps. Start typing in
the form or press Save to trigger validation.
<hr />
Total errors: ${this.totalErrorCount}
<hr/>
<hr />
<uui-tab-group @click=${this.#onTabChange}>
<uui-tab ?active=${this.tab == '1'} data-tab="1">
<uui-tab ?active=${this.tab == '1'} data-tab="1">
Tab 1
${when(this.tab1ErrorCount,()=>html`
<uui-badge color="danger">${this.tab1ErrorCount}</uui-badge>
`)}
${when(this.tab1ErrorCount, () => html` <uui-badge color="invalid">${this.tab1ErrorCount}</uui-badge> `)}
</uui-tab>
<uui-tab ?active=${this.tab == '2'} data-tab="2">
<uui-tab ?active=${this.tab == '2'} data-tab="2">
Tab 2
${when(this.tab2ErrorCount,()=>html`
<uui-badge color="danger">${this.tab2ErrorCount}</uui-badge>
`)}
${when(this.tab2ErrorCount, () => html` <uui-badge color="invalid">${this.tab2ErrorCount}</uui-badge> `)}
</uui-tab>
</uui-tab-group>
</uui-tab-group>
${when(this.tab=='1',()=>html`
${this.#renderTab1()}
`)}
${when(this.tab=='2',()=>html`
${this.#renderTab2()}
`)}
${when(this.tab == '1', () => html` ${this.#renderTab1()} `)}
${when(this.tab == '2', () => html` ${this.#renderTab2()} `)}
<uui-button look="primary" color="positive" @click=${this.#handleSave}>Save</uui-button>
<hr/>
<hr />
<h3>Validation Context Messages</h3>
<pre>${JSON.stringify(this.messages ?? [],null,3)}</pre>
<pre>${JSON.stringify(this.messages ?? [], null, 3)}</pre>
</uui-box>
`
`;
}
#renderTab1() {
@@ -120,28 +134,28 @@ export class UmbExampleValidationContextDashboard extends UmbLitElement {
<uui-form>
<form>
<div>
<label>Name</label>
<uui-form-validation-message>
<uui-input
type="text"
.value=${this.name}
@input=${(e: InputEvent)=>this.name = (e.target as HTMLInputElement).value}
${umbBindToValidation(this,'$.form.tab1.name',this.name)}
required></uui-input>
</uui-form-validation-message>
<label>Name</label>
<uui-form-validation-message>
<uui-input
type="text"
.value=${this.name}
@input=${(e: InputEvent) => (this.name = (e.target as HTMLInputElement).value)}
${umbBindToValidation(this, '$.form.tab1.name', this.name)}
required></uui-input>
</uui-form-validation-message>
</div>
<label>E-mail</label>
<uui-form-validation-message>
<uui-input
type="email"
.value=${this.email}
@input=${(e: InputEvent)=>this.email = (e.target as HTMLInputElement).value}
${umbBindToValidation(this,'$.form.tab1.email',this.email)}
@input=${(e: InputEvent) => (this.email = (e.target as HTMLInputElement).value)}
${umbBindToValidation(this, '$.form.tab1.email', this.email)}
required></uui-input>
</uui-form-validation-message>
</form>
</uui-form>
`
`;
}
#renderTab2() {
@@ -149,71 +163,68 @@ export class UmbExampleValidationContextDashboard extends UmbLitElement {
<uui-form>
<form>
<div>
<label>City</label>
<uui-form-validation-message>
<uui-input
type="text"
.value=${this.city}
@input=${(e: InputEvent)=>this.city = (e.target as HTMLInputElement).value}
${umbBindToValidation(this,'$.form.tab2.city',this.city)}
required></uui-input>
</uui-form-validation-message>
<label>City</label>
<uui-form-validation-message>
<uui-input
type="text"
.value=${this.city}
@input=${(e: InputEvent) => (this.city = (e.target as HTMLInputElement).value)}
${umbBindToValidation(this, '$.form.tab2.city', this.city)}
required></uui-input>
</uui-form-validation-message>
</div>
<label>Country</label>
<uui-form-validation-message>
<uui-input
type="text"
.value=${this.country}
@input=${(e: InputEvent)=>this.country = (e.target as HTMLInputElement).value}
${umbBindToValidation(this,'$.form.tab2.country',this.country)}
@input=${(e: InputEvent) => (this.country = (e.target as HTMLInputElement).value)}
${umbBindToValidation(this, '$.form.tab2.country', this.country)}
required></uui-input>
</uui-form-validation-message>
</form>
</uui-form>
`
`;
}
static override styles = [
css`
uui-badge {
top: 0;
right: 0;
font-size: 10px;
min-width: 17px;
min-height: 17px;
}
label {
display: block;
}
static override styles = [css`
uui-box {
margin: 20px;
}
uui-badge {
top:0;
right:0;
font-size:10px;
min-width:17px;
min-height:17px;
uui-button {
margin-top: 1rem;
}
}
label {
display:block;
}
uui-box {
margin:20px;
}
uui-button {
margin-top:1rem;
}
pre {
text-align:left;
padding:10px;
border:1px dotted #6f6f6f;
background: #f2f2f2;
font-size: 11px;
line-height: 1.3em;
}
`]
pre {
text-align: left;
padding: 10px;
border: 1px dotted #6f6f6f;
background: #f2f2f2;
font-size: 11px;
line-height: 1.3em;
}
`,
];
}
export default UmbExampleValidationContextDashboard;
export default UmbExampleValidationContextDashboardElement;
declare global {
interface HTMLElementTagNameMap {
'umb-example-validation-context-dashboard': UmbExampleValidationContextDashboard;
'umb-example-validation-context-dashboard': UmbExampleValidationContextDashboardElement;
}
}