diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/block-grid-manager/block-grid-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/block-grid-manager/block-grid-manager.context.ts
index dea6494f106f..70206bc8f333 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/block-grid-manager/block-grid-manager.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/block-grid-manager/block-grid-manager.context.ts
@@ -216,6 +216,7 @@ export class UmbBlockGridManagerContext<
) {
this.setOneLayout(layoutEntry, originData);
this.insertBlockData(layoutEntry, content, settings, originData);
+ this.notifyBlockInserted(layoutEntry, originData);
return true;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.context.ts
index 2d1dc222588b..5efe02fe33e3 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.context.ts
@@ -226,6 +226,7 @@ export class UmbBlockGridEntriesContext
areaKey: this.#areaKey,
parentUnique: this.#parentUnique,
} as UmbBlockGridWorkspaceOriginData,
+ // TODO: Check if we use this for anything. I think its not possible to configure inline editing for block grid? [NL]
createBlockInWorkspace: this._manager.getInlineEditingMode() === false,
},
};
@@ -440,7 +441,23 @@ export class UmbBlockGridEntriesContext
}
getPathForCreateBlock(index: number) {
- return this._catalogueRouteBuilderState.getValue()?.({ view: 'create', index: index });
+ const pathBuilder = this._catalogueRouteBuilderState.getValue();
+ if (!pathBuilder) return undefined;
+
+ const blockTypes = this.#allowedBlockTypes.getValue();
+ if (blockTypes?.length === 1) {
+ const elementKey = blockTypes[0].contentElementTypeKey;
+
+ if (!this._manager) return undefined;
+ // does the Block have any Content properties?
+ const contentTypeKey = this._manager.getContentTypeKeyOfContentKey(elementKey);
+ if (contentTypeKey && this._manager.getContentTypeHasProperties(contentTypeKey) === false) {
+ return undefined;
+ }
+ return pathBuilder({ view: 'create', index: index }) + 'modal/umb-modal-workspace/create/' + elementKey;
+ }
+
+ return pathBuilder({ view: 'create', index: index });
}
getPathForClipboard(index: number) {
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 b12673e6558e..d055f0e322fc 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
@@ -1,7 +1,18 @@
import type { UmbBlockGridEntryElement } from '../block-grid-entry/block-grid-entry.element.js';
-import type { UmbBlockGridLayoutModel } from '../../types.js';
+import type { UmbBlockGridLayoutModel, UmbBlockGridTypeModel } from '../../types.js';
+import type { UmbBlockGridWorkspaceOriginData } from '../../workspace/block-grid-workspace.modal-token.js';
import { UmbBlockGridEntriesContext } from './block-grid-entries.context.js';
-import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
+import {
+ css,
+ customElement,
+ html,
+ ifDefined,
+ nothing,
+ property,
+ repeat,
+ state,
+ when,
+} from '@umbraco-cms/backoffice/external/lit';
import {
getAccumulatedValueOfIndex,
getInterpolatedIndexOfPositionInWeightMap,
@@ -174,7 +185,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
private _areaKey?: string | null;
@state()
- private _canCreate?: boolean;
+ private _allowedBlockTypes?: UmbBlockGridTypeModel[];
@state()
private _configCreateLabel?: string;
@@ -208,10 +219,10 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
null,
);
this.observe(
- this.#context.amountOfAllowedBlockTypes,
- (length) => {
- this._canCreate = length > 0;
- if (length === 1) {
+ this.#context.allowedBlockTypes,
+ (allowedBlockTypes) => {
+ this._allowedBlockTypes = allowedBlockTypes;
+ if (allowedBlockTypes.length === 1) {
this.observe(
this.#context.firstAllowedBlockTypeName(),
(firstAllowedName) => {
@@ -400,7 +411,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
`,
)}
- ${when(this._canCreate, () => this.#renderCreateButtonGroup())}
+ ${when(this._allowedBlockTypes && this._allowedBlockTypes.length > 0, () => this.#renderCreateButtonGroup())}
${when(this._areaKey, () => html``)}
`;
}
@@ -426,13 +437,43 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
#renderCreateButton() {
if (this._isReadOnly && this._layoutEntries.length > 0) return nothing;
+ const createPath = this.#context.getPathForCreateBlock(-1);
return html`
{
+ // If no path, then we can conclude there is not modal flow for the user to follow, instead we will just insert the Block: [NL]
+ if (createPath === undefined) {
+ if (!this._allowedBlockTypes || this._allowedBlockTypes.length === 0) {
+ throw new Error('No block types are configured for this Block List property editor');
+ }
+ const areaKey = this._areaKey;
+ const parentUnique = this.#context.getParentUnique();
+ if (areaKey === undefined || parentUnique === undefined) {
+ throw new Error('Cannot create block without a defined areaKey and parentUnique');
+ }
+ const originData: UmbBlockGridWorkspaceOriginData = {
+ index: -1,
+ areaKey: areaKey ?? undefined,
+ parentUnique: parentUnique,
+ };
+ const created = await this.#context.create(
+ this._allowedBlockTypes[0].contentElementTypeKey,
+ // We can parse an empty object, cause the rest will be filled in by others.
+ {} as any,
+ originData,
+ );
+ if (created) {
+ this.#context.insert(created.layout, created.content, created.settings, originData);
+ } else {
+ throw new Error('Failed to create block');
+ }
+ }
+ }}
?disabled=${this._isReadOnly}>
`;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts
index 8bb2f9fbcfaa..2b7feb3f0fde 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts
@@ -1,5 +1,10 @@
import { UMB_BLOCK_LIST_ENTRY_CONTEXT } from '../../context/index.js';
-import { UMB_BLOCK_WORKSPACE_ALIAS } from '@umbraco-cms/backoffice/block';
+import type { UmbBlockListLayoutModel, UmbBlockListWorkspaceOriginData } from '../../index.js';
+import {
+ UMB_BLOCK_MANAGER_CONTEXT,
+ UMB_BLOCK_WORKSPACE_ALIAS,
+ UmbBlockInsertedEvent,
+} from '@umbraco-cms/backoffice/block';
import { css, customElement, html, nothing, property, state, when } from '@umbraco-cms/backoffice/external/lit';
import { UmbExtensionApiInitializer, UmbExtensionsApiInitializer } from '@umbraco-cms/backoffice/extension-api';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
@@ -10,6 +15,8 @@ import type { UmbApiConstructorArgumentsMethodType } from '@umbraco-cms/backoffi
import type { UmbBlockDataType, UMB_BLOCK_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/block';
import '../../../block/workspace/views/edit/block-workspace-view-edit-content-no-router.element.js';
+import { UmbContextBoundary } from '@umbraco-cms/backoffice/context-api';
+import { UMB_VIEW_CONTEXT } from '@umbraco-cms/backoffice/view';
const apiArgsCreator: UmbApiConstructorArgumentsMethodType = (manifest: unknown) => {
return [{ manifest }];
@@ -20,6 +27,7 @@ const apiArgsCreator: UmbApiConstructorArgumentsMethodType = (manifest:
*/
@customElement('umb-inline-list-block')
export class UmbInlineListBlockElement extends UmbLitElement {
+ #manager?: typeof UMB_BLOCK_MANAGER_CONTEXT.TYPE;
#blockContext?: typeof UMB_BLOCK_LIST_ENTRY_CONTEXT.TYPE;
#workspaceContext?: typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE;
#contentKey?: string;
@@ -69,6 +77,17 @@ export class UmbInlineListBlockElement extends UmbLitElement {
);
});
+ this.consumeContext(UMB_BLOCK_MANAGER_CONTEXT, (manager) => {
+ if (this.#manager) {
+ this.#manager.removeEventListener(UmbBlockInsertedEvent.TYPE, this.#onBlockInserted);
+ }
+ this.#manager = manager;
+ this.#manager?.addEventListener(UmbBlockInsertedEvent.TYPE, this.#onBlockInserted);
+ });
+
+ // Block the access to the View Context for this inline block workspace: [NL]
+ new UmbContextBoundary(this, UMB_VIEW_CONTEXT);
+
new UmbExtensionApiInitializer(
this,
umbExtensionsRegistry,
@@ -79,6 +98,9 @@ export class UmbInlineListBlockElement extends UmbLitElement {
if (permitted && context) {
this.#workspaceContext = context;
this.#workspaceContext.establishLiveSync();
+ // Avoid view context becoming active: [NL]
+ // in this case its not a routable workspace and we do not want it to become an active view, appending shortcuts or setting browser title. (maybe this code needs to be more explicit. Like a inlineMode()?) [NL]
+ this.#workspaceContext.view.destroy();
this.#workspaceContext.autoReportValidation();
this.#load();
@@ -127,6 +149,13 @@ export class UmbInlineListBlockElement extends UmbLitElement {
this.#workspaceContext.load(this.#contentKey);
}
+ #onBlockInserted = (event: Event) => {
+ const blockEvent = event as UmbBlockInsertedEvent;
+ if (blockEvent.detail.layout.contentKey === this.#contentKey) {
+ this._isOpen = true;
+ }
+ };
+
#expose = () => {
this.#workspaceContext?.expose();
};
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-entries.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-entries.context.ts
index 4280c8ae6e93..8f70d5ba90c8 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-entries.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-entries.context.ts
@@ -107,7 +107,7 @@ export class UmbBlockListEntriesContext extends UmbBlockEntriesContext<
data.originData as UmbBlockListWorkspaceOriginData,
);
if (created) {
- this.insert(
+ await this.insert(
created.layout,
created.content,
created.settings,
@@ -167,7 +167,27 @@ export class UmbBlockListEntriesContext extends UmbBlockEntriesContext<
}
getPathForCreateBlock(index: number) {
- return this._catalogueRouteBuilderState.getValue()?.({ view: 'create', index: index });
+ const pathBuilder = this._catalogueRouteBuilderState.getValue();
+ if (!pathBuilder) return undefined;
+
+ if (!this._manager) return undefined;
+ const blockTypes = this._manager.getBlockTypes();
+ if (blockTypes.length === 1) {
+ const elementKey = blockTypes[0].contentElementTypeKey;
+ if (this._manager.getInlineEditingMode()) {
+ return undefined;
+ }
+
+ // does the Block have any Content properties?
+ const contentTypeKey = this._manager.getContentTypeKeyOfContentKey(elementKey);
+ if (contentTypeKey && this._manager.getContentTypeHasProperties(contentTypeKey) === false) {
+ return undefined;
+ }
+
+ return pathBuilder?.({ view: 'create', index: index }) + 'modal/umb-modal-workspace/create/' + elementKey;
+ }
+
+ return pathBuilder?.({ view: 'create', index: index });
}
getPathForClipboard(index: number) {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts
index 0f69421b7b14..973d1cac2ea5 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts
@@ -67,8 +67,8 @@ export class UmbBlockListManagerContext<
originData: UmbBlockListWorkspaceOriginData,
) {
this._layouts.appendOneAt(layoutEntry, originData.index ?? -1);
-
this.insertBlockData(layoutEntry, content, settings, originData);
+ this.notifyBlockInserted(layoutEntry, originData);
return true;
}
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 d76582678f67..fa80394f41b5 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
@@ -3,7 +3,17 @@ import { UmbBlockListEntriesContext } from '../../context/block-list-entries.con
import type { UmbBlockListLayoutModel, UmbBlockListValueModel } from '../../types.js';
import type { UmbBlockListEntryElement } from '../../components/block-list-entry/index.js';
import { UMB_BLOCK_LIST_PROPERTY_EDITOR_SCHEMA_ALIAS } from './constants.js';
-import { css, customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit';
+import { UmbLitElement, umbDestroyOnDisconnect } from '@umbraco-cms/backoffice/lit-element';
+import {
+ html,
+ customElement,
+ property,
+ state,
+ repeat,
+ css,
+ nothing,
+ ifDefined,
+} from '@umbraco-cms/backoffice/external/lit';
import { debounceTime } from '@umbraco-cms/backoffice/external/rxjs';
import {
extractJsonQueryProps,
@@ -12,7 +22,6 @@ import {
UMB_VALIDATION_EMPTY_LOCALIZATION_KEY,
} from '@umbraco-cms/backoffice/validation';
import { jsonStringComparison, observeMultiple } from '@umbraco-cms/backoffice/observable-api';
-import { umbDestroyOnDisconnect, UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
import { UMB_CONTENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/content';
@@ -419,32 +428,55 @@ export class UmbPropertyEditorUIBlockListElement
#renderInlineCreateButton(index: number) {
if (this.readonly) return nothing;
+ const createPath = this.#entriesContext.getPathForCreateBlock(index);
return html`
+ href=${ifDefined(createPath)}
+ @click=${() => {
+ // If no path, then we can conclude there is not modal flow for the user to follow, instead we will just insert the Block: [NL]
+ if (createPath === undefined) {
+ this.#handleCreateWithNoCreatePath(index);
+ }
+ }}>
`;
}
#renderCreateButton() {
- let createPath: string | undefined;
- if (this._blocks?.length === 1) {
- const elementKey = this._blocks[0].contentElementTypeKey;
- createPath =
- this._catalogueRouteBuilder?.({ view: 'create', index: -1 }) + 'modal/umb-modal-workspace/create/' + elementKey;
- } else {
- createPath = this._catalogueRouteBuilder?.({ view: 'create', index: -1 });
- }
+ if (!this._catalogueRouteBuilder) return nothing;
+ const createPath = this.#entriesContext.getPathForCreateBlock(-1);
return html`
+ .label=${this._createButtonLabel}
+ href=${ifDefined(createPath)}
+ ?disabled=${this.readonly}
+ @click=${() => {
+ // If no path, then we can conclude there is not modal flow for the user to follow, instead we will just insert the Block: [NL]
+ if (createPath === undefined) {
+ this.#handleCreateWithNoCreatePath();
+ }
+ }}>
`;
}
+ async #handleCreateWithNoCreatePath(index?: number) {
+ if (!this._blocks || this._blocks.length === 0) {
+ throw new Error('No block types are configured for this Block List property editor');
+ }
+ if (index === undefined) {
+ index = -1;
+ }
+ const originData = { index };
+ const created = await this.#entriesContext.create(this._blocks[0].contentElementTypeKey, {}, originData);
+ if (created) {
+ this.#entriesContext.insert(created.layout, created.content, created.settings, originData);
+ } else {
+ throw new Error('Failed to create block');
+ }
+ }
+
#renderPasteButton() {
return html`
+ href=${ifDefined(createPath)}
+ ?disabled=${this.readonly}
+ @click=${async () => {
+ // If no path, then we can conclude there is not modal flow for the user to follow, instead we will just insert the Block: [NL]
+ if (createPath === undefined) {
+ if (!this._blocks || this._blocks.length === 0) {
+ throw new Error('No block types are configured for this Block List property editor');
+ }
+ const originData = { index: -1 };
+ const created = await this.#entriesContext.create(this._blocks[0].contentElementTypeKey, {}, originData);
+ if (created) {
+ this.#entriesContext.insert(created.layout, created.content, created.settings, originData);
+ } else {
+ throw new Error('Failed to create block');
+ }
+ }
+ }}>
`;
}
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 1068455fab87..3e391c0d9020 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
@@ -1,5 +1,6 @@
import type { UmbBlockWorkspaceOriginData } from '../workspace/index.js';
import type { UmbBlockLayoutBaseModel, UmbBlockDataModel, UmbBlockExposeModel } from '../types.js';
+import { UmbBlockInsertedEvent } from '../events/block-inserted.event.js';
import { UMB_BLOCK_MANAGER_CONTEXT } from './block-manager.context-token.js';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
@@ -515,6 +516,17 @@ export abstract class UmbBlockManagerContext<
this.#setInitialBlockExpose(content);
}
+ protected async notifyBlockInserted(layoutEntry: BlockLayoutType, originData: BlockOriginDataType) {
+ // Await one rendering frame, to make sure new Block has been rendered at the time of the Event firing. [NL]
+ await new Promise((resolve) => requestAnimationFrame(() => resolve(true)));
+ this.dispatchEvent(
+ new UmbBlockInsertedEvent({
+ originData,
+ layout: layoutEntry,
+ }),
+ );
+ }
+
async #setInitialBlockExpose(content: UmbBlockDataModel) {
await this.contentTypesLoaded;
const contentStructure = this.getStructure(content.contentTypeKey);
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/events/block-inserted.event.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/events/block-inserted.event.ts
new file mode 100644
index 000000000000..8ca4dffca1c4
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/events/block-inserted.event.ts
@@ -0,0 +1,26 @@
+import type { UmbBlockLayoutBaseModel } from '../types.js';
+
+export interface UmbBlockInsertedEventDetail<
+ TLayoutModel extends UmbBlockLayoutBaseModel = UmbBlockLayoutBaseModel,
+ TOriginData = unknown,
+> {
+ originData: TOriginData;
+ layout: TLayoutModel;
+}
+
+export class UmbBlockInsertedEvent<
+ TLayoutModel extends UmbBlockLayoutBaseModel = UmbBlockLayoutBaseModel,
+ TOriginData = unknown,
+> extends CustomEvent> {
+ static readonly TYPE = 'umb-internal:blockInserted';
+
+ constructor(detail: UmbBlockInsertedEventDetail) {
+ super(UmbBlockInsertedEvent.TYPE, { detail, bubbles: false, composed: false, cancelable: false });
+ }
+}
+
+declare global {
+ interface GlobalEventHandlersEventMap {
+ [UmbBlockInsertedEvent.TYPE]: UmbBlockInsertedEvent;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/events/index.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/events/index.ts
new file mode 100644
index 000000000000..df4caf92a8d2
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/events/index.ts
@@ -0,0 +1 @@
+export * from './block-inserted.event.js';
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/index.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/index.ts
index 497575eb46c6..6bbeb337339d 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/block/block/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/index.ts
@@ -1,6 +1,7 @@
export * from './clipboard/index.js';
export * from './components/index.js';
export * from './context/index.js';
+export * from './events/index.js';
export * from './modals/index.js';
export * from './property-value-cloner/index.js';
export * from './property-value-resolver/index.js';
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 e0352a53b04f..2d23365155d0 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
@@ -91,7 +91,7 @@ export class UmbBlockWorkspaceContext {
this.#blockManager = manager;