Skip to content

Issue 36702 asset picker - #36944

Open
nicobytes wants to merge 49 commits into
mainfrom
issue-36702-asset-picker
Open

Issue 36702 asset picker#36944
nicobytes wants to merge 49 commits into
mainfrom
issue-36702-asset-picker

Conversation

@nicobytes

@nicobytes nicobytes commented Aug 7, 2026

Copy link
Copy Markdown
Member

This pull request introduces a set of improvements and refactorings to the folder tree utilities and related data-access APIs, aimed at enhancing code reuse, maintainability, and consistency across the Content Drive and Host Folder Field features. The changes include moving folder tree logic into shared utilities, updating service providers, and aligning data models.

Core refactoring and utility extraction:

  • Introduced new shared utilities folder-tree.utils.ts and folder-tree-load.utils.ts in @dotcms/data-access, centralizing the logic for building, loading, and paginating folder trees. This includes new functions such as generateAllParentPaths, createTreeNode, and buildTreeFolderNodes, as well as utilities for paginated loading and "Load more" node handling. [1] [2]

API and provider updates:

  • Updated DotContentDriveService to use Angular's providedIn: 'root' for global availability, and removed it from route-level providers. This ensures the service can be used from dialogs and other contexts without explicit injection in every route. [1] [2]

Imports and dependency cleanup:

  • Updated imports in various portlet files to use the new shared utilities from @dotcms/data-access instead of local utility definitions, and cleaned up duplicate or outdated imports for components such as DotFolderListViewComponent. [1] [2] [3] [4]

Data model alignment:

  • Extended the TreeNodeContentData model to include additional metadata fields (inode, defaultBaseType, fromTable) for improved folder selection and upload behavior in the Content Drive and AssetPicker.

Constants and type re-exports:

  • Refactored and re-exported shared folder tree page size constants from the new utilities, ensuring consistent pagination limits across features, and deprecated local type definitions in favor of those from @dotcms/data-access. [1] [2] [3] [4]

These changes improve code maintainability, reduce duplication, and ensure that folder tree behaviors remain consistent across all consuming features.

This PR fixes: #36702

AssetPicker series — all seven sub-issues are resolved in this PR:

Fixes #36831 — AssetPicker 1/7: Migrate DotFolderListView to @dotcms/ui
Fixes #36832 — AssetPicker 2/7: Lift folder-tree helpers and harden drive data-access
Fixes #36833 — AssetPicker 3/7: Decouple Content Drive toolbar chrome for AssetPicker reuse
Fixes #36834 — AssetPicker 4/7: Headless browse store (no URL) for AssetPicker
Fixes #36835 — AssetPicker 5/7: Build DotAssetPicker shell in @dotcms/ui
Fixes #36836 — AssetPicker 6/7: File/Image entry configs, silent mime, CT limits, global last path
Fixes #36837 — AssetPicker 7/7: Wire AssetPicker into Edit Content File/Image fields

- Extracted DotFolderListView component and related models/constants/mocks into the new `@dotcms/ui` library.
- Updated imports in the Content Drive shell to reference the new location.
- Introduced `selectionMode` input to support both single and multiple selection.
- Re-exported necessary symbols from `@dotcms/ui` in the Content Drive UI for compatibility.
- Added tests for the new component and selection mode functionality.

This migration aims to share the folder/asset list component between Content Drive and future AssetPicker implementations, enhancing code reuse and maintainability.
Copilot AI lite review requested due to automatic review settings August 7, 2026 14:45
@github-actions github-actions Bot added the Area : Frontend PR changes Angular/TypeScript frontend code label Aug 7, 2026
@claude

claude Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Claude finished @nicobytes's task in 3m 30s —— View job


PR Review — AssetPicker / Content Drive refactor

Reviewed the current head (3a3bb826, post main re-merge) against origin/main. Rechecked the earlier reviewer findings and scanned the new commits (Block Editor / WYSIWYG wiring, DotDialog shell, folder-tree paging).

New Issues

  • 🔴 Critical: dotCMS/src/enterprise/java/com/dotcms/enterprise/publishing/sitesearch/SiteSearchAPIImpl.java:262 — A merge resolution on this branch dropped main's catch (DotDataException e) { throw e; } passthrough in defaultIndexName(). I confirmed origin/main still has it (added by the [QA-G17] Site Search portlet: alias overwritten after crawl, index selector shows IDs, cross-phase alias visibility gaps (Phase 2–3) #36983 sitesearch fix ad09f036) and this branch does not. Any DotDataException from the read provider now falls through to catch (Exception e) and is re-wrapped as new DotDataException(e.getMessage(), e), losing the original subclass and nesting the cause. The two sibling methods just below (isDefaultIndex, getAggregations) still keep the passthrough, so this is inconsistent and clearly accidental — merging this PR reverts the fix into main. Fix this →

  • 🔴 Critical: core-web/libs/ui/src/lib/components/dot-asset-picker/dot-asset-picker.component.ts:82providers: [DotAssetPickerStore, MessageService] is missing two services the picker depends on:

    • DotHttpErrorManagerService@Injectable() (no providedIn), injected in with-asset-browse.feature.ts and with-asset-folder-tree.feature.ts.
    • DotContentTypeService@Injectable() (no providedIn), injected by dot-content-type-filter via DotAssetPickerToolbarComponent.

    Both are only registered in apps/dotcms-ui/src/app/providers.ts. The legacy Dojo host apps/dotcms-binary-field-builder/src/app/app.module.ts (which bootstraps dotcms-binary-field) provides neither, and DotFileFieldComponent provides only DialogService. Since dot-file-field.component.ts:867 now opens DotAssetPickerComponent via DialogService.open, in that host DotAssetPickerStore construction throws NullInjectorError: No provider for DotHttpErrorManagerService, then DotContentTypeService, and the dialog renders blank. dot-file-field.component.legacy-availability.spec.ts only asserts construction (and mocks DialogService), so it never opens the dialog and does not catch this. Add both to the component providers array so the picker is self-sufficient in any host. Fix this →

  • 🟠 High: core-web/libs/ui/src/lib/components/dot-asset-picker/store/features/with-asset-folder-tree.feature.ts:216publish() re-points selectedNode by key and sets it to null when findNodeByKey misses. But searchFoldersInBrowsingSite deliberately leaves selectedNode on a node that is not in the searched tree (documented intent, path untouched). So the next mutateNode → publish (expand a node, or click "Load more" after a sidebar folder search) wipes the highlight → $targetFolder becomes undefined → a dropped file lands at the site root while the list still shows the previous folder, and the folder's pinned defaultBaseType is lost. Either keep the previous node when the key lookup misses, or clear path alongside it so the two cannot disagree.

  • 🟠 High: core-web/libs/portlets/dot-content-drive/portlet/src/lib/utils/tree-folder.utils.spec.ts — The sidebar-tree util suite was cut from ~38 tests to 5 (-671 lines); it no longer imports tree-folder.utils.ts at all (only ALL_FOLDER from @dotcms/ui). But tree-folder.utils.ts is byte-identical to main and still consumed by store/features/sidebar/withSidebar.ts and utils/functions.ts. generateAllParentPaths, createTreeNode, and buildTreeFolderNodes — which drive parent-path expansion and selected-node resolution in the Content Drive sidebar — are now fully uncovered. This reads as collateral damage from the ALL_FOLDER relocation rather than coverage moving with code (everything that actually moved kept/grew its tests). Restore the three suites, importing the utils from their current path.

  • 🟡 Medium: core-web/libs/edit-content/.../dot-file-field/dot-file-field.component.ts:867#openAssetPicker assigns this.#dialogRef without checking for an already-open/in-flight dialog, and the open is now gated behind the getCurrentSite() round-trip, widening the race. Double-clicking "Select Existing File" (or clicking while the lookup is pending) opens two stacked dialogs, each with a live onClose subscription; only the second is reachable through #dialogRef, so the first leaks and can still write a selection. Guard on #dialogRef/an in-flight flag, or exhaustMap the site lookup.

  • 🟡 Medium: core-web/libs/edit-content/.../dot-file-field/dot-file-field.component.ts:855 — When getCurrentSite() errors, error: is /* noop */, and when it resolves falsy the next branch does nothing. Either way the click is a completely silent no-op — no toast, no Logger, no message — whereas the comparable failure around line ~486 surfaces SERVER_ERROR. The comment documents this as intentional, but from the user's side the button just appears dead. At minimum surface a toast on the error path.

  • 🟡 Medium: core-web/libs/ui/src/lib/components/dot-folder-list-view/dot-folder-list-view.component.ts:313 — The controlled/uncontrolled check regressed from ?? to this.$selection() !== undefined, while the $selection input was simultaneously widened to accept null. A consumer binding [selection]="signal()" where "nothing selected" is null — the natural shape for the new selectionMode="single" — is treated as controlled and empty, so #internalSelection never applies and clicking a row never checks it. Latent today (both in-repo consumers pass arrays), but a trap for exactly the mode this PR adds.

  • 🔵 Low: core-web/libs/ui/src/lib/components/dot-workflow-push-publish/dot-workflow-push-publish.component.html:95 — i18n key changed 'Change''change'. Language.properties has change=change (lowercase value), so this timezone toggle now renders lowercase "change". Unrelated to AssetPicker/Content Drive — looks incidental, worth reverting.

Resolved

  • with-asset-folder-tree.feature.ts:157 — site no longer disappears from the sidebar on a folder-name search; searchFoldersInBrowsingSite re-adds the browsed site's root via mapSiteToTreeNode when the sites query filters it out.
  • with-asset-folder-tree.feature.ts:323loadFolders's tap now conditionally spreads selectedNode only when present, honoring the TreeLoadResult "absent = leave alone" contract.
  • with-asset-folder-tree.feature.ts:284 — success work now lives in tap before catchError (which returns EMPTY), so a failed load stays ERROR instead of being patched back to LOADED.
  • dot-asset-picker.component.ts:224confirm() now has an error: handler (toast, dialog stays open).
  • dot-asset-picker.store.ts:149 — free-text setSearch now resets path and calls selectRootNode(), so the tree highlight and upload target follow the site-wide scope.

Blocking: the two 🔴 (SiteSearchAPI passthrough revert, missing picker providers). The rest are non-blocking but the publish() upload-retarget (🟠) and the deleted Content Drive tree-util suite (🟠) are worth fixing before merge.
· branch issue-36702-asset-picker

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the existing Folder List View used by Content Drive into a reusable presentational component in @dotcms/ui, adding a single-selection mode intended for the upcoming AssetPicker (while preserving current Content Drive behavior via the default multiple selection mode).

Changes:

  • Moved Folder List View domain-agnostic types/constants into @dotcms/ui and re-exported them from the Content Drive UI package for compatibility.
  • Added selectionMode: 'single' | 'multiple' support to the table (checkboxes in multiple mode, radios in single mode) and normalized emitted selections to an array.
  • Updated Content Drive shell imports and updated/extended unit tests accordingly.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
File Description
core-web/libs/ui/src/lib/components/dot-folder-list-view/models.ts Introduces shared column typing and selectionMode model for the Folder List View.
core-web/libs/ui/src/lib/components/dot-folder-list-view/constants.ts Defines header column config and drag MIME type local to the component folder.
core-web/libs/ui/src/lib/components/dot-folder-list-view/mocks.ts Moves test mocks alongside the component.
core-web/libs/ui/src/lib/components/dot-folder-list-view/dot-folder-list-view.component.ts Adds selectionMode input, normalizes selection output, and updates internal imports to local UI sources.
core-web/libs/ui/src/lib/components/dot-folder-list-view/dot-folder-list-view.component.html Switches checkbox vs radio rendering based on selectionMode and updates selection binding.
core-web/libs/ui/src/lib/components/dot-folder-list-view/dot-folder-list-view.component.scss Fixes relative SCSS imports to match the libs layout.
core-web/libs/ui/src/lib/components/dot-folder-list-view/dot-folder-list-view.component.spec.ts Updates tests for the new selection model and adds coverage for single-selection behavior.
core-web/libs/ui/src/index.ts Exposes Folder List View component + related models/constants from @dotcms/ui.
core-web/libs/portlets/dot-content-drive/ui/src/lib/shared/models.ts Removes Folder List View column typing now owned by @dotcms/ui.
core-web/libs/portlets/dot-content-drive/ui/src/lib/shared/constants.ts Removes list-view constants now owned by @dotcms/ui.
core-web/libs/portlets/dot-content-drive/ui/src/index.ts Re-exports the Folder List View API surface from @dotcms/ui for Content Drive consumers.
core-web/libs/portlets/dot-content-drive/portlet/src/lib/dot-content-drive-shell/dot-content-drive-shell.component.ts Updates imports to use @dotcms/ui for the presentational list component/types.
core-web/libs/portlets/dot-content-drive/portlet/src/lib/dot-content-drive-shell/dot-content-drive-shell.component.spec.ts Aligns test imports with the updated component export location.

- Introduced new utility functions for managing folder hierarchies, including `getFolderHierarchyByPath` and `getFolderNodesByPath`, to improve folder navigation and loading in the content drive.
- Added `folder-tree-load.utils.ts` and `folder-tree.utils.ts` files to encapsulate the new logic.
- Implemented comprehensive unit tests for the new utilities to ensure functionality and reliability.
- Updated existing services to utilize the new utilities, enhancing code organization and maintainability.

These changes aim to streamline folder management and improve the user experience in the content drive interface.
Extracts the content-type, language, and search filter components (plus the
chip-filter/list-item primitives and upload button) out of the content-drive
portlet into @dotcms/ui so they can be shared with the AssetPicker. Store-
specific logic stays behind thin adapter components in the portlet.
- serve target lacked a dependsOn, so dotcms-webcomponents could be
  stale or missing when dotcms-ui starts serving
- webcomponents build target was missing outputs, preventing Nx from
  caching/detecting its build artifacts correctly
- Introduces DotAssetPickerStore in @dotcms/ui to power the upcoming
  AssetPicker dialog with a search request builder mirroring Content
  Drive's, but with no router/URL coupling so it can run inside a
  dialog over Edit Contentlet without corrupting host navigation.
- Relocates ALL_FOLDER/SYSTEM_HOST_ID out of the Content Drive UI
  library into shared dot-folder-tree constants so both Content
  Drive and the new picker consume a single source.
@nicobytes
nicobytes requested a lite review from Copilot August 7, 2026 17:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 76 out of 78 changed files in this pull request and generated no new comments.

Suppressed comments (1)

core-web/libs/ui/src/lib/components/dot-asset-picker/store/features/with-asset-folder-tree.feature.ts:97

  • loadFolders sets foldersStatus to ERROR in catchError, but then the subscribe block unconditionally patches it back to LOADED (because catchError returns an empty array). This masks folder-tree failures and makes the UI indistinguishable from a successful empty tree.

The AssetPicker (browse/pick a single asset) now composes the dropzone, upload-type selector, and folder sidebar/toolbar that Content Drive already had, so both features share one implementation instead of duplicating upload flow logic.

- Move `dot-content-drive-dropzone` and the upload-type-selector dialog out of the Content Drive portlet into `@dotcms/ui` as `DotUploadDropzoneComponent` and `DotUploadTypeSelectorComponent`, decoupled from `DotContentDriveStore` (folder/drag-state now passed via inputs/outputs)
- Add `DotAssetPickerComponent` with sidebar/toolbar subcomponents, wiring the shared dropzone, upload selector, and folder tree to a new `DotAssetPickerStore`
- Update Content Drive shell to consume the relocated shared components and derive drag/target-folder state locally
Adds a global last-used-path store, a config builder that translates
Edit Content field type (File/Image) into picker filters, and
server-side base-type narrowing for the content type filter so
restricted hosts don't page through mostly-discarded results.
- Add `--disable-dev-shm-usage` to Chromium launch options: containers default
  to 64MB /dev/shm, and exhausting it crashes the browser with a SIGSEGV and
  no test output
- Raise Node's old-space heap ceiling via NODE_OPTIONS on the Playwright
  runner process to prevent "Reached heap limit" aborts (exit 134)
- Keep CI at 2 workers rather than dropping to 1, since these two fixes
  target the actual crash causes directly
- Clarifies that the 1->2 worker bump was a measured fix from #36567/#36647
  cutting Playwright CI time from ~49m to a <30m target
- Redirects future concurrency-related changes to a dedicated issue against
  #36567 rather than reverting inline in unrelated PRs
The preview renders text assets as an editable code block and other
types as thumbnail + metadata, so a .txt seed never produced the file
name assertion this test relied on. Also assert thumbnail visibility.
@nicobytes

Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

# Conflicts:
#	core-web/libs/portlets/dot-content-drive/portlet/src/lib/components/dot-content-drive-sidebar/dot-content-drive-sidebar.component.spec.ts
#	core-web/libs/portlets/dot-content-drive/portlet/src/lib/components/dot-content-drive-sidebar/dot-content-drive-sidebar.component.ts
#	core-web/libs/portlets/dot-content-drive/portlet/src/lib/shared/constants.ts
#	core-web/libs/portlets/dot-content-drive/portlet/src/lib/utils/functions.spec.ts
#	core-web/libs/portlets/dot-content-drive/portlet/src/lib/utils/functions.ts
#	core-web/libs/portlets/dot-content-drive/portlet/src/lib/utils/tree-folder.utils.ts
#	core-web/libs/portlets/dot-content-drive/ui/src/lib/dot-tree-folder/dot-tree-folder.component.spec.ts
#	core-web/libs/portlets/dot-content-drive/ui/src/lib/dot-tree-folder/dot-tree-folder.component.ts
#	core-web/libs/portlets/dot-content-drive/ui/src/lib/shared/constants.ts
#	core-web/libs/portlets/dot-content-drive/ui/src/lib/shared/models.ts
#	core-web/libs/ui/src/index.ts
#	core-web/libs/ui/src/lib/components/dot-folder-list-view/dot-folder-list-view.component.html
#	core-web/libs/ui/src/lib/components/dot-folder-list-view/dot-folder-list-view.component.spec.ts
#	core-web/libs/ui/src/lib/components/dot-folder-list-view/dot-folder-list-view.component.ts

Co-authored-by: nicobytes <7611944+nicobytes@users.noreply.github.com>

Copilot AI commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Merge conflicts resolved in commit 5624a08. All 14 conflicted files were resolved — sidebar imports, portlet constants/functions/tree utils, UI shared models/constants, libs/ui/src/index.ts, and the DotFolderListView html/ts/spec files — keeping both the AssetPicker changes from this branch and the load-more/paging/readOnly additions from main.

- Extract a reusable dot-dialog component (header/content/footer) and migrate the
  image editor and asset picker to use it, replacing their duplicated header
  markup with a shared shell and per-feature fullscreen-toggle components
- Replace the asset picker's fixed sidebar width with a resizable p-splitter
  panel so users can adjust the folder tree vs. asset list balance
- Consolidate content-drive's folder-tree hierarchy/tree-building utilities out
  of the shared data-access lib into the portlet, and align the hierarchy page
  size with the backend's permissions-enabled per-page cap
- Fix the asset picker paginator to compute total records from the cursor-based
  Drive API's hasMoreContent flag instead of a nonexistent grand total
- Unsize the folder-list-view title column so it absorbs leftover width instead
  of overflowing the table when only a subset of columns is shown
@github-actions github-actions Bot added the Area : Backend PR changes Java/Maven backend code label Aug 14, 2026
Introduces a reusable header/content/footer layout shell (DotDialogComponent
and its slot components) so dialogs that render their own chrome can share
consistent sizing, padding, and divider styling instead of each rolling
its own layout.
The `draggable` attribute was bound as a DOM property, which browsers
sometimes ignore mid-render, letting a drag start on read-only rows.
Bind it via `[attr.draggable]` and short-circuit `onDragStart` when
`$readOnly()` is true so no drag event is ever processed for
read-only lists.
- Introduce `buildAssetPickerDialogConfig` in `@dotcms/ui` so every caller
  (file field, image field, block editor) shares the same PrimeNG dialog
  contract instead of hand-rolling it, and add `video`/`audio` picker modes.
- Repoint the block editor's image/video/audio slash items at the shared
  `DotAssetPickerComponent` (via `EditorModalService`) instead of the legacy
  `DotBrowserSelectorComponent`, resolving the current site through
  `DotSiteService` since the picker requires one.
- Make the content-type filter popover size itself to the taller of its two
  columns (clamped between 7 and 9 rows) instead of a fixed 9-row height, so
  restricted hosts like the asset picker don't show large dead space.

@zJaaal zJaaal left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: AssetPicker / Content Drive refactor

Reviewed at 7e1b2ae (136+ files). Focus was bad practices and regressions in Content Drive, plus the shared components the refactor extracted into @dotcms/ui. 11 findings inline, most severe first.

Blocking, in my read:

  1. DotAssetPickerComponent is missing two providers, so it cannot construct in the legacy Dojo binary-field host (and now the Story Block path too).
  2. A merge resolution dropped main's DotDataException passthrough in SiteSearchAPIImpl (unrelated to this PR, would land in main).
  3. The folder-tree publish() nulls selectedNode after a sidebar search, so dropped files silently land at the site root.

Content Drive specifically: the biggest issue is tree-folder.utils.spec.ts going from 38 tests to 5 while tree-folder.utils.ts is unchanged and still live. That is not coverage moving with the code: every component that actually moved kept or grew its tests.

Verified clean (flagging these because they look alarming in the diff but check out):

  • Removing #fillWidth is legitimate, not a lost fix: title is now unsized and absorbs the remainder, so the rescale is genuinely dead code.
  • The dropzone extraction is faithful, and resetContextMenu() survives as the new dragEnter output.
  • The upload-button label mapping is preserved exactly, .toUpperCase() included.
  • The content-type filter keeps the monotonic cache, the #cancelFetch$ race guard, the ensure param and the chip label fallback; the adapter keeps the same double patchFilters/removeFilter commit shape.
  • The search input improved: the old placeholder was a hardcoded untranslated "Search"; it is now the search key, same text, plus value re-sync, trim and a distinct-emission guard.
  • DotContentDriveService -> providedIn: 'root' is safe (stateless, HttpClient only).
  • ALL_FOLDER / SYSTEM_HOST_ID have a single definition re-exported, so no split identity.
  • Content Drive's main grid stays uncontrolled and passes no selectionMode/showActions/titleOpensItem, so it keeps the old defaults.
  • Spec coverage carried over where things moved: folder-list-view 2417 -> 2584 lines / 183 tests, content-type filter 41 -> 52, dropzone and upload-selector 1:1.
  • [draggable] -> [attr.draggable] is behavior-neutral; shell/sidebar spec changes are symbol renames with assertions intact.

Reviewed by Claude (Claude Code), posted from @zJaaal's account.

selector: 'dot-asset-picker',
templateUrl: './dot-asset-picker.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [DotAssetPickerStore, MessageService],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Missing providers — the picker cannot open in the legacy Dojo binary-field host.

providers covers DotAssetPickerStore and MessageService, but not:

  • DotHttpErrorManagerService (@Injectable(), no providedIn) — injected in store/features/with-asset-browse.feature.ts and with-asset-folder-tree.feature.ts
  • DotContentTypeService (@Injectable()) — injected by dot-content-type-filter.component.ts via DotAssetPickerToolbarComponent

Both are only provided in apps/dotcms-ui/src/app/providers.ts. apps/dotcms-binary-field-builder/src/app/app.module.ts — the host that bootstraps dotcms-binary-field — provides neither, and DotFileFieldComponent provides only DialogService.

Repro: legacy content editor, File/Image field with allowExistingFile → click "Select Existing File" → DialogService.open(DotAssetPickerComponent)DotAssetPickerStore construction throws NullInjectorError: No provider for DotHttpErrorManagerService (then DotContentTypeService), dialog renders blank.

This is a regression: the replaced DotBrowserSelectorComponent used only root-provided services (DotBrowsingService, DotUploadFileService, DotContentletService). dot-file-field.component.legacy-availability.spec.ts only asserts construction, never dialog open, so nothing catches it — and store/dot-asset-picker.store.spec.ts mock-provides DotHttpErrorManagerService, which confirms the dependency is real.

Adding both to this providers array is the smallest fix and makes the picker self-sufficient in any host.

public Optional<String> defaultIndexName() throws DotDataException {
try {
return router.readChecked(SiteSearchAPI::defaultIndexName);
} catch (DotDataException e) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Merge resolution dropped main's DotDataException passthrough (unrelated to this PR).

main has } catch (DotDataException e) { throw e; } here, added by ad09f0361c (the #36983 sitesearch fix). It was removed by a merge commit on this branch, and it is still missing after the 7e1b2ae re-merge of main — so merging this PR reverts that fix.

Consequence: any DotDataException from the read provider now falls through to catch (Exception e) and is re-wrapped as a new DotDataException(e.getMessage(), e), losing the original subclass and nesting the cause one level deeper.

isDefaultIndex() three lines below still has the passthrough, so the two are now inconsistent — which is the clearest sign this was accidental. Please restore it.

patchState(store, {
folders,
...(selectedKey
? { selectedNode: findNodeByKey(folders, selectedKey) ?? null }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 publish() nulls the tree highlight after a sidebar search, silently retargeting uploads to the site root.

publish() re-points selectedNode by key and sets it to null when findNodeByKey misses. But searchFoldersInBrowsingSite deliberately leaves selectedNode pointing at a node that is no longer in the published tree (documented intent), and path is left untouched. So the next mutateNodepublish wipes the highlight.

Repro: select /images/banners/ in the tree (list scopes to it) → type "logo" in "Search sites & folders" → expand any node or click "Load more". selectedNode becomes null, so $targetFolder is undefined and #uploadByBaseType falls back to hostFolder: browsingSite()?.identifier. A dropped file is created at the site root while the asset list still shows /images/banners/. The folder's pinned defaultBaseType is lost too, so the user is re-prompted for Asset/File.

Either keep the previous node when the lookup misses, or clear path alongside it so the two cannot disagree.

},
// Nothing to browse and nothing to say beyond that — the picker simply doesn't open.
error: () => {
/* noop */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 "Select Existing File" is now a completely silent no-op on failure.

Both paths swallow it: next does nothing when site is falsy, and error is an empty /* noop */. No toast, no Logger, no UI message — compare line ~486 in this same file, which surfaces SERVER_ERROR for a comparable failure.

Repro: DotSiteService.getCurrentSite() 500s (or the session has no current site) → the user clicks "Select Existing File" and nothing happens, with no way to tell the click registered. Previously the dialog opened unconditionally, so this is a new dead end.


const mode = isImage ? 'image' : 'file';

this.#dialogRef = this.#dialogService.open(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Async dialog open can stack two pickers.

#openAssetPicker assigns this.#dialogRef without checking whether a dialog is already open or in flight, and the open is now gated behind an HTTP round-trip that widens the race window.

Repro: double-click "Select Existing File" (or click again while getCurrentSite() is pending) → two getCurrentSite() requests → two DotAssetPickerComponent dialogs stacked, each with a live onClose subscription, while only the second is reachable through #dialogRef. The first leaks and can still write a selection. The previous synchronous open made this practically unreachable.

Guard on #dialogRef/an in-flight flag, or exhaustMap the site lookup.


it('should use a native PrimeNG folder icon', () => {
expect(ALL_FOLDER.icon).toBe('pi pi-folder');
import { ALL_FOLDER } from '@dotcms/ui';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Content Drive: the sidebar tree util suite was deleted (38 tests → 5), on production code that did not change.

This file no longer imports tree-folder.utils.ts at all — the surviving 5 tests only assert the shape of the ALL_FOLDER constant, which moved to @dotcms/ui.

Deleted: generateAllParentPaths (8 tests), createTreeNode (12), buildTreeFolderNodes (13, including the "rootNode as selectedNode" fallback paths — empty hierarchy, no path match, empty target path).

tree-folder.utils.ts is byte-identical to main and still consumed by store/features/sidebar/withSidebar.ts and utils/functions.ts, so it still drives sidebar tree construction, parent-path expansion and selected-node resolution. That logic is now completely uncovered.

This isn't coverage moving with the code — everything that genuinely moved kept or grew its tests (folder-list-view 2417 → 2584 lines / 183 tests, content-type filter 41 → 52, dropzone 1:1). This file looks like collateral damage from the ALL_FOLDER relocation. Worth restoring the three suites, importing the utils from their current path.

files: FileList;
targetFolder: DotFolderTreeNodeData;
}
export type DotContentDriveUploadFiles = DotUploadFiles;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Content Drive: move/upload targetFolder lost its type guarantee.

DotContentDriveUploadFiles was { files: FileList; targetFolder: DotFolderTreeNodeData }targetFolder required and folder-specific. It is now an alias of DotUploadFiles, whose targetFolder is optional and typed TreeNodeData. DotContentDriveMoveItems = Omit<..., 'files'> inherits that.

Meanwhile getMoveMetadata in dot-content-drive-shell.component.ts is unchanged and still reads event.targetFolder.path / .hostname with no guard. core-web/tsconfig.base.json sets "strict": false, so strictNullChecks cannot flag the now-optional property.

TreeNodeLoadMoreData is also assignable to TreeNodeData and carries no inode with optional path/hostname, so a move event built from a load-more node type-checks and yields a pathToMove of //undefined/ that then goes to bulkFire.

To be fair on blame: the null-targetFolder crash predates this PR (dot-tree-folder onDrop already emitted $activeDropNode() unguarded). What this PR removes is the type that documented and enforced the contract. onRequestUpload was updated to narrow on LOAD_MORE_NODE_TYPE; getMoveMetadata was not — worth doing the same there, or keeping a Content-Drive-specific alias that requires folder data.

* No width bookkeeping is needed when a column drops out: `title` is unsized (see
* `HEADER_COLUMNS`) and soaks up whatever the remaining sized columns leave over.
*/
protected readonly $fixedColumns = computed<DotFolderListViewFixedColumn[]>(() => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Content Drive: the move stripped the comments that recorded previously-fixed bugs.

The logic came across intact, but the "why" did not. Lost in this file:

  • the $columns note explaining that extras anchor to where type sits in the canonical order rather than its rendered index — keying off the rendered index appended the extras when Type was hidden, putting every extra cell one heading early
  • the item-title title attribute rationale (fixed-width column, truncate clips the rest)
  • the contentType truncate rationale
  • the locked-by-others color rationale

A later cleanup that "simplifies" $columns back to a rendered-index lookup reintroduces the misaligned-extras bug with nothing in the file warning it off. Cheap to carry these across.

@@ -1,6 +1,18 @@
export * from './lib/dot-folder-list-view/dot-folder-list-view.component';
// Presentational list lives in @dotcms/ui; re-export for Content Drive consumers.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Content Drive: two import paths now exist for the same symbols.

This barrel re-exports DotFolderListViewComponent, HEADER_COLUMNS, DOT_DRAG_ITEM and the column types straight from @dotcms/ui, and Content Drive uses both entry points for the same classes:

  • dot-content-drive-shell.component.ts imports DotFolderListViewComponent from @dotcms/ui
  • dot-content-drive-action-preview.component.ts imports it (plus DotFolderListViewColumnField) from @dotcms/portlets/content-drive/ui

Same runtime class either way, but a future deprecation or signature change has to be chased through two barrels and neither path reads as canonical. Worth picking one and either dropping the re-export or marking it explicitly transitional.

{{ $localTimezoneLabel() }}
<a href="#" data-testid="push-publish-timezone-toggle" (click)="toggleTimezone($event)">
{{ ($showTimezone() ? 'hide' : 'Change') | dm }}
{{ ($showTimezone() ? 'hide' : 'change') | dm }}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 i18n key switched to one whose value is lowercase (and out of scope for this PR).

'Change''change'. Language.properties has Change=Change (line 590) but change=change (line 589), so this timezone toggle now renders a lowercase "change" while its counterpart still renders "Hide" (hide=Hide). Inconsistent capitalization in the push-publish dialog.

Unrelated to Content Drive / AssetPicker — looks like an incidental edit worth reverting.

Reworks the File field's AssetPicker dialog helper into a shared component
usable across Story Block and WYSIWYG fields, adds E2E coverage for both,
and updates the WYSIWYG plugin service to open the shared picker (with
guarded async site lookup) instead of the legacy dialog.
- Point deleteContentlets at /api/v1/workflow (the old
  /api/v1/content/actions endpoint doesn't exist and was silently
  404ing, leaking seeded content every run)
- Drop searchFor calls in favor of relying on modDate:desc ordering
  now that seeded assets are guaranteed the newest rows
- Select the outer textbox in BlockEditorField since ProseMirror
  nests a second contenteditable textbox inside it
- Assert the TinyMCE dotAddImage button's tooltip, since TinyMCE
  promotes it to aria-label/title and it's the button's only
  accessible name
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area : Backend PR changes Java/Maven backend code Area : Frontend PR changes Angular/TypeScript frontend code

Projects

Status: No status

5 participants