Skip to content

feat(composer): improve image handling#13133

Closed
joeldj-nl wants to merge 15 commits into
nextcloud:mainfrom
joeldj-nl:externalimages
Closed

feat(composer): improve image handling#13133
joeldj-nl wants to merge 15 commits into
nextcloud:mainfrom
joeldj-nl:externalimages

Conversation

@joeldj-nl

@joeldj-nl joeldj-nl commented Jun 21, 2026

Copy link
Copy Markdown

Fixes #9960
Fixes #12097
Fixes #11261
Fixes #11131
Fixes #9350
Fixes #9290
Fixes #10617
Fixes #13109
Fixes #10977
Fixes #7641
Fixes #7142

Changes

This PR improves image handling in the rich text editor, adds several new ways to insert images, and fixes a number of related editor issues. I have invested quite some time into getting this working, and I am really looking forward to using these new features and fixes in the official app. I hope this can be reviewed and considered for inclusion soon.

  • Added support for inserting images from a URL
  • Added support for inserting images via the file manager
  • Added support for SVG images
  • Added support for copying and pasting images
  • Added an image dropdown menu with image-related actions
  • Added support for adding and editing image alt text
  • Added support for adding links to images
  • Added preconfigured image size options, similar to Gmail
  • Added support for changing image alignment
  • Fixed image alignment issues
  • Fixed image resizing by using pixel-based sizes instead of percentages
  • Fixed the popup for adding links (in the signature editor) to selected text not appearing
  • Fixed the signature text editor not respecting the configured editor mode and always using rich text mode
  • Fixed rendering external SVG images
  • Fixed images appearing side-by-side instead of stacked

Testing

Tested successfully in a fresh Nextcloud development installation using PHP 8.3.

Manual tests performed:

  • Tested the new image dropdown menu
  • Tested inserting an external image
  • Tested inserting an image via the file manager
  • Tested inserting SVG images using all available methods
  • Tested the default image alignment and changing the alignment
  • Tested the new pixel-based image sizing
  • Tested the preconfigured image size options
  • Tested how different inserted images look when sent and received in Thunderbird mobile, Thunderbird desktop, Gmail mobile, Gmail desktop, and Nextcloud Mail
  • Tested adding and editing alt text for images
  • Tested adding links to images
  • Tested using images in signatures
  • Tested sending emails with signatures that include images
  • Tested that the configured writing mode now also applies to the signature text editor

Discussion

I have tested the changes and confirmed that the new functionality works as expected. However, the implementation approach may need further review to determine whether some parts should be optimized before merging.

Notes

I used #11160 as inspiration for the different image insertion options and the dropdown menu used to display them.

Assisted-by: Claude:claude-opus-4-8

Schermafbeelding 2026-06-21 155316 Schermafbeelding 2026-06-21 155553 Schermafbeelding 2026-06-21 155622 Schermafbeelding 2026-06-21 155717 Schermafbeelding 2026-06-21 155916 Schermafbeelding 2026-06-21 160016

Signed-off-by: Joël de Jager <contact@joeldejager.nl>
Signed-off-by: Joël de Jager <contact@joeldejager.nl>
@joeldj-nl joeldj-nl changed the title xternalimages feat(composer): improve image handling Jun 21, 2026
joeldj-nl added 13 commits June 21, 2026 22:00
… image, SVG hardening, file picker)

Signed-off-by: Joël de Jager <contact@joeldejager.nl>
…endering

Make image alignment a property of the image (CKEditor image styles shown in
the balloon toolbar next to the link option) instead of the surrounding
paragraph, so changing it no longer reflows nearby text. Switch to block images
so paragraphs can be inserted before/after an image and the text-formatting
tools disable while an image is selected. Hide the "displayed text" field when
linking an image and balance the insert-image dropdown spacing.

On send, translate the editor's alignment classes into inline text-align styles
and mirror block-image resize widths (stored on the wrapping figure) onto the
width/height attributes, so other mail clients render alignment and size
correctly.

Signed-off-by: Joël de Jager <contact@joeldejager.nl>
…settings

Signed-off-by: Joël de Jager <contact@joeldejager.nl>
NcModal relocates its dialog to <body> on mount, but declaring
AccountSettings inside the frequently re-rendering navigation moved it
back into the scrollable, overflow-clipped sidebar. CKEditor's
Rect.getVisible() then clipped the editor content against the narrow
sidebar and treated it as invisible, so getOptimalPosition() returned
null and every balloon (image toolbar, link form) was parked off-screen
at -99999. Render a single store-driven AccountSettings at the stable
app root instead, matching how the composer is mounted, and drop the
now-unused per-account settings getters.

Signed-off-by: Joël de Jager <contact@joeldejager.nl>
normalizeImageDimensions only inspected an image's direct parent for
the resize width, so a linked image (<figure><a><img></a></figure>)
kept its natural width/height and rendered at full size in clients
that drop inline CSS. Walk up to the figure ancestor instead.

Signed-off-by: Joël de Jager <contact@joeldejager.nl>
Images pasted from a web page keep their external src, which the
editor CSP blocks, so they render as the alt-text placeholder. Fetch
them through the server-side image proxy and rewrite the src to a
data: URI so they display in the editor and are sent inline.

Signed-off-by: Joël de Jager <contact@joeldejager.nl>
The signature editor was always rich text, so a signature image was
mangled into a raw HTML object when the account's writing mode was plain
text. Several related problems are fixed together:

- Drive the signature editor's mode from the account's writing mode and
  re-encode the signature to the active format (HTML or plain text).
- Warn before switching from rich text to plain text, since that drops
  formatting, inline images and links — including those in the signature.
- Resolve the account-settings dialog's account reactively from the store
  instead of a one-time snapshot, so changes (e.g. the writing mode) are
  reflected live instead of only after reopening.
- Bind the writing-mode radios to the stored value and revert an
  unconfirmed native toggle, so the mode only changes once confirmed and
  cancelling no longer leaves the UI showing plain text.

Signed-off-by: Joël de Jager <contact@joeldejager.nl>
- allow HTML5 <figure>/<figcaption> in the mail HTML purifier so
  CKEditor image figures keep stacking instead of collapsing into
  inline, side-by-side images
- serve proxied external SVG images as sanitised image/svg+xml so
  browsers render them instead of leaving them blank

Signed-off-by: Joël de Jager <contact@joeldejager.nl>
Block-image alignment was encoded only as CKEditor CSS classes, which
recipients do not load. The unstyled (default) figure was left without
any inline alignment, so each client rendered it differently — Gmail and
Thunderbird centred it while Nextcloud Mail left-aligned it.

Inline auto-margins (matching CKEditor's positioning) plus text-align on
every image figure, including the default, so images align consistently
across all mail clients. Physical margins are used for Outlook
compatibility.

Signed-off-by: Joël de Jager <contact@joeldejager.nl>

@ChristophWurst ChristophWurst 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.

Thanks for the patches

Please check out https://github.com/nextcloud/.github/blob/master/AI_POLICY.md, and have a close look at the topic of focused PRs.

This PR is not humanly reviewable, because it touches many critical areas. You have to submit your changes individually, and the changes that do not have an existing, accepted ticket need discussion first.

Please also direct your agent to AGENTS.md. The agentic AI trailers are missing in your commits.

@ChristophWurst ChristophWurst marked this pull request as draft June 22, 2026 07:22
@joeldj-nl joeldj-nl closed this Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment