From 724a8a1dc2a4f4e22a80f4acc6adca9a5a05b255 Mon Sep 17 00:00:00 2001 From: Arcadio Quintero Date: Mon, 13 Apr 2026 13:48:42 -0400 Subject: [PATCH 1/3] fix(edit-content): forward contentlet languageId to file field browser selector The new Edit Content screen file field was opening the browser selector dialog without passing the contentlet's languageId, so the backend POST /api/v1/browser request defaulted to the system default language and excluded assets that only exist in the active language (e.g. French-only images were invisible when editing French content). Add an optional languageId to ContentByFolderParams and forward the current contentlet's languageId from DotFileFieldComponent.showSelectExistingFileDialog so it flows through DotBrowserSelectorComponent's $folderParams into the browser API call. The AngularFormBridge is unaffected; it already spreads params into the dialog data. Closes #34459 --- .../src/lib/dot-site/dot-site.service.ts | 1 + .../dotcms-models/src/lib/dot-site.model.ts | 1 + .../dot-file-field.component.ts | 3 +- ...-edit-content-file-field.component.spec.ts | 24 +++++++ .../dot-browser-selector.component.spec.ts | 70 +++++++++++++++++++ 5 files changed, 98 insertions(+), 1 deletion(-) diff --git a/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts b/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts index 642e8aaf752e..e0ad5e1d7a48 100644 --- a/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts +++ b/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts @@ -67,6 +67,7 @@ export interface ContentByFolderParams { showWorking?: boolean; extensions?: string[]; mimeTypes?: string[]; + languageId?: number; } export const BASE_SITE_URL = '/api/v1/site'; export const DEFAULT_PER_PAGE = 10; diff --git a/core-web/libs/dotcms-models/src/lib/dot-site.model.ts b/core-web/libs/dotcms-models/src/lib/dot-site.model.ts index 467ca53c5fc0..731a96c53ab5 100644 --- a/core-web/libs/dotcms-models/src/lib/dot-site.model.ts +++ b/core-web/libs/dotcms-models/src/lib/dot-site.model.ts @@ -32,4 +32,5 @@ export interface ContentByFolderParams { showWorking?: boolean; extensions?: string[]; mimeTypes?: string[]; + languageId?: number; } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field/dot-file-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field/dot-file-field.component.ts index 534d530fd94c..503f25ddcbc5 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field/dot-file-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field/dot-file-field.component.ts @@ -442,7 +442,8 @@ export class DotFileFieldComponent showFolders: false, showWorking: true, showArchived: false, - sortByDesc: true + sortByDesc: true, + languageId: this.$contentlet().languageId } }); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.spec.ts index 44f885018629..4b6c9aa3b21a 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.spec.ts @@ -271,6 +271,30 @@ xdescribe('DotEditContentFileFieldComponent', () => { }); }); + describe('showSelectExistingFileDialog', () => { + it('should forward the contentlet languageId to the browser selector dialog data', () => { + const dialogService = spectator.inject(DialogService); + const spyDialogOpen = jest.spyOn(dialogService, 'open'); + + spectator.setHostInput( + 'contentlet', + createFakeContentlet({ + [FILE_FIELD_MOCK.variable]: null, + languageId: 2 + }) + ); + spectator.detectChanges(); + + const fieldComponent = spectator.query(DotFileFieldComponent); + fieldComponent.showSelectExistingFileDialog(); + + expect(spyDialogOpen).toHaveBeenCalledTimes(1); + expect(spyDialogOpen.mock.calls[0][1].data).toEqual( + expect.objectContaining({ languageId: 2 }) + ); + }); + }); + describe('Disabled State Management', () => { it('should set disabled state correctly through setDisabledState method', () => { spectator.detectChanges(); diff --git a/core-web/libs/ui/src/lib/components/dot-browser-selector/dot-browser-selector.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-browser-selector/dot-browser-selector.component.spec.ts index eaad8fd7126b..e96439839196 100644 --- a/core-web/libs/ui/src/lib/components/dot-browser-selector/dot-browser-selector.component.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-browser-selector/dot-browser-selector.component.spec.ts @@ -186,3 +186,73 @@ describe('DotBrowserSelectorComponent', () => { }); }); }); + +describe('DotBrowserSelectorComponent — DynamicDialogConfig.data forwarding', () => { + let spectator: Spectator; + let mockStore: ReturnType; + + const createComponent = createComponentFactory({ + component: DotBrowserSelectorComponent, + schemas: [CUSTOM_ELEMENTS_SCHEMA], + providers: [ + mockProvider(DynamicDialogRef), + mockProvider(DotContentletService, { + getContentletByInodeWithContent: jest + .fn() + .mockReturnValue(of(createFakeContentlet())) + }), + { + provide: DynamicDialogConfig, + useValue: { + data: { + hostFolderId: SYSTEM_HOST_ID, + mimeTypes: ['image'], + languageId: 2 + } + } + } + ], + detectChanges: false + }); + + beforeEach(() => { + mockStore = createMockStore(); + + TestBed.overrideComponent(DotBrowserSelectorComponent, { + set: { + imports: [DotMessagePipe], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + providers: [{ provide: DotBrowserSelectorStore, useValue: mockStore }] + } + }); + + spectator = createComponent(); + spectator.detectChanges(); + }); + + it('should forward languageId from DynamicDialogConfig.data into $folderParams on init', () => { + expect(spectator.component.$folderParams().languageId).toBe(2); + }); + + it('should preserve languageId in $folderParams after a node selection', () => { + spectator.component.onNodeSelect(mockNodeSelectEvent('demo.dotcms.com')); + + expect(spectator.component.$folderParams()).toEqual( + expect.objectContaining({ + hostFolderId: 'demo.dotcms.com', + languageId: 2 + }) + ); + }); + + it('should pass languageId through to store.uploadFile via folderParams', () => { + const mockFile = new File(['content'], 'photo.png', { type: 'image/png' }); + + spectator.component.onFileUpload(mockFile); + + expect(mockStore.uploadFile).toHaveBeenCalledWith({ + file: mockFile, + folderParams: expect.objectContaining({ languageId: 2 }) + }); + }); +}); From 8a7b768ac1d8509bdb2710183e615718f199bfa9 Mon Sep 17 00:00:00 2001 From: Arcadio Quintero Date: Mon, 13 Apr 2026 13:51:21 -0400 Subject: [PATCH 2/3] fix(edit-content): guard contentlet null when reading languageId in file field dialog $contentlet is input.required but its value can be null while a contentlet is still being hydrated (e.g. new content with no saved contentlet yet), causing "Cannot read properties of null (reading 'languageId')" when the Select Existing File dialog is opened. Restore the optional chain so the dialog still opens in that state; languageId is forwarded as undefined which the backend treats as "any language", matching prior behavior. --- .../components/dot-file-field/dot-file-field.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field/dot-file-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field/dot-file-field.component.ts index 503f25ddcbc5..4e749312c7f5 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field/dot-file-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field/dot-file-field.component.ts @@ -443,7 +443,7 @@ export class DotFileFieldComponent showWorking: true, showArchived: false, sortByDesc: true, - languageId: this.$contentlet().languageId + languageId: this.$contentlet()?.languageId } }); From 5459116e35dcd21f4a07a3bae8e48af855019a25 Mon Sep 17 00:00:00 2001 From: Arcadio Quintero Date: Mon, 13 Apr 2026 14:12:35 -0400 Subject: [PATCH 3/3] fix(edit-content): resolve file field languageId from active locale store For new contentlets the $contentlet input has no languageId yet, so the "Select Existing File" dialog was falling back to the session language and hiding assets the user actually wanted to pick when editing in a non-default locale (e.g. fr-FR). Read the active locale from DotEditContentStore.currentLocale() first (injected as optional so the component can still render outside the edit-content layout / in unit tests) and fall back to the contentlet's languageId when the store isn't available. Verified in the UI: switching to fr-FR on a new contentlet and opening the Select Existing Image dialog now sends the fr-FR languageId in the /api/v1/browser POST and shows French-only assets. --- .../dot-file-field/dot-file-field.component.ts | 11 ++++++++++- .../dot-edit-content-file-field.component.spec.ts | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field/dot-file-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field/dot-file-field.component.ts index 4e749312c7f5..f1e991641f81 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field/dot-file-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field/dot-file-field.component.ts @@ -51,6 +51,7 @@ import { UploadedFile } from '../../../../models/dot-edit-content-file.model'; import { BaseControlValueAccessor } from '../../../shared/base-control-value-accesor'; +import { DotEditContentStore } from '../../../../store/edit-content.store'; @Component({ selector: 'dot-file-field', @@ -108,6 +109,13 @@ export class DotFileFieldComponent * This service is used to provide AI-related functionalities within the component. */ readonly #dotAiService = inject(DotAiService); + /** + * Reference to the parent edit-content store, used to resolve the current + * locale when opening the browser selector so it filters assets by the + * language the user is actively editing in (including new contentlets + * where `$contentlet` has no languageId yet). + */ + readonly #editContentStore = inject(DotEditContentStore, { optional: true }); /** * Reference to the dynamic dialog. It can be null if no dialog is currently open. * @@ -443,7 +451,8 @@ export class DotFileFieldComponent showWorking: true, showArchived: false, sortByDesc: true, - languageId: this.$contentlet()?.languageId + languageId: + this.#editContentStore?.currentLocale()?.id ?? this.$contentlet()?.languageId } }); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.spec.ts index 4b6c9aa3b21a..6107e38a353f 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.spec.ts @@ -272,7 +272,7 @@ xdescribe('DotEditContentFileFieldComponent', () => { }); describe('showSelectExistingFileDialog', () => { - it('should forward the contentlet languageId to the browser selector dialog data', () => { + it('should forward the contentlet languageId as a fallback to the browser selector dialog data', () => { const dialogService = spectator.inject(DialogService); const spyDialogOpen = jest.spyOn(dialogService, 'open');