Skip to content

feat(edit-content): enable image editor in Image and File fields (#36363)#36406

Open
oidacra wants to merge 7 commits into
mainfrom
issue-36363-image-editor-image-file-fields
Open

feat(edit-content): enable image editor in Image and File fields (#36363)#36406
oidacra wants to merge 7 commits into
mainfrom
issue-36363-image-editor-image-file-fields

Conversation

@oidacra

@oidacra oidacra commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

Enables the image editor in Image and File fields, with a versioned, published save to the referenced file-asset (#36363). A single component renders Binary/Image/File, so the save path is selected per input type via a strategy; detection and the edited-image result handling are hardened along the way.

Draft while it gets review + broader test coverage (integration/Postman).

What this changes

Availability (Image/File)

  • The image editor shows for Image fields, and for File fields only when the referenced asset is an image; hidden when it is not; empty fields never show it.
  • Detection is centralized in isImageFile() (in the image-editor lib) using the authoritative isImage flag then the image/* content type. Extension sniffing is intentionally avoided — the mime type is reliably present on the metadata the gate runs against.
  • Restricted to the new Angular Edit Content — Image/File never expose the editor in the legacy Dojo host (gated on the Angular launcher). Binary is unchanged and keeps its legacy fallback.

Save (provider per input type)

  • The "apply edited image" step is selected by ImageEditSaveStrategy:
    • Binary: applies the edit inline (existing behavior).
    • Image/File: checks in and publishes a new version of the referenced dotAsset via the default PUBLISH workflow action, in the asset's own language, then refreshes the preview. The field value (the identifier) is unchanged, so the reference is preserved and other content sharing the asset sees the update. The edited binary is passed as the staged temp file id in the asset field; the check-in resolves it server-side.
  • Adds DotWorkflowActionsFireService.publishContentletByIdentifier with optional language support (?language=).

Edited-image robustness

  • The Save servlet can return the edited temp file as metadata: null, image: false, mimeType: "unknown", which broke the thumbnail, hid the edit pencil, and crashed the file-info dialog header. Fixed by enriching the edited temp file as an image (enrichEditedImage) and null-guarding the dialog header.

Acceptance criteria

  • Image editor available in Image fields
  • Image editor available in File fields only when the file is an image
  • Image editor hidden in File fields when the file is not an image
  • Binary field behavior unchanged
  • Saving an edit creates and publishes a new version of the referenced file-asset (verified: fire/PUBLISH → 200)
  • After saving, the field reflects the updated image without changing the field value
  • Editing from Image and File both update the same referenced asset (same asset field / identifier)

Notes for reviewers

Pending / follow-up

  • No-op save guard (skip the check-in when nothing changed)
  • Specific "no edit permission" message (currently a generic server-error message)
  • "Saving" indicator during the fire
  • Integration / Postman coverage for the versioned save path

Test plan

  • Unit: edit-content (save-strategy resolver/strategies incl. dotAsset publish + refresh + error + guard; availability across field types; legacy-host restriction; preview null-metadata), image-editor (isImageFile, enrichEditedImage, with-save), data-access (fire service). All passing; lint clean.
  • Manual E2E in the new Edit Content: pencil shows on Image and image-File fields (hidden on non-image File); editing beach.png on an Image field flips + saves; PUT /api/v1/workflow/actions/default/fire/PUBLISH?language=1 → 200; the field refreshes to the new version (size changed), the reference is preserved, and the field stays re-editable; no console errors.

oidacra added 2 commits July 2, 2026 15:08
Show the image editor for Image fields, and for File fields only when the
referenced asset is an image. Image/File resolve a separate dotAsset by
identifier, so availability is driven by a shared isImageFile() predicate over
the referenced asset metadata (isImage flag, then image/* mime, then extension).
Restricted to the new Angular Edit Content: Image/File never expose the editor
in the legacy Dojo host. Binary behavior is unchanged.

Introduce an ImageEditSaveStrategy resolved by input type so the save path is
selected per field type: Binary applies the edit inline (current behavior);
Image/File get a scaffolded dotAsset strategy for the upcoming versioned
check-in (safe no-op that never corrupts the identifier reference).

Refs #36363
The Save servlet can return the edited temp file as metadata: null, image:
false, mimeType: "unknown". Downstream that stopped treating it as an image:
blank thumbnail, hidden edit pencil, and a crash in the file-info dialog header
(metadata.title on null). Enrich the edited temp file as an image (enrichEditedImage
util) so every consumer and host stays consistent, and null-guard the dialog
header binding.

Refs #36363
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

🤖 dotBot Review (Bedrock)

Reviewed 15 file(s); 8 candidate(s) → 7 confirmed, 0 uncertain (unverified, kept for review).

Confirmed findings

  • 🔴 Critical core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field/dot-file-field.component.ts:435 — Uncaught errors in image save due to missing error handling
    The removal of the #applyEditedImage method without adding error handling to the store.applyEditedImage call leaves potential errors uncaught. The current code subscribes to the store method without an error handler, which could lead to silent failures when saving edited images, causing user data loss and unawareness of failures.
  • 🟠 High core-web/libs/image-editor/src/lib/utils/is-image-file.util.ts:18 — Incorrect handling of isImage flag in isImageFile utility
    The code checks if metadata.isImage is truthy, but when the server sets isImage: false, it falls back to content type check. This can incorrectly treat non-images as images if their content type is image/*. The correct approach is to return false when isImage is explicitly false and only fallback to content type check when isImage is undefined.
  • 🟡 Medium core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field/dot-file-field.component.spec.ts:202 — Missing argument verification in applyEditedImage test
    The test at line 202 verifies applyEditedImage was called but doesn't check the arguments. The spy expectation uses .toHaveBeenCalled() without parameters, leaving the actual argument (EDITED_TEMP_FILE) unverified. This could allow incorrect parameters to pass undetected.
  • 🟡 Medium core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field/dot-file-field.component.ts:435 — Missing error handling in store.applyEditedImage subscription
    The subscription to store.applyEditedImage lacks an error handler, leaving potential failures uncaught. This could result in unhandled exceptions and no user feedback when image save operations fail. The code shows .subscribe() with only a success callback, omitting error handling for the observable stream.
  • 🟡 Medium core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.ts:370 — On publish error, stale image shown with error message
    In file-field.store.ts line 370, after a failed publish, the code sets fileStatus to 'preview' which displays the previous image while showing an error. This creates a mismatch between the error message and the displayed image, which remains unchanged from before the failed attempt. The status should remain in an error state or refresh appropriately to avoid showing stale data.
  • 🟡 Medium core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.ts:365 — Incorrect default fieldVariable for legacy FileAsset
    The code defaults fieldVariable to 'asset' when titleImage is missing, but legacy FileAssets may use 'fileAsset'. This could lead to saving edited images to the wrong field. The PR's notes indicate legacy FileAssets use 'fileAsset', so the default should align with that when titleImage isn't present.
  • 🟡 Medium core-web/libs/image-editor/src/lib/utils/enrich-edited-image.util.ts:30 — Incorrect default file size when tempFile.length is missing
    The code uses tempFile.length || 0 for fileSize and length, but if the server returns a valid image without length, these values default to 0. The DotCMSTempFile interface (from dotcms-models) shows length is optional (length?: number), so this scenario is possible. This leads to incorrect metadata showing 0 file size for valid images.

us.deepseek.r1-v1:0 · Run: #28619833600 · tokens: in: 80762 · out: 23523 · total: 104285 · calls: 28 · est. ~$0.236

Move the image-detection predicate out of libs/utils and into the image-editor
lib (alongside enrichEditedImage), exported from its public API. Groups the
image-ness logic with its owner and leaves libs/utils untouched.

Drop the file-extension fallback: rely on the authoritative isImage flag and
the image/* content type, both reliably present on the metadata the gate runs
against (dotAsset assetMetaData and enriched Binary temp files). File names are
not a trusted signal.

Refs #36363
Implement the dotAsset save strategy: on saving an image edit from an Image/File
field, check in and publish a new version of the referenced dotAsset via the
default PUBLISH workflow action, in the asset's own language, then refresh the
preview. The field value (the identifier) is unchanged, so the reference is
preserved and other content sharing the asset sees the update.

The edited binary is passed as the staged temp file id in the asset field; the
check-in resolves it server-side. Add DotWorkflowActionsFireService.publishContentletByIdentifier
with optional language support (sent as ?language=).

Verified end-to-end: PUBLISH fire returns 200, the field reflects the new
version, and the reference is preserved.

Refs #36363
@claude

claude Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Claude finished @oidacra's task in 3m 2s —— View job


I'll analyze this and get back to you.

- Version the correct binary field: use the referenced contentlet's titleImage
  (asset for dotAsset, fileAsset for a legacy FileAsset) instead of hardcoding
  'asset', so saves work for File fields backed by a FileAsset.
- Keep the Binary edit-image gate strict (isImage flag only) so its behavior is
  unchanged; the mime fallback in isImageFile applies only to Image/File.
- Surface a message instead of silently discarding the edit when the save
  strategy is reached without a resolved reference.
- Tighten the with-save regression assertion to the exact enriched payload.

Refs #36363
@oidacra oidacra marked this pull request as ready for review July 2, 2026 20:04
Comment thread core-web/libs/image-editor/src/lib/utils/is-image-file.util.ts
The Strategy pattern (interface + 2 impls + resolver + DI wiring) was
over-engineered for two stable branches. Replace it with a publishEditedAsset
rxMethod on FileFieldStore, next to applyTempFile/getAssetData where the asset
logic already lives, and branch inline in the component (Binary -> applyTempFile,
Image/File -> publishEditedAsset).

The rxMethod's switchMap also serializes concurrent saves (a re-triggered edit
cancels the in-flight publish+refresh), closing the concurrency gap the review
flagged. Behavior is unchanged: same PUBLISH-by-identifier in the asset's
language, same titleImage-based field selection (asset vs fileAsset), same
refresh without mutating the field value.

Removes the save-strategy folder and its specs; adds store coverage.

Refs #36363
// Image/File resolve a separate dotAsset/FileAsset and are only supported
// in the new Angular Edit Content, where the image-editor launcher is
// provided — never in the legacy Dojo host.
return isImageFile(this.#currentMetadata()) && !!this.#imageEditorLauncher;

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.

🔴 [Critical] Undefined metadata passed to isImageFile causes runtime error

In dot-file-field.component.ts line 230, isImageFile is called with this.value.metadata without checking if metadata exists. When this.value exists but lacks metadata (e.g. empty file field), this results in passing undefined to isImageFile, leading to runtime errors when accessing properties on metadata.

return true;
}

return !!metadata.contentType?.toLowerCase().startsWith('image/');

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.

🔴 [Critical] Potential runtime error when metadata.contentType is undefined

The code calls startsWith('image/') on metadata.contentType?.toLowerCase(), which could be undefined if contentType is missing. This would throw TypeError: Cannot read properties of undefined (reading 'startsWith'). Optional chaining should extend to the method call: metadata.contentType?.toLowerCase()?.startsWith('image/') ?? false.

The component no longer subscribes to the launcher stream or routes by input
type. onEditImage just hands the launcher's close stream to a store method,
store.applyEditedImage(result$), which owns the whole flow: filter the closed
editor (null), route by input type (Binary inline vs Image/File versioned), and
surface a server error on stream failure. Subscription is torn down with the
store.

Keeps the component thin (no #applyEditedImage, no manual subscribe/#destroyRef
for this path) and the persistence logic cohesive in the store.

Refs #36363
const focalPoint = parseFocalPoint(metadata?.focalPoint);

this.#applyEditedImage(
this.store.applyEditedImage(

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.

🔴 [Critical] Uncaught errors in image save due to missing error handling

The removal of the #applyEditedImage method without adding error handling to the store.applyEditedImage call leaves potential errors uncaught. The current code subscribes to the store method without an error handler, which could lead to silent failures when saving edited images, causing user data loss and unawareness of failures.

*/
export function isImageFile(metadata: Partial<DotFileMetadata> | null | undefined): boolean {
if (!metadata) {
return false;

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.

🟠 [High] Incorrect handling of isImage flag in isImageFile utility

The code checks if metadata.isImage is truthy, but when the server sets isImage: false, it falls back to content type check. This can incorrectly treat non-images as images if their content type is image/*. The correct approach is to return false when isImage is explicitly false and only fallback to content type check when isImage is undefined.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area : Frontend PR changes Angular/TypeScript frontend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Enable image editor in Image and File fields with versioned save to the referenced content

1 participant