Skip to content

In-app Notifications: render HTML content type in the read dialog (currently shows raw markup) #5402

Description

@EzSoftware2015

Add-on: Notifications (io.jmix.notifications) · Version: 2.8.2 · Module: jmix-notifications-flowui

Summary

InAppNotification already models an HTML content type — ContentType.HTML (text/html; charset=UTF-8) — and the public sending API accepts it (Notification.builder()…withContentType(ContentType.HTML)). However, when a recipient opens a notification, io.jmix.notificationsflowui.NotificationDialogs#createReadNotificationDialog always puts the body into a read-only TextArea. As a result, HTML notifications are displayed as raw markup (literal <p>, <a>, … tags) instead of formatted, rich content with clickable links.

The source already flags this as planned work — there is a // todo also support RichTextArea comment at exactly this location (and a related // use RichTextEditor if ContentType is HTML in CreateNotificationDialog).

Current behavior

createReadNotificationDialog renders the body unconditionally as plain text:

// todo also support RichTextArea
TextArea textArea = new TextArea();
textArea.setValue(reloadedNotification.getBody());
textArea.setReadOnly(true);
textArea.setSizeFull();
...
layout.add(textArea, actionsLayout);

Expected behavior

When InAppNotification.getContentType() equals ContentType.HTML.getValue(), the read dialog should render the body as formatted HTML (links clickable, basic formatting visible). Plain-text notifications (the default) render exactly as today.

Use case

We send in-app notifications with formatted bodies (links, emphasis, lists) generated from templates. Because body rendering is neither content-type-aware nor extensible, every project must subclass NotificationDialogs as @Primary and post-process the built dialog to swap the TextArea for a com.vaadin.flow.component.Html. That workaround relies on @Internal API and on the dialog's internal component structure, so it can break on upgrade. Native support removes the per-project hack.

Proposed change

Make the body component content-type-aware, factored into a protected method so apps can customize further. Minimal and backward-compatible (only ContentType.HTML changes; plain text is untouched).

New imports (note: com.vaadin.flow.component.Component is referenced fully-qualified, matching the existing usage in this class which already imports org.springframework.stereotype.Component):

import com.vaadin.flow.component.Html;
import com.vaadin.flow.component.orderedlayout.Scroller;
import io.jmix.notifications.entity.ContentType;
import org.springframework.lang.Nullable;

In createReadNotificationDialog(...) — replace the TextArea block and the layout.add(...) call:

     VerticalLayout layout = new VerticalLayout();
     layout.setPadding(false);
     layout.setSizeFull();
 
-    // todo also support RichTextArea
-    TextArea textArea = new TextArea();
-    textArea.setValue(reloadedNotification.getBody());
-    textArea.setReadOnly(true);
-    textArea.setSizeFull();
+    com.vaadin.flow.component.Component bodyComponent = createBodyComponent(reloadedNotification);
 
     HorizontalLayout actionsLayout = new HorizontalLayout();
     ...
-    layout.add(textArea, actionsLayout);
+    layout.add(bodyComponent, actionsLayout);
 
     dialog.add(layout);
     return dialog;
 }

New methods:

/**
 * Creates a component to display the notification body. {@link ContentType#HTML} content is
 * rendered as rich HTML, any other content type is shown as plain text in a read-only text area.
 *
 * @param notification notification to display
 * @return body component
 */
protected com.vaadin.flow.component.Component createBodyComponent(InAppNotification notification) {
    if (ContentType.HTML.getValue().equals(notification.getContentType())) {
        Scroller scroller = new Scroller(new Html(wrapInRootElement(notification.getBody())));
        scroller.setSizeFull();
        return scroller;
    }

    TextArea textArea = new TextArea();
    textArea.setValue(notification.getBody());
    textArea.setReadOnly(true);
    textArea.setSizeFull();
    return textArea;
}

/**
 * Wraps the body in a single root {@code <div>} element, as required by the Vaadin
 * {@link Html} component.
 *
 * @param body notification body, may be {@code null}
 * @return HTML string with a single root element
 */
protected String wrapInRootElement(@Nullable String body) {
    return "<div>" + (body == null ? "" : body) + "</div>";
}

Backward compatibility

The default (ContentType.PLAIN) path is unchanged — same read-only TextArea. Only notifications explicitly created with ContentType.HTML change rendering. The logic sits in protected methods, so applications can override createBodyComponent (e.g. to plug in a sanitizer or a RichTextArea).

Security consideration

com.vaadin.flow.component.Html renders the supplied markup as-is; ensuring the content is safe is the application's responsibility. Today HTML bodies are produced programmatically (the create dialog hardcodes ContentType.PLAIN), so the input is trusted. If/when the create dialog gains an HTML editor (per the // use RichTextEditor if ContentType is HTML TODO), stored bodies become user-authored and rendering them raw would be a stored-XSS surface. Recommendation: sanitize HTML content (e.g. a JSoup safelist) before rendering, ideally via a configurable/overridable hook, or restrict HTML notifications to trusted senders.

Current workaround

A @Primary subclass of NotificationDialogs that calls super.createReadNotificationDialog(...) and then walks the returned dialog's component tree to replace the TextArea with a Scroller(new Html(...)) when the content type is HTML. Functional, but depends on @Internal API and the dialog's internal structure.

inapp-notifications-html-feature-request.md

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

Status
In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions