1
+ import React from 'react' ;
2
+ import PropTypes from 'prop-types' ;
3
+ import './ContextGetHelp.css' ;
4
+ import NoteParser from '../noteparser/NoteParser' ;
5
+
6
+
7
+ const UP_ARROW_KEY = 38 ;
8
+ const DOWN_ARROW_KEY = 40 ;
9
+ const ENTER_KEY = 13 ;
10
+
11
+ class ContextGetHelp extends React . Component {
12
+ constructor ( props ) {
13
+ super ( props ) ;
14
+ this . noteParser = new NoteParser ( ) ;
15
+
16
+ // eventually we can set this up to have custom options as a prop
17
+ const defaultOptions = [
18
+ {
19
+ text : 'expand' ,
20
+ onSelect : this . expand
21
+ }
22
+ ] ;
23
+
24
+ this . state = {
25
+ selectedIndex : - 1 ,
26
+ getHelpOptions : defaultOptions
27
+ } ;
28
+ }
29
+
30
+ componentWillUnmount ( ) {
31
+ this . props . closePortal ( ) ;
32
+ }
33
+
34
+ expand = ( ) => {
35
+ this . props . closePortal ( ) ;
36
+ const transform = this . replaceCurrentShortcut ( this . props . shortcut . metadata . expandedText ) ;
37
+ return this . props . onSelected ( transform . apply ( ) , null ) ;
38
+ }
39
+
40
+ replaceCurrentShortcut = ( selection ) => {
41
+ let transform ;
42
+ transform = this . props . state . transform ( ) ;
43
+ const triggers = this . noteParser . getListOfTriggersFromText ( selection ) [ 0 ] ;
44
+ triggers . forEach ( ( trigger , idx ) => {
45
+ if ( idx !== 0 ) {
46
+ transform = this . props . insertShortcut ( trigger . definition , trigger . trigger , trigger . selectedValue , transform , 'typed' ) ;
47
+ }
48
+ if ( idx < triggers . length - 1 ) {
49
+ transform = transform . insertText ( selection . substring ( trigger . endIndex , triggers [ idx + 1 ] . startIndex ) ) ;
50
+ }
51
+ else if ( trigger . endIndex < selection . length ) {
52
+ transform = transform . insertText ( selection . substring ( trigger . endIndex ) ) ;
53
+ }
54
+ } ) ;
55
+ return transform ;
56
+ }
57
+
58
+ setSelectedIndex = ( selectedIndex ) => {
59
+ this . setState ( { selectedIndex } ) ;
60
+ }
61
+
62
+ /*
63
+ * Change the menu position based on the amount of places to move
64
+ */
65
+ changeMenuPosition = ( change ) => {
66
+ const optionsCount = this . state . getHelpOptions . length ;
67
+ let newSelectedIndex = this . state . selectedIndex ;
68
+ if ( ( change === - 1 && this . state . selectedIndex > - 1 ) || ( change === 1 && this . state . selectedIndex < optionsCount ) ) {
69
+ newSelectedIndex = this . state . selectedIndex + change ;
70
+ }
71
+ // wrap back to top on down arrow of last option
72
+ if ( change === 1 && this . state . selectedIndex === optionsCount ) {
73
+ newSelectedIndex = 0 ;
74
+ }
75
+ this . setSelectedIndex ( newSelectedIndex ) ;
76
+ }
77
+
78
+ /*
79
+ * Navigate and interact with menu based on button presses
80
+ */
81
+ onKeyDown = ( e ) => {
82
+ const keyCode = e . which ;
83
+ if ( keyCode === DOWN_ARROW_KEY || keyCode === UP_ARROW_KEY ) {
84
+ e . preventDefault ( ) ;
85
+ e . stopPropagation ( ) ;
86
+ const positionChange = ( keyCode === DOWN_ARROW_KEY ) ? 1 : - 1 ;
87
+ this . changeMenuPosition ( positionChange ) ;
88
+ } else if ( keyCode === ENTER_KEY ) {
89
+ // NOTE: This operations might not work on SyntheticEvents which are populat in react
90
+
91
+ // close portal if enter key is pressed but no dropdown option is in focus
92
+ if ( this . state . selectedIndex === - 1 ) {
93
+ e . preventDefault ( ) ;
94
+ e . stopPropagation ( ) ;
95
+ this . props . closePortal ( ) ;
96
+ }
97
+
98
+ // one of the get help options is selected via enter key
99
+ else if ( this . state . selectedIndex > 0 ) {
100
+ e . preventDefault ( ) ;
101
+ e . stopPropagation ( ) ;
102
+
103
+ // the parent 'get help' option is not included in the getHelpOptions array
104
+ // but it is included as a selectedIndex, so there is an off by one that needs
105
+ // to be calculated, hence the -1
106
+ return this . state . getHelpOptions [ this . state . selectedIndex - 1 ] . onSelect ( ) ;
107
+ }
108
+ }
109
+ }
110
+
111
+ renderOptions ( ) {
112
+ // if getHelp is not selected, don't show the additional options
113
+ if ( this . state . selectedIndex === - 1 ) return null ;
114
+
115
+ return (
116
+ < span className = "context-get-help-options" >
117
+ { this . state . getHelpOptions . map ( ( option , index ) => {
118
+ // the parent 'get help' option is not included in the getHelpOptions array
119
+ // but it is included as a selectedIndex, so there is an off by one that needs
120
+ // to be calculated, hence the updatedIndex + 1 from the index of the getHelpOptions
121
+ const updatedIndex = index + 1 ;
122
+ return (
123
+ < li key = { updatedIndex }
124
+ data-active = { this . state . selectedIndex === updatedIndex }
125
+ onClick = { option . onSelect }
126
+ onMouseEnter = { ( ) => { this . setSelectedIndex ( updatedIndex ) ; } }
127
+ >
128
+ { option . text }
129
+ </ li >
130
+ ) ;
131
+ } ) }
132
+ </ span >
133
+ ) ;
134
+ }
135
+
136
+ renderIsCompleteMessage ( ) {
137
+ const initiatingTrigger = this . props . shortcut . getDisplayText ( ) ;
138
+ return (
139
+ < ul className = "context-get-help" ref = "contextGetHelp" >
140
+ < li
141
+ className = "context-get-help-li"
142
+ >
143
+ < span className = "context-get-help-text" >
144
+ < i > { initiatingTrigger } is already complete</ i >
145
+ </ span >
146
+ </ li >
147
+ </ ul >
148
+ ) ;
149
+ }
150
+
151
+ renderIsMissingParent ( ) {
152
+ const initiatingTrigger = this . props . shortcut . getDisplayText ( ) ;
153
+ return (
154
+ < ul className = "context-get-help" ref = "contextGetHelp" >
155
+ < li
156
+ className = "context-get-help-li"
157
+ >
158
+ < span className = "context-get-help-text" >
159
+ < i > { initiatingTrigger } is missing a parent</ i >
160
+ </ span >
161
+ </ li >
162
+ </ ul >
163
+ ) ;
164
+ }
165
+
166
+
167
+
168
+ render ( ) {
169
+ // If the shortcut we're responsible for is missing a parent, display a message to the user to avoid confusion
170
+ if ( ! this . props . shortcut . hasParentContext ( ) && this . props . shortcut . hasChildren ( ) ) return this . renderIsMissingParent ( ) ;
171
+ // If the shortcut we're responsible for is complete, display a message to the user to avoid confusion
172
+ if ( this . props . shortcut . isComplete ) return this . renderIsCompleteMessage ( ) ;
173
+ // Else we should display all our getHelp message
174
+ const initiatingTrigger = this . props . shortcut . getDisplayText ( ) ;
175
+ let iconClass = 'fa fa-angle-' ;
176
+ this . state . selectedIndex === - 1 ? iconClass += 'down' : iconClass += 'up' ;
177
+ return (
178
+ < ul className = "context-get-help" ref = "contextGetHelp" >
179
+ < li
180
+ className = "context-get-help-li"
181
+ data-active = { this . state . selectedIndex === 0 }
182
+ onMouseEnter = { ( ) => { this . setSelectedIndex ( 0 ) ; } }
183
+ >
184
+ < span className = "context-get-help-text" >
185
+ < i > get help with { initiatingTrigger } </ i >
186
+ < span className = { iconClass } > </ span >
187
+ </ span >
188
+ </ li >
189
+ { this . renderOptions ( ) }
190
+ </ ul >
191
+ ) ;
192
+ }
193
+ }
194
+
195
+ ContextGetHelp . propTypes = {
196
+ closePortal : PropTypes . func . isRequired ,
197
+ shortcut : PropTypes . object . isRequired ,
198
+ state : PropTypes . object . isRequired
199
+ } ;
200
+
201
+ export default ContextGetHelp ;
0 commit comments