1010 */
1111"use strict" ;
1212
13- const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set ( [
14- "TEXTAREA" ,
15- "INPUT" ,
16- "SELECT" ,
17- "BUTTON" ,
18- ] ) ;
19-
2013const _ready = ( callback ) => {
2114 if ( document . readyState !== "loading" ) {
2215 callback ( ) ;
@@ -25,11 +18,73 @@ const _ready = (callback) => {
2518 }
2619} ;
2720
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+
2882/**
2983 * Small JavaScript module for the documentation.
3084 */
3185const Documentation = {
3286 init : ( ) => {
87+ Documentation . highlightSearchWords ( ) ;
3388 Documentation . initDomainIndexTable ( ) ;
3489 Documentation . initOnKeyListeners ( ) ;
3590 } ,
@@ -71,6 +126,51 @@ const Documentation = {
71126 Documentation . LOCALE = catalog . locale ;
72127 } ,
73128
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+
74174 /**
75175 * helper function to focus on search bar
76176 */
@@ -110,11 +210,15 @@ const Documentation = {
110210 )
111211 return ;
112212
213+ const blacklistedElements = new Set ( [
214+ "TEXTAREA" ,
215+ "INPUT" ,
216+ "SELECT" ,
217+ "BUTTON" ,
218+ ] ) ;
113219 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
118222
119223 if ( ! event . shiftKey ) {
120224 switch ( event . key ) {
@@ -136,6 +240,10 @@ const Documentation = {
136240 event . preventDefault ( ) ;
137241 }
138242 break ;
243+ case "Escape" :
244+ if ( ! DOCUMENTATION_OPTIONS . ENABLE_SEARCH_SHORTCUTS ) break ;
245+ Documentation . hideSearchWords ( ) ;
246+ event . preventDefault ( ) ;
139247 }
140248 }
141249
0 commit comments