10
10
*/
11
11
"use strict" ;
12
12
13
- const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set ( [
14
- "TEXTAREA" ,
15
- "INPUT" ,
16
- "SELECT" ,
17
- "BUTTON" ,
18
- ] ) ;
19
-
20
13
const _ready = ( callback ) => {
21
14
if ( document . readyState !== "loading" ) {
22
15
callback ( ) ;
@@ -25,11 +18,73 @@ const _ready = (callback) => {
25
18
}
26
19
} ;
27
20
21
+ /**
22
+ * highlight a given string on a node by wrapping it in
23
+ * span elements with the given class name.
24
+ */
25
+ const _highlight = ( node , addItems , text , className ) => {
26
+ if ( node . nodeType === Node . TEXT_NODE ) {
27
+ const val = node . nodeValue ;
28
+ const parent = node . parentNode ;
29
+ const pos = val . toLowerCase ( ) . indexOf ( text ) ;
30
+ if (
31
+ pos >= 0 &&
32
+ ! parent . classList . contains ( className ) &&
33
+ ! parent . classList . contains ( "nohighlight" )
34
+ ) {
35
+ let span ;
36
+
37
+ const closestNode = parent . closest ( "body, svg, foreignObject" ) ;
38
+ const isInSVG = closestNode && closestNode . matches ( "svg" ) ;
39
+ if ( isInSVG ) {
40
+ span = document . createElementNS ( "http://www.w3.org/2000/svg" , "tspan" ) ;
41
+ } else {
42
+ span = document . createElement ( "span" ) ;
43
+ span . classList . add ( className ) ;
44
+ }
45
+
46
+ span . appendChild ( document . createTextNode ( val . substr ( pos , text . length ) ) ) ;
47
+ parent . insertBefore (
48
+ span ,
49
+ parent . insertBefore (
50
+ document . createTextNode ( val . substr ( pos + text . length ) ) ,
51
+ node . nextSibling
52
+ )
53
+ ) ;
54
+ node . nodeValue = val . substr ( 0 , pos ) ;
55
+
56
+ if ( isInSVG ) {
57
+ const rect = document . createElementNS (
58
+ "http://www.w3.org/2000/svg" ,
59
+ "rect"
60
+ ) ;
61
+ const bbox = parent . getBBox ( ) ;
62
+ rect . x . baseVal . value = bbox . x ;
63
+ rect . y . baseVal . value = bbox . y ;
64
+ rect . width . baseVal . value = bbox . width ;
65
+ rect . height . baseVal . value = bbox . height ;
66
+ rect . setAttribute ( "class" , className ) ;
67
+ addItems . push ( { parent : parent , target : rect } ) ;
68
+ }
69
+ }
70
+ } else if ( node . matches && ! node . matches ( "button, select, textarea" ) ) {
71
+ node . childNodes . forEach ( ( el ) => _highlight ( el , addItems , text , className ) ) ;
72
+ }
73
+ } ;
74
+ const _highlightText = ( thisNode , text , className ) => {
75
+ let addItems = [ ] ;
76
+ _highlight ( thisNode , addItems , text , className ) ;
77
+ addItems . forEach ( ( obj ) =>
78
+ obj . parent . insertAdjacentElement ( "beforebegin" , obj . target )
79
+ ) ;
80
+ } ;
81
+
28
82
/**
29
83
* Small JavaScript module for the documentation.
30
84
*/
31
85
const Documentation = {
32
86
init : ( ) => {
87
+ Documentation . highlightSearchWords ( ) ;
33
88
Documentation . initDomainIndexTable ( ) ;
34
89
Documentation . initOnKeyListeners ( ) ;
35
90
} ,
@@ -71,6 +126,51 @@ const Documentation = {
71
126
Documentation . LOCALE = catalog . locale ;
72
127
} ,
73
128
129
+ /**
130
+ * highlight the search words provided in the url in the text
131
+ */
132
+ highlightSearchWords : ( ) => {
133
+ const highlight =
134
+ new URLSearchParams ( window . location . search ) . get ( "highlight" ) || "" ;
135
+ const terms = highlight . toLowerCase ( ) . split ( / \s + / ) . filter ( x => x ) ;
136
+ if ( terms . length === 0 ) return ; // nothing to do
137
+
138
+ // There should never be more than one element matching "div.body"
139
+ const divBody = document . querySelectorAll ( "div.body" ) ;
140
+ const body = divBody . length ? divBody [ 0 ] : document . querySelector ( "body" ) ;
141
+ window . setTimeout ( ( ) => {
142
+ terms . forEach ( ( term ) => _highlightText ( body , term , "highlighted" ) ) ;
143
+ } , 10 ) ;
144
+
145
+ const searchBox = document . getElementById ( "searchbox" ) ;
146
+ if ( searchBox === null ) return ;
147
+ searchBox . appendChild (
148
+ document
149
+ . createRange ( )
150
+ . createContextualFragment (
151
+ '<p class="highlight-link">' +
152
+ '<a href="javascript:Documentation.hideSearchWords()">' +
153
+ Documentation . gettext ( "Hide Search Matches" ) +
154
+ "</a></p>"
155
+ )
156
+ ) ;
157
+ } ,
158
+
159
+ /**
160
+ * helper function to hide the search marks again
161
+ */
162
+ hideSearchWords : ( ) => {
163
+ document
164
+ . querySelectorAll ( "#searchbox .highlight-link" )
165
+ . forEach ( ( el ) => el . remove ( ) ) ;
166
+ document
167
+ . querySelectorAll ( "span.highlighted" )
168
+ . forEach ( ( el ) => el . classList . remove ( "highlighted" ) ) ;
169
+ const url = new URL ( window . location ) ;
170
+ url . searchParams . delete ( "highlight" ) ;
171
+ window . history . replaceState ( { } , "" , url ) ;
172
+ } ,
173
+
74
174
/**
75
175
* helper function to focus on search bar
76
176
*/
@@ -110,11 +210,15 @@ const Documentation = {
110
210
)
111
211
return ;
112
212
213
+ const blacklistedElements = new Set ( [
214
+ "TEXTAREA" ,
215
+ "INPUT" ,
216
+ "SELECT" ,
217
+ "BUTTON" ,
218
+ ] ) ;
113
219
document . addEventListener ( "keydown" , ( event ) => {
114
- // bail for input elements
115
- if ( BLACKLISTED_KEY_CONTROL_ELEMENTS . has ( document . activeElement . tagName ) ) return ;
116
- // bail with special keys
117
- if ( event . altKey || event . ctrlKey || event . metaKey ) return ;
220
+ if ( blacklistedElements . has ( document . activeElement . tagName ) ) return ; // bail for input elements
221
+ if ( event . altKey || event . ctrlKey || event . metaKey ) return ; // bail with special keys
118
222
119
223
if ( ! event . shiftKey ) {
120
224
switch ( event . key ) {
@@ -136,6 +240,10 @@ const Documentation = {
136
240
event . preventDefault ( ) ;
137
241
}
138
242
break ;
243
+ case "Escape" :
244
+ if ( ! DOCUMENTATION_OPTIONS . ENABLE_SEARCH_SHORTCUTS ) break ;
245
+ Documentation . hideSearchWords ( ) ;
246
+ event . preventDefault ( ) ;
139
247
}
140
248
}
141
249
0 commit comments