3
3
function Menu ( ) {
4
4
this . $toggle = document . getElementById ( 'menu-toggle' ) ;
5
5
this . $menu = document . getElementById ( 'menu' ) ;
6
+ this . $searchBox = document . getElementById ( 'menu-search-box' ) ;
7
+ this . $searchResults = document . getElementById ( 'menu-search-results' ) ;
8
+ this . initSearch ( ) ;
6
9
7
10
this . $toggle . addEventListener ( 'click' , this . toggle . bind ( this ) ) ;
8
11
12
+ this . $searchBox . addEventListener ( 'keydown' , function ( e ) {
13
+ if ( e . keyCode === 191 && e . target . value . length === 0 ) {
14
+ e . preventDefault ( ) ;
15
+ e . stopPropagation ( ) ;
16
+ } else if ( e . keyCode === 13 ) {
17
+ e . preventDefault ( ) ;
18
+ e . stopPropagation ( ) ;
19
+ this . selectResult ( ) ;
20
+ }
21
+ } . bind ( this ) ) ;
22
+
23
+ this . $searchBox . addEventListener ( 'keyup' , debounce ( function ( e ) {
24
+ e . stopPropagation ( ) ;
25
+ this . search ( e . target . value ) ;
26
+ } . bind ( this ) ) ) ;
27
+
28
+
9
29
var tocItems = this . $menu . querySelectorAll ( '#menu-toc li' ) ;
10
30
for ( var i = 0 ; i < tocItems . length ; i ++ ) {
11
31
var $item = tocItems [ i ] ;
@@ -26,11 +46,196 @@ function Menu() {
26
46
}
27
47
28
48
Menu . prototype . toggle = function ( ) {
29
- this . $menu . classList . toggle ( "active" ) ;
49
+ this . $menu . classList . toggle ( 'active' ) ;
50
+ }
51
+
52
+ Menu . prototype . show = function ( ) {
53
+ this . $menu . classList . add ( 'active' ) ;
54
+ }
55
+
56
+ Menu . prototype . hide = function ( ) {
57
+ this . $menu . classList . remove ( 'active' ) ;
58
+ }
59
+
60
+ Menu . prototype . isVisible = function ( ) {
61
+ return this . $menu . classList . contains ( 'active' ) ;
62
+ }
63
+
64
+ Menu . prototype . initSearch = function ( ) {
65
+ var $biblio = document . getElementById ( 'menu-search-biblio' ) ;
66
+ if ( ! $biblio ) {
67
+ this . biblio = { } ;
68
+ } else {
69
+ this . biblio = JSON . parse ( $biblio . textContent ) ;
70
+ }
71
+
72
+ document . addEventListener ( 'keydown' , function ( e ) {
73
+ if ( e . keyCode === 191 ) {
74
+ e . preventDefault ( ) ;
75
+ e . stopPropagation ( ) ;
76
+
77
+ if ( this . isVisible ( ) ) {
78
+ this . _closeAfterSearch = false ;
79
+ } else {
80
+ this . _closeAfterSearch = true ;
81
+ this . show ( ) ;
82
+ }
83
+
84
+ this . show ( ) ;
85
+ this . $searchBox . focus ( ) ;
86
+ }
87
+ } . bind ( this ) )
88
+ }
89
+
90
+ Menu . prototype . search = function ( needle ) {
91
+ if ( needle . length < 2 ) {
92
+ this . hideSearch ( ) ;
93
+ } else {
94
+ this . showSearch ( ) ;
95
+ }
96
+
97
+ needle = needle . toLowerCase ( ) ;
98
+
99
+ var results = { } ;
100
+ var seenClauses = { } ;
101
+
102
+ results . ops = Object . keys ( this . biblio . ops ) . map ( function ( k ) {
103
+ return this . biblio . ops [ k ] ;
104
+ } . bind ( this ) ) . filter ( function ( op ) {
105
+ return fuzzysearch ( needle , op . aoid . toLowerCase ( ) ) ;
106
+ } ) ;
107
+
108
+ results . ops . forEach ( function ( op ) {
109
+ seenClauses [ op . id ] = true ;
110
+ } ) ;
111
+
112
+ results . productions = Object . keys ( this . biblio . productions ) . map ( function ( k ) {
113
+ return this . biblio . productions [ k ] ;
114
+ } . bind ( this ) ) . filter ( function ( prod ) {
115
+ return fuzzysearch ( needle , prod . name . toLowerCase ( ) ) ;
116
+ } ) ;
117
+
118
+ results . clauses = Object . keys ( this . biblio . clauses ) . map ( function ( k ) {
119
+ return this . biblio . clauses [ k ] ;
120
+ } . bind ( this ) ) . filter ( function ( clause ) {
121
+ return ! seenClauses [ clause . id ] && ( clause . number . indexOf ( needle ) === 0 || fuzzysearch ( needle , clause . title . toLowerCase ( ) ) ) ;
122
+ } ) ;
123
+
124
+ if ( results . length > 50 ) {
125
+ results = results . slice ( 0 , 50 ) ;
126
+ }
127
+
128
+ this . displayResults ( results ) ;
129
+ }
130
+
131
+ Menu . prototype . displayResults = function ( results ) {
132
+ var totalResults = Object . keys ( results ) . reduce ( function ( sum , record ) { return sum + record . length } , 0 ) ;
133
+
134
+ if ( totalResults > 0 ) {
135
+ this . $searchResults . classList . remove ( 'no-results' ) ;
136
+
137
+ var html = '<ul>' ;
138
+
139
+ results . ops . forEach ( function ( op ) {
140
+ html += '<li class=menu-search-result-op><a href="#' + op . id + '">' + op . aoid + '</a></li>'
141
+ } ) ;
142
+
143
+ results . productions . forEach ( function ( prod ) {
144
+ html += '<li class=menu-search-result-prod><a href="#' + prod . id + '">' + prod . name + '</a></li>'
145
+ } ) ;
146
+
147
+ results . clauses . forEach ( function ( clause ) {
148
+ html += '<li class=menu-search-result-clause><a href="#' + clause . id + '">' + clause . number + ' ' + clause . title + '</a></li>'
149
+ } )
150
+
151
+ html += '</ul>'
152
+
153
+ this . $searchResults . innerHTML = html ;
154
+ } else {
155
+ this . $searchResults . classList . add ( 'no-results' ) ;
156
+ }
157
+ }
158
+
159
+ Menu . prototype . hideSearch = function ( ) {
160
+ this . $searchResults . classList . add ( 'inactive' ) ;
161
+ }
162
+
163
+ Menu . prototype . showSearch = function ( ) {
164
+ this . $searchResults . classList . remove ( 'inactive' ) ;
165
+ }
166
+
167
+ Menu . prototype . selectResult = function ( ) {
168
+ var $first = this . $searchResults . querySelector ( 'li:first-child a' ) ;
169
+
170
+ if ( $first ) {
171
+ document . location = $first . getAttribute ( 'href' ) ;
172
+ }
173
+
174
+ this . $searchBox . value = '' ;
175
+ this . $searchBox . blur ( ) ;
176
+ this . hideSearch ( ) ;
177
+
178
+ if ( this . _closeAfterSearch ) {
179
+ this . hide ( ) ;
180
+ }
30
181
}
31
182
32
183
function init ( ) {
33
184
var menu = new Menu ( ) ;
34
185
}
35
186
36
187
document . addEventListener ( 'DOMContentLoaded' , init ) ;
188
+
189
+ function debounce ( fn ) {
190
+ var timeout ;
191
+ return function ( ) {
192
+ var args = arguments ;
193
+ if ( timeout ) {
194
+ clearTimeout ( timeout ) ;
195
+ }
196
+ timeout = setTimeout ( function ( ) {
197
+ timeout = null ;
198
+ fn . apply ( this , args ) ;
199
+ } . bind ( this ) , 150 ) ;
200
+ }
201
+ }
202
+
203
+ // The following license applies to the fuzzysearch function
204
+ // The MIT License (MIT)
205
+ // Copyright © 2015 Nicolas Bevacqua
206
+ // Permission is hereby granted, free of charge, to any person obtaining a copy of
207
+ // this software and associated documentation files (the "Software"), to deal in
208
+ // the Software without restriction, including without limitation the rights to
209
+ // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
210
+ // the Software, and to permit persons to whom the Software is furnished to do so,
211
+ // subject to the following conditions:
212
+
213
+ // The above copyright notice and this permission notice shall be included in all
214
+ // copies or substantial portions of the Software.
215
+
216
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
217
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
218
+ // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
219
+ // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
220
+ // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
221
+ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
222
+ function fuzzysearch ( needle , haystack ) {
223
+ var tlen = haystack . length ;
224
+ var qlen = needle . length ;
225
+ if ( qlen > tlen ) {
226
+ return false ;
227
+ }
228
+ if ( qlen === tlen ) {
229
+ return needle === haystack ;
230
+ }
231
+ outer: for ( var i = 0 , j = 0 ; i < qlen ; i ++ ) {
232
+ var nch = needle . charCodeAt ( i ) ;
233
+ while ( j < tlen ) {
234
+ if ( haystack . charCodeAt ( j ++ ) === nch ) {
235
+ continue outer;
236
+ }
237
+ }
238
+ return false ;
239
+ }
240
+ return true ;
241
+ }
0 commit comments