File tree Expand file tree Collapse file tree 13 files changed +424
-6
lines changed
render/components/form-fields Expand file tree Collapse file tree 13 files changed +424
-6
lines changed Original file line number Diff line number Diff line change 482482 padding : 6px 8px ;
483483 color : var (--color-text-lighter );
484484}
485+
486+ .fjs-container .fjs-image-container {
487+ width : fit-content;
488+ height : fit-content;
489+ }
490+
491+ .fjs-container .fjs-image {
492+ object-fit : contain;
493+ }
494+
495+ .fjs-container .fjs-image-placeholder {
496+ height : 64px ;
497+ margin : 2px 0 ;
498+ }
Original file line number Diff line number Diff line change @@ -37,6 +37,7 @@ const ALLOWED_ATTRIBUTES = [
3737] ;
3838
3939const ALLOWED_URI_PATTERN = / ^ (?: (?: (?: f | h t ) t p s ? | m a i l t o | t e l | c a l l t o | c i d | x m p p ) : | [ ^ a - z ] | [ a - z + . \- ] + (?: [ ^ a - z + . \- : ] | $ ) ) / i; // eslint-disable-line no-useless-escape
40+ const ALLOWED_IMAGE_SRC_PATTERN = / ^ ( h t t p s ? | d a t a ) : .* / i; // eslint-disable-line no-useless-escape
4041const ATTR_WHITESPACE_PATTERN = / [ \u0000 - \u0020 \u00A0 \u1680 \u180E \u2000 - \u2029 \u205F \u3000 ] / g; // eslint-disable-line no-control-regex
4142
4243const FORM_ELEMENT = document . createElement ( 'form' ) ;
@@ -70,6 +71,12 @@ export function sanitizeHTML(html) {
7071 }
7172}
7273
74+ export function sanitizeImageSource ( src ) {
75+ const valid = ALLOWED_IMAGE_SRC_PATTERN . test ( src ) ;
76+
77+ return valid ? src : '' ;
78+ }
79+
7380/**
7481 * Recursively sanitize a HTML node, potentially
7582 * removing it, its children or attributes.
Original file line number Diff line number Diff line change @@ -2,7 +2,7 @@ import snarkdown from '@bpmn-io/snarkdown';
22import { get } from 'min-dash' ;
33import classNames from 'classnames' ;
44
5- import { sanitizeHTML } from './Sanitizer' ;
5+ import { sanitizeHTML , sanitizeImageSource } from './Sanitizer' ;
66
77export function formFieldClasses ( type , { errors = [ ] , disabled = false } = { } ) {
88 if ( ! type ) {
@@ -43,6 +43,19 @@ export function safeMarkdown(markdown) {
4343 return sanitizeHTML ( html ) ;
4444}
4545
46+ /**
47+ * Sanitizes an image source to ensure we only allow for data URI and links
48+ * that start with http(s).
49+ *
50+ * Note: Most browsers anyway do not support script execution in <img> elements.
51+ *
52+ * @param {string } src
53+ * @returns {string }
54+ */
55+ export function safeImageSource ( src ) {
56+ return sanitizeImageSource ( src ) ;
57+ }
58+
4659export function sanitizeSingleSelectValue ( options ) {
4760 const {
4861 formField,
Original file line number Diff line number Diff line change 1+ import { useContext } from 'preact/hooks' ;
2+
3+ import { FormContext } from '../../context' ;
4+
5+ import { useExpressionValue } from '../../hooks/useExpressionValue' ;
6+
7+ import {
8+ formFieldClasses ,
9+ prefixId ,
10+ safeImageSource
11+ } from '../Util' ;
12+
13+ import ImagePlaceholder from './icons/ImagePlaceholder.svg' ;
14+
15+ const type = 'image' ;
16+
17+
18+ export default function Image ( props ) {
19+ const {
20+ field
21+ } = props ;
22+
23+ const {
24+ alt,
25+ id,
26+ source
27+ } = field ;
28+
29+ const safeSource = safeImageSource ( useExpressionValue ( source ) ) ;
30+ const altText = useExpressionValue ( alt ) ;
31+
32+ const { formId } = useContext ( FormContext ) ;
33+
34+ return < div class = { formFieldClasses ( type ) } >
35+ < div class = "fjs-image-container" >
36+ {
37+ safeSource &&
38+ < img
39+ alt = { altText }
40+ src = { safeSource }
41+ class = "fjs-image"
42+ id = { prefixId ( id , formId ) } />
43+ }
44+ { ! safeSource &&
45+ < div class = "fjs-image-placeholder" >
46+ < ImagePlaceholder alt = "This is an image placeholder" />
47+ </ div >
48+ }
49+ </ div >
50+ </ div > ;
51+ }
52+
53+ Image . create = function ( options = { } ) {
54+ return {
55+ ...options
56+ } ;
57+ } ;
58+
59+ Image . type = type ;
60+ Image . keyed = false ;
Original file line number Diff line number Diff line change @@ -3,6 +3,7 @@ import Checkbox from './form-fields/Checkbox';
33import Checklist from './form-fields/Checklist' ;
44import Default from './form-fields/Default' ;
55import FormComponent from './FormComponent' ;
6+ import Image from './form-fields/Image' ;
67import Numberfield from './form-fields/Number' ;
78import Radio from './form-fields/Radio' ;
89import Select from './form-fields/Select' ;
@@ -17,6 +18,7 @@ export {
1718 Checklist ,
1819 Default ,
1920 FormComponent ,
21+ Image ,
2022 Numberfield ,
2123 Radio ,
2224 Select ,
@@ -31,6 +33,7 @@ export const formFields = [
3133 Checkbox ,
3234 Checklist ,
3335 Default ,
36+ Image ,
3437 Numberfield ,
3538 Radio ,
3639 Select ,
Original file line number Diff line number Diff line change 1+ import useService from './useService.js' ;
2+
3+ /**
4+ *
5+ * @param {string | undefined } expression
6+ * @param {import('../../types').Data } data
7+ */
8+ export function useEvaluation ( expression , data ) {
9+ const conditionChecker = useService ( 'conditionChecker' , false ) ;
10+
11+ if ( ! conditionChecker ) {
12+ return null ;
13+ }
14+
15+ return conditionChecker . evaluate ( expression , data ) ;
16+ }
Original file line number Diff line number Diff line change 1+ import { isString } from 'min-dash' ;
2+
3+ import useService from './useService' ;
4+ import { useEvaluation } from './useEvaluation' ;
5+
6+ /**
7+ *
8+ * @param {string } value
9+ */
10+ export function useExpressionValue ( value ) {
11+ const formData = useService ( 'form' ) . _getState ( ) . data ;
12+
13+ if ( ! isExpression ( value ) ) {
14+ return value ;
15+ }
16+
17+ // We can ignore this hook rule as we do not use
18+ // state or effects in our custom hooks
19+ /* eslint-disable-next-line react-hooks/rules-of-hooks */
20+ return useEvaluation ( value , formData ) ;
21+ }
22+
23+
24+ // helper ///////////////
25+
26+ function isExpression ( value ) {
27+ return isString ( value ) && value . startsWith ( '=' ) ;
28+ }
Original file line number Diff line number Diff line change @@ -95,7 +95,7 @@ describe('Form', function() {
9595 } ) ;
9696
9797 // then
98- expect ( form . get ( 'formFieldRegistry' ) . getAll ( ) ) . to . have . length ( 14 ) ;
98+ expect ( form . get ( 'formFieldRegistry' ) . getAll ( ) ) . to . have . length ( 15 ) ;
9999 } ) ;
100100
101101
@@ -174,7 +174,7 @@ describe('Form', function() {
174174 await form . importSchema ( schemaNoIds , data ) ;
175175
176176 // then
177- expect ( form . get ( 'formFieldRegistry' ) . getAll ( ) ) . to . have . length ( 14 ) ;
177+ expect ( form . get ( 'formFieldRegistry' ) . getAll ( ) ) . to . have . length ( 15 ) ;
178178
179179 form . get ( 'formFieldRegistry' ) . forEach ( field => {
180180 expect ( field . id ) . to . exist ;
@@ -212,7 +212,7 @@ describe('Form', function() {
212212 await form . importSchema ( schema , data ) ;
213213
214214 // then
215- expect ( form . get ( 'formFieldRegistry' ) . getAll ( ) ) . to . have . length ( 14 ) ;
215+ expect ( form . get ( 'formFieldRegistry' ) . getAll ( ) ) . to . have . length ( 15 ) ;
216216 } ) ;
217217
218218
Original file line number Diff line number Diff line change 121121 }
122122 ]
123123 },
124+ {
125+ "alt" : " The bpmn.io logo" ,
126+ "type" : " image"
127+ },
124128 {
125129 "key" : " submit" ,
126130 "label" : " Submit" ,
You can’t perform that action at this time.
0 commit comments