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 6267ff7e4027..d35c6411003e 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 @@ -10,7 +10,8 @@ import { UmbAncestorsEntityContext, UmbParentEntityContext, type UmbEntityModel import { UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT, UMB_VARIANT_WORKSPACE_CONTEXT, - UMB_WORKSPACE_PATH_PATTERN, + UMB_WORKSPACE_EDIT_PATH_PATTERN, + UMB_WORKSPACE_EDIT_VARIANT_PATH_PATTERN, } from '@umbraco-cms/backoffice/workspace'; import { linkEntityExpansionEntries } from '@umbraco-cms/backoffice/utils'; import { UMB_MODAL_CONTEXT } from '@umbraco-cms/backoffice/modal'; @@ -101,29 +102,34 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um getItemHref(structureItem: UmbVariantStructureItemModel): string | undefined { const sectionName = this._sectionContext?.getPathname(); - if (!sectionName) { - return undefined; - } - UMB_WORKSPACE_PATH_PATTERN.generateAbsolute({ - sectionName, - entityType: structureItem.entityType, - }); - const path = `section/${this._sectionContext!.getPathname()}/workspace/${structureItem.entityType}/edit/${structureItem.unique}`; + if (!sectionName) return undefined; + + const unique = structureItem.unique; + if (!unique) return undefined; // find related variant id from structure item: - const itemVariantFit = structureItem.variants.find((variant) => { - return ( + const itemVariantFit = structureItem.variants.find( + (variant) => variant.culture === this.#workspaceActiveVariantId?.culture && - variant.segment === this.#workspaceActiveVariantId?.segment - ); - }); + variant.segment === this.#workspaceActiveVariantId?.segment, + ); + if (itemVariantFit) { const variantId = UmbVariantId.CreateFromPartial(itemVariantFit); - return `${path}/${variantId.toString()}`; + return UMB_WORKSPACE_EDIT_VARIANT_PATH_PATTERN.generateAbsolute({ + sectionName, + entityType: structureItem.entityType, + unique, + variantId: variantId.toString(), + }); } // If no related variantID, then lets the redirect go to the main-variant: - return path; + return UMB_WORKSPACE_EDIT_PATH_PATTERN.generateAbsolute({ + sectionName, + entityType: structureItem.entityType, + unique, + }); } async #requestStructure() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/path-pattern.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/path-pattern.class.ts index 3edab9c88dd5..cbe81c3d3b59 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/router/path-pattern.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/path-pattern.class.ts @@ -35,8 +35,10 @@ export class UmbPathPattern< constructor(localPattern: string, basePath?: UmbPathPattern | string) { this.#local = localPattern; - basePath = basePath?.toString() ?? ''; - this.#base = basePath.lastIndexOf('/') !== basePath.length - 1 ? basePath + '/' : basePath; + // Use toAbsolutePatternString() for UmbPathPattern to preserve the full path chain + const baseString = + basePath instanceof UmbPathPattern ? basePath.toAbsolutePatternString() : (basePath?.toString() ?? ''); + this.#base = baseString.length > 0 && !baseString.endsWith('/') ? baseString + '/' : baseString; } generateLocal(params: LocalParamsType) { @@ -55,6 +57,15 @@ export class UmbPathPattern< ); } + /** + * Get the full absolute pattern string including base path. + * Use this when chaining patterns to preserve the full path. + * @returns The complete pattern string (base + local) + */ + toAbsolutePatternString(): string { + return this.#base + this.#local; + } + toString() { return this.#local; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-context-base.ts index 9699f42feb13..d54187d11e5d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-context-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-context-base.ts @@ -13,6 +13,7 @@ import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import { UmbDeprecation, debounce } from '@umbraco-cms/backoffice/utils'; import { UmbParentEntityContext } from '@umbraco-cms/backoffice/entity'; import { UMB_SECTION_CONTEXT } from '@umbraco-cms/backoffice/section'; +import { UMB_WORKSPACE_EDIT_PATH_PATTERN } from '@umbraco-cms/backoffice/workspace'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbEntityModel, UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; @@ -390,8 +391,11 @@ export abstract class UmbTreeItemContextBase< // TODO: use router context constructPath(pathname: string, entityType: string, unique: string | null) { - // TODO: Encode uniques [NL] - return `section/${pathname}/workspace/${entityType}/edit/${unique}`; + return UMB_WORKSPACE_EDIT_PATH_PATTERN.generateAbsolute({ + sectionName: pathname, + entityType, + unique: unique ?? 'null', + }); } override destroy(): void { 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 b1cd9d327123..545e74084d41 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 @@ -1,4 +1,5 @@ import { UMB_WORKSPACE_CONTEXT } from '../../../workspace.context-token.js'; +import { UMB_WORKSPACE_EDIT_PATH_PATTERN } from '../../../paths.js'; import { css, customElement, html, ifDefined, map, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; @@ -77,14 +78,16 @@ export class UmbWorkspaceBreadcrumbElement extends UmbLitElement { } #getHref(structureItem: UmbStructureItemModel) { - if (structureItem.isFolder) return undefined; + if (structureItem.isFolder || !structureItem.unique) return undefined; - let href = `section/${this.#sectionContext?.getPathname()}`; - if (structureItem.unique) { - href += `/workspace/${structureItem.entityType}/edit/${structureItem.unique}`; - } + const sectionName = this.#sectionContext?.getPathname(); + if (!sectionName) return undefined; - return href; + return UMB_WORKSPACE_EDIT_PATH_PATTERN.generateAbsolute({ + sectionName, + entityType: structureItem.entityType, + unique: structureItem.unique, + }); } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/paths.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/paths.ts index 8ac71ae0688b..60c845d9fa7f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/paths.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/paths.ts @@ -6,6 +6,16 @@ export const UMB_WORKSPACE_PATH_PATTERN = new UmbPathPattern< typeof UMB_SECTION_PATH_PATTERN.ABSOLUTE_PARAMS >('workspace/:entityType', UMB_SECTION_PATH_PATTERN); +export const UMB_WORKSPACE_EDIT_PATH_PATTERN = new UmbPathPattern< + { unique: string }, + typeof UMB_WORKSPACE_PATH_PATTERN.ABSOLUTE_PARAMS +>('edit/:unique', UMB_WORKSPACE_PATH_PATTERN); + +export const UMB_WORKSPACE_EDIT_VARIANT_PATH_PATTERN = new UmbPathPattern< + { variantId: string }, + typeof UMB_WORKSPACE_EDIT_PATH_PATTERN.ABSOLUTE_PARAMS +>(':variantId', UMB_WORKSPACE_EDIT_PATH_PATTERN); + export const UMB_WORKSPACE_VIEW_PATH_PATTERN = new UmbPathPattern< { viewPathname: string }, typeof UMB_WORKSPACE_PATH_PATTERN.ABSOLUTE_PARAMS diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts index a2bf780988bd..b367fb3629c8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/grid/user-grid-collection-view.element.ts @@ -1,7 +1,7 @@ import { UMB_USER_COLLECTION_CONTEXT } from '../../user-collection.context-token.js'; import type { UmbUserCollectionContext } from '../../user-collection.context.js'; import type { UmbUserDetailModel } from '../../../types.js'; -import { UMB_USER_WORKSPACE_PATH } from '../../../paths.js'; +import { UMB_EDIT_USER_WORKSPACE_PATH_PATTERN } from '../../../paths.js'; import { css, customElement, html, nothing, repeat, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; @@ -64,7 +64,7 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement { return html` 0} ?selected=${this.#collectionContext?.selection.isSelected(user.unique)}