@umbraco-cms/backoffice 17.0.0-rc1 → 17.0.0-rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (17) hide show
  1. package/dist-cms/external/openid/src/redirect_based_handler.d.ts +6 -0
  2. package/dist-cms/external/openid/src/redirect_based_handler.js +35 -7
  3. package/dist-cms/packages/block/block/workspace/views/edit/block-workspace-view-edit-content-no-router.element.js +16 -21
  4. package/dist-cms/packages/content/content/workspace/content-detail-workspace-base.js +5 -2
  5. package/dist-cms/packages/content/content-type/workspace/views/design/content-type-design-editor-properties.element.d.ts +1 -1
  6. package/dist-cms/packages/content/content-type/workspace/views/design/content-type-design-editor-properties.element.js +34 -7
  7. package/dist-cms/packages/content/property-type/workspace/views/settings/property-workspace-view-settings.element.js +2 -2
  8. package/dist-cms/packages/core/auth/auth-flow.js +3 -0
  9. package/dist-cms/packages/core/tree/tree-item/tree-item-children.manager.js +6 -1
  10. package/dist-cms/packages/core/tree/tree-item/tree-item-expansion.manager.js +0 -5
  11. package/dist-cms/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.js +2 -1
  12. package/dist-cms/packages/data-type/workspace/data-type-workspace.context.js +1 -1
  13. package/dist-cms/packages/data-type/workspace/views/details/data-type-details-workspace-property-editor-picker.element.js +1 -1
  14. package/dist-cms/packages/documents/documents/tree/tree-item/document-tree-item.context.js +1 -1
  15. package/dist-cms/tsconfig.build.tsbuildinfo +1 -1
  16. package/dist-cms/umbraco-package.json +1 -1
  17. package/package.json +3 -3
@@ -15,6 +15,12 @@ export declare class RedirectRequestHandler extends AuthorizationRequestHandler
15
15
  locationLike: LocationLike;
16
16
  constructor(storageBackend?: StorageBackend, utils?: BasicQueryStringUtils, locationLike?: LocationLike, crypto?: Crypto);
17
17
  performAuthorizationRequest(configuration: AuthorizationServiceConfiguration, request: AuthorizationRequest): Promise<string>;
18
+ /**
19
+ * Cleanup all stale authorization requests and configurations from storage.
20
+ * This scans localStorage for any keys matching the appauth patterns and removes them,
21
+ * including the authorization request handle key.
22
+ */
23
+ cleanupStaleAuthorizationData(): Promise<void>;
18
24
  /**
19
25
  * Attempts to introspect the contents of storage backend and completes the
20
26
  * request.
@@ -61,6 +61,35 @@ export class RedirectRequestHandler extends AuthorizationRequestHandler {
61
61
  return url;
62
62
  });
63
63
  }
64
+ /**
65
+ * Cleanup all stale authorization requests and configurations from storage.
66
+ * This scans localStorage for any keys matching the appauth patterns and removes them,
67
+ * including the authorization request handle key.
68
+ */
69
+ cleanupStaleAuthorizationData() {
70
+ // Check if we're in a browser environment with localStorage
71
+ if (typeof window === 'undefined' || !window.localStorage) {
72
+ return Promise.resolve();
73
+ }
74
+ const keysToRemove = [];
75
+ // Scan localStorage for all appauth-related keys
76
+ for (let i = 0; i < window.localStorage.length; i++) {
77
+ const key = window.localStorage.key(i);
78
+ if (key &&
79
+ (key.includes('_appauth_authorization_request') ||
80
+ key.includes('_appauth_authorization_service_configuration') ||
81
+ key === AUTHORIZATION_REQUEST_HANDLE_KEY)) {
82
+ keysToRemove.push(key);
83
+ }
84
+ }
85
+ // Remove all found stale keys
86
+ const removePromises = keysToRemove.map((key) => this.storageBackend.removeItem(key));
87
+ return Promise.all(removePromises).then(() => {
88
+ if (keysToRemove.length > 0) {
89
+ log(`Cleaned up ${keysToRemove.length} stale authorization data entries`);
90
+ }
91
+ });
92
+ }
64
93
  /**
65
94
  * Attempts to introspect the contents of storage backend and completes the
66
95
  * request.
@@ -103,12 +132,8 @@ export class RedirectRequestHandler extends AuthorizationRequestHandler {
103
132
  else {
104
133
  authorizationResponse = new AuthorizationResponse({ code: code, state: state });
105
134
  }
106
- // cleanup state
107
- return Promise.all([
108
- this.storageBackend.removeItem(AUTHORIZATION_REQUEST_HANDLE_KEY),
109
- this.storageBackend.removeItem(authorizationRequestKey(handle)),
110
- this.storageBackend.removeItem(authorizationServiceConfigurationKey(handle)),
111
- ]).then(() => {
135
+ // cleanup all authorization data including current and stale entries
136
+ return this.cleanupStaleAuthorizationData().then(() => {
112
137
  log('Delivering authorization response');
113
138
  return {
114
139
  request: request,
@@ -119,7 +144,10 @@ export class RedirectRequestHandler extends AuthorizationRequestHandler {
119
144
  }
120
145
  else {
121
146
  log('Mismatched request (state and request_uri) dont match.');
122
- return Promise.resolve(null);
147
+ // cleanup all authorization data even on mismatch to prevent stale PKCE data
148
+ return this.cleanupStaleAuthorizationData().then(() => {
149
+ return null;
150
+ });
123
151
  }
124
152
  }));
125
153
  }
@@ -16,13 +16,11 @@ import { UmbLitElement } from '../../../../../core/lit-element/index.js';
16
16
  * A specific view for editing content in a block workspace placed inline within a block view/element.
17
17
  */
18
18
  let UmbBlockWorkspaceViewEditContentNoRouterElement = class UmbBlockWorkspaceViewEditContentNoRouterElement extends UmbLitElement {
19
- //@state()
20
- //private _activeTabName?: string | null | undefined;
21
19
  #blockWorkspace;
22
20
  #tabsStructureHelper;
23
21
  constructor() {
24
22
  super();
25
- //private _hasRootProperties = false;
23
+ // private _hasRootProperties = false;
26
24
  this._hasRootGroups = false;
27
25
  this.#tabsStructureHelper = new UmbContentTypeContainerStructureHelper(this);
28
26
  this.#tabsStructureHelper.setIsRoot(true);
@@ -49,20 +47,18 @@ let UmbBlockWorkspaceViewEditContentNoRouterElement = class UmbBlockWorkspaceVie
49
47
  #checkDefaultTabName() {
50
48
  if (!this._tabs || !this.#blockWorkspace)
51
49
  return;
52
- // Find the default tab to grab:
50
+ // Find the default tab to grab
53
51
  if (this._activeTabKey === undefined) {
54
52
  if (this._hasRootGroups) {
55
- //this._activeTabName = null;
56
53
  this._activeTabKey = null;
57
54
  }
58
55
  else if (this._tabs.length > 0) {
59
- //this._activeTabName = this._tabs[0].name;
60
- this._activeTabKey = this._tabs[0].key;
56
+ const tab = this._tabs[0];
57
+ this._activeTabKey = tab.ownerId ?? tab.ids[0];
61
58
  }
62
59
  }
63
60
  }
64
- #setTabName(tabName, tabKey) {
65
- //this._activeTabName = tabName;
61
+ #setTabKey(tabKey) {
66
62
  this._activeTabKey = tabKey;
67
63
  }
68
64
  render() {
@@ -70,22 +66,21 @@ let UmbBlockWorkspaceViewEditContentNoRouterElement = class UmbBlockWorkspaceVie
70
66
  return;
71
67
  return html `
72
68
  ${this._tabs.length > 1 || (this._tabs.length === 1 && this._hasRootGroups)
73
- ? html ` <uui-tab-group slot="header">
69
+ ? html `<uui-tab-group slot="header">
74
70
  ${this._hasRootGroups && this._tabs.length > 0
75
- ? html `
76
- <uui-tab
77
- label="Content"
78
- .active=${null === this._activeTabKey}
79
- @click=${() => this.#setTabName(null, null)}
80
- >Content</uui-tab
81
- >
82
- `
71
+ ? html `<uui-tab
72
+ label=${this.localize.term('general_generic')}
73
+ .active=${this._activeTabKey === null}
74
+ @click=${() => this.#setTabKey(null)}
75
+ >Content</uui-tab
76
+ >`
83
77
  : nothing}
84
78
  ${repeat(this._tabs, (tab) => tab.name, (tab) => {
79
+ const tabKey = tab.ownerId ?? tab.ids[0];
85
80
  return html `<uui-tab
86
- label=${tab.name ?? 'Unnamed'}
87
- .active=${tab.key === this._activeTabKey}
88
- @click=${() => this.#setTabName(tab.name, tab.key)}
81
+ label=${this.localize.string(tab.name ?? '#general_unnamed')}
82
+ .active=${this._activeTabKey === tabKey}
83
+ @click=${() => this.#setTabKey(tabKey)}
89
84
  >${tab.name}</uui-tab
90
85
  >`;
91
86
  })}
@@ -265,6 +265,8 @@ export class UmbContentDetailWorkspaceContextBase extends UmbEntityDetailWorkspa
265
265
  const segments = this.structure.variesBySegment ? [] : undefined;
266
266
  const repo = new UmbDataTypeDetailRepository(this);
267
267
  const propertyTypes = await this.structure.getContentTypeProperties();
268
+ const contentTypeVariesByCulture = this.structure.getVariesByCulture();
269
+ const contentTypeVariesBySegment = this.structure.getVariesByCulture();
268
270
  const valueDefinitions = await Promise.all(propertyTypes.map(async (property) => {
269
271
  // TODO: Implement caching for data-type requests. [NL]
270
272
  const dataType = (await repo.requestByUnique(property.dataType.unique)).data;
@@ -281,8 +283,9 @@ export class UmbContentDetailWorkspaceContextBase extends UmbEntityDetailWorkspa
281
283
  propertyEditorSchemaAlias: dataType.editorAlias,
282
284
  config: dataType.values,
283
285
  typeArgs: {
284
- variesByCulture: property.variesByCulture,
285
- variesBySegment: property.variesBySegment,
286
+ // Only vary if the content type varies:
287
+ variesByCulture: contentTypeVariesByCulture ? property.variesByCulture : false,
288
+ variesBySegment: contentTypeVariesBySegment ? property.variesBySegment : false,
286
289
  },
287
290
  };
288
291
  }));
@@ -14,7 +14,7 @@ export declare class UmbContentTypeDesignEditorPropertiesElement extends UmbLitE
14
14
  private _editPropertyTypePath?;
15
15
  private _sortModeActive?;
16
16
  constructor();
17
- createPropertyTypeWorkspaceRoutes(): void;
17
+ disconnectedCallback(): void;
18
18
  render(): "" | import("lit-html").TemplateResult<1>;
19
19
  static styles: import("lit").CSSResult[];
20
20
  }
@@ -39,7 +39,7 @@ let UmbContentTypeDesignEditorPropertiesElement = class UmbContentTypeDesignEdit
39
39
  if (value === this._containerId)
40
40
  return;
41
41
  this._containerId = value;
42
- this.createPropertyTypeWorkspaceRoutes();
42
+ this.#createPropertyTypeWorkspaceRoutes();
43
43
  this.#propertyStructureHelper.setContainerId(value);
44
44
  this.#addPropertyModal?.setUniquePathValue('container-id', value === null ? 'root' : value);
45
45
  this.#editPropertyModal?.setUniquePathValue('container-id', value === null ? 'root' : value);
@@ -47,6 +47,9 @@ let UmbContentTypeDesignEditorPropertiesElement = class UmbContentTypeDesignEdit
47
47
  #addPropertyModal;
48
48
  #editPropertyModal;
49
49
  #propertyStructureHelper;
50
+ #initResolver;
51
+ #initReject;
52
+ #init;
50
53
  constructor() {
51
54
  super();
52
55
  this.#sorter = new UmbSorterController(this, {
@@ -123,6 +126,10 @@ let UmbContentTypeDesignEditorPropertiesElement = class UmbContentTypeDesignEdit
123
126
  },
124
127
  });
125
128
  this.#propertyStructureHelper = new UmbContentTypePropertyStructureHelper(this);
129
+ this.#init = new Promise((resolve, reject) => {
130
+ this.#initResolver = resolve;
131
+ this.#initReject = reject;
132
+ });
126
133
  this._properties = [];
127
134
  //this.#sorter.disable();
128
135
  this.consumeContext(UMB_CONTENT_TYPE_DESIGN_EDITOR_CONTEXT, (context) => {
@@ -135,20 +142,34 @@ let UmbContentTypeDesignEditorPropertiesElement = class UmbContentTypeDesignEdit
135
142
  this.#propertyStructureHelper.setStructureManager(workspaceContext.structure);
136
143
  }
137
144
  this._ownerContentTypeUnique = workspaceContext?.structure.getOwnerContentTypeUnique();
138
- this.createPropertyTypeWorkspaceRoutes();
139
- this.observe(workspaceContext?.variesByCulture, (variesByCulture) => {
145
+ this.#createPropertyTypeWorkspaceRoutes();
146
+ const varyByCulturePromise = this.observe(workspaceContext?.variesByCulture, (variesByCulture) => {
140
147
  this._ownerContentTypeVariesByCulture = variesByCulture;
141
- }, 'observeOwnerVariesByCulture');
142
- this.observe(workspaceContext?.variesBySegment, (variesBySegment) => {
148
+ }, 'observeOwnerVariesByCulture')?.asPromise();
149
+ const varyBySegmentPromise = this.observe(workspaceContext?.variesBySegment, (variesBySegment) => {
143
150
  this._ownerContentTypeVariesBySegment = variesBySegment;
144
- }, 'observeOwnerVariesBySegment');
151
+ }, 'observeOwnerVariesBySegment')?.asPromise();
152
+ if (varyByCulturePromise && varyBySegmentPromise && this.#initResolver) {
153
+ Promise.all([varyByCulturePromise, varyBySegmentPromise])
154
+ .then(() => {
155
+ this.#initResolver?.();
156
+ this.#initResolver = undefined;
157
+ this.#initReject = undefined;
158
+ })
159
+ .catch(() => { });
160
+ }
145
161
  });
146
162
  this.observe(this.#propertyStructureHelper.propertyStructure, (propertyStructure) => {
147
163
  this._properties = propertyStructure;
148
164
  this.#sorter.setModel(this._properties);
149
165
  });
150
166
  }
151
- createPropertyTypeWorkspaceRoutes() {
167
+ disconnectedCallback() {
168
+ super.disconnectedCallback();
169
+ this.#initReject?.(new Error('Component disconnected'));
170
+ }
171
+ async #createPropertyTypeWorkspaceRoutes() {
172
+ await this.#init;
152
173
  if (!this._ownerContentTypeUnique || this._containerId === undefined)
153
174
  return;
154
175
  // Note: Route for adding a new property
@@ -169,6 +190,12 @@ let UmbContentTypeDesignEditorPropertiesElement = class UmbContentTypeDesignEdit
169
190
  }
170
191
  preset.sortOrder = sortOrderInt;
171
192
  }
193
+ if (this._ownerContentTypeVariesByCulture) {
194
+ preset.variesByCulture = true;
195
+ }
196
+ if (this._ownerContentTypeVariesBySegment) {
197
+ preset.variesBySegment = true;
198
+ }
172
199
  return { data: { contentTypeUnique: this._ownerContentTypeUnique, preset: preset } };
173
200
  })
174
201
  .observeRouteBuilder((routeBuilder) => {
@@ -43,8 +43,8 @@ let UmbPropertyTypeWorkspaceViewSettingsElement = class UmbPropertyTypeWorkspace
43
43
  this.observe(instance?.isNew, (isNew) => (this._isNew = isNew), '_observeIsNew');
44
44
  });
45
45
  this.consumeContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT, (instance) => {
46
- this.observe(instance?.variesByCulture, (variesByCulture) => (this._contentTypeVariesByCulture = variesByCulture));
47
- this.observe(instance?.variesBySegment, (variesBySegment) => (this._contentTypeVariesBySegment = variesBySegment));
46
+ this.observe(instance?.variesByCulture, (variesByCulture) => (this._contentTypeVariesByCulture = variesByCulture), 'observeVariesByCulture');
47
+ this.observe(instance?.variesBySegment, (variesBySegment) => (this._contentTypeVariesBySegment = variesBySegment), 'observeVariesBySegment');
48
48
  this._entityType = instance?.getEntityType();
49
49
  }).passContextAliasMatches();
50
50
  }
@@ -198,6 +198,9 @@ export class UmbAuthFlow {
198
198
  await this.#storageBackend.removeItem(UMB_STORAGE_TOKEN_RESPONSE_NAME);
199
199
  // clear the internal state
200
200
  this.#tokenResponse.setValue(undefined);
201
+ // Also cleanup any OAuth/PKCE artifacts that may still be in localStorage
202
+ // This is a defense-in-depth measure during logout
203
+ await this.#authorizationHandler.cleanupStaleAuthorizationData();
201
204
  }
202
205
  /**
203
206
  * This method will sign the user out of the application.
@@ -183,6 +183,11 @@ export class UmbTreeItemChildrenManager extends UmbControllerBase {
183
183
  * @memberof UmbTreeItemChildrenManager
184
184
  */
185
185
  async loadChildren() {
186
+ const target = this.targetPagination.getBaseTarget();
187
+ /* If a new target is set we only want to reload children if the new target isn’t among the already loaded items. */
188
+ if (target && this.isChildLoaded(target)) {
189
+ return;
190
+ }
186
191
  return this.#loadChildren();
187
192
  }
188
193
  /**
@@ -230,7 +235,7 @@ export class UmbTreeItemChildrenManager extends UmbControllerBase {
230
235
  ? this.targetPagination.getNumberOfCurrentItemsBeforeBaseTarget()
231
236
  : this.#takeBeforeTarget !== undefined
232
237
  ? this.#takeBeforeTarget
233
- : 5,
238
+ : this.targetPagination.getTakeSize(),
234
239
  takeAfter: reload
235
240
  ? this.targetPagination.getNumberOfCurrentItemsAfterBaseTarget()
236
241
  : this.#takeAfterTarget !== undefined
@@ -63,11 +63,6 @@ export class UmbTreeItemTargetExpansionManager extends UmbControllerBase {
63
63
  if (currentBaseTarget && !target) {
64
64
  return;
65
65
  }
66
- /* If a new target is set we only want to reload children if the new target isn’t among the already loaded items. */
67
- const targetIsLoaded = this.#childrenManager.isChildLoaded(target);
68
- if (target && targetIsLoaded) {
69
- return;
70
- }
71
66
  // If we already have children and the target didn't change then we don't have to load new children
72
67
  const isNewTarget = target !== currentBaseTarget;
73
68
  if (isExpanded && this.#childrenManager.hasLoadedChildren() && !isNewTarget) {
@@ -108,7 +108,8 @@ let UmbDataTypePickerFlowModalElement = class UmbDataTypePickerFlowModalElement
108
108
  }
109
109
  this.removeUmbController(contentContextConsumer);
110
110
  this.removeUmbController(propContextConsumer);
111
- const propertyEditorName = this.#propertyEditorUIs.find((ui) => ui.alias === params.uiAlias)?.name;
111
+ const propertyEditorUiManifest = this.#propertyEditorUIs.find((ui) => ui.alias === params.uiAlias);
112
+ const propertyEditorName = this.localize.string(propertyEditorUiManifest?.meta?.label || propertyEditorUiManifest?.name || '#general_notFound');
112
113
  const dataTypeName = `${contentContext?.getName() ?? ''} - ${propContext.getName() ?? ''} - ${propertyEditorName}`;
113
114
  return {
114
115
  data: {
@@ -149,7 +149,7 @@ export class UmbDataTypeWorkspaceContext extends UmbEntityNamedDetailWorkspaceCo
149
149
  }
150
150
  this.observe(umbExtensionsRegistry.byTypeAndAlias('propertyEditorUi', propertyEditorUIAlias), (manifest) => {
151
151
  this.#propertyEditorUiIcon.setValue(manifest?.meta.icon || null);
152
- this.#propertyEditorUiName.setValue(manifest?.name || null);
152
+ this.#propertyEditorUiName.setValue(manifest?.meta?.label || manifest?.name || null);
153
153
  // Maps properties to have a weight, so they can be sorted, notice UI properties have a +1000 weight compared to schema properties.
154
154
  this.#propertyEditorUISettingsProperties = (manifest?.meta.settings?.properties ?? []).map((x, i) => ({
155
155
  ...x,
@@ -50,7 +50,7 @@ let UmbDataTypeDetailsWorkspacePropertyEditorPickerElement = class UmbDataTypeDe
50
50
  #renderPropertyEditorReference() {
51
51
  if (!this.propertyEditorUiAlias || !this.propertyEditorSchemaAlias)
52
52
  return nothing;
53
- let name = this.propertyEditorUiName;
53
+ let name = this.localize.string(this.propertyEditorUiName);
54
54
  let alias = this.propertyEditorUiAlias;
55
55
  let error = false;
56
56
  if (!this.propertyEditorUiName) {
@@ -14,7 +14,7 @@ export class UmbDocumentTreeItemContext extends UmbDefaultTreeItemContext {
14
14
  if (isMenu) {
15
15
  this.observe(this.hasCollection, (hasCollection) => {
16
16
  if (hasCollection) {
17
- this._treeItemChildrenManager.setTargetTakeSize(2, 2);
17
+ this._treeItemChildrenManager.setTargetTakeSize(1, 1);
18
18
  this.observe(this.hasActiveDescendant, (active) => {
19
19
  if (active === false) {
20
20
  super.hideChildren();