Skip to content

feat(block-editor): image text wrap and alignment controls#35209

Merged
fmontes merged 6 commits intomainfrom
image-block-text-wrap
Apr 6, 2026
Merged

feat(block-editor): image text wrap and alignment controls#35209
fmontes merged 6 commits intomainfrom
image-block-text-wrap

Conversation

@fmontes
Copy link
Copy Markdown
Member

@fmontes fmontes commented Apr 4, 2026

video.mp4

Summary

Adds text wrap and alignment controls for the image block in the Block Editor, allowing content authors to float images left/right with text wrapping around them, or align images horizontally.

  • Text wrap: New wrap-left / wrap-right buttons in the bubble menu float the image at 50% width so surrounding text flows around it
  • Text align: Left / center / right alignment buttons control horizontal positioning when no wrap is active
  • Mutually exclusive: Selecting a wrap clears the alignment and vice versa — no conflicting styles
  • Active state: The active button is highlighted with a light blue background so authors can see the current selection
  • <figure> element: Replaced <div> wrapper with semantic <figure> across the editor, VTL template, and both React and Angular SDKs
  • SDK support: React (Image.tsx) and Angular (image.component.ts) SDK image block components updated to render textWrap and textAlign attributes

Test plan

  • Insert an image block in the Block Editor
  • Click wrap-left — image floats left at 50% width, text wraps around it, button shows active state
  • Click wrap-left again — wrap toggles off
  • Click wrap-right — image floats right, wrap-left becomes inactive
  • Click an align button while wrap is active — wrap clears, align applies
  • Click a wrap button while align is active — align clears, wrap applies
  • Verify rendered output in VTL (dotImage.vtl) applies correct float/align styles on the <figure>
  • Verify React and Angular SDK consumers render wrap/align styles correctly

🤖 Generated with Claude Code

fmontes and others added 2 commits April 3, 2026 20:23
…mn rules

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add textWrap (left/right float) and textAlign attributes to the dotImage node
- Bubble menu now has wrap-left/wrap-right buttons and align left/center/right buttons
  that are mutually exclusive — selecting one clears the other
- Active button state highlighted with blue-100 background via ::ng-deep
- Image node wrapped in <figure> (replaces <div>) in editor, VTL template, and both SDKs
- Float layout sets width: 50% so surrounding text wraps around the image
- Updated React and Angular SDK image block components to render textWrap/textAlign styles

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions bot added Area : Backend PR changes Java/Maven backend code Area : Frontend PR changes Angular/TypeScript frontend code Area : SDK PR changes SDK libraries labels Apr 4, 2026
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 4, 2026

Claude finished @fmontes's task in 4m 52s —— View job


Review

A few real issues worth addressing:


1. Bubble menu active state breaks on undo/redo

imageTextWrap and imageTextAlign signals are updated in the selection-update handler (dot-bubble-menu.component.ts:399–403) and on user click. But TipTap's onSelectionUpdate only fires when the selection changes, not when the document changes. If the user presses Ctrl+Z to undo a wrap change while the image stays selected, the editor reverts the attribute but the local signals stay stale — the wrong button remains highlighted. Subscribe to onUpdate (or use onTransaction) as well to keep signals in sync with document state.


2. Duplicate toggle logic between command and component

setImageTextWrap command reads editor.getAttributes() and toggles (image.node.ts:127–133). Then the component's setImageTextWrap also reads the local signal and independently toggles it (dot-bubble-menu.component.ts:289–290). Two separate toggle computations on two separate sources of truth. If they drift, you get a double-toggle bug. Compare with setImageTextAlign, which resolves the toggle in the component first and passes the resolved value to updateAttributes — that pattern is correct. Either: make the setImageTextWrap command a plain setter (not a toggler) and let the component own the toggle logic, or read from editor.getAttributes() in the component instead of the local signal.


3. addNodeView wrapper is still a <div>, not <figure>image.node.ts:186

The PR description says "<figure> replaced <div> across the editor", and renderHTML does emit <figure>. But addNodeView still calls document.createElement('div'). The live editor DOM shows <div> while serialized HTML (clipboard, editor.getHTML()) outputs <figure>. If the semantic goal matters, change line 186 to createElement('figure'). If it doesn't, the PR description should be corrected.


4. <img> unconstrained in Angular SDK — image.component.ts

Deferred per the review thread, but still a genuine rendering defect: when textWrap is active the <figure> is 50% wide but there is no max-width: 100%; height: auto on the <img>. Large images overflow the wrapper in host apps without global responsive image CSS. The React SDK fixed this (68a7a21). The Angular SDK should get the same fix before merge, not as a follow-up.


5. imageTextWrap signal typed as string | nulldot-bubble-menu.component.ts:102

Should be signal<'left' | 'right' | null>(null). The lax type accepts any string.


6. Redundant .has-float CSS width — dot-block-editor.component.css:480

.has-float { @apply w-1/2; } has no effect: FLOAT_STYLES already sets width: 50% as an inline style, which wins over the class. Dead CSS.


Minor / not blocking

  • parseHTML uses non-standard lowercase attribute names textwrap / textalign. data-text-wrap / data-text-align would be valid HTML and more discoverable for consumers who hand-craft content.
  • DotImageBlock.$srcURL is a computed that does no computation (return this.attrs?.['src']). A plain getter or direct template binding would be cleaner.
  • @Input() attrs! in image.component.ts should be attrs = input<BlockEditorNode['attrs']>() per the project's Angular conventions (CLAUDE.md).

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds image text-wrap and alignment controls to the Block Editor image block and propagates the new attributes through server (VTL) rendering and the React/Angular SDK renderers, while also moving the output wrapper toward a semantic <figure> element.

Changes:

  • Added textWrap / textAlign attributes and bubble-menu controls to toggle wrap-left/right and align-left/center/right (mutually exclusive).
  • Updated VTL + React SDK + Angular SDK image renderers to wrap images in <figure> and apply wrap/align styling.
  • Updated editor CSS to support floated wrappers and new active-state button styling; added block-editor library guidance doc.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
dotCMS/src/main/webapp/WEB-INF/velocity/static/storyblock/dotImage.vtl Switches wrapper to <figure> and applies inline wrap/align styles in server rendering.
core-web/libs/sdk/react/src/lib/next/components/DotCMSBlockEditorRenderer/components/blocks/Image.tsx Adds wrap/align support and renders image inside <figure> in the React SDK renderer.
core-web/libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer/blocks/image.component.ts Adds wrap/align support and renders image inside <figure> in the Angular SDK renderer.
core-web/libs/block-editor/src/lib/nodes/image-node/image.node.ts Adds textWrap/textAlign attributes + command and applies wrap/align in the editor node view/serializer.
core-web/libs/block-editor/src/lib/elements/dot-bubble-menu/dot-bubble-menu.component.ts Adds state + handlers to set/clear image wrap/align and keep UI active state in sync with selection.
core-web/libs/block-editor/src/lib/elements/dot-bubble-menu/dot-bubble-menu.component.html Rewires image alignment buttons to image-specific attrs and adds wrap-left/right controls.
core-web/libs/block-editor/src/lib/elements/dot-bubble-menu/dot-bubble-menu.component.css Adds styling for active bubble-menu buttons.
core-web/libs/block-editor/src/lib/components/dot-block-editor/dot-block-editor.component.css Updates image alignment selectors for wrapper-based classes and introduces float wrapper styles.
core-web/libs/block-editor/CLAUDE.md Adds documentation/guidance for block-editor structure and node authoring rules.

Comment thread core-web/libs/block-editor/src/lib/nodes/image-node/image.node.ts
Comment thread core-web/libs/block-editor/src/lib/nodes/image-node/image.node.ts
- Use <figure> in renderHTML() to match VTL and SDK output
- Lowercase attribute names (textwrap/textalign) to fix browser round-trip parsing
- Add width: 50% to FLOAT_STYLES so stored HTML matches SDK rendering
- Only add dot-node-{align} class when alignment is explicitly set
- Whitelist textAlign values in VTL to prevent CSS injection
- Toggle align off when re-clicking the active align button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@fmontes
Copy link
Copy Markdown
Member Author

fmontes commented Apr 4, 2026

Re: Copilot comments

  • Comment on image.node.ts:156 (FLOAT_STYLES missing width: 50%) — Fixed in commit 68a7a21.
  • Comment on image.node.ts:153 (renderHTML still emitting `div`) — Fixed in commit 68a7a21, now emits `figure`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@fmontes
Copy link
Copy Markdown
Member Author

fmontes commented Apr 4, 2026

Re: remaining Copilot comments

  • CSS selector (dot-block-editor.component.css) — Fixed. Now applies max-h-[300px] by default on img.dot-image and overrides it to max-h-none max-w-full under .has-float, avoiding the :not() selector issue.
  • img not constrained in React SDK — Fixed. Added max-width: 100%; height: auto inline on the <img> when textWrap is active so it doesn't overflow the 50% wrapper.
  • img not constrained in Angular SDK — Deferred for a follow-up.
  • Missing unit tests for toggle/mutual-exclusion — Deferred for a follow-up.

…View

HTMLAttributes keys are lowercased by renderHTML (textwrap/textalign),
so reading them returned undefined. Use node.attrs directly instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@fmontes fmontes linked an issue Apr 4, 2026 that may be closed by this pull request
4 tasks
@fmontes fmontes requested review from a team, AP2300, dario-daza and hmoreras and removed request for a team April 4, 2026 17:03
Comment thread core-web/libs/block-editor/CLAUDE.md
Comment thread core-web/libs/block-editor/src/lib/nodes/image-node/image.node.ts Outdated
Comment thread core-web/libs/block-editor/src/lib/nodes/image-node/image.node.ts Outdated
- Replace ngStyle with [style] binding in Angular SDK image component
- Use null instead of 'none' sentinel for imageTextWrap signal in bubble menu
- Simplify setImageTextWrap toggle logic in image.node.ts
- Format dotImage.vtl for readability
- Add tests for Angular SDK DotImageBlock component
- Add tests for React SDK DotCMSImage component

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 6, 2026

Rollback Safety Analysis

  • Read rollback-unsafe categories reference
  • Get full PR diff
  • Analyze diff against all unsafe categories
  • Apply appropriate label

Result: ✅ Safe To Rollback

This PR modifies only frontend/template assets — no database migrations, no Elasticsearch mapping changes, no data transformations, and no REST API contract changes. None of the rollback-unsafe categories apply:

Category Verdict
C-1 Structural Data Model Change Not applicable — no DB schema changes
C-2 Elasticsearch Mapping Change Not applicable — no ES mapping changes
C-3 Content JSON Model Version Bump Not applicable — no CURRENT_MODEL_VERSION change
C-4 DROP TABLE / DROP Column Not applicable — no DB migrations at all
H-1 One-Way Data Migration Not applicable
H-2 RENAME TABLE / COLUMN Not applicable
H-3 Primary Key Restructuring Not applicable
H-4 New ContentType Field Type Not applicable — textWrap/textAlign are TipTap node attributes, not dotCMS FieldType registrations
H-5 Binary Storage Provider Change Not applicable
H-6 DROP PROCEDURE / FUNCTION Not applicable
H-7 NOT NULL Column Without Default Not applicable
M-1 Non-Broadening Column Type Change Not applicable
M-2 Push Publishing Bundle Format Not applicable
M-3 REST / GraphQL API Contract Not applicable — new optional node attributes are additive; no REST response shape change
M-4 OSGi Plugin API Breakage Not applicable — no Java changes

Note on graceful degradation: Content saved with textWrap='left' after N is deployed would lose its float styling if rolled back to N-1 (the old VTL template doesn't handle textWrap), but nothing crashes — images simply render without the float. This is acceptable degradation, not a rollback-unsafe condition.

Label added: AI: Safe To Rollback

View job run

@fmontes fmontes added this pull request to the merge queue Apr 6, 2026
Merged via the queue into main with commit 5fa89df Apr 6, 2026
78 checks passed
@fmontes fmontes deleted the image-block-text-wrap branch April 6, 2026 19:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code Area : Frontend PR changes Angular/TypeScript frontend code Area : SDK PR changes SDK libraries

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

feat(block-editor): add text wrap support to image block

6 participants