From fd2779aa9a045aef30e41b230ba45abfe2ff844d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 23 Aug 2024 21:50:31 +0200 Subject: [PATCH 01/47] extension slot single mode + tests + block impl --- .../base-extensions-initializer.controller.ts | 2 - .../block-grid-entry.element.ts | 1 + .../block-list-entry.element.ts | 1 + .../block-rte-entry.element.ts | 1 + .../extension-slot/extension-slot.element.ts | 21 ++++++---- .../extension-slot/extension-slot.test.ts | 42 ++++++++++++++++++- .../extension-with-api-slot.element.ts | 16 ++++--- 7 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extensions-initializer.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extensions-initializer.controller.ts index 8a29e63be5..f4e1b9d4fc 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extensions-initializer.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extensions-initializer.controller.ts @@ -111,8 +111,6 @@ export abstract class UmbBaseExtensionsInitializer< manifests.forEach((manifest) => { const existing = this._extensions.find((x) => x.alias === manifest.alias); if (!existing) { - // Idea: could be abstracted into a createController method, so we can override it in a subclass. - // (This should be enough to be able to create a element extension controller instead.) this._extensions.push(this._createController(manifest)); } }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts index 4778221731..6258e3ed05 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts @@ -343,6 +343,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper default-element="umb-block-grid-block" .props=${this._blockViewProps} .filter=${this.#extensionSlotFilterMethod} + single >${this._inlineEditingMode ? this.#renderInlineEditBlock() : this.#renderRefBlock()} diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts index 6433e33864..4beee2607a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts @@ -248,6 +248,7 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper default-element=${this._inlineEditingMode ? 'umb-inline-list-block' : 'umb-ref-list-block'} .props=${this._blockViewProps} .filter=${this.#extensionSlotFilterMethod} + single >${this._inlineEditingMode ? this.#renderInlineBlock() : this.#renderRefBlock()} diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts index 8836730be1..3ca6afa49d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts @@ -151,6 +151,7 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert type="blockEditorCustomView" default-element=${'umb-ref-rte-block'} .props=${this._blockViewProps} + single >${this.#renderRefBlock()} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-slot/extension-slot.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-slot/extension-slot.element.ts index a97efe397b..5f610e076c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-slot/extension-slot.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-slot/extension-slot.element.ts @@ -15,16 +15,20 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; * @augments {UmbLitElement} */ +// TODO: Refactor extension-slot and extension-with-api slot. // TODO: Fire change event. // TODO: Make property that reveals the amount of displayed/permitted extensions. @customElement('umb-extension-slot') export class UmbExtensionSlotElement extends UmbLitElement { #attached = false; - #extensionsController?: UmbExtensionsElementInitializer; + #extensionsController?: UmbExtensionsElementInitializer | UmbExtensionElementInitializer; @state() private _permitted?: Array; + @property({ type: Boolean }) + single?: boolean; + /** * The type or types of extensions to render. * @type {string | string[]} @@ -77,7 +81,6 @@ export class UmbExtensionSlotElement extends UmbLitElement { return this.#props; } set props(newVal: Record | undefined) { - // TODO, compare changes since last time. only reset the ones that changed. This might be better done by the controller is self: this.#props = newVal; if (this.#extensionsController) { this.#extensionsController.properties = newVal; @@ -88,7 +91,7 @@ export class UmbExtensionSlotElement extends UmbLitElement { @property({ type: String, attribute: 'default-element' }) public defaultElement?: string; - @property() + @property({ attribute: false }) public renderMethod?: ( extension: UmbExtensionElementInitializer, index: number, @@ -128,15 +131,17 @@ export class UmbExtensionSlotElement extends UmbLitElement { override render() { return this._permitted ? this._permitted.length > 0 - ? repeat( - this._permitted, - (ext) => ext.alias, - (ext, i) => (this.renderMethod ? this.renderMethod(ext, i) : ext.component), - ) + ? this.single + ? this.#renderExtension(this._permitted[0], 0) + : repeat(this._permitted, (ext) => ext.alias, this.#renderExtension) : html`` : ''; } + #renderExtension = (ext: UmbExtensionElementInitializer, i: number) => { + return this.renderMethod ? this.renderMethod(ext, i) : ext.component; + }; + static override styles = css` :host { display: contents; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-slot/extension-slot.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-slot/extension-slot.test.ts index c61f08f6b3..2c9defdf6d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-slot/extension-slot.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-slot/extension-slot.test.ts @@ -7,6 +7,8 @@ import type { UmbExtensionElementInitializer } from '@umbraco-cms/backoffice/ext @customElement('umb-test-extension-slot-manifest-element') class UmbTestExtensionSlotManifestElement extends HTMLElement {} +@customElement('umb-test-extension-slot-manifest-element-2') +class UmbTestExtensionSlotManifestElement2 extends HTMLElement {} function sleep(timeMs: number) { return new Promise((resolve) => { @@ -44,9 +46,21 @@ describe('UmbExtensionSlotElement', () => { expect(element).to.have.property('filter'); }); + it('has a props property', () => { + expect(element).to.have.property('props'); + }); + it('has a defaultElement property', () => { expect(element).to.have.property('defaultElement'); }); + + it('has a renderMethod property', () => { + expect(element).to.have.property('renderMethod'); + }); + + it('has a single property', () => { + expect(element).to.have.property('single'); + }); }); }); @@ -57,6 +71,17 @@ describe('UmbExtensionSlotElement', () => { alias: 'unit-test-ext-slot-element-manifest', name: 'unit-test-extension', elementName: 'umb-test-extension-slot-manifest-element', + weight: 200, // first is the heaviest and is therefor rendered first. + meta: { + pathname: 'test/test', + }, + }); + umbExtensionsRegistry.register({ + type: 'dashboard', + alias: 'unit-test-ext-slot-element-manifest-2', + name: 'unit-test-extension-2', + elementName: 'umb-test-extension-slot-manifest-element-2', + weight: 100, meta: { pathname: 'test/test', }, @@ -65,6 +90,7 @@ describe('UmbExtensionSlotElement', () => { afterEach(async () => { umbExtensionsRegistry.unregister('unit-test-ext-slot-element-manifest'); + umbExtensionsRegistry.unregister('unit-test-ext-slot-element-manifest-2'); }); it('renders a manifest element', async () => { @@ -73,18 +99,30 @@ describe('UmbExtensionSlotElement', () => { await sleep(20); expect(element.shadowRoot!.firstElementChild).to.be.instanceOf(UmbTestExtensionSlotManifestElement); + expect(element.shadowRoot!.childElementCount).to.be.equal(2); }); it('works with the filtering method', async () => { element = await fixture( html` x.alias === 'unit-test-ext-slot-element-manifest'}>`, + .filter=${(x: ManifestDashboard) => + x.alias === 'unit-test-ext-slot-element-manifest-2'}>`, ); await sleep(20); + expect(element.shadowRoot!.firstElementChild).to.be.instanceOf(UmbTestExtensionSlotManifestElement2); + expect(element.shadowRoot!.childElementCount).to.be.equal(1); + }); + + it('works with the single mode', async () => { + element = await fixture(html``); + + await sleep(20); + expect(element.shadowRoot!.firstElementChild).to.be.instanceOf(UmbTestExtensionSlotManifestElement); + expect(element.shadowRoot!.childElementCount).to.be.equal(1); }); it('use the render method', async () => { @@ -102,6 +140,7 @@ describe('UmbExtensionSlotElement', () => { expect(element.shadowRoot!.firstElementChild?.firstElementChild).to.be.instanceOf( UmbTestExtensionSlotManifestElement, ); + expect(element.shadowRoot!.childElementCount).to.be.equal(1); }); it('parses the props', async () => { @@ -117,6 +156,7 @@ describe('UmbExtensionSlotElement', () => { expect((element.shadowRoot!.firstElementChild as any).testProp).to.be.equal('fooBar'); expect(element.shadowRoot!.firstElementChild).to.be.instanceOf(UmbTestExtensionSlotManifestElement); + expect(element.shadowRoot!.childElementCount).to.be.equal(1); }); }); }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-with-api-slot/extension-with-api-slot.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-with-api-slot/extension-with-api-slot.element.ts index 1248a8b0a0..b9d73fac90 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-with-api-slot/extension-with-api-slot.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-with-api-slot/extension-with-api-slot.element.ts @@ -17,6 +17,7 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; * @augments {UmbLitElement} */ +// TODO: Refactor extension-slot and extension-with-api slot. // TODO: Fire change event. // TODO: Make property that reveals the amount of displayed/permitted extensions. @customElement('umb-extension-with-api-slot') @@ -27,6 +28,9 @@ export class UmbExtensionWithApiSlotElement extends UmbLitElement { @state() private _permitted?: Array; + @property({ type: Boolean }) + single?: boolean; + /** * The type or types of extensions to render. * @type {string | string[]} @@ -177,15 +181,17 @@ export class UmbExtensionWithApiSlotElement extends UmbLitElement { override render() { return this._permitted ? this._permitted.length > 0 - ? repeat( - this._permitted, - (ext) => ext.alias, - (ext, i) => (this.renderMethod ? this.renderMethod(ext, i) : ext.component), - ) + ? this.single + ? this.#renderExtension(this._permitted[0], 0) + : repeat(this._permitted, (ext) => ext.alias, this.#renderExtension) : html`` : ''; } + #renderExtension = (ext: UmbExtensionElementAndApiInitializer, i: number) => { + return this.renderMethod ? this.renderMethod(ext, i) : ext.component; + }; + static override styles = css` :host { display: contents; From 7a0313b3d324ab6eb4a2f784b54e3b9e06d2e778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 23 Aug 2024 21:50:42 +0200 Subject: [PATCH 02/47] mock data --- .../mocks/data/data-type/data-type.data.ts | 24 ++++++++- .../data/document-type/document-type.data.ts | 54 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts index 7aa782b8a2..f3c0ab8d4d 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts @@ -406,6 +406,27 @@ export const data: Array = [ }, ], }, + { + name: 'Dropdown Alignment Options', + id: 'dt-dropdown-align', + parent: null, + editorAlias: 'Umbraco.DropDown.Flexible', + editorUiAlias: 'Umb.PropertyEditorUi.Dropdown', + hasChildren: false, + isFolder: false, + isDeletable: true, + canIgnoreStartNodes: false, + values: [ + { + alias: 'multiple', + value: false, + }, + { + alias: 'items', + value: ['left', 'center', 'right'], + }, + ], + }, { name: 'Slider', id: 'dt-slider', @@ -587,6 +608,7 @@ export const data: Array = [ { label: 'Headline', contentElementTypeKey: 'headline-umbraco-demo-block-id', + settingsElementTypeKey: 'headline-settings-demo-block-id', backgroundColor: 'gold', editorSize: 'medium', icon: 'icon-edit', @@ -613,7 +635,7 @@ export const data: Array = [ }, { alias: 'useInlineEditingAsDefault', - value: true, + value: false, }, { alias: 'useLiveEditing', diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts index e8ef422e04..1011e3c467 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts @@ -1507,6 +1507,60 @@ export const data: Array = [ }, ], }, + { + allowedTemplates: [], + defaultTemplate: null, + id: 'headline-settings-demo-block-id', + alias: 'headlineSettingsUmbracoDemoBlock', + name: 'Headline', + description: null, + icon: 'icon-edit', + allowedAsRoot: true, + variesByCulture: false, + variesBySegment: false, + isElement: true, + hasChildren: false, + parent: { id: 'folder-umbraco-demo-blocks-id' }, + isFolder: false, + allowedDocumentTypes: [], + compositions: [], + cleanup: { + preventCleanup: false, + keepAllVersionsNewerThanDays: null, + keepLatestVersionPerDayForDays: null, + }, + properties: [ + { + id: 'block-alignment-id', + container: { id: 'settings-group-key' }, + alias: 'blockAlignment', + name: 'Block Alignment', + description: '', + dataType: { id: 'dt-dropdown-align' }, + variesByCulture: false, + variesBySegment: false, + sortOrder: 0, + validation: { + mandatory: false, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + ], + containers: [ + { + id: 'settings-group-key', + parent: null, + name: 'Settings', + type: 'Group', + sortOrder: 0, + }, + ], + }, { allowedTemplates: [], defaultTemplate: null, From 9ad3c5880863992d595df802852d611ff9e34acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 23 Aug 2024 21:50:52 +0200 Subject: [PATCH 03/47] example using settings --- .../examples/block-custom-view/block-custom-view.ts | 13 ++++++++++++- .../examples/block-custom-view/index.ts | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/examples/block-custom-view/block-custom-view.ts b/src/Umbraco.Web.UI.Client/examples/block-custom-view/block-custom-view.ts index 40b88d0ce9..e564f537a6 100644 --- a/src/Umbraco.Web.UI.Client/examples/block-custom-view/block-custom-view.ts +++ b/src/Umbraco.Web.UI.Client/examples/block-custom-view/block-custom-view.ts @@ -11,11 +11,15 @@ export class ExampleBlockCustomView extends UmbElementMixin(LitElement) implemen @property({ attribute: false }) content?: UmbBlockDataType; + @property({ attribute: false }) + settings?: UmbBlockDataType; + override render() { return html` -
+
My Custom View

Headline: ${this.content?.headline}

+

Alignment: ${this.settings?.blockAlignment}

`; } @@ -31,6 +35,13 @@ export class ExampleBlockCustomView extends UmbElementMixin(LitElement) implemen border-radius: 9px; padding: 12px; } + + .align-center { + text-align: center; + } + .align-right { + text-align: right; + } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/examples/block-custom-view/index.ts b/src/Umbraco.Web.UI.Client/examples/block-custom-view/index.ts index e50355a08b..da81e169ac 100644 --- a/src/Umbraco.Web.UI.Client/examples/block-custom-view/index.ts +++ b/src/Umbraco.Web.UI.Client/examples/block-custom-view/index.ts @@ -7,6 +7,6 @@ export const manifests: Array = [ name: 'Block Editor Custom View Test', element: () => import('./block-custom-view.js'), forContentTypeAlias: 'headlineUmbracoDemoBlock', - forBlockEditor: 'block-grid', + forBlockEditor: 'block-list', }, ]; From 96945bb1f6802d3ec9234e35efc2e10b9444b128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Sat, 24 Aug 2024 21:32:23 +0200 Subject: [PATCH 04/47] implement localization of validation messages --- .../block-grid-entries.element.ts | 2 +- .../property-editor-ui-block-list.element.ts | 4 +- ...ultiple-color-picker-item-input.element.ts | 4 +- ...input-multiple-text-string-item.element.ts | 4 +- ...roperty-workspace-view-settings.element.ts | 12 +-- .../property-layout.element.ts | 4 +- .../form-validation-message.element.ts | 97 +++++++++++++++++++ .../src/packages/core/validation/index.ts | 3 +- .../validation/mixins/form-control.mixin.ts | 10 +- 9 files changed, 120 insertions(+), 20 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/validation/components/form-validation-message.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts index 8ecfe7f894..96b4fd3f00 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts @@ -284,7 +284,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen `, )}
- + ${this._canCreate ? this.#renderCreateButton() : nothing} `; } 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 9e263cf849..30aceb67a7 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 @@ -163,13 +163,13 @@ export class UmbPropertyEditorUIBlockListElement this.addValidator( 'rangeUnderflow', - () => this.localize.term('validation_entriesShort'), + () => '#validation_entriesShort', () => !!this._limitMin && this.#entriesContext.getLength() < this._limitMin, ); this.addValidator( 'rangeOverflow', - () => this.localize.term('validation_entriesExceed'), + () => '#validation_entriesExceed', () => !!this._limitMax && this.#entriesContext.getLength() > this._limitMax, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-item-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-item-input.element.ts index 75191cc44e..2513799e64 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-item-input.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-item-input.element.ts @@ -134,7 +134,7 @@ export class UmbMultipleColorPickerItemInputElement extends UUIFormControlMixin( override render() { //TODO: Using native input=color element instead of uui-color-picker due to its huge size and bad adaptability as a pop up return html` - +
${this.disabled || this.readonly ? nothing : html``}
@@ -183,7 +183,7 @@ export class UmbMultipleColorPickerItemInputElement extends UUIFormControlMixin( `, )}
- + `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string-item.element.ts index 9c3979f30d..9087d92a28 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string-item.element.ts @@ -79,7 +79,7 @@ export class UmbInputMultipleTextStringItemElement extends UUIFormControlMixin(U return html` ${this.disabled || this.readonly ? nothing : html``} - + - + ${when( !this.readonly, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/views/settings/property-workspace-view-settings.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/views/settings/property-workspace-view-settings.element.ts index e908214675..21f0d5d93b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/views/settings/property-workspace-view-settings.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/views/settings/property-workspace-view-settings.element.ts @@ -195,7 +195,7 @@ export class UmbPropertyTypeWorkspaceViewSettingsElement extends UmbLitElement i return html`
- + - - + + - +
- + - +
Validation diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-layout/property-layout.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-layout/property-layout.element.ts index 1724d318a1..e7e1b41dc5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-layout/property-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-layout/property-layout.element.ts @@ -81,9 +81,9 @@ export class UmbPropertyLayoutElement extends UmbLitElement {
- + - +
`; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/components/form-validation-message.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/components/form-validation-message.element.ts new file mode 100644 index 0000000000..38a4fb1181 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/components/form-validation-message.element.ts @@ -0,0 +1,97 @@ +import { UmbValidationInvalidEvent, UmbValidationValidEvent } from '../events/index.js'; +import type { UmbFormControlMixinInterface } from '../mixins/index.js'; +import { css, customElement, html, property, repeat, unsafeHTML } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +/** + * @description - Component for displaying one or more validation messages from UMB/UUI Form Control within the given scope. + * Notice: Only supports components that is build on the UMB / UUI FormControlMixing. + * @slot - for button contents + * @slot message - for extras in the messages container + * @see FormControlMixin + */ +@customElement('umb-form-validation-message') +export class UmbFormValidationMessageElement extends UmbLitElement { + /** + * Set the element containing Form Controls of interest. + * @type {string} + * @default + */ + @property({ reflect: false, attribute: true }) + public get for(): HTMLElement | string | null { + return this._for; + } + public set for(value: HTMLElement | string | null) { + let element = null; + if (typeof value === 'string') { + const scope = this.getRootNode(); + element = (scope as DocumentFragment)?.getElementById(value); + } else if (value instanceof HTMLElement) { + element = value; + } + const newScope = element ?? this; + const oldScope = this._for; + + if (oldScope === newScope) { + return; + } + if (oldScope !== null) { + oldScope.removeEventListener(UmbValidationInvalidEvent.TYPE, this.#onControlInvalid as EventListener); + oldScope.removeEventListener(UmbValidationValidEvent.TYPE, this.#onControlValid as EventListener); + } + this._for = newScope; + this._for.addEventListener(UmbValidationInvalidEvent.TYPE, this.#onControlInvalid as EventListener); + this._for.addEventListener(UmbValidationValidEvent.TYPE, this.#onControlValid as EventListener); + } + private _for: HTMLElement | null = null; + + constructor() { + super(); + if (this.for === null) { + this.for = this; + } + } + + private _messages = new Map, string>(); + + #onControlInvalid = async (e: UmbValidationInvalidEvent) => { + const ctrl = (e as any).composedPath()[0]; + if (ctrl.pristine === false) { + // Currently we only show message from components who does have the pristine property. (we only want to show messages from fields that are NOT pristine aka. that are dirty or in a from that has been submitted) + // Notice we use the localization controller here, this is different frm the UUI component which uses the same name. + this._messages.set(ctrl, this.localize.string(ctrl.validationMessage)); + } else { + this._messages.delete(ctrl); + } + this.requestUpdate(); + }; + + #onControlValid = (e: UmbValidationValidEvent) => { + const ctrl = (e as any).composedPath()[0]; + this._messages.delete(ctrl); + this.requestUpdate(); + }; + + override render() { + return html` + +
+ ${repeat(this._messages, (item) => html`
${unsafeHTML(item[1])}
`)} + +
+ `; + } + + static override styles = [ + css` + #messages { + color: var(--uui-color-danger-standalone); + } + `, + ]; +} +declare global { + interface HTMLElementTagNameMap { + 'umb-form-validation-message': UmbFormValidationMessageElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/index.ts index ecb51f74ac..4231347296 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/index.ts @@ -1,9 +1,10 @@ +export * from './components/form-validation-message.element.js'; export * from './const.js'; export * from './context/index.js'; export * from './controllers/index.js'; +export * from './directives/bind-to-validation.lit-directive.js'; export * from './events/index.js'; export * from './interfaces/index.js'; export * from './mixins/index.js'; export * from './translators/index.js'; export * from './utils/index.js'; -export * from './directives/bind-to-validation.lit-directive.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts index 4a634d197c..2732156f80 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts @@ -99,6 +99,7 @@ export declare abstract class UmbFormControlMixinElement * The mixin allows a custom element to participate in HTML forms. * @param {object} superClass - superclass to be extended. * @param defaultValue + * @returns {class} - The mixin class. * @mixin */ export function UmbFormControlMixin< @@ -172,7 +173,7 @@ export function UmbFormControlMixin< * Get internal form element. * This has to be implemented to provide a FormControl Element of choice for the given context. The element is used as anchor for validation-messages. * @function getFormElement - * @returns {HTMLElement | undefined | null} + * @returns {HTMLElement | undefined | null} - Returns the form element or undefined if not found. */ protected getFormElement(): HTMLElement | undefined | null { return this.#formCtrlElements.find((el) => el.validity.valid === false); @@ -181,7 +182,7 @@ export function UmbFormControlMixin< /** * Focus first element that is invalid. * @function focusFirstInvalidElement - * @returns {HTMLElement | undefined} + * @returns {HTMLElement | undefined} - Returns the first invalid element or undefined if no invalid elements are found. */ focusFirstInvalidElement() { const firstInvalid = this.#formCtrlElements.find((el) => el.validity.valid === false); @@ -219,6 +220,7 @@ export function UmbFormControlMixin< * @param {FlagTypes} flagKey the type of validation. * @param {method} getMessageMethod method to retrieve relevant message. Is executed every time the validator is re-executed. * @param {method} checkMethod method to determine if this validator should invalidate this form control. Return true if this should prevent submission. + * @returns {UmbFormControlValidatorConfig} - The added validator configuration. */ addValidator( flagKey: FlagTypes, @@ -252,7 +254,7 @@ export function UmbFormControlMixin< /** * @function addFormControlElement * @description Important notice if adding a native form control then ensure that its value and thereby validity is updated when value is changed from the outside. - * @param element {UmbNativeFormControlElement} - element to validate and include as part of this form association. + * @param {UmbNativeFormControlElement} element - element to validate and include as part of this form association. */ protected addFormControlElement(element: UmbNativeFormControlElement) { this.#formCtrlElements.push(element); @@ -275,7 +277,7 @@ export function UmbFormControlMixin< /** * @function setCustomValidity * @description Set custom validity state, set to empty string to remove the custom message. - * @param message {string} - The message to be shown + * @param {string} message - The message to be shown * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/setCustomValidity|HTMLObjectElement:setCustomValidity} */ protected setCustomValidity(message: string | null) { From d2b2a8efc20b738bcb1c4bb7484ac0ca0f07042d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Aug 2024 20:36:05 +0200 Subject: [PATCH 05/47] fix broken create block in workspace experience --- .../block/block-grid/context/block-grid-entries.context.ts | 1 + .../block/block-rte/context/block-rte-entries.context.ts | 1 + .../components/block-type-card/block-type-card.element.ts | 2 +- .../modals/block-catalogue/block-catalogue-modal.element.ts | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts index b40bf030ed..6cb5c0d2c1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts @@ -133,6 +133,7 @@ export class UmbBlockGridEntriesContext blockGroups: this._manager?.getBlockGroups() ?? [], openClipboard: routingInfo.view === 'clipboard', originData: { index: index, areaKey: this.#areaKey, parentUnique: this.#parentUnique }, + createBlockInWorkspace: true, }, }; }) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-entries.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-entries.context.ts index 41acf53cb6..ae7f551326 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-entries.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-entries.context.ts @@ -41,6 +41,7 @@ export class UmbBlockRteEntriesContext extends UmbBlockEntriesContext< blockGroups: [], openClipboard: routingInfo.view === 'clipboard', originData: {}, + createBlockInWorkspace: true, }, }; }) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/block-type-card/block-type-card.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/block-type-card/block-type-card.element.ts index 8f23bb3ffb..7acba0d8be 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/block-type-card/block-type-card.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/block-type-card/block-type-card.element.ts @@ -21,7 +21,7 @@ export class UmbBlockTypeCardElement extends UmbLitElement { (x) => x.unique, ); - @property({ type: String, attribute: false }) + @property({ type: String }) href?: string; @property({ type: String, attribute: false }) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts index 85c3a67e63..6d735b18f6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts @@ -140,7 +140,7 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< .backgroundColor=${block.backgroundColor} .contentElementTypeKey=${block.contentElementTypeKey} @open=${() => this.#chooseBlock(block.contentElementTypeKey)} - ?href=${this._workspacePath + .href=${this._workspacePath ? `${this._workspacePath}create/${block.contentElementTypeKey}` : undefined}> From 3c7efe447bb3f828edff7960045ac53375a7d82d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Aug 2024 22:04:50 +0200 Subject: [PATCH 06/47] consider mount of properties for create in workspace --- .../block/context/block-manager.context.ts | 4 +++ .../block-catalogue-modal.element.ts | 30 ++++++++++++++----- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts index d6be254013..0b3d26e95b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts @@ -148,6 +148,10 @@ export abstract class UmbBlockManagerContext< getContentTypeNameOf(contentTypeKey: string) { return this.#contentTypes.getValue().find((x) => x.unique === contentTypeKey)?.name; } + getContentTypeHasProperties(contentTypeKey: string) { + const properties = this.#contentTypes.getValue().find((x) => x.unique === contentTypeKey)?.properties; + return properties ? properties.length > 0 : false; + } blockTypeOf(contentTypeKey: string) { return this.#blockTypes.asObservablePart((source) => source.find((x) => x.contentElementTypeKey === contentTypeKey), diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts index 6d735b18f6..92de7b54d6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts @@ -1,6 +1,10 @@ import { UMB_BLOCK_WORKSPACE_MODAL } from '../../workspace/index.js'; import type { UmbBlockTypeGroup, UmbBlockTypeWithGroupKey } from '@umbraco-cms/backoffice/block-type'; -import type { UmbBlockCatalogueModalData, UmbBlockCatalogueModalValue } from '@umbraco-cms/backoffice/block'; +import { + UMB_BLOCK_MANAGER_CONTEXT, + type UmbBlockCatalogueModalData, + type UmbBlockCatalogueModalValue, +} from '@umbraco-cms/backoffice/block'; import { css, html, customElement, state, repeat, nothing } from '@umbraco-cms/backoffice/external/lit'; import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import { UMB_MODAL_CONTEXT, UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; @@ -14,8 +18,7 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< UmbBlockCatalogueModalData, UmbBlockCatalogueModalValue > { - // - private _search = ''; + #search = ''; private _groupedBlocks: Array<{ name?: string; blocks: Array }> = []; @@ -28,6 +31,9 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< @state() private _filtered: Array<{ name?: string; blocks: Array }> = []; + @state() + _manager?: typeof UMB_BLOCK_MANAGER_CONTEXT.TYPE; + constructor() { super(); @@ -49,6 +55,10 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< }); } }); + + this.consumeContext(UMB_BLOCK_MANAGER_CONTEXT, (manager) => { + this._manager = manager; + }); } override connectedCallback() { @@ -71,10 +81,10 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< } #updateFiltered() { - if (this._search.length === 0) { + if (this.#search.length === 0) { this._filtered = this._groupedBlocks; } else { - const search = this._search.toLowerCase(); + const search = this.#search.toLowerCase(); this._filtered = this._groupedBlocks.map((group) => { return { ...group, blocks: group.blocks.filter((block) => block.label?.toLocaleLowerCase().includes(search)) }; }); @@ -82,7 +92,7 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< } #onSearch(e: UUIInputEvent) { - this._search = e.target.value as string; + this.#search = e.target.value as string; this.#updateFiltered(); } @@ -98,7 +108,7 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< override render() { return html` - ${this.#renderViews()} ${this._openClipboard ? this.#renderClipboard() : this.#renderCreateEmpty()} + ${this.#renderViews()}${this.#renderMain()}
this.#chooseBlock(block.contentElementTypeKey)} - .href=${this._workspacePath + .href=${this._workspacePath && this._manager!.getContentTypeHasProperties(block.contentElementTypeKey) ? `${this._workspacePath}create/${block.contentElementTypeKey}` : undefined}> From f2f6ab90eb0a25ede8cf93f1ba7d147d6c10a06e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Aug 2024 22:12:28 +0200 Subject: [PATCH 07/47] grid onSubmit handler --- .../context/block-grid-entries.context.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts index 6cb5c0d2c1..a1d19fb1db 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts @@ -137,6 +137,26 @@ export class UmbBlockGridEntriesContext }, }; }) + .onSubmit(async (value, data) => { + if (value?.create && data) { + const created = await this.create( + value.create.contentElementTypeKey, + // We can parse an empty object, cause the rest will be filled in by others. + {} as any, + data.originData as UmbBlockGridWorkspaceOriginData, + ); + if (created) { + this.insert( + created.layout, + created.content, + created.settings, + data.originData as UmbBlockGridWorkspaceOriginData, + ); + } else { + throw new Error('Failed to create block'); + } + } + }) .observeRouteBuilder((routeBuilder) => { // TODO: Does it make any sense that this is a state? Check usage and confirm. [NL] this._catalogueRouteBuilderState.setValue(routeBuilder); From 2a2684430d86ac57890d4ed9b01f08edd392b88e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Aug 2024 22:12:43 +0200 Subject: [PATCH 08/47] rte updates for createInWorkspace update --- .../context/block-rte-entries.context.ts | 29 +++++++++++++++---- .../context/block-rte-manager.context.ts | 8 ++--- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-entries.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-entries.context.ts index ae7f551326..ac2501c80a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-entries.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-entries.context.ts @@ -4,7 +4,6 @@ import type { UmbBlockRteLayoutModel, UmbBlockRteTypeModel } from '../types.js'; import { UMB_BLOCK_RTE_WORKSPACE_MODAL, type UmbBlockRteWorkspaceOriginData, - type UmbBlockRteWorkspaceData, } from '../workspace/block-rte-workspace.modal-token.js'; import { UMB_BLOCK_RTE_MANAGER_CONTEXT } from './block-rte-manager.context-token.js'; import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; @@ -45,6 +44,26 @@ export class UmbBlockRteEntriesContext extends UmbBlockEntriesContext< }, }; }) + .onSubmit(async (value, data) => { + if (value?.create && data) { + const created = await this.create( + value.create.contentElementTypeKey, + // We can parse an empty object, cause the rest will be filled in by others. + {} as any, + data.originData as UmbBlockRteWorkspaceOriginData, + ); + if (created) { + this.insert( + created.layout, + created.content, + created.settings, + data.originData as UmbBlockRteWorkspaceOriginData, + ); + } else { + throw new Error('Failed to create block'); + } + } + }) .observeRouteBuilder((routeBuilder) => { this._catalogueRouteBuilderState.setValue(routeBuilder); }); @@ -115,10 +134,10 @@ export class UmbBlockRteEntriesContext extends UmbBlockEntriesContext< async create( contentElementTypeKey: string, partialLayoutEntry?: Omit, - modalData?: UmbBlockRteWorkspaceData, + originData?: UmbBlockRteWorkspaceOriginData, ) { await this._retrieveManager; - return this._manager?.create(contentElementTypeKey, partialLayoutEntry, modalData); + return this._manager?.create(contentElementTypeKey, partialLayoutEntry, originData); } // insert Block? @@ -127,10 +146,10 @@ export class UmbBlockRteEntriesContext extends UmbBlockEntriesContext< layoutEntry: UmbBlockRteLayoutModel, content: UmbBlockDataType, settings: UmbBlockDataType | undefined, - modalData: UmbBlockRteWorkspaceData, + originData: UmbBlockRteWorkspaceOriginData, ) { await this._retrieveManager; - return this._manager?.insert(layoutEntry, content, settings, modalData) ?? false; + return this._manager?.insert(layoutEntry, content, settings, originData) ?? false; } // create Block? diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts index ae67db7ac0..8f7e86d2a9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts @@ -1,5 +1,5 @@ import type { UmbBlockRteLayoutModel, UmbBlockRteTypeModel } from '../types.js'; -import type { UmbBlockRteWorkspaceData } from '../index.js'; +import type { UmbBlockRteWorkspaceOriginData } from '../index.js'; import type { UmbBlockDataType } from '../../block/types.js'; import type { Editor } from '@umbraco-cms/backoffice/external/tinymce'; import { UmbBlockManagerContext } from '@umbraco-cms/backoffice/block'; @@ -36,7 +36,7 @@ export class UmbBlockRteManagerContext< partialLayoutEntry?: Omit, // This property is used by some implementations, but not used in this. // eslint-disable-next-line @typescript-eslint/no-unused-vars - originData?: UmbBlockRteWorkspaceData, + originData?: UmbBlockRteWorkspaceOriginData, ) { const data = super.createBlockData(contentElementTypeKey, partialLayoutEntry); @@ -57,13 +57,13 @@ export class UmbBlockRteManagerContext< layoutEntry: BlockLayoutType, content: UmbBlockDataType, settings: UmbBlockDataType | undefined, - modalData: UmbBlockRteWorkspaceData, + originData: UmbBlockRteWorkspaceOriginData, ) { if (!this.#editor) return false; this._layouts.appendOne(layoutEntry); - this.insertBlockData(layoutEntry, content, settings, modalData); + this.insertBlockData(layoutEntry, content, settings, originData); if (layoutEntry.displayInline) { this.#editor.selection.setContent( From 84f222cf4830b58328c97c93c6a085a9f378f473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 26 Aug 2024 22:26:21 +0200 Subject: [PATCH 09/47] correct comment --- .../src/packages/core/property/property/property.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property/property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property/property.element.ts index f617dc0c4d..646e15b699 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property/property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property/property.element.ts @@ -332,7 +332,7 @@ export class UmbPropertyElement extends UmbLitElement { if ('checkValidity' in this._element) { const dataPath = this.dataPath; this.#controlValidator = new UmbFormControlValidator(this, this._element as any, dataPath); - // We trust blindly that the dataPath is available at this stage. [NL] + // We trust blindly that the dataPath will be present at this stage and not arrive later than this moment. [NL] if (dataPath) { this.#validationMessageBinder = new UmbBindServerValidationToFormControl( this, From 292f8214744cb1b2ab5542c1bd69e49d2c5c365c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Aug 2024 13:41:46 +0200 Subject: [PATCH 10/47] readme update --- .../src/packages/core/validation/README.md | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md b/src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md index 90bdcc7220..d034fb6ca4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md @@ -126,4 +126,28 @@ This fact enables a property to observe if there is any Message Paths that start Validators represent a component of the Validation to be considered, but it does not represent other messages of its path. To display messages from a given data-path, a Binder is needed. We bring a few to make this happen: -UmbBindServerValidationToFormControl +### UmbBindServerValidationToFormControl + +This binder takes a Form Control Element and a data-path. +The Data Path is a JSON Path defining where the data of this input is located in the model sent to the server. + +``` + this.#validationMessageBinder = new UmbBindServerValidationToFormControl( + this, + this.querySelector('#myInput"), + "$.values.[?(@.alias = 'my-input-alias')].value", + ); +``` + +Once the binder is initialized you need to keep it updated with the value your form control represents. Notice we do not recommend using events from the form control to notify about the changes. +Instead observe the value in of your data model. + +This example is just a dummy example of how that could look: +``` + this.observe( + this.#value, + (value) => { + this.#validationMessageBinder.value = value; + }, + ); +``` From f7ecaf1f4b3ef5f93e8daa7421fd028f6590fee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Aug 2024 13:42:07 +0200 Subject: [PATCH 11/47] imports --- .../property-editor-ui-block-grid.element.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 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 2f89ee2787..06756e16b9 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 @@ -1,3 +1,5 @@ +import { UmbBlockGridManagerContext } from '../../context/block-grid-manager.context.js'; +import { UMB_BLOCK_GRID_PROPERTY_EDITOR_ALIAS } from './manifests.js'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { html, customElement, property, state, css, type PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; @@ -7,8 +9,6 @@ import '../../components/block-grid-entries/index.js'; import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; -import { UmbBlockGridManagerContext } from '../../context/block-grid-manager.context.js'; -import { UMB_BLOCK_GRID_PROPERTY_EDITOR_ALIAS } from './manifests.js'; import type { UmbBlockTypeGroup } from '@umbraco-cms/backoffice/block-type'; import type { UmbBlockGridTypeModel, UmbBlockGridValueModel } from '@umbraco-cms/backoffice/block-grid'; @@ -44,7 +44,7 @@ export class UmbPropertyEditorUIBlockGridElement this.style.maxWidth = config.getValueByAlias('maxPropertyWidth') ?? ''; - //config.useLiveEditing, is covered by the EditorConfiguration of context. + //config.useLiveEditing, is covered by the EditorConfiguration of context. [NL] this.#context.setEditorConfiguration(config); } From 7ad400602381a5beb16c9be60226acd0e15efbb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Aug 2024 13:42:20 +0200 Subject: [PATCH 12/47] add validator to entries --- .../block-grid-entries/block-grid-entries.element.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts index 8ecfe7f894..9d91363042 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts @@ -11,6 +11,7 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import '../block-grid-entry/index.js'; import { UmbSorterController, type UmbSorterConfig, type resolvePlacementArgs } from '@umbraco-cms/backoffice/sorter'; import { + UmbBindServerValidationToFormControl, UmbFormControlMixin, UmbFormControlValidator, type UmbFormControlValidatorConfig, @@ -169,6 +170,12 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen constructor() { super(); + + // Currently there is no server validation for areas. So we can leave out the data path for it for now. [NL] + new UmbFormControlValidator(this, this); + + //new UmbBindServerValidationToFormControl(this, this, "$.values.[?(@.alias = 'my-input-alias')].value"); + this.observe( this.#context.layoutEntries, (layoutEntries) => { @@ -226,6 +233,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen #rangeUnderflowValidator?: UmbFormControlValidatorConfig; #rangeOverflowValidator?: UmbFormControlValidatorConfig; async #setupRangeValidation(rangeLimit: UmbNumberRangeValueType | undefined) { + console.log('setupRangeValidation', rangeLimit); if (this.#rangeUnderflowValidator) { this.removeValidator(this.#rangeUnderflowValidator); this.#rangeUnderflowValidator = undefined; From d06ca6faaa2a1454e9f74d8cc5e0a4f7c9d1dd26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Aug 2024 14:45:22 +0200 Subject: [PATCH 13/47] root validation --- .../block-grid-entries.element.ts | 17 ++--- .../context/block-grid-entries.context.ts | 63 +++++++++---------- .../property-editor-ui-block-grid.element.ts | 25 +++++++- 3 files changed, 65 insertions(+), 40 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts index 9d91363042..55ab4b266e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts @@ -11,7 +11,6 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import '../block-grid-entry/index.js'; import { UmbSorterController, type UmbSorterConfig, type resolvePlacementArgs } from '@umbraco-cms/backoffice/sorter'; import { - UmbBindServerValidationToFormControl, UmbFormControlMixin, UmbFormControlValidator, type UmbFormControlValidatorConfig, @@ -135,11 +134,20 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen }); #context = new UmbBlockGridEntriesContext(this); + #controlValidator?: UmbFormControlValidator; @property({ attribute: false }) public set areaKey(value: string | null | undefined) { this._areaKey = value; this.#context.setAreaKey(value ?? null); + this.#controlValidator?.destroy(); + if (this.areaKey) { + // Only when there is a area key we should create a validator, otherwise it is the root entries element, which is taking part of the Property Editor Form Control. [NL] + // Currently there is no server validation for areas. So we can leave out the data path for it for now. [NL] + this.#controlValidator = new UmbFormControlValidator(this, this); + + //new UmbBindServerValidationToFormControl(this, this, "$.values.[?(@.alias = 'my-input-alias')].value"); + } } public get areaKey(): string | null | undefined { return this._areaKey; @@ -171,11 +179,6 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen constructor() { super(); - // Currently there is no server validation for areas. So we can leave out the data path for it for now. [NL] - new UmbFormControlValidator(this, this); - - //new UmbBindServerValidationToFormControl(this, this, "$.values.[?(@.alias = 'my-input-alias')].value"); - this.observe( this.#context.layoutEntries, (layoutEntries) => { @@ -292,7 +295,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen `, )}
- + ${this._areaKey ? html` ` : nothing} ${this._canCreate ? this.#renderCreateButton() : nothing} `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts index a1d19fb1db..9117cd71c2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts @@ -184,8 +184,8 @@ export class UmbBlockGridEntriesContext protected _gotBlockManager() { if (!this._manager) return; - this.#getAllowedBlockTypes(); - this.#getRangeLimits(); + this.#setupAllowedBlockTypes(); + this.#setupRangeLimits(); this.observe( this._manager.propertyAlias, @@ -250,8 +250,8 @@ export class UmbBlockGridEntriesContext } this.removeUmbControllerByAlias('observeAreaType'); - this.#getAllowedBlockTypes(); - this.#getRangeLimits(); + this.#setupAllowedBlockTypes(); + this.#setupRangeLimits(); } else { if (!this.#parentEntry) return; @@ -294,22 +294,43 @@ export class UmbBlockGridEntriesContext hostEl.style.setProperty('--umb-block-grid--grid-columns', areaType?.columnSpan?.toString() ?? ''); hostEl.style.setProperty('--umb-block-grid--area-column-span', areaType?.columnSpan?.toString() ?? ''); hostEl.style.setProperty('--umb-block-grid--area-row-span', areaType?.rowSpan?.toString() ?? ''); - this.#getAllowedBlockTypes(); - this.#getRangeLimits(); + this.#setupAllowedBlockTypes(); + this.#setupRangeLimits(); }, 'observeAreaType', ); } } - #getAllowedBlockTypes() { + #setupAllowedBlockTypes() { if (!this._manager) return; this.#allowedBlockTypes.setValue(this.#retrieveAllowedElementTypes()); } - #getRangeLimits() { + #setupRangeLimits() { if (!this._manager) return; - const range = this.#retrieveRangeLimits(); - this.#rangeLimits.setValue(range); + //const range = this.#retrieveRangeLimits(); + if (this.#areaKey != null) { + this.removeUmbControllerByAlias('observeConfigurationRootLimits'); + // Area entries: + if (!this.#areaType) return undefined; + // No need to observe as this method is called every time the area is changed. + this.#rangeLimits.setValue({ + min: this.#areaType.minAllowed ?? 0, + max: this.#areaType.maxAllowed ?? Infinity, + }); + } else if (this.#areaKey === null) { + if (!this._manager) return undefined; + + this.observe( + this._manager.editorConfiguration, + (config) => { + const min = config?.getValueByAlias('validationLimit')?.min ?? 0; + const max = config?.getValueByAlias('validationLimit')?.max ?? Infinity; + this.#rangeLimits.setValue({ min, max }); + }, + 'observeConfigurationRootLimits', + ); + } } getPathForCreateBlock(index: number) { @@ -404,28 +425,6 @@ export class UmbBlockGridEntriesContext return []; } - /** - * @internal - * @returns an NumberRange of the min and max allowed items in the current area. Or undefined if not ready jet. - */ - #retrieveRangeLimits(): UmbNumberRangeValueType | undefined { - if (this.#areaKey != null) { - // Area entries: - if (!this.#areaType) return undefined; - - return { min: this.#areaType.minAllowed ?? 0, max: this.#areaType.maxAllowed ?? Infinity }; - } else if (this.#areaKey === null) { - if (!this._manager) return undefined; - - const config = this._manager.getEditorConfiguration(); - const min = config?.getValueByAlias('validationLimit')?.min ?? 0; - const max = config?.getValueByAlias('validationLimit')?.max ?? Infinity; - return { min, max }; - } - - return undefined; - } - /** * Check if given contentUdi is allowed in the current area. * @param contentUdi {string} - The contentUdi of the content to check. 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 06756e16b9..f62e9a4ffb 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 @@ -1,7 +1,15 @@ import { UmbBlockGridManagerContext } from '../../context/block-grid-manager.context.js'; import { UMB_BLOCK_GRID_PROPERTY_EDITOR_ALIAS } from './manifests.js'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { html, customElement, property, state, css, type PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; +import { + html, + customElement, + property, + state, + css, + type PropertyValueMap, + ref, +} from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; @@ -99,8 +107,23 @@ export class UmbPropertyEditorUIBlockGridElement }); } + #currentEntriesElement?: Element; + #gotRootEntriesElement(element: Element | undefined): void { + if (this.#currentEntriesElement === element) return; + if (this.#currentEntriesElement) { + throw new Error( + 'Cannot re-render root entries element because we currently do not support removing form control elements.', + ); + // TODO: If this become relevant we should implement this method: [NL] + //this.removeFormControlElement(this.#currentEntriesElement as any); + } + this.#currentEntriesElement = element; + this.addFormControlElement(element as any); + } + override render() { return html` `; } From e6c1f099d8f31bc501ade561f726c9f9315f2a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Aug 2024 15:19:52 +0200 Subject: [PATCH 14/47] clean up --- .../property-editor-ui-block-grid.element.ts | 5 ----- .../core/validation/mixins/form-control.mixin.ts | 11 +++++++---- 2 files changed, 7 insertions(+), 9 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 f62e9a4ffb..0fdd6f4cdb 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 @@ -39,11 +39,6 @@ export class UmbPropertyEditorUIBlockGridElement public set config(config: UmbPropertyEditorConfigCollection | undefined) { if (!config) return; - /*const validationLimit = config.getValueByAlias('validationLimit'); - - this.#limitMin = validationLimit?.min; - this.#limitMax = validationLimit?.max;*/ - const blocks = config.getValueByAlias>('blocks') ?? []; this.#context.setBlockTypes(blocks); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts index 4a634d197c..43d31ca6ca 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts @@ -100,6 +100,7 @@ export declare abstract class UmbFormControlMixinElement * @param {object} superClass - superclass to be extended. * @param defaultValue * @mixin + * @returns {Class} - The mixin class. */ export function UmbFormControlMixin< ValueType = FormData | FormDataEntryValue, @@ -217,8 +218,9 @@ export function UmbFormControlMixin< * ); * @function addValidator * @param {FlagTypes} flagKey the type of validation. - * @param {method} getMessageMethod method to retrieve relevant message. Is executed every time the validator is re-executed. - * @param {method} checkMethod method to determine if this validator should invalidate this form control. Return true if this should prevent submission. + * @param {function} getMessageMethod method to retrieve relevant message. Is executed every time the validator is re-executed. + * @param {function} checkMethod method to determine if this validator should invalidate this form control. Return true if this should prevent submission. + * @returns {UmbFormControlValidatorConfig} - The validator configuration. */ addValidator( flagKey: FlagTypes, @@ -252,7 +254,8 @@ export function UmbFormControlMixin< /** * @function addFormControlElement * @description Important notice if adding a native form control then ensure that its value and thereby validity is updated when value is changed from the outside. - * @param element {UmbNativeFormControlElement} - element to validate and include as part of this form association. + * @param {UmbNativeFormControlElement} element - element to validate and include as part of this form association. + * @returns {void} */ protected addFormControlElement(element: UmbNativeFormControlElement) { this.#formCtrlElements.push(element); @@ -275,7 +278,7 @@ export function UmbFormControlMixin< /** * @function setCustomValidity * @description Set custom validity state, set to empty string to remove the custom message. - * @param message {string} - The message to be shown + * @param {string} message - The message to be shown * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/setCustomValidity|HTMLObjectElement:setCustomValidity} */ protected setCustomValidity(message: string | null) { From fa5164162d1b5b33ab789567c5bcebc96eb3bf6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Aug 2024 15:37:42 +0200 Subject: [PATCH 15/47] implement validation for Grid Blocks --- .../block-grid-entry.element.ts | 78 ++++++++++++++++++- .../property-editor-ui-block-grid.element.ts | 32 +++++++- 2 files changed, 105 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts index 4778221731..1d029be7bf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts @@ -13,6 +13,8 @@ import { UMB_BLOCK_GRID, type UmbBlockGridLayoutModel } from '@umbraco-cms/backo import '../block-grid-block-inline/index.js'; import '../block-grid-block/index.js'; import '../block-scale-handler/index.js'; +import { UmbObserveValidationStateController } from '@umbraco-cms/backoffice/validation'; +import { UmbDataPathBlockElementDataQuery } from '@umbraco-cms/backoffice/block'; /** * @element umb-block-grid-entry */ @@ -37,6 +39,16 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper this._blockViewProps.contentUdi = value; this.setAttribute('data-element-udi', value); this.#context.setContentUdi(value); + + new UmbObserveValidationStateController( + this, + `$.contentData[${UmbDataPathBlockElementDataQuery({ udi: value })}]`, + (hasMessages) => { + this._contentInvalid = hasMessages; + this._blockViewProps.contentInvalid = hasMessages; + }, + 'observeMessagesForContent', + ); } private _contentUdi?: string | undefined; // @@ -89,6 +101,14 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper @state() _inlineCreateAboveWidth?: string; + // 'content-invalid' attribute is used for styling purpose. + @property({ type: Boolean, attribute: 'content-invalid', reflect: true }) + _contentInvalid?: boolean; + + // 'settings-invalid' attribute is used for styling purpose. + @property({ type: Boolean, attribute: 'settings-invalid', reflect: true }) + _settingsInvalid?: boolean; + @state() _blockViewProps: UmbBlockEditorCustomViewProperties = { contentUdi: undefined!, @@ -178,6 +198,20 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper this.#context.settings, (settings) => { this.#updateBlockViewProps({ settings }); + + this.removeUmbControllerByAlias('observeMessagesForSettings'); + if (settings) { + // Observe settings validation state: + new UmbObserveValidationStateController( + this, + `$.settingsData[${UmbDataPathBlockElementDataQuery(settings)}]`, + (hasMessages) => { + this._settingsInvalid = hasMessages; + this._blockViewProps.settingsInvalid = hasMessages; + }, + 'observeMessagesForSettings', + ); + } }, null, ); @@ -340,20 +374,34 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
${this._inlineEditingMode ? this.#renderInlineEditBlock() : this.#renderRefBlock()} ${this._showContentEdit && this._workspaceEditContentPath - ? html` + ? html` + ${this._contentInvalid + ? html`!` + : nothing} ` : nothing} ${this._hasSettings && this._workspaceEditSettingsPath - ? html` + ? html` + ${this._settingsInvalid + ? html`!` + : nothing} ` : nothing} this.#context.requestDelete()}> @@ -361,6 +409,9 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper + ${!this._showContentEdit && this._contentInvalid + ? html`!` + : nothing} ${this._canScale ? html` this.#context.scaleManager.onScaleMouseDown(e)}> @@ -387,11 +438,21 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper :host { position: relative; display: block; + --umb-block-grid-entry-actions-opacity: 0; } + :host([settings-invalid]), + :host([content-invalid]), + :host(:hover), + :host(:focus-within) { + --umb-block-list-entry-actions-opacity: 1; + } + uui-action-bar { position: absolute; top: var(--uui-size-2); right: var(--uui-size-2); + opacity: var(--umb-block-list-entry-actions-opacity, 0); + transition: opacity 120ms; } uui-button-inline-create { top: 0px; @@ -424,7 +485,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper display: none; inset: 0; border: 1px solid transparent; - border-radius: 3px; + border-radius: var(--uui-border-radius); box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.7), inset 0 0 0 1px rgba(255, 255, 255, 0.7); @@ -442,6 +503,15 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper .umb-block-grid__block { height: 100%; } + + :host([settings-invalid])::after, + :host([content-invalid])::after { + border-color: var(--uui-color-danger); + } + + uui-badge { + z-index: 2; + } `, ]; } 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 0fdd6f4cdb..15427f71f9 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 @@ -16,9 +16,10 @@ import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/ import '../../components/block-grid-entries/index.js'; import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; -import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; +import { UmbFormControlMixin, UmbValidationContext } from '@umbraco-cms/backoffice/validation'; import type { UmbBlockTypeGroup } from '@umbraco-cms/backoffice/block-type'; import type { UmbBlockGridTypeModel, UmbBlockGridValueModel } from '@umbraco-cms/backoffice/block-grid'; +import { UmbBlockElementDataValidationPathTranslator } from '@umbraco-cms/backoffice/block'; /** * @element umb-property-editor-ui-block-grid @@ -28,6 +29,9 @@ export class UmbPropertyEditorUIBlockGridElement extends UmbFormControlMixin(UmbLitElement) implements UmbPropertyEditorUiElement { + #validationContext = new UmbValidationContext(this).provide(); + #contentDataPathTranslator?: UmbBlockElementDataValidationPathTranslator; + #settingsDataPathTranslator?: UmbBlockElementDataValidationPathTranslator; #context = new UmbBlockGridManagerContext(this); // private _value: UmbBlockGridValueModel = { @@ -73,6 +77,32 @@ export class UmbPropertyEditorUIBlockGridElement constructor() { super(); + this.consumeContext(UMB_PROPERTY_CONTEXT, (context) => { + this.observe( + context.dataPath, + (dataPath) => { + // Translate paths for content elements: + this.#contentDataPathTranslator?.destroy(); + if (dataPath) { + // Set the data path for the local validation context: + this.#validationContext.setDataPath(dataPath); + + this.#contentDataPathTranslator = new UmbBlockElementDataValidationPathTranslator(this, 'contentData'); + } + + // Translate paths for settings elements: + this.#settingsDataPathTranslator?.destroy(); + if (dataPath) { + // Set the data path for the local validation context: + this.#validationContext.setDataPath(dataPath); + + this.#settingsDataPathTranslator = new UmbBlockElementDataValidationPathTranslator(this, 'settingsData'); + } + }, + 'observeDataPath', + ); + }); + // TODO: Prevent initial notification from these observes this.consumeContext(UMB_PROPERTY_CONTEXT, (propertyContext) => { this.observe( From ef022f1eb1037b1032a3afb60263e256459a99b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 27 Aug 2024 15:54:24 +0200 Subject: [PATCH 16/47] refactor --- .../property-editor-ui-block-grid.element.ts | 11 ++--------- .../property-editor-ui-block-list.element.ts | 11 ++--------- 2 files changed, 4 insertions(+), 18 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 15427f71f9..5c5ac3fe77 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 @@ -81,21 +81,14 @@ export class UmbPropertyEditorUIBlockGridElement this.observe( context.dataPath, (dataPath) => { - // Translate paths for content elements: + // Translate paths for content/settings: this.#contentDataPathTranslator?.destroy(); - if (dataPath) { - // Set the data path for the local validation context: - this.#validationContext.setDataPath(dataPath); - - this.#contentDataPathTranslator = new UmbBlockElementDataValidationPathTranslator(this, 'contentData'); - } - - // Translate paths for settings elements: 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'); } }, 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 9e263cf849..46efbf4fe4 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 @@ -139,21 +139,14 @@ export class UmbPropertyEditorUIBlockListElement this.observe( context.dataPath, (dataPath) => { - // Translate paths for content elements: + // Translate paths for content/settings: this.#contentDataPathTranslator?.destroy(); - if (dataPath) { - // Set the data path for the local validation context: - this.#validationContext.setDataPath(dataPath); - - this.#contentDataPathTranslator = new UmbBlockElementDataValidationPathTranslator(this, 'contentData'); - } - - // Translate paths for settings elements: 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'); } }, From fcf9ec3a93cbf4a8b41cb6fc1cd5db641d2f6243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 28 Aug 2024 10:25:17 +0200 Subject: [PATCH 17/47] fix rollback button slot --- ...ent-workspace-view-info-history.element.ts | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-history.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-history.element.ts index 2808d9bc26..a7745267f2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-history.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-history.element.ts @@ -97,16 +97,14 @@ export class UmbDocumentWorkspaceViewInfoHistoryElement extends UmbLitElement { override render() { return html` -
-

History

- - ${this.localize.term('actions_rollback')} - -
+ History + + ${this.localize.term('actions_rollback')} + ${this._items ? this.#renderHistory() : html` `} ${this.#renderPagination()}
`; @@ -167,18 +165,6 @@ export class UmbDocumentWorkspaceViewInfoHistoryElement extends UmbLitElement { font-size: 2rem; } - #rollback { - display: flex; - width: 100%; - align-items: center; - justify-content: space-between; - } - - #rollback h2 { - font-size: var(--uui-type-h5-size); - margin: 0; - } - uui-tag uui-icon { margin-right: var(--uui-size-space-1); } From af37a900071ef760f9501784bbb0c2dcb3479dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 28 Aug 2024 10:31:50 +0200 Subject: [PATCH 18/47] remove secondary look --- .../components/block-grid-entry/block-grid-entry.element.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts index 1d029be7bf..159ff52a4a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts @@ -383,7 +383,6 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper ${this._showContentEdit && this._workspaceEditContentPath ? html` @@ -395,7 +394,6 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper ${this._hasSettings && this._workspaceEditSettingsPath ? html` From b9adf5a3f03b1472c90d74d37698bf0228345a6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 28 Aug 2024 10:32:12 +0200 Subject: [PATCH 19/47] make action secondary look --- .../components/block-grid-entry/block-grid-entry.element.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts index 159ff52a4a..f44d204ecf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts @@ -383,6 +383,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper ${this._showContentEdit && this._workspaceEditContentPath ? html` @@ -394,6 +395,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper ${this._hasSettings && this._workspaceEditSettingsPath ? html` @@ -402,7 +404,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper : nothing} ` : nothing} - this.#context.requestDelete()}> + this.#context.requestDelete()}> From d463f4cae1f87f9f8753e6700f2d0b81a74df560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 28 Aug 2024 10:43:50 +0200 Subject: [PATCH 20/47] styling of info reference element --- .../info/data-type-workspace-view-info-reference.element.ts | 3 +++ .../info/document-workspace-view-info-reference.element.ts | 3 +++ .../views/info/media-workspace-view-info-reference.element.ts | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/info/data-type-workspace-view-info-reference.element.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/info/data-type-workspace-view-info-reference.element.ts index 29f19e05fd..5e34b90128 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/info/data-type-workspace-view-info-reference.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/info/data-type-workspace-view-info-reference.element.ts @@ -105,6 +105,9 @@ export class UmbDataTypeWorkspaceViewInfoReferenceElement extends UmbLitElement static override styles = [ UmbTextStyles, css` + :host { + display: contents; + } uui-table-cell { color: var(--uui-color-text-alt); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-reference.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-reference.element.ts index 8b4de39cb6..b1324e5ac9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-reference.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-reference.element.ts @@ -175,6 +175,9 @@ export class UmbDocumentWorkspaceViewInfoReferenceElement extends UmbLitElement static override styles = [ UmbTextStyles, css` + :host { + display: contents; + } uui-table-cell:not(.link-cell) { color: var(--uui-color-text-alt); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/views/info/media-workspace-view-info-reference.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/views/info/media-workspace-view-info-reference.element.ts index 6795b57e2d..ebf9da81fe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/views/info/media-workspace-view-info-reference.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/views/info/media-workspace-view-info-reference.element.ts @@ -196,6 +196,10 @@ export class UmbMediaWorkspaceViewInfoReferenceElement extends UmbLitElement { static override styles = [ UmbTextStyles, css` + :host { + display: contents; + } + uui-table-cell { color: var(--uui-color-text-alt); } From 8ac0f63a7ac547ab95df69fcd1f73ea7068e762e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 28 Aug 2024 13:53:15 +0200 Subject: [PATCH 21/47] area validation --- .../block-grid-entries.element.ts | 15 +++++++++++---- .../context/block-grid-entries.context.ts | 8 ++++++-- .../src/packages/block/block-grid/types.ts | 4 ++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts index 55ab4b266e..6d9dfb3f18 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts @@ -236,7 +236,6 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen #rangeUnderflowValidator?: UmbFormControlValidatorConfig; #rangeOverflowValidator?: UmbFormControlValidatorConfig; async #setupRangeValidation(rangeLimit: UmbNumberRangeValueType | undefined) { - console.log('setupRangeValidation', rangeLimit); if (this.#rangeUnderflowValidator) { this.removeValidator(this.#rangeUnderflowValidator); this.#rangeUnderflowValidator = undefined; @@ -295,14 +294,14 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen `, )}
- ${this._areaKey ? html` ` : nothing} ${this._canCreate ? this.#renderCreateButton() : nothing} + ${this._areaKey ? html` ` : nothing} `; } #renderCreateButton() { if (this._areaKey === null || this._layoutEntries.length === 0) { - return html` + return html` div { display: flex; flex-direction: column; align-items: stretch; } - uui-button-group { + #createButton { padding-top: 1px; grid-template-columns: 1fr auto; --umb-block-grid--is-dragging--variable: var(--umb-block-grid--is-dragging) none; display: var(--umb-block-grid--is-dragging--variable, grid); } + :host(:not([pristine]):invalid) #createButton { + --uui-button-contrast: var(--uui-color-danger); + --uui-button-contrast-hover: var(--uui-color-danger); + --uui-color-default-emphasis: var(--uui-color-danger); + --uui-button-border-color: var(--uui-color-danger); + --uui-button-border-color-hover: var(--uui-color-danger); + } .umb-block-grid__layout-container[data-area-length='0'] { --umb-block-grid--is-dragging--variable: var(--umb-block-grid--is-dragging) 1; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts index 9117cd71c2..a4e93e9d50 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts @@ -84,6 +84,10 @@ export class UmbBlockGridEntriesContext this.#workspaceModal.setUniquePathValue('areaKey', areaKey ?? 'null'); this.#catalogueModal.setUniquePathValue('areaKey', areaKey ?? 'null'); this.#gotAreaKey(); + + // Idea: If we need to parse down a validation data path to target the specific layout object: [NL] + // If we have a areaKey, we want to inherit our layoutDataPath from nearest blockGridEntry context. + // If not, we want to set the layoutDataPath to a base one. } setLayoutColumns(columns: number | undefined) { @@ -315,8 +319,8 @@ export class UmbBlockGridEntriesContext if (!this.#areaType) return undefined; // No need to observe as this method is called every time the area is changed. this.#rangeLimits.setValue({ - min: this.#areaType.minAllowed ?? 0, - max: this.#areaType.maxAllowed ?? Infinity, + min: this.#areaType.minAllowed ? parseInt(this.#areaType.minAllowed) : 0, + max: this.#areaType.maxAllowed ? parseInt(this.#areaType.maxAllowed) : Infinity, }); } else if (this.#areaKey === null) { if (!this._manager) return undefined; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/types.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/types.ts index fa688cfbe4..ec00759eec 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/types.ts @@ -26,8 +26,8 @@ export interface UmbBlockGridTypeAreaType { alias: string; columnSpan?: number; rowSpan?: number; - minAllowed?: number; - maxAllowed?: number; + minAllowed?: string; + maxAllowed?: string; specifiedAllowance?: Array; } From b6ea281bf79be75fc7e89cfc97ee19965e412772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 28 Aug 2024 14:05:39 +0200 Subject: [PATCH 22/47] fix originData parsing --- .../context/block-grid-manager.context.ts | 1 + .../block/workspace/block-workspace.context.ts | 15 ++++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts index 8b473a66ce..29d0aaec8e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts @@ -167,6 +167,7 @@ export class UmbBlockGridManagerContext< settings: UmbBlockDataType | undefined, originData: UmbBlockGridWorkspaceOriginData, ) { + console.log('originData', originData); this.setOneLayout(layoutEntry, originData); this.insertBlockData(layoutEntry, content, settings, originData); diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts index c94569ca8a..9ff8c1d3ad 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts @@ -9,11 +9,12 @@ import { import { UmbClassState, UmbObjectState, UmbStringState } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { ManifestWorkspace } from '@umbraco-cms/backoffice/extension-registry'; -import { UMB_MODAL_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { UMB_MODAL_CONTEXT, type UmbModalContext } from '@umbraco-cms/backoffice/modal'; import { decodeFilePath } from '@umbraco-cms/backoffice/utils'; import { UMB_BLOCK_ENTRIES_CONTEXT, UMB_BLOCK_MANAGER_CONTEXT, + type UmbBlockWorkspaceOriginData, type UmbBlockWorkspaceData, } from '@umbraco-cms/backoffice/block'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; @@ -32,7 +33,7 @@ export class UmbBlockWorkspaceContext; #retrieveModalContext; #entityType: string; @@ -68,7 +69,7 @@ export class UmbBlockWorkspaceContext { - this.#modalContext = context; + this.#modalContext = context as any; context.onSubmit().catch(this.#modalRejected); }).asPromise(); @@ -180,7 +181,7 @@ export class UmbBlockWorkspaceContext Date: Wed, 28 Aug 2024 15:24:45 +0200 Subject: [PATCH 23/47] styling + index fix --- .../block-grid-entries.element.ts | 6 +- .../block-grid-entry.element.ts | 61 +++++++++++++------ .../block/context/block-entry.context.ts | 8 +-- 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts index 6d9dfb3f18..eb269efbe3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts @@ -136,7 +136,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen #context = new UmbBlockGridEntriesContext(this); #controlValidator?: UmbFormControlValidator; - @property({ attribute: false }) + @property({ type: String, attribute: 'area-key', reflect: true }) public set areaKey(value: string | null | undefined) { this._areaKey = value; this.#context.setAreaKey(value ?? null); @@ -363,7 +363,11 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen #createButton { padding-top: 1px; grid-template-columns: 1fr auto; + display: grid; + } + // Only when we are n an area, we like to hide the button on drag + :host([area-key]) #createButton { --umb-block-grid--is-dragging--variable: var(--umb-block-grid--is-dragging) none; display: var(--umb-block-grid--is-dragging--variable, grid); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts index f44d204ecf..3027c3c2f2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts @@ -4,6 +4,7 @@ import { html, css, customElement, property, state, nothing } from '@umbraco-cms import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; import type { ManifestBlockEditorCustomView, + UmbBlockEditorCustomViewElement, UmbBlockEditorCustomViewProperties, UmbPropertyEditorUiElement, } from '@umbraco-cms/backoffice/extension-registry'; @@ -15,6 +16,8 @@ import '../block-grid-block/index.js'; import '../block-scale-handler/index.js'; import { UmbObserveValidationStateController } from '@umbraco-cms/backoffice/validation'; import { UmbDataPathBlockElementDataQuery } from '@umbraco-cms/backoffice/block'; +import { UUIBlinkAnimationValue, UUIBlinkKeyframes } from '@umbraco-cms/backoffice/external/uui'; +import type { UmbExtensionElementInitializer } from '@umbraco-cms/backoffice/extension-api'; /** * @element umb-block-grid-entry */ @@ -352,12 +355,23 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper return true; }; + #extensionSlotRenderMethod = (ext: UmbExtensionElementInitializer) => { + if (ext.component) { + ext.component.classList.add('umb-block-grid__block--view'); + } + return ext.component; + }; + #renderInlineEditBlock() { - return html``; + return html``; } #renderRefBlock() { - return html``; + return html``; } #renderBlock() { @@ -373,10 +387,11 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper : nothing}
${this._inlineEditingMode ? this.#renderInlineEditBlock() : this.#renderRefBlock()} @@ -434,6 +449,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper } static override styles = [ + UUIBlinkKeyframes, css` :host { position: relative; @@ -458,7 +474,6 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper top: 0px; position: absolute; - // Avoid showing inline-create in dragging-mode --umb-block-grid__block--inline-create-button-display--condition: var(--umb-block-grid--dragging-mode) none; display: var(--umb-block-grid__block--inline-create-button-display--condition); } @@ -473,16 +488,15 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper right: calc(1px - (var(--umb-block-grid--column-gap, 0px) * 0.5)); } - :host([drag-placeholder]) { - opacity: 0.2; + .umb-block-grid__block { + height: 100%; } - :host(::after) { + :host::after { content: ''; position: absolute; z-index: 1; pointer-events: none; - display: none; inset: 0; border: 1px solid transparent; border-radius: var(--uui-border-radius); @@ -492,16 +506,29 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper transition: border-color 240ms ease-in; } - - :host(:hover::after) { - // TODO: Look at the feature I out-commented here, what was that suppose to do [NL]: - //display: var(--umb-block-grid--block-ui-display, block); + :host(:hover):not(:drop)::after { display: block; - border-color: var(--uui-color-interactive); + border-color: var(--uui-color-interactive-emphasis); } - .umb-block-grid__block { - height: 100%; + :host([drag-placeholder])::after { + display: block; + border-width: 2px; + border-color: var(--uui-color-interactive-emphasis); + animation: ${UUIBlinkAnimationValue}; + } + :host([drag-placeholder])::before { + content: ''; + position: absolute; + pointer-events: none; + inset: 0; + border-radius: var(--uui-border-radius); + background-color: var(--uui-color-interactive-emphasis); + opacity: 0.12; + } + :host([drag-placeholder]) .umb-block-grid__block { + transition: opacity 50ms 16ms; + opacity: 0; } :host([settings-invalid])::after, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts index 13904a94b3..c338636571 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts @@ -234,11 +234,11 @@ export abstract class UmbBlockEntryContext< } #updateCreatePaths() { - const index = this.#index.value; - if (this._entries && index !== undefined) { + if (this._entries) { this.observe( - observeMultiple([this._entries.catalogueRouteBuilder, this._entries.canCreate]), - ([catalogueRouteBuilder, canCreate]) => { + observeMultiple([this.index, this._entries.catalogueRouteBuilder, this._entries.canCreate]), + ([index, catalogueRouteBuilder, canCreate]) => { + if (index === undefined) return; if (catalogueRouteBuilder && canCreate) { this.#createBeforePath.setValue(this._entries!.getPathForCreateBlock(index)); this.#createAfterPath.setValue(this._entries!.getPathForCreateBlock(index + 1)); From 9518c9a53bbe3a5ae8e5063741ad4cef492847ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 28 Aug 2024 15:31:43 +0200 Subject: [PATCH 24/47] styles --- .../block-grid-entry/block-grid-entry.element.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts index 3027c3c2f2..7a330b460b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts @@ -460,14 +460,14 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper :host([content-invalid]), :host(:hover), :host(:focus-within) { - --umb-block-list-entry-actions-opacity: 1; + --umb-block-grid-entry-actions-opacity: 1; } uui-action-bar { position: absolute; top: var(--uui-size-2); right: var(--uui-size-2); - opacity: var(--umb-block-list-entry-actions-opacity, 0); + opacity: var(--umb-block-grid-entry-actions-opacity, 0); transition: opacity 120ms; } uui-button-inline-create { @@ -535,6 +535,10 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper :host([content-invalid])::after { border-color: var(--uui-color-danger); } + :host([settings-invalid])::before, + :host([content-invalid])::before { + background-color: var(--uui-color-danger); + } uui-badge { z-index: 2; From a8b416e5a8ba21622a77385c4bc0b5061a142979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 28 Aug 2024 16:45:41 +0200 Subject: [PATCH 25/47] Block Area Rules --- .../block-grid-entries.element.ts | 45 +++++++- .../context/block-grid-entries.context.ts | 108 +++++++++++++++++- .../context/block-grid-manager.context.ts | 3 + .../block/context/block-manager.context.ts | 3 + 4 files changed, 154 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts index eb269efbe3..ce5759057a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts @@ -135,6 +135,9 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen #context = new UmbBlockGridEntriesContext(this); #controlValidator?: UmbFormControlValidator; + #typeLimitValidator?: UmbFormControlValidatorConfig; + #rangeUnderflowValidator?: UmbFormControlValidatorConfig; + #rangeOverflowValidator?: UmbFormControlValidatorConfig; @property({ type: String, attribute: 'area-key', reflect: true }) public set areaKey(value: string | null | undefined) { @@ -217,6 +220,14 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen null, ); + this.observe( + this.#context.hasTypeLimits, + (hasTypeLimits) => { + this.#setupBlockTypeLimitValidation(hasTypeLimits); + }, + null, + ); + this.#context.getManager().then((manager) => { this.observe( manager.layoutStylesheet, @@ -233,8 +244,6 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen new UmbFormControlValidator(this, this /*, this.#dataPath*/); } - #rangeUnderflowValidator?: UmbFormControlValidatorConfig; - #rangeOverflowValidator?: UmbFormControlValidatorConfig; async #setupRangeValidation(rangeLimit: UmbNumberRangeValueType | undefined) { if (this.#rangeUnderflowValidator) { this.removeValidator(this.#rangeUnderflowValidator); @@ -277,6 +286,38 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen } } + async #setupBlockTypeLimitValidation(hasTypeLimits: boolean | undefined) { + if (this.#typeLimitValidator) { + this.removeValidator(this.#typeLimitValidator); + this.#typeLimitValidator = undefined; + } + if (hasTypeLimits) { + console.log('hasTypeLimits'); + this.#typeLimitValidator = this.addValidator( + 'patternMismatch', + () => { + const invalids = this.#context.getInvalidBlockTypeLimits(); + return invalids + .map((invalidRule) => + this.localize.term( + invalidRule.amount < invalidRule.minRequirement + ? 'blockEditor_areaValidationEntriesShort' + : 'blockEditor_areaValidationEntriesExceed', + invalidRule.name, + invalidRule.amount, + invalidRule.minRequirement, + invalidRule.maxRequirement, + ), + ) + .join(', '); + }, + () => { + return !this.#context.checkBlockTypeLimitsValidity(); + }, + ); + } + } + // TODO: Missing ability to jump directly to creating a Block, when there is only one Block Type. [NL] override render() { return html` diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts index a4e93e9d50..1e15578e15 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts @@ -8,7 +8,13 @@ import { import type { UmbBlockGridLayoutModel, UmbBlockGridTypeAreaType, UmbBlockGridTypeModel } from '../types.js'; import { UMB_BLOCK_GRID_MANAGER_CONTEXT } from './block-grid-manager.context-token.js'; import type { UmbBlockGridScalableContainerContext } from './block-grid-scale-manager/block-grid-scale-manager.controller.js'; -import { UmbArrayState, UmbNumberState, UmbObjectState, UmbStringState } from '@umbraco-cms/backoffice/observable-api'; +import { + UmbArrayState, + UmbBooleanState, + UmbNumberState, + UmbObjectState, + UmbStringState, +} from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import { pathFolderName } from '@umbraco-cms/backoffice/utils'; @@ -49,6 +55,9 @@ export class UmbBlockGridEntriesContext public readonly amountOfAllowedBlockTypes = this.#allowedBlockTypes.asObservablePart((x) => x.length); public readonly canCreate = this.#allowedBlockTypes.asObservablePart((x) => x.length > 0); + #hasTypeLimits = new UmbBooleanState(undefined); + public readonly hasTypeLimits = this.#hasTypeLimits.asObservable(); + firstAllowedBlockTypeName() { if (!this._manager) { throw new Error('Manager not ready'); @@ -241,8 +250,6 @@ export class UmbBlockGridEntriesContext 'observeThisLayouts', ); - this.removeUmbControllerByAlias('observeAreaType'); - const hostEl = this.getHostElement() as HTMLElement | undefined; if (hostEl) { hostEl.removeAttribute('data-area-alias'); @@ -309,6 +316,7 @@ export class UmbBlockGridEntriesContext #setupAllowedBlockTypes() { if (!this._manager) return; this.#allowedBlockTypes.setValue(this.#retrieveAllowedElementTypes()); + this.#setupAllowedBlockTypesLimits(); } #setupRangeLimits() { if (!this._manager) return; @@ -429,6 +437,100 @@ export class UmbBlockGridEntriesContext return []; } + /** + * @internal + */ + #setupAllowedBlockTypesLimits() { + if (!this._manager) return; + + if (this.#areaKey) { + // Area entries: + if (!this.#areaType) return; + + if (this.#areaType.specifiedAllowance && this.#areaType.specifiedAllowance?.length > 0) { + this.#hasTypeLimits.setValue(true); + } + } else if (this.#areaKey === null) { + // RESET + } + } + + #invalidBlockTypeLimits?: Array<{ + groupKey?: string; + key?: string; + name: string; + amount: number; + minRequirement: number; + maxRequirement: number; + }>; + + getInvalidBlockTypeLimits() { + return this.#invalidBlockTypeLimits ?? []; + } + /** + * @internal + * @returns {boolean} - True if the block type limits are valid, otherwise false. + */ + checkBlockTypeLimitsValidity(): boolean { + if (!this.#areaType || !this.#areaType.specifiedAllowance) return false; + + const layoutEntries = this._layoutEntries.getValue(); + + this.#invalidBlockTypeLimits = []; + + const hasInvalidRules = this.#areaType.specifiedAllowance.some((rule) => { + const minAllowed = rule.minAllowed || 0; + const maxAllowed = rule.maxAllowed || 0; + + // For block groups: + if (rule.groupKey) { + const groupElementTypeKeys = + this._manager + ?.getBlockTypes() + .filter((blockType) => blockType.groupKey === rule.groupKey && blockType.allowInAreas === true) + .map((x) => x.contentElementTypeKey) ?? []; + const groupAmount = layoutEntries.filter((entry) => { + const contentTypeKey = this._manager!.getContentTypeKeyOf(entry.contentUdi); + return contentTypeKey ? groupElementTypeKeys.indexOf(contentTypeKey) !== -1 : false; + }).length; + + if (groupAmount < minAllowed || (maxAllowed > 0 && groupAmount > maxAllowed)) { + this.#invalidBlockTypeLimits!.push({ + groupKey: rule.groupKey, + name: this._manager!.getBlockGroupName(rule.groupKey) ?? '?', + amount: groupAmount, + minRequirement: minAllowed, + maxRequirement: maxAllowed, + }); + return true; + } + } + // For specific elementTypes: + else if (rule.elementTypeKey) { + const amount = layoutEntries.filter((entry) => { + const contentTypeKey = this._manager!.getContentOf(entry.contentUdi)?.contentTypeKey; + return contentTypeKey === rule.elementTypeKey; + }).length; + console.log('amount', amount); + if (amount < minAllowed || (maxAllowed > 0 ? amount > maxAllowed : false)) { + this.#invalidBlockTypeLimits!.push({ + key: rule.elementTypeKey, + name: this._manager!.getContentTypeNameOf(rule.elementTypeKey) ?? '?', + amount: amount, + minRequirement: minAllowed, + maxRequirement: maxAllowed, + }); + return true; + } + } + + // Lets fail cause the rule was bad. + console.error('Invalid block type limit rule.', rule); + return false; + }); + return hasInvalidRules === false; + } + /** * Check if given contentUdi is allowed in the current area. * @param contentUdi {string} - The contentUdi of the content to check. diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts index 29d0aaec8e..db64e29d17 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts @@ -62,6 +62,9 @@ export class UmbBlockGridManagerContext< getBlockGroups() { return this.#blockGroups.value; } + getBlockGroupName(unique: string) { + return this.#blockGroups.getValue().find((group) => group.key === unique)?.name; + } constructor(host: UmbControllerHost) { super(host); diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts index 0b3d26e95b..df113d9b81 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts @@ -148,6 +148,9 @@ export abstract class UmbBlockManagerContext< getContentTypeNameOf(contentTypeKey: string) { return this.#contentTypes.getValue().find((x) => x.unique === contentTypeKey)?.name; } + getContentTypeKeyOf(contentTypeKey: string) { + return this.#contentTypes.getValue().find((x) => x.unique === contentTypeKey)?.unique; + } getContentTypeHasProperties(contentTypeKey: string) { const properties = this.#contentTypes.getValue().find((x) => x.unique === contentTypeKey)?.properties; return properties ? properties.length > 0 : false; From 3a473f3cd986705b89f2b3ed936948ded843a0f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 28 Aug 2024 16:49:09 +0200 Subject: [PATCH 26/47] clean up --- .../block-grid-entries/block-grid-entries.element.ts | 9 ++------- .../block-grid/context/block-grid-entries.context.ts | 1 - 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts index ce5759057a..9223965ca2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts @@ -259,9 +259,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen (rangeLimit!.min ?? 0) - this._layoutEntries.length, ); }, - () => { - return this._layoutEntries.length < (rangeLimit?.min ?? 0); - }, + () => this._layoutEntries.length < (rangeLimit?.min ?? 0), ); } @@ -279,9 +277,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen this._layoutEntries.length - (rangeLimit!.max ?? this._layoutEntries.length), ); }, - () => { - return (this._layoutEntries.length ?? 0) > (rangeLimit?.max ?? Infinity); - }, + () => this._layoutEntries.length > (rangeLimit?.max ?? Infinity), ); } } @@ -292,7 +288,6 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen this.#typeLimitValidator = undefined; } if (hasTypeLimits) { - console.log('hasTypeLimits'); this.#typeLimitValidator = this.addValidator( 'patternMismatch', () => { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts index 1e15578e15..2bb572f742 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts @@ -511,7 +511,6 @@ export class UmbBlockGridEntriesContext const contentTypeKey = this._manager!.getContentOf(entry.contentUdi)?.contentTypeKey; return contentTypeKey === rule.elementTypeKey; }).length; - console.log('amount', amount); if (amount < minAllowed || (maxAllowed > 0 ? amount > maxAllowed : false)) { this.#invalidBlockTypeLimits!.push({ key: rule.elementTypeKey, From 0c7508f337f4cc41711f7221aec6c32c20ef8224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 28 Aug 2024 20:58:10 +0200 Subject: [PATCH 27/47] clean up --- .../block/block-grid/context/block-grid-manager.context.ts | 1 - .../src/packages/core/sorter/sorter.controller.test.ts | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts index db64e29d17..416ce658ba 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts @@ -170,7 +170,6 @@ export class UmbBlockGridManagerContext< settings: UmbBlockDataType | undefined, originData: UmbBlockGridWorkspaceOriginData, ) { - console.log('originData', originData); this.setOneLayout(layoutEntry, originData); this.insertBlockData(layoutEntry, content, settings, originData); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts index 3595ec1376..065e27a109 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts @@ -16,9 +16,7 @@ class UmbSorterTestElement extends UmbLitElement { itemSelector: '.item', containerSelector: '#container', disabledItemSelector: '.disabled', - onChange: ({ model }) => { - this.model = model; - }, + // TODO: In theory missing model change callback? [NL] }); getAllItems() { From 04844e4c5749b05579e8fdd0ec7f7b7296502a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 28 Aug 2024 21:00:08 +0200 Subject: [PATCH 28/47] use number range input for area limits --- .../workspace/views/settings.element.ts | 49 ++++++++++++++----- .../context/block-grid-entries.context.ts | 4 +- .../src/packages/block/block-grid/types.ts | 4 +- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/settings.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/settings.element.ts index cfbc9bd694..b8043eba6f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/settings.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/settings.element.ts @@ -1,15 +1,40 @@ -import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry'; +import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; +import type { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import type { UmbInputNumberRangeElement } from '@umbraco-cms/backoffice/components'; @customElement('umb-block-grid-area-type-workspace-view') export class UmbBlockGridAreaTypeWorkspaceViewSettingsElement extends UmbLitElement implements UmbWorkspaceViewElement { // TODO: Add Localizations... // TODO: Validation to prevent spaces and weird characters in alias: - // TODO: Add create button label field: - // TODO: Turn minAllowed and maxAllowed into one range property/input... - // TODO: Add validation permission field: + + #dataset?: typeof UMB_PROPERTY_DATASET_CONTEXT.TYPE; + + @state() + _minValue?: number; + @state() + _maxValue?: number; + + constructor() { + super(); + this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, async (context) => { + this.#dataset = context; + this.observe(await this.#dataset.propertyValueByAlias('minAllowed'), (min) => { + this._minValue = min ?? 0; + }); + this.observe(await this.#dataset.propertyValueByAlias('maxAllowed'), (max) => { + this._maxValue = max ?? Infinity; + }); + }); + } + #onAllowedRangeChange = (e: UmbChangeEvent) => { + this.#dataset?.setPropertyValue('minAllowed', (e!.target! as UmbInputNumberRangeElement).minValue); + this.#dataset?.setPropertyValue('maxAllowed', (e!.target! as UmbInputNumberRangeElement).maxValue); + }; + override render() { return html` @@ -24,14 +49,14 @@ export class UmbBlockGridAreaTypeWorkspaceViewSettingsElement extends UmbLitElem property-editor-ui-alias="Umb.PropertyEditorUi.TextBox"> - - + + + + ; } From 84654449f5885e191f4328eccf59431a8e1c8fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 29 Aug 2024 09:07:24 +0200 Subject: [PATCH 29/47] remove import --- .../components/block-grid-entry/block-grid-entry.element.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts index 7a330b460b..16ccdf5830 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts @@ -4,7 +4,6 @@ import { html, css, customElement, property, state, nothing } from '@umbraco-cms import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; import type { ManifestBlockEditorCustomView, - UmbBlockEditorCustomViewElement, UmbBlockEditorCustomViewProperties, UmbPropertyEditorUiElement, } from '@umbraco-cms/backoffice/extension-registry'; From 8a5d347b86dd8460b7ed0b79481a34ace2bff866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 29 Aug 2024 10:11:06 +0200 Subject: [PATCH 30/47] proper workspace name --- .../block-workspace-editor.element.ts | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace-editor.element.ts index ef13fc07e2..027688f399 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace-editor.element.ts @@ -1,6 +1,8 @@ +import { UMB_BLOCK_WORKSPACE_CONTEXT } from './index.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { customElement, css, html, property } from '@umbraco-cms/backoffice/external/lit'; +import { customElement, css, html, property, state, nothing } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; @customElement('umb-block-workspace-editor') export class UmbBlockWorkspaceEditorElement extends UmbLitElement { @@ -8,10 +10,29 @@ export class UmbBlockWorkspaceEditorElement extends UmbLitElement { @property({ type: String, attribute: false }) workspaceAlias?: string; + constructor() { + super(); + this.consumeContext(UMB_BLOCK_WORKSPACE_CONTEXT, (context) => { + this.observe( + observeMultiple([ + context.isNew, + context.content.structure.ownerContentTypePart((contentType) => contentType?.name), + ]), + ([isNew, name]) => { + this._headline = this.localize.term(isNew ? 'general_add' : 'general_edit') + ' ' + name; + }, + 'observeOwnerContentElementTypeName', + ); + }); + } + + @state() + _headline: string = ''; + override render() { return this.workspaceAlias - ? html` ` - : ''; + ? html` ` + : nothing; } static override styles = [ From e52ad54540ea61528ff7f24fc29df3eb7afc6ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 29 Aug 2024 10:49:37 +0200 Subject: [PATCH 31/47] tree picker disable pick --- .../tree/tree-picker-modal/tree-picker-modal.element.ts | 9 ++++++++- .../core/utils/selection-manager/selection.manager.ts | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/tree-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/tree-picker-modal.element.ts index 5299caa3c0..9ca76c005e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/tree-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/tree-picker-modal.element.ts @@ -20,6 +20,9 @@ export class UmbTreePickerModalElement { + this._hasSelection = hasSelection; + }); this.#observePickerSelection(); this.#observeSearch(); } @@ -188,7 +194,8 @@ export class UmbTreePickerModalElement + @click=${this._submitModal} + ?disabled=${!this._hasSelection}>
`; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.ts index cf0fd0aa06..d1127cbb8d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.ts @@ -13,6 +13,7 @@ export class UmbSelectionManager>[], (x) => x); public readonly selection = this.#selection.asObservable(); + public readonly hasSelection = this.#selection.asObservablePart((x) => x.length > 0); #multiple = new UmbBooleanState(false); public readonly multiple = this.#multiple.asObservable(); From ef30a832a2f26a06949d6ebb508ed169964f8c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 29 Aug 2024 10:49:44 +0200 Subject: [PATCH 32/47] change to custom error --- .../components/block-grid-entries/block-grid-entries.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts index 9223965ca2..e861c64602 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts @@ -289,7 +289,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen } if (hasTypeLimits) { this.#typeLimitValidator = this.addValidator( - 'patternMismatch', + 'customError', () => { const invalids = this.#context.getInvalidBlockTypeLimits(); return invalids From 929068eaca9abe441647beb082635f36422dc93f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 29 Aug 2024 15:45:40 +0200 Subject: [PATCH 33/47] feat removeFormControlElement --- .../property-editor-ui-block-grid.element.ts | 6 +--- .../validation/mixins/form-control.mixin.ts | 30 ++++++++++++++----- 2 files changed, 24 insertions(+), 12 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 5c5ac3fe77..57c11096d4 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 @@ -129,11 +129,7 @@ export class UmbPropertyEditorUIBlockGridElement #gotRootEntriesElement(element: Element | undefined): void { if (this.#currentEntriesElement === element) return; if (this.#currentEntriesElement) { - throw new Error( - 'Cannot re-render root entries element because we currently do not support removing form control elements.', - ); - // TODO: If this become relevant we should implement this method: [NL] - //this.removeFormControlElement(this.#currentEntriesElement as any); + this.removeFormControlElement(this.#currentEntriesElement as any); } this.#currentEntriesElement = element; this.addFormControlElement(element as any); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts index 20ff64ae08..e4f45a5518 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts @@ -80,6 +80,7 @@ export declare abstract class UmbFormControlMixinElement ) => UmbFormControlValidatorConfig; removeValidator: (obj: UmbFormControlValidatorConfig) => void; protected addFormControlElement(element: UmbNativeFormControlElement): void; + protected removeFormControlElement(element: UmbNativeFormControlElement): void; //static formAssociated: boolean; protected getFormElement(): HTMLElement | undefined | null; @@ -251,20 +252,18 @@ export function UmbFormControlMixin< } } + #runValidatorsCallback = () => this._runValidators; + /** * @function addFormControlElement * @description Important notice if adding a native form control then ensure that its value and thereby validity is updated when value is changed from the outside. - * @param {UmbNativeFormControlElement} element - element to validate and include as part of this form association. + * @param {UmbNativeFormControlElement} element - element to validate and include as part of this form control association. * @returns {void} */ protected addFormControlElement(element: UmbNativeFormControlElement) { this.#formCtrlElements.push(element); - element.addEventListener(UmbValidationInvalidEvent.TYPE, () => { - this._runValidators(); - }); - element.addEventListener(UmbValidationValidEvent.TYPE, () => { - this._runValidators(); - }); + element.addEventListener(UmbValidationInvalidEvent.TYPE, this.#runValidatorsCallback); + element.addEventListener(UmbValidationValidEvent.TYPE, this.#runValidatorsCallback); // If we are in validationMode/'touched'/not-pristine then we need to validate this newly added control. [NL] if (this._pristine === false) { element.checkValidity(); @@ -273,6 +272,23 @@ export function UmbFormControlMixin< } } + /** + * @function removeFormControlElement + * @param {UmbNativeFormControlElement} element - element to remove as part of this form controls associated controls. + * @returns {void} + */ + protected removeFormControlElement(element: UmbNativeFormControlElement) { + const index = this.#formCtrlElements.indexOf(element); + if (index !== -1) { + this.#formCtrlElements.splice(index, 1); + element.removeEventListener(UmbValidationInvalidEvent.TYPE, this.#runValidatorsCallback); + element.removeEventListener(UmbValidationValidEvent.TYPE, this.#runValidatorsCallback); + if (this._pristine === false) { + this._runValidators(); + } + } + } + private _customValidityObject?: UmbFormControlValidatorConfig; /** From a9e53412c127d33c868e0f71b639b657d15e5631 Mon Sep 17 00:00:00 2001 From: Lan Nguyen Thuy Date: Wed, 28 Aug 2024 11:14:20 +0700 Subject: [PATCH 34/47] fix bug change password for current user --- .../external/backend-api/src/services.gen.ts | 25 ++++++++- .../src/external/backend-api/src/types.gen.ts | 6 ++ .../token/change-password-modal.token.ts | 1 + .../change-password-current-user.action.ts | 7 ++- ...change-current-user-password.repository.ts | 35 ++++++++++++ ...urrent-user-password.server.data-source.ts | 55 +++++++++++++++++++ .../repository/change-password/index.ts | 1 + .../user/current-user/repository/index.ts | 1 + .../change-password-modal.element.ts | 3 +- .../change-user-password.action.ts | 2 +- .../change-user-password.repository.ts | 5 +- ...change-user-password.server.data-source.ts | 33 +++++++---- 12 files changed, 158 insertions(+), 16 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/change-current-user-password.repository.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/change-current-user-password.server.data-source.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/index.ts diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.gen.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.gen.ts index 7a132b3073..c6edaee403 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.gen.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.gen.ts @@ -3,7 +3,7 @@ import type { CancelablePromise } from './core/CancelablePromise'; import { OpenAPI } from './core/OpenAPI'; import { request as __request } from './core/request'; -import type { GetCultureData, GetCultureResponse, PostDataTypeData, PostDataTypeResponse, GetDataTypeByIdData, GetDataTypeByIdResponse, DeleteDataTypeByIdData, DeleteDataTypeByIdResponse, PutDataTypeByIdData, PutDataTypeByIdResponse, PostDataTypeByIdCopyData, PostDataTypeByIdCopyResponse, GetDataTypeByIdIsUsedData, GetDataTypeByIdIsUsedResponse, PutDataTypeByIdMoveData, PutDataTypeByIdMoveResponse, GetDataTypeByIdReferencesData, GetDataTypeByIdReferencesResponse, GetDataTypeConfigurationResponse, PostDataTypeFolderData, PostDataTypeFolderResponse, GetDataTypeFolderByIdData, GetDataTypeFolderByIdResponse, DeleteDataTypeFolderByIdData, DeleteDataTypeFolderByIdResponse, PutDataTypeFolderByIdData, PutDataTypeFolderByIdResponse, GetFilterDataTypeData, GetFilterDataTypeResponse, GetItemDataTypeData, GetItemDataTypeResponse, GetItemDataTypeSearchData, GetItemDataTypeSearchResponse, GetTreeDataTypeAncestorsData, GetTreeDataTypeAncestorsResponse, GetTreeDataTypeChildrenData, GetTreeDataTypeChildrenResponse, GetTreeDataTypeRootData, GetTreeDataTypeRootResponse, GetDictionaryData, GetDictionaryResponse, PostDictionaryData, PostDictionaryResponse, GetDictionaryByIdData, GetDictionaryByIdResponse, DeleteDictionaryByIdData, DeleteDictionaryByIdResponse, PutDictionaryByIdData, PutDictionaryByIdResponse, GetDictionaryByIdExportData, GetDictionaryByIdExportResponse, PutDictionaryByIdMoveData, PutDictionaryByIdMoveResponse, PostDictionaryImportData, PostDictionaryImportResponse, GetItemDictionaryData, GetItemDictionaryResponse, GetTreeDictionaryAncestorsData, GetTreeDictionaryAncestorsResponse, GetTreeDictionaryChildrenData, GetTreeDictionaryChildrenResponse, GetTreeDictionaryRootData, GetTreeDictionaryRootResponse, PostDocumentBlueprintData, PostDocumentBlueprintResponse, GetDocumentBlueprintByIdData, GetDocumentBlueprintByIdResponse, DeleteDocumentBlueprintByIdData, DeleteDocumentBlueprintByIdResponse, PutDocumentBlueprintByIdData, PutDocumentBlueprintByIdResponse, PutDocumentBlueprintByIdMoveData, PutDocumentBlueprintByIdMoveResponse, PostDocumentBlueprintFolderData, PostDocumentBlueprintFolderResponse, GetDocumentBlueprintFolderByIdData, GetDocumentBlueprintFolderByIdResponse, DeleteDocumentBlueprintFolderByIdData, DeleteDocumentBlueprintFolderByIdResponse, PutDocumentBlueprintFolderByIdData, PutDocumentBlueprintFolderByIdResponse, PostDocumentBlueprintFromDocumentData, PostDocumentBlueprintFromDocumentResponse, GetItemDocumentBlueprintData, GetItemDocumentBlueprintResponse, GetTreeDocumentBlueprintAncestorsData, GetTreeDocumentBlueprintAncestorsResponse, GetTreeDocumentBlueprintChildrenData, GetTreeDocumentBlueprintChildrenResponse, GetTreeDocumentBlueprintRootData, GetTreeDocumentBlueprintRootResponse, PostDocumentTypeData, PostDocumentTypeResponse, GetDocumentTypeByIdData, GetDocumentTypeByIdResponse, DeleteDocumentTypeByIdData, DeleteDocumentTypeByIdResponse, PutDocumentTypeByIdData, PutDocumentTypeByIdResponse, GetDocumentTypeByIdAllowedChildrenData, GetDocumentTypeByIdAllowedChildrenResponse, GetDocumentTypeByIdBlueprintData, GetDocumentTypeByIdBlueprintResponse, GetDocumentTypeByIdCompositionReferencesData, GetDocumentTypeByIdCompositionReferencesResponse, PostDocumentTypeByIdCopyData, PostDocumentTypeByIdCopyResponse, GetDocumentTypeByIdExportData, GetDocumentTypeByIdExportResponse, PutDocumentTypeByIdImportData, PutDocumentTypeByIdImportResponse, PutDocumentTypeByIdMoveData, PutDocumentTypeByIdMoveResponse, GetDocumentTypeAllowedAtRootData, GetDocumentTypeAllowedAtRootResponse, PostDocumentTypeAvailableCompositionsData, PostDocumentTypeAvailableCompositionsResponse, GetDocumentTypeConfigurationResponse, PostDocumentTypeFolderData, PostDocumentTypeFolderResponse, GetDocumentTypeFolderByIdData, GetDocumentTypeFolderByIdResponse, DeleteDocumentTypeFolderByIdData, DeleteDocumentTypeFolderByIdResponse, PutDocumentTypeFolderByIdData, PutDocumentTypeFolderByIdResponse, PostDocumentTypeImportData, PostDocumentTypeImportResponse, GetItemDocumentTypeData, GetItemDocumentTypeResponse, GetItemDocumentTypeSearchData, GetItemDocumentTypeSearchResponse, GetTreeDocumentTypeAncestorsData, GetTreeDocumentTypeAncestorsResponse, GetTreeDocumentTypeChildrenData, GetTreeDocumentTypeChildrenResponse, GetTreeDocumentTypeRootData, GetTreeDocumentTypeRootResponse, GetDocumentVersionData, GetDocumentVersionResponse, GetDocumentVersionByIdData, GetDocumentVersionByIdResponse, PutDocumentVersionByIdPreventCleanupData, PutDocumentVersionByIdPreventCleanupResponse, PostDocumentVersionByIdRollbackData, PostDocumentVersionByIdRollbackResponse, GetCollectionDocumentByIdData, GetCollectionDocumentByIdResponse, PostDocumentData, PostDocumentResponse, GetDocumentByIdData, GetDocumentByIdResponse, DeleteDocumentByIdData, DeleteDocumentByIdResponse, PutDocumentByIdData, PutDocumentByIdResponse, GetDocumentByIdAuditLogData, GetDocumentByIdAuditLogResponse, PostDocumentByIdCopyData, PostDocumentByIdCopyResponse, GetDocumentByIdDomainsData, GetDocumentByIdDomainsResponse, PutDocumentByIdDomainsData, PutDocumentByIdDomainsResponse, PutDocumentByIdMoveData, PutDocumentByIdMoveResponse, PutDocumentByIdMoveToRecycleBinData, PutDocumentByIdMoveToRecycleBinResponse, GetDocumentByIdNotificationsData, GetDocumentByIdNotificationsResponse, PutDocumentByIdNotificationsData, PutDocumentByIdNotificationsResponse, PostDocumentByIdPublicAccessData, PostDocumentByIdPublicAccessResponse, DeleteDocumentByIdPublicAccessData, DeleteDocumentByIdPublicAccessResponse, GetDocumentByIdPublicAccessData, GetDocumentByIdPublicAccessResponse, PutDocumentByIdPublicAccessData, PutDocumentByIdPublicAccessResponse, PutDocumentByIdPublishData, PutDocumentByIdPublishResponse, PutDocumentByIdPublishWithDescendantsData, PutDocumentByIdPublishWithDescendantsResponse, GetDocumentByIdReferencedByData, GetDocumentByIdReferencedByResponse, GetDocumentByIdReferencedDescendantsData, GetDocumentByIdReferencedDescendantsResponse, PutDocumentByIdUnpublishData, PutDocumentByIdUnpublishResponse, PutDocumentByIdValidateData, PutDocumentByIdValidateResponse, GetDocumentAreReferencedData, GetDocumentAreReferencedResponse, GetDocumentConfigurationResponse, PutDocumentSortData, PutDocumentSortResponse, GetDocumentUrlsData, GetDocumentUrlsResponse, PostDocumentValidateData, PostDocumentValidateResponse, GetItemDocumentData, GetItemDocumentResponse, GetItemDocumentSearchData, GetItemDocumentSearchResponse, DeleteRecycleBinDocumentResponse, DeleteRecycleBinDocumentByIdData, DeleteRecycleBinDocumentByIdResponse, GetRecycleBinDocumentByIdOriginalParentData, GetRecycleBinDocumentByIdOriginalParentResponse, PutRecycleBinDocumentByIdRestoreData, PutRecycleBinDocumentByIdRestoreResponse, GetRecycleBinDocumentChildrenData, GetRecycleBinDocumentChildrenResponse, GetRecycleBinDocumentRootData, GetRecycleBinDocumentRootResponse, GetTreeDocumentAncestorsData, GetTreeDocumentAncestorsResponse, GetTreeDocumentChildrenData, GetTreeDocumentChildrenResponse, GetTreeDocumentRootData, GetTreeDocumentRootResponse, PostDynamicRootQueryData, PostDynamicRootQueryResponse, GetDynamicRootStepsResponse, GetHealthCheckGroupData, GetHealthCheckGroupResponse, GetHealthCheckGroupByNameData, GetHealthCheckGroupByNameResponse, PostHealthCheckGroupByNameCheckData, PostHealthCheckGroupByNameCheckResponse, PostHealthCheckExecuteActionData, PostHealthCheckExecuteActionResponse, GetHelpData, GetHelpResponse, GetImagingResizeUrlsData, GetImagingResizeUrlsResponse, GetImportAnalyzeData, GetImportAnalyzeResponse, GetIndexerData, GetIndexerResponse, GetIndexerByIndexNameData, GetIndexerByIndexNameResponse, PostIndexerByIndexNameRebuildData, PostIndexerByIndexNameRebuildResponse, GetInstallSettingsResponse, PostInstallSetupData, PostInstallSetupResponse, PostInstallValidateDatabaseData, PostInstallValidateDatabaseResponse, GetItemLanguageData, GetItemLanguageResponse, GetItemLanguageDefaultResponse, GetLanguageData, GetLanguageResponse, PostLanguageData, PostLanguageResponse, GetLanguageByIsoCodeData, GetLanguageByIsoCodeResponse, DeleteLanguageByIsoCodeData, DeleteLanguageByIsoCodeResponse, PutLanguageByIsoCodeData, PutLanguageByIsoCodeResponse, GetLogViewerLevelData, GetLogViewerLevelResponse, GetLogViewerLevelCountData, GetLogViewerLevelCountResponse, GetLogViewerLogData, GetLogViewerLogResponse, GetLogViewerMessageTemplateData, GetLogViewerMessageTemplateResponse, GetLogViewerSavedSearchData, GetLogViewerSavedSearchResponse, PostLogViewerSavedSearchData, PostLogViewerSavedSearchResponse, GetLogViewerSavedSearchByNameData, GetLogViewerSavedSearchByNameResponse, DeleteLogViewerSavedSearchByNameData, DeleteLogViewerSavedSearchByNameResponse, GetLogViewerValidateLogsSizeData, GetLogViewerValidateLogsSizeResponse, GetManifestManifestResponse, GetManifestManifestPrivateResponse, GetManifestManifestPublicResponse, GetItemMediaTypeData, GetItemMediaTypeResponse, GetItemMediaTypeAllowedData, GetItemMediaTypeAllowedResponse, GetItemMediaTypeFoldersData, GetItemMediaTypeFoldersResponse, GetItemMediaTypeSearchData, GetItemMediaTypeSearchResponse, PostMediaTypeData, PostMediaTypeResponse, GetMediaTypeByIdData, GetMediaTypeByIdResponse, DeleteMediaTypeByIdData, DeleteMediaTypeByIdResponse, PutMediaTypeByIdData, PutMediaTypeByIdResponse, GetMediaTypeByIdAllowedChildrenData, GetMediaTypeByIdAllowedChildrenResponse, GetMediaTypeByIdCompositionReferencesData, GetMediaTypeByIdCompositionReferencesResponse, PostMediaTypeByIdCopyData, PostMediaTypeByIdCopyResponse, GetMediaTypeByIdExportData, GetMediaTypeByIdExportResponse, PutMediaTypeByIdImportData, PutMediaTypeByIdImportResponse, PutMediaTypeByIdMoveData, PutMediaTypeByIdMoveResponse, GetMediaTypeAllowedAtRootData, GetMediaTypeAllowedAtRootResponse, PostMediaTypeAvailableCompositionsData, PostMediaTypeAvailableCompositionsResponse, GetMediaTypeConfigurationResponse, PostMediaTypeFolderData, PostMediaTypeFolderResponse, GetMediaTypeFolderByIdData, GetMediaTypeFolderByIdResponse, DeleteMediaTypeFolderByIdData, DeleteMediaTypeFolderByIdResponse, PutMediaTypeFolderByIdData, PutMediaTypeFolderByIdResponse, PostMediaTypeImportData, PostMediaTypeImportResponse, GetTreeMediaTypeAncestorsData, GetTreeMediaTypeAncestorsResponse, GetTreeMediaTypeChildrenData, GetTreeMediaTypeChildrenResponse, GetTreeMediaTypeRootData, GetTreeMediaTypeRootResponse, GetCollectionMediaData, GetCollectionMediaResponse, GetItemMediaData, GetItemMediaResponse, GetItemMediaSearchData, GetItemMediaSearchResponse, PostMediaData, PostMediaResponse, GetMediaByIdData, GetMediaByIdResponse, DeleteMediaByIdData, DeleteMediaByIdResponse, PutMediaByIdData, PutMediaByIdResponse, GetMediaByIdAuditLogData, GetMediaByIdAuditLogResponse, PutMediaByIdMoveData, PutMediaByIdMoveResponse, PutMediaByIdMoveToRecycleBinData, PutMediaByIdMoveToRecycleBinResponse, GetMediaByIdReferencedByData, GetMediaByIdReferencedByResponse, GetMediaByIdReferencedDescendantsData, GetMediaByIdReferencedDescendantsResponse, PutMediaByIdValidateData, PutMediaByIdValidateResponse, GetMediaAreReferencedData, GetMediaAreReferencedResponse, GetMediaConfigurationResponse, PutMediaSortData, PutMediaSortResponse, GetMediaUrlsData, GetMediaUrlsResponse, PostMediaValidateData, PostMediaValidateResponse, DeleteRecycleBinMediaResponse, DeleteRecycleBinMediaByIdData, DeleteRecycleBinMediaByIdResponse, GetRecycleBinMediaByIdOriginalParentData, GetRecycleBinMediaByIdOriginalParentResponse, PutRecycleBinMediaByIdRestoreData, PutRecycleBinMediaByIdRestoreResponse, GetRecycleBinMediaChildrenData, GetRecycleBinMediaChildrenResponse, GetRecycleBinMediaRootData, GetRecycleBinMediaRootResponse, GetTreeMediaAncestorsData, GetTreeMediaAncestorsResponse, GetTreeMediaChildrenData, GetTreeMediaChildrenResponse, GetTreeMediaRootData, GetTreeMediaRootResponse, GetItemMemberGroupData, GetItemMemberGroupResponse, GetMemberGroupData, GetMemberGroupResponse, PostMemberGroupData, PostMemberGroupResponse, GetMemberGroupByIdData, GetMemberGroupByIdResponse, DeleteMemberGroupByIdData, DeleteMemberGroupByIdResponse, PutMemberGroupByIdData, PutMemberGroupByIdResponse, GetTreeMemberGroupRootData, GetTreeMemberGroupRootResponse, GetItemMemberTypeData, GetItemMemberTypeResponse, GetItemMemberTypeSearchData, GetItemMemberTypeSearchResponse, PostMemberTypeData, PostMemberTypeResponse, GetMemberTypeByIdData, GetMemberTypeByIdResponse, DeleteMemberTypeByIdData, DeleteMemberTypeByIdResponse, PutMemberTypeByIdData, PutMemberTypeByIdResponse, GetMemberTypeByIdCompositionReferencesData, GetMemberTypeByIdCompositionReferencesResponse, PostMemberTypeByIdCopyData, PostMemberTypeByIdCopyResponse, PostMemberTypeAvailableCompositionsData, PostMemberTypeAvailableCompositionsResponse, GetMemberTypeConfigurationResponse, GetTreeMemberTypeRootData, GetTreeMemberTypeRootResponse, GetFilterMemberData, GetFilterMemberResponse, GetItemMemberData, GetItemMemberResponse, GetItemMemberSearchData, GetItemMemberSearchResponse, PostMemberData, PostMemberResponse, GetMemberByIdData, GetMemberByIdResponse, DeleteMemberByIdData, DeleteMemberByIdResponse, PutMemberByIdData, PutMemberByIdResponse, PutMemberByIdValidateData, PutMemberByIdValidateResponse, GetMemberConfigurationResponse, PostMemberValidateData, PostMemberValidateResponse, PostModelsBuilderBuildResponse, GetModelsBuilderDashboardResponse, GetModelsBuilderStatusResponse, GetObjectTypesData, GetObjectTypesResponse, GetOembedQueryData, GetOembedQueryResponse, PostPackageByNameRunMigrationData, PostPackageByNameRunMigrationResponse, GetPackageConfigurationResponse, GetPackageCreatedData, GetPackageCreatedResponse, PostPackageCreatedData, PostPackageCreatedResponse, GetPackageCreatedByIdData, GetPackageCreatedByIdResponse, DeletePackageCreatedByIdData, DeletePackageCreatedByIdResponse, PutPackageCreatedByIdData, PutPackageCreatedByIdResponse, GetPackageCreatedByIdDownloadData, GetPackageCreatedByIdDownloadResponse, GetPackageMigrationStatusData, GetPackageMigrationStatusResponse, GetItemPartialViewData, GetItemPartialViewResponse, PostPartialViewData, PostPartialViewResponse, GetPartialViewByPathData, GetPartialViewByPathResponse, DeletePartialViewByPathData, DeletePartialViewByPathResponse, PutPartialViewByPathData, PutPartialViewByPathResponse, PutPartialViewByPathRenameData, PutPartialViewByPathRenameResponse, PostPartialViewFolderData, PostPartialViewFolderResponse, GetPartialViewFolderByPathData, GetPartialViewFolderByPathResponse, DeletePartialViewFolderByPathData, DeletePartialViewFolderByPathResponse, GetPartialViewSnippetData, GetPartialViewSnippetResponse, GetPartialViewSnippetByIdData, GetPartialViewSnippetByIdResponse, GetTreePartialViewAncestorsData, GetTreePartialViewAncestorsResponse, GetTreePartialViewChildrenData, GetTreePartialViewChildrenResponse, GetTreePartialViewRootData, GetTreePartialViewRootResponse, DeletePreviewResponse, PostPreviewResponse, GetProfilingStatusResponse, PutProfilingStatusData, PutProfilingStatusResponse, GetPropertyTypeIsUsedData, GetPropertyTypeIsUsedResponse, PostPublishedCacheCollectResponse, PostPublishedCacheRebuildResponse, PostPublishedCacheReloadResponse, GetPublishedCacheStatusResponse, GetRedirectManagementData, GetRedirectManagementResponse, GetRedirectManagementByIdData, GetRedirectManagementByIdResponse, DeleteRedirectManagementByIdData, DeleteRedirectManagementByIdResponse, GetRedirectManagementStatusResponse, PostRedirectManagementStatusData, PostRedirectManagementStatusResponse, GetItemRelationTypeData, GetItemRelationTypeResponse, GetRelationTypeData, GetRelationTypeResponse, GetRelationTypeByIdData, GetRelationTypeByIdResponse, GetRelationByRelationTypeIdData, GetRelationByRelationTypeIdResponse, GetItemScriptData, GetItemScriptResponse, PostScriptData, PostScriptResponse, GetScriptByPathData, GetScriptByPathResponse, DeleteScriptByPathData, DeleteScriptByPathResponse, PutScriptByPathData, PutScriptByPathResponse, PutScriptByPathRenameData, PutScriptByPathRenameResponse, PostScriptFolderData, PostScriptFolderResponse, GetScriptFolderByPathData, GetScriptFolderByPathResponse, DeleteScriptFolderByPathData, DeleteScriptFolderByPathResponse, GetTreeScriptAncestorsData, GetTreeScriptAncestorsResponse, GetTreeScriptChildrenData, GetTreeScriptChildrenResponse, GetTreeScriptRootData, GetTreeScriptRootResponse, GetSearcherData, GetSearcherResponse, GetSearcherBySearcherNameQueryData, GetSearcherBySearcherNameQueryResponse, GetSecurityConfigurationResponse, PostSecurityForgotPasswordData, PostSecurityForgotPasswordResponse, PostSecurityForgotPasswordResetData, PostSecurityForgotPasswordResetResponse, PostSecurityForgotPasswordVerifyData, PostSecurityForgotPasswordVerifyResponse, GetSegmentData, GetSegmentResponse, GetServerConfigurationResponse, GetServerInformationResponse, GetServerStatusResponse, GetServerTroubleshootingResponse, GetItemStaticFileData, GetItemStaticFileResponse, GetTreeStaticFileAncestorsData, GetTreeStaticFileAncestorsResponse, GetTreeStaticFileChildrenData, GetTreeStaticFileChildrenResponse, GetTreeStaticFileRootData, GetTreeStaticFileRootResponse, GetItemStylesheetData, GetItemStylesheetResponse, PostStylesheetData, PostStylesheetResponse, GetStylesheetByPathData, GetStylesheetByPathResponse, DeleteStylesheetByPathData, DeleteStylesheetByPathResponse, PutStylesheetByPathData, PutStylesheetByPathResponse, PutStylesheetByPathRenameData, PutStylesheetByPathRenameResponse, PostStylesheetFolderData, PostStylesheetFolderResponse, GetStylesheetFolderByPathData, GetStylesheetFolderByPathResponse, DeleteStylesheetFolderByPathData, DeleteStylesheetFolderByPathResponse, GetTreeStylesheetAncestorsData, GetTreeStylesheetAncestorsResponse, GetTreeStylesheetChildrenData, GetTreeStylesheetChildrenResponse, GetTreeStylesheetRootData, GetTreeStylesheetRootResponse, GetTagData, GetTagResponse, GetTelemetryData, GetTelemetryResponse, GetTelemetryLevelResponse, PostTelemetryLevelData, PostTelemetryLevelResponse, GetItemTemplateData, GetItemTemplateResponse, GetItemTemplateSearchData, GetItemTemplateSearchResponse, PostTemplateData, PostTemplateResponse, GetTemplateByIdData, GetTemplateByIdResponse, DeleteTemplateByIdData, DeleteTemplateByIdResponse, PutTemplateByIdData, PutTemplateByIdResponse, GetTemplateConfigurationResponse, PostTemplateQueryExecuteData, PostTemplateQueryExecuteResponse, GetTemplateQuerySettingsResponse, GetTreeTemplateAncestorsData, GetTreeTemplateAncestorsResponse, GetTreeTemplateChildrenData, GetTreeTemplateChildrenResponse, GetTreeTemplateRootData, GetTreeTemplateRootResponse, PostTemporaryFileData, PostTemporaryFileResponse, GetTemporaryFileByIdData, GetTemporaryFileByIdResponse, DeleteTemporaryFileByIdData, DeleteTemporaryFileByIdResponse, GetTemporaryFileConfigurationResponse, PostUpgradeAuthorizeResponse, GetUpgradeSettingsResponse, PostUserDataData, PostUserDataResponse, GetUserDataData, GetUserDataResponse, PutUserDataData, PutUserDataResponse, GetUserDataByIdData, GetUserDataByIdResponse, GetFilterUserGroupData, GetFilterUserGroupResponse, GetItemUserGroupData, GetItemUserGroupResponse, DeleteUserGroupData, DeleteUserGroupResponse, PostUserGroupData, PostUserGroupResponse, GetUserGroupData, GetUserGroupResponse, GetUserGroupByIdData, GetUserGroupByIdResponse, DeleteUserGroupByIdData, DeleteUserGroupByIdResponse, PutUserGroupByIdData, PutUserGroupByIdResponse, DeleteUserGroupByIdUsersData, DeleteUserGroupByIdUsersResponse, PostUserGroupByIdUsersData, PostUserGroupByIdUsersResponse, GetFilterUserData, GetFilterUserResponse, GetItemUserData, GetItemUserResponse, PostUserData, PostUserResponse, DeleteUserData, DeleteUserResponse, GetUserData, GetUserResponse, GetUserByIdData, GetUserByIdResponse, DeleteUserByIdData, DeleteUserByIdResponse, PutUserByIdData, PutUserByIdResponse, GetUserById2FaData, GetUserById2FaResponse, DeleteUserById2FaByProviderNameData, DeleteUserById2FaByProviderNameResponse, GetUserByIdCalculateStartNodesData, GetUserByIdCalculateStartNodesResponse, PostUserByIdChangePasswordData, PostUserByIdChangePasswordResponse, PostUserByIdResetPasswordData, PostUserByIdResetPasswordResponse, DeleteUserAvatarByIdData, DeleteUserAvatarByIdResponse, PostUserAvatarByIdData, PostUserAvatarByIdResponse, GetUserConfigurationResponse, GetUserCurrentResponse, GetUserCurrent2FaResponse, DeleteUserCurrent2FaByProviderNameData, DeleteUserCurrent2FaByProviderNameResponse, PostUserCurrent2FaByProviderNameData, PostUserCurrent2FaByProviderNameResponse, GetUserCurrent2FaByProviderNameData, GetUserCurrent2FaByProviderNameResponse, PostUserCurrentAvatarData, PostUserCurrentAvatarResponse, PostUserCurrentChangePasswordData, PostUserCurrentChangePasswordResponse, GetUserCurrentConfigurationResponse, GetUserCurrentLoginProvidersResponse, GetUserCurrentPermissionsData, GetUserCurrentPermissionsResponse, GetUserCurrentPermissionsDocumentData, GetUserCurrentPermissionsDocumentResponse, GetUserCurrentPermissionsMediaData, GetUserCurrentPermissionsMediaResponse, PostUserDisableData, PostUserDisableResponse, PostUserEnableData, PostUserEnableResponse, PostUserInviteData, PostUserInviteResponse, PostUserInviteCreatePasswordData, PostUserInviteCreatePasswordResponse, PostUserInviteResendData, PostUserInviteResendResponse, PostUserInviteVerifyData, PostUserInviteVerifyResponse, PostUserSetUserGroupsData, PostUserSetUserGroupsResponse, PostUserUnlockData, PostUserUnlockResponse, GetItemWebhookData, GetItemWebhookResponse, GetWebhookData, GetWebhookResponse, PostWebhookData, PostWebhookResponse, GetWebhookByIdData, GetWebhookByIdResponse, DeleteWebhookByIdData, DeleteWebhookByIdResponse, PutWebhookByIdData, PutWebhookByIdResponse, GetWebhookEventsData, GetWebhookEventsResponse } from './types.gen'; +import type { GetCultureData, GetCultureResponse, PostDataTypeData, PostDataTypeResponse, GetDataTypeByIdData, GetDataTypeByIdResponse, DeleteDataTypeByIdData, DeleteDataTypeByIdResponse, PutDataTypeByIdData, PutDataTypeByIdResponse, PostDataTypeByIdCopyData, PostDataTypeByIdCopyResponse, GetDataTypeByIdIsUsedData, GetDataTypeByIdIsUsedResponse, PutDataTypeByIdMoveData, PutDataTypeByIdMoveResponse, GetDataTypeByIdReferencesData, GetDataTypeByIdReferencesResponse, GetDataTypeConfigurationResponse, PostDataTypeFolderData, PostDataTypeFolderResponse, GetDataTypeFolderByIdData, GetDataTypeFolderByIdResponse, DeleteDataTypeFolderByIdData, DeleteDataTypeFolderByIdResponse, PutDataTypeFolderByIdData, PutDataTypeFolderByIdResponse, GetFilterDataTypeData, GetFilterDataTypeResponse, GetItemDataTypeData, GetItemDataTypeResponse, GetItemDataTypeSearchData, GetItemDataTypeSearchResponse, GetTreeDataTypeAncestorsData, GetTreeDataTypeAncestorsResponse, GetTreeDataTypeChildrenData, GetTreeDataTypeChildrenResponse, GetTreeDataTypeRootData, GetTreeDataTypeRootResponse, GetDictionaryData, GetDictionaryResponse, PostDictionaryData, PostDictionaryResponse, GetDictionaryByIdData, GetDictionaryByIdResponse, DeleteDictionaryByIdData, DeleteDictionaryByIdResponse, PutDictionaryByIdData, PutDictionaryByIdResponse, GetDictionaryByIdExportData, GetDictionaryByIdExportResponse, PutDictionaryByIdMoveData, PutDictionaryByIdMoveResponse, PostDictionaryImportData, PostDictionaryImportResponse, GetItemDictionaryData, GetItemDictionaryResponse, GetTreeDictionaryAncestorsData, GetTreeDictionaryAncestorsResponse, GetTreeDictionaryChildrenData, GetTreeDictionaryChildrenResponse, GetTreeDictionaryRootData, GetTreeDictionaryRootResponse, PostDocumentBlueprintData, PostDocumentBlueprintResponse, GetDocumentBlueprintByIdData, GetDocumentBlueprintByIdResponse, DeleteDocumentBlueprintByIdData, DeleteDocumentBlueprintByIdResponse, PutDocumentBlueprintByIdData, PutDocumentBlueprintByIdResponse, PutDocumentBlueprintByIdMoveData, PutDocumentBlueprintByIdMoveResponse, PostDocumentBlueprintFolderData, PostDocumentBlueprintFolderResponse, GetDocumentBlueprintFolderByIdData, GetDocumentBlueprintFolderByIdResponse, DeleteDocumentBlueprintFolderByIdData, DeleteDocumentBlueprintFolderByIdResponse, PutDocumentBlueprintFolderByIdData, PutDocumentBlueprintFolderByIdResponse, PostDocumentBlueprintFromDocumentData, PostDocumentBlueprintFromDocumentResponse, GetItemDocumentBlueprintData, GetItemDocumentBlueprintResponse, GetTreeDocumentBlueprintAncestorsData, GetTreeDocumentBlueprintAncestorsResponse, GetTreeDocumentBlueprintChildrenData, GetTreeDocumentBlueprintChildrenResponse, GetTreeDocumentBlueprintRootData, GetTreeDocumentBlueprintRootResponse, PostDocumentTypeData, PostDocumentTypeResponse, GetDocumentTypeByIdData, GetDocumentTypeByIdResponse, DeleteDocumentTypeByIdData, DeleteDocumentTypeByIdResponse, PutDocumentTypeByIdData, PutDocumentTypeByIdResponse, GetDocumentTypeByIdAllowedChildrenData, GetDocumentTypeByIdAllowedChildrenResponse, GetDocumentTypeByIdBlueprintData, GetDocumentTypeByIdBlueprintResponse, GetDocumentTypeByIdCompositionReferencesData, GetDocumentTypeByIdCompositionReferencesResponse, PostDocumentTypeByIdCopyData, PostDocumentTypeByIdCopyResponse, GetDocumentTypeByIdExportData, GetDocumentTypeByIdExportResponse, PutDocumentTypeByIdImportData, PutDocumentTypeByIdImportResponse, PutDocumentTypeByIdMoveData, PutDocumentTypeByIdMoveResponse, GetDocumentTypeAllowedAtRootData, GetDocumentTypeAllowedAtRootResponse, PostDocumentTypeAvailableCompositionsData, PostDocumentTypeAvailableCompositionsResponse, GetDocumentTypeConfigurationResponse, PostDocumentTypeFolderData, PostDocumentTypeFolderResponse, GetDocumentTypeFolderByIdData, GetDocumentTypeFolderByIdResponse, DeleteDocumentTypeFolderByIdData, DeleteDocumentTypeFolderByIdResponse, PutDocumentTypeFolderByIdData, PutDocumentTypeFolderByIdResponse, PostDocumentTypeImportData, PostDocumentTypeImportResponse, GetItemDocumentTypeData, GetItemDocumentTypeResponse, GetItemDocumentTypeSearchData, GetItemDocumentTypeSearchResponse, GetTreeDocumentTypeAncestorsData, GetTreeDocumentTypeAncestorsResponse, GetTreeDocumentTypeChildrenData, GetTreeDocumentTypeChildrenResponse, GetTreeDocumentTypeRootData, GetTreeDocumentTypeRootResponse, GetDocumentVersionData, GetDocumentVersionResponse, GetDocumentVersionByIdData, GetDocumentVersionByIdResponse, PutDocumentVersionByIdPreventCleanupData, PutDocumentVersionByIdPreventCleanupResponse, PostDocumentVersionByIdRollbackData, PostDocumentVersionByIdRollbackResponse, GetCollectionDocumentByIdData, GetCollectionDocumentByIdResponse, PostDocumentData, PostDocumentResponse, GetDocumentByIdData, GetDocumentByIdResponse, DeleteDocumentByIdData, DeleteDocumentByIdResponse, PutDocumentByIdData, PutDocumentByIdResponse, GetDocumentByIdAuditLogData, GetDocumentByIdAuditLogResponse, PostDocumentByIdCopyData, PostDocumentByIdCopyResponse, GetDocumentByIdDomainsData, GetDocumentByIdDomainsResponse, PutDocumentByIdDomainsData, PutDocumentByIdDomainsResponse, PutDocumentByIdMoveData, PutDocumentByIdMoveResponse, PutDocumentByIdMoveToRecycleBinData, PutDocumentByIdMoveToRecycleBinResponse, GetDocumentByIdNotificationsData, GetDocumentByIdNotificationsResponse, PutDocumentByIdNotificationsData, PutDocumentByIdNotificationsResponse, PostDocumentByIdPublicAccessData, PostDocumentByIdPublicAccessResponse, DeleteDocumentByIdPublicAccessData, DeleteDocumentByIdPublicAccessResponse, GetDocumentByIdPublicAccessData, GetDocumentByIdPublicAccessResponse, PutDocumentByIdPublicAccessData, PutDocumentByIdPublicAccessResponse, PutDocumentByIdPublishData, PutDocumentByIdPublishResponse, PutDocumentByIdPublishWithDescendantsData, PutDocumentByIdPublishWithDescendantsResponse, GetDocumentByIdReferencedByData, GetDocumentByIdReferencedByResponse, GetDocumentByIdReferencedDescendantsData, GetDocumentByIdReferencedDescendantsResponse, PutDocumentByIdUnpublishData, PutDocumentByIdUnpublishResponse, PutDocumentByIdValidateData, PutDocumentByIdValidateResponse, GetDocumentAreReferencedData, GetDocumentAreReferencedResponse, GetDocumentConfigurationResponse, PutDocumentSortData, PutDocumentSortResponse, GetDocumentUrlsData, GetDocumentUrlsResponse, PostDocumentValidateData, PostDocumentValidateResponse, GetItemDocumentData, GetItemDocumentResponse, GetItemDocumentSearchData, GetItemDocumentSearchResponse, DeleteRecycleBinDocumentResponse, DeleteRecycleBinDocumentByIdData, DeleteRecycleBinDocumentByIdResponse, GetRecycleBinDocumentByIdOriginalParentData, GetRecycleBinDocumentByIdOriginalParentResponse, PutRecycleBinDocumentByIdRestoreData, PutRecycleBinDocumentByIdRestoreResponse, GetRecycleBinDocumentChildrenData, GetRecycleBinDocumentChildrenResponse, GetRecycleBinDocumentRootData, GetRecycleBinDocumentRootResponse, GetTreeDocumentAncestorsData, GetTreeDocumentAncestorsResponse, GetTreeDocumentChildrenData, GetTreeDocumentChildrenResponse, GetTreeDocumentRootData, GetTreeDocumentRootResponse, PostDynamicRootQueryData, PostDynamicRootQueryResponse, GetDynamicRootStepsResponse, GetHealthCheckGroupData, GetHealthCheckGroupResponse, GetHealthCheckGroupByNameData, GetHealthCheckGroupByNameResponse, PostHealthCheckGroupByNameCheckData, PostHealthCheckGroupByNameCheckResponse, PostHealthCheckExecuteActionData, PostHealthCheckExecuteActionResponse, GetHelpData, GetHelpResponse, GetImagingResizeUrlsData, GetImagingResizeUrlsResponse, GetImportAnalyzeData, GetImportAnalyzeResponse, GetIndexerData, GetIndexerResponse, GetIndexerByIndexNameData, GetIndexerByIndexNameResponse, PostIndexerByIndexNameRebuildData, PostIndexerByIndexNameRebuildResponse, GetInstallSettingsResponse, PostInstallSetupData, PostInstallSetupResponse, PostInstallValidateDatabaseData, PostInstallValidateDatabaseResponse, GetItemLanguageData, GetItemLanguageResponse, GetItemLanguageDefaultResponse, GetLanguageData, GetLanguageResponse, PostLanguageData, PostLanguageResponse, GetLanguageByIsoCodeData, GetLanguageByIsoCodeResponse, DeleteLanguageByIsoCodeData, DeleteLanguageByIsoCodeResponse, PutLanguageByIsoCodeData, PutLanguageByIsoCodeResponse, GetLogViewerLevelData, GetLogViewerLevelResponse, GetLogViewerLevelCountData, GetLogViewerLevelCountResponse, GetLogViewerLogData, GetLogViewerLogResponse, GetLogViewerMessageTemplateData, GetLogViewerMessageTemplateResponse, GetLogViewerSavedSearchData, GetLogViewerSavedSearchResponse, PostLogViewerSavedSearchData, PostLogViewerSavedSearchResponse, GetLogViewerSavedSearchByNameData, GetLogViewerSavedSearchByNameResponse, DeleteLogViewerSavedSearchByNameData, DeleteLogViewerSavedSearchByNameResponse, GetLogViewerValidateLogsSizeData, GetLogViewerValidateLogsSizeResponse, GetManifestManifestResponse, GetManifestManifestPrivateResponse, GetManifestManifestPublicResponse, GetItemMediaTypeData, GetItemMediaTypeResponse, GetItemMediaTypeAllowedData, GetItemMediaTypeAllowedResponse, GetItemMediaTypeFoldersData, GetItemMediaTypeFoldersResponse, GetItemMediaTypeSearchData, GetItemMediaTypeSearchResponse, PostMediaTypeData, PostMediaTypeResponse, GetMediaTypeByIdData, GetMediaTypeByIdResponse, DeleteMediaTypeByIdData, DeleteMediaTypeByIdResponse, PutMediaTypeByIdData, PutMediaTypeByIdResponse, GetMediaTypeByIdAllowedChildrenData, GetMediaTypeByIdAllowedChildrenResponse, GetMediaTypeByIdCompositionReferencesData, GetMediaTypeByIdCompositionReferencesResponse, PostMediaTypeByIdCopyData, PostMediaTypeByIdCopyResponse, GetMediaTypeByIdExportData, GetMediaTypeByIdExportResponse, PutMediaTypeByIdImportData, PutMediaTypeByIdImportResponse, PutMediaTypeByIdMoveData, PutMediaTypeByIdMoveResponse, GetMediaTypeAllowedAtRootData, GetMediaTypeAllowedAtRootResponse, PostMediaTypeAvailableCompositionsData, PostMediaTypeAvailableCompositionsResponse, GetMediaTypeConfigurationResponse, PostMediaTypeFolderData, PostMediaTypeFolderResponse, GetMediaTypeFolderByIdData, GetMediaTypeFolderByIdResponse, DeleteMediaTypeFolderByIdData, DeleteMediaTypeFolderByIdResponse, PutMediaTypeFolderByIdData, PutMediaTypeFolderByIdResponse, PostMediaTypeImportData, PostMediaTypeImportResponse, GetTreeMediaTypeAncestorsData, GetTreeMediaTypeAncestorsResponse, GetTreeMediaTypeChildrenData, GetTreeMediaTypeChildrenResponse, GetTreeMediaTypeRootData, GetTreeMediaTypeRootResponse, GetCollectionMediaData, GetCollectionMediaResponse, GetItemMediaData, GetItemMediaResponse, GetItemMediaSearchData, GetItemMediaSearchResponse, PostMediaData, PostMediaResponse, GetMediaByIdData, GetMediaByIdResponse, DeleteMediaByIdData, DeleteMediaByIdResponse, PutMediaByIdData, PutMediaByIdResponse, GetMediaByIdAuditLogData, GetMediaByIdAuditLogResponse, PutMediaByIdMoveData, PutMediaByIdMoveResponse, PutMediaByIdMoveToRecycleBinData, PutMediaByIdMoveToRecycleBinResponse, GetMediaByIdReferencedByData, GetMediaByIdReferencedByResponse, GetMediaByIdReferencedDescendantsData, GetMediaByIdReferencedDescendantsResponse, PutMediaByIdValidateData, PutMediaByIdValidateResponse, GetMediaAreReferencedData, GetMediaAreReferencedResponse, GetMediaConfigurationResponse, PutMediaSortData, PutMediaSortResponse, GetMediaUrlsData, GetMediaUrlsResponse, PostMediaValidateData, PostMediaValidateResponse, DeleteRecycleBinMediaResponse, DeleteRecycleBinMediaByIdData, DeleteRecycleBinMediaByIdResponse, GetRecycleBinMediaByIdOriginalParentData, GetRecycleBinMediaByIdOriginalParentResponse, PutRecycleBinMediaByIdRestoreData, PutRecycleBinMediaByIdRestoreResponse, GetRecycleBinMediaChildrenData, GetRecycleBinMediaChildrenResponse, GetRecycleBinMediaRootData, GetRecycleBinMediaRootResponse, GetTreeMediaAncestorsData, GetTreeMediaAncestorsResponse, GetTreeMediaChildrenData, GetTreeMediaChildrenResponse, GetTreeMediaRootData, GetTreeMediaRootResponse, GetItemMemberGroupData, GetItemMemberGroupResponse, GetMemberGroupData, GetMemberGroupResponse, PostMemberGroupData, PostMemberGroupResponse, GetMemberGroupByIdData, GetMemberGroupByIdResponse, DeleteMemberGroupByIdData, DeleteMemberGroupByIdResponse, PutMemberGroupByIdData, PutMemberGroupByIdResponse, GetTreeMemberGroupRootData, GetTreeMemberGroupRootResponse, GetItemMemberTypeData, GetItemMemberTypeResponse, GetItemMemberTypeSearchData, GetItemMemberTypeSearchResponse, PostMemberTypeData, PostMemberTypeResponse, GetMemberTypeByIdData, GetMemberTypeByIdResponse, DeleteMemberTypeByIdData, DeleteMemberTypeByIdResponse, PutMemberTypeByIdData, PutMemberTypeByIdResponse, GetMemberTypeByIdCompositionReferencesData, GetMemberTypeByIdCompositionReferencesResponse, PostMemberTypeByIdCopyData, PostMemberTypeByIdCopyResponse, PostMemberTypeAvailableCompositionsData, PostMemberTypeAvailableCompositionsResponse, GetMemberTypeConfigurationResponse, GetTreeMemberTypeRootData, GetTreeMemberTypeRootResponse, GetFilterMemberData, GetFilterMemberResponse, GetItemMemberData, GetItemMemberResponse, GetItemMemberSearchData, GetItemMemberSearchResponse, PostMemberData, PostMemberResponse, GetMemberByIdData, GetMemberByIdResponse, DeleteMemberByIdData, DeleteMemberByIdResponse, PutMemberByIdData, PutMemberByIdResponse, PutMemberByIdValidateData, PutMemberByIdValidateResponse, GetMemberConfigurationResponse, PostMemberValidateData, PostMemberValidateResponse, PostModelsBuilderBuildResponse, GetModelsBuilderDashboardResponse, GetModelsBuilderStatusResponse, GetObjectTypesData, GetObjectTypesResponse, GetOembedQueryData, GetOembedQueryResponse, PostPackageByNameRunMigrationData, PostPackageByNameRunMigrationResponse, GetPackageConfigurationResponse, GetPackageCreatedData, GetPackageCreatedResponse, PostPackageCreatedData, PostPackageCreatedResponse, GetPackageCreatedByIdData, GetPackageCreatedByIdResponse, DeletePackageCreatedByIdData, DeletePackageCreatedByIdResponse, PutPackageCreatedByIdData, PutPackageCreatedByIdResponse, GetPackageCreatedByIdDownloadData, GetPackageCreatedByIdDownloadResponse, GetPackageMigrationStatusData, GetPackageMigrationStatusResponse, GetItemPartialViewData, GetItemPartialViewResponse, PostPartialViewData, PostPartialViewResponse, GetPartialViewByPathData, GetPartialViewByPathResponse, DeletePartialViewByPathData, DeletePartialViewByPathResponse, PutPartialViewByPathData, PutPartialViewByPathResponse, PutPartialViewByPathRenameData, PutPartialViewByPathRenameResponse, PostPartialViewFolderData, PostPartialViewFolderResponse, GetPartialViewFolderByPathData, GetPartialViewFolderByPathResponse, DeletePartialViewFolderByPathData, DeletePartialViewFolderByPathResponse, GetPartialViewSnippetData, GetPartialViewSnippetResponse, GetPartialViewSnippetByIdData, GetPartialViewSnippetByIdResponse, GetTreePartialViewAncestorsData, GetTreePartialViewAncestorsResponse, GetTreePartialViewChildrenData, GetTreePartialViewChildrenResponse, GetTreePartialViewRootData, GetTreePartialViewRootResponse, DeletePreviewResponse, PostPreviewResponse, GetProfilingStatusResponse, PutProfilingStatusData, PutProfilingStatusResponse, GetPropertyTypeIsUsedData, GetPropertyTypeIsUsedResponse, PostPublishedCacheCollectResponse, PostPublishedCacheRebuildResponse, PostPublishedCacheReloadResponse, GetPublishedCacheStatusResponse, GetRedirectManagementData, GetRedirectManagementResponse, GetRedirectManagementByIdData, GetRedirectManagementByIdResponse, DeleteRedirectManagementByIdData, DeleteRedirectManagementByIdResponse, GetRedirectManagementStatusResponse, PostRedirectManagementStatusData, PostRedirectManagementStatusResponse, GetItemRelationTypeData, GetItemRelationTypeResponse, GetRelationTypeData, GetRelationTypeResponse, GetRelationTypeByIdData, GetRelationTypeByIdResponse, GetRelationByRelationTypeIdData, GetRelationByRelationTypeIdResponse, GetItemScriptData, GetItemScriptResponse, PostScriptData, PostScriptResponse, GetScriptByPathData, GetScriptByPathResponse, DeleteScriptByPathData, DeleteScriptByPathResponse, PutScriptByPathData, PutScriptByPathResponse, PutScriptByPathRenameData, PutScriptByPathRenameResponse, PostScriptFolderData, PostScriptFolderResponse, GetScriptFolderByPathData, GetScriptFolderByPathResponse, DeleteScriptFolderByPathData, DeleteScriptFolderByPathResponse, GetTreeScriptAncestorsData, GetTreeScriptAncestorsResponse, GetTreeScriptChildrenData, GetTreeScriptChildrenResponse, GetTreeScriptRootData, GetTreeScriptRootResponse, GetSearcherData, GetSearcherResponse, GetSearcherBySearcherNameQueryData, GetSearcherBySearcherNameQueryResponse, GetSecurityConfigurationResponse, PostSecurityForgotPasswordData, PostSecurityForgotPasswordResponse, PostSecurityForgotPasswordResetData, PostSecurityForgotPasswordResetResponse, PostSecurityForgotPasswordVerifyData, PostSecurityForgotPasswordVerifyResponse, GetSegmentData, GetSegmentResponse, GetServerConfigurationResponse, GetServerInformationResponse, GetServerStatusResponse, GetServerTroubleshootingResponse, GetItemStaticFileData, GetItemStaticFileResponse, GetTreeStaticFileAncestorsData, GetTreeStaticFileAncestorsResponse, GetTreeStaticFileChildrenData, GetTreeStaticFileChildrenResponse, GetTreeStaticFileRootData, GetTreeStaticFileRootResponse, GetItemStylesheetData, GetItemStylesheetResponse, PostStylesheetData, PostStylesheetResponse, GetStylesheetByPathData, GetStylesheetByPathResponse, DeleteStylesheetByPathData, DeleteStylesheetByPathResponse, PutStylesheetByPathData, PutStylesheetByPathResponse, PutStylesheetByPathRenameData, PutStylesheetByPathRenameResponse, PostStylesheetFolderData, PostStylesheetFolderResponse, GetStylesheetFolderByPathData, GetStylesheetFolderByPathResponse, DeleteStylesheetFolderByPathData, DeleteStylesheetFolderByPathResponse, GetTreeStylesheetAncestorsData, GetTreeStylesheetAncestorsResponse, GetTreeStylesheetChildrenData, GetTreeStylesheetChildrenResponse, GetTreeStylesheetRootData, GetTreeStylesheetRootResponse, GetTagData, GetTagResponse, GetTelemetryData, GetTelemetryResponse, GetTelemetryLevelResponse, PostTelemetryLevelData, PostTelemetryLevelResponse, GetItemTemplateData, GetItemTemplateResponse, GetItemTemplateSearchData, GetItemTemplateSearchResponse, PostTemplateData, PostTemplateResponse, GetTemplateByIdData, GetTemplateByIdResponse, DeleteTemplateByIdData, DeleteTemplateByIdResponse, PutTemplateByIdData, PutTemplateByIdResponse, GetTemplateConfigurationResponse, PostTemplateQueryExecuteData, PostTemplateQueryExecuteResponse, GetTemplateQuerySettingsResponse, GetTreeTemplateAncestorsData, GetTreeTemplateAncestorsResponse, GetTreeTemplateChildrenData, GetTreeTemplateChildrenResponse, GetTreeTemplateRootData, GetTreeTemplateRootResponse, PostTemporaryFileData, PostTemporaryFileResponse, GetTemporaryFileByIdData, GetTemporaryFileByIdResponse, DeleteTemporaryFileByIdData, DeleteTemporaryFileByIdResponse, GetTemporaryFileConfigurationResponse, PostUpgradeAuthorizeResponse, GetUpgradeSettingsResponse, PostUserDataData, PostUserDataResponse, GetUserDataData, GetUserDataResponse, PutUserDataData, PutUserDataResponse, GetUserDataByIdData, GetUserDataByIdResponse, GetFilterUserGroupData, GetFilterUserGroupResponse, GetItemUserGroupData, GetItemUserGroupResponse, DeleteUserGroupData, DeleteUserGroupResponse, PostUserGroupData, PostUserGroupResponse, GetUserGroupData, GetUserGroupResponse, GetUserGroupByIdData, GetUserGroupByIdResponse, DeleteUserGroupByIdData, DeleteUserGroupByIdResponse, PutUserGroupByIdData, PutUserGroupByIdResponse, DeleteUserGroupByIdUsersData, DeleteUserGroupByIdUsersResponse, PostUserGroupByIdUsersData, PostUserGroupByIdUsersResponse, GetFilterUserData, GetFilterUserResponse, GetItemUserData, GetItemUserResponse, PostUserData, PostUserResponse, DeleteUserData, DeleteUserResponse, GetUserData, GetUserResponse, GetUserByIdData, GetUserByIdResponse, DeleteUserByIdData, DeleteUserByIdResponse, PutUserByIdData, PutUserByIdResponse, GetUserById2FaData, GetUserById2FaResponse, DeleteUserById2FaByProviderNameData, DeleteUserById2FaByProviderNameResponse, GetUserByIdCalculateStartNodesData, GetUserByIdCalculateStartNodesResponse, PostUserByIdChangePasswordData, PostUserByIdChangePasswordResponse, PostUserByIdResetPasswordData, PostUserByIdResetPasswordResponse, DeleteUserAvatarByIdData, DeleteUserAvatarByIdResponse, PostUserAvatarByIdData, PostUserAvatarByIdResponse, GetUserConfigurationResponse, GetUserCurrentResponse, GetUserCurrent2FaResponse, DeleteUserCurrent2FaByProviderNameData, DeleteUserCurrent2FaByProviderNameResponse, PostUserCurrent2FaByProviderNameData, PostUserCurrent2FaByProviderNameResponse, GetUserCurrent2FaByProviderNameData, GetUserCurrent2FaByProviderNameResponse, PostUserCurrentAvatarData, PostUserCurrentAvatarResponse, PostUserCurrentChangePasswordData, PostUserCurrentChangePasswordResponse, GetUserCurrentConfigurationResponse, GetUserCurrentLoginProvidersResponse, GetUserCurrentPermissionsData, GetUserCurrentPermissionsResponse, GetUserCurrentPermissionsDocumentData, GetUserCurrentPermissionsDocumentResponse, GetUserCurrentPermissionsMediaData, GetUserCurrentPermissionsMediaResponse, PostUserDisableData, PostUserDisableResponse, PostUserEnableData, PostUserEnableResponse, PostUserInviteData, PostUserInviteResponse, PostUserInviteCreatePasswordData, PostUserInviteCreatePasswordResponse, PostUserInviteResendData, PostUserInviteResendResponse, PostUserInviteVerifyData, PostUserInviteVerifyResponse, PostUserSetUserGroupsData, PostUserSetUserGroupsResponse, PostUserUnlockData, PostUserUnlockResponse, GetItemWebhookData, GetItemWebhookResponse, GetWebhookData, GetWebhookResponse, PostWebhookData, PostWebhookResponse, GetWebhookByIdData, GetWebhookByIdResponse, DeleteWebhookByIdData, DeleteWebhookByIdResponse, PutWebhookByIdData, PutWebhookByIdResponse, GetWebhookEventsData, GetWebhookEventsResponse, PostCurrentUserChangePasswordData, PostCurrentUserChangePasswordResponse } from './types.gen'; export class CultureService { /** @@ -8102,6 +8102,29 @@ export class UserService { } }); } + + /** + * @param data The data for the request. + * @param data.id + * @param data.requestBody + * @returns string OK + * @throws ApiError + */ + public static postCurrentUserByIdChangePassword(data: PostCurrentUserChangePasswordData): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/umbraco/management/api/v1/user/current/change-password', + body: data.requestBody, + mediaType: 'application/json', + responseHeader: 'Umb-Notifications', + errors: { + 400: 'Bad Request', + 401: 'The resource is protected and requires an authentication token', + 403: 'The authenticated user do not have access to this resource', + 404: 'Not Found' + } + }); + } /** * @param data The data for the request. diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts index 2d29b8b87e..ffc97171b1 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts @@ -5068,6 +5068,12 @@ export type PostUserByIdChangePasswordData = { export type PostUserByIdChangePasswordResponse = string; +export type PostCurrentUserChangePasswordData = { + requestBody?: ChangePasswordCurrentUserRequestModel; +}; + +export type PostCurrentUserChangePasswordResponse = string; + export type PostUserByIdResetPasswordData = { id: string; }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/change-password-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/change-password-modal.token.ts index d6a41803fb..7ee45ba144 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/change-password-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/change-password-modal.token.ts @@ -9,6 +9,7 @@ export interface UmbChangePasswordModalData { export interface UmbChangePasswordModalValue { oldPassword: string; newPassword: string; + isCurrentUser: boolean; } export const UMB_CHANGE_PASSWORD_MODAL = new UmbModalToken( diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/change-password-current-user.action.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/change-password-current-user.action.ts index e7607510f4..2dabb97704 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/change-password-current-user.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/change-password-current-user.action.ts @@ -3,6 +3,7 @@ import { UmbActionBase } from '@umbraco-cms/backoffice/action'; import type { UmbCurrentUserAction, UmbCurrentUserActionArgs } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UMB_CHANGE_PASSWORD_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { UmbChangeCurrentUserPasswordRepository } from '../repository/index.js'; export class UmbChangePasswordCurrentUserAction extends UmbActionBase> @@ -32,13 +33,17 @@ export class UmbChangePasswordCurrentUserAction if (!this.#unique) return; const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); - modalManager.open(this, UMB_CHANGE_PASSWORD_MODAL, { + const modalContext = modalManager.open(this, UMB_CHANGE_PASSWORD_MODAL, { data: { user: { unique: this.#unique, }, }, }); + + const data = await modalContext.onSubmit(); + const repository = new UmbChangeCurrentUserPasswordRepository(this); + await repository.changePassword(this.#unique, data.newPassword, data.oldPassword, data.isCurrentUser); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/change-current-user-password.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/change-current-user-password.repository.ts new file mode 100644 index 0000000000..939fa30ff8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/change-current-user-password.repository.ts @@ -0,0 +1,35 @@ +import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbCurrentUserRepository } from '../current-user.repository.js'; +import type { UmbNotificationContext } from '@umbraco-cms/backoffice/notification'; +import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; +import {UmbChangeCurrentUserPasswordServerDataSource} from './change-current-user-password.server.data-source.js' +export class UmbChangeCurrentUserPasswordRepository extends UmbCurrentUserRepository{ + #changePasswordSource: UmbChangeCurrentUserPasswordServerDataSource; + protected notificationContext?: UmbNotificationContext; + + constructor(host: UmbControllerHost){ + super(host); + this.#changePasswordSource = new UmbChangeCurrentUserPasswordServerDataSource(host); + + this.consumeContext(UMB_NOTIFICATION_CONTEXT, (instance) => { + this.notificationContext = instance; + }).asPromise(); + } + + async changePassword(userId: string, newPassword: string, oldPassword: string, isCurrentUser: boolean) { + if (!userId) throw new Error('User id is missing'); + if (!newPassword) throw new Error('New password is missing'); + if (isCurrentUser && !oldPassword) throw new Error('Old password is missing'); + + const { data, error } = await this.#changePasswordSource.changePassword(userId, newPassword, oldPassword, isCurrentUser); + + if (!error) { + const notification = { data: { message: `Password changed` } }; + this.notificationContext?.peek('positive', notification); + } + + return { data, error }; + } +} + +export default UmbChangeCurrentUserPasswordRepository; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/change-current-user-password.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/change-current-user-password.server.data-source.ts new file mode 100644 index 0000000000..026d518e7c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/change-current-user-password.server.data-source.ts @@ -0,0 +1,55 @@ +import { UserService } from '@umbraco-cms/backoffice/external/backend-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * A server data source for changing the password of a user + * @export + * @class UmbChangeCurrentUserPasswordServerDataSource + */ +export class UmbChangeCurrentUserPasswordServerDataSource { + #host: UmbControllerHost; + + /** + * Creates an instance of UmbChangeCurrentUserPasswordServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbChangeCurrentUserPasswordServerDataSource + */ + constructor(host: UmbControllerHost) { + this.#host = host; + } + + /** + * Change the password of a user + * @param {string} id + * @param {string} newPassword + * @returns {*} + * @memberof UmbChangeCurrentUserPasswordServerDataSource + */ + async changePassword(id: string, newPassword: string, oldPassword: string, isCurrentUser: boolean) { + if (!id) throw new Error('User Id is missing'); + + if(isCurrentUser){ + return tryExecuteAndNotify( + this.#host, + UserService.postCurrentUserByIdChangePassword({ + requestBody: { + newPassword, + oldPassword + }, + }), + ); + } + else{ + return tryExecuteAndNotify( + this.#host, + UserService.postUserByIdChangePassword({ + id, + requestBody: { + newPassword + }, + }), + ); + } + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/index.ts new file mode 100644 index 0000000000..a3b69a44c7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/index.ts @@ -0,0 +1 @@ +export { UmbChangeCurrentUserPasswordRepository } from './change-current-user-password.repository.js' \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/index.ts index d3e468a0b0..5b67e54c5f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/index.ts @@ -2,3 +2,4 @@ export { UMB_CURRENT_USER_REPOSITORY_ALIAS } from './constants.js'; export { UMB_CURRENT_USER_STORE_CONTEXT } from './current-user.store.token.js'; export { UmbCurrentUserRepository } from './current-user.repository.js'; export { UmbCurrentUserStore } from './current-user.store.js'; +export * from './change-password/index.js'; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/modals/change-password/change-password-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/modals/change-password/change-password-modal.element.ts index 09a0819e60..587a24d149 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/modals/change-password/change-password-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/modals/change-password/change-password-modal.element.ts @@ -38,9 +38,10 @@ export class UmbChangePasswordModalElement extends UmbModalBaseElement< // TODO: validate that the new password and confirm password match const oldPassword = formData.get('oldPassword') as string; const newPassword = formData.get('newPassword') as string; + const isCurrentUser = this._isCurrentUser; //const confirmPassword = formData.get('confirmPassword') as string; - this.value = { oldPassword, newPassword }; + this.value = { oldPassword, newPassword, isCurrentUser }; this.modalContext?.submit(); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts index a052d53082..2ed0dad977 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts @@ -24,7 +24,7 @@ export class UmbChangeUserPasswordEntityAction extends UmbEntityActionBase Date: Wed, 28 Aug 2024 13:55:22 +0700 Subject: [PATCH 35/47] delete redundant code --- .../change-password-current-user.action.ts | 5 +- ...change-current-user-password.repository.ts | 35 ------------ ...urrent-user-password.server.data-source.ts | 55 ------------------- .../repository/change-password/index.ts | 1 - .../user/current-user/repository/index.ts | 3 +- 5 files changed, 3 insertions(+), 96 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/change-current-user-password.repository.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/change-current-user-password.server.data-source.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/index.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/change-password-current-user.action.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/change-password-current-user.action.ts index 2dabb97704..c167e62841 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/change-password-current-user.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/change-password-current-user.action.ts @@ -3,8 +3,7 @@ import { UmbActionBase } from '@umbraco-cms/backoffice/action'; import type { UmbCurrentUserAction, UmbCurrentUserActionArgs } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UMB_CHANGE_PASSWORD_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; -import { UmbChangeCurrentUserPasswordRepository } from '../repository/index.js'; - +import {UmbChangeUserPasswordRepository} from '../../../user/user/repository/change-password/change-user-password.repository.js' export class UmbChangePasswordCurrentUserAction extends UmbActionBase> implements UmbCurrentUserAction @@ -42,7 +41,7 @@ export class UmbChangePasswordCurrentUserAction }); const data = await modalContext.onSubmit(); - const repository = new UmbChangeCurrentUserPasswordRepository(this); + const repository = new UmbChangeUserPasswordRepository(this); await repository.changePassword(this.#unique, data.newPassword, data.oldPassword, data.isCurrentUser); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/change-current-user-password.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/change-current-user-password.repository.ts deleted file mode 100644 index 939fa30ff8..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/change-current-user-password.repository.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbCurrentUserRepository } from '../current-user.repository.js'; -import type { UmbNotificationContext } from '@umbraco-cms/backoffice/notification'; -import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; -import {UmbChangeCurrentUserPasswordServerDataSource} from './change-current-user-password.server.data-source.js' -export class UmbChangeCurrentUserPasswordRepository extends UmbCurrentUserRepository{ - #changePasswordSource: UmbChangeCurrentUserPasswordServerDataSource; - protected notificationContext?: UmbNotificationContext; - - constructor(host: UmbControllerHost){ - super(host); - this.#changePasswordSource = new UmbChangeCurrentUserPasswordServerDataSource(host); - - this.consumeContext(UMB_NOTIFICATION_CONTEXT, (instance) => { - this.notificationContext = instance; - }).asPromise(); - } - - async changePassword(userId: string, newPassword: string, oldPassword: string, isCurrentUser: boolean) { - if (!userId) throw new Error('User id is missing'); - if (!newPassword) throw new Error('New password is missing'); - if (isCurrentUser && !oldPassword) throw new Error('Old password is missing'); - - const { data, error } = await this.#changePasswordSource.changePassword(userId, newPassword, oldPassword, isCurrentUser); - - if (!error) { - const notification = { data: { message: `Password changed` } }; - this.notificationContext?.peek('positive', notification); - } - - return { data, error }; - } -} - -export default UmbChangeCurrentUserPasswordRepository; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/change-current-user-password.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/change-current-user-password.server.data-source.ts deleted file mode 100644 index 026d518e7c..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/change-current-user-password.server.data-source.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { UserService } from '@umbraco-cms/backoffice/external/backend-api'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; - -/** - * A server data source for changing the password of a user - * @export - * @class UmbChangeCurrentUserPasswordServerDataSource - */ -export class UmbChangeCurrentUserPasswordServerDataSource { - #host: UmbControllerHost; - - /** - * Creates an instance of UmbChangeCurrentUserPasswordServerDataSource. - * @param {UmbControllerHost} host - * @memberof UmbChangeCurrentUserPasswordServerDataSource - */ - constructor(host: UmbControllerHost) { - this.#host = host; - } - - /** - * Change the password of a user - * @param {string} id - * @param {string} newPassword - * @returns {*} - * @memberof UmbChangeCurrentUserPasswordServerDataSource - */ - async changePassword(id: string, newPassword: string, oldPassword: string, isCurrentUser: boolean) { - if (!id) throw new Error('User Id is missing'); - - if(isCurrentUser){ - return tryExecuteAndNotify( - this.#host, - UserService.postCurrentUserByIdChangePassword({ - requestBody: { - newPassword, - oldPassword - }, - }), - ); - } - else{ - return tryExecuteAndNotify( - this.#host, - UserService.postUserByIdChangePassword({ - id, - requestBody: { - newPassword - }, - }), - ); - } - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/index.ts deleted file mode 100644 index a3b69a44c7..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/change-password/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { UmbChangeCurrentUserPasswordRepository } from './change-current-user-password.repository.js' \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/index.ts index 5b67e54c5f..28899632b9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/index.ts @@ -1,5 +1,4 @@ export { UMB_CURRENT_USER_REPOSITORY_ALIAS } from './constants.js'; export { UMB_CURRENT_USER_STORE_CONTEXT } from './current-user.store.token.js'; export { UmbCurrentUserRepository } from './current-user.repository.js'; -export { UmbCurrentUserStore } from './current-user.store.js'; -export * from './change-password/index.js'; \ No newline at end of file +export { UmbCurrentUserStore } from './current-user.store.js'; \ No newline at end of file From 5db3b7353fd3760736ed095a9f14e4a74f31e20f Mon Sep 17 00:00:00 2001 From: Lan Nguyen Thuy Date: Wed, 28 Aug 2024 17:41:46 +0700 Subject: [PATCH 36/47] move change to current user repository --- .../change-password-current-user.action.ts | 6 ++-- .../repository/current-user.repository.ts | 27 +++++++++++++++ .../current-user.server.data-source.ts | 20 +++++++++++ .../change-user-password.action.ts | 12 +++++-- .../change-user-password.repository.ts | 5 ++- ...change-user-password.server.data-source.ts | 33 ++++++------------- 6 files changed, 72 insertions(+), 31 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/change-password-current-user.action.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/change-password-current-user.action.ts index c167e62841..038b97a215 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/change-password-current-user.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/change-password-current-user.action.ts @@ -3,7 +3,7 @@ import { UmbActionBase } from '@umbraco-cms/backoffice/action'; import type { UmbCurrentUserAction, UmbCurrentUserActionArgs } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UMB_CHANGE_PASSWORD_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; -import {UmbChangeUserPasswordRepository} from '../../../user/user/repository/change-password/change-user-password.repository.js' +import UmbCurrentUserRepository from '../repository/current-user.repository.js'; export class UmbChangePasswordCurrentUserAction extends UmbActionBase> implements UmbCurrentUserAction @@ -41,8 +41,8 @@ export class UmbChangePasswordCurrentUserAction }); const data = await modalContext.onSubmit(); - const repository = new UmbChangeUserPasswordRepository(this); - await repository.changePassword(this.#unique, data.newPassword, data.oldPassword, data.isCurrentUser); + const repository = new UmbCurrentUserRepository(this); + await repository.changePassword(data.newPassword, data.oldPassword); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.repository.ts index a97f2257c9..4b736e9459 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.repository.ts @@ -2,6 +2,7 @@ import { UmbCurrentUserServerDataSource } from './current-user.server.data-sourc import { UMB_CURRENT_USER_STORE_CONTEXT } from './current-user.store.token.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; +import { UMB_NOTIFICATION_CONTEXT, UmbNotificationContext } from '@umbraco-cms/backoffice/notification'; /** * A repository for the current user @@ -12,6 +13,7 @@ export class UmbCurrentUserRepository extends UmbRepositoryBase { #currentUserSource = new UmbCurrentUserServerDataSource(this._host); #currentUserStore?: typeof UMB_CURRENT_USER_STORE_CONTEXT.TYPE; #init: Promise; + protected notificationContext?: UmbNotificationContext; constructor(host: UmbControllerHost) { super(host); @@ -20,6 +22,10 @@ export class UmbCurrentUserRepository extends UmbRepositoryBase { this.consumeContext(UMB_CURRENT_USER_STORE_CONTEXT, (instance) => { this.#currentUserStore = instance; }).asPromise(), + + this.consumeContext(UMB_NOTIFICATION_CONTEXT, (instance) => { + this.notificationContext = instance; + }).asPromise(), ]); } @@ -108,6 +114,27 @@ export class UmbCurrentUserRepository extends UmbRepositoryBase { return {}; } + /** + * Change password for current user + * @param userId + * @param newPassword + * @param oldPassword + * @param isCurrentUser + * @returns + */ + async changePassword(newPassword: string, oldPassword: string) { + if (!newPassword) throw new Error('New password is missing'); + if (!oldPassword) throw new Error('Old password is missing'); + + const { data, error } = await this.#currentUserSource.changePassword(newPassword, oldPassword); + + if (!error) { + const notification = { data: { message: `Password changed` } }; + this.notificationContext?.peek('positive', notification); + } + + return { data, error }; + } } export default UmbCurrentUserRepository; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.server.data-source.ts index 5e46118f53..31cc1a5694 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.server.data-source.ts @@ -115,4 +115,24 @@ export class UmbCurrentUserServerDataSource { return {}; } + + /** + * Change the password for current user + * @param id + * @param newPassword + * @param oldPassword + * @param isCurrentUser + * @returns + */ + async changePassword(newPassword: string, oldPassword: string) { + return tryExecuteAndNotify( + this.#host, + UserService.postCurrentUserByIdChangePassword({ + requestBody: { + newPassword, + oldPassword + }, + }), + ); + } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts index 2ed0dad977..d9ef88b767 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts @@ -3,6 +3,7 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbEntityActionArgs } from '@umbraco-cms/backoffice/entity-action'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import { UMB_MODAL_MANAGER_CONTEXT, UMB_CHANGE_PASSWORD_MODAL } from '@umbraco-cms/backoffice/modal'; +import UmbCurrentUserRepository from 'src/packages/user/current-user/repository/current-user.repository.js'; export class UmbChangeUserPasswordEntityAction extends UmbEntityActionBase { constructor(host: UmbControllerHost, args: UmbEntityActionArgs) { @@ -23,8 +24,15 @@ export class UmbChangeUserPasswordEntityAction extends UmbEntityActionBase Date: Thu, 29 Aug 2024 15:38:10 +0200 Subject: [PATCH 37/47] export repository from current user module --- .../src/packages/user/current-user/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/index.ts index 5a4c7ecb02..343c0ed765 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/index.ts @@ -2,6 +2,8 @@ export * from './action/index.js'; export * from './components/index.js'; export * from './history/current-user-history.store.js'; export * from './utils/index.js'; +export * from './repository/index.js'; export * from './current-user.context.js'; export * from './current-user.context.token.js'; + export type * from './types.js'; From 947f7ae0f475e572a95d6bfca6010a8b43b5b8ab Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 29 Aug 2024 15:38:34 +0200 Subject: [PATCH 38/47] add type to import --- .../repository/current-user.repository.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.repository.ts index 4b736e9459..e823c37939 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.repository.ts @@ -2,7 +2,8 @@ import { UmbCurrentUserServerDataSource } from './current-user.server.data-sourc import { UMB_CURRENT_USER_STORE_CONTEXT } from './current-user.store.token.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; -import { UMB_NOTIFICATION_CONTEXT, UmbNotificationContext } from '@umbraco-cms/backoffice/notification'; +import type { UmbNotificationContext } from '@umbraco-cms/backoffice/notification'; +import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; /** * A repository for the current user @@ -116,11 +117,11 @@ export class UmbCurrentUserRepository extends UmbRepositoryBase { } /** * Change password for current user - * @param userId - * @param newPassword - * @param oldPassword - * @param isCurrentUser - * @returns + * @param userId + * @param newPassword + * @param oldPassword + * @param isCurrentUser + * @returns */ async changePassword(newPassword: string, oldPassword: string) { if (!newPassword) throw new Error('New password is missing'); From 3124cfdfec2ad91c521615d060e3a88d563b7f7a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 29 Aug 2024 15:38:56 +0200 Subject: [PATCH 39/47] update import to use alias instead of relative path --- .../change-password/change-user-password.action.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts index d9ef88b767..e3b583370b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts @@ -3,7 +3,7 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbEntityActionArgs } from '@umbraco-cms/backoffice/entity-action'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import { UMB_MODAL_MANAGER_CONTEXT, UMB_CHANGE_PASSWORD_MODAL } from '@umbraco-cms/backoffice/modal'; -import UmbCurrentUserRepository from 'src/packages/user/current-user/repository/current-user.repository.js'; +import { UmbCurrentUserRepository } from '@umbraco-cms/backoffice/current-user'; export class UmbChangeUserPasswordEntityAction extends UmbEntityActionBase { constructor(host: UmbControllerHost, args: UmbEntityActionArgs) { From 264883b44599954f06688baf55159264f07748c8 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 29 Aug 2024 15:42:16 +0200 Subject: [PATCH 40/47] import from barrel --- .../current-user/profile/change-password-current-user.action.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/change-password-current-user.action.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/change-password-current-user.action.ts index 038b97a215..b37cfaeaa7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/change-password-current-user.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/profile/change-password-current-user.action.ts @@ -1,9 +1,9 @@ import { UMB_CURRENT_USER_CONTEXT } from '../current-user.context.token.js'; +import { UmbCurrentUserRepository } from '../repository/index.js'; import { UmbActionBase } from '@umbraco-cms/backoffice/action'; import type { UmbCurrentUserAction, UmbCurrentUserActionArgs } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UMB_CHANGE_PASSWORD_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; -import UmbCurrentUserRepository from '../repository/current-user.repository.js'; export class UmbChangePasswordCurrentUserAction extends UmbActionBase> implements UmbCurrentUserAction From 369b125ac71d6964658cd239ce35dd668a540d8d Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 29 Aug 2024 15:51:07 +0200 Subject: [PATCH 41/47] remove requirement on modal value --- .../src/packages/core/modal/token/change-password-modal.token.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/change-password-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/change-password-modal.token.ts index 7ee45ba144..d6a41803fb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/change-password-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/change-password-modal.token.ts @@ -9,7 +9,6 @@ export interface UmbChangePasswordModalData { export interface UmbChangePasswordModalValue { oldPassword: string; newPassword: string; - isCurrentUser: boolean; } export const UMB_CHANGE_PASSWORD_MODAL = new UmbModalToken( From ee6f9d66b82a9df755b0006139ff3526d828333f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 29 Aug 2024 15:51:25 +0200 Subject: [PATCH 42/47] remove isCurrentUser from value --- .../modals/change-password/change-password-modal.element.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/modals/change-password/change-password-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/modals/change-password/change-password-modal.element.ts index 587a24d149..09a0819e60 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/modals/change-password/change-password-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/modals/change-password/change-password-modal.element.ts @@ -38,10 +38,9 @@ export class UmbChangePasswordModalElement extends UmbModalBaseElement< // TODO: validate that the new password and confirm password match const oldPassword = formData.get('oldPassword') as string; const newPassword = formData.get('newPassword') as string; - const isCurrentUser = this._isCurrentUser; //const confirmPassword = formData.get('confirmPassword') as string; - this.value = { oldPassword, newPassword, isCurrentUser }; + this.value = { oldPassword, newPassword }; this.modalContext?.submit(); } From 630432c7a739fa49523c2fd88bb893cd8d25ebaa Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 29 Aug 2024 15:51:50 +0200 Subject: [PATCH 43/47] check for current user in entity action --- .../change-password/change-user-password.action.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts index e3b583370b..0b31df09d0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts @@ -3,7 +3,7 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbEntityActionArgs } from '@umbraco-cms/backoffice/entity-action'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import { UMB_MODAL_MANAGER_CONTEXT, UMB_CHANGE_PASSWORD_MODAL } from '@umbraco-cms/backoffice/modal'; -import { UmbCurrentUserRepository } from '@umbraco-cms/backoffice/current-user'; +import { UMB_CURRENT_USER_CONTEXT, UmbCurrentUserRepository } from '@umbraco-cms/backoffice/current-user'; export class UmbChangeUserPasswordEntityAction extends UmbEntityActionBase { constructor(host: UmbControllerHost, args: UmbEntityActionArgs) { @@ -24,15 +24,16 @@ export class UmbChangeUserPasswordEntityAction extends UmbEntityActionBase Date: Thu, 29 Aug 2024 16:01:04 +0200 Subject: [PATCH 44/47] remove type error --- .../src/packages/core/sorter/sorter.controller.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts index 3595ec1376..52b38e4a99 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts @@ -16,9 +16,6 @@ class UmbSorterTestElement extends UmbLitElement { itemSelector: '.item', containerSelector: '#container', disabledItemSelector: '.disabled', - onChange: ({ model }) => { - this.model = model; - }, }); getAllItems() { From 33436f5d4a36e3cce2c764ebb67cdc543c22c618 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 29 Aug 2024 16:20:34 +0200 Subject: [PATCH 45/47] combine change password files in same folder --- .../change-password-modal.element.ts | 0 .../change-user-password.action.ts | 2 +- .../user/change-password/manifests.ts | 24 +++++++++++++++++++ .../src/packages/user/manifests.ts | 4 ++-- .../src/packages/user/modals/manifests.ts | 12 ---------- .../user/user/entity-actions/manifests.ts | 13 ---------- 6 files changed, 27 insertions(+), 28 deletions(-) rename src/Umbraco.Web.UI.Client/src/packages/user/{modals => }/change-password/change-password-modal.element.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/user/{user/entity-actions => }/change-password/change-user-password.action.ts (95%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/change-password/manifests.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/modals/manifests.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/modals/change-password/change-password-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/change-password/change-password-modal.element.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/user/modals/change-password/change-password-modal.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/change-password/change-password-modal.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts b/src/Umbraco.Web.UI.Client/src/packages/user/change-password/change-user-password.action.ts similarity index 95% rename from src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/change-password/change-user-password.action.ts index 0b31df09d0..fbf0e35929 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/change-password/change-user-password.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/change-password/change-user-password.action.ts @@ -1,4 +1,4 @@ -import { UmbChangeUserPasswordRepository } from '../../repository/index.js'; +import { UmbChangeUserPasswordRepository } from '@umbraco-cms/backoffice/user'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbEntityActionArgs } from '@umbraco-cms/backoffice/entity-action'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/change-password/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/change-password/manifests.ts new file mode 100644 index 0000000000..083dc29663 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/change-password/manifests.ts @@ -0,0 +1,24 @@ +import type { ManifestTypes, UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry'; +import { UMB_USER_ENTITY_TYPE } from '@umbraco-cms/backoffice/user'; + +export const manifests: Array = [ + { + type: 'entityAction', + kind: 'default', + alias: 'Umb.EntityAction.User.ChangePassword', + name: 'Change User Password Entity Action', + weight: 600, + api: () => import('./change-user-password.action.js'), + forEntityTypes: [UMB_USER_ENTITY_TYPE], + meta: { + icon: 'icon-key', + label: '#user_changePassword', + }, + }, + { + type: 'modal', + alias: 'Umb.Modal.ChangePassword', + name: 'Change Password Modal', + js: () => import('./change-password-modal.element.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/manifests.ts index 16faa5c71d..96353d28f7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/manifests.ts @@ -3,7 +3,7 @@ import { manifests as userManifests } from './user/manifests.js'; import { manifests as userSectionManifests } from './user-section/manifests.js'; import { manifests as currentUserManifests } from './current-user/manifests.js'; import { manifests as userPermissionManifests } from './user-permission/manifests.js'; -import { manifests as modalManifests } from './modals/manifests.js'; +import { manifests as changePasswordManifests } from './change-password/manifests.js'; // We need to load any components that are not loaded by the user management bundle to register them in the browser. import './user-group/components/index.js'; @@ -16,5 +16,5 @@ export const manifests = [ ...userSectionManifests, ...currentUserManifests, ...userPermissionManifests, - ...modalManifests, + ...changePasswordManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/modals/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/modals/manifests.ts deleted file mode 100644 index 8c373a336e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/user/modals/manifests.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { ManifestModal, ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; - -const modals: Array = [ - { - type: 'modal', - alias: 'Umb.Modal.ChangePassword', - name: 'Change Password Modal', - js: () => import('./change-password/change-password-modal.element.js'), - }, -]; - -export const manifests: Array = [...modals]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/manifests.ts index 8e26c3c2f1..b67955449e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/entity-actions/manifests.ts @@ -55,19 +55,6 @@ const entityActions: Array = [ }, ], }, - { - type: 'entityAction', - kind: 'default', - alias: 'Umb.EntityAction.User.ChangePassword', - name: 'Change User Password Entity Action', - weight: 600, - api: () => import('./change-password/change-user-password.action.js'), - forEntityTypes: [UMB_USER_ENTITY_TYPE], - meta: { - icon: 'icon-key', - label: '#user_changePassword', - }, - }, { type: 'entityAction', kind: 'default', From e346868bf1a00f097a41739c768aee75912aaaab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 30 Aug 2024 08:40:39 +0200 Subject: [PATCH 46/47] fix test --- .../property-editor-ui-block-grid.element.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 57c11096d4..0c597e5ded 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 @@ -132,7 +132,9 @@ export class UmbPropertyEditorUIBlockGridElement this.removeFormControlElement(this.#currentEntriesElement as any); } this.#currentEntriesElement = element; - this.addFormControlElement(element as any); + if (element) { + this.addFormControlElement(element as any); + } } override render() { From 14a7c6d6fc83e7ee1968afb0f2ff901bc785692a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 30 Aug 2024 09:16:03 +0200 Subject: [PATCH 47/47] comment on test --- .../src/packages/core/sorter/sorter.controller.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts index 065e27a109..5e2e200699 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts @@ -149,6 +149,7 @@ describe('UmbSorterController', () => { describe('enable', () => { it('sets all allowed items to draggable', () => { + // [NL] I have experienced an issue with this test, it may need a little delay before testing for this. As the test relies on DOM. const items = element.getSortableItems(); expect(items.length).to.equal(3); items.forEach((item) => {