diff --git a/src/Umbraco.Web.UI.Client/devops/module-dependencies/index.js b/src/Umbraco.Web.UI.Client/devops/module-dependencies/index.js index d6e5febf2e30..39746504b55e 100644 --- a/src/Umbraco.Web.UI.Client/devops/module-dependencies/index.js +++ b/src/Umbraco.Web.UI.Client/devops/module-dependencies/index.js @@ -4,7 +4,7 @@ import { createImportMap } from '../importmap/index.js'; const ILLEGAL_CORE_IMPORTS_THRESHOLD = 5; const SELF_IMPORTS_THRESHOLD = 0; -const BIDIRECTIONAL_IMPORTS_THRESHOLD = 18; +const BIDIRECTIONAL_IMPORTS_THRESHOLD = 16; const clientProjectRoot = path.resolve(import.meta.dirname, '../../'); const modulePrefix = '@umbraco-cms/backoffice/'; diff --git a/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/collapse-menu-item-entity-action/collapse-menu-item.entity-action.ts b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/collapse-menu-item-entity-action/collapse-menu-item.entity-action.ts new file mode 100644 index 000000000000..7df5c2dc2579 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/collapse-menu-item-entity-action/collapse-menu-item.entity-action.ts @@ -0,0 +1,11 @@ +import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; +import { UMB_MENU_ITEM_CONTEXT } from '@umbraco-cms/backoffice/menu'; + +export class ExampleCollapseMenuItemEntityAction extends UmbEntityActionBase { + override async execute() { + const context = await this.getContext(UMB_MENU_ITEM_CONTEXT); + context?.expansion.collapseAll(); + } +} + +export { ExampleCollapseMenuItemEntityAction as api }; diff --git a/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/collapse-menu-item-entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/collapse-menu-item-entity-action/manifests.ts new file mode 100644 index 000000000000..69123c7f0c45 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/collapse-menu-item-entity-action/manifests.ts @@ -0,0 +1,15 @@ +export const manifests: Array = [ + { + type: 'entityAction', + kind: 'default', + alias: 'Umb.EntityAction.Document.MenuItem.Collapse', + name: 'Collapse Document Menu Item', + api: () => import('./collapse-menu-item.entity-action.js'), + forEntityTypes: ['document-type-root', 'media-type-root', 'member-type-root', 'data-type-root'], + weight: -10, + meta: { + label: 'Collapse Menu Item', + icon: 'icon-wand', + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/index.ts b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/index.ts new file mode 100644 index 000000000000..c911275efd3e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/index.ts @@ -0,0 +1,4 @@ +import { manifests as playgroundDashboardManifests } from './playground-dashboard/manifests.js'; +import { manifests as collapseMenuItemManifests } from './collapse-menu-item-entity-action/manifests.js'; + +export const manifests: Array = [...playgroundDashboardManifests, ...collapseMenuItemManifests]; diff --git a/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/playground-dashboard/manifests.ts b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/playground-dashboard/manifests.ts new file mode 100644 index 000000000000..d9d47be8b013 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/playground-dashboard/manifests.ts @@ -0,0 +1,14 @@ +export const manifests: Array = [ + { + type: 'dashboard', + kind: 'default', + name: 'Example Section Sidebar Menu Playground Dashboard', + alias: 'Example.Dashboard.SectionSidebarMenuPlayground', + element: () => import('./section-sidebar-menu-playground.element.js'), + weight: 3000, + meta: { + label: 'Section Sidebar Menu Playground', + pathname: 'section-sidebar-menu-playground', + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/playground-dashboard/section-sidebar-menu-playground.element.ts b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/playground-dashboard/section-sidebar-menu-playground.element.ts new file mode 100644 index 000000000000..5f82b4fc45fc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/playground-dashboard/section-sidebar-menu-playground.element.ts @@ -0,0 +1,102 @@ +import { html, customElement, LitElement, css, state, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import { + UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT, + UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT, + type UmbSectionMenuItemExpansionEntryModel, +} from '@umbraco-cms/backoffice/menu'; + +@customElement('example-section-sidebar-menu-playground-dashboard') +export class ExampleSectionSidebarMenuPlaygroundDashboard extends UmbElementMixin(LitElement) { + #globalContext?: typeof UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT.TYPE; + #sectionContext?: typeof UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT.TYPE; + + @state() + private _globalExpansion: Array = []; + + @state() + private _sectionExpansion: Array = []; + + constructor() { + super(); + + this.consumeContext(UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT, (section) => { + this.#globalContext = section; + this.#observeGlobalExpansion(); + }); + + this.consumeContext(UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT, (section) => { + this.#sectionContext = section; + this.#observeSectionExpansion(); + }); + } + + #observeGlobalExpansion() { + this.observe(this.#globalContext?.expansion.expansion, (items) => { + this._globalExpansion = items || []; + }); + } + + #observeSectionExpansion() { + this.observe(this.#sectionContext?.expansion.expansion, (items) => { + this._sectionExpansion = items || []; + }); + } + + #onCloseItem(event: PointerEvent, item: UmbSectionMenuItemExpansionEntryModel) { + event.stopPropagation(); + this.#sectionContext?.expansion.collapseItem(item); + } + + #onCollapseSection() { + this.#sectionContext?.expansion.collapseAll(); + } + + override render() { + return html` + + + + Collapse All + ${repeat( + this._sectionExpansion, + (item) => item.entityType + item.unique, + (item) => this.#renderItem(item), + )} + + + ${repeat( + this._globalExpansion, + (item) => item.entityType + item.unique, + (item) => this.#renderItem(item), + )} + + `; + } + + #renderItem(item: UmbSectionMenuItemExpansionEntryModel) { + return html` + this.#onCloseItem(event, item)}>Close + `; + } + + static override styles = [ + css` + :host { + display: block; + padding: var(--uui-size-layout-1); + } + `, + ]; +} + +export { ExampleSectionSidebarMenuPlaygroundDashboard as element }; + +declare global { + interface HTMLElementTagNameMap { + 'example-section-sidebar-menu-playground-dashboard': ExampleSectionSidebarMenuPlaygroundDashboard; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts index 0550e6c52cc3..70f1a9202b93 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts @@ -78,6 +78,7 @@ export class UmbBackofficeMainElement extends UmbLitElement { this._routes = newRoutes; } + // TODO: v17. Remove this section context when we have api's as part of the section manifest. private _provideSectionContext(sectionManifest: ManifestSection) { if (!this._sectionContext) { this._sectionContext = new UmbSectionContext(this); diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts index e0d12dcd4666..4459544348a8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts @@ -941,11 +941,20 @@ export abstract class UmbContentDetailWorkspaceContextBase< if (!eventContext) { throw new Error('Event context is missing'); } - const event = new UmbRequestReloadChildrenOfEntityEvent({ + + const reloadStructureEvent = new UmbRequestReloadStructureForEntityEvent({ entityType: parent.entityType, unique: parent.unique, }); - eventContext.dispatchEvent(event); + + eventContext.dispatchEvent(reloadStructureEvent); + + const reloadChildrenEvent = new UmbRequestReloadChildrenOfEntityEvent({ + entityType: parent.entityType, + unique: parent.unique, + }); + + eventContext.dispatchEvent(reloadChildrenEvent); } async #update(variantIds: Array, saveData: DetailModelType) { 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 4cbd31df808b..28cdbc9897d1 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 @@ -88,6 +88,18 @@ export class UmbExtensionSlotElement extends UmbLitElement { } #props?: Record = {}; + @property({ type: Object, attribute: false }) + set events(newVal: Record void> | undefined) { + this.#events = newVal; + if (this.#extensionsController) { + this.#addEventListenersToExtensionElement(); + } + } + get events(): Record void> | undefined { + return this.#events; + } + #events?: Record void> = {}; + @property({ type: String, attribute: 'default-element' }) public defaultElement?: string; @@ -113,6 +125,7 @@ export class UmbExtensionSlotElement extends UmbLitElement { } override disconnectedCallback(): void { // _permitted is reset as the extensionsController fires a callback on destroy. + this.#removeEventListenersFromExtensionElement(); this.#attached = false; this.#extensionsController?.destroy(); this.#extensionsController = undefined; @@ -130,6 +143,7 @@ export class UmbExtensionSlotElement extends UmbLitElement { this.filter, (extensionControllers) => { this._permitted = extensionControllers; + this.#addEventListenersToExtensionElement(); }, undefined, // We can leave the alias undefined as we destroy this our selfs. this.defaultElement, @@ -158,6 +172,36 @@ export class UmbExtensionSlotElement extends UmbLitElement { return this.renderMethod ? this.renderMethod(ext, i) : ext.component; }; + #addEventListenersToExtensionElement() { + this._permitted?.forEach((initializer) => { + const component = initializer.component as HTMLElement; + if (!component) return; + + const events = this.#events; + if (!events) return; + + this.#removeEventListenersFromExtensionElement(); + + Object.entries(events).forEach(([eventName, handler]) => { + component.addEventListener(eventName, handler); + }); + }); + } + + #removeEventListenersFromExtensionElement() { + this._permitted?.forEach((initializer) => { + const component = initializer.component as HTMLElement; + if (!component) return; + + const events = this.#events; + if (!events) return; + + Object.entries(events).forEach(([eventName, handler]) => { + component.removeEventListener(eventName, handler); + }); + }); + } + static override styles = css` :host { display: contents; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/index.ts index c73d5980e792..77dbe4fc3e13 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/index.ts @@ -2,6 +2,8 @@ export * from './conditions/index.js'; export * from './initializers/index.js'; export * from './registry.js'; export * from './utils/index.js'; +export * from './components/index.js'; + export type * from './models/types.js'; export type * from './extensions/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/action/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/action/types.ts index 6e60d0bfdd56..1da233a9bd71 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/action/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/action/types.ts @@ -1,4 +1,4 @@ -import type { MetaMenuItem } from '../../../menu-item.extension.js'; +import type { MetaMenuItem } from '../menu-item.extension.js'; import type { ManifestElementAndApi, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api'; import type { UmbAction } from '@umbraco-cms/backoffice/action'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/constants.ts new file mode 100644 index 000000000000..a86e19fbb31f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/constants.ts @@ -0,0 +1 @@ +export * from './menu-item.context.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/expansion/is-menu-item-expansion-entry.guard.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/expansion/is-menu-item-expansion-entry.guard.ts new file mode 100644 index 000000000000..84c6d2441e0c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/expansion/is-menu-item-expansion-entry.guard.ts @@ -0,0 +1,16 @@ +import type { UmbMenuItemExpansionEntryModel } from '../../types.js'; + +/** + * Checks if the provided object is a Menu Item Expansion Entry. + * @param {object } object - The object to check. + * @returns {boolean } True if the object is a Menu Item Expansion Entry, false otherwise. + */ +export function isMenuItemExpansionEntry(object: unknown): object is UmbMenuItemExpansionEntryModel { + return ( + typeof object === 'object' && + object !== null && + 'entityType' in object && + 'unique' in object && + 'menuItemAlias' in object + ); +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/expansion/menu-item-expansion.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/expansion/menu-item-expansion.manager.ts new file mode 100644 index 000000000000..b02938e46f70 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/expansion/menu-item-expansion.manager.ts @@ -0,0 +1,132 @@ +import type { UmbMenuItemExpansionEntryModel } from '../../menu/types.js'; +import { UMB_MENU_CONTEXT } from '../../menu/constants.js'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { + createObservablePart, + type UmbObserverController, + type Observable, +} from '@umbraco-cms/backoffice/observable-api'; +import { UmbEntityExpansionManager, type UmbEntityExpansionModel } from '@umbraco-cms/backoffice/utils'; + +/** + * Manages the expansion state of a menu item + * @exports + * @class UmbMenuItemExpansionManager + * @augments {UmbControllerBase} + */ +export class UmbMenuItemExpansionManager extends UmbControllerBase { + #manager = new UmbEntityExpansionManager(this); + public readonly expansion = this.#manager.expansion; + + #menuItemAlias?: string; + #menuContext?: typeof UMB_MENU_CONTEXT.TYPE; + #observerController?: UmbObserverController; + + constructor(host: UmbControllerHost) { + super(host); + + this.consumeContext(UMB_MENU_CONTEXT, (menuContext) => { + this.#menuContext = menuContext; + this.#observeMenuExpansion(); + }); + } + + #observeMenuExpansion() { + if (!this.#menuContext || !this.#menuItemAlias) { + this.#observerController?.destroy(); + return; + } + + this.#observerController = this.observe( + createObservablePart( + this.#menuContext.expansion.expansion, + (items: Array) => + items?.filter((item) => item.menuItemAlias === this.#menuItemAlias) || [], + ), + (itemsForMenuItem) => { + this.#manager.setExpansion(itemsForMenuItem); + }, + 'observeMenuExpension' + ); + } + + setMenuItemAlias(alias: string | undefined): void { + this.#menuItemAlias = alias; + this.#observeMenuExpansion(); + } + + /** + * Checks if an entry is expanded + * @param {UmbMenuItemExpansionEntryModel} entry The entry to check + * @returns {Observable} True if the entry is expanded + * @memberof UmbSectionSidebarMenuSectionExpansionManager + */ + isExpanded(entry: UmbMenuItemExpansionEntryModel): Observable { + return this.#manager.isExpanded(entry); + } + + /** + * Sets the expansion state + * @param {UmbEntityExpansionModel | undefined} expansion The expansion state + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {void} + */ + setExpansion(expansion: UmbEntityExpansionModel): void { + this.#manager.setExpansion(expansion); + this.#menuContext?.expansion.setExpansion(expansion); + } + + /** + * Gets the expansion state + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {UmbEntityExpansionModel} The expansion state + */ + getExpansion(): UmbEntityExpansionModel { + return this.#manager.getExpansion(); + } + + /** + * Opens a menu item + * @param {UmbMenuItemExpansionEntryModel} entry The item to open + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {Promise} + */ + public async expandItem(entry: UmbMenuItemExpansionEntryModel): Promise { + this.#manager.expandItem(entry); + this.#menuContext?.expansion.expandItem(entry); + } + + /** + * Expands multiple entities + * @param {UmbEntityExpansionModel} entries The entities to open + * @memberof UmbEntityExpansionManager + * @returns {void} + */ + public expandItems(entries: UmbEntityExpansionModel): void { + this.#manager.expandItems(entries); + this.#menuContext?.expansion.expandItems(entries); + } + + /** + * Closes a menu item + * @param {UmbMenuItemExpansionEntryModel} entry The item to close + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {Promise} + */ + public async collapseItem(entry: UmbMenuItemExpansionEntryModel): Promise { + this.#manager.collapseItem(entry); + this.#menuContext?.expansion.collapseItem(entry); + } + + /** + * Closes all menu items + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {Promise} + */ + public async collapseAll(): Promise { + const localEntries = this.#manager.getExpansion(); + this.#manager.collapseAll(); + this.#menuContext?.expansion.collapseItems(localEntries); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/index.ts index 6b2eaef349aa..80ecd73a78b1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/index.ts @@ -1,2 +1,3 @@ export * from './menu-item-default.element.js'; +export * from './constants.js'; export * from './action/action-menu-item.api.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/link/link-menu-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/link/link-menu-item.element.ts index b902cd18e289..0aeab0024fc6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/link/link-menu-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/link/link-menu-item.element.ts @@ -1,4 +1,4 @@ -import type { UmbMenuItemElement } from '../../../menu-item-element.interface.js'; +import type { UmbMenuItemElement } from '../menu-item-element.interface.js'; import type { ManifestMenuItemLinkKind } from './types.js'; import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/link/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/link/types.ts index 2bc68ac163a1..f5be9cefbebc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/link/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/link/types.ts @@ -1,4 +1,4 @@ -import type { ManifestMenuItem, MetaMenuItem } from '../../../menu-item.extension.js'; +import type { ManifestMenuItem, MetaMenuItem } from '../menu-item.extension.js'; export interface ManifestMenuItemLinkKind extends ManifestMenuItem { type: 'menuItem'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item-default.element.ts index 269133145cb4..035c5f972c4b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item-default.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item-default.element.ts @@ -1,5 +1,5 @@ -import type { UmbMenuItemElement } from '../../menu-item-element.interface.js'; -import type { ManifestMenuItem } from '../../menu-item.extension.js'; +import type { UmbMenuItemElement } from './menu-item-element.interface.js'; +import type { ManifestMenuItem } from './menu-item.extension.js'; import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_SECTION_CONTEXT } from '@umbraco-cms/backoffice/section'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-item-element.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item-element.interface.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-item-element.interface.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item-element.interface.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item.context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item.context.token.ts new file mode 100644 index 000000000000..f51e5f1042ea --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item.context.token.ts @@ -0,0 +1,4 @@ +import type { UmbDefaultMenuItemContext } from './menu-item.context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_MENU_ITEM_CONTEXT = new UmbContextToken('UmbMenuItemContext'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item.context.ts new file mode 100644 index 000000000000..d20e137b1baf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item.context.ts @@ -0,0 +1,22 @@ +import type { ManifestMenuItem } from '../../types.js'; +import { UmbMenuItemExpansionManager } from './expansion/menu-item-expansion.manager.js'; +import { UMB_MENU_ITEM_CONTEXT } from './menu-item.context.token.js'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbDefaultMenuItemContext extends UmbContextBase { + public readonly expansion = new UmbMenuItemExpansionManager(this); + + #manifest?: ManifestMenuItem | undefined; + public get manifest(): ManifestMenuItem | undefined { + return this.#manifest; + } + public set manifest(value: ManifestMenuItem | undefined) { + this.#manifest = value; + this.expansion.setMenuItemAlias(value?.alias); + } + + constructor(host: UmbControllerHost) { + super(host, UMB_MENU_ITEM_CONTEXT); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-item.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item.extension.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-item.extension.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item.extension.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/types.ts index 1e39b8bf82b8..ed3511d79644 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/types.ts @@ -1,2 +1,4 @@ export type * from './action/types.js'; export type * from './link/types.js'; +export type * from './menu-item-element.interface.js'; +export type * from './menu-item.extension.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/constants.ts new file mode 100644 index 000000000000..95d903ca23e5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/constants.ts @@ -0,0 +1 @@ +export * from './menu.context.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/index.ts index 242a263f7af8..be0be98f9710 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/index.ts @@ -1 +1,2 @@ export * from './menu.element.js'; +export * from './constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.context.token.ts new file mode 100644 index 000000000000..6385f345782e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.context.token.ts @@ -0,0 +1,4 @@ +import type { UmbDefaultMenuContext } from './menu.context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_MENU_CONTEXT = new UmbContextToken('UmbMenuContext'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.context.ts new file mode 100644 index 000000000000..671373df7caf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.context.ts @@ -0,0 +1,13 @@ +import { UMB_MENU_CONTEXT } from './menu.context.token.js'; +import type { UmbMenuItemExpansionEntryModel } from './types.js'; +import { UmbEntityExpansionManager } from '@umbraco-cms/backoffice/utils'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbDefaultMenuContext extends UmbContextBase { + public readonly expansion = new UmbEntityExpansionManager(this); + + constructor(host: UmbControllerHost) { + super(host, UMB_MENU_CONTEXT); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.element.ts index 7e051db0e0cd..9ed2c2d95d41 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.element.ts @@ -1,7 +1,11 @@ import type { ManifestMenu } from '../../menu.extension.js'; -import type { ManifestMenuItem } from '../../menu-item.extension.js'; +import { UmbDefaultMenuItemContext } from '../menu-item/menu-item.context.js'; +import type { ManifestMenuItem } from '../menu-item/types.js'; +import { UmbDefaultMenuContext } from './menu.context.js'; +import type { UmbMenuItemExpansionEntryModel } from './types.js'; import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import type { UmbEntityExpansionModel } from '@umbraco-cms/backoffice/utils'; import '../menu-item/menu-item-default.element.js'; @@ -10,13 +14,24 @@ export class UmbMenuElement extends UmbLitElement { @property({ attribute: false }) manifest?: ManifestMenu; + public get expansion(): UmbEntityExpansionModel { + return this.#context.expansion.getExpansion(); + } + public set expansion(value: UmbEntityExpansionModel) { + this.#context.expansion.setExpansion(value); + } + + #context = new UmbDefaultMenuContext(this); + override render() { + //TODO: We should probably have resolved the MENU ITEM CONTEXT not by providing it not as an API, currently this work cause menu items does not have APIs as of their manifest. But if they do this, this would be a problem. [NL] return html` - items.meta.menus.includes(this.manifest!.alias)}> - + `; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/types.ts new file mode 100644 index 000000000000..dc3d827be241 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/types.ts @@ -0,0 +1,5 @@ +import type { UmbEntityExpansionEntryModel } from '@umbraco-cms/backoffice/utils'; + +export interface UmbMenuItemExpansionEntryModel extends UmbEntityExpansionEntryModel { + menuItemAlias: string; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/types.ts index c858b14791dd..e03ad3173595 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/types.ts @@ -1 +1,2 @@ export type * from './menu-item/types.js'; +export type * from './menu/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/constants.ts new file mode 100644 index 000000000000..fd7b96c0f839 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/constants.ts @@ -0,0 +1 @@ +export * from './section-sidebar-menu/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts index c7d6c36f9e18..cce4aa408e06 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts @@ -3,6 +3,7 @@ export * from './menu-tree-structure-workspace-context-base.js'; export * from './menu-structure-workspace-context.context-token.js'; export * from './menu-variant-structure-workspace-context.context-token.js'; export * from './menu-variant-tree-structure-workspace-context-base.js'; +export * from './constants.js'; export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/manifests.ts index a9d3417c6195..350fea31bce1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/manifests.ts @@ -1,8 +1,12 @@ -import { manifests as menuItemManifests } from './components/menu-item/manifests.js'; import { manifest as menuAliasConditionManifest } from './conditions/menu-alias.condition.js'; +import { manifests as menuItemManifests } from './components/menu-item/manifests.js'; +import { manifests as sectionSidebarMenuManifests } from './section-sidebar-menu/manifests.js'; +import { manifests as sectionSidebarMenuWithEntityActionsManifests } from './section-sidebar-menu-with-entity-actions/manifests.js'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ ...menuItemManifests, + ...sectionSidebarMenuManifests, + ...sectionSidebarMenuWithEntityActionsManifests, menuAliasConditionManifest, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-structure/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-structure/types.ts new file mode 100644 index 000000000000..08be58ee57e1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-structure/types.ts @@ -0,0 +1,17 @@ +import type { ManifestWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; + +export interface ManifestWorkspaceContextMenuStructureKind extends ManifestWorkspaceContext { + type: 'workspaceContext'; + kind: 'menuStructure'; + meta: MetaWorkspaceContextMenuStructureKind; +} + +export interface MetaWorkspaceContextMenuStructureKind { + menuItemAlias: string; +} + +declare global { + interface UmbExtensionManifestMap { + umbManifestWorkspaceContextMenuStructureKind: ManifestWorkspaceContextMenuStructureKind; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-tree-structure-workspace-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-tree-structure-workspace-context-base.ts index 21df3dcb2724..fabaaccd8011 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-tree-structure-workspace-context-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-tree-structure-workspace-context-base.ts @@ -1,5 +1,6 @@ -import type { UmbStructureItemModel } from './types.js'; +import type { ManifestWorkspaceContextMenuStructureKind, UmbStructureItemModel } from './types.js'; import { UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT } from './menu-structure-workspace-context.context-token.js'; +import { UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT } from './section-sidebar-menu/index.js'; import type { UmbTreeRepository, UmbTreeItemModel, UmbTreeRootModel } from '@umbraco-cms/backoffice/tree'; import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; @@ -7,6 +8,8 @@ import { UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/back import { UmbArrayState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbAncestorsEntityContext, UmbParentEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import { linkEntityExpansionEntries } from '@umbraco-cms/backoffice/utils'; +import { UMB_MODAL_CONTEXT } from '@umbraco-cms/backoffice/modal'; interface UmbMenuTreeStructureWorkspaceContextBaseArgs { treeRepositoryAlias: string; @@ -14,6 +17,8 @@ interface UmbMenuTreeStructureWorkspaceContextBaseArgs { // TODO: introduce base class for all menu structure workspaces to handle ancestors and parent export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContextBase { + manifest?: ManifestWorkspaceContextMenuStructureKind; + #workspaceContext?: typeof UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT.TYPE; #args: UmbMenuTreeStructureWorkspaceContextBaseArgs; @@ -28,6 +33,8 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex #parentContext = new UmbParentEntityContext(this); #ancestorContext = new UmbAncestorsEntityContext(this); + #sectionSidebarMenuContext?: typeof UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT.TYPE; + #isModalContext: boolean = false; constructor(host: UmbControllerHost, args: UmbMenuTreeStructureWorkspaceContextBaseArgs) { super(host, UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT); @@ -35,18 +42,45 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex this.provideContext('UmbMenuStructureWorkspaceContext', this); this.#args = args; + this.consumeContext(UMB_MODAL_CONTEXT, (modalContext) => { + this.#isModalContext = modalContext !== undefined; + }); + + this.consumeContext(UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT, (instance) => { + this.#sectionSidebarMenuContext = instance; + }); + this.consumeContext(UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT, (instance) => { this.#workspaceContext = instance; this.observe(this.#workspaceContext?.unique, (value) => { if (!value) return; this.#requestStructure(); }); + + this.observe(this.#workspaceContext?.isNew, (value) => { + if (value === undefined) return; + this.#requestStructure(); + }); }); } async #requestStructure() { + const isNew = this.#workspaceContext?.getIsNew(); + const uniqueObservable = isNew + ? this.#workspaceContext?._internal_createUnderParentEntityUnique + : this.#workspaceContext?.unique; + const entityTypeObservable = isNew + ? this.#workspaceContext?._internal_createUnderParentEntityType + : this.#workspaceContext?.entityType; + let structureItems: Array = []; + const unique = (await this.observe(uniqueObservable, () => {})?.asPromise()) as string; + if (unique === undefined) throw new Error('Unique is not available'); + + const entityType = (await this.observe(entityTypeObservable, () => {})?.asPromise()) as string; + if (!entityType) throw new Error('Entity type is not available'); + const treeRepository = await createExtensionApiByAlias>( this, this.#args.treeRepositoryAlias, @@ -65,22 +99,10 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex ]; } - const isNew = this.#workspaceContext?.getIsNew(); - - const entityTypeObservable = isNew - ? this.#workspaceContext?._internal_createUnderParentEntityType - : this.#workspaceContext?.entityType; - const entityType = (await this.observe(entityTypeObservable, () => {})?.asPromise()) as string; - if (!entityType) throw new Error('Entity type is not available'); + const isRoot = entityType === root?.entityType; // If the entity type is different from the root entity type, then we can request the ancestors. - if (entityType !== root?.entityType) { - const uniqueObservable = isNew - ? this.#workspaceContext?._internal_createUnderParentEntityUnique - : this.#workspaceContext?.unique; - const unique = (await this.observe(uniqueObservable, () => {})?.asPromise()) as string; - if (!unique) throw new Error('Unique is not available'); - + if (!isRoot) { const { data } = await treeRepository.requestTreeItemAncestors({ treeItem: { unique, entityType } }); if (data) { @@ -93,13 +115,19 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex }; }); - structureItems.push(...ancestorItems); - - this.#structure.setValue(structureItems); - this.#setParentData(structureItems); this.#setAncestorData(data); + + structureItems.push(...ancestorItems); } } + + this.#structure.setValue(structureItems); + this.#setParentData(structureItems); + + const menuItemAlias = this.manifest?.meta?.menuItemAlias; + if (menuItemAlias && !this.#isModalContext) { + this.#expandSectionSidebarMenu(structureItems, menuItemAlias); + } } #setParentData(structureItems: Array) { @@ -139,4 +167,25 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex this.#ancestorContext.setAncestors(ancestorEntities); } + + #expandSectionSidebarMenu(structureItems: Array, menuItemAlias: string) { + const linkedEntries = linkEntityExpansionEntries(structureItems); + // Filter out the current entity as we don't want to expand it + const expandableItems = linkedEntries.filter((item) => item.unique !== this.#workspaceContext?.getUnique()); + const expandableItemsWithMenuItem = expandableItems.map((item) => { + return { + ...item, + menuItemAlias, + }; + }); + this.#sectionSidebarMenuContext?.expansion.expandItems(expandableItemsWithMenuItem); + } + + override destroy(): void { + super.destroy(); + this.#structure.destroy(); + this.#parent.destroy(); + this.#parentContext.destroy(); + this.#ancestorContext.destroy(); + } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts index db207951b03f..3ef07138e2f3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts @@ -1,5 +1,6 @@ -import type { UmbVariantStructureItemModel } from './types.js'; +import type { ManifestWorkspaceContextMenuStructureKind, UmbVariantStructureItemModel } from './types.js'; import { UMB_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT } from './menu-variant-structure-workspace-context.context-token.js'; +import { UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT } from './section-sidebar-menu/section-context/section-sidebar-menu.section-context.token.js'; import type { UmbTreeItemModel, UmbTreeRepository, UmbTreeRootModel } from '@umbraco-cms/backoffice/tree'; import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; @@ -7,6 +8,8 @@ import { UmbArrayState, UmbObjectState } from '@umbraco-cms/backoffice/observabl import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbAncestorsEntityContext, UmbParentEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; import { UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; +import { linkEntityExpansionEntries } from '@umbraco-cms/backoffice/utils'; +import { UMB_MODAL_CONTEXT } from '@umbraco-cms/backoffice/modal'; interface UmbMenuVariantTreeStructureWorkspaceContextBaseArgs { treeRepositoryAlias: string; @@ -14,7 +17,8 @@ interface UmbMenuVariantTreeStructureWorkspaceContextBaseArgs { // TODO: introduce base class for all menu structure workspaces to handle ancestors and parent export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends UmbContextBase { - // + manifest?: ManifestWorkspaceContextMenuStructureKind; + #workspaceContext?: typeof UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT.TYPE; #args: UmbMenuVariantTreeStructureWorkspaceContextBaseArgs; @@ -29,6 +33,8 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um #parentContext = new UmbParentEntityContext(this); #ancestorContext = new UmbAncestorsEntityContext(this); + #sectionSidebarMenuContext?: typeof UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT.TYPE; + #isModalContext: boolean = false; public readonly IS_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT = true; @@ -38,6 +44,14 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um this.provideContext('UmbMenuStructureWorkspaceContext', this); this.#args = args; + this.consumeContext(UMB_MODAL_CONTEXT, (modalContext) => { + this.#isModalContext = modalContext !== undefined; + }); + + this.consumeContext(UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT, (instance) => { + this.#sectionSidebarMenuContext = instance; + }); + this.consumeContext(UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT, (instance) => { this.#workspaceContext = instance; this.observe( @@ -108,6 +122,11 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um this.#structure.setValue(structureItems); this.#setParentData(structureItems); this.#setAncestorData(data); + + const menuItemAlias = this.manifest?.meta?.menuItemAlias; + if (menuItemAlias && !this.#isModalContext) { + this.#expandSectionSidebarMenu(structureItems, menuItemAlias); + } } } @@ -148,4 +167,25 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um this.#ancestorContext.setAncestors(ancestorEntities); } + + #expandSectionSidebarMenu(structureItems: Array, menuItemAlias: string) { + const linkedEntries = linkEntityExpansionEntries(structureItems); + // Filter out the current entity as we don't want to expand it + const expandableItems = linkedEntries.filter((item) => item.unique !== this.#workspaceContext?.getUnique()); + const expandableItemsWithMenuItem = expandableItems.map((item) => { + return { + ...item, + menuItemAlias, + }; + }); + this.#sectionSidebarMenuContext?.expansion.expandItems(expandableItemsWithMenuItem); + } + + override destroy(): void { + super.destroy(); + this.#structure.destroy(); + this.#parent.destroy(); + this.#parentContext.destroy(); + this.#ancestorContext.destroy(); + } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/manifests.ts new file mode 100644 index 000000000000..4d7249e76c81 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/manifests.ts @@ -0,0 +1,14 @@ +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'kind', + alias: 'Umb.Kind.SectionSidebarAppMenuWithEntityActions', + matchKind: 'menuWithEntityActions', + matchType: 'sectionSidebarApp', + manifest: { + type: 'sectionSidebarApp', + element: () => import('./section-sidebar-menu-with-entity-actions.element.js'), + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/section-sidebar-menu-with-entity-actions.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/section-sidebar-menu-with-entity-actions.element.ts index adce5916467c..f7c885fc10e1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/section-sidebar-menu-with-entity-actions.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/section-sidebar-menu-with-entity-actions.element.ts @@ -1,22 +1,8 @@ import { UmbSectionSidebarMenuElement } from '../section-sidebar-menu/section-sidebar-menu.element.js'; import type { ManifestSectionSidebarAppMenuWithEntityActionsKind } from '../section-sidebar-menu/types.js'; import { css, html, customElement, type PropertyValues, state } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbParentEntityContext } from '@umbraco-cms/backoffice/entity'; -const manifestWithEntityActions: UmbExtensionManifestKind = { - type: 'kind', - alias: 'Umb.Kind.SectionSidebarAppMenuWithEntityActions', - matchKind: 'menuWithEntityActions', - matchType: 'sectionSidebarApp', - manifest: { - type: 'sectionSidebarApp', - elementName: 'umb-section-sidebar-menu-with-entity-actions', - }, -}; -umbExtensionsRegistry.register(manifestWithEntityActions); - @customElement('umb-section-sidebar-menu-with-entity-actions') export class UmbSectionSidebarMenuWithEntityActionsElement extends UmbSectionSidebarMenuElement { @state() diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/constants.ts new file mode 100644 index 000000000000..125299d13ae6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/constants.ts @@ -0,0 +1,2 @@ +export * from './global-context/constants.js'; +export * from './section-context/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/constants.ts new file mode 100644 index 000000000000..b55969770056 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/constants.ts @@ -0,0 +1 @@ +export * from './section-sidebar-menu.global-context.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/expansion/section-sidebar-menu-app-expansion.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/expansion/section-sidebar-menu-app-expansion.manager.ts new file mode 100644 index 000000000000..9e58ecca5924 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/expansion/section-sidebar-menu-app-expansion.manager.ts @@ -0,0 +1,23 @@ +import type { UmbSectionMenuItemExpansionEntryModel } from '../../types.js'; +import type { Observable } from '@umbraco-cms/backoffice/observable-api'; +import { UmbEntityExpansionManager } from '@umbraco-cms/backoffice/utils'; + +/** + * Manages the expansion state of a section sidebar menu. + * @exports + * @class UmbSectionSidebarMenuAppExpansionManager + * @augments {UmbControllerBase} + */ +export class UmbSectionSidebarMenuAppExpansionManager extends UmbEntityExpansionManager { + /** + * Returns an observable of the expansion state filtered by section alias. + * @param {string} sectionAlias The alias of the section to filter by. + * @returns {Observable} An observable of the expansion state for the specified section alias. + * @memberof UmbSectionSidebarMenuAppExpansionManager + */ + expansionBySectionAlias(sectionAlias: string): Observable { + return this._expansion.asObservablePart((entries) => + entries.filter((entry) => entry.sectionAlias === sectionAlias), + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/manifests.ts new file mode 100644 index 000000000000..1812e1d5f6cb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/manifests.ts @@ -0,0 +1,10 @@ +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'globalContext', + alias: 'Umb.GlobalContext.SectionSidebarMenu', + name: 'Section Sidebar Menu Global Context', + api: () => import('./section-sidebar-menu.global-context.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/section-sidebar-menu.global-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/section-sidebar-menu.global-context.token.ts new file mode 100644 index 000000000000..20c8eb36fa84 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/section-sidebar-menu.global-context.token.ts @@ -0,0 +1,6 @@ +import type { UmbSectionSidebarMenuGlobalContext } from './section-sidebar-menu.global-context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT = new UmbContextToken( + 'UmbSectionSidebarMenuGlobalContext', +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/section-sidebar-menu.global-context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/section-sidebar-menu.global-context.ts new file mode 100644 index 000000000000..afcd007487db --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/section-sidebar-menu.global-context.ts @@ -0,0 +1,14 @@ +import { UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT } from './section-sidebar-menu.global-context.token.js'; +import { UmbSectionSidebarMenuAppExpansionManager } from './expansion/section-sidebar-menu-app-expansion.manager.js'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbSectionSidebarMenuGlobalContext extends UmbContextBase { + public readonly expansion = new UmbSectionSidebarMenuAppExpansionManager(this); + + constructor(host: UmbControllerHost) { + super(host, UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT); + } +} + +export { UmbSectionSidebarMenuGlobalContext as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/index.ts index 2d3149a8d43d..0371f2072ea1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/index.ts @@ -1 +1,2 @@ export * from './section-sidebar-menu.element.js'; +export * from './constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/manifests.ts new file mode 100644 index 000000000000..b04a4c16615f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/manifests.ts @@ -0,0 +1,18 @@ +import { manifests as sectionContextManifests } from './section-context/manifests.js'; +import { manifests as globalContextManifests } from './global-context/manifests.js'; +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'kind', + alias: 'Umb.Kind.SectionSidebarAppMenu', + matchKind: 'menu', + matchType: 'sectionSidebarApp', + manifest: { + type: 'sectionSidebarApp', + element: () => import('./section-sidebar-menu.element.js'), + }, + }, + ...sectionContextManifests, + ...globalContextManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/constants.ts new file mode 100644 index 000000000000..75f40d61ad85 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/constants.ts @@ -0,0 +1 @@ +export * from './section-sidebar-menu.section-context.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/expansion/section-sidebar-menu-section-expansion.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/expansion/section-sidebar-menu-section-expansion.manager.ts new file mode 100644 index 000000000000..162861d5b823 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/expansion/section-sidebar-menu-section-expansion.manager.ts @@ -0,0 +1,139 @@ +import { UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT } from '../../global-context/section-sidebar-menu.global-context.token.js'; +import type { UmbSectionMenuItemExpansionEntryModel } from '../../types.js'; +import type { UmbMenuItemExpansionEntryModel } from '../../../components/menu/types.js'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { Observable } from '@umbraco-cms/backoffice/observable-api'; +import { UMB_SECTION_CONTEXT } from '@umbraco-cms/backoffice/section'; +import { UmbEntityExpansionManager, type UmbEntityExpansionModel } from '@umbraco-cms/backoffice/utils'; + +/** + * Manages the expansion state of a section sidebar menu. + * @exports + * @class UmbSectionSidebarMenuSectionExpansionManager + * @augments {UmbControllerBase} + */ +export class UmbSectionSidebarMenuSectionExpansionManager extends UmbControllerBase { + #manager = new UmbEntityExpansionManager(this); + public readonly expansion = this.#manager.expansion; + + #sectionContext?: typeof UMB_SECTION_CONTEXT.TYPE; + #globalContext?: typeof UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT.TYPE; + #currentSectionAlias?: string; + + constructor(host: UmbControllerHost) { + super(host); + + this.consumeContext(UMB_SECTION_CONTEXT, (sectionContext) => { + this.#sectionContext = sectionContext; + this.#observeCurrentSectionAlias(); + }); + + this.consumeContext(UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT, (globalContext) => { + this.#globalContext = globalContext; + this.#observeGlobalMenuExpansion(); + }); + } + + #observeCurrentSectionAlias() { + this.observe(this.#sectionContext?.alias, (alias) => { + if (!alias) return; + this.#currentSectionAlias = alias; + }); + } + + #observeGlobalMenuExpansion() { + if (!this.#globalContext || !this.#currentSectionAlias) return; + this.observe(this.#globalContext?.expansion.expansionBySectionAlias(this.#currentSectionAlias), (expansion) => { + this.#manager.setExpansion(expansion); + }); + } + + /** + * Checks if an entry is expanded + * @param {UmbMenuItemExpansionEntryModel} entry The entry to check + * @returns {Observable} True if the entry is expanded + * @memberof UmbSectionSidebarMenuSectionExpansionManager + */ + isExpanded(entry: UmbMenuItemExpansionEntryModel): Observable { + return this.#manager.isExpanded(entry); + } + + /** + * Sets the expansion state + * @param {UmbEntityExpansionModel | undefined} expansion The expansion state + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {void} + */ + setExpansion(expansion: UmbEntityExpansionModel): void { + this.#manager.setExpansion(expansion); + const entries = this.#bindEntriesToSection(expansion); + this.#globalContext?.expansion.setExpansion(entries); + } + + /** + * Gets the expansion state + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {UmbEntityExpansionModel} The expansion state + */ + getExpansion(): UmbEntityExpansionModel { + return this.#manager.getExpansion(); + } + + /** + * Opens a menu item + * @param {UmbMenuItemExpansionEntryModel} entry The item to open + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {Promise} + */ + public async expandItem(entry: UmbMenuItemExpansionEntryModel): Promise { + this.#manager.expandItem(entry); + const entries = this.#bindEntriesToSection([entry]); + this.#globalContext?.expansion.expandItem(entries[0]); + } + + /** + * Expands multiple entities + * @param {UmbEntityExpansionModel} entries The entities to open + * @memberof UmbEntityExpansionManager + * @returns {void} + */ + public expandItems(entries: UmbEntityExpansionModel): void { + this.#manager.expandItems(entries); + const entriesWithSectionAlias = this.#bindEntriesToSection(entries); + this.#globalContext?.expansion.expandItems(entriesWithSectionAlias); + } + + /** + * Closes a menu item + * @param {UmbMenuItemExpansionEntryModel} entry The item to close + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {Promise} + */ + public async collapseItem(entry: UmbMenuItemExpansionEntryModel): Promise { + this.#manager.collapseItem(entry); + const entries = this.#bindEntriesToSection([entry]); + this.#globalContext?.expansion.collapseItem(entries[0]); + } + + /** + * Closes all menu items + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {Promise} + */ + public async collapseAll(): Promise { + // Collapse all items in the global context matching the current section + const entries = this.#bindEntriesToSection(this.#manager.getExpansion()); + this.#manager.collapseAll(); + this.#globalContext?.expansion.collapseItems(entries); + } + + #bindEntriesToSection( + expansion: UmbEntityExpansionModel, + ): UmbEntityExpansionModel { + return expansion.map((item) => ({ + ...item, + sectionAlias: this.#currentSectionAlias, + })); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/manifests.ts new file mode 100644 index 000000000000..19c4552e6d03 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/manifests.ts @@ -0,0 +1,10 @@ +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'sectionContext', + alias: 'Umb.SectionContext.SectionSidebarMenu', + name: 'Section Sidebar Menu Section Context', + api: () => import('./section-sidebar-menu.section-context.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/section-sidebar-menu.section-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/section-sidebar-menu.section-context.token.ts new file mode 100644 index 000000000000..bdab53e799ff --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/section-sidebar-menu.section-context.token.ts @@ -0,0 +1,8 @@ +import { UMB_SECTION_CONTEXT } from '@umbraco-cms/backoffice/section'; +import type { UmbSectionSidebarMenuSectionContext } from './section-sidebar-menu.section-context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT = new UmbContextToken( + UMB_SECTION_CONTEXT.contextAlias, + 'UmbSectionSidebarMenuSectionContext', +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/section-sidebar-menu.section-context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/section-sidebar-menu.section-context.ts new file mode 100644 index 000000000000..e4cfe3861f78 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/section-sidebar-menu.section-context.ts @@ -0,0 +1,14 @@ +import { UmbSectionSidebarMenuSectionExpansionManager } from './expansion/section-sidebar-menu-section-expansion.manager.js'; +import { UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT } from './section-sidebar-menu.section-context.token.js'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbSectionSidebarMenuSectionContext extends UmbContextBase { + public readonly expansion = new UmbSectionSidebarMenuSectionExpansionManager(this); + + constructor(host: UmbControllerHost) { + super(host, UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT); + } +} + +export { UmbSectionSidebarMenuSectionContext as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-sidebar-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-sidebar-menu.element.ts index 51b8b8c34dea..fc50249783b4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-sidebar-menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-sidebar-menu.element.ts @@ -1,23 +1,12 @@ import type { ManifestMenu } from '../menu.extension.js'; +import { isMenuItemExpansionEntry } from '../components/menu-item/expansion/is-menu-item-expansion-entry.guard.js'; import type { ManifestSectionSidebarAppBaseMenu, ManifestSectionSidebarAppMenuKind } from './types.js'; +import { UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT } from './section-context/section-sidebar-menu.section-context.token.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; - -// TODO: Move to separate file: -const manifest: UmbExtensionManifestKind = { - type: 'kind', - alias: 'Umb.Kind.SectionSidebarAppMenu', - matchKind: 'menu', - matchType: 'sectionSidebarApp', - manifest: { - type: 'sectionSidebarApp', - elementName: 'umb-section-sidebar-menu', - }, -}; -umbExtensionsRegistry.register(manifest); +import { UmbExpansionEntryCollapsedEvent, UmbExpansionEntryExpandedEvent } from '@umbraco-cms/backoffice/utils'; +import { UmbExtensionSlotElement } from '@umbraco-cms/backoffice/extension-registry'; @customElement('umb-section-sidebar-menu') export class UmbSectionSidebarMenuElement< @@ -34,14 +23,73 @@ export class UmbSectionSidebarMenuElement< return html`

${this.localize.string(this.manifest?.meta?.label ?? '')}

`; } + #sectionSidebarMenuContext?: typeof UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT.TYPE; + + #extensionSlotElement = new UmbExtensionSlotElement(); + #muteStateUpdate = false; + + constructor() { + super(); + this.#initExtensionSlotElement(); + this.consumeContext(UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT, (context) => { + this.#sectionSidebarMenuContext = context; + this.#observeExpansion(); + }); + } + + #initExtensionSlotElement() { + /* For better performance and UX we prevent lit from doing unnecessary rerenders we programmatically create the element, + and manually update the props when needed. */ + + this.#extensionSlotElement.type = 'menu'; + this.#extensionSlotElement.filter = (menu: ManifestMenu) => menu.alias === this.manifest?.meta?.menu; + this.#extensionSlotElement.defaultElement = 'umb-menu'; + this.#extensionSlotElement.events = { + [UmbExpansionEntryExpandedEvent.TYPE]: this.#onExpansionChange.bind(this), + [UmbExpansionEntryCollapsedEvent.TYPE]: this.#onExpansionChange.bind(this), + }; + } + + #observeExpansion() { + this.observe(this.#sectionSidebarMenuContext?.expansion.expansion, (items) => { + if (this.#muteStateUpdate) return; + + this.#extensionSlotElement.props = { + expansion: items || [], + }; + }); + } + + #onExpansionChange(e: Event) { + const event = e as UmbExpansionEntryExpandedEvent | UmbExpansionEntryCollapsedEvent; + event.stopPropagation(); + const eventEntry = event.entry; + + if (!eventEntry) { + throw new Error('Entity is required to toggle expansion.'); + } + + // Only react to the event if it is a valid Menu Item Expansion Entry + if (isMenuItemExpansionEntry(eventEntry) === false) return; + + if (event.type === UmbExpansionEntryExpandedEvent.TYPE) { + this.#muteStateUpdate = true; + this.#sectionSidebarMenuContext?.expansion.expandItem(eventEntry); + this.#muteStateUpdate = false; + } else if (event.type === UmbExpansionEntryCollapsedEvent.TYPE) { + this.#muteStateUpdate = true; + this.#sectionSidebarMenuContext?.expansion.collapseItem(eventEntry); + this.#muteStateUpdate = false; + } + } + override render() { - return html` - ${this.renderHeader()} - - `; + return html` ${this.renderHeader()} ${this.#extensionSlotElement}`; + } + + override disconnectedCallback(): void { + super.disconnectedCallback(); + this.#extensionSlotElement.destroy(); } static override styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/types.ts index af00746dafbf..85a1cf64ab4f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/types.ts @@ -1,3 +1,4 @@ +import type { UmbMenuItemExpansionEntryModel } from '../components/menu/types.js'; import type { ManifestSectionSidebarApp } from '@umbraco-cms/backoffice/section'; export interface MetaSectionSidebarAppMenuKind { @@ -24,3 +25,7 @@ export interface ManifestSectionSidebarAppMenuWithEntityActionsKind extends Mani export interface MetaSectionSidebarAppMenuWithEntityActionsKind extends MetaSectionSidebarAppMenuKind { entityType: string; } + +export interface UmbSectionMenuItemExpansionEntryModel extends UmbMenuItemExpansionEntryModel { + sectionAlias?: string; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/types.ts index 93dda2eb92fc..028299b81adf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/types.ts @@ -2,9 +2,9 @@ import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; export type * from './components/types.js'; export type * from './conditions/types.js'; -export type * from './menu-item-element.interface.js'; -export type * from './menu-item.extension.js'; +export type * from './menu-structure/types.js'; export type * from './menu.extension.js'; +export type * from './section-sidebar-menu/types.js'; // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface UmbStructureItemModelBase extends UmbEntityModel {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/section-alias.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/section-alias.condition.ts index f18f3e75f681..91dc47b6bf6c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/section-alias.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/section-alias.condition.ts @@ -1,8 +1,8 @@ -import { UmbConditionBase } from '../../../extension-registry/conditions/condition-base.controller.js'; -import { UMB_SECTION_CONTEXT } from '../../section.context.js'; import type { SectionAliasConditionConfig } from '../types.js'; +import { UMB_SECTION_CONTEXT } from '../../section.context.token.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; +import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; export class UmbSectionAliasCondition extends UmbConditionBase diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/default/default-section.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/default/default-section.context.ts new file mode 100644 index 000000000000..26378d491464 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/default/default-section.context.ts @@ -0,0 +1,8 @@ +import { UmbSectionContext } from '../section.context.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbDefaultSectionContext extends UmbSectionContext { + constructor(host: UmbControllerHost) { + super(host); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/default/default-section.element.ts similarity index 84% rename from src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/section/default/default-section.element.ts index 9b3d6fc293bf..aed2293213cd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/default/default-section.element.ts @@ -1,6 +1,7 @@ -import type { ManifestSectionRoute } from './extensions/section-route.extension.js'; -import type { UmbSectionMainViewElement } from './section-main-views/section-main-views.element.js'; -import type { ManifestSection, UmbSectionElement } from './types.js'; +import type { ManifestSectionRoute } from '../extensions/section-route.extension.js'; +import type { UmbSectionMainViewElement } from '../section-main-views/section-main-views.element.js'; +import type { ManifestSection, UmbSectionElement } from '../types.js'; +import { UmbDefaultSectionContext } from './default-section.context.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, nothing, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; @@ -21,7 +22,7 @@ import { UMB_MARK_ATTRIBUTE_NAME } from '@umbraco-cms/backoffice/const'; * @description - Element hosting sections and section navigation. */ @customElement('umb-section-default') -export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectionElement { +export class UmbDefaultSectionElement extends UmbLitElement implements UmbSectionElement { private _manifest?: ManifestSection | undefined; @property({ type: Object, attribute: false }) @@ -32,7 +33,7 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio const oldValue = this._manifest; if (oldValue === value) return; this._manifest = value; - + this.#api.setManifest(value); this.requestUpdate('manifest', oldValue); } @@ -45,6 +46,9 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio @state() private _splitPanelPosition = '300px'; + // TODO: v17: Move this to a manifest api. It will have to wait for a major as it will be a breaking change. + #api = new UmbDefaultSectionContext(this); + constructor() { super(); @@ -111,7 +115,7 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio ...routes, { path: '**', - component: () => import('./section-main-views/section-main-views.element.js'), + component: () => import('../section-main-views/section-main-views.element.js'), setup: (element) => { (element as UmbSectionMainViewElement).sectionAlias = this.manifest?.alias; }, @@ -177,8 +181,16 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio ]; } +export { UmbDefaultSectionElement as element }; + declare global { interface HTMLElementTagNameMap { - 'umb-section-default': UmbSectionDefaultElement; + 'umb-section-default': UmbDefaultSectionElement; } } + +/** + * + * @deprecated Since 16. Use UmbDefaultSectionElement instead. UmbSectionDefaultElement will be removed in v18. + */ +export { UmbDefaultSectionElement as UmbSectionDefaultElement }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/default/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/default/manifests.ts new file mode 100644 index 000000000000..1802584f0f23 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/default/manifests.ts @@ -0,0 +1,20 @@ +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'kind', + alias: 'Umb.Kind.Section.Default', + matchKind: 'default', + matchType: 'section', + manifest: { + type: 'section', + kind: 'default', + weight: 1000, + element: () => import('./default-section.element.js'), + meta: { + label: '', + pathname: '', + }, + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/default/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/default/types.ts new file mode 100644 index 000000000000..17eeb41ff005 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/default/types.ts @@ -0,0 +1,11 @@ +import type { ManifestSection } from '../../section/extensions/index.js'; + +export interface ManifestSectionDefaultKind extends ManifestSection { + kind: 'default'; +} + +declare global { + interface UmbExtensionManifestMap { + umbDefaultSectionKind: ManifestSectionDefaultKind; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/section-context.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/section-context.extension.ts new file mode 100644 index 000000000000..25e0e114cc2e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/section-context.extension.ts @@ -0,0 +1,17 @@ +import type { ManifestApi, ManifestWithDynamicConditions, UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export interface ManifestSectionContext + extends ManifestWithDynamicConditions, + ManifestApi { + type: 'sectionContext'; + meta: MetaType; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface MetaSectionContext {} + +declare global { + interface UmbExtensionManifestMap { + UmbManifestSectionContext: ManifestSectionContext; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/section.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/section.extension.ts index 63f363013bac..b3d90397f536 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/section.extension.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/section.extension.ts @@ -2,6 +2,7 @@ import type { UmbSectionElement } from './section-element.interface.js'; import type { ManifestElement, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api'; export interface ManifestSection + // TODO: v17 change to extend Element and Api extends ManifestElement, ManifestWithDynamicConditions { type: 'section'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/types.ts index e4d5de7f8b8f..9e65c9399679 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/types.ts @@ -1,3 +1,4 @@ +export type * from './section-context.extension.js'; export type * from './section-element.interface.js'; export type * from './section-sidebar-app-element.interface.js'; export type * from './section-view-element.interface.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/index.ts index 9ac642e834b7..03fa9cfd53eb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/index.ts @@ -1,11 +1,10 @@ export * from './components/index.js'; export * from './constants.js'; -export * from './section-default.element.js'; +export * from './default/default-section.element.js'; export * from './section-main/index.js'; export * from './section-picker-modal/section-picker-modal.token.js'; export * from './section-sidebar-context-menu/index.js'; -export * from '../menu/section-sidebar-menu-with-entity-actions/index.js'; -export * from '../menu/section-sidebar-menu/index.js'; export * from './section-sidebar/index.js'; export * from './section.context.js'; +export * from './section.context.token.js'; export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main/section-main.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main/section-main.element.ts index 144a2b27049c..ebae159f82fe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main/section-main.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main/section-main.element.ts @@ -1,8 +1,9 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, html, LitElement, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @customElement('umb-section-main') -export class UmbSectionMainElement extends LitElement { +export class UmbSectionMainElement extends UmbLitElement { override render() { return html`
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.token.ts new file mode 100644 index 000000000000..b70b7b5ebfe4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.token.ts @@ -0,0 +1,4 @@ +import type { UmbSectionContext } from './section.context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_SECTION_CONTEXT = new UmbContextToken('UmbSectionContext'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.ts index fbb626897fb3..510697e14607 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.ts @@ -1,8 +1,10 @@ import type { ManifestSection } from './extensions/section.extension.js'; +import { UMB_SECTION_CONTEXT } from './section.context.token.js'; import { UmbStringState } from '@umbraco-cms/backoffice/observable-api'; -import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import { UmbExtensionsApiInitializer } from '@umbraco-cms/backoffice/extension-api'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; export class UmbSectionContext extends UmbContextBase { #manifestAlias = new UmbStringState(undefined); @@ -12,8 +14,11 @@ export class UmbSectionContext extends UmbContextBase { public readonly pathname = this.#manifestPathname.asObservable(); public readonly label = this.#manifestLabel.asObservable(); + #sectionContextExtensionController?: UmbExtensionsApiInitializer; + constructor(host: UmbControllerHost) { super(host, UMB_SECTION_CONTEXT); + this.#createSectionContextExtensions(); } public setManifest(manifest?: ManifestSection) { @@ -25,6 +30,18 @@ export class UmbSectionContext extends UmbContextBase { getPathname() { return this.#manifestPathname.getValue(); } -} -export const UMB_SECTION_CONTEXT = new UmbContextToken('UmbSectionContext'); + #createSectionContextExtensions() { + if (this.#sectionContextExtensionController) { + this.#sectionContextExtensionController.destroy(); + } + + this.#sectionContextExtensionController = new UmbExtensionsApiInitializer( + this, + umbExtensionsRegistry, + 'sectionContext', + [], + undefined, + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/tree-expansion-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/tree-expansion-manager.ts index a2144f8c1cc0..26f6bba57deb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/tree-expansion-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/tree-expansion-manager.ts @@ -1,7 +1,8 @@ import type { UmbTreeExpansionModel } from './types.js'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; -import { UmbArrayState, type Observable } from '@umbraco-cms/backoffice/observable-api'; +import type { Observable } from '@umbraco-cms/backoffice/observable-api'; +import { UmbEntityExpansionManager } from '@umbraco-cms/backoffice/utils'; /** * Manages the expansion state of a tree @@ -10,8 +11,8 @@ import { UmbArrayState, type Observable } from '@umbraco-cms/backoffice/observab * @augments {UmbControllerBase} */ export class UmbTreeExpansionManager extends UmbControllerBase { - #expansion = new UmbArrayState([], (x) => x.unique); - expansion = this.#expansion.asObservable(); + #manager = new UmbEntityExpansionManager(this); + expansion = this.#manager.expansion; /** * Checks if an entity is expanded @@ -22,9 +23,7 @@ export class UmbTreeExpansionManager extends UmbControllerBase { * @memberof UmbTreeExpansionManager */ isExpanded(entity: UmbEntityModel): Observable { - return this.#expansion.asObservablePart((entries) => - entries?.some((entry) => entry.entityType === entity.entityType && entry.unique === entity.unique), - ); + return this.#manager.isExpanded(entity); } /** @@ -34,7 +33,7 @@ export class UmbTreeExpansionManager extends UmbControllerBase { * @returns {void} */ setExpansion(expansion: UmbTreeExpansionModel): void { - this.#expansion.setValue(expansion); + this.#manager.setExpansion(expansion); } /** @@ -43,7 +42,7 @@ export class UmbTreeExpansionManager extends UmbControllerBase { * @returns {UmbTreeExpansionModel} The expansion state */ getExpansion(): UmbTreeExpansionModel { - return this.#expansion.getValue(); + return this.#manager.getExpansion(); } /** @@ -55,7 +54,7 @@ export class UmbTreeExpansionManager extends UmbControllerBase { * @returns {Promise} */ public async expandItem(entity: UmbEntityModel): Promise { - this.#expansion.appendOne(entity); + this.#manager.expandItem(entity); } /** @@ -67,7 +66,7 @@ export class UmbTreeExpansionManager extends UmbControllerBase { * @returns {Promise} */ public async collapseItem(entity: UmbEntityModel): Promise { - this.#expansion.filter((x) => x.entityType !== entity.entityType || x.unique !== entity.unique); + this.#manager.collapseItem(entity); } /** @@ -76,6 +75,6 @@ export class UmbTreeExpansionManager extends UmbControllerBase { * @returns {Promise} */ public async collapseAll(): Promise { - this.#expansion.setValue([]); + this.#manager.collapseAll(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/types.ts index 7d5c0500296d..0585b8ed8c1a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/types.ts @@ -1,3 +1,3 @@ -import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import type { UmbEntityExpansionModel } from '@umbraco-cms/backoffice/utils'; -export type UmbTreeExpansionModel = Array; +export type UmbTreeExpansionModel = UmbEntityExpansionModel; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts index c947f5199f28..daa67a87892a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts @@ -7,7 +7,7 @@ export * from './entity-actions/reload-tree-item-children/index.js'; export * from './entity-actions/sort-children-of/index.js'; export * from './folder/index.js'; export * from './tree-item/index.js'; -export * from './tree-menu-item-default/index.js'; +export * from './tree-menu-item/index.js'; export * from './tree.element.js'; export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/manifests.ts index e1972b17cc9a..ec8a28e85f2a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/manifests.ts @@ -1,8 +1,9 @@ -import { manifests as folderManifests } from './folder/manifests.js'; import { manifests as defaultTreeItemManifests } from './tree-item/tree-item-default/manifests.js'; import { manifests as defaultTreeManifests } from './default/manifests.js'; -import { manifests as treePickerManifests } from './tree-picker-modal/manifests.js'; import { manifests as entityActionManifests } from './entity-actions/manifests.js'; +import { manifests as folderManifests } from './folder/manifests.js'; +import { manifests as treeMenuItemManifests } from './tree-menu-item/manifests.js'; +import { manifests as treePickerManifests } from './tree-picker-modal/manifests.js'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ @@ -10,5 +11,6 @@ export const manifests: Array = ...defaultTreeManifests, ...entityActionManifests, ...folderManifests, + ...treeMenuItemManifests, ...treePickerManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item-default/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item-default/index.ts deleted file mode 100644 index af047cc2d43d..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item-default/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './tree-menu-item-default.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item-default/tree-menu-item-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item-default/tree-menu-item-default.element.ts deleted file mode 100644 index 93ce54fa6d57..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item-default/tree-menu-item-default.element.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { ManifestMenuItemTreeKind } from './types.js'; -import { html, nothing, customElement, property } from '@umbraco-cms/backoffice/external/lit'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -import type { UmbMenuItemElement } from '@umbraco-cms/backoffice/menu'; - -// TODO: Move to separate file: -const manifest: UmbExtensionManifestKind = { - type: 'kind', - alias: 'Umb.Kind.Tree', - matchKind: 'tree', - matchType: 'menuItem', - manifest: { - type: 'menuItem', - elementName: 'umb-menu-item-tree-default', - }, -}; -umbExtensionsRegistry.register(manifest); - -@customElement('umb-menu-item-tree-default') -export class UmbMenuItemTreeDefaultElement extends UmbLitElement implements UmbMenuItemElement { - @property({ type: Object }) - manifest?: ManifestMenuItemTreeKind; - - override render() { - return this.manifest - ? html` - - ` - : nothing; - } -} - -export default UmbMenuItemTreeDefaultElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-menu-item-tree-default': UmbMenuItemTreeDefaultElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/index.ts new file mode 100644 index 000000000000..c4d5f8a3f079 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/index.ts @@ -0,0 +1 @@ +export * from './tree-menu-item.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/manifests.ts new file mode 100644 index 000000000000..1ace3b635eb4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/manifests.ts @@ -0,0 +1,14 @@ +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'kind', + alias: 'Umb.Kind.Tree', + matchKind: 'tree', + matchType: 'menuItem', + manifest: { + type: 'menuItem', + element: () => import('./tree-menu-item.element.js'), + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/tree-menu-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/tree-menu-item.element.ts new file mode 100644 index 000000000000..62624dd3d5f7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/tree-menu-item.element.ts @@ -0,0 +1,87 @@ +import type { ManifestMenuItemTreeKind } from './types.js'; +import { html, nothing, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UMB_MENU_ITEM_CONTEXT, type UmbMenuItemElement } from '@umbraco-cms/backoffice/menu'; +import { + UmbExpansionEntryCollapsedEvent, + UmbExpansionEntryExpandedEvent, + type UmbEntityExpansionModel, +} from '@umbraco-cms/backoffice/utils'; + +@customElement('umb-menu-item-tree-default') +export class UmbMenuItemTreeDefaultElement extends UmbLitElement implements UmbMenuItemElement { + @property({ type: Object }) + manifest?: ManifestMenuItemTreeKind; + + @state() + private _menuItemExpansion: UmbEntityExpansionModel = []; + + #menuItemContext?: typeof UMB_MENU_ITEM_CONTEXT.TYPE; + #muteStateUpdate = false; + + constructor() { + super(); + + this.consumeContext(UMB_MENU_ITEM_CONTEXT, (context) => { + this.#menuItemContext = context; + this.#observeExpansion(); + }); + } + + #observeExpansion() { + this.observe(this.#menuItemContext?.expansion.expansion, (items) => { + if (this.#muteStateUpdate) return; + this._menuItemExpansion = items || []; + }); + } + + #onExpansionChange(event: UmbExpansionEntryExpandedEvent | UmbExpansionEntryCollapsedEvent) { + event.stopPropagation(); + const eventEntry = event.entry; + + if (!eventEntry) { + throw new Error('Entry is required to toggle expansion.'); + } + + if (!this.manifest) { + throw new Error('Manifest is required to toggle expansion.'); + } + + if (event.type === UmbExpansionEntryExpandedEvent.TYPE) { + this.#muteStateUpdate = true; + this.#menuItemContext?.expansion.expandItem({ ...eventEntry, menuItemAlias: this.manifest.alias }); + this.#muteStateUpdate = false; + } else if (event.type === UmbExpansionEntryCollapsedEvent.TYPE) { + this.#muteStateUpdate = true; + this.#menuItemContext?.expansion.collapseItem({ ...eventEntry, menuItemAlias: this.manifest.alias }); + this.#muteStateUpdate = false; + } + } + + override render() { + return this.manifest + ? html` + + ` + : nothing; + } +} + +export default UmbMenuItemTreeDefaultElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-menu-item-tree-default': UmbMenuItemTreeDefaultElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item-default/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/types.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item-default/types.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/types.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.element.ts index 889ab0849b0b..a091f5e73269 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.element.ts @@ -18,6 +18,13 @@ export class UmbTreeElement extends UmbExtensionElementAndApiSlotElementBase extends UmbControllerBase { + protected _expansion = new UmbArrayState([], (x) => x.entityType + x.unique); + expansion = this._expansion.asObservable(); + + /** + * Checks if an entity is expanded + * @param {EntryModelType} entity The entity to check + * @returns {Observable} True if the entity is expanded + * @memberof UmbEntityExpansionManager + */ + isExpanded(entity: EntryModelType): Observable { + return this._expansion.asObservablePart((entries) => + entries?.some((entry) => entry.entityType === entity.entityType && entry.unique === entity.unique), + ); + } + + /** + * Sets the expansion state + * @param {UmbEntityExpansionModel | undefined} expansion The expansion state + * @memberof UmbEntityExpansionManager + * @returns {void} + */ + setExpansion(expansion: UmbEntityExpansionModel): void { + this._expansion.setValue(expansion); + this.getHostElement()?.dispatchEvent(new UmbExpansionChangeEvent()); + } + + /** + * Gets the expansion state + * @memberof UmbEntityExpansionManager + * @returns {UmbEntityExpansionModel} The expansion state + */ + getExpansion(): UmbEntityExpansionModel { + return this._expansion.getValue(); + } + + /** + * Expands an entity + * @param {EntryModelType} entity The entity to open + * @memberof UmbEntityExpansionManager + * @returns {Promise} + */ + public async expandItem(entity: EntryModelType): Promise { + this._expansion.appendOne(entity); + this.getHostElement()?.dispatchEvent(new UmbExpansionEntryExpandedEvent(entity)); + this.getHostElement()?.dispatchEvent(new UmbExpansionChangeEvent()); + } + + /** + * Expands multiple entities + * @param {UmbEntityExpansionModel} entities The entities to open + * @memberof UmbEntityExpansionManager + * @returns {void} + */ + public expandItems(entities: UmbEntityExpansionModel): void { + if (!entities || entities.length === 0) return; + this._expansion.append(entities); + entities.forEach((entity) => { + this.getHostElement()?.dispatchEvent(new UmbExpansionEntryExpandedEvent(entity)); + }); + this.getHostElement()?.dispatchEvent(new UmbExpansionChangeEvent()); + } + + /** + * Collapses an entity + * @param {EntryModelType} entry The entity to open + * @memberof UmbEntityExpansionManager + * @returns {Promise} + */ + public async collapseItem(entry: EntryModelType): Promise { + this._expansion.filter((x) => x.entityType !== entry.entityType || x.unique !== entry.unique); + this.getHostElement()?.dispatchEvent(new UmbExpansionEntryCollapsedEvent(entry)); + this.getHostElement()?.dispatchEvent(new UmbExpansionChangeEvent()); + } + + /** + * Collapses multiple entities + * @param {UmbEntityExpansionModel} entries The entities to close + * @memberof UmbEntityExpansionManager + * @returns {void} + */ + public collapseItems(entries: UmbEntityExpansionModel): void { + if (!entries || entries.length === 0) return; + this._expansion.filter( + (x) => !entries.some((entry) => entry.entityType === x.entityType && entry.unique === x.unique), + ); + entries.forEach((entry) => { + this.getHostElement()?.dispatchEvent(new UmbExpansionEntryCollapsedEvent(entry)); + }); + this.getHostElement()?.dispatchEvent(new UmbExpansionChangeEvent()); + } + + /** + * Collapses all expanded entities + * @memberof UmbEntityExpansionManager + * @returns {Promise} + */ + public async collapseAll(): Promise { + this._expansion.setValue([]); + this.getHostElement()?.dispatchEvent(new UmbExpansionChangeEvent()); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-change.event.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-change.event.ts new file mode 100644 index 000000000000..bfc0374014f8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-change.event.ts @@ -0,0 +1,8 @@ +export class UmbExpansionChangeEvent extends Event { + public static readonly TYPE = 'expansion-change'; + + public constructor() { + // mimics the native change event + super(UmbExpansionChangeEvent.TYPE, { bubbles: true, composed: false, cancelable: false }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-entry-collapsed.event.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-entry-collapsed.event.ts new file mode 100644 index 000000000000..b946982ab964 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-entry-collapsed.event.ts @@ -0,0 +1,12 @@ +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +export class UmbExpansionEntryCollapsedEvent extends Event { + public static readonly TYPE = 'expansion-entry-collapsed'; + entry: EntryModelType; + + public constructor(entry: EntryModelType) { + // mimics the native change event + super(UmbExpansionEntryCollapsedEvent.TYPE, { bubbles: true, composed: false, cancelable: false }); + this.entry = entry; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-entry-expanded.event.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-entry-expanded.event.ts new file mode 100644 index 000000000000..0831115d91fa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-entry-expanded.event.ts @@ -0,0 +1,12 @@ +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +export class UmbExpansionEntryExpandedEvent extends Event { + public static readonly TYPE = 'expansion-entry-expanded'; + entry: EntryModelType; + + public constructor(entry: EntryModelType) { + // mimics the native change event + super(UmbExpansionEntryExpandedEvent.TYPE, { bubbles: true, composed: false, cancelable: false }); + this.entry = entry; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/index.ts new file mode 100644 index 000000000000..7a4b74e03484 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/index.ts @@ -0,0 +1,3 @@ +export * from './expansion-change.event.js'; +export * from './expansion-entry-collapsed.event.js'; +export * from './expansion-entry-expanded.event.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/index.ts new file mode 100644 index 000000000000..ee4761b07d62 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/index.ts @@ -0,0 +1,4 @@ +export * from './entity-expansion.manager.js'; +export * from './events/index.js'; +export * from './utils/index.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/types.ts new file mode 100644 index 000000000000..ada5d4b7dd4e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/types.ts @@ -0,0 +1,9 @@ +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +export type UmbEntityExpansionModel< + EntryModelType extends UmbEntityExpansionEntryModel = UmbEntityExpansionEntryModel, +> = Array; + +export interface UmbEntityExpansionEntryModel extends UmbEntityModel { + target?: UmbEntityModel; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/index.ts new file mode 100644 index 000000000000..510a193d399c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/index.ts @@ -0,0 +1 @@ +export * from './link-entity-expansion-entries.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/link-entity-expansion-entries.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/link-entity-expansion-entries.test.ts new file mode 100644 index 000000000000..5c9ec731832b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/link-entity-expansion-entries.test.ts @@ -0,0 +1,26 @@ +import { expect } from '@open-wc/testing'; +import { linkEntityExpansionEntries } from './link-entity-expansion-entries'; + +describe('LinkEntityExpansionEntries', () => { + const input = [ + { entityType: 'document', unique: '1' }, + { entityType: 'document', unique: '2' }, + { entityType: 'document', unique: '3' }, + ]; + + it('should return an array of expansion entries with target', () => { + const result = linkEntityExpansionEntries(input); + expect(result).to.be.an('array').that.has.lengthOf(3); + expect(result[0]).to.have.property('entityType', 'document'); + expect(result[0]).to.have.property('unique', '1'); + expect(result[0]).to.have.property('target').that.deep.equals({ entityType: 'document', unique: '2' }); + + expect(result[1]).to.have.property('entityType', 'document'); + expect(result[1]).to.have.property('unique', '2'); + expect(result[1]).to.have.property('target').that.deep.equals({ entityType: 'document', unique: '3' }); + + expect(result[2]).to.have.property('entityType', 'document'); + expect(result[2]).to.have.property('unique', '3'); + expect(result[2]).to.not.have.property('target'); + }); +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/link-entity-expansion-entries.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/link-entity-expansion-entries.ts new file mode 100644 index 000000000000..04759ec3a1f2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/link-entity-expansion-entries.ts @@ -0,0 +1,22 @@ +import type { UmbEntityExpansionEntryModel, UmbEntityExpansionModel } from '../types.js'; +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +export const linkEntityExpansionEntries = (data: Array): UmbEntityExpansionModel => { + return data.map((item, index) => { + const result: UmbEntityExpansionEntryModel = { + entityType: item.entityType, + unique: item.unique, + }; + + const next = data[index + 1]; + + if (next) { + result.target = { + entityType: next.entityType, + unique: next.unique, + }; + } + + return result; + }); +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts index da5a0ec8c6a4..99bd098a0225 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts @@ -5,6 +5,7 @@ export * from './deprecation/index.js'; export * from './diff/index.js'; export * from './direction/index.js'; export * from './download/blob-download.function.js'; +export * from './expansion/index.js'; export * from './get-guid-from-udi.function.js'; export * from './get-processed-image-url.function.js'; export * from './guard-manager/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-menu-breadcrumb/workspace-menu-breadcrumb.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-menu-breadcrumb/workspace-menu-breadcrumb.element.ts index c23a374acd0d..0fcb8deab4fe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-menu-breadcrumb/workspace-menu-breadcrumb.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-menu-breadcrumb/workspace-menu-breadcrumb.element.ts @@ -14,6 +14,9 @@ export class UmbWorkspaceBreadcrumbElement extends UmbLitElement { @state() private _structure: UmbStructureItemModel[] = []; + @state() + private _isNew: boolean = false; + // TODO: figure out the correct context type #workspaceContext?: any; #sectionContext?: typeof UMB_SECTION_CONTEXT.TYPE; @@ -28,6 +31,7 @@ export class UmbWorkspaceBreadcrumbElement extends UmbLitElement { this.consumeContext(UMB_WORKSPACE_CONTEXT, (instance) => { this.#workspaceContext = instance as any; + this.#observeIsNew(); this.#observeStructure(); this.#observeName(); }); @@ -38,16 +42,15 @@ export class UmbWorkspaceBreadcrumbElement extends UmbLitElement { }); } - #observeStructure() { - if (!this.#structureContext || !this.#workspaceContext) return; - const isNew = this.#workspaceContext.getIsNew(); - + #observeIsNew() { this.observe( - this.#structureContext.structure, + this.#workspaceContext?.isNew, (value) => { - this._structure = isNew ? value : value.slice(0, -1); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this._isNew = value || false; }, - 'menuStructureObserver', + 'breadcrumbWorkspaceIsNewObserver', ); } @@ -61,16 +64,30 @@ export class UmbWorkspaceBreadcrumbElement extends UmbLitElement { ); } + #observeStructure() { + if (!this.#structureContext || !this.#workspaceContext) return; + + this.observe( + this.#structureContext.structure, + (value) => { + this._structure = value; + }, + 'menuStructureObserver', + ); + } + #getHref(structureItem: UmbStructureItemModel) { if (structureItem.isFolder) return undefined; return `section/${this.#sectionContext?.getPathname()}/workspace/${structureItem.entityType}/edit/${structureItem.unique}`; } override render() { + const structure = this._isNew ? this._structure : this._structure.slice(0, -1); + return html` ${map( - this._structure, + structure, (structureItem) => html`${this.localize.string(structureItem.name)} = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.DataTypes', + alias: UMB_DATA_TYPE_MENU_ITEM_ALIAS, name: 'Data Types Menu Item', weight: 600, meta: { @@ -16,9 +17,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Data Type Menu Structure Workspace Context', alias: 'Umb.Context.DataType.Menu.Structure', api: () => import('./data-type-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_DATA_TYPE_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/constants.ts index eb8dd5d7b139..8ed0d150bdcd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/constants.ts @@ -6,5 +6,6 @@ export * from './repository/constants.js'; export * from './search/constants.js'; export * from './tree/constants.js'; export * from './workspace/constants.js'; +export * from './menu-item/constants.js'; export { UMB_DICTIONARY_ROOT_ENTITY_TYPE, UMB_DICTIONARY_ENTITY_TYPE } from './entity.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/constants.ts new file mode 100644 index 000000000000..d2abd8712297 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/constants.ts @@ -0,0 +1 @@ +export const UMB_DICTIONARY_MENU_ITEM_ALIAS = 'Umb.MenuItem.Dictionary'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/manifests.ts index 03da8a1d404b..109608104cd6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/manifests.ts @@ -1,5 +1,6 @@ import { UMB_DICTIONARY_ENTITY_TYPE } from '../entity.js'; import { UMB_DICTIONARY_TREE_ALIAS } from '../tree/index.js'; +import { UMB_DICTIONARY_MENU_ITEM_ALIAS } from './constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_TRANSLATION_MENU_ALIAS } from '@umbraco-cms/backoffice/translation'; @@ -7,7 +8,7 @@ export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.Dictionary', + alias: UMB_DICTIONARY_MENU_ITEM_ALIAS, name: 'Dictionary Menu Item', weight: 400, meta: { @@ -21,9 +22,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Dictionary Menu Structure Workspace Context', alias: 'Umb.Context.Dictionary.Menu.Structure', api: () => import('./dictionary-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_DICTIONARY_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/constants.ts index d07e60d1021c..c4577efe6ebb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/constants.ts @@ -5,5 +5,6 @@ export * from './repository/constants.js'; export * from './search/constants.js'; export * from './tree/constants.js'; export * from './workspace/constants.js'; +export * from './menu/constants.js'; export { UMB_DOCUMENT_TYPE_ENTITY_TYPE, UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE } from './entity.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/constants.ts new file mode 100644 index 000000000000..6f56101e30f5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/constants.ts @@ -0,0 +1 @@ +export const UMB_DOCUMENT_TYPE_MENU_ITEM_ALIAS = 'Umb.MenuItem.DocumentTypes'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/manifests.ts index 35ea56aca9f8..1e28f22619f3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/manifests.ts @@ -1,11 +1,12 @@ import { UMB_DOCUMENT_TYPE_TREE_ALIAS } from '../tree/index.js'; +import { UMB_DOCUMENT_TYPE_MENU_ITEM_ALIAS } from './constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.DocumentTypes', + alias: UMB_DOCUMENT_TYPE_MENU_ITEM_ALIAS, name: 'Document Types Menu Item', weight: 900, meta: { @@ -16,9 +17,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Document Type Menu Structure Workspace Context', alias: 'Umb.Context.DocumentType.Menu.Structure', api: () => import('./document-type-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_DOCUMENT_TYPE_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/constants.ts index b0cd03a572f1..8b2ea67a99c5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/constants.ts @@ -4,6 +4,7 @@ export * from './collection/constants.js'; export * from './entity-actions/constants.js'; export * from './entity-bulk-actions/constants.js'; export * from './item/constants.js'; +export * from './menu/constants.js'; export * from './modals/constants.js'; export * from './paths.js'; export * from './property-dataset-context/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/constants.ts new file mode 100644 index 000000000000..e2fd4065cc19 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/constants.ts @@ -0,0 +1 @@ +export const UMB_DOCUMENT_MENU_ITEM_ALIAS = 'Umb.MenuItem.Document'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/manifests.ts index dc764170937e..f50b53291103 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/manifests.ts @@ -1,4 +1,5 @@ import { UMB_DOCUMENT_TREE_ALIAS } from '../tree/index.js'; +import { UMB_DOCUMENT_MENU_ITEM_ALIAS } from './constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_CONTENT_MENU_ALIAS = 'Umb.Menu.Content'; @@ -12,7 +13,7 @@ export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.Document', + alias: UMB_DOCUMENT_MENU_ITEM_ALIAS, name: 'Document Menu Item', weight: 200, meta: { @@ -24,9 +25,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Document Menu Structure Workspace Context', alias: 'Umb.Context.Document.Menu.Structure', api: () => import('./document-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_DOCUMENT_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/constants.ts index c80e3ac5e4f6..86c491dd0079 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/constants.ts @@ -1,11 +1,12 @@ export * from './entity-actions/constants.js'; export * from './media-type-root/constants.js'; +export * from './menu/constants.js'; export * from './paths.js'; export * from './property-type/constants.js'; export * from './repository/constants.js'; +export * from './search/constants.js'; export * from './tree/constants.js'; export * from './workspace/constants.js'; -export * from './search/constants.js'; export { UMB_MEDIA_TYPE_ENTITY_TYPE, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu/constants.ts new file mode 100644 index 000000000000..462c1600e7ba --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu/constants.ts @@ -0,0 +1 @@ +export const UMB_MEDIA_TYPE_MENU_ITEM_ALIAS = 'Umb.MenuItem.MediaTypes'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu/manifests.ts index 65107a259681..9272a5fafd40 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu/manifests.ts @@ -1,11 +1,12 @@ import { UMB_MEDIA_TYPE_TREE_ALIAS } from '../constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { UMB_MEDIA_TYPE_MENU_ITEM_ALIAS } from './constants.js'; export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.MediaTypes', + alias: UMB_MEDIA_TYPE_MENU_ITEM_ALIAS, name: 'Media Types Menu Item', weight: 800, meta: { @@ -16,9 +17,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Media Type Menu Structure Workspace Context', alias: 'Umb.Context.MediaType.Menu.Structure', api: () => import('./media-type-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_MEDIA_TYPE_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/menu/constants.ts index 11ceb55bf092..34b70788f476 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/menu/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/menu/constants.ts @@ -1 +1,2 @@ export const UMB_MEDIA_MENU_ALIAS = 'Umb.Menu.Media'; +export const UMB_MEDIA_MENU_ITEM_ALIAS = 'Umb.MenuItem.Media'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/menu/manifests.ts index a4bd3d6b8a4e..93bb4b6b5bdf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/menu/manifests.ts @@ -1,5 +1,5 @@ import { UMB_MEDIA_TREE_ALIAS } from '../constants.js'; -import { UMB_MEDIA_MENU_ALIAS } from './constants.js'; +import { UMB_MEDIA_MENU_ALIAS, UMB_MEDIA_MENU_ITEM_ALIAS } from './constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ @@ -11,7 +11,7 @@ export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.Media', + alias: UMB_MEDIA_MENU_ITEM_ALIAS, name: 'Media Menu Item', weight: 100, meta: { @@ -23,9 +23,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Media Menu Structure Workspace Context', alias: 'Umb.Context.Media.Menu.Structure', api: () => import('./media-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_MEDIA_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/constants.ts index 5f144502c4c1..1c409bd6f9d9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/constants.ts @@ -1,5 +1,6 @@ export * from './entity-actions/constants.js'; export * from './member-type-root/constants.js'; +export * from './menu/constants.js'; export * from './paths.js'; export * from './property-type/constants.js'; export * from './repository/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/menu/constants.ts new file mode 100644 index 000000000000..9f23bcf60c88 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/menu/constants.ts @@ -0,0 +1 @@ +export const UMB_MEMBER_TYPE_MENU_ITEM_ALIAS = 'Umb.MenuItem.MemberTypes'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/menu/manifests.ts index 9c563c1bd2ae..b00f960418b0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/menu/manifests.ts @@ -1,11 +1,11 @@ -import { UMB_MEMBER_TYPE_TREE_ALIAS } from '../constants.js'; +import { UMB_MEMBER_TYPE_MENU_ITEM_ALIAS, UMB_MEMBER_TYPE_TREE_ALIAS } from '../constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.MemberTypes', + alias: UMB_MEMBER_TYPE_MENU_ITEM_ALIAS, name: 'Member Type Menu Item', weight: 700, meta: { @@ -16,9 +16,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Member Type Menu Structure Workspace Context', alias: 'Umb.Context.MemberType.Menu.Structure', api: () => import('./member-type-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_MEMBER_TYPE_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/constants.ts index 6afae2870025..f582a679afdc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/constants.ts @@ -1,5 +1,6 @@ export * from './entity-actions/constants.js'; +export * from './entity.js'; +export * from './menu/constants.js'; export * from './repository/constants.js'; export * from './tree/constants.js'; export * from './workspace/constants.js'; -export * from './entity.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/menu/constants.ts new file mode 100644 index 000000000000..ad80a4635dea --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/menu/constants.ts @@ -0,0 +1 @@ +export const UMB_PARTIAL_VIEW_MENU_ITEM_ALIAS = 'Umb.MenuItem.PartialView'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/menu/manifests.ts index 414875d9d6d8..7189714ccddf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/menu/manifests.ts @@ -1,11 +1,12 @@ import { UMB_PARTIAL_VIEW_TREE_ALIAS } from '../tree/index.js'; +import { UMB_PARTIAL_VIEW_MENU_ITEM_ALIAS } from './constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.PartialView', + alias: UMB_PARTIAL_VIEW_MENU_ITEM_ALIAS, name: 'Partial View Menu Item', weight: 40, meta: { @@ -16,9 +17,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Partial View Menu Structure Workspace Context', alias: 'Umb.Context.PartialView.Menu.Structure', api: () => import('./partial-view-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_PARTIAL_VIEW_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/menu/manifests.ts index ae46cc2ec9b0..b7960bb90d80 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/menu/manifests.ts @@ -17,9 +17,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Script Menu Structure Workspace Context', alias: 'Umb.Context.Script.Menu.Structure', api: () => import('./script-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_SCRIPT_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/constants.ts index cb3c3c7348ec..1b0f9b850a05 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/constants.ts @@ -1,5 +1,6 @@ export * from './entity-actions/constants.js'; export * from './global-components/constants.js'; +export * from './menu/constants.js'; export * from './repository/constants.js'; export * from './tree/constants.js'; export * from './workspace/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/menu/constants.ts new file mode 100644 index 000000000000..ac1784982e9d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/menu/constants.ts @@ -0,0 +1 @@ +export const UMB_STYLESHEET_MENU_ITEM_ALIAS = 'Umb.MenuItem.Stylesheets'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/menu/manifests.ts index 6e69c7425824..b14e048c3b9b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/menu/manifests.ts @@ -1,11 +1,12 @@ import { UMB_STYLESHEET_TREE_ALIAS } from '../constants.js'; +import { UMB_STYLESHEET_MENU_ITEM_ALIAS } from './constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.Stylesheets', + alias: UMB_STYLESHEET_MENU_ITEM_ALIAS, name: 'Stylesheets Menu Item', weight: 20, meta: { @@ -16,9 +17,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Stylesheet Menu Structure Workspace Context', alias: 'Umb.Context.Stylesheet.Menu.Structure', api: () => import('./stylesheet-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_STYLESHEET_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/constants.ts index 9879ba5d2ce6..eac8bbbe487f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/constants.ts @@ -1,4 +1,5 @@ export * from './conditions/constants.js'; +export * from './menu/constants.js'; export * from './repository/constants.js'; export * from './search/constants.js'; export * from './workspace/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/menu/constants.ts new file mode 100644 index 000000000000..0b25774bd486 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/menu/constants.ts @@ -0,0 +1 @@ +export const UMB_TEMPLATE_MENU_ITEM_ALIAS = 'Umb.MenuItem.Templates'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/menu/manifests.ts index 3c63d3fe2bd8..02f0e9dd3d09 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/menu/manifests.ts @@ -1,11 +1,12 @@ import { UMB_TEMPLATE_TREE_ALIAS } from '../tree/index.js'; +import { UMB_TEMPLATE_MENU_ITEM_ALIAS } from './constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.Templates', + alias: UMB_TEMPLATE_MENU_ITEM_ALIAS, name: 'Templates Menu Item', weight: 40, meta: { @@ -17,9 +18,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Template Menu Structure Workspace Context', alias: 'Umb.Context.Template.Menu.Structure', api: () => import('./template-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_TEMPLATE_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ChildrenContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ChildrenContent.spec.ts index 40e475199dbc..109ea86a0abf 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ChildrenContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ChildrenContent.spec.ts @@ -41,10 +41,10 @@ test('can create child node', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) = expect(await umbracoApi.document.doesNameExist(childContentName)).toBeTruthy(); const childData = await umbracoApi.document.getChildren(contentId); expect(childData[0].variants[0].name).toBe(childContentName); - // verify that the child content displays in the tree after reloading children + // Verify that the child content displays in the tree after reloading children await umbracoUi.content.clickActionsMenuForContent(contentName); await umbracoUi.content.clickReloadChildrenActionMenuOption(); - await umbracoUi.content.clickCaretButtonForContentName(contentName); + await umbracoUi.content.openContentCaretButtonForName(contentName); await umbracoUi.content.doesContentTreeHaveName(childContentName); // Clean @@ -67,7 +67,7 @@ test('can create child node in child node', async ({umbracoApi, umbracoUi}) => { await umbracoUi.content.goToSection(ConstantHelper.sections.content); // Act - await umbracoUi.content.clickCaretButtonForContentName(contentName); + await umbracoUi.content.openContentCaretButtonForName(contentName); await umbracoUi.content.clickActionsMenuForContent(childContentName); await umbracoUi.content.clickCreateActionMenuOption(); await umbracoUi.content.chooseDocumentType(childOfChildDocumentTypeName); @@ -80,7 +80,7 @@ test('can create child node in child node', async ({umbracoApi, umbracoUi}) => { const childOfChildData = await umbracoApi.document.getChildren(childContentId); expect(childOfChildData[0].variants[0].name).toBe(childOfChildContentName); // verify that the child content displays in the tree - await umbracoUi.content.clickCaretButtonForContentName(childContentName); + await umbracoUi.content.openContentCaretButtonForName(childContentName); await umbracoUi.content.doesContentTreeHaveName(childOfChildContentName); // Clean @@ -98,7 +98,7 @@ test('cannot publish child if the parent is not published', async ({umbracoApi, await umbracoUi.content.goToSection(ConstantHelper.sections.content); // Act - await umbracoUi.content.clickCaretButtonForContentName(contentName); + await umbracoUi.content.openContentCaretButtonForName(contentName); await umbracoUi.content.clickActionsMenuForContent(childContentName); await umbracoUi.content.clickPublishActionMenuOption(); await umbracoUi.content.clickConfirmToPublishButton(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts index 86124bbebd2d..65437ebcacd7 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts @@ -84,7 +84,7 @@ test('can create multiple child nodes with different document types', async ({um // verify that the child content displays in the tree after reloading children await umbracoUi.content.clickActionsMenuForContent(contentName); await umbracoUi.content.clickReloadChildrenActionMenuOption(); - await umbracoUi.content.clickCaretButtonForContentName(contentName); + await umbracoUi.content.openContentCaretButtonForName(contentName); await umbracoUi.content.doesContentTreeHaveName(firstChildContentName); await umbracoUi.content.doesContentTreeHaveName(secondChildContentName); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithListViewContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithListViewContent.spec.ts index 8d0af1b43bb0..6c509ff7bb88 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithListViewContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithListViewContent.spec.ts @@ -164,7 +164,7 @@ test.skip('child is removed from list after child content is deleted', async ({u expect(await umbracoApi.document.getChildrenAmount(documentId)).toEqual(1); // Act - await umbracoUi.content.clickCaretButtonForContentName(contentName); + await umbracoUi.content.openContentCaretButtonForName(contentName); await umbracoUi.content.clickActionsMenuForContent(childContentName); await umbracoUi.content.clickTrashActionMenuOption(); await umbracoUi.content.clickConfirmTrashButton(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts index 7429e8b2e14b..0a274fdd1ba6 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts @@ -114,7 +114,7 @@ test('can create a folder in a folder in a folder', async ({umbracoApi, umbracoU // Act await umbracoUi.dataType.clickRootFolderCaretButton(); - await umbracoUi.dataType.clickCaretButtonForName(dataTypeFolderName); + await umbracoUi.dataType.openCaretButtonForName(dataTypeFolderName); await umbracoUi.dataType.clickActionsMenuForDataType(childFolderName); await umbracoUi.dataType.createDataTypeFolder(childOfChildFolderName); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts index 8861d71dcc8f..0e2560c55d49 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts @@ -126,7 +126,7 @@ test('can allow bulk move in the media section', async ({umbracoApi, umbracoUi}) await umbracoUi.media.selectMediaWithName(secondMediaFileName); await umbracoUi.waitForTimeout(200); await umbracoUi.media.clickBulkMoveToButton(); - await umbracoUi.media.clickCaretButtonForName('Media'); + await umbracoUi.media.openCaretButtonForName('Media'); await umbracoUi.media.clickModalTextByName(mediaFolderName); await umbracoUi.media.clickChooseModalButton(); await umbracoUi.waitForTimeout(500); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts index 954ce8827201..251f455072bd 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts @@ -137,10 +137,8 @@ test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.clickSaveButton(); // Assert - //await umbracoUi.media.waitForMediaItemToBeCreated(); // This is flaky, and Playwright seems to succeed even with its default timeout await umbracoUi.media.isMediaTreeItemVisible(parentFolderName); - await umbracoUi.media.isMediaTreeItemVisible(folderName, false); - await umbracoUi.media.clickMediaCaretButtonForName(parentFolderName); + await umbracoUi.media.openMediaCaretButtonForName(parentFolderName); await umbracoUi.media.isMediaTreeItemVisible(folderName, true); // Clean diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts index 7ce674fea0b5..4c571df2245f 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts @@ -25,7 +25,7 @@ test('can create a empty document type folder', {tag: '@smoke'}, async ({umbraco await umbracoUi.documentType.waitForDocumentTypeToBeCreated(); expect(await umbracoApi.documentType.doesNameExist(documentFolderName)).toBeTruthy(); // Checks if the folder is in the root - await umbracoUi.documentType.clickCaretButtonForName('Document Types'); + await umbracoUi.documentType.openCaretButtonForName('Document Types'); await umbracoUi.documentType.isDocumentTreeItemVisible(documentFolderName); }); @@ -105,7 +105,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb // Act await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings); await umbracoUi.documentType.clickRootFolderCaretButton(); - await umbracoUi.documentType.clickCaretButtonForName(grandParentFolderName); + await umbracoUi.documentType.openCaretButtonForName(grandParentFolderName); await umbracoUi.documentType.clickActionsMenuForName(parentFolderName); await umbracoUi.documentType.clickCreateActionMenuOption(); await umbracoUi.documentType.clickCreateDocumentFolderButton(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts index f249e10f2379..fa19f68f4d2c 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts @@ -82,7 +82,7 @@ test('can create a media type folder in a folder', async ({umbracoApi, umbracoUi // Assert await umbracoUi.mediaType.waitForMediaTypeToBeCreated(); - await umbracoUi.mediaType.clickCaretButtonForName(mediaTypeFolderName); + await umbracoUi.mediaType.openCaretButtonForName(mediaTypeFolderName); await umbracoUi.mediaType.isTreeItemVisible(childFolderName, true); const parentFolderChildren = await umbracoApi.mediaType.getChildren(parentFolderId); expect(parentFolderChildren[0].name).toBe(childFolderName); @@ -102,7 +102,7 @@ test('can create a media type folder in a folder in a folder', async ({umbracoAp // Act await umbracoUi.mediaType.clickRootFolderCaretButton(); - await umbracoUi.mediaType.clickCaretButtonForName(grandparentFolderName); + await umbracoUi.mediaType.openCaretButtonForName(grandparentFolderName); await umbracoUi.mediaType.clickActionsMenuForName(mediaTypeFolderName); await umbracoUi.mediaType.clickCreateActionMenuOption(); await umbracoUi.mediaType.clickFolderButton(); @@ -111,7 +111,7 @@ test('can create a media type folder in a folder in a folder', async ({umbracoAp // Assert await umbracoUi.mediaType.waitForMediaTypeToBeCreated(); - await umbracoUi.mediaType.clickCaretButtonForName(mediaTypeFolderName); + await umbracoUi.mediaType.openCaretButtonForName(mediaTypeFolderName); await umbracoUi.mediaType.isTreeItemVisible(childFolderName, true); const grandParentFolderChildren = await umbracoApi.mediaType.getChildren(grandParentFolderId); expect(grandParentFolderChildren[0].name).toBe(mediaTypeFolderName); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialViewFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialViewFolder.spec.ts index 3cd2806f9bd1..642cda5801e3 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialViewFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialViewFolder.spec.ts @@ -69,8 +69,7 @@ test('can create a partial view in a folder', async ({umbracoApi, umbracoUi}) => const childrenData = await umbracoApi.partialView.getChildren(folderPath); expect(childrenData[0].name).toEqual(partialViewFileName); // Verify the partial view is displayed in the folder under the Partial Views section - await umbracoUi.partialView.isPartialViewRootTreeItemVisible(partialViewFileName, false, false); - await umbracoUi.partialView.clickCaretButtonForName(folderName); + await umbracoUi.partialView.openCaretButtonForName(folderName); await umbracoUi.partialView.isPartialViewRootTreeItemVisible(partialViewFileName, true, false); // Clean @@ -86,7 +85,7 @@ test('can create a partial view in a folder in a folder', async ({umbracoApi, um //Act await umbracoUi.partialView.reloadPartialViewTree(); - await umbracoUi.partialView.clickCaretButtonForName(folderName); + await umbracoUi.partialView.openCaretButtonForName(folderName); await umbracoUi.partialView.clickActionsMenuForPartialView(childFolderName); await umbracoUi.partialView.clickCreateOptionsActionMenuOption(); await umbracoUi.partialView.clickNewEmptyPartialViewButton(); @@ -118,7 +117,7 @@ test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { expect(await umbracoApi.partialView.doesNameExist(childFolderName)).toBeTruthy(); const partialViewChildren = await umbracoApi.partialView.getChildren('/' + folderName); expect(partialViewChildren[0].path).toBe('/' + folderName + '/' + childFolderName); - await umbracoUi.partialView.clickCaretButtonForName(folderName); + await umbracoUi.partialView.openCaretButtonForName(folderName); await umbracoUi.partialView.isPartialViewRootTreeItemVisible(childFolderName, true, false); }); @@ -132,7 +131,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb // Act await umbracoUi.partialView.reloadPartialViewTree(); - await umbracoUi.partialView.clickCaretButtonForName(folderName); + await umbracoUi.partialView.openCaretButtonForName(folderName); await umbracoUi.partialView.clickActionsMenuForPartialView(childFolderName); await umbracoUi.partialView.createPartialViewFolder(childOfChildFolderName); @@ -141,7 +140,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb expect(await umbracoApi.partialView.doesNameExist(childOfChildFolderName)).toBeTruthy(); const partialViewChildren = await umbracoApi.partialView.getChildren('/' + folderName + '/' + childFolderName); expect(partialViewChildren[0].path).toBe('/' + folderName + '/' + childFolderName + '/' + childOfChildFolderName); - await umbracoUi.partialView.clickCaretButtonForName(childFolderName); + await umbracoUi.partialView.openCaretButtonForName(childFolderName); await umbracoUi.partialView.isPartialViewRootTreeItemVisible(childOfChildFolderName, true, false); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Script/ScriptFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Script/ScriptFolder.spec.ts index 9698acb51feb..88117c05b7a6 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Script/ScriptFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Script/ScriptFolder.spec.ts @@ -65,7 +65,7 @@ test('can create a script in a folder', async ({umbracoApi, umbracoUi}) => { expect(scriptChildren[0].path).toBe('/' + scriptFolderName + '/' + scriptName); const scriptData = await umbracoApi.script.get(scriptChildren[0].path); expect(scriptData.content).toBe(scriptContent); - await umbracoUi.stylesheet.clickCaretButtonForName(scriptFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(scriptFolderName); await umbracoUi.script.isScriptRootTreeItemVisible(scriptName, true, false); }); @@ -85,7 +85,7 @@ test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { expect(await umbracoApi.script.doesNameExist(childFolderName)).toBeTruthy(); const scriptChildren = await umbracoApi.script.getChildren('/' + scriptFolderName); expect(scriptChildren[0].path).toBe('/' + scriptFolderName + '/' + childFolderName); - await umbracoUi.stylesheet.clickCaretButtonForName(scriptFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(scriptFolderName); await umbracoUi.script.isScriptRootTreeItemVisible(childFolderName, true, false); }); @@ -99,7 +99,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb // Act await umbracoUi.script.reloadScriptTree(); - await umbracoUi.script.clickCaretButtonForName(scriptFolderName); + await umbracoUi.script.openCaretButtonForName(scriptFolderName); await umbracoUi.script.clickActionsMenuForScript(childFolderName); await umbracoUi.script.createScriptFolder(childOfChildFolderName); @@ -108,7 +108,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb expect(await umbracoApi.script.doesNameExist(childOfChildFolderName)).toBeTruthy(); const scriptChildren = await umbracoApi.script.getChildren('/' + scriptFolderName + '/' + childFolderName); expect(scriptChildren[0].path).toBe('/' + scriptFolderName + '/' + childFolderName + '/' + childOfChildFolderName); - await umbracoUi.stylesheet.clickCaretButtonForName(childFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(childFolderName); await umbracoUi.script.isScriptRootTreeItemVisible(childOfChildFolderName, true, false); }); @@ -121,7 +121,7 @@ test('can create a script in a folder in a folder', async ({umbracoApi, umbracoU // Act await umbracoUi.script.reloadScriptTree(); - await umbracoUi.script.clickCaretButtonForName(scriptFolderName); + await umbracoUi.script.openCaretButtonForName(scriptFolderName); await umbracoUi.script.clickActionsMenuForScript(childFolderName); await umbracoUi.script.clickCreateOptionsActionMenuOption(); await umbracoUi.script.clickNewJavascriptFileButton(); @@ -133,7 +133,7 @@ test('can create a script in a folder in a folder', async ({umbracoApi, umbracoU expect(await umbracoApi.script.doesNameExist(scriptName)).toBeTruthy(); const scriptChildren = await umbracoApi.script.getChildren('/' + scriptFolderName + '/' + childFolderName); expect(scriptChildren[0].path).toBe('/' + scriptFolderName + '/' + childFolderName + '/' + scriptName); - await umbracoUi.stylesheet.clickCaretButtonForName(childFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(childFolderName); await umbracoUi.script.isScriptRootTreeItemVisible(scriptName, true, false); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/StylesheetFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/StylesheetFolder.spec.ts index 3c270b6d37c2..b2c9f045286f 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/StylesheetFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/StylesheetFolder.spec.ts @@ -59,7 +59,7 @@ test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { expect(await umbracoApi.stylesheet.doesNameExist(childFolderName)).toBeTruthy(); const styleChildren = await umbracoApi.stylesheet.getChildren('/' + stylesheetFolderName); expect(styleChildren[0].path).toBe('/' + stylesheetFolderName + '/' + childFolderName); - await umbracoUi.stylesheet.clickCaretButtonForName(stylesheetFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(stylesheetFolderName); await umbracoUi.stylesheet.isStylesheetRootTreeItemVisible(childFolderName, true, false); }); @@ -73,7 +73,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb // Act await umbracoUi.stylesheet.reloadStylesheetTree(); - await umbracoUi.stylesheet.clickCaretButtonForName(stylesheetFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(stylesheetFolderName); await umbracoUi.stylesheet.clickActionsMenuForStylesheet(childFolderName); await umbracoUi.stylesheet.createStylesheetFolder(childOfChildFolderName); @@ -82,7 +82,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb expect(await umbracoApi.stylesheet.doesNameExist(childOfChildFolderName)).toBeTruthy(); const styleChildren = await umbracoApi.stylesheet.getChildren('/' + stylesheetFolderName + '/' + childFolderName); expect(styleChildren[0].path).toBe('/' + stylesheetFolderName + '/' + childFolderName + '/' + childOfChildFolderName); - await umbracoUi.stylesheet.clickCaretButtonForName(childFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(childFolderName); await umbracoUi.stylesheet.isStylesheetRootTreeItemVisible(childOfChildFolderName, true, false); }); @@ -108,7 +108,7 @@ test('can create a stylesheet in a folder', async ({umbracoApi, umbracoUi}) => { expect(stylesheetChildren[0].path).toBe('/' + stylesheetFolderName + '/' + stylesheetName); const stylesheetData = await umbracoApi.stylesheet.get(stylesheetChildren[0].path); expect(stylesheetData.content).toBe(stylesheetContent); - await umbracoUi.stylesheet.clickCaretButtonForName(stylesheetFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(stylesheetFolderName); await umbracoUi.stylesheet.isStylesheetRootTreeItemVisible(stylesheetName, true, false); }); @@ -122,7 +122,7 @@ test('can create a stylesheet in a folder in a folder', async ({umbracoApi, umbr //Act await umbracoUi.stylesheet.reloadStylesheetTree(); - await umbracoUi.stylesheet.clickCaretButtonForName(stylesheetFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(stylesheetFolderName); await umbracoUi.stylesheet.clickActionsMenuForStylesheet(childFolderName); await umbracoUi.stylesheet.clickCreateActionMenuOption(); await umbracoUi.stylesheet.clickNewStylesheetButton(); @@ -137,7 +137,7 @@ test('can create a stylesheet in a folder in a folder', async ({umbracoApi, umbr expect(stylesheetChildren[0].path).toBe('/' + stylesheetFolderName + '/' + childFolderName + '/' + stylesheetName); const stylesheetData = await umbracoApi.stylesheet.get(stylesheetChildren[0].path); expect(stylesheetData.content).toBe(stylesheetContent); - await umbracoUi.stylesheet.clickCaretButtonForName(childFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(childFolderName); await umbracoUi.stylesheet.isStylesheetRootTreeItemVisible(stylesheetName, true, false); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts index ea84ad5161f3..b2445d458865 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts @@ -52,7 +52,7 @@ test('can see root start node and children', async ({umbracoApi, umbracoUi}) => // Assert await umbracoUi.content.isContentInTreeVisible(rootDocumentName); - await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.openContentCaretButtonForName(rootDocumentName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentOneName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentTwoName); }); @@ -70,7 +70,7 @@ test('can see parent of start node but not access it', async ({umbracoApi, umbra await umbracoUi.content.isContentInTreeVisible(rootDocumentName); await umbracoUi.content.goToContentWithName(rootDocumentName); await umbracoUi.content.doesDocumentWorkspaceHaveText('Access denied'); - await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.openContentCaretButtonForName(rootDocumentName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentOneName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentTwoName, false); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts index c8720a7a410d..f16608353b50 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts @@ -45,7 +45,7 @@ test('can see root media start node and children', async ({umbracoApi, umbracoUi // Assert await umbracoUi.media.isMediaTreeItemVisible(rootFolderName); - await umbracoUi.media.clickCaretButtonForMediaName(rootFolderName); + await umbracoUi.media.openMediaCaretButtonForName(rootFolderName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderOneName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderTwoName); }); @@ -63,7 +63,7 @@ test('can see parent of start node but not access it', async ({umbracoApi, umbra await umbracoUi.media.isMediaTreeItemVisible(rootFolderName); await umbracoUi.media.goToMediaWithName(rootFolderName); await umbracoUi.media.doesMediaWorkspaceHaveText('Access denied'); - await umbracoUi.media.clickCaretButtonForMediaName(rootFolderName); + await umbracoUi.media.openMediaCaretButtonForName(rootFolderName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderOneName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderTwoName, false); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts index 1648b0e6ff86..82178e73fce0 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts @@ -53,7 +53,7 @@ test('can see root start node and children', async ({umbracoApi, umbracoUi}) => // Assert await umbracoUi.content.isContentInTreeVisible(rootDocumentName); - await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.openContentCaretButtonForName(rootDocumentName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentOneName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentTwoName); }); @@ -72,7 +72,7 @@ test('can see parent of start node but not access it', async ({umbracoApi, umbra await umbracoUi.content.isContentInTreeVisible(rootDocumentName); await umbracoUi.content.goToContentWithName(rootDocumentName); await umbracoUi.content.doesDocumentWorkspaceHaveText('Access denied'); - await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.openContentCaretButtonForName(rootDocumentName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentOneName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentTwoName, false); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DefaultPermissionsInContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DefaultPermissionsInContent.spec.ts index 2960ad27fa56..32837242d172 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DefaultPermissionsInContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DefaultPermissionsInContent.spec.ts @@ -435,7 +435,7 @@ test.fixme('can move content with move to permission enabled', {tag: '@release'} await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); // Act - await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.openContentCaretButtonForName(rootDocumentName); await umbracoUi.content.clickActionsMenuForContent(childDocumentOneName); await umbracoUi.content.clickMoveToActionMenuOption(); await umbracoUi.content.moveToContentWithName([], moveToDocumentName); @@ -444,7 +444,7 @@ test.fixme('can move content with move to permission enabled', {tag: '@release'} await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.moved); await umbracoUi.content.reloadContentTree(); await umbracoUi.content.isCaretButtonVisibleForContentName(moveToDocumentName, true); - await umbracoUi.content.clickCaretButtonForContentName(moveToDocumentName); + await umbracoUi.content.openContentCaretButtonForName(moveToDocumentName); await umbracoUi.content.isChildContentInTreeVisible(moveToDocumentName, childDocumentOneName, true); await umbracoUi.content.isCaretButtonVisibleForContentName(rootDocumentName, false); expect(await umbracoApi.document.getChildrenAmount(rootDocumentId)).toEqual(0); @@ -489,7 +489,7 @@ test.fixme('can sort children with sort children permission enabled', {tag: '@re // Assert // TODO: uncomment when it is not flaky - await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.openContentCaretButtonForName(rootDocumentName); await umbracoUi.content.doesIndexDocumentInTreeContainName(rootDocumentName, childDocumentTwoName, 0); await umbracoUi.content.doesIndexDocumentInTreeContainName(rootDocumentName, childDocumentOneName, 1); }); @@ -669,4 +669,4 @@ test('can create and update content with permission enabled', {tag: '@release'}, // Cleanup await umbracoApi.document.ensureNameNotExists(updatedDocumentName); -}); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/GranularPermissionsInContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/GranularPermissionsInContent.spec.ts index e2cb5eedef93..bd5b2f94d1db 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/GranularPermissionsInContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/GranularPermissionsInContent.spec.ts @@ -223,7 +223,7 @@ test('can move a specific content with move to permission enabled', async ({umbr await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); // Act - await umbracoUi.content.clickCaretButtonForContentName(firstDocumentName); + await umbracoUi.content.openContentCaretButtonForName(firstDocumentName); await umbracoUi.content.clickActionsMenuForContent(childDocumentName); await umbracoUi.content.clickMoveToActionMenuOption(); await umbracoUi.content.moveToContentWithName([], moveToDocumentName); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts index 7c71bb01fcb5..be8b531d44b5 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts @@ -45,7 +45,7 @@ test('can see root media start node and children', {tag: '@release'}, async ({um // Assert await umbracoUi.media.isMediaTreeItemVisible(rootFolderName); - await umbracoUi.media.clickCaretButtonForMediaName(rootFolderName); + await umbracoUi.media.openMediaCaretButtonForName(rootFolderName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderOneName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderTwoName); }); @@ -64,7 +64,7 @@ test('can see parent of start node but not access it', async ({umbracoApi, umbra await umbracoUi.media.isMediaTreeItemVisible(rootFolderName); await umbracoUi.media.goToMediaWithName(rootFolderName); await umbracoUi.media.doesMediaWorkspaceHaveText('Access denied'); - await umbracoUi.media.clickCaretButtonForMediaName(rootFolderName); + await umbracoUi.media.openMediaCaretButtonForName(rootFolderName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderOneName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderTwoName, false); });