@@ -11,8 +11,10 @@ import { Constraints } from 'lib/Filters';
11
11
import DateTimeEntry from 'components/DateTimeEntry/DateTimeEntry.react' ;
12
12
import Icon from 'components/Icon/Icon.react' ;
13
13
import Parse from 'parse' ;
14
+ import Popover from 'components/Popover/Popover.react' ;
15
+ import Position from 'lib/Position' ;
14
16
import PropTypes from 'lib/PropTypes' ;
15
- import React , { useCallback } from 'react' ;
17
+ import React , { useCallback , useState , useRef } from 'react' ;
16
18
import styles from 'components/BrowserFilter/BrowserFilter.scss' ;
17
19
import validateNumeric from 'lib/validateNumeric' ;
18
20
@@ -21,6 +23,153 @@ for (const c in Constraints) {
21
23
constraintLookup [ Constraints [ c ] . name ] = c ;
22
24
}
23
25
26
+ const RegexOptionsButton = ( { modifiers, onChangeModifiers } ) => {
27
+ const [ showOptions , setShowOptions ] = useState ( false ) ;
28
+ const buttonRef = useRef ( null ) ;
29
+ const dropdownRef = useRef ( null ) ;
30
+
31
+ // Parse modifiers string into individual flags
32
+ const modifiersArray = modifiers ? modifiers . split ( '' ) : [ ] ;
33
+ const hasI = modifiersArray . includes ( 'i' ) ;
34
+ const hasU = modifiersArray . includes ( 'u' ) ;
35
+ const hasM = modifiersArray . includes ( 'm' ) ;
36
+ const hasX = modifiersArray . includes ( 'x' ) ;
37
+ const hasS = modifiersArray . includes ( 's' ) ;
38
+
39
+ const toggleModifier = ( modifier ) => {
40
+ let newModifiers = [ ...modifiersArray ] ;
41
+ if ( newModifiers . includes ( modifier ) ) {
42
+ newModifiers = newModifiers . filter ( m => m !== modifier ) ;
43
+ } else {
44
+ newModifiers . push ( modifier ) ;
45
+ }
46
+ onChangeModifiers ( newModifiers . join ( '' ) ) ;
47
+ } ;
48
+
49
+ React . useEffect ( ( ) => {
50
+ const handleClickOutside = ( event ) => {
51
+ if (
52
+ dropdownRef . current &&
53
+ ! dropdownRef . current . contains ( event . target ) &&
54
+ buttonRef . current &&
55
+ ! buttonRef . current . contains ( event . target )
56
+ ) {
57
+ setShowOptions ( false ) ;
58
+ }
59
+ } ;
60
+
61
+ if ( showOptions ) {
62
+ document . addEventListener ( 'mousedown' , handleClickOutside ) ;
63
+ return ( ) => {
64
+ document . removeEventListener ( 'mousedown' , handleClickOutside ) ;
65
+ } ;
66
+ }
67
+ } , [ showOptions ] ) ;
68
+
69
+ const optionsDropdown = showOptions ? (
70
+ < Popover
71
+ fixed = { true }
72
+ position = { Position . inDocument ( buttonRef . current ) }
73
+ data-popover-type = "inner"
74
+ >
75
+ < div
76
+ ref = { dropdownRef }
77
+ style = { {
78
+ background : '#1e1e2e' ,
79
+ border : '1px solid #66637A' ,
80
+ borderRadius : '5px' ,
81
+ padding : '8px' ,
82
+ minWidth : '150px' ,
83
+ color : 'white' ,
84
+ fontSize : '14px' ,
85
+ boxShadow : '0 4px 6px rgba(0, 0, 0, 0.3)'
86
+ } }
87
+ >
88
+ < div style = { { marginBottom : '4px' , fontWeight : 'bold' , paddingBottom : '4px' , borderBottom : '1px solid rgba(255,255,255,0.2)' } } >
89
+ Regex Options
90
+ </ div >
91
+ < label
92
+ style = { { display : 'flex' , alignItems : 'center' , padding : '4px 0' , cursor : 'pointer' } }
93
+ onClick = { ( ) => toggleModifier ( 'i' ) }
94
+ >
95
+ < input
96
+ type = "checkbox"
97
+ checked = { hasI }
98
+ readOnly
99
+ style = { { marginRight : '8px' , cursor : 'pointer' } }
100
+ />
101
+ < span > Case insensitive (i)</ span >
102
+ </ label >
103
+ < label
104
+ style = { { display : 'flex' , alignItems : 'center' , padding : '4px 0' , cursor : 'pointer' } }
105
+ onClick = { ( ) => toggleModifier ( 'u' ) }
106
+ >
107
+ < input
108
+ type = "checkbox"
109
+ checked = { hasU }
110
+ readOnly
111
+ style = { { marginRight : '8px' , cursor : 'pointer' } }
112
+ />
113
+ < span > Unicode (u)</ span >
114
+ </ label >
115
+ < label
116
+ style = { { display : 'flex' , alignItems : 'center' , padding : '4px 0' , cursor : 'pointer' } }
117
+ onClick = { ( ) => toggleModifier ( 'm' ) }
118
+ >
119
+ < input
120
+ type = "checkbox"
121
+ checked = { hasM }
122
+ readOnly
123
+ style = { { marginRight : '8px' , cursor : 'pointer' } }
124
+ />
125
+ < span > Multiline (m)</ span >
126
+ </ label >
127
+ < label
128
+ style = { { display : 'flex' , alignItems : 'center' , padding : '4px 0' , cursor : 'pointer' } }
129
+ onClick = { ( ) => toggleModifier ( 'x' ) }
130
+ >
131
+ < input
132
+ type = "checkbox"
133
+ checked = { hasX }
134
+ readOnly
135
+ style = { { marginRight : '8px' , cursor : 'pointer' } }
136
+ />
137
+ < span > Extended (x)</ span >
138
+ </ label >
139
+ < label
140
+ style = { { display : 'flex' , alignItems : 'center' , padding : '4px 0' , cursor : 'pointer' } }
141
+ onClick = { ( ) => toggleModifier ( 's' ) }
142
+ >
143
+ < input
144
+ type = "checkbox"
145
+ checked = { hasS }
146
+ readOnly
147
+ style = { { marginRight : '8px' , cursor : 'pointer' } }
148
+ />
149
+ < span > Dotall (s)</ span >
150
+ </ label >
151
+ </ div >
152
+ </ Popover >
153
+ ) : null ;
154
+
155
+ return (
156
+ < >
157
+ < button
158
+ ref = { buttonRef }
159
+ type = "button"
160
+ className = { styles . remove }
161
+ onClick = { ( ) => {
162
+ setShowOptions ( ! showOptions ) ;
163
+ } }
164
+ title = "Regex options"
165
+ >
166
+ < Icon name = "gear-solid" width = { 14 } height = { 14 } fill = "rgba(0,0,0,0.4)" />
167
+ </ button >
168
+ { optionsDropdown }
169
+ </ >
170
+ ) ;
171
+ } ;
172
+
24
173
function compareValue (
25
174
info ,
26
175
value ,
@@ -29,7 +178,9 @@ function compareValue(
29
178
active ,
30
179
parentContentId ,
31
180
setFocus ,
32
- currentConstraint
181
+ currentConstraint ,
182
+ modifiers ,
183
+ onChangeModifiers
33
184
) {
34
185
if ( currentConstraint === 'containedIn' ) {
35
186
return (
@@ -60,6 +211,21 @@ function compareValue(
60
211
return null ;
61
212
case 'Object' :
62
213
case 'String' :
214
+ if ( currentConstraint === 'matches' ) {
215
+ return (
216
+ < div style = { { display : 'flex' , alignItems : 'center' , gap : '4px' } } >
217
+ < input
218
+ type = "text"
219
+ value = { value }
220
+ onChange = { e => onChangeCompareTo ( e . target . value ) }
221
+ onKeyDown = { onKeyDown }
222
+ ref = { setFocus }
223
+ style = { { width : '106px' } }
224
+ />
225
+ < RegexOptionsButton modifiers = { modifiers } onChangeModifiers = { onChangeModifiers } />
226
+ </ div >
227
+ ) ;
228
+ }
63
229
return (
64
230
< input
65
231
type = "text"
@@ -85,6 +251,7 @@ function compareValue(
85
251
case 'Boolean' :
86
252
return (
87
253
< ChromeDropdown
254
+ width = "140"
88
255
color = { active ? 'blue' : 'purple' }
89
256
value = { value ? 'True' : 'False' }
90
257
options = { [ 'True' , 'False' ] }
@@ -131,10 +298,12 @@ const FilterRow = ({
131
298
currentField,
132
299
currentConstraint,
133
300
compareTo,
301
+ modifiers,
134
302
onChangeClass,
135
303
onChangeField,
136
304
onChangeConstraint,
137
305
onChangeCompareTo,
306
+ onChangeModifiers,
138
307
onKeyDown,
139
308
onDeleteRow,
140
309
active,
@@ -234,13 +403,14 @@ const FilterRow = ({
234
403
buildSuggestions = { buildFieldSuggestions }
235
404
buildLabel = { ( ) => '' }
236
405
/>
237
- < ChromeDropdown
238
- width = { compareInfo . type ? '175' : '325' }
239
- color = { active ? 'blue' : 'purple' }
240
- value = { Constraints [ currentConstraint ] . name }
241
- options = { constraints . map ( c => Constraints [ c ] . name ) }
242
- onChange = { c => onChangeConstraint ( constraintLookup [ c ] , compareTo ) }
243
- />
406
+ < div style = { { flex : 1 } } >
407
+ < ChromeDropdown
408
+ color = { active ? 'blue' : 'purple' }
409
+ value = { Constraints [ currentConstraint ] . name }
410
+ options = { constraints . map ( c => Constraints [ c ] . name ) }
411
+ onChange = { c => onChangeConstraint ( constraintLookup [ c ] , compareTo ) }
412
+ />
413
+ </ div >
244
414
{ compareValue (
245
415
compareInfo ,
246
416
compareTo ,
@@ -249,7 +419,9 @@ const FilterRow = ({
249
419
active ,
250
420
parentContentId ,
251
421
setFocus ,
252
- currentConstraint
422
+ currentConstraint ,
423
+ modifiers ,
424
+ onChangeModifiers
253
425
) }
254
426
< button type = "button" className = { styles . remove } onClick = { onDeleteRow } >
255
427
< Icon name = "minus-solid" width = { 14 } height = { 14 } fill = "rgba(0,0,0,0.4)" />
@@ -267,4 +439,6 @@ FilterRow.propTypes = {
267
439
currentConstraint : PropTypes . string . isRequired ,
268
440
compareTo : PropTypes . any ,
269
441
compareInfo : PropTypes . object ,
442
+ modifiers : PropTypes . string ,
443
+ onChangeModifiers : PropTypes . func ,
270
444
} ;
0 commit comments