Forms: Display uploaded files with thumbnail preview on confirmation page#46753
Forms: Display uploaded files with thumbnail preview on confirmation page#46753
Conversation
…mation page Updated the post-submission confirmation page to properly render file upload fields with: - Document icon for visual identification - Filename as clickable download link - File size in human-readable format Changes: - Added get_render_web_value() support for file fields in Feedback_Field - Added getFiles() function in view.js for client-side file data extraction - Added get_files() static method in Contact_Form for PHP template rendering - Added field-files template section in HTML output - Added CSS styles for file display components Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Change the file upload display from horizontal inline layout to vertical stacking with the icon on the left and filename/size stacked vertically. This matches the design specification where the filename appears as an underlined link with the file size below it in smaller muted text. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
For AJAX form submissions, capture file preview data (blob URLs for images, icon SVG URLs for other files) before submission and preserve them for the confirmation page display. - Add captureFilePreviews() to capture preview URLs from DOM - Update getFiles() to include previewUrl, iconUrl, and hasPreview - Add thumbnail element to confirmation template with conditional display - Add CSS for thumbnail with background-image and mask-image support - Image files show actual thumbnail preview - Non-image files show appropriate file type icon (PDF, DOC, etc.) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Match thumbnail size (46px) with form field preview - Use circular border-radius (50%) to match form field style - Remove download link from filename (requires auth that front-end users don't have) - Display filename as plain text instead of anchor Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖 🔴 Action required: Please include detailed testing steps, explaining how to test your change, like so: 🔴 Action required: We would recommend that you add a section to the PR description to specify whether this PR includes any changes to data or privacy, like so: Follow this PR Review Process:
If you have questions about anything, reach out in #jetpack-developers for guidance! |
There was a problem hiding this comment.
Pull request overview
This PR implements display of uploaded files on the form submission confirmation page with thumbnail/icon previews. For AJAX submissions, client-side preview URLs (blob URLs for images, icon SVGs for other file types) are captured from the DOM and preserved for display. Non-AJAX submissions show a generic file icon.
Changes:
- Added file preview capture mechanism for AJAX submissions to preserve blob URLs and icon URLs
- Created file display template with 46px circular thumbnails matching the form field preview appearance
- Added conditional rendering logic to show thumbnail previews when available or fallback icons otherwise
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
projects/packages/forms/src/modules/form/view.js |
Added captureFilePreviews() to capture preview URLs from DOM, getFiles() to format file data with preview information, and integration with form submission flow |
projects/packages/forms/src/contact-form/css/grunion.scss |
Added styles for file list display with grid layout, 46px circular thumbnails, and support for both image previews and file type icons |
projects/packages/forms/src/contact-form/class-feedback-field.php |
Added file field handling in get_render_web_value() to return structured array with file metadata |
projects/packages/forms/src/contact-form/class-contact-form.php |
Added get_files() method and updated confirmation template rendering to include file display with thumbnails and fallback icons |
projects/packages/forms/changelog/add-forms-response-file-upload |
Added changelog entry for the feature |
| <template data-wp-each--file="context.submission.files"> | ||
| <div class="field-file"> | ||
| <div class="field-file__thumbnail" data-wp-style--background-image="context.file.previewUrl" data-wp-style--mask-image="context.file.iconUrl" data-wp-bind--hidden="!context.file.hasPreview"></div> | ||
| <svg class="field-file__icon" data-wp-bind--hidden="context.file.hasPreview" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> |
There was a problem hiding this comment.
The SVG file icon should include aria-hidden="true" to indicate it's decorative, since the file information is already conveyed through the adjacent text elements (file name and size). This prevents screen readers from announcing unnecessary information.
| <svg class="field-file__icon" data-wp-bind--hidden="context.file.hasPreview" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <svg class="field-file__icon" data-wp-bind--hidden="context.file.hasPreview" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> |
| $html .= '<svg class="field-file__icon" data-wp-bind--hidden="context.file.hasPreview" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"'; | ||
| $html .= $has_preview ? ' hidden' : ''; | ||
| $html .= '>'; | ||
| $html .= '<path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.89 22 5.99 22H18C19.1 22 20 21.1 20 20V8L14 2ZM18 20H6V4H13V9H18V20Z" fill="currentColor"/>'; | ||
| $html .= '</svg>'; |
There was a problem hiding this comment.
The SVG file icon should include aria-hidden="true" to indicate it's decorative, since the file information is already conveyed through the adjacent text elements (file name and size). This prevents screen readers from announcing unnecessary information.
| Significance: patch | ||
| Type: added | ||
|
|
||
| Forms: Display uploaded files with icon, filename link, and size on confirmation page. |
There was a problem hiding this comment.
The changelog mentions "filename link" but according to the PR description, the non-functional download link was removed. The changelog should be updated to reflect that files are displayed with icon, filename, and size (without a link).
| const getFiles = value => { | ||
| if ( value?.type === 'file' && value?.files ) { | ||
| return value.files.map( file => { | ||
| const fileName = file.name ?? ''; | ||
| const preview = capturedFilePreviews.get( fileName ); | ||
| const hasPreview = !! ( preview?.previewUrl || preview?.iconUrl ); | ||
|
|
||
| return { | ||
| name: fileName, | ||
| size: file.size ?? '', | ||
| url: file.url ?? '', | ||
| // Include preview data if available (for AJAX submissions) | ||
| previewUrl: preview?.previewUrl ?? null, | ||
| iconUrl: preview?.iconUrl ?? null, | ||
| // Boolean flag for easier binding evaluation | ||
| hasPreview, | ||
| }; | ||
| } ); | ||
| } | ||
|
|
||
| return null; | ||
| }; |
There was a problem hiding this comment.
The getFiles function is missing JSDoc documentation. Other similar functions in this file (like getImages, getUrl, captureFilePreviews) have JSDoc comments that describe their parameters and return values. Following the established pattern in this file, this function should include JSDoc documentation describing its purpose, the value parameter, and the return type.
| const getFiles = value => { | ||
| if ( value?.type === 'file' && value?.files ) { | ||
| return value.files.map( file => { | ||
| const fileName = file.name ?? ''; | ||
| const preview = capturedFilePreviews.get( fileName ); | ||
| const hasPreview = !! ( preview?.previewUrl || preview?.iconUrl ); | ||
|
|
||
| return { | ||
| name: fileName, | ||
| size: file.size ?? '', | ||
| url: file.url ?? '', | ||
| // Include preview data if available (for AJAX submissions) | ||
| previewUrl: preview?.previewUrl ?? null, | ||
| iconUrl: preview?.iconUrl ?? null, | ||
| // Boolean flag for easier binding evaluation | ||
| hasPreview, | ||
| }; | ||
| } ); | ||
| } | ||
|
|
||
| return null; | ||
| }; |
There was a problem hiding this comment.
The new getFiles function lacks test coverage. Given that this file contains comprehensive tests for the similar getImages function (lines 159-311), test coverage should be added for getFiles following the same pattern. Tests should cover: returning null for non-file values, extracting file metadata correctly, handling missing properties gracefully, and verifying the hasPreview flag logic.
| if ( fileName ) { | ||
| previews.set( fileName, { | ||
| // For images, the background-image contains the blob URL | ||
| previewUrl: backgroundImage && backgroundImage !== 'none' ? backgroundImage : null, | ||
| // For non-images, the mask-image contains the icon SVG URL | ||
| iconUrl: maskImage && maskImage !== 'none' ? maskImage : null, | ||
| } ); |
There was a problem hiding this comment.
The captureFilePreviews function uses filename as the Map key, which could cause issues if a user uploads multiple files with the same name. When multiple files share the same filename, only the preview data from the last file processed will be stored, causing earlier files with the same name to lose their preview data. Consider using a more unique identifier (e.g., combining filename with array index or using the clientFileId if accessible from the DOM) to ensure each file's preview is captured correctly.
| // Capture file preview URLs before submission (blob URLs for images, icon URLs for other files) | ||
| capturedFilePreviews = captureFilePreviews( context.formHash ); |
There was a problem hiding this comment.
The capturedFilePreviews Map stores references to blob URLs (in previewUrl) that are never cleaned up. While the blob URLs themselves are created and managed by the file-field module (which does revoke them on file removal), storing additional references to these URLs in capturedFilePreviews could prevent proper garbage collection. Consider clearing the capturedFilePreviews Map after the form submission is complete (either in the success or error path) to avoid potential memory leaks, especially on pages where users might submit forms multiple times without a page refresh.
| $html .= '<div data-wp-each-child class="field-file">'; | ||
| // Thumbnail for AJAX submissions (has preview data) | ||
| $html .= '<div class="field-file__thumbnail" data-wp-style--background-image="context.file.previewUrl" data-wp-style--mask-image="context.file.iconUrl" data-wp-bind--hidden="!context.file.hasPreview"'; | ||
| $html .= $has_preview ? '' : ' hidden'; | ||
| $html .= '></div>'; | ||
| // SVG fallback for non-AJAX submissions | ||
| $html .= '<svg class="field-file__icon" data-wp-bind--hidden="context.file.hasPreview" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"'; | ||
| $html .= $has_preview ? ' hidden' : ''; | ||
| $html .= '>'; | ||
| $html .= '<path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.89 22 5.99 22H18C19.1 22 20 21.1 20 20V8L14 2ZM18 20H6V4H13V9H18V20Z" fill="currentColor"/>'; | ||
| $html .= '</svg>'; | ||
| $html .= '<span class="field-file__name" data-wp-text="context.file.name">' . esc_html( $file_name ) . '</span>'; | ||
| $html .= '<span class="field-file__size" data-wp-text="context.file.size">' . esc_html( $file_size ) . '</span>'; | ||
| $html .= '</div>'; |
There was a problem hiding this comment.
The file display elements lack proper accessibility attributes. The file name and size information should be associated with the thumbnail/icon for screen reader users. Consider wrapping the file information in a semantic structure (e.g., adding role="group" to the .field-file container with an aria-label describing the file), or adding aria-label attributes to the thumbnail/icon elements that include the file name and size.
| <div class="field-file"> | ||
| <div class="field-file__thumbnail" data-wp-style--background-image="context.file.previewUrl" data-wp-style--mask-image="context.file.iconUrl" data-wp-bind--hidden="!context.file.hasPreview"></div> | ||
| <svg class="field-file__icon" data-wp-bind--hidden="context.file.hasPreview" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
| <path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.89 22 5.99 22H18C19.1 22 20 21.1 20 20V8L14 2ZM18 20H6V4H13V9H18V20Z" fill="currentColor"/> | ||
| </svg> | ||
| <span class="field-file__name" data-wp-text="context.file.name"></span> | ||
| <span class="field-file__size" data-wp-text="context.file.size"></span> | ||
| </div> |
There was a problem hiding this comment.
The same accessibility issue exists here: file display elements lack proper accessibility attributes. The file name and size information should be associated with the thumbnail/icon for screen reader users. Consider adding role="group" to the .field-file container with an aria-label describing the file, or add aria-label attributes to the thumbnail/icon elements.
The SVG icons are decorative since file information is conveyed through adjacent text elements (filename and size). Adding aria-hidden="true" prevents screen readers from announcing unnecessary information. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The download link was removed since it requires authentication that front-end users don't have. Updated changelog to reflect that files are displayed with icon, filename, and size (without a link). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Following the established pattern in this file for similar functions like captureFilePreviews, getImages, and getUrl. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Following the established pattern for getImages and getUrl tests: - Returns null for non-file values - Extracts file metadata correctly - Handles missing properties gracefully - Verifies hasPreview flag logic with captured preview data Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Clears the Map storing blob URL references after form submission completes (success or error) to avoid potential memory leaks on pages where users might submit forms multiple times without refresh. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary
Implements RESPONSE-014: Display uploaded files on the form submission confirmation page with thumbnail/icon previews.
Changes
PHP (
class-contact-form.php,class-feedback-field.php):previewUrl,iconUrl, andhasPreviewin file dataJavaScript (
view.js):captureFilePreviews()to capture preview URLs from DOM before AJAX submissiongetFiles()to include preview data for confirmation displayCSS (
grunion.scss):background-image(images) andmask-image(file type icons)Test plan
🤖 Generated with Claude Code