feat: inline images in website and email channels (#14516)
# Pull Request Template ## Description This PR adds support for inline image uploads in the reply editor for Email and Website (chat widget) channels. Agents can now insert images inline between text and resize them directly in the editor by dragging the bottom corner, similar to the help center editor experience. Image sizes are preserved through markdown using the `cw_image_width` URL param and render correctly in both outgoing emails and chat widget messages. Agents can also paste copied images directly into Email or Website replies using **Shift+Cmd+V** (Shift+Ctrl+V on Windows/Linux). The image gets inserted inline at the cursor position and supports resizing just like uploaded images. Regular **Cmd+V / Ctrl+V** behavior remains unchanged and continues to add images as attachments, so both inline and attachment flows are supported. ### Prosemirror repo PR: https://github.com/chatwoot/prosemirror-schema/pull/48 Fixes https://linear.app/chatwoot/issue/CW-7133/inline-images-in-live-chat-and-email https://linear.app/chatwoot/issue/CW-7225/ghsa-8j9w-jppp-xcfc-html-attribute-injection-via-unvalidated-cw-image ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? ### Screencast https://github.com/user-attachments/assets/a928f852-ab15-413a-9d35-6ea69b718ecf <img width="414" height="654" alt="image" src="https://github.com/user-attachments/assets/205e0729-8f2d-4cc5-9c55-7696f032eca4" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
co-authored by
Muhsin Keloth
parent
d028cc1984
commit
1beaa284c6
@@ -1,9 +1,9 @@
|
||||
class BaseMarkdownRenderer < CommonMarker::HtmlRenderer
|
||||
def image(node)
|
||||
src, title = extract_img_attributes(node)
|
||||
height = extract_image_height(src)
|
||||
sizing_style = extract_image_sizing_style(src)
|
||||
|
||||
render_img_tag(src, title, height)
|
||||
render_img_tag(src, title, sizing_style)
|
||||
end
|
||||
|
||||
private
|
||||
@@ -15,9 +15,25 @@ class BaseMarkdownRenderer < CommonMarker::HtmlRenderer
|
||||
]
|
||||
end
|
||||
|
||||
def extract_image_height(src)
|
||||
# Drag-resize from the reply editor encodes the chosen width as cw_image_width
|
||||
# on the URL; the older message-signature picker uses cw_image_height. Width
|
||||
# wins when both are set so the agent's most recent intent is honored.
|
||||
def extract_image_sizing_style(src)
|
||||
query_params = parse_query_params(src)
|
||||
query_params['cw_image_height']&.first
|
||||
width = sanitize_pixel_value(query_params['cw_image_width']&.first)
|
||||
return "width: #{width}; max-width: 100%; height: auto;" if width
|
||||
|
||||
height = sanitize_pixel_value(query_params['cw_image_height']&.first)
|
||||
height ? "height: #{height};" : nil
|
||||
end
|
||||
|
||||
# Only allow a bounded `<digits>px` value so the decoded query param can't
|
||||
# break out of the inline style attribute (HTML attribute injection).
|
||||
def sanitize_pixel_value(raw)
|
||||
return unless raw =~ /\A(\d+)px\z/
|
||||
|
||||
px = Regexp.last_match(1).to_i
|
||||
"#{px}px" if px.between?(1, 2000)
|
||||
end
|
||||
|
||||
def parse_query_params(url)
|
||||
@@ -27,13 +43,13 @@ class BaseMarkdownRenderer < CommonMarker::HtmlRenderer
|
||||
{}
|
||||
end
|
||||
|
||||
def render_img_tag(src, title, height = nil)
|
||||
def render_img_tag(src, title, sizing_style = nil)
|
||||
title_attribute = title.present? ? " title=\"#{title}\"" : ''
|
||||
# Use inline style instead of the HTML height attribute: email clients and
|
||||
# the in-app Letter view both run images through CSS (e.g. prose /
|
||||
# Use inline style instead of HTML width/height attributes: email clients
|
||||
# and the in-app Letter view both run images through CSS (e.g. prose /
|
||||
# lettersanitizer's `img { height: auto }`) which overrides presentational
|
||||
# attributes. Inline style has higher specificity and survives.
|
||||
style_attribute = height ? " style=\"height: #{height};\"" : ''
|
||||
style_attribute = sizing_style ? " style=\"#{sizing_style}\"" : ''
|
||||
|
||||
plain do
|
||||
# plain ensures that the content is not wrapped in a paragraph tag
|
||||
|
||||
Reference in New Issue
Block a user